基于 VGG19 实现图像分类

曾赤脚在山坡 以为世界是我们的

基于 VGG19 实现图像分类

实验目的:

本实验旨在加深对卷积神经网络设计原理与工程实现的理解,掌握 VGG19网络结 构在图像分类任务中的应用,能够用 Python 语言在 CPU 与 MLU 平台上分别实现该网络, 并分析其运行性能。

实验内容:

本实验使用 VGG19 网络在 CPU 和 MLU 平台上进行图像分类。首先建立 VGG19 的网 络结构,然后利用 VGG19 的官方模型参数对给定图像进行分类。VGG19 网络的模型参数 是在 ImageNet数据集上训练获得的,VGG19 网络的输出结果对应 ImageNet 数据集中的 1000 个类别概率。 在工程实现中,由于本实验只涉及 VGG19 网络的推理过程,因此本实验仅包括数据加 载模块、基本单元模块、网络结构模块、网络推理模块,不包括网络训练模块。

实验环境:

采用 ImageNet 图 像数据集,该数据集以.jpg 或.png 压缩文件格式存放每张 RGB 图像,且不同图像的尺寸可 能不同。

本实验使用官方基于 ImageNet 数据集训练好的模型参数,不需 要使用 ImageNet 数据集进行 VGG19 模型的训练。

运行环境:

镜像收藏:mlu370_ubuntu22.04-for-student:v6.1

实验目录:

/opt/code_chap6/exp_6_2_VGG19classification/exp_6_2_1_infercpu/ 目录

CPU平台的图像分类

数据加载模块

因为输入图像的尺寸可能不同,需要先统一输入的大小:

  1. 将图像缩放到 224 × 224 大小,并存储在矩阵中

  2. 对输入图像做标准化,将输入值范围从 [0,255] 标准化为均值为 0 的区间。具体做法是每个像素值减去ImageNet数据集的像素均值

  3. 最后将图像矩阵转化为神经网络输入的统一维度,即 N × C × H × W。

    其中 N 是输入的样本数,由于图像是逐张读入的,因此 N = 1。

    C 是输入的通道数,本实 验输入图像是 RGB 彩色图像,因此 C = 3。

    H 和 W 分别表示输入图像的高和宽,本实验中缩放后的图像的高和宽均为 224。

基本单元模块

仅实现 VGG19 的推理过程,因此不需要实现反向传播计算和参数的更新,仅需实现层的初始化、参数初始化、前向传播计算、参数加载等基本操作。

全连接层、ReLU 层和 Softmax 层可以直接使用第6.2节实验中已经实现的相应网络层,本节重点介绍卷积层和池化层的实现

卷积层

与全连接层类似,卷积层中的参数包括权重(即卷积核)和偏置。VGG19 中使用的都是多输入输出特征图的卷积运算。

其中 $N$ 是输入的样本个数,在本实验中 $N=1$,$C_{in}$ 是输入的通道数,$H_{in}$ 和 $W_{in}$ 是输入特征图的高和宽。

输入特征图 $\boldsymbol{X}$ :维度为 $N \times C_{in} \times H_{in} \times W_{in}$

卷积核张量 $\boldsymbol{W}$ :四维矩阵,维度为 $C_{in} \times K \times K \times C_{out}$,其中 $K \times K$ 为卷积核的高度 $\times$ 宽度,也称为卷积窗口大小,$C_{out}$ 为输出特征图的通道数。

卷积层的偏置 $\boldsymbol{b}$ :一维向量,维度为 $C_{out}$。

输入特征图的边界扩充大小 $p$、卷积步长 $s$。

输出特征图 $\boldsymbol{Y}$ :维度为 $N \times C_{out} \times H_{out} \times W_{out}$,其中 $H_{out}$ 和 $W_{out}$ 是输出特征图的高和宽。

由 于 VGG19 网络中的所有卷积层都是 3 × 3 卷积核,即 K = 3,边界扩充大小 p = 1,步长 s = 1,因此 VGG19 网络中的所有卷积层输出特征图的高和宽与输入特征图相同。(所以也可以直接就等于输入的高和宽)

前向传播

总之就是先pad后传播

对卷积层的输入 $\boldsymbol{X}$ 做边界扩充(padding):为了保证卷积之后的有效输出尺寸与输入尺寸一致。就是在输入特征图的上下以及左右边界分别增加 $p$ 行以及 $p$ 列的 $0$。得到扩充后的特征图 $\boldsymbol{X}{pad}$:
$$
\boldsymbol{X}
{pad}(n, c_{in}, h, w) =
\begin{cases}
\boldsymbol{X}(n, c_{in}, h - p, w - p) , & \text{如果 } (p \leq h < p+H_{in}) & (p \leq w < p+W_{in}) \
0, & \text{其他}
\end{cases} \tag{6.22}
$$

其中 $n \in [0, N)$、$c_{in} \in [0, C_{in})$、$h \in [0, H_{in})$、$w \in [0, W_{in})$ 分别表示输入特征图的样本号、通道号、行号、列号,均为整数。$\boldsymbol{X}{pad}$ 的维度为 $N \times C{in} \times H_{pad} \times W_{pad}$,其中高度 $H_{pad}$ 和宽度 $W_{pad}$ 分别为:

$$
H_{pad} = H_{in} + 2p, \quad W_{pad} = W_{in} + 2p \tag{6.23}
$$

在$\boldsymbol{X}_{pad}$上滑动卷积窗口,依次计算窗口内的特征图数据与卷积核的矩阵内积加上偏置得到输出特征图 $\boldsymbol{Y}$:

$$
\boldsymbol{Y}(n, c_{out}, h, w) = \sum_{c_{in}, k_h, k_w} \boldsymbol{W}(c_{in}, k_h, k_w, c_{out}) \boldsymbol{X}{pad}(n, c{in}, hs + k_h, ws + k_w) + \boldsymbol{b}(c_{out}) \tag{6.24}
$$

其中 $n \in [0, N)$、$c_{out} \in [0, C_{out})$、$h \in [0, H_{out})$、$w \in [0, W_{out})$ 分别表示输出特征图的样本号、通道号、行号、列号;$k_h \in [0, K)$、$k_w \in [0, K)$ 表示卷积核的行号和列号;$c_{in} \in [0, C_{in})$ 表示输入特征图的通道号。这些符号的值均为整数。输出特征图 $\boldsymbol{Y}$ 的高度和宽度分别是:

$$
H_{out} = \left\lfloor \frac{H_{pad} - K}{s} \right\rfloor + 1 = \left\lfloor \frac{H_{in} + 2p - K}{s} \right\rfloor + 1, \quad W_{out} = \left\lfloor \frac{W_{pad} - K}{s} \right\rfloor + 1 = \left\lfloor \frac{W_{in} + 2p - K}{s} \right\rfloor + 1 \tag{6.25}
$$

实现

先扩展到pad,然后计算。计算时先算出Window更好理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def forward(self, input):
start_time = time.time()
self.input = input # [N, C, H, W]
# TODO: 边界扩充
height = self.input.shape[2] + 2 * self.padding
width = self.input.shape[3] + 2 * self.padding
self.input_pad = np.zeros([self.input.shape[0], self.input.shape[1], height, width])
# 保留所有样本(第一维)保留所有通道(第二维)
self.input_pad[:, :, self.padding:self.padding+self.input.shape[2], self.padding:self.padding+self.input.shape[3]] = self.input
height_out = (height - self.kernel_size) // self.stride + 1
width_out = (width - self.kernel_size) // self.stride + 1
self.output = np.zeros([self.input.shape[0], self.channel_out, height_out, width_out])
for idxn in range(self.input.shape[0]):
for idxc in range(self.channel_out):
for idxh in range(height_out):
for idxw in range(width_out):
# TODO: 计算卷积层的前向传播,特征图与卷积核的内积再加偏置
hs, ws = idxh * self.stride, idxw * self.stride
# 算Window
window = self.input_pad[idxn, :, hs:hs+self.kernel_size, ws:ws+self.kernel_size]
# weight shape: [C_in, K, K, C_out]
self.output[idxn, idxc, idxh, idxw] = np.sum(window * self.weight[:, :, :, idxc]) + self.bias[idxc]
return self.output

反向传播

假设损失函数为 $L$,损失函数对本层输出的偏导为 $\nabla_{\boldsymbol{Y}} L$,其维度与卷积层的输出特征图相同,均为 $N \times C_{out} \times H_{out} \times W_{out}$。根据链式法则,可以计算权重和偏置的梯度 $\nabla_{\boldsymbol{W}} L$、$\nabla_{\boldsymbol{b}} L$ 以及损失函数对边界扩充后的输入特征图的偏导 $\nabla_{\boldsymbol{X}{pad}} L$,计算公式为:
$$
\nabla
{\boldsymbol{W}}(c_{in}, k_h, k_w, c_{out}) L = \sum_{n, h, w} \nabla_{\boldsymbol{Y}}(n, c_{out}, h, w) L \cdot \boldsymbol{X}{pad}(n, c{in}, hs + k_h, ws + k_w)
$$

$$
\nabla_{\boldsymbol{b}}(c_{out}) L = \sum_{n, h, w} \nabla_{\boldsymbol{Y}}(n, c_{out}, h, w) L
$$

$$
\nabla_{\boldsymbol{X}{pad}}(n, c{in}, hs + i_h, ws + i_w) L = \sum_{c_{out}} \sum_{f_h=0}^{\lfloor K/s \rfloor - i_h} \sum_{f_w=0}^{\lfloor K/s \rfloor - i_w} \nabla_{\boldsymbol{Y}}(n, c_{out}, h - f_h, w - f_w) L \cdot \boldsymbol{W}(c_{in}, f_h s + i_h, f_w s + i_w, c_{out}) \tag{6.26}
$$

其中,$n \in [0, N)$,$c_{in} \in [0, C_{in})$,$c_{out} \in [0, C_{out})$,$h \in [0, H_{out})$,$w \in [0, W_{out})$,$k_h \in [0, K)$,$k_w \in [0, K)$,$i_h \in [0, s)$,$i_w \in [0, s)$。之后剪裁掉 $\nabla_{\boldsymbol{X}{pad}} L$ 中扩充的边界,得到本层的 $\nabla{\boldsymbol{X}} L$,计算公式为:

$$
\nabla_{\boldsymbol{X}}(n, c_{in}, h, w) L = \nabla_{\boldsymbol{X}{pad}}(n, c{in}, h + p, w + p) L \tag{6.27}
$$

其中 $n \in [0, N)$,$c_{in} \in [0, C_{in})$,$h \in [0, H_{in})$,$w \in [0, W_{in})$。

实现

本次实验中没训练步骤所以没有反向传播部分的实现

最大池化层

输入特征图 $\boldsymbol{X}$ :维度为 $N \times C \times H_{in} \times W_{in}$

池化窗口:高和宽均为 $K$,池化步长为 $s$,

输出特征图 $\boldsymbol{Y}$ :维度为 $N \times C \times H_{out} \times W_{out}$,其中 $H_{out}$ 和 $W_{out}$ 是输出特征图的高和宽。

前向传播

输出特征图 $\boldsymbol{Y}$ 中某一位置的值是输入特征图 $\boldsymbol{X}$ 的对应池化窗口内的最大值,计算公式为:
$$
\boldsymbol{Y}(n, c, h, w) = \max_{k_h, k_w} \boldsymbol{X}(n, c, hs + k_h, ws + k_w) \tag{6.28}
$$

其中 $n \in [0, N)$、$c \in [0, C)$、$h \in [0, H_{out})$、$w \in [0, W_{out})$ 分别表示输出特征图的样本号、通道号、行号、列号,$k_h \in [0, K)$、$k_w \in [0, K)$ 表示池化窗口内的坐标位置,均为整数。

实现

先算出window,然后取最大值就好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def forward(self, input):
start_time = time.time()
self.input = input # [N, C, H, W]
self.max_index = np.zeros(self.input.shape)
height_out = (self.input.shape[2] - self.kernel_size) // self.stride + 1
width_out = (self.input.shape[3] - self.kernel_size) // self.stride + 1
self.output = np.zeros([self.input.shape[0], self.input.shape[1], height_out, width_out])
for idxn in range(self.input.shape[0]):
for idxc in range(self.input.shape[1]):
for idxh in range(height_out):
for idxw in range(width_out):
# TODO: 计算最大池化层的前向传播, 取池化窗口内的最大值
hs, ws = idxh * self.stride, idxw * self.stride
window = self.input[idxn, idxc, hs:hs+self.kernel_size, ws:ws+self.kernel_size]
self.output[idxn, idxc, idxh, idxw] = np.max(window)
return self.output

反向传播

给定损失函数对本层输出的偏导 $\nabla_{\boldsymbol{Y}} L$,其维度与最大池化层的输出特征图相同,均为 $N \times C \times H_{out} \times W_{out}$。由于最大池化层在前向传播后仅保留池化窗口内的最大值,因此在反向传播时,仅将后一层损失中对应该池化窗口的值传递给池化窗口内最大值所在位置,其他位置值置为 $0$。在反向传播时需先计算最大值所在位置 $\boldsymbol{p}$,计算公式为:
$$
\boldsymbol{p}(n, c, h, w) = \underset{k_h, k_w}{\operatorname{argmax}} \left( \boldsymbol{X}(n, c, hs + k_h, ws + k_w) \right) \tag{6.29}
$$

其中 $\underset{k_h, k_w}{\operatorname{argmax}}$ 代表取最大值所在位置的函数,返回最大值位于池化窗口中的坐标向量 $\boldsymbol{p}(n, c, h, w) = [q(0), q(1)]$,其中 $q(0)$ 对应 $h$ 方向的坐标,$q(1)$ 对应 $w$ 方向的坐标。$n \in [0, N)$,$c \in [0, C)$,$h \in [0, H_{out})$,$w \in [0, W_{out})$,$k_h \in [0, K)$,$k_w \in [0, K)$ 分别为输入输出特征图和池化窗口上的位置坐标。利用最大值所在位置 $[q(0), q(1)]$ 可得最大池化层的 $\nabla_{\boldsymbol{X}} L$,计算公式为:

$$
\nabla_{\boldsymbol{X}}(n, c, hs + q(0), ws + q(1)) L = \nabla_{\boldsymbol{Y}}(n, c, h, w) L \tag{6.30}
$$

flatten 层

flatten 层用于改变特征图的维度,将输入特征图中每个样本的特征平铺成一个向量

输入特征图 X :维度为 N × C × H × W,其中 N 是输入的样 本个数,在本实验中 N = 1,C 是输入的通道数,H 和 W 是输入特征图的高和宽。

输出特征图:维度变为 N × (CHW)。

前向传播

注意 VGG19 官方模型所使用的深度学习平台 MatConvNet的特征图存储方式与本实验不同。 MatConvNet 中特征图维度为 N × H × W × C,而本实验中特征图 X 的维度为 N × C × H × W。 因此需要将输入特征图进行维度交换,保持与 MatConvNet 的特征图存储方式一致。

1
2
3
4
5
6
7
8
9
def forward(self, input):
assert list(input.shape[1:]) == list(self.input_shape)
self.input = input
# matconvnet feature map dim: [N, height, width, channel]
# ours feature map dim: [N, channel, height, width]
self.input = self.input.transpose(0, 2, 3, 1) # 转置
self.output = self.input.reshape([self.input.shape[0]] + list(self.output_shape))
show_matrix(self.output, 'flatten out ')
return self.output

网络结构模块

VGG19 网络的基本结构

名字 类型 卷积核/池化核 步长 边界扩充 输入通道数 输出通道数 输出特征图的高和宽
conv1_1 卷积层 3 1 1 3 64 224
conv1_2 卷积层 3 1 1 64 64 224
pool1 最大池化层 2 2 - 64 64 112
conv2_1 卷积层 3 1 1 64 128 112
conv2_2 卷积层 3 1 1 128 128 112
pool2 最大池化层 2 2 - 128 128 56
conv3_1 卷积层 3 1 1 128 256 56
conv3_2 卷积层 3 1 1 256 256 56
conv3_3 卷积层 3 1 1 256 256 56
conv3_4 卷积层 3 1 1 256 256 56
pool3 最大池化层 2 2 - 256 256 28
conv4_1 卷积层 3 1 1 256 512 28
conv4_2 卷积层 3 1 1 512 512 28
conv4_3 卷积层 3 1 1 512 512 28
conv4_4 卷积层 3 1 1 512 512 28
pool4 最大池化层 2 2 - 512 512 14
conv5_1 卷积层 3 1 1 512 512 14
conv5_2 卷积层 3 1 1 512 512 14
conv5_3 卷积层 3 1 1 512 512 14
conv5_4 卷积层 3 1 1 512 512 14
pool5 最大池化层 2 2 - 512 512 7
fc6 全连接层 - - - 25088 4096 -
fc7 全连接层 - - - 4096 4096 -
fc8 全连接层 - - - 4096 1000 -
Softmax 损失层 - - - - - -

实现

卷积层的参数顺序是:(self, kernel_size, channel_in, channel_out, padding, stride)

池化层的参数顺序:(self, kernel_size, stride)

flatten层的参数顺序:(self, input_shape, output_shape)

注意每个卷积层和前 2 个全连接层(fc6 层和 fc7 层) 后面都跟随有 ReLU 激活函数层。此外,pool5 层和 fc6 层中间有一个 flatten 层,用于改变 特征图的维度。在VGG19类中也有层的名称顺序。然后就按表格来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class VGG19(object):
def __init__(self, param_path='../../imagenet-vgg-verydeep-19.mat'):
self.param_path = param_path
self.param_layer_name = (
'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1',
'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2',
'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3', 'relu3_3', 'conv3_4', 'relu3_4', 'pool3',
'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3', 'relu4_3', 'conv4_4', 'relu4_4', 'pool4',
'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3', 'relu5_3', 'conv5_4', 'relu5_4', 'pool5',
'flatten', 'fc6', 'relu6', 'fc7', 'relu7', 'fc8', 'softmax'
)

def build_model(self):
# TODO:定义VGG19 的网络结构
print('Building vgg-19 model...')

self.layers = {}
self.layers['conv1_1'] = ConvolutionalLayer(3, 3, 64, 1, 1)
self.layers['relu1_1'] = ReLULayer()
self.layers['conv1_2'] = ConvolutionalLayer(3, 64, 64, 1, 1)
self.layers['relu1_2'] = ReLULayer()
self.layers['pool1'] = MaxPoolingLayer(2, 2)

self.layers['conv2_1'] = ConvolutionalLayer(3, 64, 128, 1, 1)
self.layers['relu2_1'] = ReLULayer()
self.layers['conv2_2'] = ConvolutionalLayer(3, 128, 128, 1, 1)
self.layers['relu2_2'] = ReLULayer()
self.layers['pool2'] = MaxPoolingLayer(2, 2)

self.layers['conv3_1'] = ConvolutionalLayer(3, 128, 256, 1, 1)
self.layers['relu3_1'] = ReLULayer()
self.layers['conv3_2'] = ConvolutionalLayer(3, 256, 256, 1, 1)
self.layers['relu3_2'] = ReLULayer()
self.layers['conv3_3'] = ConvolutionalLayer(3, 256, 256, 1, 1)
self.layers['relu3_3'] = ReLULayer()
self.layers['conv3_4'] = ConvolutionalLayer(3, 256, 256, 1, 1)
self.layers['relu3_4'] = ReLULayer()
self.layers['pool3'] = MaxPoolingLayer(2, 2)

self.layers['conv4_1'] = ConvolutionalLayer(3, 256, 512, 1, 1)
self.layers['relu4_1'] = ReLULayer()
self.layers['conv4_2'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu4_2'] = ReLULayer()
self.layers['conv4_3'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu4_3'] = ReLULayer()
self.layers['conv4_4'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu4_4'] = ReLULayer()
self.layers['pool4'] = MaxPoolingLayer(2, 2)

self.layers['conv5_1'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu5_1'] = ReLULayer()
self.layers['conv5_2'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu5_2'] = ReLULayer()
self.layers['conv5_3'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu5_3'] = ReLULayer()
self.layers['conv5_4'] = ConvolutionalLayer(3, 512, 512, 1, 1)
self.layers['relu5_4'] = ReLULayer()
self.layers['pool5'] = MaxPoolingLayer(2, 2)

self.layers['flatten'] = FlattenLayer()
self.layers['fc6'] = FullyConnectedLayer(512*7*7, 4096)
self.layers['relu6'] = ReLULayer()
self.layers['fc7'] = FullyConnectedLayer(4096, 4096)
self.layers['relu7'] = ReLULayer()

self.layers['fc8'] = FullyConnectedLayer(4096, 1000)

self.layers['softmax'] = SoftmaxLossLayer()

self.update_layer_list = []
for layer_name in self.layers.keys():
if 'conv' in layer_name or 'fc' in layer_name:
self.update_layer_list.append(layer_name)

网络推理模块

本实验使用的官方模型的下载地址为http://www.vlfeat.org/matconvnet/models/beta16/imagenet-vgg-verydeep-19.mat(环境中已经下好了)

本实验在 pool5 层和 fc6 层之间添加了 flatten 层来改变特征图的维度,而官方提供的模型不包含 flatten 层,因此 fc6 层及之后的层在读取参数时需要偏移。

卷积层:MatConvNet 中卷积权重维度为 H × W × Cin × Cout ,而本实 验中权重的维度为 Cin × H × W × Cout。读取卷积层权重时需要对输入权重做维度交换

运行

补全 stu_upload 中的 layer_1.py(可以直接贴实验1的)、layer_2.py、vgg_cpu.py 文件。

全连接层的init_param改了bias的初始化

1
2
3
4
def init_param(self, std=0.01):
self.weight = np.random.normal(loc=0.0, scale=std, size=(self.num_input, self.num_output))
# self.bias=np.zeros([1, self.num_output])
self.bias = np.zeros([self.num_output])

运行:

1
python main_infer_cpu.py

思考

在实现深度神经网络后,如何确保整个网络的实现是正确的?

感觉最容易出错的就是每一层之间的维度有没有对应….weight和bias啥的全都出错了好几遍。或许可以打印出来检查一遍

还有很容易错的就是什么维度展平啊转置啊,总之是因为训练数据的维度定义和本实验不一致,搞得头疼

MLU 平台上的实验步骤

结构几乎一模一样,不用自己写更简单了

注意:使用pycnnl创建vgg网络不需要flatten层

数据加载模块

调用 pycnnl 库的 Python 接口前需要将数据类型从 numpy.float32 转换为 numpy.float64。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def load_image(self, image_dir):
# loading image
self.image = image_dir
image_mean = np.array([123.68, 116.779, 103.939])
print('Loading and preprocessing image from ' + image_dir)
input_image = imageio.imread(image_dir)
pil_img = Image.fromarray(input_image)
pil_img = pil_img.resize((224, 224), Image.Resampling.LANCZOS)
input_image = np.array(pil_img, dtype=np.float32)
input_image -= image_mean
input_image = np.reshape(input_image, [1]+list(input_image.shape))
# TODO:调整输入数据
input_data = input_image.flatten().astype(np.float64)

self.net.setInputData(input_data)

网络结构模块

VGG19 中包含的卷积层、ReLU 层、最大池化层、全连接层和 Softmax 层可以直接调 用 pycnnl 库来实现对应层的初始化、参数加载、前向传播等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
class VGG19(object):
def __init__(self):
# set up net

self.net = pycnnl.CnnlNet()
self.input_quant_params = []
self.filter_quant_params = []


def build_model(self, param_path='../../imagenet-vgg-verydeep-19.mat'):
self.param_path = param_path


# TODO: 使用net的createXXXLayer接口搭建VGG19网络
# creating layers
self.net.setInputShape(1, 3, 224, 224)
# conv1_1

input_shape1=pycnnl.IntVector(4)
input_shape1[0]=1
input_shape1[1]=3
input_shape1[2]=224
input_shape1[3]=224
self.net.createConvLayer('conv1_1', input_shape1,64, 3, 1, 1, 1)

# relu1_1
self.net.createReLuLayer('relu1_1')
# conv1_2

input_shape12=pycnnl.IntVector(4)
input_shape12[0]=1
input_shape12[1]=64
input_shape12[2]=224
input_shape12[3]=224

self.net.createConvLayer('conv1_2',input_shape12, 64, 3, 1, 1, 1)

# relu1_2
self.net.createReLuLayer('relu1_2')

input_shapepool1 = pycnnl.IntVector(4)
input_shapepool1[0] = 1
input_shapepool1[1] = 64
input_shapepool1[2] = 224
input_shapepool1[3] = 224
# pool1
self.net.createPoolingLayer("pool1", input_shapepool1, 2, 2)

# conv2_1
input_shape2 = pycnnl.IntVector(4)
input_shape2[0] = 1
input_shape2[1] = 64
input_shape2[2] = 112
input_shape2[3] = 112
self.net.createConvLayer("conv2_1", input_shape2, 128, 3, 1, 1, 1)

# relu2_1
self.net.createReLuLayer("relu2_1")
# conv2_2

input_shape22 = pycnnl.IntVector(4)
input_shape22[0] = 1
input_shape22[1] = 128
input_shape22[2] = 112
input_shape22[3] = 112

self.net.createConvLayer("conv2_2", input_shape22, 128, 3, 1, 1, 1)

# relu2_2
self.net.createReLuLayer("relu2_2")
# pool2
input_shapepool2 = pycnnl.IntVector(4)
input_shapepool2[0] = 1
input_shapepool2[1] = 128
input_shapepool2[2] = 112
input_shapepool2[3] = 112
# pool2
self.net.createPoolingLayer("pool2", input_shapepool2, 2, 2)

# conv3_1
input_shape3 = pycnnl.IntVector(4)
input_shape3[0] = 1
input_shape3[1] = 128
input_shape3[2] = 56
input_shape3[3] = 56
self.net.createConvLayer("conv3_1", input_shape3, 256, 3, 1, 1, 1)
# relu3_1
self.net.createReLuLayer("relu3_1")
# conv3_2
input_shape32 = pycnnl.IntVector(4)
input_shape32[0] = 1
input_shape32[1] = 256
input_shape32[2] = 56
input_shape32[3] = 56
self.net.createConvLayer("conv3_2", input_shape32, 256, 3, 1, 1, 1)
# relu3_2
self.net.createReLuLayer("relu3_2")
# conv3_3
input_shape33 = pycnnl.IntVector(4)
input_shape33[0] = 1
input_shape33[1] = 256
input_shape33[2] = 56
input_shape33[3] = 56
self.net.createConvLayer("conv3_3", input_shape33, 256, 3, 1, 1, 1)
# relu3_3
self.net.createReLuLayer("relu3_3")
# conv3_4
input_shape34 = pycnnl.IntVector(4)
input_shape34[0] = 1
input_shape34[1] = 256
input_shape34[2] = 56
input_shape34[3] = 56

self.net.createConvLayer("conv3_4", input_shape34, 256, 3, 1, 1, 1)

# relu3_4
self.net.createReLuLayer("relu3_4")
# pool3
input_shapepool3 = pycnnl.IntVector(4)
input_shapepool3[0] = 1
input_shapepool3[1] = 256
input_shapepool3[2] = 56
input_shapepool3[3] = 56
# pool3
self.net.createPoolingLayer("pool3", input_shapepool3, 2, 2)

# conv4_1
input_shape4 = pycnnl.IntVector(4)
input_shape4[0] = 1
input_shape4[1] = 256
input_shape4[2] = 28
input_shape4[3] = 28
self.net.createConvLayer("conv4_1", input_shape4, 512, 3, 1, 1, 1)
# relu4_1
self.net.createReLuLayer("relu4_1")
# conv4_2
input_shape42 = pycnnl.IntVector(4)
input_shape42[0] = 1
input_shape42[1] = 512
input_shape42[2] = 28
input_shape42[3] = 28
self.net.createConvLayer("conv4_2", input_shape42, 512, 3, 1, 1, 1)
# relu4_2
self.net.createReLuLayer("relu4_2")
# conv4_3
input_shape43 = pycnnl.IntVector(4)
input_shape43[0] = 1
input_shape43[1] = 512
input_shape43[2] = 28
input_shape43[3] = 28
self.net.createConvLayer("conv4_3", input_shape43, 512, 3, 1, 1, 1)
# relu4_3
self.net.createReLuLayer("relu4_3")
# conv4_4
input_shape44 = pycnnl.IntVector(4)
input_shape44[0] = 1
input_shape44[1] = 512
input_shape44[2] = 28
input_shape44[3] = 28

self.net.createConvLayer("conv4_4", input_shape44, 512, 3, 1, 1, 1)

# relu4_4
self.net.createReLuLayer("relu4_4")
# pool4
input_shapepool4 = pycnnl.IntVector(4)
input_shapepool4[0] = 1
input_shapepool4[1] = 512
input_shapepool4[2] = 28
input_shapepool4[3] = 28
# pool4
self.net.createPoolingLayer("pool4", input_shapepool4, 2, 2)

# conv5_1
input_shape5 = pycnnl.IntVector(4)
input_shape5[0] = 1
input_shape5[1] = 512
input_shape5[2] = 14
input_shape5[3] = 14
self.net.createConvLayer("conv5_1", input_shape5, 512, 3, 1, 1, 1)
# relu5_1
self.net.createReLuLayer("relu5_1")
# conv5_2
input_shape52 = pycnnl.IntVector(4)
input_shape52[0] = 1
input_shape52[1] = 512
input_shape52[2] = 14
input_shape52[3] = 14
self.net.createConvLayer("conv5_2", input_shape52, 512, 3, 1, 1, 1)
# relu5_2
self.net.createReLuLayer("relu5_2")
# conv5_3
input_shape53 = pycnnl.IntVector(4)
input_shape53[0] = 1
input_shape53[1] = 512
input_shape53[2] = 14
input_shape53[3] = 14
self.net.createConvLayer("conv5_3", input_shape53, 512, 3, 1, 1, 1)
# relu5_3
self.net.createReLuLayer("relu5_3")
# conv5_4
input_shape54 = pycnnl.IntVector(4)
input_shape54[0] = 1
input_shape54[1] = 512
input_shape54[2] = 14
input_shape54[3] = 14

self.net.createConvLayer("conv5_4", input_shape54, 512, 3, 1, 1, 1)

# relu5_4
self.net.createReLuLayer("relu5_4")
# pool5
input_shapepool5 = pycnnl.IntVector(4)
input_shapepool5[0] = 1
input_shapepool5[1] = 512
input_shapepool5[2] = 14
input_shapepool5[3] = 14
# pool5
self.net.createPoolingLayer("pool5", input_shapepool5, 2, 2)

# fc6
input_shapem6 = pycnnl.IntVector(4)
input_shapem6[0] = 1
input_shapem6[1] = 1
input_shapem6[2] = 1
input_shapem6[3] = 25088
weight_shapem6 = pycnnl.IntVector(4)
weight_shapem6[0] = 1
weight_shapem6[1] = 1
weight_shapem6[2] = 25088
weight_shapem6[3] = 4096
output_shapem6 = pycnnl.IntVector(4)
output_shapem6[0] = 1
output_shapem6[1] = 1
output_shapem6[2] = 1
output_shapem6[3] = 4096

self.net.createMlpLayer("fc6", input_shapem6, weight_shapem6, output_shapem6)
# relu6
self.net.createReLuLayer("relu6")

# fc7
input_shapem7 = pycnnl.IntVector(4)
input_shapem7[0] = 1
input_shapem7[1] = 1
input_shapem7[2] = 1
input_shapem7[3] = 4096
weight_shapem7 = pycnnl.IntVector(4)
weight_shapem7[0] = 1
weight_shapem7[1] = 1
weight_shapem7[2] = 4096
weight_shapem7[3] = 4096
output_shapem7 = pycnnl.IntVector(4)
output_shapem7[0] = 1
output_shapem7[1] = 1
output_shapem7[2] = 1
output_shapem7[3] = 4096

self.net.createMlpLayer("fc7", input_shapem7, weight_shapem7, output_shapem7)
# relu7
self.net.createReLuLayer("relu7")

# fc8
input_shapem3=pycnnl.IntVector(4)
input_shapem3[0]=1
input_shapem3[1]=1
input_shapem3[2]=1
input_shapem3[3]=4096
weight_shapem3=pycnnl.IntVector(4)
weight_shapem3[0]=1
weight_shapem3[1]=1
weight_shapem3[2]=4096
weight_shapem3[3]=1000
output_shapem3=pycnnl.IntVector(4)
output_shapem3[0]=1
output_shapem3[1]=1
output_shapem3[2]=1
output_shapem3[3]=1000

self.net.createMlpLayer('fc8', input_shapem3,weight_shapem3,output_shapem3)

# softmax

input_shapes=pycnnl.IntVector(3)
input_shapes[0]=1
input_shapes[1]=1
input_shapes[2]=1000


self.net.createSoftmaxLayer('softmax',input_shapes ,1)

网络推理模块

维度交换+数据类型转为 np.float64 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def load_model(self):
# loading params ...
print('Loading parameters from file ' + self.param_path)
params = scipy.io.loadmat(self.param_path)
self.image_mean = params['normalization'][0][0][0]
self.image_mean = np.mean(self.image_mean, axis=(0, 1))

count = 0
for idx in range(self.net.size()):
if 'conv' in self.net.getLayerName(idx):
weight, bias = params['layers'][0][idx][0][0][0][0]
# TODO:调整权重形状
# matconvnet: weights dim [height, width, in_channel, out_channel]
# ours: weights dim [out_channel, height, width,in_channel]
weight = np.transpose(weight, [3, 0, 1, 2]).flatten().astype(np.float64)
bias = bias.reshape(-1).astype(np.float64)
self.net.loadParams(idx, weight, bias)
count += 1
if 'fc' in self.net.getLayerName(idx):
# Loading params may take quite a while. Please be patient.
weight, bias = params['layers'][0][idx][0][0][0][0]
weight = weight.reshape([weight.shape[0]*weight.shape[1]*weight.shape[2], weight.shape[3]])
weight = weight.flatten().astype(np.float64)
bias = bias.reshape(-1).astype(np.float64)


self.net.loadParams(idx, weight, bias)
count += 1

运行

1
python main_infer_cpu.py