今回は、Webから取得したテキストファイルを読み込ませて、それが何語で書かれたテキストなのかを判定しました。
機械学習の一大テーマである自然言語処理の復習です。(そこまで大掛かりなことをやっているわけではないですが・・・。)
今回も Pythonによるスクレイピング&機械学習開発テクニック増補改訂 Scrapy、BeautifulSoup、scik [ クジラ飛行机 ]の第4章を参考にさせていただきながら、取り組んでいます。
※今回は、「lang/train」,「lang/test」ディレクトリの配下に英語、フランス語、インドネシア語、タガログ語の四種類の言語のファイルを配置してある前提です。また、それぞれのファイルは先頭2文字がen(英語),fr(フランス語),in(インドネシア語),tl(タガログ語)というデータを作成します。(機会があれば、このファイルを自動で作成またはスクレイピングで集めるプログラムも作りたいと思います。)
では、振り返っていきたいと思います。
コード全体は以下の通りで、「lang-train.py」に保存しました。 今回は、テキストファイル内のアルファベットの出現頻度を調べる「check_freq」という関数を定義して、ディレクトリ内のファイルを一括して処理する「load_files」という関数から呼び出すことで、訓練データや検証データを作成しています。 from sklearn import svm, metrics cnt = [0 for n in range(0, 26)] for ch in text: total = sum(cnt) #(3) 今後のためにJSONで結果を保存 # (4)学習と予測 #(5)精度を確認する では、コードを順番に見ていきます。 まず、「check_freq」の関数の中身を見ていきます。 最初に、引数で渡された「fname」から、os.pathのbasename()メソッドをつかって、ファイル名に当たる部分だけを抜き出します。 def check_freq(fname): 次に正規表現を扱うモジュール re の match()メソッドを使って、ファイル名の先頭2文字に当たる部分を取得し、group()メソッドを使って該当した文字列を返します。 '^'が先頭を表して、'[a-z]'がアルファベット(小文字)を表し'{2}'が二文字分を表します。全体で、「先頭から2文字分のアルファベット(小文字)」を表しています。 lang = re.match(r'^[a-z]{2}', name).group() 対象のファイルを開いて、中身を「text」に格納します。 with open(fname, "r", encoding="utf-8") as f: lower()メソッドを使ってtext内のアルファベットをすべて小文字に変換します。 text = text.lower() # 小文字に変換 アルファベットの出現頻度を数えるためにアルファベットの文字数分のカウンタを作って全部ゼロにします。 cnt = [0 for n in range(0, 26)] 続いて、[a]と[z]のUnicodeを取得します。ちなみに[a]は97、[z]は122となります。 code_a = ord("a") 次に、'text'内の文字を1文字ずつ'ch'に格納して処理を行います。 for ch in text: まず、格納された文字のUnicodeを取得します。 n = ord(ch) 取得した文字のUmicodeが[a]のコード(97)以上、[z]のコード(122)以下なら、該当する文字のカウントを+1します。(cnt[0]に[a]の数、cnt[1]に[b]の数、…と格納されます。) if code_a <= n <= code_z: # a-zの間なら 'text'内のすべての文字についてカウントが完了したらアルファベットの出現回数の合計を求めます。 total = sum(cnt) map関数と、lambda式をつかって、アルファベットの各文字の出現回数を、合計出現回数で割った値のリストを'freq’に格納します。(いわゆる「正規化」を行います。) freq = list(map(lambda n: n / total, cnt)) 最後に、正規化されたアルファベットの出現回数と、そのファイルの言語を表す2文字を返します。 return (freq, lang) 続いて、各ファイルを読み込んで出現頻度のデータと、そのラベルデータを返す「load_files」関数を定義します。 まず、出現回数のデータとなるリストと、ラベルデータとなるリストを初期化します。 def load_files(path): glob.glob()メソッドを使って、ファイル名のリストを取得します。 引数に、ワイルドカード(*:任意の文字列、?:任意の一文字 など)を使うことで、複数のファイル名をリストとして取得できます。 file_list = glob.glob(path) ファイル名リストから一つずつファイル名を取り出して先ほど定義した「check_freq」関数で処理して、アルファベットの出現回数と、何語かの正解ラベルを取得します。 for fname in file_list: 取得した結果の1つ目を、出現回数データのリストに追加して、2つ目をラベルデータとなるリストに追加します。 freqs.append(r[0]) すべてのファイルに対して処理が終わったら、辞書型で出現回数データのリストと、ラベルデータのリストを処理結果として返します。 return {"freqs":freqs, "labels":labels} 定義した「load_files」関数を使って、lang/train配下のテキストファイルを訓練データとして、lang/test配下のテキストファイルを検証データとして読み込みます。 data = load_files("./lang/train/*.txt") 以上でデータの取得が完了です。 今回の言語判定では使わないのですが、今後分析をする際に、毎回ファイルを読み込んで、アルファベットの出現回数をカウントするのは非効率なので、カウントした結果の訓練データ、検証データをJSON形式で保存しておきます。 with open("./lang/freq.json", "w", encoding="utf-8") as fp: JSON形式のデータの読込については、以下もご参照ください。 今回も、scikit-learnからインポートしたsvm(サポートベクターマシン)を利用していきます。 SVM(サポートベクターマシン)自体の理論的なところについては、以下もご参照ください。 さっそく、SVC(Support Vector Classification)のクラスを呼び出して、サポートベクターマシンを使った分類ができるモデルの実体を作ります。 モデルの実体ができたら、あとは、fit()メソッドを使って、訓練データ(data['freqs'])と正解ラベル(data['labels'])を引数に渡して実行するだけで、学習は完了です。 clf.fit(data["freqs"], data["labels"]) 続いて、学習したモデル(clf)を使って、予測を行います。 予測についても学習と同様に簡単で、predict()メソッドに検証データ(test['freqs'])を渡すことで、予測した結果を出力してくれます。 predict = clf.predict(test["freqs"]) 予測した結果(predict)の精度を算出します。 まず、scikit-learnからmetricsモジュールをimportして、accuracy_score()メソッドを使って全体の精度を求めます。 accuracy_score()は、検証データの正解ラベル(test['labels'])と予測した結果(predict)を引数に渡す事で、予測した結果が正解ラベルと同じだった割合を算出してくれます。 ac_score = metrics.accuracy_score(test["labels"], predict) また、scikit-learnからmetricsモジュールの、classification_report()メソッドを使ってラベル毎の精度を求めます。 cl_report = metrics.classification_report(test["labels"], predict) 最後に、求めた全体の精度と、ラベル毎の精度を出力します。 print("正解率=", ac_score) 先ほど作成したファイルを実行してみます。 python3 lang-train.py 以下の通り、複数言語のテキストファイルを読み込んで、それぞれのアルファベットの出現回数を特徴量として、英語、フランス語、インドネシア語、タガログ語に分類し、その精度を出力することができました。 正解率と1となり、手元に用意したデータでは100%分類することができました。 今回は、自然言語の特徴量としてアルファベットの出現回数のみを特徴量として、何語なのかを判定しました。 実際の自然言語処理では、単語毎の関係なども特徴量として考慮する必要がありますが、初めの一歩としては、 よい経験になりました。 今後も、Pythonによるスクレイピング&機械学習開発テクニック増補改訂 Scrapy、BeautifulSoup、scik [ クジラ飛行机 ]で、スクレイピングと機械学習開発に取り組んでいきたいと思います。 【過去記事】 2019年8月31日(土)にE資格を受験して、合格しました! E資格対策として勉強の進め方や、参考書などをまとめました。 これから受験される方がいらっしゃいましたらご参考まで。 2019年3月9日(土)にG検定を受験し、見事合格できました! 受験の体験記や勉強法などを別のブログにまとめました。 これから受験される方がいらっしゃいましたらご参考まで。 【E資格対策に使った参考書】 SVMで言語を判定する
1.全体像
import glob, os.path, re, json
#(1)アルファベットの出現頻度を調べる
def check_freq(fname):
name = os.path.basename(fname)
lang = re.match(r'^[a-z]{2}', name).group()
with open(fname, "r", encoding="utf-8") as f:
text = f.read()
text = text.lower()
code_a = ord("a")
code_z = ord("z")
n = ord(ch)
if code_a <= n <= code_z: # a-zの間なら
cnt[n - code_a] += 1
freq = list(map(lambda n: n / total, cnt))
return (freq, lang)
#(2) 各ファイルを処理する
def load_files(path):
freqs =
labels =
file_list = glob.glob(path)
for fname in file_list:
r = check_freq(fname)
freqs.append(r[0])
labels.append(r[1])
return {"freqs":freqs, "labels":labels}
data = load_files("./lang/train/*.txt")
test = load_files("./lang/test/*.txt")
with open("./lang/freq.json", "w", encoding="utf-8") as fp:
json.dump([data, test], fp)
clf = svm.SVC()
clf.fit(data["freqs"], data["labels"])
predict = clf.predict(test["freqs"])
ac_score = metrics.accuracy_score(test["labels"], predict)
cl_report = metrics.classification_report(test["labels"], predict)
print("正解率=", ac_score)
print("レポート=")
print(cl_report)2.アルファベットの出現頻度を調べる
name = os.path.basename(fname)
text = f.read()
code_z = ord("z")
cnt[n - code_a] += 1
3.各ファイルを処理する
freqs =
labels =
r = check_freq(fname)
labels.append(r[1])
test = load_files("./lang/test/*.txt")4.今後のためにJSONで結果を保存
json.dump([data, test], fp)5.学習と予測
6.精度を確認する
print("レポート=")
print(cl_report)7.コマンドラインから実行してみる