「わかりやすさ」に注目した、機械学習による技術ブログの検索

新しい技術を身に付けたい！と思って検索をしても、検索上位に来る記事が「わかりやすい」かというとそうではない、ということはよくあります。
記事のビュー数、またQiitaのいいね数やはてブ数は、この参考にはなりますが体感として高ければいいというものでもない印象です。

そこで、こうした文章の「評判」だけでなく、文章そのものの構成や書き振りなどに注目して、その「わかりやすさ」を評価できないか？ということで実験的に行ってみたものが以下の「Elephant Sense」になります。

[chakki-works/elephant_sense](https://github.com/chakki-works/elephant_sense)
(Starを頂ければ励みになりますm(_ _)m)

![top.png](https://qiita-image-store.s3.amazonaws.com/0/25990/2ec5da3e-e7fc-6090-777a-98ca43490a44.png)

※名前の由来は、象の感覚って実はものすごいらしいという話から

開発に際しては基本的な自然言語処理/機械学習の手続き踏んで行ったので、その過程を本記事でご紹介したいと思います。

## ゴール

事前にQiitaの記事を対象に、「単純に検索した場合」「いいね数が高いものを抽出した場合」とで、どれぐらいわかりやすいと感じられる記事があるかを調べてみました(この時の「わかりやすさ」は主観で評価しました。以降のフェーズで、この感覚をどう扱うか説明してきます)。
その結果、以下のような状態であることがわかりました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/25990/0a08b9b2-24c4-c35c-b014-02a55a5871a2.png" width="600px">

いいねを基準に抽出しても5割には到達しない・・・という状況のようだったので、まずは半分はわかりやすいと感じられる状態にしたい、というところをゴールに設定します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/25990/8b29f5ac-2a80-8064-c984-0db2c940acb1.png" width="600px">


## 先行研究

文章のわかりやすさに対する評価、というのは先行研究がいくつかあります。英語圏で有名なものは、以下の2つの指標です(「[読みやすさの評価指標（1）](http://doksyo-tek.hatenablog.com/entry/2015/05/01/093751)」のブログに詳しいです)。

* Flesch Reading Ease
  * 一文あたりの単語数(文の長さ)、一単語あたりの音節数を利用して算出する
* Flesch-Kincaid Grade Level
  * Flesch Reading Easeと同様の特徴を使うが、低い方が高学年向けになる(=難しい)

つまり、「多くの単語が詰め込まれた長い文はわかりにくい」ということですね。逆に適切な文長で、利用されている単語が全体として少なければスコアはよくなります。
ちなみにMS Wordではこの指標を算出することが可能です。試しに計測してみるのも良いかもしれません。

この指標の用途ですが、主に特定の文章が小学生向けか、高校生向けか、といったテキストの難易度の測定に利用されています。国内では以下のような研究の取り組みがあります。

* [長岡技術科学大学 リーダビリティー・リサーチ・ラボ](http://readability.nagaokaut.ac.jp/)
* [名古屋大学 大学院工学研究科 電子情報システム専攻 佐藤・松崎研究室 日本語テキストの難易度を測る](http://kotoba.nuee.nagoya-u.ac.jp/sc/obi3/)

ただ、何も教科書的な文章を想定しているため、そのスコアは文章に含まれている教育漢字とひらがなの割合などに大きく左右され、今回の技術ブログのように、標準表記で書かれているテキストの文章レベルを測定・推定するには限界がある印象です(この点は、「[日本語学習者にとって読みやすい文章について](https://www.ninjal.ac.jp/event/specialists/project-meeting/files/JCLWorkshop_no2_papers/JCLWorkshop2012_2_34.pdf)」でも指摘されています)。

近い研究としては、以下の計算機マニュアルのわかりやすさの測定についての研究がありました。

[計算機マニュアルの分かりやすさの定量的評価方法](https://ipsj.ixsq.nii.ac.jp/ej/?action=pages_view_main&active_action=repository_view_main_item_detail&item_id=14869&item_no=1&page_id=13&block_id=8)

こちらの研究ではマニュアルにおける表現の品質として、以下2つの観点を提示しています。

* 表現の正確さ：内容の正しさ、表記の正しさ(文法誤りなど)
* 表現の品質：理解しやすいか、読みやすいか、使いやすいか(検索性)、図表は具体的か、例示は適切かなど

技術ブログ的にいえば、「書いている内容が合っているか」と「書き方は適切か」という二点ということです。この研究と同様、今回は「表現の品質」にフォーカスします(表現が正確かどうかは外部知識が必要なため)。後述しますが、利用する特徴量についてもこの研究を参考にさせて頂きました。

その他、文の特徴に関する研究として以下の論文にもざっと目を通しました(直近のNIPS/EMNLPあたりを参考に)。

* [Document Summarization Using Sentence Features](http://www.igi-global.com/article/document-summarization-using-sentence-features/128277)
* [Extractive Summarization Using Supervised and Semi-supervised Learning](http://anthology.aclweb.org/C/C08/C08-1124.pdf)
* [Recognizing contextual polarity in phrase-level sentiment analysis](http://dl.acm.org/citation.cfm?id=1220619)
* [Analyzing Linguistic Knowledge in Sequential Model of Sentence](https://www.aclweb.org/anthology/D16-1079)

## データの用意

今回は機械学習を利用することは決めていたので、何はともあれデータが必要です。具体的には、「この記事はわかりやすい/わかりにくい」といったアノテーションがされたデータということです。

通常、アノテーションを行うにあたってはまずタスクをしっかりと設計することが重要になります。

[NAISTテキストコーパスのアノテーション仕様書](https://sites.google.com/site/naisttextcorpus/ntc-annotation-scheme)

この方式については、以下の書籍にとてもよくまとまっているので、テキストデータのアノテーションを行う際は一度目を通しておくと良いと思います。

[Natural Language Annotation for Machine Learning](http://shop.oreilly.com/product/0636920020578.do)

今回、「わかりやすさ」について「後輩に読ませられるか」という基準にすることにしました。これは、アノテーターの知識によってわかりやすさがぶれることを回避するためです。そして、その評価は0/1で行うことにしました。これは、段階の定義が難しかったのと評価に動員できるのがチームの3人だけだったので、なるべく揺らぎがないようにする必要があったためです(さらっと書いてますが、この「わかりやすさ」をどうアノテートするかはだいぶ議論がありました)。

また、対象となるコーパスはQiitaから取ることにしました。つまり今回は「Qiitaの記事に対して後輩に読ませられるかどうかを0/1で評価」し、そのスコアを予測することで「わかりやすいと感じられる記事の量を50%に増やす」のが目的になります(上記の書籍によれば、アノテーションの作業とその先の達成したいタスクがきちんと連結しているのが重要とのことでした)。

アノテーションは100件の記事に対して行いました。ただ、無作為に抽出するといいね数が少ない記事が大半になってしまうのでそこは調整しました。
最終的なアノテーション結果は以下のようになりました(3人で行なったので、3がMAX(全員が1をつけた)になります)。

![annotation_result.png](https://qiita-image-store.s3.amazonaws.com/0/25990/6354f0a8-3a93-e934-56d0-6032ad1c99fa.png)

こちらのデータを利用し、モデルの構築を行なっていきます。

## モデルの構築

ニューラルネット依存症になっているとここでWord2VecでもかましてRNNでエンコードするとかそんな感じになりがちですが、今回はベースラインになるようなモデルをきっちり作ることにしました。

先行研究を参考にし、候補にした特徴量は以下の通りです。

* Length Feature
  * 文の長さ(平均・最大・最小)
  * 見出しの長さ
  * セクションの長さ
  * 文章の長さ
* Counting Feature
  * 単語数
  * 単語TF/IDF
  * ひらがな/カタカナ/漢字/英字/数字の割合
  * 句読点の数
  * 改行の数
  * 強調語の数(カギカッコ/ダブルクウォーテーションのペア)
  * 箇条書きの数・割合(全文数に対する)
  * 見出しの数・割合(全文数に対する)
  * 図の数(image)・割合(全文数に対する)

自然言語における特徴量は、基本的に「長さ」か「数」のどちらかに大きく分けられるというイメージです。「数」は、さらに母数を伴うことで「頻度(確率)」、前提条件をつけることで「条件付き確率」が導出できます。

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/31d02e6f-9a65-dfde-3132-7c2de8a49e03.png)

単語についても、今回は前処理を結構丁寧にやりました(この辺りは@Hironsanがあとで解説してくれるはず)。

リストアップした特徴量を利用してシンプルな[RandomForest](http://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)を利用し、テストを行なってみました(RandomForestは、各特徴量の貢献度が簡単にわかるのでその点も良いです)。

実際にモデルを構築した結果はこちらになります。

[elephant_sense/notebooks/feature_test.ipynb](https://github.com/chakki-works/elephant_sense/blob/master/notebooks/feature_test.ipynb)

<img src="https://qiita-image-store.s3.amazonaws.com/0/25990/0b41ea21-5863-f05d-6ff2-3c5f516b913b.png" width="400px">

データセットは小さいですが、大分精度は高いものになっています。特徴量をみると、以下のものがよく効いているようです。

* image_count: 図の数
* sentence_max_length: 文の最大長
* user_followers_count: ユーザーのフォロワーの数

「信頼できる人が書いていて、適切な文長で書かれていて図が含まれている」ということですね。特に図の有無は大きく効いていました。
「ユーザーのフォロワーの数」は今回の趣旨に反しており、Qiita独自の特徴のため当初は入れない方針でしたが、精度＋今後他の文章に応用する際も「書いた人」に関する情報は何かしらの形で取れるということで入れることにしました。

なお、今回の比較対象となるいいねのみで分類してみた結果は以下になります。

[elephant_sense/notebooks/like_based_classifier.ipynb](https://github.com/chakki-works/elephant_sense/blob/master/notebooks/like_based_classifier.ipynb)

<img src="https://qiita-image-store.s3.amazonaws.com/0/25990/732e2902-0548-1f67-9f23-14c8c06a0aab.png" width="400px">

こちらをみると、「わかりやすい」文章(1)の予測ができていないことがわかります。そういった意味では、今回のモデルは思ったよりも役立ちそうです。

## アプリケーションへの組み込み

構築したモデルを組み込んで、実際に使えるアプリケーションを作成してみます。

アプリケーションに機械学習モデルを組み込むとき、そのモデルで利用した正規化のためのオブジェクト(今回[StandardScaler](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html))も必要になる点に注意してください。モデルを作るときに正規化を行なっていたのなら、当然予測をする際にも正規化が必要になります。

フロントエンドは簡易さ重視で、[Vue.js](https://jp.vuejs.org/)/[axios](https://github.com/mzabriskie/axios)で構築しました。axiosはhttpリクエストを送るためのライブラリで、サーバーサイド(Node)でも使えるほかクライアント側のサポートもしっかりしています(特にXSRF Cookie対応はありがたかった)。

出来上がったアプリケーションはHerokuへデプロイしました。今回はscikit-learnなどを利用しているため通常のデプロイは難しいので、Dockerを利用したデプロイを行なっています。

[Container Registry and Runtime](https://devcenter.heroku.com/articles/container-registry-and-runtime)

今まではbuildpack職人にならないといけなかったので、これは便利(ただ、プロキシ環境からは使えないのでちと辛い)。

デプロイしたアプリケーションは以下になります。

[Elephant Sense](https://elephant-sense.herokuapp.com/)

![image](https://qiita-image-store.s3.amazonaws.com/0/25990/08018736-e3f1-6218-01e8-2f63c352828b.png)

しかし・・・遅い！本当は評価対象の投稿をもっと増やしたいのですが、パフォーマンスの関係で絞っています(今は50件の検索結果をスコアリングして表示しています)。一応並列処理化などもしているのですが、やはり各文章をhtmlパースして特徴を抽出して・・・とやっているので、かなり遅くなってしまいます。ここはフロントエンド側の工夫も含めて、改善が必要だなと思います。

表示結果については、まあまあ、といった印象です。ただ、目標の50%に達するにはやはりより多くの文章を評価してから表示しないといけないので、上のパフォーマンス問題と表裏一体なところはあります。

なお、スコアの表示には[`meter`](http://www.htmq.com/html5/meter.shtml)タグを利用しています。今回の開発で初めて存在を知りました。`progress`のタグもそうですが、意外と知らないタグもあるなと。

## 今後

問題設定・データの収集・モデルの構築・アプリケーション化、と一通りのプロセスをへて、そこそこできたかなという印象です。
これがより進化すれば、設計書やAPIドキュメントなどに適用しドキュメントの品質向上を行なったり、新規参入したメンバが「よくまとまった」文章を探すのをサポートするのに使えるのではないかと考えています。

現在、私の所属するチーム[chakki](https://github.com/chakki-works)では、「すべての人がティータイム(15:00)までに帰れる社会」を、機械学習/自然言語処理によって実現しようという試みを行なっています。まずは自分達からということで、システム開発における自然言語情報(ドキュメントやレビュー、そしてソースコードなど)を対象に現在は活動を行なっています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/25990/47eb2173-2992-16cf-6519-174ec6d34e30.png" width="300px">
[@chakki_works](https://twitter.com/chakki_works)

興味がある方は、[ぜひ話を聞きに来て頂ければと思います！](https://www.wantedly.com/companies/tis)