PyTorch简明教程

栏目: Python · 发布时间: 5年前

内容简介:简单的PyTorch教程,来自官网教程PyTorch是一个基于Python的科学计算包,它主要有两个用途:Tensor类似与NumPy的ndarray,但是可以用GPU加速。使用前我们需要导入torch包:

简单的PyTorch教程,来自官网教程 60分钟PyTorch教程通过例子学PyTorch迁移学习教程

目录

60分钟PyTorch教程

什么是PyTorch?

PyTorch是一个基于 Python 的科学计算包,它主要有两个用途:

  • 类似Numpy但是能利用GPU加速
  • 一个非常灵活和快速的用于深度学习的研究平台

Tensor

Tensor类似与NumPy的ndarray,但是可以用GPU加速。使用前我们需要导入torch包:

from __future__ import print_function
import torch

下面的代码构造一个$5 \times 3$的未初始化的矩阵:

x = torch.empty(5, 3)
print(x)

# 输出:
tensor([[-1.9998e+05,  4.5818e-41,  3.4318e-37],
[ 0.0000e+00,  0.0000e+00,  0.0000e+00],
[ 0.0000e+00,  0.0000e+00,  1.2877e+29],
[ 2.0947e-30,  0.0000e+00,  0.0000e+00],
[ 0.0000e+00,  0.0000e+00, -4.5328e+05]])

我们可以使用rand随机初始化一个矩阵:

x = torch.rand(5, 3)
print(x)

#输出:
tensor([[ 0.9656,  0.5782,  0.0482],
[ 0.7462,  0.5838,  0.1844],
[ 0.8262,  0.4507,  0.6128],
[ 0.2961,  0.8956,  0.3092],
[ 0.4973,  0.2203,  0.9200]])

下面的代码构造一个用零初始化的矩阵,它的类型(dtype)是long:

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

#输出:
tensor([[ 0,  0,  0],
[ 0,  0,  0],
[ 0,  0,  0],
[ 0,  0,  0],
[ 0,  0,  0]])

我们也可以使用Python的数组来构造Tensor:

x = torch.tensor([5.5, 3])
print(x)

我们可以从已有的tensor信息(size和dtype)来构造tensor。但也可以用不同的dtype来构造。

x = x.new_ones(5, 3, dtype=torch.double)      # new_* methods take in sizes
print(x)

x = torch.randn_like(x, dtype=torch.float)    # override dtype!
print(x)

我们可以是用size函数来看它的shape:

print(x.size())
#输出:
torch.Size([5, 3])

注意torch.Size其实是一个tuple,因此它支持所有的tuple操作。

Operation

接下来我们来学习一些PyTorch的Operation。Operation一般可以使用函数的方式使用,但是为了方便使用,PyTorch重载了一些常见的运算符,因此我们可以这样来进行Tensor的加法:

y = torch.rand(5, 3)
print(x + y)

我们也可以用add函数来实现加法:

print(torch.add(x, y))

我们也可以给加法提供返回值(而不是生成一个新的返回值):

result = torch.empty(5, 3)
torch.add(x, y, out=result) # x + y的结果放到result里。
print(result)

我们也可以把相加的结果直接修改第一个被加数:

# 把x加到y
y.add_(x)
print(y)

注意:就地修改tensor的operation以下划线结尾。比如: x.copy_(y), x.t_(), 都会修改x。

Tensor的变换

我们也可以使用类似numpy的下标运算来操作PyTorch的Tensor:

#打印x的第一列
print(x[:, 1])

如果想resize或者reshape一个Tensor,我们可以使用torch.view:

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # -1的意思是让PyTorch自己推断出第一维的大小。
print(x.size(), y.size(), z.size())

如果一个tensor只有一个元素,可以使用item()函数来把它变成一个Python number:

x = torch.randn(1)
print(x)
#输出的是一个Tensor
tensor([-0.6966])

print(x.item())
#输出的是一个数
-0.6966081857681274

Tensor与Numpy的互相转换

Torch Tensor和NumPy数组的转换非常容易。它们会共享内存地址,因此修改一方会影响另一方。把一个Torch Tensor转换成NumPy数组的代码示例为:

a = torch.ones(5)
print(a)
#tensor([ 1.,  1.,  1.,  1.,  1.])
b = a.numpy()
print(b)
#[1. 1. 1. 1. 1.]

修改一个会影响另外一个:

a.add_(1)
print(a)
# tensor([ 2.,  2.,  2.,  2.,  2.])
print(b)
# [2. 2. 2. 2. 2.]

把把NumPy数组转成Torch Tensor的代码示例为:

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
# [2. 2. 2. 2. 2.]
print(b)
# tensor([ 2.,  2.,  2.,  2.,  2.], dtype=torch.float64)

CPU上的所有类型的Tensor(除了CharTensor)都可以和Numpy数组来回转换。

CUDA Tensor

Tensor可以使用to()方法来移到任意设备上:

# 如果有CUDA
# 我们会使用``torch.device``来把tensors放到GPU上
if torch.cuda.is_available():
	device = torch.device("cuda")          # 一个CUDA device对象。
	y = torch.ones_like(x, device=device)  # 直接在GPU上创建tensor
	x = x.to(device)                       # 也可以使用``.to("cuda")``把一个tensor从CPU移到GPU上
	z = x + y
	print(z)
	print(z.to("cpu", torch.double))       # ``.to``也可以在移动的过程中修改dtype
	
# 输出:
tensor([ 0.3034], device='cuda:0')
tensor([ 0.3034], dtype=torch.float64)

Autograd: 自动求导

PyTorch的核心是autograd包。 我们首先简单的了解一些,然后用PyTorch开始训练第一个神经网络。autograd为所有用于Tensor的operation提供自动求导的功能。我们通过一些简单的例子来学习它基本用法。

从自动求导看Tensor

torch.Tensor 是这个包的核心类。如果它的属性requires_grad是True,那么PyTorch就会追踪所有与之相关的operation。当完成(正向)计算之后, 我们可以调用backward(),PyTorch会自动的把所有的梯度都计算好。与这个tensor相关的梯度都会累加到它的grad属性里。

如果不想计算这个tensor的梯度,我们可以调用detach(),这样它就不会参与梯度的计算了。为了阻止PyTorch记录用于梯度计算相关的信息(从而节约内存),我们可以使用 with torch.no_grad()。这在模型的预测时非常有用,因为预测的时候我们不需要计算梯度,否则我们就得一个个的修改Tensor的requires_grad属性,这会非常麻烦。

关于autograd的实现还有一个很重要的Function类。Tensor和Function相互连接从而形成一个有向无环图, 这个图记录了计算的完整历史。每个tensor有一个grad_fn属性来引用创建这个tensor的Function(用户直接创建的Tensor,这些Tensor的grad_fn是None)。

如果你想计算梯度,可以对一个Tensor调用它的backward()方法。如果这个Tensor是一个scalar(只有一个数),那么调用时不需要传任何参数。如果Tensor多于一个数,那么需要传入和它的shape一样的参数,表示反向传播过来的梯度。

创建tensor时设置属性requires_grad=True,PyTorch就会记录用于反向梯度计算的信息:

x = torch.ones(2, 2, requires_grad=True)
print(x)

然后我们通过operation产生新的tensor:

y = x + 2
print(y)

是通过operation产生的tensor,因此它的grad_fn不是None。

print(y.grad_fn)
# <AddBackward0 object at 0x7f35409a68d0>

再通过y得到z和out

z = y * y * 3
out = z.mean()

print(z, out)
# z = tensor([[ 27.,  27.],[ 27.,  27.]]) 
# out = tensor(27.)

requires_grad_()函数会修改一个Tensor的requires_grad。

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)

输出是:

False
True
<SumBackward0 object at 0x7f35766827f0>

梯度

现在我们里反向计算梯度。因为out是一个scalar,因此out.backward()等价于out.backward(torch.tensor(1))。

out.backward()

我们可以打印梯度d(out)/dx:

print(x.grad)
# tensor([[ 4.5000,  4.5000],
[ 4.5000,  4.5000]])

我们手动计算来验证一下。为了简单,我们把out记为o。 $o = \frac{1}{4}\sum_i z_i$, $z_i = 3(x_i+2)^2$ 并且 $z_i\bigr\rvert_{x_i=1} = 27$。

因此,$\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2)$,因此$\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5$。

我们也可以用autograd做一些很奇怪的事情!比如y和x的关系是while循环的关系(似乎很难用一个函数直接表示y和x的关系?对x不断平方直到超过1000,这是什么函数?)

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
	y = y * 2

print(y)
# tensor([ -692.4808,  1686.1211,   667.7313])
gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients)

print(x.grad)
# tensor([  102.4000,  1024.0000,     0.1024])

我们可以使用”with torch.no_grad()”来停止梯度的计算:

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
	print((x ** 2).requires_grad)

输出为:

True
True
False

PyTorch神经网络简介

神经网络可以通过torch.nn包来创建。我们之前简单的了解了autograd,而nn会使用autograd来定义模型以及求梯度。一个nn.Module对象包括了许多网络层(layer),并且有一个forward(input)方法来返回output。如下图所示,我们会定义一个卷积网络来识别mnist图片。

PyTorch简明教程 图:识别MNIST数据的神经网络

训练一个神经网络通常需要如下步骤:

  • 定义一个神经网络,它通常有一些可以训练的参数
  • 迭代一个数据集(dataset)
  • 处理网络的输入
  • 计算loss(会调用Module对象的forward方法)
  • 计算loss对参数的梯度
  • 更新参数,通常使用如下的梯度下降方法来更新:

    weight = weight - learning_rate * gradient

定义网络

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

	def __init__(self):
		super(Net, self).__init__()
		# 输入是1个通道的灰度图,输出6个通道(feature map),使用5x5的卷积核
		self.conv1 = nn.Conv2d(1, 6, 5)
		# 第二个卷积层也是5x5,有16个通道
		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):
		# 32x32 -> 28x28 -> 14x14 
		x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
		# 14x14 -> 10x10 -> 5x5
		x = F.max_pool2d(F.relu(self.conv2(x)), 2)
		x = x.view(-1, self.num_flat_features(x))
		x = F.relu(self.fc1(x))
		x = F.relu(self.fc2(x))
		x = self.fc3(x)
		return x
	
	def num_flat_features(self, x):
		size = x.size()[1:]  # 除了batch维度之外的其它维度。
		num_features = 1
		for s in size:
		num_features *= s
		return num_features


net = Net()
print(net)
# Net(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(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)
)

我们只需要定义forward函数,而backward函数会自动通过autograd创建。在forward函数里可以使用任何处理Tensor的函数。我们可以使用函数net.parameters()来得到模型所有的参数。

params = list(net.parameters())
print(len(params))
# 10
print(params[0].size())  # conv1的weight
# torch.Size([6, 1, 5, 5])

测试网络

接着我们尝试一个随机的32x32的输入来检验(sanity check)网络定义没有问题。注意:这个网络(LeNet)期望的输入大小是32x32。如果使用MNIST数据集(28x28),我们需要缩放到32x32。

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
# tensor([[-0.0198,  0.0438,  0.0930, -0.0267, -0.0344,  0.0330,  0.0664,
0.1244, -0.0379,  0.0890]])

默认的梯度会累加,因此我们通常在backward之前清除掉之前的梯度值:

net.zero_grad()
out.backward(torch.randn(1, 10))

注意:torch.nn只支持mini-batches的输入。整个torch.nn包的输入都必须第一维是batch,即使只有一个样本也要弄成batch是1的输入。

比如,nn.Conv2d的输入是一个4D的Tensor,shape是nSamples x nChannels x Height x Width。如果你只有一个样本(nChannels x Height x Width),那么可以使用input.unsqueeze(0)来增加一个batch维。

损失函数

损失函数的参数是(output, target)对,output是模型的预测,target是实际的值。损失函数会计算预测值和真实值的差别,损失越小说明预测的越准。

PyTorch提供了这里有许多不同的损失函数: http://pytorch.org/docs/nn.html#loss-functions。最简单的一个损失函数是:nn.MSELoss,它会计算预测值和真实值的均方误差。比如:

output = net(input)
target = torch.arange(1, 11)  # 随便伪造的一个“真实值” 
target = target.view(1, -1)  # 把它变成output的shape(1, 10) 
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

如果从loss往回走,需要使用tensor的grad_fn属性,我们Negative看到这样的计算图:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss

因此当调用loss.backward()时,PyTorch会计算这个图中所有requires_grad=True的tensor关于loss的梯度。

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Add
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # Expand

#输出:
<MseLossBackward object at 0x7f445b3a2dd8>
<AddmmBackward object at 0x7f445b3a2eb8>
<ExpandBackward object at 0x7f445b3a2dd8>

计算梯度

在调用loss.backward()之前,我们需要清除掉tensor里之前的梯度,否则会累加进去。

net.zero_grad()     # 清掉tensor里缓存的梯度值。

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

更新参数

更新参数最简单的方法是使用随机梯度下降(SGD):

我们可以使用如下简单的代码来实现更新:

learning_rate = 0.01
for f in net.parameters():
	f.data.sub_(f.grad.data * learning_rate)

通常我们会使用更加复杂的优化方法,比如SGD, Nesterov-SGD, Adam, RMSProp等等。为了实现这些算法,我们可以使用torch.optim包,它的用法也非常简单:

import torch.optim as optim

# 创建optimizer,需要传入参数和learning rate
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 清除梯度
optimizer.zero_grad()  
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # optimizer会自动帮我们更新参数

注意:即使使用optimizer,我们也需要清零梯度。但是我们不需要一个个的清除,而是用optimizer.zero_grad()一次清除所有。

训练一个分类器

介绍了PyTorch神经网络相关包之后我们就可以用这些知识来构建一个分类器了。

如何进行数据处理

一般地,当我们处理图片、文本、音频或者视频数据的时候,我们可以使用python代码来把它转换成numpy数组。然后再把numpy数组转换成torch.xxxTensor。

  • 对于处理图像,常见的lib包括Pillow和OpenCV
  • 对于音频,常见的lib包括scipy和librosa
  • 对于文本,可以使用标准的Python库,另外比较流行的lib包括NLTK和SpaCy

对于视觉问题,PyTorch提供了一个torchvision包(需要单独安装),它对于常见数据集比如Imagenet, CIFAR10, MNIST等提供了加载的方法。并且它也提供很多数据变化的工具,包括torchvision.datasets和torch.utils.data.DataLoader。这会极大的简化我们的工作,避免重复的代码。

在这个教程里,我们使用CIFAR10数据集。它包括十个类别:”airplane”, “automobile”, “bird”, “cat”, “deer”, “dog”, “frog”, “horse”, “ship”,”truck”。图像的对象是3x32x32,也就是3通道(RGB)的32x32的图片。下面是一些样例图片。

PyTorch简明教程 图:cifar10样例

训练的步骤

  • 使用torchvision加载和预处理CIFAR10训练和测试数据集。
  • 定义卷积网络
  • 定义损失函数
  • 用训练数据训练模型
  • 用测试数据测试模型

数据处理

通过使用torchvision,我们可以轻松的加载CIFAR10数据集。首先我们导入相关的包:

import torch
import torchvision
import torchvision.transforms as transforms

torchvision读取的datasets是PILImage对象,它的取值范围是[0, 1],我们把它转换到范围[-1, 1]。

transform = transforms.Compose(
	[transforms.ToTensor(),
	transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='/path/to/data', train=True,
	download=True, transform=transform)
	trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
	shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='/path/to/data', train=False,
	download=True, transform=transform)
	testloader = torch.utils.data.DataLoader(testset, batch_size=4,
	shuffle=False, num_workers=2)

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

我们来看几张图片,如图\ref{fig:pytorch-cifar-sample}所示,生成的代入如下:

import matplotlib.pyplot as plt
import numpy as np

# 显示图片的函数


def imshow(img):
img = img / 2 + 0.5     #  [-1,1] -> [0,1]
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0))) # (channel, width, height) -> (width, height, channel)


# 随机选择一些图片
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
# 打印label
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

PyTorch简明教程 图:随机选择的图片

定义卷积网络

网络结构和上一节的介绍类似,只是输入通道从1变成3。

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(3, 6, 5)
		self.pool = nn.MaxPool2d(2, 2)
		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 = self.pool(F.relu(self.conv1(x)))
		x = self.pool(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

net = Net()

\subsubsection{定义损失函数和optimizer} 我们这里使用交叉熵损失函数,Optimizer使用带冲量的SGD。

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

\subsubsection{训练网络} 我们遍历DataLoader进行训练。

for epoch in range(2):  # 这里只迭代2个epoch,实际应该进行更多次训练 

	running_loss = 0.0
	for i, data in enumerate(trainloader, 0):
		# 得到输入
		inputs, labels = data
		
		# 梯度清零 
		optimizer.zero_grad()
		
		# forward + backward + optimize
		outputs = net(inputs)
		loss = criterion(outputs, labels)
		loss.backward()
		optimizer.step()
		
		# 定义统计信息
		running_loss += loss.item()
		if i % 2000 == 1999:
			print('[%d, %5d] loss: %.3f' %
				(epoch + 1, i + 1, running_loss / 2000))
		running_loss = 0.0

print('Finished Training')

在测试数据集上进行测试

我们进行了2轮迭代,可以使用测试数据集上的数据来进行测试。首先我们随机抽取几个样本来进行测试。

dataiter = iter(testloader)
images, labels = dataiter.next()


imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

随机选择出来的测试样例如下图所示。

PyTorch简明教程 图:随机测试的结果

我们用模型来预测一下,看看是否正确预测:

outputs = net(images)

outputs是10个分类的logits。我们在训练的时候需要用softmax把它变成概率(CrossEntropyLoss帮我们做了),但是预测的时候没有必要,因为我们只需要知道哪个分类的概率大就行。

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
		for j in range(4)))

# cat  ship  ship  ship

预测中的四个错了一个,似乎还不错。接下来我们看看在整个测试集合上的效果:

correct = 0
total = 0
with torch.no_grad():
for data in testloader:
	images, labels = data
	outputs = net(images)
	_, predicted = torch.max(outputs.data, 1)
	total += labels.size(0)
	correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
	100 * correct / total))

# Accuracy of the network on the 10000 test images: 55 %

看起来比随机的瞎猜要好,因为随机猜的准确率大概是10%的准确率,所以模型确实学到了一些东西。我们也可以看每个分类的准确率:

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
	for data in testloader:
		images, labels = data
		outputs = net(images)
		_, predicted = torch.max(outputs, 1)
		c = (predicted == labels).squeeze()
		for i in range(4):
			label = labels[i]
			class_correct[label] += c[i].item()
			class_total[label] += 1


for i in range(10):
	print('Accuracy of %5s : %2d %%' % (
		classes[i], 100 * class_correct[i] / class_total[i]))

结果为:

Accuracy of plane : 52 %
Accuracy of   car : 66 %
Accuracy of  bird : 49 %
Accuracy of   cat : 34 %
Accuracy of  deer : 30 %
Accuracy of   dog : 45 %
Accuracy of  frog : 72 %
Accuracy of horse : 71 %
Accuracy of  ship : 76 %
Accuracy of truck : 55 %

GPU上训练

为了在GPU上训练,我们需要把Tensor移到GPU上。首先我们看看是否有GPU,如果没有,那么我们还是fallback到CPU。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
# cuda:0

用GPU进行训练:

class Net2(nn.Module):
def __init__(self):
super(Net2, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5).to(device)
self.pool = nn.MaxPool2d(2, 2).to(device)
self.conv2 = nn.Conv2d(6, 16, 5).to(device)
self.fc1 = nn.Linear(16 * 5 * 5, 120).to(device)
self.fc2 = nn.Linear(120, 84).to(device)
self.fc3 = nn.Linear(84, 10).to(device)

def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(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


net = Net2()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

for epoch in range(20):

	running_loss = 0.0
	for i, data in enumerate(trainloader, 0):
		# 得到输入
		inputs, labels = data 
		inputs, labels = inputs.to(device), labels.to(device) 
		# 梯度清零 
		optimizer.zero_grad()
		
		# forward + backward + optimize
		outputs = net(inputs)
		loss = criterion(outputs, labels)
		loss.backward()
		optimizer.step()
		
		# 定义统计信息
		running_loss += loss.item()
		if i % 2000 == 1999:
			print('[%d, %5d] loss: %.3f' %
				(epoch + 1, i + 1, running_loss / 2000))
			running_loss = 0.0
		
		print('Finished Training')

通过例子学PyTorch

下面我们通过使用不同的方法来实现一个简单的三层(一个隐层)的全连接神经网络来熟悉PyTorch的常见用法。

使用Numpy实现三层神经网络

我们需要实现一个全连接的激活为ReLU的网络,它只有一个隐层,没有bias,用于回归预测一个值,loss是计算实际值和预测值的欧氏距离。这里完全使用numpy手动的进行前向和后向计算。numpy数组就是一个n维的数值,它并不知道任何关于深度学习、梯度下降或者计算图的东西,它只是进行数值运算。

import numpy as np

# N是batch size;D_in是输入大小
# H是隐层的大小;D_out是输出大小。
N, D_in, H, D_out = 64, 1000, 100, 10

# 随机产生输入与输出
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)

# 随机初始化参数
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
	# 前向计算y
	h = x.dot(w1)
	h_relu = np.maximum(h, 0)
	y_pred = h_relu.dot(w2)
	
	# 计算loss
	loss = np.square(y_pred - y).sum()
	print(t, loss)
	
	# 反向计算梯度 
	grad_y_pred = 2.0 * (y_pred - y)
	grad_w2 = h_relu.T.dot(grad_y_pred)
	grad_h_relu = grad_y_pred.dot(w2.T)
	grad_h = grad_h_relu.copy()
	grad_h[h < 0] = 0
	grad_w1 = x.T.dot(grad_h)
	
	# 更新参数
	w1 -= learning_rate * grad_w1
	w2 -= learning_rate * grad_w2

使用Tensor来实现三层神经网络

和前面一样,我们还是实现一个全连接的Relu激活的网络,它只有一个隐层并且没有bias。loss是预测与真实值的欧氏距离。之前我们用Numpy实现,自己手动前向计算loss,反向计算梯度。这里还是一样,只不过把numpy数组换成了PyTorch的Tensor。但是使用PyTorch的好处是我们可以利用GPU来加速计算,如果想用GPU计算,我们值需要在创建tensor的时候指定device为gpu。

import torch


dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 如果想在GPU上运算,把这行注释掉。

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500): 
	h = x.mm(w1)
	h_relu = h.clamp(min=0) # 使用clamp(min=0)来实现ReLU
	y_pred = h_relu.mm(w2)
	
	loss = (y_pred - y).pow(2).sum().item()
	print(t, loss)
	
	grad_y_pred = 2.0 * (y_pred - y)
	grad_w2 = h_relu.t().mm(grad_y_pred)
	grad_h_relu = grad_y_pred.mm(w2.t())
	grad_h = grad_h_relu.clone()
	grad_h[h < 0] = 0
	grad_w1 = x.t().mm(grad_h)
	
	w1 -= learning_rate * grad_w1
	w2 -= learning_rate * grad_w2

实现autograd来实现三层神经网络

还是和前面一样实现一个全连接的网络,只有一个隐层而且没有bias,使用欧氏距离作为损失函数。这个实现使用PyTorch的Tensor来计算前向阶段,然后使用PyTorch的autograd来自动帮我们反向计算梯度。PyTorch的Tensor代表了计算图中的一个节点。如果x是一个Tensor并且x.requires_grad=True,那么x.grad这个Tensor会保存某个scalar(通常是loss)对x的梯度。

import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # 如果有GPU可以注释掉这行

# N是batch size;D_in是输入大小
# H是隐层的大小;D_out是输出大小。
N, D_in, H, D_out = 64, 1000, 100, 10

# 创建随机的Tensor作为输入和输出
# 输入和输出需要的requires_grad=False(默认),
# 因为我们不需要计算loss对它们的梯度。
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 创建weight的Tensor,需要设置requires_grad=True 
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
	# Forward阶段: mm实现矩阵乘法,但是它不支持broadcasting。
	# 如果需要broadcasting,可以使用matmul
	# clamp本来的用途是把值clamp到指定的范围,这里实现ReLU。 
	y_pred = x.mm(w1).clamp(min=0).mm(w2)
	
	# pow(2)实现平方计算。 
	# loss.item()得到这个tensor的值。也可以直接打印loss,这会打印很多附加信息。
	loss = (y_pred - y).pow(2).sum()
	print(t, loss.item())
	
	# 使用autograd进行反向计算。它会计算loss对所有对它有影响的
	# requires_grad=True的Tensor的梯度。
	
	loss.backward()
	
	# 手动使用梯度下降更新参数。一定要把更新的代码放到torch.no_grad()里
	# 否则下面的更新也会计算梯度。后面我们会使用torch.optim.SGD,
	# 它会帮我们管理这些用于更新梯度的计算。
	
	with torch.no_grad():
		w1 -= learning_rate * w1.grad
		w2 -= learning_rate * w2.grad
		
		# 手动把梯度清零 
		w1.grad.zero_()
		w2.grad.zero_()

使用自定义的ReLU函数

这里还是那个全连接网络的例子,不过这里我们不使用clamp来实现ReLU,而是我们自己来实现一个MyReLU的函数。

import torch


class MyReLU(torch.autograd.Function):
	"""
	为了实现自定义的实现autograd的函数,我们需要基础torch.autograd.Function,
	然后再实现forward和backward两个函数。
	"""
	
	@staticmethod
	def forward(ctx, input):
		"""
		在forward函数,我们的输入是input,然后我们根据input计算输出。
		# 同时为了下面的backward,
		我们需要使用save_for_backward来保存用于反向计算的数据到ctx里,
		# 这里我们需要保存input。
		"""
		ctx.save_for_backward(input)
		return input.clamp(min=0)
	
	@staticmethod
	def backward(ctx, grad_output):
		"""
		从ctx.saved_tensors里恢复input
		然后用input计算梯度
		"""
		input, = ctx.saved_tensors
		grad_input = grad_output.clone()
		grad_input[input < 0] = 0
		return grad_input


dtype = torch.float
device = torch.device("cpu")

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
	# 为了调用我们自定义的函数,我们需要使用Function.apply方法,把它命名为'relu'
	relu = MyReLU.apply
	
	# 我们使用自定义的ReLU来进行Forward计算
	y_pred = relu(x.mm(w1)).mm(w2)
	
	loss = (y_pred - y).pow(2).sum()
	print(t, loss.item())
	
	loss.backward()
	
	with torch.no_grad():
		w1 -= learning_rate * w1.grad
		w2 -= learning_rate * w2.grad
		
		w1.grad.zero_()
		w2.grad.zero_()

和Tensorflow的对比

这里我们还是和前面一样,实现一个隐层的全连接神经网络,优化的目标函数是预测值和真实值的欧氏距离。这个实现使用基本的Tensorflow操作来构建一个计算图,然后多次执行这个计算图来训练网络。Tensorflow和PyTorch最大的区别之一就是Tensorflow使用静态计算图和PyTorch使用动态计算图。在Tensorflow里,我们首先构建计算图,然后多次执行它。

import tensorflow as tf
import numpy as np

# 首先构建计算图。

# N是batch大小;D_in是输入大小。
# H是隐单元个数;D_out是输出大小。
N, D_in, H, D_out = 64, 1000, 100, 10

# 输入和输出是placeholder,在用session执行graph的时候
# 我们会feed进去一个batch的训练数据。
x = tf.placeholder(tf.float32, shape=(None, D_in))
y = tf.placeholder(tf.float32, shape=(None, D_out))

# 创建变量,并且随机初始化。 
# 在Tensorflow里,变量的生命周期是整个session,因此适合用它来保存模型的参数。
w1 = tf.Variable(tf.random_normal((D_in, H)))
w2 = tf.Variable(tf.random_normal((H, D_out)))

# Forward pass:计算模型的预测值y_pred 
# 注意和PyTorch不同,这里不会执行任何计算,
# 而只是定义了计算,后面用session.run的时候才会真正的执行计算。
h = tf.matmul(x, w1)
h_relu = tf.maximum(h, tf.zeros(1))
y_pred = tf.matmul(h_relu, w2)

# 计算loss 
loss = tf.reduce_sum((y - y_pred) ** 2.0)

# 计算梯度。 
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])

# 使用梯度下降来更新参数。assign同样也只是定义更新参数的操作,不会真正的执行。
# 在Tensorflow里,更新操作是计算图的一部分;
# 而在PyTorch里,因为是动态的”实时“的计算,
# 所以参数的更新只是普通的Tensor计算,不属于计算图的一部分。
learning_rate = 1e-6
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)

# 计算图构建好了之后,我们需要创建一个session来执行计算图。
with tf.Session() as sess:
	# 首先需要用session初始化变量 
	sess.run(tf.global_variables_initializer())
	
	# 这是fake的训练数据
	x_value = np.random.randn(N, D_in)
	y_value = np.random.randn(N, D_out)
	for _ in range(500):
		# 用session多次的执行计算图。每次feed进去不同的数据。
		# 这里是模拟的,实际应该每次feed一个batch的数据。
		# run的第一个参数是需要执行的计算图的节点,它依赖的节点也会自动执行,
		# 因此我们不需要手动执行forward的计算。
		# run返回这些节点执行后的值,并且返回的是numpy array
		loss_value, _, _ = sess.run([loss, new_w1, new_w2],
				feed_dict={x: x_value, y: y_value})
		print(loss_value)

使用nn模块来实现三层神经网络

我们接下来使用nn模块来实现这个简单的全连接网络。前面我们通过用Tensor和Operation等low-level API来创建 动态的计算图,这里我们使用更简单的high-level API。

import torch
print(torch.__version__)

# N是batch size;D_in是输入大小
# H是隐层的大小;D_out是输出大小。
N, D_in, H, D_out = 64, 1000, 100, 10

# 创建随机的Tensor作为输入和输出
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 使用nn包来定义网络。nn.Sequential是一个包含其它模块(Module)的模块。
# 每个Linear模块使用线性函数来计算,它会内部创建需要的weight和bias。
model = torch.nn.Sequential(
	torch.nn.Linear(D_in, H),
	torch.nn.ReLU(),
	torch.nn.Linear(H, D_out),
)

# 常见的损失函数在nn包里也有,不需要我们自己实现
loss_fn = torch.nn.MSELoss(size_average=False)

learning_rate = 1e-4
for t in range(500):
# 前向计算:通过x来计算y。Module对象会重写__call__函数,
# 因此我们可以把它当成函数来调用。
y_pred = model(x)

# 计算loss 
loss = loss_fn(y_pred, y)
print(t, loss.item())

# 梯度清空,调用Sequential对象的zero_grad后所有里面的变量都会清零梯度
model.zero_grad()

# 反向计算梯度。我们通过Module定义的变量都会计算梯度。
loss.backward()

# 更新参数,所有的参数都在model.paramenters()里

with torch.no_grad():
	for param in model.parameters():
		param -= learning_rate * param.grad

使用optim包

前面我们使用nn模块时是自己来更新模型参数的,PyTorch也提供了optim包,我们可以使用里面的Optimizer来自动的更新模型参数。除了最基本的SGD算法,这个包也实现了常见的SGD+momentum, RMSProp, Adam等算法。

import torch

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = torch.nn.Sequential(
	torch.nn.Linear(D_in, H),
	torch.nn.ReLU(),
	torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(size_average=False)

# 使用Adam算法,需要提供模型的参数和learning rate 
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500): 
	y_pred = model(x)
	
	loss = loss_fn(y_pred, y)
	print(t, loss.item())
	
	# 梯度清零,原来调用的是model.zero_grad,现在调用的是optimizer的zero_grad
	optimizer.zero_grad()
	
	loss.backward()
	
	# 调用optimizer.step实现参数更新
	optimizer.step()

自定义nn模块

对于复杂的网络结构,我们可以通过基础Module了自定义nn模块。这样的好处是用一个类来同样管理,而且更容易复用代码。

import torch


class TwoLayerNet(torch.nn.Module):
	def __init__(self, D_in, H, D_out):
		"""
		在构造函数里,我们定义两个nn.Linear模块,把它们保存到self里。
		"""
		super(TwoLayerNet, self).__init__()
		self.linear1 = torch.nn.Linear(D_in, H)
		self.linear2 = torch.nn.Linear(H, D_out)
	
	def forward(self, x):
		"""
		在forward函数里,我们需要根据网络结构来实现前向计算。
		通常我们会上定义的模块来计算。
		"""
		h_relu = self.linear1(x).clamp(min=0)
		y_pred = self.linear2(h_relu)
		return y_pred


N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = TwoLayerNet(D_in, H, D_out)

criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500): 
	y_pred = model(x)
	
	loss = criterion(y_pred, y)
	print(t, loss.item())
	
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()

流程控制和参数共享

为了展示PyTorch的动态图的能力,我们这里会实现一个很奇怪模型:这个全连接的网络的隐层个数是个1到4之间的随机数,而且这些网络层的参数是共享的。

import random
import torch


class DynamicNet(torch.nn.Module):
	def __init__(self, D_in, H, D_out):
		"""
		构造3个nn.Linear实例。
		"""
		super(DynamicNet, self).__init__()
		self.input_linear = torch.nn.Linear(D_in, H)
		self.middle_linear = torch.nn.Linear(H, H)
		self.output_linear = torch.nn.Linear(H, D_out)
	
	def forward(self, x):
		"""
		输入和输出层是固定的,但是中间层的个数是随机的(0,1,2),
		并且中间层的参数是共享的。
		
		因为每次计算的计算图是动态(实时)构造的,
		所以我们可以使用普通的Python流程控制代码比如for循环
		来实现。读者可以尝试一下怎么用TensorFlow来实现。
		另外一点就是一个Module可以多次使用,这样就
		可以实现参数共享。
		"""
		h_relu = self.input_linear(x).clamp(min=0)
		for _ in range(random.randint(0, 3)):
		h_relu = self.middle_linear(h_relu).clamp(min=0)
		y_pred = self.output_linear(h_relu)
		return y_pred


N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = DynamicNet(D_in, H, D_out)

criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500): 
	y_pred = model(x)
	
	loss = criterion(y_pred, y)
	print(t, loss.item())
	
	optimizer.zero_grad()
	loss.backward()
	optimizer.step()

迁移学习示例

在这个教程里,我们会学习怎么使用迁移学习来训练模型。通常我们的训练数据量不会很大,很难达到像ImageNet那样上百万的标注数据集。我们可以使用迁移学习来解决训练数据不足的问题。迁移学习里,我们根据训练数据的多少通常可以采取如下方法:

  • 训练数据很少

    那么我们通常把一个pretraning的网络的大部分固定住,然后只是把最后一个全连接层换成新的(最后一层通常是不一样的,因为分类的数量不同),然后只训练这一层

  • 训练数据较多

    我们可以把pretraining的网络的前面一些层固定住,但后面的层不固定,把最后一层换新的,然后训练

  • 训练数据很多

    所有的pretraining的层都可以fine-tuning,只是用pretraining的参数作为初始化参数。

首先我们引入依赖:

from __future__ import print_function, division

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy

plt.ion()

加载数据

我们使用torchvision和torch.utils.data包来加载数据。我们要解决的问题是训练一个模型来区分蚂蚁和蜜蜂,每个类别我们大概有120个训练数据,另外每个类有75个验证数据。这是一个很小的训练集,如果直接用一个神经网络来训练,效果会很差。现在我们用迁移学习来解决这个问题。数据可以在 这里 下载,下载后请解压到data目录下。

# 训练的时候会做数据增强和归一化
# 而验证的时候只做归一化
data_transforms = {
	'train': transforms.Compose([
		transforms.RandomResizedCrop(224),
		transforms.RandomHorizontalFlip(),
		transforms.ToTensor(),
		transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
	]),
	'val': transforms.Compose([
		transforms.Resize(256),
		transforms.CenterCrop(224),
		transforms.ToTensor(),
		transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
	]),
}

data_dir = '../data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
		data_transforms[x]) 
	for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
		shuffle=True, num_workers=4)
	for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

可视化图片

我们来显示几张图片看看,是一个batch的图片,显示的代码如下:

def imshow(inp, title=None):
	inp = inp.numpy().transpose((1, 2, 0))
	mean = np.array([0.485, 0.456, 0.406])
	std = np.array([0.229, 0.224, 0.225])
	inp = std * inp + mean
	inp = np.clip(inp, 0, 1)
	plt.imshow(inp)
	if title is not None:
		plt.title(title)
	plt.pause(0.001)


# 得到一个batch的数据
inputs, classes = next(iter(dataloaders['train']))

# 把batch张图片拼接成一个大图
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[class_names[x] for x in classes])

PyTorch简明教程 图:迁移学习数据示例

训练模型

现在我们来实现一个用于训练模型的通用函数。这里我们会演示怎么实现:

  • learning rate的自适应
  • 保存最好的模型

在下面的函数中,参数scheduler是来自torch.optim.lr_scheduler的LR scheduler对象(_LRScheduler的子类)

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
	since = time.time()
	
	best_model_wts = copy.deepcopy(model.state_dict())
	best_acc = 0.0
	
	for epoch in range(num_epochs):
		print('Epoch {}/{}'.format(epoch, num_epochs - 1))
		print('-' * 10)
		
		# 每个epoch都分为训练和验证阶段
		for phase in ['train', 'val']:
			if phase == 'train':
				scheduler.step()
				model.train()  # 训练阶段
			else:
				model.eval()   # 验证阶段
			
			running_loss = 0.0
			running_corrects = 0
			
			# 变量数据集
			for inputs, labels in dataloaders[phase]:
				inputs = inputs.to(device)
				labels = labels.to(device)
			
			# 参数梯度清空
			optimizer.zero_grad()
			
			# forward
			# 只有训练的时候track用于梯度计算的历史信息。
			with torch.set_grad_enabled(phase == 'train'):
				outputs = model(inputs)
				_, preds = torch.max(outputs, 1)
				loss = criterion(outputs, labels)
				
				# 如果是训练,那么需要backward和更新参数 
				if phase == 'train':
					loss.backward()
					optimizer.step()
			
			# 统计
			running_loss += loss.item() * inputs.size(0)
			running_corrects += torch.sum(preds == labels.data)
			
			epoch_loss = running_loss / dataset_sizes[phase]
			epoch_acc = running_corrects.double() / dataset_sizes[phase]
			
			print('{} Loss: {:.4f} Acc: {:.4f}'.format(
				phase, epoch_loss, epoch_acc))
			
			# 保存验证集上的最佳模型
			if phase == 'val' and epoch_acc > best_acc:
				best_acc = epoch_acc
				best_model_wts = copy.deepcopy(model.state_dict())
			
			print()
	
	time_elapsed = time.time() - since
	print('Training complete in {:.0f}m {:.0f}s'.format(
		time_elapsed // 60, time_elapsed % 60))
	print('Best val Acc: {:4f}'.format(best_acc))
	
	# 加载最优模型
	model.load_state_dict(best_model_wts)
	return model

可视化预测结果的函数

def visualize_model(model, num_images=6):
	was_training = model.training
	model.eval()
	images_so_far = 0
	fig = plt.figure()
	
	with torch.no_grad():
		for i, (inputs, labels) in enumerate(dataloaders['val']):
			inputs = inputs.to(device)
			labels = labels.to(device)
			
			outputs = model(inputs)
			_, preds = torch.max(outputs, 1)
			
			for j in range(inputs.size()[0]):
				images_so_far += 1
				ax = plt.subplot(num_images//2, 2, images_so_far)
				ax.axis('off')
				ax.set_title('predicted: {}'.format(class_names[preds[j]]))
				imshow(inputs.cpu().data[j])
				
				if images_so_far == num_images:
					model.train(mode=was_training)
					return
		model.train(mode=was_training)

fine-tuning所有参数

我们首先加载一个预训练的模型(imagenet上的resnet),因为我们的类别数和imagenet不同,所以我们需要删掉原来的全连接层,换成新的全连接层。这里我们让所有的模型参数都可以调整,包括新加的全连接层和预训练的层。

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)

model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

# 所有的参数都可以训练
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# 每7个epoch learning rate变为原来的10% 
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,
	num_epochs=25)

最终我们得到的分类准确率大概在94.7%。

fine-tuning最后一层参数

我们用可以固定住前面层的参数,只训练最后一层。这比之前要快将近一倍,因为反向计算梯度只需要计算最后一层。但是前向计算的时间是一样的。

model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
	param.requires_grad = False

# 新加的层默认requires_grad=True 
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)

model_conv = model_conv.to(device)

criterion = nn.CrossEntropyLoss()

# 值训练最后一个全连接层。
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

model_conv = train_model(model_conv, criterion, optimizer_conv,
	exp_lr_scheduler, num_epochs=25)

最终我们得到的分类准确率大概在96%。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Charlotte's Web

Charlotte's Web

E. B. White / Puffin Classics / 2010-6-3 / GBP 6.99

This is the story of a little girl named Fern who loved a little pig named Wilbur and of Wilbur's dear friend, Charlotte A. Cavatica, a beautiful large grey spider. With the unlikely help of Templeton......一起来看看 《Charlotte's Web》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具