2017/09/17

Pythonのyield構文

Pythonのyield構文について、どういう動作をしているのか簡単に調べてみました。Python 3.5で調べています。

forループで使用


3回yieldで返す関数genを作成し、forループで値を取得してみました。
1
2
3
4
5
6
7
8
9
def gen():
    yield 1
    yield 3
    yield 2
    
g = gen()
for v in g:
    print(v)
print("End")
実行すると、
1
3
2
End
が得られました。yieldで返した値が順に表示されます。

次に、無限に出力できるようにしてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def gen():
    x = 0
    while True:
        x += 1
        yield x

g = gen()
for v in g:
    print(v)
print("End")
実行すると、1, 2, 3,...と順に1ずつ値を増やしながら停止するまで無限に整数を出力し続けます。

nextを使用


forループを使わず、1回ずつ呼び出してみます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def gen():
    yield 1
    yield 3
    yield 2
    
g = gen()
try:
    c = 1
    print(next(g))
    c = 2
    print(next(g))
    c = 3
    print(next(g))
    c = 4
    print(next(g)) # raise a StopIteration exception
    c = 5
    print(next(g))
    c = 6
except StopIteration:
    print("End")
print("c =", c)
実行結果は、
1
3
2
End
c = 4
となります。yieldが返すことが出来るのは3回だけなので、4回目のnextで例外が発生します。

次に、グローバル変数aの値を変更・参照しながら、gen()を呼び出してみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
a = 0
def gen():
    c = 1
    yield c + a
    c += 2
    yield c + a
    c -= 1
    yield c + a
    
g = gen()
try:
    c = 1
    print(next(g))
    a = 10; c = 2
    print(next(g))
    a = 100; c = 3
    print(next(g))
    c = 4
    print(next(g)) # raise a StopIteration exception
    c = 5
    print(next(g))
    c = 6
except StopIteration:
    print("End")
print("c =", c)
実行結果は、
1
13
102
End
c = 4
となります。nextを呼び出す前にaを変更していますが、反映されていますね。cは固定値ではないですが、正しく前の値を覚えてくれています。

動作の仕組みは?


内部の仕組みを調べたわけではありませんので、以下は、事実と異なる可能性が高いのでご注意を。

上のコードのように、nextを呼び出すとgenが実行され、yieldに到達すると、yieldで指定されている値を返す直前に、yieldの次に実行すべき行番号や関数内でのみ使われている変数が保存されているのでしょう。次にnext経由で当該関数が呼び出されると、保存していた行番号や変数の値を復元し、続きを実行しているのだと思われます。

2017/09/15

ダイソーの乾電池

ずいぶん昔に買った、ダイソーの乾電池を使おうと思ってみてみたら
色々残念なことになっていました。

型番は
スーパーアルカリ乾電池
単4型AAA LR03
KW-13-80
T097
アルカリ電池No.14
で、使用推奨期限が2018年8月。

6本入りのうち、4本が元のパッケージに入った状態で新品として残っていました。
しかし、保管状況が悪かったのか、1本は液漏れ、2本は放電してしまっていて
電圧が低すぎて使えず、なぜか1本だけ正常でした。

品質のばらつきが大きすぎですね。

安い電池に文句を言っても仕方がありませんが、
ストック用に買うのはいまいちなようです。

ニューラルネットワークで正弦波にフィット(その2)

前回の正弦波フィットでは、フィット具合がいまいちでした。そこで、どういうパラメータにすると、どの程度まで正弦波にフィットできるのかを調べてみました。

前回と同様、\(y=\sin(2x)+n\) (\(n\)がノイズ)からサンプルしたデータにフィッティングをして、\(y=\sin(2x)\)を学習できるか試してみました。\(x\)の値の生成範囲は[-10, 10)です。モデルのパラメータは下表の値の組み合わせで、合計72パターン試しました。

Parameter nameValues
Activation typetanh, relu
# of units for each layer1, 2, 4, 8, 16, 32
# of hidden layers1, 2, 3, 4, 5, 6

活性化関数が tanh の場合


まずは、Activationをtanhにした場合です。u1は各レイヤーのユニット数が1で、f2は元の関数が\(\sin(2x)\)であることを表します。図中の各系列名のlayerの数値は隠れ層-1を表します。背景の薄い青色の正弦波が学習データの元のになった正弦波です。これに近いほど、うまく学習できていることになります。
ユニット数が1なので、layerが1だと、 \[y=w_2 \tanh(w_1 x + b_1) + b_2\] になるため、単に\(\tanh\)の平行移動と拡大縮小ができるだけです。

ユニット数が2つになると、次の図のように、原点の前後の正弦波の山に近づくようにフィットできるようになります。

隠れ層が1(図中ではlayer=0)の場合、\(\tanh\)を2つ、拡大縮小移動して重ね合わせて作ることができます。 隠れ層が2なら、\(\tanh\)を4つ重ね合わせていることになります。隠れ層が1増えるたびに使える\(\tanh\)は2倍になるので、フィットしやすくなります。しかし、この数では原点の前後の正弦波の山にフィットできるのみでした。

ユニット数が4つになると、原点から2つ目までの山と谷にフィットできそうになります(下図)。

ユニット数が8つになると、隠れ層が6層なら、原点の前後3個の山と谷にフィットでき、4つめはもう少しでフィットできそうです(下図)。

ユニット数が16個になると、隠れ層が6層なら、原点の前後5個の山と谷にフィットできます。

ユニット数が32個になると、隠れ層が4層以上なら原点の前後7個の山と谷にフィットできます。これは、学習データの存在範囲の正弦波にフィットできていることを表します。

活性化関数が relu の場合

次に、reluの場合です。ユニット数が1の場合は、\(\tanh\)の場合と同じで、ReLUそのものの形を拡大縮小と平行移動させたグラフになります。

ユニット数が2に増えると、隠れ層の数によっては折れ曲がる箇所が増えます。

ユニット数が4に増えると、山が大きめになります。

ユニット数が8に増えると、隠れ層の数によっては、原点の前後の1つ目の山または谷に近い形になります。

ユニット数が16に増えると、隠れ層の数が6の場合、学習データの\(x\)の範囲である[-10, 10)の範囲では、不完全ながらも正弦波に追従しています。

ユニット数が32の場合、隠れ層の数が5または6なら、正弦波をかなり近似できているように見えます。

ユニット数と隠れ層の数がそこそこ大きくなれば、活性化関数が\(\tanh\)でもReLUでも今回の条件の正弦波からサンプリングしたデータから元の正弦波に近いモデルを学習できることが分かりました。

ソースコード


学習時のソースコードは以下の通りです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# This script trains a neural network model using MNIST.
import keras, os, math
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

TRAIN_EPOCH = 100

# Model for fitting
#  nunit : # of units for each dense layer
#  nlayer: # of hidden layers - 1
#  act   : activation type
def approx_model(nunit, nlayer, act):
    model = Sequential()
    model.add(Dense(nunit, input_shape=(1,)))
    model.add(Activation(act))
    for v in range(nlayer):
        model.add(Dense(nunit))
        model.add(Activation(act))
    model.add(Dense(1))
    print(model.summary())
    return model

# Make samples on sine curve with noise
def sine_dist(freq):
    xd = np.random.rand(10000,)*20-10
    yd = [math.sin(x*freq)+np.random.normal(0, 1) for x in xd]
    return xd, yd

if __name__ == '__main__':
    if not os.path.exists("models"):
        os.mkdir("models")

    for act in ['tanh', 'relu']:
        for nunit in [1, 2, 4, 8, 16, 32]:
            for nlayer in [0, 1, 2, 3, 4, 5]:
                # Make a model
                model = approx_model(nunit, nlayer, act)
                opt = keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                              epsilon=1e-08, decay=1e-4)
                model.compile(optimizer=opt, loss='mean_squared_error')
                model.save_weights("initial-weights.hdf5")

                for freq in [2]:
                    # Make data for training and validation
                    x_train, y_train = sine_dist(freq)
                    x_val, y_val = sine_dist(freq)
                    
                    # Train and save a model
                    model.load_weights("initial-weights.hdf5")
                    model.fit(x_train, y_train, validation_data=(x_val, y_val),
                              epochs=TRAIN_EPOCH, batch_size=32)
                    model.save("models/sine-{0}-u{1}-l{2}-f{3}.hdf5".format(act, nunit, nlayer, freq))

また、Jupyter notebookでグラフを表示させるコードは次の通りです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
%matplotlib inline
import keras, math
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-20, 20, 0.001)
for act in ['tanh', 'relu']:
    for u in [1, 2, 4, 8, 16, 32]:
        plt.figure(figsize=(15, 10))
        plt.title('{0}-u{1}-f2'.format(act, u))
        plt.grid()
        yc = np.array([math.sin(xi*2) for xi in x])
        plt.plot(x, yc, color='lightblue')
        for l in range(6):
            model = keras.models.load_model(
                'models/sine-{0}-u{1}-l{2}-f2.hdf5'.format(act, u, l))
            y = model.predict(x)
            plt.plot(x, y, label='layer={0}'.format(l))
        plt.legend(loc='upper left', frameon=False)
        plt.show()

2017/09/13

ニューラルネットワークで正弦波にフィット

ニューラルネットワークを使って、正弦波のデータ\(y=\sin(fx)\)にノイズをのせた、\(y=\sin(fx)+n\) (\(n\)がノイズ)からサンプルしたデータにフィッティングをして、\(y=\sin(fx)\)を学習できるか試してみました。\(f\)は周波数の大小を表します。

モデルは、直線フィッティングのときと同じです。

学習データを[-10,10)の範囲で一様乱数で10000サンプルとって、100エポック学習しました。 学習できたモデルの出力は\(f\)の値ごとに下図のようになりました。横軸がx、縦軸がyです。 薄い水色の線は、ノイズを加える前の学習データの生成に使った正弦波です。紺色の線が、学習したニューラルネットワークが生成した曲線です。

\(f=0.5\)

\(f=1\)

\(f=2\)

\(f=4\)

\(f=8\)

\(f=16\)

\(f=0.5\)や\(f=1\)はともかく、それ以降はフィットできていません。特に\(f=4\)以降は完全に崩れています。\(f=16\)では、そもそも周波数が高すぎて、図では元の正弦波がつぶれてしまっています。きちんとフィットさせるためにはニューラルネットワークをもう少し工夫する必要があることが分かりました。

\(f=4\)のときの学習データに使ったデータの生成と同じ条件で、ただし乱数違いで生成したデータをプロットしたものが下図です。横軸の範囲が異なることに注意してください。

\(f=4\) sampled with noise
まだ正弦波を見て取れるのですが、今回使ったニューラルネットワークでは難しいようです。

同様に、\(f=16\)のときの学習データに使ったデータの生成と同じ条件で、ただし乱数違いで生成したデータをプロットしたものが下図です。一応、正弦波の名残は残っていますが、元が正弦波とは言いがたいです。

\(f=16\) sampled with noise

今回の実験のモデルの学習時に使ったコードは以下です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# This script trains a neural network model using MNIST.
import keras, os, math
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

TRAIN_EPOCH = 100

def approx_model():
    model = Sequential()
    model.add(Dense(32, input_shape=(1,)))
    model.add(Activation('tanh'))
    model.add(Dense(32))
    model.add(Activation('tanh'))
    model.add(Dense(1))
    print(model.summary())
    return model

def sine_dist(freq):
    xd = np.random.rand(10000,)*20-10
    yd = [math.sin(x*freq)+np.random.normal(0, 1) for x in xd]
    return xd, yd

if __name__ == '__main__':
    if not os.path.exists("models"):
        os.mkdir("models")

    # Make a model
    model = approx_model()
    opt = keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                  epsilon=1e-08, decay=1e-4)
    model.compile(optimizer=opt, loss='mean_squared_error')
    model.save_weights("initial-weights.hdf5")

    for freq in [0.5, 1, 2, 4, 8, 16]:
        # Make data for training and validation
        x_train, y_train = sine_dist(freq)
        x_val, y_val = sine_dist(freq)
        
        # Train and save a model
        model.load_weights("initial-weights.hdf5")
        model.fit(x_train, y_train, validation_data=(x_val, y_val),
                  epochs=TRAIN_EPOCH, batch_size=32)
        model.save("models/sine-{0}.hdf5".format(freq))

グラフの描画に使ったコードは以下です(\(f=1\)の場合)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
%matplotlib inline
import keras, math
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-30, 30, 0.001)
model = keras.models.load_model('models/sine-1.hdf5')
y = model.predict(x)
yc = np.array([math.sin(xi*1) for xi in x])
plt.grid()
plt.plot(x, yc, color='lightblue')
plt.plot(x, y, color='midnightblue')
plt.show()

2017/09/11

ニューラルネットワークで直線にフィット

ニューラルネットワークを使って、直線のデータ\(y=x\)にノイズをのせた、\(y=x+n\) (\(n\)がノイズ)からサンプルしたデータにフィッティングをして、\(y=x\)を学習できるか試してみました。

Kerasを使って学習します。モデルは、

model = Sequential()
model.add(Dense(32, input_shape=(1,)))
model.add(Activation('tanh'))
model.add(Dense(32))
model.add(Activation('tanh'))
model.add(Dense(1))
としました。ただの直線フィッティングにしてはゴージャスな構成です。

学習データを[-10,10)の範囲で一様乱数で10000サンプルとって、100エポック学習したところ、学習できたモデルの出力は下図のようになりました。横軸が\(x\)、縦軸が\(y\)です。

学習データのある\(x\)が-10から10の範囲では目で見る限りにおいてノイズに惑わされず直線になっていますが、その範囲外は明らかに\(y=x\)ではありません。

学習用のコードは以下です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# This script trains a neural network model by using y=x with noise.
import keras, os
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

TRAIN_EPOCH = 100

def approx_model():
    model = Sequential()
    model.add(Dense(32, input_shape=(1,)))
    model.add(Activation('tanh'))
    model.add(Dense(32))
    model.add(Activation('tanh'))
    model.add(Dense(1))
    print(model.summary())
    return model

if __name__ == '__main__':
    if not os.path.exists("models"):
        os.mkdir("models")

    # Make data for training and validation
    x_train = (np.random.rand(10000,)*20-10).reshape(10000, 1)
    y_train = np.array([x+np.random.normal(0, 1) for x in x_train]).reshape(10000, 1)
    x_val = (np.random.rand(10000,)*20-10).reshape(10000, 1)
    y_val = np.array([x+np.random.normal(0, 1) for x in x_val]).reshape(10000, 1)

    # Make a model
    model = approx_model()

    # Train the model
    opt = keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                  epsilon=1e-08, decay=1e-4)
    model.compile(optimizer=opt, loss='mean_squared_error', metrics=['mse'])
    hist = model.fit(x_train, y_train, validation_data=(x_val, y_val),
                     epochs=TRAIN_EPOCH, batch_size=32).history

    # Evaluate the last model by train and validation data
    score_train = model.evaluate(x_train, y_train, batch_size=32)
    score_val = model.evaluate(x_val, y_val, batch_size=32)
    model.save("models/linear.hdf5")

図の表示用のコードは、Jupyter notebook用のコードで、以下の通りです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
%matplotlib inline
import keras
import matplotlib.pyplot as plt
import numpy as np
x = np.array([v/10 for v in range(-300,300)])
model = keras.models.load_model('models/linear.hdf5')
y = model.predict(x)
plt.grid()
plt.scatter(x, y, s=1)
plt.show()

2017/09/10

入力データ加工の効果

MNISTで学習するときの、入力画像の加工について実験をしてみます。
加工方法は2種類準備しました。1つは変形、もう1つは一部の消去です。

画像の変形については、Kerasに付属のImageDataGeneratorクラスを利用します。 パラメータは次のように設定しました。

datagen = ImageDataGenerator(
        rotation_range=20,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=math.pi/4, # 45 degree
        zoom_range=0.3,
        fill_mode="constant",
        cval=-1, # constant value for fill_mode
        )
これにより、MNISTの学習用画像を回転・移動・シアー・拡大縮小させます。

画像の一部の消去については、ランダムに8x8の四角形の領域を背景色に塗りつぶすようにしました。数字の一部が薄かったり、線の一部が見えない場合の画像の認識に対してロバストになることを期待しています。 この処理を行えるKerasに付属のクラスがなかったため、MaskedImageGeneratorというクラスをImageDataGeneratorを参考にして作成しました。詳細は、最後に示すコードで確認してください。

オプティマイザは、

Adamax(lr=0.002, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=1e-4)
を利用しました。今回利用している2層CNN+2層DenseのモデルでMNISTの学習をするときに、性能が良い傾向があるためです(以前の実験を参照)。

それでは、まず、学習時のLossの変化を下図に示します。

加工なしの場合(No augmentation)、Lossが非常に小さくなります。一方、変形(ImageDataGenerator)・一部消去(MaskedImageGenerator)については学習用データがランダムに変化するため、なかなか低くはなりません。

次に、Validation errorの経過を下図に示します。

変形については加工なしに比べてエラーが大きく、今回の実験に関しては、学習用画像の変形は有効な手法ではないことが分かります。 一方、画像の一部消去については、加工なしと同じか、若干エラーが低いように見えます。統計的に有意な差があるかまでは調べていません。

加工する場合のコードを以下に示します。加工なしの場合については、冗長なので省略しますが、画像の加工がない以外の差はありません。違いは、fit_generatorではなくfitを利用するようにする部分のみです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# This script trains a neural network model using MNIST with image augmentation
import gc, keras, os, re, datetime, math
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape
from keras.layers.convolutional import Conv2D
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Flatten, Dropout
from keras.datasets import mnist
from keras.preprocessing.image import ImageDataGenerator, NumpyArrayIterator

TRAIN_EPOCH = 100
BATCH_SIZE = 32
MASK_WIDTH = 8  # Width of the region to be erased
MASK_HEIGHT = 8 # Height of the region to be erased
USE_MASKED_IMAGE_GENERATOR = True # If False, use ImageDataGenerator

# Image size of MNIST
IMAGE_WIDTH = 28
IMAGE_HEIGHT = 28

# Define a discriminator model for numbers
def num_discriminator_model():
    model = Sequential()
    model.add(Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                     input_shape=(IMAGE_WIDTH, IMAGE_HEIGHT, 1),
                     data_format='channels_last'))
    model.add(LeakyReLU(0.2))
    model.add(Conv2D(128, (5, 5), strides=(2, 2), data_format='channels_last'))
    model.add(LeakyReLU(0.2))
    model.add(Flatten())
    model.add(Dense(256))
    model.add(LeakyReLU(0.2))
    model.add(Dropout(0.5))
    model.add(Dense(10))
    model.add(Activation('softmax'))
    print(model.summary())
    return model

class MaskIterator(NumpyArrayIterator):
    def __init__(self, x, y, masked_image_generator,
                 batch_size=32, shuffle=False, seed=None,
                 data_format='channels_last',
                 save_to_dir=None, save_prefix='', save_format='png'):
        super(MaskIterator, self).__init__(x, y, masked_image_generator,
                                           batch_size, shuffle, seed,
                                           data_format, save_to_dir,
                                           save_prefix, save_format)

# Erase the part of an image randomly
class MaskedImageGenerator(ImageDataGenerator):
    def __init__(self, mask_width=MASK_WIDTH, mask_height=MASK_HEIGHT):
        self.mask_width = mask_width
        self.mask_height = mask_height

        # channels_last
        self.channel_axis = 3
        self.row_axis = 1
        self.col_axis = 2

    def flow(self, x, y=None, batch_size=32, shuffle=True, seed=None,
             save_to_dir=None, save_prefix='', save_format='png'):
        return MaskIterator(
            x, y, self,
            batch_size=batch_size,
            shuffle=shuffle,
            seed=seed,
            data_format='channels_last',
            save_to_dir=save_to_dir,
            save_prefix=save_prefix,
            save_format=save_format)

    def random_transform(self, x, seed=None):
        img_row_axis = self.row_axis - 1
        img_col_axis = self.col_axis - 1
        img_channel_axis = self.channel_axis - 1
        if seed is not None:
            np.random.seed(seed)

        # Select the upper left corner of the box to be erased
        row = np.random.randint(x.shape[img_row_axis] - self.mask_height)
        col = np.random.randint(x.shape[img_col_axis] - self.mask_width)
        for r in range(row, row + self.mask_height):
            for c in range(col, col + self.mask_width):
                x[r][c] = -1 # Fill a pixel by background color
        return x

    def standardize(self, x):
        return x

def format_x(x):
    x = (x.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x = x.reshape((x.shape[0], x.shape[1], x.shape[2], 1))
    return x

def train_by(model, opt, datagen, fname):
    model.compile(optimizer=opt,
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    mc = keras.callbacks.ModelCheckpoint(filepath="models/ndisc_"+fname+"-{epoch:02d}.hdf5")
    hist = model.fit_generator(datagen.flow(x_train, y_train, batch_size=BATCH_SIZE),
                               steps_per_epoch=len(x_train)/BATCH_SIZE, epochs=TRAIN_EPOCH,
                               validation_data=(x_val, y_val),
                               callbacks=[mc]).history

    # Evaluate the last model by train and validation data
    score_train = model.evaluate(x_train, y_train, batch_size=32)
    score_val = model.evaluate(x_val, y_val, batch_size=32)

    # Write training history into a file
    with open("history.input-mask.log", mode="a") as f:
        d = datetime.datetime.today()
        f.write("#"+d.strftime("%Y-%m-%d %H:%M:%S")+"\n")
        f.write("#Data gen type : "+str(type(datagen))+"\n")
        f.write("#Data gen params : "+str(vars(datagen))+"\n")
        f.write("#Optimizer :"+str(type(opt))+str(opt.get_config())+"\n")
        f.write("#Data in the last line are calculated by the last model"
                " and not calculated in model.fit()\n")
        f.write("#epoch train-loss train-acc val-loss val-acc\n")
        for v in range(0, TRAIN_EPOCH):
            f.write("{0} {1:10.6f} {2:10.6f} {3:10.6f} {4:10.6f}\n"
                    .format(v, hist["loss"][v], hist["acc"][v],
                            hist["val_loss"][v], hist["val_acc"][v]))
        f.write("{0} {1:10.6f} {2:10.6f} {3:10.6f} {4:10.6f}\n"
                .format(v+1, score_train[0], score_train[1],
                        score_val[0], score_val[1]))
        f.write("\n\n")
    model.save("models/ndisc_"+fname+"-final.hdf5")

if __name__ == '__main__':
    if not os.path.exists("models"):
        os.mkdir("models")

    # Load MNIST data
    (x_train, y_train), (x_val, y_val) = mnist.load_data()
    x_train = format_x(x_train)
    x_val = format_x(x_val)

    # Encode labels into 1-hot vectors
    y_train = keras.utils.to_categorical(y_train, num_classes=10)
    y_val = keras.utils.to_categorical(y_val, num_classes=10)

    # Select MaskedImageGenerator or ImageDataGenerator
    if USE_MASKED_IMAGE_GENERATOR:
        fname = "input-mask-w{0}-h{1}".format(MASK_WIDTH, MASK_HEIGHT)
        datagen = MaskedImageGenerator()
    else:
        fname = "image-gen"
        datagen = ImageDataGenerator(
            rotation_range=20,
            width_shift_range=0.2,
            height_shift_range=0.2,
            shear_range=math.pi/4, # 45 degree
            zoom_range=0.3,
            fill_mode="constant",
            cval=-1, # constant value for fill_mode
            )

    # Make and train a model
    model = num_discriminator_model()
    o = keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                epsilon=1e-08, decay=1e-4)
    train_by(model, o, datagen, fname)
    gc.collect() # To suppress error messages of TensorFlow

腸のサイズの恒常性

腸のサイズが変わらない理由がショウジョウバエで確認されたとのこと[1, 2, 3]。
健康な腸細胞が幹細胞の増殖を抑えていて、細胞が古くなって死んでいく細胞は
抑制ができなくなる。その結果、幹細胞が分裂して、新しい細胞に置き換わる。
これが、時間と空間において局所的に起きるため、腸のサイズが大きくなったり
小さくなったりすることがない。

うまい機構で恒常性が保たれているが、成長中はどうなっているのだろう?

それはそうと、ショウジョウバエの腸は
http://www.nature.com/nature/journal/v548/n7669/images/cover_nature.jpg
の形だそうだ。単純。

-- 参考文献 --
[1] http://www.natureasia.com/ja-jp/nature/highlights/88610
[2] http://www.nature.com/nature/journal/v548/n7669/fp/nature23678_ja.html?lang=ja
[3] http://www.nature.com/nature/journal/v548/n7669/full/nature23678.html (doi:10.1038/nature23678)