TransformerとSentencepieceでなんJスレタイ生成器を構築する(3回目)
なんJスレタイを生成するシリーズ3回目です。
おさらい
なんJスレタイ生成とは
ニューラルネットの文書生成を利用して、なんJのスレッドタイトルのような文書を生成します。
過去のシリーズ
前回(2回目)のおさらい
- 文書生成を行う生成器Generatorと、その文書の評価器Evaluatorの2つのモデルを利用する。
- Generatorは過去のなんJスレタイを利用して学習し、Evaluatorは真のスレタイかGeneratorが生成したスレタイかを分類するよう学習する。
- Generatorでたくさんのスレタイを生成し、Evaluatorが高スコアを出したものだけを出力する
後で似たアイデアの論文*1を見つけましたので、本稿ではその表記に合わせて生成器をGenerator・評価器をFilterと呼びます。
3回目のなんJスレタイ生成
今回と前回(2回目)との差分
大きく3つの点を変更しています。
- 文書生成にTransformer(のEncoder)を利用する(前回:多層GRU)
- 文書のトークナイズにsentencepieceを利用する(前回:MeCab + NEologd)
- 評価器(Filter)にCNNを利用する(前回:多層Bidirectional GRU)
Transformerによる文書生成
前回は文書生成に多層GRUを利用していましたが、流行りのTransformerのほうが文書生成も強いのでは?と思って導入しました。
TransformerはもともとSeq2Seqのモデルなのでその前半のEncoder部分だけ利用します。 Masked Multi-Head Attentionを適用したEncoderを用いて、1文字前までのトークンシーケンスから次のトークンを予測します。
※詳しくはgithubのソースコードを参照してください
sentencepieceによるトークナイズ
前回はMeCab+NEologdを利用していましたが、Sentencepiece : ニューラル言語処理向けトークナイザ - Qiita によると機械翻訳においてはMeCab+NEologdよりsentencepieceを使うほうが良いそうです。
機械翻訳も文書生成タスクの一種であることを考えると、単純な文書生成においてもsentencepieceの方が良い気がしたので採用しました。
sentencepieceを使うことで2点良いことがありました。
特に後者が強力で、今回数字も特殊トークン化しなくても違和感のない文書を生成できました(後の生成例参照)。 昔は何でも形態素解析をしていた印象ですが、今の主流はsentencepieceなのでしょうか。
評価器(Filter)による選別
前回は多層Bidirectional GRUを利用していましたが、今回はより精度が良かったCNN3層による文書分類器で実装しました。 他にもいくつかモデルを試しましたが、イマイチ精度が良くなりませんでした。ここは改良の余地がありそうです。
実験
※ 前回同様、一部ブログに書くにはアレな文書も生成されていたので選抜しています。 選抜していないものを見たい方はこちら
※ 文書の開始・終了を表すトークンは表示していません、<kusa>
は「w」が2つ以上連打されることを意味する特殊トークン、スレタイの後ろの数値はFilter
の評価値です。
制限なしで生成したスレタイ
「はん」を「さ」に変えて予測変換が一致する唯一の球団 0.866355836391449 ワイの彼女「ふにゃふにゃふにゃふにゃゅ」ワイ「ふゅうごゅ?」 0.8488953113555908 コミケのオリジナルグッズガチでクオリティが高そうな件について<kusa> 0.814984917640686 コンビニでいつも悩んでいるオヤジにブチ切れて 0.8129239082336426 dmmでゲームやってるとやってるニキおる? 0.8119136095046997 ワイ「今日はリリースさてそろそろでしょ」上司「ちょっと待って!それ面白いやつくわ!」 0.8110376596450806 【悲報】古参オタクに意見を「お前のスレ」と書き込まれたこともなく何故か開く 0.8091040849685669 ワイ草野球で143キロ計測 0.8077213764190674 禁書の七人に一人無能がおるよな 0.8069646954536438 【画像】「私はロボットではありません」と主張する男がsnsに広がり始める 0.801096498966217
Prefix「三大」で生成したスレタイ
三大購入特典で必ず発送された商品「発送」「6時」「前売り」 0.8469894528388977 三大モテない奴は難しい言葉「努力以上の努力が必要」「努力は必ず責め」「代表」 0.8417271375656128 三大怖さがよくわからない単語 「マクナル」「マクド」 0.8301395177841187 三大気になっててくる車『山登る』『砂降り』『雪の降る街』『雪』 0.8113009333610535 三大深夜12時にありがちな事「伸びてるレス」「実は内容が急に伸びていた」 0.8103548884391785 三大理解不能な「若干滑ったな」「それでも否定コメしたらええんや」あとひとつは? 0.8086734414100647 三大アフィ大喜利本 「 頭 く い な っ て の い」 0.8043513894081116 三大森野メジャーで獲らない球団「日ハム」「ヤクルト」あと1つは? 0.8020216822624207 三大彼女連れてきた車「砂漠」「まや」「ゲド戦」 0.7955830097198486 三大なんかあるミステリー小説『ダディ』『ゲームの面別』 0.7863622903823853
Prefix「【なぞなぞ】」で生成したスレタイ
【なぞなぞ】平常時川で橋の上に座る? 0.897125244140625 【なぞなぞ】鉄板志の鉄が1つだけ買えるポジションってなーんだ? 0.8656874895095825 【なぞなぞ】屁がこえることが出来ない奴と良い法律で同居できる問題 0.8614312410354614 【なぞなぞ】2chではすぐngにするとその程度の書き込みができる人ってなーんだ? 0.8599361181259155 【なぞなぞ】『校長部類』と「先生」しか居ない説 0.8505473136901855 【なぞなぞ】トロースでかい野菜ってなーんだ? 0.8492956757545471 【なぞなぞ】野菜がパンしか食えない食べ物ってなーんだ? 0.8488330841064453 【なぞなぞ】女の子が大好きな男をデートに誘うために行くものってなーんだ? 0.8411668539047241 【なぞなぞ】痒くもなく終える前に食べるものはなんでしょう? 0.8402188420295715 【なぞなぞ】「悪の趣味」を使って文章を作りなさい 0.8386388421058655
Prefix「ゲーミング」で生成したスレタイ
ゲーミングpc買うのに利用者数分金払ってるやつ<kusa> 0.8862704038619995 ゲーミングpcとwindowsとドラえもんってどっち選ぶんや? 0.8519608974456787 ゲーミングpc買いたいからオススメのオススメ教えてクレメンス 0.8464795351028442 ゲーミングpcのチャクチャチャのiphone買えばええ? 0.8367217183113098 ゲーミングpcでpubg配信した結果 0.8358103632926941 ゲーミングpcをsfcとやりたいんだけど何か良いゲーム教えて 0.828700602054596 ゲーミングpcの探しが遅いニキおる? 0.8259073495864868 ゲーミングスマホ買おうと思うんやけどオススメのゲームない? 0.8258553743362427 ゲーミングpc使いにいったら気づいた事<kusa> 0.8251885771751404 ゲーミングpcのパーツ1tb使ってるやつおる? 0.8218405842781067
雑感
- 全体的に前回より精度が良い文書生成ができたと思います。
- 本当はちゃんと比較をしたかったのですが、色々変えて直接比較ができそうになかったため諦めました。
- パラメータの問題かもしれませんが、前回よりややユーモアのある文書が生成できているようにも見えます
- 数が入る文書(
ワイ草野球で143キロ計測
、禁書の七人に一人無能がおるよな
)も違和感ない数値を使った文書を生成できています(※両者とも学習データに同じ文書は存在せず、似たものは存在する) - sentencepieceの影響?
- 前回と比べて
UNK
トークンの生成率が大幅に下がりました。 - 若干文法がおかしい文書が生成されやすくなった気がします。
鉄板志
のような(おそらく)造語を使った文書が生成されるようになりました。
- 前回と比べて
参考にしたページ
(最終版)Karabiner-Elementsで「EISUU to Escape(入力ソースが日本語以外の場合のみ)」を実現する
以前に紹介したKarabinerの設定「EISUU to Escape(入力ソースが日本語以外の場合のみ)」をKarabiner-Elementsを使って実現する方法について、最終版と思える設定を実現できたため紹介します。
以前の記事ではComplex Modificationの変数を使ったゴリ押し設定で実現する方法を紹介しましたが、Karabiner-Elements 11.2.0でinput_source_if
、input_source_if
が追加されたため完全再現ができるようになりました。 *1
設定方法
今回もComplex Modificationの設定で実現をします。
.config/karabiner/assets/complex_modifications/EisuuToEscapeInTerminal.json
に以下のようなjsonファイルを配置します。
frontmost_application_if
のconditionは特定のアプリケーションを利用しているときだけに発動させたいときに設定します。自分の環境に合わせて適宜変更してください。
{ "title": "Terminal等で入力ソースが日本語以外の場合に英数をEscapeに変更する", "rules": [ { "description": "Terminal等で入力ソースが日本語以外の場合に英数をEscapeに変更する", "manipulators": [ { "type": "basic", "from": { "key_code": "japanese_eisuu", "modifiers": { "optional": [ "any" ] } }, "to": [ { "key_code": "escape"} ], "conditions": [ { "type": "input_source_unless", "input_sources": [ { "language": "^ja$" } ] }, { "type": "frontmost_application_if", "bundle_identifiers": [ "^com\\.apple\\.Terminal$", "^org\\.vim\\.", "^com\\.googlecode\\.iterm2$", "^com\\.microsoft\\.VSCode$" ] } ] } ] } ] }
次に、Karabiner-ElementsのPreferenceから「Complex Modifications」→「Add rule」→「Terminal等で入力ソースが日本語以外の場合に英数をEscapeに変更する」を有効にします。
以上で設定は完了です。
まとめ
非常にシンプルな設定ファイルになって満足しています。
自分以外にJSキーボード+英数をEscにする派は見たことがないですが、お役に立つ方がいたら嬉しいです。
*1:実は以前の記事を公開した時点でKarabiner-Elements 11.2.0がリリースされていたのですが、気づいていませんでした、、
【Swift】WKWebViewを使ってパワーポイントをPDFに変換する
以前開発したPDF Margin Adjusterについて、パワーポイントのファイルを直接読み込めるようにしてほしいという要望を頂きました。
なんとかそれらしいものを実装できましたが、意外と手こずったのでハマった部分などをまとめました。
前回の記事:PDFの余白を調整するだけのアプリを作りました - のどあめ
apps.apple.comソースコード
今回はパワーポイントからPDFに変換する部分だけを切り出したコードを公開しています。
ハマり1:パワーポイントファイルのUniform Type Identifier(UTI)がわからない
iOSアプリはファイルの読み込み・他アプリから連携できるファイルをUTIで指定します。
今回はパワーポイントファイルの読み込みを対応させるので、対応するUTIをInfo.plistに記載する必要がありました。
UTIの公式は System-Declared Uniform Type Identifiers のようなのですが、サードパーティで定義されたUTIは網羅されていないようです。
調べると com.microsoft.powerpoint.ppt
org.openxmlformats.presentationml.presentation
あたりはすぐ見つかるのですが、
これだけでは開けないファイルも少なからずあります。
GitHubでUTI名で検索して周辺の類似したものを取得するという方法で、
以下のものを追加しました。
これで手元にあったパワーポイントファイルは開けるようになりましたが、すべてを網羅できているかは不明です。。
com.microsoft.powerpoint.ppt com.microsoft.powerpoint.pot com.microsoft.powerpoint.pps com.microsoft.powerpoint.openxml.presentation com.microsoft.powerpoint.openxml.slideshow com.microsoft.powerpoint.openxml.template org.openxmlformats.presentationml.presentation org.openxmlformats.presentationml.slideshow org.openxmlformats.presentationml.template
ハマり2:WKWebViewからPDF変換のあれこれ
PDF変換方法
UIViewからPDFに変換する方法はいくつかあるようですが、 一部の方法ではページの後半が描画されなかったりして苦戦しました。
結論としては UIPrintPageRenderer
を使ってPDFデータを取得する方法でうまくいきました。
実装は このあたり です
WKWebViewで開いているパワーポイントのスライドサイズを取得する
UIPrintPageRenderer
は印刷用のレンダラーなので用紙サイズ(=1スライドのサイズ)を指定する必要があります。
以下のブログを参考に、描画後の innerHTMLからスライドサイズが入っている要素をevaluateJavascriptで取得します。
実装は このあたり です
WKWebViewのマージン削除
WKWebViewでパワーポイントファイルを開くと、top・left・ページ間にマージンが発生します(下図)
UIPrintPageRenderer
を使ってPDFファイルに変換すると、このマージンも正確に描画されてしまうため、実際のスライドサイズとずれてしまいます。
そこでWKWebViewConfiguration
を使ってファイル読み込み後にCSSを適用し、このマージンを削除します。
実装は このあたり です
WKWebViewのロード後1秒待つ
WKWebViewがパワーポイントファイルを描画するときに、先に一部が描画されて少し時間が経ってから全体が描画されることがあります
全体が描画されたかどうかを判定する術はなさそう(例えばisLoadingは一部が描画された時点で false
になる)ですが、一部だけ描画されたタイミングでPDF変換をすると、その一部だけが描画されたPDFになってしまいます。
今回はファイルをロードしてから1秒待つという残念ロジックでこれを回避しました。
(GitHubに載せたコードでは省略しています)
用紙サイズをスライドサイズの0.8倍にする(おまじない)
先程取得したスライドサイズを UIPrintPageRenderer
のpaperRect
printableRect
に指定するのですが、これをそのまま指定すると何故か用紙サイズがちょっと大きくなります。
色々試しましたが、paperRect
やprintableRect
に用紙サイズ * 0.8を入れることで安定してPDFファイルに変換することができました。
理由は不明で完全におまじない状態です。。
実装は このあたり です
まとめと感想
WKWebViewからPDFに変換する方法と、そのハマりポイントをまとめました。
PDF変換周りは挙動が怪しいところがあるので、もう二度とやりたくないです。。
謎のおまじないを残してしまったのは残念ですが、これ以上時間をかけても意味がないので放置しました。
どなたかわかる方がいらっしゃいましたらご指摘いただけると助かります。
SwiftUIを初めて触ってみましたが、結構良さげな雰囲気がありますね。
iOSだけだったらReactNative+Expoなどを使わずとも簡単にアプリ作れそうです。
AlphaZeroで最強のターン制ぷよぷよAIを作る!
最近こちらの本を読みました。
丁寧にAlphaZeroの解説が載っているので、AlphaZeroに興味ある方にはおすすめです。
この本を読んでゲームAI作りたい欲が湧いてきたので、
今回はAlphaZeroでターン制ぷよぷよ(細かいルールは後述)の最強AIの学習にチャレンジしました。
※今回はかなり本を参考に実装したためソースコード公開はしません
結果(CNN モデル VS ResNetモデル)
先に結果だけ見せるとこんな感じです。
自己対戦だけで初代ぷよぷよの定石(早めに中連鎖する)を学習することができました!
AlphaZeroとは
AlphaZero自体については色々な方の解説記事が沢山あるので割愛します。
例えばブレインパッドさんの記事がおすすめです。
強化学習入門 Part3 - AlphaGoZeroでも重要な技術要素! モンテカルロ木探索の入門 - - Platinum Data Blog by BrainPad
AlphaZeroでターン制ぷよぷよのゲームAIを学習する
AlphaZeroは二人零和有限確定完全情報ゲームのためのアルゴリズムです。
今回はぷよぷよをターン制にした二人零和有限確定ゲーム(完全情報ではない)を考え、これを対象としました。
ぷよぷよを参考にしたターン制落ち物ゲームは過去いくつか考えられているようです。
今回はpuyoloftさんの連棋(rengi)というゲームを参考にルールを決めました
「連棋(Rengi)」ターン制落ち物対戦パズルゲーム
今回扱うターン制ぷよぷよのルール
実装の都合などで本家から色々改変していますが、 基本的にはフィールドが小さい初代ぷよぷよだと思って貰えればOKです。
- 基本ルール
- プレイヤー2人
- ターン制で同時着手方式(※この要素で完全情報ゲームでなくなっている)
- ぷよは4色+お邪魔ぷよ
- お邪魔ぷよ以外の同色ぷよが4つ以上つながると消える
- お邪魔ぷよは隣接するぷよが消えたとき一緒に消える
- フィールドは横6列×縦9段(※計算時間短縮のため本家より4段小さい)
- 左から3列目、下から8段目が埋まると負け
- N連鎖するとN+1ターンの待状態になる
- ツモ(組ぷよ)
- 2個1組のツモ
- ツモがあるときはどこかに置かなければならない
- ツモ固定
- 未来のツモが既知 (※AlphaZeroのアルゴリズム上そうなる)
- 点数計算・お邪魔ぷよ
- 相殺なし(初代ぷよぷよルール)
- 点数計算、落ちてくるお邪魔ぷよの数は本家と同じ
- お邪魔ぷよがおちる場所は(ターン数シードの)ランダム
モデル(デュアルネットワーク)
AlphaZeroは「自己対戦→棋譜を作成→モデルを学習」を繰り返してゲームAIの学習を行います。
AlphaZeroのモデルには、現在のゲーム状態を入力・ (policy, value)の2値を出力とするデュアルネットワークを使用します。 ここでpolicyは次の手の確率(行動数次元の出力)、valueはゲーム状態の評価値(1次元)みたいなイメージです。
(詳しくはAlphaZeroを解説している記事を参照してください)
行動数
行動数はツモを落とすパターン+パスの23通りです。
そのため、モデルのpolicy出力は23次元です。
ツモを落とすパターン | 行動数 |
---|---|
○▲ | 左端〜右端までの5通り |
▲○ | 左端〜右端までの5通り |
○ ▲ |
左端〜右端までの6通り |
▲ ○ |
左端〜右端までの6通り |
ツモなし | 1通り ※自分・相手の連鎖待ちなどでツモがない場合はこの行動を取る |
ゲーム状態のテンソル表現
モデルの入力にするためにターン制ぷよぷよのゲーム状態をテンソルで表現します。
今回はゲーム状態を(9 × 6 × 49)次元で表現します。
ここで(9 × 6)はフィールドのサイズで、49のchannelに以下の要素が含まれます。
1.フィールドの状態
- ぷよが存在する位置は1, それ以外は0のテンソル
- {自分, 相手} × {ぷよ 5種 ※お邪魔ぷよ含む}の10 channel
2.ツモ色を(9 × 6 × channel数)にone-hotエンコーディングっぽくしたもの
- {自分, 相手} × {現在, Next} × {ツモ個数 2個} × {ぷよ 4種} の32 channel
- 例えば、現在ツモが「●●」のときは、以下のような(9 × 6 × 8)次元のテンソルになる。
3.上と同様にその他の情報を(9 × 6 × channel数)にエンコーディングしたもの
- 以下の7channel
- 経過ターン数 1 channel
- 次ターンに落ちるおじゃまぷよの数 {自分, 相手} の 2channel
- 連鎖が終わったら落ちるおじゃまぷよの数 {自分, 相手} の 2channel
- 連鎖待ち時間 {自分, 相手}の 2 channel
モデル
今回はTensorflow 2.1のtensorflow.keras
で2つのモデルを試しました。
本家AlphaZeroに比べるとかなり軽量なモデルです。
モデル名 | ネットワーク詳細 |
---|---|
CNNモデル | Conv2D→Conv2D→MaxPooling2D→DropOut→Flatten→Dense |
ResNetモデル | Conv2D→BatchNormalization→ResidualBlock※*4→GlobalAveragePooling2D→Dense |
※ResidualBlockは Conv2D→BatchNormalization→Conv2D→BatchNormalizationで1ブロックになっています。
※ResidualBlockの出力は、入力と最後のBatchNormalization出力の和になる
実装の工夫
自己対戦では現在のモデルのPredict値を使ってモンテカルロ木探索します。
そのため、普通に実装すると1手ごとにモンテカルロ木探索数だけ直列にPredictが走ってしまいます。
そこで計算時間短縮のために以下の工夫を行いました。
multiprocessing
で自己対戦の並列化- 自己対戦は独立なので並列に行っても問題ありません
- Tensorflowが絡む部分(モデルのPredictionなど)はメインプロセスで行います。
- 自己対戦プロセスからPredictionの入力(ゲーム状態)を
Queue
でメインプロセスに送り、メインプロセスはPrediction結果を後述のキャッシュに格納します。
- (ゲーム状態, モデルのPredict値)、(ゲーム状態, モンテカルロ木探索結果)のキャッシュ
- サーバープロセスのDictにPredictionを保存し、自己対戦プロセスで使い回します。
- multiprocessing --- プロセスベースの並列処理 — Python 3.8.2 ドキュメント
正確に測っていませんが、体感で10倍以上の高速化になっていると思います。
(もうちょっと賢い方法はありそう)
実験
パラメータ・学習時間
パラメータ | 備考 | |
---|---|---|
エポック数 | 100 | 自己対戦〜モデル学習のサイクル数 |
1エポック数あたりの自己対戦数 | 150 | AlphaZero本家は25,000回。学習時間の削減のためかなり少なめ。 |
1エポック数あたりの時間 | 約60分 | 高速化した割には遅い気がする。。 |
うちモデル学習時間 | 約1分 | 学習のほとんど自己対戦している時間。GPUなんていらなかった。 |
結果(再掲)
左がCNNモデル、右がResNetモデルで対戦した結果です。
CNNモデルが3連鎖ダブル、ResNetモデルが4連鎖でResNetモデルの勝ちです。
ターン制だけあってちょっとアグレッシブな積み方をしていますが、 いらないぷよを端に避けたりしいて面白いですね。
考察
- 自己対戦の時間
- 自己対戦ではPredictionのキャッシュがあるので同じような展開が多いと学習時間が短くなります。
- 学習時間はエポックが進むに連れて単調に落ちると思ったのですが、新手を見つけるとキャッシュに当たらなくなり学習が遅くなるようです。(下図)
- 学習時間を見ることでブレイクスルー的なものが確認できるかもしれません。
- 学習時間が増えている「エポック数40-50」、「エポック数80-100」の差を見てみました。
エポック数40と50の差
エポック数40と50では、3連鎖 vs 3連鎖(2連鎖目ダブル)という違いが出ています。
中盤で置くぷよの位置が変わったようです。
エポック数80と100の差
エポック数80と100では、3連目での消えるぷよが1個増えています。
その結果、お邪魔ぷよが落ちたときにギリギリ1手差で勝つようになりました。
まとめと感想
- AlphaZeroを使うことでターン制ぷよぷよのゲームAIを作ることができました
- ツモが固定問題
- ツモ固定のため、対戦というより中連鎖の最適解を探すモデルになっていたようです。
- 実際相手の盤面をほとんど見ないモデルになっていそうでした。
- ツモを固定にせず学習すれば汎用的なぷよぷよAIになるかもしれません。
- 自己対戦時間がネック
- AlphaZeroにおいて自己対戦をいかに早くするかが勝負です。
- モデルの学習よりモンテカルロ木探索やゲーム自体の最適化が重要だと思います。
- もちろんお金があればサーバを横に並べて力で殴っても良いと思います。
- 過学習問題
- 早々に過学習してしまう場合があり、AlphaZeroを安定して学習するのは難しそうです。
- 例えば1〜5エポックでカエル積みを覚えてしまうと、何エポック経ってもそこから抜け出せないことがありました。
- 1エポック数あたりの自己対戦数を増やすと解決するかもしれません。
- matplotlibでアニメーション作るのが想像以上に便利でした。
Tensorflow 2.0でなんJスレタイ生成器を構築する(2回目)
前回なんJスレタイ生成器を作る記事を書きましたが、 これには以下の問題がありました。
- 当時は
train_on_batch
の存在を知らずにゴリ押しでモデル学習をしていた - 生成されるサンプルに多様性がなかった
今回Tensorflow 2.0を勉強するついでになんJスレタイ生成器を作り直してみました。
注意
NLPもニューラルネットもよくわかってない人が書いているので間違っている部分があると思います。
生成例
※ 一部ブログに書くにはアレな文書も生成されていたので選抜しています。
選抜していないものを見たい方はこちら
※ SOS
はスタートトークン、EOS
はエンドトークン、PAD
はパディングトークン、UNK
は未知語トークンを表します
制限なし
かなりバリエーションのある文書を生成できるようになりました。
また、今回は割と長めの文書も生成できるようになっています。
※ スレタイの後ろにあるスコアはEvaluator(後述)の出力値です。
SOS 名前 に 「 デス 」 が 付く 曲 NUM つ しか ない EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 4.6163287 SOS 敵 「 ワイ の こと 好き な ん ? 」 トッモ 「 は ? 僕 は ? 」 EOS PAD PAD PAD PAD score: 3.3199003 SOS なんj 民 が 知っ て いる 一番 の UNK は ? EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 3.029164 SOS ワイ NUM 歳 大学生 結婚 し たい のに 仕事 が ない EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 2.7726371 SOS (*^◯^*) ← 今年 ? 行か なけれ ば なら ない EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 2.641209
prefix「三大」
前回の例でも出した「三大」から始まるスレタイです。
今回も「三大〜「A」「B」あと一つは?」のような構造を学習できています。 (一部怪しいのもありますが)
SOS 三大 やらかし た UNK の 根本 の 中 で の 会話 「 ドア 机 」 「 杉浦 」 EOS PAD PAD PAD PAD score: 3.601511 SOS 三大 う けど 勢い 揃っ てる 気 が つい た 欠陥 UNK 馬 EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 3.2168586 SOS 三大 UNK が UNK に 必要 な もの 「 早稲田 」 「 ポテチ 」 あと 一 人 は ? EOS PAD PAD PAD score: 3.192483 SOS 三大 値段 高い と 思う な カッコイイ モビルスーツ ランキング ( NUM 番 最初 数字 ) EOS PAD PAD PAD PAD PAD PAD PAD score: 3.0752976 SOS 三大 当たり 出し が 激しい チーム 助っ人 采配 イチロー EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD score: 3.0569434
prefix「【なぞなぞ】」
【なぞなぞ】から始まるスレタイはたいてい最後が問いかけになるのですが、 その構造がうまく取れています。ちゃんとなぞなぞっぽくなっていますね。
SOS 【 なぞなぞ 】 誰 でも 作る べき UNK の 曲 なー ん だ ? EOS PAD PAD PAD PAD PAD PAD PAD PAD score: 4.264069 SOS 【 なぞなぞ 】 ゴム つけ て そのまま ケーキ 誘う もの ってな ー ん だ ? EOS PAD PAD PAD PAD PAD PAD PAD score: 3.9145274 SOS 【 なぞなぞ 】 口 に 入れる と 美味しい 食べ物 ってな ~ ん だ ? EOS PAD PAD PAD PAD PAD PAD PAD PAD score: 3.8251681 SOS 【 なぞなぞ 】 UNK で 海 ワン が かける 言葉 ってな ー ん だ ? EOS PAD PAD PAD PAD PAD PAD PAD score: 3.6090522 SOS 【 なぞなぞ 】 バイク が 食わ れ て の sa お トラブル みたい な 名前 の メンバー は なん でしょ う ? EOS score: 3.5852108
モデルの詳細
今回のなんJスレタイ生成器は、スレタイっぽい文章を生成するGeneratorと、 Generatorの生成した文書を評価するEvaluaterの2つで構成されます。
Generatorでたくさんスレタイを生成し、Evaluaterで高スコアのスレタイのみを出力します。
Generator
Generatorは4層GRUによるテキスト生成器です。モデル構造はnotebookで見たほうが早いと思います。
Evaluater
EvaluatorはBidirectional GRUで構成された「Generatorの生成したスレタイ」と「本物のスレタイ」を区別する識別器です。 こちらもモデル構造はnotebookを見てください。
工夫した点・苦労した点
Generator / Evaluator構成
最初はVAEでスレタイを潜在ベクトルzで表現し、zをGANで生成する、というアイデアで文書生成を試みました。
ところが、このアイデアはVAEまではうまくいったのですがGANの学習がうまく行かず断念し、 最終的にGenerator・Evaluator構成に落ち着きました。
ちなみに、Evaluatorがないと以下のように構造を捉えられていないスレタイが生成されてしまうので、Evaluatorには一定の効果はありそうです。
SOS 【 なぞなぞ 】 女児 に 笑わ れ た 男 EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD SOS 【 なぞなぞ 】 bbq に いる 女の子 と は なん だ ? EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD SOS 【 なぞなぞ 】 なんj を UNK て も 訪れ なかっ た もの EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD PAD SOS 【 なぞなぞ 】 ミサイル が 嫌 に なら なかっ た 球場 に ビビっ てる スレ で は ない チーム は ? 日本 だけ SOS 【 なぞなぞ 】 精神的 に しか いる ん だ う なー ンゴ ? EOS PAD PAD PAD PAD PAD PAD PAD PAD PAD
Functional APIの利用
Tensorflow 2.0のチュートリアルの一部では
tf.keras.Model
を継承したクラスを作ってモデルを作っている例が出ていますが、
この場合、そのままだとsave_weights
などでモデルを保存することができません。
KerasのFunctional APIを使えば回避できるので、 よっぽどでなければFunctional APIを使うのが良いと思います。
tf.functionを使うときはなるべくtf.functionで完結させる
Better performance with tf.function | TensorFlow Core によると、tf.function
を使うと色々と性能UPするようです。
今回の検証でもtf.functionを使うと学習時間が早くなり、大きいバッチサイズでもOOMしないなどメモリ効率も良くなりました。
一方で、副作用が出ないように注意したのに@tf.function
だとエラーが出る、という挙動になることがままありました。
いろいろ試した結論としては「tf.function
を使うときはなるべくtf.function
内ですべてを完結させる」ということです。
例えば一部の前処理を tf.function
外で行ってその結果をtf.function
の引数で渡すのではなく、
前処理も含めてtf.function
内で動かすと良いです。
tf.function
の引数はdatasetから取得したデータだけにする、ぐらいの心持ちで実装するのが良いと思います。
まとめ
Tensorflow 2.0を触るついでに、なんJスレタイ生成器を作り直しました。
はじめてTensorflow 2.0を使ってハマることも多かったですが、 最終的にまあまあの精度の生成器ができたので満足です。
参考にしたサイト
PDFの余白を調整するだけのアプリを作りました
最近論文を読むときはiPad + Apple Pencil + GoodNotes 4を使ってPDFにメモを書き込みながら読んでいます。 以前は紙に印刷して直接書き込んでいたのですが、この方法だと論文とメモをデジタルで残しておけるので非常に便利です。
しかし、1点だけ不満を思っていることがあります。 それは論文のPDFは余白が小さくメモをするスペースが狭いことです。 *1
PDF Margin Adjuster
そこで、この不満を解決すべくPDFのファイルの余白の大きさを調整するアプリを開発しました。
開いたPDFファイルを[+][-]ボタンでインタラクティブに余白の大きさを調整できます。
これで、余白を大きくして広々とメモを取ることができ、快適に論文を読めるようになりました。
GoodNotes 4で開いたときのイメージ
それでは、皆様も良い論文ライフをお過ごしください!
*1:拡大してメモすればいいじゃんといえばそうなのですが、個人的には英文全体を見ながらメモしたい気持ちがあります
ReactNative + Expo.io で素早く画像認識アプリを作る
最近、顔の美しさをアノテーションしたデータセットSCUT-FBP5500-Databaseを知り、これでイケメン度・美女度判定アプリをつくったらウケるのでは?と考えたりしています。
これに限らず、ポケモン図鑑的な(写真をとったら何か結果を返してくれる)アプリはいろいろと応用が効くため、前々から作ってみたいと思っていました。
そこで、今回比較的時間をかけずにAndroid, iOS共通のアプリが作れると噂のReactNative + Expo.ioを使った画像認識アプリの作成に挑戦しました。
作りたいアプリのイメージ
- アプリで写真を撮影する
- アプリで撮った写真を何らかの方法で画像認識する
- 結果をアプリに表示する
事前調査
Expoの使い方
こちらの方の記事がよくまとまっています。
http://ykubot.com/2017/07/30/react-native-expo/
画面遷移の方法
今はreact-nativigationを使うのが主流だそうです。
こちらの記事がよくまとまっています。
https://bagelee.com/programming/react-native/react-navigation-react-native/
画像認識の方法
ReactNative+ExpoでkerasやTensorflowのモデルを動かせるか?
- Feature Requestは存在しますが、これは現状はできないようです。
- react-native-tensorflowというズバリみたいなパッケージもありますが、これは
react-native link
が必要なパッケージでExpoと両立させることはできません。
(※deatchすればできますが、detachするならそもそもExpoを使わないほうが良いと思います。)
ReactNative+Expo単体では画像認識が難しいので、base64でエンコードした画像をAPIにPOSTして結果を受け取るようにします。
GoogleのVision APIなどを使ってもよいですし、自力でAPIを作っても良いです。
最終的に作ったもの
Overview
以下のようなclient(アプリ)とserver(Webサーバ)を作ります。
server(Webサーバ)
- ソース
- flaskで実装、PCFでデプロイすることを想定しています。
/recognize
にbase64でエンコードした画像が入ったjsonをPOSTすると、numpy.ndarray
に変換してshape
を返す仕様です。- 本当はここでkerasなりtensorflowなりでpredictした結果を返すことを想定しています。
@app.route('/recognize', methods=['POST']) def recognize(): if request.headers['Content-Type'] != 'application/json': print(request.headers['Content-Type']) return jsonify(res='error'), 400 # decode base64 to image enc_data = request.json["base64"] dec_data = base64.b64decode(enc_data) img = Image.open(BytesIO(dec_data)) x = np.asarray(img, dtype='uint8') # run something with 'x' return jsonify(result=str(x.shape))
client (アプリ)
- ソース
- 大抵はexpoで初期で作成されるファイルなので、
.js
ファイルだけ見ていただければと思います。 - ReactNativeで実装、Expoで実機にデプロイすることを想定しています。
- react-navigationを使って、画面遷移をする設定をします。
- 今回はOverviewに書いてある
MainScreen
(初期)とResultScreen
です。 stackNavigatiorを使うと自動的に戻るボタンが作成されるので便利です(※後のスクリーンショットを参照)
MainScreen
の実装が書いてあります。- 「Camera」や「CameraRoll」ボタンで画像を取得します。
- Expoに実装されているImagePickerを利用します。
- 使い方はこちらの記事がよくまとまっています。
- http://ykubot.com/2017/10/22/react-native-expo-image-picker/
- 取得した画像はImageManipulatorを使ってCropping, Resize, base64変換を行います(
_cropAndResize
関数) - 変換結果はstateに保存します。
- Expoに実装されているImagePickerを利用します。
- 「Recognize!」ボタンで
ResultScreen
に遷移します。このとき、stateのimageをResultScreen
に渡します。
ResultScreen
の実装が書いてあります。- 読み込み時に
MainScreen
から受け取った画像をserverにPOSTします(_getResultAsync
関数)- このとき、react-native-loading-containerを使って処理が終わるまでLoading画面が表示されます
- serverから結果が返ってきたら、結果をstateに保存してRenderします。
所管
ReactNative(Expo)は決められたプリセットを組み合わせるだけのアプリなら簡単に作れますが、 枠から外れた途端何もできなくなる(難しくなる)印象です。
以下のものは実装を検討しましたがExpoで簡単に実装するのは無理だと判断し、諦めました。
- KerasやTensorflowのモデルを動かす
- 撮影した画像の任意の場所でCroppingする
- Camera撮影画面をいじる
余談
SCUT-FBP5500-Databaseを使ったイケメン度・美女度判定アプリの大枠は無事完成しました。 が、このデータセットは研究目的でしか使えないことに気づいたので公開はしません。残念。