Neural Network Console(ニューラルネットワークコンソール 以後NNCと書きます)で自然言語処理をするために、「00」~「FF」の256パターンが文章中に出現する比率を求めて数値データ化してみるのが、今回のテーマです。
今回のアイディアについて
テキストデータそのままでは、NNCで使えません。
テキストデータを、NNCで使える「おおむね-1.0~1.0の間に正規化した数値データ」にするか・・がポイントです。
NNCで安定して使うには、文章に登場する単語の増減に関係なく列数(次元)を固定にする必要があります。
有限個であらゆる文章をカバーできるのは、なんだろう?
考えてみました。
それは「文字コード=16進数」だな。
ということで。
今回は汎用版として、「00」~「FF」の256パターンが文章中に出現する比率を求めて、1行256列のデータに変換してみます。
今回の方針
さてコード(16進数)を単位して、列数(次元)を、'00'~'FF'までの256列(次元)でやってみると決めました。
それ以外の決め事は、以下のような感じでいきます。
コードを単位にNNCデータに変換するpythonソース
メインのコード部分です。
import os import csv import re import codecs import numpy as np from janome.tokenizer import Tokenizer # CSVにテキストファイル(フルパス)とラベルを書いている前提 def make_data(textfile,s_outputfile): l_in = get_input_csv(textfile) outf = open(s_outputfile,'w',newline='',encoding='utf8') csvwriter = csv.writer(outf) csvwriter.writerow(['x:image','y:label']) for i,l_p in enumerate(l_in): l_text = get_input_txt(l_p[0]) s_path = '.\\' + l_p[1] + '\\' + str(i) + '.csv' csvwriter.writerow([s_path,l_p[1]]) make_data_csv(l_text,l_p[1],i) outf.close()
関数の使い方とアウトプットのイメージ
この関数の使い方はこうです。
file_result.csv は、学習対象のテキストファイルのパスと、正解ラベルを書いたCSVファイルです。
内容例
C:/data/sports/sports-watch-4597641.txt,0
C:/data/sports/sports-watch-4601248.txt,0
C:/data/sports/sports-watch-4604621.txt,0
C:/data/sports/sports-watch-4609913.txt,0
C:/data/sports/sports-watch-4612295.txt,0
C:/data/sports/sports-watch-4623406.txt,0
livedoor ニュースコーパス
学習するデータは、「livedoor ニュースコーパス」の一部を使います。
様々なジャンルのニュースがありますが、今回は「sports-watch」901ファイルと、「peachy」(女性向けの記事みたい)843ファイルを学習と推論に使います。
「sports-watch」を分類0、「peachy」を分類1にして、140ファイルくらいを推論用、残りを学習用にします。
ファイル単位にわかれているので、それらは適当なフォルダにおいて、そのパスと正解ラベルを書いたCSVをインプットにします。
後ろの引数、「0_result.csv」の方は、アウトプットの一つであるNNCのデータ・セットCSVの名前です。
処理をして、学習した結果をラベル(0 か 1)のフォルダに保存して、そのデータCSVへのパスと、正解ラベルを書いたファイルです。
x:image,y:label
.\0\0.csv,0
.\0\1.csv,0
.\0\2.csv,0
.\0\3.csv,0
.\0\4.csv,0
.\0\5.csv,0
.\0\6.csv,0
上記のpythonソースのポイント補足
このソースのポイントを補足します。
まず、各ニュースファイルを読み込む部分です。
def get_input_txt(textfile): with codecs.open(textfile,'r','utf8') as f: data = f.read() data2 = re.sub(r'[\s \t]',"",data) data3 = re.sub(r'^http.+0900',"",data2) return re.split(r'[。??]',data3)
元データ(ニュース)についての補足
ちょっと、妙な部分があるので解説しておきます。
ニュースはこんな感じの構成です。
http://news.livedoor.com/article/detail/4304647/
2009-08-19T11:30:00+0900
“バレバレ”では男性も幻滅 つけまつ毛専用マスカラ「つけまマスカラ」誕生
女性がメイクをする上で最も気合が入るのが、アイメイク。部分使いに、上下フルに、2枚重ねと、パッチリ印象的な目元作りの為につけまつ毛を利用している人が増え続けている。簡単に長く、黒々としたまつ毛が手に入るつけまつ毛だが、“つけているのがバレバレ”では男性からしても幻滅ポイントに。そんな悩みを解消すべく生まれたのが、つけまつ毛用マスカラ「つけまマツカラ」だ。自分のまつ毛とつけまつ毛の隙間をしっかり埋めて、美しく目元をボリュームアップしてくれる優れ物。
最初にURLと日付時刻で2行あって、3行目から記事がはじまります。
ただ、内容には余分な空白や改行・タブ文字などが混在してます。
学習させるのに、前の2行は邪魔なので消して、改行だけだとへんなところで切れるので、行の切れ目を「。??」で識別し、余計な空白とタブ文字は消してる・・という処理がはいってます。
これで読み込んで、行ごとに分割したリストを返します。
文字コードごとに分館してカウントするpythonプログラム
そのリストを受けて、文字コードごとに分解してカウントして、構成比を計算してファイルを書く部分のコードです。
# NNC用に数値データに変換したデータを1行ずつ別ファイルにして保存する def make_data_csv(l_in,label,n_out): s_pat = r'記号' o_reg = re.compile(s_pat) na_ar = np.array([]) l_line = ['00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F'] #初期化 00からFFをひとつずつおいて、位置を決定する for fo in range(256): if(fo > 15): s = str(hex(fo)).upper() l_line.append(s[2:4]) # 形態素解析・・単語に分割するインスタンス o_t = Tokenizer() for i,l_p in enumerate(l_in): # 1行分の単語を分割 o_malist = o_t.tokenize(l_p) for n in o_malist: # 記号以外を対象にする if not (o_reg.match(n.part_of_speech)): # 分割した単語を1行分リストに加えていく for i in range(len(n.surface)): h = str(hex(ord(n.surface[i]))).upper() if(len(h) >=4): l01 = h[2:4] l_line.append(l01) if(len(h) >=6): l02 = h[4:6] l_line.append(l02) # コードごとにカウントする c_dic = {} t_cnt = 0 for tok in l_line: c_dic.setdefault(tok,0) c_dic[tok] += 1 t_cnt += 1 # ファイルの中の出現割合に変換する l_output = [] for k, v in sorted(c_dic.items()): rt = v / t_cnt l_output.append(rt) # 1行分ずつCSVファイルに保存する。フォルダは正解ラベル毎にわけておく os.makedirs(label,exist_ok=True) with open('.\\' + label + '\\' + str(n_out) + '.csv','w',newline='',encoding='utf8') as df: cw = csv.writer(df) cw.writerow(l_output) df.close()
ソースコードのポイント
ポイントは、この部分です。
for n in o_malist: # 記号以外を対象にする if not (o_reg.match(n.part_of_speech)): # 分割した単語を1行分リストに加えていく for i in range(len(n.surface)): h = str(hex(ord(n.surface[i]))).upper() if(len(h) >=4): l01 = h[2:4] l_line.append(l01) if(len(h) >=6): l02 = h[4:6] l_line.append(l02)
単語を16進数のコードに変換してから、文字列(大文字)にしてます。
その時、全角なら「0xA0FF」、半角なら「0x31」のような文字列になります。
前の0xはいらないので、コード部分だけ2桁ずつスライスで取り出して、リストに追加しているというわけです。
それを辞書でカウントして、ファイルの中の総数で割ることで、0~1.0の間の数値に正規化して、1行ずつ、CSVデータファイルに書き出してます。
これでとりあえず、ニュースのテキストデータをNNCで処理可能なデータに変換できます。
これを使って、NNCで学習・推論をやってみるのは、別の回で書きますね。
今回のpythonソースコードの全文
最後にソースコードの全文です。
import os import csv import re import codecs import numpy as np from janome.tokenizer import Tokenizer # CSVにテキストファイル(フルパス)とラベルを書いている前提 def make_data(textfile,s_outputfile): l_in = get_input_csv(textfile) outf = open(s_outputfile,'w',newline='',encoding='utf8') csvwriter = csv.writer(outf) csvwriter.writerow(['x:image','y:label']) for i,l_p in enumerate(l_in): l_text = get_input_txt(l_p[0]) s_path = '.\\' + l_p[1] + '\\' + str(i) + '.csv' csvwriter.writerow([s_path,l_p[1]]) make_data_csv(l_text,l_p[1],i) outf.close() # 元になるCSVファイルを読み込んでリストに変換する def get_input_csv(csvfile): with open(csvfile,'r',encoding='utf8') as csvf: csvreader = csv.reader(csvf) return list(csvreader) def get_input_txt(textfile): with codecs.open(textfile,'r','utf8') as f: data = f.read() data2 = re.sub(r'[\s \t]',"",data) data3 = re.sub(r'^http.+0900',"",data2) return re.split(r'[。??]',data3) # NNC用に数値データに変換したデータを1行ずつ別ファイルにして保存する def make_data_csv(l_in,label,n_out): s_pat = r'記号' o_reg = re.compile(s_pat) na_ar = np.array([]) l_line = ['00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F'] #初期化 00からFFをひとつずつおいて、位置を決定する for fo in range(256): if(fo > 15): s = str(hex(fo)).upper() l_line.append(s[2:4]) # 形態素解析・・単語に分割するインスタンス o_t = Tokenizer() for i,l_p in enumerate(l_in): # 1行分の単語を分割 o_malist = o_t.tokenize(l_p) for n in o_malist: # 記号以外を対象にする if not (o_reg.match(n.part_of_speech)): # 分割した単語を1行分リストに加えていく for i in range(len(n.surface)): h = str(hex(ord(n.surface[i]))).upper() if(len(h) >=4): l01 = h[2:4] l_line.append(l01) if(len(h) >=6): l02 = h[4:6] l_line.append(l02) # コードごとにカウントする c_dic = {} t_cnt = 0 for tok in l_line: c_dic.setdefault(tok,0) c_dic[tok] += 1 t_cnt += 1 # ファイルの中の出現割合に変換する l_output = [] for k, v in sorted(c_dic.items()): rt = v / t_cnt l_output.append(rt) # 1行分ずつCSVファイルに保存する。フォルダは正解ラベル毎にわけておく os.makedirs(label,exist_ok=True) with open('.\\' + label + '\\' + str(n_out) + '.csv','w',newline='',encoding='utf8') as df: cw = csv.writer(df) cw.writerow(l_output) df.close()
今回はこのへんで・・。
追記(2バイトコードでやったバージョンもあります)
今回は'00'から’FF'の1バイトコードでやってます。
ただ、日本語なので2バイトコードの方が良いのではないか・・という指摘があり、それでやってみたバージョンもあります。
この考え方を応用したデモを作って、イベントでしゃべった時に記事です。