搭建一个简单的网络

写在前面

瞎扯

我们终究不是做研究的,因此对于神经网络,只要会看流程图然后直接搭,或者直接调包就行了,所以我们需要搞明白的是:有什么输入、有什么输出、需要什么模块。本文将讲述Pytorch和TensorFlow的基础组件,用他们搭建出一个简单的神经网络,并进行量化。本文还会对比两个库一些操作的异同(都是坑啊),并展示几种模型的转换。TensorFlow主要是使用Keras框架和TensorFlow Lite(好吧,其实都是在TensorFlow库)

你需要的基础

你需要知道Python的基础语法,有Numpy的基础,知道什么是卷积,以及卷积的相关知识。文章将会先讲解两个库的组件,如何搭建神经网络,如何进行量化

数据的结构

在这里,我们以图像分类为例,在我们传统认知中,一张图的参数有长宽和通道数(就是RGB三通道啦),所以图像分类的输入就是一个三维数组,吗?可以,但是一般我们不会只输入一张图去训练,一般来说会一个批(batch)一起训练,所以就还有一个参数batchSize,也就是说一般是一个四维数组。
需要注意的是,Pytorch和TensorFlow的输入结构不同,Pytorch的输入为(batchSize,channel,height,width)既NCHW,而TensorFlow为(batchSize,height,width,channel)既NHWC。
为什么两个库的存储结构不同呢?

数据的类型

就是dtype啦,Pytorch用的是torch.比如torch.uint8,这是因为Pytorch的数据都是以torch.Tensor的形式存在的(可以理解为多维数组)。而TensorFlow可能直接用numpy的ndarray,这是因为TensorFlow可以将其自动转化为tf.Tensor,所以其dtype也是numpy的,比如numpy.uint8

运行的设备

就是device啦,需要注意的是,Pytorch有CPU和同时兼容GPU/CPU的版本,而在训练时,如果使用GPU进行训练的话,需要对输入进行.to(device),也需要对模型进行,具体如下:

import torch device=torch.device("cuda" if torch.cuda.is_available() else "cpu")#这一句是判断有没有GPU的,语法为torch.device(设备名字),比如torch.device("cuda1") model=CNN(inputChannels,width,height,num=3) model.load_state_dict(torch.load("example"),strict=True)#这里通过函数.load_state_dict来加载权重等参数 model.to(device)#将模型加载到指定的设备上 model.eval()#将模型切换为推理状态,避免模型被改变 inputs=torch.randn(1,3,224,224) inputs.to(device)#将输入加载到指定的设备上 outputs=model(inputs) print("Output shape:",outputs)

相应的,如果是TensorFlow可以使用with tf.device(device)

import tensorflow as tf import numpy as np device="/gpu:0" if tf.test.is_gpu_available() else "/cpu:0"#tf.test.is_gpu_available()是判断有没有GPU的 model=CNN(inputChannels,width,height,num=3) dummy_input=tf.constant(np.zeros((1,224,224,3),dtype=np.float32))#这里是搞一个虚拟输入,让他初始化模型,输入只需要和你期望的输入形状一样即可,没有值的要求 #对了,如果是单通道,比如(1,224,224,1),你可以只输入(1,224,224),TensorFlow会自己补齐,这里会在后面的tensorflow.keras.layers.Input函数讲到 model(dummy_input) model.load_weights("weight.h5")#通过函数.load_weights来加载权重 inputs=np.random.rand(1,224,224,3).astype(np.float32) with tf.device(device):#在设备上进行推理 inputs=tf.constant(inputs) outputs=model(inputs) print("Output shape:",outputs)

由于需要CPU对GPU进行数据的I/O,如果服务器的CPU拉胯的话会出现瓶颈。关于这点,可以看看这里(不是广告)。

有什么组件

传统的神经网络可以分为特征提取和分类两个部分,特征提取有卷积、残差、自动编码器等方法,分类部分可以是传统的K临近、支持向量机、全连接。在TensorFlow部分,我们用的是Keras框架,故以下Keras等价于TensorFlow。

全连接层

全连接层一般用于分类,放在特征提取部分后面,其输入和输出都是一维的(不考虑batchSize的话)。
Pytorch

torch.nn.Linear(in_features,out_features,bias=True,device=None,dtype=None) Example: torch.nn.Linear(12,1,bias=False)

in_features是指输入的大小
out_features是指输入的大小
bias是bool值,表示是否要开启bias

什么是bias?就是$\hat{y}=wx+b$的b

devicedtype,上面有说,不再赘述。
Keras

tf.keras.layers.Dense( units, activation=None, use_bias=True )#我们主要看这三个参数,其余参数见文档 Example: tf.keras.layers.Dense(1,activation='sigmoid',use_bias=False)

units是指输出的大小,Keras是不管输入的大小的,所以需要对模型进行初始化
activation是指激活函数,和Pytorch不同,Keras没有把激活函数分开(不过Keras也有可单独调用的激活函数),而是直接写在参数里,如果不需要激活函数,那就用默认的None即可
use_bias和Pytorch一样,表示是否要开启bias

(二维)卷积层

我们的目的是做图像分类,所以现在只考虑二维卷积层,下称卷积层。卷积层的作用是提取图像的特征,一个卷积层有多个卷积核,其结果放在不同通道,因此一张单通道的图放进去,出来可能就变成了16通道(有16个不同的卷积核)。
Python

torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True,padding_mode='zeros',device=None,dtype=None) Example: torch.nn.Conv2d(1,3,kernel_size=14)

in_channels,out_channels就是输入输出的通道数
kernel_size是卷积核的大小
stride是卷积核每一步的滑动距离
padding是填充的大小,dilation_mode就是填充方式
dilation和groups是更进一步的内容了,用不上就不赘述了
Keras

keras.layers.Conv2D(filters,kernel_size,strides=(1,1),padding='valid',data_format=None,dilation_rate=(1,1),activation=None,use_bias=True,kernel_initializer='glorot_uniform',bias_initializer='zeros',kernel_regularizer=None,bias_regularizer=None,activity_regularizer=None,kernel_constraint=None,bias_constraint=None) Example: tf.keras.layers.Conv2D(3,kernel_size=14)

参数有点多,但是常用的参数总体上和Pytorch基本相似,不同点在于TensorFlow可以让卷积核在两个方向上滑动步长不同,以及和全连接一样可以把激活函数嵌进去。注意filters是卷积核的数量,这是与Pytorch不同的(尽管在样例中他们用相同的值)。

激活函数层

这里只考虑常用的两个激活函数:sigmoid和ReLU,他们的形态如下: 激活函数示意图
sigmoid一般用在需要对输出进行归一化的地方,比如分类中用sigmoid处理全连接的输出来表示概率;而如果提取特征的话,一般就用ReLU,比如卷积层后面接的大多是ReLU。更多信息请看:激活函数汇总1激活函数汇总2
Pytorch

self.conv1=torch.nn.Sequential( torch.nn.Conv2d(1,3,kernel_size=14), torch.nn.ReLU(), )

Keras
除了前面提到的可以单独调用的激活函数,还有可以单独调用的激活函数层,我们可以往卷积层后面塞这个,比如

self.conv1=tf.keras.Sequential([#这个可以把多个层放在一起,方便管理和构造更大的块,之后会讲 tf.keras.layers.Conv2D(3,kernel_size=14), tf.keras.layers.ReLU() ])

输入层(建造中)

Keras

tf.keras.layers.Input( shape=None, batch_size=None, name=None, dtype=None, sparse=None, tensor=None, ragged=None. type_spec=None, ) Example: layers.Input(shape=(blockSize*height,blockSize*width),dtype=np.float32)

函数层(建造中)

tf.keras.layers.Lambda

量化

什么是量化

简单来说,就是简化神经网络的结构(合并一些层),减小参数的数据量(比如说,把float32类型的权重变为int8型),通过一系列方法压缩模型体积来满足嵌入式设备的要求。