File Icons
BLOG
(ch-6) テキストデータを整形して、ロジスティック回帰 + BoWによる感情分析を改善する

目次

  1. 前回の結果の振り返り
  2. 今回やること
  3. データの前処理
    1. データの準備
    2. テキストの前処理
  4. 精度の比較
    1. 文章の分かち書き
    2. クロスバリデーションのための分割
    3. モデルの作成と評価
  5. まとめ

前回の結果の振り返り

前回の記事では、感情分析のためのベンチマークとして、ロジスティック回帰と、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()
image.png (36.8 kB)

テキストの前処理

ここが今回の記事のメイン部分になります。テキストから意味を抜き出しやすくするために、テキストデータを前処理します。具体的には、以下の処理を行います。

  • 全角・半角文字を統一する
  • 桁区切りの数字を削除する
  • テキストを小文字に寄せる
  • 記号をスペースに変換する

全角・半角文字を統一する

■python

# 全角・半角文字を統一する
import neologdn

def apply_neologdn(text):
    return neologdn.normalize(text)

# 全角・半角文字を統一する
df['sentence'] = df['sentence'].apply(apply_neologdn)
df.head()
image.png (36.8 kB)

桁区切りの数字を削除する

■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()
image.png (37.2 kB)

テキストを小文字に寄せる

■python

# テキストを小文字に寄せる
def apply_lower(text):
    return text.lower()

# テキスト中の英単語を小文字に寄せる
df['sentence'] = df['sentence'].apply(apply_lower)
df.head()
スクリーンショット 2023-09-14 19.18.51.png (34.5 kB)

記号をスペースに変換する

■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()
image.png (37.2 kB)

改行コードを削除する

■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()
image.png (37.1 kB)

精度の比較

前処理したテキストを使って、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)
image.png (56.3 kB)

クロスバリデーションのための分割

■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)

モデルの作成と評価

前回の記事と同様に、分割したデータセットごとに、以下の処理を実行します。

  1. 学習用データと検証用データに分割する
  2. Bag of Words特徴量を作成する
  3. モデルを学習する
  4. 正解率を計算する

評価指標としては、正解率(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%にわずかに下がってしまいました。

次回以降は、異なる特徴量を使うことで、精度を上げることが出来るかを試してみます。

CONTACT
ご依頼やご相談、サービスについてのご質問やご要望がございましたら、お気軽にお問い合わせください。
送付いただいた内容を確認の上、担当者からご連絡させていただきます。
お問い合わせ