深度学习框架Pytorch入门与实践——读书笔记

2 快速入门

2.1 安装和配置

pip install torch
pip install torchvision

#IPython魔术命令
import torch as t
a=t.Tensor(3,4)
%timeit a.sum()  #检测某条语句的执行时间
%hist                  #查看输入历史

2.2.2 Autograd:自动微分

autograd.Variable是Autograd中的核心类。
Variable主要包含三个属性:

  • data:保存Variable所包含的Tensor
  • grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。
  • grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。

Autograd实现了反向传播功能,但是使用还略显复杂。torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可用来定义和运行神经网络。

import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
      def __init__(self):
         super(Net,self).__init__()
         self.conv1 = nn.Conv2d(1,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.max_pool2d(F.relu(self.conv1(x)),(2,2))
        x = F.max_pool2d(F.relu(self.conv2(x)),2)
        x = x.view(x.size()[0],-1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
net = Net()
print(net)

for name,parameters in net.named_parameters():
    print(name,':',parameters.size())

forward函数的输入和输出都Variable,只有Variable才具有自动求导功能,Tensor是没有的,需封装称Variable。
torch.nn只支持mini-batches,不支持一次只输入一个样本,即每次输入必为1个batch。如果只输入一个样本,则用input.unsqueeze(0)将batch_size设置为1.例如,nn.Conv2d输入必须为4维的,形如nSamplesnChannelsHeightWidth,可将nSample设置为1,即1nChannelsHeightWidth。

nn实现了神经网络中大多数的损失函数。例如nn.MESLoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵函数。

torchvision实现了常用的图像数据加载功能,例如Imagenet、CIFAR10、MNIST等,以及常用的数据转换操作。
CIFAR分类步骤:

  • 使用torchvision加载并预处理CIFAR-10数据集。
  • 定义网络。
  • 定义损失函数和优化器。
  • 训练网络并更新网络参数
  • 测试网络。
    CIFAR-10是一个常用的彩色图片数据集(http://www.cs.toronto.edu/~kriz/cifar.html)。
    Dataset对象是一个数据集,可以按下标访问,返回形如(data,label)的数据。
    DataLoader是一个可迭代的对象,它将dataset返回的每一条数据样本拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对dataset的所有数据遍历完一遍后,对DataLoader也完成了一次迭代。
    所有的网络训练流程都类似:
  • 输入数据
  • 前向传播+反向传播
  • 更新参数

3 Tensor和autograd

可以通过tensor.view方法来调整tensor的形状,但必须保证调整前后元素总数一致。view不会修改自身的数据,返回的新tensor与源tensor共享内存,会发生联动。在实际应用中如果修改维度,需要使用sequeeze和unsqueeze两个函数。
resize为调整size的另一种方法,此方法可以修改tensor的尺寸。如果修改后的尺寸超过了原尺寸,会自动分配新的内存空间。如果修改后的尺寸小于原尺寸,则之前的数据依旧会保存。

函数 功能
index_select(input, dim, index) 在指定维度dim上选取,例如选取某些行、某些列
masked_select(input, mask) 例如a[a>0],使用ByteTensor进行选取
non_zero(input) 非0元素的下标
gather(input, dim, index) 根据index,在dim维度上选取数据,输出的size与index一样

gather是较为复杂的操作,对于一个二维tensor,输出的每个元素如下:

out[i][j] = input[index[i][j]][j] #dim=0
out[i][j] = input[i][index[i][j]] #dim=1

与gather相对应的逆操作为scatter_,gather把数据从input中按index取出,而scatter_是把取出的数据再放回去。scatter_函数是inplace操作。

out = input .gather(dim,index)  #---->近似逆操作
out = Tensor()
out.scatter_(dim,index)

https://blog.csdn.net/akari0216/article/details/108573648
PyTorch 0.2的高级索引一般不和原始的Tensor共享内存。
Tensor有不同的数据类型,每种类型分别对应CPU和GPU版本(HalfTensor除外)。默认的tensor是FloatTensor,可通过t.set_deault_tensor_type修改默认tensor类型(如果默认类型为GPU tensor,则所有操作都将在GPU上进行)。Tensor的类型对分析内存占用很有帮助。例如,一个size为(1000,1000,1000)的Float-Tensor,它有100010001000=10^9个元素,每个元素占32bit/8=4Byte内存,所以共占4GB内存/显存。HalfTensor是专门为GPU版本设计的,同样的元素个数,显存占用只有FloatTensor的一半,可以极大地缓解GPU显存不足的问题,但由于HalfTensor表示的数值和精度有限,所以容易出现溢出的问题。
tensor数据类型

数据类型 CPU tensor GPU tensor
32bit 浮点 torch.FloatTensor torch.cuda.FloatTensor
64bit浮点 torch.DoubleTensor torch.cuda.DoubleTensor
16bit半精度浮点 N/A torch.cuda.HalfTensor
8bit无符号整形(0~255) torch.ByteTensor torch.cuda.ByteTensor
8bit有符号整形(-128~127) torch.CharTensor torch.cuda.CharTensor
16bit有符号整形 torch.ShortTensor torch.cuda.ShortTensor
32bit有符号整形 torch.IntTensor torch.cuda.IntTensor
64bit有符号整形 torch.LongTensor torch.cuda.LongTensor

各数据类型之间可以相互转换,type(new_type)是通用的做法,同时还有float、long、half等快捷方法。CPU tensor和GPU tensor之间的互相转换通过tensor.cuda和tensor.cpu的方法实现。
常见的逐元素操作

函数 功能
abs/sqrt/div/exp/fmod/log/pow 绝对值/平方根/除法/指数/求余/求幂
cos/sin/asin/atan2/cosh 三角函数
ceil/round/floor/trunc 上取整/四舍五入/下取整/只保留整数部分
clamp(input, min, max) 超过min和max部分截断
sigmod/tanh… 激活函数

归并操作

函数 功能
mean/sum/median/mode 均值/和/中位数/众数
norm/dist 范数/距离
std/var 标准差/方差
cumsum/cumprod 累加/累乘

以上函数大多含有参数,dim的关联可以按下记忆。
假设原形状为(m,n,k):

  • 指定dim=0,输出形状为(1,n,k)或(n,k)
  • 指定dim=1,输出形状为(m,1,k)或(m,k)
  • 指定dim=2,输出形状为(m,n,1)或(m,n)

常用的比较函数

函数 功能
gt/lt/ge/le/eq/ne 大于/小于/大于等于/小于等于/等于/不等
topk 最大的k个数
sort 排序
max/min 比较两个tensor的最大值和最小值

max函数举例:

  • t.max(tensor):返回tensor中最大的一个数
  • t.max(tensor, dim):指定维度上最大的数,返回tensor和下标
  • t.max(tensor1,tensor2):比较两个tensore相比较大的元素

PyTorch的线性函数主要封装了Blas和Lapack
常用的线性代数函数

函数 功能
trace 对角线元素之和(矩阵的迹)
diag 对角线的元素
triu/tril 矩阵的上三角/下三角,可指定偏移量
mm/bmm 矩阵乘法,batch的矩阵乘法
addmm/addbmm/addmv 矩阵运算
t 转置
dot/cross 内积/外积
inverse 求逆矩阵
svd 奇异值分解

矩阵的转置会导致存储空间不连续,需调用.contiguous方法转为连续。

广播法则在快速执行向量化的同时不会占用额外的内存/显存。Numpy的广播法则定义如下:

  • 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分通过在前面加1补齐。
  • 两个数组要么在某一个维度的长度一致,要么其中一个为1,否则不能计算。
  • 当输入数组的某个维度的长度为1时,计算时沿此维度复制扩充成一样的形状。
    PyTorch建议使用以下两个函数实现广播法则:
  • unsqueeze或者view:为数组某一维的形状补1,实现法则1.
  • expand或者expand_as,重复数组,实现法则3,该操作不会复制数组,不会增加额外空间。
  • repeat会把相同数据复制多份,会占用额外的空间。

tensor分为头信息区(Tensor)和存储区(Storage),信息区主要保存着tensor的形状size、步长stride、数据类型type等信息。
不同tensor的头信息一般不同,但却可能使用相同的storage。

计算图Computation Graph是现代深度学习框架的核心,为自动求导算法——反向传播Back Propogation提供了理论支持。

Variable的构造函数需要传入tensor,同时有两个可选参数:

  • requires_grad(bool):是否需要对该Variable进行求导
  • volatile(bool):意为“挥发”,设置为True,构建在该variable之上的图都不会求导,专为推理阶段设计。

Variable支持大部分tensor支持的函数,但不支持inplace函数,因为此函数会修改tensor自身,在反向传播过程中,variable需要缓存原来的tensor来计算梯度。
variable.backward(grad_variables=None,retain_graph=None,create_graph=None)其三个参数:

  • grad_variables:$$形状与variable一致,对于y.backward(), grad_variables相当于脸是法则。grad_v ariables也可以是tensor或序列。
  • retain_graph:反向传播需要缓存一些中间结果,反向传播之后,这些缓存就被清空,可通过指定这个参数不清空缓存,用来多次反向传播。
  • create_graph:对反向传播过程中再次构建计算图,可通过backward of backward实现求高阶导数。

计算图是一种特殊的有向无环图(DAG,用于记录算子与变量之间的关系。

返回错误:

AttributeError: ‘MulBackward0’ object has no attribute ‘saved_variables’
原因确实是版本问题,PyTorch0.3 中把许多python的操作转移到了C++中,saved_variables 现在是一个c++的对象,无法通过python访问。(https://github.com/chenyuntc/pytorch-book/issues/7)

可以查看这里进行学习https://github.com/chenyuntc/pytorch-book/blob/0.3/chapter3-Tensor和autograd/Autograd.ipynb,省掉上面的操作:

变量的requires_grad属性默认为False,如果某一个节点requires_grad被设置成True,那么所有依赖它的节点requires_grad都是True(根据链式法则)。
volatile=True是很重要的标示,其优先级高于requires_grad。volatile=True的节点不会求导,对于不需要反向传播的情景(如in-ference,推理测试时),该参数可实现一定程度的速度提升,并节省约一半显存,因为其不需要分配空间保存梯度。
在反向传播过程中非叶子节点的导数计算完后即被清空。有两种方式可以查看计算出的变量梯度。

  • 使用autograd.grad函数
  • 使用hook
    推荐使用hook,但是在实际使用中应尽量避免修改grad的值。

利用装饰器@once_differentiable,能够在backward函数中自动将输入的variable提取成tensor,把计算结果的tensor自动封装成variable。

import torch as t
from torch.autograd import Variable as V
%matplotlib inline
from matplotlib import pyplot as plt
from IPython import display
t.manual_seed(1000)
def get_fake_data(batch_size = 8):
    x = t.rand(batch_size,1)*20
    y = x*2 + (1+t.randn(batch_size,1))*3
    return x,y
x,y = get_fake_data()
plt.scatter(x.squeeze().numpy(),y.squeeze().numpy())

w = V(t.rand(1,1),requires_grad = True)
b = V(t.zeros(1,1),requires_grad = True)

lr = 0.001
for ii in range(8000):
    x,y = get_fake_data()
    x,y = V(x),V(y)
    
    y_pred = x.mm(w) + b .expand_as(y)
    loss = 0.5 * (y_pred - y ) **2
    loss = loss.sum()
    
    loss.backward()
    
    w.data.sub_(lr * w.grad.data)
    b.data.sub_(lr * b.grad.data)
    
    w.grad.data.zero_()
    b.grad.data.zero_()
    
    if ii % 1000 == 0:
        display.clear_output(wait=True)
        x = t.range(0,19).view(-1,1)
        y = x.mm(w.data) + b.data.expand_as(x)
        plt.plot(x.numpy(),y.numpy())
        
        x2,y2 = get_fake_data(batch_size=20)
        plt.scatter(x2.numpy(),y2.numpy())
        
        plt.xlim(0,20)
        plt.ylim(0,41)
        plt.show()
        plt.pause(0.5)
print(w.data.squeeze()[0],b.data.squeeze()[0])

4 神经网络工具箱nn

torch.nn专门为深度学习设计的模块。torch.nn的核心数据结构是Module,是抽象概念,既可以表示神经网络中的某个层layer,也可以表示一个包含很多层的神经网络。
全联接层,又名仿射层。

import torch as t
from torch import nn
from torch.autograd import Variable as V
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)
 
layer = Linear(4,3)
input = V(t.rand(2,4))
output = layer(input)
output

for name, parameter in layer.named_parameters():
    print(name,parameter)

全联接层的实现需注意以下几点:

  • 自定义层Linear必须继承nn.Module,并且在其构造函数中需调用nn.Module的构造函数,即
super(Linear,self).__init__()或nn.Module.__init__(self)
  • 在构造函数__init__中必须自己定义可学习的参数,并封装成Parameter。Parameter是一种特殊的Variable,但其默认需要求导(requires_grad=True)。
  • forwad函数实现前向传播过程,其输入可以是一个或多个variable,对x的操作也必须是variable支持的操作。
  • 无需写反向传播函数,因其前向传播都是对variable进行操作,nn.Module能够利用autograd自动实现反向传播。
  • 可将layer等价于函数。
  • Module中的可学习参数可以通过named_parameters()或parameters()返回迭代器。

多层感知机由两个全联接层组成,采用sigmoid函数作为激活函数。

class Perceptron(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        nn.Module.__init__(self)
        self.layer1 = Linear(in_features,hidden_features)
        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())

图像相关层主要包括卷积层-Conv、池化层-Pool,池化方式分为平均池化-AvgPool、最大值池化-MaxPool、自适应池化-AdaptiveAvgPool等。卷积层出了前向卷积,还有逆卷积-TransposeConv。

from PIL import Image
from torchvision.transforms import ToTensor,ToPILImage
to_tensor = ToTensor()
to_pil = ToPILImage()
lena = Image.open('lena.png')
lena = lena.convert("L")   
lena.save("lena2.png")
lena

input = to_tensor(lena).unsqueeze(0)

kernel = t.ones(3,3)/-9.
kernel[1][1] = 1
conv = nn.Conv2d(1,1,(3,3),1,bias = False)
conv.weight.data = kernel.view(1,1,3,3)

out = conv(V(input))
to_pil(out.data.squeeze(0))

图像的卷积操作还有各种变体,可以参照卷积
池化层可以看作是一种特殊的卷积层,用来下采样。但是池化层没有可学习参数,其weight是固定的。

pool = nn.AvgPool2d(2,2)
list(pool.parameters())

out = pool(V(input))
to_pil(out.data.squeeze(0))

出了卷积层和池化层,还有以下层:

  • Linear:全连接层
  • BatchNorm:批规范化层,分为1D、2D、3D。除了标准的BatchNorm外,还有在风格迁移中常用的InstanceNorm层。
  • Dropout:用来防止过拟合,同样分为1D、2D、3D

PyTorch实现了常见的激活函数,官方文档接口信息。

relu = nn.ReLU(inplace=True)
input = V(t.randn(2,3))
print(input)
output=relu(input)
print(output)

ReLU有inplace函数,如果设置成True,会把输出直接覆盖到输入,以此来节省内存/显存。覆盖的原因为在计算ReLU的反向传播,只需根据输出就能算出反向传播的梯度。只有少数的autograd操作支持inplace操作(如variable.sigmoid()),除非明确知道自己在做什么,否则一般不要使用inplace操作。
每一层都作为下一层的输入,称为前馈传播网络-Feedforward Neural Network。可以使用ModuleList和Sequential进行简化。

  • ModuleList是一个特殊的Module,可以包含几个子module,可以像使用list使用,但不能直接把输入传给ModuleList。
  • Sequential是特殊的Module,包含几个子module,前向传播会将一层接一层传递下去。
net1 = nn.Sequential()
net1.add_module('conv',nn.Conv2d(3,3,3))
net1.add_module('batchnorm',nn.BatchNorm2d(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.conv,net2[0],net3.conv1

input = V(t.rand(1,3,4,4))
output = net1(input)
output = net2(input)
output = net3(input)
output = net3.relu1(net1.batchnorm(net1.conv(input)))

modellist = nn.ModuleList([nn.Linear(3,4),nn.ReLU(),nn.Linear(4,2)])
input = V(t.rand(1,3))
for model in modellist:
    input = model(input)

Q:为什么不直接使用Python中自带的list,而使用ModuleList。
因为ModuleList是Module的子类,Module使用它时,能自动识别为子module,list中的子module并不能被主module识别。如果用list保存子module,则无法调整其参数,因其未能加入主module的参数中。
ParameterList同理。
循环神经网络-RNN,colah文章入门。
PyTorch实现了最常用的三种:RNN-vanilla RNN、LSTM和GRU。此外还有对应三种RNNCell。
RNN和RNNCell层的区别在与前者能够处理整个序列,而后者一次只处理序列中一个时间点的数据,前者更壮更完备更易于使用,后者更具灵活性。RNN层可以通过组合调用RNNCell来实现。

t.manual_seed(1000)
input = V(t.randn(2,3,4))
lstm = nn.LSTM(4,3,1)
h0 = V(t.randn(1,3,3))
c0 = V(t.randn(1,3,3))
out, hn = lstm(input,(h0,c0))
out

t.manual_seed(1000)
input = V(t.randn(2,3,4))
lstm = nn.LSTMCell(4,3)
hx = V(t.randn(3,3))
cx = V(t.randn(3,3))
out = []
for i_ in input:
    hx,cx=lstm(i_,(hx,cx))
    out.append(hx)
t.stack(out)

为处理词向量,PyTorch提供了Embedding层。

embedding = nn.Embedding(4,5)
embedding.weight.data = t.range(0,19).view(4,5)

input = V(t.arange(3,0,-1)).long()
output = embedding(input)
output

loss函数官方文档。

score = V(t.rand(3,2))
label = V(t.Tensor([1,0,1])).long()

criterion = nn.CrossEntropyLoss()
loss = criterion(score,label)
loss

所有的优化方法都是继承基类optim.Optimizer,并实现了优化步骤。以随机梯度下降法SGD举例。

from torch import optim
optimizer = optim.SGD(params = net.parameters(),lr=1)
optimizer.zero_grad() #梯度清零,等价于net.zero_grad()

input = V(t.randn(1,3,32,32))
output = net(input)
output.backward(output)

optimizer.step()

optimizer = optim.SGD([
    {'params':net.features.parameters()},
    {'params':net.classifier.parameters(),'lr':1e-2}
],lr=1e-5)

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)

调整学习率主要有两种方法:1⃣️修改optimizer.param_groups中对应的学习率;2⃣️新建优化器(更简单也更推荐),由于optimizer十分轻量级,构建开销很小,故此方法可行。但新建优化器会重新初始化动量等状态信息,对于使用动量的优化器来说(如带momentum的sgd),可能会造成损失函数在收敛过程中震荡。

#调整学习率,新建一个optimizer
old_lr = 0.1
optimizer = optim.SGD([
    {'params':net.features.parameters()},
    {'params':net.classifier.parameters(),'lr':old_lr*0.1}
],lr=1e-5)

nn.functional和nn.Module的主要区别在于,用nn.Module实现的layers是一个特殊的类,都是由class Layer(nn.Module)定义,会自动提取可学习的参数;而nn.functional中的函数更像是纯函数,由def function(input)定义。

input = V(t.randn(2,3))
model = nn.Linear(3,4)
output1 = model(input)
output2 = nn.functional.linear(input,model.weight,model.bias)
output1 == output2

b = nn.functional.relu(input)
b2 = nn.ReLU()(input)
b == b2

如果模型有可学习的参数,最好用nn.Module,否则两者都可使用。由于激活函数(ReLU、sigmoid、tanh)、池化(MaxPool)等层没有可学习参数,可是使用对应的functional函数代替,而卷积、全连接等具有可学习参数的网络,建议使用nn.Module。
虽dropout没有可学习参数,但还是建议使用nn.Module和nn.functional.dropout,因为dropout在训练和测试两个阶段的行为有所差别,使用nn.Module对象能够通过model.eval操作加以区分。

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

有可学习参数的模块,可以用functional代替,但是需要手动定义参数parameter,如之前实现自定义的全连接层,就可将weight和bias两个参数单独拿出来,在构造函数中初始化为parameter。

class MyLinear(nn.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)

良好的初始化能让模型更快收敛。PyTorch中的nn.init模块专门为初始化设计,实现了常用的初始化策略。如果某种初始化策略不提供,可以自行设计并直接初始化。

#使用nn.init初始化
from torch.nn import init
linear = nn.Linear(3,4)
t.manual_seed(1)

init.xavier_normal(linear.weight)

#直接初始化
import math
t.manual_seed(1)
std = math.sqrt(2)/math.sqrt(7.)
linear.weight.data.normal_(0,std)

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

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

属性解释如下:

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.params1 = nn.Parameter(t.rand(3,3))
        self.submodel1 = nn.Linear(3,4)
    def forward(self,input):
        x = self.param1(input)
        x = self.submodel1(x)
        return x 
net = Net()
net

net._modules

net._parameters

net.params1

for name,param in net.named_parameters():
    print(name,param.size())
    
for name,submodel in net.named_modules():
    print(name,submodel)

bn = nn.BatchNorm1d(2)
input = V(t.rand(3,2),requires_grad=True)
output = bn(input)
bn._buffers

Kaiming He的深度残差网络(ResNet)实现:

from torch import nn
import torch as t
from torch.nn import functional as F
class ResidualBlock(nn.Module):
    def __init__(self,inchannel, outchannel, stride =1, shortcut = None):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,3, stride, 1, bias = False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            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):
    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))
        self.layer1 = self._make_layer(64,128,3)
        self.layer2 = self._make_layer(128,256,4,stride=2)
        self.layer3 = self._make_layer(256,512,6,stride=2)
        self.layer4 = self._make_layer(512,512,3,stride=2)
        
        self.fc = nn.Linear(512,num_classes)
        
    def _make_layer(self,inchannel,outchannel,block_num,stride=1):
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel,outchannel,1,stride,bias=False),
            nn.BatchNorm2d(outchannel))
        layers = []
        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.autograd.Variable(t.randn(1,3,224,224))
o = model(input)

#对比
from torchvision import models
model = models.resnet34()

5 PyTorch中常用的工具

数据加载
数据集被抽象为Dataset类,实现自定义的数据集需要继承Dataset,并实现两个Python魔法方法。

__getitem__ #返回一条数据或一个样本。obj[index]等价于obj.__getitem__(index)
__len__     #返回样本的数量。len(obj)等价于obj.__len__()

Dogs vs Cats是一个二分类问题,加载数据,根据前缀名判断是猫还是狗。

import torch as t
from torch.utils import data
import os
from PIL import Image
import numpy as np

class DogCat(data.Dataset):
    def __init__(self,root):
        imgs = os.listdir(root)
        self.imgs = [os.path.join(root,img) for img in imgs]
    def __getitem__(self,index):
        img_path = self.imgs[index]
        label = 1 if 'dog' in img_path.split('/')[-1] else 0
        pil_img = Image.open(img_path)
        array = np.asarray(pil_img)
        data = t.from_numpy(array)
        return data, label
    def __len__(self):
        return len(self.imgs)

dataset = DogCat('./dogcat/')  #存放图片的路径
img,label = dataset[0]
for img,label in dataset:
    print(img.size(),img.float().mean(),label)

返回的数据有两个问题:

  • 返回样本的形状不一,图片大小不一,对于batch训练来说,很难进行批量学习
  • 返回样本的数值较大,未归一化至[-1,1]

PyTorch提供了torchvision,torchvision是一个视觉工具包,其中transforms模块提供了对PIL Image对象和Tensor对象的常用操作。

  • Resize:调整图片大小
  • CenterCrop、RandCrop、RandomSizedCrop:裁减图片
  • Pad:填充
  • ToTensor:将PIL Image对象转换成Tensor,会自动将[0,255]归一化至[0,1]

对Tensor的常见操作:

  • Normalize:标准化,即减均值,除以标准差
  • ToPILImage:将Tensor转成PIL Image对象。
import os
from PIL import Image
import numpy as np
from torchvision import transforms as T

transform = T.Compose([
    T.Resize(224),
    T.CenterCrop(224),
    T.ToTensor(),
    T.Normalize(mean=[.5,.5,.5],std=[.5,.5,.5])
])

class DogCat(data.Dataset):
    def __init__(self,root,transforms = None):
        imgs = os.listdir(root)
        self.imgs = [os.path.join(root,img) for img in imgs]
        self.transforms = transforms
    def __getitem__(self,index):
        img_path = self.imgs[index]
        label = 1 if 'dog' in img_path.split('/')[-1] else 0
        data = Image.open(img_path)
        if self.transforms:
            data = self.transforms(data)
        return data, label
    def __len__(self):
        return len(self.imgs)

dataset = DogCat('/Users/Nicole/python/dogcat/',transforms = transform)
img,label = dataset[0]
for img,label in dataset:
    print(img.size(),label)

transforms还可以通过Lambda封装成自定的转换策略。eg,对PIL Image进行随机旋转,则可写成trans=T.Lamdba(lambda img: img.rotate)random()*360))

(P139-P146待补充)
torchvision主要包含以下三部分:

  • models:提供深度学习中各种经典网络的网络结构及预训练好的模型,包括AlexNet、VGG、ResNet系列、Inception系列等。
  • datasets:提供常用的数据集加载、设计上都是继承torch.utils.data.Dataset,主要包括MNIST、CIFAR10/100、ImageNet、COCO等
  • transforms:提供常用的数据预处理操作,主要包括对Tensor和PIL Image对象的操作。
from torchvision import models
from torch import nn
resnet34 = models.resnet34(pretrained=True,num_classes=1000)
resnet34.fc = nn.Linear(512,10)

from torchvision import datasets
dataset = datasets.MNIST('data/',download=True,train=False,transform = transform)

转换分两步:1⃣️构建转换操作,例如transf = transform.Normalize(mean = x,std = y);2⃣️执行转换操作,例如output = transf(input)。还可将多个处理操作用Compose拼接起来。

from torchvision import transforms
to_pil = transforms.ToPILImage()
to_pil(t.randn(3,64,64))

torchvision 提供了两个函数,make_grid将多张图片拼接在一个网格中;save_img,能将Tensor保存为图片

from torch.utils.data import DataLoader
dataloader = DataLoader(dataset,shuffle = True,batch_size = 16)
from torchvision.utils import make_grid,save_image
dataiter = iter(dataloader)
img=make_grid(next(dataiter)[0],4)
to_img(img)

#上述代码还有问题,没办法转成3通道

save_image(img,'a.png')
Image.open('a.png')

可视化工具:
TensorBoard和visdom
TensorBoard

#安装TensorBoard
#第一步:pip install TensorFlow
#第二步:pip install tensorboard_logger
#启动Tensorboard
tensorboard --logdir= --port=

from tensorboard_logger import Logger
logger = Logger(logdir = 'experimient_cnn',flush_secs = 2)
for ii in range(100):
    logger.log_value('loss',10-ii**0.5, step = ii)
    logger.log_value('accuracy',ii**0.5/10)

#运行完上述代码后,打开http://localhost:6008,如果端口有冲突,可以修改。

6 Pytorch实战指南

深度学习研究,程序一般都实现以下几个功能:
· 模型定义
· 数据加载和处理
· 训练模型(Train&Validate)
· 训练过程的可视化
· 测试(Test/Inference)
程序最好满足以下要求:模型具有高度可配置性,便于修改参数、修改模型和反复试验;代码具有良好的组织架构;代码应具有良好的说明。

  • 模型定义
  • 数据加载
  • 训练和测试
checkpoints/
data/
	__init__.py
	dataset.py
	get_data.sh
models/
	__init_.py
	AlexNet.py
	BasicModule.py
	ResNet34.py
utils/
	__init__.py
	visualize.py
config.py
main.py
requirements.txt
README.md

7 AI插画师:生成对抗网络

你可能感兴趣的