この記事は Classi Advent Calendar 2019の3日目の記事です。
みなさん、こんにちは。ClassiのデータAI部でデータサイエンティストをしている @tetsuroito です。普段は一般的なデータ活用推進をしていますが、一人R&Dチームをやっていることもあります。
大学との共同研究をしながら、リサーチクエスチョンを立てたり、データ整備をしたり、学会で研究発表などもしています。今年は日本テスト学会で共著の発表と日本行動計量学会でポスター発表をしました。
そんなR&D活動の最中で起こったおもしろ話を紹介したいと思います。
この記事を書こうと思ったきっかけ
とある日に物体検出の実験をしていました。DeepLearningが流行り出して久しいので、画像処理のタスク自身は目新しさはありませんが、夏に発表した研究でChainarを使ったモデルを使ったりもしていたので、やってみようと思い立ったのがきっかけです。
そこで、少し前に話題になったYOLO(You Look Only Once)で色々と遊んでいました。
YOLOに関する記述の詳細は割愛しますが、こちらの記事(物体検出手法の歴史 : YOLOの紹介)に歴史や認識方法、処理手順などが紹介されているので、ご一読ください。
色々と試行錯誤した上で、YOLO V2の学習済みモデルから、最近取った写真をいくつか物体認識させていた中での出来事です。
私の愛してやまない🍣の画像をYOLOに食わせてみたところ、あろうことか「hot dog」という認識をしてしまったのですね。
特に文脈の伴っていないツイートでしたが、絵力の強さもあってまあまあ伸びました。
どうしてこうなった
ここで冷静に考えるべきは出力結果に憤ることではありません。
まずは冷静にYOLOの学習モデルに立ち返ることにしましょう。
# Convolutional neural network
model = rm.Sequential([
# 1st Block
rm.Conv2d(channel=64, filter=7, stride=2, padding=3),
rm.LeakyRelu(slope=0.1),
rm.MaxPool2d(stride=2, filter=2),
# 2nd Block
rm.Conv2d(channel=192, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.MaxPool2d(stride=2, filter=2),
# 3rd Block
rm.Conv2d(channel=128, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=256, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=256, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.MaxPool2d(stride=2, filter=2),
# 4th Block
rm.Conv2d(channel=256, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=256, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=256, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=256, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.MaxPool2d(stride=2, filter=2),
# 5th Block
rm.Conv2d(channel=512, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=512, filter=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, stride=2, padding=1),
rm.LeakyRelu(slope=0.1),
# 6th Block
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
rm.Conv2d(channel=1024, filter=3, padding=1),
rm.LeakyRelu(slope=0.1),
# 7th Block
rm.Flatten(),
rm.Dense(512),
rm.LeakyRelu(slope=0.1),
rm.Dense(4096),
rm.LeakyRelu(slope=0.1),
rm.Dropout(0.5),
# 8th Block
rm.Dense(last_layer_size),
])
# Loss function.
yolo_detector = Yolo(cells=cells, classes=label_length)
ReNomの公式ドキュメントによれば、このように8層のCNNで特徴量を抽出するCNNモデルが組まれていることがわかります。ここに対して、学習データとテストデータを食わせることで物体認識ができるようになります。
学習済みモデルとは
今回YOLOが広く使われている所以として、このCNNモデルの学習済みの重みを利用できることが要因として考えられるでしょう。モデルはVersion管理されており、私が試したあとのYOLO V3ではSOTAを更新しており、日々進化を続けているモデルでもあります。
重みづけとはすなわち、先ほど示した8層のモデルの各層の重みのことです。
つまり、そのモデルが学習したデータを表現しているということに他ならないため、今回私が食わせた🍣を見たモデルがホットドッグを出力したということは、アメリカで学んできたモデルに違いないだろうと仮説立てました。
学習済みモデルはその利用の際に、育ってきた環境を類推しないといけないのだなと強く感じました。
機械学習の適用の難しさ
機械学習モデルを選択することの難しさは、よく知られているところです。
ある解決したい課題に対して、どのように定式化を行い、そこに投入するモデルがLogistic RegressionなのかRandom ForestなのかSupport Vector MachineなのかXGBoostなのか...
この辺は近年Kaggleが活発に行われているので、Kaggleマスターと呼ばれる方々が非常に得意な領域ではないでしょうか。
このようなオフライン検証におけるSOTAモデルを実際にオンラインのプロダクション環境で動作させると、想定していなかった挙動を起こすこともしばしばありますし、想定していない学習データに対する汎化性の問題もすぐに出てくるでしょう。
機械学習システムは通常のWebアプリケーションよりも技術的負債がたまりやすく、クレジットカードの利率の話を掛け合わせた論文(Machine Learning:The High-Interest Credit Card of Technical Debt)はあまりにも有名な話ではないでしょうか。
このような課題から、近年ではML Opsと呼ばれる機械学習モデルをデプロイした後の話によりフォーカスが当たるようになり、新たな局面を迎え始めています。
当社のような業界では、まだまだICT推進をしていかないといけない領域でもあるため、このような最新の研究事例の適用範囲は狭いです。しかし、先人の辿った道をきちんと理解・把握することで、枯れてきた技術と業界の推進性の最適解をうまく見出し、適切なタイミングでデプロイできるようにR&Dを進めています。
他にも色々なテーマがありますが、今日のところはこんなところで。
明日は @yukoono さんです。楽しみですね。