在介绍下一步分的代码之前,我们必须要先来看看我们之后要用的被加载进来的数据长什么鬼样子,那我们就来拿出一个数据来看一下数据长啥样好了。
img, label = mnistTrain[0] print(type(img)) #tensor print(img) #图片对应的像素值矩阵 print(type(label)) #int print(label) #5,图片的标签也就是说,数据集里面图片给的是一个tensor,但是标签给的是int,所以之后我们要自己把读出来的标签转化成我们想要one-hot向量。
由于我们训练集有60000张图片,所以如果用cpu进行训练的话可能要花很长的时间,能用GPU的话还是用GPU进行训练吧,这里给出一个通用代码,有没有GPU都可以的。
device = torch.device('cuda:0') if torch.cuda.is_available() else torch.device('cpu') #如果电脑有N卡的GPU,就可以把模型放GPU上,否则就放CPU上接下来终于到我们的重点啦啊啊啊啊啊!现在呢我们要开始构建我们的神经网络了。为了让小伙伴们都能看懂,所以这部分我们就老老实实地一步一步构造,也不写什么用Sequential全都包起来的骚操作,就一层一层地来:
H1:in_channel = 1, out_channel = 12, kernel_size = (5, 5), stride = (2, 2), padding = 2
激活函数:1.7159Tanh(2/3 * x)
self.conv1 = nn.Conv2d(1, 12, 5, stride = 2, padding = 2) self.act1 = nn.Tanh() #这一部分先用着Tanh(),等到后面写forward函数的时候再把系数乘上去H2:in_channel = 12, out_channel = 12, kernel_size = (5, 5), stride = (2, 2), padding = 2
激活函数:同上
self.conv2 = nn.Conv2d(12, 12, 5, stride = 2, padding = 2) self.act2 = nn.Tanh()H3:全连接层: 192 * 30
激活函数:同上
self.fc1 = nn.Linear(192, 30) self.act3 = nn.Tanh()output:全连接层:30 * 10
激活函数:同上
self.fc2 = nn.Linear(192, 30) self.act4 = nn.Tanh()我们需要把刚刚的这一堆全都放在自定义的LeNet1989类的构造函数里面,到时候所有的代码我会全部在最下面整理一下的,所以先别急吖。
在构造完基本构造以后,别忘了论文里面还说了,我们要对权重做一个基本的初始化,所以我们还要敲下面的代码:
for m in self.modules(): if isinstance(m, nn.Conv2d): F_in = m.kernel_size[0] * m.kernel_size[1] * m.out_channels m.weight.data = torch.rand(m.weight.data.size()) * 4.8 / F_in - 2.4 / F_in if isinstance(m, nn.Linear): F_in = m.out_features m.weight.data = torch.rand(m.weight.data.size()) * 4.8 / F_in - 2.4 / F_in关于这段代码,有一些需要注意的事情:
首先,当我们自定义的网络结构中有很多的基本结构的时候(比如说这个例子我们有两个卷积还有两个全连接),为了能够访问全部的基本结构,我们可以用Module的成员函数modules(),会返回一个包括自身内部所有基本结构的可迭代结构
在我们基本的结构,比如卷积层中,通过查看源码我们可以知道,里面是有weight成员和bias成员分别表示权重和偏置的
对于我们的所有的神经网络的基本模型(卷积层以及线性层),参数本来应该会直接存在tensor里面,但是为了在模型中进行一些中间结果的暂存(比如RNN的隐藏层输出),所有的参数会被打包放进一个叫做Parameter的类里面,Paramter里面有data成员用来存储tensor的数据,而还有一个成员requires_grad用来确定是否该参数可以求梯度,不过因为没用到所以先不提,感兴趣的小伙伴可以去看一下官方网站上的关于torch.nn.Module以及torch.nn.parameter.Parameter的源码。(说这么多主要是为了解释 m.weight.data的含义)
tensor在进行rand()初始化的时候,生成的随机数满足以[0, 1)为区间的均匀分布,想要转换成我们想要的分布的话就要自己做一些简单的变换。具体来说,假设我们要转换为[a, b),就需要进行下面的转换:x * (b-a) + a,这样的话就从[0, 1)转换为[a, b)了
上面的一大坨的代码就是我们定义的神经网络的构造以及初始化部分了。对于我们自行构造的神经网络结构来说,下面一个很重要的函数就是forward函数了,没有这个函数我们的网络就没得跑。但是由于我们前面结构已经定义好了,并且里面根本没有什么复杂的东西,所以这个模型的forward函数其实蛮好写的。(关于为什么一定要有forward函数,我在关于浙大AI的口罩识别作业里有简单的说明,或者大家可以读一下我之前推荐的《Deep Learning with Pytorch》)
def forward(self, x): x = self.conv1(x) x = 1.7159 * self.act1(2.0 * x / 3.0) #这里就是我们之前说的,实际论文用的激活函数并不是简单的Tanh x = self.conv2(x) x = 1.7159 * self.act2(2.0 * x / 3.0) x = x.view(-1, 192) #这一步是由于我们实际上在上面的一层中输出的x的维度为[12, 4, 4] #我们必须把它变成[1, 192]的形式才能输入到全连接层。 #详细的原因我在之前的浙大AI口罩作业的博客里有提到的 x = self.fc1(x) x = 1.7159 * self.act3(2.0 * x / 3.0) x = self.fc2(x) out = 1.7159 * self.act4(2.0 * x / 3.0) return out