のんびりしているエンジニアの日記

ソフトウェアなどのエンジニア的な何かを書きます。

Kaggleのコンペティションで公開されている手法・ソースコードのリンクをまとめてみた Part2

皆さんこんにちは
お元気ですか。私は元気です。

今日は前回の以下のページからだいぶ更新が立ち、Kaggleのコンペ的にも多くの開催がありました。
そこで、新しいページでリンクを纏めてみました。
中にはインタビューやフォーラム、githubなど様々なものが混合しているのはお許し下さい。

nonbiri-tereka.hatenablog.com

Airbnb New User Bookings

2nd-GitHub - Keiku/kaggle-airbnb-recruiting-new-user-bookings: 2nd Place Solution in Kaggle Airbnb New User Bookings competition
こちらは日本人の方でKaggle Meetupでの発表資料があります。
sssslide.com

Neural Networkでの失敗経験やアンチパターンを語る

皆さんこんにちは
お元気ですか。私は元気です。

今日は珍しくNeural Networkを使っていく上での失敗経験について語ります。
学習の時に案外、失敗するのですが、だいたい原因は決まっています。そう大体は・・・
ということで、今回は失敗の経験、アンチパターンのようなものを書こうと思います。

Trouble1:学習時にNanを叩き出す。

こんな経験よくあるのでは無いでしょうか?
なぜかよくわからないけれども学習していると気がついたらNanになっていることがあります。

原因1 cross-entropy誤差を使っている。

まずは、cross-entropy誤差関数を見てみましょう。
(今回はTheanoのbinary cross entropyを取り上げます)

{ \displaystyle
crossentropy(t,o) = -(t\cdot log(o) + (1 - t) \cdot log(1 - o))
}

実はこの数式、nanを出す要因があります。対数に着目してみます。
対数は0を入力するとnanが返ってきて、計算不可能な値となります。
何らかの原因で、対数に0が入力され、計算がおかしくなっています。

この場合はsigmoid関数やnp.log1pの関数を使い、関数に一工夫が必要になります。
しかし、sigmoid関数についても、nanが出力される場合があります。

原因2 結果が小さすぎて、0と認識される。

原因1の事象はsigmoid関数を使っている場合でも発生します。
sigmoid関数は∞に大きくなれば0や1を計算することが可能です。
Neural Networkの計算において、sigmoid関数はfloat32の場合において、小さすぎて0と算出されます。
これが原因になっていることがあります。

以下のコードを書いて検証してみました。

import numpy as np
def sigmoid(z):
	return 1/(1+np.exp(-z))

sigmoid(-500) #7.1245764067412845e-218
sigmoid(-500).astype(np.float32) #0.0

原因3 重みがあらぬ方向へ学習する。

回帰式を計算した時によくあることなのですが、
重みが凄い方向へ学習してそのままinfを叩き出し、nanになることがあります。

Trouble2:収束しない

作った関数が収束しない、これもよくあることです。
大体原因は決まっている気がします。

原因1 学習率が高すぎる

学習率が高過ぎると重みがぶれ、学習ができません。
あまりにも遅ければ上げてみる、もしくはAdamなど、自動的に学習してくれる
学習法を使ってみると良いと思います。

chainerのMNISTのexample(
chainer/train_mnist.py at master · pfnet/chainer · GitHub)
に対して学習方法をSGDにして、学習率1000000000.0とした場合の標準出力
正当率があがらず、誤差もnanが出続けています。

epoch 1
graph generated
train mean loss=nan, accuracy=0.0988166666403
test  mean loss=nan, accuracy=0.0979999999329
epoch 2
train mean loss=nan, accuracy=0.0987166666767
test  mean loss=nan, accuracy=0.0979999999329
epoch 3
train mean loss=nan, accuracy=0.0987166667202
test  mean loss=nan, accuracy=0.0979999999329

原因2 学習率が低すぎる

学習率が低すぎると、学習が遅くなります。
対策としては原因1と同じです。見なおしてみてください。

chainerのMNISTのexampleに対して学習方法をSGDにして、学習率0.0000001とした場合は以下になります。
縦軸が誤差、横軸がepoch数、で学習が殆ど進んでいないことが見てわかります。

f:id:tereka:20160310001616p:plain

対して学習率を0.1とした場合の誤差は以下のようになります。

f:id:tereka:20160310000439p:plain

個人的にこのパラメータは0.01周りを使っていればトラブルが少ないと考えています。

原因3 適切な誤差関数ではない

誤差関数がよくないといった失敗をやったことがあります。
基本的に誤差関数は、誤っている場合の誤差が大きければ、大きいほど、学習の進みがよくなります。
マルチラベルの場合最小二乗誤差ではなく、cross-entropyを使わなければ、収束がすごく遅いです。

因みにこれを私はcross-entropyと最小二乗誤差でやりました。

原因4 活性化関数を誤った

昔、回帰の問題を解いていました(100,300などを予測)が
盛大な誤差関数を出力するが、誤差落ちなかったことがありました。

モデルを調べた結果、なぜかsigmoid関数を使っていたことがありました。
全然減らないと思った時は最後の出力の活性化関数を見直すのも良いでしょう。

因みに負の値の予測が必要な時にReLuを使うのもよろしくありません。式を見れば一目瞭然ですが・・・

原因5 そもそも入力が誤っている

画像に変な処理をかけたことで、入力が誤っていることがあります。
例えばuint8に対してintの処理をすることで期待と異なる結果が返る場合があります。

2 / 100 #0
2 / 100.0 #0.02

Trouble3:Validation Scoreが低い

Neural Networkを作ったけれども精度が出ない、そんな失敗もあります。

原因1 過学習しているにも関わらず、気づかなかった。

Early Stoppingを実施しなかった時によく起こる問題だと思います。
Early Stoppingを実施しない場合は、一定の間は普通ですが、
あるときから、Validation Scoreが低下していきます。

原因2 与えるデータとラベルの1対1が誤っている

与えるデータとラベルを何らかの処理で取得している場合
1対1が間違っている可能性があります。

例えば、与えるデータとラベルをうっかりValidationの時に各々でシャッフルするなどあります。
あまりにも学習データと乖離しているときは、与えているデータが違っていないか疑ってみるのもよいでしょう。

Trouble4:正しい入力を与えたはずなのに、ライブラリから変なエラーを吐かれる場合

これは、どちらかというとライブラリ周りの話です。

原因1 モデル構築誤り

この場合はだいたい、入力誤りです。
連結している部分のfor文あたりが間違っている事が多いです。

例えば、連結が途切れているなどはこの場合に含まれるでしょう。

原因2 入力データのデータ形式誤り

ライブラリによって異なりますが、regressionとclassificationでは大体入力の方法が異なっています。
これに気づかずにやるとデータの形式にハマります。
時々、ConvolutionやDense Layer(Liner Layer)の対応マップが誤って出ることもあります。

Theano関連のライブラリだと以下の様な結果が出ることでしょう。

ValueError: Input dimension mis-match. (input[0].shape[1] = 2, input[1].shape[1] = 1)

Apply node that caused the error: Elemwise{sub,no_inplace}(Elemwise{Composite{tanh((i0 + i1))}}[(0, 0)].0, <TensorType(float64, matrix)>)
Inputs types: [TensorType(float64, matrix), TensorType(float64, matrix)]
Inputs shapes: [(50, 2), (50, 1)]
Inputs strides: [(16, 8), (8, 8)]

Python: keras shape mismatch error - Stack Overflowより

因みに大体のライブラリで回帰問題とクラス分類問題によって決まっており、
以下の入力をすれば、特段問題はないと思います。

[[1],[3],[4]] #regression
[1,2,3,4] #classification

まとめ

大体上記4つのトラブルに失敗の経験は含まれると思います。(私はそうでした)
皆さんの失敗経験がこれで減りますように。
他に失敗経験があったらぜひ教えて下さい。

Chainerにおけるグラフ構造をループで書いてみる。

皆さんこんにちは
お元気ですか。私は元気です。

実は私、Chainerでのfor文でLinkとして作成できることを知らず、
今の複雑なネットワークにChainer使いにくいと思っていましたが、以下にサンプルがあって
こうすれば複雑なネットワークも組めるんだ。みたいなところがわかりました。

Deep Residual Network definition by Chainer · GitHub

ChainerのLink構造について

以下のスライドにChainer version1.5のチュートリアル解説があります。
このうち今回で必要な情報はChain、Link、Functionが何を示しているのかです。

www.slideshare.net

chainerの関数 概要
chainer.Function 関数
chainer.Link パラメータ付き関数
chainer.Chain パラメータ付き関数集合

これに基づいて、関数集合を構築していけば良いといったところです。

※上記の関数はv1.5以降です。1.4以前では多分異なるので気をつけてください。

グラフ構造を容易にかくには

部分構造を構築する。

通常に計算できるパラメータ付き関数集合を作りつつ、Linkに突っ込めば実装可能です。
後は、linkから必要な情報を取り出し、forwardを構築するのみです。

今回はVGGNetを用いて実施してみます。
VGGNetの部分構造であれば、以下のように書くことができます。
例えば、ニューラルネットワークのとある箇所を部分的に書くと以下のようになります。

class RoopBlock(chainer.Chain):
    def __init__(self,n_in,n_out,stride=1):
        super(RoopBlock,self).__init__(
            conv1 = L.Convolution2D(n_in,n_out,3,stride,1),
            conv2 = L.Convolution2D(n_out,n_in,3,stride,1),
            conv3 = L.Convolution2D(n_out,n_in,3,stride,1)
        )

    def __call__(self,x,t):
        h = F.relu(self.conv1(x))
        h = F.relu(self.conv2(h))
        h = F.relu(self.conv3(h))

        return h

Chainと呼ばれる関数集合を構築することをまず行います。
これをforなどを使ってうまく書くと、VGGNetを以下のように記述することができます。

class VGGNet(chainer.Chain):
    def __init__(self):
        super(VGGNet,self).__init__()

        links = [("root0",RoopBlock(3,64))]
        n_in = 64
        n_out = 128
        for index in xrange(1,5,1):
            links += [("root{}".format(index),RoopBlock(n_in,n_out))]

            n_in *= 2
            n_out *= 2
        links += [("fc"),L.Linear(25088, 1000)]
        self.forward = links
        for link in links:
            self.add_link(*link)

        self.train = True

    def __call__(self, x, t):
        for name,func in self.forward:
            x = func(x)
        if self.train:
            self.loss = F.softmax_cross_entropy(x,t)
            self.accuracy = F.accuracy(x, t)
            return self.loss
        else:
            return F.softmax(x)

この書き方によるメリットは層を一つ増やしたいとなった場合に簡単に追加できることです。
add_linkを使うことで、パラメータをリンクとして登録しておきます。

ResNetを実際に実験するにあたって調べてて見つけた内容ですが、
この方法はチュートリアルにも掲載されていないので、あんまり見つけられないかもしれません。

参考文献は以下の通り。

GitHub - mitmul/chainer-cifar10: Various CNN models including Deep Residual Networks (ResNet) for CIFAR10 with Chainer (http://chainer.org)

Pythonで書ける便利な書き方を紹介します。

皆さんこんにちは
お元気ですか。私は元気です。

今日はPythonにおける便利記法を書いてみます。
他の言語しか使ったこと無い人もぜひ

※1/25間違いかと思われる箇所の加筆修正を実施

リストに関するイテレーション

enumerate(インデックスとオブジェクトのループ)

enumerateはiterate回数とオブジェクトを同時にループの記載をすることができます。
要素のi番目を取得してループを書くと以下の記載方法になります。

list = [1,2,3,4,5,6]

for i in xrange(len(list)):
    print i,list[i] 

enumerate関数を使うとより、直感的に記載することができます。

for i,number in enumerate(list):
    print i,number

zip(同時に2つのオブジェクトでループ)

zipを使用して固めて、2つ以上の配列をiterationする。
一つだけでなく、セットで固めることができます。

list,list2 = [1,2,3,4],[3,4,5,6]
for i in xrange(len(list)):
    print list[i],list2[i]

これをzipを使って書くと次のようになります。

list,list2 = [1,2,3,4],[3,4,5,6]
for num1,num2 in zip(list,list2):
    print num1,num2

因みにzipとenumerateを組み合わせ、何番目かつ、zipにより固めることができます。

list,list2 = [1,2,3,4],[3,4,5,6]
for index,(num1,num2) in enumerate(zip(list,list2)):
    print index,num1,num2

yield(途中で一旦返す)

yield記法は個人的にバッチ学習の時に使うことが多いです。
直感的にわかりにくいのですが、yieldの時に一回値を返すといったことをします。

def iteration_list(list,batch=2):
    for i in xrange(0,len(list),batch):
         yield list[i:i+batch]

for obj in iteration_list():
    print obj #list[i:i+batch]が表示される。

for-else(forで途中で停止しない場合の条件)

私もこれを作成している時に初めて知ったのですが、
forにelse文をつけることで、breakされなかった時の処理を記述することができます。

for i in xrange(10):
    if i == 100:
        break
else:
    print i #実行される

リスト内包表記

リストの内部にfor文を書き、短く書くことができます。
次に記載するのは0~9までの数字を持ったリストです。

print [i for i in xrange(10)]

また、条件式を使い特定の値を取得することができます。
通常のリストを構築し、appendを実施するより高速に動作するので、単純な処理であればこちらを記述してみましょう。

print [i for i in xrange(10) if i == 2]

Collections

Counter

import collections

counter_list = ["a","b","c","d","e","a","a"]
counter = collections.Counter(counter_list)
print counter
print counter["a"]

default dict(辞書のデフォルト値設定)

一言で説明すると辞書に存在しないキーの初期値を決めることができます。
通常であれば、以下の記述をすることになると思います。

dict = {}
if "a" in dict:
    dict["a"] += 1
else:
    dict["a"] = 0

しかし、collectionsを使うことで簡単に記述ができます。

import collections
dict = collections.defaultdict(int)
dict["a"] += 1

print dict

Sort(ソート)

sorted

sortedを使うと、色々なオブジェクトをソートすることができます。
昇順、降順は

list = [1,4,3,6,5,8]
print sorted(list)
print sorted(list,reverse=True)

出力

[1, 3, 4, 5, 6, 8]
[8, 6, 5, 4, 3, 1]

辞書をソートするコードは以下になります。

dict = {"a":1,"b":2,"c":1}
print sorted(dict.items(),key=lambda x:x[1],reverse=True)
[('b', 2), ('a', 1), ('c', 1)]

因みにソートしたいキーをkeyに選択すれば良いので、基本的にどんな
オブジェクトでもソートすることができます。

if文の書き方

Pythonでif文の条件は連結して書くことができます。
どういうことかといいますと、以下の例を見てみます。
これは、変数aが10より高い、かつ、100未満の時のプログラムにおける一般的な記載方法です。

a = 70
if a > 10 and a < 100:
    print "True"
else:
    print "False"

しかし、Pythonでは、以下のように条件を連結して書くことができます。

if 100 > a > 10:
    print "True"
else:
    print "False"

Fileに関する処理(with)

fileに関する処理は通常以下のように書き、自分の手でクローズしなければなりません。

f = open("test.txt")
#何らかの処理
f.close()

しかし、with statementの導入により、自分でクローズをする必要がなくなります。

with open("test.txt") as f
    print f.read()
    #何らかの処理

in(オブジェクトに存在しているかどうか)

文字列やリストに存在しているかどうか、調べるにはどうすればよいでしょう?
自分で書くには、以下のように書かなければなりません。

searched_number = 4
for i in [1,2,3,4,5,6]:
    if i == searched_number:
        print "find!"
        break

しかし、Pythonにはinと呼ばれるステートメントがあり、以下のように記載することができます。

searched_number = 4
if searched_number in list:
    print "find!"

関数について

返り値を複数返す。

実はPythonは返り値を複数返すことができます。

def return_function(a,b):
    return a,b

関数のオブジェクト化

関数はオブジェクトにすることができます。
条件分岐とかでどの関数を実行するかを選択する場合に主に利用しています。

def sum(a,b):
    return a + b

def diff(a,b):
    return a - b

func_obj = sum
print func_obj(1,2)

Pythonで少なくメモリを使用する方法

皆さんこんにちは
お元気ですか。私は元気です。

今日はPythonにおけるメモリ少なく使う方法を紹介したいと思います。
なぜ、そんな方法を書くに至ったか。それは、こんなエラーをしょっちゅう見ているからですね。

Traceback (most recent call last):
  File "lasagne_wheal.py", line 48, in <module>
    prediction = model.predict_proba(np.array(X_test))
MemoryError

画像処理を行っている人間ならよくやりがちかもしれませんが、
要はint8で持っている情報をfloatに変換してデータが膨れ上がったことによってメモリが
不足していることから発生しています。

Convolutional Neural Networkを大きな画像で実施しようとするとよく発生するのではないでしょうか。
さて、今回はこれを避ける方法をご紹介したいと思います。

1.必要ないとき以外はデータをfloatで保持しない。
2.手動GCをする。
3.バッチサイズを下げる(CNN)
4.画像サイズ自体を落とす。

メモリリークを避ける方法

1.必要ないとき以外はデータをfloatで保持しない。

要は必要ない時以外はデータを保持しなければ良いのです。
例えば、バッチごとにfloatに変換するなどがあります。一部分ずつfloatにすることにより、
不要なデータをメモリで持つ必要がなくなります。

2.手動GC

Pythonでは、基本的にメモリ解放は自動的に行います。
しかし、メモリ解放は自動的に行われるので、メモリ管理を原則
気にせずに管理することができます。

ただ、手動でガーベジコレクション(GC)を行う方法があります。
因みにガーベジコレクションは、使っていないメモリを解放させる手法です。
GCの方法は色々ありますが、今回は考えないでおきます。

import gc
import numpy as np

a = np.zeros((100,100))
del a
gc.collect()

メモリを解放したい対象について delを実施し、その後にgc.collect()を呼び出すことにより
確保している使用しないメモリを再利用することができます。

バッチサイズを下げる。

GPUで、メモリが割り当てられないエラーが出てくる時の対策です。
Convolutional Neural Networkにおいて入力バッチが大きいとGPUで計算させる時にメモリーリークが
発生することがあります。
その時は入力するときのバッチを下げましょう。そうするとよくなります。

画像サイズを落とす。

バッチサイズを下げたくない時の対策です。
例えば、画像サイズを半分落とすことにより、画像データのサイズを1/4まで下げることができます。

まとめ

GPUやCPUのメモリが少ないと機械学習では苦労しますが、
工夫次第では、システムを動作させることができるので、メモリが足りない時は工夫してみましょう。