NPSアンケートを自動分類した話

研究開発部に2月18日から3月15日までアルバイトとして参加している岩手県立大学修士1年の橋本(@b_b_134)です。大学では画像認識について学びを深めています。

本稿では、アルバイトで行ったNPS®アンケート1の機械学習を用いた分類と、その結果を社内で活用してもらうための取り組みについて紹介します。

NPSアンケートについて

クックパッドではイベントやアンケートなどを通じてユーザーからの声を集計しています。その一環として、2年前からNPSアンケートを実施しています。

NPSアンケートはサービス(今回はクックパッド)を他者にオススメするかを調査するのが目的です。アンケートでは必須回答の「スコア」と任意回答の「コメント」を頂いており、スコアは0~10の値を取ります。

この「スコア」が9、10で回答してくださった方を推薦者、7、8で回答してくださった方を中立者、6以下で回答してくださった方を批判者と定義します(下図参照)。

f:id:kazuyuki-hashimoto:20190315174636p:plain

「NPS」の定義は推薦者の割合から批判者の割合を引いた値になります。

「コメント」は実に様々な意見が寄せられます。従業員全員が全て目を通せると良いのかもしれませんが、難しい話です。しかし、サービス改善に役立てられる貴重な声を無視することはできません。

現在は、社内の担当者が分析し社内で報告しています。社内ではこのアンケートをより一層活用したいという気運が高まっていました。私個人としてもユーザーがサービスについてどう思っているのかということに興味があったため、アルバイトで取り組むタスクに定めました。そこで、現在分析に携わっているスタッフとミーティングし、要件をまとめていきました。要件としては以下が挙がりました。

  • 頂いたコメントが何を話題にしているかを分類したい
  • ある話題に関するコメントのNPSの遷移をグラフなどで見たい

「推薦者はどこに魅力を感じているか」「批判者はどこに不満を感じているか」をより定量的な評価が可能になります。また、機能やサービスに関して早くフィードバックを得ることができます。

これまでも「頂いたコメントが何を話題にしているか」についてはルールベースおよび目視で分類してきました。しかし同時に、目視での作業量の多さなどから分類コストが大きいという問題がありました。そこで我々は機械学習によって分類コストを低減しました。

分析とデプロイ

では、NPSに関する課題を機械学習でどう解決したかを説明します。本プロジェクトは分析とデプロイ、二つのステージに分けられます。

ここで分析はコメントがどういった話題に関するものなのか予測するモデルを作成することを指します。 デプロイは作成したモデルをもとにアプリケーションを作成することを指します。ではまず分析でやったことを紹介します。

分析

今回は各NPSアンケートに含まれるテキストがどういった話題に関するものなのか機械学習を使って自動でラベル付けするモデルを作ります。過去に「検索」「レシピの量」など18種類に社内で人手でラベル付けしたデータがあったため、それを教師データとして教師あり学習を行いました。教師あり学習とは、あるデータに対して正解がわかっているとき、モデルがデータをもとに予測できるようにし、未知のデータが渡されたときに正しく予測出来るようにモデルを訓練することです。

構築したモデルを利用して、ラベルの付いていないコメントにラベルを付けます。今回はマルチラベル問題として取り組み、二値分類器を各話題ごとに構築して独立したラベルを付与しました。 このやり方は以前のtechlifeの記事ご意見分類業務と同様です。(下図参照)

f:id:kazuyuki-hashimoto:20190315174630p:plain

文章のベクトル化

まずは、アンケート文章を分かち書きし、ベクトル化します。 分かち書きは、以下のように単語ごとに文章を分割することです。

(元の文)
毎日の料理を楽しみにする。

(分かち書き後)
毎日 の 料理 を 楽しみ に する 。

導入の容易さから分析時点では分かち書きのフレームワークとしてJanomeを選択しました。今回はベクトル化ではTF-IDFを採用しましたが、将来的にfastTextやWord2Vecとの比較も考えています。

分類

機械学習でよく使われるscikit-learnの公式ドキュメントにはChoosing the right estimatorというページがあり、今回モデルの選定に利用しました。具体的には対象となるデータはラベルつきのデータがあり、かつ量が多くないためLinear SVCを選択しました。

ここでデータの内容を見てみましょう。

ラベル 件数
機能 / 検索 251/1143
レシピ/量 341/1143
レシピ/難易度 152/1143
レシピ/味 177/1143
サービス全般/有料 124/1143
サービス全般/ユーザビリティ 45 /1143
サービス全般/役立ち度 463 /1143

上の表は全コメントに対して各ラベルが付与された比率が記述されています。なお、ラベルは1つのコメントにつき複数のラベルが付いていることがあります。サービス全般/役立ち度のように、正例が多い話題(463件)もあります。しかし、50件に満たないラベルもある不均衡なラベルもあります。

分析をし始めた当初、評価指標については、最初はAccuracyを見ていました。 しかし後日になって、社内のNPS担当者はラベル付けされたデータをさらに人手でチェックして、NPSデータを分析することが判明しました。 そのため、Accuracyを評価指標として使うより、関連する可能性のあるコメントを確実にラベル付けができるようにRecallを重視するほうがよいと考えました。 ちょうどscikit-learnにはclass_weightというパラメータがあり正例と負例の重みを調整できるパラメータが提供されています。 今回の実験ではこのパラメータ class_weightを利用してRecallの重みを大きくした場合についても合わせて検証しました。 単純なグリッドサーチを用いた場合とclass_weightを用いた場合の結果を以下の表にまとめます。

ラベル Recall
(GS)
Recall
(GS+CW)
f1
(GS)
f1
(GS+CW)
機能 / 検索 0.7500 0.8064 0.8413 0.8650
レシピ/量 0.7311 0.7999 0.8051 0.8241
レシピ/難易度 0.7804 0.8555 0.8707 0.8750
レシピ/味 0.5420 0.6600 0.6863 0.6000
サービス全般/有料 0.6712 0.6835 0.7967 0.7999
サービス全般/ユーザビリティ 0.4482 0.6071 0.6190 0.7555
サービス全般/役立ち度 0.8507 0.8505 0.8718 0.8868

※GS: GridSearch, CW: class_weight

上の表にはRecallとF1スコアが記述されており、それぞれ、GridSearchのみ、およびGridSearchとclass_weightを組み合わせた結果です。 上の表をみるとラベルによって、性能が出ているものと出ていないものがあるのがわかります。 十分なデータを確保できていないラベルに関しては性能が十分に出せていません。 しかし、自動分類の効果を確認してもらうため、まずは性能の高いラベルについてのみデータを分析者が見られるようにデプロイすることにしました。

デプロイ

前節で、プロジェクトの分析ステージが終わったので、デプロイに取り掛かります。 クックパッドでは研究開発部の各メンバーがデータの分析からモデルのデプロイまでの責任を持ちます(詳しくはこの記事を参照してください)。

今回のタスク、もともとの要件は以下の通りでした。

  • 頂いたコメントが何を話題にしているかを分類したい
  • ある話題に関するコメントのNPSの遷移をグラフなどで見たい

分析が終了しモデルが構築できたので、分類したデータをRedShiftに入れ、担当者が閲覧できる状態にすることを目標とします。

機械学習の結果をシステムに組み込む方法は逐次処理、バッチ処理に分けられます。 今回はリアルタイムに結果が分かる必要はないため、バッチとしてシステムに組み込むことにしました。 クックパッドではHakoというECS上に展開されるコンテナオーケストレーション環境があり、この上に先に構築した機械学習モデルをデプロイします。 システムの全体構成は以下のようになります。

f:id:kazuyuki-hashimoto:20190315174653j:plain
システム構成図

まずDockerコンテナ環境で実行できるCUIアプリケーションにします。 今回の分析ステージではJupyter Labを使いながら分析を進めたので、関数やクラスなどを抽出する作業が発生します。 このときデータ読み出し時のカラム名や前処理後のベクトルの形などに関するテストを順次追加しました。

次にCI上でテストを動作させる設定を追加しました。このときローカルファイルに依存したテストがFailしていたので、順次ファイル依存の問題を解決しました。 また、前処理の速度が気になる問題がありました。そのため分かち書きの処理をJanomeからMeCabに変更する修正も加えました。

次にHako上でアプリケーションとして動かせるようにします。クックパッドでは、バッチを実行する環境はスポットインスタンスを利用する環境と通常のインスタンスを利用する環境があります。 今回はHakoからS3へCSVの転送が途中で止まってもきちんと再実行できる設計にした上で、スポットインスタンスを利用する環境を選びました。

以上の取り組みから、毎月のデータをロード、NPSでの話題を分析し、RedShiftに保存するバッチフローが完成しました。

ここまでの機能を追加したあと、分析担当者に機能の共有をしました。ぜひこの内容を常に見られるようにしてほしいというフィードバックをもらいました。 そこで、より多くのスタッフにNPSのデータに興味を持ってもらいたいと考え、作成したグラフをSlackのボットで月次で投稿するようにしました。

ふりかえり

機械学習をプロダクションに組み込む作業は初めてでした。アルバイトを通じて機械学習の知識だけではなく、システム設計について考える点がたくさんあることがわかりました。

私は修士の学生として在籍している研究室では画像処理に取り組んでいます。 今回、はじめて自然言語処理のタスクを扱いましたが、思っていた以上に楽しかったです。 画像処理では慣れていることもあり、違和感なくベクトル化できますが、言語は一筋縄でいかないイメージがありました。 今回、自然言語処理タスクに取り組んだことで、この分野でも様々なツールが提供されかなり気軽に始められることが分かったのは収穫です。

今回のタスク(NPS)には自然言語の不均衡データという特徴がありました。 画像であれば、少ないデータに対して回転やクリッピングなどのデータオーギュメントを気軽に適用できます。 離散的な値を特徴とする自然言語では大きく意味が変わりかねず、類語を用いた言い換えは容易ではなく、目視で確認する作業が必要です。 このあたりについて、今後調べて見たいと考えています。


  1. Net Promoter®およびNPS®は、ベイン・アンド・カンパニー、フレッド・ライクヘルド、サトメトリックス・システムズの登録商標です。正味推奨者比率などと訳されます。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/