Skip to content

10. 自然言語処理 ①:テキストの前処理

第 10 回では、自然言語処理(NLP: Natural Language Processing)におけるテキストの前処理について学びます。画像処理と同様に、テキストデータも機械学習で扱うためには適切な前処理が必要です。

自然言語処理は、人間が使う言葉(自然言語)をコンピュータで処理する技術です。機械翻訳、感情分析、文章生成など、私たちの身の回りで広く活用されています。本講義では、日本語テキストデータを機械学習モデルで扱うための基礎的な前処理手法を学んでいきます。

10.1 自然言語処理とは

10.1.1 自然言語処理の定義

自然言語処理(NLP: Natural Language Processing) は、人間が日常的に使用する言葉(自然言語)をコンピュータで処理・理解・生成する技術です。

自然言語処理の応用例:

  • 機械翻訳: Google 翻訳、DeepL など
  • 音声アシスタント: Siri、Alexa、Google Assistant
  • 感情分析: SNS の投稿から感情を分析
  • チャットボット: 自動応答システム
  • 文章要約: ニュース記事の自動要約
  • スパム判定: メールのスパム検出

10.1.2 自然言語処理の難しさ

自然言語は、画像データと比べて以下のような難しさがあります。

  • 曖昧性: 「銀行」は金融機関か川岸か
  • 文脈依存性: 「彼は走った」は過去のいつのことか
  • 言語の多様性: 方言、スラング、新語、造語
  • 背景知識が必要: 常識、文化的背景、皮肉、比喩

10.1.3 自然言語処理のワークフロー

自然言語処理の典型的なワークフローは以下の通りです。

  1. 生テキスト
  2. テキストクリーニング
  3. 形態素解析
  4. 正規化
  5. ベクトル化
  6. 機械学習モデル
  7. 予測結果

本講義では、主に「テキストクリーニング」から「正規化」までの前処理を学びます。

10.2 テキストの読み込みと基本操作

Python には、文字列を操作するための便利な関数やメソッドがさまざま用意されています。以下ではその代表例を紹介します。

10.2.1 文字列の結合

文字列の結合を行うには、join メソッドを用います。[区切り文字].join([文字列のリスト]) のようにすることで、文字列のリスト(または他のイテラブル)を区切り文字で連結し、一つの文字列にすることができます。

words = ["Python", "は", "強力です"]
print("".join(words))
print(" ".join(words))
Output
Pythonは強力です
Python は 強力です

10.2.2 文字列の分割

文字列を分割したい場合は、split メソッドを用います。[文字列].split([区切り文字]) のようにすることで、文字列を指定した区切り文字で分割し、リストにすることができます。区切り文字を省略した場合は、スペースで分割されます。

text = "Python is simple, powerful, and widely used."
print(text.split(","))
print(text.split())
Output
['Python is simple', ' powerful', ' and widely used.']
['Python', 'is', 'simple,', 'powerful,', 'and', 'widely', 'used.']

10.2.3 文字列の置換

特定の文字列を別の文字列に置換したり、除去したりしたい場合は、replace メソッドを用います。[文字列].replace([置換前の文字列], [置換後の文字列]) のようにすることで、文字列の置換を行うことができます。

text = "Pythonは楽しいです。"
print(text.replace("Python", "プログラミング"))
Output
プログラミングは楽しいです。

replace による文字列の除去

置換後の文字列を空文字列 ("") にすることで、指定した文字列を取り除くことができます。

10.2.4 ファイルからのテキスト読み込み

テキストファイル (.txt) を開いてデータを読み込むには、with openread() メソッドを用います。

# テキストファイルの読み込み
with open("sample.txt", "r", encoding="utf-8") as f:
    text = f.read()

print(text[:100])  # 最初の100文字を表示

ここで "r" は、ファイルを「読み取り専用」モード (read mode) で開こうとしていることを意味します。

10.2.5 日本語テキストの特性

日本語テキストは、英語とは異なる特性があります。

# 日本語テキストの例
japanese_text = "これは日本語のサンプルテキストです。機械学習を学びます。"

# 文字数
print(f"文字数: {len(japanese_text)}")

# 日本語の分割(単純なsplitでは不十分)
words = japanese_text.split()
print(f"split結果: {words}")  # スペースで区切られていないので1つの文字列

# 文字単位で処理
chars = list(japanese_text)
print(f"文字リスト(最初の10文字): {chars[:10]}")
Output
文字数: 30
split結果: ['これは日本語のサンプルテキストです。機械学習を学びます。']
文字リスト(最初の10文字): ['こ', 'れ', 'は', '日', '本', '語', 'の', 'サ', 'ン', 'プ']

日本語の特性

  • 単語の区切りがない: 英語のようにスペースで単語が区切られていない
  • 形態素解析が必要: 文を単語に分割するには専用のツールが必要
  • 文字種が複数: ひらがな、カタカナ、漢字、英数字が混在

10.3 テキストクリーニング

10.3.1 クリーニングの重要性

生のテキストデータには、不要な情報(記号、空白、URL など)が含まれていることがあります。これらを除去することで、機械学習モデルの性能を向上させます。このような前処理はクリーニング(またはクレンジング)と呼ばれます。

10.3.2 replace によるクリーニング

最も簡単なクリーニング方法は、replace メソッドを使った文字列の置換です。

text = "これは【重要】なお知らせです。詳細は★こちら★をご覧ください。"

# 不要な記号を削除
text = text.replace("【", "")
text = text.replace("】", "")
text = text.replace("★", "")

print(text)
Output
これは重要なお知らせです。詳細はこちらをご覧ください。

replace メソッドでは、置換後の文字列を空文字 "" にすることで、特定の文字列を削除できます。

10.3.3 正規表現によるクリーニング

複数のパターンを一度に処理したい場合は、Python の re モジュールを使うと便利です。re.sub() 関数を使うと、正規表現にマッチする部分を置換できます。

import re

re.sub(パターン, 置換後の文字列, 対象の文字列)

正規表現の基本パターン

正規表現(Regular Expression)は、文字列のパターンを表現するための記法です。代表的なパターンを以下に示します。

パターン 意味
. 任意の1文字 a.c → "abc", "a1c" など
* 直前の文字の0回以上の繰り返し ab*c → "ac", "abc", "abbc" など
+ 直前の文字の1回以上の繰り返し ab+c → "abc", "abbc" など("ac" は不可)
? 直前の文字の0回または1回 ab?c → "ac", "abc"
\d 数字(0-9) \d+ → "123", "42" など
\s 空白文字(スペース、タブ、改行) \s+ → 連続する空白
\S 空白以外の文字 \S+ → 空白以外の連続する文字
\w 単語を構成する文字(英数字とアンダースコア) \w+ → "hello", "test_1" など
[...] 角括弧内のいずれか1文字 [abc] → "a", "b", "c" のいずれか
[^...] 角括弧内以外の1文字 [^abc] → "a", "b", "c" 以外
{n} 直前の文字のn回の繰り返し \d{4} → 4桁の数字
{n,m} 直前の文字のn回以上m回以下の繰り返し \d{2,4} → 2〜4桁の数字
^ 文字列の先頭 ^Hello → 先頭が "Hello"
$ 文字列の末尾 end$ → 末尾が "end"

正規表現を使ったクリーニングの例

import re

# 日本語テキストのサンプル
text = """
これは日本語のサンプルです。
メールアドレス: test@example.com
URL: https://example.jp
電話番号: 03-1234-5678
"""

print("元のテキスト:")
print(text)

# URL の削除
text = re.sub(r'https?://\S+', '', text)

# メールアドレスの削除
text = re.sub(r'\S+@\S+', '', text)

# 電話番号の削除(日本の形式)
text = re.sub(r'\d{2,4}-\d{2,4}-\d{4}', '', text)

# 余分な空白・改行の削除
text = re.sub(r'\s+', ' ', text).strip()

print("クリーニング後:")
print(text)
Output
元のテキスト:

これは日本語のサンプルです。
メールアドレス: test@example.com
URL: https://example.jp
電話番号: 03-1234-5678


クリーニング後:
これは日本語のサンプルです。 メールアドレス: URL: 電話番号:

上記のコードで使用した正規表現パターンを解説します。

パターン 解説
https?://\S+ http または httpss? で s が0回か1回)で始まり、:// の後に空白以外の文字(\S+)が続く
\S+@\S+ @ の前後に空白以外の文字が1文字以上ある(メールアドレスの簡易パターン)
\d{2,4}-\d{2,4}-\d{4} 2〜4桁の数字、ハイフン、2〜4桁の数字、ハイフン、4桁の数字(電話番号のパターン)
\s+ 1つ以上の連続する空白文字(スペース、タブ、改行など)

raw 文字列(r'')について

正規表現パターンの前に r を付けると、バックスラッシュをエスケープせずにそのまま使えます。例えば、r'\d+''\\d+' と同じ意味になります。正規表現ではバックスラッシュを多用するため、r'' を使うのが一般的です。

10.4 形態素解析

10.4.1 形態素解析とは

形態素解析 とは、文を意味を持つ最小単位の単語(形態素)に分割する操作のことです。日本語の文章に対して自然言語処理を適用する場合、文章を単語単位に分割する分かち書き(形態素解析)が必要になります。

例えば、「吾輩は猫である」という文は、「吾輩」「は」「猫」「で」「ある」に分割されます。

10.4.2 MeCab のインストール

形態素解析を実際に実行するために、MeCab と辞書データをインストールします。Google Colaboratory を使用している場合は、以下のコマンドを順に実行してください。

# MeCab と辞書データのインストール(Google Colab)
!apt-get -y update
!apt-get -y install mecab libmecab-dev mecab-ipadic-utf8 fonts-ipafont-gothic
!pip install mecab-python3
!ln -s /etc/mecabrc /usr/local/etc/mecabrc

ここでは日本語の形態素解析用の辞書である mecab-ipadic-utf8(IPA 辞書)をインストールしています。

インストール後、以下のようにインポートすることで、MeCab を使えるようになります。

import MeCab

CHIKUWA Editor における形態素解析器と辞書データについて

CHIKUWA Editor には、形態素解析器として「MeCab」が、日本語辞書として「mecab-ipadic-utf8」が標準でインストールされており、これらを用いて形態素解析の練習ができるようになっています。

10.4.3 MeCab による形態素解析の基礎

MeCab を用いた形態素解析の基本的な流れを見ていきましょう。

まずは、MeCab の解析器(Tagger)のインスタンスを作成します。

tagger = MeCab.Tagger()

そして、Tagger のインスタンスから parse() メソッドを呼び出すことで、形態素解析が実行されます。

MeCab による形態素解析
import MeCab

# MeCab のインスタンス生成
tagger = MeCab.Tagger()

# 解析するテキスト
text = "すもももももももものうち"

# 形態素解析を実行し、結果を表示
result = tagger.parse(text)
print(result)
Output
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も   助詞,係助詞,*,*,*,*,も,モ,モ
もも  名詞,一般,*,*,*,*,もも,モモ,モモ
も   助詞,係助詞,*,*,*,*,も,モ,モ
もも  名詞,一般,*,*,*,*,もも,モモ,モモ
の   助詞,連体化,*,*,*,*,の,ノ,ノ
うち  名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

10.4.4 形態素解析のオプション

MeCab では、MeCab.Tagger() にオプションの文字列を渡すことで、形態素解析の出力結果を細かく制御することができます。

分かち書き (-Owakati)

-Owakati を指定することで、単語ごとにスペースで区切った単純な分かち書きの結果を出力することができます。

tagger = MeCab.Tagger("-Owakati")
result = tagger.parse("すもももももももものうち")
print(result)
Output
すもも も もも も もも の うち

単語の読み (-Oyomi)

-Oyomi を指定することで、単語の読み(カタカナ)だけを出力することができます。

tagger = MeCab.Tagger("-Oyomi")
result = tagger.parse("すもももももももものうち")
print(result)
Output
スモモモモモモモモノウチ

辞書による出力形式の違い

使用する辞書によって、feature の形式や使用可能なオプションが異なる場合があります。本講義では ipadic を使用しています。

10.4.5 特定の品詞の抽出

形態素解析ができたら、特定の品詞を抽出してみましょう。文書の解析でよく利用される重要な品詞は、「名詞」「形容詞」「動詞」です。

まずは、parseToNode() メソッドを用いて、形態素解析の結果をノードに変換します。ノードとは、数珠つなぎの要素のようなイメージで、next によって隣り合うノードを順番に見て回ることができる仕組みを指します。

node = tagger.parseToNode(text)

ノードの surface 属性には単語が、feature 属性には単語の特徴に関する情報が入っています。形態素解析をした後、単語のリストと品詞のリストを作成するプログラムは以下のようになります。

単語と品詞のリストを作成
import MeCab

# MeCab のインスタンス生成
tagger = MeCab.Tagger()

# 解析するテキスト
text = "すもももももももものうち"

# 形態素解析を実行し、ノードに変換
node = tagger.parseToNode(text)

# 単語と品詞を格納するためのリスト
words = []
types = []

# ノードがなくなるまで解析結果を表示
while node:
    if node.surface:  # surface 属性(単語)が存在する場合
        words.append(node.surface)
        types.append(node.feature.split(",")[0])
    node = node.next  # 次のノードに移動

print("単語:", words)
print("品詞:", types)
Output
単語: ['すもも', 'も', 'もも', 'も', 'もも', 'の', 'うち']
品詞: ['名詞', '助詞', '名詞', '助詞', '名詞', '助詞', '名詞']

node.feature の中には、"名詞,一般,*,*,*,*,すもも,スモモ,スモモ" のように、複数の情報がまとめて入っています。ここから "名詞" という情報だけ抽出したい場合には、上のコードで示しているように、split メソッドでテキストをカンマで区切り、生成されたリストの先頭を参照すれば良いというわけです。

品詞が「名詞」の単語のみを抽出してリスト化するには、以下のようにループの中で if 文を使用します。

名詞のみを抽出
import MeCab

# MeCab のインスタンス生成
tagger = MeCab.Tagger()

# 解析するテキスト
text = "すもももももももものうち"

# 形態素解析を実行し、ノードに変換
node = tagger.parseToNode(text)

# 単語を格納するためのリスト
nouns = []

# ノードがなくなるまで解析結果を表示
while node:
    pos = node.feature.split(",")[0]  # 品詞を取得
    if pos == "名詞":  # 品詞が「名詞」であった場合
        nouns.append(node.surface)
    node = node.next  # 次のノードに移動

print("名詞:", nouns)
Output
名詞: ['すもも', 'もも', 'もも', 'うち']

10.5 テキスト正規化

10.5.1 正規化とは

正規化(Normalization) は、テキストを統一された形式に変換する処理です。日本語のテキスト処理では、主に以下の正規化が行われます。

  • ストップワード除去: 「は」「を」「が」などを削除
  • 品詞によるフィルタリング: 名詞・動詞・形容詞のみ抽出
  • 表記の統一: 全角・半角の統一

10.5.2 ストップワード除去

ストップワード(Stop Words) は、頻繁に出現するが意味的には重要でない単語です。日本語では「は」「を」「が」「に」「で」などの助詞がこれに該当します。

import MeCab

# 日本語のストップワード(例)
japanese_stop_words = ['は', 'を', 'が', 'に', 'で', 'と', 'の', 'も', 'や', 'など', 'て', 'た', 'だ', 'する', 'ある', 'いる']

# MeCab のインスタンス生成(分かち書き)
tagger = MeCab.Tagger("-Owakati")

text = "私は機械学習を勉強しています。"

# 形態素解析を実行
result = tagger.parse(text)
tokens = result.strip().split()
print(f"元のトークン: {tokens}")

# ストップワード除去
filtered_tokens = [word for word in tokens if word not in japanese_stop_words]
print(f"フィルタ後: {filtered_tokens}")
Output
元のトークン: ['私', 'は', '機械', '学習', 'を', '勉強', 'し', 'て', 'い', 'ます', '。']
フィルタ後: ['私', '機械', '学習', '勉強', 'し', 'い', 'ます', '。']

10.5.3 品詞によるフィルタリング

より効果的な方法として、品詞情報を使って必要な単語のみを抽出する方法があります。

import MeCab

tagger = MeCab.Tagger()
text = "私は機械学習を勉強しています。自然言語処理は面白いです。"

node = tagger.parseToNode(text)

# 名詞、動詞、形容詞のみを抽出
content_words = []
while node:
    pos = node.feature.split(",")[0]
    if pos in ["名詞", "動詞", "形容詞"]:
        content_words.append(node.surface)
    node = node.next

print("内容語:", content_words)
Output
内容語: ['私', '機械', '学習', '勉強', 'し', '自然', '言語', '処理', '面白い']

10.6 テキスト前処理のパイプライン

10.6.1 前処理パイプライン

これまで学んだ処理を組み合わせて、前処理パイプラインを作成しましょう。

import re
import MeCab

def preprocess_japanese_text(text):
    """日本語テキストの前処理パイプライン"""

    # 1. URLの削除
    text = re.sub(r'https?://\S+', '', text)

    # 2. メールアドレスの削除
    text = re.sub(r'\S+@\S+', '', text)

    # 3. 余分な空白・改行の削除
    text = re.sub(r'\s+', '', text)

    # 4. 形態素解析
    tagger = MeCab.Tagger()
    node = tagger.parseToNode(text)

    tokens = []
    while node:
        # 品詞情報を取得
        features = node.feature.split(",")
        pos = features[0]  # 品詞
        surface = node.surface  # 表層形

        # 名詞、動詞、形容詞のみを抽出
        if pos in ['名詞', '動詞', '形容詞']:
            tokens.append(surface)

        node = node.next

    return tokens

# テスト
sample_text = """
私は機械学習を勉強しています。
自然言語処理は非常に面白いです。
詳細は https://example.jp をご覧ください。
"""

print("元のテキスト:")
print(sample_text)

processed_tokens = preprocess_japanese_text(sample_text)

print("前処理後のトークン:")
print(processed_tokens)

print(f"\nトークン数: {len(processed_tokens)}")
Output
元のテキスト:

私は機械学習を勉強しています。
自然言語処理は非常に面白いです。
詳細は https://example.jp をご覧ください。

前処理後のトークン:
['私', '機械', '学習', '勉強', 'し', 'い', '自然', '言語', '処理', '非常', '面白い', '詳細', 'ご覧', 'ください']

トークン数: 14

10.6.2 複数文書の前処理

複数の文書を一度に処理する場合の例です。

import MeCab

def extract_nouns(text, tagger):
    """テキストから名詞を抽出する関数"""
    node = tagger.parseToNode(text)
    nouns = []
    while node:
        pos = node.feature.split(",")[0]
        if pos == "名詞":
            nouns.append(node.surface)
        node = node.next
    return nouns

# 複数の文書
documents = [
    "吾輩は猫である。名前はまだ無い。",
    "どこで生れたかとんと見当がつかぬ。",
    "何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。"
]

# MeCab のインスタンス生成
tagger = MeCab.Tagger()

# 各文書から名詞を抽出
for i, doc in enumerate(documents, 1):
    nouns = extract_nouns(doc, tagger)
    print(f"文書{i}: {nouns}")
Output
文書1: ['吾輩', '猫', '名前']
文書2: ['見当']
文書3: ['何', '所', '事', '記憶']

10.7 演習問題

演習 10-1: 形態素解析の実践

演習 10-1

以下の日本語テキストに対して、MeCab を使った形態素解析を実行してください。

text = """
今日は天気が良いので、公園に散歩に行きました。
桜の花がとても綺麗でした。
写真を撮って、友達に送りました。
"""

タスク:

  1. 形態素解析を実行し、全ての形態素を表示
  2. 各形態素の品詞を表示
  3. 名詞のみを抽出してリストにする
  4. 動詞のみを抽出してリストにする

演習 10-2: 前処理パイプラインの作成

演習 10-2

以下のテキストに対して、完全な前処理パイプラインを適用してください。

text = """
機械学習の勉強的なことをしてみませんか?
詳細は https://ml2025.ibadai.com まで
お問い合わせは info@example.jp までお願いします。
このメッセージはあなたの人生にとって最も重要な案内のうちの一つです!
"""

タスク:

  1. URLとメールアドレスを削除
  2. 形態素解析を実行
  3. 名詞・動詞・形容詞のみを抽出
  4. 非自立語・接尾語を除外
  5. 結果を表示

10.9 まとめ

本章では、自然言語処理におけるテキストの前処理について学びました。

第 10 回のまとめ

自然言語処理の基礎

  • 人間の言葉をコンピュータで処理する技術
  • 機械翻訳、感情分析、チャットボットなど幅広い応用
  • 言語の曖昧性、文脈依存性が課題

テキストクリーニング

  • URL、メールアドレス、記号の削除
  • 余分な空白の削除
  • 正規表現を使った柔軟な処理

形態素解析(MeCab)

  • 文を形態素(単語)に分割
  • parse(): 形態素解析の実行
  • parseToNode(): ノード単位での詳細情報取得
  • -Owakati: 分かち書き出力

テキスト正規化

  • ストップワード除去: 頻出だが重要でない単語を削除
  • 品詞フィルタリング: 名詞・動詞・形容詞のみ抽出

前処理パイプライン

  • 複数の処理を順番に適用
  • データの品質向上に不可欠
  • タスクに応じて適切な処理を選択

次回(第 11 回)は、文章のベクトル化について学びます。