テキストの分類は自然言語処理における最も重要なタスクの1つである.これは、テキスト文字列や文書を、文字列の内容に応じて異なるカテゴリに分類するプロセスである。
テキスト分類は、ツイートからユーザーの感情を検出する、電子メールをスパムかハムか分類する、ブログ記事を異なるカテゴリに分類する、顧客からの問い合わせに自動的にタグ付けするなど、様々な用途がある。
今回は、実際のテキスト分類の例を見てみよう。
ここでは、ある映画の批評が肯定的か否定的かを予測する機械学習モデルを学習させる。
これは感傷的な分析の典型的な例で、ある特定の実体に対する人々の感情を異なるカテゴリーに分類する。
データセット
今回使用するデータセットは、Cornell Natural Language Processing Groupからダウンロードすることができる。
このデータセットは合計2000の文書からなる。
半分の文書は映画に関する肯定的なレビューを含み、残りの半分は否定的なレビューが含まれています。
データセットの詳細については、このリンクを参照してください。
データセットをダウンロードしたら、解凍するか展開する。
txt_sentoken」フォルダーを開く。
このフォルダーには2つのサブフォルダーがあります。
“neg “と “pos “です。
これらのフォルダを開くと、映画のレビューが書かれたテキスト文書が表示されます。
Scikit-Learnによるセンチメント分析
さて、データをダウンロードしたところで、いよいよ実行に移します。
このセクションでは、様々な映画のレビューからセンチメントを予測するために必要な一連のステップを実行します。
これらのステップは、あらゆるテキスト分類タスクに使用することができます。
Pythonの機械学習用ライブラリScikit-Learnを使用して、テキスト分類モデルを学習します。
以下は、Pythonでテキスト分類モデルを作成するために必要なステップです。
- ライブラリのインポート
- データセットのインポート
- テキストの前処理
- テキストから数値への変換
- 学習セットとテストセット
- テキスト分類モデルの学習とセンチメント予測
- モデルの評価
- モデルの保存と読み込み
ライブラリのインポート
以下のスクリプトを実行し、必要なライブラリをインポートしてください。
import numpy as np
import re
import nltk
from sklearn.datasets import load_files
nltk.download('stopwords')
import pickle
from nltk.corpus import stopwords
データセットのインポート
データセットをアプリケーションにインポートするために、sklearn_datasets
ライブラリの load_files
関数を使用します。
load_files` 関数は、データセットを自動的にデータセットとターゲットセットに分割します。
例えば、今回のケースでは、”txt_sentoken” ディレクトリへのパスを渡します。
txt_sentoken “フォルダ内の各フォルダを1つのカテゴリとして扱い、そのフォルダ内のすべてのドキュメントに対応するカテゴリが割り当てられる。
以下のスクリプトを実行すると、load_files
機能が実際に動作するのを確認できます。
movie_data = load_files(r"D: xt_sentoken")
X, y = movie_data.data, movie_data.target
上のスクリプトでは、load_files
関数が “neg” と “pos” の両方のフォルダのデータを X
変数にロードし、対象のカテゴリは y
に格納されています。
ここで X
は 2000 個の文字列型要素のリストであり、各要素が 1 つのユーザーレビューに対応します。
同様に、y
はサイズ 2000 の numpy 配列である。
画面に y
を表示すると、1 と 0 の配列が表示されます。
これは、load_files
関数が、各カテゴリごとに、対象となるnumpy配列に数字を追加しているからです。
私たちは2つのカテゴリを持っています。
「したがって、1 と 0 がターゲット配列に追加されました。
テキスト前処理
データセットがインポートされたら、次のステップはテキストの前処理です。
テキストには、数字や特殊文字、不要なスペースが含まれることがあります。
直面する問題によって、テキストからこれらの特殊文字や数字を削除する必要がある場合とない場合があります。
しかし、ここでは説明の便宜上、テキストからすべての特殊文字、数字、不要なスペースを削除することにします。
以下のスクリプトを実行し、データの前処理を行う。
documents = []
from nltk.stem import WordNetLemmatizer
stemmer = WordNetLemmatizer()
for sen in range(0, len(X)):
# Remove all the special characters
document = re.sub(r'W', ' ', str(X[sen]))
# remove all single characters
document = re.sub(r's+[a-zA-Z]s+', ' ', document)
# Remove single characters from the start
document = re.sub(r'^[a-zA-Z]s+', ' ', document)
# Substituting multiple spaces with single space
document = re.sub(r's+', ' ', document, flags=re.I)
# Removing prefixed 'b'
document = re.sub(r'^bs+', '', document)
# Converting to Lowercase
document = document.lower()
# Lemmatization
document = document.split()
document = [stemmer.lemmatize(word) for word in document]
document = ' '.join(document)
documents.append(document)
上のスクリプトでは、Python reライブラリのRengex Expressionsを使って、さまざまな前処理を行う。
まず、特殊文字や数字など、単語以外の文字をすべて削除することから始める。
次に、一文字をすべて削除する。
例えば、「David’s」から句読点を取り除き、スペースに置き換えると、「David」と「s」という意味のない1文字ができる。
このような1文字を削除するには、左右にスペースがある1文字をすべてスペース1文字に置き換える正規表現 s+[a-zA-Z]s+
を使用します。
次に、文書の先頭の1文字を空白1文字に置換するために、^[a-zA-Z] s+
正規表現を使用します。
1文字を1つのスペースに置き換えると、複数のスペースが生じる可能性があり、これは理想的ではありません。
この場合も、正規表現 s+
を使って、1つ以上のスペースを1つのスペースに置き換えます。
バイト形式のデータセットでは、すべての文字列の前にアルファベットの “b “が付加される。
正規表現 ^bs+
は、文字列の先頭から “b” を削除する。
次に、同じ単語でも大文字と小文字が異なる単語を区別するために、データを小文字に変換する。
最後の前処理は、lemmatizationである。
lemmatizationでは、単語を辞書の語源形式に落とし込む。
例えば、”cats “は “cat “に変換される。
レンマタイゼーションは、意味的には似ているが構文的には異なる特徴を作らないようにするために行われる。
例えば、意味的には似ているが、”cats “と “cat “という2種類の素性はいらないので、lemmatizationを行う。
テキストを数値に変換する
機械は人間と違って、生のテキストを理解することはできない。
機械は数字しか見ることができない。
特に、機械学習のような統計的手法は、数字しか扱えません。
そのため、テキストを数値に変換する必要がある。
テキストを対応する数値に変換するために、さまざまなアプローチが存在する。
Bag of WordsモデルやWord Embeddingモデルは、最もよく使われるアプローチの一つである。
この記事では、Bag of Wordsモデルを用いてテキストを数値に変換する。
ーーバッグオブワーズ
以下のスクリプトは、Bag of Wordsモデルを用いて、テキスト文書を対応する数値特徴に変換するものである。
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(max_features=1500, min_df=5, max_df=0.7, stop_words=stopwords.words('english'))
X = vectorizer.fit_transform(documents).toarray()
上記のスクリプトは sklearn.feature_extraction.text
ライブラリの CountVectorizer
クラスを使用している。
このクラスのコンストラクタに渡す必要がある重要なパラメータがいくつかある。
最初のパラメータは max_features
で、これは 1500 に設定される。
これは、Bag of Word 法を用いて単語を数値に変換する際に、全ての文書に含まれる一意な単語が特徴量に変換されるからである。
すべての文書には何万というユニークな単語が含まれている可能性がある。
しかし、出現頻度が極めて低い単語は、異常に文書を分類するための良いパラメータとはならない。
そこで、max_features
パラメータを 1500 に設定する。
これは、最も出現頻度の高い 1500 個の単語を分類器の学習用の特徴量として使用したいことを意味する。
次のパラメータは min_df
で、これは 5 に設定されている。
これはこの特徴を含むべき最小限の文書数に相当する。
つまり、少なくとも5つの文書に出現する単語のみを対象とする。
同様に、max_df
の値は0.7に設定されており、これはパーセンテージに相当する。
ここで0.7は、全文書の最大70%に出現する単語のみを含めることを意味する。
ほとんどすべての文書に出現する単語は、文書に関する固有の情報を提供しないので、通常、分類には適さない。
最後に、テキストからストップ・ワードを削除します。
センチメント分析の場合、ストップ・ワードには有用な情報が含まれていない可能性があるからです。
ストップワードを除去するために、 nltk.corpus
ライブラリから stopwords
オブジェクトを stop_words
パラメータに渡します。
CountVectorizerクラスの
fit_transform` 関数は、テキスト文書を対応する数値特徴に変換する。
TFIDFの検索
Bag of wordsのアプローチは、テキストを数値に変換する際にはうまくいく。
しかし、1つの欠点がある。
それは、特定の文書での出現率に基づいて、単語にスコアを割り当てることである。
このため、その単語が他の文書でも高い頻度で出現している可能性があるという事実が考慮されない。
TFIDFは、単語の出現頻度に逆文書頻度を乗じることでこの問題を解決している。
TFは「Term Frequency」、IDFは「Inverse Document Frequency」の略である。
項周波数は次のように計算される。
Term frequency = (Number of Occurrences of a word)/(Total words in the document)
そして、逆文書周波数は次のように計算される。
IDF(word) = Log((Total number of documents)/(Number of documents containing the word))
ある文書に含まれる単語のTFIDF値は、その単語の出現頻度がその特定の文書で高く、他のすべての文書で低ければ高くなる。
bag of wordsモデルで得られた値をTFIDF値に変換するには、以下のスクリプトを実行する。
from sklearn.feature_extraction.text import TfidfTransformer
tfidfconverter = TfidfTransformer()
X = tfidfconverter.fit_transform(X).toarray()
注
以下のスクリプトにより、テキスト文書を直接TFIDF特徴量に変換することも可能です(文書をBag of Word特徴量に変換する必要はありません)。
from sklearn.feature_extraction.text import TfidfVectorizer
tfidfconverter = TfidfVectorizer(max_features=1500, min_df=5, max_df=0.7, stop_words=stopwords.words('english'))
X = tfidfconverter.fit_transform(documents).toarray()
トレーニングセットとテストセット
他の教師あり機械学習問題と同様に、データをトレーニングセットとテストセットに分割する必要があります。
これを行うには、sklearn.model_selection
ライブラリの train_test_split
ユーティリティを使用します。
以下のスクリプトを実行してください。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
上記のスクリプトは、データを20%のテストセットと80%のトレーニングセットに分割する。
テキスト分類モデルの学習とセンチメント予測
データをトレーニングセットとテストセットに分けました。
これからが本番です。
ここでは、Random Forest Algorithm を使ってモデルを学習する。
他のモデルも使用可能です。
ランダムフォレストアルゴリズムを用いた機械学習モデルを学習するために、sklearn.ensemble
ライブラリの RandomForestClassifier
クラスを使用します。
このクラスの fit
メソッドを用いて、アルゴリズムの学習を行います。
このメソッドには、学習データと学習用ターゲットセットを渡す必要があります。
以下のスクリプトを見てください。
classifier = RandomForestClassifier(n_estimators=1000, random_state=0)
classifier.fit(X_train, y_train)
最後に、テストセットの文書に対する sentiment を予測するために、以下のように RandomForestClassifier
クラスの predict
メソッドを利用することができます。
y_pred = classifier.predict(X_test)
おめでとうございます。
最初のテキスト分類モデルの学習に成功し、いくつかの予測を行うことができました。
ここで、作成したモデルの性能を確認してみましょう。
モデルの評価
学習した分類モデルの性能を評価するために、混同行列、F1 メジャー、精度などのメトリクスを使用することができます。
これらの値を求めるには、sklearn.metrics
ライブラリの classification_report
, confusion_matrix
, accuracy_score
ユーティリティを用います。
そのためには、以下のスクリプトを実行する。
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print(confusion_matrix(y_test,y_pred))
print(classification_report(y_test,y_pred))
print(accuracy_score(y_test, y_pred))
出力は以下のようになります。
[[180 28]
[ 30 162]]
precision recall f1-score support
0 0.86 0.87 0.86 208
1 0.85 0.84 0.85 192
avg / total 0.85 0.85 0.85 400
0.855
出力から、我々のモデルが85.5%の精度を達成したことがわかります。
これは、ランダムフォレストアルゴリズムと同様に、CountVectorizer
の全てのパラメータをランダムに選択したという事実を考えると、非常に良い結果です。
モデルの保存と読み込み
上記のスクリプトでは、機械学習モデルの実行にそれほど時間はかからなかった。
学習時間が短い理由の一つは、学習セットが比較的少なかったことである。
2000個の文書があり、そのうちの80%(1600個)を学習に用いた。
しかし、現実のシナリオでは、数百万件の文書が存在することもある。
そのような場合、アルゴリズムの学習に数時間、あるいは数日(処理速度の遅いマシンを使っている場合)かかることもある。
そのため、一度学習させたモデルは保存しておくことをお勧めする。
Pythonでは、モデルを pickle
オブジェクトとして保存することができる。
これを行うには、以下のスクリプトを実行する。
with open('text_classifier', 'wb') as picklefile:
pickle.dump(classifier,picklefile)
上記のスクリプトを実行すると、作業ディレクトリに text_classifier
ファイルが生成されます。
これは学習したモデルを保存したもので、後で学習せずに直接予測を行うために使用することができます。
モデルをロードするには、以下のコードを使用します。
with open('text_classifier', 'rb') as training_model:
model = pickle.load(training_model)
学習したモデルをロードし、変数 model
に格納しました。
ロードしたモデルを用いてテストセットの sentiment を予測し、同じ結果が得られるかどうか見てみましょう。
以下のスクリプトを実行します。
y_pred2 = model.predict(X_test)
print(confusion_matrix(y_test, y_pred2))
print(classification_report(y_test, y_pred2))
print(accuracy_score(y_test, y_pred2))
出力はこのようになります。
[[180 28]
[ 30 162]]
precision recall f1-score support
0 0.86 0.87 0.86 208
1 0.85 0.84 0.85 192
avg / total 0.85 0.85 0.85 400
0.855
この出力は、モデルの保存と読み込みに成功したことを示す、先ほどの出力と同じようなものです。
さらに上を目指す – 手作りのEnd to Endプロジェクト
好奇心旺盛なあなたは、もっと先を目指したいと思っていませんか?ガイド付きプロジェクトのチェックをお勧めします。
「CNNとKerasのTransformerを使った画像キャプション作成」をご覧ください。
をご覧ください。
このガイド付きプロジェクトでは、画像を入力として受け取り、出力としてテキストキャプションを生成する画像キャプションモデルの構築方法を学びます。
このプロジェクトでは、次のことを学びます。
- テキストを前処理する
- 入力されたテキストを簡単にベクトル化する
-
tf.data
APIを使用し、パフォーマンスの高いデータセットを構築する。 - TensorFlow/Keras と KerasNLP – 最先端の NLP モデルを構築するための Keras への公式な水平方向の追加機能 – を使用してゼロから Transformers を構築する。
- あるネットワークの出力が別のネットワークにエンコードされるハイブリッドアーキテクチャの構築
画像キャプションをどのようにとらえるか?私たちはネットワークに説明を生成するように教えているので、ほとんどの人が生成的な深層学習の例だと考えています。
しかし、私はこれをニューラル機械翻訳の一例と見なしたいと思います。
つまり、画像の視覚的特徴を言葉に翻訳しているのです。
翻訳することで、単に新しい意味を生成するだけでなく、その画像の新しい表現を生成しているのです。
翻訳、ひいては生成と捉えることで、このタスクは別の観点から見ることができ、もう少し直感的に理解できるようになります。
問題を翻訳の問題としてとらえることで、どのアーキテクチャを使いたいかを考えることが容易になる。
エンコーダのみのトランスフォーマーは、エンコーダが意味のある表現をエンコードするため、テキストの理解(感情分析、分類など)に適している。
デコーダのみのモデルは、デコーダが意味のある表現を同じ意味を持つ別のシーケンスに推論することができるため、生成(GPT-3など)に最適である。
翻訳とは通常、エンコーダーとデコーダーのアーキテクチャによって行われ、エンコーダーは文(我々の場合は画像)の意味のある表現をエンコードし、デコーダーはこのシーケンスを我々にとってより解釈しやすい別の意味のある表現(文など)に変換することを学習するものである。
結論
テキストの分類は、最も一般的に使用される自然言語処理タスクの1つです。
今回は、Pythonでテキスト分類が行える簡単な例を見ました。
映画のレビューのセンチメンタルな分析を行いました。
私はあなたがパフォーマンスを向上させることができるかどうかを確認するために、いくつかの他の機械学習アルゴリズムを変更することをお勧めします。
また、CountVectorizer
クラスのパラメータを変更して、改善が得られるかどうか試してみてください。