1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエンジニアへの道:データ操作からAIをマスターする30日間ロードマップ - 11日目

Posted at

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) の概念を理解しました。

これらの技術は、テキスト分類(感情分析、スパム検出)、情報検索、質問応答システムなど、あらゆる自然言語処理アプリケーションの基盤となります。

明日からは、推薦システムやアソシエーションルールマイニングといった、ビジネス応用で非常に重要な「教師なし学習」の応用モデルに焦点を当てていきます。テキストデータ処理の知識は、これらのモデルがユーザーの行動パターンを分析する際にも役立つでしょう。

それでは、また明日!


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?