通常來說,數據標準化預處理對於淺層模型就足夠有效了。隨着模型訓練的進行,當每層中參數更新時,靠近輸出層的輸出較難出現劇烈變化。但對深層神經網絡來說,即使輸入數據已做標準化,訓練中模型參數的更新依然很容易造成靠近輸出層輸出的劇烈變化。這種計算數值的不穩定性通常令我們難以訓練出有效的深度模型。
批量歸一化(batch normalization)是一個能讓較深的神經網絡的訓練變得更加容易的方法。在模型訓練時,批量歸一化利用小批量上的均值和標準差,不斷調整神經網絡中間輸出,從而使整個神經網絡在各層的中間輸出的數值更穩定。簡單來說,batch normalization就是對神經網絡某一層的激活函數的輸入進行歸一化,使批量呈標準正態分佈(均值爲0,標準差爲1)
相關公式如下:
給定一個批量B={},我們需要學習拉伸參數和。
所以定義:
(1)
(2)
(3)
(4)
其中,(1)式求出均值;(2)式求出方差;(3)式:(輸入-均值)/方差,加上是爲了保證分母不小於0,最終稿得到一個均化的;(4)式:和就是在訓練過程中進行更新變化,有點類似和。
根據公式我們來實現一個簡單的批量歸一化層:
# BN要考慮全連接層和卷積層,當爲卷積層時,需要對每個通道進行歸一化,當爲全連接層時,
# 每個批量都要使用
def batch_norm(X,gamma,beta,eps=1e-5):
# len(X.shape) in (2,4) # 就是說X如果是全連接,那麼就是2維,卷積是一個4維的東西,所以是4
if len(X.shape)==2:
# 每個輸入維度在樣本上的均值和方差 (batch_size*feature)
mean=X.mean(axis=0) # 均值
vari=((X-mean)**2).mean(axis=0) # 方差
else:
# 2D卷積(NCHW--batch_size*channel*height*width)
mean=X.mean(axis=(0,2,3),keepdims=True)
vari=((X-mean)**2).mean(axis=(0,2,3),keepdims=True)
# 均一化
X_hat=(X-mean)/nd.sqrt(vari+eps)
#拉昇和偏移(維度一致,便於計算)
return gamma.reshape(mean.shape)*X_hat+beta.reshape(mean.shape)
1)運行一個全連接的實例
A=nd.arange(6).reshape((3,2))
print(A.shape)
result=batch_norm(A,gamma=nd.array([1,1]),beta=nd.array([0,0])) # A是一個3X2的矩陣,gamma是一個1X2的矩陣
print(result)
結果:
我們的預期結果是對每一列進行歸一化,下面我們來分析一下,看這一列:
我們可以目測發現:均值爲0,方差爲1,符合結果。
2)運行一個2D卷積的實例
B=nd.arange(18).reshape((1,2,3,3))
print(B)
result_conv=batch_norm(B,gamma=nd.array([1,1]),beta=nd.array([0,0]))
print(result_conv)
運行結果:
同樣符合預期結果,均值爲0,方差爲1。
以上爲訓練過程所使用的batch norm,那麼,測試的時候要不要使用BN?這個問題其實不太好回答:
1、不用的話訓練出的模型參數在測試時也許就不準了;
2、用的話,測試時有可能只有一個數據。
事實上,測試時也是需要使用BN的,只不過需要改動一下。在測試時,需要把原先訓練的均值和方差替換成整個訓練數據的均值和方差,但是當訓練數據很大時,計算量就非常大,所以,使用移動平均的方法來近似計算。
這裏有個思路:在訓練時存儲全局的均值和方差,測試時使用就好了。所以更新一下BN代碼:
def BN(X,gamma,beta,is_training,moving_mean,moving_vari,moving_momentum,eps=1e-5):
# len(X.shape) in (2,4) # 就是說X如果是全連接,那麼就是2維,卷積是一個4維的東西,所以是4
if len(X.shape) == 2:
# 每個輸入維度在樣本上的均值和方差 (batch_size*feature)
mean = X.mean(axis=0) # 均值
vari = ((X - mean) ** 2).mean(axis=0) # 方差
else:
# 2D卷積(NCHW--batch_size*channel*height*width)
mean = X.mean(axis=(0, 2, 3), keepdims=True)
vari = ((X - mean) ** 2).mean(axis=(0, 2, 3), keepdims=True)
moving_mean=moving_mean.reshape(mean.shape) # 維度需要一致
moving_vari=moving_vari.reshape(vari.shape)
# 均一化
if is_training:
X_hat = (X - mean) / nd.sqrt(vari + eps)
# 更新全局的均值和方差
moving_mean[:]=moving_momentum*moving_mean+(1-moving_momentum)*mean # moving_momentum--平滑因子
moving_vari[:]=moving_momentum*moving_vari+(1-moving_momentum)*vari
else:
# 測試時使用全局均值和方差
X_hat = (X - moving_mean) / nd.sqrt(moving_vari + eps)
#拉昇和偏移(維度一致,便於計算)
return gamma.reshape(mean.shape)*X_hat+beta.reshape(mean.shape) #
BN應用到CNN中
這裏需要提前說明一點,BN只能讓模型收斂加快,對於提升準確率的方法需要查找其它方法。
import mxnet.ndarray as nd
import mxnet.autograd as ag
import mxnet.gluon as gn
import mxnet as mx
'''
# BN要考慮全連接層和卷積層,當爲卷積層時,需要對每個通道進行歸一化,當爲全連接層時,
# 每個批量都要使用
def batch_norm(X,gamma,beta,eps=1e-5):
# len(X.shape) in (2,4) # 就是說X如果是全連接,那麼就是2維,卷積是一個4維的東西,所以是4
if len(X.shape)==2:
# 每個輸入維度在樣本上的均值和方差 (batch_size*feature)
mean=X.mean(axis=0) # 均值
vari=((X-mean)**2).mean(axis=0) # 方差
else:
# 2D卷積(NCHW--batch_size*channel*height*width)
mean=X.mean(axis=(0,2,3),keepdims=True) # CSDN對應的公式(1)
vari=((X-mean)**2).mean(axis=(0,2,3),keepdims=True) # CSDN對應的公式(2)
# 均一化
X_hat=(X-mean)/nd.sqrt(vari+eps) # CSDN對應的公式(3)
#拉昇和偏移(維度一致,便於計算)
return gamma.reshape(mean.shape)*X_hat+beta.reshape(mean.shape) # CSDN對應的公式(4)
# 運行一個實例看看(全連接的實例)
A=nd.arange(6).reshape((3,2))
print(A)
result=batch_norm(A,gamma=nd.array([1,1]),beta=nd.array([0,0])) # A是一個3X2的矩陣,gamma是一個1X2的矩陣
print(result)
# 運行一個實例看看(卷積的實例)
B=nd.arange(18).reshape((1,2,3,3))
print(B)
result_conv=batch_norm(B,gamma=nd.array([1,1]),beta=nd.array([0,0]))
print(result_conv)
'''
def BN(X,gamma,beta,is_training,moving_mean,moving_vari,moving_momentum=0.9,eps=1e-5):
# len(X.shape) in (2,4) # 就是說X如果是全連接,那麼就是2維,卷積是一個4維的東西,所以是4
if len(X.shape) == 2:
# 每個輸入維度在樣本上的均值和方差 (batch_size*feature)
mean = X.mean(axis=0) # 均值
vari = ((X - mean) ** 2).mean(axis=0) # 方差
else:
# 2D卷積(NCHW--batch_size*channel*height*width)
mean = X.mean(axis=(0, 2, 3), keepdims=True) # CSDN對應的公式(1)
vari = ((X - mean) ** 2).mean(axis=(0, 2, 3), keepdims=True) # CSDN對應的公式(2)
moving_mean=moving_mean.reshape(mean.shape) # 維度需要一致
moving_vari=moving_vari.reshape(vari.shape)
# 均一化
if is_training:
X_hat = (X - mean) / nd.sqrt(vari + eps) # CSDN對應的公式(3)
# 更新全局的均值和方差
moving_mean[:]=moving_momentum*moving_mean+(1-moving_momentum)*mean # moving_momentum--平滑因子
moving_vari[:]=moving_momentum*moving_vari+(1-moving_momentum)*vari
else:
# 測試時使用全局均值和方差
X_hat = (X - moving_mean) / nd.sqrt(moving_vari + eps) # CSDN對應的公式(3)
#拉昇和偏移(維度一致,便於計算)
return gamma.reshape(mean.shape)*X_hat+beta.reshape(mean.shape) # CSDN對應的公式(4)
ctx=mx.gpu()
'''---參數定義---'''
# 1、創建第一個卷積層的w、b ,核大小爲5,單通道圖片(輸入),輸出20個特徵圖
c1,c2=20,50
w1 = nd.random_normal(shape=(c1, 1, 5, 5), scale=0.01,ctx=ctx)
b1 = nd.random_normal(shape=w1.shape[0], scale=0.01, ctx=ctx)
# BN
gamma1=nd.random_normal(shape=(c1),scale=0.01,ctx=ctx) #如果是卷積的話,gamma和beta的數目跟上一層卷積輸出特徵圖數是一樣的
beta1=nd.random_normal(shape=(c1),scale=0.01,ctx=ctx)
moving_mean1=nd.zeros(shape=c1,ctx=ctx)
moving_vari1=nd.zeros(shape=c1,ctx=ctx)
# 2、創建第二個卷積層的w、b ,核大小爲5,輸入爲20,輸出50個特徵圖
w2 = nd.random_normal(shape=(c2, c1, 3, 3), scale=0.01, ctx=ctx)
b2 = nd.random_normal(shape=w2.shape[0], scale=0.01, ctx=ctx)
# BN
gamma2=nd.random_normal(shape=(c2),scale=0.01,ctx=ctx)
beta2=nd.random_normal(shape=(c2),scale=0.01,ctx=ctx)
moving_mean2=nd.zeros(shape=c2,ctx=ctx)
moving_vari2=nd.zeros(shape=c2,ctx=ctx)
# 3、全連接層參數------(1250,128)
f3=128
w3 = nd.random_normal(shape=(1250, f3), scale=0.01, ctx=ctx)
b3 = nd.random_normal(shape=f3, scale=0.01, ctx=ctx)
# 4、全連接層參數------(128,10)
w4 = nd.random_normal(shape=(f3, 10), scale=0.01, ctx=ctx)
b4 = nd.random_normal(shape=10, scale=0.01, ctx=ctx)
params=[w1, b1, w2, b2, w3, b3, w4, b4,gamma1,beta1,gamma2,beta2] # moving_mean和moving_vari是不需要更新的
for param in params:
param.attach_grad()
'''---定義模型(注意BN是在卷積之後,激活函數之前)---'''
def cnn_net(X,is_training=False):# 這裏只對卷積做BN
# 第一層卷積+池化
h1_conv = nd.Convolution(data=X.reshape((-1, 1, 28, 28)), weight=w1, bias=b1, kernel=w1.shape[2:],
num_filter=w1.shape[0])
h1_bn=BN(h1_conv,gamma1,beta1,is_training,moving_mean1,moving_vari1)
h1_activity = nd.relu(data=h1_bn) # relu激活
h1_pool = nd.Pooling(data=h1_activity, kernel=(2, 2), pool_type="max", stride=(2, 2)) # 最大池化
# 第二層卷積+池化
h2_conv = nd.Convolution(data=h1_pool, weight=w2, bias=b2, kernel=w2.shape[2:], num_filter=w2.shape[0])
h2_bn = BN(h2_conv, gamma2, beta2, is_training, moving_mean2, moving_vari2)
h2_activity = nd.relu(data=h2_bn) # relu激活
h2_pool = nd.Pooling(data=h2_activity, kernel=(2, 2), pool_type="max", stride=(2, 2)) # 最大池化
# 第一層全連接
h2 = nd.Flatten(h2_pool) # 拉成一個batch*一維向量(2D矩陣)
h3_linear = nd.dot(h2, w3) + b3
h3 = nd.relu(h3_linear)
# 第二層全連接
h4 = nd.dot(h3, w4) + b4
return h4
'''---讀取數據和預處理---'''
def load_data_fashion_mnist(batch_size, resize=None):
transformer = []
if resize:
transformer += [gn.data.vision.transforms.Resize(resize)]
transformer += [gn.data.vision.transforms.ToTensor()]
transformer = gn.data.vision.transforms.Compose(transformer)
mnist_train = gn.data.vision.FashionMNIST( train=True)
mnist_test = gn.data.vision.FashionMNIST( train=False)
train_iter = gn.data.DataLoader(
mnist_train.transform_first(transformer), batch_size, shuffle=True)
test_iter = gn.data.DataLoader(
mnist_test.transform_first(transformer), batch_size, shuffle=False)
return train_iter, test_iter
batch_size=128
train_iter,test_iter=load_data_fashion_mnist(batch_size)
cross_loss = gn.loss.SoftmaxCrossEntropyLoss()
# 定義準確率
def accuracy(output, label):
return nd.mean(output.argmax(axis=1) == label).asscalar()
def evaluate_accuracy(data_iter, net): # 定義測試集準確率
acc = 0
for data, label in data_iter:
data,label = data.as_in_context(ctx),label.as_in_context(ctx)
label = label.astype('float32')
output = net(data)
acc += accuracy(output, label)
return acc / len(data_iter)
# 梯度下降優化器
def SGD(params, lr):
for pa in params:
pa[:] = pa - lr * pa.grad # 參數沿着梯度的反方向走特定距離
# 訓練
lr = 0.2
epochs = 5
for epoch in range(epochs):
train_loss = 0
train_acc = 0
for image, y in train_iter:
image,y = image.as_in_context(ctx),y.as_in_context(ctx)
y = y.astype('float32')
with ag.record():
output = cnn_net(image,is_training=True)
loss = cross_loss(output, y)
loss.backward()
# 將梯度做平均,這樣學習率不會對batch_size那麼敏感
SGD(params, lr / batch_size)
train_loss += nd.mean(loss).asscalar()
train_acc += accuracy(output, y)
test_acc = evaluate_accuracy(test_iter, cnn_net)
print("Epoch %d, Loss:%f, Train acc:%f, Test acc:%f"
% (epoch, train_loss / len(train_iter), train_acc / len(train_iter), test_acc))
訓練結果:
這裏就不繼續跑下去了,機器太渣…