2. チュートリアル

2.1. 全結合ニューラルネットワーク

_images/mnist.png

Figure 1: 手書き文字

ReNomを使い、全結合型ニューラルネットワークの学習を行ってみましょう。ここでは手書き文字画像のデータセットであるMNISTデータセット [1] を用いて多クラス分類問題について取り組みます。

MNISTは0~9までの手書き数字を、合計70000枚集めたデータセットです。今回は、学習用データとして63000枚を使用し、テスト用データとして7000枚を使用することにします。扱うデータは28 x 28 [px]の1チャネル二次元画像になります。

まずここで扱うニューラルネットワークモデルについて説明します。下図のようなモデルを構築してみます。入力データは28 x 28の784次元です。そのため入力層のユニット数は784となります。中間層のユニットは100、活性化関数はシグモイド関数を設定しました。誤差関数にはソフトマックス層を使用します。今回の多クラス分類では10クラスを分類しますので、出力層のユニット数は10とします。

_images/network1.png

Figure 2: ニューラルネットワーク

ReNomではこのモデルをいくつかの層が連結したものと考えます。ネットワークを、ReNomを用いて構築すると次のようなコードで書くことができます。

1
2
3
4
5
input_layer = Input(784)                    # 入力層
middle_layer = Dense(input_layer, 100)      # 中間層
activate = Sigmoid(middle_layer)            # 中間層の活性化関数
output_layer = Dense(activate, 10)          # 出力層
loss = Soft_Max_Cross_Entropy(output_layer) # 誤差関数
_images/network2.png

Figure 3: ReNomにおけるニューラルネットワークのイメージ

ここまでで、ニューラルネットワークモデルを構築することができました。次にモデルにデータを学習させます。ニューラルネットワークの学習は勾配降下法を用いて行われます。そこで、ReNomでは様々な勾配降下手法を定義したOptimizerクラスを継承したインスタンスを使い、学習を実行します。今回は確率的勾配降下法( Sgd : Stochastic Gradient Decent )を採用します。

1
opt = Sgd(Model(input_layer, loss), lr = 0.1)   # 勾配降下法の定義

モデルオブジェクトを引数として渡すことでSgdクラスをインスタンス化しています。同時にパラメータとして学習率を0.1と設定しています。 実際にニューラルネットワークにデータを学習させます。学習はOptimizerに定義されている関数を使用します。具体的にコードを見てみましょう。

1
2
3
4
5
# 学習ループ
for ep in range(epoch):
    opt.forward(train_data, label_data)     # 順伝播計算
    opt.backward()                          # 逆伝播計算
    opt.update()                            # 重みの更新

「epoch」で指定する回数、学習を実行しています。optはOptimizerクラスのインスタンスです。モデルの順伝播計算を行うforward関数に、学習データであるtrain_dataとそれに対応した教師データであるlabel_dataを引数として与えています。順伝播の後はbackward関数を呼び出して逆伝播を行います。最後に、逆伝播によって計算された勾配を用いて、各層のパラメータを更新します。 以上のコードにより学習を行った結果を以下に示します。今回はバッチサイズ100で、学習を100eopch行いました。グラフのプロットにはpythonのグラフィックライブラリであるmatplot [2] を使用しております。学習曲線は次のようになりました。

_images/mnist_graph.png

Figure 4: 学習曲線

最後に、学習させたニューラルネットワークを用いて、テストデータに対するクラス分類を行ってみましょう。学習済みニューラルネットワークを使用するには、predict関数を使用します。

1
opt.predict(test_data)  # テストデータの分類

MNISTデータを分類した精度を示します。ここでは、scikit-learn [3] の関数を使用して、混合行列を作成し、識別精度を確認しています。識別精度として、F1スコアの平均値を見ると、94%の精度で分類ができていることがわかります。

以上のようにして全結合型ニューラルネットワークのモデル構築、学習、テストを行うことができます。

_images/mniat_accracy.png

Figure 5: 分類精度

以下に今回のMNIST学習に使用した全ソースコードを示します。

 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
# numpy
import numpy as np
# scikit-learn
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import confusion_matrix, classification_report
#ReNom
from renom.model.model import Model
from renom.optimizers.sgd import Sgd
from renom.layers.activation.sigmoid import Sigmoid
from renom.layers.loss.softmax_cross_entropy import Soft_Max_Cross_Entropy
from renom.layers.function.dense import Dense
from renom.layers.function.input import Input

# mnistデータのロード
mnist = fetch_mldata('MNIST original', data_home=".")

X = mnist.data          # 学習データ
Y = mnist.target        # 教師データ

X = X.astype(np.float32)    # 32bit float型に変換
X /= X.max()                # 画素値を正規化

# データを学習用とテスト用に分割
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.1)

train = X_train
label = LabelBinarizer().fit_transform(Y_train) # One-Hot表現に変換

# ネットワーク定義
input_layer = Input(784)                    # 入力層
middle_layer = Dense(input_layer, 100)      # 中間層
activate = Sigmoid(middle_layer)            # 中間層の活性化関数
output_layer = Dense(activate, 10)          # 出力層
loss = Soft_Max_Cross_Entropy(output_layer) # 誤差関数

opt = Sgd(Model(input_layer, loss))         # 勾配降下法定義

N = train.shape[0]
batch = 100 # バッチサイズ100
epoch = 100 # epoch数 100

# 学習ループ
for i in xrange(epoch):
    print "epoch : %d"%i
    perm = np.random.permutation(N)                         # 学習データから、
    error = 0                                               # ランダムに
    for j in xrange(0, N/batch):                            # バッチ数分のデータを
        train_batch = train[perm[j*batch:(j+1)*batch]]      # 取り出して学習
        responce_batch = label[perm[j*batch:(j+1)*batch]]
        error += opt.forward(train_batch, responce_batch)   # 順伝播
        opt.backward()                                      # 逆伝播
        opt.update()                                        # 重み更新
    print "error = %f" % (error/(N/batch))

# テストデータの分類
o = opt.predict(X_test)
predictions = np.argmax(o, axis = 1)

# 結果表示
print confusion_matrix(Y_test, predictions)
print classification_report(Y_test, predictions)

2.2. 畳み込みニューラルネットワーク

このセクションでは、ReNomを使用して畳み込みネットワークの構築方法を紹介します。具体的な例として、Cifar10の画像データセットを使用して、画像分類を行います。Cifer10 [4] は、車や犬といった32×32[px]サイズのカラー画像を10クラス分集めたデータセットで、合計50000枚からなります。データは以下のリンクからダウンロードすることができます。

_images/cifar10.png

Figure 6: Cifer-10

はじめに畳み込みニューラルネットワークを構築してみましょう。今回は次のような二層の畳み込み層、プーリン グ層と全結合層からなる畳み込みネットワークを構築しました。

_images/cnn.png

Figure 7: 畳み込みニューラルネットワーク

このニューラルネットワークモデルを、ReNomを用いて書くと次のようになります。一行目の入力層では、入力値をテンソルとして受け取るために入力サイズを(32, 32, 3)としています。また畳み込み層の初期化時には、出力チャネル数と、フィルタサイズを与えています。プーリング層の出力チャネルは、入力されたチャネル数と変わらないので、フィルタサイズとストライド幅を指定しています。 さらに過学習を抑制するために、全結合層にはドロップアウトを使用しています。ドロップアウト率は0.5としました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
input_layer = Input((32, 32, 3))                                        # 入力層
conv1 = Conv2d(input_layer, channel = 32,                               # 畳み込み層1初期重みを
                filter_size = (3, 3), initial_weight_scale = 0.1)       # 標準偏差0.1の正規分布で初期化
pool1 = Relu(Pool2d(conv1, filter_size = (3, 3), stride = (2, 2)))      # プーリング層+活性化関数
conv2 = Conv2d(pool1, channel = 32,                                     # 畳み込み層2初期重みを
                filter_size = (3, 3), initial_weight_scale = 0.1)       # 標準偏差0.1の正規分布で初期化
pool2 = Relu(Pool2d(conv2, filter_size = (3, 3), stride = (2, 2)))      # プーリング層+活性化関数
flat = Flatten(pool2)
dense1 = Dropout(Relu( Dense( flat, 384,
                initial_weight_scale = 0.1)), 0.5)                      # 全結合層①
dense2 = Dropout(Relu(Dense(dense1, 192,
                initial_weight_scale = 0.1)), 0.5)                      # 全結合層②
output_layer = Dense(dense2, 10)                                        # 出力層
loss = Soft_Max_Cross_Entropy(output_layer)                             # 誤差関数

学習については、MNISTの学習と同様に、ネットワークモデルをOptimizerの引数に渡すことで、インスタンス化します。後はミニバッチを用いて、学習を行います。ミニバッチのサイズは100としております。

学習曲線はこのようになりました。

_images/cnn_accuracy.png

Figure 8: 学習曲線

MNIST分類の評価方法と同じく、Cifar10データセットについてもscilit-learnの混同行列を用いて精度を確認します。F値の平均は73%となりました。

_images/cifar10_.png

Figure 9: 分類精度

以下に、cifer10データセットについて多クラス分類を行うソースコードを示します。

 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
import numpy as np
import cPickle
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.cross_validation import train_test_split
from renom.layers.function.dropout import Dropout
from renom.layers.function.dense import Dense
from renom.layers.function.conv2d import Conv2d
from renom.layers.function.pool2d import Pool2d
from renom.layers.loss.softmax_cross_entropy import Soft_Max_Cross_Entropy
from renom.layers.activation.relu import Relu
from renom.optimizers.adam import Adam
from renom.utils.normalization import normalize
from renom.model.model import Model
from renom.layers.function.input import Input
from renom.layers.function.flatten import Flatten

# データのロード
dir = "cifar-10/"
paths = ["data_batch_1","data_batch_2","data_batch_3",
        "data_batch_4","data_batch_5"]

train = None
label = None

for p in paths:
    d = cPickle.load(open(dir + p, "rb"))
    if train is None:
        train = np.array(d["data"])
        label = np.array(d["labels"])
    else:
        train = np.concatenate((train, d["data"]),axis=0)
        label = np.concatenate((label, d["labels"]),axis=0)

train = train.reshape(50000, 3, 32, 32) # データのテンソル化
X = train.transpose(0, 2, 3, 1)         # h, w, c に並び替え
Y = label.reshape(-1,1)
X = normalize(X)                        # 画像の正規化

train_x, test_x, train_y, test_y = train_test_split(X, Y, test_size=0.1)
train_y = LabelBinarizer().fit_transform(train_y)

# ネットワークモデル作成
input_layer = Input((32, 32, 3))
conv1 = Conv2d(input_layer, channel = 32,
            filter_size = (3, 3), initial_weight_scale = 0.1)
pool1 = Relu(Pool2d(conv1, filter_size = (3, 3), stride = (2, 2)))
conv2 = Conv2d(pool1, channel = 32,
            filter_size = (3, 3), initial_weight_scale = 0.1)
pool2 = Relu(Pool2d(conv2, filter_size = (3, 3), stride = (2, 2)))
flat  = Flatten(pool2)  # テンソルデータを行列に変換
dense1 = Dropout(Relu( Dense( flat, 384,
            initial_weight_scale = 0.1)), 0.5)
dense2 = Dropout(Relu(Dense( dense1, 192,
            initial_weight_scale = 0.1)), 0.5)
outpu_layer = Dense(dense2, 10 )
loss = Soft_Max_Cross_Entropy(layer = outpu_layer)

opt = Adam(Model(input_layer, loss))

N = train_x.shape[0]
batch=100

# 学習ループ
for i in xrange(0,100):
    print "epoch %d"%i
    perm = np.random.permutation(N)
    error=0
    for j in xrange(0, N/batch):
        train_batch = train_x[perm[j*batch:(j+1)*batch]]
        responce_batch = train_y[perm[j*batch:(j+1)*batch]]
        error += opt.forward(train_batch, responce_batch)
        opt.backward()
        opt.update()
    print "error = %f" % (error/(N/batch))

# テスト
o = opt.predict(test_x)
predictions = np.argmax(o, axis = 1)

print confusion_matrix(test_y, predictions)
print classification_report(test_y, predictions)

2.3. 再起型ニューラルネットワーク

ここでは、IMDB [5] という映画サイトのレビューデータについてLstm(Long Short Term Memory)を用いた感情分析を行います。25000件の、映画に関するレビュー文章を学習することでレビューがポジティブな意見を言っているか、ネガティブな意見を言っているのかを判断します。以下にポジティブな意見とネガティブな意見の例を示します。データセットは以下のURLからダウンロードすることができます。

●Positive eview
Lars von Trier’s Europa is a worthy echo of The Third Man, about an American coming to post-World War II Europe and finds himself entangled in a dangerous mystery...
●Negative Review
There are many different versions of this one floating around, so make sure you can locate one of the unrated copies, otherwise some gore and one scene of nudity might be missing. Some versions also omit most of the opening sequence and other bits here and there ...

まず、ニューラルネットワークにおいて文章を扱うには、文章内の単語をベクトルに置き換える必要があります。今回は単語の表現方法として、One-Hotベクトル表現を使用します。One-Hotベクトルでは、単語と数字を関連付けた辞書を用意し、数字のインデックスを1、それ以外を0とするベクトルです。単語は次のように置き換えることができます。

今回使用するデータセットに出現する単語の種類は80000種類を超えており、すべての単語をベクトル化する場合、ベクトルの次元は80000次元を超えてしまいます。そこで、今回は頻出単語上位19998単語に、パディング、未登録といったラベルを含め、合計で20000次元のデータを使用します。 ここでのネットワーク構造は、入力層は20000ユニット、中間層はLSTMユニットを128ユニット使用します。出力はシグモイド交差エントロピーを使用し結果を二値に分類します。勾配降下法としてAdamを使用します。

1
2
3
4
5
input_layer = Input(20000)                                  # 入力層
lstm = Lstm(input_layer, 128, initial_weight_scale= 0.01)   # LSTM レイヤ
output_layer = Dense(lstm, 1, initial_weight_scale= 0.01)   # 出力層
loss = Sigmoid_Cross_Entropy(output_layer)                  # シグモイド交差エントロピー
opt = Adam(Model(input_layer, loss))                        # 勾配降下法 Adam

LSTMの学習はBPTT [6] 法を用いて行います。ReNomではBPTT法を用いた学習を次のようにして行います。

1
2
3
4
5
6
loss = 0
for t in xrange(MAX_WORDS):                                 # MAX_WORDS 分の単語を入力
    loss += opt.forward( vec, responce_batch )              # 順伝播
    opt.backward()                                          # BPTT 逆伝播
    opt.update()                                            # 重みの更新
    opt.truncate()                                          # 時系列のつながりを打ち切る

時系列方向の誤差伝播を打ち切る場合、Optimizerのtruncate関数を呼び出します。以上の処理により、BPTT法を用いたLSTMの学習ができます。

テスト結果を見てみましょう。全体的な感情分析結果としては80%の精度となりました。実際に次のような文章を入れた場合、ポジティブなレビューと判断されているのがわかります。ReNomでは、RNNも今回同様にBPTT法を用いて学習させることができます。

_images/rnn_acc.png

Figure 11: 分類精度

i found the story to be just enough of a thriller that the wonderful henry mancici music didn’t lull me. julie andrews was excellant and i sure don’t understand why this movie had problems at the box office when it came out because it just makes me happy at the end to have everybody singing. and i do like happy ever after endings which i think you can say this movie has along with some traditional blake edwards humor...

  • possibility of positive review is 0.98

以下に、LSTMを用いたレビュー分析のコード全体を示します。

  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
import os
import numpy as np
from sklearn.metrics import confusion_matrix, classification_report
from renom.layers.function.dense import Dense
from renom.layers.function.lstm import Lstm
from renom.layers.loss.sigmoid_cross_entropy import Sigmoid_Cross_Entropy
from renom.optimizers.adam import Adam
from renom.model.model import Model

MAX_WORDS = 128         # 分析に使用する単語長
MAX_VOCABS = 20000 - 2  # 単語種類数
UNKNOWN_WORD = 1

# 単語をOnehotベクトルに変換する関数
def embed(x):
    vec = np.zeros((x.shape[0], MAX_VOCABS + 2), np.float32)
    for i, index in enumerate(x):
        vec[i, index] = 1.
    return vec

# データをロードするための関数
def load():
    #データの加工には、時間がかかります。
    #そのため、一度作ったデータをpickle化し、
    #次回からはそれを読み込むことを推奨します。
    voc = {}
    train_label = []
    train_data = []
    test_label = []
    test_data = []
    #各単語の出現数を数える
    for dir in ("train/pos/", "train/neg/", "test/pos/", "test/neg/"):
        for file in os.listdir(dir):
            path = dir + file
            if not os.path.isfile(path):continue
            with open(path, "r") as feature:
                for line in feature:
                    line.replace("<","")
                    line.replace("br","")
                    line.replace(">","")
                    line.replace("/","")
                    split = line.split(" ")
                    for d in split:
                        if voc.get(d):
                            voc[d] += 1
                        else:
                            voc[d] = 1
    voc = sorted(voc.items(), key = lambda x:x[1], reverse = True)
    voc = [x[0] for x in voc][:MAX_VOCABS]
    #使用する単語を、出現頻度上位19998種類に絞る

    for dir in ("train/pos/", "train/neg/", "test/pos/", "test/neg/"):
        for file in os.listdir(dir):
            path = dir + file
            if not os.path.isfile(path):continue
            with open(path, "r") as feature:
                for line in feature:
                    l = np.zeros((MAX_WORDS), dtype = np.int)
                    split = line.split(" ")
                    for n,d in enumerate(split):
                        try:
                            ind = voc.index(d)
                            if ind < MAX_VOCABS:
                                l[n] = ind + 2
                            else:
                                l[n] = UNKNOWN_WORD
                        except:
                            l[n] = UNKNOWN_WORD
                        if n >= MAX_WORDS - 1 :
                            break
                    if "train" in dir:
                        train_data.append(l)
                        train_label.append(1 if "pos" in dir else 0)
                    else:
                        test_data.append(l)
                        test_label.append(1 if "pos" in dir else 0)

    train_data = np.array(train_data)
    train_label = np.array(train_label).reshape(-1, 1).astype(np.float32)
    test_data = np.array(test_data)
    test_label = np.array(test_label).reshape(-1, 1).astype(np.float32)
    return train_data, train_label, test_data, test_label


data, label, test, test_label = load()
print "Data loaded."

input = Dense(None, MAX_VOCABS + 2)
l1 = Lstm(input, 128, initial_weight_scale= 0.01)
l2 = Dense(l1, 1, initial_weight_scale= 0.01)
output = Sigmoid_Cross_Entropy(l2)
opt = Adam( Model(input, output) )

N = len(data)
batch = 100
epoch = 2
error = 0

for n in xrange(epoch):
    perm = np.random.permutation(N)
    error = 0
    for i in xrange(N/batch):
        train_batch = data[perm[i*batch:(i+1)*batch]]
        responce_batch = label[perm[i*batch:(i+1)*batch]]
        error_tmp = 0
        for t in xrange(MAX_WORDS):
            vec = embed(train_batch[:,t])
            error_tmp += opt.forward(vec, responce_batch)
        opt.backward()
        opt.update()
        opt.truncate()
        error += error_tmp/ MAX_WORDS
    print "epoch {}".format(n)
    print error/ (N/batch)

for t in xrange(MAX_WORDS):
    vec = embed(test[:,t])
    o = opt.predict(vec)
predict = np.array(o > 0.5).astype(np.float32)

print confusion_matrix(test_label, predict)
print classification_report(test_label, predict)

2.4. オートエンコーダ

ReNomを用いて、オートエンコーダによる特徴量抽出を実行してみましょう。ここではMNISTデータについてオートエンコーダによる特徴抽出を行い、中間層の重みを可視化します。全結合型ニューラルネットワークを用いたMNIST多クラス分類プログラムを少し変更し、オートエンコーダを作成します。

1
2
3
4
5
input_layer = Input(784)                   # 入力層
middle_layer = Dense(input_layer, 100)     # 全結合層
activate = Relu(middle_layer)              # Relu活性化関数
output_layer = Dense(activate, 784)        # 出力層
loss = Mean_Squared_Error(output_layer)    # 誤差関数

中間層が100ユニット、活性化関数にRelu関数を使用しています。さらに出力層は入力層と同じユニット数の784、誤差関数は二乗誤差関数としました。このネットワークを学習させてみましょう。チュートリアル1と同様に、勾配法による学習を行います。

学習が終わりましたら、次に中間層のユニットの重みを可視化します。ReNomでは重みパラメータを行列で表現しております。詳細はデータの表現方法を参照してください。中間層が持つ サイズ 784 x 100の行列の中から、中間層の第一ユニットに関する重みを可視化します。行列内では、第一行目の重みがそれに当たります。重みを取得するには、次のようなコードを書きます。

1
2
weight = layar.parameter["w"]                                   # 重みの取得
plt.imshow(weight["w"][:, 1].reshape(28,28), cmap = "gray")     # 第一ユニットに関する重みを可視化

可視化には、Matplotを使用しております。このようにして、オートエンコーダにより特徴量を可視化することが できます。

次に、スパースオートエンコーダを用いて、なるべく少ないユニットを用いて特徴量を表現してみます。スパースオートエンコーダを実装するためには、スパースレイヤを使用します。

1
2
3
4
5
6
input_layer = Input(784)                       # 入力層
middle_layer = Dense(input_layer, 100)         # 全結合層
activate = Relu(middle_layer)                  # Relu 活性化関数
sparse = Sparse(activate)                      # スパース層を追加
output_layer = Dense(sparse, 784)             # 出力層
loss = Mean_Squared_Error(output_layer)        # 誤差関数

それでは、このネットワークを用いて特徴量を抽出してみます。スパースレイヤの引数によって、スパース正則化の強さをコントロールできます。以下に、スパース正則化の強さによって得られる特徴量の違いを見てみましょう。

_images/ae1.png

Figure 12: b = 0.0

_images/ae2.png

Figure 13: b = 0.1

_images/ae3.png

Figure 14: b = 2.0

最後に、オートエンコーダで得られた重みをほかのネットワークに使用し、事後学習、テストを行います。 重みをほかの層にコピーするには、次のようなコードを書きます。このコードでは、layer2の重み、バイアスパラメータをlayer1 へコピーしています。コピーを行うためにはlayer1とlayer2 の重み、バイアスの行列サイズが等しい必要があります。

layer1.parameter = layer2.parameter

以下に、オートエンコーダによって得られた重みを可視化するコードを記載します。

 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
import numpy as np
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import LabelBinarizer
import matplotlib.pyplot as plt
from renom.layers.function.dense import Dense
from renom.layers.loss.mean_squared_error import Mean_Squared_Error
from renom.optimizers.momentum_sgd import Momentum_sgd
from renom.layers.activation.relu import Relu
from renom.layers.function.sparse import Sparse
from renom.model.model import Model
from renom.layers.function.input import Input

mnist = fetch_mldata('MNIST original', data_home='./')

X = mnist.data
y = mnist.target

X = X.astype(np.float32)
X /= np.max(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)

labels_train = LabelBinarizer().fit_transform(y_train)

train = X_train
label = labels_train

input_layer = Input(784)                                        #オートエンコーダ
middle_layer = Dense(input_layer, 100)
activate = Relu(middle_layer)
sparse = Sparse(activate, b = 0.1)
output_layer = Dense(sparse, 784)
loss = Mean_Squared_Error(output_layer)

ae = Momentum_sgd(Model(input_layer, loss), 0.1, 0.1)
batch_size = 100
N = train.shape[0]
for i in range(500):
    perm = np.random.permutation(N)
    error = 0
    for j in range(N/batch_size):
        train_batch = train[perm[j*batch_size:(j+1)*batch_size]]
        error += ae.forward(train_batch, train_batch)
        ae.backward()
        ae.update()
    print 'errot %f' %(error/(N/batch_size))
    plt.subplot("231")                                                # 重みの可視化
    plt.imshow(middle_layer.parameter["w"][:,4].reshape(28,28),
            cmap = "gray")

    plt.subplot("232")
    plt.imshow(middle_layer.parameter["w"][:,7].reshape(28,28),
            cmap = "gray")
    plt.subplot("233")
    plt.imshow(middle_layer.parameter["w"][:,31].reshape(28,28),
            cmap = "gray")
    plt.subplot("234")
    plt.imshow(middle_layer.parameter["w"][:,32].reshape(28,28),
            cmap = "gray")
    plt.subplot("235")
    plt.imshow(middle_layer.parameter["w"][:,33].reshape(28,28),
            cmap = "gray")
    plt.subplot("236")
    plt.imshow(middle_layer.parameter["w"][:,34].reshape(28,28),
            cmap = "gray")
    plt.pause(0.01)

2.5. 制限ボルツマンマシン

このセクションでは、制限ボルツマンマシンによる事前学習を行います。制限ボルツマンマシン (以下RBM) の学習はCD法によって行います。以下では第一セクションで実行した手書き文字認識に対して事前学習を行います。RBMでは、データを0か1のバイナリ値で保持します。そのため、ここではMNISTデータは二値化されていることを 前提とします。 次にRBMのモデルを作成します。RBMは可視層と隠れ層の二つの全結合層からなります。RBMモデルは次のように作成します。事前学習にはCD法を使います。

1
2
3
4
5
6
visible_layer = Input(784)                    # 可視層
hidden_layer = Dense(visible_layer, 100)            # 隠れ層
opt = CD_Method(visible_layer, hidden_layer)        # 学習方法定義
for ep in range( epoch ):
    opt.train(train_data, label_data)               # CD 法を用いた学習
    opt.update()                                    # 重みの更新

定義したモデルに対して学習を行います。CD法を用いた学習には、train関数を使用することで重みの更新量を求めることができます。次にこの重みをニューラルネットワークで使用します。今回は、学習したレイヤをそのままニューラルネットワークに使用します。

1
2
3
4
5
visible_layer = Input(784)                          # 入力層
hidden_layer = Dense(visible_layer, 100)            # 全結合層
activate = Sigmoid( layer = hidden_layer )          # Sigmoid 活性化関数
output_layer = Dense ( activate, 10 )               # 出力層
loss = Soft_Max_Cross_Entropy( output_layer )       # 誤差関数

最後にニューラルネットワーク全体の重みを学習します。ここから先は第一セクションで行った学習、テスト方法と同じですので、省略します。 学習曲線と混同行列を確認し、精度を見てみましょう。それぞれ次のようになります。

_images/rbm_.png

Figure 15: 学習曲線

_images/rbm_acc.png

Figure 16: 分類精度

RBMによるボルツマンマシンの使用方法を確認することができました。以下にRBMを用いた事前学習からMNISTの分類に至るまでのコードを記します。

 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
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import confusion_matrix, classification_report

from renom.layers.function.dense import Dense
from renom.layers.activation.sigmoid import Sigmoid
from renom.layers.loss.softmax_cross_entropy import Soft_Max_Cross_Entropy
from renom.optimizers.sgd import Sgd
from renom.bm.cd_method import CD_Method
from renom.utils.normalization import normalize
from renom.model.model import Model
from renom.layers.function.input import Input

np.random.seed(12)

mnist = fetch_mldata('MNIST original', data_home=".")

X = mnist.data
y = mnist.target

X = np.array(X > 122).astype(np.float32)    #画像の二値化
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)
labels_train = LabelBinarizer().fit_transform(y_train)
labels_test = LabelBinarizer().fit_transform(y_test)

train = X_train
label = labels_train

l0 = Input(28*28)
l1 = Dense(l0, 100, initial_weight_scale= 0.01)
l2 = Sigmoid(l1)
l3 = Dense(l2, 10)
l4 = Soft_Max_Cross_Entropy(l3)

N=train.shape[0]
batch=100
learning_curve=[]

opt = CD_Method(l0, l1, 0.001)
for i in range(100):
    perm = np.random.permutation(N)
    loss=0
    for j in xrange(0,N/batch):
        train_batch = train[perm[j*batch:(j+1)*batch]]
        l = opt.train(train_batch)
        opt.update()
        loss += l
    print loss/(N/batch)

opt = Sgd(Model(l0, l4), 0.1)

for i in xrange(0,101):
    print "epoch "+str(i)
    perm = np.random.permutation(N)
    loss=0
    for j in xrange(0,N/batch):
        train_batch = train[perm[j*batch:(j+1)*batch]]
        responce_batch = label[perm[j*batch:(j+1)*batch]]
        l = opt.forward(train_batch, responce_batch)
        opt.backward()
        opt.update()
        loss += l

    lc = loss/(N/batch)
    learning_curve.append(lc)
    print lc
predictions = []

for i in xrange(X_test.shape[0]):
    o = opt.predict(X_test[i].reshape(1,28*28))
    predictions.append(np.argmax(o))
res1=confusion_matrix(y_test, predictions)
res2=classification_report(y_test, predictions)
print res1
print res2
plt.plot(learning_curve, linewidth = 3)
plt.ylabel("error")
plt.xlabel("epoch")
plt.show()

2.6. その他のネットワーク

ここでは、ドロップアウトや分岐を持つニューラルネットワークを構築する方法について説明します。

●ドロップアウト レイヤ内のユニットをランダムに無効化させたい場合、Dropoutレイヤを使用します。

1
2
3
4
5
input_layer = Dropout(Input(784), dropout_ratio = 0.5 )     # ドロップアウト付入力層
middle_layer = Dense(input_layer, 100)                      # 全結合層
activate = Sigmoid(middle_layer)                            # 活性化関数
output_layer = Dense(activate, 10)                          # 全結合層
loss = Soft_Max_Cross_Entropy(output_layer)                 # 誤差関数

Dropoutレイヤに入力されたデータは学習時、dropout_ratioで定められた割合でノードをランダムに残し、それ以外のノードを存在しないものとして扱います。またpredict関数の実行時には、ドロップアウトは行わず、それぞれのユニットの出力に対し、ドロップアウトされなかったノードの割合をかけた結果を出力します。

●スパース正則化オートエンコーダ オートエンコーダを用いた事前学習時、目的関数にスパースな重みを付け加えたい場合は、Sparceレイヤを使用します。Sparceレイヤを追加したレイヤの重みは、少ないユニット数で出力を表現しようとします。詳しくは、Sparceレイヤのリファレンスを参照してください。

1
2
3
4
5
6
input_layer = Input(784)                    # 入力層
middle_layer = Dense(input_layer, 100)      # 全結合層1
activate = Relu(middle_layer)               # 1に対する活性化関数
sparse = Sparse(activate)                   # 全結合層2
output_layer = Dense (sparse, 784)          # 誤差関数
loss = Mean_Squared_Error(output_layer)

●分岐を持つネットワーク構造 ReNomでは次のような分岐を持つネットワーク構造をSplitとConcatというレイヤによって表現します。Splitレイヤでは、引数として与えられたレイヤの出力を複数のレイヤに対して出力します。Concatレイヤでは、複数のレイヤからの入力をまとめて、一つのレイヤとして出力します。

_images/branch.png

Figure 17: 分岐構造を持つネットワーク

分岐を持つネットワーク構造は、次のようなコードで書くことができます。ReNomでは、全結合レイヤについて、分岐を持つネットワークの構築が可能です。バージョンアップにつれ、畳み込み層などのレイヤの分岐を逐次実装していきます。

1
2
3
4
5
6
7
input_layer = Input(4)              # 入力層
split = Split(input_layer)          # 全結合層1
layer1_1 = Dense(split, 2)          # 1に対する活性化関数
layer1_2 = Dense(layer1_1,4)        # 全結合層2
layer2_1 = Dense(split,3)           # 誤差関数
concat = Concat(layer1_2, layer2_1)
output_layer = Dense(concat)
[1] THE MNIST DATABASE of handwritten digits http://yann.lecun.com/exdb/mnist
[2] matplotlib http://matplotlib.org/
[3] scilit-learn http://scikit-learn.org/stable/
[4] Cifar10 dataset https://www.cs.toronto.edu/ kriz/cifar.html
[5] Large Movie Review Dataset http://ai.stanford.edu/ amaas/data/sentiment/
[6] Paul J.Werbos (1990) Backpropagation through time: what it does and how to do it IEEE, VOL 78,NO. 10, OCTOVER 1990