ディープラーニングについてディープにラーニング。

思いっきり、自分が学んだことのアウトプットしていきますが、何分初心者。もし何かアドバイス等頂けましたら幸いです!

分散と標準偏差

知らない単語を知った。

分散と標準偏差


全く難しい話ではない。

分散も、標準偏差も同じことを言っている。
つまりは数字のばらつきを表す指標として使われており、
数字が多ければ多いほど、ばらついている、ということ。


f:id:kawam0t0:20200101175103p:plain

こちらが分散。

求め方としては、とてもシンプル。

①まずは、すべての平均値をとる。
②平均値をとったら、実際の数字と今回とった平均値との差を二乗し平均をとる
これにより数字が大きいとばらつきがみられる。

ちなみに、標準偏差はこの分散に√をかぶせたもの。
単位がKGになるので、
基本的には、標準偏差を使用されることが多いらしい。

分散には、np.var(x)を使って、標準偏差にはnp.std(x)を使用する。

偏微分・全微分

微分つながりで今回は偏微分について。
そこまで今回は難しくなかった。

偏微分について

複数の変数を持つ関数に対して、一つの変数にしか微分しないことを偏微分という。

ちょっと言葉ではまたわかりにくいので、
下の図を参照。

f:id:kawam0t0:20200101123413p:plain

こちらの関数はX / Yの二つの変数を保持している。
偏微分はこのXないしYのどちらかだけに微分を行う、というもの。

人工知能で使われる場面としては、
一つのパラメータが及ぼす影響を求めるときとかにつかう。

偏微分については、以上。
微分の計算をちゃんと抑えていればそこまで、難しい話ではない。

次に全微分について

微分

偏微分人工知能の分野において、
一つのパラメータがどれくらい影響を与えるか
微分は、全てのパラメータが与える影響について求めたものである。
例えば、2変数関数Zの場合は図の様に表現する

f:id:kawam0t0:20200101132259p:plain


これは2変数関数の場合なので、より一般的にしたもので行くと、下の図になる。

f:id:kawam0t0:20200101132425p:plain


では、ここで全結合の例について記載しておく。

f:id:kawam0t0:20200101132612p:plain


ここで、ΔXとΔYが各偏微分に足算されている点には気を付ける必要がある。
あくまで、変化量である。


そういうや連鎖律は掛け算だった…


では、また。

連鎖律

今日は微分の流れで連鎖律についてもアウトプット。


合成関数とは

連鎖律を扱う前に合成関数の説明。
例えば、こんな関数があったとする。y=(x**2 + 1)**3
二つの関数に置き換えることでき、

y = u**3
u = x**2 + 1

このように、違う関数を挟める関数のことを合成関数という。


連鎖律


合成関数の微分を書く合成関数の導関数の積で表現できる。
これを連鎖律、という。

↓公式
f:id:kawam0t0:20200101112923p:plain


まぁ、公式だけ見ても、え?ってなるんで、
例えを出す。

こちらの式で考えてみる。
f:id:kawam0t0:20200101113056p:plain


まずは、UとYに関数を別々にする。
そこから、それぞれの微分を行って(導関数を抽出して)、
それぞれ出てきたものを掛け算して完了。


f:id:kawam0t0:20200101113242p:plain

極限と微分

今年の年始一発目も数学から入る。

今回は、極限と微分
とても、難しかった…
アウトプットを重点的にします。


極限とは

極限とは、関数における変数の値(Y)をある値に近づけるとき、極限まで、その値に近づける事。
例えば、Y=x+1の関数でYをどうしても1に近づけたいとする。
Xが1の時はYは2
Xが0の時はYは1

というように、極限まである関数の変数をある値に近づけたいとき、関数の値が限りなくちかづく値のこと。
数式で書くと、こんな感じになるらしい。

f:id:kawam0t0:20200101101053p:plain

微分

関数式 Y=f(x)において、xの微小な変化量ΔXとして、xをΔx分だけ変化させた際の値の書き方は、こうなる。

f:id:kawam0t0:20200101101335p:plain


この時、yの変化量を示そうとしたら、こうなる。

f:id:kawam0t0:20200101101524p:plain

じゃあ、Δx(xの変化量)とΔy(yの変化量)の割合の式は

f:id:kawam0t0:20200101101740p:plain


で、Δxの値を0に限りなく近づける極限を考えた場合、
さっき出てきたlimで表現され、新たな関数y=f'(x)として定義される。

f:id:kawam0t0:20200101102012p:plain

このf'(x)のことをf(x)の導関数という。
そして、f(x)からf'(x)を得ることをf(x)を微分するという。

こんな風に、表現する場合もある。

f:id:kawam0t0:20200101102347p:plain

後述するが、このfを前持ってきてもう少し見えやすくする公式もある。

この導関数によって、xにたいするyの変化の割合(勾配)を求めることができる。

ちょうどこんな図の様に。

f:id:kawam0t0:20200101103114p:plain

そして、とても重要なのが、
この接線の求められる式だ。

y=f'(a)x+f(a)-f'(a)a


がこの求められる式になる。
つまり、導関数にxを掛け算し、さらに関数を足したものをさらに導関数で引き算を行う。
ただし、あくまで、これは接線の公式だし導関数が導かれないと表せない。

色々な公式があるんだけど、
下の二次関数を使って、微分をしてみたほうが早い。

f:id:kawam0t0:20200101104024p:plain

①まずは公式を使って、それぞれにf(x)の関数を導関数に当てはめてみる。
微分の公式より、書く定数を前に出しす
③その前に出した定数と○○乗されている数字をかけていく
④3×2+4×1-5×0
⑤さらに公式からxの○○乗の○○を1ずつ引いていってさっきの式にはめ込む。
そうすると、導関数を求めることができて、勾配も計算できるようになった。

f:id:kawam0t0:20200101104054p:plain


ここからは描写の話になる。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline


def my_func(x):
    return 3 * x **2 + 4 * x - 5

def my_func_diff(x):
    return 6 * x + 4

#-3~3の50個の数字
x = np.linspace(-3,3)
y = my_func(x)


a = 1
y_t = my_func_diff(a)*x + my_func(a)-my_func_diff(a) * a

plt.plot(x , y , label="Y")
plt.plot(x,y_t , label="X")

plt.legend()

f:id:kawam0t0:20200101104606p:plain

図で書いたほうが勾配の傾き具合もわかってGOOD。

ではまた。

固有値と固有ベクトル

今日からUDEMYに切り替える。

今回は、固有値固有ベクトルについて


固有ベクトル

固有ベクトルとは、
ある行列に演算をしても、与えられた定数分にしか増加・減少せず、向きは一切
変わらないベクトルのこと。
その与えられた定数のことを固有値とよぶ。

f:id:kawam0t0:20191231180315p:plain

式はこの式になる。
行列AにベクトルのXをかけても、λ分の定数分、長さは変化するが、
全体の向きは変わらない。

また、大事なこととして、
単位行列をかけたとしても、表列には影響はないため、このようにもかける。


f:id:kawam0t0:20191231180638p:plain

んで、
この右辺を全て左辺に移行するとこんな感じになる。
OKOK。ここまではOK。

f:id:kawam0t0:20191231180751p:plain


ここからがなんでこれが固有方程式になるかがわからんかった。

f:id:kawam0t0:20191231181709p:plain

わからんなりに、最後の方程式が固有方程式っていうらしい。

固有ベクトル、・固有値を求める

ここでは、固有ベクトル固有値の求め方について書く。
まずは固有値を求めていくのだが、
固有ベクトルがまだわかってない状態なので、
まずは、固有方程式の式を使って、λ(固有値)を調べる

det( A - λE ) = 0
Eは単位行列、でAは今回調べたい固有値の行列。

f:id:kawam0t0:20191231182110p:plain


この結果、λは2 か 5かである。
では、次に固有値が決まったので、固有ベクトルについても
求めてみる。
まず、→Xについては( P Q )と仮において、このように計算。

f:id:kawam0t0:20191231182421p:plain

この条件を満たすベクトルが、Aの固有ベクトルになるってこと。

CNNの実装

今日もアウトプットから。

f:id:kawam0t0:20191228184930p:plain

今回は、
このようなネットワークの構成でモデルを作成する。

重み、バイアスについては、
Conv層・Affine層(全結層)・Affine層(全結層)の3か所に当てはめる。

↓コード

import numpy as np

class SimpleConvNet:
    #input_dim -> 入力データ
    """
    5×5のフィルターが30個ある。パディングは0でストライドは1。
    更に隠れ層は100層あって、アウトプットは10個、最初の学習係数は0.01に設定
    引数にディクショナリーってあるんや笑
    """
    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"]
        #入力データのサイズ: 28
        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 = {}
        #まさかのこれでキーと値が追加できるってよ笑
        #np.random.randn -> ①重みの個数②チャネル数③行サイズ④列サイズ
        #5×5のフィルターサイズでチャネル数は1のフィルターが30個ある
        self.params["w1"] = weight_init_std * np.random.randn(filter_num,input_dim[0],filter_size,filter_size)
        #3次元のベクトルデータ
        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 , out_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"] = Relu()
        self.layers["pool1"] = 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_layers = SoftmaxWithLoss()
        
    """
    xは入力データでtは教師ラベルになる。
    """
    def predict(self,x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x
    
    def loss(self , x , t):
        y = self.predict(x)
        
        return self.lastLayer.forward(y,t)
        
    def gradient(self , x ,t):
        #forward
        self.loss(x,t)
        #backward→逆伝播を行う
        dout = 1
        dout = self.Lastlayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
        grads = {}
        grads["w1"] = self.layers["conv1"].dw
        grads["b1"] = self.layers["conv1"].db
        grads["w2"] = self.layers["Affine1"].dw
        grads["b2"] = self.layers["Affine1"].db
        grads["w3"] = self.layers["Affine2"].dw
        grads["b3"] = self.layers["Affine2"].db
        
        return grads

第一層での重みはサイズが5×5のチャネル数が1の重みが30個あった。
この重みは学習回数を重ねていったり、
層が深くなるにつれて、抽象的な曲線に変化していく。
初めの層は横、縦、など、かなり線形のグラデーションだった。

プーリングとコンボリューションの実装

今日もアウトプット。

今日はプーリング層について。

プーリング層

プーリングとは、各プーリング層で対応する値の最大値をとってくる演算のこと。
目的は、過学習の抑制や、位置変化に対しても対応ができるようになる点。
図のほうがわかりやすい。

f:id:kawam0t0:20191226220453p:plain

特徴はおよそ2点

・学習するパラメータがない。
 なぜなら、単純にプーリング層に沿って最大値を取るだけだから

チャンネル数は変化しない
 プーリングの演算によって、出力チャネルは変わらない
 →Convはフィルターの数で出力のチャネル数が変わった

Convolution / Pooling層の実装


畳み込み層やプーリング層の実装はとても複雑である。


なんせ、CNNにおいて、各層に流れるデータは4次元。
(10,2,30,30)で表される。
30×30サイズで2チャンネルのデータが10個あるってこと。


パイソンで実行。

import numpy as np

x = np.random.rand(10,2,30,30)

print(x.shape) #全チャネルの次元数
print(x[0]) #一つ目のデータの次元数 
print(x[0].shape) #(2,30,30) -> 1個目の次元数
print(x[1].shape) #(2,30,30) -> 2個目の次元数
im2colで4次元データを2次元データに

CNNでは、なんせ大量のデータを計算するので、

計算自体やたらめんどくさい。
そんな時は、im2colを使えば難なくクリア。

こいつは中々やり手なやつで、
入力データに対して、フィルターを適用するすべての領域を横方向に展開するって
言われても図で見たほうが早い。

f:id:kawam0t0:20191226221200p:plain

つまりは、3次元(バッチサイズが加わったら4次元)のでーたにたいして、まさかの2次元配列を実行してくれる。
こうすることで、多少のメモリは食うものの、
行列の計算ライブラリを使って、大きな掛け算ができたり、する。

f:id:kawam0t0:20191226221321p:plain

整理しておくと、im2colは3次元配列に対して、
横方向の配列に変換してくれる。
使われる場所は入力データからフィルター(カーネル)に行くときに行列の配列に変えて、更にそこから内積を求めてくれる。

そして、そのままでは全結合層ニューラルネットワークと同じになるので、
そこから4次元になんとリシェイプしてくれる。

パイソンでイメージをつかむためにコードでも記載する

class Convolution:
    #初期化
    def __init__(self,w,b,stride=1,pad=0):
        self.w = w
        self.b = b
        self.stride = stride
        self.pad = pad
    #順伝播    
    def forward(self , x):
        #重み
        fn , c , fh , fw = x.shape
        out_h = int(1 + (h + 2 * self.pad - fh) / self.stride)
        out_w = int(1 + (w + 2 * self.pad - fw) / self.stride)
        
        col = im2col(x , fh , fw ,self.stride , self.pad)
        #行列変換
        col_w = self.w.reshape(fn , -1).T
        #内積
        out = np.dot(col , col_w) + self.b
        #2次元の配列を4次元に変換
        out = out.reshape(n , out_h , out_w , -1).trainspose(0,3,1,2)
        
        return out
            
#プーリング層の実装
"""
パラメータを保持しない層で畳み込みを行わない。
その代わり、位置変化に対して対応可能になる。
過学習を抑えることができるのがメリット
"""

class Pooling:
    def __init__(self,pool_h , pool_w , stride=1,pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
    def forward(self , x):
        n , c , h , w = x.shape
        out_h = int(1+(h - self.pool_h) / self.stride)
        out_w = int(1 + (w - self.pool_w) / self.stride)
        #初期化
        col = im2col(x , self.pool_h , self.pool_w,self.stride,self.pad)
        col = col.reshape(-1,self.pool_h * self.pool_w)
        
        out = np.max(col,axis=1)
        out = out.reshape(n , out_h , out_w,c).transpose(0,3,1,2)
        return out

コメントとかそろってないけど、ご勘弁。
では、また。