曾赤脚在山坡 以为世界是我们的
基于 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平台的图像分类 数据加载模块 因为输入图像的尺寸可能不同,需要先统一输入的大小:
将图像缩放到 224 × 224 大小,并存储在矩阵中
对输入图像做标准化 ,将输入值范围从 [0,255] 标准化为均值为 0 的区间。具体做法是每个像素值减去ImageNet数据集的像素均值
最后将图像矩阵转化为神经网络输入的统一维度,即 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 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): hs, ws = idxh * self .stride, idxw * self .stride window = self .input_pad[idxn, :, hs:hs+self .kernel_size, ws:ws+self .kernel_size] 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 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): 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 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 ): 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([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 ): 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)) 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 ): 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 self .net.setInputShape(1 , 3 , 224 , 224 ) 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 ) self .net.createReLuLayer('relu1_1' ) 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 ) 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 self .net.createPoolingLayer("pool1" , input_shapepool1, 2 , 2 ) 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 ) self .net.createReLuLayer("relu2_1" ) 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 ) self .net.createReLuLayer("relu2_2" ) input_shapepool2 = pycnnl.IntVector(4 ) input_shapepool2[0 ] = 1 input_shapepool2[1 ] = 128 input_shapepool2[2 ] = 112 input_shapepool2[3 ] = 112 self .net.createPoolingLayer("pool2" , input_shapepool2, 2 , 2 ) 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 ) self .net.createReLuLayer("relu3_1" ) 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 ) self .net.createReLuLayer("relu3_2" ) 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 ) self .net.createReLuLayer("relu3_3" ) 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 ) self .net.createReLuLayer("relu3_4" ) input_shapepool3 = pycnnl.IntVector(4 ) input_shapepool3[0 ] = 1 input_shapepool3[1 ] = 256 input_shapepool3[2 ] = 56 input_shapepool3[3 ] = 56 self .net.createPoolingLayer("pool3" , input_shapepool3, 2 , 2 ) 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 ) self .net.createReLuLayer("relu4_1" ) 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 ) self .net.createReLuLayer("relu4_2" ) 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 ) self .net.createReLuLayer("relu4_3" ) 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 ) self .net.createReLuLayer("relu4_4" ) input_shapepool4 = pycnnl.IntVector(4 ) input_shapepool4[0 ] = 1 input_shapepool4[1 ] = 512 input_shapepool4[2 ] = 28 input_shapepool4[3 ] = 28 self .net.createPoolingLayer("pool4" , input_shapepool4, 2 , 2 ) 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 ) self .net.createReLuLayer("relu5_1" ) 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 ) self .net.createReLuLayer("relu5_2" ) 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 ) self .net.createReLuLayer("relu5_3" ) 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 ) self .net.createReLuLayer("relu5_4" ) input_shapepool5 = pycnnl.IntVector(4 ) input_shapepool5[0 ] = 1 input_shapepool5[1 ] = 512 input_shapepool5[2 ] = 14 input_shapepool5[3 ] = 14 self .net.createPoolingLayer("pool5" , input_shapepool5, 2 , 2 ) 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) self .net.createReLuLayer("relu6" ) 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) self .net.createReLuLayer("relu7" ) 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) 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 ): 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 ] 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): 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