ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

TF-IDFとword2vecを結合する

fnc-1の優勝モデルでは、TF-IDFやword2vecをはじめ、5種類の特徴量が結合されています。これを参考に、TF-IDFとword2vecを結合したら文書分類の精度が上がるのかを検証します。(ただし、実際にはword2vecというより、nnlm-ja-dim128を使います)

事前準備

データはスクレイピングによって取得しますが、著作権などもあるので、ここでは公開しません。しかし、手順だけ示しておきます:

  1. 二値分類モデルとして定義し、カテゴリー1とカテゴリー2を定義する。
  2. カテゴリー1を提供しているニュースサイト数種から記事数千に対するタイトルと本文を抽出。
  3. カテゴリー2を提供しているニュースサイト数種から記事数千に対するタイトルと本文を抽出。

Jupyter notebookで実行

データのロード。

In[1]:

import pandas as pd
df = pd.read_csv("credibility_dataset.csv")
X = df[['body', 'title']]
y = df['label'].tolist()

必要なモジュールをインポート。

In[2]:

import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer

TF-IDFモデルの検証。

In[3]:

pipe = Pipeline([('tfidf',TfidfVectorizer(min_df=3, max_df=10)), ("clf", LogisticRegression())])
np.mean(cross_val_score(pipe, X["title"], y, cv=10)), np.mean(cross_val_score(pipe, X["body"], y, cv=10))

Out[3]:

(0.866622646802691, 0.9214263887346552)

word2vecモデルの検証。

In[4]:

def texts_encoder(texts):
    with tf.Graph().as_default():
        embed = hub.Module("https://tfhub.dev/google/nnlm-ja-dim128/1")
        embeddings = embed(texts)
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer())
            sess.run(tf.tables_initializer())
            result = sess.run(embeddings)
    return result


class  MeanEmbeddingVectorizer:
    def __init__(self):
        pass

    def fit(self, X, y):
        return self

    def transform(self, X):
        if isinstance(X, pd.core.series.Series):
            X = X.tolist()
        return texts_encoder(X)

pipe = Pipeline([('w2v',MeanEmbeddingVectorizer()), ("clf", LogisticRegression())])
np.mean(cross_val_score(pipe, X["title"], y, cv=10)), np.mean(cross_val_score(pipe, X["body"], y, cv=10))

Out[4]:

(0.9849258120637222, 0.9846317732046735)

TF-IDFとword2vecを結合したモデルの検証。

In[5]:

class  TfIdfWord2VecVectorizer:
    def __init__(self):
        pass

    def fit(self, X, y):
        self.tfidf_model = TfidfVectorizer(min_df=3, max_df=10).fit(X)
        return self

    def transform(self, X):
        return self.tfidf_and_w2v(X, self.tfidf_model)
    
    def texts_encoder(self, texts):
        with tf.Graph().as_default():
            embed = hub.Module("https://tfhub.dev/google/nnlm-ja-dim128/1")
            embeddings = embed(texts)
            with tf.Session() as sess:
                sess.run(tf.global_variables_initializer())
                sess.run(tf.tables_initializer())
                result = sess.run(embeddings)
        return result
    
    def tfidf_and_w2v(self, X, tfidf_model):
        tmp = tfidf_model.transform(X)
        a = tmp.toarray()
        if isinstance(X, pd.core.series.Series):
            X = X.tolist()
        b = self.texts_encoder(X)
        return np.hstack((a, b))

pipe = Pipeline([('vector',TfIdfWord2VecVectorizer()), ("clf", LogisticRegression())])
np.mean(cross_val_score(pipe, X["title"], y, cv=10)), np.mean(cross_val_score(pipe, X["body"], y, cv=10))

Out[5]:

(0.9878243676588914, 0.9863709085099875)

考察

tf-idfとword2vecのベクトルを結合した特徴量が高い精度を出すことがわかりました。一般的に、モデルのシンプルさと精度はトレードオフになる傾向にありますが、特徴量を結合することにより、両方の特徴量から学習できたのだと思います。

このため、CountVectorizerやSVD、さらには感情分析の結果など様々な特徴量を結合したとしても、精度が上がる可能性があります。全く異なる性質のベクトルを結合してそれを機械学習アルゴリズムが読み取ってくれるのか怪しかったのですが(あるいは過学習するかもしれないと思いましたが)、TF-IDFやWord2Vecを単体で使うだけでなく、結合させる方法もある、ということでした。