Batch Normalizationの実験

Practical Deep Learning For Coders, Part 1の lesson3.ipynb

Batchnorm resolves this problem by normalizing each intermediate layer as well. The details of how it works are not terribly important (although I will outline them in a moment) - the important takeaway is that all modern networks should use batchnorm, or something equivalent. There are two reasons for this:

Adding batchnorm to a model can result in 10x or more improvements in training speed Because normalization greatly reduces the ability of a small number of outlying inputs to over-influence the training, it also tends to reduce overfitting.

と、Batch Normalizationを使うことが強く推奨されていますが MNISTでBatch Normalization(BN)を使ってみたところ、あまり変わりませんでした。

というか、BN使ったほうが微妙に劣っていました。。

lr=0.001で1epoch学習後にlr=0.0001でさらに8epoch学習

loss acc val_loss val_acc
BNなし 0.0133 0.9957 0.0205 0.9936
BNあり 0.0158 0.9948 0.0369 0.9889

というわけで、MNISTとCIFAR10のデータセットを使って少し実験してみました。

Batch Normalization

日本語で概要をつかむには以下の記事が分かりやすかったです。 deepage.net

Batch Normalizationでの逆伝搬の仕組みについてはいろいろな記事で Understanding the backward pass through Batch Normalization Layer がよく引用されていました。

MNIST

モデルは以下のように定義しています。

model = Sequential([
    Lambda(norm_input, input_shape=(1,28,28)),
    ZeroPadding2D((1, 1)),
    Convolution2D(32, 3, 3, activation='relu'),
    MaxPooling2D(),
    ZeroPadding2D((1, 1)),
    Convolution2D(64, 3, 3, activation='relu'),  
    MaxPooling2D(),
    Flatten(),
    Dense(512, activation='relu'),
    Dropout(0.5),
    Dense(10, activation='softmax')
    ])

Batch Normalizationを使うと大きい学習係数を使えると言われているので学習率を10倍にしてみました。

lr=0.01で1epoch学習後にlr=0.001でさらに8epoch学習 (学習率を10倍にする)

loss acc val_loss val_acc
BNなし 0.2219 0.9352 0.1436 0.9582
BNあり 0.0507 0.9886 0.0567 0.9877

BNなしだと 93.5% 程度で止まってしまったのに対して BNありだと 98.8% まで学習が進みました!

確かに効果が確認できました。

CIFAR10

続いてMNISTより難しい分類問題のCIFAR10です。

モデルは Kerasのチュートリアルにあるコードを参考に定義しました。

model = Sequential()
model.add(ZeroPadding2D((1, 1), input_shape=x_train.shape[1:]))
model.add(Convolution2D(32, 3, 3, activation='relu'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))
model.add(Dropout(0.25))
model.add(ZeroPadding2D((1, 1)))
model.add(Convolution2D(64, 3, 3, activation='relu'))
model.add(Convolution2D(64, 3, 3, activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

50epoch学習した結果

loss acc val_loss val_acc
lr=0.0001、BNなし 0.8875 0.7005 0.8007 0.7398
lr=0.0001、BNあり 0.6417 0.7824 0.7124 0.7671
lr=0.001、BNなし 2.1267 0.2226 2.0676 0.2230
lr=0.001、BNあり 0.5938 0.8006 0.5055 0.8264

MNISTよりも差が顕著に出ました。 学習係数を大きくした lr = 0.001 だと BNなしでは発散してしまっています。

一方、BNありは学習係数を大きくしても学習が進んでいき、 学習係数の大きいlr=0.001 + BNありが最もよい結果になりました。

FC層のみをファインチューニングする場合

Practical Deep Learning For Coders, Part 1の中で幾度となく登場する あらかじめ学習したモデルで最終Conv層の出力を保存しておき、FC層のみをファインチューニングする方法 でもやってみました。

MNIST

FC層のみをlr=0.0001で8epoch学習(lr=0.001で1epoch学習したモデルでconv層出力を計算しておく)

loss acc val_loss val_acc
lr=0.0001, BNなし 0.0170 0.9949 0.0226 0.9918
lr=0.0001, BNあり 0.0144 0.9955 0.0237 0.9923

FC層のみをlr=0.001で8epoch学習(lr=0.01で1epoch学習したモデルでconv層出力を計算しておく)

loss acc val_loss val_acc
lr=0.001, BNなし 0.0664 0.9790 0.0663 0.9801
lr=0.001, BNあり 0.0765 0.9756 0.0704 0.9794

なんと、学習係数の大きい lr=0.001でも BNありと BNなし で差が見られなくなってしまいました。。

CIFAR10

FC層のみをlr=0.0001で25epoch学習(lr=0.0001で25epoch学習したモデルでconv層出力を計算しておく)

loss acc val_loss val_acc
lr=0.0001、BNなし 0.6768 0.7764 0.7716 0.7444
lr=0.0001、BNあり 0.4751 0.8418 0.7227 0.7636

BNありだと学習データのロスがより小さくなりましたが完全に過学習になってます。。

実験結果のまとめ

  • 複雑なモデル、学習率を高くした場合でBatch Normalizationによる安定化効果が効く
  • FC層のファインチューニングだけだとそのメリットが小さくなる。むしろ学習が進みすぎて過学習を招く危険性?!

BNを使うと学習が安定する = 学習が速く進む ということが確認できました。

FC層のファインチューニングだとありがたみが薄れたので学習初期ほどBatch Normalizationによる安定化が効果を発揮するということでしょうか

今回使ったコードはhttps://github.com/tanakatsu/bn-experiments にあります。

fast.aiの記事から

http://www.fast.ai/2017/09/08/introducing-pytorch-for-fastai/

With the increased productivity this enabled, we were able to try far more techniques, and in the process we discovered a number of current standard practices that are actually extremely poor approaches. For example, we found that the combination of batch normalisation (which nearly all modern CNN architectures use) and model pretraining and fine-tuning (which you should use in every project if possible) can result in a 500% decrease in accuracy using standard training approaches. (We will be discussing this issue in-depth in a future post.) The results of this research are being incorporated directly into our framework.

Conv層出力のprecompute + FC層のファインチューニングは良くないと書いてあります。

oh... やはりそうだったのか・・・ !

We will be discussing this issue in-depth in a future post とあるので記事を待ちたいと思います。

Practical Deep Learning For Coders, Part 1をやっと終えた

fast.ai のMOOC講座、Practical Deep Learning For Coders, Part 1をやっていて 一通り学習できたので Part 2 へ進む前にまとめてみます。

どんな講座?

7個のレッスンがありディープラーニングのBest practicesを学べます。 講座の中でKaggleの実際のコンペ(Dogs vs. Cats Redux: Kernels EditionState Farm Distracted Driver DetectionThe Nature Conservancy Fisheries Monitoring)にも挑戦し、上位に入れるテクニックを教えてもらえます。

これが無料。すごい世の中になりました。ƪ(•◡•ƪ)"

レッスンあたり10時間、you should plan to spend around 10 hours a week for 7 weeks to complete the material と書いてありましたが3倍ぐらいは必要な印象です。

私は3周ぐらいリピートしたので相当に時間がかかってしまいました。。

学べる内容

  • Image Classification (Lesson1,2,3,4,7)
    • 転移学習 (VGG16)
    • Overfitting対策
      • (Dropout)
      • Data Augmentation
      • Batch Normalization
    • Semi-Supervised Learning (Pseudo labelling)
    • いろいろなアーキテクチャ
      • Multi-Input (メタデータの利用)
      • Multi-Output (Bounding Box出力)
      • Fully Convolution Network (Global Average Pooling)
      • Larger size input (640x360)
      • Inception model
      • Resnet
  • NLP (Lesson 4,5,6)
    • Embedding
    • Collaborative filtering
    • Pre-trained word embedding (Glove)
    • Sentiment Analysis
      • Multi-size CNN, LSTM
    • Text generation (character-level language model)
      • Sequential RNN, Stateful RNN, GRU

学習環境

Python2 + Keras 1.x系を使い、KerasのバックエンドはTheanoです。ただ、Theanoは開発が止まることが決定したのでちょっとアレな感じになっちゃいました。。

基本的にバックエンドに依存しないKerasの高レベルAPIだけで使うのでTheanoの開発が終わったあとに学習を始めても無駄にならないはず。Part 2では TensorFlowとPyTourchになるようです

TensorFlowをバックエンドしても動きますが、学習済みの重みファイルを変換しないとちゃんとした精度がでないので要注意です。 (「fast.aiでTensorFlowをバックエンドとして使う」)

非力な自宅PC(メモリ8GB、GPUメモリ2GB)ではLesson2ぐらいまでしか通用せず、基本的にはAWSやAzureのGPUインスタンスが必要です。

中間出力ファイルのサイズが大きいので全部のレッスンをファイルを消さずに進めるとディスクは70GB以上使います。

なので、ディスク容量への課金を節約するためにインスタンスのディスクサイズをミニマムにするといちいちファイルを消さないといけなくちょっと面倒かもしれません。

感想

日本語のブログ記事は

あたりしか見つかりませんでした。(調べ方がよくないのだろうか??)

惜しい点

  • 提供されているnotebookをその通りに実行しても講師のjeremyの結果をなぜか再現できないことが所々ある
  • keras 1.1.0(提供されているAWSのイメージでのkerasバージョン)とkeras 1.2.2(Azure用の環境構築スクリプトでインストールしたときのkerasバージョン)で挙動が違っていてkeras1.2.2だと所々動かなかったりする

ここでハマるとかなり時間のロスになります。。

あと、公開からそこそこ時間も経っているせいか今、フォーラムで質問してもレスがない場合が多い印象です。

ハマったところ

  • lesson2.ipynb

    keras 1.2.2 では Iteratorのメソッドが N ではなく n に変わっている...

      model.evaluate_generator(get_batches(path+'valid', gen, False, batch_size*2), val_batches.n) # keras 1.2.2
      #model.evaluate_generator(get_batches(path+'valid', gen, False, batch_size*2), val_batches.N) # keras 1.1.0
    
  • dogs_cats_redux.ipynb

    sklearnのバージョンが0.19.1だと log_lossでエラーになる...

      # y = [log_loss([1],[[i*.0001,1-(i*.0001)]],eps=1e-15) for i in range(1,10000,1)] # this raise error...
      # http://forums.fast.ai/t/dogs-cats-redux-error-y-true-contains-only-one-label/6137/4
      y = [log_loss([1,0],[i*.0001,1-(i*.0001)],eps=1e-15) for i in range(1,10000,1)]
    
  • lesson3.ipynb

    notebook通りのlearning rateではJeremyの結果が再現できない...

      # We won't reproduce Jeremy's results with parameters (p=0.6, lr=0.001)
      # http://forums.fast.ai/t/bn-loss-scores-increasing-during-bn-model-fit-training-in-l3/4617
      p=0.3
    
  • lesson4.ipynb

    notebook通りのパラメータではJeremyの結果が再現できない...

      # u = Embedding(n_users, n_factors, input_length=1, W_regularizer=l2(1e-4))(user_in)
      u = Embedding(n_users, n_factors, input_length=1, W_regularizer=l2(1e-6))(user_in) # http://forums.fast.ai/t/lesson-4-discussion/210/51
    
  • statefarm.ipynb

    5倍にするとどうやってもメモリエラーになってしまう...

      # da_conv_feat = conv_model.predict_generator(da_batches, da_batches.nb_sample*5)
      da_conv_feat = conv_model.predict_generator(da_batches, da_batches.nb_sample*1) # use 1 time instead of 5 times to avoid Memory Error...
    
  • wordvectors.ipynb

    Jupyter notebookを開くとNotJSONErrorになる...

    NotJSONError in wordvectors.ipynb · Issue #152 · fastai/courses · GitHub

  • lesson5.ipynb

    notebook通りではJeremyの結果を再現できない...

      # http://forums.fast.ai/t/lesson-5-discussion/233/23
      # try starting the embedding layer as being trainable, and then set it to non-trainable after a couple of epochs. 
      # It improved my accuracy from 0.83 to 0.9. Nothing else did.
      model.layers[0].trainable=True
    
  • lesson6.ipynb

    notebook通りだと' 'が出力される...

      #model.optimizer.lr=0.000001 
    
      # http://forums.fast.ai/t/lesson-6-discussion/245/26
      model.optimizer.lr = 0.001 # Adam's default learning rate will be fine.
    

    notebook通りだとエラーになる...

      #model.optimizer.lr.set_value(0.000001) # This causes AttributeError: 'float' object has no attribute 'set_value' !
      # http://forums.fast.ai/t/python-and-keras-questions-and-tips/224/18
      model.optimizer.lr = 0.000001
    
  • lesson7

    keras 1.2.2 だと gen.flow でエラーが出る...

      # In keras 1.2.2, you'll encounter 'ValueError: NumpyArrayIterator is set to use the dimension ordering convention "th" (channels on axis 1), i.e. expected either 1, 3 or 4 channels on axis 1'.
      # http://forums.fast.ai/t/pseudo-labeling-section-in-lesson-7-throwing-a-valueerror/1746/4
    
      if keras.__version__ == '1.2.2':
          import image # override NumpyArrayIterator in keras 1.1.0.
    

    utils.pyのMixIteratorをそのまま使うとうまくいかない

      # Change the method in utils.py a bit so that it can work
      class MixIterator(object):
          def __init__(self, iters):
              self.iters = iters
              self.multi = type(iters) is list
          if self.multi:
              # self.N = sum([it[0].N for it in self.iters])
              self.N = sum([it.N for it in self.iters])
          else:
              self.N = sum([it.N for it in self.iters])
    
          def reset(self):
              for it in self.iters: it.reset()
    
          def __iter__(self):
              return self
    
          def next(self, *args, **kwargs):
              if self.multi:
                  #nexts = [[next(it) for it in o] for o in self.iters]
                  nexts = [next(it) for it in self.iters]
                  n0 = np.concatenate([n[0] for n in nexts])
                  n1 = np.concatenate([n[1] for n in nexts])
                  return (n0, n1)
              else:
                  nexts = [next(it) for it in self.iters]
                  n0 = np.concatenate([n[0] for n in nexts])
                  n1 = np.concatenate([n[1] for n in nexts])
                  return (n0, n1)
    

これらの修正を含めた私のnotebookはこちらにあります。 github.com

追記

Part1のv2がリリースされるようです

Unofficial release of part 1 v2 - Part 1 - Deep Learning Course Forums

BTW, if you’re in the middle of v1 of the course, I’d strongly encourage you to switch to the new v2. It’s a huge step up from v1 in terms of the quality of models that you’ll learn and the amount you’ll be able to do. If you’ve already completed v1, you’ll find most of v2 is new, so it’s worth doing as well (especially lessons 2-4).

とあるのでPart2の前にPart1のv2をやったほうがいいのかなあ

Kerasのログを可視化するスクリプト

KerasのバックエンドがTensorFlowだとTensorBoardを使ってLossやAccurayを可視化できますがバックエンドTheanoだとTensorBoardに相当するツールがありません..。そこでログからグラフを作成するスクリプトを作りました。

github.com

使い方

  1. Cmd + C でログをクリップボードにコピー

    例えば、

     Train on 50000 samples, validate on 10000 samples
     Epoch 1/10
     50000/50000 [==============================] - 4s - loss: 1.1514 - acc: 0.5976 - val_loss: 0.9149 - val_acc: 0.6829
     Epoch 2/10
     50000/50000 [==============================] - 4s - loss: 1.0144 - acc: 0.6488 - val_loss: 0.9346 - val_acc: 0.6722
     Epoch 3/10
     50000/50000 [==============================] - 4s - loss: 0.9641 - acc: 0.6665 - val_loss: 0.9359 - val_acc: 0.6847
     Epoch 4/10
     50000/50000 [==============================] - 4s - loss: 0.9388 - acc: 0.6775 - val_loss: 0.9194 - val_acc: 0.6845
     Epoch 5/10
     50000/50000 [==============================] - 4s - loss: 0.9073 - acc: 0.6849 - val_loss: 0.9186 - val_acc: 0.6955
     Epoch 6/10
     50000/50000 [==============================] - 4s - loss: 0.8893 - acc: 0.6953 - val_loss: 0.9356 - val_acc: 0.6851
     Epoch 7/10
     50000/50000 [==============================] - 4s - loss: 0.8660 - acc: 0.7039 - val_loss: 0.9151 - val_acc: 0.7018
     Epoch 8/10
     50000/50000 [==============================] - 4s - loss: 0.8497 - acc: 0.7074 - val_loss: 0.9022 - val_acc: 0.6981
     Epoch 9/10
     50000/50000 [==============================] - 4s - loss: 0.8419 - acc: 0.7096 - val_loss: 0.8970 - val_acc: 0.6927
     Epoch 10/10
     50000/50000 [==============================] - 4s - loss: 0.8261 - acc: 0.7157 - val_loss: 0.9318 - val_acc: 0.7027
    

    クリップボードへコピー

  2. $ python generate_graph_from_log.py を実行

    次のようなグラフが表示される

    f:id:sanshonoki:20171229193641p:plain

    -o output_filename をつけて実行すればファイルに出力されます

内部で pbpasteコマンドを使っているのでクリップボードからの読み込みはMacだけですがファイルに保存しておけば他のOSでも使えます(引数で入力ファイル名を渡す)。

とりあえず自分用としてはこれで用を足しそうですが暇があったらグラフ出力をカスタマイズできるようにしたいと思います。

ベイズの定理

ベイズの定理と言えば、赤玉白玉の問題が定番ですがせっかく勉強するので競馬を題材にやってみました。

ベイズの定理の公式

よく見かけるけど、よう分からんという式ですね。。

事象Aを観測したあとで事象Bが発生する確率は


\displaystyle P(B | A) = \frac{P(A | B) P(B)}{P(A)}

と表すことができます。

事象Aに関するデータを観測することで、事象Bの確率が P(B)から P(B|A) へ、より尤もらしい値へと更新される ということを意味しています。

ここで、

  • P(B): 事前確率
  • P(B|A): 事後確率
  • P(A|B): 尤度(ゆうど)

と呼びます。

P(A) は実際には B が取りうるすべての値に対しての P(A | B) * P(B) に関する積分であり、


\displaystyle P(B | A) = \frac{P(A | B) P(B)}{P(A)} = \frac{P(A | B) P(B)}{\sum_{{B}}P(A|B)P(B)}

となります。

P(A)と書くとシンプルに見えますが、実際の計算は大変です。。

その他、

  • P(A, B) = P(A | B) * P(B) = P(B | A) * P(A): 同時確率
  • P(A): 周辺尤度

と言うようです。

また、より学問的な世界では A, Bではなく DもしくはX(データ), θ(分布パラメータ) という表現をするようです。


\displaystyle P(\theta | X) = \frac{P(X | \theta) P(\theta)}{P(X)}

ただ、θ という見慣れない記号を見るだけで急に難しく感じ引いてしまうのは私だけではないでしょう。。

問題設定

では問題設定してみます。

問題

  • 厩舎は成績によってAクラス、Bクラス、Cクラスに分類される
  • 厩舎に所属する馬が2頭連続で勝利した場合、それらの馬がAクラス厩舎所属である確率は?

せっかくなのでリアルな数字でやってみたいと思います。

netkeibaの調教師リーディングのページから 厩舎ごとの勝利数を取得し、勝利数の分布をプロットしてみます。

厩舎の成績データを取得するコードはこんな感じです。(python2)

# encoding: utf-8
from bs4 import BeautifulSoup
import urllib2
import chardet
import traceback
import pickle


def fetchPage(url):
    req = urllib2.Request(url)
    res = urllib2.urlopen(req)
    body = res.read()
    guess_enc = chardet.detect(body)
    try:
        unicode_html = body.decode(guess_enc['encoding'])
    except UnicodeDecodeError:
        print url, guess_enc
        print traceback.format_exc()
        unicode_html = None

    if not unicode_html:
        try:
            unicode_html = body.decode('shift_jisx0213')
            print 'Decoding in shift_jisx0213 is successful.'
        except UnicodeDecodeError:
            print traceback.format_exc()
            unicode_html = body

    return unicode_html


def getTrainerResult(html, offset=0):
    results = []
    soup = BeautifulSoup(html, "html.parser")
    trainer_leading_table = soup.select("table.race_table_01 tr")
    for i, row in enumerate(trainer_leading_table[2:]):
        # print row
        data = row.select("td")
        name = unicode(data[1].string)
        stable = data[2].string
        win_count = int(data[4].string)
        second_place_count = int(data[5].string)
        third_place_count = int(data[6].string)
        unplaced_count = int(data[7].string)
        grade_race_win = int(data[9].string)
        stakes_race_win = int(data[11].string)
        not_stakes_race_win = int(data[13].string)
        if stable != u'栗東' and stable != u'美浦':
            continue
        result = {'no': i + offset,
                  'name': name,
                  'win_count': win_count,
                  'second_place_count': second_place_count,
                  'third_place_count': third_place_count,
                  'unplaced_count': unplaced_count,
                  'grade_race_win': grade_race_win,
                  'stakes_race_win': stakes_race_win,
                  'not_stakes_race_win': not_stakes_race_win}
        results.append(result)
    return results


leading_results = []
for i in range(4):
    page = i + 1
    html = fetchPage("http://db.netkeiba.com/?pid=trainer_leading&year=2017&page=%d" % page)
    # print(html)
    results = getTrainerResult(html, offset=len(leading_results))
    # print(results)
    leading_results += results

print(leading_results)
print(len(leading_results))

with open("trainer_leading.pkl", "w") as f:
    pickle.dump(leading_results, f)

成績のヒストグラムをプロットすると次のようになりました。(X軸:勝利数、Y軸:頻度)

f:id:sanshonoki:20171227094444p:plain

このヒストグラムを見て次のように3つのカテゴリに分けます。

  • Aクラス: 25勝以上 (35厩舎)
  • Bクラス: 11勝以上25勝未満 (110厩舎)
  • Cクラス: 10勝以下 (51厩舎)

それぞれのクラスでの平均勝率は計算して以下のようになりました。

  • Aクラスの勝率: 11.8%
  • Bクラスの勝率: 6.7%
  • Cクラスの勝率: 3.3%

求めるものは 事後確率 P(Stable=A | win,win) となります。

ベイズの定理を使って事後確率を計算する

事前確率を計算する

まず、事前確率を求めます。

厩舎の分布から計算します。

P(Stable=A) = 35 / (35 + 110 + 51) = 18%
P(Stable=B) = 110 / (35 + 110 + 51) = 56%
P(Stable=C) = 51 / (35 + 110 + 51) = 26%

尤度を計算する

次に、尤度を計算します。

各クラスでの勝率は

P(win | Stable=A) = 11.8%
P(win | Stable=B) = 6.7%
P(win | Stable=C) = 3.3%

なので、

2勝するという確率は

P(win,win | Stable=A) = 0.118 * 0.118 = 0.013923999999999999
P(win,win | Stable=B) = 0.067 * 0.067 = 0.004489000000000001
P(win,win | Stable=C) = 0.033 * 0.033 = 0.0010890000000000001

となります。

P(win, win)を計算する

とりうる厩舎クラスに対して同時確率を積分します。

P(win,win) = P(Stable=A) * P(win,win | Stable=A) 
                      + P(Stable=B) * P(win,win | Stable=B) 
                      + P(Stable=C) * P(win,win | Stable=C)
    = 0.005303300000000001

事後確率を計算する

ベイズの定理に従って事後確率を計算します。

P(Stable=A | win,win) = P(win,win | Stable=A) * P(Stable=A) / P(win,win)
    = 0.013923999999999999 * 0.18 / 0.005303300000000001
    = 0.47259630795919505

約47%となりました。

何も情報がない状態では ある馬が Aクラスの厩舎の所属馬であるかどうかは 厩舎数の分布から見積もった 11.8% ということになりますが 勝利というデータを観測することによってその確率は 47% に高まります。

1勝の場合

2勝ではなく1勝だったときの事後確率 P(win | Stable=A) は

P(win | Stable=A) = 11.8% = 0.118
P(win | Stable=B) = 6.7% = 0.067
P(win | Stable=C) = 3.3% = 0.033

P(win) = P(Stable=A) * P(win | Stable=A) + P(Stable=B) * P(win | Stable=B) + P(Stable=C) * P(win | Stable=C)
    = 0.06734000000000001

P(Stable=A | win) = P(win | Stable=A) * P(Stable=A) / P(win) 
    = 0.2988416988416988

となり、約30% となります。

勝利が続くことでAクラス厩舎である確率がより高まることが分かります。(30% → 47%)

分母は無視することが多い(らしい)

P(win, win) は 積分した結果であり P(Stable=A) を変数として含まないので分母は無視して計算することが多いようです。

実際に計算してみます。

分子のみを計算し、比率を求めます。

P(win,win | Stable=A) * P(Stable=A) = 0.013923999999999999 * 0.18 = 0.00250632
P(win,win | Stable=B) * P(Stable=B) = 0.004489000000000001 * 0.56 = 0.0025138400000000007
P(win,win | Stable=C) * P(Stable=C) = 0.0010890000000000001 * 0.26 = 0.00028314000000000003

P(Stable=A | win,win) = P(win,win | Stable=A) * P(Stable=A) / 
                                     (P(win,win | Stable=A) * P(Stable=A) 
                                   + P(win,win | Stable=B) * P(Stable=B)
                                   + P(win,win | Stable=C) * P(Stable=C)) 
    = 0.47259630795919505

分母も使って計算したときと同じ47%になりました!

AzureのGPUインスタンスを立てる

Azureのアカウントを持っているのでAzureでもGPUインスタンスを立ててみました。

fast.ai に Azure用GPUインスタンスセットアップのドキュメントスクリプトが用意されていたので基本的には楽チンにセットアップができます。

が一部ハマったのでメモ

install-gpu-azure.sh
# 8行目あたり
# sudo apt-get -y install cuda
sudo apt-get -y install cuda-8-0 # http://forums.fast.ai/t/setup-problems-azure/5879

# 22行目あたり
# pip install theano
pip install theano==0.9.0

CUDA8.0を明示的に指定しないとCUDAの最新版(CUDA9.0)がインストールされて次のエラーが出ちゃいました。

Exception: The nvidia driver version installed with this OS does not give good results for reduction.Installing the nvidia driver available on the same download page as the cuda package will fix the problem: http://developer.nvidia.com/cuda-downloads

Theanoも同様にバージョン指定してやる必要がありました。 バージョンを指定しないと 1.0.0 がインストールされて実行時に次のエラーが出ます。

ValueError: You are tring to use the old GPU back-end. It was removed from Theano. Use device=cuda* now. See https://github.com/Theano/Theano/wiki/Converting-to-the-new-gpu-back-end%28gpuarray%29 for more information.

1.0.0用の設定や修正をすれば動くようになると思いますが触らぬ神に祟りなしということで0.9.0を入れることとしました。

AWSと比べて

スペック比較

Azure (NC6) AWS (p2.xlarge)
vCPU 6 4
RAM (GiB) 56 61
GPU 1 (Tesla K80) 1 (Tesla K80)
Price ¥110.57/hour 0.900 USD/hour

https://azure.microsoft.com/ja-jp/pricing/details/virtual-machines/windows/ https://aws.amazon.com/jp/ec2/instance-types/p2/

ほぼ同等のスペックのはずなのですが学習を実行させるとなぜかAzureが遅かったです。。(後述)

課金体系

ちょっと前まではAWSが時間単位での課金だったので圧倒的にAzureに軍配が上がっていましたが

jp.techcrunch.com

の記事にあるようにAWSが10/2から秒単位での課金になったようです。

Azureは分単位ですが趣味の勉強の範囲では分単位か秒単位かはあまり気になりません。 でも、ビジネスとしてプロダクトで使うときは気にするんだろうなと思います。

CNMEM

http://forums.fast.ai/t/performance-issues-in-azure-800-seconds-for-lesson-1/2815/3 にも書いてあるようになぜか学習にかかる時間が遅い・・・ ╮(•ω•)╭

[global]
device = gpu
floatX = float32
[lib]
cnmem = 0.95

な感じで .theanorc を編集し、CNMEMを有効にするとAWSとほぼ同等の速度になりました。

Lesson1の The punchline: state of the art custom model in 7 lines of code の学習時間の比較
Azure (NC6) AWS (p2.xlarge)
CNMEM disabled 1107 sec 772 sec
CNMEM enabled 95% 686 sec 647 sec

AzureはVMのディスクタイプでSSDを選ぶとGPUインスタンスであるNC6インスタンスが選べず、 GPUを使いたい場合はHDDしか選択できないのですがそのせいなんでしょうか??

毎回IPアドレスが変わる

https://blogs.technet.microsoft.com/mskk-cloudos/2016/04/06/azure-ip/

● パブリックIPアドレスの値を決めることはできない 「パブリックIPアドレス」ではIPアドレスの値そのものを利用者が決めることはできません。

とあるので固定IPは無理のようです。

ただ、DNSを設定することはでき

http://[YourDnsName].eastus.cloudapp.azure.com:8888/というようなURLで jupyter notebookを開けます。

DNSの設定
  1. リソースからパブリックIPアドレスを選択
  2. 左側にある 構成 をクリック
  3. DNSラベルをつける

ディスクサイズが小さい

デフォルトでは30GBになります。 一見、十分そうですがfast.aiの課題をやっているとすぐに枯渇しちゃいます。 dogscatsの次のデータセットのstate-farmをダウンロードして展開しようとしたら既に100%になってしまいました。。

ディスクサイズの増やし方

超簡単です。

  1. リソースからディスクを選ぶ
  2. サイズを変更し、保存する (シャットダウンした状態で)
  3. VMを再起動する

Step by Step: how to resize a Linux VM OS disk in Azure (ARM) – Cloud Solution Architect

CentOSとか他のOSだと作業がいろいろとあるようですがubuntuだとこれだけです。 ubuntu素敵。( •ᴗ•)

Jupyter Notebookを印刷するときにレスペーパー化する

fast.aiの Practical Deep Learning for Coders は Jupyter Notebookで教材が提供されています。 コードは紙に出力したい派なのでipynbファイルを紙に印刷してそれを見ながら勉強しています。

ただ、

以下のようなTraining時の長い出力がダラダラと含まれていたりで

f:id:sanshonoki:20171115045932p:plain:w400

俯瞰できずに読みにくいし、印刷ページも無駄に増えて何か勿体ない ! ><

というのがあり、印刷時に不必要な部分をカットできるツールを作りました。

github.com

具体的には Trainingの出力のうち、最初と最後のepochの出力だけを残して後はカットします。

このツールを使うと、先ほどのページが

f:id:sanshonoki:20171115050218p:plain:w400

というような感じになり、とてもスッキリ〜 ( •ᴗ•)

MNISTのノートブック、mnist.ipynb が 何と 13ページ → 8ページ に! w

印刷代も30円節約できます。。

いきなりHerokuにRailsアプリがデプロイできなくなった..

密かに運営している POG集計サービス「Poger mailing service」の中で行っているスクレイピング処理が今日から誤動作し始めちゃいました。。

これはhtmlの出力仕様が変わったに違いない! (当たり)

急いで不具合を修正して、

Herokuに git push heroku master でいつものようにデプロイしたら

Unable to negotiate a key exchange method

というエラーが出てデプロイできなくなってしまった..! Oh, my god...

何だコレ?

エラーメッセージで検索してみると 公式ページに 「弱い暗号化アルゴリズムは使えなくした」とある。

https://devcenter.heroku.com/changelog-items/1311

We strengthened the SSH transport for our Git service by disabling the following algorithms:

解決法

https://stackoverflow.com/questions/46834011/suddenly-cant-push-to-heroku-unable-to-negotiate-key-exchange-method にあるように

$ brew install openssh

で解決 !!

ふぅ、焦った。。