Amebaにおける
レコメンデーションシステムの紹介
2016 July 25
CyberAgent, Inc. All Rights Reserved
内藤 遥(ないとう よう)
●2012年サイバーエージェント入社
秋葉原ラボ所属
●やっていること
推薦基盤や検索基盤の開発、運用
●趣味
カラオケ、炭水化物ダイエット
タイトル TITLE事業内容
インターネット広告事業 メディア事業 ゲーム事業
広告代理事業
自社広告商品
(アドテク) など
など など
本日のアジェンダ
①レコメンデーションシステムの概要について
②Item-to-Item Collaborative FilteringのSpark実装について
本日のアジェンダ
①レコメンデーションシステムの概要について
②Item-to-Item Collaborative FilteringのSpark実装について
秋葉原ラボが提供しているレコメンデーションシステム
● A.J.A. Recommend Engine
○ 外部メディア向けのテキスト類似度を基にしたレコメンド
● Phoenix(プロジェクト名)
○ 協調フィルタリングによるレコメンドがメイン
○ Spark を使って推薦結果を作成
● バトルレコメンド
○ 各種ゲームのギルドのバトルなどのマッチングをするシステム
以降、レコメンデーションシステム = Phoenix のレコメンデーションシステムとして話を進めていきます
今回のお話
レコメンデーションシステムの利用
Seasparrow (データ解析基盤)
レコメンデーションシステム概要
HDFS
YARN
Spark
HBase
ZooKeeper
Services
アクティビティログ
・閲覧
・購入
・いいね
・お気に入りなど
● アクティビティログの転送
○ 購入履歴や閲覧履歴といったユーザの行動のログを
データ解析用のHadoopクラスタに転送する
Seasparrow (データ解析基盤)
レコメンデーションシステム概要
HDFS
YARN
Spark
HBase
ZooKeeper
Services
Job Scheduler
Recommendation
Program
● サービスごとに必要なデータソースや推薦アルゴリズムを選択
● 推薦結果の作成
submit
推薦アルゴリズム
・Matrix Factorization(MLLib)
・Item-to-Item Collaborative Filtering(実装)
・PLSA(実装) など
外部データソース
Seasparrow (データ解析基盤)
レコメンデーションシステム概要
HDFS
YARN
Spark
HBase
ZooKeeper
Services
Recommendation
Program
● 推薦結果をHBaseに格納
rowkey: <ソルト(<> ハッシュ値の先頭1byte)> _<推薦ID(service + algorithm)> _
<user or itemID>_<version(timestamp)>
value: <> に対する推薦結果(アイテムID, スコアなどのリスト)をシリアライズ
● ZooKeeper上の推薦IDに紐づくversion情報を更新
API
notification
推薦ID: version
Seasparrow (データ解析基盤)
レコメンデーションシステム概要
HDFS
YARN
Spark
HBase
ZooKeeper
Services
● 推薦リクエスト
● imp, clickログの転送
Services
Zero (リアルタイムカウンタ)
API
・時間
・推薦ID (service + algorithm)
・枠ID (どの面で推薦結果を表示したか)
・推薦対象 (user or itemID)
・推薦結果
・imp/click など
LB
request
response
・user or itemID
・推薦ID
・version
Seasparrow (データ解析基盤)
レコメンデーションシステム概要
HDFS
YARN
Spark
HBase
ZooKeeper
Services
● CTRのレポーティング、CTRを基にした推薦結果のリランキング
Services
Zero (リアルタイムカウンタ)
Counting Program
reporting
API
LB
推薦結果のimp/click
バンディットアルゴリズム
CTRを基にリランキング
counting
本日のアジェンダ
①レコメンデーションシステムの概要について
②Item-to-Item Collaborative FilteringのSpark実装について
Item-to-Item Collaborative Filtering
● ユーザベースの協調フィルタリング
○ ユーザ間の類似度をアイテムの評価を要素にしたベクトルで求め、自分とよく似
ているユーザの評価が高いアイテムを推薦する
○ 問題点
■ アイテムの評価が少ないと精度が悪い
■ 計算量が多く、オンラインでの推薦が困難
● アイテムベースの協調フィルタリング
○ アイテム間の類似度をユーザの評価を要素にしたベクトルで求め、対象のアイテ
ムと同じように評価されているアイテムを推薦する
○ 購入履歴などの評価情報をリクエストに含めることで、オンラインでの推薦が可
能
○ アイテムの評価が少なくても精度の良い推薦ができる
Item-to-Item Collaborative Filtering
user item1 item2 item3 item4
A ◯ ◯ ◯
B ◯ ◯ ◯
C ◯
D ◯ ◯
Item-to-Item Collaborative Filtering
user item1 item2 item3 item4
A ◯ ◯ ◯
B ◯ ◯ ◯
C ◯
D ◯ ◯
アイテム間の共起数 (ユーザの重複数)、
アイテムを評価したユーザ数 がわかればよい
Sparkでの実装(Java)
● 事前準備として、各itemを int のIDに変換する(データ量によってはuserも)
Sparkではzip関数でユニークなIDを付与することもできるが、long型であまり
メモリ効率がよくないため、int型のIDに変換する内製のIDMakerを利用している
(user ID, item ID) の評価データの集合のRDDを生成する
JavaPairRDD<Integer, Integer> userItemPairData = ...
Sparkでの実装(Java)
アイテムを評価したユーザ数の取得
JavaPairRDD<Integer, Integer> itemCountData = userItemPairData.mapToPair(tuple -> {
return new Tuple2<>(tuple._2, tuple._1);
}).groupByKey().mapToPair(tuple -> {
return new Tuple2<>(tuple._1, Iterators.size(tuple._2.iterator()));
});
Int2IntMap itemCountMap = new Int2IntOpenHashMap(itemCountData.collectAsMap());
Broadcast<Int2IntMap> broadcastItemCountMap = context.broadcast(itemCountMap);
Sparkでの実装(Java)
アイテムを評価したユーザ数の取得
JavaPairRDD<Integer, Integer> itemCountData = userItemPairData.mapToPair(tuple -> {
return new Tuple2<>(tuple._2, tuple._1);
}).groupByKey().mapToPair(tuple -> {
return new Tuple2<>(tuple._1, Iterators.size(tuple._2.iterator()));
});
Int2IntMap itemCountMap = new Int2IntOpenHashMap(itemCountData.collectAsMap());
Broadcast<Int2IntMap> broadcastItemCountMap = context.broadcast(itemCountMap);
user, itemの並び替え
item ごとに user を連結 Guavaライブラリ
Sparkでの実装(Java)
アイテムを評価したユーザ数の取得
JavaPairRDD<Integer, Integer> itemCountData = userItemPairData.mapToPair(tuple -> {
return new Tuple2<>(tuple._2, tuple._1);
}).groupByKey().mapToPair(tuple -> {
return new Tuple2<>(tuple._1, Iterators.size(tuple._2.iterator()));
});
Int2IntMap itemCountMap = new Int2IntOpenHashMap(itemCountData.collectAsMap());
Broadcast<Int2IntMap> broadcastItemCountMap = context.broadcast(itemCountMap);
fastutil ライブラリを使って省メモリ・高速化
メモリ量はそんなに大きくならないため、ブロードキャスト変数を利用して各ワーカーに転送
複雑なJoinの操作がなくなり、処理がシンプルに書ける
Sparkでの実装(Java)
アイテム間の共起数の取得、コサイン類似度の計算
// アイテムと共起するアイテムリストのRDDを生成
JavaPairRDD<Integer, int[]> itemCoItemsPairData = userItemPairData.groupByKey().values()
.flatMapToPair(items -> {
int[] itemArr = StreamSupport.stream(items.spliterator(), false)
.mapToInt(i -> i).toArray();
return Arrays.stream(itemArr).mapToObj(i -> new Tuple2<>(i, itemArr))
.collect(Collectors.toList());
});
Sparkでの実装(Java)
アイテム間の共起数の取得、コサイン類似度の計算
// アイテムと共起するアイテムリストのRDDを生成
JavaPairRDD<Integer, int[]> itemCoItemsPairData = userItemPairData.groupByKey().values()
.flatMapToPair(items -> {
int[] itemArr = StreamSupport.stream(items.spliterator(), false)
.mapToInt(i -> i).toArray();
return Arrays.stream(itemArr).mapToObj(i -> new Tuple2<>(i, itemArr))
.collect(Collectors.toList());
});
ユーザごとのアイテムリストにフォーカス
primitive の配列を使うとメモリ効率が良い
アイテムごとに共起アイテムの配列を紐付け
Sparkでの実装(Java)
アイテム間の共起数の取得、コサイン類似度の計算
itemCoItemsPairData.groupByKey().mapToPair(tuple -> {
Int2IntOpenHashMap itemCoMap = new Int2IntOpenHashMap(); int item = tuple._1;
Int2IntMap itemCountMap = broadcastItemCountMap.value();
for (int[] coItems : tuple._2) {
for (int coItem : coItems) {
if (item == coItem) continue; // 自身のitemは含めない
itemCoMap.addTo(coItem, 1);
}
}
for (Int2IntMap.Entry entry : itemCoMap.int2IntEntrySet()) {
int coItem = entry.getIntKey(); int coCount = entry.getIntValue();
double similarity = coCount / (Math.sqrt(itemCountMap.get(item)) * Math.sqrt(itemCountMap.get(coItem)));
... // sort
Sparkでの実装(Java)
アイテム間の共起数の取得、コサイン類似度の計算
itemCoItemsPairData.groupByKey().mapToPair(tuple -> {
Int2IntOpenHashMap itemCoMap = new Int2IntOpenHashMap(); int item = tuple._1;
Int2IntMap itemCountMap = broadcastItemCountMap.value();
for (int[] coItems : tuple._2) {
for (int coItem : coItems) {
if (item == coItem) continue; // 自身のitemは含めない
itemCoMap.addTo(coItem, 1);
}
}
for (Int2IntMap.Entry entry : itemCoMap.int2IntEntrySet()) {
int coItem = entry.getIntKey(); int coCount = entry.getIntValue();
double similarity = coCount / (Math.sqrt(itemCountMap.get(item)) * Math.sqrt(itemCountMap.get(coItem)));
... // sort
アイテムごとの共起アイテムの配列を集める
共起数をカウント
コサイン類似度の計算
Tips
● 推薦結果のアイテムを鮮度の新しいものだけにしたい
○ マスターデータから条件に合致する推薦候補のアイテムリストを作成して、ブロー
ドキャスト変数として転送
○ 推薦結果作成時にバリデーションをかける
IntSet validItemSet = IntOpenHashSet();
... // 有効なアイテムの追加
Broadcast<IntSet> broadcastValidItemSet = context.broadcast(validItemSet);
Tips
● Spark上のバッチアプリケーションをJMXでモニタリングしたいが、portの管理が面倒く
さい
○ 事前にレコメンデーションが使うport のレンジを決めておき、zookeeper上に最新
バッチで使用しているport番号をセットし、
バッチ実行の度にインクリメントしていく
○ curatorライブラリの DistributedAtomicInteger を使うと便利
メンバーを募集しています!
●詳しくはコーポレートサイトまで!
    https://www.cyberagent.co.jp/recruit/career/jobs/
●もしくはお気軽に内藤までお声かけ
ください!

Amebaにおけるレコメンデーションシステムの紹介