基于 Numpy 在 MNIST 上实现 CNN 反向传播训练
卷积神经网络利用局部连接和权重共享提取图像特征,解决全连接层参数过多问题。 CNN 核心组件原理,包括卷积运算、填充步幅、池化操作及 Im2col 加速技巧。通过 Numpy 从零实现 Convolution 和 Pooling 类,构建 SimpleConvNet 网络结构,并在 MNIST 数据集上进行反向传播训练,展示损失变化与准确率结果。

卷积神经网络利用局部连接和权重共享提取图像特征,解决全连接层参数过多问题。 CNN 核心组件原理,包括卷积运算、填充步幅、池化操作及 Im2col 加速技巧。通过 Numpy 从零实现 Convolution 和 Pooling 类,构建 SimpleConvNet 网络结构,并在 MNIST 数据集上进行反向传播训练,展示损失变化与准确率结果。

本文介绍基于 Numpy 的卷积神经网络(Convolutional Networks, CNN)的实现,旨在深入理解原理和底层逻辑。
卷积神经网络(CNN)是一种具有局部连接、权重共享和平移不变特性的深层前馈神经网络。
CNN 利用了可学习的 kernel 卷积核(filter 滤波器)来提取图像中的模式(局部和全局)。传统图像处理会手动设计卷积核(例如高斯核,来提取边缘信息),而 CNN 则是数据驱动的。
在数学上,针对一维序列数据,卷积运算可以被理解为一种移动平均。而二维卷积运算,通常在图像处理中用于平滑信号达到滤波或提取特征等。
CNN 解决了 MLP 在处理图像时面临的两个问题:(1)参数过多,(2)缺乏局部不变性。自然图像中的物体都具有局部不变性特征,比如尺度缩放、平移、旋转等操作不影响其语义信息。而 MLP 很难提取这些局部不变性特征。换句话说,MLP 会忽视图像的形状(像素之间的空间信息),将图像展开为一维的输入数据来处理,所以无法利用与形状相关的信息,而 CNN 则不会改变形状(引入了归纳偏置)。
目前的 CNN 一般是由卷积层、汇聚层和全连接层堆叠而成。其中卷积和汇聚层可以视为用滑动窗口来提取特征。CNN 的滑动窗口带来的优势:1)局部(稀疏)连接,2)参数共享(复用),3)平移不变。接下将介绍 CNN 的卷积和池化操作。
令输入数据(图片)的形状为(H, W),其中 H 为图片的高 height,W 为图片的宽 width,卷积核 (滤波器 Filter) 的形状为(FH, FW),其中 FH 代表 Filter Height,FW 代表 Filter Width。
卷积运算将在输入数据上,以一定间隔(Stride 步长或步幅)整体地滑动滤波器的窗口并将滤波器各个位置上的权重值和输入数据的对应元素相乘。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都做一遍,就能得到卷积运算的输出。此外,在卷积后,通常会在每个位置的数据上加偏置项。
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(例如 0 等)以确保输出数据(特征图,Feature Map)的大小,这称为填充(padding),是卷积运算中经常会用到的处理。填充也被应用于反卷积中(进行较大范围的填充,使输出数据的形状变大,完成上采样)。
'幅度为 1 的填充'是指用幅度为 1 像素的 0 填充周围。很容易得知,形状为的(H, W)输入数据在进行幅度 P 的填充后,其形状将变为(H+2P, W+2P)。
应用滤波器的位置间隔称为步幅(stride)。如上图所示,之前的例子中步幅 S 都是 1,如果将步幅 S 设为 2,应用滤波器的窗口的间隔变为 2 个元素。综上,增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。对于填充和步幅,输出大小的关系如下式所示:
之前的卷积运算的例子都是以有高、长方向的 2 维形状为对象的。但是,图像是 3 维数据,除了高、长方向之外,还需要处理通道方向(例如,RGB)。上图以 3 通道的数据为例,展示了卷积运算的结果。和处理 2 维数据时相比,可以发现纵深方向(通道方向)上特征图增加了。通道方向上有多个特征图时,可以按通道进行输入数据和滤波器的卷积运算,并将结果相加,从而得到输出。不同通道的 Kernel 大小应该一致。
为了便于理解 3 维数据的卷积运算,我们这里将数据和滤波器结合长方体的方块来考虑。方块是上图所示的 3 维长方体。把 3 维数据表示为多维数组时,书写顺序为(channel, height, width)。比如,通道数为 C、高度为 H、长度为 W 的数据的形状可以写成(C, H, W)。滤波器也一样,要对应顺序书写。比如,通道数为 C、滤波器高度为 FH、长度为 FW 时,可以写成(C, FH, FW)。若使用 FN 个滤波器,输出特征图也将有 FN 个。如果将这 FN 个特征图汇集在一起,就得到了形状为 (FN, OH, OW) 的方块。
卷积运算中(和全连接层一样)存在偏置。如果进一步追加偏置的加法运算处理,要对滤波器的输出结果 (FN, OH, OW) 按通道加上相同的偏置值。
当前只是一个输入(单个 3 通道图像),还可以输入 N 个图像,构成一个 Batch,以矩阵乘法加速。
池化层(汇聚层,Pooling Layer)也叫子采样层(Subsampling Layer),其作用是进行特征选择,降低特征数量,从而减少参数数量。具体来说,池化是缩小高、长方向上的空间的运算(多变少)。在卷积层之后加上一个汇聚层,可以降低特征维数,避免过拟合。
池化层的特性:
1)没有要学习的参数 池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数
通道数不发生变化 经过池化运算,输入数据和输出数据的通道数不会发生变化
对微小的位置变化具有鲁棒性(健壮,容噪) 当输入数据发生微小偏差时,池化仍会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性
目前,卷积网络的整体结构趋向于使用更小的卷积核(比如 1 × 1 和 3 × 3)以及更深的结构(比如层数大于 50)。此外,由于卷积的操作性越来越灵活(比如不同的步长),汇聚层的作用也变得越来越小,因此目前比较流行的卷积网络中,汇聚层的比例正在逐渐降低,趋向于全卷积网络。
卷积层和池化层的实现看起来很复杂,但实际上可通过使用技巧来简化实现。本节将介绍先 im2col 技巧,然后再进行卷积层的实现。
如前所述,CNN 中各层间传递的数据是 4 维数据。所谓 4 维数据,比如数据的形状是 (10, 1, 28, 28),则它对应 10 个高为 28、长为 28、通道为 1 的数据。对于这样的 4 维数据此卷积运算的实现看上去会很复杂,但是通过使用下面要介绍的 im2col(Image to column) 技巧,问题将变得很简单。
如果老老实实地实现卷积运算需要多重循环,这样做不仅实现复杂且速度较慢。为避免这一问题,我们引入了 im2col 函数。im2col 是一个将输入数据展开以适合滤波器(权重)的函数。如上图所示,对 3 维的输入数据应用 im2col 后,im2col 会把输入数据展开以适合滤波器(权重)。具体地说,对于输入数据,将应用滤波器的区域(3 维方块)横向展开为 1 列(转置后为一行)。im2col 会在所有应用滤波器的地方进行展开处理。
上图为便于观察,将步幅设置得很大,以使滤波器的应用区域不重叠。而在实际的卷积运算中,滤波器的应用区域几乎都是重叠的。在滤波器的应用区域重叠的情况下,使用 im2col 展开后,展开后的元素个数会多于原方块的。因此,使用 im2col 比普通实现消耗更多内存。但是,汇总成一个大矩阵可减少计算耗时。
实际上,im2col 函数就是将输入数据中所有滤波器需要处理的局部数据(即滑动窗口对应的数据)事先拿出来,展开为矩阵形式(每一行对应一个数据),然后将卷积核也展开为列向量,随后就可将两者做矩阵乘法运算来加速卷积操作(本质上,卷积核和对应数据的卷积运算就是在做内积)。这和全连接层的 Affine 层进行的处理基本相同(滤波器本质上仍是权重矩阵)。
此外,对于大小相同的一批数据,由于卷积层的滤波器没变,所以只需将数据按行拼接,计算后再 reshape 即可。im2col 的实现如下,就是按卷积核来滑动窗口预先取出并展开数据:
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
"""
把对应卷积核的数据部分拿出来,reshape 为向量,进一步拼为矩阵
Parameters:
input_data (tensor): 由 (数据量,通道,高,宽) 的 4 维张量构成的输入数据
filter_h (int): 滤波器的高
filter_w (int): 滤波器的宽
stride (int): 步幅
pad (int): 填充
Returns:
col (tensor): 2 维数组
"""
N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
此外,给出其逆操作,以便实现梯度反向传播:
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
"""
im2col 的逆处理,将展开后的数据还原回原始输入数据形式
Parameters:
col (tensor): 2 维数组
input_shape (int): 输入数据的形状(例:(10, 1, 28, 28))
filter_h (int): 滤波器的高
filter_w (int): 滤波器的宽
stride (int): 步幅
pad (int): 填充
Returns:
"""
N, C, H, W = input_shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)
img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
return img[:, :, pad:H + pad, pad:W + pad]
卷积层将被实现为名为 Convolution 的类。卷积层的初始化方法将滤波器(权重)、偏置、步幅、填充作为参数接收。滤波器是 (FN, C, FH, FW) 的 4 维形状。另外,FN、C、FH、FW 分别是 FilterNumber(滤波器数量)、Channel、Filter Height、Filter Width 的缩写。
在 forward 的实现中,先用 im2col 展开输入数据,并用 reshape 将滤波器展开为 2 维数组。然后,计算展开后的矩阵的乘积。最后会将输出大小转换为合适的形状。通过使用 im2col 进行展开,基本上可以像实现全连接层的 Affine 层一样来实现。
接下来是卷积层的反向传播的实现,因为和 Affine 层的实现有很多共通的地方,所以就不再介绍。但需注意的是,在进行卷积层的反向传播时,必须进行 im2col 的逆处理(卷积核参数的梯度容易获取,关键是如何获取输入数据关于损失函数的梯度,以便回传)。除了使用 col2im 这一点,卷积层的反向传播和 Affine 层的实现方式都一样。
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
# 卷积层的初始化方法将滤波器(权重)、偏置、步幅、填充作为参数
# 滤波器是 (FN, C, FH, FW),Filter Number 滤波器数量、Channel、Filter Height、Filter Width
self.W = W # 每一个 Filter(原本为 3 维 tensor 权重) 将 reshape 为权重向量 [(C*FH*FW) X 1], 列向量
self.b = b # C 一个 Filter 将拼接为为卷积核权重矩阵 [(C*FH*FW) X FN]
self.stride = stride
self.pad = pad
# 中间数据(backward 时使用)
self.x = None
self.col = None
self.col_W = None
# 权重和偏置参数的梯度
self.dW = None
self.db = None
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
out_w = 1 + int((W + 2*self.pad - FW) / self.stride)
# 用 im2col 展开输入数据 x,并用 reshape 将滤波器权重展开为 2 维数组。
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + .b
out = out.reshape(N, out_h, out_w, -).transpose(, , , )
.x = x
.col = col
.col_W = col_W
out
():
FN, C, FH, FW = .W.shape
dout = dout.transpose(,,,).reshape(-, FN)
.db = np.(dout, axis=)
.dW = np.dot(.col.T, dout)
.dW = .dW.transpose(, ).reshape(FN, C, FH, FW)
dcol = np.dot(dout, .col_W.T)
dx = col2im(dcol, .x.shape, FH, FW, .stride, .pad)
dx
池化层的实现和卷积层相同,都使用 im2col 展开输入数据。不过,池化的情况下,在通道方向上是独立的。具体地讲,如图上所示,池化的应用区域按通道单独展开。像这样展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状即可(池化无参数)。池化操作的反向传播计算过程和 Relu 非常类似,它仅仅回传池化后的元素的梯度。
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
# 池化层的实现和卷积层相同,都使用 im2col 展开输入数据
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
self.x = None
self.arg_max = None
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)
# X 展开之后,只需对展开的矩阵求各行的最大值,并转换为合适的形状
arg_max = np.argmax(col, axis=1)
out = np.max(col, axis=1)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
self.x = x
self.arg_max = arg_max # 仅对池化后的元素求梯度(相当于一个特殊的 Relu,mask 掉了其他元素)
return out
def backward():
dout = dout.transpose(, , , )
pool_size = .pool_h * .pool_w
dmax = np.zeros((dout.size, pool_size))
dmax[np.arange(.arg_max.size), .arg_max.flatten()] = dout.flatten()
dmax = dmax.reshape(dout.shape + (pool_size,))
dcol = dmax.reshape(dmax.shape[] * dmax.shape[] * dmax.shape[], -)
dx = col2im(dcol, .x.shape, .pool_h, .pool_w, .stride, .pad)
dx
简单 CNN 分类网络由'Convolution - ReLU - Pooling -Affine -ReLU - Affine - Softmax'的构成,它被实现为 SimpleConvNet。可以堆叠多个 Convolution、Relu、Pooling 等组件实现更复杂的卷积网络。
class SimpleConvNet:
"""简单的 ConvNet:conv - relu - pool - affine - relu - affine - softmax
Parameters:
input_dim : 输入大小(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 * \n 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 * \n np.random.randn(pool_output_size, hidden_size)
.params[] = np.zeros(hidden_size)
.params[] = weight_init_std * \n np.random.randn(hidden_size, output_size)
.params[] = np.zeros(output_size)
.layers = OrderedDict()
.layers[] = Convolution(.params[], .params[],
conv_param[], conv_param[])
.layers[] = Relu()
.layers[] = Pooling(pool_h=, pool_w=, stride=)
.layers[] = Affine(.params[], .params[])
.layers[] = Relu()
.layers[] = Affine(.params[], .params[])
.last_layer = SoftmaxWithLoss()
():
layer .layers.values():
x = layer.forward(x)
x
():
y = .predict(x)
.last_layer.forward(y, t)
():
.loss(x, t)
dout =
dout = .last_layer.backward(dout)
layers = (.layers.values())
layers.reverse()
layer layers:
dout = layer.backward(dout)
grads = {}
grads[], grads[] = .layers[].dW, .layers[].db
grads[], grads[] = .layers[].dW, .layers[].db
grads[], grads[] = .layers[].dW, .layers[].db
grads
():
params = {}
key, val .params.items():
params[key] = val
(file_name, ) f:
pickle.dump(params, f)
():
(file_name, ) f:
params = pickle.load(f)
key, val params.items():
.params[key] = val
i, key ([, , ]):
.layers[key].W = .params[ + (i+)]
.layers[key].b = .params[ + (i+)]
LeNet-5 是由 Yann LeCun 提出的第一个也是非常经典的卷积神经网络模型。LeNet-5 的网络结构如上图所示。LeNet-5 共有 7 层,接受输入图像大小为 32 × 32 = 1 024,输出对应 10 个类别的得分。LeNet 中使用了 sigmoid 函数,而现在的 CNN 中主要使用 ReLU 函数。
AlexNet 堆叠了多个卷积层和池化层,最后经由全连接层输出结果。虽然结构上 AlexNet 和 LeNet 没有大的不同,但有以下几点差异。它的激活函数用了 ReLU,应用了 Dropout,并使用了局部正规化的 LRN(Local Response Normalization)层来避免过拟合。
上述两个网络都可以用 Numpy 来实现,不过为了实现方便和避免重复造低效的轮子,可以直接用 Pytorch 或 Tensorflow 等框架来实现或使用现成的网络。例如,LeNet-5 可以直接用如下几行 pytorch 代码实现:
至于更深的卷积神经网络,就不在详细展开。它们往往具有如下特点:
这里加入了数据加载、以及优化器和训练代码以及依赖的层和函数实现。
这里没有使用数据集全集进行训练。
部分训练损失变化 === epoch:20, train acc:0.993, test acc:0.957 === train loss:0.012233594746587742 train loss:0.008365986258816209 train loss:0.02212213864855614 train loss:0.02711406676975275 train loss:0.0418349000312822 train loss:0.057708698769212516 train loss:0.015403229650386896 train loss:0.017599953533757498 train loss:0.015486894589432332 train loss:0.031158008798079506 train loss:0.054530107471154644 train loss:0.009704471774258436 train loss:0.010295140042204599 train loss:0.05049087595095596 train loss:0.010830895810426724 train loss:0.010738003713808189 =============== Final Test Accuracy =============== test acc:0.955 Saved Network Parameters!
参数以嵌套字典的形式,存为了 pickle 文件。
# coding: utf-8
import urllib.request
import os.path
import gzip
import pickle
import numpy as np
import sys, os
from collections import OrderedDict
import numpy as np
import matplotlib.pyplot as plt
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
'train_img':'train-images-idx3-ubyte.gz',
'train_label':'train-labels-idx1-ubyte.gz',
'test_img':'t10k-images-idx3-ubyte.gz',
'test_label':'t10k-labels-idx1-ubyte.gz'
}
dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"
train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784
def _download(file_name):
file_path = dataset_dir + "/" + file_name
if os.path.exists(file_path):
return
print("Downloading " + file_name + " ... ")
urllib.request.urlretrieve(url_base + file_name, file_path)
print("Done")
def download_mnist():
for v in key_file.values():
_download(v)
():
file_path = dataset_dir + + file_name
( + file_name + )
gzip.(file_path, ) f:
labels = np.frombuffer(f.read(), np.uint8, offset=)
()
labels
():
file_path = dataset_dir + + file_name
( + file_name + )
gzip.(file_path, ) f:
data = np.frombuffer(f.read(), np.uint8, offset=)
data = data.reshape(-, img_size)
()
data
():
dataset = {}
dataset[] = _load_img(key_file[])
dataset[] = _load_label(key_file[])
dataset[] = _load_img(key_file[])
dataset[] = _load_label(key_file[])
dataset
():
download_mnist()
dataset = _convert_numpy()
()
(save_file, ) f:
pickle.dump(dataset, f, -)
()
():
T = np.zeros((X.size, ))
idx, row (T):
row[X[idx]] =
T
():
os.path.exists(save_file):
init_mnist()
(save_file, ) f:
dataset = pickle.load(f)
normalize:
key (, ):
dataset[key] = dataset[key].astype(np.float32)
dataset[key] /=
one_hot_label:
dataset[] = _change_one_hot_label(dataset[])
dataset[] = _change_one_hot_label(dataset[])
flatten:
key (, ):
dataset[key] = dataset[key].reshape(-, , , )
(dataset[], dataset[]), (dataset[], dataset[])
():
x.ndim == :
x = x.T
x = x - np.(x, axis=)
y = np.exp(x) / np.(np.exp(x), axis=)
y.T
x = x - np.(x)
np.exp(x) / np.(np.exp(x))
:
():
.mask =
():
.mask = (x <= )
out = x.copy()
out[.mask] =
out
():
dout[.mask] =
dx = dout
dx
:
():
.out =
():
out = sigmoid(x)
.out = out
out
():
dx = dout * ( - .out) * .out
dx
:
():
.W =W
.b = b
.x =
.original_x_shape =
.dW =
.db =
():
.original_x_shape = x.shape
x = x.reshape(x.shape[], -)
.x = x
out = np.dot(.x, .W) + .b
out
():
dx = np.dot(dout, .W.T)
.dW = np.dot(.x.T, dout)
.db = np.(dout, axis=)
dx = dx.reshape(*.original_x_shape)
dx
:
():
.loss =
.y =
.t =
():
.t = t
.y = softmax(x)
.loss = cross_entropy_error(.y, .t)
.loss
():
batch_size = .t.shape[]
.t.size == .y.size:
dx = (.y - .t) / batch_size
:
dx = .y.copy()
dx[np.arange(batch_size), .t] -=
dx = dx / batch_size
dx
:
():
.dropout_ratio = dropout_ratio
.mask =
():
train_flg:
.mask = np.random.rand(*x.shape) > .dropout_ratio
x * .mask
:
x * ( - .dropout_ratio)
():
dout * .mask
:
():
.gamma = gamma
.beta = beta
.momentum = momentum
.input_shape =
.running_mean = running_mean
.running_var = running_var
.batch_size =
.xc =
.std =
.dgamma =
.dbeta =
():
.input_shape = x.shape
x.ndim != :
N, C, H, W = x.shape
x = x.reshape(N, -)
out = .__forward(x, train_flg)
out.reshape(*.input_shape)
():
.running_mean :
N, D = x.shape
.running_mean = np.zeros(D)
.running_var = np.zeros(D)
train_flg:
mu = x.mean(axis=)
xc = x - mu
var = np.mean(xc**, axis=)
std = np.sqrt(var + )
xn = xc / std
.batch_size = x.shape[]
.xc = xc
.xn = xn
.std = std
.running_mean = .momentum * .running_mean + (-.momentum) * mu
.running_var = .momentum * .running_var + (-.momentum) * var
:
xc = x - .running_mean
xn = xc / ((np.sqrt(.running_var + )))
out = .gamma * xn + .beta
out
():
dout.ndim != :
N, C, H, W = dout.shape
dout = dout.reshape(N, -)
dx = .__backward(dout)
dx = dx.reshape(*.input_shape)
dx
():
dbeta = dout.(axis=)
dgamma = np.(.xn * dout, axis=)
dxn = .gamma * dout
dxc = dxn / .std
dstd = -np.((dxn * .xc) / (.std * .std), axis=)
dvar = * dstd / .std
dxc += ( / .batch_size) * .xc * dvar
dmu = np.(dxc, axis=)
dx = dxc - dmu / .batch_size
.dgamma = dgamma
.dbeta = dbeta
dx
():
N, C, H, W = input_data.shape
out_h = (H + *pad - filter_h)//stride +
out_w = (W + *pad - filter_w)//stride +
img = np.pad(input_data, [(,), (,), (pad, pad), (pad, pad)], )
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
y (filter_h):
y_max = y + stride*out_h
x (filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(, , , , , ).reshape(N*out_h*out_w, -)
col
():
N, C, H, W = input_shape
out_h = (H + *pad - filter_h)//stride +
out_w = (W + *pad - filter_w)//stride +
col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(, , , , , )
img = np.zeros((N, C, H + *pad + stride - , W + *pad + stride - ))
y (filter_h):
y_max = y + stride*out_h
x (filter_w):
x_max = x + stride*out_w
img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
img[:, :, pad:H + pad, pad:W + pad]
:
():
.W = W
.b = b
.stride = stride
.pad = pad
.x =
.col =
.col_W =
.dW =
.db =
():
FN, C, FH, FW = .W.shape
N, C, H, W = x.shape
out_h = + ((H + *.pad - FH) / .stride)
out_w = + ((W + *.pad - FW) / .stride)
col = im2col(x, FH, FW, .stride, .pad)
col_W = .W.reshape(FN, -).T
out = np.dot(col, col_W) + .b
out = out.reshape(N, out_h, out_w, -).transpose(, , , )
.x = x
.col = col
.col_W = col_W
out
():
FN, C, FH, FW = .W.shape
dout = dout.transpose(,,,).reshape(-, FN)
.db = np.(dout, axis=)
.dW = np.dot(.col.T, dout)
.dW = .dW.transpose(, ).reshape(FN, C, FH, FW)
dcol = np.dot(dout, .col_W.T)
dx = col2im(dcol, .x.shape, FH, FW, .stride, .pad)
dx
:
():
.pool_h = pool_h
.pool_w = pool_w
.stride = stride
.pad = pad
.x =
.arg_max =
():
N, C, H, W = x.shape
out_h = ( + (H - .pool_h) / .stride)
out_w = ( + (W - .pool_w) / .stride)
col = im2col(x, .pool_h, .pool_w, .stride, .pad)
col = col.reshape(-, .pool_h*.pool_w)
arg_max = np.argmax(col, axis=)
out = np.(col, axis=)
out = out.reshape(N, out_h, out_w, C).transpose(, , , )
.x = x
.arg_max = arg_max
out
():
dout = dout.transpose(, , , )
pool_size = .pool_h * .pool_w
dmax = np.zeros((dout.size, pool_size))
dmax[np.arange(.arg_max.size), .arg_max.flatten()] = dout.flatten()
dmax = dmax.reshape(dout.shape + (pool_size,))
dcol = dmax.reshape(dmax.shape[] * dmax.shape[] * dmax.shape[], -)
dx = col2im(dcol, .x.shape, .pool_h, .pool_w, .stride, .pad)
dx
:
():
filter_num = conv_param[]
filter_size = conv_param[]
filter_pad = conv_param[]
filter_stride = conv_param[]
input_size = input_dim[]
conv_output_size = (input_size - filter_size + *filter_pad) / filter_stride +
pool_output_size = (filter_num * (conv_output_size/) * (conv_output_size/))
.params = {}
.params[] = weight_init_std * \
np.random.randn(filter_num, input_dim[], filter_size, filter_size)
.params[] = np.zeros(filter_num)
.params[] = weight_init_std * \
np.random.randn(pool_output_size, hidden_size)
.params[] = np.zeros(hidden_size)
.params[] = weight_init_std * \
np.random.randn(hidden_size, output_size)
.params[] = np.zeros(output_size)
.layers = OrderedDict()
.layers[] = Convolution(.params[], .params[],
conv_param[], conv_param[])
.layers[] = Relu()
.layers[] = Pooling(pool_h=, pool_w=, stride=)
.layers[] = Affine(.params[], .params[])
.layers[] = Relu()
.layers[] = Affine(.params[], .params[])
.last_layer = SoftmaxWithLoss()
():
layer .layers.values():
x = layer.forward(x)
x
():
y = .predict(x)
.last_layer.forward(y, t)
():
t.ndim != : t = np.argmax(t, axis=)
acc =
i ((x.shape[] / batch_size)):
tx = x[i*batch_size:(i+)*batch_size]
tt = t[i*batch_size:(i+)*batch_size]
y = .predict(tx)
y = np.argmax(y, axis=)
acc += np.(y == tt)
acc / x.shape[]
():
loss_w = w: .loss(x, t)
grads = {}
idx (, , ):
grads[ + (idx)] = numerical_gradient(loss_w, .params[ + (idx)])
grads[ + (idx)] = numerical_gradient(loss_w, .params[ + (idx)])
grads
():
.loss(x, t)
dout =
dout = .last_layer.backward(dout)
layers = (.layers.values())
layers.reverse()
layer layers:
dout = layer.backward(dout)
grads = {}
grads[], grads[] = .layers[].dW, .layers[].db
grads[], grads[] = .layers[].dW, .layers[].db
grads[], grads[] = .layers[].dW, .layers[].db
grads
():
params = {}
key, val .params.items():
params[key] = val
(file_name, ) f:
pickle.dump(params, f)
():
(file_name, ) f:
params = pickle.load(f)
key, val params.items():
.params[key] = val
i, key ([, , ]):
.layers[key].W = .params[ + (i+)]
.layers[key].b = .params[ + (i+)]
():
y.ndim == :
t = t.reshape(, t.size)
y = y.reshape(, y.size)
t.size == y.size:
t = t.argmax(axis=)
batch_size = y.shape[]
-np.(np.log(y[np.arange(batch_size), t] + )) / batch_size
:
():
.lr = lr
():
key params.keys():
params[key] -= .lr * grads[key]
:
():
.lr = lr
.beta1 = beta1
.beta2 = beta2
. =
.m =
.v =
():
.m :
.m, .v = {}, {}
key, val params.items():
.m[key] = np.zeros_like(val)
.v[key] = np.zeros_like(val)
. +=
lr_t = .lr * np.sqrt( - .beta2**.) / ( - .beta1**.)
key params.keys():
.m[key] += ( - .beta1) * (grads[key] - .m[key])
.v[key] += ( - .beta2) * (grads[key]** - .v[key])
params[key] -= lr_t * .m[key] / (np.sqrt(.v[key]) + )
:
():
.network = network
.verbose = verbose
.x_train = x_train
.t_train = t_train
.x_test = x_test
.t_test = t_test
.epochs = epochs
.batch_size = mini_batch_size
.evaluate_sample_num_per_epoch = evaluate_sample_num_per_epoch
optimizer_class_dict = {:SGD, :Adam}
.optimizer = optimizer_class_dict[optimizer.lower()](**optimizer_param)
.train_size = x_train.shape[]
.iter_per_epoch = (.train_size / mini_batch_size, )
.max_iter = (epochs * .iter_per_epoch)
.current_iter =
.current_epoch =
.train_loss_list = []
.train_acc_list = []
.test_acc_list = []
():
batch_mask = np.random.choice(.train_size, .batch_size)
x_batch = .x_train[batch_mask]
t_batch = .t_train[batch_mask]
grads = .network.gradient(x_batch, t_batch)
.optimizer.update(.network.params, grads)
loss = .network.loss(x_batch, t_batch)
.train_loss_list.append(loss)
.verbose: ( + (loss))
.current_iter % .iter_per_epoch == :
.current_epoch +=
x_train_sample, t_train_sample = .x_train, .t_train
x_test_sample, t_test_sample = .x_test, .t_test
.evaluate_sample_num_per_epoch :
t = .evaluate_sample_num_per_epoch
x_train_sample, t_train_sample = .x_train[:t], .t_train[:t]
x_test_sample, t_test_sample = .x_test[:t], .t_test[:t]
train_acc = .network.accuracy(x_train_sample, t_train_sample)
test_acc = .network.accuracy(x_test_sample, t_test_sample)
.train_acc_list.append(train_acc)
.test_acc_list.append(test_acc)
.verbose: ( + (.current_epoch) + + (train_acc) + + (test_acc) + )
.current_iter +=
():
i (.max_iter):
.train_step()
test_acc = .network.accuracy(.x_test, .t_test)
.verbose:
()
( + (test_acc))
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=)
x_train, t_train = x_train[:], t_train[:]
x_test, t_test = x_test[:], t_test[:]
max_epochs =
network = SimpleConvNet(input_dim=(,,),
conv_param = {: , : , : , : },
hidden_size=, output_size=, weight_init_std=)
trainer = Trainer(network, x_train, t_train, x_test, t_test,
epochs=max_epochs, mini_batch_size=,
optimizer=, optimizer_param={: },
evaluate_sample_num_per_epoch=)
trainer.train()
network.save_params()
()
markers = {: , : }
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker=, label=, markevery=)
plt.plot(x, trainer.test_acc_list, marker=, label=, markevery=)
plt.xlabel()
plt.ylabel()
plt.ylim(, )
plt.legend(loc=)
plt.show()

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online