テキストの前処理

自然言語処理

今回は自然言語処理の中でも重要な前処理について解説しています。実行環境はGoogleのColaboratoryですので、挑戦しやすいかと思います。

現在、機械学習の勉強中ではありますが、備忘録として誰かのお役に立てればと思い書いています。

なお、各セクションのコードは例としてChatGPTなどの生成系AIを用いて書いています。最後にすべての処理をまとめたコードを書いていますが一つ一つの工程を理解したい方は最後の組み合わせた部分はぜひご自身で実装してみてください。

形態素解析を行う(トークン化)

形態素解析は、自然言語処理(NLP)において非常に重要な役割を果たします。以下にその役割を簡潔にまとめます:

  1. 単語の分割: テキストを意味のある最小単位(形態素)に分割します。例えば、「私は本を読みました」を「私」「は」「本」「を」「読みました」に分けます。
  2. 品詞の識別: 各形態素に対して品詞(名詞、動詞、助詞など)を付与します。これにより、文法的な構造を理解しやすくなります。
  3. 前処理: 形態素解析は、機械翻訳、テキスト分類、感情分析などの後続のNLPタスクのための前処理ステップとして機能します。
  4. 意味の理解: テキストの意味や文脈をより深く理解するための基礎を提供します。

形態素解析を行うことで、コンピュータはテキストを単なる文字列としてではなく、文法的な構造を持ったデータとして処理できるようになります。

MeCabを使って形態素解析を行う

MeCabのディフォルトの辞書では新しい言葉を正しく解析することができないのでここでは、「NEologd」という辞書をダウンロードして使用しています。

イメージとしては辞書が古いから更新して新しい辞書を参照しながら解析してくれているような感じです。

# MeCabのインストール
!apt-get install -y mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file

# MeCab Pythonバインディングのインストール
!pip install mecab-python3

# NEologdのインストールに必要なパッケージ
!apt-get install -y sudo

# NEologdのダウンロードとインストール
!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
!echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd
# NEologdのダウンロード先を出力
!echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
import MeCab
import os

# NEologdの辞書パスを取得
neologd_path = '/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd'

# 環境変数 MECABRC を設定 (mecabrcファイルへのパスを設定)
os.environ['MECABRC'] = "/etc/mecabrc" 

# MeCabのセットアップ
mecab = MeCab.Tagger(f"-d {neologd_path}")

# テキストを解析
text = "私は最近鬼滅の刃を映画館で観てきました"
print(mecab.parse(text))

Colaboratory上で実行しているかたはneologd_path がみんな同じだと思いますが、ローカル環境で実行している方は二つ目の「NEologdのダウンロード先を出力」で確認したPATHを貼り付けてください。

上記のコードを実行すると下記のような結果が得られると思います。

私	名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
最近	名詞,副詞可能,*,*,*,*,最近,サイキン,サイキン
鬼滅の刃	名詞,固有名詞,一般,*,*,*,鬼滅の刃,キメツノヤイバ,キメツノヤイバ
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
映画館	名詞,一般,*,*,*,*,映画館,エイガカン,エイガカン
で	助詞,格助詞,一般,*,*,*,で,デ,デ
観	動詞,自立,*,*,一段,連用形,観る,ミ,ミ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
き	動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
まし	助動詞,*,*,*,特殊・マス,連用形,ます,マシ,マシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
EOS

ちなみにNEologdを使用しないと下記のようになってしまいます。

# 辞書を使用しない場合
mecab = MeCab.Tagger()
私	名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
最近	名詞,副詞可能,*,*,*,*,最近,サイキン,サイキン
鬼	名詞,一般,*,*,*,*,鬼,オニ,オニ
滅	名詞,一般,*,*,*,*,滅,メツ,メツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
刃	名詞,一般,*,*,*,*,刃,ハ,ハ
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
映画	名詞,一般,*,*,*,*,映画,エイガ,エイガ
館	名詞,接尾,一般,*,*,*,館,カン,カン
で	助詞,格助詞,一般,*,*,*,で,デ,デ
観	動詞,自立,*,*,一段,連用形,観る,ミ,ミ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
き	動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
まし	助動詞,*,*,*,特殊・マス,連用形,ます,マシ,マシ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
EOS

「鬼滅の刃」や、「映画館」などをひとつの単語として認識できていないことがわかります。

テキストの正規化

テキストの正規化は、自然言語処理において非常に重要なステップです。以下に、具体的な手法とその目的を詳しく説明します:

  1. 全角・半角の統一
    目的: 全角と半角の違いを無視して、一貫性を持たせるため。 例: “アルファベット” → “アルファベット”
  2. カタカナ・ひらがなの統一
    目的: カタカナとひらがなの違いを無視して、一貫性を持たせるため。 例: “カタカナ” → “かたかな”
  3. 小文字化
    目的: 大文字と小文字の違いを無視して、一貫性を持たせるため。 例: “ABC” → “abc”
  4. 句読点や特殊文字の削除
    目的: テキストのノイズを減らし、解析を容易にするため。 例: “こんにちは、世界!” → “こんにちは世界”
  5. スペースの正規化
    目的: 不要なスペースを削除し、テキストの一貫性を保つため。 例: “こんにちは 世界” → “こんにちは 世界”
  6. 数字の正規化
    目的: 数字を統一された形式に変換することで、解析を容易にするため。 例: “二十四” → “24”
  7. 特定のパターンの置換
    目的: 特定のパターン(例: URL、メールアドレス)を統一されたトークンに置き換えることで、解析を容易にするため。 例: “お問い合わせは
    support@example.com まで” → “お問い合わせは EMAIL まで”
  8. アキュートアクセントやダイアクリティカルマークの削除
    目的: アルファベットの一貫性を保つため。 例: “café” → “cafe”

テキストの正規化を実装

import re
import unicodedata

def normalize_text(text):
    # 小文字化
    text = text.lower()
    
    # 全角・半角の統一
    text = unicodedata.normalize('NFKC', text)
    
    # 句読点や特殊文字の削除
    text = re.sub(r'[^\w\s]', '', text)
    
    # スペースの正規化
    text = ' '.join(text.split())
    
    # カタカナをひらがなに変換
    katakana_to_hiragana = str.maketrans(
        "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴァィゥェォヵヶッャュョ",
        "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔぁぃぅぇぉゕゖっゃゅょ"
    )
    text = text.translate(katakana_to_hiragana)

    # 数字をすべて0に変換
    text = re.sub(r'\d+', '0', text)
    
    return text

text = "2024年2月18日 こんにちは、世界!アルファベット"
normalized_text = normalize_text(text)
print(normalized_text)

下記のような実行結果が出れば正しいです。

0年0月0日 こんにちは世界あるふぁベっと

ストップワードの除去

ストップワードの除去は、自然言語処理(NLP)やテキストマイニングにおいて重要な前処理の一つです。ストップワードとは、文章中に頻繁に出現するが、解析においてはあまり意味を持たない単語のことを指します。例えば、日本語では「の」「に」「は」などが該当します。

ストップワードの除去の目的

ストップワードを除去することで、以下のようなメリットがあります:

  1. 解析の精度向上:不要な単語を除去することで、重要な情報に焦点を当てやすくなります。
  2. 計算コストの削減:データ量が減少するため、処理速度が向上します。
  3. モデルの性能向上:ノイズが減ることで、機械学習モデルの性能が向上します。

ストップワードの除去を実装

Pythonを用いたストップワードの除去の例を紹介します。日本語の場合、形態素解析を行った後にストップワードを除去します。以下は、MeCabを使用した例です:

import MeCab

# MeCabのセットアップ
mecab = MeCab.Tagger("-Owakati")

# ストップワードのリスト
stop_words = set(["の", "に", "は", "を", "た", "が", "で", "て", "と", "し", "れ", "さ", "ある", "いる"])

# テキストの例
text = "これはストップワードの除去の例です。"

# 形態素解析
words = mecab.parse(text).split()

# ストップワードの除去
filtered_sentence = [word for word in words if word not in stop_words]

print(filtered_sentence)

実行結果は下記のようになります。

['これ', 'ストップ', 'ワード', '除去', '例', 'です', '。']

ストップワードの選定方法

上記のコードでは品詞ベースでストップワードを決めていましたが実際の文章では、分野ごとにストップワードが異なる場合が多いです。そのため選定方法についても少し付け加えておきます。

  1. 頻度ベース:出現頻度が高い単語をストップワードとして選定します。
  2. 品詞ベース:特定の品詞(例:助詞、助動詞)をストップワードとして選定します。
  3. タスク特化:特定のタスクにおいて不要な単語を選定します。

すみません。文章長くなっちゃうのでいったんここのコードは省略させていただきます。それほど難しいプログラムではないのでChatGPTやCopilotに聞けば関数を書いてくれると思います!

得られた結果をリスト型でstop_words に代入していただければ大丈夫です。

前処理をひとつの関数に

さて、最後にここで紹介した前処理をひとつの関数にまとめてみます。

なお、文章の種類によっては不要な前処理もありますのでそこのコードは抜いておいてください。

例えば下記の例ではカタカナをひらがなに直してしまうとうまく形態素解析ができなかったため、そこの変換を抜いています。

import MeCab
import os
import re
import unicodedata

def analyze_text(text):

  # NEologdの辞書パスを取得
  neologd_path = '/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd'

  # 環境変数 MECABRC を設定 (mecabrcファイルへのパスを設定)
  os.environ['MECABRC'] = "/etc/mecabrc" 

  # MeCabのセットアップ
  mecab = MeCab.Tagger(f"-d {neologd_path} -Owakati")
  
  # テキストを解析
  result = mecab.parse(text).split()

  # 結果を返す
  return result

def normalize_text(text):
    # 小文字化
    text = text.lower()
    
    # 全角・半角の統一
    text = unicodedata.normalize('NFKC', text)
    
    # 句読点や特殊文字の削除
    text = re.sub(r'[^\w\s]', '', text)
    
    # スペースの正規化
    text = ' '.join(text.split())
    
    # # カタカナをひらがなに変換
    # katakana_to_hiragana = str.maketrans(
    #     "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンヴァィゥェォヵヶッャュョ",
    #     "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんゔぁぃぅぇぉゕゖっゃゅょ"
    # )
    # text = text.translate(katakana_to_hiragana)

    # 数字をすべて0に変換
    text = re.sub(r'\d+', '0', text)
    
    return text

# ストップワードの削除
def filter_stopwords(words):
    # ストップワードのリスト
    stop_words = set(["の", "に", "は", "を", "た", "が", "で", "て", "と", "し", "れ", "さ", "ある", "いる", "です"])

    # ストップワードの除去
    filtered_sentence = [word for word in words if word not in stop_words]

    return (filtered_sentence)

def preprocess_text(text):
    # 正規化
    text = normalize_text(text)

    # 形態素解析
    words = analyze_text(text)

    # ストップワードの除去
    words = filter_stopwords(words)

    return words

# テキストの例
text = "これはストップワードの除去の例です。"

# テキストの前処理
preprocessed_text = preprocess_text(text)
print(preprocessed_text)

下記のような結果が得られました。

['これ', 'ストップワード', '除去', '例']

自然言語処理における前処理の感想

この記事では、自然言語処理における前処理について解説しました。日本語は英語のように単語ごとに空白が入っていないため、形態素分析などが少し難しいそうです。

本当は前処理にベクトル化も入れようか迷いましたが、いまいちまだ理解できなかったので次の記事で解説します。

※このシリーズは下記の書籍を参考に書いています。

コメント

タイトルとURLをコピーしました