今回は,KyTeaという既成のツールキットを利用して簡単な言語処理を実装していきたいと思います。また,本記事はpython実践講座シリーズの内容になります。その他の記事は,こちらの「Python入門講座/実践講座まとめ」をご覧ください。
問題設定
今回は,キャッチーさを重視して「暗号文解析」をしてみたいと思います。例えば,下のような暗号文をあるルールに則って解読していこうと思います。
Wewianoula.(①)
今回の暗号文のルールは「英単語の頭から2文字を並べる」というものだとします。上の暗号文は,様々な解釈ができますが,一例としてこのような文章が想定できます。
We will analyze our languages.(②)
今回の目的は,①のような暗号文が与えられたときに,もっともらしい文章を生成するようなモデルを構築していきます。
kyTeaとは
KyTeaは「単語分割」「タグ付け」等の学習に特化した言語処理のツールキットです。こちらのページが本家のHPになります。基本的な使い方をまとめておきます。
インストール
本家のHPより最新版のKyTeaをダウンロードします。圧縮されていますので,以下のコマンドで解凍します。「X.X.X」にはダウンロードしたKyTeaのバージョンを入れてください。
tar -xzf kytea-X.X.X.tar.gz
続いて,解凍したファイルのディレクトリまで移動します。もちろん,自身のやりやすいディレクトリにKyTeaをコピーしてから行っても構いません。
cd kytea-X.X.X
その後,魔法のコマンドでソースコードをコンパイルします。
./configure
make
make install
もし,以下のコマンドを打ってエラーを吐かなければ,インストール成功です。
kytea --help
学習
学習のためには,コーパスが必要です。KyTeaに学習させるコーパスは,以下のような形式を取るようにします。
教師単語/アノテーション
今回は,単語の頭文字をアノテーションとして学習させていきます。
The/Th apple/ap is/is red/re.
このときの注意点として,冠詞の「a」に代表されるような1文字からなる英単語の場合は,ダミーとして「@」を付け加えることによって,全ての英単語を2文字からなる前提で話を進められるようにします。
A@/A@ teacher/te plays/pl baseball/ba.
これらのテキストファイルを利用して,Kyteaでは以下のコマンドによってモデルを学習させることができます。
train-kytea -full テキストファイルパス -model model.dat
テスト
テスト用のデータは,暗号文を作成します。
I@wetothsc.
テストをする際には,以下のコマンドを利用します。
kytea -model model.dat < テストファイルパス > 保存先のファイル名指定
データセット
今回使用するデータセットは,学習用・テスト用それぞれ研究室のコーパスを利用しました。ですので,お見せすることはできませんが,それぞれ先頭から1000文章ずつ選んで利用しました。
モデリング
パラメータ設定等は,KyTeaデフォルトの設定を利用しました。
コード解説
以下では,部分に分けてコードを解説していきたいと思います。
データ作成のパラメータ設定
train_nm = 1000
test_nm = 1000
txt_dir = "hoge.txt"
今回は,大きなコーパス”hoge.txt”から1000文章ずつを先頭から選ぶことにします。
with open(txt_dir) as f:
output = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
if len(list(w)) == 1:
ab_w_list.append(''.join(list(w)[:2]) + '@/' + w + "@")
else:
ab_w_list.append(''.join(list(w)[:2]) + '/' + w)
output.append(' '.join(ab_w_list))
各単語ごとに「1文字であれば@をつけ」,頭の2文字をアノテーションとして後ろに付け加えます。
with open(txt_dir) as f:
output2 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
if len(list(w)) == 1:
ab_w_list.append(''.join(list(w)[:1]) + "@")
else:
ab_w_list.append(''.join(list(w)[:2]))
output3.append(' '.join(ab_w_list))
同様のことをします。今回は,アノテーションなしのテストデータ(暗号文)を作成します。
for i in range(len(output)-1):
if ("&" in output[i]) or ("//" in output[i]):
output[i] = 0
for i in range(len(output3)-1):
if ("&" in output3[i]) or ("//" in output3[i]):
output3[i] = 0
output = [output[i] for i in range(len(output)) if output[i] != 0]
output3 = [output3[i] for i in range(len(output3)) if output3[i] != 0]
注意点として,KyTeaでは「&」や「/」を含む文章をうまく処理できないため,あらかじめ除いておく必要があります。
training_data = output[:train_nm]
training_test_data = output3[:train_nm]
test_data = output[train_nm:train_nm + test_nm]
test_data_original = output3[train_nm:train_nm + test_nm]
冒頭に設定したパラメータの文だけスライスして取ってきます。
training_data = '\n'.join(training_data)
training_test_data = '\n'.join(training_test_data)
test_data = '\n'.join(test_data)
test_data_original = '\n'.join(test_data_original)
リストを1つの文字列型として合体させます。
file = open('training_data.txt', 'w')
file.write(training_data)
file.close
file = open('training_test_data.txt', 'w')
file.write(training_test_data)
file.close
file = open('test_data.txt', 'w')
file.write(test_data)
file.close
file = open('test_data_original.txt', 'w')
file.write(test_data_original)
file.close
作成したデータを,それぞれテキストファイルとして保存します。
モデルの学習
train-kytea -full training_data.txt -model model.dat
モデルを学習させます。フルアノテーションできていますので,「-full」を指定します。
モデルのテスト
kytea -model model.dat -nows -notag 2 -notag 3 < training_test_data.txt > result_original.full
先ほど学習したモデルを利用して,実際に解析を行います。「-nows」を指定することで単語分割を行わないように指定します。「-notag 2」「-notag 3」を指定することで,無駄なタグ推定を行わないように指定します。
txt_dir1 = "result_original.full"
txt_dir2 = "training_data.txt"
txt_dir3 = "result.full"
txt_dir4 = "test_data.txt"
with open(txt_dir1) as f:
result = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result.append(ab_w_list)
with open(txt_dir2) as f:
result2 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w))[3:])
result2.append(ab_w_list)
with open(txt_dir3) as f:
result3 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result3.append(ab_w_list)
with open(txt_dir4) as f:
result4 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result4.append(ab_w_list)
結果を読み取ります。3文字目から推定結果が出力されているため,[3:]としてスライスしています。賢明な読者の方々は,ぜひ繰り返し構文を利用してください。
score = 0
for i in range(len(result)):
for j in range(len(result[i])):
if result[i][j] == result2[i][j]:
score += 1
else:
print(result[i][j])
count_all_list = [len(v) for v in result]
count_all = 0
for i in range(len(count_all_list)):
count_all += count_all_list[i]
accuracy = score/count_all
print("accuracy:" + str(accuracy*100) + "%")
score = 0
for i in range(min([len(result3), len(result4)])):
for j in range(min([len(result3[i]), len(result4[i])])):
if result3[i][j] == result4[i][j]:
score += 1
count_all_list = [len(v) for v in result3]
count_all = 0
for i in range(len(count_all_list)):
count_all += count_all_list[i]
accuracy = score/count_all
print("accuracy:" + str(accuracy*100) + "%")
Accuracyを計算します。今回は,「(合っている単語数)/(全単語数)」を定量的指標として利用しました。
結果
Train_accuracy:99.0%
Test_accuracy:53.2%
圧倒的過学習でした。間違えた単語を見てみると,「Th」「Ex」「Pr」から始まる単語が多かったです。これらの単語も複数の出力候補をソフトに決定するためには,モデリングの手法を変更する必要がありそうです。デフォルト設定で識別器はSVMを利用していますので,改善の余地はありそうです。
もし理論にモヤモヤがあれば
(2024/12/04 11:13:56時点 楽天市場調べ-詳細)
こちらの参考書は,PRMLよりも平易に機械学習全般の手法について解説しています。おすすめの1冊になりますので,ぜひお手に取って確認してみてください。
全コード
train_nm = 1000
test_nm = 1000
txt_dir = "temp.en"
with open(txt_dir) as f:
output = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
if len(list(w)) == 1:
ab_w_list.append(''.join(list(w)[:2]) + '@/' + w + "@")
else:
ab_w_list.append(''.join(list(w)[:2]) + '/' + w)
output.append(' '.join(ab_w_list))
with open(txt_dir) as f:
output3 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
if len(list(w)) == 1:
ab_w_list.append(''.join(list(w)[:1]) + "@")
else:
ab_w_list.append(''.join(list(w)[:2]))
output3.append(' '.join(ab_w_list))
for i in range(len(output)-1):
if ("&" in output[i]) or ("//" in output[i]):
output[i] = 0
for i in range(len(output3)-1):
if ("&" in output3[i]) or ("//" in output3[i]):
output3[i] = 0
output = [output[i] for i in range(len(output)) if output[i] != 0]
output3 = [output3[i] for i in range(len(output3)) if output3[i] != 0]
training_data = output[:train_nm]
training_test_data = output3[:train_nm]
test_data = output[train_nm:train_nm + test_nm]
test_data_original = output3[train_nm:train_nm + test_nm]
training_data = '\n'.join(training_data)
training_test_data = '\n'.join(training_test_data)
test_data = '\n'.join(test_data)
test_data_original = '\n'.join(test_data_original)
file = open('training_data.txt', 'w')
file.write(training_data)
file.close
file = open('training_test_data.txt', 'w')
file.write(training_test_data)
file.close
file = open('test_data.txt', 'w')
file.write(test_data)
file.close
file = open('test_data_original.txt', 'w')
file.write(test_data_original)
file.close
txt_dir1 = "result_original.full"
txt_dir2 = "training_data.txt"
with open(txt_dir1) as f:
result = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result.append(ab_w_list)
with open(txt_dir2) as f:
result2 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w))[3:])
result2.append(ab_w_list)
score = 0
for i in range(len(result)):
for j in range(len(result[i])):
if result[i][j] == result2[i][j]:
score += 1
else:
print(result[i][j])
count_all_list = [len(v) for v in result]
count_all = 0
for i in range(len(count_all_list)):
count_all += count_all_list[i]
accuracy = score/count_all
print("accuracy:" + str(accuracy*100) + "%")
txt_dir3 = "result.full"
txt_dir4 = "test_data.txt"
with open(txt_dir3) as f:
result3 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result3.append(ab_w_list)
with open(txt_dir4) as f:
result4 = list([])
for l in f:
w_list = l.strip().split(' ')
ab_w_list = []
for w in w_list:
ab_w_list.append(''.join(list(w)[3:]))
result4.append(ab_w_list)
score = 0
for i in range(min([len(result3), len(result4)])):
for j in range(min([len(result3[i]), len(result4[i])])):
if result3[i][j] == result4[i][j]:
score += 1
count_all_list = [len(v) for v in result3]
count_all = 0
for i in range(len(count_all_list)):
count_all += count_all_list[i]
accuracy = score/count_all
print("accuracy:" + str(accuracy*100) + "%")