niconicoにおけるコンテンツレコメンド

大元司氏(以下、大元):はじめまして。株式会社ドワンゴDwango Media Village部知能情報システムセクションからやってきました。大元と申します。本日は『niconicoにおけるコンテンツレコメンドの取り組み』と題して発表させていただきます。

まずはじめに自己紹介です。私は大元と申します。

神戸大学の大学院システム情報学情報科学専攻博士課程前期課程を2015年に修了しました。専門は機械学習で、とくにトピックモデルと呼ばれる生成モデルと、時系列データを対象に研究を行っておりました。

その後2015年にそのまま現職、株式会社ドワンゴに新卒として入社し、現在の所属がDwango Media Village部知能情報システムセクションというところになります。

Dwango Media Village部というとおそらく多くの人は聞き慣れない部署だと思うんですけれども、ここでは主に機械学習の研究や機械学習を用いたサービス開発を行っており、自分が所属している知能情報システムセクションというところでは特に機械学習を利用したniconicoのサービス開発や運用を行っています。

その中で、自分の業務としてはレコメンドシステムの開発であったり、その運用であったり。いきなり傾向が変わってしまうんですが、国政選挙の当確予測であったり、競馬の予測や教育として機械学習講座の教材作成を行っております。

この中でドワンゴが企画として競馬の予測をやっていたのをご存知の方はいらっしゃいますか? いない(笑)。そうなんですよ。今年の3月から6月の間にドワンゴは競馬の予測というのをちょっと企画に使っております。

どんな企画かと言うと、ユーザーから寄付金を募り、それを直接運用するといろいろな法律の壁にぶつかってしまうので、同額をドワンゴが拠出し仮想的にユーザーからの寄付金相当額を競馬で運用したという企画をやりました。

その冠名が「人工知能募金」と言います。最終的な結果を申し上げると、ユーザーからの寄付金が50万円弱ぐらい集まったところ、競馬の運用によってそれが100万強ぐらいまで増えたという結果になりました。

こういった機械学習を使った企画を全般的に引き受けているのが、知能情報システムセクションです。メンバーの構成としてはだいたい5、6人。1人平均2個か3個のAPIを主担当で持っています。

一方で、個人でも会社でもさまざまなオープンソースソフトウェアへの貢献を行っており、現在はマイクロソフトが作っている勾配ブースティングライブラリのLightGBMのコミッターの1人として活動させていただいてます。

ドワンゴに入社した経緯

「なんでドワンゴに入ったのか?」みたいなことをよく聞かれるんですが、その経緯がおもしろいのでちょっと紹介させてください。2014年から就職活動を行っていてほかにも何社か受けていました。

当時ドワンゴという会社はGitHub採用、プロダクトを提出して評価をしてもらえるという採用制度があり、今もたぶんあると思うんですけど。それに研究で作ったコードを提出したところ1週間後ぐらいに人事の方から「最終面接に来てください」と。

それで最終面接に行ったところ、実質1回目の面接なので1次面接のようなことをやり、まあ無難に終わったなと思っていると、駅の改札に入る前ぐらいに電話がかかってきて。10分くらいですよね。「内定なのですぐ来てください」みたいな感じで電話が来て(笑)。

そこで「現場に裁量を持たせてもらってるいい会社なんだなぁ」と直感的に思ってこの会社を選んだという経緯があります。今もおそらくそんな感じの会社だと思うので、興味のある方はぜひうちのセクションや部に関わらずドワンゴという会社を調べてみてもらえると嬉しいです。

ニコニコで利用されるレコメンダー

ここから本題に入っていきます。niconicoのサービスといっても多岐にわたっています。まずはニコニコ動画やニコニコ生放送。ニコニコ静画、ニコニコチャンネル、ニコニ立体、ニコニコニュース、ニコニコ大百科、RPGアツマールなどなどいろいろあって。

その中でもやはりメインとなるのはニコニコ動画と生放送であることは間違いありません。おそらくこの2つのサービスに関しては多くの方がご存知かと思います。

ちょっとすこし宣伝してしまうんですが、6月28日にいろいろあってついに新バージョン、「niconico(く) 」8(注:「く」はクレッシェンド)というバージョンがリリースされました。おそらく本当にかゆいところまで手が届くような改善が施されていると思うので、またみなさんぜひ使ってみてください。

さて、そのようなniconicoサービスの中で利用されているレコメンダーにどのようなものがあるのか、ざっくり紹介します。主にオススメの動画であったり、オススメのタグであったり、オススメの生放送で使われています。

左上がおすすめの動画で、右上の写真は動画の再生終了後に出てくる動画のレコメンドです。左の真ん中が生放送のオススメで、下2つがタグの推薦になっています。このようにさまざまな場所でレコメンダーが動いています。

本日のトーク内容を紹介します。

本日はレコメンダーの事例紹介をいくつか行い、上4つがレコメンダーの事例紹介です。そのあとに少し傾向が変わったレコメンダーのまとめ役であるレコメンダーコーディネーターの説明をします。

その中でレコメンドの効果測定をどのように行なっているのかというのをざっくりと紹介できたらなと思います。その後、これらのレコメンダーを支える開発や運用方法についてご紹介させていただきます。

類似生主レコメンダー

さっそくレコメンダーの事例紹介に入っていきます。まず1つ目は「類似生主レコメンダー」とタイトルを付けました。これはユーザーにユーザー生放送をレコメンドするものです。ユーザー生放送というのはユーザー自身が配信者として番組を放送している放送のことで、この中でおすすめの生放送とは何か? というのを考えます。

ユーザーが視聴した生放送の配信者、niconicoのサービス内の用語ではこれを「生主」と呼びます。生主に似た配信者の放送は同じような放送していると考えられるので、これがおすすめできるのではないかと考えます。つまり、ユーザーに生放送をレコメンドする問題を類似生主の推薦の問題に置き換えることができます。

では生主の特徴量、つまり生主はどのような値で表現できるのかというのを考えると、例えばどんな生放送するのか? それは過去の生放送タイトルであったり、その説明文、その生放送に付いているタグやカテゴリを見ることでその生主がどんな放送を行っているのかというのがわかりそうです。

一方で別の視点から見ると、どんな人がその生主の放送を見てくれているのか? つまり視聴者の好みみたいなものを統計的に集計してあげることで、生主の表現が視聴者でできると考えることができます。そこでこの類似生主レコメンダーは、生主を視聴者の集合で表現することを考えていきます。

ここでは各生主のトピック分布を視聴者から推定することを説明します。左上の生主の放送には音楽が好きなユーザーが2人、映画が好きなユーザーが1人視聴しています。その下の生主の放送にはスポーツが好きなユーザーと映画が好きなユーザーが2人視聴しています。

これらからここでタームとして出てきたトピック分布を右の図のように表現することができます。

上の生主は音楽が好きな人が2人集まっていて映画が好きな人は1人集まっている。下も同様に見ると、これでユーザーの好み、今回の場合は音楽と映画とスポーツという3つのトピックで生主の傾向を表現することができます。

さて、各生主のトピック分布をトピックモデルで学習します。これは先ほど自己紹介で紹介したんですけれども自分の学生のときの専門分野の1つでした。このトピックモデルの実装にはscikit-learnと呼ばれる有名なPythonの機械学習ライブラリを用いて学習します。ただ注意が必要で、sklearnには2つのLDAが存在します。

1つはLinear Discriminant Analysisと言われる日本語では「線形判別分析」と呼ばれるもので、今回はこちらは利用しません。今回利用するのは下のLatent Dirichlet Allocation、日本語では「潜在ディリクレ配分法」と呼ばれるものを利用します。

詳しい人はPythonの有名なトピックモデル実装にgensimというパッケージがあるのご存知かもしれません。ただsklearnにもバージョン0.17から今回利用するLDAの実装が導入されているのでこれを利用したいと思います。

さて、このLDAのパラメータの調整についてご紹介します。まず学習方法 (learning_method) のパラメータは“batch”にします。今回の類似生主レコメンダーで要求されるものはオンライン用途、つまり逐次的に入力が入ってきてその都度モデルの更新を行うような必要性はないので、性能が良いことが知られているバッチ学習を利用します。

次に必ず調整すべきパラメータはn_topicsというパラメータです。これはいくつのトピック、カテゴリのようなもので生主を表現するのか。先ほどの図だと、n_topicsが3となるんですが、これをいくつにするのかを決めます。

この決め方はパープレキシティ(perplexity)と呼ばれる評価尺度を利用します。これもsklearnのAPIに入っているので学習後にパープレキシティのメソッドを叩いてあげて、これの大きさを比較してあげることでモデルの性能の良し悪しを測ることができます。

今回利用している学習データには直近1ヶ月の生放送視聴ログを利用しています。Sparkで各行が生主-視聴者となるような学習データを作成します。niconicoの生放送サービスの場合、1ヶ月の視聴ログでもオーダー的には10の3乗×10の6乗以上の行列ができあがります。これはおそらくNumPyで表現するにはちょっと大きいかなと思います。

そこから前処理として視聴者をフィルターしていきます。これにはscikit-learnのCountVectorizerを使い、パラメータをmax_df=50とmin_df=3にします。つまりこれは学習データ中に50回以上表れるユーザーと3回未満しか表れないユーザーというのを取り除いています。

これらのユーザーはbot的な行動をしていたり、限定的な知識しか与えないことが多々あるので学習データから取り除いています。

その後CountVectorizerとLatent Dirichlet Allocationをつなげるパイプライン (Pipeline) を構築します。これはプロダクト環境で再現性のある前処理とモデルの学習を構築するためです。

ここで注意するべきことは、LDAのrandom_stateを必ず固定することです。LDAは学習中に乱数を使うので再現性を確保するためにrandom_stateを固定しておく必要があります。でないと思わぬバグに引っかかったときの調査にすごく沼にはまってしまうことになります。

この図はトピック数の実験で、横軸がトピック数、縦軸が先ほど説明したパープレキシティと呼ばれる評価尺度なんですが、図を見るとトピック数が25から30のところでパープレキシティが最小値を迎えているのがわかるかと思います。

なので現行のシステムではトピック数を25にし、このトピック25というのは生主をなにかしらのカテゴリのようなもので表現しているという意味になります。

そしてこのなにか25個のトピックというのを機械学習で求めるのがトピックモデルです。生主レコメンダーのシステム構成に関してはこのようになっており、ユーザーがサービスにアクセスしたときにレコメンダーAPIは放送中の生主とユーザーの最近の視聴ログとLDAで学習した学習結果を取得します。

そこから最近視聴した放送の生主と似ている生主を類似検索し、その生主が放送している番組をレコメンド結果として返します。以上が類似生主レコメンダーの説明でした。

タグレコメンダー

次にタグレコメンダーというのを紹介します。これはユーザーに動画をレコメンドするレコメンダーで、ユーザーの好みの動画を動画に付与されたタグを利用して探します。

ニコニコ動画のタグの前提について少し説明します。投稿者やユーザーが自由にタグを動画に最大10個まで登録できるのがニコニコ動画のタグの機能です。

検索性のためにユーザーが信用できるタグを付けてくれます。付けてくれることが多いです。たまに無意味なのもあるんですが、だいたい意味のあるものが付いていることが多いです。

これらのタグを用いてタグ関係ツリーの構築を考えます。このタグ関係ツリーの分析により、実際のタグの使われ方やユーザーの好みを最近見た動画のタグから推定することができそうです。

タグ関係ツリーには親子関係が存在します。例えばゲーム、任天堂、ポケモンの順に概念的に大きいものから小さいものに親子関係が広がっていくと考えられます。この親子関係をどうやってツリーで表現するかを説明します。

タグの親子関係は共起関係を見ることで計算することができます。

ここでタグs、tに関してn(s)とn(t)はそれぞれのタグが登録された動画の数、n(s.t)というのは2つのs、tのタグが同時に登録された動画の数です。これらを用いてシンプソン係数と呼ばれる、真ん中の式が計算できます。

これは何をやっているのかと言うと、登録されたタグの集合の小さいほう分の同時に登録された動画の数。割合を求めています。このタグs、tについてシンプソン係数を計算して閾値を超えたときにs、tに親子関係があるとします。つまりs、tの間にエッジを引くことができます。我々のシステムでは経験的にこの値を0.4にしています。

さらにn(s)のほうが大きいときにsをtの親と定義してすべてのタグを親のツリーの根とみなしています。つまりsからtの方向へエッジを引くことができます。

ここで1つ問題が発生します。次のように3つのタグの関係が発生し得ることがあります。

つまりタグuの親がsとt、2つある場合です。これらのときタグの親子関係をとるにはツリーの構造が直列であるほうが計算しやすいです。

そこで私たちはタグuの親としてs、tの2つ存在するとき、n(s)、n(t)の小さい方を唯一の親として採用しました。

このように構築したタグツリーを探索することで、ユーザーごとに好みのタグを調べることができます。

ユーザーが最近視聴した動画に付与されている動画を集め、ツリー上でもっとも細かいタグだけを抽出します。

例えば『スプラトゥーン』 の動画にはゲーム、スプラトゥーン、実況プレイ、などが同時に登録されることが多いです。これらのタグの概念の広さは明らかにゲーム、実況プレイ、スプラトゥーンの順番であることが考えられます。なので、この動画を見たユーザーの好みを表すタグはスプラトゥーンであると仮定しています。

ここで細かいタグというのはまさにツリー上でもっとも葉に近いタグになります。右下の例では赤いノードが最近ユーザーが見た動画のタグのツリーのパスになっており、ここからもっとも葉に近いタグというのは実況プレイとマリオカートであると計算します。

タグレコメンダーの処理時間、パフォーマンスについて紹介します。まずツリーの構築には動画の数×タグの数の2乗の計算量がかかります。そして動画の数は現在10の7乗以上のオーダーがあり、これをSparkで10分弱程度で計算します。

もう1つ、タグの抽出。レコメンド部分にどのくらいの時間がかかるのかと言うと、タグ全体に対してNの2乗の計算量、さらにツリーの深さがだいたいNくらいなので、これは十分リアルタイムに捌けることがわかっています。

ちょっと見づらいんですが、右の図は実際にタグのレコメンドをさせてみた結果です。これは自分のniconicoのIDで表示させたときもので、よーく目を凝らしてみると、機械学習やDeep Learningや、ちょっとこの画面では見づらいんですが、競馬が好きなので有馬記念が出てきてますね。かなり有効なレコメンダーであることがわかります。

検索履歴ベースレコメンダー

次に検索履歴ベースレコメンダーを紹介します。これはすごくシンプルで、ユーザーの検索という行動には明確な意思が含まれていると考えます。

みなさんも自分で検索エンジンを使うときのことを想像してみてほしいんですが、なにか目的を持って検索ワードを打ち込んでいるかと思います。検索ワードに、例えば「何かおもしろいこと」みたいな抽象的なことを打ち込むことはたぶんあまりないと思うんですけど、niconicoのサービス内でもだいたいの場合、なにか意思を持った検索が行われています。

この検索履歴を利用することで、事前に気の利いたコンテンツが出せるはずです。例えば、最近「ゲーム A」を検索したユーザーのレコメンドに「ゲーム」と「A」に関連するものを含めておく。

この現在の検索履歴ベースレコメンダーでは、ユーザーごとに検索履歴を10件ずつRedisに保存しています。そして、最近の検索に重みを置いた確率で、1つ検索履歴を確率的に選択してコンテンツを再検索した結果をレコメンドしています。そのため、検索履歴を持たないユーザーにはなにも返せないという弱点があります。

検索履歴ベースレコメンダーについては以上で、すごいシンプルなんですが、極めて有効なレコメンダーの1つになりうると思うので、もしこういうものをまだ持っていないというところは、ぜひ作ってみるといいかもしれません。

リアルタイムレコメンダー

次にリアルタイムレコメンダーについて紹介します。これはユーザーの視聴行動をリアルタイムに反映するレコメンダーです。ここで言うリアルタイムというのは「数分以内」を定義しています。

これにはJubatusと呼ばれるレコメンダーを利用しています。JubatusはPFNとNTTによって開発されているオンライン機械学習向けの分散処理フレームワークです。そしてこのレコメンダーには一般的な協調フィルタリングと呼ばれる手法が採用されています。たぶん。

ちょっと協調フィルタリングの例を紹介します。左下の図の読み方なんですけど、ユーザーAの列を見ていくと、ユーザーAは動画1・2・3を視聴したという見方になります。隣のユーザーBは動画2と4。つまり「1」が立っているところが動画の視聴を意味します。ユーザーCに関しては、動画1は見ているけど、動画3は不明。ユーザーDに関しては、動画1が不明で、2と3を見ている。

このようなログがあったときに、ユーザーDに次におすすめする動画はなんでしょうか? また、動画3に似ている動画というのはなんでしょうか? ということを考えたいことがしばしばあります。そこでこういう問題を、特徴が似ているユーザーやアイテムから推定する解き方を協調フィルタリングと呼びます。

ただ、リアルタイムレコメンダーのJubatusの残念なところなんですが、Jubatusレコメンダーにはどうもロー (row)、つまり行に対するメソッドしか生えていません。

下の表の場合は、動画に対する計算、つまり「動画1に似ている動画はなにか?」みたいなことは簡単に計算できるんですが、「ユーザーAに似ている者はどのユーザーか?」みたいな列に対する操作のメソッドがどうもなさそうです。

そこでアイテムベース、つまり動画に対するレコメンドには標準APIをそのまま利用し、ユーザーベースのレコメンドにはJubatusの計算結果を抽出し、そこから自前で対象ユーザーに最も近いユーザー10人を集めてきて、彼らの過去の視聴アイテムから推薦する動画を選んでいます。

パフォーマンスはJubatusに完全に依存しているんですが、サービスのピーク時にはだいたい秒間3,300前後のリクエストを捌いています。

レコメンダーコーディネーター

ここまでが各種レコメンダーの紹介でした。ここからはちょっと傾向が変わって、レコメンダーコーディネーターというレコメンダーのまとめ役の紹介をします。

以前のniconicoのサービスでは、各サービスが直接レコメンダーを参照していました。この方法では、各サービスがそれぞれクライアントの開発に似たような実装をする必要があります。新規レコメンダーが追加された場合に、クライアントの追加実装が必要となりますし、また、レコメンダーを開発している我々の立場としても、どこでなにが利用されているのかの管理がしづらくなります。

そこで、サービスとレコメンダーの間に一層「レコメンダーコーディネーター」というのをかませます。

これは各種レコメンダーを良い感じにまとめる役割を持っています。このレコメンダーコーディネーターの導入によりうれしいことがいくつかあります。というか、いくつもあります。

まず、レコメンダーの利用方法を統一することができます。また、すべてのレコメンダーをこのレコメンダーコーディネーターが管理するため、クリック率やA/Bテストなどのロジック導入が容易になります。

また、レコメンダーの追加やリプレイスのためにクライアントの開発が不要となります。もちろん、それ自体の開発や運用コストはかかるんですが、はるかにメリットのほうが大きいです。

このレコメンダーコーディネーターにはレコメンダーをまとめる「レシピ」という概念が存在します。レシピとはレコメンダーの組み合わせ方やフィルター条件などを書いた定義書で、これはYAMLで書かれています。

各サービスの担当者はレシピを作ることで独自の“レコメンダー”をつくることが可能となります。つまり、既存レコメンダーAとBを組み合わせたレシピを作ることで、実質的にレコメンダーC、AとBを使うCを作ったことになります。

また、リクエストパラメータが変わらないかぎり、レシピ内の変更でレコメンド内容の変更が可能になります。

レコメンドコーディネーターのレシピ例

レシピ自体は凝ったDSLではなく、レシピからPython objectを作成できるようなシンプルな構文にしています。

この図はレシピの例なんですけど、レシピ上でレコメンダーやフィルターはツリー構造で表現されているので、ツリー構造になるようにYAMLを書いています。

ちょっとマウスカーソルが出ないので説明しづらいんですが、「type: mixture」と書いているのが「レコメンド結果を混ぜます」という意味で、「in_order」と書かれているのが「それを順番に混ぜます」。「type: filter」というのはフィルターで、「limit: 5」というのは要するに「5件までフィルターしなさい」という意味であったり、「filter: watched」というのは「視聴済みのアイテムは結果から外しなさい」。

このようなフィルターを追加していたり、レコメンダーA・B・Cというのが存在するみたいな読み方をします。ただ、これらはエンジニアにとっては定義することがそんなに苦ではないと思うんですが、やはりサービス担当者にとってはこれを直接書くのはだいぶ荷が重いと考えられるので、今後の展開としては、レシピエディタの社内公開を考えています。

さて、レコメンダーコーディネーターは各サービスからレコメンドに関連したログを送ってもらっています。2つ送ってもらっていて、それは、実際にコンテンツを表示した表示ログと、実際にクリックしてコンテンツを見た反応ログです。

フロントサービスは、レコメンドAPIを叩いたときに、レコメンドAPIはアクセスごとにユニークなRecommend IDを採番して、レコメンド結果を一緒にレスポンスを返します。

そしてフロントはその結果をサービスに表示するんですけど、そのときにRecommend IDと実際に表示したコンテンツを表示ログAPIに送ってもらいます。さらに、表示したものに反応があったときは、Recommend IDと反応があったアイテムを反応ログAPIに送ってもらいます。

こうすることでなにができるかというと、クリック率の集計ができるようになります。

表示ログと反応ログを送ってもらい、レコメンドAPIのログとジョインすることで、クリック率のほぼリアルタイムな計測ができます。

ここでいうクリック率というのは「レコメンドを表示して1時間以内にクリックされた率」と定義しています。実際、表示してから1日後にクリックされるものもあれば、一瞬で表示されるものもあるんですが、このウィンドウ幅はそのサービスの要件に応じて変えることが可能です。

また、同じレコメンドエンジンでも、表示の仕方やレシピの内容によってクリック率が異なることが確認できます。

真ん中のこの図は、時系列データを扱うのが得意なInfluxDBとそのデータを可視化するGrafanaを使ってクリック率の表示をしたものです。このような可視化ツールをサービス担当者に提供しておくことで、サービス担当者がレシピをいじり最適なレコメンド結果を求めていくことができるようになります。

駆け足でしたが、ここまでがレコメンダーの事例紹介です。

レコメンダーを支えるシステムアーキテクチャ

ここからはレコメンダーを支える運用や開発方法についてご紹介します。

まず、レコメンド全体のシステムアーキテクチャとして、私たちはKubernetesとDockerでAPIサーバやバッチによる学習ジョブの実行環境を構築しています。ここはけっこう特徴的なんですが、APIサーバにはマルチプロセスWebフレームワークのTornadoを使うことが非常に多いです。

ちょっとアンケートを取ってみたいんですが、この中でAPIにどのフレームワークを使っているか? Tornadoを使ったことがある人?

(会場挙手)

あまりいない。Djangoを使っている人?

(会場挙手)

……がやっぱり多いかなぁ。ですよね。じゃあ Flaskで簡潔に?

(会場挙手)

……これもまぁまぁいる。やっぱりTornadoはあんまり人気ないようですね。

ですが、Djangoを使うようなものでも、レコメンダーに関してはなにかレンダリングしないといけないようなものはないので、うちらはパフォーマンスがまぁまぁいいTornadoを用いています。

また、レシピの変更によって、要するにレコメンドエンジンを何個か追加したり重いエンジンを追加したようなときに、APIの負荷が上がったりするんですけれども、そういうときにもデプロイの調整がたいへん容易、簡単になりました。

これらの構成を、ビルドやデプロイの仕組みは構成管理ツールのAnsibleを利用しています。

機械学習モデルのデプロイについて

次に機械学習モデルのデプロイ方法について紹介します。私たちは2つの方法を持っており、まずモデルそのものが必要な場合では、つまり入力データに対して都度モデルの予測値が必要となるときは社内のデータストア基盤にモデルファイルを置いています。

例えば、先ほど紹介した類似生主レコメンダーでは、生主のトピック分布、つまりベクトルを生主のIDをキーにRedisに格納しています。そして、各レコメンダーにモデルの更新機能を実装します。これはメモリ上のモデルより作成日が新しいモデルがあったときに、新しいモデルをフェッチする機能です。

もう一方で、モデルの出力が必要な場合。これは入力データが既知であり、事前にモデル予測値が計算できるようなときです。このような場合は、モデルの予測値をRedisやMySQLに格納しています。どういうことかというと、例えば、すべての動画に対して、事前にレコメンドするべきコンテンツが計算できるのであれば、その計算結果を格納しておこうというアイデアです。

次にサービスの監視方法について紹介します。サービスの監視にはDataDogと呼ばれるモダンなモニタリングサービスを全仮想マシンに導入しています。エラー通知はSlackに流しています。また、DataDogのいいところは、エラーだけではなく、レコメンダーのさまざまなパフォーマンスチェックを行うことができます。

例えば、流量であったりレスポンスタイム、キャッシュのヒット率や、当然、コンテナのCPUやメモリ使用率なども計測しています。この図は、あるレコメンダーのリクエスト数とレスポンスタイムをDataDogで計測している一例です。

また、DataDogを活用すれば、例えばPythonのメソッドが何回コールされたのかなども取れるので、パフォーマンスチューニングなどにも利用できます。Pythonのプロファイラが使えないような状況でも、DataDogと連携することでうまいことメトリックスを取れたりすることができます。

本日の内容をまとめます。

本日のトークでは、niconicoサービス内でのレコメンダーの事例紹介として、トピックモデルを利用した「類似生主レコメンダー」と、シンプソン係数を計算しタグツリーを構築する「タグレコメンダー」と、極めて気の利いた「検索履歴ベースレコメンダー」と、Jubatusによる「リアルタイムレコメンダー」の4つを紹介しました。

そして、これらのレコメンダーを良い感じに統合・管理する「レコメンダーコーディネーター」の存在を紹介し、そこでクリック率によるレコメンドの効果測定の事例を紹介しました。

最後に、レコメンドを支える開発や運用方法の紹介として、レコメンド全体のシステムアーキテクチャや、機械学習モデルのデプロイ方法や、サービスの監視方法について紹介しました。

以上で本日の発表を終わりたいと思います。Dwango Media Villageでは一緒に働く仲間を募集しています。もしよければこの、先日、部のホームページがリニューアルされたので、https://dmv.nico/かTwitterを見ていただけると非常にうれしいです。

本日はご清聴ありがとうございました。

(会場拍手)

参加者からの質問

司会者:大元さん、ありがとうございました。質問を受け付けたいと思います。ある方は挙手をお願いいたします。

質問者1:貴重なお話ありがとうございました。素人質問で恐縮なんですけれども、Jubatusに対してカラムに対して操作することができないという話があったと思うんですけれども、あれ、例えばその前のデータの行と列を反転させるみたいな方法でやることはできないんですかね?

大元:検討します(登壇者注:注:開発者に確認したところ、実際は行っていました)。

質問者1:ありがとうございます。

質問者2:モデルの定期更新の部分でちょっとお聞きしたいんですけれども。モデル作るときにログから学習データを作ってモデリングみたいな流れを踏むと思うんですが、ログがバグって変なモデルができちゃったみたいなことがあると思うんですけれども、そういうのを自動で検知する仕組み等々は用意されているのでしょうか?

大元:すごい良い指摘だと思います。それに関しては、まだ現状そういう仕組みが入れられているわけではないんですが、今パッと思いつくのは、例えばパープレキシティという評価尺度を説明したと思うんですが、そのパープレキシティがだいたいどのへんに落ち着くかは経験的にわかるので、その前後でエラー検知としてモデルの更新を止めるかみたいなことは入れられるかなと思いますし、そういうのをDataDogに投げつけて自動的に通知させるみたいな機能というか仕組みというのは考えられるかなと思います。

質問者2:ありがとうございます。

質問者3:お話ありがとうございます。トピックモデルのところなんですけど、パープレキシティを見ると25が一番よかったということだったと思うんですけど、具体的にデータを見たらどういう感じに分かれるんでしょうか?

例えば、新聞とかでトピックモデルをしたら、政治っぽいクラスタとスポーツっぽいクラスタがあるみたいなことが起きると思うんですけど、ニコ生の場合だったらどういうふうに?

大元:そうですね、今回はニュースでニコ生を例えると、記事に対応するのが生主になっていて、単語に対応するのが視聴者となっているので、現れるトピックの中をまず直接見ることができるのは、視聴者の集合がなにかしら集まっていますと。

トピック1にはユーザーAとBとCがいて、トピック2はAとDとEがいるとか、そういう感じになるんですけど、そこからなにかトピックに意味を与えるとすると、ユーザーの好みであったり年齢だったり性別だったり、プロフィールみたいなのを取ってこないとそのトピックがなにを表しているのかというのはちょっとわからなくて、それは直接見てはいません。

質問者3:ありがとうございます。

モデルのバージョン管理について

質問者4:おもしろいお話ありがとうございます。モデルの話なんですが、できたモデルをデータストア基盤に保存するみたいなお話されていたと思うんですけど、モデルのバージョン管理はやっていらっしゃったりしますか? また、どんな工夫をしていらっしゃるかとかちょっとお聞きしたいです。

大元:現状モデルのバージョン管理はできていないものもあります。それも社内のデータストア基盤と連携してできていけたらいいなと考えています。

質問者4:ありがとうございます。

質問者5:レコメンダーコーディネーターのところで複数のレコメンダーを組み合わせるみたいな話をされてておもしろいなと思ったんですけど、そのレシピ自体をさらに最適化するみたいなこと、自動化するみたいなことって考えていたりしますか?

大元:ああ、そうですね。それはおもしろい課題になりそうだなと今思いました。現状、やはり人手で「これとこれを組み合わせる」であったり、どういうフィルターを組み合わせるかみたいなことしかできないので、例えば、A/Bテストのロジックなどを導入していき、「こっちとこっち、どっちがいいか?」みたいなのをなにかしらのアルゴリズムで探索していくというのは1つありかなと思います。

質問者6:貴重なお話ありがとうございました。ちょっとビジネス面な質問になってしまうかもしれないんですけれども、niconicoで利用しているレコメンダーや機械学習自体の、ユーザーにどう行動してほしいかといった目標や、それが達成できているかどうかの効果検証をどのように行っているか、あるいは行おうをしているかということをお聞きできればと思います。

大元:やはりレコメンダーを作る上で重要な指標だと思うんですけれども、我々のDMVやうちの情報システムセクションでは、あまりそこには立ち入っておらず、企画側が「こういうのが欲しい」という何か目的があって持ってきてくれたアイデアをなんとか実現するというところにフォーカスを当てているので、直接その質問に回答できる内容はないんですけれども。

ただ、やっぱりサービス側ではしっかりそういう、例えばKPIを定義して、それを最適化するような施策を打っているのは間違いないと思います。

質問者6:ありがとうございました。

司会者:大丈夫ですかね。大元さん、ありがとうございました。

(会場拍手)