11日目:非構造化データを扱う!テキストデータの前処理と数値化
皆さん、こんにちは!AI学習ロードマップ11日目を迎えました。これまでの学習では、主に表形式の構造化データを扱ってきましたが、現実世界のデータは、ソーシャルメディアの投稿、顧客からのレビュー、ニュース記事、音声の文字起こしなど、その多くが「非構造化データ」であるテキストデータです。
今日のテーマは、このテキストデータをAIモデルが理解できる形に変換するための「前処理と数値化」です。テキストデータは、そのままでは機械学習モデルに入力できません。単語や文をモデルが計算できる数値表現に変換する技術は、自然言語処理(NLP)の基盤であり、AIエンジニアにとって必須のスキルです。
本日は、テキストデータ特有の前処理ステップ、そして様々な数値化(特徴量化)手法について、Pythonでの実装例とともに詳しく解説していきます。
1. なぜテキストデータの前処理と数値化が必要なのか?
テキストデータは、人間にとっては意味のある情報源ですが、機械学習モデルにとっては単なる文字の羅列です。モデルは数値しか理解できないため、テキストを数値に変換する必要があります。
しかし、単に文字を数値に置き換えるだけでは不十分です。テキストデータには以下のような課題があり、適切な前処理が不可欠です。
- 多様な表現: 同じ意味でも、「好き」「大好き」「めっちゃ好き」のように多様な表現が存在します。
- ノイズ: 絵文字、URL、HTMLタグ、記号、スペルミス、顔文字など、分析には不要な情報が含まれることがあります。
- 形態の変化: 「走る」「走った」「走っている」のように、単語が文脈によって形を変えることがあります。
- 意味の曖昧さ: 同じ単語でも文脈によって意味が異なることがあります(例:「りんご」は果物か会社か)。
- 膨大な語彙: 人間の言語には数百万の単語が存在し、そのまま扱うと高次元のスパースなデータになってしまいます。
これらの課題に対処し、モデルがテキストから効果的に学習できるように、前処理と数値化のプロセスが重要になります。
2. テキストデータの前処理テクニック
AIモデルに投入する前に、テキストを「クリーン」にするための基本的なステップです。
2.1. クリーニング (Cleaning)
不要な文字やパターンを除去します。
- HTMLタグ、URLの除去: ウェブサイトからスクレイピングしたデータなどによく含まれます。
- 絵文字、特殊記号の除去: 解析の妨げになる場合があるため、除去します。
-
数字の扱い: 数字を全て除去するか、
[NUM]
のようなプレースホルダーに置き換えるか、タスクによって判断します。 - 小文字化 (Lowercase Conversion): 「Apple」と「apple」を同じ単語として扱うために、全て小文字に統一します。
import re
import string
text = "Hello, World! This is a test. Visit our website: https://example.com 😊. Price is $100."
# 1. 小文字化
text_lower = text.lower()
print(f"1. 小文字化: {text_lower}")
# 2. URLの除去
text_no_url = re.sub(r'http\S+|www.\S+', '', text_lower)
print(f"2. URL除去: {text_no_url}")
# 3. 絵文字の除去 (簡単な例、より頑健な方法は外部ライブラリを使う)
# Unicodeの絵文字ブロックを指定して削除
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002702-\U000027B0"
u"\U000024C2-\U0001F251"
"]+", flags=re.UNICODE)
text_no_emoji = emoji_pattern.sub(r'', text_no_url)
print(f"3. 絵文字除去: {text_no_emoji}")
# 4. 句読点、数字、その他不要な文字の除去
# string.punctuation: '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
# re.escape(string.punctuation): 正規表現で特殊文字をエスケープ
text_clean = re.sub(f'[{re.escape(string.punctuation)}]', '', text_no_emoji)
text_clean = re.sub(r'\d+', '', text_clean) # 数字を除去
text_clean = text_clean.strip() # 前後の空白除去
print(f"4. 句読点・数字除去: {text_clean}")
2.2. トークン化 (Tokenization)
テキストを意味を持つ最小単位(単語やサブワード)に分割するプロセスです。
- 単語トークン化 (Word Tokenization): スペースや句読点を区切りとして単語に分割します。
- 文トークン化 (Sentence Tokenization): 文の区切り(ピリオドなど)で文に分割します。
-
サブワードトークン化:
transformers
ライブラリなどで使われる手法で、未知語問題を緩和するために単語をさらに小さい単位(例:un
+happy
)に分割します。
from nltk.tokenize import word_tokenize, sent_tokenize
import nltk
# NLTKのトークナイザーをダウンロード (初回のみ)
try:
nltk.data.find('tokenizers/punkt')
except nltk.downloader.DownloadError:
nltk.download('punkt')
text = "Hello, World! This is an example sentence. I am learning NLP."
# 文トークン化
sentences = sent_tokenize(text)
print(f"\n--- 文トークン化 ---")
print(sentences)
# 単語トークン化 (小文字化してから適用するのが一般的)
words = word_tokenize(text.lower())
print(f"\n--- 単語トークン化 ---")
print(words)
2.3. ストップワード除去 (Stop Word Removal)
「the」「a」「is」「and」のように、文法的には重要だが、文の意味を大きく変えない頻出単語(ストップワード)を除去します。これにより、特徴量の次元数を減らし、ノイズを削減できます。
from nltk.corpus import stopwords
# NLTKのストップワードリストをダウンロード (初回のみ)
try:
nltk.data.find('corpora/stopwords')
except nltk.downloader.DownloadError:
nltk.download('stopwords')
stop_words = set(stopwords.words('english')) # 英語のストップワードリスト
filtered_words = [word for word in words if word not in stop_words]
print(f"\n--- ストップワード除去後 ---")
print(filtered_words)
2.4. ステミング (Stemming) / レンマ化 (Lemmatization)
単語の活用形を元の形に戻すプロセスです。
- ステミング: 単語の語尾を削ることで、語幹(stem)に変換します。単純なルールベースのため、必ずしも正確な単語に戻るとは限りません(例:「running」→「runn」)。
- レンマ化: 単語の原型(lemma)に変換します。辞書や形態素解析の知識を利用するため、より正確な原形に戻すことができます(例:「better」→「good」、「running」→「run」)。
from nltk.stem import PorterStemmer, WordNetLemmatizer
# NLTKのWordNetをダウンロード (初回のみ)
try:
nltk.data.find('corpora/wordnet')
except nltk.downloader.DownloadError:
nltk.download('wordnet')
stemmer = PorterStemmer()
lemmatizer = WordNetLemmatizer()
words_to_process = ["running", "runs", "ran", "beautiful", "better", "geese"]
stemmed_words = [stemmer.stem(word) for word in words_to_process]
lemmatized_words = [lemmatizer.lemmatize(word) for word in words_to_process] # デフォルトは名詞
print(f"\n--- ステミング ---")
print(f"元: {words_to_process}")
print(f"ステミング後: {stemmed_words}")
print(f"\n--- レンマ化 ---")
print(f"元: {words_to_process}")
print(f"レンマ化後: {lemmatized_words}") # 'better'が'good'にならないのは品詞情報がないため
# レンマ化は品詞情報(pos)を指定するとより正確に動作する
lemmatized_words_pos = [lemmatizer.lemmatize("better", pos='a')] # 'a'は形容詞
print(f"レンマ化 (better, pos='a'): {lemmatized_words_pos}")
レンマ化は、品詞(名詞: n
, 動詞: v
, 形容詞: a
, 副詞: r
)を指定することで、より正確な原型を得られます。
3. テキストデータの数値化(特徴量化)テクニック
クリーンになったテキストデータを、AIモデルが学習できる数値ベクトルに変換します。
3.1. BoW (Bag of Words)
テキストを単語の集合(袋)として捉え、各単語の出現回数を数えることでベクトル表現にします。単語の順序は考慮されません。
- メリット: シンプルで直感的。計算が速い。
- デメリット: 単語の順序や文脈が失われる。語彙数が増えると高次元でスパースなベクトルになる。
from sklearn.feature_extraction.text import CountVectorizer
documents = [
"I love machine learning",
"Machine learning is fascinating",
"I love AI",
"AI is the future"
]
# BoWモデルの初期化
vectorizer = CountVectorizer()
# ドキュメントを学習し、BoWベクトルに変換
X_bow = vectorizer.fit_transform(documents)
print(f"\n--- BoW (Bag of Words) ---")
print(f"単語辞書: {vectorizer.get_feature_names_out()}")
print(f"BoWベクトル:\n{X_bow.toarray()}")
3.2. TF-IDF (Term Frequency-Inverse Document Frequency)
BoWの改良版で、単語の重要度を考慮した重み付けを行います。ある単語が特定のドキュメントで頻繁に出現し、かつ他の多くのドキュメントではあまり出現しない場合に、その単語の重要度が高くなります。
- TF (Term Frequency): ドキュメント内での単語の出現頻度。
-
IDF (Inverse Document Frequency): コーパス(全ドキュメント)内での単語の珍しさの指標。多くのドキュメントに出現する単語のIDFは低く、特定のドキュメントにしか出現しない単語のIDFは高くなります。
- ※「コーパス(Corpus)」とは、自然言語の文章や使い方を大規模に収集し、コンピュータで検索できるよう整理されたデータベースのことです。 日本語では「言語全集」などとも呼ばれます。 AIが自然言語を扱うためには、膨大な量のデータ学習が必要です。
$TFIDF(t, d, D) = TF(t, d) \times IDF(t, D)$
- メリット: 単語の重要度を考慮するため、BoWよりも表現力が高い。
- デメリット: 単語の順序や文脈が失われる点はBoWと同様。
from sklearn.feature_extraction.text import TfidfVectorizer
# BoWと同じドキュメントを使用
# TF-IDFモデルの初期化
tfidf_vectorizer = TfidfVectorizer()
# ドキュメントを学習し、TF-IDFベクトルに変換
X_tfidf = tfidf_vectorizer.fit_transform(documents)
print(f"\n--- TF-IDF ---")
print(f"単語辞書: {tfidf_vectorizer.get_feature_names_out()}")
print(f"TF-IDFベクトル:\n{X_tfidf.toarray()}")
3.3. Word Embedding (単語埋め込み)
単語を低次元の密なベクトル(高次元空間での単語の意味や関係性を表現するベクトル)に変換する手法です。これにより、単語間の意味的な類似性(例:「王」と「女王」はベクトル空間で近い位置にある)を捉えることができます。
-
代表的な手法: Word2Vec, GloVe, FastText
-
深層学習ベースの手法: BERT, GPT, ELMoなどのTransformerベースのモデルは、より高度な文脈依存の単語埋め込みを提供します。
-
メリット: 単語の意味的・文法的な関係性を捉えることができる。次元数がBoWやTF-IDFよりも大幅に低い(通常数百次元)。
-
デメリット: 事前学習済みのモデルや大規模なコーパスが必要。計算コストが高い。
# Word2Vecの簡易的なイメージ (Gensimライブラリを使用するのが一般的)
# 実際には大規模なコーパスで事前に学習させる
# 実際には以下のように大規模なデータで学習済みモデルを使用するか、自前で学習させる
# from gensim.models import Word2Vec
# from nltk.tokenize import word_tokenize
#
# sentences_for_w2v = [word_tokenize(doc.lower()) for doc in documents]
#
# # モデルの学習 (ここでは非常に小規模な例)
# model_w2v = Word2Vec(sentences_for_w2v, vector_size=5, window=2, min_count=1, workers=4)
#
# print(f"\n--- Word Embedding (Word2Vec) ---")
# print(f"'love'のベクトル: {model_w2v.wv['love']}")
# print(f"'machine'のベクトル: {model_w2v.wv['machine']}")
# print(f"類似度 ('love'と'ai'): {model_w2v.wv.similarity('love', 'ai')}")
# # 小規模なデータでは意味のある類似度は出にくい
単語埋め込みは、単語レベルでの意味を捉えるため、より高度なNLPタスク(感情分析、機械翻訳など)で高い性能を発揮します。
4. テキストデータ前処理のパイプライン
実際のプロジェクトでは、これらのステップを組み合わせてパイプラインを構築します。
- ※ 機械学習におけるパイプラインとは、AI(予測モデル)を作るためのデータ処理と学習の全工程を、自動で実行する一連の仕組みです。これにより、手作業なしで効率的に、かつ安定してAIを開発できます。例えるなら、AI製造の自動ラインです。
def preprocess_text(text):
text = text.lower()
text = re.sub(r'http\S+|www.\S+', '', text) # URL除去
text = emoji_pattern.sub(r'', text) # 絵文字除去
text = re.sub(f'[{re.escape(string.punctuation)}]', '', text) # 句読点除去
text = re.sub(r'\d+', '', text) # 数字除去
text = text.strip()
words = word_tokenize(text)
filtered_words = [word for word in words if word not in stop_words]
lemmatized_words = [lemmatizer.lemmatize(word) for word in filtered_words] # 名詞としてレンマ化
return " ".join(lemmatized_words)
# 元のドキュメント
raw_documents = [
"I love machine learning!",
"Machine learning is fascinating, visit www.example.com 😊",
"I love AI, AI is the future. My favorite price is $100."
]
# 前処理を適用
processed_documents = [preprocess_text(doc) for doc in raw_documents]
print(f"\n--- 前処理後のドキュメント ---")
for doc in processed_documents:
print(doc)
# TF-IDFを適用
tfidf_vectorizer = TfidfVectorizer()
X_processed_tfidf = tfidf_vectorizer.fit_transform(processed_documents)
print(f"\n--- 前処理&TF-IDF後のベクトル (一部) ---")
print(f"単語辞書: {tfidf_vectorizer.get_feature_names_out()[:5]}...") # 全ては表示しきれないので一部
print(f"TF-IDFベクトル:\n{X_processed_tfidf.toarray()}")
5. まとめと次へのステップ
本日は、AI学習ロードマップの11日目として、非構造化データであるテキストをAIモデルが扱えるようにするための「前処理と数値化」の重要性を学びました。
- テキストデータには、多様な表現、ノイズ、活用形、曖昧さといった固有の課題があるため、適切な前処理が不可欠です。
- 主要な前処理テクニックとして、クリーニング(小文字化、URL/記号除去)、トークン化、ストップワード除去、ステミング/レンマ化を習得しました。
- テキストを数値に変換する主要な手法として、BoW(Bag of Words)、TF-IDF、そして 単語埋め込み(Word Embedding) の概念を理解しました。
これらの技術は、テキスト分類(感情分析、スパム検出)、情報検索、質問応答システムなど、あらゆる自然言語処理アプリケーションの基盤となります。
明日からは、推薦システムやアソシエーションルールマイニングといった、ビジネス応用で非常に重要な「教師なし学習」の応用モデルに焦点を当てていきます。テキストデータ処理の知識は、これらのモデルがユーザーの行動パターンを分析する際にも役立つでしょう。
それでは、また明日!