【Pytorch学习-2】pytorch入门

本笔记实现了入门部分的全部代码,一定是跑的通的!!!
(使用Jupyter notebook)
认真排版好的笔记链接:
pytorch入门笔记

以下是笔记的内容,推荐使用链接观看:
2 Pytorch快速入门
2.1 入门第一步
2.2.1 Tensor
Tensor是Pytorch中的数据结构,可以认为是高维数组,Tensor和numpy中的ndarrays相似,但是Tensor可以使用GPU加速!
Tensor的基本用法:
from future import print_function
import torch as t
“”"------------------------------------------------------------------"""
x = t.Tensor(5, 3) # 构建5 * 3 矩阵, 只是分配了空间,未初始化
print(x)
“”"------------------------------------------------------------------"""
x = t.rand(5, 3) # 使用[0, 1]均匀分布随机初始化二维数组
print(x)
“”"------------------------------------------------------------------"""
print(x.size()) # 查看x的形状
print(x.size()[0], x.size(1)) # 查看行列个数的两种等价写法
“”"------------------------------------------------------------------"""
y = t.rand(5, 3)
print(x + y) # 加法的第一种写法
t.add(x, y) # 加法的第二种写法

加法的第三种写法:指定加法结果的输出目标为result

result = t.Tensor(5,3) # 预先分配空间
t.add(x, y, out=result) # 输入到result
result
“”"------------------------------------------------------------------"""
print(“最初的y:”)
print(y)
print(“第一种加法,y的结果:”)
y.add(x) # 普通加法,不改变y的内容
print(y)
print(“第二种加法,y的结果:”)

函数名后面带下划线_的函数会修改Tensor本身

y.add_(x) # inplace加法, y变了
print(y)
“”"------------------------------------------------------------------"""

Tensor的选取操作和numpy类似

x[:,1]
“”"------------------------------------------------------------------"""
a = t.ones(5) # 新建一个全是1的tensor
b = a.numpy() # Tensor --> numpy
“”"------------------------------------------------------------------"""
import numpy as np
a = np.ones(5)
b = t.from_numpy(a) # Numpy -> Tensor
print(a)
print(b)
“”"------------------------------------------------------------------"""
b.add_(1)

以_结尾的函数会修改自身

print(a)
print(b) # Tensor 与 Numpy共享内存
“”"------------------------------------------------------------------"""

在不支持cuda的机器下,下一步不会运行

if t.cuda.is_available():
x = x.cuda()
y = y.cuda()
print(x+y)
2.2.2 Autograd:自动微分
深度学习的本质是通过反向传播求导,Pytorch的Autograd模块就是实现这个功能。在Tensor上的所有操作,Autograd都能提供自动微分,避免手动计算倒数的复杂过程。
autograd.Variable是Autograd的核心类,它简单封装了Tensor,并支持几乎所有Tensor的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度。Variable的数据结构如下所示:

Variable主要包含三个属性:
data:保存Variable所包含的Tensor。
grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样。
grad_fn:指向一个Function对象,这个Function用来计算反向传播计算输入的梯度。
from torch.autograd import Variable

使用Tensor创建一个Variable

为tensor设置 requires_grad 标识,代表着需要求导数

pytorch 会自动调用autograd 记录操作

x = Variable(t.ones(2,2),requires_grad =True)

上一步等价于

x = t.ones(2,2)

x.requires_grad = True

print(x)
“”"------------------------------------------------------------------"""
y = x.sum()
print(y)
print(y.grad_fn)
print(y.backward)

y = x.sum() = (x[0][0] + x[0][1] + x[1][0] + x[1][1])

每个值的梯度都为1

print(x.grad)
“”"------------------------------------------------------------------"""

grad在反向传播中是累加的,说明每次运行反向传播,梯度都会累加之前的梯度

所以反向传播之前需要把梯度清零

y.backward()
x.grad
y.backward()
x.grad

以下划线结束的函数是inplace操作

x.grad.data.zero_()
y.backward()
x.grad
“”"------------------------------------------------------------------"""
2.2.3 神经网络
Autograd实现了反向传播功能,但是直接用来写深度学习的代码在很多情况下还是稍显复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可用来定义和运行神经网络。nn.Module是nn中最重要的类,可以把它看做是一个网络的封装,包括网络各层定义以及forward方法,调用forward(input)方法,可返回前向传播的结果。我们以最早的卷积神经网络LeNet为例,来看看如何用nn.Module实现。LeNet的网络结构如下所示:

这是一个基础的前向传播(feed-forward)网络:接收输入,经过层层传递运算,得到输出。
定义网络
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数init中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放。
import torch as t
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
def init(self):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
# 下式等价于nn.Module.init(self)
super(Net, self).init()
# 卷积层‘1’表示输入图片为单通道,‘6’表示输出的通道数目
# ‘5’表示卷积核为 5 * 5
# 卷积层
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 仿射层/全连接层 y = Wx + b
self.fc1 = nn.Linear(1655, 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)
    # reshape, '-1' 表示自适应
    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)
只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。在forward函数中可使用任何Variable支持的函数,还可以使用if、for循环、print、log等Python语法,写法和标准的Python写法一致。
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
params = list(net.parameters())
print(len(params))
#输出含有的参数个数
print(params) # 每个参数具体包含数值
“”"------------------------------------------------------------------"""

输出各层的名称和torch.size大小

for name,parameters in net.named_parameters():
print(name, “:”,parameters.size())
forward函数的输入与输出都是Variable,只有Variable才具有自动求导功能,Tensor是没有的,所以在输入时,要把Tensor转换成Variable
input = Variable(t.randn(1, 1, 32, 32))
out = net(input)
out.size()
“”"------------------------------------------------------------------"""
net.zero_grad() #所有的参数清零
out.backward(t.ones(1, 10)) #反向传播
需要注意的是,torch.nn只支持mini-batch,不支持一次只输入一个样本,即一次必须是一个batch。如果只想输入一个样本,则用input.unsqueeze(0)将batch_size设为1。例如,nn.Conv2d输入必须是4维的,形如nSamples * nChannels * Height * Width。可将nSamples设为1,即1 * nChannels * Height * Width。
损失函数
nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
output = net(input)
target = t.arange(0.0, 10)
print(target)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
如果对loss进行反向传播溯源(使用grad_fn属性),可以看到它的计算图如下:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
# 运行.bakcward ,观察调用之前和之后的grad
net.zero_grad() # 把net中的所有可学习参数的梯度清零
print(“反向传播之前的conv1.grad.bias的梯度”)
print(net.conv1.bias.grad)
loss.backward()
print(“反向传播之后的conv1.bias的梯度”)
print(net.conv1.bias.grad)
优化器
在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。例如,随机梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
手动实验如下:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate) # inplace 减法
torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,更便于使用,因此通常并不需要手动写上述代码。
import torch.optim as optim

新建一个优化器,指定要调整的参数与学习率

optimizer = optim.SGD(net.parameters(), lr = 0.01)

在训练过程中

先梯度清零(net.zero_grad()效果一样)

optimizer.zero_grad()

计算损失

output = net(input)
loss = criterion(output, target)

反向传播

loss.backward()
# 更新参数
optimizer.step()
print(loss)
数据加载和预处理
在深度学习中数据加载与预处理是非常复杂繁琐的,但PyTorch提供了一些可简答简化和加快数据处理流程的工具。同时,对于常用的数据集,PyTorch也提供了封装好的接口供用户快速调用,这些数据集主要保存在torchvision中。
torchvision实现了常用的图像数据加载功能,例如ImageNet、CIFAR10、MNIST等,以及常用的数据转换操作,这极大地方便了数据加载。
2.2.4 小试一牛刀:CIFAR-10分类
下面我们来尝试实现对CIFAR10数据集的分类,步骤如下:
(1)使用torchvision加载并预处理CIFAR10数据集。
(2)定义网络。
(3)定义损失函数和优化器。
(4)训练网络并更新网络参数。
(5)测试网络。

(1)CIFAR-10数据集加载及预处理
CIFAR-10是一个常用的彩色图片数据集,它有10个类别:airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck。每张图片都是3 * 32 * 32,也即3通道彩色图片,分辨率为32 * 32。提前下载数据集放到指定目录下,如E:/data/,在加载器中root参数指向该目录,程序检测到该文件已存在就直接解压
import torch as t
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
import torchvision as tv
import torchvision.transforms as transforms
from torchvision.transforms import ToPILImage
show = ToPILImage() # 可以把tensor转换成Image, 方便可视化

第一次运行程序torchvision会自动下载CIFAR-10数据集

需要一定的时间(100M左右)

如果已经下载了CIFAR-10,可以通过root参数指定

定义对数据的预处理

transform = transforms.Compose(
[
transforms.ToTensor(), # 转换为Tensor
transforms.Normalize((0.5, 0.5,0.5), (0.5, 0.5, 0.5)), # 归一化
])

训练集

trainset = tv.datasets.CIFAR10(
root = ‘/home/cy/data’,
train = True,
download = True,
transform = transform)
trainloader = t.utils.data.DataLoader(
trainset,
batch_size = 4,
shuffle = True,
num_workers = 2)

测试集

testset = tv.datasets.CIFAR10(
root = ‘/home/cy/data’,
train = False,
download = True,
transform = transoform)
testloader = t.utils.data.DataLoader(
testset,
batch_size = 4,
shuffle = False,
num_workers = 2)

类别

classes = (‘plane’,‘car’, ‘bird’,‘cat’,‘deer’,‘dog’,‘frog’,‘horse’,‘ship’,‘truck’)

Dataset 对象是一个数据集,可以依下列方法进行访问。返回(data, label)的数据
(data, label) = trainset[100]
print(classes[label])

(data + 1) / 2 是为了还原被归一化的数据,程序输出的图片如下所示

show((data + 1)/2).resize((100, 100))

Dataloader 是一个可迭代对象,将dataset返回的每一条数据样本拼接成一个batch, 并通过多线程加速优化与数据打乱等操作,当程序对dataset的所有数据遍历完成一般后,对Dataloder也完成了一次迭代。
dataiter = iter(trainloader)
images, labels = dataiter.next() # 返回4张图片及标签
print(" “.join(”%11s"%classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images+1)/2)).resize((400, 100))

(2)定义网络
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(1655, 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)
    x = F.max_pool2d(F.relu(self.conv2(x)), (2, 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)

(3)定义损失函数和优化器。
from torch import optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001,momentum = 0.9)
(4)训练网络并更新网络参数。
所有网络的训练流程都是相似的!
训练数据
前向传播 + 反向传播
更新参数
from torch.autograd import Variable
for epoch in range(10):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 输入数据
inputs, labels = data
inputs,labels = Variable(inputs), Variable(labels)
# 梯度清零
optimizer.zero_grad()
# forward+ backwaord
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
# 更新参数
optimizer.step()
# 打印信息
running_loss += loss
if i % 2000 == 1999:# 每2000batch打印一次训练状态
print("[%d, %5d] loss: %.3f" %(epoch+1, i+1, running_loss/2000))
running_loss = 0.0

print(“Finished Training”)

(5)测试网络。
dataiter = iter(trainloader)
images,labels = dataiter.next() # 返回4张图片及标签
print(“实际的Labels”+’, ‘.join(’%11s’ % classes[labels[j]] for j in range(4)))
show(tv.utils.make_grid((images)/2 - 0.5)).resize((400,100))

“”"----------------------------------------------"""

网络的预测结果测试

outputs = net(Variable(images))

得出最高那类

_,predicted = t.max(outputs.data, 1)

print(“预测结果: " ,” “.join(”%5s" % classes[predicted[j]] for j in range(4)))

“”"----------------------------------------------"""

在整个测试集上进行 测试

correct = 0 # 测试正确的照片
total = 0 # 总共的图片个数
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = t.max(outputs.data, 1)

"_"下划线的目的是接收到正确的类别

total += labels.size(0)
correct +=(predicted == labels).sum()

print(“10000张测试集中准确率为: %d %%” % (100 * correct/total))

猜你喜欢

转载自blog.csdn.net/Leomn_J/article/details/112792451