分散と標準偏差
知らない単語を知った。
極限と微分
今年の年始一発目も数学から入る。
今回は、極限と微分。
とても、難しかった…
アウトプットを重点的にします。
極限とは
極限とは、関数における変数の値(Y)をある値に近づけるとき、極限まで、その値に近づける事。
例えば、Y=x+1の関数でYをどうしても1に近づけたいとする。
Xが1の時はYは2
Xが0の時はYは1
というように、極限まである関数の変数をある値に近づけたいとき、関数の値が限りなくちかづく値のこと。
数式で書くと、こんな感じになるらしい。
微分
関数式 Y=f(x)において、xの微小な変化量ΔXとして、xをΔx分だけ変化させた際の値の書き方は、こうなる。
この時、yの変化量を示そうとしたら、こうなる。
じゃあ、Δx(xの変化量)とΔy(yの変化量)の割合の式は
で、Δxの値を0に限りなく近づける極限を考えた場合、
さっき出てきたlimで表現され、新たな関数y=f'(x)として定義される。
このf'(x)のことをf(x)の導関数という。
そして、f(x)からf'(x)を得ることをf(x)を微分するという。
こんな風に、表現する場合もある。
後述するが、このfを前持ってきてもう少し見えやすくする公式もある。
この導関数によって、xにたいするyの変化の割合(勾配)を求めることができる。
ちょうどこんな図の様に。
そして、とても重要なのが、
この接線の求められる式だ。
y=f'(a)x+f(a)-f'(a)a
がこの求められる式になる。
つまり、導関数にxを掛け算し、さらに関数を足したものをさらに導関数で引き算を行う。
ただし、あくまで、これは接線の公式だし導関数が導かれないと表せない。
色々な公式があるんだけど、
下の二次関数を使って、微分をしてみたほうが早い。
①まずは公式を使って、それぞれにf(x)の関数を導関数に当てはめてみる。
②微分の公式より、書く定数を前に出しす
③その前に出した定数と○○乗されている数字をかけていく
④3×2+4×1-5×0
⑤さらに公式からxの○○乗の○○を1ずつ引いていってさっきの式にはめ込む。
そうすると、導関数を求めることができて、勾配も計算できるようになった。
ここからは描写の話になる。
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()
図で書いたほうが勾配の傾き具合もわかってGOOD。
ではまた。
固有値と固有ベクトル
今日からUDEMYに切り替える。
CNNの実装
今日もアウトプットから。
今回は、
このようなネットワークの構成でモデルを作成する。
重み、バイアスについては、
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個あった。
この重みは学習回数を重ねていったり、
層が深くなるにつれて、抽象的な曲線に変化していく。
初めの層は横、縦、など、かなり線形のグラデーションだった。
プーリングとコンボリューションの実装
今日もアウトプット。
今日はプーリング層について。
プーリング層
プーリングとは、各プーリング層で対応する値の最大値をとってくる演算のこと。
目的は、過学習の抑制や、位置変化に対しても対応ができるようになる点。
図のほうがわかりやすい。
特徴はおよそ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を使えば難なくクリア。
こいつは中々やり手なやつで、
入力データに対して、フィルターを適用するすべての領域を横方向に展開するって
言われても図で見たほうが早い。
つまりは、3次元(バッチサイズが加わったら4次元)のでーたにたいして、まさかの2次元配列を実行してくれる。
こうすることで、多少のメモリは食うものの、
行列の計算ライブラリを使って、大きな掛け算ができたり、する。
整理しておくと、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
コメントとかそろってないけど、ご勘弁。
では、また。