記録帳

クラウド、データ分析、ウイスキーなど。

ダンディハウス銀座店というエデンに行った話

2021年も半分が過ぎた今日この頃、いかがお過ごしでしょうか。
先日kaggleのコンペも無事爆死して終了したので、いつかは行ってみたいランキング16位のダンディハウスに行ってきました。
そのレポートです。

結論から

  • 通い続ければ、肌はきれいに、顔は小顔になるであろうクオリティ。
  • ただ、高い。
  • スタッフの人可愛い。

なぜダンディハウスに?

ここ3か月、体重を落とすことにハマっています。
3か月で7kgほど痩せて、腹の肉が目に見えて落ちました。
しかし、いつまでたっても顔が痩せない。
というか、生まれてから今までで顔が痩せていたことがない。
これは一度専門店(?)へ行くしかない!ととある日のお昼12時に思い立ち、その日の14時からの予約をしました。

ダンディハウスを選んだのは、なんか怪しい店も多いから、名前聞いたことあるところがいいだろうと思ったからです。

ダンディハウス銀座店に突入

銀座駅から歩いてすぐ。エレベータで上がってついに入店。
全体的に、ゴージャス。マーベラス。どこに行ってもいいにおいがする。
将棋の駒で言ったら金。なんだこの高級感は。
待合室これですからね。(ホットペッパーから拝借)

f:id:supa25:20210703223142j:plain
ダンディ

真ん中の地球儀に気を取られつつ、良いにおいのする待合室で雑誌を読んで待つ。
置いてある雑誌もすごい。

「財界」て。直接的すぎるやろ。どこで売ってるんだこの雑誌。
とまぁ全体的にゴージャスです。

カウンセリングを受ける

待合室で待った後は、個室でお姉さんとカウンセリング。
今日受けたい施術や、顔痩せの基本的な情報(リンパと顔の筋肉が重要らしい。前者が詰まっていると老廃物が流れていかないからむくみ、
後者が固いとなんだか痩せないらしい。)や、コースの金額など。
この時点で金額を伝えるのは、良心的かなと思った。
スタンダードに通うと、3か月でドラム型洗濯機が買えるくらいのお値段。これは一般ピーポーにはきつい。

ただ、押し売りしてくる感じはしないため、ちょっと考えさせてくださいー的な逃げはできる。
(ここで完全に断ると、この後サービス悪くなったりしないかな…)
などと考え(おそらく断っても普通にやってくれると思うけど)、ここでははぐらかしておきました。
このカウンセリングが終わると、いよいよ施術へ。

施術スタート

もちろん個室。ここで、カウンセリングしてくれたお姉さんとは別のきれいなお姉さんが登場する。
まぁ、やってるのがエステだし必然的にきれいな人が集まってしまうんだろうけど、それにしても異常。
ミスコンの控室なのか?ってくらい右を見ても左を見てもきれいな人しかいない。
そして客は普通のおっさん。(若い人もいたが)
表現は良くないかもしれないけど、これお酒を飲む代わりにエステを受けるキャバクラだよ。

今回は、フェイススリムコースでやってもらった。
最初のクレンジング?的なのがとても気持ちよかった。
そのあとのマッサージ的な手技は、意外と結構痛かった。顔の筋肉が固いからかもしれない。
最後はパック。顔に塗って時間がたつと固まるタイプ。そんなものがあるのか。
全部で40分くらいかな?ちょくちょくお姉さんと雑談しながら、そんな感じで進んでいきました。

最後にもう1カウンセリング

また前にカウンセリングやった部屋に戻って、対談。
「すごい、全然違いますよ!」と褒められる。うれしい。
でも確かに、顔のたるみが取れた感じはする。
自分でbeforeの写真を撮っていたので、それと比べても差は見えた感じはした。
ただ、肌がめちゃくちゃ潤っているのはとてもわかる。

そこからは、今後も通ってもらいたいお姉さんとその気はない自分のラリーの応酬。
なんとか制して、終了といった流れでした。

総括

サービス自体はすごい良くて、金が有り余ってたら絶対に通っている。
が、金がないために通えない。
そもそもの優先順位として、顔のやせ具合よりも服装や髪型のほうが面積が広いんだから、そっちからだよな
と銀座を歩きながら思いました。
ただ何度も言う通り、サービスは満点だしお姉さんはかわいいしなので、
お金に余裕のある人は通うと幸せになれると思いました。以上!

誤差逆伝播法がなぜ速いのか考えてみた

GW最終日の昼下がり。いかがお過ごしでしょうか。
このGWで復習しよう!と思い、ゼロから作るの①を読み返しています。

今まで2回ほど読んでいて、毎回第5章の誤差逆伝播法で詰まっています。
今回は、以前よりは深く理解できたと思います。それは今度別記事に書く予定です。
今回の記事では、なぜ誤差逆伝播法はこんなに速いか?を解き明かそうという試みです。

実際、どれくらい速いのか?

以下のコードを実行して、数値微分であるnumerical_gradients関数と誤差逆伝播法であるgradient関数でどれくらい時間がかかっているかを調査します。
(具体的な関数の中身は後述)

import sys, os
from dataset.mnist import load_mnist
import tqdm

# MNISTのデータロード
(x_train, t_train), (x_test, t_test) = load_mnist(one_hot_label=True, normalize=True)

# パラメータ設定
iters_num = 10000
batch_size = 100
learning_rate = 0.1
train_size = x_train.shape[0]
network = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
train_loss_list = []
train_acc_list = []
test_acc_list = []

# 1バッチ目実行
batch_mask = np.random.choice(train_size, batch_size)
x = x_train[batch_mask]
t = t_train[batch_mask]
grads = {}
%time grads = network.gradient(x, t)
#%time grads = network.numerical_gradients(x, t)

結果は・・・
数値微分:239s
誤差逆伝播法:0.0256s
つまり、約1万倍も誤差逆伝播法の方が速いという結果になりました。
また、相対的ではなく絶対的に評価しても、239秒はかなり遅いですね。
本来はイテレーション数(今回は10000回)の数だけループするので、
239s × 10000 = 2,390,000s ≒ 664時間 ≒ 28日
MNISTの学習するだけで1か月かかってしまいます。

では、なぜここまで差が出るのかを考えてみます。

仮説1. 数値微分はパラメータごとに微分計算するから遅い

まずは、めちゃくちゃ遅い方の数値微分の関数numerical_gradients()の中身を見てみます。

  # x:入力データ、t:教師データ
  def numerical_gradients(self, x, t):
    loss_W = lambda W: self.loss(x, t)
    
    grads = {}
    grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
    grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
    grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
    grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
    
    return grads

ここでは、各パラメータW1,W2,b1,b2ごとにそれぞれ微分を計算しています。
この微分計算は、
 \dfrac{df\left( x\right) }{dx}=\lim _{h\rightarrow 0}\dfrac{f\left( x+h\right) -f\left( x-h\right) }{2h}
この数式を計算している関数です。
このf(x)に当たる関数は何かというと、損失関数です。
今回使っている損失関数は、このloss()関数。

  def predict(self, x):
    W1, W2 = self.params['W1'], self.params['W2']
    b1, b2 = self.params['b1'], self.params['b2']
    
    z1 = np.dot(x,W1) + b1
    a1 = sigmoid(z1)
    z2 = np.dot(a1, W2) + b2
    a2 = softmax(z2)

    return a2
  
  def loss(self, x, t):
    y = self.predict(x)
    return cross_entropy_error(y, t)

loss()関数の中でpredict()関数を呼んでいます。
このpredict()関数が、いわゆるニューラルネットワークの計算部分です。
np.dot()ということで、内積計算をしてます。いかにも重そうな気配です。
重そうなので、このpredict()関数を1回実行する単位を1predictとして名付けます。
数値微分と誤差逆電波法で何predictの処理を実行しているかで、処理時間の比較を試みます。

さて、先ほどの微分の数式を思い出すと、分子にf(x)が2回出てきました。
そのため、1回微分するごとに2predictとなります。
そして、それがnumerical_gradients()関数の中でパラメータごと(W1,W2,b1,b2)に計算されるため、
数値微分では合計8predictすることになりそうです。

次に、誤差逆伝播法の関数gradient()を見てみます。

  def gradient(self, x, t):
      W1, W2 = self.params['W1'], self.params['W2']
      b1, b2 = self.params['b1'], self.params['b2']
      grads = {}

      batch_num = x.shape[0]

      # forward
      a1 = np.dot(x, W1) + b1 
      z1 = sigmoid(a1)
      a2 = np.dot(z1, W2) + b2
      y = softmax(a2)

      # backward
      dy = (y - t) / batch_num
      grads['W2'] = np.dot(z1.T, dy)
      grads['b2'] = np.sum(dy, axis=0)

      dz1 = np.dot(dy, W2.T)
      da1 = sigmoid_grad(a1) * dz1
      grads['W1'] = np.dot(x.T, da1)
      grads['b1'] = np.sum(da1, axis=0)

      return grads

こちらは色々とやっていますが、つまるところforward処理とbackward処理の2つの処理を実行しています。
ここで注目してほしいのが、forward処理は先ほど出てきたpredict()関数と全く同じ内容です。
つまり、forward処理=1predictと考えられます。
次にbackward処理ですが、こちらは中でnp.dot処理を3回実施しています。
predict()関数は2回でしたから、大体1.5predictとカウントしましょう。
誤差逆伝播法では合計2.5predictすることになりそうです。

ここまでのまとめを図示すると、以下のようになります。
ただ、これだと8predictと2.5predictで3.2倍にしかなりません。
実際は1万倍ほどの差が出ていたので、パラメータごとに計算するからという仮説だけでは足りなさそうです。

f:id:supa25:20210509172113p:plain
比較

これだけだとまだ足りないようなので、今回は数式で表していた微分計算の具体的なコードを見ていきます。

仮説2. 数値微分はパラメータごとに微分計算するから遅い+微分計算をループで実行しているから遅い

めちゃくちゃ遅い方の数値微分の関数numerical_gradients()の中身を再掲します。

  # x:入力データ、t:教師データ
  def numerical_gradients(self, x, t):
    loss_W = lambda W: self.loss(x, t)
    
    grads = {}
    grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
    grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
    grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
    grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
    
    return grads

このnumerical_gradient()関数の中身を見ていく…前に、1つnumpyのメソッドを紹介します。
numpyのnditer関数です。
この関数は、arrayの要素数だけループを回すときに使う関数です。
以下のコードと実行結果を見ると、何をする関数かわかると思います。

実行するコード

np_array = np.random.randn(2, 3)
print('np_array')
print(np_array)

nditer = np.nditer(np_array, flags=['multi_index'])

print('loop_start')
while not nditer.finished:
  print(nditer.multi_index)
  print(np_array[nditer.multi_index])
  nditer.iternext()

実行結果

np_array
[[-0.55654663  1.02078965 -0.18507347]
 [ 0.14575905  1.21458774 -1.91342087]]
loop_start
(0, 0)
-0.5565466339864574
(0, 1)
1.0207896548415565
(0, 2)
-0.18507347073349137
(1, 0)
0.14575904769533748
(1, 1)
1.2145877434392889
(1, 2)
-1.9134208654465972

さて、では改めてnumerical_gradient()の中身です。

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x) # f(x+h)
        
        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        x[idx] = tmp_val # 値を元に戻す
        it.iternext()   
        
    return grad

先ほどのnumpyのnditer関数を用いて、ループを回しています。
仮説1では、この微分計算でf(x)を2回計算している、としていましたが、
それが間違いだったようです。
確かに、f(x)は2回計算していますが、それをxの要素数分ループしているようです。

f(x)は損失関数、つまりはpredict()を計算しているため、ループ回数×2predictが本来のpredict数となります。
では、ループ回数=xの要素数はいくつなのか?を調べます。

仮説1の最後の図にも合った通り、今回はW1,W2,b1,b2それぞれでこのnumerical_gradient()を計算します。
つまり、ループ回数は、W1,W2,b1,b2の要素数の合計になります。
それぞれの要素数は以下の通り。

W1:748×100
W2:100×10
b1:1×100
b2:1×10

素数の合計は、75,910個となるので、ループ回数×2predictを計算すると151,820となります。
数値微分では合計151,820predictすることになりそうです。
(仮説1では8predictにしていたので、めちゃくちゃ増えた…)

誤差逆伝播法の方は、特に考えたかは変わらないので2.5predictのままです。
つまり、差は151,820predict ÷ 2.5predictを計算して、60,728倍となりました。
実際の計測値が約1万倍だったので、ズレてはいますがオーダーは合っていますね。

まとめ

数値微分よりも誤差逆伝播法の方が、なぜ1万倍も処理速度が速いのか?
それは
・数値微分はパラメータごとに微分計算をする
・その微分計算をパラメータの要素数分のループで実装している
ためということがわかりました。

これ、せめて微分計算をループではなくて行列で計算できれば時間短縮になるのでは?と思いましたが、
数値微分は1要素だけちょびっと変化させて計算する方法なので、この方法でやる限りは無理そうですね。
こう考えると、微分を別の方法で求められるようになった誤差逆伝播法は偉大ですね。

このつぶやきをしてから、色々手元でいじりながら、ようやく答えっぽいのにたどり着けました。
みんな気になるところだと思うんだけど、検索しても特に見つからなかったんだよなぁ。
どうしてだろうか。まぁ、今回は自分で解消できたので良かった良かった。

グレンフィデックとシーバスリーガルミズナラを飲み比べてみた

ウイスキーを飲もうのコーナー。
最近はもっぱらハイボールしか飲まないですが、酒の香水と呼ばれるウイスキー、もともとはストレートやらで楽しんでいただろう!
ということで、いろいろ味わっていくことにしました。
しかし、ただ飲むだけだと経験が浅い自分は絶対評価ができないので、なにかと比べながら飲むことにしました。

今回は、グレンフィデック12年とシーバスリーガルミズナラ12年を飲み比べていこうと思います。

f:id:supa25:20210508231918j:plain
フィデック12年とシーバスミズナラ12年

なぜこの2本か。
自分の手持ちの中で近いのがこの2つだったからです。笑
「普通、比較するならフィデックとリベットじゃないの?」
シングルモルトとブレンデッド比べるってどうなの?」
と通な人からは突っ込みがあると思いますが、私のアホ舌ではどちらも「すっきり華やかバニラ系」で一緒です。
なので、この2つで比較してみることにしました。

テイスティング

香り

フィデック12年は、強いりんご感。フルーティ。

ミズナラ12年は、弱いりんご。アルコール感や、薬品?臭がちょっとする。

香りはフィデックのほうが好み。

ストレート

フィデック12年は、果実のような甘みがとにかく広がる。華やかでフルーティ。りんごっぽい。
後味は特に感じず、すっきり終わる感じ。

ミズナラ12年は、ヒノキみたいな木の甘み。多分これがミズナラなんだろう。突き抜けた甘味ではないけど、いい甘み。
後味はちょっとアルコール感、苦みみたいなものがある。

ストレートもフィデックが好み。

ちょい加水

フィデック12年は、甘さがちょっと収まった感じ。苦みが増した?
もともとすっきりした甘味だったので、加水すると薄い感じがする。

ミズナラ12年は、薄いというよりはマイルドな甘みになった。
ストレートで感じていた苦みも消えた気がする。

ちょい加水はミズナラのほうが好み。

ロック

フィデック12年は、滑らかな甘みになってさらに飲みやすい。

ミズナラ12年は、しっとりとした甘みでおいしい。

ロックは、どっちもうまい。

ハイボール

(このあたりから、酔っぱらってきている)
フィデック12年は、華やか。甘みが染み出ている感じ。

ミズナラ12年は、甘いんだが、なんというか、甘さが箱にしまってある感じ。

ハイボールは、フィデックが好み。

総評

全体的に、グレンフィデック12年のほうが好みだった。
それぞれ、グレンフィデック12年はストレート、シーバスリーガルミズナラ12年はロックが好きだった。
ただ、自分はそもそも炭酸が好きなのでハイボールが大好き。
フィデックのハイボールは爽やかでフルーティでかなり飲みやすく美味しいので、今後リピートしていくと思う。
(これ書きながら、フィデックのハイボールを飲んでます)

同じような甘さだと思っていたが、特にストレートで違いを感じた。
グレンフィデックは果実系の甘さ、ミズナラは木のような甘さなんだな。
ヒノキ風呂を好きでよく入っていたが、ミズナラはそれを思い出した。
「同じような系統だと思ってたシリーズ」は面白かったので、次は大好きな臭いピート系で比較してみたいと思う。

Arduino+ラズパイ3+Azureで生物検出システムを作る

コロナ真っ最中のGW、いかがお過ごしでしょうか。
最近、仕事でエッジ関連の話題が出ているため、積読だったこの本を読んで、生物検出システムを作ってみました。

どんなシステム?

何か生き物が近づいたら、カメラが起動し写真を撮って、「何の生物が近づいたか?」をLINEに通知するシステムです。
こんな通知が届きます。

f:id:supa25:20210504183726p:plain
LINEのイメージ

鳥とか獣でも試したかったですが、テストできそうになかったので諦めました。
こんな時、ペットを飼っていれば…。

アーキテクチャ

物理的なマシンは灰色、クラウド上は青色で示しています。
ほぼ本に書いてあった通りの構成です。ただ、本では機械学習部分を「自分で教師データをスクレイピングしてきて、アノテーションして、学習させてね!」と書いてあったので、
そこだけはAzure Cognitive Servicesの中のComputer Visionを使っています。
今この本が出たら、絶対同じように既存のAPI使うだろうな。このあたりの技術の進歩はすごいですね。(この本は2017年5月発売)

f:id:supa25:20210504184206p:plain
アーキ図

ちなみに、実際のデバイス類はこのような感じです。
ラズパイ、名刺サイズで普通にOSが動くんですね。すごい。

f:id:supa25:20210508234534j:plain
バイス

処理の流れ

①センサーが動くものを検知する
②検知したことをブレッドボードからBluetoothでラズパイに通知する
③通知が来たら写真を撮って、wifi通してAzureに送る
④IoT Hubで一時的に保持して、StreamAnalyticsに送る
⑤StreamAnalyticsで処理(今回は全データ取得するだけ)して、ServiceBusQueueに送る
⑥ServiceBusQueueで一時的に保持して、AzureVMに送る
⑦AzureVM上のpythonで、届いた画像を引数にComputerVisionのAPIを呼び出す
⑧そのレスポンスと画像をLINEに通知する
⑨LINEにメッセージが届く

①ー③の物理的な部分はわかりやすいですが、④以降のAzureサービス部分の役割が少しわかりにくいですね。
具体的には、なぜStreamAnalyticsやServiceBusQueueを挟むのか?IoT Hubから直接AzureVMでもよいのでは?と思いましたが、
以下のブログにこのような記載がありました。(この方の場合は、ServiceBusの後がAzureVMではなくLogicAppsですが)

クラウドでは、この"イベント(プログラムの区切り)"を意識して、各々の処理を得意とするサービスを使うと、楽にサービスを組み立てることができます。
今回のシナリオではこんな感じです。

  • IoT Hubがテレメトリデータの収集と保持に集中
  • Stream Analytics が時間軸を意識した処理に集中
  • Service Bus が発生イベントの保持に集中
  • Logic Apps がメール配信処理に集中

これらは要件は1サーバー内に構築することも可能です。しかし、それでは部分的なスケールアウトやサーバー停止に対して脆弱な状態となってしまいます。
そこで、"処理"と"保持"をイベントの切り替えタイミングでうまく利用してそれぞれの作業を得意とするサービスを使います。
こうすることで、例えばメールサーバーの停止に伴うエラーがたくさん出たとしても、少なくともテレメトリデータの保持や時間軸を意識した警告には影響が無い仕組みを簡単に作ることができます。

qiita.com

つまり、1つのサービスでもできるが、一部分の性能UPや障害の対応がよりやりやすいように、「保持」と「処理」を分けているということでした。
機械学習では「前処理」「学習」「推論」を分けて実装して、問題の特定が素早くできるようにしますが、それのストリーミング処理版というところですかね。

大変だったこと

プログラムは書籍についていたので、これをそのまま流せば楽勝…と思っていましたが、ところどころエラーが発生して、その処理に時間がかかりました。
逆に、慣れていないハード面の扱い(基盤に抵抗刺したり、Bluetoothを飛ばしてラズパイで検知したり)のところは書いてある通りやればいけました。

中でも一番時間がかかったのが、保存したカメラの写真をAzureに送るために開こうとすると発生するUnauthorizedAccessException。
これは画像を開いてそれをBase64エンコードする処理ですが、そのReadAllBytesで発生していました。

byte[] bufPhoto = await Task.Run(() => File.ReadAllBytes(photoFile.Path));
string b64Photo = CryptographicBuffer.EncodeToBase64String(bufPhoto.AsBuffer());

しかし、なぜか以下のOpenReadAsyncで開くと正常に動作します。

using (var photoStream = await photoFile.OpenReadAsync()){
    // 撮った写真を画面に表示する
    BitmapImage bitmap = new BitmapImage();
    bitmap.SetSource(photoStream);
    capturedImage.Stretch = Stretch.None;
    capturedImage.Source = bitmap;

原因は結局分からなかったので、後者のやり方で画像を開いてBase64エンコードする処理をgoogle検索し続けました。
(2つの処理でそれぞれ返り値の型が異なるので、そのままは使えない。このあたりのC#の文法の理解も時間がかかりました)

結果、以下の神Stackoverflowを見つけ、何とか解決。
how to convert an image to base64string in c# - Stack Overflow

ほぼ流用させてもらい、以下のように修正したら通りました。
正直、このDataReaderが何をしているのかさっぱりわかりません。わかる方いたら教えてください。

byte[] fileBytes = null;
using (var photoStream = await photoFile.OpenReadAsync())
{
    fileBytes = new byte[photoStream.Size];
    using (var reader = new DataReader(photoStream))
    {
        await reader.LoadAsync((uint)photoStream.Size);
        reader.ReadBytes(fileBytes);
    }

    string b64Photo = Convert.ToBase64String(fileBytes);

気づき

API化は正義

マイコン、Azure領域は、かなりAPI化が進んでいたのでそこまで句ではありませんでした。
例えばマイコンだと、Bluetoothに送るときは
Serial.write(1);
で終わりです。
また、AzureもAI関連のAPIが多いので画像さえ取得できれば学習もさせずにいろいろなことができます。
ラズパイの上での実装は、C#そのまま書くのではなくなにかフレームワークが欲しいところです。

統合開発環境は正義

一般的には、IDE使えば画面もDBもロジックも開発できると思います。
ただ、今回のIoTシステムではArduino IDEとVisualStudio2019とVSCodeの3つを使いました。
マイコン、ラズパイ、Azure上で開発するためにはそうする必要があったからですが、ここも統一できるようにして欲しいところです。

物理的な動作はオモロイ

基本的にソフトウェアの開発をずっとしていたので、「実際のセンサが動く」「カメラで写真が撮られる」のようなこととは無縁でした。
今回実際やってみて、このあたりの物理的なデバイスの動作を見るのがかなり面白かったです。
ブレッドボードに素子ぶっさすのも楽しかったので、IoTシステムは他にも作りたいと思いました。

チェックポイントで動作させるとモチベが続く

今回、ところどころで動作確認をするようにしていました。
マイコンのランプをチカらせる
Bluetoothに値が乗っているかアプリでチェックする
・カメラがちゃんと撮れてるか確認する
・Azureに画像がちゃんと飛んでるか確認する
・ちゃんと画像分類するか確認する
など。
最初からこれらのチェックポイントを意識したわけではなかったですが、今回4日間にわたって自主的に作れたのはこのおかげだと思ってます。

ToDo

気づきを受けて、次にやりたいこと。
①最近のIoTシステム開発事情の調査
今回感じた不便さは、世の中みんな感じるはずなので、今は解決しているはず。
AWSとかがサービス出してるのではないか?ということで調査したいと思います。

②別のIoTシステム作る
かなり楽しかったので、また作りたいと思っています。
今度は、アームとか動かしてみたい。そしてAWSで構築してみたい。


久々に書いたらかなり長くなってしまいました。
残りのGW、楽しんでいきましょう!

COVID-19コンペ参戦記

少し遅くなってしまいましたが、Covid-19コンペに参戦したので、やったことなどを記載しておきます。

 

コンペ概要

OpenVaccine: COVID-19 mRNA Vaccine Degradation Prediction
https://www.kaggle.com/c/stanford-covid-vaccine

COVID-19のワクチンの筆頭候補として、mRNAというワクチンがあります。
このワクチンの問題として、今考えられているワクチンの構造だと不安定すぎて冷蔵保存だと壊れてしまいます。
そうすると世界に輸出することができず、非常に困ります。
ということで、安定したmRNAを設計するため、データセットであるRNA分子の各塩基ごとの分解率を予測するというコンペです。

また、データセットの特徴として、testの中でpublicとprivateのものがどれかすでに判明しています。
以下、表の項目の説明。
RNAnum:RNA分子の数。
RNAlen:RNA分子に含まれる塩基の数。
scored:実際にkaggle上でscoringの対象となる塩基。
つまり、trainであれば1つのRNAの中の1~68個目の塩基の分解率はscoreの対象になるが、それ以降は無視されます。

  RNAnum RNAlen scored
train 2400 107 68
test(pub) 629 107 68
test(pri) 3005 130 91

 

やったこと

モデルについて

前半は、以下のkernelを参考に(というかほぼコピペで)GRU+LSTMモデルを構築しました。

https://www.kaggle.com/its7171/gru-lstm-with-feature-engineering-and-augmentation

そして後半は、以下のkernelを参考に(というかh(ry)AE pretrain + GNN + Attn + CNNモデルを構築しました。
正直、このkernelで構築しているモデルはほぼ理解していないまま使っていたので、後の勉強項目です。

https://www.kaggle.com/mrkmakr/covid-ae-pretrain-gnn-attn-cnn

フィルタリングについて

コンペ初期は、trainとtest(pub)だけノイズとなる結果を運営がフィルタリングしていました。(フィルタリングしたものだけをscoringに使用する)
しかし、test(pri)だけフィルタリングしない状態だと、フィルタリングしたものに過学習したモデルを使うことでtest(pri)だと精度が落ちる可能性があるため、
途中で急遽test(pri)もフィルタリングする、と運営から発表がありました。

これを受けて、フィルタリングについては色々試しました。
・trainとvalidどちらもフィルタリング実施する
・validのみフィルタリングする
・trainとvalidに、ノイズ成分を同じ割合ずつ含ませる
普通に考えると、trainもtest(pub)もフィルタリングしているため、学習時もフィルタリングしたほうが精度は良くなると思いましたが…
publicLBはそこまで変わらずでした。

データ増幅について

「モデルについて」の1つ目のkernelでは、データの増幅を実施していました。
arnieというライブラリを用いると、1つの塩基の配列から複数の塩基の構造を作り出せます。
1つ目のkernelでは、データの増幅量を2倍だけにしていたので、以下の神kernelを参考に出せるだけ出して増幅しました。
約3倍位に増えていたはず。
これはCVがめちゃくちゃ上がりましたが、LBはそこまで上がらずでした。
(ちなみに、このデータ増幅を実施するには私の8GBメモリUbuntuマシンではメモリエラー。すぐにヨドバシで16GBメモリ×2を購入してきて気合の実行しました。)

https://www.kaggle.com/its7171/gru-lstm-with-feature-engineering-and-augmentation


データ増幅したものを含めた、cvとLBのグラフがこちら。
(2倍増幅を2x aug, 2倍より少し増幅したものを2.5x aug, できるだけ増幅したものをfull augと記載してます)

f:id:supa25:20201025181530p:plain

cvVSLB

2倍にしたものはcvとLBは相関していましたが、それより増幅量を増やすとcvは低くなるがLBがあまり良くない傾向にありました。
やはり増幅したものと実際のtestのデータセットだとなにか違ったのか?arnieの中身を理解しようとしてもドキュメントどこにも落ちていなかったので理解が進まず…。
このkernel作ってくれた方すごいと思います。さすがGM

かつ、bppsファイルという、どの塩基がくっついているかを配列で示すファイルも1つのRNAごとに提供されていましたが、
これも上記の増幅kernelで同じく作り出せるようでした。
ですが、「モデルについて」の1つ目のkernelではbppsは増幅せず既存のものを使いまわしていたので、追加でbppsも作ってinputとしました。
…が、cvもpublicLBもあまり上がらず。なぜだ…。

アンサンブルについて

いつも通り、ここを一番頑張りました。
最終的に作ったアンサンブル結果は70個以上。
-データの増幅量の違い
-フィルタリングの有無
-seed違い
とココらへんを混ぜまくりました。
最終的には、

[2x_augと2.5x_augのseedアンサンブル(10個)]*0.45 +  [full_augのアンサンブル(3個)]*0.55

という黒魔術式でsubmitしました。これが銅メダル。
2x_aug&2.5x_augとfull_augでそれぞれ分けているのは、full_augは単体のcvが良すぎて、全てをアンサンブルで混ぜるとfull_augのみを混ぜるのが一番良いです!という結果になってしまうため。

結果

139/1636で銅メダル獲得でした。
このコンペ、結果発表でも2つほど波乱がありましたね。

 

1つ目:もうすぐ終了だし早めにスコア再計算しときますかw事件
〜コンペ終了の12時間前〜
運営「コンペ終わってないけど一応スコア計算し直しますわwwその間LBぐっちゃぐちゃになるけどサーセンwww」
kaggler「は????今からラストのsubしようと思ってたんだが???」
運営「大丈夫!すぐ計算終わるからさ!」

〜コンペ終了の9時間前〜
運営「リスコアに問題が発生したため、コンペ終了を24時間伸ばします(キリッ」
kaggler「はああああああああああああ???????」

 

2つ目:フィルタリング忘れてたてへぺろ事件
〜コンペ終了〜
kaggler「お疲れ様でしたー!めっちゃshakeしてるwww」
とあるGM「これフィルタリング有効化されてなくね??」
運営「フィルタリングが正しくアップデートされてませんでした。スコア変わります。」
まさかのPrivateLBのスコアが更新。ランキングが大きく入れ替わる。
kaggler「はあああああああああああああ?????????」

色々合ったコンペでしたが、とりあえず銅メダルゲットできたのでよし。
ただ、もう少しNNのモデルの部分を勉強しないといけない。

 おわりに

今回は(も?)kernelで良さそうなものを使って、他のkernelの要素を組み込んでいき、最後はアンサンブルというパターンで乗り切りました。

他の方のsolutionをまだあまり読めていないので、そこを早く読まないといけない。

 

VSCodeでのgit連携

いつもどおり、環境設定に1時間ほど格闘したのでメモ。

VSCODEで開発したあとそのままgitにpushできたら、ええやん。そう思った30歳。

一般的なエンジニアからみたら、「6月ってよく雨が降るな…」レベルのことだと思うけど。

 

・1つ前の記事を実施し、指定のvenvをVSCODEに読み込ませる。

VSCODEのTERMINALで「git clone https:... 」とcloneコマンド実施。

・上記実施すると、ディレクトリ一覧のところにcloneされたディレクトリが作成される。

・そのディレクトリで新しいファイル作る。

・commit、pushする

・終わり

親の顔より見たpython開発環境構築

UbuntuPythonの仮想環境を構築した。メモ。

テーマは、「poetryを使おう」

 

・python3のインストール
・pip3のインストール
・python3、pip3をそれぞれpython,pipで呼び出せるようにbashrcに追記
・poetryのインストール
・poetryをPATHに通した
・python3-venvインストール
・poetryでプロジェクト作成
・poetry addでライブラリ追加
・venvのファイルを削除(デフォのvenvファイルの保存先だとVSCodeで読み取れない)
https://qiita.com/tasogarei/items/c486215c6c81b1aaf45a
VSCodeでvenvファイルを読み込む
https://hachian.com/2019/09/19/vscode_venv/#toc5

これで、poetryでプロジェクトを作って、それをVSCodeで読み込むと完成!

 

【2021/5/23 追記】

WindowsのPoetryインストール有用記事。

Windows 10 で Python のインストールから Poetry と pyenv の利用 - Qiita