1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

多層パーセプトロンの境界値を可視化する

Last updated at Posted at 2020-03-08

#はじめに

学習後の境界値を可視化します。
以下のような図です。

moon_plot.png

入力の次元が2次元の場合は、このように境界を可視化できます。
今回は、TensorFlow の多層パーセプトロンを例に可視化してみたいと思います。

#目次
1.学習用データ
2.モデル(多層パーセプトロン)
3.可視化の方針
4.meshgrid
5.等高線プロット
6.全コード
履歴

#1.学習用データ

今回利用するデータは、scikit-learn の dataset にある三日月型のデータセットを利用します。
2次元のデータですので、平面で図示できます。

from sklearn import datasets
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
'''
三日月データを生成する
'''
N = 300  # データ数を指定する
X, y = datasets.make_moons(N, noise=0.2)
Y = y.reshape(N, 1)

# トレーニングデータとテストデータに分ける
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=0.8)

'''
データプロット
'''
plt.figure(figsize=(10, 6))
plt.ylim([-1,1.5])
plt.xlim([-1.5,2.5])
plt.scatter(X[y==1][:,0], X[y==1][:,1], s=10, marker='.')
plt.scatter(X[y==0][:,0], X[y==0][:,1], s=10, marker='^')
plt.show()

moon_plot.png

このデータを使って学習し、境界線を引いてみます。

#2.モデル(多層パーセプトロン)

学習は TensorFlow (1.x) の多層パーセプトロンでやってみます。
モデルは一例ですので、境界値を引くためのモデルは何でもかまいません。
隠れ層を1つにした。3層パーセプトロンのコードは以下です。

class ThreelayerPerceptron(object):
    '''
    初期化
    '''
    def __init__(self, n_in, n_hidden, n_out):
        self.n_in = n_in
        self.n_hidden = n_hidden
        self.n_out = n_out
        self.weights = []
        self.biases = []

        self._x = None
        self._y = None
        self._t = None,
        self._sess = None
        self._history = {
            'accuracy': [],
            'loss': []
        }

    '''
    重み
    '''
    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    '''
    バイアス
    '''
    def bias_variable(self, shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    '''
    モデル定義(3層パーセプトロン)
    '''
    def inference(self, x):
        
        # 入力層 - 隠れ層
        self.weights.append(self.weight_variable([self.n_in, self.n_hidden]))
        self.biases.append(self.bias_variable([self.n_hidden]))
        h = tf.nn.sigmoid(tf.matmul(x, self.weights[-1]) + self.biases[-1])

        # 隠れ層 - 出力層
        self.weights.append(self.weight_variable([self.n_hidden, self.n_out]))
        self.biases.append(self.bias_variable([self.n_out]))
        y = tf.nn.sigmoid(tf.matmul(h, self.weights[-1]) + self.biases[-1])

        return y

    '''
    損失関数
    '''
    def loss(self, y, t):
        cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(y) + (1 - t) * tf.log(1 - y)))
        return cross_entropy

    '''
    最適化アルゴリズム
    '''
    def training(self, loss):
        optimizer = tf.train.GradientDescentOptimizer(0.05)
        train_step = optimizer.minimize(loss)
        return train_step

    '''
    正解率
    '''
    def accuracy(self, y, t):
        correct_prediction = tf.equal(tf.cast(tf.greater(y, 0.5),tf.float32), t)
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        return accuracy
    
    '''
    予測
    '''
    def prediction(self, y):
        return tf.cast(tf.greater(y, 0.5),tf.float32)

    '''
    学習
    '''
    def fit(self, X_train, Y_train,
            nb_epoch=100, batch_size=100, p_keep=0.5,
            verbose=1):
        x = tf.placeholder(tf.float32, shape=[None, self.n_in])
        t = tf.placeholder(tf.float32, shape=[None, self.n_out])

        self._x = x
        self._t = t

        y = self.inference(x)
        loss = self.loss(y, t)
        train_step = self.training(loss)
        accuracy = self.accuracy(y, t)

        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

        self._y = y
        self._sess = sess

        N_train = len(X_train)
        n_batches = N_train // batch_size

        for epoch in range(nb_epoch):
            X_, Y_ = shuffle(X_train, Y_train)

            for i in range(n_batches):
                start = i * batch_size
                end = start + batch_size

                sess.run(train_step, feed_dict={
                    x: X_[start:end],
                    t: Y_[start:end]
                })
            loss_ = loss.eval(session=sess, feed_dict={
                x: X_train,
                t: Y_train
            })
            accuracy_ = accuracy.eval(session=sess, feed_dict={
                x: X_train,
                t: Y_train
            })
            self._history['loss'].append(loss_)
            self._history['accuracy'].append(accuracy_)

            if verbose:
                print('epoch:', epoch,
                      ' loss:', loss_,
                      ' accuracy:', accuracy_)

        return self._history

    def evaluate(self, X_test, Y_test):
        accuracy = self.accuracy(self._y, self._t)
        return accuracy.eval(session=self._sess, feed_dict={
            self._x: X_test,
            self._t: Y_test
        })

    def predict(self, X_test):
        prediction = self.prediction(self._y)
        return prediction.eval(session=self._sess, feed_dict={
            self._x: X_test
        })

#3.可視化の方針

境界値の可視化は、格子点ごとに学習結果をプロットすることで行います。
以下の黒い格子点をテストデータとみなし、結果を予測します。

20200308_01.png

仮に、青線が境界値だった場合、
・青線より上の格子点は結果が「0」
・青線より下の格子点は結果が「1」
というような結果が得られます。
この結果を後述する「等高線プロット」で色分けします。

#4.meshgrid

格子点は numpy.meshgrid を使います。
例えば、以下のようにすると、5 × 5 のリストが取得できます。

x, y  = np.meshgrid(np.arange( 0, 10, 2),
                    np.arange( 1, 6, 1))
x
y
array([[0, 2, 4, 6, 8],
       [0, 2, 4, 6, 8],
       [0, 2, 4, 6, 8],
       [0, 2, 4, 6, 8],
       [0, 2, 4, 6, 8]])
array([[1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3],
       [4, 4, 4, 4, 4],
       [5, 5, 5, 5, 5]])

これを x軸、y軸に指定することで、下図のような格子点の配列になります。
配列(grids)の 0 から順に、格子点の矢印の順番になります。

grids = np.array([x.ravel(), y.ravel()]).T

20200308_02.png

#5.等高線プロット

境界値は matplotlib の contourf で線を引きます。
第1引数にX軸、第2引数にY軸、第3引数に高さを設定します。
高さは、今回のケースでは2値分類の予測結果なので、「0」「1」のどちらかです。

from matplotlib.colors import ListedColormap
cmap = ListedColormap( ( "mistyrose","lightcyan") )
plt.contourf(x, y, pred, cmap=cmap)

格子点の予測を pred とすると、このコードで下図のような境界線を引くことができます。

moon_plot.png

#6.全コード

モデルによる学習を含めた、境界線プロットの全コードは以下です。

import numpy as np
import tensorflow as tf
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
import matplotlib.pyplot as plt

np.random.seed(0)
tf.set_random_seed(0)

class ThreelayerPerceptron(object):
    '''
    初期化
    '''
    def __init__(self, n_in, n_hidden, n_out):
        self.n_in = n_in
        self.n_hidden = n_hidden
        self.n_out = n_out
        self.weights = []
        self.biases = []

        self._x = None
        self._y = None
        self._t = None,
        self._sess = None
        self._history = {
            'accuracy': [],
            'loss': []
        }

    '''
    重み
    '''
    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape, stddev=0.01)
        return tf.Variable(initial)

    '''
    バイアス
    '''
    def bias_variable(self, shape):
        initial = tf.zeros(shape)
        return tf.Variable(initial)

    '''
    モデル定義(3層パーセプトロン)
    '''
    def inference(self, x):
        
        # 入力層 - 隠れ層
        self.weights.append(self.weight_variable([self.n_in, self.n_hidden]))
        self.biases.append(self.bias_variable([self.n_hidden]))
        h = tf.nn.sigmoid(tf.matmul(x, self.weights[-1]) + self.biases[-1])

        # 隠れ層 - 出力層
        self.weights.append(self.weight_variable([self.n_hidden, self.n_out]))
        self.biases.append(self.bias_variable([self.n_out]))
        y = tf.nn.sigmoid(tf.matmul(h, self.weights[-1]) + self.biases[-1])

        return y

    '''
    損失関数
    '''
    def loss(self, y, t):
        cross_entropy = tf.reduce_mean(-tf.reduce_sum(t * tf.log(y) + (1 - t) * tf.log(1 - y)))
        return cross_entropy

    '''
    最適化アルゴリズム
    '''
    def training(self, loss):
        optimizer = tf.train.GradientDescentOptimizer(0.05)
        train_step = optimizer.minimize(loss)
        return train_step

    '''
    正解率
    '''
    def accuracy(self, y, t):
        correct_prediction = tf.equal(tf.cast(tf.greater(y, 0.5),tf.float32), t)
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        return accuracy
    
    '''
    予測
    '''
    def prediction(self, y):
        return tf.cast(tf.greater(y, 0.5),tf.float32)

    '''
    学習
    '''
    def fit(self, X_train, Y_train,
            nb_epoch=100, batch_size=100, p_keep=0.5,
            verbose=1):
        x = tf.placeholder(tf.float32, shape=[None, self.n_in])
        t = tf.placeholder(tf.float32, shape=[None, self.n_out])

        self._x = x
        self._t = t

        y = self.inference(x)
        loss = self.loss(y, t)
        train_step = self.training(loss)
        accuracy = self.accuracy(y, t)

        init = tf.global_variables_initializer()
        sess = tf.Session()
        sess.run(init)

        self._y = y
        self._sess = sess

        N_train = len(X_train)
        n_batches = N_train // batch_size

        for epoch in range(nb_epoch):
            X_, Y_ = shuffle(X_train, Y_train)

            for i in range(n_batches):
                start = i * batch_size
                end = start + batch_size

                sess.run(train_step, feed_dict={
                    x: X_[start:end],
                    t: Y_[start:end]
                })
            loss_ = loss.eval(session=sess, feed_dict={
                x: X_train,
                t: Y_train
            })
            accuracy_ = accuracy.eval(session=sess, feed_dict={
                x: X_train,
                t: Y_train
            })
            self._history['loss'].append(loss_)
            self._history['accuracy'].append(accuracy_)

            if verbose:
                print('epoch:', epoch,
                      ' loss:', loss_,
                      ' accuracy:', accuracy_)

        return self._history

    def evaluate(self, X_test, Y_test):
        accuracy = self.accuracy(self._y, self._t)
        return accuracy.eval(session=self._sess, feed_dict={
            self._x: X_test,
            self._t: Y_test
        })

    def predict(self, X_test):
        prediction = self.prediction(self._y)
        return prediction.eval(session=self._sess, feed_dict={
            self._x: X_test
        })


'''
三日月データを生成する
'''
N = 300  # 全データ数
X, y = datasets.make_moons(N, noise=0.2)
Y = y.reshape(N, 1)

# トレーニングデータとテストデータに分ける
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=0.8)

'''
データプロット
'''
plt.figure(figsize=(10, 6))
plt.ylim([-1,1.5])
plt.xlim([-1.5,2.5])
plt.scatter(X[y==1][:,0], X[y==1][:,1], s=10, marker='.')
plt.scatter(X[y==0][:,0], X[y==0][:,1], s=10, marker='^')
plt.show()

'''
モデル設定
'''
model = ThreelayerPerceptron(n_in=len(X[0]),
                             n_hidden=3,
                             n_out=len(Y[0]))

'''
モデル学習
'''
history = model.fit(X_train, Y_train,
                    nb_epoch=400,
                    batch_size=20,
                    verbose=1)

'''
予測精度の評価
'''
accuracy = model.evaluate(X_test, Y_test)
print('accuracy: ', accuracy)

'''
グラフを色分けするための格子データを生成する
'''
meshgrids  = np.meshgrid(
               np.arange( -1.5, 2.6, 0.01 ),
               np.arange( -1, 1.6, 0.01 )
             )
xx = meshgrids[0]
yy = meshgrids[1]
# 格子点のリストにする
grids = np.array([xx.ravel(), yy.ravel()]).T

'''
格子データの予測結果を取得する
'''
pred = model.predict(grids)
pred = pred.reshape( xx.shape )

'''
データプロット
'''
from matplotlib.colors import ListedColormap
w_[1,1] * v_[1])
plt.figure(figsize=(10, 6))
plt.ylim([-1,1.5])
plt.xlim([-1.5,2.5])
cmap = ListedColormap( ( "mistyrose","lightcyan") )
plt.contourf(xx, yy, pred, cmap=cmap)
plt.scatter(X[y==1][:,0], X[y==1][:,1], s=10, marker='.')
plt.scatter(X[y==0][:,0], X[y==0][:,1], s=10, marker='^')
plt.show()

#履歴
2020/03/08 初版公開
2020/03/09 損失関数の定義修正

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?