修士課程を休職してスタートアップに転職しました

はじめに

半分釣りですが、概ねタイトル通りのことをしています。

一応決まり切ったわけではないのですが、今年の10月から交換留学制度を使って留学することになっていて、それまでの半年間休学してスタートアップで働いています。

勤務地は東大構内なのであまり大きく生活が変わったわけではないのですが、フルタイムワーカーとしてやり始めて一ヶ月ほど経ったのでその感想を残しておこうかと思います。

経緯

休学に至った理由を事細かに書くと長くなってしまうので1あえて詳細に書くことはしませんが、ざっくりいうと修士の期間が2年では足りないと感じたからです。

留学することが先にあったわけでもなく、元々は修士課程が短すぎるという感覚が先にあったので結果として休学→留学という流れになりましたが、半年留学してきて帰ってきてからはまたちゃんと修士課程に復帰して学生を1年やります。

修士課程が短すぎると言いつつ修士課程をやっている期間は変わらなくね?という話もあるのですが、自分としては休学と留学をしている期間も自分の勉強/研究のためになると考えているのでこの期間も含めて修士の学生をやっているという感覚です。

さて、その上でスタートアップで働き始めた経緯についてですが、休学を決めてインターンでもするかと思っていたときに誘われたから、というのが大まかな経緯です。

もともと休学期間中にはインターン(メンターがついている、研究要素が少し入っていそうなやつ)に行こうと思っていたのですが面識があった今の会社の社長からお誘いがあり、条件も業務内容もいいと感じたので半年限定という条件付きで働かせていただくことにしました。

業務について

受託のデータ分析や顧客企業の業務自動化の研究開発をしています。

自分は今二つのプロジェクトに関わっていますが、他にもいくつかプロジェクトが走っているようです。主力製品を軸にビジネスをする、というスタイルの企業ではないのでどのプロジェクトも受託中心のようです。

業務内容は「論文を読み、実装をする」に尽きていると言えます。その他にも自分のプロジェクトに関わる環境整備(GPUマシンのセットアップ・docker image作成など)もしていますが実際大したことはしていないので大きなものはやはりリサーチということになると思います。

環境について

10時 - 19時で仕事をしています。2一応当初は9時 - 18時で始めたのですが満員電車が無理だったので変えてもらいました。

オフィスは

このカフェがある建物の中にあります。いわゆるインキュベータ施設の中ということになります。

まだ創業して間もないのでオフィスの中は急ピッチで整えられている最中ですが、まさに期待していたようなスタートアップ感があってなかなか面白いです。

開発環境としては、貸与のPC(多分ある程度までは要望が通る)と手元に1070が入った小型のGPUマシンが何台か3 + GPUありのクラウド環境(必要であれば高性能なGPUありの環境も使える)といった感じです。

その他にもコーヒー飲み放題、お菓子食べ放題、技術書購入は全額補助、昼食代補助など、魅力的な職場環境作りにはかなり積極的な印象です。

1か月働いてみて

1ヶ月で判断できることはあまり多くないので今のところの印象の話になってしまいますが概ね満足しています。

項目順に書いていこうと思います。

環境・待遇面

環境に関しては上に書いたとおりですが、かなりいいと思います。その環境の良さに甘んじすぎず頑張ろうという気分になります。

待遇に関しても不満はありません。自分のスキルセットを考慮したうえでの相場感というものがあまりよく分かっていないのですが、妥当(というかやや多め)に貰っている気がします。半年限定という要素を加味するとかなり好条件だと思います。

仕事に関して

今のところうまく行っています。

1ヶ月やってみて感じたのは、受託のデータ分析プロジェクトの成否を決めるのは営業の腕とマネージャの期待値コントロールのうまさにかかっているところが大きいのではないか、ということでした。

これは何か炎上案件を目の当たりにしたとかそういうわけではなく、逆に今まで関わってきたあらゆるプロジェクトというもの(仕事であるかどうかに関わらず)と比べて最も平穏無事にやれていて、おそらくこの理由はプロジェクトスタート時の条件設定が良かったことに由来しているのではないか、という感覚があるからです。

生活に関して

大きくは変わっていませんが生活時間帯が規則正しくなりました。

4月までは週2で終日インターンをしていたのですがインターンがある日は朝6時起き4で、次の日何もない(大学に行って作業をするかもしれない)日は9時に起きるみたいな生活でしたが、今は朝7時に起きて夜1時ごろに寝る生活が毎日続いています。

忙しさに関しては修士の学生をやっている時と同じくらいでしょうか?1日8時間は仕事をしているので自由に使える時間は減るかと思っていたのですが、案外学生をやっている時の方が細かいタスクが割り込んできたりして時間に追われていた気もします。

多分ですが、学生の方がタスクをスケジュール上にどう配置するかの自由度が高い分調子に乗ってタスクを抱え込みすぎる傾向にあったのかなと思っています。

人間関係について

実際のところあまり社員が多くないので人間関係がこじれることも起きづらいですが、特に何もありません。

お互いに私生活には干渉せず、つつがなくやっていけています。

全体として

学部3年から修士1年にかけてだいぶタスクに追われたりして鬱屈とした日々を送っていた気がするのですが、ここ最近は比較的平穏な日々が送れています。

仕事に関しても毎日学びが多いと感じています。個人的に小さなチームで、1人が受け持つ仕事の範囲が広めである働き方をしてみたいと思っていたのでまさにちょうど望んでいた働き方ができています。5

一方で、リサーチという役割で働く上で自分にはアカデミックな実績がないのが痛いというのもひしひしと感じています。論文を読み、実装をするというサイクル自体はそれなりに回せているかとは思うのですが、アカデミックな実績6が伴っていないと人前に出たときなどになんとなく後ろめたさを感じてしまうのも事実です。今こうして学生に今後戻ることが確かな中でその点に関して実感を持って認識することができたのは幸運だったと考え今後の糧にしていきたいです。

留学という観点で考えると、今の職場は比較的英語を喋る機会が多い7のがよかったです。自分としては英語を話す機会は多ければ多いほどいいのでとても助かっています。

現状何よりも良かったと思っているのは、今後も学生をしばらくやっていく上で、社会人としての視点も今学ぶことができていることです。学生を終えて社会人になるというのはほぼ誰もが通る道ですが、社会人から学生へと回帰する人はそう多くありません。僕が休学してインターンをしようと思っていたことの理由のひとつには社会で何が求められ、何が大事なのかという視点を身に着け、それを研究に生かす8それが今まさにできています。

今のところは一旦休学をして働くという決断が大当たりだったというのが率直な印象です。

今後について

今後に関してはまだまだ不明瞭なことが多いです。とりあえず当面数ヶ月は今の会社で勤めつつ留学の準備をし、留学に行くところまではだいたい決まっているのですが、帰ってきた後どこに就職するか9といったことや、結局修士の研究で何を行うのか10という大きな問題は判断を先延ばしにしているだけでもあります。

その意味で今の平穏な生活はいつまでも続くものではないということを認識して緊張感を持って今後数ヶ月をいきていきたいと思います。


  1. 要望があればまた別の記事で書こうかと思います。

  2. ランチ休憩1時間含めです、念の為

  3. 手元に自由に使えるGPUマシンがいっぱいあるというのは実際のところかなりいいです。バリバリに高性能というわけでもないですがGPUメモリは8GBあるのでほどほどのモデルなら動作しますし、何より手軽に実験を行うことができます。個人的には分散GPUコンピューティングをやってみたい(が何もわかっていない)・・・

  4. 6時半だったかもしれないです。出社は9時半までにする必要がありましたが、埼京線が遅れるので9時ごろ出社するようにしていました。その上で自分は起きてから家を出るまで1時間半くらいなぜか掛かってしまうので早めに起きていました。

  5. 自分の受け持つ仕事としては、データの分析と関連技術の調査、スライド作成、必要であれば計算環境の用意や顧客との意見調整などです。

  6. 論文・発表歴などなど

  7. 英語の面接でインタビュアーをしています。

  8. 僕の研究室はテーマについて学生が各人で勝手に決めることになっています。研究としてやっていけるなら何をやってもいいので皆別々の研究をやっていこうとしますが、完全ソロプレイとも言えるわけで正直なところ自分には自分で自由にテーマを選びやり通すというのは辛いと思っていました。その上で一人でも研究テーマを選びやり通す力を身に着けるというのが学生期間を一年伸ばした大きな理由です。

  9. 今の会社もとてもいいですが、他の企業も見てみたいです。一点心配なのは帰ってくる頃の就活事情がどうなっているのか全くわからないことです。通年採用をやっているような企業を見ようとは思っていますが、そもそも経団連企業含めて通年採用になるという話もあるのでよくわからないです。

  10. 修士に来たからには研究をしっかりとやって成果を出したいという気分はあるのですが今のところ修士1年を使って何も成していない(というかちょっと手をつけてはすぐ目移りしてしまうみたいな感じで上手くいっていない)です。自分の好きなこと・やりたいことをやるといいよと指導教員の先生からは言われるのですが、それを一人でやり通すというのはとんでもなく難しいことな気がしてなりません。最近は博士課程に在籍していたり、博士持ちだったりする人と話す機会も多いですが、博士の方々はそういった訓練を積んできたわけで(と勝手に思っています)、勉強させてもらいたいと思っています。

GCI優秀者研修旅行【ベルリン編】

はじめに

この記事はGCI優秀者研修旅行のベルリン編です。

GCI優秀者研修旅行とはなんぞや?という方はこちらをご覧ください。

1日目 テーゲル空港-夕食

前回はパリから飛行機でベルリンに向かったという話で終わっていましたが、パリからベルリンのテーゲル空港までは意外と時間がかかりついた頃には夕焼けが見える頃でした。よくヨーロッパは狭いという話を聞くことがあるのですが、ベルリンはドイツの中でも東寄りにあるので、距離でいうと1000kmほどあるそうです。

日本でいうと本州の約半分を縦に移動する感覚でしょうか?個人的には鉄道でフランスの田舎やドイツの田舎を眺めながら行きたかったのですがここまで長距離だと難しそうですね。

テーゲル空港についてからは、3、4組に別れてUberでホテルに向かいました。この旅行、今までの記事には書いていなかったのですが最初から最後までUberにかなりの頻度でお世話になりました。Uberの運転手も国によって色があるという印象で、フランスは賑やかな方、ドイツは静かな方が多かったです。僕の組ではなかったのですが、ほかの組だと運転手さんから少し遠回りするルートを通る代わりに少しお金を弾めば良いところを見せてもらえる、といったオファーを受けていたところもあったらしく、戦勝記念塔を見ているところもありました。

f:id:koukyo1213:20181104172044j:plain
戦勝記念塔

その後ホテルでしばらく待ってから、夕飯に行きました。夕飯は本場のドイツ料理店が食べられるところだったのですが、ドイツではじゃがいもがメインディッシュになり得ることを知らずに料理を頼みすぎて酷い目に会いました。その後松尾先生の提案で近場のラーメン屋に行きましたが、店員さんに日本人が多く遠い異国の地で店員さんと日本語と会話することができました。

f:id:koukyo1213:20181104175614j:plainf:id:koukyo1213:20181104175618j:plain

2日目- Merantix訪問

2日目は朝からAI系スタートアップ、Merantixを訪問しました。Merantixはベルリン市内にある社員数〜50名くらいのスタートアップで、メインの事業として自動運転の研究開発用シミュレータの開発を行っており、そのほかにも医療系画像からの自動診断などにも取り組んでいるようです。後から知った話ですが、Merantix自体はインキュベータとなっておりその中にいくつか事業を抱えていて、ある程度大きくなった時点で子会社化する、という経営手法を取っているとのことでした。

www.merantix.com


MerantixではCEOの方から企業における研究開発と大学における研究開発の違いにフィーチャした話を伺いました。CEOのRasmus博士は、ETHZ(チューリッヒ工科大)で博士を取っており元々研究者としてもやっていた方でしたが、Merantixを創業して現在は経営をやっているとのことでした。


話の内容自体はこちらのMediumの記事にまとまっていますので興味のある方は是非読んでみてください。

medium.com

その後は少し体調がすぐれなかったので一旦離脱したのですが他の人たちはまた別のインキュベータ施設を見に行っていたようです。

その夜は、オリンピアスタジアムでベルリン対ミュンヘンのサッカーの試合観戦をしました。

オリンピアスタジアムはヒトラー時代に建てられたということですが、かなり大きく立派な建物でした。

試合開始の1時間くらい前には現地入りしていたと思うのですが、すでに駅から競技場に向かう道には屋台が立ち並びザワークラフトやらカリーブルストやらビールやらを売っていて長蛇の列ができていました。

しばらく待ってから開場されたので入ったのですが比較的厳し目の荷物検査などがありテロを警戒しているのかな?と思いました。

自分たちはビールを中で買ったのですが1番大きいサイズは1Lでプラスチックの小さいバケツのようなものでドイツ人は飲んでいました。

スタジアムの中は上の写真のような感じでした。とにかくデカいの一言に尽きます。自分たちはミュンヘン側の観客席でしたが、ベルリン開催のためにミュンヘン側にもベルリン側サポーターの伏兵が紛れ込んでいました。


自分は普段特段にサッカーを見ないので詳しいことはわかりませんが、どちらのチームもとてつもなくハイレベルなのは分かりました。

その上でミュンヘン側が終始押し気味ではあったのですが、ベルリンの堅い守りを崩せずにしばらく攻防が繰り広げられちょっとした隙にベルリン側にボールが渡ったときに速攻でベルリンが点を取る、というパターンで結局ベルリンが勝ちました。

ベルリンのホームなこともあってベルリン側サポーターの治安があまり良くなく、ミュンヘン側が有利になったりすると盛大なブーイングが飛ぶなどサポーターを見ているだけでも楽しめました。

帰りの電車に乗るときに興奮しすぎたファン同士が乱闘をはじめかけて目の前で逮捕者が出ていたのが結構印象的でした。それだけ本場は加熱するということでしょうか?

3日目 -ベルリン観光-

3日目は土曜だったのもあって公式に訪問できるところがなかったので各人思い思いに過ごしていました。

僕は個人的に買い物をしたり観光をしたりとやっていました。

最後の日の食事はレストランに行ったのですが、そこで出されたワインが衝撃的な大きさでした。

ちなみに1番大きいものは5Lあるらしいです。幾ら幾らで買わないか?みたいに聞かれたのですが飲めなさそうだったので丁重にお断りしました。

まとめ

最後は日本に帰ってきたんですが、不思議なことに記憶が全くありません。

6ヶ月あいてのドイツ編投稿でしたが、やはりドイツは街並みの綺麗さや人の落ち着きが日本に近いものを感じました。

個人的にはイギリスの街並みが好きですが、ご飯の美味しさでいうとドイツが1番でした。

この研修旅行はスタートアップや、大学などを見てAI技術の社会実装をしっかりと目に焼き付けるという意味合いがあったのですが、プログラミングスクール(Ecole 42)やOECDなどにも行ったりと多面的な捉え方をできるように考えられていたと思います。

自分が将来どのような道に進むかはわかりませんが、この経験がなにがしかの影響を判断に与えそうだと感じました(事実、この後僕はMerantixの長期インターンに応募しています。残念ながら落ちてしまったのでその話はまた後日)

競プロ用オレオレ環境ジェネレータを作った

はじめに

この間、競プロ始めました。

去年の10月くらいから興味が出てきていたんですが(主にKaggleの人たちが競プロをやっているのを見て)なかなか腰が重くしばらく様子見していました。

最近、ちょっと暇ができたタイミングでちょうどAtCoderでABC120をやっていたので始めてしまった、というわけなんですがしばらくやってみて忙しくて手が回らさそうならやめてしまうかもしれません(と予防線だけ張っておく)。

とまあ、一回しかまだやっていないわけですが一回やってみて思ったのはAtCoderのサイトのエディタでやるのはちょっときついと(それはそう)。

そこで、手元の環境で競プロ用のコードをかけるように環境構築していたんですが、今回はその記録になります。

コード類は、

github.com

においてあります。

環境構築の詳細

実行環境

Docker使いました。

Dockerfileはこんな感じ。

FROM ubuntu:16.04
RUN apt-get update &&\
    apt-get install -yq software-properties-common

RUN add-apt-repository universe &&\
    apt-get update &&\
    apt-get install -yq g++ libboost-all-dev cmake make gdb &&\
    apt-get clean &&\
    rm -rf /var/lib/apt/lists/*
RUN mkdir /app
WORKDIR /app

CMD ["/bin/bash"]

出来るだけ実際の実行環境と同じような環境を作りたかったのでDockerを使うことにしたわけですが、やっぱりDocker便利ですね。

docker runコマンドも手打ちするのが面倒なのでmakeを使います。

Makefile(抜粋)はこんな感じ。

IMAGE := competitive:1.0

build: Dockerfile
    docker build -t ${IMAGE} .

env:
    docker run -v `pwd`:/app -it ${IMAGE} /bin/bash

環境ジェネレータとは

コンテストごとに、プログラムを書くディレクトリ構成を複製したかったので'環境ジェネレータ'です。

treeコマンドでジェネレートしたディレクトリ構成をみてみると

ABC119/
├── Makefile
├── README.md
├── build
├── cases
│   ├── a
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   ├── b
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   ├── c
│   │   ├── 0
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   ├── 1
│   │   │   ├── answer.txt
│   │   │   └── input.txt
│   │   └── 2
│   │       ├── answer.txt
│   │       └── input.txt
│   └── d
│       ├── 0
│       │   ├── answer.txt
│       │   └── input.txt
│       ├── 1
│       │   ├── answer.txt
│       │   └── input.txt
│       └── 2
├── jobs.sh
└── src
    ├── a.cpp
    ├── b.cpp
    ├── c.cpp
    └── d.cpp

19 directories, 25 files

といった感じになります。

srcが各問題に対応したC++ソースファイル(当然ながら解法まではジェネレートしてくれません)が入り、buildはコンパイル済みオブジェクトが入ります。

casesには、それぞれの問題のテストケースが入ります(この部分はスクレピングで取ってきています)。

README.mdには問題の説明が入り、実行制御用のMakefileとjobs.shはテンプレートをコピーしてきています。

Makefileのテンプレート(抜粋)は

CXX := g++
ACPP := src/a.cpp

CFLAGS := -O2 -Wall -std=c++11 -DDEBUG
A := build/a.out

prob-a: ${A}
    ./jobs.sh ${A} a


${A}: ${ACPP}
    ${CXX} ${CFLAGS} ${ACPP} -o ${A}

みたいな感じになっています。make prob-aコンパイルから答え合わせまでやってくれます。

jobs.shの中身は

for f in 0 1 2; do \
  ./$1 < cases/$2/$f/input.txt > cases/$2/$f/output.txt; \
  diff cases/$2/$f/output.txt cases/$2/$f/answer.txt; \
done

となっています。input.txtの中身を標準入力で読んで、output.txtに標準出力で吐き、diffでanswer.txtとの差分を取って答え合わせをするという流れが書いてあります。

ジェネレート部分

ファイル生成はシェルで行います。

if [ ! -d $1 ]; then \
  mkdir $1; \
  cp Makefile.template $1/Makefile; \
  cp jobs.sh.template $1/jobs.sh; \
  mkdir $1/src $1/build $1/cases; \
  touch $1/build/.keep $1/cases/.keep; \
  for f in a.cpp b.cpp c.cpp d.cpp; do \
    touch $1/src/$f; \
  done; \
fi

あらかじめ書いておいたテンプレートをコピーして、所定の手続きでディレクトリやらファイルやらを作るだけです。

README.mdへの問題の書き出しとテストケースの取得はPythonで書いていますが、ちょっと長くなるので割愛します。

コンテストごとにそんなに問題の書き方は変わらないよね?という前提で処理しています。

まとめ

こんなことばっかりやっているので積みタスクが増えていくわけですが、競プロもできたら頑張りたいですね。

オレシカナイトデータコンペティションで優勝しました!

はじめに

サイバーエージェント主催の『オレシカナイト』データ分析コンペティションに参加し、優勝しました!

cyberagent.connpass.com

実際の分析時間はトータルで6時間程度でしたので初期の戦略で大きく結果が左右されるコンペでしたが、Kaggleエキスパートも数人参加する中で 非常にいい経験となりました。本記事では、運営の確認の上で、問題のない範囲でコンペの概要や解法を紹介します。

なお、今回のコンペで二位に輝いたupuraさんもこのコンペについて記事を書いております。

upura.hatenablog.com

コンペの概要

オレシカナイトについて

オレシカナイトとは、AbemaTVなどを運営するサイバーエージェントのアドテクノロジー開発を行う部門主催の技術者向けの勉強会だそうです。機械学習に限らずアドテクに関連する様々なテクノロジーについての講演、LTなどが過去にも開催されているようで今回のコンペはそのスピンオフとのことでした。

ちなみにこの「オレシカナイト」の由来はコンペ開催前から気になっていたのですが、ゲーム「俺の屍を越えてゆけ」が元になっているようで「自分や他の人の失敗を糧にみんなに成長してもらえるよう共有する」ということが目的となっているとのことでした。

コンペについて

今回のコンペは実は全く同じデータ・問題設定の学生向けイベントが以前に開催されていたとのことで、その時の上位3名のスコアがリーダーボードに表示されているなど若干の緊張感を煽られるコンペでした。上位3名のスコアはかなり高く正直抜くのにかなり苦労しました。運営の方も、学生のレベルが上がっていると言っており、同世代の人間がデータ分析という世界でも活躍し始めていることを実感させられました。

f:id:koukyo1213:20190128233054p:plain
最終的なリーダーボードの様子。上位3-5位が前回の1-3位でした。背景が花火なのは一位になった時に表示される特別仕様です笑

本コンペの参加者は15名で、うち自分を含めた3人が学生でした。
本コンペではAbemaTVのデータを用いて、そこに配信される広告が実際にどの程度閲覧されるかを予測しました。
学習データは2ヶ月分与えられており、それを用いて直後の1週間分のデータに対して予測を行うというもので、時系列性を考慮することが一つの鍵となっていることが特徴的でした。

個人的には時系列性を考慮するまでには至らず、一般的なテーブルデータに対して行われるようなモデル作成を最後まで行なっていましたが、後から考えると時系列性を考慮した特徴を作れていればさらにいい結果が得られていたかもしれません。

データの内容としては、

  • チャンネル内での広告の配信位置などを示す広告枠に関する情報
  • CMの開始時刻などの配信タイミングに関する情報

など、時間に関する情報とターゲットを除いてカテゴリ変数のみで構成されたもので、カテゴリ変数の扱い方が問われたコンペティションでもありました。また、ほとんどの情報は匿名化、符号化されている点も特徴的でした。

データ数はtrainが543万レコード、testが59万レコードで全体としては300MBほどでしたが、全てがカテゴリ変数なため密行列表現でone-hot化すると自分の16GBメモリPCがメモリエラーを起こすくらいの大きさに膨れ上がります。そのためか、メモリ不足との戦いで苦慮している方も散見されました。

なお、このデータは実際のデータを間引いたりして、(おそらく)各カテゴリの間の偏りをならすなどの整形がされており実際の業務で触る際はさらに様々な困難があることが伺えるデータでした。普段のデータハンドリングはSQL(BigQuery)を用いているとのことで巨大データとの格闘というアドテクらしい話も垣間みえていました。

精度評価は広告配信という分野の性質から下記のような独特なものが用いられていました。

  • ユーザをあるまとまりで分類し
  • まとまりの中での予測閲覧数と実際の閲覧数との比をとり
  • 比が0.9より大きく1.1未満であった時にそのまとまりの得点がえられるとして
  • 全てのまとまりに対する、得点が得られたまとまりの比を最終的なスコアとする

という指標でした。この指標が使われる理由として、予測値が実際の値よりも大きすぎても小さすぎてもいけないというビジネス上の制約があるというものが挙げられていました。すなわち、

  • 大きすぎた場合、本来の料金より低い料金で広告枠を提供してしまったことになり割引に当たる、またその広告枠を他の広告に提供できたはずなので機会損失と捉えることもできる。
  • 小さすぎた場合、一定の閲覧数以上を提供するという契約に違反したことになり違約金が生じる

という不都合があるとのことで、閲覧数予測はビジネス的にも大きな価値を持ったタスクであることが伺えました。

このスコアを大きくすることが今回のコンペの目的で、最終的な優勝もこのスコアを元に決められていましたが、参考値としてMSEも計算されてリーダーボードに表示されるようになっており、これを一番低くした3名にも商品があるということでした。僕は欲張りなのでスコアの最大化とMSEの最小化を同時に行なって両方で同時優勝を目指していましたが、このスコアをあげるとしばしばMSEも上がるといった逆転現象が起きることも今回のコンペの特徴で、参加者を大いに悩ませた一因でもありました。

また、リーダーボードのスコアはそのまま最終的なスコアに用いられるものでしたのでLBプロービングなどが行えるというやや不健全なものでしたが、ルールの範囲内でしたので有効活用させていただきました。

自分の取り組み

解法

自分の取り組みはかなりオーソドックスなテーブルデータに対する手順を踏んでいたかと思っています。

特徴は、

  • カテゴリカルな特徴はそのまま用い、時間に関する情報のみ曜日・時間(hour/min)のカテゴリ特徴に直したもの
  • それらカテゴリ特徴をTarget Encodingしたもの

の二種類を用意しました。upuraさんの解法にあるような時間の循環性を意識したエンコード手法には思い至らなかったため時間に関しては非常にシンプルにカテゴリ化しています。

やや特徴的な点があるとすれば、Target Encodingではmeanだけではなくmax、std、median、skewも計算していたことと、これは主に時間的な制約が厳しくて手が回らなかったためですがTarget Encoding時に弱リークが発生しないようにCVで計算するなどの工夫は行なっていないことが挙げられます。

また、この二種類の計算方法を、

  • 与えられた2ヵ月分のデータ全てに対して計算したもの
  • テストデータに近い1ヵ月分のデータのみに対して計算したもの

の二種類を用意しています。すなわち2×2の計4種類のデータセットを使っています。

これらのデータをLightGBMに5fold CVで流し、出てきた結果をブレンデッドアンサンブルしたものが最終的な提出ファイルとなりました。LightGBMのハイパラチューニングなどは手作業で少し行なったのみで概ね、Kaggleのメルカリコンペに参加した際に用いていた値を流用しました。

結果として、最終的には性質の異なるデータセットを複数用意できたことが勝因と考えています。

f:id:koukyo1213:20190129202146j:plain

戦略

upuraさんは実応用の観点も持ってシングルモデルにこだわっていらっしゃったとのことでしたが、自分はのっけからアンサンブルする気満々でしたのでいかに多種の性質の違うモデルを作れるかどうか、という一点にかけた戦略をとりました。このためには実験量がある程度ものをいうと考えていたため、自分のローカルPC(MacBook Pro) + Google Colaboratory4枚(自分の個人のアカウントと大学での個人アカウントでそれぞれCPUとGPUの2枚ずつ)の5環境での並列実験を行いました。

具体的には、ローカルでは一番勝率が高そうな取り組みを計算しつつ、ColaboratoryではMLPや線形モデル、CatBoostなどJust Ideaだけどモデルの多様性に寄与しそうなアイデアを試していくということを行いました。Colaboratoryで計算した結果で最終的なモデルに寄与したものは実はなかったのですが、時間がかかりそうなアイデアも他の環境に投げれば手元で他のことができるという安心感から試したいことを思いっきり試せたのも優勝の遠因かと思います。

なお最終的にはLightGBM一本に絞ったものの一番最初は非常に素朴にone-hot表現+Ridge回帰からはじめ、多項式ベースの手法もいくつか試していましたがどれもうまくいきませんでした。これは、ほとんどすべてがカテゴリ特徴だったからと考えられますが、単純な線形モデルでもある程度の精度を出せるだろうと考えていたので結構面食らいました。

所感

自分はこうしたオフラインコンペに参加するのは初めてでしたが、新たな楽しみを覚えてしまいました。Kaggleとはまた少し違った能力が問われるため新鮮な気分で臨むことができたのですが、ちょうど手の速さを強みにしていきたいと考えていたところでしたので自分の現状を評価する上でも非常にいい機会となりました。

個人的には、今回のように限られた時間の中で競いながら精度を高め合うというのはかなりうまく自分にハマっていると感じたので、今後もKaggleなどのコンペでも同じチームの人と1日かけて競い合いながら精度を向上させるようなハッカソン的な枠組みを取り入れていけたらと感じています。

運営の方々のサポートも素晴らしく、1日を快適に過ごすことができました。

f:id:koukyo1213:20190129220556j:plain
お昼の高級弁当

f:id:koukyo1213:20190129220637j:plain
差し入れのシュークリーム

f:id:koukyo1213:20190129220722j:plain
懇親会のお寿司

個人的にはSlackのtimesチャンネルで騒ぎながら競技に臨めたのが非常に楽しかったです。このような場を提供してくださった運営の皆様方、そして競技を盛り上げてくださった参加者の皆様、ありがとうございました。

Kaggle APIとSlack APIとGoogle Apps Scriptで新規カーネル投稿を監視する

はじめに

最近Kaggleにハマっています。今はQuoraのコンペをやっているところです。

NNなんも分からんけど楽しい!

Kaggle Advent Calendarのu++さんの記事を読んで自分も新規カーネルの投稿を監視する仕組みを作りたくなったのですが、u++さんの方法は自分のMacが通信できる状態にないといけないため、一番カーネルを読む時間が取りやすい電車内などで新規カーネルの投稿を監視できなくなってしまいます。

upura.hatenablog.com

家のデスクトップでcronで叩いてもよかったのですがそのためだけにデスクトップを立ち上げておくのも少し気がひけるので今回は、スクリプトの定期実行が無料で簡単にできるGoogle Apps Sciptを用いてシステムを構築してみました。

コードは下記のリポジトリにて公開しています。

github.com

全体像

システムの全体像は

上の図のようになっています。全体の流れは以下の通りです。

  1. まずGoogle Apps ScriptからKaggle APIのエンドポイントを叩き、現在あるカーネルのリストを入手します。
  2. 次にこのリストをGoogle Spreadsheet内に作った簡易データベースと照合し、新しいカーネルがないかを調べます。
  3. 新しいカーネルが見つかった場合は時系列昇順に並べてシートの行末に追記していきます。また、新しいカーネルについてはSlack APIと連携して自分用slackにも通知を飛ばします。

Slack通知の様子

Kaggle APIカーネルのリストを入手するとJSONの配列の形で送られてきます。1つ1つのカーネルに関してはかなり多くの情報が用意されているのですが(タイトル、作成者、GPU onかどうか、などなど)ほとんどがnullになっているので、実際に使えるのは、タイトル、作成者、オリジンからのパス、更新日時くらいかと思います。

これらを少し整形して送っている様子が上の通知の画像です。

使ってみて

やはり一覧表示されていると情報量に圧倒されてしまうので、変化があったときにだけ見るのは非常に役立っています。これからもどんどん利用していきたいと思います。

技術的な話

Kaggle APIについて

今回は公式が出しているKaggle APIクライアントを使えないので、APIクライアントのソースを読んでエンドポイントの叩き方を調べる必要がありました。

github.com

このAPIクライアントはSwaggerのコードジェネレート機能を用いて構築されているらしく読んでいてなかなか勉強にもなりました。

まず、APIのエンドポイントについてですが、KaggleSwagger.ymlの中に記述されています。

また認証に関しては、複数のファイルに分散していますが、kaggle.jsonの情報を用いてBasic認証を行なっているようです。この際のエンコーディングなどはconfiguration.pyで行なっています。

挙動を定義するために--competitionなどで与えるパラメータはURLの中にクエリパラメータとして入れることができるようになっています。

Spreadsheetによる簡易DBについて

Google Apps Scriptの強みはやはりGoogleのサービスを操作できることかと思います。

今回はKaggle APIで作成日順にソートとして得られたカーネルの情報を、初回実行時にスプレッドシートに順に入れておき、次回以降Kaggle APIを叩いて得られたときはJSON配列とスプレッドシートの中身の配列の長さを用いて新規カーネルがあるかどうかを判定しています。

また、このように順番をソートしておくことで新しいカーネルがあった場合もソートされている上位n個のカーネルが新規である、と判定することができ、過去に投稿があったものかどうかの判定が簡素になっています。

Slack連携

Slackへの通知はGASライブラリSlackAppを用いています。

このライブラリを使うことで、SlackのAPIトークンを渡すだけでSlack通知ができるようになります。

テスト

今回は小規模なのであまり必要はないのですが今後拡張することも考えてGSUnitを用いてテストを行っています。

クレデンシャルの扱いについて

Google Apps Scriptでは、クレデンシャルなどのハードコーディングを防ぐ仕組みとしてPropertiesServiceというものがあります。これを用いることでキー:バリューの形で保存したプロパティを呼び出すことができます。

今回はこれを用いてSlackのAPIトークン、Kaggle APIBasic認証トークン、そしてspreadsheetのIDを保存しています。

なお、この情報はWebUIなどでみたときには普通に見えてしまうため、共用のアカウントなどで行うことはオススメしません。

深層カルマンフィルタについて(1)

この記事は東京大学航空宇宙工学科/専攻 Advent Calendar2018の七日目の記事として書かれています。

adventar.org

昨年はカルマンフィルタについての記事を書きましたが、今年は引き続きカルマンフィルタについて書いていきます。といっても一年前と同じことを書いても芸がないので近年流行りのディープラーニングを用いた「深層カルマンフィルタ」[1] に関するお話です。猫も杓子もディープラーニングな時代ですから航空宇宙工学専攻でもきっとディープラーニングが花開いているんですよ、多分。

はじめに

去年に引き続きAdvent Calendarの記事がやって来たわけで、昨年のカルマンフィルタの記事が比較的好評だったのもあって、今年も真面目に専門的な話題を書いていこうと思ったわけですが、ふとすると専門無し弱者になりがちな弊学科にあって研究テーマに悩みに悩んで大した研究もしてこなかった僕としては専門的な話を他の優秀な学科民のようにはできないような気がしてしまったりしまわなかったりします。

仕方ないのであえて言うなら専門と言えないでもない状態推定のお話をしようと思ったわけですが、せっかくなので流行りに乗ってディープラーニングでもしてみむとするかと思い、深層カルマンフィルタの記事を書くことにしました。そこで早速深層カルマンフィルタについて調べ始めたのが、この記事が公開される一週間前とかになります。

なにが言いたいかと言うと、ドシロートが雰囲気で書いている記事なので大目にみてくださいよろしくお願いします。

事前知識

カルマンフィルタの復習

カルマンフィルタは、「モデルがわかっている線形システムに関して、観測誤差による影響と状態遷移誤差による影響を上手に扱うことにより精度よく状態推定を行う関数」でした。去年と微妙に文言が変わっていますがお気になさらず。

ここでキモになってくるのが、「線形モデルが分かっている」ことと「誤差に関する情報は事前に分かっている」ことが前提にある点です。現実的には線形モデルではうまくいかない場合は数多くありますし、そもそも数理モデルが存在しなようなシステムというのも一般には存在します。また、観測誤差、状態遷移誤差も実際のところ事前に確率分布が分かっているということは多くはないかと思います。

実際に式を書いてみると、時刻tの状態変数を\mathbf{x}_t、観測変数\mathbf{y}_t、制御入力を\mathbf{u}_tとして

$$ \begin{align} \mathbf{x}_{t+1} &= \mathbf{A}\mathbf{x}_t + \mathbf{B}\mathbf{u}_t + \mathbf{w}_t \\ \mathbf{y}_{t} &= \mathbf{C}\mathbf{x}_t + \mathbf{v}_t \\ \mathbf{w}_t &\sim \mathcal{N}(\mathbf{0},\mathbf{Q}) \\ \mathbf{v}_t &\sim \mathcal{N}(\mathbf{0}, \mathbf{R}) \end{align} $$

と表されるような系が対象となるわけですがこの\mathbf{A}\mathbf{B}\mathbf{C}がわからないような系というのも一般には数多く存在すると考えられます。例をあげるとすれば、

  1. ある講義を受けている受講者の学力の推移
  2. 治療を受ける患者の体調の推移

などが挙げられます1。2.は[1]で論文著者たちが利用法として挙げている例ですね。1.の例を状態推定という枠組みで見るとすれば、学力が状態変数\mathbf{x}_tに対応し観測変数\mathbf{y}_tとしてはテストのスコアが対応するでしょう。制御入力\mathbf{u}_tは講義になります。同様に、2.の例では状態変数が体調、観測変数が検査スコア、制御入力が治療になるのでしょうか?

いずれにせよ数理モデルとしての表現は容易では無いように思える例ですがこのような例に関して推定を行いたいという状況もありうるでしょう。

データからの学習

話は変わって、機械学習にうつります。機械学習、近年大流行りですよね。

先に挙げた数理モデルを論理的に構築する2のが困難な状況においても、データが豊富であればそのデータを使ってなんらかのモデルを構築することはできます。これを可能にしているのが機械学習の技術です。ここで「数理モデル」ではなく「モデル」というぼやかした表現にしているのは必ずしも数式的な表現ができる形になるとは限らないということを表現する3意味合いを持たせています。

最も単純な例として線形重回帰モデルを考えてみます。線形重回帰モデルは、説明変数と目的変数の間に線形な関係が存在するという仮定を置いたモデルになります。つまり、目的変数は説明変数の線形結合(アフィン結合)

$$ \begin{equation} y=\sum_{i=0}^N w_i x_i +b \end{equation} $$

と表されるという仮定を置いています。このとき各説明変数にかけられた係数w_iをデータを最もよく説明するように調節します。この場合であれば、ある説明変数\mathbf{x}_jに対応した目的変数y_jがあるとしてこの説明変数に対してモデルが出力する値を\hat{y}_{j}とするとモデルの予測値と目的変数の値から計算される二乗誤差\sum_{j=1}^M (y_j - \hat{y}_j)^2を最小化するように係数を調節する、などが妥当です。

この例は教師あり学習の例ですが、基本的に教師あり学習はどの手法であっても根っこの部分では同じ思想に基づいていると言えます。すなわち、

  • データには説明変数と目的変数のセットがあります。
  • モデルは説明変数と目的変数の関係を表現するようにフィッティングさせます。
  • フィッティングの作業はモデルの予測値と目的変数の値を近づける4ようにします。

だいぶざっくりした話ですが、この考え方を使えば適切なモデルとデータを用いることで先に挙げた状態方程式・観測方程式が数理モデルとして与えられない場合にも対応できそうな気がします。

とまあ、ここまで話してきたわけですが実のところデータが豊富にあってただ単に説明変数と目的変数の関係が抽出したいのならばカルマンフィルタの良さというのは使うことなく終わるので機械学習のモデルを組めば良いわけです。もう一度カルマンフィルタとは何だったのかを振り返ってみると

「モデルがわかっている線形システムに関して、観測誤差による影響と状態遷移誤差による影響を上手に扱うことにより精度よく状態推定を行う関数」

でした。ポイントとなるのは後半の「観測誤差による影響と状態遷移誤差による影響を上手に扱うことにより精度よく状態推定を行う」の部分です。精度よく状態推定を行うと言っていますが、これは誤差の伝播を評価して事後確率分布の誤差分散が小さくなるようにしていくと言い換えることもできます。

ニューラルネットワークについて

話はニューラルネットワークに飛びます。

ニューラルネットワーク機械学習のモデルの1つになります。さまざまなアーキテクチャやバリエーションがあるためその全てを紹介することは出来ませんが、基本となる部分は同じですのでその部分についてまず紹介します。

ニューラルネットワークを構成するのは人工ニューロンと呼ばれる素子です。ニューロンは複数の入力を受け取り1つの出力を吐き出す関数と見ることが出来ます。人工ニューロン内部では次のような演算を行っています。

  1. 入力の重み付き線形和をとる。
  2. 重み付き線形和の値を非線形な活性化関数に通す。
  3. 活性化関数の出力値を人工ニューロンの出力値とする。

f:id:koukyo1213:20181206131954p:plain
人工ニューロン

ポイントとなるのは重み付き線形和と、活性化関数です。

まず重み付き線形和ですがニューラルネットワークをデータに対してフィットさせるときに調節するのはこの重み付き線形和の各入力に対する重みの値です。この値を適切に調節することで説明変数と目的変数を結ぶ関数を表現できるようにします。活性化関数はの役割は、ネットワークの出力に非線形性を加えることです。ここが線形関数だったり重み付き線形和をそのまま出力していたりするとネットワークは全体として線形になってしまいます。

非線形関数としてよく使われるのはシグモイド関数5

$$ \begin{equation} f(x) = \frac{1}{1 + \exp(-x)} \end{equation} $$

ReLU関数

$$ f(x) = \max (0, x) $$

tanh関数

$$ f(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}} $$

などがありますがいずれも微分値が存在し6、容易に計算できるものです。これらを使って1つの人工ニューロンを関数として表現すると

$$ \begin{equation} f(x) = h\left(\sum_{i=0}^{N} w_i x_i + b\right) \end{equation} $$

となります。h(\cdot)が活性化関数です。

ニューラルネットワークはこの人工ニューロンを多数まとめた層をいくつも積み重ねるようにして構成し、層から層へ値を伝播して変換を施していくようなモデルです。

f:id:koukyo1213:20181206162355g:plain
人工ニューラルネットワーク

このとき、一番最初の層は説明変数の入力を受け取るため入力層と呼ばれ、最後の層は目的変数の予測値を出力するため出力層と呼ばれます。間の層は中間層、隠れ層などと呼ばれますがこの中間層の存在がニューラルネットワークが複雑な関数に対しても高い近似性能を誇る理由となっています。

深層ニューラルネットワークまたの名をディープラーニングはこの隠れ層が一般に2~3以上になった深いネットワーク構造に対する呼称ですが実際のところ明確な定義はありません。

ニューラルネットワークの学習

ニューラルネットワーク教師あり学習アルゴリズムの1つ7なので、学習データに対するフィッティングの作業があり、その作業が学習と呼ばれるという点は機械学習一般の話と変わりません。この場合もやはり、予測値と目的変数値から計算される損失関数を最小化することでフィッティングを行います。この際には微分が重要となってきます。

人工ニューロンを1つとってみたとき、出力値に対する重みによる微分というものを計算することを考えます。いま、人工ニューロンの入力と出力の計算において

$$ \begin{align} y &= \sum_{i=0}^{N} w_i x_i + b \\ f(\mathbf{x}) &= h(y) \end{align} $$

のように書くことにすると、その出力に対する重み \mathbf{w}による微分とは

$$ \begin{align} \frac{\partial f}{\partial \mathbf{w}} = \frac{\partial h}{\partial y} \cdot \frac{\partial y}{\partial \mathbf{w}} \end{align} $$

となります。この右辺第一項は活性化関数に依存しますが、入力から出力へと値を伝播するとき(forwardといいます)に計算した値を保存しておくことで効率的に計算することができます。

また右辺第二項は入力されたベクトル\mathbf{x}そのものになります。

さて、このように人工ニューロンは出力に対する重みの微分が簡単に求められるのですが、これは多層に重なっていても同じです。同様にして各演算についての偏微分の積になりますので、出力層での出力値に対する第一層の重みによる微分みたいなものも簡単に求められます。

ニューラルネットワークの学習においては、損失関数に対する各重みによる微分というものを計算します。この重みによる微分の意味を考えるとその重みを少しずらした時にどれくらい損失関数の値が変わるかを表現していると言えますので、損失関数の値が小さくなるように微分値を用いて各重みを更新してやれば損失関数を最小化することができます。

もちろん、損失関数は山あり谷ありなことがほとんどですので、単純に損失関数値が下がる方向にだけ調節しても大域的な最適解というものに辿り着かないこともあります。そこのあたりの最適化に関しては今回は割愛させていただきます。

潜在変数モデル

またまた、話は飛びます。カルマンフィルタのような、観測変数の背後に非観測の状態変数の存在を仮定するモデルを潜在変数モデルと言います。潜在変数モデルの考え方は機械学習の世界では非常によくみられます。これは、潜在変数モデルにおける潜在変数の考え方が機械学習の適用先となる現実世界の問題設定によくある階層構造やデータの分布構造をよく表現できているからだと考えられます。

カルマンフィルタは潜在変数が時系列性を持つ隠れマルコフモデルですが、それ以外にも単語群の出現の背後にトピックと呼ばれる潜在変数の存在を仮定するトピックモデルや、変分オートエンコーダも潜在変数モデルの1つと考えることができます。

今回、深層カルマンフィルタを理解する上でこの変分オートエンコーダを潜在変数モデルの1つと考えることがキモとなります。

潜在変数モデルにおいてよく現れる考え方として、潜在変数の分布が観測変数の分布の事前分布となる、すなわち潜在変数の取る値が観測変数を生成する分布のパラメータとなる、というものがあります。実際トピックモデルなどにおいてはトピックと呼ばれる潜在変数の分布をディリクレ分布として仮定し、その値に応じて各単語の出現確率を決定する多項分布のパラメータが変化する、というLDAというモデルが一般的に用いられます。

変分オートエンコーダ

変分オートエンコーダの説明をする前に、オートエンコーダについて簡単に触れておこうと思います。

オートエンコーダは、ニューラルネットワークによる次元削減の教師なし学習アルゴリズムの1つです。オートエンコーダは入力に対応した出力として入力と同じデータを用います。

f:id:koukyo1213:20181207113355p:plain
オートエンコーダの構造

上の図のような構造のニューラルネットワークの入力と出力に同じデータを与えた場合このネットワークが学習するのはなんでしょうか?

左半分のネットワーク、すなわち入力から中間層へと流れていく部分に関しては、層を挟むごとにどんどん層のサイズが小さくなるようにします。これにより、入力データをより圧縮したような中間的な表現が中間層には現れることになります。そして右半分の中間層から出力層へと流れていく部分では、この圧縮された中間的な表現を再度拡張して元の入力と同じようなデータを出力するようにしています。結果としてこの構造をもったニューラルネットワークの入力と出力に全く同じデータを与えた場合には、そのデータの本質的な部分のみを抜き出したような表現が得られると考えられます。この中間表現を得ることは画像や自然言語など情報が疎な構造に分散している場合には有効であると考えられています。

このオートエンコーダを先のニューラルネットワークの学習という観点で見直すと、説明変数と目的変数が同じものになっていると考えれば教師あり学習の枠組みで捉えることが出来ます。したがって出力の予測値と出力(入力)が近づくと小さくなるような損失関数を与えてやりそれを最小化するように学習すればネットワークはそのデータに対してフィットしたということが出来ます。

さて、変分オートエンコーダでは、オートエンコーダでは特に制限を加えなかったこの中間層に関して、多次元標準正規分布に従うような制限をかけることを考えます。これにより、例えば画像群に関してフィットさせると各画像は中間層においては標準正規分布を構成する一点として配置されるようになります。そして、学習データが射影されていない点というのをその正規分布からサンプリングすることで学習データには含まれないデータを得ることが出来ます。このように、学習によって分布を得るような機械学習のモデルを生成モデルと言いますが、これが生成モデルと言われるのは学習データにはないデータも生成できるから、という背景があります。

変分オートエンコーダは、オートエンコーダの中間表現を標準正規分布を構成するような制限をかけたもの、ということはわかりましたがこれをどのように実現するのでしょうか?

世の中には変分オートエンコーダに関する説明がいろいろと溢れていますのでそれらとの差異を出すためにも、ここではお気持ちについて詳しく述べていこうと思います。

まず、標準正規分布を構成するように制限をかける、という点に関してですが、これは「エンコーダでの行き先の分布を標準正規分布にできるだけ近づける」ということを行って実現します。ここで分布同士の近さを表現する指標としてKullback-Leiblerダイバージェンスと呼ばれる指標

$$ \begin{equation} D_{KL}[p(x)||q(x)] = \int p(x) \log\frac{p(x)}{q(x)}dx \end{equation} $$

と呼ばれるものが導入されます。この指標は、分布が完全に一致する場合には0となり、それ以外の場合は正の値をとります。また、一般には非可換です。したがってこの値を0に近づける(=最小化する)ようにすれば、2つの分布は近づいていくことになります。

エンコーダでの行き先の分布を標準正規分布に近づけるためにはエンコーダの出力が分布(のパラメータ)になっている必要があります。したがって、エンコーダ部分に関して言えば、さきにニューラルネットの学習で述べた損失関数としてこのKullback-Leiblerダイバージェンスを設定し、エンコーダの出力としては多次元正規分布のパラメータ\mu\Gammaを設定します。これらの値がKLダイバージェンスを最小化するように学習をすすめればいいのです。

一方で、エンコーダで標準正規分布を近似するように構成された潜在変数をデコーダでは入力データを再構成するように復元してやる必要があります。これを実現するために潜在変数からデコードされたデータと入力データの再構成誤差

$$ \begin{equation} \sum_{i=1}^{D} \left( x_i \log y_i + (1 - x_i) \log (1-y_i) \right) \end{equation} $$

を最小化するようにしてやります。この2つの和を最小化するように学習をすすめることで、エンコーダの行き先の分布を標準正規分布に近づけるようにしながら入力と出力ができるだけ同じようになるニューラルネットワークというものを得ることが出来ます。

f:id:koukyo1213:20181207175757p:plain
変分オートエンコーダの構造

深層カルマンフィルタとは

ここまでくるともうお気づきの方も多いかもしれませんが、深層カルマンフィルタとは状態方程式\mathbf{x}_{t+1} = \mathbf{A}\mathbf{x}_{t} + \mathbf{B}\mathbf{u}_t + \mathbf{w}_tや観測方程式\mathbf{y}_t = \mathbf{C}\mathbf{x}_t + \mathbf{v}_tを深層ニューラルネットワーク(DNN)に置き換えたものになります。

数学的な表現をすると、記号の定義は同じとして

$$ \begin{align} \mathbf{x}_{0} &\sim \mathcal{N} \left(\mu_{0} , \Gamma_{0}\right) \\ \mathbf{x}_{t} &\sim \mathcal{N} \left( G_{\alpha}(\mathbf{x}_{t-1}, \mathbf{u}_{t-1}, \Delta t), S_{\beta}(\mathbf{x}_{t-1},\mathbf{u}_{t-1}, \Delta t) \right) \\ \mathbf{y}_{t} &\sim \Pi (F_{\gamma}(\mathbf{x}_{t})) \end{align} $$

となります。このうちG_{\alpha}(\cdot)S_{\beta}(\cdot)F_{\gamma}(\cdot)の部分がDNNで表現されているというのが深層カルマンフィルタです。この、G_{\alpha}(\cdot)S_{\beta}(\cdot)の部分をエンコーダ、F_{\gamma}(\cdot)の部分をデコーダと考えると、これは変分オートエンコーダに非常によく似たモデルであることがわかります。変分オートエンコーダとの違いがどこにあるかというと、制御入力\mathbf{u}があることと状態変数\mathbf{z}_tが時系列性をもつ、すなわち

$$ \begin{equation} q_{\phi}(\mathbf{z} | \mathbf{x}, \mathbf{u}) = \Pi_{t=1}^{T}q(z_t | z_{t-1}, x_1, x_2, \cdots, x_T, \mathbf{u}) \end{equation} $$

となっている点です。また、用途としても変分オートエンコーダが主にデコーダを用いて未知画像を生成する、といった用途を考えている8のに対し、深層カルマンフィルタではエンコーダ、すなわち観測データと制御入力の系列から状態を推定する方がメインになっている点が異なります。

このq(z_t | z_{t-1}, x_1, x_2, \cdots, x_T, \mathbf{u})はエンコーダ部分に対応しています。デコーダ部分に関して言えばやっていることは変分オートエンコーダと変わりません。

このネットワークの学習においてはどのような損失関数を最小化すれば良いのでしょうか?ここでも、詳細な証明は元の論文[1]が詳しいため割愛させていただくとして、お気持ちだけ書いておこうと思います。

まず、デコーダ部分は潜在変数を入力としてエンコーダに入ったデータと全く同じデータが出力されるようにしたいため再構成誤差を最小化するようにします。この部分は変分オートエンコーダと変わりません。

変分オートエンコーダと大きく異なるのが、状態遷移が存在する点です。結論から言えば変分オートエンコーダがエンコーダとデコーダの2つのネットワークを用いていたのに対し、深層カルマンフィルタではエンコーダとデコーダと状態遷移の3つのニューラルネットワークを使います。ではどのようにしてこれを学習するか、というと初期状態については初期の潜在変数分布とエンコーダの出力の分布を近づけるようにKullback-Leiblerダイバージェンスを最小化するのですが、それ以降の時間ステップに置いては、状態遷移のニューラルネットワークの出力と、1ステップずれた観測変数+制御入力の入力があったエンコーダの出力の分布を近づけるように学習していく、という点が変分オートエンコーダとの大きな違いです。

f:id:koukyo1213:20181207224131p:plain
Deep Kalman Filterのアーキテクチャ

まとめ

今回は深層カルマンフィルタの説明を行いました。変分オートエンコーダの理論的な詳細や深層カルマンフィルタの理論的な説明は僕の理解力不足もあっておざなりになってしまったので今後余裕があったら追加していこうと思います。また、実装に関してもできたらいいなと思っています。かなり中途半端ですが、アドベントカレンダーに間に合わせるために今回は一旦ここで終わりにしたいと思います。

参考文献

[1] Krishnan, Rahul G., Uri Shalit, and David Sontag. "Deep kalman filters." arXiv preprint arXiv:1511.05121 (2015).

[2] Kingma, Diederik P., and Max Welling. "Auto-encoding variational bayes." stat 1050 (2014): 10.

[3] Variational Autoencoder徹底解説
https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24

[4] 猫でも分かるVariational AutoEncoder
https://www.slideshare.net/ssusere55c63/variational-autoencoder-64515581

[5] DL_hacks_20151225
https://deeplearning.jp/wp-content/uploads/2017/07/DL_hacks_20151225.pdf

[6] 深層カルマンフィルタ
http://ubnt-intrepid.hatenablog.com/entry/2016/10/10/205229

[7] 著者GitHub
https://github.com/clinicalml/structuredinference


  1. 正確にはこの二例はおそらく非線形になるので、\mathbf{A}\mathbf{B}\mathbf{C}が分かっていない場合の例としては不適切だが説明の手間を考えてごまかしています。

  2. 論理的に構築する、というのがなんともぼやかした言い方ですが、自然科学の伝統的方法を鑑みるに「データからの法則性の抽出」と「法則性の適用と組み合わせ」によって数理モデルは成り立っているというのが個人的な見解です。この「法則性の適用と組み合わせ」を「論理的に構築する」と表現したつもりです。

  3. 数理モデルの定義としてこれが妥当なのかはよくわからない。

  4. 近づけるという表現が結構曖昧ですがまあここはごまかしています。

  5. \expの中身に実数倍かけたものであることもあります。

  6. 正確にはReLUはx=0微分不可能であるが劣微分により、x > 0で1、x \le 0で0とする。

  7. それ以外の使い方もありますが

  8. 違うかもしれない

GCI優秀者研修旅行【パリ編】

はじめに

この記事はGCI優秀者研修旅行のパリ編です。

GCI優秀者研修旅行とはなんぞや?という方はこちらをご覧ください。

パリ到着 - 夜まで

前の記事ではユーロスターでパリに向かうところで終わったのでその続きから始めます。

パリについたのはお昼すぎだったのですが、なかなかタクシーが捕まらず徒歩でホテルまで行きました。パリはひったくりが多いと聞いていたので内心結構警戒心強めでした。

f:id:koukyo1213:20181025112928j:plain
道すがら見かけた教会

ついてからしばらくホテルにチェックインできなかったのですが、荷物を置いたらすぐに次の訪問先のEcole42に向かいました。

f:id:koukyo1213:20181025114005j:plain
パリのホテルの中庭

Ecole42

Ecole42はパリにある私設のプログラミングスクールです。学校、と言っても卒業することで特別な資格がもらえるわけではないとのことでしたが、それでも毎年1万人以上の応募があるのだとか。その秘密はいくつかあるのですが、いくつか紹介しようと思います。

f:id:koukyo1213:20181025114732j:plain
Ecole42

この学校の面白いところは先生がいないことです。いるのは生徒とスタッフだけで基本的に生徒たちは用意された課題(プロジェクト)を解いて行くという形で学習をして行きます。

f:id:koukyo1213:20181025144927j:plain
Ecole42のシステム

生徒たちは上の写真のようなシステムを用いてプロジェクトを進めます。この水色になっているところがすでに完了済みのプロジェクトで、やや明るい灰色の部分がこれから始められるプロジェクト、暗い灰色の部分はまだ手をつけることができないプロジェクトです。このプロジェクトは、スタッフが用意したものもあれば、生徒が作ったものもあるとのことでした。

扱う内容は多岐に渡り、シェルの作成やWebプログラミングからOSやセキュリティ、アルゴリズム、果ては機械学習まで様々なものが用意されているとのことでした。

卒業の要件というものは明確に定められていないとのことでしたが、学生たちは入学するとまずピシーヌと呼ばれる一連のプロジェクトに携わることになります。この期間は、他の学生から助けを受けることができず自分一人でやり方を発見していかなければいけない期間ということですが、これを修了するとその後は、すぐにやめてもいいしそのまま続けても良いとのことでした。

さらにこのEcole42がすごいのはこの環境が無料で提供されていることです。この学校はフランスの大富豪による寄付で成り立っており、生徒たちは一銭も払うことなく居続けることができるとのことでした。

f:id:koukyo1213:20181025152738j:plain

OECD

f:id:koukyo1213:20181025151854j:plain
OECD

Ecole42での見学を終えた後、僕たちはOECDで松尾先生が講演をするのについて行きました。

ここでは、OECDに日本の様々な官庁から出向している官僚が集まっておりそこで松尾先生が人工知能の導入とその影響や各国の反応などを講演しました。僕たちはそれを傍で聞いているという形でした。

この内容に関しては、松尾先生がよく講演しているもの(年間200回以上同じ内容の講演をしているとのことでした)でしたが、それを聴く人たちの反応を見ているのはなかなか面白かったです。松尾先生も話し方を僕たち学生にするときとは少し変えていて、あとでそのことについてお聞きしたら話の掴みで何人かよくうなずく人を見つけておき、その人たちの反応を見ながら聴衆に合わせた難易度の話し方をするようにしているとのことでした。

食事

この日の夜はフランス料理を食べましたが、お肉とレモンキャビア?みたいなのが美味しかったです。

f:id:koukyo1213:20181025152945j:plain
お肉

2日目-パリ観光とベルリンへの移動-

2日目はある程度の暇をいただいていたので、パリ観光をしました。朝早く起きた組はルーブルに行っていたようなのですが、僕の部屋は二人とも疲れが溜まっていたのかかなり遅くまでダラダラしていたので10時過ぎにホテルをチェックアウトして近場を回りました。

f:id:koukyo1213:20181025153546j:plain
運河

僕は部屋が一緒だったM2の人とその日は行動していました。その人はKaggleエキスパートだったので一緒にKaggleなどについて話しながらあちこちを回りました。最初に行ったのは近くにある運河です。上の写真だとなかなか綺麗に見えますが実は結構緑がかっていてややパリの印象から外れていました。

f:id:koukyo1213:20181025154008j:plain

その後、ピカソ美術館に向かって歩きました。ピカソ美術館はそこそこ広く全部は見切れなかったです。

f:id:koukyo1213:20181025154126j:plain

f:id:koukyo1213:20181025154136j:plainf:id:koukyo1213:20181025154146j:plainf:id:koukyo1213:20181025154152j:plain
ピカソ美術館

その後、空港に向かい、ドイツのテーゲル空港まで行きました。この日は基本的に移動が多い日でそのくらいしかしませんでした。

f:id:koukyo1213:20181025154334j:plain
空港にて遊ぶ人たち

続きはベルリン編で・・・