この記事は、ドワンゴAdvent calendar(表)の15日目の記事です
ドワンゴでエンジニアをしている @kinoppyd です
TL;DR
ニコ生を監視し、新しく始まったユー生を定期的に視聴し続け、Deep learning Framework の Caffe を使って画像認識し、何が放送されているのかをリアルタイムに知るシステムを作りました。
注意:この記事の中で使っているニコ生へのアクセス方法に関しては、すべてGoogle検索で分かる範囲の情報を使っています。開発者として知っている情報は一切使っていないことを明言しておきます。また、そこそこの数のリクエストを飛ばすので、真似してニコ生側から垢BANされたとしても、一切の責任を負うことはできません。
何の話か
弊社では、なんか人工知能のラボが立ち上がるくらい、人工知能や Deep learning への関心を高めています。毎日サラッとみんなDLの話をしているのに、私自身はDLをまだ利用したことが無かったので、体験してみたくなって作ったという話です。
残念ながら、DLにあまり造詣が無いので、DLの仕組みやチューニングの話は、この記事ではトピックに上がりません。
ともあれ、DLといえば画像認識の話をよく聞きます。画像に何が映っているのかを、かなり高い精度で理解することが出来るデモを幾つか見たこともあります。そこで、生放送のようなリアルタイム性の高いサービスをリアルタイムに画像認識し、何が映っているのかをリアルタイムに認識させるのは面白いんじゃないか? そうだ、せっかくだし今やってる番組全部解析させてみればいいかな、と思ってこのシステムを作ってみました。
建前
画像認識を使ってニコ生の全放送を監視することによって、ニコ生では今何がトレンドで、どういう放送が人気を集めるかということを可視化して分析するツールが作りたかった。
本音
猫が出てくる生放送をすぐに知りたいんだよおおおおお仕事中に猫の映像を見てなごみたいんだよおおおおおお
構成
構成図は、なんかごちゃごちゃしてますがこんな感じです
流れとしては、ユーザーがブラウザで見るニコ生のストリームを直接CLIで受け取りながら、ffmpegを使ってストリームのスクリーンショットを取得し、それをCaffeで画像認識した結果を取得します。
各パーツはそれぞれ Docker で構築されていて、ストリーム監視とキャプチャ作成に関しては、WebAPIのリクエスト経由で、都度Dockerコンテナを立ち上げて処理をしています。
Docker
各部分は微妙にセットアップが曲者揃いのソフトウェアばかりなので、それぞれDockerを使ってセットアップしました。
-
CaffeのDocker image
-
ffmpegのDocker image
-
CLIでストリームを見続けるDocker image
このうち、Caffeとffmpegに関しては、すでにImageが提供されているので、docker pull してくるだけで利用できます。ストリームを取得してくる部分に関しては、Ubuntuの公式イメージをベースに、自作のスクリプトをGistに置いてDockerfileに取得してきたり、必要なツールを入れたりしたものをbuildしています。
Caffe
画像認識に特化したDeep learning framework と聞いています。正直、まだはじめて日が浅いため、全く見識はありません。
今回の画像認識は、リファレンスの学習済みモデルを使用しました。分類とかはかなりイマイチですが、他の学習済みモデルを入れて動作確認する時間が無かったこと、猫を探すだけならリファレンスでも足りるだろうということで、そのままです。
Dockerを使って、既に構築済みの Caffe 環境をそのまま手に入れられますが、なんか古いみたいでいろいろと手を入れる必要がありました。Yahoo!の記事をベースに参考にしつつ、そのサポートという形で複数の記事を参照しました。
Caffeで機械学習 -1- リファレンスモデルを使って画像をカテゴリ分類
OSX10.10でCaffeをインストール、リファレンスモデルで画像を分類
本当であれば、このすごいエントリを見て感化されたので Google Cloud Vision API を心の底から使いたかったのですが、一週間ほど前に利用申請を出しても今日の今日まで完全にガン無視されてしまったので、泣く泣く自前で Caffe のサーバーを立てて使います。Cloud Vision API を使ってみたかった……。Cloud Vision API を本当に使ってみたかったから、残念で仕方ない。悲しい。つらい。
ffmpeg
ニコ生は、Flashを使ったFLV形式で放送を配信しているので、それをキャプチャするためにffmpegを使いました。これも普通にセットアップするのは面倒ですが、Dockerを使ってコマンドを実行するごとにコンテナを立ち上げれば非常に簡単に使えました。
ひとつはまった点として、ドキュメントのとおりにキャプチャを作成すると、引数の順番の関係で一度flvを再エンコードしたうえでキャプチャを生成するので、非常に遅くなるということでした。下記のQiita記事を参考に、リアルタイムで切り抜けるようになりました。
ニコ生を監視し続ける部分
ここに関しては、いくらGoogle検索で集められる情報とはいえ、あまりおおっぴらに書くものでも無いので割愛します。ただ、安定して動かなくて困ったという感想だけ。
Sinatra+Unicorn
それぞれのDockerを制御するために、WebAPIを用意しました。リクエストを受け取りDockerを起動させるものや、起動しているDockerの中でなにか処理をしたりと様々ですが、それぞれの受け口をすべてをSinatra+Unicornで手っ取り早く用意しました。Sinatraは、単一機能の簡単な処理をするAPIなどを作るときに、非常に簡単に記述できます。Unicornを使っているのは、複数のWorkerを手軽に立てたかったからです。
-
ニコ生番組のIDを受け取り、監視用のDockerを起動するAPI
-
ニコ生番組のIDと秒数を受け取り、ストリームの指定の秒数をキャプチャするDockerを起動するAPI
-
画像を受け取り、Caffeで画像認識を行った結果を返すAPI
以上の3つを作成しました。番組の監視とキャプチャは、Dockerコンテナを起動することが目的なので、Dockerのホスト側で動かしています。それに対して、Caffeの画像認識は、既に起動しているDockerのコンテナ内で、画像を受け取り認識するために動作しています。
Ansible
各サーバーのプロビジョニングには、Ansibleを使用しました。基本的に、サーバー側での作業はDockerをインストールして、イメージをビルド、あとはUnicornを走らせる環境を用意するだけなので、シングルファイルのPlaybookで十分足ります
全生放送を監視する関係上、番組を監視する部分にトラフィックがかかるため、今回は10台の監視サーバーを用意しました。そしてそれに合わせて、毎秒大量の画像認識のクエリが発行されるため、Caffeのサーバーはコアとメモリの大きめのものを2台用意して、中で複数台のコンテナを立てて、画像を受け取り分析しています。
ConoHa
インフラストラクチャには、このはちゃんを使用しました。なんでかっていうと、このはちゃんのファンだからです。本来ならば、AWSのGPU付きサーバーを借りるのがベストだとは思いますが、トラフィックの料金の見積もりが微妙に心もとなかったのと、学習済みモデルを使うので別にGPU使うほどでは無いだろう、というのが理由です。ただ、やはり学習済みモデルを使っても、画像認識にはかなりの時間がかかったので、素直にAWS借りておけばよかったと後悔しました。
実行
実際には、このシステムで1日中ずっとニコ生に張り付き続けていると、普通に攻撃していると受け取られてBANされかねないので、30分だけ全番組監視システムを作動させてみました。また、毎秒のキャプチャを解析していると全然マシンリソースが足りないので、5秒に1回に設定しています。とはいえ、同時に数百の番組の1シーンを解析すると、それでも5秒程度のインターバルでは足りないと思いますが……
ニコ生では、だいたいピーク時間で2000番組前後のユーザー生放送が同時に放送されていますが、そのうちシステムを起動した時間以降にスタートした番組を監視します。
結果
大変悲しいことに、猫は見つかりませんでした……
一応、猫と判定された画像は2枚あったのですが、ポケモンのプレイ画面とFPSのやたらブレた画面で、一体何がどういう理由で猫と判定されたのかは不明です。
正直、1枚くらい猫が見つかってくれると思っていたので、悲しい気持ちでいっぱいです。
とはいえ、猫が見つからなかっただけでは結果としてアレなので、大量の画像認識の中で一体何が最も多く検知されたのかを見ていきたいと思います。
以下が、 Caffe のリファレンスモデルを使ってニコ生をリアルタイム監視した結果、最も多く得られたクラスの上位です
420 web site, website, internet site, site 370 scoreboard 166 bubble 110 toyshop 107 television, television system
1位の Web Site というのは、だいたいデスクトップをキャプチャした放送や、ゲーム配信で多く見られました。どうやら、画面を操作するためのアイコンがやメニューが並んでいる様子が、ブラウザに見えるようです。
2位の Scoreboard も同様に、ゲームのリザルト画面や、普通のゲームの画面のがチョイスされていました。おそらく、数字が規則的に並んでいるためにスコアボードと判定されたのだと思います。面白いところでは、麻雀のゲーム画面がスコアボードと判定されていました。
3位の Bubble はなんだかよくわかりませんでしたが、なんか丸っぽい線が多く現れている画像に多かったです。PS4の配信禁止中の画像なんかは、だいたいこれに引っかかっていました。
4位以下は、正直もうなんだかよくわかりません。
また、 Caffe は画像認識をした結果、マッチ率のようなものを%で出してくれるのですが、これもほぼ頻出上位とかぶっていて、微妙でした。
感想
ニコ生を Deep learning してみた結果、とりあえずゲーム配信がすごく多いというのはわかりました。キャプチャした画像を並べて観ているだけで、ああゲームばっかりだ、という感じです。PS4配信などで簡単にニコ生で放送できる手軽さが影響しているのかも知れないし、もともとそうだったのかも知れませんがよくわかりません。
技術的な感想。
大量のコンテナを立ててみたけれど、やっぱり Caffe で画像認識するところがものすごい詰まります。このボトルネックに引っ張られて、数百番組のリアルタイムの監視とは程遠い速度でした。数十程度ならリアルタイムで監視出来ると思うので、実用的には Caffe を動かすマシンに GPU を使い、もっと後ろに大量に並べるべきかなと思います。また、ニコ生を監視する部分も全然足りてませんでした。1サーバー数十番組くらいイケるかと思いましたが、裏の画像認識部分が詰まっていたので、引っ張られて監視部分も完全に詰まってました。
Ansible+Docker構成なので、横にブン並べるだけであれば札束で殴ることも可能です。ですが、そうすると今度は全体を制御する部分にものすごい負荷がかかります。今回は、各APIをコントロールしていたのは手元のホストマシンで、Rubyで書いたスクリプトで大量のスレッドを生成して各監視サーバーにクエリを投げて回してました。実際に数百番組を同時に監視するのであれば、やはりメッセージキューイング等の非同期クラスタで本格的な対応が必要だと思います。
膨大な情報量に対して、多少こちらの準備が甘かったため、やや残念な結果に終わってしまいましたが、まともなジョブ管理と札束があれば、意外と実用的に全ニコ生監視システムが作れそうな実感はありました。
ソース類に関しては、4日ほどで作ったためかなり荒削りなところが多いことと、普通に公開するとやっぱり怒られそうな気がするので、ある程度整備したうえで問題なければ後日に公開しようと思います。
ひとまず、 Deep learning に触れてみるという目的を達成したので、満足しました。