2017/09/09

Kerasのオプティマイザの比較

Kerasのオプティマイザを比較します。データはMNIST、モデルは、フォントの学習時に使った2層のCNN+2層のFCです。 10エポックのみですので、もっと長く学習させると異なる結果となるかもしれません。

比較結果は下表の通りです。

Optimizer Decay Learning
rate
Momentum Epsilon Nesterov Rho Beta_1 Beta_2 Schedule
decay
Train
Validation
Loss Error Loss Error
SGD 0 0.01 0 n/a False n/a n/a n/a n/a 0.03682 1.12%
0.04107 1.31%
SGD 0 0.01 0.9 n/a False n/a n/a n/a n/a 0.01186 0.40%
0.03228 1.04%
SGD 1e-4 0.01 0 n/a False n/a n/a n/a n/a 0.04970 1.50%
0.05264 1.69%
SGD 1e-4 0.01 0.9 n/a False n/a n/a n/a n/a 0.00687 0.16%
0.02503 0.91%
SGD 0 0.01 0 n/a True n/a n/a n/a n/a 0.03496 1.06%
0.04069 1.30%
SGD 0 0.01 0.9 n/a True n/a n/a n/a n/a 0.01203 0.40%
0.03281 0.97%
SGD 1e-4 0.01 0 n/a True n/a n/a n/a n/a 0.05123 1.53%
0.05515 1.74%
SGD 1e-4 0.01 0.9 n/a True n/a n/a n/a n/a 0.00730 0.20%
0.02470 0.84%
RMSprop 0 0.001 n/a 1e-8 n/a 0.9 n/a n/a n/a 0.02124 0.62%
0.06075 1.51%
RMSprop 1e-4 0.001 n/a 1e-8 n/a 0.9 n/a n/a n/a 0.00596 0.18%
0.04909 1.11%
Adagrad 0 0.01 n/a 1e-8 n/a n/a n/a n/a n/a 0.00734 0.17%
0.02752 0.93%
Adagrad 1e-4 0.01 n/a 1e-8 n/a n/a n/a n/a n/a 0.01216 0.31%
0.02688 0.98%
Adadelta 0 1 n/a 1e-8 n/a 0.95 n/a n/a n/a 0.01282 0.41%
0.03215 1.10%
Adadelta 1e-4 1 n/a 1e-8 n/a 0.95 n/a n/a n/a 0.01258 0.33%
0.02489 0.88%
Adam 0 0.001 n/a 1e-8 n/a n/a 0.9 0.999 n/a 0.02548 0.65%
0.06531 1.35%
Adam 1e-4 0.001 n/a 1e-8 n/a n/a 0.9 0.999 n/a 0.00425 0.14%
0.04176 0.96%
Adamax 0 0.002 n/a 1e-8 n/a n/a 0.9 0.999 n/a 0.00696 0.21%
0.02920 0.91%
Adamax 1e-4 0.002 n/a 1e-8 n/a n/a 0.9 0.999 n/a 0.00435 0.09%
0.02528 0.88%
Nadam n/a 0.002 n/a 1e-8 n/a n/a 0.9 0.999 0.004 4.64530 28.83%
4.52680 28.09%

各オプティマイザのパラメータを調整すれば、もっと良い結果が得られるかもしれません。 今回の結果から、10エポック目のモデルで一番良いのはSGDのdecayとmomentumをそれぞれ1e-4と0.9に設定したもので、エラーは0.84%でした。 Decayを1e-4に設定したAdadeltaとAdamaxがその次に良く、エラーは0.88%でした。

エポックごとのValidation errorの変化を下図に示します。

エラーの低下の過程を見ると、decay=1e-4のAdamaxが収束の速さの観点では一番良いようです。 理由は不明ですが、Nadamはなぜか学習を進めるにつれてエラーが増えていくという不思議な挙動をしています。

評価に使ったコードは以下の通りです。

  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
# This script trains a neural network model using MNIST.
import gc, keras, os, re, datetime
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

TRAIN_EPOCH = 10

# 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

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):
    model.compile(optimizer=opt,
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    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)

    # Write training history into a file
    with open("history.log", mode="a") as f:
        d = datetime.datetime.today()
        f.write("#"+d.strftime("%Y-%m-%d %H:%M:%S")+"\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")

if __name__ == '__main__':
    # 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)

    # Make a model
    model = num_discriminator_model()

    # Save initial weights which will be used to reset weights
    model.save_weights("initial_weights.hdf5") # 

    # Make a list of optimizers
    opt = []
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=0.0, nesterov=False))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0,   decay=1e-4, nesterov=False))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=1e-4, nesterov=False))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=True))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=0.0, nesterov=True))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0,   decay=1e-4, nesterov=True))
    opt.append(keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=1e-4, nesterov=True))

    opt.append(keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0))
    opt.append(keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=1e-4))

    opt.append(keras.optimizers.Adagrad(lr=0.01, epsilon=1e-08, decay=0.0))
    opt.append(keras.optimizers.Adagrad(lr=0.01, epsilon=1e-08, decay=1e-4))

    opt.append(keras.optimizers.Adadelta(lr=1.0, rho=0.95, epsilon=1e-08, decay=0.0))
    opt.append(keras.optimizers.Adadelta(lr=1.0, rho=0.95, epsilon=1e-08, decay=1e-4))

    opt.append(keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999,
                                     epsilon=1e-08, decay=0.0))
    opt.append(keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999,
                                     epsilon=1e-08, decay=1e-4))

    opt.append(keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                       epsilon=1e-08, decay=0.0))
    opt.append(keras.optimizers.Adamax(lr=0.002, beta_1=0.9, beta_2=0.999,
                                       epsilon=1e-08, decay=1e-4))

    opt.append(keras.optimizers.Nadam(lr=0.002, beta_1=0.9, beta_2=0.999,
                                      epsilon=1e-08, schedule_decay=0.004))
    # Train for each optimizer
    for o in opt:
        model.load_weights("initial_weights.hdf5") # Initialize weights
        train_by(model, o)
    gc.collect() # To suppress error messages of TensorFlow

分類に失敗した画像の出力

MNISTの画像の識別器を学習させていると、精度(accuracy)だけではなく、誤って識別した画像の一覧を見たい場合があります。

以下のコードを使うと、学習済みのKerasのモデルをロードし、識別し、誤った画像を.png形式で出力できます。"mnist-tranied.hdf5" の部分は読み込みたいモデルのファイルパスに書き換えてください。

 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
# This script loads a trained model and outputs error images.
import gc, keras, os
import numpy as np
from keras.models import load_model
from keras.datasets import mnist
from PIL import Image, ImageDraw

IMAGE_PATH = 'error-images'
if not os.path.exists(IMAGE_PATH):
    os.mkdir(IMAGE_PATH)

# Load MNIST data for validation
(_, _), (x_val, y_val) = mnist.load_data()
x_val = (x_val.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
x_val = x_val.reshape((x_val.shape[0], x_val.shape[1], x_val.shape[2], 1))

# Predict classes for MNIST validation data
model = load_model("mnist-tranied.hdf5")
pred = model.predict_classes(x_val)

# Write error images
for i, (p, y) in enumerate(zip(pred, y_val)):
    if p != y:
        image = x_val[i]*127.5 + 127.5 # [-1,1] --> [0,255]
        img = Image.fromarray(image.reshape((image.shape[0], image.shape[1])).astype(np.uint8))
        img.save(IMAGE_PATH+"/{0}-c{1}-p{2}.png".format(i, y, p)) # c=correct, p=predict

数字フォントでモデル学習で使ったニューラルネットワークにMNISTの訓練用データを入力して学習したモデルを使って、上記のコードを走らせると、どうして認識できないのだろうという画像と、これは無理だろうという画像などが目で見て分かるようになります。

例えば、

正解は1
正解は7
正解は6

は人間でも難しいでしょう。

2017/09/02

数字画像の水増し

ただ単にフォントで描いた画像で学習したモデルではMNISTの手書き数字はうまく認識できなかったので、フォントで描いた画像を加工して水増しする方法(Data augmentation)を試してみます。

画像の加工には、KerasのImageDataGeneratorを使います。前回のコードとほぼ同じですが、加工部分が異なっています。

  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
# This script trains a neural network model from font-based number images.
# Image data are augmented by changing their shape.
# The trained model is evaluated by validation data of MNIST.
import gc, keras, math, os, re
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape
from keras.layers.convolutional import Conv2D
from keras.preprocessing.image import ImageDataGenerator
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Flatten, Dropout
from keras.datasets import mnist
from PIL import Image, ImageDraw, ImageFont # For drawing font images

FONT_DIR = "C:/Windows/Fonts"
TRAIN_EPOCH = 30
BATCH_SIZE = 32

# Image size is the same as MNIST
IMAGE_WIDTH = 28
IMAGE_HEIGHT = 28

# Make images and labels of 0-9 by using the font specified by font_name
def make_image(font_name):
    font = ImageFont.truetype(FONT_DIR+"/{}".format(font_name), 25)
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for i in range(10): # Draw 0-9 as image
        image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), (0, 0, 0))
        draw = ImageDraw.Draw(image)
        font_width, font_height = font.getsize(str(i))
        draw.text(((IMAGE_WIDTH-font_width)/2, (IMAGE_HEIGHT-font_height)/2),
                  str(i), font=font, fill=(255, 255, 255))
        ni = np.delete(np.asarray(image), [1, 2], 2) # Remove green and blue
        ni = ni.reshape(1, ni.shape[0], ni.shape[1]) # Convert to 1x28x28 matrix
        images = np.append(images, ni, axis=0)
        labels = np.append(labels, np.array([i]).reshape(1, 1), axis=0)
    return images, labels

# Collect images of numbers for all fonts
def make_images():
    # Collect only true type fonts including numbers
    rp = re.compile(".*ttf")
    font_list = [f for f in os.listdir(FONT_DIR) if rp.match(f)
                 and (f != "webdings.ttf" and f != "wingding.ttf" and 
                      f != "marlett.ttf" and f != "opens___.ttf" and
                      f != "symbol.ttf")]
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for font_name in font_list:
        fimages, flabels = make_image(font_name)
        images = np.append(images, fimages, axis=0)
        labels = np.append(labels, flabels, axis=0)
    return images, labels

# 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

if __name__ == '__main__':
    # Make images of numbers from fonts
    x_train, y_train = make_images()
    x_train = (x_train.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], x_train.shape[2], 1))

    # Load MNIST data for validation
    (_, _), (x_val, y_val) = mnist.load_data()
    x_val = (x_val.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_val = x_val.reshape((x_val.shape[0], x_val.shape[1], x_val.shape[2], 1))

    # 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)

    # For data augmentation
    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, compile and train a model
    model = num_discriminator_model()
    model.compile(optimizer='rmsprop',
                loss='categorical_crossentropy',
                metrics=['accuracy'])
    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)).history

    # Evaluate the model by train and validation data
    score = model.evaluate(x_train, y_train, batch_size=32)
    print("\ntrain acc : ", score)
    score = model.evaluate(x_val, y_val, batch_size=32)
    print("\nmnist val acc : ", score)

    # Write training history into a file
    f = open("history.dat", mode="w")
    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.close()
    gc.collect() # To suppress error messages of TensorFlow
30エポック学習させたモデルを使ったときのLossとAccuracyは次のようになりました。
AugmentationLossAccuracy
TrainOff0.002670.99852
TrainOn0.091110.97811
MNISTOff6.101930.52960
MNISTOn1.019090.71590
学習データを加工しない場合に比べて、ずいぶんMNISTの結果がよくなりました。一方で、学習データに対する性能は低下しています。

では、学習中のLossの変化を見てみましょう。

学習データの加工なしの場合に比べて、MNISTのLossもある程度は低下しています。

学習中のAccuracyの変化は次のようになりました。

MNISTに関しては、ざっくり70%くらいの精度でしょうか。データ加工なしの場合が約50%だったので、結構改善しました。

数字フォントでモデル学習

せっかく数字フォントで画像をつくったので、それでニューラルネットワークモデルを学習させてみます。
そのモデルで手書き数字のMNISTをどれだけ学習できるか、についても見てみます。

モデルの学習にはKerasを使います。コードは以下の通り。

  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
# This script trains a neural network model from font-based number images.
# The trained model is evaluated by validation data of MNIST.
import gc, keras, os, re
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 PIL import Image, ImageDraw, ImageFont # For drawing font images

FONT_DIR = "C:/Windows/Fonts"
TRAIN_EPOCH = 30

# Image size is the same as MNIST
IMAGE_WIDTH = 28
IMAGE_HEIGHT = 28

# Make images and labels of 0-9 by using the font specified by font_name
def make_image(font_name):
    font = ImageFont.truetype(FONT_DIR+"/{}".format(font_name), 25)
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for i in range(10): # Draw 0-9 as image
        image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT), (0, 0, 0))
        draw = ImageDraw.Draw(image)
        font_width, font_height = font.getsize(str(i))
        draw.text(((IMAGE_WIDTH-font_width)/2, (IMAGE_HEIGHT-font_height)/2),
                  str(i), font=font, fill=(255, 255, 255))
        ni = np.delete(np.asarray(image), [1, 2], 2) # Remove green and blue
        ni = ni.reshape(1, ni.shape[0], ni.shape[1]) # Convert to 1x28x28 matrix
        images = np.append(images, ni, axis=0)
        labels = np.append(labels, np.array([i]).reshape(1, 1), axis=0)
    return images, labels

# Collect images of numbers for all fonts
def make_images():
    # Collect only true type fonts including numbers
    rp = re.compile(".*ttf")
    font_list = [f for f in os.listdir(FONT_DIR) if rp.match(f)
                 and (f != "webdings.ttf" and f != "wingding.ttf" and
                      f != "marlett.ttf" and f != "opens___.ttf" and
                      f != "symbol.ttf")]
    images = np.empty((0, IMAGE_WIDTH, IMAGE_HEIGHT))
    labels = np.empty((0, 1))
    for font_name in font_list:
        fimages, flabels = make_image(font_name)
        images = np.append(images, fimages, axis=0)
        labels = np.append(labels, flabels, axis=0)
    return images, labels

# 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

if __name__ == '__main__':
    # Make images of numbers from fonts
    x_train, y_train = make_images()
    x_train = (x_train.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_train = x_train.reshape((x_train.shape[0], x_train.shape[1], x_train.shape[2], 1))

    # Load MNIST data for validation
    (_, _), (x_val, y_val) = mnist.load_data()
    x_val = (x_val.astype(np.float32) - 127.5)/127.5 # [0,255] --> [-1,1]
    x_val = x_val.reshape((x_val.shape[0], x_val.shape[1], x_val.shape[2], 1))

    # 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)

    # Make, compile and train a model
    model = num_discriminator_model()
    model.compile(optimizer='rmsprop',
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    hist = model.fit(x_train, y_train, validation_data=(x_val, y_val),
                     epochs=TRAIN_EPOCH, batch_size=32).history

    # Evaluate the model by train and validation data
    score = model.evaluate(x_train, y_train, batch_size=32)
    print("\ntrain acc : ", score)
    score = model.evaluate(x_val, y_val, batch_size=32)
    print("\nmnist val acc : ", score)

    # Write training history into a file
    f = open("history.dat", mode="w")
    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.close()
    gc.collect() # To suppress error messages of TensorFlow

乱数の影響で実行するたびに精度は変わりますが、
LossAccuracy
Train0.002670.99852
MNIST6.101930.52960
となりました。全然ダメですね。MNISTでの精度が53%しかありません。

学習中のLossの変化が下図です。

1エポックあたりのサンプル数は3380個です。図を描くときにEpochの値が1だけ左にずれてしまいましたが、学習が進むにつれてMNIST側のLossがどんどん増えていることがわかります。

学習中のAccuracyの変化が下図です。

Trainの精度は高いですが、MNISTに対しては最初に少しよくなるものの、50%前後から進みません。 やはり、同じ数字画像であっても、傾向の異なるデータではうまく学習できませんでした。

数字フォントの画像化

システムにインストールされているフォントを使って数字を描画し、jupyter notebookで表示します。
次のコードをjupyter notebookに貼り付けて実行すると、 色々なフォントの画像が生成されます。

数字フォントを眺めたい場合にどうぞ。
(You can see images of numbers 0-9 for all fonts in your system by executing the code below in jupyter notebook)

 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
%matplotlib inline
import os
import re
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw, ImageFont

fontdir = "C:/Windows/Fonts"
width = 28
height = 28
def make_image(font_name):
    font = ImageFont.truetype(fontdir+"/{}".format(font_name), 25)
    for i in range(10): # Draw 0-9 as image
        image = Image.new('RGB', (width, height), (0, 0, 0))
        draw = ImageDraw.Draw(image) 
        font_width, font_height = font.getsize(str(i))
        draw.text(((width-font_width)/2, (height-font_height)/2),
                  str(i), font=font, fill=(255, 255, 255))
        ax = plt.subplot(1, 10, i+1) # Horizontally align images
        ax.tick_params(labelleft='off', labelbottom='off',
                       left='off', bottom='off') # Remove ticks
        plt.imshow(np.asarray(image))
    plt.show() # Finish drawing an image and output it

# Collect only true type fonts including numbers
rp = re.compile(".*ttf")
font_list = [f for f in os.listdir(fontdir) if rp.match(f)
             and (f!="webdings.ttf" and f!="wingding.ttf")]
for font_name in font_list:
    print(font_name)
    make_image(font_name)
http://fushime2.hatenablog.com/entry/2017/01/13/193844
を参考に作成しました。

2017/07/30

音の周波数と場所を表すニューロンは共有されている

海馬のニューロンの一部が物理的空間内の位置を表現していることは既に知られている。
今回、ラットを使った[2]の実験により、音の周波数を表現しているニューロンが、空間を表すニューロンと重複していることが分かった。つまり、空間的な位置を覚えないといけないタスクをこなす時と、音の周波数を覚えないといけないタスクをこなす時とで、共通のニューロンのネットワークが利用されているということである(解説記事は[1])。

実験では、ラットはジョイスティックを操作して音を鳴らし、ジョイスティックを放したときに音が特定の周波数の範囲に入っていたら水が報酬として与えられる。なお、ジョイスティックを押した時間に応じてニューロンが反応している可能性を排除するために、押している間の周波数の変化速度を変動させて実験している。


空間と音とで共有できているなら、他の何かとも共有している可能性はある。そうなると、そもそも海馬内のこのネットワークは何を符号化しているのだろう…。

-- 参考文献 --
[1] https://www.nature.com/nature/journal/v543/n7647/full/543631a.html (doi:10.1038/543631a)
[2] https://www.nature.com/nature/journal/v543/n7647/full/nature21692.html (doi:10.1038/nature21692)

2017/07/29

DCGAN で数字画像を生成

色々なところでGenerative Adversarial Network (GAN) が使われているということなので、GANの初心者向け解説である
https://elix-tech.github.io/ja/2017/02/06/gan.html
を参考にして数字画像生成を試してみました。

試したのは、GANの中でもCNNを使うDeep Convolutional GAN (DCGAN) です。
同解説の中でコードを含めて解説されているのでほぼそのまま使いました。
学習に用いるデータはおなじみのMNISTです。
ただし、Keras 2.0.6 を用いたため、少し修正が必要になりました。

修正後のコードは

 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
from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import UpSampling2D, Conv2D

# For discriminator
from keras.layers.advanced_activations import LeakyReLU
from keras.layers import Flatten, Dropout

def generator_model():
    model = Sequential()
    model.add(Dense(1024, input_dim=100))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(128*7*7))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Reshape((128, 7, 7), input_shape=(128*7*7,)))
    model.add(UpSampling2D((2, 2), data_format='channels_first'))
    model.add(Conv2D(64, (5, 5), padding='same', data_format='channels_first'))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(UpSampling2D((2, 2), data_format='channels_first'))
    model.add(Conv2D(1, (5, 5), padding='same', data_format='channels_first'))
    model.add(Activation('tanh'))
    print(model.summary())
    return model

def discriminator_model():
    model = Sequential()
    model.add(Conv2D(64, (5, 5), strides=(2, 2), padding='same', input_shape=(1, 28, 28), 
                     data_format='channels_first'))
    model.add(LeakyReLU(0.2))
    model.add(Conv2D(128, (5, 5), strides=(2, 2), data_format='channels_first'))
    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(1))
    model.add(Activation('sigmoid'))
    print(model.summary())
    return model
となります。これ以外の部分のコードは、同解説を参照ください。

5エポックまで学習を進めてみたところ、うまく文字が生成できる場合と、全く文字を生成できない場合がありました。文字が生成できる場合がSuccess{1,2,3}で、出来ない場合がFailです。

Epoch=0, Batch=0

Epoch=0, Batch=500

Epoch=0, Batch=1000

Epoch=0, Batch=1500

Epoch=1, Batch=0

Epoch=2, Batch=0

Epoch=3, Batch=0

Epoch=4, Batch=0

Epoch=5, Batch=0




Lossは次の図のようになりました。バッチサイズは32で、1エポックで実行されるバッチの総数は1875です。バッチごとにプロットしています。




初期値に大きく依存しているようで、全く同じように実行しても成功したり失敗したりします。安定して画像が生成できるようにするためにはもう少し工夫が必要なようです。

また、こうやって単純に画像を生成すると、生成した画像にラベルが付かないので、MNISTの学習データを増やす目的には使えません。ラベルと画像をペアで生成できればいいのですが、この点についてももう少し工夫が必要そうです。

脳の過成長と自閉症スペクトラム障害に関連が

生後6ヶ月から12ヶ月の間に起きる大脳皮質の表面積の過拡張が
自閉症スペクトラム障害(ASD)の発生と関連していることが報告された[1]。

遺伝的なリスクが高く、生後24ヶ月でASDと判定された乳児(HR-ASD)が15名、
遺伝的なリスクが高いが生後24ヶ月でASDと判定されなかった乳児(HR-neg)が91名、
遺伝的なリスクが低く、生後24ヶ月でASDと判定されなかった乳児(LR)が42名、
のデータで評価が行われた。

脳の表面積の計測にはMRIが用いられ、6ヶ月目、12ヶ月目、24ヶ月目の3回計測された。
測定結果のグラフ([1]のFigure 1)を見ると、結構ばらつきが大きいように見えるが、
6ヶ月目から12ヶ月目までの表面積の成長率がHR-negに対してHR-ASDのほうがP=0.04で
大きく、LRに対してはP=0.01で大きいという結果が得られたとのこと。

また、6ヶ月目と12ヶ月目のMRIから得られる表面積からディープラーニングを使って
24ヶ月目にASDと判定されるかどうかを予測したところ、8割くらい当てられたそうだ。

ここで使われたディープラーニングの手法はオートエンコーダで、
[2]で示されている方法を用いている。

医療分野の研究にもディープラーニングが用いられるようになってきていることが分かる。


-- 参考文献 --
[1] http://www.nature.com/nature/journal/v542/n7641/full/nature21369.html (doi:10.1038/nature21369)
[2] http://science.sciencemag.org/content/313/5786/504 (DOI: 10.1126/science.1127647)

2017/07/28

Proxima b まで探査機を送る計画

地球サイズの惑星 Proxima b まで探査機を送る計画があるそうだ[1]。
YouTubeの動画は[2]にある。
レーザーで光速の20%まで探査機を加速することで21.1年で到達する計算とのこと
(減速なしで最初の加速時間は無視するという仮定で計算)。


それはともかく、同記事の図を見るとオールトの雲は太陽からおおよそ1光年の距離にある。
一方、Proxima bは4.22光年の距離にある。Proxima bが回っている地球にとっての太陽は
Proxima Centauriだが、これにも同様のオールトの雲があり、距離も同じだと仮定すると、
太陽から見て、3.22光年あたりに分布していることになる。
そうするとオールトの雲同士の距離は2.22光年しか離れていないということになる。

[太陽] --(1光年)--[オールトの雲]------(2.22光年)------[オールトの雲]--(1光年)--[P.C.]

実際には遠いのだが、このように考えるとお隣の恒星が意外と近く感じるのではないだろうか。



-- 参考文献 --
[1] https://www.nature.com/news/what-it-would-take-to-reach-the-stars-1.21402 (doi:10.1038/542020a)
[2] https://www.youtube.com/watch?v=zVySM1oofmY

天文学も GAN を活用

天文学でもGenerative Adversarial Networks (GANs)を利用して
重力レンズで歪んだ銀河を生成し、それらを検出するソフトウェアの
性能を向上させようとしているそうだ[1]。

しかしながら、GANで生成した画像はいい感じのときもあれば、
奇妙な画像を生成することもあるわけで、芸術ならともかく
基礎科学の分野で使うなら十分に注意深く使う必要があるだろう。


-- 参考文献 --
[1] https://www.nature.com/news/astronomers-explore-uses-for-ai-generated-images-1.21398 (doi:10.1038/542016a)

2017/07/23

良くくっつく接着パッチ

乾燥してても、湿っていても、水中でも、油中でも良くくっつく接着パッチができたとのこと[1, 2, 3]。

タコの吸盤っぽい形にすることで実現できた模様。
何度も繰り返し使え、吸盤からの汚染もない。

安く簡単に手に入るようになる日は来るのだろうか。
普通の吸盤は時間とともに剥がれてしまうので、
日用品に使われるようになれば、とても便利になりそうだ。


-- 参考文献 --
[1] http://www.natureasia.com/ja-jp/nature/highlights/86698
[2] http://www.nature.com/nature/journal/v546/n7658/full/546358a.html (doi:10.1038/546358a)
[3] http://www.nature.com/nature/journal/v546/n7658/full/nature22382.html (doi:10.1038/nature22382)

暗黒物質と暗黒エネルギー

暗黒物質と暗黒エネルギーについての2016年のまとめ記事。
http://www.nature.com/nature/outlook/dark-universe/index.html

4つの大きな疑問点が
http://www.nature.com/nature/journal/v537/n7622_supp/full/537S206a.html (doi:10.1038/537S206a)
に挙げられています。

1. 暗黒物質の粒子は存在するの?
2. 暗黒物質はどんなものとも相互作用するの?
3. 宇宙定数で暗黒エネルギーを説明できるの?
4. 最終的に宇宙に何が起きるの?

10年以上前からある疑問で、まだ解決していないということですね。
詳細はリンク先を参照。

人工ブラックホールでホーキング輻射

(2016年の記事より)

光の代わりに音波を使ってホーキング輻射を検証するための人工ブラックホールをつくり、
ホーキング輻射を観測したとのこと[1, 2]。
人工的に本物のブラックホールが作られたわけではない。


-- 参考文献 --
[1] https://www.nature.com/news/artificial-black-hole-creates-its-own-version-of-hawking-radiation-1.20430 (doi:10.1038/536258a)
[2] http://www.nature.com/news/one-man-band-the-solo-physicist-who-models-black-holes-in-sound-1.20437 (doi:10.1038/nature.2016.20437)

脳から情報を読み取り自分の足を動かす

(2016年の記事より)

脊髄損傷により片足が麻痺したアカゲザルの足を、損傷部分をスキップする経路を作ることで
自由に歩けるようにできたとのこと[1, 2]。

おおまかな方法は次の通り
1. 脳に96チャネルのマイクロアレイを埋め込んで、そこから得られた信号を適切に変換する。
2. 変換後の信号をパルス生成器にワイヤレスで転送する。
3. 転送された信号に基づいて電気信号をパルス生成器が作る。
4. 腰の辺りの脊髄に埋め込んだ電極により電気信号を神経に伝達する。
5. 足が動く。

-- 参考文献 --
[1] http://www.nature.com/nature/journal/v539/n7628/full/539177a.html (doi:10.1038/539177a)
[2] http://www.nature.com/nature/journal/v539/n7628/abs/nature20118.html (doi:10.1038/nature20118)

地球サイズの惑星が近くの恒星で発見

(2016年の記事より)

地球から1.3パーセクの距離にあるProxima Centauriの周りを回っている惑星が発見された[1, 2, 3, 4]。
Proxima Centauri b (またはProxima b)と名づけられたその惑星の質量は地球の1.3倍とのこと。公転周期は11.2日。
ただし、Proxima Centauriは強力なフレアを放出している。
さらに、Proxima Centauri bが受けるX線の量も地球の400倍と大きい。
このため、大気が存在するかは今のところ分かっていない。


-- 参考文献 --
[1] http://www.natureasia.com/ja-jp/nature/highlights/77976
[2] https://www.nature.com/news/earth-sized-planet-around-nearby-star-is-astronomy-dream-come-true-1.20445
[3] https://www.nature.com/nature/journal/v536/n7617/full/536408a.html
[4] https://www.nature.com/nature/journal/v536/n7617/full/nature19106.html (doi:10.1038/nature19106)