2. チュートリアル

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

../../_images/mnist.png

Figure 2: 手書き文字

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 3: ニューラルネットワーク

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import renom.layers as L
from renom.model import Model

# ニューラルネットワークモデル
model = Model(
    layers = [
        L.Input(unit = 784),        # 入力層
        L.Dense(unit = 100),        # 中間層
        L.Sigmoid(),                # 活性化関数
        L.Dense(unit = 10),        # 出力層
        L.Soft_max_cross_entropy()   # 誤差関数
    ])
../../_images/network2.png

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

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

1
2
# 学習アルゴリズム
opt = Sgd(model, lr = 0.01, momentum = 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で、 学習を30epoch行いました。グラフのプロットにはpythonのグラフィックライブラリである matplot [2] を使用しております。学習曲線は次のようになりました。

../../_images/mnist_graph.png

Figure 5: MNIST学習曲線

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

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

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

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

以下に今回の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
83
84
85
86
#!/usr/bin/env python
# encoding: utf-8

from __future__ import division, print_function
import matplotlib.pyplot as plt
import numpy as np

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.model import Model
import renom.layers as L
from renom.optimizers import Sgd

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

X = mnist.data
y = mnist.target

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

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)

# モデル構築
model = Model(
    layers=[
        L.Input(28 * 28),
        L.Dense(100),
        L.Relu(),
        L.Dense(10),
        L.Soft_max_cross_entropy()
    ])
opt = Sgd(model, lr=0.01, momentum=0.7)

# 学習ループ構築
N = X_train.shape[0]
epoch = 30
batch = 64
count = 0
learning_curve = []
test_learning_curve = []

for i in xrange(epoch):
    perm = np.random.permutation(N)
    loss = 0
    for j in xrange(0, N // batch):
        # ランダムにミニバッチを作成
        train_batch = X_train[perm[j * batch:(j + 1) * batch]]
        responce_batch = labels_train[perm[j * batch:(j + 1) * batch]]

        # 各データに対する学習
        l = opt.forward(train_batch, responce_batch)
        opt.backward()
        opt.update()
        loss += l

    # epochごとの平均訓練誤差を計算
    train_loss = loss / (N // batch)
    learning_curve.append(train_loss)

    # テスト誤差を計算
    test_loss = opt.forward(X_test, labels_test, training=False)
    test_learning_curve.append(test_loss)

    # epochごとの学習率を表示
    print("epoch %03d train_loss:%f test_loss:%f" % (i, train_loss, test_loss))

# 学習済みモデルをテスト
predictions = np.argmax(opt.predict(X_test), axis=1)
print(confusion_matrix(y_test, predictions))
print(classification_report(y_test, predictions))

# 学習曲線を表示
plt.hold(True)
plt.plot(learning_curve, linewidth=3, label="train_loss")
plt.plot(test_learning_curve, linewidth=3, label="test_loss")
plt.ylabel("error")
plt.xlabel("epoch")
plt.legend()
plt.show()

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

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

../../_images/cifar10.png

Figure 6: Cifer-10

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

../../_images/cnn.png

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

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

 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
from renom.model.model import Model
import renom.layers as L

# ネットワークモデルの定義
model = Model(
    layers=[
        L.Input((3, 32, 32)),
        L.Conv2d(32),
        L.Relu(),
        L.Conv2d(32),
        L.Relu(),
        L.Pool2d(filter_size=(2, 2), stride=(2, 2)),
        L.Dropout(dropout_ratio=0.25),

        L.Conv2d(64),
        L.Relu(),
        L.Conv2d(64),
        L.Relu(),
        L.Pool2d(filter_size=(2, 2), stride=(2, 2)),
        L.Dropout(dropout_ratio=0.25),

        L.Flatten(),

        L.Dense(512),
        L.Dropout(dropout_ratio=0.5),
        L.Relu(),
        L.Dense(10, weight_scale=0.01),
        L.Soft_max_cross_entropy()

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

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

../../_images/cnn_accuracy.png

Figure 8: 学習曲線

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

以下に、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
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division, print_function
import os
import cPickle
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.cross_validation import train_test_split
from renom.model.model import Model
from renom.optimizers import Adam
import renom.layers as L
from renom.utils import trainer
from renom.cuda import cuda
import sys

args = sys.argv
if len(args) > 1:
    if "gpu" in args[1]:
        cuda.init()
        gpu = True
else:
    print("If you want to use gpu, you can use it with $ python cnn.py gpu")


# Cifar10 データのロード
def unpickle(f):
    data_dir = "cifar-10/"
    fo = open(os.path.join(data_dir, f), 'rb')
    d = cPickle.load(fo)
    fo.close()
    return d


paths = ["data_batch_1", "data_batch_2", "data_batch_3",
         "data_batch_4", "data_batch_5"]

loaded = [(data["data"], data["labels"]) for data in map(unpickle, paths)]
train_d = np.vstack([data[0] for data in loaded])
label = np.vstack([data[1] for data in loaded])

# データの前処理
train_d = train_d.reshape(50000, 3, 32, 32)
X = train_d.copy()
Y = label.reshape(-1, 1)
X = X.astype(np.float32)
X /= 255.

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


# ネットワークモデルの定義
model = Model(
    layers=[
        L.Input((3, 32, 32)),
        L.Conv2d(32),
        L.Relu(),
        L.Conv2d(32),
        L.Relu(),
        L.Pool2d(filter_size=(2, 2), stride=(2, 2)),
        L.Dropout(dropout_ratio=0.25),

        L.Conv2d(64),
        L.Relu(),
        L.Conv2d(64),
        L.Relu(),
        L.Pool2d(filter_size=(2, 2), stride=(2, 2)),
        L.Dropout(dropout_ratio=0.25),

        L.Flatten(),

        L.Dense(512),
        L.Dropout(dropout_ratio=0.5),
        L.Relu(),
        L.Dense(10, weight_scale=0.01),
        L.Soft_max_cross_entropy()
    ])

# 学習アルゴリズムの定義
opt = Adam(model)

trained = trainer(opt, train_x=train_x, train_y=label, validate_x=test_x, validate_y=answer,
                  epoch=100, save_result=False, batch_size=100)

# テスト
o = opt.predict(test_x)
if gpu:
    o = cuda.to_cpu(o)
predictions = np.argmax(o, axis=1)

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

畳み込みニューラルネットワークでは、各層が持つユニットの数が大きくなりがちです。 その結果各層への入力値が非常に大きい値を取り、オーバフローを起こすことがあります。 そのような場合は、予め重みの初期値を小さく設定しておくことが有効です。

上のネットワークではConv2d層とDense層にそれぞれ “weight_scale = 0.01” という値を セットしています。これは、重みを標準偏差0.01の正規分布で初期化することを表しています。

逆に”weight_scale = 1.0”という値をセットして学習を行うと以下のように値がオーバフローし、 学習が失敗することがあります。

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
6
7
8
9
import renom.layers as L
from renom.model.model import Model
model = Model(
    layers = [
        L.Input(20000),             # 入力層
        L.Lstm(128),                # LSTM レイヤ
        L.Dense(1),                 # 出力層
        L.Sigmoid_cross_entropy()   #シグモイド交差エントロピー
    ])

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法を用いて学習させることができます。

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
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
# encoding: utf-8
from __future__ import division, print_function
import os
import sys
import cPickle
import numpy as np
from sklearn.metrics import confusion_matrix, classification_report
from renom.optimizers.adam import Adam
from renom.model.model import Model
import renom.layers as L
from renom.utils.trainer.train import train

MAX_WORDS = 128
MAX_VOCABS = 20000 - 2
UNKNOWN_WORD = 1
file_name = "movie_data.pickle"


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

# load関数の定義


def load():
    # データの加工には、時間がかかります。
    # そのため、一度作ったデータをpickle化し、
    # 次回からはそれを読み込むことを推奨します。
    replace_chrs = ["<", "br", ">", "/"]
    sub_dirs = ["train/pos/", "train/neg/", "test/pos/", "test/neg/"]
    if not os.path.exists(file_name):
        print(file_name + " is not found, generating the data.")
        voc = {}
        train_label = []
        train_data = []
        test_label = []
        test_data = []
        # 各単語の出現数を数える
        for dir in sub_dirs:
            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:
                        for repc in replace_chrs:
                            line.replace(repc, "")
                        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 sub_dirs:
            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)
                        # pad with 0
                        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)
        with open(file_name, "wb") as f:
            out = train_data, train_label, test_data, test_label
            cPickle.dump(out, f)
    else:  # 作成済みのデータをロードする場合
        train_data, train_label, test_data, test_label = cPickle.load(open(file_name, "r"))
    return train_data, train_label, test_data, test_label
# load関数定義終わり


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

model = Model(
    layers=[
        L.Input(MAX_VOCABS + 2),
        L.Lstm(128, weight_scale=0.01),
        L.Dense(1, weight_scale=0.01),
        L.Sigmoid_cross_entropy(),
    ]
)

opt = Adam(model)

N = len(data)
batch = 100
epoch = 10

for n in range(epoch):  # 学習ループ
    perm = np.random.permutation(N)
    error = 0
    for i in range(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 range(MAX_WORDS):  # BPTT法の実行
            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))

N = len(test)
o = []
for i in range(N // batch):
    temp = 0
    for t in range(MAX_WORDS):  # BPTT法での予測
        vec = embed(test[i * batch:(i + 1) * batch, t])
        temp += opt.predict(vec)
    o.append(temp / float(MAX_WORDS))
o = np.asarray(o).reshape(-1, 1)
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
6
7
model = Model(layers=[
    Input(784),
    Dense(100),
    Relu(),
    Dense(784),
    Mean_Squread_Error()
])

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

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

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

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

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

1
2
3
4
5
6
7
8
9
    layers=[
        L.Input(784),  # 入力層
        L.Dense(units**2, name="mid", weight_scale=0.01),  # 全結合層
        L.Relu(),  # Relu 活性化関数
        L.Sparse(),  # スパース層を追加
        L.Dense(784),  # 出力層
        L.Mean_squared_error()  # 誤差関数
    ])

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

../../_images/ae1.png

Figure 10: b = 0.0

../../_images/ae2.png

Figure 11: b = 0.1

../../_images/ae3.png

Figure 12: b = 2.0

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

model_name["dst_layer_name"].parameter["w"] = model_name["src_layer_name"].parameter["w"]

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

 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
# encoding: utf-8
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.optimizers import Rmsprop
from renom.model.model import Model
import renom.layers as L

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

X = mnist.data
y = mnist.target

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

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

units = 5
model = Model(
    layers=[
        L.Input(784),  # 入力層
        L.Dense(units**2, name="mid", weight_scale=0.01),  # 全結合層
        L.Relu(),  # Relu 活性化関数
        L.Sparse(),  # スパース層を追加
        L.Dense(784),  # 出力層
        L.Mean_squared_error()  # 誤差関数
    ])

ae = Rmsprop(model, weight_decay=0.01)

N = train.shape[0]
batch = 50
batch_n = int(np.ceil(N / batch))
for i in range(100):
    perm = np.random.permutation(N)
    error = 0
    for j in range(batch_n):
        train_batch = train[perm[j * batch:(j + 1) * batch]]
        error += ae.forward(train_batch, train_batch)
        ae.backward()
        ae.update()
    print('Epoch %03d: error %f' % (i, error / batch_n))
    for j in range(units**2):
        plt.subplot(units, units, j + 1)
        plt.axis('off')
        temp = ae["mid"].parameter["w"][:, j].reshape(28, 28)
        plt.imshow(temp, 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 = model["visible_layer_name"]                    # 可視層
hidden_layer = model["hidden_layer_name"]           # 隠れ層
opt = CD_Method(visible_layer, hidden_layer)        # 学習方法定義
for ep in range( epoch ):
    opt.train(train_data, label_data)               # CD 法を用いた学習
    opt.update()                                    # 重みの更新

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

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

../../_images/rbm_graph.png

Figure 13: 学習曲線

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

from renom.optimizers import Sgd
from renom.model.model import Model
import renom.layers as L
from renom.bm.cd_method import CD_Method
from renom.utils import trainer

# RBMの学習用関数


def cdm_train(cdm, train, epoch=100, batch=100):
    N = train.shape[0]
    for i in range(epoch):
        perm = np.random.permutation(N)
        loss = 0
        for j in range(N // batch):
            indices = perm[j * batch:(j + 1) * batch]
            train_batch = train[indices]
            l = cdm.train(train_batch)
            cdm.update()
            loss += l
        lc = loss / (N // batch)
        print("Epoch{0:>3d}: Error {1:>2.5f}".format(i, lc))


# 学習曲線を取得するcallback
tr_loss = []
ts_loss = []


def get_loss(train_loss, test_loss, model):
    global tr_loss, ts_loss
    tr_loss.append(train_loss[-1])
    ts_loss.append(test_loss[-1])


np.random.seed(111)
mnist = fetch_mldata('MNIST original', data_home='./')

X = mnist.data
y = mnist.target

# 画像を二値化
X = X > 128
X = X.astype(np.float32)

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

train = X_train
labels = LabelBinarizer().fit_transform(y_train)
answer = LabelBinarizer().fit_transform(y_test)

# ネットワークモデルの定義
model = Model(
    layers=[
        L.Input(784, name="in"),
        L.Dense(100, name="md1"),
        L.Sigmoid(),
        L.Dense(50, name="md2"),
        L.Sigmoid(),
        L.Dense(10),
        L.Soft_max_cross_entropy()
    ])

# RBMで事前学習するモデルと事前学習無しモデルを定義
opt_with_rbm = Sgd(model, 0.01)
opt_vanilla = Sgd(copy.deepcopy(model), 0.01)


# RBMの学習パラメータ
epoch = 30
batch = 128

# RBMによる事前学習
cdm1 = CD_Method(model["in"], model["md1"])
cdm_train(Sgd(cdm1, 0.01), train, epoch, batch)

cdm2 = CD_Method(model["md1"], model["md2"])
cdm_train(Sgd(cdm2, 0.01), cdm1.visible_to_hidden(train), epoch, batch)

# 事前学習済みモデルの学習
tr_loss_with_rbm = []
ts_loss_with_rbm = []
tr_loss = tr_loss_with_rbm
ts_loss = ts_loss_with_rbm
trained_with_rbm = trainer(opt_with_rbm, train_x=train, train_y=labels,
                           validate_x=X_test, validate_y=answer, callback=get_loss,
                           epoch=50, save_result=False, batch_size=128)
predicted_with_rbm = np.argmax(trained_with_rbm.predict(X_test), axis=1)

# 事前学習無しモデルの学習
tr_loss_vanilla = []
ts_loss_vanilla = []
tr_loss = tr_loss_vanilla
ts_loss = ts_loss_vanilla
trained_vanilla = trainer(opt_vanilla, train_x=train, train_y=labels,
                          validate_x=X_test, validate_y=answer, callback=get_loss,
                          epoch=50, save_result=False, batch_size=128)
predicted_vanilla = np.argmax(trained_vanilla.predict(X_test), axis=1)

# 結果の表示
plt.plot(tr_loss_with_rbm, label="train_loss with rbm")
plt.plot(ts_loss_with_rbm, label="test_loss with rbm")
plt.plot(tr_loss_vanilla, label="train_loss vanilla")
plt.plot(ts_loss_vanilla, label="test_loss vanilla")

print("")
print("/// Result with rbm ///\n")
print(confusion_matrix(y_test, predicted_with_rbm))
print(classification_report(y_test, predicted_with_rbm))

print("/// Result vanilla ///\n")
print(confusion_matrix(y_test, predicted_vanilla))
print(classification_report(y_test, predicted_vanilla))

plt.ylabel("loss")
plt.xlabel("epoch")
plt.legend()
plt.tight_layout()
plt.show()
[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