ゼロからはじめるPython 第127回 形態素解析をマスターして文書校正ツールを作ってみよう

2025年5月25日(日)8時51分 マイナビニュース


形態素解析とは、文章を単語などの最小の意味単位に分解し、それぞれの品詞などを分析する技術だ。Pythonを使えば、手軽に形態素解析が可能になる。形態素解析ができると、さまざまな自然言語処理が行える。ここでは、形態素解析を利用して簡単な文書校正ツールを作成しよう。
○形態素解析とは?
形態素解析を行うと文章を最小の意味単位である「形態素」に分割できる。例えば、「この猫の名前はタマです」という文章は、「この / 猫 / の / 名前 / は / タマ / です」のように分割される。それぞれの形態素に対して、「名詞」や「動詞」といった品詞の解析も行われる。
具体的には次のようになる。形態素ごとに分割されるだけでなく、文章中で語がどのような役割を果たしているのかまで把握できる。
○Pythonでどうやって形態素解析をするの?
Pythonの「janome」というパッケージを使えば、手軽に形態素解析を実行できる。janomeをインストールするには、ターミナル(WindowsはPowerShell、macOSはターミナル.app)を起動し、次のコマンドを実行する。
$ pip install janome
次のようなプログラムを作成することで、形態素解析を実行できる。プログラムは「example.py」という名前で保存しよう。
from janome.tokenizer import Tokenizer
text = "この猫の名前はタマです"
# Tokenizerを生成する --- (※1)
tokenizer = Tokenizer()
# 形態素解析してトークンに分割する --- (※2)
tokens = tokenizer.tokenize(text)
# 結果を取りだして表示 --- (※3)
for token in tokens:
surface = token.surface
pos = token.part_of_speech
print(f"| {surface} | {pos} |")
上記のプログラムを実行するには、下記のようなコマンドを実行する。プログラムを実行すると、「この猫の名前はタマです」という文章を形態素に分割して結果を表示する。
$ python example.py
| この | 連体詞,*,*,* |
| 猫 | 名詞,一般,*,* |
| の | 助詞,連体化,*,* |
| 名前 | 名詞,一般,*,* |
| は | 助詞,係助詞,*,* |
| タマ | 名詞,固有名詞,人名,名 |
| です | 助動詞,*,*,* |
プログラムの各部分を確認してみよう。(※1)では、janomeのTokenizerオブジェクトを生成する。(※2)では、tokenizeメソッドで形態素への分割と語の解析を行う。(※3)では、for ... in ... 文を使って、分割された形態素を画面に出力する。
○文章の校正プログラムを作ってみよう
形態素解析を利用して、簡単な文章の校正ツールを作ってみよう。本格的なものを作ると、あっというまに長くなってしまうため、接続詞の繰り返しチェックと、一文の長さをチェックする機能だけを作って90行ちょっとのプログラムを作ってみた。
一気に掲載するには長いため、プログラム全体はこちらのGist( https://gist.github.com/kujirahand/cb811206070506fc4cecee6ff4a62cd3 )にアップしたので後から全体を確認してみよう。
最初に、文章の問題を発見する関数check_textの定義を見てみよう。
import sys
from janome.tokenizer import Tokenizer
# 形態素解析のためのTokenizerのインスタンスを生成 --- (※1)
tokenizer = Tokenizer()
# 接続詞の一覧 --- (※2)
setuzokusi = set()
def check_text(text):
"""テキストをチェックする"""
errors = []
# 改行コードを統一する --- (※3)
text = text.replace("\r
", "
").replace("\r", "
")
text = text.replace("
", " ¶ ") # 行数をカウントするため「 ¶」を挿入
text += " ¶ "
# 形態素解析してトークンに分割 --- (※4)
tokens = []
for t in tokenizer.tokenize(text):
pos = t.part_of_speech
# 接続詞を抽出 --- (※5)
if pos.startswith(("接続詞")):
setuzokusi.add(t.surface)
tokens.append(t.surface)
# 一文の長さをチェックする --- (※6)
errors += check_length(tokens)
# 接続詞の繰り返しを検出 --- (※7)
print("[INFO] 接続詞の繰り返しをチェックしています...", setuzokusi)
for word in setuzokusi:
errors += check_repeat(tokens, word, 8)
errors.sort(key=lambda x: int(x.split(":")[0])) # 行番号でソート
if len(errors) == 0:
print("[OK] 特に問題は見つかりませんでした。")
else:
for error in errors:
print(f"[ERROR] {error}")
return errors
上記のプログラムを確認しよう。(※1)では、形態素解析を行うために、janomeのTokenizerのインスタンスを作成する。(※2)では、接続詞の一覧を覚えておくためのset型を初期化する。(※3)では、改行コードを統一した後、エラーレポートのために改行コードを便宜的に記号「¶」に置き換えている。
(※4)では形態素解析を行って、文章を形態素に分割する。そして、文章全体を確認して(※5)で接続詞を検出し、変数setuzokusiに追加する。
(※6)では、一文の長さをチェックするために、この後定義した関数check_lengthを呼び出す。そして、(※7)では、関数check_repeatを呼び出して、接続詞の繰り返しがないかをチェックする。
○長い一行を検出してエラーにする関数を作ろう
それでは、次に一行の長さをチェックする関数check_lengthを確認してみよう。この関数では句点までの文字数を調べて、100文字以上だった場合に、エラーを返すという仕組みにしている。
def check_length(tokens: list[str], max_length: int = 100):
"""一文の長さをチェックする"""
errors = []
s = ""
line_no = 1
for t in tokens:
if t in ["¶", "。"]:
s_len = len(s)
if s_len >max_length:
errors.append(f"{line_no}: 一文が長すぎます({s_len}文字)
" + \
f" - {s[:30]}…")
s = ""
if t == "¶":
line_no += 1
continue
s += t
return errors
ここでは、一文の長さを調べるために、文章の最初から末尾まで、改行や句点「。」を探すという処理にしている。文の区切りを見つけたら、カウンタ用の変数sをリセットするという処理になっている。そして、一文が100字以上の場合にエラーを出している。
○連続する接続詞を検出する関数を作ろう
続いて、連続で出現する接続詞を調べる関数check_repeatを確認しよう。この関数では、8行以内に同じ接続詞が登場したらエラーを返すという仕組みにした。
def check_repeat(tokens: list[str], word: str, limit: int = 8):
"""繰り返しをチェックする"""
# 近くに同じ接続詞が登場しないかチェック
last_line = 0
last_near = ""
line_no = 1
errors = []
for i, t in enumerate(tokens):
if t == "¶":
line_no += 1
continue
if t != word:
continue
if last_line == 0:
last_line = line_no
elif line_no - last_line <= limit:
near = "".join(tokens[i:i+10])
errors.append(
f"{line_no}:「{word}」が連続しています
" + \
f" -{last_line:4}行目: {last_near}…
" + \
f" -{line_no:4}行目: {near}…")
last_line = line_no
last_near = "".join(tokens[i: i+10])
return errors
形態素解析で分割した単語(tokens)を、上から順に調べていって該当する接続詞が出て来たら、その位置をメモっておく。そして、その後limit行以内に同じ接続詞を見つけたら、エラーを表示するという処理になっている。
続く部分では、ファイルを読み出して、チェックするという処理になっている。
def check_file(file_path):
"""ファイルをチェックする"""
with open(file_path, 'r', encoding='utf-8') as file:
text = file.read()
# テキストをチェックする
return check_text(text)
if __name__ == "__main__":
if len(sys.argv) == 2:
# コマンドライン引数でファイル名が指定された場合
check_file(sys.argv[1])
else:
print("使い方: python proofreading.py [ファイル名]")
○プログラムを実行してみよう
ここまで紹介したプログラムを「proofreading.py」という名前で保存したら、ターミナルで次のようなコマンドを実行することで、テキストファイルの問題を洗い出すことができる。例えば以下の実行例では「test.txt」という文章をチェックする。
python proofreading.py test.txt
本連載の第126回の原稿にわざと問題を追加して「test.txt」に保存してから試してみた。正しく問題を報告できた。
○GUIツールを作って利便性アップ
.

マイナビニュース

「解析」をもっと詳しく

「解析」のニュース

「解析」のニュース

トピックス

x
BIGLOBE
トップへ