目次
- 前回の結果の振り返り
- 今回やること
- データの前処理
- データの準備
- テキストの前処理
- 精度の比較
- 文章の分かち書き
- クロスバリデーションのための分割
- モデルの作成と評価
- まとめ
前回の結果の振り返り
前回の記事では、感情分析のためのベンチマークとして、ロジスティック回帰と、Bag of Words特徴量の組み合わせのモデルを作成しました。
ベンチマークの予測精度として、クロスバリデーションを行ったときの「検証データに対する正解率:88.8%」という結果が得られました。
今回やること
前回は、特にテキストデータを加工せず、そのままBag of Words特徴量を作成しました。今回は、テキストデータを前処理した上で、BoW特徴量を作成して、精度が上がるかを試してみます。
データの前処理
データの準備
■python
- import pandas as pd
- import spacy
- from sklearn.model_selection import StratifiedKFold
- from sklearn.feature_extraction.text import CountVectorizer
- from sklearn.linear_model import LogisticRegression
- from sklearn.metrics import accuracy_score
■python
- # データの読み込み
- df = pd.read_csv('../data/interim/chabsa-sentiment-analysis.csv')
- df.head()

テキストの前処理
ここが今回の記事のメイン部分になります。テキストから意味を抜き出しやすくするために、テキストデータを前処理します。具体的には、以下の処理を行います。
- 全角・半角文字を統一する
- 桁区切りの数字を削除する
- テキストを小文字に寄せる
- 記号をスペースに変換する
全角・半角文字を統一する
■python
- # 全角・半角文字を統一する
- import neologdn
- def apply_neologdn(text):
- return neologdn.normalize(text)
- # 全角・半角文字を統一する
- df['sentence'] = df['sentence'].apply(apply_neologdn)
- df.head()

桁区切りの数字を削除する
■python
- # 桁区切りの数字を削除する
- import re
- def apply_remove_comma(text):
- """文字列中の数値を0に置き換える"""
- text_removed_comma = re.sub(r'(\d+)[,.](\d+)', r'\1\2', text)
- text_replaced_number = re.sub(r'\d+', '0', text_removed_comma)
- return text_replaced_number
- # 数字を削除する
- df['sentence'] = df['sentence'].apply(apply_remove_comma)
- df.head()

テキストを小文字に寄せる
■python
- # テキストを小文字に寄せる
- def apply_lower(text):
- return text.lower()
- # テキスト中の英単語を小文字に寄せる
- df['sentence'] = df['sentence'].apply(apply_lower)
- df.head()

記号をスペースに変換する
■python
- # 記号をスペースに変換する
- def apply_replace_symbol(text):
- """記号をスペースに置き換える"""
- replaced_text = re.sub(r'[!-/:-@[-`{-~]', ' ', text)
- return replaced_text
- # 記号をスペースに変換する
- df['sentence'] = df['sentence'].apply(apply_replace_symbol)
- df.head()

改行コードを削除する
■python
- # 改行コードを削除する
- def apply_remove_newline(text):
- """改行コードを削除する"""
- removed_text = re.sub(r'\\n|\n', '', text)
- return removed_text
- # 記号をスペースに変換する
- df['sentence'] = df['sentence'].apply(apply_replace_symbol)
- df.head()

精度の比較
前処理したテキストを使って、Bag of Words特徴量を作成し、モデルの精度が向上したかを確認します。
文章の分かち書き
■python
- # sentence列を分かち書きする
- nlp = spacy.load('ja_core_news_md')
- def apply_nlp(text):
- doc = nlp(text)
- return [token.text for token in doc]
- # tokensに分かち書きされた単語が入る
- df['tokens'] = df['sentence'].apply(apply_nlp)
- # 分かち書きのリストが作成できているかを確認する
- df.head(5)

クロスバリデーションのための分割
■python
- # クロスバリデーションのために、kfoldの列を追加する
- # k-fold
- df['kfold'] = -1
- # データをシャッフルする
- df = df.sample(frac=1).reset_index(drop=True)
- # StratifiedKFoldを使う
- skf = StratifiedKFold(
- n_splits=5,
- shuffle=False
- )
- # データを分割する
- for fold, (train_idx, val_idx) in enumerate(skf.split(X=df, y=df.rating.values)):
- # foldの列にkfoldの分割番号を入れる
- df.loc[val_idx, 'kfold'] = fold
- # kfoldの分割番号が正しく振られていることを確認する
- df.kfold.value_counts()
- # データを書き出す
- df.to_csv('../data/interim/chabsa-sentiment-analysis-with-kfold-preprocessed.csv', index=False)
モデルの作成と評価
前回の記事と同様に、分割したデータセットごとに、以下の処理を実行します。
- 学習用データと検証用データに分割する
- Bag of Words特徴量を作成する
- モデルを学習する
- 正解率を計算する
評価指標としては、正解率(Accuracy)を使用します。
■python
- # foldごとに以下の処理を行う
- # 1. 学習用データと検証用データに分割する
- # 2. BoW特徴量を作成する
- # 3. モデルを学習する
- # 4. 正解率を計算する
- for fold_ in range(5):
- # 学習用データと検証用データに分割する
- df_train = df[df.kfold != fold_].reset_index(drop=True)
- df_valid = df[df.kfold == fold_].reset_index(drop=True)
- # CountVectorizerインスタンスを作成する
- count_vec = CountVectorizer(
- tokenizer=lambda x: x,
- token_pattern=None,
- lowercase=False
- )
- # 学習用データのtokens列を使ってBoW特徴量を作成する
- count_vec.fit(df_train.tokens)
- # 正解ラベルと特徴量を作成する
- # 特徴量は、学習用データ文章にfitしたCountVectorizerを使って、学習用データと検証用データのBoW特徴量を作成する
- y_train = df_train.rating
- y_valid = df_valid.rating
- X_train = count_vec.transform(df_train.tokens)
- X_valid = count_vec.transform(df_valid.tokens)
- # ロジスティック回帰のインスタンスを作成する
- # BoWは疎な特徴量なので、L1正則化を使うことにする
- lr = LogisticRegression(penalty='l1', solver='liblinear')
- # ロジスティック回帰のモデルを学習する
- lr.fit(X_train, y_train)
- # 検証用データに対する予測を行う
- preds = lr.predict(X_valid)
- # 正解率(Accuracy)を計算する
- accuracy = accuracy_score(
- y_valid,
- preds
- )
- # 予測結果をdfに追加する
- df.loc[df.kfold == fold_, 'preds'] = preds
- # 正解率を出力する
- print(f'Fold={fold_} 検証用データに対する正解率: {accuracy:.4f}')
- # トータルの正解率を出力する
- accuracy = accuracy_score(
- df.rating,
- df.preds
- )
- print(f'トータルの正解率: {accuracy:.4f}')
- Fold=0 検証用データに対する正解率: 0.8845
- Fold=1 検証用データに対する正解率: 0.8899
- Fold=2 検証用データに対する正解率: 0.9023
- Fold=3 検証用データに対する正解率: 0.8808
- Fold=4 検証用データに対する正解率: 0.8950
- トータルの正解率: 0.8905
検証データに対する正解率は89.05%という結果が得られました。
まとめ
テキストに前処理をすることによって、意味をうまく抜き出すことができるようになり、予測精度が上がるかを確かめてみましたが、前処理をしたことによる効果はほぼ見られませんでした。
ロジスティック回帰とBag of Wordsを使った結果、検証データに対する正解率は、89.2% → 89.05%にわずかに下がってしまいました。
次回以降は、異なる特徴量を使うことで、精度を上げることが出来るかを試してみます。