見出し画像

バンガロールインターンシップ体験記

はじめに

こんにちは皆さん!
この記事が記念すべき我が人生初noteとなります。
そして、Softbank AI部 Advent Calendar 2019の14日目の記事となります。

さて、私は東京大学大学院 新領域創成科学研究科 先端エネルギー工学専攻 鈴木研究室修士課程2年の河﨑太郎と申します。普段、研究室ではDeep Learning を利用して航空機の形状変更に対応した衝撃波位置の予測に関する研究に取り組んでおります。今回、2019/8月から9月の終わりまでの2か月間、SETIプログラムに参加し、インド・バンガロールにあるmfine(主に社内で常駐している医者と患者をアプリを使ってマッチングを行い、医療相談サービス患者に提供する会社)においてData Scienceチーム(社内ではまだ出来たばかりで数人程度のチーム)の一員としてインターンシップを行いました。この記事ではインターンシップの体験記と滞在中の生活と旅行について記したいと考えています。

SETI

SETIとは「Student Entrepreneur Training & InternshipProgram」の略で、東京大学産学協創推進本部教授 各務茂夫先生が中心となり企画された、東京大学の学生にインドのスタートアップ(主にTech Company。執筆中、Tech Companyってなんだ?定義は?コード書いてたらTech Companyなのか?と思ったので調べるとHarvard Business Reviewの記事に定義っぽいものがありました。1. スケーラビリティがあり 2. 低資本で 3. ネットワーク効果がある会社の事らしいです。)にインターンシップの機会を与える、今年から始まったプログラムです。今年は私以外にも計6人のメンバーが選出され、夏休み期間中にそれぞれ別の会社でインターンシップを行いました。ヨーロッパやアメリカ、中国に行く機会は度々散見されますが、なかなかインドに行って暮らす機会はないと思います。恐らく、来年以降も開催されると思いますので、興味のある学生の方はぜひ参加してみてください。

バンガロール

画像1

通勤途中で見つけたORACLE Tech Hub

バンガロールはインド半島の南の中心に位置し、日本よりも赤道に近いものの、標高が920m(日本の軽井沢と同じくらいの標高)であるため、一年を通して20℃を保っており、年中過ごし易い気候となっています。そのため、湿度が高くムシムシとした暑さのある日本で夏を過ごすより、バンガロールのほうがよほど快適でした。

インドのシリコンバレー」と呼ばれ、GoogleやMicrosoft, Adobe,  Oracle, といったアメリカのIT企業の支社や、FlipcartやOlaといったインド発のユニコーン企業の本社があります。[1][2][3]

滞在先の企業の紹介


私が滞在したmfineは、医療診断サービスを提供している会社です。mfineのアプリを利用することで、患者はわざわざ病院に行く必要がなく、家にいながらも医師の医療診断を受けることができます。日本よりも国土が広く、人口の多いインドでは一人当たりの医療診断機会が不足しており、医療診断の効率性の向上が必須となっている状況下で、機械学習を用いて日々の医療診断の中で自動化できる部分を自動化する試みは非常に有意義ですし、ビジネス的にも大きなチャンスがあると思いました。加えて、代替できる部分が多くなるにつれて、医師を一人雇う代金からサーバー代の電気代だけで患者の医療診断ができるため、冒頭で出てきたTech Companyの定義に合致しそうです。

この会社では日々の医療診断(患者に患部の写真を送ってもらい、その写真を通して医師が診断を行う)を通して、患部のデータが集まっており、このデータを利用して患部の自動診断システムを作成したいと考えていました。最終的なゴールとしては患者から送られてきた画像や音声に対して、"100%"の精度で"即座"に"医者を介在せず"に医療診断を行うことです。が、いきなりそこまでは無理ですので、現在の通常医療診断の1st スクリーニングを自動で行い、医師の回転率を上げるシステムの作成が当座のデータサイエンスチームのプロジェクト目的となっていました。とは言っても、様々な病状があり、一つのモデルだけで汎用的な診断システムを作ることは困難であるため、分割統治法的な考えの元、このプロジェクトの病状毎に分けて、サブプロジェクトを作成し一つ一つをクリアしていくと方針でチームは動いていました。今回のインターンシップでは、サブプロジェクトの一つである「皮膚病」診断システムの作成に関わらせてもらいました

業務内容

医師免許を持っていない私がどうやって「皮膚病」診断システムを作成するのでしょうか?例えばですが、

画像2

ISIC(International Skin Imaging Collaboration) 2019のデータから借用

この二つの皮膚病はそれぞれ違った病名を持っていますが、素人目から見てほとんど差異がないです。もしこれをルールベースで一から作成しようと思えば、定性的ではなく定量的に病状判断の基準を明確にし、コード作成する必要があり、とてもインターンシップ期間中に終わる作業ではありません。
ここで役立つのがデータドリブンな深層学習を使ったシステムとなります。

さて、機械学習を利用する際に一にも二にも重要なのがデータです。社内には皮膚病のデータはたくさんあるものの、そのほとんどがアノテーションされていないデータ(当たり前で、犬とか猫のアノテーションは誰に対しても容易だが、皮膚病のアノテーションは新人ではなく経験豊かな医師でないと難しい)でしたので、上司から「ISIC 2019という皮膚病の8クラス分類のコンペティションがあるから、そこのデータで学習・検証しよう」というアドバイスを頂き、そのデータのモデル作りを行いました。

医療画像診断で厄介なのがデータが不均衡であることです。

画像3

クラス毎のデータ数を表したヒストグラム

データ数のヒストグラムが表すように、最も多くデータがあるクラスと最も少ないデータのクラスでは55倍もデータ数にひらきがあります。この理由として、病状の中には頻繁に発生する物や滅多に発生しないものがあり、あまねくデータを取得するのは難しい現状があります。

不均衡データに対処せず、そのまま学習を行うとどういうことになるのか?Qitaの記事に説明がありましたので、そのまま引用します。[4]

例えば、AとBの2つのクラスがあるとします。クラスAがデータセットの90%を、クラスBが残りの10%をそれぞれ占めますが、あなたは特にクラスBのインスタンスの識別を正しく行いたい、というような状況を考えます。全てのインスタンスに対してクラスがAであると予測する予測器の精度 (Accuracy) は90%になりますが、これは今回のあなたの目的にとっては全く無意味なものです。一方で、適切に補正された手法を用いると、単純な精度 (Accuracy) こそ下がるかもしれませんが、かなり高い真陽性率1を実現できるはずであり、これは今回の問題設定でまさにあなたが最適化すべき指標であると言えます。

つまり、数学、物理、化学、英語、歴史のテストを控える学生が勉強を始める時、歴史以外は100点に対して、歴史だけは5000点であれば、全ての時間をかけて歴史のみを勉強し、それ以外の科目を全く勉強しなくなる事と同じ現象が発生します。なぜならそれが点数をたくさん取りたいという目的に対して合理的な手法だからです。

学生

どの科目に勉強時間をかけるべきか?


具体的な精度は差し控えますが、訓練データが不均衡であることを考慮さずに学習を行ったモデルで検証データを予測し、混合行列の作成を行うと、確かにサンプル数の少ないクラスの精度が軒並み悪くなってしまいました。これは、前述のように、モデルが「特徴量をちゃんと学習せずに、とりあえずサンプル数の多いクラスを予測すれば上手くいくだろう。」と学習してしまった事を表します。

ちょっと調べて、この問題への対処法としては、
1. 目的関数に変更を加える
2. データのサンプリングを変える

のどちらかのようです。本来ならどちらの手法を使用したとしても、上手いやり方(SMOTEというサンプリングの上手いやり方があるそうです)で行えば同様の結果になる気が致しますが、前者の方がISICコンペの上位リーダーボードの手法では多く使われているようでしたので、今回は前者を採用しました。

行っている事は簡単で、Nはデータ総数、n_cはクラス毎のデータ数を表し、データ総数に占めるあるクラスのデータ数の逆数を画像分類の損失関数であるクロスエントロピーに掛け、損失関数へのサンプル数が少ないクラスの寄与度を大きくしています。先ほどのテストの例で言えば、傾斜配点(特定の科目に一定の倍率を掛ける)を行い、得点の配分を均す作業を行いました。

画像5

Cはクラスの総数、Nはサンプル総数、n_cはクラス毎のサンプル数

これが一番簡単な手法で、他にも2018年にFacebook AI Researchのチームが発表したFocal Loss というのもあります。

画像6

p_tは予測確率、γはハイパーパラメータ(経験的に決める値)


こちらは、クロスエントロピーに何かしらの重み(テストの例でいうと傾斜のかけ方)を掛け、損失関数に対する少ないサンプルのクラスの寄与度を変更するという点では同じですが、重みが学習中に変更されていきます。

画像7

予測確率と損失関数の関係を表すグラフ(Focal Loss for Dense Object Detectionより転載)

(1-p_t)という係数は予測に成功している場合はとても小さくなる性質があります。そのため、サンプル数の多いクラスが上手く予測できた始めた場合(例えばp_t=0.6)に、損失関数への寄与が小さくなります。上の図ではγ=0の時(つまり、何も考慮していない普通のクロスエントロピー)とγ=5の時で違いがあることを確認できます。赤矢印が示すように、十分分類が可能なクラスに対しては損失関数に対する寄与が小さくなっていることが確認できます。先ほどのテストの例で言うと、一つ一つの科目に最低点を設けるような感覚だと思います。

今回、Pytorchを使って実装を行ったので、CrossEntropyに重みをつけるためのコードを掲載しておきます。

weights = [C0,C1,C2,....,CN] # クラスの数 C0~CNだけ重みを決定する

class_weights = torch.FloatTensor(weights).cuda() # Tensorに変換。cudaを使って並列化したい場合は.cuda()も忘れない

criterion = torch.nn.CrossEntropyLoss(weight=class_weights) # CrossEntropyLossはクラスなので、インスタンスの引数にweightを入れる

結果としては、具体的な数値は割愛しますが、混合行列ので結果を見ると、サンプル数の多いクラスの精度は多少減少しますが、サンプル数の少ないクラスの精度が軒並み良くなり、ちゃんと学習ができていることがわかりました。しかし、focal lossの方が手の込んだ事を施している割には、サンプル数の逆数を使ったlossの方が若干良かった。また、評価指標としてはbalanced_accuracy_scoreというsklearnに実装されている指標を用いた。これを使用せずに正解したデータ数/全データ数でaccuracyを算出すると、不均衡データではサンプルの多いクラスが上手く予測できたかどうかしか評価できないためである。

ここまで実装を行い、約2か月のインターン終了までに同僚に訓練済みモデルを渡して実際に1st スクリーニングとして使ってもらっているところまで行けたので、良かったのではないかと思っています。

Discussion

実際の実装は実は3週間ほどで終え、あとは何に頭を悩ませていたかというと、early stoppingのタイミングです。

画像8

左はepoch vs validation loss。右はepoch vs validation accuracy

通常、モデルが過学習になることを防ぐため学習時に何かの指標の変遷を観察し、モデルの収束の判定を行います。通常、訓練データのlossが改善されても、検証データのlossが改善されなくなったタイミングで学習を終了させ、予測精度が高く、汎化性能も高い状態でモデルのパラメータのupdateを終了させます。今回の場合、左図のepoch vs validation lossのグラフの場合、early stoppingするタイミングは9 epochになります。しかし、右図のepoch vs validation accuracyで見ると9 epoch以降もまだ上昇しており、validation accuracyだけで見るなら9 epochでearly stoppingするタイミングはまだ先のように見えます。accuracyで評価する場合、予測した確率の中で最も高い確率のクラスが画像から予測したクラスとなります。そのため、予測した確率が1.0でも0.3でも、accuracyには反映されません。一方で、lossの場合、cross entropyを採用しているため、1.0と0.3では評価が変わります。lossが上昇しつつも、accuracyが上昇している状況では、正しく予測はできているものの他のクラスの確率にも満遍なく予測している、または、間違えて予測したものは徹底的に間違えるようなモデルになると考えられます。加えて、違うクラスでも似たような画像があったりしたので、お互い間違えるようになったとも考えられます。最終的に見るのはaccuracyだから別に途中の確率はどうでもいいやという考えもできますし、いやいや、満遍なく予測しているだけだからあまり信頼性がないんじゃないのかという考えもできる気がします。(ここの部分、どなたか詳しい方がいらっしゃいましたら、教えて頂けますとありがたいです。)ですので、結局は上司にこの状況を報告し、lossが一番良かった時のモデルとaccuracyが一番良かったモデルを渡して、実際に使ってみて良かった方を採用して欲しいという妥協案を提示しました。

あと、ここからは完全に愚痴になります。ISIC 2018のリーダーボードで公開されている論文を参考にしながら今回の実装を行いましたが、上位のモデルが
pytorchやkerasに実装されている様々な事前学習モデルのensembleモデル

コンペで公開されているデータに加えて独自のデータを医師の力を借りて作る
というチートっぽいことをしていました。今回の仕様上、患者が入力した画像にどれだけ時間かけても良いわけではなかったので、ensembleモデルを使うことができませんでした(本当は並列化すれば行けるかも)。これは良しとしても、独自のデータを新しくアノテーションして、リーダーボードにのっていたので、参加企業の執念とマネーパワーを感じました(笑)上司とこのことで話題になり、「自分も似たような経験が前職であり、大学の研究テーマとして2~3年かけてやるような問題も、前職の大手の企業では金でデータを買って、一瞬で終わった」と仰っていました。やはり、データがいっぱいあるのが最強ですね。

画像9

https://developers-jp.googleblog.com/2019/01/inevitable-ja-night25.htmlより

以上がインターンシップ内容編でした。ご視聴ありがとうございました。

感想、意見、アドバイス等がありましたら、twitterにDMをください。

続きの現地の生活編とインターンシップ中に行ったガンジス川での経験も、後日、書いていこうと思います。

生活と旅行編

* 河﨑、バンガロールで暮らしてみる
To be continued

* 河﨑、インドにおける全ての料理、カレーに収束するの法則を見つける
To be continued

* 河﨑、Uber & Uber eatsに感謝する
To be continued

* 河﨑、mfineってどうやって作ったの?って創業者に聞いてみる
To be continued

* 河﨑、バックパッカーの聖地 ガンジス川に行き、元気なフランス人のおじいさんと遭遇する
To be continued

* 河﨑、ぼったくりの聖地 ガンジス川に行き、ぼったくりに勝利する
To be continued

* 河﨑、野良犬に対処する
メインストーリー以外には野良犬がたくさん歩いており、屋台で夜ごはんを買うとすぐにどこからともなく野良犬が寄ってきました。また生ごみが道路で放置されている場所もあり、なかなか外を歩くのは辛かったりと、最初の3日程は少し戸惑いましたが、すぐに対処法を見つけ、例えば野良犬対策には食べ物を買ったらその包装紙を丸めてデコイとして犬たちに投げつけ、犬たちが気をとられている間に逃げるなど、段々と慣れていきました。

Reference

[1]  https://www.tank-sakurai.com/pre_bangalore/ ↩︎

[2] https://ja.wikipedia.org/wiki/バンガロール ↩︎

[3] https://thescalers.com/5-indian-startups-that-became-unicorns-in-bangalore/ ↩︎

[4] https://qiita.com/r-takahama/items/631a59953fc20ceaf5d9


この記事が気に入ったらサポートをしてみませんか?