為了完成畢設, 最近開始入門深度學習.
在此和大家分享一下本人閱讀魚書時的筆記,若有遺漏,歡迎斧正!
若轉載請注明出處!
一、感知機
感知機(perceptron)接收多個輸入信號,輸出一個信號。
如圖感知機,其接受兩個輸入信號。其中 \(\theta\) 為閾值,超過閾值 神經元就會被激活。

感知機的局限性在於,它只能表示由一條直線分割的空間,即線性空間。多層感知機可以實現復雜功能。
二、神經網絡
神經網絡由三部分組成:輸入層、隱藏層、輸出層

1. 激活函數
激活函數將輸入信號的總和轉換為輸出信號,相當於對計算結果進行簡單篩選和處理。
如圖所示的激活函數為階躍函數。

1) sigmoid 函數
sigmoid函數是常用的神經網絡激活函數。
其公式為:

如圖所示,其輸出值在 0到 1 之間。
2) ReLU 函數
ReLU(Rectified Linear Unit)函數是最近常用的激活函數。
3) tanh 函數
2. 三層神經網絡的實現
該神經網絡包括:輸入層、2 個隱藏層和輸出層。
def forward(network, x): # x為輸入數據
# 第1個隱藏層的處理,點乘加上偏置后傳至激活函數
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
# 第2個隱藏層的處理
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
#輸出層處理 identidy_function原模原樣輸出a3
a3 = np.dot(z2, W3) + b3
y = identify_function(a3)
return y # y為最終結果
3. 輸出層激活函數
一般來說,回歸問題選擇恆等函數,分類問題選擇softmax函數。
softmax函數的公式:
假設輸出層有 \(n\) 個神經元,計算第 \(k\) 個神經元的輸出 \(y_{k}\) 。
softmax函數的輸出值的總和為 1。因此我們可以將它的輸出解釋為概率。
輸出層神經元數量一般和設定類別數量相等。
4. 手寫數字識別
使用 MNIST 數據集。
使用 pickle 包序列化與反序列化所需數據,可以加快讀取速度。
正規化 Normalization:將數據限定到某個范圍內。
批處理 Batch
將輸入數據成批次打包,可以一次處理多張圖片。
batch_size = 100
for in range(0, len(x), batch_size) # x為輸入數據
x_batch = x[i:i+batch_size] # 切片處理,一次取batch_size張圖片
y_batch = predict(network, x_batch)
p = np.argmax(y_batch, axis = 1)
三、神經網絡的學習
學習是指從訓練過程中自動獲取最優權重參數的過程。
1. 數據驅動方式
從圖像中提取特征量(SIFT、SURF 或 HOG),使用這些特征量將圖像數據轉換為向量,然后對轉換后的向量使用機器學習中的 SVM、KNN 等分類器進行學習。
2. 損失函數
神經網絡將損失函數作為指標來尋找最優權重參數。
神經網絡學習的目的就是盡可能地降低損失函數的值。
我們一般使用均方誤差和交叉熵誤差函數。
1) 均方誤差
Mean Squared Error。
\(y_{k}\) 表示神經網絡的輸出結果, \(t_{k}\) 表示正確解標簽,\(k\) 表示數據維度。
one-hot表示:正確解標簽表示為 1,其他標簽表示為 0。
如:
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] # 假設在進行數字識別,數字“2”為正確結果
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
2) 交叉熵誤差
Cross Entropy Error
\(y_{k}\) 表示神經網絡的輸出結果, \(t_{k}\) 表示正確解標簽。
3) mini-batch 學習
如果我們要求所有訓練數據的平均損失函數,以交叉熵誤差為例,則為:
我們可以從全部數據中選出一部分,作為全部數據的代表。這一部分就是 mini-batch。
好比抽樣調查。
train_size = x_train.shape[0] # 訓練集的全部數據個數
batch_size = 10 #mini-batch的大小
batch_mask = np.random.choice(train_size, batch_size)#該函數從train_size個數字隨機挑選batch_size個數
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
3. 數值微分
1) 導數
使用中心差分來近似求解導數。
def numerical_diff(f, x) #求函數f(x)在x處的導數
h = 1e-4 #微小值
return (f(x+h)-f(x-h)) / (2 * h)
2) 梯度
由全部變量的偏導數匯總成的向量稱為梯度。
如,對於函數 \(f(x,y)=x^2+y^2\) ,其在 \((x,y)\) 處的梯度為 \((\frac{\partial f}{\partial x},\frac{\partial f}{\partial y})\)
其 Python 實現如下所示:
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x) #生成和變量組x大小相同的空數組存放梯度
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h)
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h)
x[idx] = tmp_val - h
fxh2 = f(x)
# 計算x[idx]的偏導數
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 還原值
梯度指向各點處的函數值減少最多的方向。
3) 梯度下降法
我們通常沿着梯度方向,使用梯度下降法循環尋找損失函數的最小值。
以上面提到的函數為例,用下面的式子不斷更新梯度值:
\(\eta\) 是一個更新量,稱為學習率。學習率的初始值一般為 0.01 或 0.001
用Python實現梯度下降法如下:
# f為函數,init_x為初始的變量組,學習率0.01,循環100次
def gradient_descent(f, init_x, learning_rate = 0.01, step_num = 100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x = x - lr*grad
return x
4. 神經網絡的梯度
神經網絡的學習要求損失函數關於權重參數的梯度。
比如一個 2*3 的權重參數 \(W\),損失函數為 \(L\) ,則梯度 \(\frac{\partial L}{\partial W}\) 為:
5. 學習算法的實現
動態調整權重和偏置以擬合訓練數據過程稱為學習。共有四個步驟:
- mini-batch:挑選mini-batch數據,目標是減少其損失函數的值。隨機梯度下降法 SGD。
- 計算梯度:計算各個權重參數的梯度
- 更新參數:將權重沿梯度方向進行微小更新
- 重復以上步驟
假設一個神經網絡有兩個權重參數 \(W1\) 和 \(W2\),兩個偏置參數 \(b1\),$ b2$ :
class TwoLayerNet:
# 計算並返回網絡輸出值
def predict(self, x):
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# 計算損失值 t為正確解標簽
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
# 計算梯度
def count_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
# 計算梯度 其他參數省略號
grads['W1'] = numerical_gradient(loss_W, params['W1'])
mini-batch的實現:
# 超參數
iters_num = 10000 # 下降次數
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
network = TwoLayerNetwork(input_size = 784, hidden_size = 50, output_size = 10)
for i in range(iters_num):
# 獲取mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 計算梯度
grad = network.count_gradient(x_batch, t_batch)
# 更新參數
for key in ('W1','b1','W2','b2'):
network.params[key] -= leraning_rate * grad[key]
一個 epoch 表示學習中所有訓練數據均被使用過的一次時的更新次數。
四、誤差反向傳播法 BP
使用誤差反向傳播法能夠快速計算權重參數的梯度。
其基於鏈式法則。
- 加法結點的反向傳播將上游的值原封不動地輸出到下游。
- 乘法結點的反向傳播乘以輸入信號的翻轉值。
1. 激活函數層的實現
1) ReLU
class Relu:
def __init__(self):
self.mask = None
# 前向傳播
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
# 反向傳播
def backward(self,dout):
dout[self.mask] = 0
dx = dout
return dx
2) sigmoid
class Sigmoid:
def __init__(self):
self.out = None
# 前向傳播
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
# 反向傳播
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
2. Affine/Softmax 層的實現
1) Affine
神經網絡正向傳播的流程是根據輸入數據和權重、偏置計算加權和,經過激活函數后輸出至下一層。
其中進行的矩陣的乘積運算在幾何學領域被稱為仿射變換,因此我們將進行仿射變換的處理實現為Affine層。
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
# 前向傳播
def forward(self, x):
self.x = x
out = np.dot(x, self.W) + self.b
return out
# 反向傳播
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis = 0)
return dx
2) Softmax
softmax函數將輸入值正規化后輸出。考慮到這里也包含作為損失函數的交叉熵誤差,因此稱為 Softmax-with-Loss層。
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
# 前向傳播
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
# 反向傳播
def backward(self, dout = 1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
五、與學習相關的技巧
1. 參數的更新
神經網絡的學習目的是找到使損失函數的值盡可能小的參數,這個過程被稱為最優化 Optimization
常用方法有SGD、Momentum、AdaGrad和Adam等。
1) SGD
隨機梯度下降法。
class SGD:
def __init__(self, lr = 0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
2) Momentum
SGD方法的缺點在於梯度的方向並沒有志向最小值的方向。Momentum 是動量的意思。
其數學式如下:
表示物體在梯度方向上受力。
class Momentum:
def __init__(self, lr = 0.01, momentum = 0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
params[key] += self.v[key]
3) AdaGrad
AdaGrad 方法保留了之前所有梯度值的平方和,會為參數的每個元素適當調整學習率。
Ada 表示 Adaptive
class AdaGrad:
def __init__(self, lr = 0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
4) Adam
Adam是最近出現的一種參數更新方法,它會設置三個超三處。
2. 權重的初始值
各層的激活值的分布要求有適當的廣度,否則可能會出現梯度消失現象。
1) Xavier 初始值
在一般的深度學習框架中, Xavier初始值已被作為標准使用。
在 Xavier 初始值中,如果前一層的節點數為 \(n\),則初始值使用標准差為 \(\frac{1}{\sqrt{n}}\) 的分布。
node_num = 100
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
2) ReLU 的 He 初始值
當激活函數使用 ReLU 時,一般使用 He 初始值。
如果前一層的節點數為 \(n\),則初始值使用標准差為 \(\sqrt{\frac{2}{n}}\) 的高斯分布。
3. Batch Normalization
為了使各層的激活值的分布有適當的廣度,使用 Batch Normalization 方法進行強制調整。
因此,我們需要在 Affine 層和激活函數層之間插入一個 Batch Norm 層。以進行學習時的 mini-batch 為單位進行正則化。
對 mini-batch 的 \(n\) 個輸入數據的集合 \(B={x_{1},x_{2},...,x_{m}}\) 求均值 \(\mu B\) 和 方差 \(\sigma_{B}^2\)。
4. 過擬合的抑制
機器學習中,過擬合是一個很常見的問題。過擬合指的是只能擬合訓練數據,但不能很好地擬合不包含在訓練數據中的其他數據的狀態。
因此我們需要一些方法來抑制過擬合。權值衰減是方法之一。
1) 權值衰減
對於所有權重,權值衰減方法都會為損失函數加上 \(\frac{1}{2}\lambda W^2\),即權重的 \(L2\) 范數。
因此在求權重梯度的計算中,要為之前的誤差反向傳播法的結果加上正則化項的導數 \(\lambda W\)。
2) Dropout
網絡模型復雜時,使用Dropout方法抑制過擬合。
Dropout是一種在學習過程中刪除神經元的方法。訓練時,隨機選出隱藏層的神經元並將其刪除。被刪除的神經元不再進行信號的傳遞。
class Dropout:
def __init__(self, dropout_ratio = 0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg = True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
else:
return x * (1.0 - self.dropout_ratio)
def backward(self, dout):
return dout * self.mask
5. 超參數的驗證
超參數有神經元數量、batch大小、學習率等。
我們不能使用測試數據評估超參數的性能,否則會造成過擬合
調整超參數時,必須使用超參數專用的確認數據,稱為驗證數據 validation data
六、卷積神經網絡
CNN 的結構可以像積木一樣進行組裝。其中出現了卷積層 Convolution 和池化層 Pooling。
在 CNN 中,層的連接順序是 :Convolution - ReLU - Pooling.
Pooling有時被省略。
1. 卷積層
在全連接層中,數據的形狀被忽略了。而卷積層可以保持形狀不變。當輸入數據是圖像時,卷積層以 3 維數據的形式接收輸入數據,並同樣以 3 維數據的形式輸出至下一次。
卷積層的輸入輸出數據稱為特征圖 Feature Map。
1) 卷積運算
卷積運算相當於過濾器。
濾波器即輸出中的權重W。
濾波器會提取邊緣或斑塊等原始信息。

如圖,輸入數據大小是 \((5,5)\),濾波器大小是 \((3,3)\),輸出大小是 \((3,3)\)。
2) 填充 Padding
在進行卷積層處理前,有時要像輸入數據的周圍填入固定數據來擴充數據。
使用填充主要是為了調整輸出的大小。

3) 步幅 Stride
應用濾波器的位置間隔稱為步幅。
增大步幅后,輸出表小;增大填充后,步幅變化。
假設輸入大小為 \((H,W)\) ,濾波器大小為 \((FH,FW)\),輸出大小為 \((OH,OW)\),填充為 \(P\) ,步幅為 \(S\)。
則輸出大小為:
4) 三維數據的卷積運算
以 3 通道 RGB 圖像為例,其縱深方向上的特征圖增加了。通道方向上有多個特征圖時,會按通道進行輸入數據和濾波器的卷積運算,並將結果相加。

輸入數據的通道數和濾波器的通道數要相同。

當有多個濾波器時,輸出特征圖同樣有多層。
2. 池化層
池化是縮小高、長方向上的空間運算。簡單來說,池化用來精簡數據。
Max池化上獲取最大值的運算。一般來說,池化窗口大小會和步幅相同。

3. 卷積層和池化層的實現
一個關鍵的函數為 im2col。它將輸入的三維數據展開為二維矩陣以適合濾波器。

1) 卷積層
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 = self.W.shape # 濾波器的數量、通道數、高、長
N, C, H, W = x.shape # 輸入數據的數量、通道數、高、長
# 計算輸出數據的長和高
out_h = int(1 + (H + 2 * self.pad - FH) / self.stride)
out_w = int(1 + (W + 2 * self.pad - FW) / self.stride)
# 使用im2col將三維數據轉換為矩陣
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 # 乘以權重后加上便宜
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) # 重新變為三維數據
return out
2) 池化層

class Pooling:
def __init__(self, pool_h, pool_w, stride = 1, pad = 0):
self.pool_h, self.pool_w, self.stride, self.pad = pool_h, pool_w, stride, 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
4. MNIST 數字識別神經網絡的實現
手寫數字識別的CN
class SimpleConvNet:
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):
"""
:param input_dim: 輸入數據的通道數與長高
:param conv_param: 卷積層的參數,濾波器數量、維度、填充、步幅
:param hidden_size: 隱藏層神經元數量
:param output_size: 輸出層神經元數量
:param weight_init_std: 初始化權重標准差
"""
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 = {'W1': weight_init_std * np.random.randn(filter_num, input_dim[0], filter_size, filter_size),
'b1': np.zeros(filter_num),
'W2': weight_init_std * np.random.randn(pool_output_size, hidden_size),
'b2': np.zeros(hidden_size),
'W3': weight_init_std * np.random.randn(hidden_size, output_size),
'b3': np.zeros(output_size)}
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'], self.params['stride'], self.params['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_layer = SoftmaxWithLoss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(t)
return self.last_layer.forward(y, t)
# 反向傳播求梯度
def gradient(self, x, t):
# 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 = {'W1': self.layers['Conv1'].dW, 'b1': self.layers['Conv1'].db, 'W2': self.layers['Affine1'].dW,
'b2': self.layers['Affine1'].db, 'W3': self.layers['Affine2'].dW, 'b3': self.layers['Affine2'].db}
return grads