《深度学习框架PyTorch入门与实践》学习笔记---第四章 torch.nn

# -------神经网络工具箱nn------------------------------------------
# torch.nn是专门为深度学习而设计的模块,torch.nn的核心数据结构是Module
# 既可以表示神经网络中的某个层(layer),也可以表示包含很多层的神经网络
# 实际使用中,最常用的做法是继承nn.Module,撰写自己的网络/层,
# 全连接层有名仿射层,

import torch as t
from torch import nn
import pylab


# 全连接层的实现
# PyTorch的nn.Linear()是用于设置网络中的全连接层的,
# 需要注意在二维图像处理的任务中,全连接层的输入与输出一般都设置为二维张量,
# 形状通常为[batch_size, size],不同于卷积层要求输入输出是四维张量。
class Linear(nn.Module):  # 继承nn.Module父类
    def __init__(self, in_features, out_features):  # 必须初始化构造函数
        super(Linear, self).__init__()  # 等价于nn.Module.__init__(self)
        self.w = nn.Parameter(t.randn(in_features, out_features))  # 自封装学习参数
        self.b = nn.Parameter(t.randn(out_features))

    def forward(self, x):
        x = x.mm(self.w)
        return x + self.b.expand_as(x)
        # 无需写后向传播过程,nn.Module会利用autograd自动实现反向传播


# in_features指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。
#   out_features指的是输出的二维张量的大小,
# 即输出的二维张量的形状为[batch_size,output_size],
# 当然,它也代表了该全连接层的神经元个数。
#   从输入输出的张量的shape角度来理解,
# 相当于一个输入为[batch_size, in_features]的张量
# 变换成了[batch_size, out_features]的输出张量。

layer = Linear(4, 3)  # in_features=4,out_features=3
input = t.randn(2, 4)  # (batch_size,in_feature)
output = layer(input)
print(output)

for name, parameter in layer.named_parameters():
    print(name, parameter)  # w and b


# 全连接层实现非常简单,但是需要注意以下几点:
# (1)自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,
# 即super(Linear,self).__init__()
# (2)在构造函数__init__中必须自己定义课学习的参数,并封装成Parameter

# ---------多层感知机---------------------------------------------------------
# 它由两个全连接层组成,采用sigmoid作为激活函数
class Perceptron(nn.Module):  # 继承nn.Module父类
    def __init__(self, in_features, hidden_features, out_features):
        nn.Module.__init__(self)  # 必须调用构造函数,初始化参数
        # 必须封装自定义参数
        self.layer1 = Linear(in_features, hidden_features)  # 此处的Linear是前面自定义的全连接层
        self.layer2 = Linear(hidden_features, out_features)

    def forward(self, x):
        x = self.layer1(x)
        x = t.sigmoid(x)
        return self.layer2(x)


perceptron = Perceptron(3, 4, 1)
for name, param in perceptron.named_parameters():
    print(name, param.size())
# layer1.w torch.Size([3, 4])
# layer1.b torch.Size([4])
# layer2.w torch.Size([4, 1])
# layer2.b torch.Size([1])

# 构造函数__init__中,可利用前面自定义的Linear层(module)
# 作为当前module对象的一个子module,它的可学习参数,也会成为当前module的可学习参数

# ---module中parameter的命名规范---------
# 输入输出的形状,如nn.linear的输入形状是(N,input_features),
# 输出为(N,output_features),N是batch_size
# 这些自定义Layer对输入形状都有形状,输入的不是单个数据,而是一个batch
# 输入只有一个数据,则必须调用tensor.unsqueeze(0)或tensor[None]将
# 将数据伪装成batch_size=1的batch

# ------------4.1 常用神经网络层---------------------------------------------------
# ----------4.1.1 图像相关层-------------------------------
# 图像相关层主要包括卷积层(Conv)\池化层(Pool),这些层在实际使用中可分为
# 一维(1D),二维(2D),三维(3D),池化方式又分为平均池化(AvgPool),
# 最大值池化(MaxPool),自适应池化(Adaptive AvgPool)等
# 而卷积层除了常用的前向卷积之外,还有逆卷积(TransposeConcv)
# 一般来说,一维卷积用于文本数据,二维卷积用于图像数据,
# 对宽度和高度都进行卷积,三维卷积用于视频及3D图像处理领域(检测动作及人物行为),
# 对立方体的三个面进行卷积 。二维卷积的用处范围最广,在计算机视觉中广泛应用。
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
import numpy as np

to_tensor = ToTensor()  # Img->tensor
to_pil = ToPILImage()  # tensor->Img
lena = Image.open('lena.png')
print(lena)
# lena.show()  # 显示图片
# 将PIL Image图片转换为numpy数组
im_arry = np.array(lena)
print(im_arry.size)  # 40000
# 也可以np.asarray(im)区别是np.array()是深拷贝,np.asarray()是浅拷贝

# 输入是一个batch,batch_size=1
# 大小为200*200
input = to_tensor(lena).unsqueeze(0)
# 锐化卷积核
kernel = t.ones(3, 3) / -9.
print('kernel:', kernel)
kernel[1][1] = 1
print('kernel:', kernel)
# 锐化卷积核
# kernel: tensor([[-0.1111, -0.1111, -0.1111],
#         [-0.1111,  1.0000, -0.1111],
#         [-0.1111, -0.1111, -0.1111]])
# 输入通道,输出通道,步长,卷积核大小,,是否添加偏置进行参数学习
conv = nn.Conv2d(1, 1, (3, 3), 1, bias=False)
conv.weight.data = kernel.view(1, 1, 3, 3)
# 这里卷积核w的参数个数是(3*3*3+1)*1,
# (输入通道3,卷积核大小3*3,一个偏置,输出通道1)
print(conv.weight.data)
out = conv(input)
# to_pil(out.data.squeeze(0)).show()

# 池化层可以看作一种特殊的卷积层,用来下采样,
# # 但池化层没有可学习参数,其weight是固定的
pool = nn.AvgPool2d(2, 2)  # 2维最大化操作
print(list(pool.parameters()))  # []没有参数
out = pool(input)
# to_pil(out.data.squeeze(0)).show()
# 转化为一维数组才可以,squeeze(0),维度为1的压缩

# 除了卷积层和池化层,深度学习还将常用到
# Linear:全连接层;
# BatchNorm:批规范化层,分为1D,2D,3D,
# 除了标准的BatchNorm之外,还有风格迁移中常用InstanceNorm层
# Dropout层,用来防止过拟合,同样分为1D,2D,3D
# 输入batch_size=2,维度3

# (1)Linear层
input = t.randn(2, 3)  # [-1,1]
linear = nn.Linear(3, 4)  # 输入,输出
h = linear(input)
print(h)
# tensor([[-0.2813, -1.1104, -0.6374,  1.0212],
#         [ 0.1217, -1.2646, -0.1236,  0.5959]], grad_fn=<AddmmBackward0>)

# (2)BatchNorm---批规范化层
# 4 channel,初始化标准差为4,均值为0
bn = nn.BatchNorm1d(4)
bn.weight.data = t.ones(4) * 4
bn.bias.data = t.zeros(4)
bn_out = bn(h)
print(bn_out)
# tensor([[ 3.9999, -4.0000, -3.9995,  3.9931],
#         [-3.9999,  4.0000,  3.9995, -3.9931]],
#        grad_fn=<NativeBatchNormBackward0>)
print(bn_out.mean(0), "\n", bn_out.var(0, unbiased=False))  # 0/1代表维度
# 输出均值为0,方差为16(有正负)
# 方差是标准差的平方,计算无偏分差分母会减一
# 使用unbiased=False,分母不减一

# (3)Dropout层
dropout = nn.Dropout(0.5)  # 每个元素以0.5的概率舍弃
out = dropout(bn_out)
print(bn_out)
print(out)  # 有一半左右的数变为0
# tensor([[-7.9998,  7.9998, -7.9998, -0.0000],
#         [ 0.0000, -0.0000,  7.9998,  0.0000]], grad_fn=<MulBackward0>)

# a=t.rand(2,4)*5
# print(a)
# tensor([[4.4786, 4.4752, 3.1746, 3.7649],
#         [2.7463, 0.6550, 4.0261, 1.9380]])
# dropout = nn.Dropout(0.5)
# out_a=dropout(a)
# print(out_a)
# tensor([[0.0000, 8.9505, 0.0000, 0.0000],
#         [5.4926, 0.0000, 8.0522, 0.0000]])

# ----------------------4.1.2 激活函数-------------------------------------------------
relu = nn.ReLU(inplace=True)
input = t.randn(2, 3)
print(input)
output = relu(input)  # max(0,x),小于0的都被截断为0
print(output)  # 等价于input.clamp(min=0)
# 在以上的例子中,将每一层的输出直接作为下一层的输入,这种网络称为前馈传播网络
# feedforward neural network
# 对于此类网络如果每次都写复杂的forward函数比较,简化方式为ModuleList和Sequential
# 其中Sequential是一种特殊的Module,包含几个子Module,前向传播时会将输入一层接一层的传递下去
# ModuleList一个特殊的module,可以包含几个子module,可以像用list一样使用它
# 但不能直接把输入传给ModuleList

# Sequential的三种写法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))  # 输入通道,输出通道,卷积核大小
net1.add_module('batchnorm', nn.BatchNorm2d(3))  # 均值0,标准差3
net1.add_module('activation_layer', nn.ReLU())  # 激活层

net2 = nn.Sequential(nn.Conv2d(3, 3, 3), nn.BatchNorm2d(3), nn.ReLU())

from collections import OrderedDict

net3 = nn.Sequential(OrderedDict([
    ('conv1', nn.Conv2d(3, 3, 3)),
    ('bn1', nn.BatchNorm2d(3)),
    ('relu1', nn.ReLU())
]))

print('net1:', net1)
print('net2:', net2)
print('net3:', net3)
# net1: Sequential(
#   (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#   (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
#   (activation_layer): ReLU()
# )
# net2: Sequential(
#   (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#   (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
#   (2): ReLU()
# )
# net3: Sequential(
#   (conv1): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#   (bn1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
#   (relu1): ReLU()
# )
# BatchNorm1d(num_features, eps=1e-05, momentum=0.1, affine=True)
# num_features=batch_size,
# eps:为保持数值稳定性(分母不能趋近或取0),给分母加上的数值,默认为1e-5
# momentum:动态均值和动态方差使用的动量,默认为0.1
# affine:一个布尔值,当设为true,给该层添加可学习的仿射变换参数

# 可根据名字或序号取出子module
print(net1.conv, "\n", net2[0], "\n", net3.conv1)
# Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#  Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#  Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))

input = t.rand(1, 3, 4, 4)
print(input)
print(net1(input))  # 4-3+1=2
# tensor([[[[0.9605, 0.9013],
#           [0.0000, 0.0000]],
#
#          [[0.0758, 1.5793],
#           [0.0000, 0.0000]],
#
#          [[0.0000, 1.2621],
#           [0.0000, 0.5575]]]], grad_fn=<ReluBackward0>)
output = net2(input)
output = net3(input)
output = net3.relu1(net1.batchnorm(net1.conv(input)))

# ModuleList的写法
modellist = nn.ModuleList([nn.Linear(3, 4), nn.ReLU(), nn.Linear(4, 2)])
input = t.randn(1, 3)
for model in modellist:
    input = model(input)


# 下面会报错,因为modelist没有实现forward方法
# output=modelist(input)

# 不使用Python自带的list,因为Modulelist是Module的子类,
# 当在Module中它时,就能自动识别为子module
# 举例说明
class MyModule(nn.Module):  # 继承父类Module
    def __init__(self):
        super(MyModule, self).__init__()  # 必须初始化构造函数
        self.list = [nn.Linear(3, 4), nn.ReLU()]
        self.module_list = nn.ModuleList([nn.Conv2d(3, 3, 3), nn.ReLU()])

    def forward(self):
        pass


model = MyModule()
print(model)
# MyModule(
#   (module_list): ModuleList(
#     (0): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
#     (1): ReLU()
#   )
# )
for name, param in model.named_parameters():
    print(name, param.size())
# module_list.0.weight torch.Size([3, 3, 3, 3])
# module_list.0.bias torch.Size([3])
# 可见,list的子Module不能被主module所识别,而Mdolelist中的子module能够被主module
# 所识别,这意味着如果用List保存子module,将无法调整参数,因为并未加入到主Module的参数中

# ------------------------4.1.3 循环神经网络(RNN)------------------------------------------------
# 说明:LSTM有7个参数,前三个是必须输入的参数
# (1)input_size:输入的特征维度
# (2)hidden_size:隐状态的特征维度
# (3)num_layer:层数(和时序展开要区分开)
# (4)bias:如果False,那么LSTM不会使用b,默认为True
# (5)batch_first:如果为True,输入输出形状为(batch,seq,feature)
# (6)dropout-如果为非零的话,将在RNN每层后加一个dropout,最后一层除外
# (7)bidirectional如果为True,将会变成双向RNN,默认为False

# 设置随机数种子
t.manual_seed(1000)
# 输入:batch_size=3,序列长度都为2,序列中每个元素占4维
input = t.randn(2, 3, 4)
# lstm输入层特征维度4维,隐藏层特征维度为3,共堆叠1层
lstm = nn.LSTM(4, 3, 1)
# lstm输入input,(h0,c0)
# h_0保存着batch中每个元素的初始化隐状态的tensor
# c_0保存着batch中每个元素的初始化细胞状态的tensor
# 初始状态:1层,batch_size=3,3个隐藏元
h0 = t.randn(1, 3, 3)
c0 = t.randn(1, 3, 3)
out, hn = lstm(input, (h0, c0))
print(out)
# tensor([[[-0.3610, -0.1643,  0.1631],
#          [-0.0613, -0.4937, -0.1642],
#          [ 0.5080, -0.4175,  0.2502]],
#
#         [[-0.0703, -0.0393, -0.0429],
#          [ 0.2085, -0.3005, -0.2686],
#          [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward0>)
t.manual_seed(1000)
input = t.randn(2, 3, 4)
# LSTMCell的输入参数
# input_size:输入的特征维度
# hidden_size:隐状态的维度
# bias:如果False,将不会使用bias,默认为True
# 一个LSTMCell对应的层数只能是一层
lstm = nn.LSTMCell(4, 3)
hx = t.randn(3, 3)
cx = t.randn(3, 3)
out = []
for i_ in input:
    hx, cx = lstm(i_, (hx, cx))
    out.append(hx)
print(t.stack(out))
# tensor([[[-0.3610, -0.1643,  0.1631],
#          [-0.0613, -0.4937, -0.1642],
#          [ 0.5080, -0.4175,  0.2502]],
#
#         [[-0.0703, -0.0393, -0.0429],
#          [ 0.2085, -0.3005, -0.2686],
#          [ 0.1482, -0.4728,  0.1425]]], grad_fn=<StackBackward0>)

# -----------------------------4.1.4 损失函数----------------------------------------------------------
# 深度学习中损失函数(loss function),也可以看做一种特殊的layer,
# PyTorch也将这些损失函数实现为nn.Module的子类,
# 然而实际使用中通常将这些loss function专门提取出来,与主模型相互独立

# batch_size=3,计算对应每个类别的分数(只有两个类别)
score = t.randn(3, 2)  # [正态分布]
print(score)
# 三个样本分别属于1,0,1类,label必须是LongTensor
label = t.Tensor([1, 0, 1]).long()
# loss与普通的layer无差异
criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数
loss = criterion(score, label)
print(loss)


# --------------------------4.2 优化器--------------------------------------------------------------------
# PyTorch将深度学习中常用的优化方法封装在torch.optim中
# 所有的优化方法都是继承基类optim.Optimizer,并实现了自己的优化步骤
# 最基本的优化方法,随机梯度下降法(SGD)
# 主要内容分别是:
# (1)优化方法的基本使用方法
# (2)对模型的不同部分设置不同的学习率
# (3)调整学习率
# 首先定义一个LetNet网络
class Net(nn.Module):  # 继承基类Module
    def __init__(self):  # 初始化构造函数
        super(Net, self).__init__()
        self.features = nn.Sequential(  # Sequential会自动将一层的输出到下一层的输入
            nn.Conv2d(3, 6, 5),  # 输入通道,输出通道,卷积核大小
            nn.ReLU(),
            nn.MaxPool2d(2, 2),  # 池化窗口大小
            nn.Conv2d(6, 16, 5),  # 输入通道,输出通道,卷积核大小
            nn.ReLU(),
            nn.MaxPool2d(2, 2)  # 池化窗口大小
        )
        self.classifier = nn.Sequential(
            nn.Linear(16 * 5 * 5, 120),  # 输入维度,输出维度
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, 10)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(-1, 16 * 5 * 5)
        x = self.classifier(x)
        return x


net = Net()
from torch import optim

optimizer = optim.SGD(params=net.parameters(), lr=1)
optimizer.zero_grad()  # 梯度清零,等价于net.zeros_grad()
input = t.randn(1, 3, 32, 32)
output = net(input)
output.backward(output)  # fake backward
optimizer.step()  # 执行优化
print("----------------------")
print(optimizer)
# SGD (
# Parameter Group 0
#     dampening: 0
#     lr: 1
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
# )
# ------------(1)为不同子网络设置不同的学习率,在finetune中经常用到---------------
# 如果对某个参数不指定学习率,就是用最外层的默认学习率
optimizer = optim.SGD([
    {'params': net.features.parameters()},
    {'params': net.classifier.parameters(), 'lr': 1e-2}
], lr=1e-5)
print("----------------------")
print(optimizer)
# SGD (
# Parameter Group 0
#     dampening: 0
#     lr: 1e-05
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
#
# Parameter Group 1
#     dampening: 0
#     lr: 0.01
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
# )
# --------------------(2)只为两个全连接层设置较大的学习率,其余层的学习率较小--------------
special_layers = nn.ModuleList([net.classifier[0], net.classifier[3]])
special_layers_params = list(map(id, special_layers.parameters()))
base_params = filter(lambda p: id(p) not in special_layers_params,
                     net.parameters())
optimizer = t.optim.SGD([
    {'params': base_params},
    {'params': special_layers.parameters(), 'lr': 0.01}
], lr=0.001)
print("------------------")
print(optimizer)
# SGD (
# Parameter Group 0
#     dampening: 0
#     lr: 0.001
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
#
# Parameter Group 1
#     dampening: 0
#     lr: 0.01
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
# )

# --------------调整学习率-----------------------
# 对于如何调整学习率,主要两种做法:
# (1)修改optimizer.params_groups中对应的学习率,
# (2)新建优化器--更简单,较为推荐
# 由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer
# 但是后者对于使用动量的优化器,会丢失动量等状态信息,
# 可能会造成损失函数的收敛出现震荡等情况
# --------------方法1:调整学习率,新建一个optimizer------------
old_lr = 0.01
optimizer1 = optim.SGD([
    {'params': net.features.parameters()},
    {'params': net.classifier.parameters(), 'lr': old_lr * 0.1}
], lr=1e-5)
print(optimizer1)
# SGD (
# Parameter Group 0
#     dampening: 0
#     lr: 1e-05
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
#
# Parameter Group 1
#     dampening: 0
#     lr: 0.001
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
# )
# ----------方法2:调整学习率,手动decay,保存动量-------------------------
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1  # 学习率为之前的0.1倍
print(optimizer)
# SGD (
# Parameter Group 0
#     dampening: 0
#     lr: 0.0001
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
#
# Parameter Group 1
#     dampening: 0
#     lr: 0.001
#     momentum: 0
#     nesterov: False
#     weight_decay: 0
# )

# -----------------------------4.3 nn.functional------------------------------------------------
# nn中很常用的模块,nn.functional,nn中多数layer在functional中都有一个与之相对应的函数
# nn.functional中的函数和nn.Module的主要区别在于,用nn.Module实现的layers是一个特殊的类
# 都是由class layer(nn.Module)定义,会自动提取可学习的参数,而nn.functional中的函数
# 更像一个纯函数,由def function(input)定义
input = t.randn(2, 3)
model = nn.Linear(3, 4)
output1 = model(input)
output2 = nn.functional.linear(input, model.weight, model.bias)
print(output1 == output2)
print(output1)
print(output2)  # 输出一模一样

b = nn.functional.relu(input)
b2 = nn.ReLU()(input)
print(b == b2)  # 一样
# 如果模型(卷积,全连接)有可学习的参数,最好用nn.Module
# 否则(激活函数,池化)都可以,二者在性能上没有太大差异
# 另外虽然dropout没有可学习参数,但建议nn.Dropout
# 因为dropout在训练和测试两个阶段的行为有所差别,
# 使用nn.Module对象能够通过model.eval操作加以区分
# 下列在模型中搭配使用nn.Module和nn.functional
from torch.nn import functional as F


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5),
        self.conv2 = nn.Conv2d(6, 16, 5)
        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 = F.pool(F.relu(self.conv1(x)), 2)
        x = F.pool(F.relu(self.conv2(x)), 2)
        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


# 对于不具备可学习参数的(激活、池化),将它们用函数代替,这样可以不用放置在
# 构造函数__init__中,对于由可学习参数的模块,也可以用funciotnal代替
# 但是较为繁琐,需要自定义参数parameter,如前面的实现的自定义全连接层
# 可以将weight和bias两个参数单独拿出来,在构造函数中初始化parameter
# class MyLinear(nn.Module):  # 继承基类Module
#    def __init__(self):  # 必须初始化构造函数
#        super(MyLinear, self).__init__()
#        self.weight = nn.Parameter(t.randn(3, 4))
#       self.bias = nn.Parameter(t.zeros(3))
#
#   def forward(self):  ##----------!!!------------
#       return F.linear(input, weight, bias)


# --------------------------4.4 初始化策略-----------------------------------------------
# 深度学习中参数的初始化,良好的初始化能让模型更快收敛,糟糕的初始化可能是的模型迅速瘫痪
# PyTorch中nn.Module的模块参数采取了较为合理的初始化策略
# PyTorch中nn.init模块就是专门为初始化而设计,
# 如果某种初始化策略nn.init不提供,用户也可以自己直接初始化
# (1)利用nn.init初始化-----------
from torch.nn import init

linear = nn.Linear(3, 4)
# 设置随机数种子
t.manual_seed(1)
# 等价于 linear.weight.data.normal_(0,std)
# 用一个均匀分布生成之,填充输入的张量或变量,结果张量中的值采样子U(-bound,bound)
init.xavier_normal_(linear.weight)
print(linear.weight)
# (2)直接初始化------------------
import math

t.manual_seed(1)
# xavier初始化的计算公式
std = math.sqrt(2) / math.sqrt(7.)
linear.weight.data.normal_(0, std)
print(linear.weight)

# (3)对模型所有参数进行初始化
for name, params in net.named_parameters():
    if name.find('linear') != -1:
        # 初始化Linear
        param[0]  # weight
        param[1]  # bias
    elif name.find('conv') != -1:
        pass
    elif name.find('norm') != -1:
        pass


# ---------------------------4.5 mm.Module深入分析-----------------------------------------------------------
# nn.Module基类的构造函数
# def __init__(self):
#    self._parameters=OrderedDict()
#    self._modules=OrderedDict()
#    self._buffers=OrderedDict()
#    self._backward_hooks=OrderedDict()
#    self._forward_hooks=OrderedDict()
#    self.training=True
# 对每个属性解释如下:
# (1)_parameters:字典,保存用户直接设置的parameters,
# self.param1=nn.Parameter(t.randn(3,3))会被检测到,在字典中
# 加入一个key为‘param',value对应parameter的item,
# 而self.submodule=nn.Linear(3,4)中的parameter则不会存于此
# (2)_modules:子modules:子module,
# 通过slef.submodel=nn.Linear(3,4)指定的子module会保存于此
# 下面举例说明
class Net(nn.Module):  # 继承基类(Module)
    def __init__(self):  # 初始化构造函数
        super(Net, self).__init__()
        # 等价与self.register_parameter('param1',nn.Parameter(t.randn(3,3)))
        self.param1 = nn.Parameter(t.rand(3, 3))
        self.submodule1 = nn.Linear(3, 4)

    def forward(self, input):
        x = self.param1.mm(input)
        x = self.submodule1(x)
        return x


net = Net()
print(net)
# Net(
#   (submodule1): Linear(in_features=3, out_features=4, bias=True)
# )
print(net._modules)
# OrderedDict([('submodule1', Linear(in_features=3, out_features=4, bias=True))])
print(net._parameters)
# tensor([[0.3398, 0.5239, 0.7981],
#         [0.7718, 0.0112, 0.8100],
#         [0.6397, 0.9743, 0.8300]], requires_grad=True))])
print(net.param1)  # 等价于net._patameters['param1']
# Parameter containing:
# tensor([[0.3398, 0.5239, 0.7981],
#         [0.7718, 0.0112, 0.8100],
#         [0.6397, 0.9743, 0.8300]], requires_grad=True)
for name, param in net.named_parameters():
    print(name, param.size())
# param1 torch.Size([3, 3])
# submodule1.weight torch.Size([4, 3])
# submodule1.bias torch.Size([4])
for name, submodel in net.named_modules():
    print(name, submodel)
# Net(
#   (submodule1): Linear(in_features=3, out_features=4, bias=True)
# )
# submodule1 Linear(in_features=3, out_features=4, bias=True)

# (3)_buffers:缓存,------------------
# 如batchnorm使用momentum机制,每次前向传播需用到上一次前向传播的结果
# batchNorm1d是对小批量进行批标准化操作,在每个小批量数据中
# 计算输入各个维度的均值和标准差
# --在训练时--,该层计算每次输入的均值和方差,并进行移动平均,移动平均默认的动量值为0.1
# --在验证时--,训练求得的均值/方差用于标准化验证数据
# num_features:来自期望输入的特征数
# eps,为保证数值稳定性(分母不能趋近或取0),给分母加上值,默认为1e-5
# momentum:动态均值和动态方差所使用的动量,默认为0.1
# affine,一个布尔值,当设为true,该给层添加可学习的仿射变换参数
bn = nn.BatchNorm1d(2)  # 批量规范化,2是输入的特征维度
input = t.rand(3, 2)
print(input)
# tensor([[0.4452, 0.0193],
#         [0.2616, 0.7713],
#         [0.3785, 0.9980]])
output = bn(input)
print(bn._buffers)  # 缓存作用
# OrderedDict([('running_mean', tensor([0.0362, 0.0596])),# 输入平均值
# ('running_var', tensor([0.9009, 0.9262])),#输入方差
# ('num_batches_tracked', tensor(1))])

# nn.Module在实际使用中可能层层嵌套,一个Module包含若干个子module,
# 每个子module又包含了更多的子module,
# 为方便用户访问各个子module,nn.Module实现了很多方法,如函数children可以查看直接子module
# 函数module可以查看所有的子module(包括当前module),
# 与之相对应的还有函数named_children和named_modules,
# 其能够在返回module列表的同时返回它们的名字

# ---(4)training:BatchNorm和Dropout层在训练阶段和测试阶段中采取的策略不同,-----------
# 通过判断training值来决定前向传播策略
input = t.arange(0, 12).view(3, 4)
print(input)
model = nn.Dropout()
# 在训练阶段,会有一半左右的数被随机置为0
# model(input)
model.training = False
# 在测试阶段,dropout什么都不做
model(input)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]])
# 对于batchnorm,dropout,instancenorm等在训练阶段和测试阶段行为差异巨大的层
# 如果测试中不将其training值设为True,可能会有很大影响。
print(net.training, net.submodule1.training)  # True True
net.eval()
print(net.training, net.submodule1.training)  # False False
print(list(net.named_modules()))
# [('', Net(
#   (submodule1): Linear(in_features=3, out_features=4, bias=True)
# )), ('submodule1', Linear(in_features=3, out_features=4, bias=True))]

# (4——----(5)backward_hooks与__forward_hooks:钩子技术,用来提取中间变量,类似于variable的hook
# register_forward_hook与register_backward_hook,这两个函数功能类似于variable函数
# 的register_hook,可在module前向传播或反向传播时注册钩子,每次前向传播执行结束后悔执行钩子函数(hook)
# 前向传播的钩子函数具有如下形式,hook(module,input,output)->None,
# 反向传播如下形式:hook(module,grad_input,grad_output)->Tensor or None
# 钩子函数不应该修改输入和输出,并且在使用后应及时删除,以避免每次都运行钩子增加运行负载
# 钩子函数主要用在获取某些中间结果的情景,如中间某一层的输出或某一层的梯度,
# 这些结果本应写在forward函数中,但如果forward函数中专门加上这些处理,可能会使处理逻辑比较复杂
# 假设,有一个预训练好的模型,需要提取模型的某一层(不是最后一层)的输出作为特征进行分类
# 但有不希望修改其原有的模型定义文件,这时就可以利用钩子函数,下面为实现的伪代码
# mode# l=VGG()
# feat# ures=t.Tensor()
# def # hook(module,input,output):
# """把这层的输出拷贝到features"""
#     features.copy_(output.data)
# handle=model.layer8.register+forward_hook(hook)
# _=model(input)
# 用完hook删除
# handle.remove()


# --------setatter和getattr函数------------------
# nn.Module实现了自定义的__setattr__函数,当执行module.name=value时,
# 会在__setattr__中判断value是否为Parameter或者nn.Module对象,
# 如果是则将这些对象加到_parameters和modules两个字典中,而如果是其他类型的对象
# 如variable\list\dict等,则调用默认的操作,将这个值保存在__dict__中
module = nn.Module()
module.param = nn.Parameter(t.ones(2, 2))
print(module._parameters)
# OrderedDict([('param', Parameter containing:
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True))])
submodule1 = nn.Linear(2, 2)
submodule2 = nn.Linear(2, 2)
module_list = [submodule1, submodule2]
# 对于List对象,调用building函数,保存在__dict__中
module.submodules = module_list
print('_modules:', module._modules)
print("__dict__['submodules']:", module.__dict__.get('submodules'))
# _modules: OrderedDict()
# __dict__['submodules']:
# [Linear(in_features=2, out_features=2, bias=True),
# Linear(in_features=2, out_features=2, bias=True)]

module_list = nn.ModuleList(module_list)  # 开始嵌套
module.submodules = module_list
# isinstance()函数,Python内置函数,用来判断一个函数是否为一个已知的类型
print('ModuleList is instance of nn.Module:', isinstance(module_list, nn.Module))
print('_modules:', module._modules)
print("__dict__['submodules']:", module.__dict__.get('submodules'))
# ModuleList is instance of nn.Module: True
# _modules: OrderedDict([('submodules', ModuleList(
#   (0): Linear(in_features=2, out_features=2, bias=True)
#   (1): Linear(in_features=2, out_features=2, bias=True)
# ))])
# __dict__['submodules']: None

# 因为_modules和_parameters中的item为保存在__dict__中,所以默认的getatter方法无法获取它,
# 因为nn.Module实现了自定义__getatter__方法,如果磨人的getatter无法处理
# 就调用自定义的__getatter__方法,尝试从_modules\_parameters\_buffers这三个字典中获取
print(getattr(module, 'training'))  # 等价于module.training
# True


# ---------------4.6 nn和auotgrad的关系----------
# nn.Module利用的autograd技术,其主要工作实现前向传播,
# 在forward函数中,nn.Module对输入的tensor进行的各种操作,本质是用autograd技术
# autograd.Function和nn.Module之间的区别:
# (1)autograd.Function利用了Tensor对autograd技术的拓展,为autogtad实现了新的运算op,
# 不仅要实现前向传播还要手动实现反向传播
# (2)nn.Module利用了autograd技术,对nn的功能进行拓展,实现了深度学习中的更多层,
# 只需实现前向传播功能,autograd会自动实现反向传播
# (3)nn.functional是一些autograd操作的集合,是经过封装的函数
# 作为两大类的选取,如果某一个操作在autograd尚未支持,那么只能实现Function接口对应的前向传播和反向传播
# 如果某些利用autograd接口比较复杂,可以利用Funtion将多个操作聚合,实现优化
# 如果只是想在深度学习中增加某一层,使用nn.Module进行封装则更为简单高效


# ---------------------------4.7 搭建ReSet-深度残差网络-------------------------------------------------------
# 考虑到Residual block和layer出现了多次,将其实现为一个子Module或函数,这里将Residual block
# 实现为一个子Module,而将Layer实现为一个函数,
# 跨层直连的shortcut
# ResNet中将一个跨层直连的单元称为Residual block
# 拥有多个Residual block单元的结构称之为layer,这里的layer是几个层的集合
# 下面为实现代码,规律总结如下:
# 1、对于模型中重复部分,实现为子Module或用函数生成相应的module make_layer
# 2、nn.Functianal和nn.Module结合使用
# 3、尽量使用nn.Sequrntial
from torch import nn
import torch as t
from torch.nn import functional as F


class ResidualBlock(nn.Module):  # 继承基类nn.Module
    """实现子module:Residual Block"""

    # 输入通道,输出通道,步长
    def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
        super(ResidualBlock, self).__init__()  # 构造函数初始化
        self.left = nn.Sequential(
            # 输入通道,输出通道,卷积核大小,步长,不学习参数偏差bias
            nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=None),
            nn.BatchNorm2d(outchannel),  # 批量规范化,计算平均值及标准差
            # 参数inplace=True,将会改变输入的数据,否则不会改变原输入,只会产生新的输出
            nn.ReLU(inplace=True),
            # 卷积层参数,padding图像四周填0的层数;dilation控制卷积核元素点之间的空间距离
            # groups分组卷积,默认输出输入的所有通道各为一组
            # 如果group=2,输入通道=32,输出通道=48,那么对应要将输入的32个通道分为2个16通道,
            # 将输出的通道分为2个24通道,对输出的2个24通道,第一个24通道与输入的第一个16通道进行全卷积
            # 第二个24通道与输入的第二个通道进行全卷积
            # 卷积核的深度=输入通道数
            # 卷积核的数量=输出通道数
            # 输出大小计算:输出大小=(输入大小-卷积核大小+2*填充值大小)/步长大小+1
            nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),
            nn.BatchNorm2d(outchannel)  # 批量规范化
        )
        self.right = shortcut

    def forward(self, x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)


class ResNet(nn.Module):  # 继承基类nn.Module
    """实现主Module:ResNet34
    ResNet34包含多个layer,每个layer又包含多个residual block
    用子moudel实现residual block,用_make_layer函数来实现layer(多个Residual block单元的结构)"""

    def __init__(self, num_classes=1000):
        super(ResNet, self).__init__()
        # 前几层图像转换
        self.pre = nn.Sequential(
            nn.Conv2d(3, 64, 7, 2, 3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),  # 替换输入值
            nn.MaxPool2d(3, 2, 1)  # 窗口大小(默认为窗口大小),移动步长,补充0的层数
        )
        # 重复的layer,分别有3,4,6,3个residual block
        self.layer1 = self._make_layer(64, 64, 3)
        self.layer2 = self._make_layer(64, 128, 4, stride=2)
        self.layer3 = self._make_layer(128, 256, 6, stride=2)
        self.layer4 = self._make_layer(256, 512, 3, stride=2)
        # 分类用的全连接
        self.fc = nn.Linear(512, num_classes)

    def _make_layer(self, inchannel, outchannel, block_num, stride=1):
        """构建layer,包含多个residual block"""
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),  # 卷积核大小为1,相当于没有
            nn.BatchNorm2d(outchannel)
        )
        layers = []  # append函数会在数组后加上相应的元素
        layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))  # 会修改原值
        for i in range(1, block_num):
            layers.append(ResidualBlock(outchannel, outchannel))
        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.pre(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = F.avg_pool2d(x, 7)
        x = x.view(x.size(0), -1)
        return self.fc(x)


model = ResNet()
input = t.randn(1, 3, 224, 224)
o = model(input)
print(o)

上一篇:IDEA中Project 和 Module的区别


下一篇:【Vue】Webpack的安装与使用