• 【小白学PyTorch】4 构建模型三要素与权重初始化


    文章目录:

    1 模型三要素

    三要素其实很简单

    1. 必须要继承nn.Module这个类,要让PyTorch知道这个类是一个Module
    2. 在__init__(self)中设置好需要的组件,比如conv,pooling,Linear,BatchNorm等等
    3. 最后在forward(self,x)中用定义好的组件进行组装,就像搭积木,把网络结构搭建出来,这样一个模型就定义好了

    我们来看一个例子:
    先看__init__(self)函数

    def __init__(self):
    	super(Net,self).__init__()
    	self.conv1 = nn.Conv2d(3,6,5)
    	self.pool1 = nn.MaxPool2d(2,2)
    	self.conv2 = nn.Conv2d(6,16,5)
    	self.pool2 = nn.MaxPool2d(2,2)
    	self.fc1 = nn.Linear(16*5*5,120)
    	self.fc2 = nn.Linear(120,84)
    	self.fc3 = nn.Linear(84,10)
    

    第一行是初始化,往后定义了一系列组件。nn.Conv2d就是一般图片处理的卷积模块,然后池化层,全连接层等等。

    定义完这些定义forward函数

    def forward(self,x):
    	x = self.pool1(F.relu(self.conv1(x)))
    	x = self.pool2(F.relu(self.conv2(x)))
    	x = x.view(-1,16*5*5)
    	x = F.relu(self.fc1(x))
    	x = F.relu(self.fc2(x))
    	x = self.fc3(x)
    	return x
    

    x为模型的输入,第一行表示x经过conv1,然后经过激活函数relu,然后经过pool1操作
    第三行表示对x进行reshape,为后面的全连接层做准备

    至此,对一个模型的定义完毕,如何使用呢?

    例如:

    net = Net()
    outputs = net(inputs)
    

    其实net(inputs),就是类似于使用了net.forward(inputs)这个函数。

    2 参数初始化

    简单地说就是设定什么层用什么初始方法,初始化的方法会在torch.nn.init中

    话不多说,看一个案例:

    # 定义权值初始化
    def initialize_weights(self):
    	for m in self.modules():
    		if isinstance(m,nn.Conv2d):
    			torch.nn.init.xavier_normal_(m.weight.data)
    			if m.bias is not None:
    				m.bias.data.zero_()
    		elif isinstance(m,nn.BatchNorm2d):
    			m.weight.data.fill_(1)
    			m.bias.data.zero_()
    		elif isinstance(m,nn.Linear):
    			torch.nn.init.normal_(m.weight.data,0,0.01)
    			# m.weight.data.normal_(0,0.01)
    			m.bias.data.zero_()
    

    这段代码的基本流程就是,先从self.modules()中遍历每一层,然后判断更曾属于什么类型,是否是Conv2d,是否是BatchNorm2d,是否是Linear的,然后根据不同类型的层,设定不同的权值初始化方法,例如Xavier,kaiming,normal_等等。kaiming也是MSRA初始化,是何恺明大佬在微软亚洲研究院的时候,因此得名。

    上面代码中用到了self.modules(),这个是什么东西呢?

    # self.modules的源码
    def modules(self):
    	for name,module in self.named_modules():
    		yield module
    

    功能就是:能依次返回模型中的各层,yield是让一个函数可以像迭代器一样可以用for循环不断从里面遍历(可能说的不太明确)。

    3 完整运行代码

    我们用下面的例子来更深入的理解self.modules(),同时也把上面的内容都串起来(下面的代码块可以运行):

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    from torch.utils.data import Dataset,DataLoader
    
    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(3, 6, 5)
            self.pool1 = nn.MaxPool2d(2, 2)
            self.conv2 = nn.Conv2d(6, 16, 5)
            self.pool2 = nn.MaxPool2d(2, 2)
            self.fc1 = nn.Linear(16 * 5 * 5, 120)
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
    
        def forward(self, x):
            x = self.pool1(F.relu(self.conv1(x)))
            x = self.pool2(F.relu(self.conv2(x)))
            x = x.view(-1, 16 * 5 * 5)
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
        def initialize_weights(self):
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    torch.nn.init.xavier_normal_(m.weight.data)
                    if m.bias is not None:
                        m.bias.data.zero_()
                elif isinstance(m, nn.BatchNorm2d):
                    m.weight.data.fill_(1)
                    m.bias.data.zero_()
                elif isinstance(m, nn.Linear):
                    torch.nn.init.normal_(m.weight.data, 0, 0.01)
                    # m.weight.data.normal_(0,0.01)
                    m.bias.data.zero_()
    
    net = Net()
    net.initialize_weights()
    print(net.modules())
    for m in net.modules():
        print(m)
    

    运行结果:

    # 这个是print(net.modules())的输出
    <generator object Module.modules at 0x0000023BDCA23258>
    # 这个是第一次从net.modules()取出来的东西,是整个网络的结构
    Net(
      (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
      (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
      (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
      (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
      (fc1): Linear(in_features=400, out_features=120, bias=True)
      (fc2): Linear(in_features=120, out_features=84, bias=True)
      (fc3): Linear(in_features=84, out_features=10, bias=True)
    )
    # 从net.modules()第二次开始取得东西就是每一层了
    Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
    MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    Linear(in_features=400, out_features=120, bias=True)
    Linear(in_features=120, out_features=84, bias=True)
    Linear(in_features=84, out_features=10, bias=True)
    

    其中呢,并不是每一层都有偏执bias的,有的卷积层可以设置成不要bias的,所以对于卷积网络参数的初始化,需要判断一下是否有bias,(不过我好像记得bias默认初始化为0?不确定,有知道的朋友可以交流)

    torch.nn.init.xavier_normal(m.weight.data)
    if m.bias is not None:
    	m.bias.data.zero_()
    

    上面代码表示用xavier_normal方法对该层的weight初始化,并判断是否存在偏执bias,若存在,将bias初始化为0。

    4 尺寸计算与参数计算

    我们把上面的主函数部分改成:

    net = Net()
    net.initialize_weights()
    layers = {}
    for m in net.modules():
        if isinstance(m,nn.Conv2d):
            print(m)
            break
    

    这里的输出m就是:

    Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    

    这个卷积层,就是我们设置的第一个卷积层,含义就是:输入3通道,输出6通道,卷积核(5 imes 5),步长1,padding=0.

    【问题1:输入特征图和输出特征图的尺寸计算】

    之前的文章也讲过这个了,

    (output = frac{input+2 imes padding -kernel}{stride}+1)

    用代码来验证一下这个公式:

    net = Net()
    net.initialize_weights()
    input = torch.ones((16,3,10,10))
    output = net.conv1(input)
    print(input.shape)
    print(output.shape)
    

    初始结果:

    torch.Size([16, 3, 10, 10])
    torch.Size([16, 6, 6, 6])
    

    第一个维度上batch,第二个是通道channel,第三个和第四个是图片(特征图)的尺寸。

    (frac{10+2 imes 0-5}{1}+1=6) 算出来的结果没毛病。

    【问题2:这个卷积层中有多少的参数?】
    输入通道是3通道的,输出是6通道的,卷积核是(5 imes 5)的,所以理解为6个(3 imes 5 imes 5)的卷积核,所以不考虑bias的话,参数量是(3 imes 5 imes 5 imes 6=450),考虑bais的话,就每一个卷积核再增加一个偏置值。(这是一个一般人会忽略的知识点欸)

    下面用代码来验证:

    net = Net()
    net.initialize_weights()
    for m in net.modules():
        if isinstance(m,nn.Conv2d):
            print(m)
            print(m.weight.shape)
            print(m.bias.shape)
            break
    

    输出结果是:

    Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
    torch.Size([6, 3, 5, 5])
    torch.Size([6])
    

    都和预料中一样。

    人不可傲慢。
  • 相关阅读:
    tomcat部署web应用的4种方法以及部署多个应用
    LeetCode算法题目解答汇总(转自四火的唠叨)
    16、手把手教你Extjs5(十六)Grid金额字段单位MVVM方式的选择
    15、手把手教你Extjs5(十五)各种Grid列的自定义渲染
    14、手把手教你Extjs5(十四)模块字段和Grid列的定义[2]
    13、手把手教你Extjs5(十三)模块字段和Grid列的定义[1]
    12、手把手教你Extjs5(十二)执行菜单命令在tabPanel中显示模块
    11、手把手教你Extjs5(十一)模块界面的总体设计
    10、手把手教你Extjs5(十)自定义模块的设计
    9、手把手教你Extjs5(九)使用MVVM特性控制菜单样式
  • 原文地址:https://www.cnblogs.com/PythonLearner/p/13587092.html
Copyright © 2020-2023  润新知