我们终究不是做研究的,因此对于神经网络,只要会看流程图然后直接搭,或者直接调包就行了,所以我们需要搞明白的是:有什么输入、有什么输出、需要什么模块。本文将讲述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
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()
])
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型),通过一系列方法压缩模型体积来满足嵌入式设备的要求。