特定の属性を持ったキャラクターのプロフィールを生成する

こんばんは。midzです。

※こちらは[創作+機械学習 Advent Calendar 2021](https://kivantium.hateblo.jp/entry/advent-calendar-2021) 7日目の記事です。（実は12日目に投稿してます）

6日目の記事はXiong Jieさんの[プレゼン動画自動作成プログラムの試作](https://www.youtube.com/watch?v=FykR1h9OOkQ)です。
8日目の記事はうすいさんの[『創作作家AI』というツールを作った＆使ってもらった話](https://zenn.dev/usui/articles/8a2316f2e93bd8)
です。

予告では、セリフ文の表情推定を行うと書いていましたが、Xiong Jieさんの６日目の記事[「プレゼン動画を自動的に作成するプログラムの試作」](https://www.youtube.com/watch?v=FykR1h9OOkQ)の要素技術として表情推定が紹介されていて二番煎じになりそうだったのでとりあえずキャラクターのプロフィールを生成に変えました。

同じネタを４年前にVAEを使ってやっています。
[VAEでキャラクターの設定を作る。](https://qiita.com/thetenthart/items/07b2988255b48cbaba57)

アップデート点は二点です。

1. 昨今は性能の上がった大規模事前学習モデルがあるので、そちらでより良いプロフィールを生成します。
2. 前回はガウス分布から文を生成したので生成されるプロフィールが制御不能でしたが、今回はある特定の属性を指定して生成できるようにします。


## データ

データはwikipedia からクロールしたキャラクターのプロフィールデータ＋αを使います。
ぶっちゃけ四年前にクロールしたのでどうやって前処理をしたのか忘れました。
ただ、プロフィールは約46000件ほどあり、学習には一応十分なのかなと思っています。


## モデル
最初[京大BART](https://nlp.ist.i.kyoto-u.ac.jp/?BART%E6%97%A5%E6%9C%AC%E8%AA%9EPretrained%E3%83%A2%E3%83%87%E3%83%AB)を使おうとしましたが、うまく学習できず、繰り返し文ばかり生成するようになったのでやめて、[sonoisaさんのT5](https://huggingface.co/sonoisa/t5-base-japanese)を使いました。

## タスク
以下のようにキャラクターの特性を表す言葉（属性語：例としては、髪型や性格、所属などを表す言葉）をSeq2seqモデル（T5）のEncoderに入力すると、プロフィール文をDecoderが生成するよう学習します。

![スクリーンショット 2021-12-11 1.41.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173499/1b08f55e-0aed-5cfc-22c0-b22b7e2aab3b.png)

属性語を取得するのは時間がなかったので自動で取得しました。
プロフィールデータにはどの作品（アニメ／漫画）のキャラクターなのかの情報も付随しているので、それを手がかりに属性語を抽出しようとしました。
例えば一つの作品にしか登場しない単語はほとんどの確率でキャラクター名などの固有表現なので属性語候補から外します。
逆にあらゆる作品に頻出するような単語は属性語とは言い難い単なる一般用語なのでそれも除去します。


> 相対出現比率 = (単語の総出現回数)/(単語の出現する作品数)


という謎の数値を設定し、
出現作品数が5以上でかつ、総出現回数が10以上、相対出現比率は10以下の名詞に限定するとなんとなく属性語っぽいのが取れるだろうという職人技的な決定方法で、属性語を抽出しました。

属性語の例は以下です。

> 高校生 凪 画家 学業 仕事 日数 留年 人

かなり荒い方法なので、本当は属性語辞書を作った方が良いです。

プロフィール文から上記属性語を抽出して、属性語を入力としてプロフィール文を出力とするタスクをモデルに学習させます。
名詞判定には[Juman++](https://nlp.ist.i.kyoto-u.ac.jp/?JUMAN%2B%2B)を使いました。

属性語は全部教えるとヒントが多すぎるのでランダムに6割除去して学習させました。
また、一つの作品にしか出ていないものは固有表現【人名】に置換しました。（人名じゃない可能性もあります。後これは結果的にはやらなくても良かった気がします）

## 結果
結果はこんな感じになりました。
ここで使っている正解プロフィール文とそこから抽出した属性語は実際のwikiデータのものです（学習データには入っていないもの）。
生成プロフィール文は属性語をT5モデルに入力した時に出力された文です。

>属性語:     クラスメイト 幼なじみ クラス 男子 人気
>生成プロフィール文: 【人名】のクラスメイトで幼なじみ。【人名】と同じクラスの男子に人気がある。
>正解プロフィール文:    【人名】のクラスメイトで幼なじみ。りぜるの親友でもある。おしとやかでクラスの男子からの人気もある。龍之介にひそかな想いを寄せている。

　

>属性語:     野山 親 女子 プロ 眠り 森 荊 道 女王
>生成プロフィール文: 野山で親に捨てられた女子大生。プロポーズされた【人名】を「眠りの森」に連れて行くが、その正体は荊の道の女王であった。
>正解プロフィール文:    野山野家長女。イッキ達の親のような存在。女子プロで活躍。元「眠りの森」で「荊の道」の女王。

　 


>属性語:     シン 国 皇子 メイ 異母 兄妹 細目 少年 族 出身 地位 不老不死
> 生成プロフィール文: シン国の皇子で、メイの異母兄。【人名】とは従兄妹にあたる。細目で小柄な少年。族長という地位に甘んじず、不老不死を謳歌する武人でもあり、その才覚と聡明さを兼ね備えている。
正解プロフィール文:    シン国第十二皇子。メイとは異母兄妹にあたる。初登場時15歳。後にグリードとなる。細目の少年。50万人からなるヤオ族の出身で、ヤオ族の地位向上と次期皇帝になるため、不老不死の法を求めてアメストリス国に不法入国してくる。ラッシュバレーで出会ったエルリック兄弟が不老不死の法(「賢者の石」)に通じていると直感し、兄弟を追いかける。ランファンとフーを護衛役として連れているが身勝手な行動でよくはぐれる。また、大食漢でもあり腹を空かせて行き倒れていることが多い。基本的に笑顔で陽気な性格。細目なのは、目つきが悪いことを気にして意図的にしている。その反面、出身部族代表として、多くの兄弟達と次期皇帝の座を競いあっており、シビアな一面もみせる。そして「王は民のために在る者」と統治者としての強い信念を持つ。後に「強欲」の賢者の石が注入された際には、シンの皇帝となることを公言してグリードに気に入られている。


ただ、このままだと、文の長さが属性語の数にかなり依存する形になってしまいました。
実用的には沢山属性を考えるのは面倒くさいので、少数属性を入力したらある程度長いプロフィールを生成するようになって欲しいです。

## 属性語拡張
プロフィール生成モデルに入力する属性語を拡張するためにトピックモデル（LDA）を使います。

トピックモデルは単語集合からトピックを推定することができます。
そして各トピックは単語の出現分布を表します。

図のように、属性語集合から各トピックに所属する確率（pA,pB,pC）を計算することができます。
そして各トピックは単語の出現分布を表しているので、トピック所属確率を積算して、足し合わせることで混合単語分布が作れます。
この単語分布から属性語をサンプリングして、属性語を増やします。

![スクリーンショット 2021-12-11 12.59.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173499/c9c962b1-e59e-bd37-d70c-80114f515d58.png)

gensimに、ldamodelが入っているのでそれを使います。
実装としては以下の感じです。

```python:lda.py
list_zokusei = ["妹","巫女"]
bow = dictionary.doc2bow(list_zokusei)
list_topic_tupl = lda.get_document_topics(bow)　#トピックIDとトピック所属確率のtuplを取得。
list_wordid, list_prob = [], []
for topicid, prob in list_topic_tupl:
    list_wordprob_tupl = lda.get_topic_terms(topicid,topn=100) #各トピックに対応する単語分布から出現確率上位100までの属性語と確率を取得。
    list_wordid += [term for term, prob in list_wordprob_tupl] #属性語の配列を取得
    list_prob += [prob for term, prob in list_wordprob_tupl] #確率の配列を取得

#属性語を10個サンプリング。最初に指定した属性語(list_zokusei)を足す。
list_kakucho_zokusei = list_zokusei+[word for word in set([dictionary[word_id] for word_id in random.choices(list_term, weights=list_prob, k=10)]) if word not in list_zokusei]
#最初に指定したlist_zokuseiが重要なのでプロフィール文の前半に出てきて欲しいが、同じ順番だと同じ文が生成されてしまうので、最初の５単語の順序だけ入れ替える。
first_half,last_half = list_kakucho_zokusei[:5],list_kakucho_zokusei[5:]
random.shuffle(first_half)
list_kakucho_zokusei = first_half,last_half
```

### 属性指定してプロフィール文生成
例えば、「小学生 ツインテール」という２単語を入力すると、以下のように属性語を拡張し、プロフィール文を出力することができます。

**拡張属性語**:     小学生 ツインテール 2 両親 萌え ブラジル 成績 パン 少女 性格 双子 大学
**生成プロフィール文**: 小学生の頃はツインテールだったが、中学2年の時にツインテールになった。両親が萌え系ブラジル人のため成績も優秀でパンツ一丁の少女だが、性格は姉と瓜二つである。双子の妹・【人名】がいる。大学受験に失敗して中退し、実家に居候している。

**両親が萌え系ブラジル人のため成績も優秀でパンツ一丁の少女だが**

すごいキャラクターができました。
まず、**萌え系ブラジル人**とは何なのか。
そして**両親が萌え系ブラジル人**だと **成績が優秀でパンツ一丁になる**とはどういうことなのか。
常人には理解できないロジックです。


**拡張属性語**:     歴史 ツインテール 弟 暴力 小学生 11 組 小学校 進学 行方 成績
**生成プロフィール文**: 歴史教師。ツインテールの姉がいる。弟に暴力をふるう小学生11人組の末っ子で、小学校進学後に行方不明となる。成績優秀だがおっちょこちょいな一面もある。

**拡張属性語**:     楽観 小学生 ツインテール 兄 両親 ゲーム 姉 進学 新聞 性格 外見 双子
**生成プロフィール文**: 楽観的な小学生。髪型はツインテール。兄が両親を亡くしたため、ゲーム好きの姉に育てられた。高校進学後は新聞部所属となる。明るく前向きな性格で、外見も双子とは思えないほど大人びている。

双子だと一般的には外見が子供っぽくなるらしい。
ロジックが謎な文が割と生成されますが、それが却って味を出している気がします。

次に、「巫女 妹」で属性語拡張して、プロフィール文を生成すると以下のような感じです。

| 拡張属性語 | 生成プロフィール文 |
|:-:|:-:|
|美貌 テクノロジー 妹 巫女 人間 出来事 主人公 コンピュータ 出演 読者 作 部分  | 【人名】の美貌とテクノロジーに魅せられて、【人名】の妹となる。巫女として人間界で暮らしていたが、ある出来事をきっかけに主人公を「コンピュータ」と呼ぶようになる。『.C..』には未登場だが、『.C..』『.C..』にも出演している。読者からの人気も高く、作中ではヒロイン的な部分もある。  |
|  着物 妹 巫女 いたずら 卵 見学 樹 出世 実験 中々 アニメ  |   【人名】の着物の妹で巫女。【人名】にいたずらをして卵を産ませてしまったが、その卵から孵ったメフィスト達と仲良くなり、一緒に実験台になったりもした。しかし中々上手くいかず、結局は失敗に終わる。アニメでは未登場。 |
| バレンタイン システム 妹 巫女 面 出来事 葬式 シーン プリンセス 野望 ゼロ 干渉  | バレンタインデーのシステムを操る妹。巫女としての能力は高いが、内面はかなり幼稚なところがある。【人名】との出来事で葬式に参列するシーンがあるが、プリンセス・オブ・エヴァンゲリオンという野望を持っており、ゼロへの干渉も厭わない。  |
|  巫女 数 ライト 妹 悪役 言葉 卵 見学 樹 実験 干渉 |  【人名】の巫女。数年前、ライトの妹である【人名】を殺そうと目論むが失敗し、悪役の言葉に騙され卵を産むことになってしまう。見学に来た樹たちの実験には干渉せず、自らの意思で参加する。  |
| 1 妹 園児 巫女 孫 言葉 主人公 勇者 作者 レオ 戦闘| 【人名】の1歳下の妹で、幼稚園児。巫女の孫である【人名】を「お兄ちゃん」と呼び慕っている。言葉は話せないが主人公に勇者の言葉を伝えることができる。作者によるとレオと戦闘中に出会ったらしい。|
|妹 巫女 ライト システム いたずら 4 主人公 着物 出演 人称 彼女|【人名】の妹。巫女装束(ライトシステム)を身に纏い、いたずら好きの少女。4巻では主人公と共に着物姿で出演した。一人称は「あたし」で、彼女も語尾に「〜ですわ」とつけて話す。|
|一方 巫女 事 撤回 妹 主人公 葬式 出世 勇者 作| 【人名】の一方的な理解者で、【人名】が巫女を辞める事を撤回した事に憤慨する。妹は主人公の葬式に出席した事で出世し勇者になったという過去を持つ。本作の主人公でもある。|
|巫女 手 妹 鎌 美貌 とどめ 主人公 シーン 自分 姉 作 戦闘|  【人名】の巫女。【人名】に手を貸すが、その前に妹を殺されたという過去を持つ。武器は鎌で、美貌と強さを兼ね備えている。とどめとして主人公と共に戦うシーンがあるが、自分より強い姉がいると思い込んでおり、作中でも戦闘力はかなり高い。|


### ランダムにプロフィール文生成

トピックモデルは生成モデルなので、「無」から単語を生成することもできます。
そもそも属性を思いつかない場合は、「無」からキャラクターの属性語を生成し、そこからプロフィール文を生成することも可能です。

| 属性語 | 生成プロフィール文 |
|:-:|:-:|
| 一流 戦法 田中 周囲 顔 172 刃 躊躇 マフィア 外見  |  一流の戦法で田中を追い詰めるも、周囲に顔が知られて敗北する。身長172cmと小柄な体格だが刃は鋭く躊躇なく切り刻むことができる。マフィア風の外見とは裏腹にかなりの毒舌家である。 |
|戦闘 勝 黒人 頭 体 生活 もの 飴 彼女 カメラマン   | 戦闘が得意な男勝りの黒人女性。頭は良いが体が弱く、生活に支障をきたすものには容赦がない。飴が好きで、彼女もカメラに収めようとしている。  |
| 一 料理 11 星 ファイター 歌 曰く 禁断 力 話  |  第一話から登場。料理が得意な11歳の星型ファイター。歌が好きで、【人名】曰く「禁断の力を秘めている」とのこと。アニメ第1話にも登場。 |
| 作 場 彼 教師 信頼 現場 容姿 隊 天然 ピアノ  |  本作の主人公。場を和ませるのが得意で、彼の教師としての信頼も厚い。現場に居合わせた【人名】と仲が良く、よく一緒に遊んでいる。容姿端麗・成績優秀・運動神経抜群であり、生徒や警官隊からも一目置かれている。かなりの天然ボケだが、ピアノは上手い。 |
| 古代 自動車 巻 離 世話 学 紆余 疑惑 飼い主 戦  | 古代自動車の開発者。第1巻で離乳食の世話を学に依頼し、紆余曲折の末に愛玩犬疑惑が浮上する。【人名】とは飼い主を巡って争奪戦を繰り広げている。  |
| 上空 彼 最期 宿敵 バランス 幼馴染 漢字 力 とき ところ  |   【人名】の上空で【人名】と対峙し、彼の最期を看取る。彼にとっては宿敵とも言える存在だが、バランスが取れていないことを気にしており、幼馴染である【人名】に漢字の力を教えてやろうとしているときも躊躇いを見せているところがある。 |
|  機 妨害 剣 剣士 傷跡 神 捨て 体調 バンド 字  |  【人名】機と【人名】機を妨害した剣士。左頬に傷跡がある。神使いだが、捨て駒として体調を崩している。髪はヘアバンドで整えている。字が下手。 |
| 中心 ライバル 場 外見 生活 3 曲 増大 アニメ サード  | 【人名】の中心人物で、【人名】をライバル視している。場に溶け込みやすい外見をしているが、生活力は高い。第3期では曲ごとに攻撃力が増大し、アニメではサードステージでも強さを見せている。  |
|  オペレーター の 助け 性格 流 うえ 3 国語 人物 2 |    オペレーター。オペレーターになるのが夢で、困っている人に助けを求めている。性格はおっとりしていてマイペースだが、流暢な英語を話せるうえに3ヶ国語も話せるなど国語力が高い人物。2年組在籍。|
| スーツ 少佐 ちゃ 誰 隣 試験 思い出 執着 制約 実力 | 黒いスーツを着ている少佐。おちゃらけた性格で誰とでも仲良くなれるが、隣に座る【人名】には頭が上がらない。試験の思い出作りにも執着しており、限られた時間の中でしか実力を見出せずにいる。  |
| ワンピース 神 妻 学校 92 感謝 原作 明 心 奴  |  ワンピースの神。妻と共に学校に通っている。92話で【人名】と再会し、感謝の言葉を贈った。原作では「明朗快活な心優しい奴」として描かれているが、アニメでは逆になっている。|
| 街 エピソード 前 性格 娘 長身 担当 成長 伝達 憎悪  |  【人名】が街で出会った少女。【人名】に恋心を抱いており、そのエピソードを話す前に彼女に告白する。明るく前向きな性格の娘だが、長身なためか戦闘能力は低い。担当教科は算数。成長期を迎えており、伝達系科目も習得している模様。【人名】に対して強い憎悪を抱いている。 |
| 作 言動 誤解 壊滅 行動 全体 特 二 特訓 幼馴染  |  本作の主人公。【人名】に好意を抱いているが、その言動から周囲からは誤解されがちである。壊滅的な打撃を受けながらも行動力は高く、全体のバランスも良い。特訓後は幼馴染のアヤカと仲良くなる。 |
| 室 姿 山吹 召喚 トライデント 時代 系 購入 学園 寮  |   【人名】の執務室に居候している少女。【人名】と瓜二つの姿をしており、山吹が召喚した「トライデント」を操る。学生時代は魔術系で遊んでいたらしく、を購入して学園の寮に住み着いている。 |
| アニメ 本名 際 変化 12 男性 セミロング 庶民 ひと  |  一人称は「あたし」。アニメでは本名不明だが、【人名】に呼ばれた際に名前が変更された。12歳くらいの男性。セミロングの髪を後ろで束ねている。庶民のひとり暮らしをしているが、本人はそのことに気づいていない。 |
|アニメ 徴 ミス 教 道 小説 諍い 100 職人 大和   | アニメ『新世紀エヴァンゲリオン』に登場。【人名】徴のミスで教導学校の教師をクビになり、その道に進もうとするが失敗する。小説版にも同名の人物が登場し諍いを起こしている。100年前は職人として大和撫子をしていたらしい。  |
|解散  星 情報 救出 戦闘 牢 位置 オス 火山|【人名】解散後、【人名】が星から情報を引き出そうとしていた際、救出に現れた。戦闘中に牢に閉じ込められてしまい、その位置はオス・メス・メスの3つの火山であることが判明する。|
|会長 彼 互 批判 際 誕生日 判明 隙 役 途中|【人名】会長。【人名】の親友で、彼とは互いをよく批判し合っている。その際、彼の誕生日が判明しており、その隙を突かれて副会長の座を奪おうとしたが、途中棄権した。|
|役割 中 雰囲気 風 雑誌 年度 心 二 兄| 【人名】の役割を担っており、【人名】の中では比較的落ち着いた雰囲気を持つ。風紀雑誌の編集も担当している。前年度の年度末で心に深い傷を負っているが、二人には兄のように慕われている。|


## 感想
一部実際にいそうなキャラクターのプロフィールになったものもある気がしますが、**このキャラクターで物語を作ったら面白そう**と思えるのは生成できてないです。キャラクターの設定だと意外なものが面白いので、言語モデルの流暢性は却って下げた方が面白くなる可能性はあります。

## 解決できそうな課題
**課題1.** 今回属性語の作り方が雑だったので、もっと精密に作ると良いモデルになると思います。
**課題2.** トピックモデルは単語を生成しますが、語順は適当なので、最適な語順で単語を出すというのができません。語順も含めて良い感じに属性語を出力するのはVAEのseq2seqモデルを使うとできそうです。そもそもT5の学習時に属性語の語順をシャッフルするという手段もありそうですが。

## 難しそうな課題
**課題1.** 属性語はプロフィール文に含まれている単語から抽出していますが、実際には当該キャラクターの属性であることが望ましいです。単に単語を含んでいるだけだと「ツインテールの妹がいるキャラクター」と「ツインテールの妹キャラクター」の区別がつかなくなってしまいます。とは言え、この辺を区別する方法は現状思いつかないです。地道なアノテーションは面倒くさいですし。
**課題2.** 物語には登場人物は複数いるので、人物間の関係性を考慮した上で、プロフィールが生成できると更に良いものができる気がしますが、これはやり方がすぐには思いつかないですが。


##コード
コードは以下にあります。学習済モデルから生成ができます。
Google DriveのディレクトリMyDrive以下にgit cloneしてGoogle Colabで動かす前提でコード(generate_t5.ipynb)を書いてます。
https://github.com/ashwatthaman/ProfileGenerator.git
今学位論文執筆中で時間が取れないですが、落ち着いたらモデルを改善したいです。