おひとりさま現役パラレルワーカーフクコろぐ

これからの会社員は、複数の収入源をもとう! 会社に翻弄されずアナタらしく仕事ができる。 そんなパラレルワーカーを広めるフクコのブログです。

【文系プログラミング初心者deE資格】ゼロつくディープラーニング #40

f:id:fukuko-parallel-work:20211209203437j:plain

こんにちは!

現役パラレルワーカー フクコです。

f:id:fukuko-parallel-work:20210212204833p:plain



前回の記事↓に続き
www.fukuko-parallel-work.com





来年の2月の試験に向けてE資格試験勉強中のため


E資格とは?の記事はコチラ↓

www.fukuko-parallel-work.com




ゼロつくシリーズでおなじみ

オーライリーから出版されている

ディープラーニングの本格的な入門書でよくおススメされる

「ゼロからつくる Deep Learning」本


f:id:fukuko-parallel-work:20211029220008j:plain





この本↑を毎日5ページずつコツコツこなす

約2か月間で今年中に終了するので

来年のE資格試験までにこれで基礎力をつけることにしました。(^^)


ついつい私は何もないとだらけてしまうので(笑)

毎日5ページ終わった後の記録とまとめを書いていこうと思います。


と、まとめに入る前に…

やる気を出すためのコトバをシェアします!!(主に私のやる気を出すために 笑)

「刀の打ち方を知ってるか。

 刀はな、叩いて叩いて叩き上げて不純物や余分なものを飛ばし

 鋼の純度を高め強靭な刀を造るんだ。」


by じいちゃん(桑島慈悟郎)


私の大好きな鬼滅の刃から

じいちゃんこと、桑島慈悟郎(くわじま じごろう)の名言です。


そう!

たとえわからなくても、間違っても

何度も、何度も、ひたすら続けているうちに

どんどん精度が上がってきて

気がついたら周囲の誰よりもすごく出来るようになっているんですよね。


そうなるためには…

ひたすらコツコツ努力、毎日精進あるのみ!!!


じいちゃん、どうもありがとう! (^0^)

今日もやる気が出てきました! 

よし!! 今日も頑張るぞ~! お~!! 

というコトで、

私も叩いて叩いて叩きあげて、ゼッタイE資格、受かってみせますよ!  

なので、今日もノルマはゼッタイこなします!


ではでは、いい加減まとめに入ります。笑



その前に本の目次の紹介です。

ゼロつくディープラーニングは、下記↓の合計8章で構成されています。

本の目次

  • 5章 誤差伝播法
  • 6章 学習に関するテクニック



ちなみに…

ゼロつくディープラーニング第1章はPython入門のセクション(20ページ分)なので、

とりあえず今回私は飛ばし、第2章からまとめています。

現在は、第7章 畳み込みニューラルネットワークで~す。



第7章 畳み込みニューラルネットワーク つづき


第7章のテーマは、

畳み込みニューラルネットワーク(Convolutional Neural Network: CNN)

7.5 CNNの実装のつづき


昨日の続きで、


単純なCNNのネットワークを構成してみる。

↓の図が例


f:id:fukuko-parallel-work:20211203215242j:plain


このCNN↑のネットワークを、SimpleConvNetというクラスで実装してみる。


Pythonで実装すると↓のとおり

# 7.5 CNNの実装
# 単純なCNNのネットワークを構成

# coding: utf-8
# まずはライブラリインポート
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient


class SimpleConvNet:
#     ""単純なConvNet

#     conv - relu - pool - affine - relu - affine - softmax
    
#     Parameters
#     ----------
#     input_size : 入力サイズ(MNISTの場合は784)
#     hidden_size_list : 隠れ層のニューロンの数のリスト(e.g. [100, 100, 100])
#     output_size : 出力サイズ(MNISTの場合は10)
#     activation : 'relu' or 'sigmoid'
#     weight_init_std : 重みの標準偏差を指定(e.g. 0.01)
#         'relu'または'he'を指定した場合は「Heの初期値」を設定
#         'sigmoid'または'xavier'を指定した場合は「Xavierの初期値」を設定
        
    def __init__(self, input_dim =(1, 28, 28),
                conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
                hidden_size = 100, output_size=10, weight_init_std=0.01):
        filter_num = conv_param['filter_num']
        filter_size = conv_param['filter_size']
        filter_pad = conv_param['pad']
        filter_stride = conv_param['stride']
        input_size = input_dim[1]
        conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
        pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))
        
        #重みの初期化
        
        self.params = {}
        self.params['W1'] = weight_init_std * \
                            np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
        self.params['b1'] = np.zeros(filter_num)
        self.params['W2'] = weight_init_std * \
                            np.random.randn(pool_output_size, hidden_size)
        self.params['b2'] = np.zeros(hidden_size)
        self.params['W3'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b3'] = np.zeros(output_size)
        
        #レイヤの生成
        
        self.layers = OrderedDict()
        self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
                                          conv_param['stride'], conv_param['pad'])
        
        self.layers['Relu1'] = Pooling(pool_h=2, pool_w=2, stride=2)
        self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])
        self.layers['Relu2'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])
        
        self.last_year = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    def loss(self, x, t):
        """損失関数を求める
        引数のxは入力データ、tは教師ラベル
        """
        y = self.predict(x)
        return self.last_layer.forward(y, t)

    def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        acc = 0.0
        
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict(tx)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt) 
        
        return acc / x.shape[0]

    def numerical_gradient(self, x, t):
        """勾配を求める(数値微分)

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        各層の勾配を持ったディクショナリ変数
            grads['W1']、grads['W2']、...は各層の重み
            grads['b1']、grads['b2']、...は各層のバイアス
        """
        loss_w = lambda w: self.loss(x, t)

        grads = {}
        for idx in (1, 2, 3):
            grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])

        return grads

    def gradient(self, x, t):
        """勾配を求める(誤差逆伝搬法)

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        各層の勾配を持ったディクショナリ変数
            grads['W1']、grads['W2']、...は各層の重み
            grads['b1']、grads['b2']、...は各層のバイアス
        """
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 設定
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
        grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads
        
    def save_params(self, file_name="params.pkl"):
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)

    def load_params(self, file_name="params.pkl"):
        with open(file_name, 'rb') as f:
            params = pickle.load(f)
        for key, val in params.items():
            self.params[key] = val

        for i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):
            self.layers[key].W = self.params['W' + str(i+1)]
            self.layers[key].b = self.params['b' + str(i+1)]


↑のようにSimpleConvNetで初期化をしてしまえば

推論を行うpredictメソッド

損失関数の値を求めるlossメソッドを実装

その後

最後の層のSoftmaxWithLossレイヤまでforward処理を行う


パラメータの勾配は、誤差逆伝播法によって求める

最後に

gradsというディクショナリに各重みパラメータの勾配を格納

7.6 CNNの可視化


畳み込み層の可視化を通じて

CNNで何が行われているのか探索していく

7.6.1 1層目の重みの可視化


MNISTデータセットにたいして単純なCNNの学習を行ってみる


Pythonで実装すると↓のとおり。

# 7.6.1 1層目の重みの可視化

# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNet

def filter_show(filters, nx=8, margin=3, scale=10):
    """
    c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
    """
    FN, C, FH, FW = filters.shape
    ny = int(np.ceil(FN / nx))

    fig = plt.figure()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

    for i in range(FN):
        ax = fig.add_subplot(ny, nx, i+1, xticks=[], yticks=[])
        ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.show()


network = SimpleConvNet()
# ランダム初期化後の重み
filter_show(network.params['W1'])

# 学習後の重み
network.load_params("params.pkl")
filter_show(network.params['W1'])


学習前と学習後の1層目の画像の表示は次↓のとおり


学習前


f:id:fukuko-parallel-work:20211209213411j:plain


学習後


f:id:fukuko-parallel-work:20211209213428j:plain



↑の画像データが示すように

タダのぼんやりした映像に見えるが(笑)

規則性のあるフィルターが「何を見ている」かというと、

エッジ=色が変化する境目

ブロブ局所的に塊のある領域

を見ている。



このように

畳み込み層のフィルター

エッジやブロブなどのプリミティブ(原始的、基本的)な情報を

抽出することがわかる

7.6.2 階層構造による情報抽出


1層目の畳み込み層では、

エッジやブロブなどのプリミティブ(原始的、基本的)な情報が抽出される

では、

畳み込み層を何層も重ねると

層が深くなるにつれて、より複雑で抽象化された情報が抽出される

つまり、

層が深くなるにつれて

ニューロンは単純な形状から「高度」な情報へと変化していく

今日のまとめ


ハイ、今日はここまで!!

第7章はあともう少し!

引き続き頑張りまっす。


最後まで読んでくださり、ありがとうございます!

フクコ


ディープラーニング入門書おススメ本


E資格とは?

www.fukuko-parallel-work.com