📝

自然言語処理で分類問題をやってみた

2021/03/20に公開

CountVectorizer と TfidVectorizer を使って自然言語処理の分類問題をやってみました。

scikit-learn の 20newsgroup のデータセット【英語】を使っています。

コードはGoogle Colabはこちら、GitHubはこちら

データセット

見やすいようにラベル名を追加したDataFrameに加工します。

dataset = fetch_20newsgroups(shuffle=True, random_state=SEED)

name_list = list(dataset.target_names)

df = pd.DataFrame(dataset.data)
df.columns = ["text"]
df["test_num"] = dataset.target
df["test"] = [name_list[i] for i in df["test_num"]]

Count Vectorizer

単語の出現回数をベクトル化する処理を行います。

vec_count = CountVectorizer()
train = vec_count.fit_transform(df["text"])

インデックスごとに[1, 1, 0, 2, 0, 0...]といったゼロを多く含むベクトルが作成されます。
そのため、メモリ削減のため疎行列となっている点を抑えましょう。

type(train)

出力:
scipy.sparse.csr.csr_matrix

https://hamukazu.com/2014/09/26/scipy-sparse-basics/

単語ごとにカウントするため、かなり学習に時間がかかります。

X_train, X_test, y_train, y_test = train_test_split(train, df["test_num"], test_size=TEST_RATE, random_state=SEED)
model = linear_model.RidgeClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f1_score(y_test, y_pred, average = 'micro'))

f1スコア: 0.86
処理時間: 325.2秒

20個のマルチクラス分類ではありますが、CountVectorizerでもf1スコアは86%とかなりいい結果となりました。

処理が重いのが玉に瑕ですね。

Tfidf Vectorizer

TF(単語の出現頻度)とIDF(単語のレア度)を組み合わせたTF-IDF(索引語頻度逆文書頻度)をベクトル化する処理を行います。

vec_tfidf = TfidfVectorizer()
train = vec_tfidf.fit_transform(df["text"])
X_train, X_test, y_train, y_test = train_test_split(train, df["test_num"], test_size=TEST_RATE, random_state=SEED)
model = linear_model.RidgeClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f1_score(y_test, y_pred, average = 'micro'))

CountVectorizerと違い、意味はないにも関わらず出現頻度が多い単語に引っ張られなくなるため、精度が向上しています。

また、CountVectorizerに比べ圧倒的に処理が早くなっています。

f1スコア: 0.92
処理時間: 6.8秒

https://qiita.com/fujin/items/b1a7152c2ec2b4963160

参考1 混合行列+ヒートマップ

混合行列をヒートマップ表示すると、特にマルチクラスの場合はどのクラスの精度が良いのかわかりやすいです。

plt.figure(figsize=(9, 9))
sns.heatmap(pd.DataFrame(confusion_matrix(y_test, y_pred)), annot=True,  square=True)

参考2 TruncatedSVD で次元削減+Count Vectorizer

PCAと違い、TruncatedSVDによる主成分分析は疎行列のままでも使えるので便利です。

PCAとは異なり、この推定量は特異値分解を計算する前にデータを中央に配置しません。
これは、スパース行列を効率的に処理できることを意味します。
Google 翻訳

https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html

vec_count = CountVectorizer()
train = vec_count.fit_transform(df["text"])

for i in [10,  100,  500, 1000]:
    start = time.time()

    svd = TruncatedSVD(n_components=i, random_state=SEED)
    feature = svd.fit_transform(train)
    X_train, X_test, y_train, y_test = train_test_split(feature, df["test_num"], test_size=TEST_RATE, random_state=SEED)
    model = linear_model.RidgeClassifier()
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)

n_components別の処理時間、精度は以下のとおり。

経過時間 f1スコア
10 1.6秒 0.14
100 8.2秒 0.50
500 35.9秒 0.76
1000 75.6秒 0.82
次元削減なし 325.2秒 0.86

n_components別のヒートマップを見ると、クラスごとの精度の変化がわかります。

https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.TruncatedSVD.html

以上になります、最後までお読みいただきありがとうございました。

Discussion