Rock Book

history | grep `future`

T5 (てゆうか超手軽に強いラッパー作れるんですけど)

本年(度) もいつものやつ、やっていきたいと思います。

昨年(度) はこんなのを作りました。

masaki925.hatenablog.com

本年(度) はどうしようかということで、顧問HIPHOP アドバイザーのkenichi 氏との企画会議にて、 HIPHOP には「リアルかフェイクか」という議論があることを教わりました。

確かに調べてみると、様々な考え、解釈がありそうです。

ヘッズの横好きの域を出ない私にとって、この判断はなかなか難しいものです。

どうすればリアルかどうかを判断できるのでしょうか?

機械学習を活用したいところですが、判断基準が不明なため教師データが用意できず、二値分類(リアル or not) は使えません。

一方、昨今発展の目覚ましい自然言語処理の領域では、AI (機械学習による言語モデル) が文章を生成する精度が飛躍的に向上しています。少し前にはGitHub Copilot がプログラムを自動生成することで話題になりました。

これを応用します。

すなわち、「リアルなラッパーが言いそうなこと言ってるやつはリアル」です。

f:id:masaki925_8107:20220106053859p:plain
リアルなラッパーが言いそうなこと言ってるやつはリアル

起点となる「リアルなラッパー」はどうしても選定が必要となるため、kenichi 氏に「この人は文句なし」と思わしきラッパーを選定していただき、そのリリックを学習データとして利用することとしました。

f:id:masaki925_8107:20220106054133p:plain:w200
厳正なる審査の様子

そしてこの怪物級のラッパーたちを超合成して出来上がったのが... コイツだッッ!!

f:id:masaki925_8107:20220106055425p:plain:w300
MC キメラ

怪獣にも「生き様」がある。それが垣間見えるバースを返してくれます。

f:id:masaki925_8107:20220106055800p:plain:w300

f:id:masaki925_8107:20220106055826p:plain:w300

生まれながらにして怪獣であるキメラの生き様

最後は槍で攻められたことを逆手に取って、投げやりな態度を取っている様子が伺えます。うーん、リアル。

みなさんも大吉が出るまで何度もおみくじを引いてないで、MC キメラのバースと自分の予想を見比べて、自分の「リアル」を占ってみてはいかがでしょうか?

※ 1回目のメッセージは寝ていることがあります。

f:id:masaki925_8107:20220106060314p:plain:w200
MC キメラ

@721qnjke

line.me

MC キメラのつくり方

ソースはこちらです。

github.com

データ

こちらです。

MCキメラ 学習用データ - Google スプレッドシート

train するときの中身はこんなかんじです。最後の数字はrapper_id です。分類タスクを試したときに利用しました。

❯ head train.tsv
革命家でrapの松尾芭蕉 かましてるwavy毎日fosho   woooこりゃ荒波 マジな話でこのremixはヤバすぎるlook look look  8
そこは情報過多な競争社会 俺が居なくなりゃ頂上が無いも同然   俺はここらじゃイケてるほう ol、ショウガールにモデルも  2
まだ駆け出してギャラは三万円 ライブの後に消えるシャンパンで    あなたの才能は特別よ 君の言葉だけを信じれたよ 6
this is no flavor syndrome お前らじゃ味がしないんだよ,hey  help me doctor変なんです 味がしない死にそうです 9
funky flowで熱伝導 最新章のlet it flow  年中年中エンドレス シビレきらすファンクネス    1
世界一microphoneが 似合うプリンセスはだあれ i'm coming back yo you can't forget me yo   9
2、3日後体が空く 家まで迎えに行くからよまた会おう    降ろすrhyme災いや偽りはいらない 耳に入るその飛び偽物は効かない  5
いつでもどんなシチュエーションにも すぐに馴染んでいくでしょ?不思議  お前みたいなヤツは全然いない 変幻自在my baby  2
いらねえ警察 達麻の傑作  いくらつければ拭えるパクられる命落ちたか いつも居る崖の淵押すなバビロンこちら側  5
声パクリそしてフローパクリ ステージでの振る舞いも超パクリ   マジ神経疑うぜまるでモノマネ歌合戦 親子で出にゃつまらんぜ   3

たくさん改良の余地があると思います。

  • 曲の選定基準 (順位)
    • 人によって量が違うので偏りが出そう
  • バースの切り方
  • 重複(サビなど) の扱い
  • バトルなどでのパンチラインをデータに入れる
  • etc...

日本語事前学習済みT5

こちらを参考にさせていただきました。

qiita.com

今回利用したcolab です。

Google Colab

FastAPI + Line Messenger API

こちらを参考にさせていただきました。

qiita.com

基本構成はこちらをベースにしています

github.com

Cloud Run

image をbuild する際、Cloud Build + GitHub でやろうとしたものの、学習済みモデルをそのままGitHub にpush できず、lfs もCloud Build でうまく動かずで苦戦しました。

https://git-lfs.github.com/

結局今回はハードコードっぽく、CI でGCS から転送してbuild, image 化しています。

また、pytorch やモデルの読み込みでメモリを食うため、4GB インスタンスが必要です。

TF Lite 化などが気になります。

qiita.com

おわりに

BERT, T5 の流れのおかげで、エンコーダーデコーダー形式のモデルの実装難易度がかなり下がったように感じました。

一番時間がかかったのは、データを収集する部分と、ブログの記事(文章) を書くところでした。。

良質なデータを集められるプラットフォームの必要性が増していることを感じます。

下記の記事にもある、データ・セントリックにも通ずるものがありそうです。

ja.stateofaiguides.com

また、同記事にもあるように、エンコーダーデコーダー形式の一般化が進んだことにより、画像や音声などの領域にも手が届きそうな状況になってきました。

テキストだけのラップボットは今年で卒業し、来年(度) こそはマルチモーダルな世界に足を踏み入れたいと思います。

ラップボット界のホームラン王、現る

こんにちは。ラップボットプロデューサーの @masaki925 です。

今年もこの季節がやってきました、毎年恒例のラップボットです。

昨年は流行りのBERT に手を出して痛い目を見ましたが、今年も懲りずにやっていきたいと思います。

ラップの強さと韻の飛距離

私の目標の1つは強いラップボットを作ることです。

では、強い、とはなんでしょうか?

韻の硬さ、フロウの柔軟さ、エモいバックグラウンド、色々あると思いますが、

ライムのクオリティを測る指標として、韻の飛距離という概念があります。

こちらの記事が参考になります。

https://news.1242.com/article/209776

R-指定:「A」という言葉と「B」という言葉で踏もうとしたら、「A」と「B」の言葉の響きは近ければ近いほどいい。でも、その内容がかけ離れていれば離れているほど、韻として面白いというか。

...

(中略)

...

R-指定:その時、ZORNさんがブワーッと電話越しで言ってくれたのが、「表参道のオープンカフェより、お嫁さんと食う醤油ラーメン」っていう。この飛距離、わかる?

バトル中にこんな飛距離あるライミングをされたら思わず吹っ飛ばされてしまいますね。

今年はこの韻の飛距離を計測すると同時に、宇宙の果てまで飛んでいくようなパンチラインかますラップボットを開発しました。

韻の飛距離メー(タ|カ)ー MC ドカベン

韻の飛距離メーター 兼 韻の飛距離メーカー、MC ドカベンです。

f:id:masaki925_8107:20201225104045p:plain
MC ドカベン

あなたがキックしたバースの飛距離を採点するのと同時に、 アンサーとなるバースを飛距離と共に返し、飛距離を比較して勝敗を判定してくれます。

f:id:masaki925_8107:20201225105252p:plain
今まで(5年間) の苦労がにじみ出るライム(?)

※ めちゃくちゃ重いです。。辛抱強くお試しください

国際航空連盟によれば高度100km (100,000m) からを宇宙と定義しているらしいので、宇宙旅行も夢じゃありませんね。

ぜひお友達になって、ガガーリンも真っ青な長距離砲ライムをかましてみてください。

f:id:masaki925_8107:20201225105428p:plain
LINE QR コード

友達になる

※ 予算の都合上、年内いっぱいで活動休止するかもしれません。BERT メモリ食い過ぎ。。ということで、バトルはお早めに!

以降、開発の裏側の話です。

MC ドカベンのつくりかた

アルゴリズム

まず、ライムを作るときの人間の思考方法について考えました。

それがこの図です。

f:id:masaki925_8107:20201225105508j:plain
ライム生成アルゴリズム

時間がない 企業秘密のため詳細は省きますが、

要するに、「まず2小節目に使うキラーフレーズを選んで、そこに韻を乗せるように1小節目を構成する」です。

加えて、アンサー要素として「相手のバースに関連した内容を入れ込む」ことと、

レペゼン要素として「自分のキャラに合った内容を入れ込む」です。

MC ドカベンでは、この1小節目と2小節目の組み合わせをいくつか試行して、もっとも飛距離が出るものを採用することにしています。

※ 今回はとりあえずレペゼン「野球」に固定しています。

飛距離に関しては、ラップボットコンサルタントのkenichi 氏に教えてもらいました。まじリスペクト。

細かい部分は色々とありますが、ざっくり上記の方針でロジックを組んでいます。

実装編

ソースコードです。

github.com

github.com

前回までと同様、LINE Messenger API を利用して、フロントはRails, 裏側はFlask で構成しています。

学習済みモデルなどでかいファイルはignore してあります。

正直中身ぐちゃぐちゃです。

肝は、小節間の意味的な距離を測るためにBertScore を利用している部分でしょうか。

github.com

ただ、精度的にもサイズ的にもこれじゃなくてもいいのでは感が否めません。。今後の課題です。

キラーフレーズには、水島新司さんの野球漫画「ドカベン」から名言っぽいものを拝借しました。「飛距離」から着想を得ています。 今は数が少ないと、ラップっぽくないものも多数混ざってしまっているので、改善の余地が大量にあると思います。

文章生成のマルコフ連鎖の部分は、以前作ったDJマルコのコードをベースとして、少し改良したものになっています。

masaki925.hatenablog.com

他にもBERT 使うためにDocker コンテナサイズと格闘した話(負けた) とか、Heroku でCedar が古すぎてupgrade のためにbuildpack と格闘したりしました(勝った) が、今年は 時間の都合 企業秘密で詳細は割愛します。

気が向いたら別記事を書くかも。

なにか気になるところあればコメントでもDM でもください。

以上です。良いお年を!

BERT でBATTLE

はじめに

こんにちは。
今日も元気に言葉のウェイトトレーニングしてますか?

本記事は、LINE Bot Advent Calendar 2019 の23日目の記事となります。

今回も懲りずに、ラップボットシリーズの第4作目です。

前回はマルコフ連鎖を用いて、血の気盛んなバトルラッパー DJマルコを生み出しました。

masaki925.hatenablog.com

今回はもっとAI や機械学習的なことを

f:id:masaki925_8107:20191223120437p:plain
ということで、 最近話題の自然言語処理モデル、BERT を利用してみました。

BERT に関しては、こちらの記事がわかりやすかったので、もしご存知ない方は参照してみてください。

Googleが公開した自然言語処理の最新技術、BERTとは何者なのか | AI-SCHOLAR

余談ですが、ラップボットが取り組む問題は文章生成なので、 個人的にはBERT と同じく話題の言語モデル GPT-2 (参考記事) を利用してみたかったのですが、 日本語での事前学習モデルが公開されていなかったため、 今回は京都大学の研究室が公開している、BERT の日本語事前学習モデルを利用させていただくこととしました。

BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB

と、御託はこのくらいにして、今回のラッパーの紹介です。
今回のラッパーは、こいつだ!

本人は全くその気はないのに、大御所の存在により演歌歌手に間違えられがちな咆哮系ラッパー、鳥羽さんです。

f:id:masaki925_8107:20191223213540p:plain:w300
鳥羽さん

ぜひお友達になって、バトルしてみてください!



… と言いたいところだったのですが、 なんとこの鳥羽さん、パフォーマンスに難ありでデプロイできず、 お披露目することができませんでした。。
くやしい。

代わりにいくつか生成したパンチラインを紹介するに留めておきたいと思います。

詳細は後述しますが、以下は収集したラップバトルのパンチラインを学習(fine tuning) させてモデルを構築し、文字数を指定(下記は40) して文章を自動生成したものになります。



おいACE[UNK][UNK][UNK][UNK][UNK][UNK]に教えてやる Beatで踏んでるお客さんそれがお豆

 突然のお豆。 ([UNK] はUnkown の略で、うまく言葉を生成できなかった部分です。)

俺にとってはビビい所から来たメンタルはやっぱりこのスキルをアップデートしないとこだよ

 くー。今回のスキル不足を指摘されてるみたいで刺さる。精進します。

アンタに勝てるぜレペゼンパッパラパパパパパッパラ そっからはお客さん

 レペゼンどこそれ?からの無茶振りw

お前を見てヤバいブーメラン本当そうだな このステージのお母ちゃんテメーじゃん

 「ブーメラン」「お母ちゃん」「テメーじゃん」で韻踏んでるような。。?


とまぁこんなかんじになりました。

正直、クオリティ的には過去のラッパーたちに比べても大きく改善したとはいいにくいですが、
「ビビい所」「パッパラパパパパパッパラ」といった造語っぽいものや、 上記にもあった韻を踏んでるっぽいところは、今後の可能性の片鱗を見せてくれたような気がします。

今後、言葉のウェイトとは裏腹に身軽になった鳥羽さんが降臨してくれることに期待しましょう…!!

以下、実装編となります。

鳥羽さんの作り方

概要

GitHub - masaki925/bert_rap: BATTLE on BERT

(あんまり整備できてないので気になる部分があればお気軽にお声がけください)

  • 構成
    • フロントは過去のものを流用してheroku
    • バックエンドは昨今のGCP の勢いに乗って、Google Cloud Run を利用しました。 => メモリ食い過ぎ(3.5GB じゃ足りない) && 処理重すぎ(生成に約1分半かかる) により失敗。

方針

下記の記事を参考にさせていただきました。

qrunch.net

こちらで紹介されている BertMouth は、 自分でテキストを用意してfine tuning することで独自モデルが構築できるようになっています。

生成時は指定された長さの文字列を穴埋め問題化して、1文字ずつ予測して埋めていくという手法です。

ので、今回のポイントは

  • どういったデータを
  • どのくらい
  • どういった形式で用意するか

といったものでした。

まずはデータ集めです。

データセット

放送回と、書き起こしサイトの対応表も一応載せておきます。
単純にクロールして済む問題でもないので、地道に丹精込めて集めました。

フリースタイルダンジョン書き起こし - Google スプレッドシート

※ なおYouTube の字幕も見てみましたが、やはり日本語の精度はまだ使い物にならなかったです。

単純な1行の文章でいくと、2722文を集めることができました。

ポケモンの方が4000文以上集めていたのでそこを目指したのですが、やや及ばずでした。

実物はこんなかんじです。

高鳴って来たぜ心拍数 コイツは俺に負けて自身無くす
この会場で俺は進化する 俺がバトルを始めればみんな夢中
ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター
俺からすればボンクララッパー 返してこいちゃんとしたドープなアンサー

フリースタイルダンジョン:シーズン1 REC1:Dragon One VS T-PABLOW | フリースタイル文字起こし

これは記念すべき1回目の放送(っぽい) で、Dragon One のバースですね。
教科書のようなライミングです。

他にも、

Remember
ファーストチャレンジャー
I’m a danger
あ?二代目だ?あんまナメんな
これは事件だ
後ろにはアベンジャーズ
やっぱり
ヤベェメンバーも集まってんだよ
余裕で獲る天下
これは口喧嘩
度が過ぎてんだ
若干してるぜ 劣化
与えるぜ プレッシャー
速攻でステップアップ
崇勲? 俺1人で充分

https://hiphopzanmai.com/freestyledungeon/81/

またもやDragon One のバースですが、 こんなかんじで、短めの一行もあれば、これらがまとめて一行になっているものも混ざっています。

こちらを使って学習していきます。

学習

手元のMacbook で学習しようとしたところ時間がかかりすぎたため、colab を利用しました。

colab.research.google.com

GPU がサクッと使えるのは感動的ですね。

デフォルトのランタイムがCPU にセットされていて、GPU に変更するとそれまで利用したファイルが消えてしまうので要注意です。

また時間(90分?) が経つとセッションが切られて、これまたダウンロード・インストールしたファイル (特にBERT モデルなど、大きいファイル) が削除されてしまいきっつー となるので、Auto Refresh を入れて定期的に接続するようにしました。

が、学習途中でリフレッシュされて学習が止まってしまったことがあった(たぶん。1回学習を回すのに1時間以上かかるため、待ちながら他のことやってたりするので記憶が曖昧になる。。) ので、要注意でした。。

学習の様子

12/21/2019 11:34:51 - INFO - __main__ -   train starts
Epoch:   0% 0/100 [00:00<?, ?it/s]12/21/2019 11:34:57 - INFO - __main__ -   [1 epochs] train loss: 1.02 
12/21/2019 11:34:58 - INFO - __main__ -   sampled sequence: ののののののののののののののののののののののののは
12/21/2019 11:34:58 - INFO - __main__ -   [1 epochs] valid loss: 1.02
Epoch:   1% 1/100 [00:06<11:14,  6.82s/it]12/21/2019 11:35:04 - INFO - __main__ -   [2 epochs] train loss: 0.965 
12/21/2019 11:35:05 - INFO - __main__ -   sampled sequence: ははははははははははははははははははははははははは
12/21/2019 11:35:05 - INFO - __main__ -   [2 epochs] valid loss: 0.96
Epoch:   2% 2/100 [00:13<11:16,  6.91s/it]12/21/2019 11:35:11 - INFO - __main__ -   [3 epochs] train loss: 0.908 
12/21/2019 11:35:12 - INFO - __main__ -   sampled sequence: ののののののののののののののののののののののののは
12/21/2019 11:35:12 - INFO - __main__ -   [3 epochs] valid loss: 0.894
Epoch:   3% 3/100 [00:20<11:06,  6.87s/it]12/21/2019 11:35:18 - INFO - __main__ -   [4 epochs] train loss: 0.829 
12/21/2019 11:35:21 - INFO - __main__ -   sampled sequence: 俺はは俺は俺は俺は俺俺は俺俺は俺は俺は俺は俺は俺は
12/21/2019 11:35:21 - INFO - __main__ -   [4 epochs] valid loss: 0.847
Epoch:   4% 4/100 [00:30<12:09,  7.60s/it]12/21/2019 11:35:27 - INFO - __main__ -   [5 epochs] train loss: 0.799 
12/21/2019 11:35:31 - INFO - __main__ -   sampled sequence: 俺のの俺ののの俺ののがねぇねぇ俺のののののののねぇ
12/21/2019 11:35:31 - INFO - __main__ -   [5 epochs] valid loss: 0.869
Epoch:   5% 5/100 [00:39<12:51,  8.12s/it]12/21/2019 11:35:36 - INFO - __main__ -   [6 epochs] train loss: 0.771 
12/21/2019 11:35:40 - INFO - __main__ -   sampled sequence: ねねぇねねぇ俺はお前はお前はねぇ俺お前ははお前はお前はねねぇ
12/21/2019 11:35:40 - INFO - __main__ -   [6 epochs] valid loss: 0.766
Epoch:   6% 6/100 [00:48<13:15,  8.47s/it]12/21/2019 11:35:46 - INFO - __main__ -   [7 epochs] train loss: 0.762 
12/21/2019 11:35:48 - INFO - __main__ -   sampled sequence: がねねぇ俺は俺は俺は俺が俺がねぇぜ俺は俺は俺は俺は
12/21/2019 11:35:48 - INFO - __main__ -   [7 epochs] valid loss: 0.82
Epoch:   7% 7/100 [00:56<13:04,  8.43s/it]12/21/2019 11:35:54 - INFO - __main__ -   [8 epochs] train loss: 0.693 
12/21/2019 11:35:55 - INFO - __main__ -   sampled sequence: お前の俺の俺のの俺の俺の俺の俺の俺の俺ののだぜお前のの
12/21/2019 11:35:55 - INFO - __main__ -   [8 epochs] valid loss: 0.827
Epoch:   8% 8/100 [01:04<12:18,  8.03s/it]12/21/2019 11:36:01 - INFO - __main__ -   [9 epochs] train loss: 0.729 
12/21/2019 11:36:04 - INFO - __main__ -   sampled sequence: 俺はお前の俺だぜぜお前の俺のは俺の俺だぜ俺がだねぇねぇ
12/21/2019 11:36:04 - INFO - __main__ -   [9 epochs] valid loss: 0.763
Epoch:   9% 9/100 [01:13<12:37,  8.32s/it]12/21/2019 11:36:10 - INFO - __main__ -   [10 epochs] train loss: 0.738

最初は「俺の」しか言わない

12/21/2019 11:41:45 - INFO - __main__ -   sampled sequence: 俺はオメェ俺はHIPHOPフロウだよ?まぁまぁラップしてるぜこの勝利
12/21/2019 11:41:45 - INFO - __main__ -   [50 epochs] valid loss: 0.617
Epoch:  50% 50/100 [06:53<06:32,  7.84s/it]12/21/2019 11:41:50 - INFO - __main__ -   [51 epochs] train loss: 0.528 
12/21/2019 11:41:53 - INFO - __main__ -   sampled sequence: 俺らはここで俺の方が俺はこの上でじゃないぜ俺の方が俺の方だ
12/21/2019 11:41:53 - INFO - __main__ -   [51 epochs] valid loss: 0.607
Epoch:  51% 51/100 [07:01<06:27,  7.90s/it]12/21/2019 11:41:58 - INFO - __main__ -   [52 epochs] train loss: 0.517 
12/21/2019 11:42:01 - INFO - __main__ -   sampled sequence: お前のフロウの上には踏めないオメェがヤバいしてるぜこのアンタの
12/21/2019 11:42:01 - INFO - __main__ -   [52 epochs] valid loss: 0.561
Epoch:  52% 52/100 [07:10<06:29,  8.12s/it]12/21/2019 11:42:07 - INFO - __main__ -   [53 epochs] train loss: 0.529 
12/21/2019 11:42:11 - INFO - __main__ -   sampled sequence: 俺の事俺は俺はこの事じゃないのは何だよ俺の事じゃないののか?
12/21/2019 11:42:11 - INFO - __main__ -   [53 epochs] valid loss: 0.614
Epoch:  53% 53/100 [07:19<06:37,  8.46s/it]12/21/2019 11:42:16 - INFO - __main__ -   [54 epochs] train loss: 0.478 
12/21/2019 11:42:18 - INFO - __main__ -   sampled sequence: 俺らはこの上のアンタに1人俺らはアンタはこの事じゃないぜ俺の事
12/21/2019 11:42:18 - INFO - __main__ -   [54 epochs] valid loss: 0.611
Epoch:  54% 54/100 [07:27<06:18,  8.24s/it]12/21/2019 11:42:24 - INFO - __main__ -   [55 epochs] train loss: 0.511 
12/21/2019 11:42:28 - INFO - __main__ -   sampled sequence: このアンタ俺がフリースタイル俺が俺の方がお前らだけじゃないオメェお前らだよ
12/21/2019 11:42:28 - INFO - __main__ -   [55 epochs] valid loss: 0.627
Epoch:  55% 55/100 [07:36<06:24,  8.54s/it]12/21/2019 11:42:33 - INFO - __main__ -   [56 epochs] train loss: 0.538 
12/21/2019 11:42:36 - INFO - __main__ -   sampled sequence: このクソだよアンタの上だけじゃねぇどっちがHIPHOPアンタだよここで
12/21/2019 11:42:36 - INFO - __main__ -   [56 epochs] valid loss: 0.696
Epoch:  56% 56/100 [07:45<06:18,  8.59s/it]12/21/2019 11:42:42 - INFO - __main__ -   [57 epochs] train loss: 0.531 
12/21/2019 11:42:45 - INFO - __main__ -   sampled sequence: フロウ俺はフロウで何年もここでここでここで踏めないか?ここでこの勝利
12/21/2019 11:42:45 - INFO - __main__ -   [57 epochs] valid loss: 0.606
Epoch:  57% 57/100 [07:53<06:09,  8.60s/it]12/21/2019 11:42:51 - INFO - __main__ -   [58 epochs] train loss: 0.518 
12/21/2019 11:42:53 - INFO - __main__ -   sampled sequence: ヤバい言葉の上俺がヤバいぜ俺らがこのフロウの上じゃない俺らが
12/21/2019 11:42:53 - INFO - __main__ -   [58 epochs] valid loss: 0.568
Epoch:  58% 58/100 [08:02<05:58,  8.54s/it]12/21/2019 11:42:59 - INFO - __main__ -   [59 epochs] train loss: 0.536 
12/21/2019 11:43:03 - INFO - __main__ -   sampled sequence: 何がないぜこの事フリースタイルマジでフロウだけじゃないぜオメェはヤバいだけだ
12/21/2019 11:43:03 - INFO - __main__ -   [59 epochs] valid loss: 0.63
Epoch:  59% 59/100 [08:11<05:58,  8.75s/it]12/21/2019 11:43:08 - INFO - __main__ -   [60 epochs] train loss: 0.503 

だいぶ語彙が増えてくるが、同じ言葉を繰り返してしまったりしている。

12/21/2019 11:47:37 - INFO - __main__ -   sampled sequence: FORK俺が俺の方俺の方がこのラップには俺の方じゃないぜどっちの方
12/21/2019 11:47:37 - INFO - __main__ -   [91 epochs] valid loss: 0.578
Epoch:  91% 91/100 [12:45<01:17,  8.63s/it]12/21/2019 11:47:42 - INFO - __main__ -   [92 epochs] train loss: 0.455
12/21/2019 11:47:45 - INFO - __main__ -   sampled sequence: このラップは踏めないぜお前らは俺のオメェのフロウはHIPHOP俺の方
12/21/2019 11:47:45 - INFO - __main__ -   [92 epochs] valid loss: 0.585
Epoch:  92% 92/100 [12:54<01:08,  8.62s/it]12/21/2019 11:47:51 - INFO - __main__ -   [93 epochs] train loss: 0.428
12/21/2019 11:47:54 - INFO - __main__ -   sampled sequence: このフロウフロウ大丈夫だろ大丈夫俺はアンタにはここにはない大丈夫俺は
12/21/2019 11:47:54 - INFO - __main__ -   [93 epochs] valid loss: 0.605
Epoch:  93% 93/100 [13:02<00:59,  8.55s/it]12/21/2019 11:47:59 - INFO - __main__ -   [94 epochs] train loss: 0.418
12/21/2019 11:48:01 - INFO - __main__ -   sampled sequence: 俺の方がこの勝利まぁ[UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK][UNK]
12/21/2019 11:48:01 - INFO - __main__ -   [94 epochs] valid loss: 0.624
Epoch:  94% 94/100 [13:09<00:49,  8.19s/it]12/21/2019 11:48:07 - INFO - __main__ -   [95 epochs] train loss: 0.423
12/21/2019 11:48:09 - INFO - __main__ -   sampled sequence: 俺の方ぶちがコイツがどっちとかだよ?お前らのフロウで俺らは
12/21/2019 11:48:09 - INFO - __main__ -   [95 epochs] valid loss: 0.553
Epoch:  95% 95/100 [13:17<00:40,  8.13s/it]12/21/2019 11:48:15 - INFO - __main__ -   [96 epochs] train loss: 0.435
12/21/2019 11:48:18 - INFO - __main__ -   sampled sequence: 来い大丈夫だ俺は俺らだったらNo.1俺ら1人の方が俺の方だ
12/21/2019 11:48:18 - INFO - __main__ -   [96 epochs] valid loss: 0.565
Epoch:  96% 96/100 [13:26<00:32,  8.20s/it]12/21/2019 11:48:23 - INFO - __main__ -   [97 epochs] train loss: 0.419
12/21/2019 11:48:26 - INFO - __main__ -   sampled sequence: 何?俺が俺の方が俺がこのラップだけだよ?お前らは大丈夫?ここで俺
12/21/2019 11:48:26 - INFO - __main__ -   [97 epochs] valid loss: 0.582
Epoch:  97% 97/100 [13:34<00:24,  8.26s/it]12/21/2019 11:48:32 - INFO - __main__ -   [98 epochs] train loss: 0.464
12/21/2019 11:48:35 - INFO - __main__ -   sampled sequence: この勝利俺はNo.大丈夫だろこの場でラップO....ラップしてるぜ一回
12/21/2019 11:48:35 - INFO - __main__ -   [98 epochs] valid loss: 0.634
Epoch:  98% 98/100 [13:43<00:17,  8.55s/it]12/21/2019 11:48:41 - INFO - __main__ -   [99 epochs] train loss: 0.431
12/21/2019 11:48:43 - INFO - __main__ -   sampled sequence: もうここで1回もコイツ俺はこの上に行くぜこのラップよりもいいよそんな事だぜ
12/21/2019 11:48:43 - INFO - __main__ -   [99 epochs] valid loss: 0.647
Epoch:  99% 99/100 [13:51<00:08,  8.30s/it]12/21/2019 11:48:48 - INFO - __main__ -   [100 epochs] train loss: 0.418
12/21/2019 11:48:51 - INFO - __main__ -   sampled sequence: くだらうぜフロウの事俺らの方じゃないのかだろここで俺らの方が
12/21/2019 11:48:51 - INFO - __main__ -   [100 epochs] valid loss: 0.523

あんまりいい例を選び損ねてしまったのですが、段々と性能が上がっていることが見て取れます。

試したこと

学習で試したパターンは以下の通りです

  • 1行ずつ
    • 1-1. 約1200文 (train : valid = 600 : 100)
    • 1-2. 約2700文 (2500 : 200)
  • 1ターンをまるごと1行にする
    • 2-1. 約380文 (300 : 80)
    • 2-2. 約2400文 水増し + (2200 : 200)
    • 2-3. 約2400文 水増し + 半角スペースを全角スペースに置換 したもの (2200 : 200)

まず、最初の1200文ではいい文章にならなかったため、諦めて 気合を入れて残りを集めました。データ量は正義ですね。

まるごと1行に関しては、「アンサー」を再現したかったので実施しました。

XXXXX >>> OOOOO

のような形式に加工し、右側(O の部分) を予測するよう学習させれば、ラップバトルのように相手の言葉を加味して返答を考える、といったことを期待しました。

また、1つ前だけではなく複数前のターンの内容も加味させられると面白くなるかなぁと。

その前段として、1行にまとめるのを試して、、、今回は燃え尽きました。

また「水増し」に関して、
1行にまとめると文章数が減ってしまうため、やはり精度が悪く使い物になりませんでした。

そこで、汚職政治家と機械学習業界ではおなじみの水増しを実施しました。

文章数を6倍くらいにしたかったため、 文章の中から名詞を抽出し、random で6つ選び、韻を踏んでいる単語に置き換えました。単語の検索には前回もお世話になった、「WEB 便利ツール」を利用しました。

kujirahand.com

こんなかんじ

変換前: ドープ
変換後: 覆う
高鳴って来たぜ心拍数 コイツは俺に負けて自身無くす この会場で俺は進化する 俺がバトルを始めればみんな夢中 ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター 俺からすれば ボンクララッパー 返してこいちゃんとした覆うなアンサー

変換前: 会場
変換後: 靉嘔
高鳴って来たぜ心拍数 コイツは俺に負けて自身無くす この靉嘔で俺は進化する 俺がバトルを始めればみんな夢中 ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター 俺からすれば ボンクララッパー 返してこいちゃんとしたドープなアンサー

変換前: 俺
変換後: カラオケ
高鳴って来たぜ心拍数 コイツはカラオケに負けて自身無くす この会場でカラオケは進化する カラオケがバトルを始めればみんな夢中 ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター カラオケからすれば ボンクララッパー 返してこいちゃんとしたドープなアンサー

変換前: 心拍
変換後: 金髪
高鳴って来たぜ金髪数 コイツは俺に負けて自身無くす この会場で俺は進化する 俺がバトルを始めればみんな夢中 ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター 俺からすれば ボンクララッパー 返してこいちゃんとしたドープなアンサー

変換前: 自身
変換後: 政審
高鳴って来たぜ心拍数 コイツは俺に負けて政審無くす この会場で俺は進化する 俺がバトルを始めればみんな夢中 ゴングは鳴ったぜ文句はあっか これは荒手のモンスターハンター 俺からすれば ボンクララッパー 返してこいちゃんとしたドープなアンサー

変換前: ゴング
変換後: レスポンス
高鳴って来たぜ心拍数 コイツは俺に負けて自身無くす この会場で俺は進化する 俺がバトルを始めればみんな夢中 レスポンスは鳴ったぜ文句はあっか これは荒手のモンスターハンター 俺からす れば ボンクララッパー 返してこいちゃんとしたドープなアンサー

さらに、文章の途中に切れ目を入れてくれないことが気に食わなかったため、半角スペースを全角スペースに直したパターンも試しました。

ただ、思い通りにいかなかったため、冒頭の生成文では時間が無いので手元にあった それなりに精度の出た2-2. のモデルを採用しました。

トラブルシューティング

メモ程度。。

  • Mac にJuman++ 入れるの大変

    • => Docker 使え
  • ValueError: invalid literal for int() with base 10: '\'

    • => Juman は半角使えない

http://nlp.ist.i.kyoto-u.ac.jp/index.php?KNP%2FFAQ

半角スペースを含む文が解析できないのですが。 † JUMAN/KNPでは、入力文が全角文字で記述されていることを前提としています。半角スペースなどの半角文字は全角文字に変換してから入力してください。

  • ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.11' not found (required by /opt/conda/lib/python3.6/site-packages/torch/lib/libtorch_python.so)

    • よくわからないが、リンカの設定が食い違っている? Tokenizer の前にimport torch しないといけない。謎。
  • Cloud Run

  • files named ['vocab.txt'] but couldn't find such vocabulary files at this path or url.

    • gcloud build submit は、.gitignore を踏襲するので、model ディレクトリがpush されてなかった
OSError: Model name './rap_bert_model' was not found in tokenizers model name list (bert-base-uncased, ..., bert-base-german-dbmdz-uncased).
We assumed './rap_bert_model' was a path or url to a directory containing vocabulary files named ['vocab.txt'] but couldn't find such vocabulary files at this path or url.
  • すぐDisk Full になる
    • Docker で試行錯誤してるとすぐ一杯になる
    • いま250GB しか積んでないから、次買い換えるときは拡張しよう。。

今後の展望

今回はBERT の導入と学習に精一杯で、ユーザー目線での改善がほとんどできませんでした。

これではユーザー会のみなさんに怒られてしまうので、下記を意識して、次回作では技術に走りすぎないように留意したいと思います。

  • ユーザー観点でのラップボットとしての改善点

    • 成長
    • 音楽
    • DOPE 度測りたい
    • 1つ前までしか(今までの内容を) 覚えてくれない
  • 文章生成モデルとしての改善点

    • データセットを増やしたい (ご協力お待ちしております)
    • データセットをアンサー形式にしたい

f:id:masaki925_8107:20191223212503j:plain
顧客が求めるもの

以上です、長文お読みいただきありがとうございました。

Flutter でScrollView がrebuild されたときにscroll to top したい

TL;DR

scheduler 使うとよい。(マサカリ歓迎)

import 'package:flutter/scheduler.dart';

class _MyWidgetState extends State<MyWidget> {
  ScrollController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
  }

  @override
  Widget build(BuildContext context) {
    if (_controller.hasClients) {
      SchedulerBinding.instance.addPostFrameCallback((_) =>
          _controller.jumpTo(0.0));
    }

    return Container(
      child: ...

エラーログ

そのままやると:

  Widget build(BuildContext context) {
    if (_controller.hasClients) {
      _controller.jumpTo(0.0);
    }

怒られる

Performing hot reload...
Syncing files to device iPhone XR...
Reloaded 1 of 553 libraries in 949ms.
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building MyWidget(dirty, dependencies:
flutter: [_LocalizationsScope-[GlobalKey#ab210], _InheritedTheme], state: _MyWidgetState#100b7):
flutter: Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.
flutter: The replaceSemanticsActions() method can only be called outside of the build phase.

参考

  • medium.com

    • 今回はjumpTo を使ったけどanimateTo などもある
  • flutterbyexample.com

    • (re) build の後に必ず呼ばれるメソッドが見つけられなかった。というかどこかにオフィシャルドキュメントありそう
  • stackoverflow.com

    • 自分はエラーメッセージから類推して「flutter how to add task after build phase has finished」でググって行き着いた

さて、なにしようとしてたんだっけ。そうだ、朝メシ。

JS周りのリハビリ備忘録(React + Babel + Webpack4)

今さらながらReact 入門しようと思ったんですが、JS 界隈の時代の流れに完全に置いてけぼりになっていました。

https://html5hive.org/react-tutorial/

↑ これをやってるときに、「あれ、browser-sync で表示されるのはいいとして、普通にfile open で開けないんだっけ?」と思っていろいろ調べたのがきっかけです。

※ ちなみに、browser-sync

$ browser-sync start --server . --files "**/*"

単純にfile open すると、ブラウザのconsole に下記エラーが表示されていました。

Access to XMLHttpRequest at 'file://~/tmp/my_react_tutorial2/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
transform.load @ browser.min.js:4

次に、

<script type="text/babel" src="main.js"></script>

type="text/babel" を消してみたところ、

Uncaught SyntaxError: Unexpected token <

というエラーが出ました。以下、リハビリのためにこのあたりを解消していった備忘録です。

サマリ

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G4015
$ npm -v
5.6.0
$ npx -v
9.7.1

before (ファイル構成)

$ tree .
.
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js

3 directories, 4 files

作業

$ npm i -D babel-core babel-loader@7 babel-preset-react webpack webpack-cli
$ npm list --depth=0
webpack-demo@1.0.0 ~/tmp/my_react_tutorial2
├── babel-core@6.26.3
├── babel-loader@7.1.5
├── babel-preset-react@6.24.1
├── lodash@4.17.11
├── webpack@4.28.2
└── webpack-cli@3.1.2
$ cat webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['react']
            }
          }
        ],
      }
    ]
  }
};
$ cat package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-react": "^6.24.1",
    "webpack": "^4.28.2",
    "webpack-cli": "^3.1.2"
  }
}
$ mkdir src
$ mv main.js src/index.js
$ tree . -L 1
.
├── babel.config.js
├── dist
├── node_modules
├── package-lock.json
├── package.json
├── src
└── webpack.config.js

3 directories, 4 files

$ tree dist/ src
dist/
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js
src
└── index.js

3 directories, 5 files

$ tree dist/ src/
dist/
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js
src/
└── index.js

3 directories, 5 files

webpack した結果をfile open

$ npx webpack
$ mv index.html assets/ dist/
$ open dist/index.html

これで、browser-sync したときと同じように表示されました。

以下、調査メモ

まず、手動でbabel でtranspile するとどうなるのか。

babeljs.io

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env
$ npm install --save @babel/polyfill

$ cat babel.config.js
const presets = [
  [
    "@babel/env",
    {
      targets: {
        edge: "17",
        firefox: "60",
        chrome: "67",
        safari: "11.1",
      },
      useBuiltIns: "usage",
    },
  ],
];

module.exports = { presets };
$ ./node_modules/.bin/babel src --out-dir lib

{ SyntaxError: ~/tmp/my_react_tutorial2/src/main.js: Unexpected token (114:8)

  112 |       //as a prop, so we need a separate id for that.
  113 |       return (
> 114 |         <Home
      |         ^
  115 |           key={index}
  116 |           id={index}
  117 |           onToggleSave={toggleSave}
    at Parser.raise (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:4051:15)
    at Parser.unexpected (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5382:16)
    at Parser.parseExprAtom (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6541:20)
    at Parser.parseExprSubscripts (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6104:21)
    at Parser.parseMaybeUnary (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6083:21)
    at Parser.parseExprOps (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5968:21)
    at Parser.parseMaybeConditional (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5940:21)
    at Parser.parseMaybeAssign (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5887:21)
    at Parser.parseParenAndDistinguishExpression (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6699:28)
    at Parser.parseExprAtom (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6473:21)
  pos: 3211,
  loc: Position { line: 114, column: 8 },
  code: 'BABEL_PARSE_ERROR' }

JSX がパースできていないらしい。

React はJSX と使うのがいいよ って話でBabel が入っていると認識していたのに、Babel だけではJSX はパースできないとは何事だ。

stackoverflow.com

を見ていると、webpack の話が出てくる。

そもそもBabel って何者だっけ、とかwebpack とはどういう関係だっけ、とか思い始めて調べ始める。

qiita.com

mizchi.hatenablog.com

www.youtube.com

↑ このあたりはとても参考になりました。

とりあえず概念を掴んだので、webpack からbabel を呼ぼうとする

$ npm run webpack
npm ERR! missing script: webpack

npm ERR! A complete log of this run can be found in:
npm ERR!     ~/.npm/_logs/2018-12-26T07_26_02_958Z-debug.log

ちょっとよくわからない。あ、local にインストールしたからか、と思って --development をつけても一緒。

Getting Started

ここらへんをみると、どうやら最近は npx を使うようになっているらしいのでやってみる

$ npx webpack

Insufficient number of arguments or no entry found.
Alternatively, run 'webpack(-cli) --help' for usage info.

Hash: b98875b6bc270ed9ed54
Version: webpack 4.28.2
Time: 64ms
Built at: 2018/12/26 16:30:09

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in Entry module not found: Error: Can't resolve './src' in '~/tmp/my_react_tutorial2'

src ディレクトリは存在しているのに。。

ERROR in Entry module not found: Error: Can't resolve './src' in 'C:\Idessign' · Issue #6858 · webpack/webpack · GitHub

を見ると、設定ファイルが無いとデフォルトで src/index.js を見に行く、とあるので、rename してみる

$ mv src/main.js src/index.js
$ npx webpack
Hash: 2887e62448d27ce303a0
Version: webpack 4.28.2
Time: 285ms
Built at: 2018/12/26 16:34:37
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 268 bytes {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js 114:8
Module parse failed: Unexpected token (114:8)
You may need an appropriate loader to handle this file type.
|       //as a prop, so we need a separate id for that.
|       return (
>         <Home
|           key={index}
|           id={index}

元のエラーに戻った。

これでwebpack が使えるようになったっぽいので、元のStackoverflow に戻って、 見よう見まねでwebpack のconfig を書いて実行してみる

$ cat webpack.config.js
{
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: [
    {
      loader: 'babel-loader',
      options: {
        presets: ['react']
      }
    }
  ],
}

$ npx webpack
Unexpected token :

シンタックスエラー。リハビリ中なのでしょうがない。正しくは、上記を rules に入れなきゃいけなかった (Stackoverflow にも書いてあった)

module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['react']
            }
          }
        ]
      }
    ]
  }
};
$ npx webpack

Insufficient number of arguments or no entry found.
Alternatively, run 'webpack(-cli) --help' for usage info.

Hash: 53a7a87a9524e153353b
Version: webpack 4.28.2
Time: 49ms
Built at: 2018/12/26 16:41:37

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in Entry module not found: Error: Can't resolve 'babel-loader' in '~/tmp/my_react_tutorial2'

足りないモジュールを入れてリトライ

$ npm install babel-core babel-loader --save-dev
npm WARN babel-loader@8.0.4 requires a peer of @babel/core@^7.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN babel-loader@8.0.4 requires a peer of webpack@>=2 but none is installed. You must install peer dependencies yourself.
npm WARN my-awesome-package@1.0.0 No description
npm WARN my-awesome-package@1.0.0 No repository field.
npm WARN my-awesome-package@1.0.0 No license field.

+ babel-loader@8.0.4
+ babel-core@6.26.3
added 48 packages, removed 520 packages and moved 5 packages in 8.407s

$ npx webpack
One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:
 - webpack-cli (https://github.com/webpack/webpack-cli)
   The original webpack full-featured CLI.
We will use "npm" to install the CLI via "npm install -D".
Do you want to install 'webpack-cli' (yes/no): yes
Installing 'webpack-cli' (running 'npm install -D webpack-cli')...
npm WARN babel-loader@8.0.4 requires a peer of @babel/core@^7.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN babel-loader@8.0.4 requires a peer of webpack@>=2 but none is installed. You must install peer dependencies yourself.
npm WARN webpack-cli@3.1.2 requires a peer of webpack@^4.x.x but none is installed. You must install peer dependencies yourself.
npm WARN my-awesome-package@1.0.0 No description
npm WARN my-awesome-package@1.0.0 No repository field.
npm WARN my-awesome-package@1.0.0 No license field.

+ webpack-cli@3.1.2
added 76 packages in 2.462s
{ Error: Cannot find module 'webpack-cli'
    at Function.Module._resolveFilename (module.js:555:15)
    at Function.Module._load (module.js:482:25)
    at Module.require (module.js:604:17)
    at require (internal/module.js:11:18)
    at runCommand.then (~/.npm/_npx/45193/lib/node_modules/webpack/bin/webpack.js:142:5)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:160:7) code: 'MODULE_NOT_FOUND' }

$ npm list | grep webpack-cli
└─┬ webpack-cli@3.1.2
npm ERR! peer dep missing: @babel/core@^7.0.0, required by babel-loader@8.0.4
npm ERR! peer dep missing: webpack@>=2, required by babel-loader@8.0.4
npm ERR! peer dep missing: webpack@^4.x.x, required by webpack-cli@3.1.2

webpack-cli は入ってるはずなのに、存在しないからインストールしようとしてくる。yes と答えてインストールしても、また同じエラーになる。

どういうことなの。。。

$ which npx
/usr/local/bin/npx

$ ll /usr/local/bin/npx

lrwxr-xr-x  1 admin  46 12 17  2017 /usr/local/bin/npx@ -> /usr/local/lib/node_modules/npm/bin/npx-cli.js
$ ll /usr/local/lib/node_modules/ | grep webpack
$ 

どうやらnpm のlocal にうまくwebpack が入っていなかったようで、もう一度npm i -D webpack する

$ ll node_modules/ | grep webpack
drwxr-xr-x    7 staff    224 12 26 16:48 webpack-cli/
#=> webpackが入ってない!
$ npx webpack
Hash: c0af3a1e4f45824bccdb
Version: webpack 4.28.2
Time: 263ms
Built at: 2018/12/26 16:55:19
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 2.81 KiB {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module '@babel/core'
 babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.
    at Function.Module._resolveFilename (module.js:555:15)
    at Function.Module._load (module.js:482:25)
    at Module.require (module.js:604:17)
    at require (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at Object.<anonymous> (~/tmp/my_react_tutorial2/node_modules/babel-loader/lib/index.js:10:11)
    at Module._compile (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:178:30)
    at Object.Module._extensions..js (module.js:671:10)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)
    at Module.require (module.js:604:17)
    at require (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at loadLoader (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/loadLoader.js:13:17)
    at iteratePitchingLoaders (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/LoaderRunner.js:169:2)
    at runLoaders (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/LoaderRunner.js:362:2)
    at NormalModule.doBuild (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModule.js:280:3)
    at NormalModule.build (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModule.js:427:15)
    at Compilation.buildModule (~/tmp/my_react_tutorial2/node_modules/webpack/lib/Compilation.js:633:10)
    at moduleFactory.create (~/tmp/my_react_tutorial2/node_modules/webpack/lib/Compilation.js:1019:12)
    at factory (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:405:6)
    at hooks.afterResolve.callAsync (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:155:13)
    at AsyncSeriesWaterfallHook.eval [as callAsync] (eval at create (~/tmp/my_react_tutorial2/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:6:1)
    at AsyncSeriesWaterfallHook.lazyCompileHook (~/tmp/my_react_tutorial2/node_modules/tapable/lib/Hook.js:154:20)
    at resolver (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:138:29)
    at process.nextTick (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:342:9)
    at process._tickCallback (internal/process/next_tick.js:150:11)

バージョン依存があったらしい。勝手に解決してくれないのか。自分で指定して入れる。

npm i -D babel-loader@7
$ npx webpack
Hash: 75e3609ad92c952b5d1e
Version: webpack 4.28.2
Time: 636ms
Built at: 2018/12/26 16:59:07
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 1.59 KiB {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Couldn't find preset "react" relative to directory "~/tmp/my_react_tutorial/src"
    at ~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:293:19
    at Array.map (<anonymous>)
    at OptionManager.resolvePresets (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:275:20)
    at OptionManager.mergePresets (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:264:10)
    at OptionManager.mergeOptions (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:249:14)
    at OptionManager.init (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:368:12)
    at File.initOptions (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/index.js:212:65)
    at new File (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/index.js:135:24)
    at Pipeline.transform (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/pipeline.js:46:16)
    at transpile (~/tmp/my_react_tutorial/node_modules/babel-loader/lib/index.js:50:20)
    at Object.module.exports (~/tmp/my_react_tutorial/node_modules/babel-loader/lib/index.js:173:20)

今度はreact が見つからない、とのことなので、関係ありそうなモジュールを追加

$ npm i -D babel-preset-react
$ npx webpack
Hash: 3500f1c5383324b5f4a0
Version: webpack 4.28.2
Time: 412ms
Built at: 2018/12/26 17:00:28
  Asset      Size  Chunks             Chunk Names
main.js  3.22 KiB       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 3.84 KiB {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

これで、ようやく dist 配下に、bundle されたmain.js が置かれた。

時代の流れについていくのは大変だなぁ。

あれ、何しようとしてたんだっけ

AWS Lambda でHeroku の無料枠でサーバーが寝ないようにする(2018年版)

先日、ラップボットのDJ マルコをご紹介しました。

masaki925.hatenablog.com

DJ マルコはheroku にデプロイしてるんですが、下記のような問題がありました。

  • フロント(Rails on Heroku) とバック(Flask on Heroku) の2台構成になっている
  • heroku サーバーは30分リクエストが無いと寝てしまうので、ただでさえ起動が遅いのに加えて、フロントがバックの起動を待っている間にタイムアウトして、必ず1リクエスト目がエラーになってしまっていた

そのため、定期的にping して起こしておきたいと思いました。 少し調べたところ、下記の記事が見つかりました。

casualdevelopers.com

UptimeRobot は設定が非常に簡単で良かったのですが、監視の時間帯を設定することができないようでした。

そうすると、24時間 2つのdyno が動き続けることになり、合計で24 * 2 * 30 = 1440 時間となってしまい、無料枠の1000時間を超えてしまう問題がありました。

他の案も、dyno 時間を消費してしまうので、今回の要件には合わなそうでした。

そこで、AWS Lambda でhealth check だけするfunction を作って、AWS Watch Rules で昼間だけ動くようにしました

  • lambda_handler.py
from urllib.request import urlopen
from datetime import datetime, timedelta

def validate(expected, res):
    return expected in str(res)

def lambda_handler(event, context):
    try:
        if not validate(event['expected'], urlopen(event['site']).read()):
            raise Exception('Validation failed')
    except:
        print('Check failed!')
        raise
    else:
        print('Check passed!')
        return 'OK'
    finally:
        print('Check complete at {}'.format(str(datetime.utcnow() + timedelta(hours=9))))

ping 対象のURL と、チェック文言を起動時の引数で与えるようにしているので、他のURL でも流用が可能です。

今回は

{"site": "https://rhyme-bot.herokuapp.com/health/marco", "expected": "OK"}       

AWS Watch Rules に設定しています

f:id:masaki925_8107:20181225152808p:plain

これで、9時〜24時の15時間だけ起こしておくことになったので、15 * 2 * 30 = 900 時間で、無料枠(1000時間) に無事収まります。やったね!

マルコフ連鎖でラップボットつくった

追記 (2020/01/14)

BERT 版はこちら

masaki925.hatenablog.com

はじめに

こんにちは。ヴァイブス満タンですか?

この投稿は LINEBot&Clova Advent Calendar 2018 の14日目の投稿です。

一昨年昨年 に続いて、ラップボットの第3弾となります。

昨年はDopeLearning を使って、ノスタルジックなラッパー、ピーさんをリリースしました。

昨年を振り返って反省したこととしては、「バトル感が無い」ということでした。

そこで今年はもっとバトル感を醸成すべく、世のラッパーたちが過去に残したパンチラインを学習し、前人未踏のパンチの効いたヴァースをかますラッパーを生み出したいと考えました。

そこで目をつけたのが、「マルコフ連鎖」という文章生成モデルです。

マルコフ連鎖での文章生成については、下記の記事がわかりやすかったので紹介させていただきます。

f:id:masaki925_8107:20181222121617p:plain 引用元: マルコフ連鎖による文章生成 - 知識のサラダボウル

簡単に言うと、上記の図の通り、「ある単語が来たら次に来そうな単語はこれだ」というパターンをあらかじめ学習しておくことで、パターンに沿って自由に文章を生み出すことができるようになります。

そうして生まれたのがこちら、「お麩」界のカリスマこと、DJ マルコ です。

f:id:masaki925_8107:20181222114330p:plain:w300

マルコにバトルを仕掛けると、こんなかんじでケンカを買ってくれます。

f:id:masaki925_8107:20181223182813p:plain:w300

いかがでしょうか。

「お前のライム ナチュラル無農薬野菜」は、攻撃力が無くてむしろ健康になっちゃうぜ、的なdis りなのか、あるいは手間ひまかけていいもの作ってるね、的な賛辞なのか、一瞬戸惑ってしまう奥深いパンチラインですね。

そんなDJ マルコとバトルをしてみたいという奇特な方がいらっしゃったら、ぜひ下記のリンクからバトルを仕掛けてみてください。

f:id:masaki925_8107:20181223183919p:plain:w200

友だち追加

DJ マルコの育て方

サーバー構成について

  • チャットボットのフロントエンドとして、LINE Messanger API + Rails を利用しました。Rails なのは前回のコードを流用したからです。

github.com

  • ラップ生成エンジンを別API として、Python(Flask) で動くWeb API をDocker で固めてHeroku にデプロイしました

github.com

ラップ生成エンジンについて

大変だったこと

どうすればラップっぽい文章が生成できるか

マルコフ連鎖で今回ポイントになると考えた点と、TextGenerator での実装は下記の通りでした。

  • スタートをどう始めるか
    • 開始フラグ( __BEGIN__ ) がついたノードをランダムに選択
  • 途中のノードをどう選択するか
    • 出現比率に応じてランダム
  • 文章の最後はどう終わるか
    • 終了フラグ( __END__ ) がついたノードに当たったら

ここで、ラップバトルには「アンサー」と呼ばれる、相手のヴァースに対してメンションし、うまく次の攻撃に繋げるという特徴があることから、スタート部分を下記のとおり改変しました。

  • チャットで受け取った文をMeCab形態素解析し、単語を含むレコードを起点として文章を生成

しかし、今回は、パンチラインの数が少なかったこともあり、該当する単語がDB に無いことが頻発しました。

そこでword2vec で類似する単語を推定し、元の単語が見つからないときには類似単語でレコードから参照するようにしています。

word2vec の語彙コーパスとしては、 Livedoor コーパス を利用しました。

その他つまづきポイント

heroku container:push が重い

MeCab の辞書を含んでいたため、1GB を超えるファイル転送が走り、待ち時間が長くなってしまうことがあったため、なるべくローカルでdocker run して問題がないことを確認してからpush するようにしました

Mac とalpine のmecab で、surface の返り値が異なる

これがなかなか曲者で、「俺がウドの大木」という文章をパースした際、下記のような違いがありました

Mac

mt = MeCab.Tagger()
node = mt.parseToNode('俺がウドの大木')
node = node.next # EOS をスキップ
node.feature
#=> '名詞,代名詞,一般,*,*,*,俺,オレ,オレ'
node.surface
#=> '俺'

Alpine

(途中省略)

node.feature
#=> '名詞,代名詞,一般,*,*,*,俺,オレ,オレ'
node.surface
#=> '俺がウドの大木'

わかりづらい。。

今後の展望

  • 韻を踏めてないので、踏めるようにしたい
  • パンチラインのデータベースを増やしたい
  • マルコフ連鎖のノード選択を、ランダムではなく何かしらのロジックを入れたい

以上となります。