機械学習データ前処理入門

機械学習のためのデータ前処理:テキストデータを扱う方法【Pandas入門】

Tags: 機械学習, データ前処理, テキスト処理, Pandas, Python

はじめに:なぜテキストデータの前処理が必要なのか

ビジネスにおいて、顧客からのフィードバック、アンケートの自由記述、社内文書、SNSの投稿など、様々な形でテキストデータが存在します。これらのテキストデータには、顧客の意向や市場のトレンド、潜在的な課題など、ビジネスにとって非常に価値のある情報が含まれている可能性があります。

機械学習を使ってこれらのテキストデータを分析し、有用な知見を得たり、特定のタスク(例:感情分析、トピック分類、情報抽出)を実行したりするためには、データの前処理が不可欠です。なぜなら、機械学習モデルは基本的に数値を扱うため、テキストデータそのままではモデルに入力できないからです。また、同じ意味を持つ単語でも表記が揺れていたり、分析には不要な情報が含まれていたりするため、これらを整形・統一する必要があります。

この記事では、機械学習にテキストデータを活用するための基本的な前処理ステップと、Pythonの代表的なライブラリであるPandas、そしてテキスト処理に特化したライブラリ(NLTKなど)や機械学習ライブラリ(Scikit-learn)を使った具体的な実装方法について、初心者の方にも分かりやすく解説します。

テキストデータ前処理の基本的な流れ

テキストデータを機械学習で扱える形式にするためには、いくつかの標準的なステップを踏む必要があります。これらのステップを経て、テキストデータは単語やフレーズの集まりとして捉えられ、最終的に数値データに変換されます。

一般的なテキストデータ前処理のステップは以下の通りです。

  1. クリーニング: テキストデータに含まれる不要な文字(HTMLタグ、URL、記号、絵文字など)を除去したり、表記を統一したりします。
  2. トークン化: テキストを意味を持つ最小単位(単語やフレーズなど)に分割します。この分割された単位を「トークン」と呼びます。
  3. ストップワード除去: 文章の意味に大きく影響しない一般的な単語(例:「て」「に」「を」「は」「です」「ます」など)を除去します。
  4. 正規化(ステミング/レンマ化): 単語の語形変化(例:「走る」「走ります」「走った」)を吸収し、原型や語幹に統一します。

これらの前処理を施した後、テキストデータを機械学習モデルが理解できる数値ベクトルに変換します。この数値変換についても後述します。

PythonとPandasでのテキストデータ準備

まずは、簡単なテキストデータを含むPandas DataFrameを作成し、前処理を行う準備をします。

import pandas as pd

# サンプルデータを含むDataFrameを作成
data = {'id': [1, 2, 3, 4, 5],
        'text': ["この商品は本当に素晴らしいです。また購入します!",
                 "最悪の商品。全く使えませんでした。",
                 "サービスの対応がとても良かったです。感動しました。",
                 "普通ですね。良くもなく悪くもなく。",
                 "購入して良かった!大満足の商品です!"]}
df = pd.DataFrame(data)

print("元のDataFrame:")
print(df)

上記のコードで、text列に日本語のレビューテキストを含むDataFrameが作成されました。ここから、このtext列に対して様々な前処理を施していきます。

ステップ1: クリーニング

クリーニングでは、分析に不要な文字やパターンを除去します。例えば、URL、メールアドレス、特定の記号などが考えられます。ここでは、簡単な例として、句読点や感嘆符などを削除し、数字も取り除くことを考えます。Pythonの正規表現ライブラリreを使用することが一般的です。

import re

def clean_text(text):
    # 全角記号を半角に変換(必要に応じて)
    text = text.replace('!', '!')
    text = text.replace('?', '?')
    text = text.replace('。', '.')
    text = text.replace('、', ',')

    # 不要な記号、数字などを除去
    text = re.sub(r'[!?,.「」()【】『』()[]<>:"\';&]+', '', text) # 記号の除去
    text = re.sub(r'[0-9]+', '', text) # 数字の除去

    # スペースの連続を単一スペースに置換(必要に応じて)
    text = re.sub(r'\s+', ' ', text).strip()

    return text

# 'text'列にクリーニング関数を適用
df['cleaned_text'] = df['text'].apply(clean_text)

print("\nクリーニング後のDataFrame:")
print(df)

この例では、正規表現を使って特定の文字パターンを空文字列に置換しています。実際のテキストデータに応じて、除去すべきパターンは異なります。

ステップ2: トークン化

次に、クリーニングされたテキストを単語や形態素(意味を持つ最小単位)に分割します。日本語の場合、単語の区切りが曖昧なため、「形態素解析」という技術が必要になります。形態素解析には、JanomeやMeCabなどのライブラリがあります。ここでは、手軽に使えるJanomeライブラリを例に挙げます。

Janomeを使うには、事前にインストールが必要です (pip install Janome)。

from janome.tokenizer import Tokenizer

# Tokenizerを初期化
t = Tokenizer()

def tokenize_text(text):
    # 形態素解析を実行し、単語(形態素)のリストを取得
    tokens = [token.surface for token in t.tokenize(text)]
    return tokens

# 'cleaned_text'列にトークン化関数を適用
df['tokens'] = df['cleaned_text'].apply(tokenize_text)

print("\nトークン化後のDataFrame:")
print(df)

実行結果を見ると、「この」「商品」「は」「本当に」「素晴らしい」のように、テキストが単語(形態素)ごとに分割されていることが分かります。

ステップ3: ストップワード除去

トークン化された単語リストから、分析に不要なストップワードを除去します。NLTKのような英語圏のテキスト処理ライブラリには標準的なストップワードリストが含まれていますが、日本語の場合は自作するか、公開されているリストを使用する必要があります。ここでは簡単な例として、いくつかの助詞や一般的な名詞をストップワードとして定義します。

# 日本語の簡単なストップワードリストの例
# 実際の分析ではより網羅的なリストを使用することが多いです
stopwords_ja = set(['の', 'は', 'に', 'を', 'が', 'と', 'より', 'も', 'ます', 'です', 'た', 'こと', 'もの'])

def remove_stopwords(tokens):
    # ストップワードリストに含まれないトークンだけを残す
    filtered_tokens = [word for word in tokens if word not in stopwords_ja]
    return filtered_tokens

# 'tokens'列にストップワード除去関数を適用
df['filtered_tokens'] = df['tokens'].apply(remove_stopwords)

print("\nストップワード除去後のDataFrame:")
print(df)

ストップワード除去により、「は」「です」「た」といった単語がリストから取り除かれていることが確認できます。

ステップ4: 正規化(ステミング/レンマ化)

単語の揺れを吸収する正規化ステップです。「購入する」「購入します」「購入した」といった単語を「購入」のように統一することで、同じ意味を持つ単語が別々のものとして扱われるのを防ぎます。

日本語の形態素解析ライブラリ(Janomeなど)は、トークン化の際に単語の原型(基本形)を取得する機能を持っています。この原型を使用することが、日本語における正規化の一つの方法です。

# ステップ2のトークン化関数を修正し、基本形を取得するように変更
def tokenize_and_lemmatize(text):
    # 形態素解析を実行
    tokens_with_pos = t.tokenize(text) # 品詞情報なども取得
    lemmatized_tokens = []
    for token in tokens_with_pos:
        # 単語の基本形(原型)を取得。ない場合は表層形を使用
        base_form = token.base_form if token.base_form != '*' else token.surface
        # さらに、特定の品詞(例:名詞、動詞、形容詞など)のみを抽出することも多い
        # if token.part_of_speech.split(',')[0] in ['名詞', '動詞', '形容詞']:
        lemmatized_tokens.append(base_form)
    return lemmatized_tokens

# 'cleaned_text'列に正規化(基本形取得)関数を適用
# この結果に対してストップワード除去を行う
df['lemmatized_tokens'] = df['cleaned_text'].apply(tokenize_and_lemmatize)

print("\n正規化(基本形取得)後のDataFrame:")
print(df)

# 正規化後のトークンリストに対してストップワード除去を再度適用
df['processed_tokens'] = df['lemmatized_tokens'].apply(remove_stopwords)

print("\n前処理完了後のトークンリストDataFrame:")
print(df)

このように、トークン化の段階で基本形を取得し、その後のステップに利用することができます。

機械学習で扱える数値形式への変換

前処理によって、各テキストは単語(トークン)のリストになりました。これを機械学習モデルに入力するためには、数値ベクトルに変換する必要があります。代表的な手法として、Bag-of-Words (BoW) と TF-IDF があります。

Bag-of-Words (BoW)

Bag-of-Wordsは、テキストを単語の「袋」とみなし、単語の出現順序を無視して、各単語がテキスト内にどれだけ出現するかを数える方法です。

例えば、「この 商品 は 素晴らしい」と「素晴らしい 商品 です」という2つのテキストがあったとします。 登場する全ての単語(語彙)は「この」「商品」「は」「素晴らしい」「です」です。 それぞれのテキストを、これらの語彙の出現回数でベクトル化すると以下のようになります。

このように、テキストを数値ベクトルに変換できます。

Scikit-learnライブラリのCountVectorizerを使うと、このBoWベクトル化を簡単に行うことができます。

from sklearn.feature_extraction.text import CountVectorizer

# CountVectorizerを初期化
# 各テキストをスペース区切りの文字列に戻す必要がある
corpus = df['processed_tokens'].apply(lambda tokens: ' '.join(tokens)).tolist()

# ベクトル化
vectorizer = CountVectorizer()
bow_matrix = vectorizer.fit_transform(corpus)

# 結果を確認(疎行列形式)
print("\nBag-of-Words 行列:")
print(bow_matrix)

# どのような単語が列に対応しているかを確認
print("\nBag-of-Words の語彙:")
print(vectorizer.get_feature_names_out())

# 密行列に変換して表示(小さいデータセット向け)
print("\nBag-of-Words 密行列:")
print(bow_matrix.toarray())

CountVectorizerは、入力されたテキストデータから語彙を構築し、各テキストをその語彙における単語出現回数のベクトルに変換します。

TF-IDF

Bag-of-Wordsは単語の出現回数だけを考慮しますが、例えば「商品」のような単語は多くのレビューに登場する可能性が高く、その単語だけでレビューの内容を特徴づけるのは難しい場合があります。一方、特定のレビューにだけ出現する単語は、そのレビューの特徴を表している可能性が高いと言えます。

TF-IDF (Term Frequency-Inverse Document Frequency) は、このような問題を解決するために考案された手法です。特定の単語が「文書内での出現頻度 (TF: Term Frequency)」と「文書全体でのレア度 (IDF: Inverse Document Frequency)」を組み合わせて、単語の重要度を数値化します。

TF-IDF値が高い単語ほど、その文書を特徴づける重要な単語であるとみなされます。

Scikit-learnライブラリのTfidfVectorizerを使うと、TF-IDFベクトル化を簡単に行うことができます。TfidfVectorizerは内部でトークン化(必要に応じて)や単語カウントを行い、そのままTF-IDF値を計算してくれますが、ここでは前処理済みのトークンリストをスペース区切りで結合した文字列を入力として使用します。

from sklearn.feature_extraction.text import TfidfVectorizer

# TfidfVectorizerを初期化
# CountVectorizerと同様に、スペース区切りの文字列を入力
vectorizer_tfidf = TfidfVectorizer()
tfidf_matrix = vectorizer_tfidf.fit_transform(corpus)

# 結果を確認(疎行列形式)
print("\nTF-IDF 行列:")
print(tfidf_matrix)

# どのような単語が列に対応しているかを確認
print("\nTF-IDF の語彙:")
print(vectorizer_tfidf.get_feature_names_out())

# 密行列に変換して表示(小さいデータセット向け)
print("\nTF-IDF 密行列:")
print(tfidf_matrix.toarray())

TF-IDF値は0から1の間の浮動小数点数で表現され、単語の重要度を示します。このTF-IDF行列を機械学習モデル(分類器や回帰モデルなど)の入力として使用することができます。

まとめ

この記事では、機械学習でテキストデータを扱うための基本的な前処理ステップと、Pythonおよび関連ライブラリ(Pandas, Janome, Scikit-learn)を使った具体的な実装方法を解説しました。

テキストデータはそのままでは機械学習モデルで扱えませんが、クリーニング、トークン化、ストップワード除去、正規化といったステップを経て、単語のリストに変換できます。さらに、Bag-of-WordsやTF-IDFといった手法を用いることで、テキストデータを数値ベクトルとして表現し、機械学習モデルに入力可能な形式にすることができます。

今回紹介した内容は、テキストデータ前処理の基本的な入口です。実際の応用では、より高度な正規表現、品詞フィルタリング、N-gram(単語の連なり)、単語埋め込み(Word Embedding)といった様々な手法が用いられます。しかし、まずは基本的なステップを理解し、実践することが、テキストデータを活用した機械学習の第一歩となります。

この記事で学んだ内容が、皆様の機械学習プロジェクトにおけるテキストデータの前処理に役立てば幸いです。