Quantcast
Channel: ALBERT Official Blog
Viewing all 191 articles
Browse latest View live

創立10周年記念パーティー

$
0
0

2015年7月1日に創立10周年を迎えました。
今まで支えてくださった皆様に感謝の気持ちを込め、先月末にささやかな創立10周年記念パーティーを開催いたしました。

150803albert051

まずは、2011年に業務提携いたしました、デジタル・アドバタイジング・コンソーシアム株式会社の代表取締役 社長執行役員 CEO 矢嶋様と、
当社会長山川の恩師である、中央大学大学院 客員教授 朝野様からご祝辞を賜りました。

150803albert055

150803albert060

続いて乾杯は、freee株式会社 代表取締役 佐々木様。

150803albert069

当社社長上村は学生時代、ALBERTの前身の会社であるインタースコープ(現マクロミル社)でインターンをしており、佐々木氏は当時のインターン仲間でした。ALBERTのCFOだったこともある佐々木氏による乾杯の音頭は、会長、社長共に感慨深いものがあったのではないかと思います。そんなALBERTの歴史を感じた一幕でした。

この後はしばしのご歓談。
今回のパーティーは、ALBERTの古株スタッフに協力してもらい、手作り感あふれるパーティーになりました。

司会は、勤続9年目、現在はチーフコンサルタントとして活躍しております、菅が担当。

150803albert044

会場の生演奏は、勤続6年目のアルバイトあやぴーとご友人による四重奏。
とっても素敵な演奏でした!

150803albert135

歓談の後は、ALBERTの10年を振り返る映像を鑑賞。

150803albert115

この映像も、勤続8年の社員で今はチーフアナリストとして活躍する巣山が担当。
15分で一気に10年を振り返る壮大な映像に仕上がっていました!

映像で10年を振り返った後は、社長による今後の展望です。

150803albert132

150803albert124

社長が熱く今後の展望を語りすぎ、持ち時間を大幅にオーバー!!
この後、幹事の社員にものすごく怒られることを、この時点で社長はまだ知りません。(笑)

そして、会長からの挨拶で、パーティーは無事終了。

150803albert140

お忙しい中ご来場いただいた皆様に、感謝の気持ちを込めて、ALBERTオリジナルグッズをお持ち帰りいただきました。

150803albert155

中身をチラリとご紹介♪

オリジナルボールペン
20150806190143617

オリジナルハンドタオル
20150806190144423

オリジナルキャンディ
20150806190143776
(マンゴー味です♪)

オリジナルグラス
IMG_4256

お客様が帰られた後は、参加したスタッフで記念写真を撮りました。

150803albert159

今後も15年20年・・・と、スタッフ一同力を合わせてALBERTを盛り上げていきたいと思います。

今後とも、どうぞよろしくお願いいたします。


『トピックモデルによる統計的潜在意味解析』読書会を開催中です

$
0
0

はじめまして。データ分析部の越水です。

本日は、弊社セミナールームで定期開催している 『トピックモデルによる統計的潜在意味解析』読書会について ご紹介したいと思います。

『トピックモデルによる統計的潜在意味解析』について

トピックモデルとは、文書中の各単語およびそれらの単語が属するトピックが確率的に生成されていると仮定するモデルです。近年活発に研究が行われている分野であり、ニュースサイトでの記事の自動分類や、ユーザープロファイリングなど、多岐にわたる応用例があります。

ここでは、簡単にトピックモデルについて解説します。 まず、文書には潜在的なトピック(その文書のカテゴリ・話題のようなもの)があり、そのトピックから各単語が生成されていると想定します。その潜在的なトピックを、得られた文書集合に含まれる単語から推定することが主たる目的です。

例えば、いろいろなニュース記事のトピックについて考えてみます。あるニュースが「自民党は28日、総裁選挙を~」という内容で始まっていたとしましょう。我々人間は、一目見て「ああ、これは政治に関するニュースだな」ということがすぐにわかります。同様に、あるニュースが「サッカー日本代表のメンバーが発表され~」という内容で始まっていれば、スポーツに関するニュースであることがわかります。たとえそれが「スポーツ」カテゴリであることが隠されていたとしても、ニュースに含まれる単語を見ることにより、そのニュースがもつ潜在的なトピックを想像することが可能です。

topic_v2

トピックモデルを用いて大量の文書データから学習することで、人間がトピックを判断せずとも、それぞれの文書が持つトピックを自動的に推定することが可能となります。

『トピックモデルによる統計的潜在意味解析』(監修:奥村学, 著:佐藤一誠, 発行:コロナ社)は、今年3月に発行された、トピックモデルについて解説された書籍です。この本を読むことで、トピックモデルのみならず、ギブスサンプリングや変分ベイズ法などの関連する機械学習手法についても習得できるような内容になっています。統計学・機械学習に関する広範囲の知識が必要となるため、必ずしも実務で扱わなくとも、データ分析者にとってトピックモデルを学習する価値は大いにあるのではないかと思います。

トピックモデルによる統計的潜在意味解析 (自然言語処理シリーズ)
佐藤一誠
コロナ社
売り上げランキング: 47,477

どんな読書会なのか

topicstudy05第5回勉強会の模様です。

この読書会は、『トピックモデルによる統計的潜在意味解析』、通称「#トピ本」を、月2回のペースで読み進める会です。

読書会の形式は、各担当者に本の内容を発表していただき、その後質疑応答タイムで議論を行う、というスタンダードなものです。また、発表の後にはLTも行われ、本の内容に留まらない様々な発表をしていただいております。

これまでに5回の勉強会が行われ、第3章3.5節までの発表が終わりました。
ここでは、軽くこれまでの会を振り返ってみたいと思います。

第1回(2015/6/4) 範囲:第1章, 第2章

記念すべき第1回の範囲は、第1章「統計的潜在意味解析とは」・第2章「Latent Dirichlet Allocation」でした。

第1章の発表者は、私、越水が担当いたしました。

「潜在的意味」とはそもそもどういうものか、潜在意味解析の歴史から始まり、 本書で頻出するグラフィカルモデルの説明を行いました。

第2章前半の発表者は、@_kobacky さん。 Latent Dirichlet Allocation(LDA)の概要に加え、 LDAを理解するのに不可欠な確率分布である多項分布とDirichlet分布について、 わかりやすく解説していただきました。

第2章後半の発表者は、@aki_n1wa さん。 LDAではどのように単語が生成されるかを、わかりやすい図とともに説明していただきました。 また、LDAの応用例についても発表いただき、言語データに留まらない様々な応用例をご紹介いただきました。

第2回(2015/6/18) 範囲:第3章3.1, 3.2

第2回の範囲は、第3章3.1「統計的学習アルゴリズム」・第3章3.2「サンプリング近似法」でした。

第3章3.1は、弊社アナリストの中野が担当いたしました。 本書の最大の山場である第3章を乗り切るために、このあたりはしっかりとおさえておきたいところですね!

第3章3.2の発表者は、同じく弊社アナリストの青木が担当いたしました。 第1回の復習も兼ねて、グラフィカルモデルを使わずに数式から条件付き分布を導出しました。 次に、LDAで用いられる学習アルゴリズムとして、ギブスサンプリング・周辺化ギブスサンプリングについて解説しました。 だんだん難しくなってきました…!

第3回(2015/7/7) 範囲:第3章3.3前半

第3回の範囲は、第3章3.3「変分近似法」でした。 発表者は、@tn1031 さん。

ギブスサンプリングとは異なり、決定論的アルゴリズムである変分ベイズ法。この分野を説明した和書は少ないのですが、どういうアルゴリズムなのか、どういう性質なのかを詳しく解説していただきました。

第4回(2015/7/30) 範囲:第3章3.3後半, 3.4

第4回の範囲は、第3章3.3「変分近似法」後半・3.4「逐次ベイズ学習——変分近似法の場合——」でした。

第3章3.3後半の発表者は、越水が担当いたしました。 LDAにおいて変分ベイズ法をどのように適用するのか、また、周辺化変分ベイズ法(CVB)についても解説いたしました。

第3章3.4の発表者は、@MOTOGRILL さん。 これまでの学習アルゴリズムは一括(バッチ)学習と呼ばれ、データ全体に大して学習を繰り返す必要がありました。本発表では、データをひとつひとつ逐次(オンライン)学習する確率的変分ベイズ法についてご紹介いただきました。

第5回(2015/8/27) 範囲:第3章3.5

第5回の範囲は、第3章3.5「逐次ベイズ学習——サンプリング近似法の場合——」でした。

第3章3.5の発表者は、@y__uti さん。 前回の変分近似法と同様に、逐次学習をサンプリング近似法でどのように行うか、というのがテーマです。 周辺化ギブスサンプリングや粒子フィルタについて、わかりやすいイメージ図を用いて説明していただきました。 また、本文中で言及されている、サンプルの活性化についての論文もご紹介いただきました。

会場について

この勉強会の会場は、弊社オフィス内のセミナールームです。このセミナールームでは、他にも弊社開催の「データサイエンティスト養成講座」や、エンジニア向け勉強会「Java女子部JVM勉強会」が開催されておりますので、ご興味のある方はぜひご参加ください。

次回日程と範囲

次回は9月17日(木)を予定しております! 範囲は第4章4.1~4.3節『潜在意味空間における回帰と識別』ですので、皆様ぜひご参加くださいませ。 今後の勉強会の進め方ですが、本書の第3章はやや重い内容なので残りの節はスキップし、先に第4章・第5章を学習します。そして、最後に第3章の残りの節に挑む予定となっております。

参加者・発表者ともに大募集中ですので、ご興味のある方はぜひ下記URLからご応募ください!
第6回『トピックモデルによる統計的潜在意味解析』読書会 – connpass –

また、これまでの読書会の詳細や発表資料は下記URLから確認できますので、合わせてご覧ください。
『トピックモデルによる統計的潜在意味解析』読書会 buy domain it .

DMPを活用したシナリオ設計

$
0
0

こんにちは。ALBERT コンサルティング・アクティベーション推進部の黛です。
今回はプライベートDMPを活用したシナリオ設計の考え方とポイントについてお話します。

プライベートDMPを導入すると、それまで散在していたデータを一元管理し、可視化したり、分析したりすることが出来ます。さらに分析結果に基づいて、顧客毎の嗜好性や購買モチベーションに合わせた施策を打ち分けることも出来るようになります。

以前の記事でも触れましたが、マーケティングオートメーションツールやDMPで管理する施策のことを「シナリオ」と呼びます。「シナリオ」は連係するチャネル(メール/Webサイト/広告/アプリ等)毎に、「いつ」「だれに」「どのように」アプローチするかを定義したものです。

異なる特性を持つ顧客が多くいれば、それに応じてパーソナライズされたシナリオが出来るわけですから、シナリオ数は多くなります。大手総合通販のクライアントではシナリオ数が100本を越えることも珍しくはありません。
ALBERTのプライベートDMP構築ソリューション「smarticA!DMP」はキャンペーンマネジメントの機能を持っているので、多様なシナリオを管理し、自動化することが可能です。

 

シナリオ設計の際に考慮すべき4項目

1. ターゲット

CRMのコミュニケーションはパーソナライズやOne to Oneが基本ですので、まずはアプローチするターゲットの特性を見極めることが大切です。性別や年齢等の属性でセグメントをつくってシナリオを打ち分ける方法もありますが、DMPを導入する場合は、蓄積されたログデータ(購買ログ/サイト閲覧ログ等)を活用して、「嗜好性」と「購買モチベーション」を判別することが有効なシナリオをつくるポイントと考えています

  • 嗜好性

    単品通販の場合は別として、多くの企業は複数のブランドやカテゴリの商品を取り扱っています。ECサイトを利用している顧客に対するシナリオを考える際、 まずはその顧客が「どのブランド、カテゴリ」に興味があるかを見極めます。これは特定の商品情報ページを閲覧したというログからもわかりますが、過去の購 買履歴や閲覧履歴を分析することで顧客毎の嗜好性を判別することが出来ます。ALBERTでは、DMPを導入する際に顧客の「クラスター分析」を行うことが多いです。これは購買履歴や閲覧履歴等の行動ログを分析し、似たような嗜好 性を持つ顧客をグルーピングする方法です。従来の属性ベースのセグメントではなく、実際の行動ベースの顧客分類ができるため、クラスター別のシナリオ設計 で大きな成果を出すことが出来ます。
    またsmarticA!DMPには「データマイニングエンジン」が搭載されています。日々サイトに来訪する顧客(見込客も含む)が、どのクラスターに属するかを自動的に判別して分類する機能を持ち、導入企業様にご活用いただいております。

  • 購買モチベーション

    ブランドやカテゴリの嗜好性に加えて、施策を打つタイミングにおける顧客の「購買モチベーション」、つまり「どれだけ買う気になっているか」を見極めることも大事です。サイトに訪問したからといって買う気になっているとは限りません。サイト訪問の度にプロモーションメールを送っていては、スパムメール扱いされ、最悪の場合、離脱されてしまうこともあります。
    購買モチベーションも様々なログを見ることで判別することが出来ます。ECであれば、トップページや商品一覧ページしか見ていない人に比べて、特定の商品 の商品詳細ページを見たり、お気に入り登録したりした人は購買モチベーションが高いのは明らかです。また、ECサイト流入時の検索ワードや直前に見ていた サイトの情報、サイトへの訪問回数、訪問頻度によってもモチベーションの違いを見出すことが可能です。

2.タイミング

施策を打つタイミングも重要です。顧客のモチベーションが高まったタイミングでないとコンバージョンはもちろんのこと、開封やクリックすらしてもらえないことになります。
事前に「購買期間分析」をして、商品の購買頻度や買い替えサイクルの傾向を把握しておけば、アプローチタイミングも見えてきます。リピート通販の場合であ れば、次に購入するタイミング(商品を使い切るタイミング)、旅行であれば年間の旅行回数や旅行検討期間に関する傾向を分析することになります。

3.メッセージ/レコメンド

「ターゲット(誰に)」と「タイミング(いつ)」が決まったら、どのようなメッセージを届けるかを考えます。ターゲットの嗜好性やモチベーションに合わせ て訴求内容を決め、メールやバナーのクリエイティブを制作します。クリエイティブの中にレコメンドアイテムを入れることが出来る場合はレコメンドルールの 設計を行います。
よくHTMLメールやバナー広告、Webサイトの中に、「あなたへのおすすめ」や「レコメンドアイテム」として複数の商品がパーソナライズ表示されている ものを見かけます。レコメンドにも様々あり、過去の購買履歴を使うこともあれば、最近のサイト閲覧履歴からレコメンド商品を決めることもあります。また購 買履歴がない場合や、どの商品に興味があるか判別が難しい場合は、その顧客の属性(性別・年代等)やクラスター別のランキング情報を出すこともあります。
レコメンドルールの設計も、多くの顧客分析や商品アソシエーション分析の経験があり、レコメンドエンジンの開発にも取り組んできたALBERTの知見が活かせるところです。

4. チャネル

最後に顧客にアプローチする際のチャネル(接点)と、その特性を考えます。
顧客が商品を認知、あるいは購買を思い立ってから購入するまでの、いわゆるカスタマージャーニーを描きながら、どの段階でどんなチャネルに接触するのかを見極めます。
チャネル毎に出来る施策も違いがあります。メールやプッシュ通知であれば、プッシュ形のアプローチが可能ですが、WebサイトのLPOやレコメンドはプル型のアプローチになります。メールとバナーとプッシュ通知では伝えられるメッセージの量も異なります。

 

このように、シナリオ設計には顧客や商品に関する分析と深い考察が必要になりますが、じっくり取り組むことが成果につながることは間違いありません。また、シナリオ毎のKPIを設定してPDCAを回し、継続的に改善することも大切です。

ALBERTでは、通販・小売業だけではなくメーカーやメディア企業のプライベートDMP導入プロジェクトも増えており、多くの業界・業種のお客様から引合いいただいております。こうした実績を活かしながら、クライアント企業とその先にいる顧客を見据えた最適なシナリオ設計を支援いたします。

※お詫び※
前回10/21付けで「DMPを活用した顧客育成」と題してシナリオ設計についての記事を公開しましたが、わかりにくい表現があったため、今回内容を改めて投稿しました。また「顧客育成」もDMPを導入・運用する上で非常に大切な視点ですので、別途記事を投稿させていただきます。

TensorFlowの特徴と性能

$
0
0

はじめまして、データ分析部の島田です。今日はGoogleが先日公開したTensorFlowについて書かせていただきます。既に、動かしてみた系の記事は出ていますので、サンプルコードを使ったコードの特徴の説明とChainerとの速度比較を中心に書きました。

記事アウトライン

  • TensorFlowとは
  • TensorFlowで出来ること
  • TensorFlowのコードの特徴
  • TensorFlowとChainerの速度比較
  • まとめ

TensorFlowとは

以下の記事ををご参照ください。
TensorFlow – Google’s latest machine learning system, open sourced for everyone
Googleの内部ツールだったDistBeliefをインフラの依存性を排除しつつ、性能を高めてオープンソース化したものです。自社比較ではDistBeliefの2倍速くなったそうです。

TensorFlowで出来ること

以下の記事をご参照ください。
TensorFlow: Large-Scale Machine Learning on Heterogeneous Distributed Systems
株式会社ネクストさんのBlog:Googleの公開した人工知能ライブラリTensorFlowを触ってみた
既存ツールとの違いはdata flow graphなどがWEBインターフェースで見ることが可能となっていることでしょうか。
TensorBoardを使用した可視化についてはネクストさんのブログやこちらを参照願います。
他、Multi-Deviceでの処理も行えると記載がありますが、検証出来ておりませんので今回の記事の対象からは除外します。

TensorFlowのコードの特徴

TensorFlowのgithub上に公開されているMNISTの手書き文字認識のコードを抜粋しつつ説明いたします。

大まかな特徴

  • 基本モデルの記述はシンボル
  • weightとbiasを各層で定義する必要がある
  • モデルはinference(構造定義),  loss(ロス関数定義), training, evaluationの大きく4つのブロックに分けて記述出来る
  • flags/FLAGSを使ってmodelで使用する定数をハンドリングする(使用は必須ではない)
  • tf.Graph().as_default()内にモデルの記述をすることでdata flow graphを作成
  • tf.name_scopeを使用して定義した名前がdata flow graph内に表示される
  • tf.Session()で記述した処理の実行管理を行う
  • tf.Session()で作成したセッション(sessとする)をsess.run()することにより、シンボルで記述された処理を実行する
  • tf.train.Saver()に途中経過をrestoreする(別途saver.save()する必要あり)
  • tf.train.SummaryWriter()でmodelの収束具合などの概要データを保存する
  • tf.app.run()でmain()を実行

続いて、TensorFlowに独特な記述部分について説明します。

model定義に使用する定数の記述(tf.app.flags)

まずflags/FLAGSを作成します。

# 定義用のflagsを作成
flags = tf.app.flags

# 値取得用のFLAGSを作成
FLAGS = flags.FLAGS

これを使用して定義と値の取得を行います。

# int型でバッチサイズの定義
flags.DEFINE_integer('batch_size', 100, 'Batch size. Must divide evenly into the dataset sizes.')

# batch_sizeを取得して利用
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size, FLAGS.fake_data)

sessionとsaver

sessionで処理を管理しsaverで変数の値を保存します。
ファイルはdefaultで直近5チェックポイント分保存されます。チェックポイント毎に保存を行い、保存されるファイル数を制限する事によってstorageが圧迫される事を防いでいるという説明がドキュメントに書かれています。

# sessionの生成
sess = tf.Session()

# saverの生成
saver = tf.train.Saver()

# 値の初期化
init = tf.initialize_all_variables()
sess.run(init)  # <- ここで初期化を実行している

# チェックポイント毎に値をファイルに書き出す
# global_stepオプションをつけるとstepの番号がファイル名に記入される
if (step + 1) % 1000 == 0 or (step + 1) == FLAGS.max_steps:
    saver.save(sess, FLAGS.train_dir, global_step=step)

TensorFlow vs Chainer

ChainerのMNISTのexampleを元にTensorFlowのexampleと構造を合わせて実行してみました。
Chainerのコードの変更箇所は以下のとおりです。

  • 訓練データ数を変更(60000 -> 55000)
  • epoch数を1に変更
  • 最適化手法の変更(Adam -> SGD)
  • 各層のユニット数を変更

# 各階層のユニット数がunits=1000となっていたのですが
n_units = 1000
model = chainer.FunctionSet(l1=F.Linear(784, n_units),
                            l2=F.Linear(n_units, n_units),
                            l3=F.Linear(n_units, 10))
# 以下に変更
model = chainer.FunctionSet(l1=F.Linear(784, 128),
                            l2=F.Linear(128, 32),
                            l3=F.Linear(32, 10))

  • dropoutを除外

# dropoutを行うようになっていましたが
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h1 = F.dropout(F.relu(model.l1(x)),  train=train)
    h2 = F.dropout(F.relu(model.l2(h1)), train=train)
    y = model.l3(h2)

# 以下のとおりにdropoutの処理を外しました
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h1 = F.relu(model.l1(x))
    h2 = F.relu(model.l2(h1))
    y = model.l3(h2)

速度比較結果

1batch(=100個の画像データ)当たりの処理速度で比較を行いました。
Chainerはコンスタントに0.004sec前後の値で処理が終了しており、平均処理速度は0.0044sec/batch。
一方TensorFlowの平均処理速度は0.0062sec/batchでした。データsaveの部分は計測から除外しているため単純に演算速度の差が現れているはずです。
極端な例ですが今回と同じ構造で同じデータを処理した場合、300万回mini batch処理をすると100分程度の差が出る結果となります。
TensorFlowはDistBeliefの2倍の速いそうなのですが、Chainerはそれを上回っていました。速いですね。

ちなみに私のマシンのスペックは以下のとおりです。

  • Mac OS X v10.11(EI Capitan)
  • プロセッサ 2.7GHz Intel Core i5
  • メモリ 16GB(DDR3)

まとめ

記述量的にはそこまで変わらないですし個人的にはChainerの方が扱い易いというのが感想です(慣れの問題だとは思いますが。)。
まだMulti-Device ExecutionやDistributed Executionは検証出来ておりませんので、TensorFlowの真価はわかっていない可能性も高いです。
ただ、本格的にTensorFlowに時間を費やすのはもう少し様子を見てからでも遅くないような気もしています。

新宿野村ビルで北海道物産展☆

$
0
0

こんにちは。広報担当の大森です。

ALBERTが入っている新宿野村ビルでは、夏祭りや日本酒祭りなどいろいろな催しが開催されています。
今回は、昨日今日と2日間限定で開催されている北海道物産展の様子をご紹介!

Slack for iOS Upload (1)

北海道物産展といえば、百貨店でのテッパン人気企画!お仕事の合間に気軽に立ち寄れるなんて嬉しいですね♪

もちろんお弁当の販売もされているのですが、初日は11:30には売り切れていたとの情報を仕入れ、今日は開店の11:00に突撃!!

同じ考えの人たちがたくさん来ていて、すぐにお弁当コーナーはスカスカです。
Slack for iOS Upload (2)

IMG_2462

この中から4~5種類くらいが販売されていました。

私は、カップチラシをGET!!

Slack for iOS Upload

おいしそうです。早く食べたい!

お弁当の他にも、蟹・イカ・鮭・ホタテ、乾き物やお菓子もあり充実したラインナップです。

Slack for iOS Upload (3)

いつもなら買わないのについいろいろ買ってしまいました・・・

IMG_2463

ALBERTはお昼休みの時間が決まっていないので、各自自由に休憩をとるスタイル。
ランチは、外食する人・お弁当を作ってくる人・愛妻弁当持参の人・コンビニでお弁当を買う人など様々。
お弁当は、自席でネットサーフィンしながら食べたり、リフレッシュスペースでわいわい食べたり、ビルの外のベンチで鳩と一緒に食べたりすることができます。

151029ALBERT045

151029ALBERT048

次はどんな催しがあるのかな~!楽しみです♪

t-SNE を用いた次元圧縮方法のご紹介

$
0
0

こんにちは。データ分析部の越水です。

以前、 弊社ブログ記事
高次元データの可視化の手法をSwiss rollを例に見てみよう
にて、高次元データの可視化手法を複数ご紹介いたしました。
今回は、 Kaggle などのデータコンペで最近注目を集めている可視化手法として、
t-SNE をご紹介したいと思います。

t-SNE は、高次元データの次元を圧縮するアルゴリズムであり、特に高次元データを可視化する際に有用です。
高次元データの関係性をうまく捉えられるという特徴があり、
最近 Kaggle などのデータコンペでよく用いられるようになりました。

t-SNE はどんな仕組みなのか?

まず、 t-SNE のアルゴリズムを紹介したいと思います。
厳密さよりも分かりやすさを重視した説明なので、詳細を知りたい方は原論文をご覧ください。

2点間の「近さ」を確率分布で表現する

このアルゴリズムの一番の特徴は、 2 点間の「近さ」を確率分布で表現するところにあります。

t-sneのイメージ

例えば、点 x_i とそれ以外の全ての点との距離を考えましょう。
t-SNE では、基準となる点 x_i を中心とした正規分布を考え、
距離を測りたい点 x_j が抽出される確率密度を、点 x_i から見た点 x_j の近さ p_{j|i} とします。
これにより、x_i の近くにある点ほど p_{j|i} は大きくなり、遠くにある点ほど p_{j|i} は小さくなります。
数式で表すと、条件付き確率 p_{j|i} と同時確率 p_{ij} は次のように表されます。


p_{j|i} = \frac{\exp(-||x_i - x_j||^2 / 2\sigma_i^2)}{\sum_{k\neq i}\exp(-||x_i - x_k||^2 / 2\sigma_i^2)},    p_{i|i} = 0
p_{ij} = \frac{p_{j|i}+p_{i|j}}{2N}.

次に、次元圧縮後の点 y_i と点 y_j の「近さ」 q_{j|i} を考えます。これらは、次元圧縮前の点 x_i と点 x_j に対応します。
こちらも同様に確率分布で表現するのですが、
次元圧縮後の近さは正規分布ではなく自由度 1 の t 分布で考えるところがミソです。
裾の重い t 分布を用いることで、圧縮前の x_ix_j が近くないデータの場合、圧縮後の y_iy_j をより遠くに配置することが可能になります。


q_{ij} = \frac{(1+||y_i - y_j||^2)^-1}{\sum_{k\neq i}(1+||y_i - y_k||^2)^-1},    q_{ii} = 0.

圧縮前と圧縮後の確率分布の KL 情報量を最小化する

では、この圧縮後の点 y_i はどのようにして決定すればよいでしょうか?
もし、圧縮前の点 x_i と点 x_j の近さを点 y_i と点 y_j で正確に表すことができたなら、条件付き確率 p_{j|i}q_{j|i} は一致するはずです。
完璧に一致させることは難しいので、なるべく近づけるようにしましょう。
ここでは、分布間の近さを測る方法として、KL (カルバック・ライブラー)情報量を用います。
圧縮前の確率分布と圧縮後の確率分布の KL 情報量を計算し、これを最小化することを目指します。

この KL 情報量を損失関数 C とし、C が最小になるような y_i を求め、それを圧縮後の点とします。


C = \sum_{i}\sum_{j}p_{ij} \log \frac{p_{ij}}{q_{ij}}

以上が、 t-SNE のアルゴリズムの概要です。

t-SNE を用いて、データ可視化を行う

それでは、実際に t-SNE を実行してみましょう。

t-SNE は様々なプログラミング言語に対応したパッケージが用意されています。
R などのパッケージで実装されているのは、通常の t-SNE を高速化した Barnes-Hut t-SNE と呼ばれる手法です。

この手法は、簡単に言えば、

  • 十分に離れている 2 点間の確率を 0 と近似
  • quadtree (3 次元に圧縮する場合は Octree) を用いて空間を分割し、同じセルに含まれている点はまとめて中心点で近似

という 2 つの近似により、通常の t-SNE よりも高速かつ省メモリで実行可能な手法です。

また、t-SNE のパッケージは、デフォルトで事前に PCA (主成分分析) を実行しています。
これは、事前に 30 次元ほどに落としてから t-SNE を行うことで計算時間を節約するためであり、原論文でも推奨されているテクニックです。

今回は、t-SNE と、比較のために PCA で可視化を試みます。

データセット

http://www.cs.columbia.edu/CAVE/software/softlib/coil-20.php

今回、データセットは Coil-20 を使います。
Coil-20 は、20 種類の物体を 5 度ずつ角度を変えて撮影した画像データセットです。
画像は全て白黒であり、20 種類 × 72 = 1440 枚の画像が用意されています。
これらを 32 × 32 pixel に圧縮し、全 1024 区画のグレースケールを取得します。

この 1440 枚、 1024 次元のデータに対し、可視化を試みます。

PCA の実行結果

まず、PCA を実行した結果は下記のとおりです。

PCAの結果

散布図を見ると、各物体が混ざってしまっていて、
まったく分類できていないことがわかります。

PCA を実行する R コードは下記のとおりです。

# Coil-20 データセットの読み込み
coil20 <- read.csv("coil20_train.csv")
# PCA の実行
d_pca <- prcomp(coil20[,-ncol(coil20)], scale=TRUE)

# plot のための準備
coil20.levels <- levels(coil20$class)
coil20.levels.colors <- rainbow(length(coil20.levels))
coil20.idx <- sapply(coil20$class, function(cls){match(cls, coil20.levels)})
coil20.colors <- sapply(coil20.idx, function(idx){coil20.levels.colors[idx]})

# plot
par(mar=c(3,3,3,7))
plot(d_pca$x[,1:2], t='n', xlab='', ylab='')
text(d_pca$x[,1:2], labels=coil20.idx, col=coil20.colors)
par(xpd=T)
legend(par()$usr[2] + 0.2, par()$usr[4]
, legend=paste(as.character(seq(1,20)), coil20.levels, sep=": ")
, pch=16
, col=coil20.levels.colors
, cex=1.0)

t-SNE の実行結果

では、t-SNE を実行してみましょう。
下記のような実行結果が得られました。

t-SNEの実行結果

20 種の写真が、おおむね綺麗に分類できていることが分かります。

Coil-20 の画像データは、ひとつの物体を5度ずつ角度を変えて撮影したものです。
正面から撮った写真と、 360 度近くの回転をして撮った写真は、当然似た写真になります。
t-SNE では、そういった構造をループ状に表しており、
多次元空間での構造を低次元空間に表すことができていると考えられます。
なかなか面白い結果ですよね。

よく見ると、右上あたりに、いくつかの線状に連なっているものがありますね。。。
これらは、3 種のミニカーの写真のようです。

minicars

実際に写真を見ればわかりますが、
同じ車種で異なる角度のミニカーより、異なる車種でも同じ角度のミニカーのほうが、
類似した写真であるため、t-SNE では近い空間に配置されているというわけです。

t-SNE を実行する R コードは下記のとおりです。
上述の通り、t-SNE は様々なプログラミング言語に対応したパッケージが用意されており、
今回は {Rtsne} パッケージを使用しました。

実行は非常に簡単です。
{Rtsne} パッケージを読み込み、

Rtsne()
関数の引数にデータセットとオプションを指定するだけで実行できます。
# Rtsne パッケージの読み込み
# install.packeges("Rtsne") # インストール
library(Rtsne)

# t-SNE の実行
d_tsne.coil20 <- Rtsne(coil20[,-ncol(coil20)])

# plot
par(mar=c(3,3,3,7))
plot(d_tsne.coil20$Y, t='n', xlab='', ylab='')
text(d_tsne.coil20$Y, labels=coil20.idx, col=coil20.colors)
par(xpd=T)
legend(par()$usr[2] + 0.2, par()$usr[4]
, legend=paste(as.character(seq(1,20)), coil20.levels, sep=": ")
, pch=16
, col=coil20.levels.colors
, cex=1.0)

他の手法と比較するとどうなのか

原論文では、 Coil-20 データセットに対して Isomap や LLE など他の次元削減手法を適用した結果が掲載されています。
その結果を見ると、 t-SNE 以外はうまく可視化できていないことがわかります。

Isomap や LLE などの手法は、Swissroll のようなデータがつながっている構造を捉えるのは得意ですが、Coil-20 のようないくつかの部分に分かれているデータには不向きのようです。
t-SNE ではデータが分かれている構造をしていても有効なので、複数のクラスタがあるデータを可視化したいときには便利な手法であるといえるでしょう。

原論文の Supplemental Material には、
さらに他の手法と比較した結果も掲載されているので、興味のある方は是非ご覧ください。

おわりに

以上、t-SNE の紹介および実行結果でした。
高次元データの関係性をうまく捉えることができる手法であることをご理解いただけたと思います。

注意点として、t-SNE で圧縮する次元は、 2・3 次元が推奨されています。
4 次元や 5 次元でも計算できないことはないのですが、計算コストがかなり高くなってしまうので実用的ではありません。
そのため、 2・3 次元以外の次元に圧縮したい場合は別のアルゴリズムを用いるべきです。
しかし、t-SNE の主な用途は可視化であるので、特に大きな問題ではないかと思われます。

高次元データを可視化する際には、多様体学習に加え、 t-SNE を是非お試しください。

また、多様体学習と t-SNE の得意領域はデータのつながり度合いの構造によって決まりそうですが、高次元データにおいてその構造はどのように解明できるのでしょうか?
このような高次元データの構造を解明する方法の一つとして、「トポロジカルデータアナリシス(TDA)」という分野を用いる方法があります。
こちらについても、後日 弊社ブログにてご紹介させていただく予定ですので、お楽しみに!

参考文献

  • L.J.P. van der Maaten and G.E. Hinton. Visualizing High-Dimensional Data Using t-SNE. Journal of Machine
    Learning Research
    , 9(Nov):2431–2456, 2008.
  • L.J.P. van der Maaten. Accelerating t-SNE using Tree-Based Algorithms. Journal of Machine Learning Research 15(Oct):3221-3245, 2014.

新受付システム!

$
0
0

はじめまして。おぐにです。広報と採用の窓口を担当しています。
趣味や休日の過ごし方は、自他共に認める引き篭もりで、
ALBERTへは餌付けされながら出社しております。今後とも宜しくお願いいたします!

さて、本題です。
ALBERTへお越しになったお客様を最初にお迎えする受付。
今まではお客様ひとりひとりのお名前をカードに記載してお迎えしていたのですが、
これがハイテクシステムになりましたー!

今まで、長くALBERTの受付を務めていたウェルカムボードさん
img_ウェルカムボード2
お客様にも、手書きの文字に温かさを感じるとお喜びいただいていました。

遠目に見ても温かさが伝わります。伝わってください。
img_ウェルカムボード1

そんなウェルカムボードがこの秋、ハイテク化の波に飲まれて変身しました!

img_受付システム2

img_受付システム3

これからは物理学者アインシュタインの似顔絵がお迎えしまーす。
画面には、アインシュタインの似顔絵と共に名言も記載してあるのですが・・・

おかげさまで日々たくさんのお客様にご来社いただき、
ご案内画面に隠れて見えないことが多いのです。一体なんと書かれているのでしょう?

img_受付システム1

「The more I learn, the more I realize I don’t know.
The more I realize I don’t know, the more I want to learn.」

-学べば学ぶほど、自分がどれだけ無知であるか思い知らされる。
自分の無知に気づけば気づくほど、より一層学びたくなる。-

ALBERTでは社内勉強会も頻繁に行なわれていますが、
社員ひとりひとりの知識欲もとても強いので、各人がこの言葉には
深く頷くのではないかなーと(きっと頷いていると)思います!

私も・・・まずは無知であることを思い知りたいです。

年末年始休暇のお知らせ

$
0
0

こんにちは。広報の大森です。

12月に入ってすっかり寒くなりましたね。
新宿野村ビルもクリスマスムード1色!

S__130170886

どーんと入り口にクリスマスツリーが飾られています☆
そして、夜になると外の街路樹もキラキラ!

S__130170883

素敵~!!

さてさて、クリスマスが過ぎればお正月がやってきますね。
ALBERTは、12月26日(土)~1月4日(月)の10日間、年末年始休暇をいただきます。
他の企業と比べて、かなり長~い休暇のようです。

なぜかというと、
ALBERTでは、年間の営業日数を240日と定めており、1年365日から土日祝日を引いたその他の日はすべて会社の特別休暇として、年末年始や飛び石連休などにあらかじめ割り当てているためです。
年末に翌年のALBERT独自カレンダーを作成しているんですよ。

さらに、有給休暇もとりやすい環境づくりに努めていますので、有給休暇と合わせてゆっくり海外旅行へ行ったり、のんびりリフレッシュしたり、スタッフそれぞれ休暇を使ってプライベートも充実!!

・・・とスタッフにはわくわくの休暇ですが、営業をなさっているお取引先の皆様にはご迷惑をおかけすることと存じます。
大変申し訳ございませんが、何卒ご理解を賜りますようお願い申し上げます。


GAE Managed VM & Custom Runtimeについて

$
0
0

このエントリーはGoogle Cloud Platform Advent Calendar 2015の14日目です。

10月に入社しましてブログには初めて投稿します石井です。休日はJavaScriptやGoで趣味の開発を行っていたり、家に知人を集めてボードゲーム会をしてたりします。

さて、ALBERTはAWSメインの開発を行っているのですが、GCP (Google Cloud Platform) も最近盛り上がって来ている!という事で、新しい案件をGCPで構築してみることにしました。その際にGAE (Google AppEngine) 周りの調査を色々と行ったので、今回は特にManaged VMとCustom Runtimeについて紹介できればと思います。

GAE Managed VM, Custom Runtimeは簡単に言うと、GAEが裏で管理していたコンテナを触れることができるようになったり、自分で定義したDockerのコンテナをGAE上で動かすことがでるようになるものです。


GAE sandbox

予めGCPのコンソールから適当なプロジェクトを作成しておきます。

まずは基本のGAEプロジェクトから段階的にVM使うようにしてみます。
app.yamlとそこから呼び出してるmain.pyを作成、デプロイします。

runtime: python27
api_version: 1
threadsafe: false

handlers:
- url: /
  script: main.app

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

app = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

$ gcloud preview app deploy app.yaml –promote
でデプロイします。

ブラウザでhttps://プロジェクト名.appspot.com/を見てみるとHello, World!が出ます。
ここまではよくあるsandboxのhello worldですね。


GAE Managed VM (MVM)

次に、このpythonアプリケーションをMVMに変更してみます。

runtime: python27
vm: true
api_version: 1
threadsafe: false

handlers:
- url: /
  script: main.app

manual_scaling:
  instances: 1

さっきと比べ、app.yamlに
vm: true
manual_scaling: {instances: 1}

が追加されただけです。

MVMで重要なのはvm: trueの方で、これを追加するとMVMモードでアプリケーションが動作するようになります。
manual_scalingはスケーリング・負荷分散の設定値です。MVMはデフォルトで2つのインスタンスを立ち上げ分散するようになっているのですが、今回は特に必要ないので1つのインスタンスで処理するように設定しています。

先ほどと同じように
$ gcloud preview app deploy app.yaml –promote
でデプロイします。

MVMモードでVMを立ち上げているのでsandboxの時より少し時間が掛かるかと思います。気長に待って、先ほどと同じhttps://プロジェクト名.appspot.com/にアクセス。ちゃんと動作しているのが確認できます。また、GCEのコンソールへ移動すると今デプロイしたアプリケーション用のインスタンスが起動しているのが確認できると思います。sandboxで隠されていたインスタンスの中身があれこれ確認できるのですね。感激です。

注意する点として、MVMで立ち上がったインスタンスは新しいバージョンのアプリケーションがデプロイされても削除されず動き続けます。消したい場合はGAEコンソールから過去のバージョンを削除しないといけません。GCEから直接インスタンスを削除しても、しばらくするとオートスケーリングの設定により自動的にインスタンスが立ち上がってしまいます。このへん新しいバージョンのアプリケーションがデプロイされたら自動で消えるようにしたかったのですが調べてもよくわかりませんでした。何かご存じの方はご教授頂ければと思います。

さて、せっかく立ち上げたインスタンスですが、これだけじゃVMの何が嬉しいのかわからないのでちょっとアプリケーションをいじってみます。と言ってもsqliteでローカルのディスクへ書き込む簡単なものですが。
GAE sandboxだとfsへのアクセスができないのですが、vmにすればfsにも触れる用になるので、sqliteでローカルにDBファイルを作って読み書きすることも可能なはずです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import webapp2
import sqlite3
from time import time

class MainPage(webapp2.RequestHandler):
    def __init__(self, *args, **kwargs):
        super(MainPage, self).__init__(*args, **kwargs)
        self.conn = sqlite3.connect('mydb')
        self.cur = self.conn.cursor()
        self.cur.execute('''
                    CREATE TABLE IF NOT EXISTS welcome
                    (time NUMBER DEFAULT 0)
                    ;''')

    def update_count(self):
        self.cur.execute('INSERT INTO welcome(time) VALUES (?);', (time(),))
        self.cur.execute('SELECT count(*) from welcome')
        self.conn.commit()
        return self.cur.fetchone()

    def get(self):
        count = self.update_count()
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('''Hello, World!
You are %sth visitor!
''' % count)

app = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

シンプルすぎるカウンター。
どうしようもないアプリケーションですがそこは気にせず、これを今までのようにデプロイするとアクセスのたびにカウンターがちゃんとインクリメントされているのが確認できます。
スクリーンショット 2015-12-14 13.02.37
何気ないアプリケーションですがGAEでローカルファイルへ書き込むことができるようになりました!

さて、MVMで立ち上がったインスタンスですが、GCEのインスタンスとして立ち上がっているのでもちろんSSH接続して中身を確認することができます。MVMで立ち上がったインスタンスはgoogle管理のインスタンスとなり、「途中で中身をいじって変更したまま動かし続ける」事はできませんが、弄ることだけはできます。試しにGCEインスタンスの一覧からsshボタンを押すと「ユーザー管理への切り替え」とダイアログが出てきて、変更は全て失われますと表示されます。
スクリーンショット 2015-12-14 13.05.07
何はともあれsshしてみるとそれらしいコンテナが動いているのが確認できますので、それに接続してみます。

$ sudo su -
$ docker ps
CONTAINER ID        IMAGE                                                                             COMMAND                  CREATED             STATUS              PORTS                              NAMES
9d2f45e52d31        gcr.io/google_appengine/mvm-agent                                                 "/usr/local/bin/gunic"   10 minutes ago      Up 10 minutes                                          condescending_allen
ca653eb06a3e        gcr.io/google_appengine/nginx-proxy                                               "/var/lib/nginx/bin/s"   10 minutes ago      Up 10 minutes       0.0.0.0:8080->8080/tcp, 8090/tcp   evil_meninsky
a68171a218ac        appengine.gcr.io/498347750172306472/プロジェクト名.default.20151214t123834   "/usr/bin/python2.7 /"   10 minutes ago      Up 10 minutes       8080/tcp                           gaeapp
73cb013cc55b        gcr.io/google_appengine/memcache-proxy                                            "/home/memcache/memca"   10 minutes ago      Up 10 minutes       11211/tcp                          memcache
40812862abb0        gcr.io/google_appengine/fluentd-logger                                            "/opt/google-fluentd/"   11 minutes ago      Up 11 minutes                                          boring_tesla

$ docker exec -it a68171a218ac bash --login

コンテナの中に入れました!

$ pwd
/home/vmagent/app
$ ls
Dockerfile  app.yaml  main.py  main.pyc  tmpTIqZW9
$ cat Dockerfile
# Dockerfile extending the generic Python image with application files for a
# single application. This is only used for pre- env: 2 deployments.
# The name of this image is for historical reasons 'python-compat', but it
# refers to a version of the runtime separate from the 'runtime: python-compat'
# image.
FROM gcr.io/google_appengine/python-compat
ADD . /app

$ Ctrl+p
$ Ctrl+q

最後はCtrl+p→Ctrl+qでコンテナから抜けます。豪快にexitするとコンテナが終了してアプリケーションが死んでしまうので気をつけてください。
いろいろと面白いものが見れましたね。ログインしたディレクトリにあったDockerfileが今まさにGAEで動いているコンテナのイメージファイルになります。


GAE MVM Custom Runtimes

実はこのDockerfileを自分で書いてグリグリするのもCustom Runtimeという形で公式にサポートされています。

GoogleCloudPlatform/appengine-nginx-hello
公式のサンプルではnginxを動かしています。これを真似てpythonのflaskを動かすコンテナを書いてみたのがこれです。以下はこれの説明です。
airtoxin/gae-mvm-custom-runtime-sample

runtime: custom
vm: true
api_version: 1
threadsafe: false

handlers:
- url: /
  script: dynamic

manual_scaling:
  instances: 1

nginxのyamlファイルを参考にしました。runtime: customとしてDockerfileを同梱するとそれを動かせるようになります。
handelrsのscript: dynamicは公式のマニュアルをどれだけ探しても出てこないのですが…おそらくリクエストをGAE側でハンドリングせず、サーバーに直接届けてくれるようなオプションかと思います。

で、動かすDockerfileですが、せっかくなのでpython3を動かしてみます。

FROM python:3.4.3

ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt
ADD . .

ENTRYPOINT ["python3", "main.py"]

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from flask import Flask
app = Flask(__name__, static_folder='static')

@app.route("/")
def hello():
    return app.send_static_file('index.html')

@app.route("/_ah/start")
def _ah_start():
    return "ok"

@app.route("/_ah/health")
def _ah_health():
    return "ok"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

こんな感じのDockerfileとpythonのスクリプトを用意して、doker runするとmain.pyでサーバーが起動するようにしてやります。

サーバーはflaskで立ててみます。sandboxだとdjangoかwebapp2くらいしか呼び出せず少々めんどくさかったのが、好きなライブラリを導入できるので軽量なライブラリでさっと書けるのがいいですね。
/へのルーティングはstatic/index.htmlを返すようにしていますので、これは適当に配置しておきます。/_ah/start/_ah/healthはGAEのライフサイクルイベントで、GAEが定期的にリクエストを送り、コンテナがreadyなのかhealthyなのかを確認するのでそれに応答するようにしています。healthの方はステータス200で何かメッセージを送ればよいようです。

あとはrequirements.txtに依存ライブラリを記述しておきます。これは先程のDockerfile内で、コンテナ内のpythonにインストールするようにしています。

Flask==0.10.1
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
Werkzeug==0.11.2
wheel==0.24.0

ここまできたらデプロイはお馴染み
$ gcloud preview app deploy app.yaml –promote
ですね。
スクリーンショット 2015-12-14 14.45.48
来ました!勝手に定義したDockerfileがGAEで動いてます!


まとめ

GAE sandboxはVMの管理をGAEがやってくれる代わりにfsを触れないなどの制限がある。
そこでManaged VMにするとVMの管理をユーザーが行う必要がある代わりにそれらの制限を取っ払える。
さらにCustom RuntimeにするとVM自体をユーザーが定義したものを使用できる。

Custom RuntimeまでやるともはやGCEでやるのとほぼ同じ環境が手に入れられますね。スケーリング周りを引き続き面倒見てくれるので、この辺とリクエストのハンドリング周りの自由度で使い分ける感じでしょうか。

ALBERT大忘年会!

$
0
0

ALBERTでは毎年の恒例行事として忘年会を行なっており、演奏やダンス、ゲームといった様々な企画で盛り上がります。
例年は、代々木オフィスの近くにあった代々木倶楽部を会場に行なっておりましたが、今年2月にオフィスを移転し、代々木倶楽部が遠くなってしまったので、今年は開催が危ぶまれておりました。
しかし、「今年も忘年会をやりたい!」という声に応えるかたちで一部の社員が立ち上がり、何とか今年も忘年会を開催!

賞品争奪ダーツや、借り人競争・ALBERTクイズなど、今年は豪華賞品をめぐるゲームが盛りだくさん♪
ダーツは皆が下手すぎてなかなか賞品がなくならないという予想外のハプニング(?)も・・・

そして恒例のハマダンス!これを見ないと年が越せません(笑)
IMG_2865

衰えないキレキレダンスをありがとうございました!しかも今年はラップつき!最高!

こんな自作ウチワを作るファンも現れ、盛り上がりました!
ハマ⭐︎ファンクラブ

頑張ってくれた、幹事の菅(右)と司会の山下(左)。
IMG_2850
他に、準備や進行に活躍してくれたスタッフも多数。お疲れ様でした!

今回は受付でこんなカードが配られこのカードでチームを組んだり、回答者を決めたりしましたよ。
IMG_2819
ALBERTらしい忘年会の演出でした。

ALBERTの2015年の営業は、明日25日(金)で終了です。
本年も広報ブログにお付き合いいただき、誠にありがとうございました。
来年も、ALBERTの社内の様子などなど皆様にたくさんお伝えできればと思っております。
どうぞよろしくお願いいたします。

では、皆様、良いお年をお迎えくださいませ^^

位相的データ解析(Topological Data Analysis)について

$
0
0

データ分析部の藤本、今井です。今回は共同で位相的データ解析(Topological Data Analysis)についてのご紹介をしようと思います。

位相的データ解析とはデータの集合をトポロジーと呼ばれる「柔らかい」幾何を用いて解析する手法です。幾何学を使った統計学ですと情報幾何学と呼ばれる分野がありますが、こちらはデータの集合ではなく確率分布に対して微分幾何という「硬い」幾何学を用いた分野です。

位相的データ解析は最近ホットな分野で、ビジネス業界でもこの位相的データ解析に注力している会社のAyasdiが総額100億円近く資金調達しており非常に期待されています。位相的データ解析の実データへの応用としては画像認識などがあります。

さて、「柔らかい」幾何、トポロジーとは何でしょうか?通常、私たちは以下の図形は別のものと見なしますが、トポロジーの世界では図形を伸ばしたりなど変形させたものも同じ図形と見なすため、これらの図形を全て同じものだとします。

top1

ではどういった図形を異なる図形と見なすのでしょうか。トポロジー的に上記と異なる図形の1つの例として、上記の図形に1つ穴を開けた図形(下図参照)は異なると見なします。ただし、穴を空けた図形同士は同じものだと見なします。また、「円が1つ」の図形と「円が2つ」の図形は異なるものだと見なします。

top2

このように円と三角を同じと見なすことでどういった利点があるのでしょうか。1つには余計な情報を落とすことで、図形の本質を見ることができるということがあげられます。例えば、以下の文字を見てください。

top3

私たちはこれら全ての文字をALBERTだという認識できます。フォントによって丸みを帯びたり角ばったりしていますが、文字の認識においては文字の太さや文字が丸いか角ばっているかは本質ではなく、細い線と太い線、円と角を同じものだと思っても問題ありません。ここからも位相的データ解析が画像認識で使えそうだと想像がつくかと思います。

以下の図の左上にある5つの点をデータの集まりだとしましょう。ここから右上のように点の周りに円を作り、その円を少しずつ大きくしていくことを考えましょう。少し大きくしただけだと5つの円がある状態ですが、さらに大きくしていくと左下のように円が重なっていきます(左下は真ん中に穴が1つだけ空いているので、円盤とトポロジー的に同じ図形となります)。そこからさらに大きくすると右下のように全て繋がり、1つの円とトポロジー的に同じ図形となります。

phom1

これを用いて何ができるのでしょうか?まず始めに思い浮かぶことはクラスタ分析のクラスタ数の決定ができそうです(古典的にもSilhouette基準やGap基準など既に色々提案されています)。

例として以下のデータで考えてみましょう。

clust1

この例だと2次元のデータなので可視化すればすぐに3つの集まりだと分かります。位相的データ解析でどうこれを捉えるかというと、データの点の周りに小さい円を描き、その円を大きくしていきます。そうすると円の半径の途中まではそれぞれの色でどんどんくっついていきますが、各色でくっついた後はしばらく3つの塊の状態が続きます。そのままどんどん大きくしていけば最終的には1つの塊になります。この「3つの塊の状態が続く」ことを捉えれば、自動的にクラスター数が決定できそうです。また、このケースでは2次元ですが、位相的データ解析は高次元のデータに対しても適用可能なので、高次元で簡単に可視化できないようなケースで特に活躍できそうです。

実際に上記のことをライブラリを用いてやってみましょう。位相的データ解析を行えるライブラリはいくつかありますが、今回はRのTDAというパッケージを用います。

install.packages("TDA")
library(MASS)
library(TDA)

#データの生成
mu1 <- c(0, 0)
Sigma1 <- matrix(c(1, 0.3, 0.3, 1), 2, 2)
x1 <- mvrnorm(30, mu1, Sigma1)
mu2 <- c(8, 0)
Sigma2 <- matrix(c(1, -0.2, -0.2, 1), 2, 2)
x2 <- mvrnorm(30, mu2, Sigma2)
mu3 <- c(4, 6)
Sigma3 <- matrix(c(1, 0, 0, 1), 2, 2)
x3 <- mvrnorm(30, mu3, Sigma3)
x <- rbind(x1,x2,x3)

#TDAによる計算
Diag <- ripsDiag(X = x, maxdimension = 0, maxscale = 5, library = "Dionysus")

#可視化(バーコードプロット)
D <- Diag[["diagram"]]
n <- nrow(D)
plot(c(min(D[,2]), max(D[,3])-1.5), c(1,n + 1), type="n", xlab = "radius", ylab ="", xlim=c(min(D[,2]), max(D[,3])-1.5), ylim=c(1, n + 1), yaxt ='n')
segments(D[,2], 1:n, sort(D[,3]), 1:n, lwd=2)

 

barcode_plot_H0

上記はバーコードプロットと呼ばれているものです。この図の見方としては、x軸の半径の大きさに対して、そこで縦線を引いた時にバーコードの線がいくつあるかを数えることで、例えば半径の大きさが2.5の時は3つの塊、3.5の時は1つの塊であることが分かります。この図から分かることは、半径が2より少し小さいところから3つの塊になり、そこから半径を大きくしていってもしばらく変化していないので、このデータは3つの塊からできているであろうと予想できます。

上記では塊の数を数えましたが、穴の数を数えるということもできます。穴の数を自動的に数えられれば、例えば「A」「L」「B」という文字を識別する時に、それぞれ穴が1個, 0個, 2個という情報を自動的に取得できるため、文字の識別するための特徴量として使えそうです。また、点の周りに円を描いて各点を大きくしてあげることで、薄れた文字の認識をする時に役立ちそうです。実際に以下のドットの文字を表現したデータから穴の数を計算してみましょう。

image_ALB

library(TDA)

#データの生成
image_A <- cbind(x= c(0,1,2,3,4,5,6,7,8,9,10,3.5,5,6.5) * 0.6 + 2,
 y =c(0,3,6,9,12,15,12,9,6,3,0,6,6,6)*0.55 + 1.25)
image_L <- cbind(x = c(0,0,0,0,0,0,0,0,0,1,0.25,0.5,0.75,1.25,1.5) * 4 + 2 ,
 y = c(0,1,2,3,4,5,6,7,8,0,0,0,0,0,0) + 1.25)
image_B <- cbind(x = c(0,0,0,0,0,0,0,0,0,1,1,1,1.25,1.25,1.5,1.5,1.5,0.25,0.25,0.25,0.5,0.5,0.5,0.75,0.75,0.75,1.25,1.25) * 4 +2,
 y = c(0,1,2,3,4,5,6,7,8,0,4,8,5.3,6.6,1,2,3,0,4,8,0,4,8,0,4,8,3.5,0.5) + 1.25)

#TDAによる計算
Diag_A <- ripsDiag(X = image_A,maxdimension = 1,maxscale = 5)
Diag_L <- ripsDiag(X = image_L,maxdimension = 1,maxscale = 5)
Diag_B <- ripsDiag(X = image_B,maxdimension = 1,maxscale = 5)

#可視化(パーシステント図)
plot(Diag_A$diagram)
plot(Diag_L$diagram)
plot(Diag_B$diagram)

以下が得られた結果となります。(左から「A」「L」「B」の結果となります)

p_ALB

こちらの見方としては、各点の周りの円の半径の大きさを大きくしていった時に、穴が生まれた時(Birth)の半径の大きさと、穴がなくなった時(Death)の半径の大きさをプロットしたものです。この図はパーシステント図と呼ばれています。穴の数は赤い三角で表現されています。ここからすぐにそれぞれの文字の穴の数が1個、0個、2個ということが分かります。

位相的データ解析の実データへの応用としては上記のように画像認識がよく知られていますが、ここでは他に応用できるかを考えてみたいと思います。例えば複数のモデルから複数のデータ集合が生成された時に、生成モデルごとに分類したい場合を考えましょう。この場合、福水先生が提案されているようなカーネル法による分類が応用できそうです。一言で説明すると、一つのパーシステント図にカーネル法で変換した先の再生核ヒルベルト空間の一つの点を対応させ、この再生核ヒルベルト空間上の点の集合について変化点検出を行い、相転移の温度を推定することで分類を行うことが可能となりそうです。

位相的データ解析はまだできたばかりの分野でその実力も未知数ですが、実力次第ではディープラーニングのように一世を風靡する分野になるかもしれません。位相的データ解析を学んでみたくなった人のために以下の文献を挙げておきます。

参考文献
タンパク質構造とトポロジー ―パーシステントホモロジー群入門― (シリーズ・現象を解明する数学) 共立出版 

この本はタンパク質の構造ですが、使用されているパーシステントホモロジーについての入門にもなります。

TOPOLOGY AND DATA

元スタンフォード大の教授で、現在はAyasdiに在籍しているGunnar Carlsonによる解説論文。彼の動画による位相的データ解析の解説もお勧めです。

BARCODES: THE PERSISTENT TOPOLOGY OF DATA

バーコードプロットの良い解説です。

バレンタインデー☆

$
0
0

広報ブログのお時間です。
昨日は、バレンタイン☆ということで、毎年恒例のバレンタイン企画が今朝突如行なわれました!

ALBERTでは毎日10:00に朝礼が行なわれており、その朝礼では、担当者がくじ箱に入っているお題に沿ってスピーチをするという「朝の一言コーナー」があります。
(ALBERTで働いているみんなの人となりを知ろうという目的で、ずいぶん昔から行なわれているんですよ。)

20160215104418836
いつものようにくじ箱からテーマを引いて・・・・・と思いきや、くじ箱の中は全部「バレンタイン」!!(笑)

朝の一言コーナーをジャックしてバレンタイン企画のはじまりです☆
今年のテーマは「collaboration」。
バレンタインのイベントを通して、スタッフ同士のコミュニケーション活性化をはかる企画。
内容はこちらです↓
20160215104419334
ガチャガチャマシーンを回して、でてきた問題から正解の女性を探し、その女性の席に行くとお菓子や豪華賞品(みんなで食事にいける権利)がもらえます。

ここで、ガチャガチャマシーンが登場!

ジャーン!!!
20160215104419131

なんという手作り感あふれる!!!温かみがハンパないですね。
女性の有志メンバーが頑張って作ってくれました!

20160215104419268

カプセルに入った問題の数々

早速スタート!!

20160215104420376

でてきた!
問題を見せてもらいました。

20160215104420684

「2013年新卒入社の女性。データサイエンティスト養成講座の運営も担当。」

なるほど。あの子ですね。
といった感じで、女性社員の席へ行き、バレンタインのスペシャル詰め合わせセットをもらいます。

詰め合わせには、毎年恒例になりつつあるオリジナルチロルもちゃんと入っています♪

DSC_1141

自由参加の今回の企画でしたが、気がつけばガチャ行列が・・・

DSC_1164

日頃は、同じ部署の人や良く仕事をする人にコミュニケーションが限定されてしまいがちですが、新たな交流を生む素晴らしい企画でした!

この1年で人数がかなり増えたALBERT。お菓子の詰め合わせも業者並(笑)

DSC_1137

人数が増えると、企画の実行が大変になるので簡略化されがちですが、人数が増えたからこそさらに面白く、意味のあるものになると感じたバレンタインでした♡

有志の女子社員の皆さん、ありがとうございました!

ホワイトデー☆

$
0
0

本日は3月14日、ホワイトデー!ということで、
2月のバレンタインデーのお返しを男性陣からいただきました!
ありがたや~♪

今年はくじを引いてあたった賞品をもらうシステム。

IMG_3607

司会はデータ分析部のこっしーです。←リンクを見ていただくとおわかりいただけるかと思いますが、このブログでは真面目な記事を書いているこっしー。社内イベントでもいつも何かと活躍してくれるイケてるメンズであります。

私の番がやってきました。

IMG_3608

本格的な抽選券!商店街の福引のようで朝からほっこり♪
よく見るとかわいいくじ箱も、この日のために準備してくれたそうです。

結果は惜しくも4等・・・
お菓子の詰め合わせをいただきました。
焼き菓子だけじゃなく、紅茶もさりげなく入っているという細やかな心遣い!!
イベントの後には、社長から日ごろの感謝を込めてということで、社長の出身地である京都の桜饅頭、会長からはフランス菓子もいただき、デスクがお菓子の山で嬉しい悲鳴です。

IMG_3613

IMG_3631

そして見事1等を当てたのは、データ分析部のおおきさん!
おねだりして中身をみせてもらいました☆

IMG_3634

Sabonのソープと4等の焼き菓子に加えて豪華お菓子セット!
うらやましい!!

2等も、ジェラートピケのハンドクリームとお菓子だったそうで、セレクトの女子力の高さに女性人一同関心しきりのホワイトデーでした!

男性の皆様、ありがとうございました!

プライベートDMP活用事例:カート放棄シナリオのクロスチャネル展開

$
0
0

こんにちは。ALBERT コンサルティング・アクティベーション推進部の笠原です。

今回はプライベートDMPを活用し、総合通販企業にてカート放棄シナリオをクロスチャネルで実施した事例をお話します。

ECサイトで商品購入を検討していて、カート(買い物かご)に入れたものの購入していない状態を「カート放棄」といいます。カート放棄されている顧客に実施する施策が「カート放棄シナリオ」です。

カート放棄シナリオで非常に有効なもののひとつに、「カート放棄メール」というものがありますが、これは、カート放棄状態の顧客に、リマインドメールを送るものです。カートに商品が残っていて購入されていないということは、比較検討していた他社・他ブランドの商品を購入してしまったと思いがちです。しかし、カート放棄メールを配信すると、クリック数に対して10%を超える注文率が出ることも珍しくはありません。さらに、カート投入商品のリマインドと合わせて、一緒に購入されやすい商品をレコメンドすることで、注文数アップを図ることも可能です。

【カート放棄シナリオフローチャート】

カート放棄シナリオフローチャート

上図の「カート放棄DM」では、「ECサイトでカート投入商品あり、投入後7日間購入履歴なしメール許諾なし」の顧客を抽出し、DMでカート投入商品のリマインドを行いました。この際、カート投入商品のリマインド+合わせて購入されやすい商品をレコメンドするDMを、オンデマンド印刷のひとつである、バリアブル印刷を用いて送付しました。バリアブル印刷とは、外部ファイルやデータベースのデータに基づき、印刷するページの一枚一枚に対して、テキスト、画像などといった内容を変えて印刷する技術です。この方法を活用するとDMでも顧客毎にパーソナライズされた情報を出し分けることができます。

 

【カート放棄シナリオ 効果比較】
カート放棄シナリオ効果比較

効果の比較は、「DMからのサイト遷移率」と「注文率」を計測することで検証しました。

メールと比べて、DMはサイト遷移率では落ちますが、サイトに訪問した人の注文率は2ポイント高くなっています。またDMは製作・発送などコストがかかるので、ROI(Return On Investment:投資対効果 ここでは施策の実施コストに対して得られる利益を指します)では劣りますが、現在はバリアブル印刷技術の進化によりパーソナライズDMの配信コストも下がってきており、その結果これまでアプローチできなかった顧客(メールの配信許諾を得られない顧客等)への購入促進が出来るようになったということで、クライアントから好評を得ています。

このクライアントでは、DMの反応率を上げる取組みもしており、さらにROIの改善が進められています。

総合通販企業の場合、まずECサイトでDMPを導入し、Eメールで様々なシナリオをテスト、成果が出たものをリアルチャネル(DM等)に展開するケースが増えています。また、DMPのシナリオは1つのチャネルだけではなく、複数のチャネルを組み合わせて活用すること(クロスチャネルアプローチ)が成果につながります。例えば、「メールで反応しない顧客にDMを送付」「メールを開封しない顧客にDSPで広告配信」等です。

 

ALBERTではプライベートDMPを導入いただいたクライアントに対して、コンサルタントがシナリオ設計・運用サポートを実施しており、DMPをクライアントのニーズに合わせてフル活用していただくことを目指しています。

私のように、前職の通販企業側で日々シナリオ設計・改善活動に取り組んでいたコンサルタントもおりますので、ぜひご相談ください。

シェアランチの会におよばれしました♪

$
0
0

桜が満開!やっと春らしい陽気になりましたね。
こんなお花見日和は、お手製弁当を持ってお出かけしたくなります。

お手製弁当といえば、ALBERTは料理上手のスタッフが多く、先日まで、月に1度シェアランチ会という名のお料理持ち寄り会が開かれていました。
いつもスタッフのSNSにアップされるおいしそうな画像によだれをたらしていた私ですが、今後はシェアディナー会になり、社内開催ではなくなってしまうという噂を聞きつけ、最後のチャンスにとお願いしておよばれに成功!わくわくしながらラストシェアランチの会当日を迎えるのであります。

当日、会場であるリフレッシュスペースに行ってみると・・・・
データ分析部、CRMソリューション部、コンサルティング・アクティベーション推進部、経営管理部などなどいろいろな部署からの精鋭が集まり、既にテーブルにたくさんのお料理が並んでおりました。

IMG_3626

品数もすごいですが、クオリティーもすごいシェアランチ会。ホテルのビュッフェさながらです!せっかくなのでぜーーんぶご紹介♪(サラダ多めです)

クラムチャウダー(玉ねぎ嫌いの人のために玉ねぎ抜きも作ってくれるという気配り!)
IMG_3619

アボカドとパクチーのサラダ、ふりかけハンバーグ、いろいろ野菜のバルサミコサラダ
IMG_3624

よだれ鶏、ブロッコリーとベーコンのサラダ、シーフードサラダ
IMG_3625

鶏とキャベツ焼き
IMG_3621

シーフードサラダ2個目
IMG_3622

牛筋の赤ワイン煮込み(スタッフの旦那様作。ご自宅に遊びに行った際にご馳走になってから忘れられず何かとリクエストしてしまう1品!)
IMG_3623

そしてそして、デザートももちろん充実☆

なんかおいしいみかん(名前を忘れました)(ミントがのっている!)
IMG_3630

チーズケーキも出てきました!(チーズとゼラチン混ぜて固めただけじゃないですよ。ちゃんとカスタードから作ったのだそう。とろける美味しさでした~)
IMG_3617

デザートのお供はマリアージュフレール!(最後までおしゃれ~♪)
IMG_3616

手ぶらで食べに行った私にも皆さん快くランチを振舞ってくださり、美味しいし楽しいし最高のシェアランチ会でした^^
シェアランチ会の皆様、ありがとうございました!

12573796_827156687393854_834854037042086516_n


ベイズ情報量規準及びその発展 ~概説編~

$
0
0

今井です。今回より数回にわたってベイズ情報量規準及びその発展について書きたいと思います。

情報量規準と聞くとAIC(Akaike, 1973)やBIC(Schwarz, 1978)が真っ先に思い浮かぶ人が多いかと思います。情報量規準を勉強したことのある人であれば、予測精度を上げるためにモデル選択をするのであればAIC、データが生成されている構造を知ろうとするのであればBICを用いるという使い分けをすることもご存知だと思います。以下ではベイズ情報量規準(BIC)に絞って説明をしていきます。

ベイズ情報量規準の目的である、予測ではなく妥当なモデルの構造を知りたい時とはどういった場合でしょうか。例えば、単なる売上の予測だけではなくMMM(Marketing Mix Modeling)を因果モデル化したモデルによる広告などの施策の効果を知りたい場合や、k-means法でクラスター分析をする時のkを決める時などが上げられます。

後者の方が簡単なので、後者から説明します。先日弊社で行われたデータサイエンティスト養成講座のクラスター分析の岩崎先生による基調講演の中で次のようなお話をされていました。下の3つの混合分布の内、一番右の分布であれば誰でも2つの分布から構成されていることが分かるが、統計を用いて真ん中、さらには一番左の分布も2つの分布から構成されていることが分かるかが問題になる、と。

GMM_distribution

前回のブログ記事でTDA(topological data analysis)でもこの混合数を推定できると説明しましたが、こういった微妙なケースではTDAで判別するのは難しそうです。ではこの問題を情報量規準を用いて解けるかを調べてみます。上記の真ん中の分布であるN(1,1)とN(-1,1)の1/2ずつのmixtureと、少し分布を近づけたN(0.85,1)とN(-0.85,1)の1/2ずつのmixtureが1,2,3つの分布のいくつから構成されているかを情報量規準によるモデル選択を用いて、2つの分布から構成されているということを当てられるかを見てみましょう。2つの混合する分布が近づいていく時、BICを適用できる条件であるフィッシャー情報行列の正則性が無くなっていってしまうので、正則性が成り立たなくても適用できる情報量規準WBIC(Widely applicable BIC, Watanabe 2013)も比較対象として入れて見てみましょう。WBICを計算する際の事前分布は一様分布を用いています。

GMM_IC_result1

このくらいの例だとBICで数千~数万サンプルあれば正しい分布の混合数が選択できそうです。WBICはもう少し混合する分布が近くて特異性が高い所だと力を発揮しますが(渡辺先生のHPの例では分布の差が0.3のケースで見ているので上記よりもっと特異性が高い状況です)、正則条件が成り立つような状況だとBICに負けてしまうようです(正則条件下ではWBICはBICと漸近一致となるような基準ではありますが)。

では次に時系列の因果推論モデルを考えてみましょう。

causal_representation

上記の左図のようにu時点後にa→bに直接的な効果があるものを赤い矢印で表現します。また、上記の右図のように観測されていない潜在変数Lによって、L→aに(s-u)時点後に直接効果があり、L→bにs時点後に直接効果があるとします。この場合、Lが観測されていないことによってa→bにu時点後に効果を与えているように見えてしまいます。これを擬似的な因果関係と呼び、直接的な因果効果と区別するために緑の点線の矢印で表現します。

この2つを識別できるかという問題は高度な設定になってしまうため、まずは次のような簡単なモデル選択で情報量規準の性能を見てみましょう。

causal_candidate_models

データを生成しているモデルとして上図のモデル3のように変数1と変数2に互いに1時点遅れで直接的な因果関係があり( x_1(t-1) \to x_2(t), x_2(t-1) \to x_1(t) )、変数1と変数3には互いに擬似的な因果関係があるとします( L(t-1) \to x_1(t), L(t-2) \to x_3(t), L(t-1) \to x_3(t), L(t-2) \to x_1(t) )。この時、データからは変数2と変数3の間にも2時点遅れで関係があるように見えるため、モデル4, 5のようなモデル候補を用意しておきます。またモデル0, 1, 2はモデル3のサブモデルとしてモデル候補を用意しておきます。この6つのモデルから情報量規準によってモデル3が正しいモデルだと当てられるかをシミュレーションしてみます。マーケティング領域だとデータが週次ベースで、新たなIMC(Integrated Marketing Communication:  統合マーケティングコミュニケーション)施策を行っていた期間が数ヶ月程度の時点で広告施策の効果を知りたいということがあるため、数十サンプル程度でシミュレーションしてみます。また上記のパラメトリックモデルとしてARMAモデルを用いました。

Causal_IC_result1

このモデルでは特異性が高いからかWBICの方がBICよりも高い性能を示しています。数十サンプルでも高い確率で正しいモデル選択がされていることが分かります。

先ほどリリースを出した応用統計学会の発表で受賞した研究は、このWBICの改良版にあたるiWBIC(improved WBIC, Imai & Kuroki 2016)の提案及びその性質を明らかにしたものです。こちらの研究内容も今後ブログで説明予定です。iWBICはWBICで用いているWatanabe理論(参考文献[4])を用いているため、次回以降はWatanabe理論とWBICの説明を行っていく予定です。

シミュレーション結果だけ先にお見せしますと、今回見てきたシミュレーション例に対するiWBICの性能は下図の緑の線のとおりでうまく改善されていることが分かります。

GMM_IC_result2

Causal_IC_result2

それでは次回以降もよろしくお願いいたします。

参考文献

[1] Imai, T. and Kuroki, M. (2016). An improved widely applicable Bayesian information criterion for singular models, In Preparation.
[2] Schwarz, G. E. (1978). Estimating the dimension of a model. Annals of Statistics, 6, 461-464.
[3] Watanabe, S. (2013). A widely applicable Bayesian information criterion. Journal of Machine Learning Research, 14, 867-897.
[4] Watanabe, S. (2009). Algebraic geometry and statistical learning theory. Cambridge University Press

 

Apache Spark を使ったシステム構築のための Tips

$
0
0

システムソリューション部の佐藤奏です。

並列分散処理フレームワークApache Sparkがホットな昨今。サンプルコードや活用事例もいろいろと公開されていますが、では実際にSparkを利用してシステムを構築しようとするとき、どのような考慮が必要なのでしょうか。

今回は「SparkとAWS EMRを使ったシステム構築」を念頭に、開発の初期段階――技術選定や開発スケジュール検討、外部設計、プロトタイプ作成・評価――において有用な情報や開発の進め方のポイントをいろいろとご紹介してみようと思います。結構、地味な話が多いですがお付き合いください。

本記事執筆時点でのSparkの最新バージョン1.6.1をベースとした記述です。

Apache Spark 超概要

Spark自体の紹介は既に多くの記事がありますので、ここでは簡単にとどめます。

特徴

  • Scala製の、大規模データ並列分散処理フレームワーク
  • 処理はオンメモリで実施
  • Scala, Java, Python, R で利用可能
  • 機械学習ライブラリMLlibが使える
  • お洒落なロゴ(私見)

処理がオンメモリで行われるところがHadoopとの大きな違いで、機械学習とも相性がいいと言われます。先日バージョン2.0.0のプレビューリリースが行われるなど活発に開発が行われています。

もう少し詳しい紹介資料としては

が分かりやすく、かつ新しい内容です。NTTデータ社の他のスライドにもSparkがテーマのものが多数あります。

導入/利用方法

クラウドベンダー提供のプラットフォーム

Amazon Web Service (AWS) Elastic MapReduce (EMR) にSparkが標準搭載され、自前でインストール作業をすることなく利用できます1

そのほか、筆者は試していませんが Microsoft AzureGoogle Cloud Platform でも利用できます。

スタンドアロン環境への導入

つまり、分散処理させずに手元のPCで動かすということです。 Windows では、以下の記事が大変参考になりました。

Mac ではHomebrewを利用し

brew install apache-spark
で導入できます。

チュートリアル

具体的なコード例は本記事の趣旨から離れるため掲載しませんが、比較的新しく非常に分かりやすいスライドがありますのでご紹介します。ずいぶん簡潔な書き方で使えることが分かると思います。

よく使うのは下記のコマンドですね。

  • spark-shell
    : SparkのREPL(対話型評価環境)。Spark風味のScala REPLが起動する。
  • spark-submit
    : jarファイルを渡してSparkの処理をキックする。

Tips & Traps

ここからが本題です。筆者が実際にSparkでアプリのプロトタイプ作成を行った際に「考えどころ」になったポイントを列挙します。

技術選定(Scala, Java, Python, R, どれを使うべきか)

システム構築という観点からいうと、現時点では Scalaが順当 です。他の言語は以下のデメリットがあります。

  • Java:コードが冗長になる。
    • そもそも言語自体の特徴としてScalaよりコードが長くなりがちだが、Sparkでは
      RDD#map()
      など引数に関数をとる関数を頻繁に使うので、より顕著。特にJava 8が使えない(=ラムダ式が使えない)とつらい。
    • というかJava 8でも結構大変だったよ、とは同僚談。「Tupleをいちいちnewしたり、パターンマッチングが無かったり、RDDをJavaRDDに変換したり」いろいろ苦労がある、とのことです。
  • Python:パフォーマンスの点で難あり。
  • R:どちらかというとインタラクティブな分析作業に向いた言語であり、システム構築にはなじまない。

一方でScalaは、ウェブ上の情報量はJava等に比べると少ないですし、技術者の数も限られる、というデメリットがあります。もっともSparkに関していえば、処理対象範囲が限定的なことが多く、Scalaの深い知識は必ずしも必要ないと思います。むしろSpark自体のチューニング手法を研究することの方が重要です。

上記はあくまでシステム構築の観点から見た場合の話です。普段データ分析業務に従事し、PythonやRに慣れている方が、アドホックな分析で大規模データ処理の効率化のためにPython, RでSparkを使うということは全然アリですし、有効に利用できると思います。

また、RDDよりも新しいAPIである DataFrame API では、開発言語の違いによる著しい性能劣化は起こらないそうです!(前掲スライドp.51参照) 現段階ではまだ情報が少ないですが、PySparkでのシステム構築が容易になることが見込まれ、要注目です。

外部設計

Sparkで 実施しない 処理を明確にしましょう。

Sparkを使ったコードの中に、分散処理させる必要のないような処理を混ぜ込むことも出来てしまいますし、アドホックに実行する分にはそれでも構わないのですが、システム化の際にはそのようなコードは切り出して区別した方が全体として合理的な構成になり、開発も運用保守もしやすいです。

また、そもそもメモリに乗り切らないようなデータセットを扱う場合、Sparkでは対応できないので、Hadoop MapReduceなど別の方法が必要になります。

いずれにしても、実際のプロジェクトではSparkだけで処理が完結しないことの方が多いと思います。Sparkはあくまでシステムの構成要素の一部と位置付け、他の処理との間のインタフェースを明確に定義しておくと開発作業がスムーズに進むでしょう。

デバッグ

実行方法

本番環境では

spark-submit
にfat jar2を渡す形で起動することになります。

開発においては

spark-submit
ではなくIDE上でのデバッグ、テストコードでのテスト実施も可能です。その際、
spark-submit
へオプションで与える各種の値(マスターのURLなど)は、代わりに
SparkConf#setMaster()
等で与えてやる必要があります。

また、EMRでテスト実行を行う前に PCローカル環境で

spark-submit
で実行し、うまくいくことを確認しておくべきです 。後述のようにEMRですんなりいかない場合も多いので、問題の切り分けに役立ちます。

注意点

RDDの操作は「変換」「アクション」の2つに大別されますが、遅延評価の仕組みにより「アクション」で初めて実処理が実行されることから、 デバッグがやりにくい場合がある ことを知っておきましょう。

Sparkのメソッドは基本的にlazy evaluation(遅延評価)なので、デバッグがやりにくく、メモリ消費量を予測するのも難しいです。また、並列処理の中でprintデバッグとかをしようとしてもコンソールには出力されず、それぞれのスレーブ上の指定のstdoutファイルに出力されてしまいます。

【後編】Apache Sparkを使って、メモリ使用量が大きいバッチ処理をスケールアウト | ADN LAB’s Blog

また、PCローカルで動かすのとクラスターで動かすのとでは、当然ながら環境の差異が大きいです。ローカルではできるのに、EMRに乗せると動かない、なぜだろう……といったことがよくあり、調査にそれなりの時間がかかります。しかもクラスターの起動・終了が伴ったりすると時間的オーバーヘッドがどうしても発生します3動作環境によらないロジック自体のテストはなるべくローカルで実施 すること、EMRの習熟含め クラスター上でテスト実行するための作業期間を十分に確保 することが重要です。

便利な関数ふたつ

全く恣意的なチョイスですが、あまり目立たないけれど便利だなーと思った関数を紹介します。

各ノードに渡す定数は
SparkContext#broadcast()
で包む(性能向上)

読み取り専用の定数を使う場合、

SparkContext#broadcast()
でラップしましょう。ノードへの値の配布が効率化されます。例えばあるRDDと別のテーブル(メモリに載る範囲でそこそこ大きいサイズ)とを突合するロジックがあるとして、そのテーブルを非RDD化して
List
Map
で保持する……といった場合に効果的です。

実際、定数をbroadcastしただけでパフォーマンスが2倍ほどになった経験があります。社内の別のプロジェクトでも、テーブルの「非RDD化&broadcast」で10倍以上高速化しました。SparkというとRDDにばかり気を取られがちですが、定数で、かつ適切なデータ構造(例えばキーでランダムアクセスをするなら

Map
)で持たせることによっても高速化が可能だったりします。

RDD#sample()
でランダム抽出(テスト時に便利)

本番データを使って性能評価をするような場合、全量を相手にすると時間がかかりすぎるなら

RDD#sample()
を使いましょう。指定の確率でランダムにレコードを抽出して処理させることができます。例えば全体の約1%を抽出するなら
sample(false, 0.01)
などとします。

第2引数

fraction
はあくまで確率なので、例えば全量が100件のRDDに
sample(false, 0.01)
したとして毎回ちょうど1件だけ拾われるとは限らず、多少のブレがあります。

関連書籍

おわりに

Spark、面白いです。簡潔な記述で並列分散処理が実現できるのが良いですね。ついでにScalaの経験がほとんどなかった筆者としては、Scalaの良い練習にもなりました。

紹介した各種資料・サイトの作成者の皆様に感謝申し上げます。


  1. 参考:Amazon Web Services ブログ: Apache Spark on Amazon EMR 
  2. sbtのプラグイン sbt-assembly を入れて
    sbt assembly
    で作成。 
  3. デバッグ時はEMRは起動しっぱなしにしておき、随時ステップ追加で処理させた方が作業効率がいいと思います。 

エントランス動画を作成しました!

$
0
0

こんにちは!広報の大森です。

毎日たくさんのお客様をお迎えするALBERTのエントランスに、ちょっとだけ変化がありましたのでご紹介します。

お気づきの方もいらっしゃるかもしれませんが、つい2日前から、ALBERTのソリューション・サービスをご紹介する動画が流れるようになりました。

IMG_4851

右の奥にあるディスプレイに動画が流れています。

IMG_4852

向かい側にはソファがあるので、座ってゆっくりとご覧いただけるようになっています。
6分半の動画ですので、新宿にお越しの際には、是非寛ぎにいらしてください♡

それから、新宿野村ビルまで見にくるのはちょっとめんどうだなぁと思ったそこのあなたに朗報です。youtubeにもアップしました☆

今後、ナレーションをつけるなど、グレードアップも予定しています。
お楽しみに~!

プライベートDMPを活用したインターネット広告最適化

$
0
0

こんにちは。ALBERT コンサルティング・アクティベーション推進部の武田です。

ALBERTに入社する以前はインターネット広告の専業代理店におりました。今回は前職で考えていたこととALBERTに入社して気づいたことをまとめて、「プライベートDMPを活用したインターネット広告の最適化」というテーマでお話します。

インターネット広告はこの十数年で目覚ましい進化を遂げ、手法が高度化してきています。最近は様々なアドテクノロジーが開発され、多様なターゲティング広告を配信できるようになりました。

しかし、実際は広告効果を改善する取り組みの度合いが広告主ごとにまちまちで、適切なPDCAサイクルをまわしている広告主は少ないのが現状です。本来、アドテクノロジーの活用は広告配信の手段であって、目的ではありません。本来の目的は自社のマーケティング活動を、より成果が出る質の高い活動にしていくことです。しかしながらその運用の複雑さゆえに実施することに精一杯で手段が目的化してしまい、本来の目的からは遠いものになってしまっているケースが見受けられます。

そんな状況でプライベートDMPを導入すれば、さらに広告配信・運用が煩雑になってしまうのではないかとお感じの方もいるかもしれません。しかし、プライベートDMPを導入することでこれまで煩雑に感じていた運用が整理され、ターゲティングも適切に行えるようになるため、本来行うべきマーケティング活動の最適化ができるようになるのです。

ポイントは「顧客ニーズの理解」と「広告配信の最適化」です。

インターネット広告といっても様々ありますが、今回は多くの広告主が実施している「リターゲティング広告」にスポットをあて、リターゲティング広告の最適化におけるプライベートDMP活用のメリットをお伝えできればと思います。

プライベートDMPに蓄積されたデータを分析することで「顧客ニーズの理解」が可能になり、「配信ターゲットの最適化」と「広告メッセージ・クリエイティブの最適化」を実現できます。

図

1.顧客ニーズの理解

自社のリターゲティング広告がどんな人を追いかけているか、しっかりと把握できているでしょうか。もちろん一度自社サイトに訪問した人であることは当然ですが、「どういったニーズを持って訪れた人か」を事前に考えてリターゲティングリストの設計をしているかが重要になります。なぜならば、自社サイトを訪れる人は実に様々なニーズを持っており、何となくサイトを訪れた比較的ニーズの低い人から、今日にでも購入を考えているニーズの高い人まで様々いるからです。

では、そのニーズの高低をどのようにしてリターゲティングリストに反映するのでしょうか。従来のやり方では、自社サイトのアクセスログを基に、トップページしか見ていない人はニーズが低い、商品詳細やフォームまで到達したらニーズが高い、といった具合に分類していました。

一方、プライベートDMPにはアクセスログ以外にも様々な情報を格納することができます。特に購買履歴やメールへの反応履歴、顧客ランク情報等のCRM情報は顧客ニーズを明らかにする上で有用です。

・3ヶ月前に商品を購入して、買い替え時期が近い顧客
・メールを送付してもほとんど開封しない顧客
・セールやクーポンがあれば高確率で購入する顧客

これらの人物像は、「自社サイトのアクセスログ」だけではわからないとてもリアルな顧客像です。これらはプライベートDMPに自社の顧客データを繋ぎこむことで初めてわかることなのです。

こういった顧客像は、リターゲティング広告の成果を向上させるのに大いに役立ちます。なぜならば、その人のニーズがより深く把握できているため、どのようにアプローチをすればコンバージョン率が高まるかを描きやすいからです。従来の訪問ページを軸とした分類だけでは、本当にリアルなニーズを把握するには限界があります。この具体的なニーズの可視化こそ、プライベートDMPを活用する大きなメリットなのです。

2.配信ターゲットの最適化

顧客ごとの具体的なニーズを可視化したら、やるべきことは「ターゲットごとの成果に基づいた最適化」をしていくことです。プライベートDMPを活用すれば様々なターゲットセグメントを生み出すことができます。サイトの閲覧履歴と購買履歴やメルマガ施策の結果、コールセンターからのフィードバックを掛け合わせて「これはコンバージョンに繋がりやすい」と考えられるターゲットをグルーピングしてリターゲティング配信することが可能となるのです。

ターゲット像・ニーズを明確にして広告を配信していると、自ずと成果差が表れてきます。クリック率が高いもの、コンバージョン率が高いもの、あるいはその逆といった具合です。よって、日々の運用の中で成果の良いターゲットに予算を配分したり、類似のターゲットを作ってみたり、といった最適化を行うことができます。

逆にアクセスログだけといった限定的な情報からセグメントを作って広告配信を実施していると、改善・チューニングポイントが見えにくく、PDCAサイクルが滞りがちです。

自社の顧客をよく観察し、どうすればコンバージョンに結び付くかを徹底的に考える。これこそが本来のマーケティングであり、実施すべきPDCAサイクルと言えます。

3.広告メッセージ・クリエイティブの最適化

次にお話するのは広告メッセージとクリエイティブ(バナーやテキスト、リンク先)についてです。自社のリターゲティング広告のクリエイティブはどうなっていますか? すべて同じバナー、あるいは2~3種類のバナーをABテストとして同時に出稿している、など様々かと思います。

しかし、ここで重要視したいのはそのクリエイティブとリターゲティングリスト(=ターゲット)との適性についてです。どのリターゲティングリストにも共通のクリエイティブを設定していないでしょうか。もしそうだとしたら、それは大変もったいないことです。

前項で「顧客ニーズの理解」について述べました。リターゲティングにおけるターゲットは「ニーズの違い」によってわけられていると言っても良いかと思います。ニーズが異なればクリエイティブも異なる、というのはごく自然な話です。

では、なぜ一律のクリエイティブを設定している広告主が多いのでしょうか。
そこには「クリエイティブを複数制作するリソース・費用が足りない」という面と「どういったクリエイティブを作れば良いかわからないので汎用的なものを作った」という面があり、結果的にどのリターゲティングリストに対しても同じようなクリエイティブを設定してしまっているケースが多いのです。

この中で後者の問題を解決することが非常に重要です。「どういったクリエイティブを作れば良いかわからない」という悩みは、「誰に見せるクリエイティブなのか」をしっかりと考えることで解決できます。前項で述べたように、ターゲットが具体的に定義できていれば、それに適したクリエイティブを制作することは難しいことではありません。

プライベートDMPを活用することで、「そろそろ買い替え時期が近い人」ということがわかっていればクリエイティブは自ずと「買い替え時期ではありませんか」という訴求になりますし、「クーポン訴求に刺さる人」ということがわかっていれば「今ならクーポンでお得!」となるわけです。もちろんそれが最良のキャッチコピーかどうかは掲載して結果を分析しないとわかりませんが、少なくとも「どのようなクリエイティブを作ったら良いか」という視点における指針にはなるということです。

ターゲットと親和性の高いクリエイティブはクリック率、コンバージョン率ともに汎用的なものと比較すると上昇する可能性が高く、良し悪しの選別ができるためPDCAサイクルもまわしやすくなります。

また、明確化されたターゲットに対して、優先度の高い順に適切なクリエイティブを作っていけば、最小限のリソース・費用で最大限の効果を得ることが可能となり「リソース・費用が足りない」という問題についての一つの解決策ともなるのです。

 

以上、リターゲティング広告最適化におけるプライベートDMP活用のメリットについてお話させて頂きました。リターゲティング広告の最適化には他にも管理画面の設定や日々の運用テクニックなど様々な最適化ポイントがあります。しかし、根源的にはターゲットを明確化するというマーケティング活動の基礎とも言える部分に取り組むことが後の成果向上に大きく寄与します。これを機会に今一度、自社のターゲット像を具体的にイメージして最適化に活かして頂ければ幸いです。

また、プライベートDMPとパブリックDMPのデータをシンクさせることで実現する新たな顧客セグメントの作成や優良顧客のデータを基にしたオーディエンス拡張等、プライベートDMPを使った広告配信の最適化は様々な手法があります。これらはまた別の機会にお話いたします。

ALBERTではプライベートDMP「smarticA!DMP」の導入、活用を支援しております。専門のコンサルタントがクライアントのニーズに合わせた最適なご提案をしながら、成果改善に取り組んで参ります。

私のようにインターネット広告の企画・運用を専門的に行ってきたコンサルタントもおりますので、ぜひご相談ください。

DeepLearningがなぜうまく学習出来るのか

$
0
0

データ分析部の島田です。今回はDeepLearningがなぜうまく学習出来ているのか、についてサーベイしてみました(簡単なコード付きです)。

記事アウトライン

  • 用語の解説
  • 事前知識:NNがうまく学習できなかった理由
  • DeepLearningがうまく学習できる理由
  • 参考コード
  • まとめ

用語の解説

以降のsectionで出てくる単語についてまとめておきます。

Hessian(ヘッセ行列)

(多変数スカラー値)関数の二階偏導関数全体が作る正方行列で対称行列。
Hessianの固有値の符号をみることにより極小点や凸性の判定を行えます。
Hessianの固有値が全て正であれば、凸関数になるので大域解が求まることを保証できたりします。
次に示す鞍点では、Hessianの固有値が正負の両者を含んでいます。

鞍点

最適化対象の関数が鞍のような形になっている部分。
鞍点はどの方向に対しても平坦に近くなっており、そのため勾配がゼロに近くなり学習が進まなくります。
鞍点などの停留点に到達して学習が停滞している様はプラトーと呼ばれています。
Alec Radfordさんが作成した下のアニメーションは、DeepLearningで使用されている最適化アルゴリズムにおける鞍点での動きを示しています。
モーメンタムがないと鞍点に捕まってしまって抜け出せていない様子もわかります。
escape saddle point
出典:http://imgur.com/a/Hqolp

モーメンタム(モーメンタム法)

SGDを最適化の進行方向に加速させて、振動を抑制し学習の停滞を少なくする手法です。
物理でいうところの慣性です。
一時点前の進行方向に対してある程度の速度成分を維持していることになります。
これにより鞍点から脱する事が出来ます。

事前知識

まず、DeepLearningがうまく学習出来ている理由について見る前に、うまく学習出来ていなかった理由について言及します。
ニューラルネットワークの層を深くするとうまく学習出来なかった理由としては以下があげられます。

  • 勾配消失
  • 過学習
  • 鞍点の増加

従来問題となっていた、勾配消失1や過学習を解決したのがReluやDropoutのような手法でした。
正則化という観点では、FC層ではなくConv.層を使うことで共有されるパラメタが増え(パラメタ数自体は減る)正則化の効果がある2という見方もあります。
次のsubsectionで紹介する得居さんの資料に詳しく説明がありますので、ご興味のある方はそちらを参考にして頂きたく。
最近では、計算量や使用メモリ削減のためにFC層の使用をさけて全てConv.層で構築されたネットワーク(FCN)も出てきています。

上の問題がクリアされた事により、層を深くし、パラメタ数を多くする事が可能になったのですが、次は高次元になると鞍点が増加しやすくなりうまく学習が出来なくなってしまうのではないか、という懸念が生まれてきます。
鞍点の増加については次のsubsectionで説明します。

鞍点の増加

最適化から見たディープラーニングの考え方, 得居, OR学会誌’15には以下のような記述があります。

ヘッセ行列の負の固有値の個数を指数というが,停留点は指数によって分類できる.そこで[11] では,高次元のガウス確率場から生成された関数は,ほとんどの停留点が鞍点であることは,直感的に次のように理解できる.目的関数がランダムであり,停留点のヘッセ行列の要素が平均 0 の同じ分布に従うならば,その固有値の分布はウィグナーの半円分布に従う.これは 0 を平均・中央値とする半楕円形をした分布で,特に正負の値が半々の確率で生成される.このとき,停留点の指数が 0 となる確率は,次元を高くするにつれて指数的に減少する.

つまり、次元数が高く(パラメタ数が多く)なればなるほど鞍点が発生する確率が高くなります。
鞍点は局所解よりも性能が悪いので、鞍点を素早く抜けられる事も収束性能に関わってきます。
鞍点を抜ける原始的な方法としてはモーメンタムがあります。
今回は実験条件をある程度固定したいので、学習係数係数を一定としてmomentumSGDを使用しています。
上の図で見て頂いたようにmomentumSGDより賢い最適化関数がありますので、通常はそちらを使用されるのが良いと思います。

DeepLearningがうまく学習できる理由

理論的解析

loss関数がだいたい凸なため大域解に到達する事が可能、というのが理論的な解析で明らかになってきているようです。
Deep Learning without Poor Local Minima,Kenji Kawaguchi, NIPS’16などの最新の理論研究では、いくつかの仮定が付いてはいますが局所解が大域解になるという事が証明されています。
まだ完全に一般化されているわけではないようですが、こういった理論解析が進んできているようです。
Why does Deep Learning work?というblogにはspin glassの観点からみた場合の話がされています。
エネルギー関数の形状の図などがあり視覚的にもわかりやすいと思います。

実験的解析

今回は実験的に学習過程を解析しているQualitatively characterizing neural network optimization problems, Goodfellow+, ICLR’15を例に実験してみました。
論文中では2次元のデータで多くの情報を語ってくれるのはloss curveである、という記載がありloss curveによる可視化が行われています。
この論文がどのようなことを示したかと言うと、学習の始点と結果的な終点を線形で中割りしてその経路に沿ってloss curveを見るとなめらかな下り坂だったということです。
“学習の始点と結果的な終点を線形で中割り”というのは、具体的な操作として、パラメタの初期値と学習の終点でのパラメタを線形的に補間することに対応します。
パラメタを線形的に変化させている部分を式で書くと\theta=(1-\alpha)\theta_i+\alpha\theta_fとなります。
ここで、\theta_iは初期状態のパラメタ、\theta_fは学習を打ち切った時点でのパラメタです。
これは学習が障壁なく進んでいるとすればパラメタを線形に変化させるとlossも線形的に変化するはず、という仮定のもとlossを観測していることになります。
通常はlossを使ってweightを更新していくので、通常のtrainingとは逆の操作をしている事になります。
論文中の実験結果を見ると、ほとんどの場合大きな障壁なくlossがスムーズに減少している様子が伺えます。
つまり多数の局所解や鞍点により学習が極端に難しいと長く考えられていたが、実験してみると学習の経路以外も意外とスムーズな下りの関数であったということです。
今回は論文内容の追試も兼ねて、以下の2パターンについて実験しました(論文中ではもっと多くのバリエーションで実験が行われています)。

層数 隠れ層unit数 図中のlabel
4 100 4nn_hlunit=100
3 1000 3nn_hlunit=1000

通常のtraining時のloss-epoch curveは以下になります。
どちらの構造でも綺麗に収束しています。
loss per epoch

線形的に補間したパラメタの値を用いてlossを計算したloss-alpha curveが以下になります。
論文中ではパラメタの初期値と収束後のパラメタを線形的に変化させているのですが、今回は1epoch時点のパラメタと200epoch時点のパラメタを使用し、alphaを[0, 1]で変化させてlossを計算しています。
そのため、スタート地点でのlossの値が小さ目に出ています。
下の図を見ると、4nn_hlunit=100では一本の滑らかなスロープを障壁なしに下っていっているような様子がわかります。
最適化対象の関数が凸に近い形状になっていると判断できます。
一方、3nn_hlunit=1000では線形的にlossが落ちておらず、alpha=[0.0, 0.3]付近では最適化対象の関数が非凸な形状になっている事が予想されます。
上の図では停滞している様子は見られなかったため、非凸な形状の部分を上手く避けて(通り抜けて)学習が進んだと考えられます。
loss per alpha

参考コード

今回はMNISTデータセットを使用し、chainerのMNIST exampleのコードを変更したもので実験を行っています。

コードとしては主に、

  • MNISTデータでの訓練用script
  • 学習済みモデルのパラメタから学習過程を眺めるためのscript

の2つを作成しています。
他、MNIST用のNNモデルのコードを分離して2つのscriptで使いまわしています。
こちらも以下に貼り付けてあります。
実行環境はPython 3.5.1、Chainer 1.13.0です。

MNIST用の4層NNモデル

一般的な全結合のNNです。
Dropoutのハイパパラメタはdefault値の0.5のまま使用しています。
lossの計算をしていないのは、chainerのclassifierでwrapしているためです。
classifierではこちらで指定しなければ、softmax_cross_entropyを計算してくれるようになっています。

import chainer
import chainer.functions as F
import chainer.links as L


# Network definition
class MLP(chainer.Chain):
    def __init__(self, n_in, n_units, n_out):
        super().__init__(
            l1=L.Linear(n_in, n_units),
            l2=L.Linear(n_units, n_units),
            l3=L.Linear(n_units, n_out),
        )
        self.train = True

    def __call__(self, x):
        x.volatile = False if self.train else True

        h1 = F.dropout(F.relu(self.l1(x)), train=self.train)
        h2 = F.dropout(F.relu(self.l2(h1)), train=self.train)
        return self.l3(h2)

MNISTデータでの訓練用script

PFNさんが用意してくださっているtrain_mnist.pyをほぼそのまま使用しています。
今回はtrainerではなくmodelのsnapshotが欲しかったので、snapshot_objectでmodelのobjectを保存するようにしています。
SGDのmomentumはdefaultの0.9としています。

from easydict import EasyDict as edict
import chainer
import chainer.links as L
from chainer import training
from chainer.training import extensions
from mlp import MLP


def train(args):
    print('GPU: {}'.format(args.gpu))
    print('# unit: {}'.format(args.n_unit))
    print('# Minibatch-size: {}'.format(args.batch_size))
    print('# epoch: {}'.format(args.epoch))
    print('')

    model = L.Classifier(MLP(784, args.n_unit, 10))
    if args.gpu>=0:
        chainer.cuda.get_device(args.gpu).use()
        model.to_gpu()

    optimizer = chainer.optimizers.MomentumSGD(lr=1e-3)
    optimizer.setup(model)
    optimizer.add_hook(chainer.optimizer.WeightDecay(5e-4))

    # Load the MNIST dataset
    train, test = chainer.datasets.get_mnist()

    train_iter = chainer.iterators.SerialIterator(train, args.batch_size)
    test_iter = chainer.iterators.SerialIterator(test, args.batch_size,
                                                 repeat=False, shuffle=False)

    # Set up a trainer
    updater = training.StandardUpdater(train_iter, optimizer, device=args.gpu)
    trainer = training.Trainer(updater, (args.epoch, 'epoch'), out=args.out_dir)

    # testing
    eval_model = model.copy()
    eval_model.train=False

    snapshot_interval = (len(train)//args.batch_size, 'iteration')

    trainer.extend(extensions.Evaluator(test_iter, eval_model, device=args.gpu))
    trainer.extend(extensions.dump_graph('main/loss'))
    trainer.extend(extensions.snapshot())
    trainer.extend(extensions.snapshot_object(
                model, 'snapshot_model_iter_{.updater.iteration}'), \
                trigger=snapshot_interval)
    trainer.extend(extensions.LogReport())
    trainer.extend(extensions.PrintReport(
        ['epoch', 'main/loss', 'validation/main/loss',
         'main/accuracy', 'validation/main/accuracy']))
    trainer.extend(extensions.ProgressBar())

    if args.snapshot_file_path:
        chainer.serializers.load_npz(args.snapshot_file_path, trainer)
    trainer.run()

if __name__ == '__main__':
    n_unit = 100
    args = edict(
            {'batch_size': 100,
            'epoch': 200,
            'gpu': -1,
            'out_dir': './result_{}'.format(n_unit),
            'snapshot_file_path': '',
            'n_unit': n_unit}
        )
    train(args)

学習済みモデルのパラメタから学習過程を眺めるためのscript

classifierを使用しているので、weightを取得する際に一度predictorを経由する必要があります。
この点、少しわかりわかりづらくなっていると思います。

from easydict import EasyDict as edict
import os
from copy import deepcopy
import numpy as np
import chainer
from chainer import Link, serializers, Variable
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions
from mlp import MLP


def load_model(model, model_path, args):
    assert os.path.exists(model_path), '{} is not exist.'.format(model_path)

    serializers.load_npz(model_path, model)
    return model


# 学習済みモデルからパラメタを取得
def get_weights(classifier):
    weights = {}
    for network_obj in classifier.children():
        for child in network_obj.children():
            if isinstance(child, Link):
                weights[child.name] = {}
                for p in child.namedparams():
                    s = p[0].replace('/', '')
                    weights[child.name][s] = p[1].data
    return weights


# 学習済みモデルから取得し、線形補間したパラメタをモデルにセット
def set_weights(base_model, first_model, final_model, alpha):
    weights_first_model = get_weights(first_model)
    weights_final_model = get_weights(final_model)
    for link_name, p_first in weights_first_model.items():
        p_final = weights_final_model[link_name]
        for param_name, first_values in p_first.items():
            s_member = 'base_model.predictor.{}.{}'.format(link_name, param_name)
            setattr(eval(s_member), 'data', (1-alpha)*first_values+alpha*p_final[param_name])
    return base_model


def experiment_linearity(args):
    base_model = L.Classifier(MLP(784, args.n_unit, 10))

    first_model = load_model(deepcopy(base_model), \
                            args.first_snapshot_file_path, args)
    final_model = load_model(deepcopy(base_model), \
                            args.final_snapshot_file_path, args)
    base_model.train = False

    train, _ = chainer.datasets.get_mnist()

    linear_loss = []
    for alpha in args.alpha_sets:
        base_model = set_weights(base_model, first_model, final_model, alpha)
        if args.gpu>=0:
            chainer.cuda.get_device(args.gpu).use()
            base_model = base_model.to_gpu()

        sum_loss = 0
        train_iter = chainer.iterators.SerialIterator( \
                        train, args.batch_size, repeat=False, shuffle=False)
        for n_batch, batch in enumerate(train_iter):
            np_batch = np.asarray(batch)
            raw_x, raw_t = np.array([d for d in np_batch[:,0]], dtype='f'),\
                                        np_batch[:,1].astype(np.int32)

            if args.gpu>=0:
                x = chainer.cuda.to_gpu(raw_x, device=args.gpu)
                t = chainer.cuda.to_gpu(raw_t, device=args.gpu)
            else:
                x = raw_x
                t = raw_t
            x = Variable(x)
            t = Variable(t)

            loss = base_model(x, t)
            sum_loss += loss.data
        linear_loss.append(sum_loss/(n_batch+1))

    with open(os.path.join(args.out_dir, 'linear_loss.csv'), 'w') as f:
        s = ','.join(map(str, linear_loss))
        f.write(s)


if __name__=='__main__':
    target_n_unit = 100
    result_dir = './result_{}'.format(target_n_unit)

    n_iter_per_epoch = 600
    n_epoch  = 200
    first_snapshot = 'snapshot_model_iter_{}'.format(n_iter_per_epoch)
    final_snapshot = 'snapshot_model_iter_{}'.format(n_iter_per_epoch*n_epoch)

    args = edict(
            {'batch_size': 100,
            'epoch': 200,
            'alpha_sets': np.arange(0,1.0001,0.001),
            'gpu': -1,
            'out_dir': result_dir,
            'first_snapshot_file_path': os.path.join(result_dir, first_snapshot),
            'final_snapshot_file_path': os.path.join(result_dir, final_snapshot),
            'n_unit': target_n_unit}
        )
    experiment_linearity(args)

まとめ

無駄にパラメタ数を増やしてみると完全にlinearではなくloss関数が歪んでいることがわかりました。
loss関数が非凸であっても学習に対する大きな障壁がなくスムーズに学習が進んでいる様子も確認出来ました。
今回紹介した内容とは異なりますが、DeepLearningのモデルが画像のどこをみて答えを出したか などの研究3も進んできているので、学習過程などDeepLearningの気持ちを理解する研究が更に進めば、お客さんに対する説明力も更に高まると考えています。
今後もDeepLearningの可視化や理論的/実験的解析の研究に注目していきたいと思います。


  1. 低層のパラメタ勾配がほぼゼロになってしまう現象。活性化関数の微分の積を繰り返す事によって発生する事が多い。重みの初期値に依存する場合もありうる。 
  2. パラメタを共有すると、少ないパラメタ(表現力の低いモデル)で必要な情報を抽出しようとするため正則化効果が見込まれる。 
  3. “Why Should I Trust You?” Explaining the Predictions of Any Classifier, Ribeiro+, ’16OBJECT DETECTORS EMERGE IN DEEP SCENE CNNS, Zhou+, ‘15 
Viewing all 191 articles
Browse latest View live