PyTorch教程(2)CNN

在像PyTorch这样的图形计算平台中,概率和随机变量是计算中不可分割的一部分。理解概率及其相关概念是至关重要的。本章涵盖了概率分布和使用PyTorch的实现,以及如何解释测试结果。在概率和统计中,随机变量结果依赖于一个纯粹的随机现象。概率分布有不同的类型,包括正态分布,二项分布,多项分布,伯努利分布。每种统计分布都有自己的特性。

设置损失函数

我们如何建立一个损失函数并优化它?选择正确的损失函数会增加模型收敛的机会。
在该教程中,我们使用另一个张量作为更新变量,并将该张量引入样本模型,计算误差或损失。然后计算损失函数的变化率来衡量模型收敛时损失函数的选择。
在下面的例子中,t_c和t_u是两个张量。这可以从NumPy数组构造。

import  torch
print(torch.__version__)
# 1.7.1
t_c = torch.tensor([0.5,.4,.6,7.6,8.7,10.,22.0])
t_u = torch.tensor([0.25,.2,.35,3.6,4.3,5.,11.0])

样本模型只是一个线性方程。损失函数使用MSE。在本章中,我们将增加模型的复杂性。现在,这只是一个简单的线性方程计算。
现在让我们定义模型。w参数是权重张量,它乘以t_u张量。结果加上一个常张量b,选择的损失函数是定制的;在下面的例子中,t_u是使用的张量,t_p是预测的张量,t_c是预先计算的张量,需要与预测的张量进行比较来计算损失函数。

def model(t_u,w,b):
	return w * t_u + b
def loss_fn(t_p, t_c):
	squared_diffs=(t_p - t_c)**2
	return squared_diffs.mean()
w = torch.ones(1)
b = torch.zeros(1)
t_p = model(t_u, w, b)
print(t_p)
# tensor([ 0.2500,  0.2000,  0.3500,  3.6000,  4.3000,  5.0000, 11.0000])
loss = loss_fn(t_p, t_c)
print(loss)
# tensor(25.9321)

初始损失值为25.9321,由于初始权重的选取,损失值偏高。对第一轮迭代中的误差进行反向传播,以减少第二轮迭代中的误差,对于第二轮迭代,需要更新初始权值。因此,在估计过程中,损失函数的变化率对于更新权值至关重要。

delta=0.1
loss_rate_of_change_w = (loss_fn(model(t_u, w+delta, b), t_c) - 
				loss_fn(model(t_u, w-delta, b), t_c)) / (2.0*delta)
learning_rate = 1e-2
w = w - learning_rate * loss_rate_of_change_w

loss_rate_of_change_b = (loss_fn(model(t_u, w, b+delta), t_c) - 
				loss_fn(model(t_u, w, b-delta), t_c)) / (2.0*delta)
b = b - learning_rate * loss_rate_of_change_b
print(w)
print(b)
# tensor([1.5129])
# tensor([0.0355])

让我们看看下面的例子。在PyTorch的神经网络模块中的MSELoss函数。

from torch import nn
import torch
loss = nn.MSELoss()
inp = torch.randn(10, 5, requires_grad=True)
target = torch.randn(10,5)
output = loss(inp, target)
output.backward()
print(output)
# tensor(1.8528, grad_fn=<MseLossBackward>)
# 当我们查看用于反向传播的梯度计算时,它显示为MSELoss。
print(output.grad_fn)
# <MseLossBackward object at 0x0000028CEC202588>

估计损失函数的导数

我们如何估计损失函数的导数?
使用下面的例子,我们将损失函数改为输入和输出张量差的两倍,而不是
MSELoss函数。下面的grad_fn被定义为一个自定义函数,它向用户展示了最终输出如何获得loss函数的导数。

让我们看看下面的例子。在前面的方法中,脚本的最后一行显示了grad_fn作为嵌入到输出对象张量中的对象。在这个教程中,我们解释如何计算它。grad_fn是损失函数对模型参数的导数。这正是我们在下面的grad_fn中所做的。

def dloss_fn(t_p, t_c):
	dsq_diffs = 2 * (t_p-t_c)
	return dsq_diffs
def model(t_u, w, b):
	return w * t_u + b
def dmodel_dw(t_u, w, b):
	return t_u
def dmodel_db(t_u, w, b):
	return 1.0
def grad_fn(t_u, t_c, t_p, w, b):
	dloss_dw = dloss_fn(t_p, t_c) * dmodel_dw(t_u, w, b)
	dloss_db = dloss_fn(t_p, t_c) * dmodel_db(t_u, w, b)
	return torch.stack([dloss_dw.mean(), dloss_db.mean()])
# 参数为模型训练的输入、偏差设置、学习率和epochs。这些参数的估计为方程提供了值。
params = torch.tensor([1.0, 0.0])
nepochs = 100
learning_rate = 1e-2
for epoch in range(nepochs):
	w, b = params
	t_p = model(t_u, w, b)
	loss = loss_fn(t_p, t_c)
	print("Epoch %d, Loss %f" % (epoch, float(loss)))
	# backward pass
	grad = grad_fn(t_u, t_c, t_p, w, b)
	print("Params:", params)
	print("Grad:", grad)
	params = params - learning_rate * grad
# Epoch 0, Loss 25.932142
# Params: tensor([1., 0.])
# Grad: tensor([-51.2886,  -7.1714])
# Epoch 1, Loss 6.054207
# Params: tensor([1.5129, 0.0717])
# Grad: tensor([-24.7462,  -3.4085])
# Epoch 2, Loss 1.429309
# Params: tensor([1.7603, 0.1058])
# Grad: tensor([-11.9434,  -1.5939])
# Epoch 3, Loss 0.353200
# Params: tensor([1.8798, 0.1217])
# Grad: tensor([-5.7679, -0.7192])
# ...

这是初始结果的样子。Epoch是从前面定义的损失函数中产生损失值的迭代。参数向量是需要更新的系数和常数。迭代次数(Epochs)依赖于输入数据、输出数据以及损失和优化函数的选择。

如果我们降低学习率,我们可以将相关的值传递给梯度,参数更新的更好,模型收敛的更快。

params = torch.tensor([1.0, 0.0])
nepochs = 100
learning_rate = 1e-4
for epoch in range(nepochs):
	w, b = params
	t_p = model(t_u, w, b)
	loss = loss_fn(t_p, t_c)
	print("Epoch %d, Loss %f" % (epoch, float(loss)))
	# backward pass
	grad = grad_fn(t_u, t_c, t_p, w, b)
	print("Params:", params)
	print("Grad:", grad)
	params = params - learning_rate * grad
# Epoch 0, Loss 25.932142
# Params: tensor([1., 0.])
# Grad: tensor([-51.2886,  -7.1714])
# Epoch 1, Loss 25.664642
# Params: tensor([1.0051e+00, 7.1714e-04])
# Grad: tensor([-51.0232,  -7.1338])
# Epoch 2, Loss 25.399904
# Params: tensor([1.0102, 0.0014])
# Grad: tensor([-50.7591,  -7.0964])
# Epoch 3, Loss 25.137903
# Params: tensor([1.0153, 0.0021])
# Grad: tensor([-50.4964,  -7.0591])

微调模型

我们如何通过应用优化函数来优化损失函数以此来找到损失函数的梯度?我们将使用backward()函数。

让我们看看下面的例子。函数的作用是:计算函数相对于其参数的梯度。在本节中,我们将使用新的超参数集对模型进行重新训练。

import torch
def model(t_u, w, b):
	return w * t_u + b
def loss_fn(t_p, t_c):
	sq_diffs=(t_p-t_c)**2
	return sq_diffs.mean()
params = torch.tensor([1.0, 0.0], requires_grad=True)
nepochs=5000
learning_rate=1e-2
inp = torch.randn(10, 5)
target = torch.randn(10,5)
for epoch in range(nepochs):
	# forward pass
	t_p = model(inp, *params)
	loss = loss_fn(t_p, target)
	print("Epoch %d, Loss %f" % (epoch, float(loss)))
	# backward pass
	if params.grad is not None:
		params.grad.zero_()
	loss.backward()
	# params.grad.clamp_(-1.0, 1.0)
	# print(params, params.grad)
	params = (params - learning_rate * params.grad).detach().requires_grad_()
print(params)
# Epoch 0, Loss 2.656600
# Epoch 1, Loss 2.590139
# Epoch 2, Loss 2.526078
# Epoch 3, Loss 2.464329
# Epoch 4, Loss 2.404807
# Epoch 5, Loss 2.347435
# Epoch 6, Loss 2.292133
# Epoch 7, Loss 2.238826
# Epoch 8, Loss 2.187443
# Epoch 9, Loss 2.137915
# ...
# tensor([-0.1958, -0.0114], requires_grad=True)
	

选择优化器

PyTorch中嵌入了一些函数,用户需要创建一些优化函数。

import torch
import torch.optim as optim
print(dir(optim))
# ['ASGD', 'Adadelta', 'Adagrad', 'Adam', 'AdamW', 'Adamax', 'LBFGS', 
# 'Optimizer', 'RMSprop', 'Rprop', 'SGD', 'SparseAdam', '__builtins__', 
# '__cached__', '__doc__', '__file__', '__loader__', '__name__', 
# '__package__', '__path__', '__spec__', '_multi_tensor', 'functional', 
# 'lr_scheduler', 'swa_utils']

每种优化方法在解决问题时都是独特的。稍后我们将对此进行描述。

Adam优化器是一种基于梯度的随机目标函数的一阶优化。它是基于自适应估计的低阶矩。对于在大型数据集上部署,这在计算上足够高效。为了使用torch.optim,我们必须在代码中构建一个优化器对象,该对象将保存参数的当前状态,并根据计算出的梯度、moments和学习率更新参数。为了构造优化器,我们必须给它一个包含参数的可迭代对象,并确保所有参数都是要优化的变量。然后,我们可以指定优化器特定的选项,如学习率、权重衰减、moments等等。

Adadelta是另一个能够快速处理大型数据集的优化器。这种方法不需要手动微调学习率;算法会在内部处理它。

import torch
import torch.optim as optim
def model(t_u, w, b):
	return w * t_u + b
def loss_fn(t_p, t_c):
	sq_diffs=(t_p-t_c)**2
	return sq_diffs.mean()
params = torch.tensor([1.0, 0.0], requires_grad=True)
nepochs=1000
learning_rate=1e-2
optimizer = optim.SGD([params], lr=learning_rate)
inp = torch.randn(10, 1)
target = inp * 0.1
print(inp.requires_grad)
print(target.requires_grad)
for epoch in range(nepochs):
	# forward pass
	t_p = model(inp, *params)
	loss = loss_fn(t_p, target)
	print("Epoch %d, Loss %f" % (epoch, float(loss)))
	# backward pass
	optimizer .zero_grad()
	loss.backward()
	optimizer .step()
print(params)
# Epoch 0, Loss 2.058807
# Epoch 1, Loss 2.004358
# Epoch 2, Loss 1.952444
# Epoch 3, Loss 1.902945
# Epoch 4, Loss 1.855746
# Epoch 5, Loss 1.810738
# Epoch 6, Loss 1.767817
# Epoch 7, Loss 1.726884
# Epoch 8, Loss 1.687845
# Epoch 9, Loss 1.650610
# tensor([ 0.7887, -0.0077], requires_grad=True)

进一步优化功能

我们如何优化训练集,并使用随机样本验证集测试它?
让我们看看下面的例子。这里我们设置了样本的数量,然后使用shuffled_indices将20%的数据作为验证样本。我们从所有数据中随机抽取样本。训练和验证集的目的是在训练集中建立模型,对验证集进行预测,并检查模型的准确性。

import torch
import torch.optim as optim
def model(t_u, w, b):
	return w * t_u + b
def loss_fn(t_p, t_c):
	sq_diffs=(t_p-t_c)**2
	return sq_diffs.mean()
params = torch.tensor([1.0, 0.0], requires_grad=True)
nepochs=1000
learning_rate=1e-2
optimizer = optim.SGD([params], lr=learning_rate)
inp = torch.randn(10, 1)
target = inp * 0.1

n_samples=inp.shape[0]
n_val = int(0.2 * n_samples)
shuffled_indices = torch.randperm(n_samples)
train_indices = shuffled_indices[:-n_val]
val_indices = shuffled_indices[-n_val:]
print(train_indices, val_indices)
# tensor([3, 1, 7, 9, 2, 6, 8, 0]) tensor([5, 4])
t_u_train = inp[train_indices]
t_c_train = target[train_indices]

t_u_val = inp[val_indices]
t_c_val = target[val_indices]


for epoch in range(nepochs):
	# forward pass
	t_p_train = model(t_u_train , *params)
	loss_train = loss_fn(t_p_train , t_c_train)
	with torch.no_grad():
		t_p_val = model(t_u_val , *params)
		loss_val = loss_fn(t_p_val , t_c_val)
		
	print("Epoch %d, Traing Loss %f, Validation loss %f" % (epoch, float(loss_train), float(loss_val)))
	# backward pass
	optimizer .zero_grad()
	loss_train.backward()
	optimizer .step()
print(params)
# tensor([1.0004e-01, 1.0135e-05], requires_grad=True)

实现CNN卷积神经网络

torchvision上有各种内置的数据集。我们使用MNIST数据集,并建立一个CNN模型。

让我们看看下面的例子。作为第一步,我们建立超参数。第二步是设置体系结构。最后一步是训练模型并进行预测。

import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision
import matplotlib.pyplot as plt
torch.manual_seed(1)  # 可重现

在前面的代码中,我们导入了使用MNIST数据集部署卷积神经网络模型所需的库。MNIST数据集是用于计算机视觉和图像处理的深度学习中最流行的数据集。

# 超参数
EPOCH = 1  # 训练输入数据n次,为了节约时间,这里我们训练一个epoch
BATCH_SIZE = 50  # 一次训练50个样本
LR = 0.001  # 学习率
DOWNLOAD_MNIST = True  # 如果你已经下载了,则置为False

# MNIST数据集
# torchvision.transforms.ToTensor(), 
# 归一化为[0., 1.0],形状为(C,H,W)的torch.FloatTensor类型)
train_data = torchvision.datasets.MNIST(root="./mnist", train=True, 
			transform=torchvision.transforms.ToTensor(),
			download=DOWNLOAD_MNIST)
# 显示一个样例
print(train_data.train_data.size())
print(train_data.train_labels.size())
plt.imshow(train_data.train_data[0].numpy(), cmap="gray")
plt.title("%i" % train_data.train_labels[0])
plt.show()
# torch.Size([60000, 28, 28])
# torch.Size([60000])

在这里插入图片描述

让我们使用加载器功能加载数据集.

# 数据加载器,batch形状为(50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# 选取2000样本来测试
test_data = torchvision.datasets.MNIST(root="./mnist/", train=False)
test_x = torch.unsqueeze(test_data.test_data, dim=1).type(torch.FloatTensor)[:2000]/255.
test_y = test_data.test_labels[:2000]

在卷积神经网络结构中,由于数据集的维度,我们无法对其建模以预测输出。上图中的输出层有汽车、卡车、货车和自行车等类。输入的自行车图像应该有CNN模型能够使用以及正确预测的特征。卷积层总是伴随着池化层,池化层可以是最大池化和平均池化。池化和卷积不断进行,直到维度降低到我们可以使用完全连接的简单神经网络来预测正确的类。

class CNN(nn.Module):
	def __init__(self):
		super(CNN, self).__init__()
		self.conv1 = nn.Sequential(
			nn.Conv2d(in_channels=1,out_channels=16, kernel_size=5, stride=1, padding=2),  
			# 输入尺寸为(1, 28, 28)
			# 如果想卷积前后尺寸相同,当stride=1时padding=(kernel_size-1)/2 
			# 输出尺寸为(16, 28, 28)
			nn.ReLU(),  # 激活函数
			nn.MaxPool2d(kernel_size=2),
			# 选择2x2区域的最大值,输出尺寸为(16, 14, 14)
			)
		self.conv2 = nn.Sequential(
			nn.Conv2d(16, 32, 5, 1, 2), # 输入尺寸(16, 14, 14)
			nn.ReLU(),
			nn.MaxPool2d(2), # 输出尺寸(32, 7, 7)
			)
		self.out = nn.Linear(32*7*7, 10)  # 全连接层,输出10分类
	def forward(self, x):
		x = self.conv1(x)
		x = self.conv2(x)
		x = x.view(x.size(0), -1)
		# 将conv2的输出拉伸到(batchsize, 32*7*7)
		output = self.out(x)
		return output, x  #输出x是为了可视化
cnn = CNN()
print(cnn)	
# CNN(
#   (conv1): Sequential(
#     (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
#     (1): ReLU()
#     (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#   )
#   (conv2): Sequential(
#     (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
#     (1): ReLU()
#     (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#   )
#   (out): Linear(in_features=1568, out_features=10, bias=True)
# )	
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)  # 优化所有的cnn参数
loss_func = nn.CrossEntropyLoss()  # 标签不是one-hot
from matplotlib import cm
try:from sklearn.manifold import TSNE;HAS_SK=True
except: HAS_SK=False;print("Please install sklearn for layer visualization, if not there")
def plot_with_labels(lowDWeights, labels):
	plt.cla()
	X,Y=lowDWeights[:,0], lowDWeights[:,1]
	for x, y, s in zip(X,Y,labels):
		c = cm.rainbow(int(255 * s /9))
		plt.text(x, y, s, backgroundcolor=c, fontsize=9)
	plt.xlim(X.min(), X.max())
	plt.ylim(Y.min(), Y.max())
	plt.title("visualize last layer")
plt.ion()
for epoch in range(EPOCH):
	for step, (x, y) in enumerate(train_loader):
		b_x = x
		b_y = y
		output = cnn(b_x)[0]  # cnn output
		loss = loss_func(output, b_y)   # cross entropy loss
		optimizer.zero_grad()
		loss.backward()
		optimizer.step()
		if step % 100 == 0:
			test_output, last_layer = cnn(test_x)
			pred_y = torch.max(test_output, 1)[1].data.squeeze()
			accuracy = (pred_y == test_y).sum().item()/float(test_y.size(0))
			print("Epoch: ", epoch, "| train loss: %.4f" % loss.item(), "| test accuracy: %.2f" % accuracy)
			if HAS_SK:
				# 可视化最后一层T-SNE
				tsne = TSNE(perplexity=30, n_components=2, init="pca", n_iter=5000)
				plot_only = 500
				low_dim_embs = tsne.fit_transform(last_layer.data.numpy()[:plot_only, :])
				labels = test_y.numpy()[:plot_only]
				plot_with_labels(low_dim_embs, labels)
plt.ioff()

在这里插入图片描述
在最后step/epoch中有相似数字的数字放在一起。在成功地训练了一个模型之后,下一步就是利用这个模型进行预测。下面的代码解释了预测过程。
输出对象编号为0、1、2,等等。以下是真实数据和预测数据。

# 打印10个预测结果
test_output, _ = cnn(test_x[:10])
pred_y = torch.max(test_output,1)[1].data.numpy().squeeze()
print(pred_y, "prediction number")
print(test_y[:10].numpy(), "real number")
# [7 2 1 0 4 1 4 9 5 9] prediction number
# [7 2 1 0 4 1 4 9 5 9] real number

重新加载模型

我们如何存储和重新加载一个已经训练过的模型?
考虑到深度学习模型的性质(通常需要更长的训练时间),计算过程给公司带来了巨大的成本。我们可以用新的输入重新训练模型并存储模型吗?

在生产环境中,我们通常不能同时进行训练和预测,因为训练过程需要很长时间。只有在训练过程完成后,才能应用预测服务。需要将训练过程与预测过程分离开来;因此,我们需要存储训练模型。

import torch
import matplotlib.pyplot as plt
torch.manual_seed(1)

# 采样数据
# x data tensor ,shape(100,1)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)  
# y data shape(100, 1) with noisy
y = x.pow(2) + 0.2*torch.rand(x.size())  
print(x.requires_grad, y.requires_grad)
# False False

前面的脚本包含一个因变量Y和一个自变量X作为样本数据点,用于创建神经网络模型。下面的save函数存储模型。net1对象是经过训练的神经网络模型,可以使用两种不同的协议进行存储:(1)保存整个神经网络模型,包含所有的权值和偏差,(2)只保存权值。如果训练的模型对象的大小很大,我们应该只保存权重的参数;如果训练的对象大小较小,则可以存储整个模型。

def save():
	net1 = torch.nn.Sequential(
		torch.nn.Linear(1, 10),
		torch.nn.ReLU(),
		torch.nn.Linear(10,1)
	)
	optimizer = torch.optim.SGD(net1.parameters(), lr=0.5)
	loss_func = torch.nn.MSELoss()
	for t in range(100):
		prediction = net1(x)
		loss = loss_func(prediction, y)
		optimizer.zero_grad()
		loss.backward()
		optimizer.step()
	# 绘制结果
	plt.figure(1, figsize=(10, 3))
	plt.subplot(131)
	plt.title("Net1")
	plt.scatter(x.data.numpy(), y.data.numpy())
	plt.plot(x.data.numpy(), prediction.data.numpy(), "r-", lw=5)
	# 保存模型的两种方式
	torch.save(net1, "net.pkl")  # 保存整个模型包含参数
	torch.save(net1.state_dict(), "net_params.pkl")  # 仅保存参数

使用load函数,可以将预先建立的神经网络模型重新加载到现有的PyTorch会话。为了测试net1对象并进行预测,我们加载net1对象并将模型存储为net2。通过使用net2对象,我们可以预测结果变量。最后一行代码中的prediction.data.numpy()显示了预测的结果。

def restore_net():
	net2 = torch.load("net.pkl")
	prediction = net2(x)
	# 绘制结果
	plt.subplot(132)
	plt.title("Net2")
	plt.scatter(x.data.numpy(), y.data.numpy())
	plt.plot(x.data.numpy(), prediction.data.numpy(), "r-", lw=5)

加载整个神经网络的pickle文件比较慢;然而,如果我们只对一个新的数据集进行预测,我们只能以pickle格式加载模型的参数,而不能加载整个网络。

def restore_params():
	net3 = torch.nn.Sequential(
		torch.nn.Linear(1, 10),
		torch.nn.ReLU(),
		torch.nn.Linear(10,1)
	)
	net3.load_state_dict(torch.load("net_params.pkl"))
	prediction = net3(x)
	#绘制结果
	plt.subplot(133)
	plt.title("Net3")
	plt.scatter(x.data.numpy(), y.data.numpy())
	plt.plot(x.data.numpy(), prediction.data.numpy(),"r-",lw=5)
	plt.show()

重用模型。restore函数保证了训练后的参数可以被模型重用。要恢复模型,可以使用load_state_dict()函数来加载模型的参数。如果我们在图中看到以下三个模型,它们是相同的,因为net2和net3是net1的副本。

save() # save net1
restore_net()  # restore entire net (may slow)
restore_params()  # restore only the net parameters

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值