アドベントカレンダーは、ぎりぎりにならないと書けないを解決する

この記事は freee Developers Advent Calendar の 23 日目の記事です。


悩みの種の泉へようこそ

ここでは皆様から送られてきた悩みの種を ひも(@him0net) が無駄に技術で解決し、どのくらい解決できているかを担当編集者の takuma に評価してもらいます。

それでは、悩みの種を紹介しましょう。

「こんにちは、会計 freee のアプリケーションをエンジニアをしている ひも です。弊社では freee developer アドベントカレンダーというものがあり、12/1 から 12/25 のクリスマスまで、1日1つブログ記事を公開していくという取り組みをしています。私は毎年11月に、この執筆者の募集で立候補してしまうのですが、いざ12月になるとバタバタして全然記事が書けていないことに状態で苦しんでいます。ここで思ったのですが、技術の力で記事の執筆を支援することはできないでしょうか?よろしくおねがいします。」

この悩みの種、技術で解決するとこうなります。

developers ブログの記事をもとに文章の自動生成を行うと「xxx」という文章ができる。

実際に実装してみた

まずは、文章の生成の方法の技術選定を行う。文書の生成プログラムをざっくり調べたところ、Chainer や TensorFlow を用いた RNN (回帰型ニューラルネットワーク)によるものと、マルコフ連鎖に基づくものが見つかった。

時間が無い(超重要)ので、アルゴリズムを理解しているマルコフ連鎖に基づく文章の自動生成を採用することにした。

また freee のアドベントカレンダーの記事を作成することが目的なので、シードデータには過去のアドベントカレンダー (2018-2020) の記事を利用することにする。記事の収集には先進の技術(コピペ)を用いた。

f:id:him0:20201223011411g:plain
アドベントカレンダーをコピペしてシードデータを集めている様子。

マルコフ連鎖の辞書作成にあたり、分かち書きには、形態素解析エンジン MeCab を用いた。セットアップには ipa辞書インストールも必要であった。

python から MeCab が呼び出せる環境を構築を行い、分かち書きを行った。

$ poetry run python wakati.py
['', 'こんにちは', '、', 'freee', '株式会社', 'で', 'エンジニア', 'を', 'やっ', 'て', 'いる', 'id', ':', 'ymrl', 'です', '。', '\n', 'はやい', 'もの', 'で', '、', '2018', '年', 'も', '残す', 'ところ', '1', 'ヶ月', 'と', 'なり', 'まし', 'た', '。', '\n', '12', '月', 'と', 'いえ', 'ば', '年末', '調整', 'と', 'Advent', 'Calendar', 'です', 'ね', '!', 'という', 'わけ', 'で', '、', 'この', '記事', 'は', 'freee', 'Developers', 'Advent', 'Calendar', 'の', '1', '日', '目', 'です', '。', '\n', '今年', 'も', 'また', '12', '月', '25', '日', 'まで', '毎日', 'リレー', 'で', '記事', 'を', '掲載', 'し', 'て', 'いき', 'ます', '。', '\n', 'さて', '、', '今回', 'は', '開発', '者', 'ブログ', '、', 'つまり', ...

分かち書きが正しくできているのか確認すため、単語の出現回数順で表示してみた。

$ poetry run python collection_order.py
('の', 4699)
('、', 4405)
('\n', 3983)
('。', 3968)
('を', 3824)
('て', 3585)
('に', 3415)
('が', 2946)
('は', 2708)
('で', 2459)
('た', 2301)
('し', 2279)
('ます', 1709)
('と', 1603)
('です', 1125)
('こと', 971)
('も', 970)
('い', 898)
('する', 832)
('な', 797)
('まし', 789)
('いる', 599)
('という', 572)
('freee', 561)
...

freee という単語がこの順位ででてくるのはなかなかにこの freee developers blog らしい文章ができそうで期待が高まってきた。

マルコフ連鎖のロジックの実装を行う。階層は適当に決め打ちで3階でいくことにした。3単語をキーにして、次に出てくる単語が決定する。分かち書きを行った単語をキーとその後に出てくる単語のリストと言う形で辞書として格納していく。

dictionary = [
  ([ 'こんにちは', '、', 'freee' ], [ '株式会社', ... ]),
  ([ '、', 'freee', '株式会社' ], [ 'で', ... ]), ...
]

実際の文章を生成するロジックでは最新の3単語は保持して、マルコフ連鎖の辞書から次のワードのリストを決定し、リストの要素からランダムで1つの単語を抽選する。

prev_three = ['こんにちは', '、', 'freee']

# python で、array find するの地味に大変だった
next_word_list =  next(filter(lambda x: (x[0][0] == prev_three[0] and x[0][1] == prev_three[1] and x[0][2] == prev_three[2]), dictionary), None)
next_word = random.choice(next_word_list)

ロジックが出揃った。

実際に出力してみた。

さて、どんな文章が生成されるのか!!!

f:id:him0:20201223135850g:plain
terminal でプログラムを実行している様子

$ poetry run python markov_chain.py
昔からエンジニアリングもマネージメントも好きなので、エンジニアとしての原点回帰であっても、どういった料理が来たかや何をお皿によそったのかを知るために編 み出した方法として、巷では自作キーボード界隈でいうところの終着点的な意味です。

こうしてこの世界でまた1つ悩みの種が技術で解決された。

developers ブログの記事をもとに文章の自動生成を行うと「エンジニアから料理に目覚め、自作キーボードに落ち着く」という文章ができる。

運命の評価発表

それでは、どのくらい解決できたかを評価していただきます。

him0「どうですか?」

takuma「エンジニアとしてのキャリア像について語るかと思いきや実際には料理について考察しているようで、非常に興味深いです。」

takuma「でも、そんなことより、来年は11月から準備してくださいね。」

him0 🥺

him0「明日は、hg さんの記事です。」

him0, takuma 「それでは、またお会いしましょう。でわでわ〜」


自分はマルコフ連鎖の辞書をタプルで実装してしまったが、[Python]N階マルコフ連鎖で文章生成 の deque を使った実装がきれいだったので、コードはこちらを参考にした方が良いです。また、文章の初めに [BOS] を設定し、 を FOS として扱う実装もこちらを参考にさせていただきました。ありがとうございました。