Edited at

Kerasで実装するSeq2Seq -その1 日本語訓練データの準備

 本稿では、Seq2Seq(Sequence to Sequence)モデルによるチャットボットをKerasベースで作成するにあたり、学習用の日本語会話データ収集、整形、品詞分解手順を記述します。


1. はじめに

 Kerasは少ないコードでニューラルネットワークを構築することができ、大変重宝しています。あまりに便利なので、KerasベースでSeq2Seqを実装しようと思ったときにも、「Seq2Seqレイヤー」のようなものがすでにあって、1行で実装完了!などと言ったことを期待していましたが、残念ながらそうではありませんでした。

 そこで、Keras : Ex-Tutorials : Seq2Seq 学習へのイントロを参考に、Kerasベースの日本語チャットボット作成に挑戦してみます。


2. 本稿のゴール

 以下の段取りを踏んで、Seq2Seqモデルによるチャットボットを作成していきます。

 これらの内容を順次投稿していく予定ですが、今回はそれらに先立つものとして、日本語コーパスの元データ入手、整形、品詞分解などを行います。品詞分解には、京大黒橋・河原研究室のJUMAN++を使用します。

 なお、本稿の前提となるソフトウェア環境は、以下の通りです。


  • Ubuntu 16.04 LTS

  • Python 3.6.4

  • Anaconda 5.1.0

  • TensorFlow-gpu 1.3.0

  • Keras 2.1.4

  • Jupyter 4.4.0


3. コーパス元データの入手


3-1. データ入手元について

 会話データを大量に入手する方法としては、Twitterを利用する手法などがありますが、今回は既存の会話データに頼ることにしました。

 インターネットから入手可能な会話データとしては、名大会話コーパスが有名ですので、これを利用させていただきます。

 ただし、もう少しコーパスのボリュームを増やしたかったので、青空文庫戯曲データも利用することにしました。

 更には、会話といえば落語だ!ということで、こちらのページ(手垢のついたものですが)落語はろー落語速記編から、落語のテキストデータを入手いたしました。

 それぞれのリンク先からZIPファイルをダウンロードし、適当なフォルダに展開しておきます。


3-2. 文字コード変換

 入手したファイルの文字コードはShift-JISなので、これをUtf-8に変換します。以下のコマンドで変換しますが、複数ファイル指定ができるので便利です。

$ nkf -w -Lu --overwrite *.txt


4. 会話データの作成


4-1. 基本方針

 以下の3つのフェーズで実施します。

1. 会話文の整形

 元データから会話部分を抜き出し、不要文字を削除して共通フォーマットに整える

2. 品詞分解

 JUMAN++を用いて会話文を品詞分解する

3. 分解された単語で構成される1次元配列の生成

 品詞分解の過程で発生する不要単語を削除し、単語の一次元配列を生成する


4-2. 会話文の整形


4-2-1. 概要

 一般に、ChatBotの学習データは、発話文と応答文の対で構成されています。しかし会話というものは、前の文を受けて次の文が発話されるのだから、すべての文が発話文であり、かつ応答文であると解釈することにしました。これにより、コーパスの量が単純に2倍になります。

 この方針を受けて、会話データファイルは発話用と応答用の2種類作成するのではなく、すべての会話を発話順に1つのファイルに収録しました。また、以下のように、セパレータ「SSSS」+文章、という形に整形することにしました。

SSSSご飯食べた?

SSSSうん。
SSSS何食べた?
SSSS「ベラ」っていうやつ。

 ところで、会話文の中には長いものもありますが、これがニューラルネットワークで定義する系列長を超える場合は、学習データに使えず、捨てざるを得なくなります。これはもったいないので、一定長より長い文章は、句点のところにセパレータを挿入して、複数の文章に分割することにしました。同一人物の単一発言が発話と応答に分かれることになりますが、そこは捨てるよりはましと、割り切ります。

 以下、それぞれの元データに対する処理の説明が出てきますが、コードはJupyter Notebook上での実行を前提としています。また、元データごとにノートを分ける前提で、説明やコードは記述されています。

 整形処理の主な内容は、会話文の抜き出しと、補足説明などのメタ情報の削除です。処理結果の確認とソースの修正を繰り返すことで、決定していきました。元データの種類によって会話文の記述方法が異なっていますので、処理もそれぞれ異なったものになっています。


4-2-2. 名大会話コーパスの整形

 名大会話コーパスの各ファイルは、基本的に以下のような構造になっています。


  • 最初と最後に、「@」で始まるヘッダおよびフッタがある

  • 発言者名は、「Mnnn」または「Fnnn」の半角英数字4文字で表記される(nは0から9までの数字)

  • 発言行は、「発言者名」+半角「:」+「発言内容」で構成される(F123:おはよう!)

  • 伏字が全角アスタリスク「***」で表記されている。また、強調等の意味合いで*が挿入されていることがある

  • 発言内容に、発言者名(「Mnnn」または「Fnnn」)が現れることがある

 これらを踏まえて、以下のコードによって整形を実施し、その結果を1つのファイルにまとめます。コードはJupyter上での実行を前提としており、各コードブロックを個別のCellで実行するイメージとなります。

 まず、整形処理の本体です。会話文の中に現れる「***」や発言者名を、不明単語を表す文字列「UNK」に置き換えます。また、()で挟まれる補足説明等を削除します。コードの最後のほうに、長い会話文を分割する処理が入っています。

import numpy as np

import csv
import glob
import re

def make_data(fname,data2) :
f = open(fname, 'r')
df1 = csv.reader(f)
data1 = [ v for v in df1]

print(len(data1))
#ファイル読み込み
text = ''
for i in range(0,len(data1)):
if len(data1[i]) == 0:
print('null')
continue

s = data1[i][0]
if s[0:5] == "%com:" :
continue
if s[0] != '@' :
#不明文字をUNKに置き換え
s = s.replace('***','UNK')
#会話文セパレータ
if s[0] == 'F' or s[0] == 'M':
s = 'SSSS'+s[5:]
if s[0:2] == 'X:':
s = 'SSSS'+s[2:]

s = re.sub('F[0-9]{3}',"UNK",s)
s = re.sub('M[0-9]{3}',"UNK",s)
s = s.replace("*","")
else :
continue

while s.find("(") != -1 :
start_1 = s.find("(")
if s.find(")") != -1 :
end_1 = s.find(")")
if start_1 >= end_1 :
s = s.replace(s[end_1],"")
else :
s = s.replace(s[start_1:end_1+1],"")
if len(s) == 0 :
continue
else :
s=s[0:start_1]

while s.find("[") != -1 :
start_2 = s.find("[")
if s.find("]") != -1 :
end_2=s.find("]")
s=s.replace(s[start_2:end_2+1],"")
else :
s=s[0:start_2]

while s.find("<") != -1 :
start_3 = s.find("<")
if s.find(">") != -1 :
end_3 = s.find(">")
s = s.replace(s[start_3:end_3+1],"")
else :
s = s[0:start_3]

while s.find("【") != -1 :
start_4 = s.find("【")
if s.find("】") != -1 :
end_4 = s.find("】")
s = s.replace(s[start_4:end_4+1],"")
else :
s = s[0:start_4]

#いろいろ削除したあとに文字が残っていたら出力文字列に追加
if s != "\n" and s != "SSSS" :
text += s
#セパレータごとにファイル書き込み
text =text[4:]
while text.find("SSSS") != -1 :
end_s = text.find("SSSS")
t = text[0:end_s]
#長い会話文を分割
if end_s > 100 :
while len(t) > 100 :
if t.find("。") != -1 :
n_period = t.find("。")
data2.append("SSSS"+t[0:n_period+1])
t = t[n_period+1:]
else :
break
data2.append("SSSS"+t)
text = text[end_s+4:]
f.close()
return

 上記の処理を、元データのファイルごとに実行します。フォルダ「nucc」配下に、元データファイルが格納されていますが、ファイル名の一覧を取得して、ファイル単位に順次実行します。また、整形データはファイル「nucc2/corpus.txt」に書き込まれます。

file_list = glob.glob('nucc/*')

print(len(file_list))

data2=[]
for j in range(0,len(file_list)) :
print(file_list[j])
make_data(file_list[j],data2)

#ファイルセーブ
f = open('nucc2/corpus.txt','w')
for i in range(0,len(data2)):
f.write(str(data2[i])+"\n")
f.close()
print(len(data2))


4-2-3. 戯曲データの整形

 戯曲の各ファイルは、以下のような特徴があります。


  • 会話でない文章として、ヘッダ、フッタおよび、会話中にもト書きが現れるが、特定の文字から始まるということが無いので、プログラムによる識別が難しい

  • 発言行において、発言者名と発言内容の区切りは、半角スペースの場合と半角「:」の場合がある

  • ルビやその他の補足説明などのメタ情報が、《》や[]に挟まれて出現する

 整形処理の本体は以下の通りです。行頭のチェック(最初の文字が「底本」かどうかの判定、など)は、会話文かどうかの判定処理です。

import numpy as np

import csv
import glob
import re

def make_data(fname,data2) :
f=open(fname, 'r')
df1 = csv.reader(f)
data1 = [ v for v in df1]

#ファイル読み込み
for i in range(0,len(data1)):
if len(data1[i]) == 0:
continue

s=data1[i][0]
if s[0] == " " :
continue

if s[0:2]=="底本" :
continue

if s[0:3]=="[#]" :
continue

if s[0:2]=="校正" :
continue

if s[0:2]=="初出" :
continue

if s[0:3]=="(例)" :
continue

if s.find("! ") != -1 :
s=s.replace("! ","!")

if s.find("? ") != -1 :
s=s.replace("? ","?")

if s.find("|") != -1 :
s=s.replace("|","")

if s.find(" ") !=-1 :

sp=s.find(" ")
s=s.replace(s[0:sp+1],"SSSS")
else :
continue

while s.find("(") != -1 :
start_1 =s.find("(")
if s.find(")") != -1 :
end_1=s.find(")")
s=s.replace(s[start_1:end_1+1],"")
if len(s) == 0 :
continue
else :
s=s[0:start_1]

while s.find("[") != -1 :
start_2 =s.find("[")
if s.find("]") != -1 :
end_2=s.find("]")
s=s.replace(s[start_2:end_2+1],"")
else :
s=s[0:start_2]

while s.find("《") != -1 :
start_3 =s.find("《")
if s.find("》") != -1 :
end_3=s.find("》")
s=s.replace(s[start_3:end_3+1],"")
else :
s=s[0:start_3]

if s.find(" ") != -1 :
s=s.replace(" ","")

if len(s) > 100 :
while len(s) > 100 :
if s.find("。") !=-1 :
n_period=s.find("。")
data2.append(s[0:n_period+1])
s="SSSS"+s[n_period+1:]
else :
break

if s !="\n" and s != "SSSS":
data2.append(s)

f.close()
return

 メイン処理は以下の通りです。

file_list=glob.glob('drama/*')

print(len(file_list))

data2=[]

for j in range(0,len(file_list)) :
print(file_list[j])
make_data(file_list[j],data2)

#ファイルセーブ
f=open('drama2/corpus_drama.txt','w')
for i in range(0,len(data2)):
f.write(str(data2[i])+"\n")
f.close()
print(len(data2))


4-2-4. 落語データの整形

 落語データの各ファイルは、以下のような特徴があります。


  • 会話文はかならず「」で括られている

  • 会話の中に、()等で挟まれたメタ情報が現れることがある

 整形処理の本体は以下の通りです。

import numpy as np

import csv
import glob
import re

def make_data(fname,data2) :
f=open(fname, 'r')
df1 = csv.reader(f)
data1 = [ v for v in df1]

#ファイル読み込み
for i in range(0,len(data1)):
if len(data1[i]) == 0:
continue

s=data1[i][0]
if s[0] == " " :
continue

if s.find("「") != -1 :
start_1 =s.find("「")+1
if s.find("」") != -1 :
end_1=s.find("」")
else :
end_1=len(s)
s="SSSS"+s[start_1:end_1]
else :
continue

if s.find("|") != -1 :
s=s.replace("|","")

while s.find("(") != -1 :
start_1 =s.find("(")
if s.find(")") != -1 :
end_1=s.find(")")
s=s.replace(s[start_1:end_1+1],"")
if len(s) == 0 :
continue
else :
s=s[0:start_1]

while s.find("[") != -1 :
start_2 =s.find("[")
if s.find("]") != -1 :
end_2=s.find("]")
s=s.replace(s[start_2:end_2+1],"")
else :
s=s[0:start_2]

while s.find("《") != -1 :
start_3 =s.find("《")
if s.find("》") != -1 :
end_3=s.find("》")
s=s.replace(s[start_3:end_3+1],"")
else :
s=s[0:start_3]

if s.find(" ") != -1 :
s=s.replace(" ","")

if s[len(s)-1] !="。" :
s=s+"。"

if len(s) > 100 :
while len(s) > 100 :
if s.find("。") !=-1 :
n_period=s.find("。")
data2.append(s[0:n_period+1])
s="SSSS"+s[n_period+1:]
else :
break

if s !="\n" and s != "SSSS":
data2.append(s)

f.close()
return

 メイン処理は以下の通りです。

file_list=glob.glob('rakugo/*')

print(len(file_list))

data2=[]

for j in range(0,len(file_list)) :
print(file_list[j])
make_data(file_list[j],data2)

#ファイルセーブ
f=open('rakugo2/corpus_rakugo.txt','w')
for i in range(0,len(data2)):
f.write(str(data2[i])+"\n")
f.close()
print(len(data2))

 ここまでの処理で、3つの会話文ファイル「corpus.txt」、「corpus_drama.txt」、「corpus_rakugo.txt」が出来上がります。


4-3. 品詞分解

 前述のJUMAN++を使って、4-2節で作成したファイルを品詞分解します。

 まず、JUMAN++のページから実行ファイルをダウンロードして、自環境にインストールします。インストールの仕方はマニュアルを参照してください。

 インストールができたら、コマンドラインから品詞分解を実施します。実施方法も、JUMAN++のマニュアルを参照願います。実行には、結構時間がかかります。環境にも依りますが、各ファイルとも1時間程度か、それ以上を見込んでおいたほうが良いでしょう。


4-4. 分解された単語で構成される1次元配列の生成

 JUMAN++の出力結果は、JUMAN++のページにあるように、デリミタが半角スペースのCSVファイルの形態をとっています。これに対し、


  • 1列目を取り出す

  • 「@」で始まる行(同音異義語)や「EOS」の行を削除する

  • 変な文字列をデリミタ「SSSS」や不明文字「UNK」に置き換える

 などの加工をします。これを1次元配列に整形します。

 コードは以下の通りです。ここまでに作成したノートとは、別のノートで実施することを前提にしています。

import numpy as np

import csv

def genarate_npy(source_csv,genarated_npy) :

df2 = csv.reader(open(source_csv, 'r'),delimiter=' ')

data2 = [ v for v in df2]

mat=np.array(data2)
print(mat.shape)
mat_corpus=[]

#補正
for i in range(0,mat.shape[0]):
if mat[i][0] != '@' and mat[i][0] != 'EOS' and mat[i][0] != '┐' and mat[i][0] != '┘':
if mat[i][0]=='SSSSUNK:' :
mat_corpus.append('SSSS')
elif mat[i][0]=='SSSSUNKUNK' :
mat_corpus.append('SSSS')
elif len(mat[i][0]) > 4 and mat[i][0][0:4]=='SSSS' :
mat_corpus.append('SSSS')
mat_corpus.append(mat[i][0][4:])
elif mat[i][0]=='UNK:'or mat[i][0]=='X:':
mat_corpus.append('SSSS')
elif mat[i][0]=='UNKUNK' :
mat_corpus.append('UNK')
else :
mat_corpus.append(mat[i][0])

#デリミタ連続対策

mat_corpus1=[]

for i in range(1,len(mat_corpus)) :
if mat_corpus[i] == 'SSSS' and mat_corpus[i-1] == 'SSSS' :
continue
else :
mat_corpus1.append((mat_corpus[i]))

mat_corpus1.append('SSSS') #最終行にセパレータを入れる
mat_corpus1=np.array(mat_corpus1).reshape(len(mat_corpus1),1)
#コーパス行列セーブ
np.save(genarated_npy, mat_corpus1)
print(mat_corpus1.shape)

return

 上記の関数を、元データごとに実行します。

genarate_npy('nucc2/seq2seq_corpus.csv','mat_corpus.npy')

genarate_npy('drama2/seq2seq_corpus_drama.csv','mat_corpus_drama.npy')
genarate_npy('rakugo2/seq2seq_corpus_rakugo.csv','mat_corpus_rakugo.npy')


5. 訓練データ作成


5-1. 概要

 本章の目的は、前章までに作成した単語の1次元配列を自然数の配列に変換し、更に、Seq2Seqニューラルネットワークの訓練データに再編成することです。

 配列の数字化は、登場する単語に一意のインデックスを付与することによって実現します。訓練データの方は、Seq2Seqニューラルネットワークの特徴上、少し変わった構成をとります。

 Seq2Seqのニューラルネットワークは、以下の図のように、エンコーダーとデコーダーの2つのニューラルネットワークから構成されています。

fig1.png

 それぞれのニューラルネットワークに入力が必要なので、入力は2種類必要になります。出力はデコーダーの方にだけあるので、ラベルデータは1種類必要となります。都合、3種類のデータを生成する必要があります。

 エンコーダー用の入力データEncoder Inputは、発話文から生成します。デコーダー用の入力データDecoder Inputとラベルデータは、応答文から生成します。どちらも同じものから生成しますが、デコーダーは入力単語の1つ先の単語を予測するように訓練しますので、この2つは1単語分ずれています。

 fig2.png

 以下、5-2節および5-3節にコードが出てきますが、これまでとは別のノートブック上で、2つ連続して実行することを想定しています。


5-2. 辞書ファイルの作成と、単語→インデックス変換

 前章までに作成した3つのファイルを1つの配列にマージし、そこから単語←→インデックス両引き辞書を作成します。次いで、この辞書を使って、単語をインデックスに変換した配列を作成します。この配列が、訓練データの元になります。

 ソースコードは以下の通りです。

import numpy as np

import pickle

#1次元配列ロード
mat_corpus = np.load('mat_corpus.npy')
data1 = [v[0] for v in mat_corpus]

mat_corpus_drama = np.load('mat_corpus_drama.npy')
data2 = [v[0] for v in mat_corpus_drama]

mat_corpus_rakugo = np.load('mat_corpus_rakugo.npy')
data3 = [v[0] for v in mat_corpus_rakugo]

mat1 = np.array(data1).reshape((len(data1),1))
mat2 = np.array(data2).reshape((len(data2),1))
mat3 = np.array(data3).reshape((len(data3),1))

mat0 = ['SSSS'] #先頭のデリミタ
mat0 = np.array(mat0).reshape((1,1))

mat = np.r_[mat0[:,0],mat1[:,0],mat2[:,0],mat3[:,0]] #各配列の先頭にデリミタがないので、
#マージ後に改めて付与する
words = sorted(list(set(mat)))
cnt = np.zeros(len(words))

print('total words:', len(words))
word_indices = dict((w, i) for i, w in enumerate(words)) #単語をキーにインデックス検索
indices_word = dict((i, w) for i, w in enumerate(words)) #インデックスをキーに単語を検索

#単語の出現数をカウント
for j in range (0,len(mat)):
cnt[word_indices[mat[j]]] += 1

#出現頻度の少ない単語を「UNK」で置き換え
words_unk = [] #未知語一覧

for k in range(0,len(words)):
if cnt[k] <= 3 :
words_unk.append(words[k])
words[k] = 'UNK'

print('words_unk:',len(words_unk)) # words_unkはunkに変換された単語のリスト

#低頻度単語をUNKに置き換えたので、辞書作り直し
words = list(set(words))
words.append('\t') #0パディング対策。インデックス0用キャラクタを追加
words = sorted(words)
print('new total words:', len(words))
word_indices = dict((w, i) for i, w in enumerate(words)) #単語をキーにインデックス検索
indices_word = dict((i, w) for i, w in enumerate(words)) #インデックスをキーに単語を検索

#単語インデックス配列作成
mat_urtext = np.zeros((len(mat),1),dtype=int)
for i in range(0,len(mat)):
if mat[i] in word_indices : #出現頻度の低い単語のインデックスをunkのそれに置き換え
mat_urtext[i,0] = word_indices[mat[i]]
else:
mat_urtext[i,0] = word_indices['UNK']

print(mat_urtext.shape)

#作成した辞書をセーブ
with open('word_indices.pickle', 'wb') as f :
pickle.dump(word_indices , f)

with open('indices_word.pickle', 'wb') as g :
pickle.dump(indices_word , g)

#単語ファイルセーブ
with open('words.pickle', 'wb') as h :
pickle.dump(words , h)

#コーパスセーブ
with open('mat_urtext.pickle', 'wb') as ff :
pickle.dump(mat_urtext , ff)

 インデックス0は、ニューラルネットワークのMasking用に予約したいので、これに単語がアサインされないよう、辞書ファイルに、単語ソート時に必ず先頭に来る「\t」(タブ)を追加します。

 また、出現頻度の低い単語は、学習コストの割に予測精度が上がらないので、出現頻度が3回以下の単語は思い切って、十把一絡げに不明単語を表す文字列「UNK」に置き換えます。


5-3. 訓練データの生成

 前節で作成したインデックス配列から、訓練データの配列を生成します。配列の種類はEncoder Input、Decoder Input、およびラベルの3種類で、いずれも3次元テンソルです。以下の図のように、まず会話文のリストを作成し、そこから生成します。

fig3.png

 ソースコードは以下の通りです。5-2節のコードブロックを実行してから、以下のコードブロックを実行してください。

import numpy.random as nr

maxlen_e = 50 #入力語数
maxlen_d = 50 #出力語数

#
#コーパスを会話文のリストに変換
#
separater = word_indices['SSSS']
data=[]

for i in range(0,mat_urtext.shape[0]-1) :
if mat_urtext[i,0] == separater :
dialog = []
else :
dialog.append(mat_urtext[i,0])
if mat_urtext[i+1,0] == separater :
data.append(dialog)

print(len(data))

#encode_input_data
enc_input = data[:-1]

#decode_input_data
dec_input = []
for i in range(1,len(data)):
enc_dialog = data[i][:]
enc_dialog.insert(0, separater)
dec_input.append(enc_dialog)

#target
target = []
for i in range(1,len(data)):
dec_dialog = data[i][:]
dec_dialog.append(separater)
target.append(dec_dialog)

e_input = []
d_input = []
t_l=[]
for i in range(len(enc_input)) :
if len(enc_input[i]) <= maxlen_e and len(dec_input[i]) <= maxlen_d :
e_input.append(enc_input[i][:])
d_input.append(dec_input[i][:])
t_l.append(target[i][:])

#
#0パディング
#
for i in range (0,len(e_input)):
#リストの後ろに0追加
e_input[i].extend([0]*maxlen_e)
d_input[i].extend([0]*maxlen_d)
t_l[i].extend([0]*maxlen_d)
#系列長で切り取り
e_input[i] = e_input[i][0:maxlen_e]
d_input[i] = d_input[i][0:maxlen_d]
t_l[i] = t_l[i][0:maxlen_d]

#リストから配列に変換
e = np.array(e_input).reshape(len(e_input),maxlen_e,1)
d = np.array(d_input).reshape(len(d_input),maxlen_d,1)
t = np.array(t_l).reshape(len(t_l),maxlen_d,1)

#
#シャッフル
#
z = list(zip(e, d, t))
nr.seed(12345)
nr.shuffle(z) #シャッフル
e,d,t=zip(*z)
nr.seed()

e = np.array(e).reshape(len(e_input), maxlen_e, 1)
d = np.array(d).reshape(len(d_input), maxlen_d, 1)
t = np.array(t).reshape(len(t_l), maxlen_d, 1)

print(e.shape,d.shape,t.shape)

#Encoder Inputデータをセーブ
with open('e.pickle', 'wb') as f :
pickle.dump(e , f)

#Decoder Inputデータをセーブ
with open('d.pickle', 'wb') as g :
pickle.dump(d , g)

#ラベルデータをセーブ
with open('t.pickle', 'wb') as h :
pickle.dump(t , h)

#maxlenセーブ
with open('maxlen.pickle', 'wb') as maxlen :
pickle.dump([maxlen_e, maxlen_d] , maxlen)

 会話文は可変長なので、固定長になるように0パディングします。左詰めになるように、右側に0を付与します。

 出来上がった訓練データテンソルをZIPにしてシャッフルしていますが、ここでseed指定しているのは、実行時のランダム要素をなるべく排除して、ソースを修正、再実行したときの結果を確認しやすくするためです。

 ここまでに作成、セーブした訓練データ、辞書等は、実際に訓練するときやチャットの応答文作成時にロードして使用します。


6. おわりに

 訓練に必要な各種データがそろいましたので、次回以降、ニューラルネットワークの構築と訓練、および会話応答文の生成を行っていきます。

 その内容は以下の投稿にまとめてあります。


  1. Kerasで実装するSeq2Seq -その2 単層LSTM

  2. Kerasで実装するSeq2Seq -その3 多層LSTMとBidirectional

  3. Kerasで実装するSeq2Seq -その4 Attention

 また、訓練データをTwitterから取得し、それを使用してニューラルネットワークを訓練する方法について、以下のように投稿しましたので、ご覧ください。


  1. TwitterAPIを用いた会話データ収集

  2. Twitterデータを用いたチャットボットの訓練

  3. Twitterデータを用いたチャットボットの訓練 -その2 処理性能とメモリ使用量改善

 訓練したニューラルネットワークは、Twitter上で利用できるようにしてあります。スクリーンネーム@Gacky01Bにつぶやくと、ニューラルネットワークが生成した応答文をリプライします。以下のような感じです。

fig_20190326.jpg


変更履歴

項番
日付
変更箇所
内容

1
2018/10/01
-
初版

2
2018/10/04
5章
5-2節のコードと5-3節のコードを連続して実行する旨を追記

3
2018/11/29
3-1節
「名大会話コーパス」へのリンクが切れたので再リンク

4
2018/12/15
3-1節
筆者の投稿「TwitterAPIを用いた会話データ収集」へのリンク追加

5
2019/1/30
6章
筆者の投稿「Twitterデータを用いたチャットボットの訓練」「Twitterデータを用いたチャットボットの訓練 -その2 処理性能とメモリ使用量改善」へのリンク追加

6
2019/3/25
6章
Twitterボット@Gacky01Bへのリンク追加