チコちゃんに叱られないよう、チカちゃんとしりとりをする

「チコちゃんに叱られる！」はNHKの番組です。チコちゃんの素朴な疑問に答えられないと「ボーっと生きてんじゃねーよ！」と叱られるという番組です。ちなみにこの記事を書いている12月末ではさだまさし先生がゲストで登場しました([さだまさしアドヴェントカレンダー](https://qiita.com/advent-calendar/2015/sadatech)を懐かしく思い出します)。

この番組の中で「チコの部屋」というしりとりゲームをするコーナーがあります。チコちゃんは5才とは思えない語彙力でゲストを圧倒するのですが、これを見ていた私は大人げなくチコちゃんにどうにか勝ちたいと思いました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/f2e904fc-2431-e7d0-7302-3680da20d074.png" width=500>
出典: [NHK 「チコちゃんに叱られる！」より](https://www4.nhk.or.jp/chikochan/)

そして生まれたのが、体はNuxt x Firebase、頭脳はPython x 自然言語処理で構成されたその名もチカちゃんです(イラストはまだない)。全体の構成とデータの流れは以下のような感じになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/45396927-f7f0-f45c-3ae5-7044bbc9d004.png)

実装はこちらです。

[icoxfog417/chika: Japanese Shiritori Master Chika](https://github.com/icoxfog417/chika)

チカちゃんの開発では、チコちゃんに負けない教養を身に着けることを意識しました。そのため、単純な辞書系のデータ(Wikipediaなど)ではなく、文学作品から語彙を取得しています。具体的には青空文庫の[2019年4月のアクセスランキングトップ500](https://www.aozora.gr.jp/access_ranking/2019_04_xhtml.html)から語彙を取得しています。

実際のアプリケーションの画面は以下になります(↓の画像をクリックするとアプリケーションに飛びます)。

<a href="https://chika-5bc38.web.app/" target="_blank">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/c451e2b6-8b38-bfb6-1a18-7ca017ed6579.png" alt="application image" width=500/?
</a>

実際の作品にふれられるよう、単語が使われている文と作品へのリンクを付与しています(そして縦書きです)。一部未実装なしりとりのルールはありますが、そこそこ遊べると思います。これでチコちゃんに負けない頭脳を目指す！

**チカちゃんのここがすごい！**

しりとりをしつつ日本の古典に触れることで語彙と教養が同時に身に着く！

**チカちゃんのここがすごくない！**

大人げない！

ここから先は、使用した技術についてポイントを解説を行っていこうと思います。

1. Firebase
2. Nuxt
3. Pythonによる自然言語処理

## Firebase

近年のアプリケーション開発では、フロントエンドとバックエンドをAPIで連携させることが多いです。これにより、フロントエンドは表示機能、バックエンドはデータ処理に集中できます。いわゆるマイクロサービスの構成です。

Firebaseは、「バックエンド」の実装を楽にしてくれるサービスです。Firebaseを使用することで、開発する側は「フロントエンド」に集中できるだけでなく様々なフロントエンド媒体(モバイル、Web、ゲームなど)でバックエンド処理を使いまわすことができます、

以下は、Firebaseの設定画面です。「バックエンド」に必要な機能がすべてそろっていることがわかると思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/e20b3f2f-d036-ae41-24b5-d9961fc1e0ee.png" width=300>

* Authentication: 認証
* Database: データの保存/読み取り
* Storage: 静的データの保存/配信
* Hosting: アプリケーションのホスティング
* Functions: ビジネスロジック/定期処理の実装
  * 実体はCloud Functionなので、GCP側でも実装可能
  * ML Kitを使用することで、機械学習機能も使用できる(画像認識など)

実装だけでなく、テスト・アクセス分析などアプリケーションの改善サイクルを回すための機能も提供されています。アプリケーション開発者の強い味方と言えるでしょう。料金も、無料のSpark/無償枠のある従量課金のBlazeが用意されており、ちょっとしたアプリケーションなら無料枠の範囲で作成可能です(ただし油断はしない方がいいので、アラートは設定しておくのが吉)。

<img scr="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/63fbe431-277e-c168-1a01-0615fcbed8be.png" width=500/>

Firebaseは以下から使い始めることが可能です。

[Firebase](https://firebase.google.com/)

Firebaseの設定/使い方については既にわかりやすいチュートリアルがたくさんあります。以下の記事がわかりやすかったです。

* [Nuxt.js プロジェクトを Firebase Hosting にデプロイして公開する方法](https://upd.world/nuxtjs-deploy-firebase-hosting/)
* [Nuxt で Firebase を使う (Cloud Firestore & Hosthing)](https://chaika.hatenablog.com/entry/2019/01/21/113000)

前述の通り、今回はフロントエンドにNuxtを使用しているのでそれを前提とした記事になっています。1点注意があり、Nuxtのホスティングには2つ方法があります。

1. SPA: `nuxt generate`でコンテンツを生成し、それをHostingする
2. SSR: フロントエンドをHostingするだけでなく、SSRをCloud Functionで稼働させる

Nuxtはサーバーサイドレンダリング(SSR)の機能があります。これは、サーバー側で事前にレンダリングを行いフロントエンドに配信する機能です。「サーバー側で」と述べた通り、SSRの機能はいわゆる「バックエンド」側の処理になります。そのため"Hosting"ではなく"Functions"として実装する必要があります。
つまり、"2"の方法では「フロントエンド」としてのNuxtと「バックエンド」(SSR)としてのNuxt 2つをデプロイする必要があるということです。

"2"の実装方法は以下で解説されています。フロントエンド用は`src`、バックエンド用は`functions`でフォルダを分けるのが通例のようです。

* [Nuxt.jsとFirebaseでSPA×SSR×PWA×サーバーレスを実現する](https://inside.dmm.com/entry/2018/04/10/nuxt-firebase)
* [potato4d/nuxt-firebase-sns-example](https://github.com/potato4d/nuxt-firebase-sns-example)
* [Deploy nuxt on Firebase](https://dev.to/kiritchoukc/deploy-nuxt-on-firebase-4ad8)

ただ、"2"の方法はあまりお勧めしません。あれ、どっちのNode/Nuxtで開発してるんだっけ?という混乱が生じて思わぬバグを踏む可能性があるためです(なお[Cloud Functionで使えるNode.jsのバージョンは限定されており](https://firebase.google.com/docs/functions/manage-functions?hl=ja#set_nodejs_version)、使用可能なバージョンを選択する必要があります)。SSRが必須でない限り、"1"でやる方が簡単だと思います。事前にサイトを生成してしまうので、動的なSSRが必要ない限り速度も速くなります。

## Nuxt

[Nuxt](https://ja.nuxtjs.org/guide)は[Vue.js](https://jp.vuejs.org/index.html)をコアとして開発されたアプリケーションフレームワークです。Vue.jsはフロントエンドを実装するための「ライブラリ」なので、モジュール化の方法やそれに伴うフォルダ構成、また開発ツール(BABELとか)は開発者の選択にゆだねられています。これらはほっておくと無法地帯になりがちなので、それらについてのベストプラクティスを「フレームワーク」としてまとめたものがNuxtになります。

Nuxtの開発で必要な知識は、[Vue.js](https://jp.vuejs.org/index.html)と[Vuex](https://vuex.vuejs.org/ja/)が中心になります。特に、Vuexの状態管理パターンは事前に読んでおく価値があります。以下は、Vuexの状態管理フローと、フローを実装するNuxtのフォルダを矢印で示した図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/766809d7-3ca7-3138-3120-da5154d73c66.png" width=600 />

* Vue Component
  * 表示(画面)を担当します。
  * Nuxtのフォルダでは、`layouts`でページのテンプレート、`pages`で各種ページ、`components`でページのパーツを定義します。
* Vuex(Actions/Mutations/State)
  * アプリケーションの状態はすべて`State`で管理します。`State`を更新できるのは`Mutations`だけです。
  * [`Mutations`は同期処理でないといけないというルールがあります](https://vuex.vuejs.org/ja/guide/mutations.html)。「同期的な`Mutations`前後以外で`Store`の値が変わることはない」という制約を課すことで、アプリケーションの状態変化を追いやすくする意図があります(多分)。この制約がないと、様々かつ非同期で実行され得る`Actions`を全部追跡しないといけなくなります。
  * Componentからは`Action`を呼び出し(`dispatch`)、`Actions`からは`Mutations`を呼び出します(`commit`)。
  * 共通処理は`plugins`に切り出します。
  * `middleware`はページ遷移時のフック処理を実装できる機能です。これにより権限によるページ遷移などを実装できます。

Firebaseの開発では、様々なパッケージが提供されています。これらを使用して開発を行っていきます。

* [`firebase`](https://firebase.google.com/docs/web/setup?hl=ja)
  * Firebaseの各種機能(データを保存する`firestore`や関数呼び出しの`functions`など)を呼び出すことができます。
* [`firebase-admin`](https://firebase.google.com/docs/admin/setup?hl=ja)
  * `firebase`とほぼ同じ機能ですが、Functions(サーバーサイド)で使用されることを想定しています。参考: [Firebase Authentication vs Firebase Admin](https://stackoverflow.com/questions/51045549/firebase-authentication-vs-firebase-admin)
* [`firebase-tools`](https://firebase.google.com/docs/web/setup?hl=ja#install-cli_deploy)
  * Firebaseへのデプロイなどを行うためのツールです。 

### Store/Plugin vs Functions

どこまでNuxtで実装して、どこからFunctionsで実装したほうがいいのか、というのは悩ましいポイントです。今回はビジネスロジックに相当する部分(しりとりチェックの一部)をFunctionsで実装しました。Functionsは異なるアプリケーション媒体から使えるので、媒体独自の処理・ローカルデータを用いた処理・レイテンシが問題になる処理など以外はFunctionsで実装したほうがいいのかもしれません。

今回は、汎用的なしりとりルールをFunctionsで実装しました。

* 意味のある単語か
* しりをとっているか(前回単語の末尾を送信)
* 「ん」がついていないか

しりとりでは単語の「読み」がわからないと話にならないので(「平等院鳳凰堂」の場合、先頭の文字は「ビ」で最後の文字は「ウ」)、読みの取得を[gooの形態素解析API](https://labs.goo.ne.jp/api/jp/morphological-analysis/)で行いチェックしています。

逆に、ローカルデータを用いたルールの実装はクライアント側で行っています(=しりとりのやり取りを逐一サーバーには送っていません)。

* 既出の単語でないか

Functionsの開発は以下のチュートリアルがわかりやすいです。

[Cloud Functions Tutorial](https://firebase.google.com/docs/functions/get-started?hl=ja)

* regionは、[FirestoreのFunctionsは今のところus-central1のみのようです。](https://firebase.google.com/docs/functions/locations?hl=ja#http_and_client-callable_functions)。
* 外部への送信は請求登録なしでは無理のようです。 [Error: getaddrinfo EAI_AGAIN](https://stackoverflow.com/questions/40182121/error-getaddrinfo-eai-again)
* アプリケーションから呼び出す場合は、デフォルトの`onRequest`ではなく[`onCall`を使用する必要があります](https://firebase.google.com/docs/functions/callable?hl=ja)(そうでないと、恒例のCORSエラーが発生します)。

### Get from Firestore

データの補完・取得はFirestoreから行っています。Realtime Databaseとどちらがいいの、と気になりますが基本的には後発のFirestoreの方が良いようです。

[Realtime Database vs. Cloud Firestore — Which Database is Suitable for your Mobile App](https://medium.com/datadriveninvestor/realtime-database-vs-cloud-firestore-which-database-is-suitable-for-your-mobile-app-87e11b56f50f)

しりとりでは使われていない単語を「ランダムに」抜く必要があります。xxから始まる単語を全部抜いてサンプルする・・・なんてことをやっていた日にはえらい量を通信しないといけないので、今回は各単語(ドキュメント)にIDを振り、IDを生成/指定してデータを取得しています。

Firestoreからのデータ取得は、以下のチュートリアルがわかりやすいです。

[Cloud Firestore でデータを取得する](https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja)

## Pythonによる自然言語処理

データの取得は[青空文庫](https://www.aozora.gr.jp/)から行いました。[アクセスランキング](https://www.aozora.gr.jp/access_ranking/)が提供されていたので、そこから各作品のURLを取得し、XHTMLファイルのデータをダウンロードしています。サイトに負荷をかけないよう、一回アクセスしたページはhtmlを保存して次回からアクセスせずに処理できるようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/7ac681fd-0178-ba57-ca7c-e2edad587831.png)

形態素解析には[Janome](https://mocobeta.github.io/janome/)を使用しています。単語は複数の形態素に分かれるケースがあるため(平等院鳳凰堂なら平等/院/鳳凰/堂、など)、これを連結する必要があります。Janomeにはこれをサポートする[Analyzer](https://mocobeta.github.io/janome/#experimental-analyzer-v0-3-4)という機能があります。Analyzerは、形態素解析における定型的な処理(前処理、フィルタ、マージ)などをパイプラインで実装できる機能です。

名詞を連結して複合名詞にする`CompoundNounFilter`、また名詞のみを取り出すのに使える`POSKeepFilter`はデフォルトで提供されているので、今回はこれらを使用しました。

なるべく各作品・頭の単語が均等になるよう単語を取りましたがやはり偏りがでました(前処理が足りず、一部変な開始単語もありますが)。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25990/00134274-d1d7-8cb5-35a5-3fece715c69a.png" width=800 />

ここで取得したデータをFunctions経由でFirestoreに登録して、チカちゃんの頭脳としています。

チカちゃんとしりとりをして、豊富な教養をもって2020年を迎えましょう！