博麗霊夢さん&琴葉茜ちゃんと会話してみた①【Transformerとキャラ対話データを用いた転移学習による対話botの作成】

目次

はじめに

こんにちは, CCSの素敵なDTMerのゆんらべです.

大学も春休みに入って長期休暇となったので以前から温めていた計画を始動させました.

それは...

「パライソちゃんと会話したい!!!!!」

パライソちゃんとは, Fate/Grand Orderに登場するキャラクターです.

推しと会話する方法については素晴らしいバイブルがあります.

私たちの界隈では『みりあちゃんの人』と呼び称えている方の記事です. この記事ではSeq2Seqという学習モデルを使用しており, 発言に対する応答を学習させることで対話botを作成するというものです.

推しと会話するには, 推しの口調を学習した対話botを作成するのが最適だと考えたわけですが...

「パライソちゃんのデータ少なスギィ!!!!!」

機械学習においてデータの量は重要な要素の一つです. 対話botを作成するためには対話データセットが必要となります. パライソちゃんはお世辞にも出番が多いと言えるキャラクターではなく, ゲーム本編に登場している対話データを収集したとしても200も超えないと思います.

みりあちゃんの人は, みりあちゃんの対話データをSSなどから収集していましたが, 天下のアイドルマスターの人気キャラクターよりもパライソちゃんのSSが少ないのは火を見るよりも明らかです. 実際少なかった. そのため, 私自身の知識と技術ではパライソちゃん対話botの作成は難しいと判断しました.

そこで, 

  • 人気キャラクター
  • 対話データ収集が容易
  • 自分が好きなコンテンツ(超重要)

の全ての条件を満たすキャラクターを探すことにしました.

選ばれたのは...

東方Project博麗霊夢でした.

私はそこら辺に数多く存在する東方オタクの一人であり, 東方Projectといえば博麗霊夢さんです. これでデータ数と自分のモチベーションの条件は完全にクリアしました.

次にデータ収集ですが, 東方Projectの原作はゲーム本編でキャラクター同士が対話するため, 原作の台詞をそのまま学習データに使用できます. しかも, 東方Projectは20作品以上リリースしている上に霊夢さんは殆どの作品に登場しており, データ数も稼げる良キャラクターです. なお推しは火焔猫燐さん.

???「幻想入りできなければ, 霊夢さんを外の世界に連れてくればいいじゃない」

 

 

対話モデルを作成しよう

対話モデルについて

f:id:jungrave3:20200418233154j:plain

https://towardsdatascience.com/understanding-encoder-decoder-sequence-to-sequence-model-679e04af4346

機械学習において自然言語処理をする場合, Encoder-Decoderモデルを使用することが多いです. Encoderで中間表現を獲得し, それを元にDecoderで出力を生成します.

例えば, 英語からドイツ語に機械翻訳するモデルでは原文が英語, 目的文がドイツ語となります. 対話モデルを作成する場合は原文が発言文, 目的文が応答文となります.

 

Transformerとは

f:id:jungrave3:20200418233034p:plain

Transformer architecture diagram from Attention is All You Need

Transformerとは, RNNやCNNを使わずAttentionのみ使用したEncorder-Decorderモデルの一種で, 論文『Attention Is All You Need』で提案されたニューラル機械翻訳モデルです.

Attentionのみ使用しているためRNNとは違い並列処理が可能であり, self-attention機構によって系列データの関係性や文中のどこが重要か学習できます. そして, 当時のBLEUスコアは英仏:41.0, 英独:28.4で堂々の1位でした. つまり, 早い!賢い!強い!モデルです. 

AttentionやTransformerの詳細はRyobot(りょぼっと)氏の論文解説が判りやすいです. また, Transformerの実装はtksm氏の記事を参考にさせていただきました. ありがとうございます!

 

転移学習と霊夢さんモデル

転移学習とは, 教師データが少ない問題の学習を行う為に, 十分なデータで学習した異なる問題のモデルのパラメータを引き継いで, 更に学習を行う方法です.

霊夢さんの対話データだけでは対話botを作ることは難しいので, 大量の対話データを使用して事前学習を行い, 少量の霊夢さんデータを使用して転移学習を行います. 事前学習で基本的な応答を学習し, 転移学習で霊夢さんの口調を学習するといった感じです.

 

口調の学習

転移学習で口調を学習する方法は, 論文『転移学習を用いた対話応答のスタイル制御』を参考にしました.

https://www.anlp.jp/proceedings/annual_meeting/2017/pdf_dir/B3-3.pdf

事前学習用のN_p単語のうち, 事前学習データで出現頻度の低いN_s単語と転移学習データで出現頻度の高いN_sを差し替えて, 新たに転移学習用のN_p単語のトークナイザ(後述)を作成して転移学習を行います. 詳細は論文を読んでみて下さい.

今回の例では, 事前学習データで60000単語に絞り, 事前学習データで出現頻度の低い下位1000単語と霊夢さんデータで出現頻度の高い上位1000単語を差し替えることで『事前学習59000単語+霊夢さん1000単語』のトークナイザを作成します. この方法によって霊夢さんの語尾や東方Project特有の単語をトークナイザに含めることができます.

転移学習する際のパラメータは, 全て事前学習のままにしておきます.

 

作成したモデル図

f:id:jungrave3:20200407230151p:plain

複雑怪奇

本当はモジュールごとに纏まったモデル図になるのですが, 紆余曲折あってこのようなモデル図になってしまいました.

 

 

前処理とかしよう

前処理

ここの部分の実装もtksm氏の記事を参考にさせていただきました.

ここではtksm氏の記事で触れていなかったことについて解説していきます.

分かち書き

日本語は西洋語と異なり, 通常の文章で空白によって単語を区切ることがないため, 文章を単語ごとに区切る必要があります. PythonにはMeCabという形態素解析エンジンで動作するライブラリが存在し, 今回はそれを使用しました. 350万のTwitterデータを分かち書きする時, 非常に時間がかかってしまうのでmap関数を用いて並列処理を行いました.

# Mecabで分かち書き
def preprocess_sentence(sentence):
    tagger = MeCab.Tagger("-Owakati")
    parser = tagger.parse(sentence)
    return re.sub('\n', '', parser)

# わかち書きなどの前処理を行う
with Pool(16) as pool:
    input_data = list(pool.map(preprocess_sentence, input_data))
    output_data = list(pool.map(preprocess_sentence, output_data))
トークナイザ

トークナイザとは, 字句解析器のことです. ここでは単語を整数値にエンコードし, 整数値を単語にデコードする機能という認識で大丈夫です. 今回はTensorFlow DatasetsのSubwordTextEncoderでサブワード分割します. サブワード分割とは, 高頻度語は1単語として扱い, 低頻度語はより短い単位に分割することで, 限られた語彙数の中で可能な限り多くの単語を表現できる手法です.

tokenizer = tfds.features.text.SubwordTextEncoder.build_from_corpus(input_data + output_data, TARGET_VOCAB_SIZE)

訓練/評価データ

機械学習では実際に学習を行う訓練データと汎化性能を確認するための評価データを準備します. 今回はTensorflow Datasetのskip関数とtake関数を使用してデータを分けました.

def split_train_test(data, TRAIN_SIZE: int, BUFFER_SIZE: int, SEED=123):
    valid_data = data.skip(TRAIN_SIZE)
    train_data = data.take(TRAIN_SIZE).shuffle(BUFFER_SIZE, seed=SEED)
    return train_data, valid_data

 

保存/読み込み

重み

推論のみを行う場合や前回の続きから学習を始めたい場合, 重みの保存が必要となります. 今回はtf.keras.Modelのload_weights関数とsave_weights関数を使用しました. 新しい tensorflow形式や従来のhdf5形式で保存できます.

プログラム実行時にモデルを構築し, そこに保存した重みを渡すことで前回の状態を再現できます. 重みと一緒にモデルを保存すれば手間が少ないのですが, モデルの構築方法が悪いのか, Tensorflow2.0.0が悪いのか, 度々謎のエラーを吐きます... そのため, 重みだけ保存して実行するたびにモデルを構築することにしました.

# 重みを保存する
model.save_weights(os.path.join(path, config.save_filename), save_format='tf')
# 重みをロードする model.load_weights(os.path.join(path, config.load_filename))
トークナイザなど

推論のみを行う場合や前回の続きから学習を始めたい場合, トークナイザを保存しておけば実行ごとに作成する必要がなくなります. 今回はファイル形式に関係なく保存できるpickle形式で保存しました.

pickle形式は複数のオブジェクトを1つにまとめて保存できるので, 必要なデータをまとめて保存することにしました.

# トークナイザを保存する
def
save_text_tokenizer(tokenizer, file_name): print("tokenizer saving...") with open(file_name+".pickle", 'wb') as handle: pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL) # トークナイザをロードする def load_text_tokenizer(file_name): with open(file_name + ".pickle", 'rb') as handle: return pickle.load(handle)

 

 

ソースコード

ここまでのソースコードはGithubにあげてあります. ガバガバですがご了承ください.

 

 

データ収集をしよう

事前学習データ

霊夢さんと会話するために, まずは普通に会話してくれる対話botを作成する必要があります.

様々な記事や論文で紹介されている方法に倣って, Twitterのツイートとリプライから対話データを集めることにしました. ツイートを収集するためにTwitterAPIに登録しました.

登録はしましたが, reppy4620氏が収集したTwitterデータを公開してくださっていたので, 有難く使用させて頂くことにしました. ありがとうございます!これで事前学習用データが350万集まりました!自分で集めるのがめんどくさかった.

 

転移学習データ

キャラクターの口調を学習するにはキャラクターが喋っているデータが必要です. 今回は対話形式のデータが欲しいので「発話文+霊夢さんの応答文」のセットが必要になります.

魔理沙「紅魔館を爆発させようぜ」

霊夢「いいわね」

こんな感じの対話データを収集し, データの整形をする必要があります. 独り言や3人以上での会話シーンは取り除きます.

原作ゲームから収集

今回は東方Project原作の台詞を纏めて下さっているRadical Discovery様のサイトに記載されていた「東方紅魔郷」~「東方剛欲異聞」の対話データを収集しました.

Radical Discovery - 東方 セリフ集

収集はコピペ+手作業データ整形でゴリ押しました. これで転移学習用データが1400集まりました!情報系学生にあるまじきアナログ手法.

データはキャラクター名を省略したり, 最後の句読点を除いたり, 括弧を取り除いたりして整形しました. 下記のような感じです.

ところで、霊夢?
何?

何でこの神社まで来たの?
あ、そうだ。神奈子か諏訪子に聞きたい事があったんだ

二人とも留守ですよ。それで私はお留守番です
あ、そう。やっぱり居なかったのね

関連書籍から収集

しかし, これでもデータは足りないため, 東方Projectの関連書籍からデータを収集することにしました. 霊夢さんの登場回数が多く, 会話場面が多くなる漫画作品から, 元々所持していた『東方鈴奈庵』に加えて,『東方儚月抄』と『東方茨歌仙』を購入しました.

本をスタンドに立て, 幻想郷を堪能しながら手打ちでデータを収集しました. これで2000集まりました!正直少ないですね.

f:id:jungrave3:20200418233751j:plain

実質, 幻想郷

 

 

学習をさせよう

環境

今回のモデル作成に使う環境は次の通りです. Windowsなのは, 後でWindowsのみで動作するソフトウェアを使用するからです.

  • Python 3.6.2
  • Tensorflow 2.0.0
  • GeForce GTX 1660Ti
  • Windows 10

 

事前学習

Twitterデータで学習

Twitterデータを基にGPUを使用して事前学習していきます. ハイパーパラメータは表のとおりです.

パラメータ名
エンコーダ/デコーダの層数 2
Embedding層の次元数 256
Multi-Head Attentionのヘッド数 8
Feed Forward Networkの次元数 512
Dropoutを適用する率 0.1
最大単語列の長さ 64
バッチサイズ 64
学習単語数 60000

1エポックあたり6時間半かかり, 学習時間は約40時間でした.

Twitterモデルの会話例

Twitterデータで作成したモデルの会話です.

f:id:jungrave3:20200327103545j:plain

「おはよう」は「おはようございます」と返してくれたり「ちょっと寒くない?」は「寒いのは苦手です」と返してくれたり, ちゃんと返事してくれました!

しかし, 汎用性の高い「それはやばい」って返事が多かったり「でも私は好きな人には無理だよ」っていう意味不明な返事をしたり, 完璧ではありませんが何となく会話している気分になりました. 会話というよりは相槌ですが, それでも嬉しいですね! 

 

転移学習

霊夢さんデータで学習

元々の学習単語数も多くなかったので学習単語数は60000単語のままに設定し, Twitterデータで出現頻度の低い下位1000単語と霊夢さんデータで出現頻度の高い上位1000単語を差し替えて「Twitter59000単語+霊夢さん1000単語」を学習単語とします. 60000単語の差し替えはサブワードファイルをテキストファイルに変換してコピペでゴリ押しました.

他のパラメータは全て引き継いで学習を行いました. 1500エポック, 学習時間は約6時間でした.

霊夢さんモデルの会話例

霊夢さんデータで作成したモデルの会話です.

f:id:jungrave3:20200328214940j:plain

台詞がちゃんと霊夢さんになってる!人を責めたてる言葉に霊夢さんを感じます.

「こんにちは」は「ふーん、じゃあ割ってみようよ」と返したり, そのことを言及したら「......それ、どこで手に入れたの?」と返したり, 人のことを黒幕扱いしたり, 少し支離滅裂ですが霊夢さんと会話している気分になってきませんか?

 

考察

しかし, 問題点として以下が挙げられます.

  • 責めるような言葉
  • 会話がドッジボール
責めたてるような言葉

責めたてるような言葉は, ユーザーが敵と見做されているからです.

妖夢「これ以上踏み込んで、お嬢様に殺されても知らないわよ!」
霊夢「殺されるのはあんたじゃなくて?」

原作のストーリーは基本的に異変解決であり, 発言側が妖怪だったり, 道中を邪魔する妖精だったり, 最後の黒幕だったりします. そのため, 発言側=ユーザーが敵となるような対話データが多く存在し, これが責めたてるような言葉の要因となっています.

会話がドッジボール

会話がドッジボールのようになっているのは, 普通とはかけ離れた会話が多かったり, 単純にデータが少ないことが要因だと考えられます.

咲夜「また、お掃除の邪魔する~」
霊夢「あなた……は、ここの主人じゃなさそうね」

普通の会話だったら掃除を邪魔したことに対して応答するはずですが, 霊夢さんは紅魔館の主人に関する話題を提示しています. 一応ストーリーの流れとしては正しい反応なのですが, 対話botと会話する人が話す内容とはかけ離れています. このような対話データが多く含まれていて, 挨拶に対して挨拶で応答するようなデータが少ないため, 普通の会話をすることが苦手になってしまったと思われます.

単純にデータが少ないことは明白です. みりあちゃんの人は2700, 論文の黒柳徹子は12500なので, 霊夢さんデータ2000は正直少ないです. しかも少ないデータで1500エポックも学習させたので, 過学習が起きています. このことが普通会話苦手巫女に拍車をかけています.

そうは言っても, 過学習が起きる前の霊夢さんモデルは1つの返事しかしないクソリプbotだったので, 過学習させた方が返事のバリエーションが多く, こちらを採用せざるを得ませんでした...

まとめ

まとめると『原作の対話データは数が少ない上に対話データが特殊である』という結論に至ります. これによって日常的な応答を上手く学習できなかったと考えられます.

原作からデータを収集するのは限界があり, ちゃんとした返事をする霊夢さんを作成するなら, 二次創作SS等からデータを収集する必要がありますね.

色々書きましたが何はともあれ, 霊夢さんと会話できるモデルが作成できました!やったね!

 

 

次回予告

作成した霊夢さんと会話する方法を模索していきます!