上一节中,我们使用autograd的包来定义模型并求导。本节中,我们将使用torch.nn包来构建神经网络。

一个nn.Module包含各个层和一个forward(input)方法,该方法返回output.

上图是一个简单的前馈神经网络。它接受一个输入。然后一层接着一层地传递。最后输出计算的结果。

神经网络模型的训练过程

神经网络的典型训练过程如下:

  1. 定义包含一些可学习的参数(或者叫做权重)的神经网络模型。
  2. 在数据集上迭代。
  3. 通过神经网络处理输入。
  4. 计算损失函数(输出结果和正确值的差值大小)。
  5. 将梯度反向传播回网络的权重等参数。
  6. 更新网络的权重。主要使用以下的更新原则:weight = weight - learining_rate*gradient.

完整的代码如下(跟官方比,做了详细的中文注释):

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data.dataloader as Data
import torchvision #torchvision模块包括了一些图像数据集,如MNIST,cifar10等
import matplotlib.pyplot as plt # 1 准备数据
# 所有的torchvision.datasets数据集的类都是torch.utils.data.Dataset的子类,实现了__getitem__和__len__方法。故可传递给torch.utils.data.DataLoader来加载
# 创建用于Train的数据集,若root目录无数据集,则Download;若root目录有数据集,则从PIL图像数据转换为Tensor
train_data = torchvision.datasets.MNIST(root='./mnist',train=True,transform=torchvision.transforms.ToTensor(),download=True)
test_data = torchvision.datasets.MNIST(root='./mnist',train=False,transform=torchvision.transforms.ToTensor())
print("train_data:",train_data.data.size())
print("train_labels:",train_data.targets.size())
print("test_data:",test_data.data.size()) # 根据数据集创建响应的dataLoader
# shuffle(bool, 可选) – 如果每一个epoch内要打乱数据,就设置为True(默认:False)
train_loader = Data.DataLoader(dataset=train_data, batch_size=50, shuffle=True)
test_loader = Data.DataLoader(dataset=test_data, batch_size=500, shuffle=False) # 2 创建模型
class CNN(nn.Module): # 定义了一个类,名字叫CNN
#注意: 在模型中必须要定义 `forward` 函数,`backward` 函数(用来计算梯度)会被`autograd`自动创建。 可以在 `forward` 函数中使用任何针对 `Tensor` 的操作。
def __init__(self): # 每个类都必须有的构造函数,用来初始化该类
super(CNN, self).__init__() # 先调用父类的构造函数
# 1 input image channel, 6 output channels, 5x5 square convolution
# kernel
# 本函数配置了卷积层和全连接层的维度
# Conv2d(in_cahnnels, out_channels, kernel_size, stride, padding=0 ,...)
self.conv1 = nn.Conv2d(1, 16, 5, 1, 2) # 卷积层1: 二维卷积层, 1x28x28,16x28x28, 卷积核大小为5x5
self.conv2 = nn.Conv2d(16, 32, 5, 1, 2) # 卷积层2: 二维卷积层, 16x14x14,32x14x14, 卷积核大小为5x5
# an affine(仿射) operation: y = Wx + b # 全连接层1: 线性层, 输入维度32x7x7,输出维度128
self.fc1 = nn.Linear(32 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10) # 全连接层2: 线性层, 输入维度128,输出维度10 def forward(self, x): #定义了forward函数
# Max pooling over a (2, 2) window
conv1_out = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # 先卷积,再池化
# If the size is a square you can only specify a single number
conv2_out = F.max_pool2d(F.relu(self.conv2(conv1_out)), 2) # 再卷积,再池化
res = conv2_out.view(conv2_out.size(0), -1) # 将conv3_out展开成一维(扁平化)
fc1_out = F.relu(self.fc1(res)) # 全连接1
out = self.fc2(fc1_out) # 全连接2
#return out
return F.log_softmax(out),fc1_out #返回softmax后的Tensor,以及倒数第二层的Tensor(以进行低维Tensor的可视化) cnn = CNN() #新建了一个CNN对象,其实是一系列的函数/方法的集合
cnn = cnn.cuda() #*.cuda()将模型的所有参数和缓存移动到GPU
print(cnn) def plot_with_labels(lowDWeights, labels):
plt.cla() #clear当前活动的坐标轴
X, Y = lowDWeights[:, 0], lowDWeights[:, 1] #把Tensor的第1列和第2列,也就是TSNE之后的前两个特征提取出来,作为X,Y
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.text(x, y, str(s),color=c,fontdict={'weight': 'bold', 'size': 9}) #在指定位置放置文本
plt.xlim(X.min(), X.max());
plt.ylim(Y.min(), Y.max());
plt.title('Visualize last layer');
plt.show();
plt.pause(0.01) # 3 定义损失函数-这里默认是交叉熵函数
loss_func = torch.nn.CrossEntropyLoss() # 4 初始化:优化器
optimizer = optim.Adam(cnn.parameters(), lr=0.01) #list(cnn.parameters())会给出一个参数列表,记录了所有训练参数(W和b)的数据
# optimizer =optim.Adam([ {'params': cnn.conv1.weight}, {'params': cnn.conv1.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.conv2.weight}, {'params': cnn.conv2.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.fc1.weight}, {'params': cnn.fc1.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.fc2.weight}, {'params': cnn.fc2.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.conv3.weight}, {'params': cnn.conv3.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.conv4.weight}, {'params': cnn.conv4.bias, 'lr': 0.002,'weight_decay': 0 },
# {'params': cnn.conv5.weight}, {'params': cnn.conv5.bias, 'lr': 0.002,'weight_decay': 0 },], lr=0.001, weight_decay=0.0001) # 5 训练:
def train(epoch):
print('epoch {}'.format(epoch))
#直接初始化为0的是标量,tensor调用item()将返回标量值
train_loss = 0
train_acc = 0
#step是enumerate()函数自带的索引,从0开始
for step, (batch_x, batch_y) in enumerate(train_loader):
# 把batch_x和batth_y移动到GPU
batch_x = batch_x.cuda()
batch_y = batch_y.cuda()
# 正向传播
out,_ = cnn(batch_x)
loss = loss_func(out, batch_y)
train_loss += loss.item()
# torch.max(tensor,dim:int):tensor找到第dim维度(第0维度是数据下标)上的最大值
# return: 第一个Tensor是该维度的最大值,第二个Tensor是最大值相应的下标
pred = torch.max(out, 1)[1]
# 直接对逻辑量进行sum,将返回True的个数
train_correct = (pred == batch_y).sum()
train_acc += train_correct.item()
if step % 20 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch, step * len(batch_x), len(train_loader.dataset),100. * step / len(train_loader), loss.item())) #反向传播
optimizer.zero_grad() # 所有参数的梯度清零
loss.backward() #即反向传播求梯度
optimizer.step() #调用optimizer进行梯度下降更新参数
print('Train Loss: {:.6f}, Acc: {:.6f}'.format(train_loss / (len(train_data)), train_acc / (len(train_data)))) from matplotlib import cm try:
from sklearn.manifold import TSNE; HAS_SK = True
except:
HAS_SK = False; print('Please install sklearn for layer visualization') # 6 准确率
def test():
cnn.eval()
eval_loss = 0
eval_acc = 0
# 打开imshow()交互模式:更新图像后直接执行以后的代码,不阻塞在plt.show()
plt.ion()
#无需反向传播计算梯度,不需要进行求导运算
with torch.no_grad():
for step, (batch_x, batch_y) in enumerate(test_loader):
batch_x = batch_x.cuda()
batch_y = batch_y.cuda()
out,last_layer = cnn(batch_x)
loss = loss_func(out, batch_y)
#loss = += F.nll_loss(out, batch_y, size_average=False).item()
eval_loss += loss.item()
pred = torch.max(out, 1)[1]
num_correct = (pred == batch_y).sum()
eval_acc += num_correct.item()
#若需绘图,将下面代码块注释去掉
if step % 100 == 0:
#t-SNE 是一种非线性降维算法,非常适用于高维数据降维到2维或者3维,进行可视化
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
#最多只画500个点
plot_only = 500
#fit_transform函数把last_layer的Tensor降低至2个特征量,即3个维度(2个维度的坐标系)
low_dim_embs = tsne.fit_transform(last_layer.cpu().data.numpy()[:plot_only, :])
labels = batch_y.cpu().numpy()[:plot_only]
plot_with_labels(low_dim_embs, labels)
#若需绘图,将上面代码块注释去掉
print('Test Loss: {:.6f}, Accuracy: {}/{} ({:.2f}%'.format(eval_loss / (len(test_data)),eval_acc, len(test_data) ,100.*eval_acc / (len(test_data))))
plt.ioff() # 共训练/测试 20轮
# 每轮训练整个数据集1遍,每轮有len(dataset)/batch_size次训练
# 每次训练要训练batch_size个数据
# 每个batch的数据,第一个维度是数据的下标:0,1,2,...,batch_size-1
for epoch in range(1, 21):
train(epoch)
test()

除了上面代码段中的实现,你可能还需要了解以下知识:

损失函数与autograd反向传播

一个损失函数需要一对输入:模型输出和目标,然后计算一个值来评估输出距离目标有多远。

根据之前学习的理论,一般是计算y和y'的交叉熵.

例如,对于线性拟合案例,损失函数就是均方误差,即MSE.

output = net(input)
target = torch.randn(10) # a dummy target, for example
target = target.view(1, -1) # make it the same shape as output
criterion = nn.MSELoss() loss = criterion(output, target)
print(loss)

将输出:

tensor(1.3389, grad_fn=<MseLossBackward>)

现在,如果你跟随损失到反向传播路径,可以使用它的 .grad_fn 属性,你将会看到一个这样的计算图:

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

所以,当我们调用 loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True 的张量将会让他们的 grad 张量累计梯度。

为了演示,我们将跟随以下步骤来跟踪反向传播。

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU

输出:

<MseLossBackward object at 0x7fab77615278>
<AddmmBackward object at 0x7fab77615940>
<AccumulateGrad object at 0x7fab77615940>

为了实现反向传播损失,我们所有需要做的事情仅仅是使用 loss.backward()。你需要清空现存的梯度,要不梯度将会和现存的梯度累计到一起。

现在我们调用 loss.backward() ,然后看一下 con1 的偏置项在反向传播之前和之后的变化。

net.zero_grad()     # zeroes the gradient buffers of all parameters,清空现存的梯度
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

输出:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0054, 0.0011, 0.0012, 0.0148, -0.0186, 0.0087])

上面介绍了如何使用损失函数

更新神经网络参数

最简单的更新规则就是随机梯度下降:weight = weight - learning_rate * gradient

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

复杂的神经网络参数可这样使用:

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01) # in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update

t-SNE:数据可视化

本节摘自 这里

数据降维与可视化——t-SNE

t-SNE 是目前来说效果最好的数据降维与可视化方法,但是它的缺点也很明显,比如:占内存大,运行时间长。但是,当我们想要对高维数据进行分类,又不清楚这个数据集有没有很好的可分性(即同类之间间隔小,异类之间间隔大),可以通过 t-SNE 投影到 2 维或者 3 维的空间中观察一下。如果在低维空间中具有可分性,则数据是可分的;如果在高维空间中不具有可分性,可能是数据不可分,也可能仅仅是因为不能投影到低维空间。

t-distributed Stochastic Neighbor Embedding(t-SNE)

t-SNE(TSNE)将数据点之间的相似度转换为概率。原始空间中的相似度由高斯联合概率表示,嵌入空间的相似度由“学生 t 分布”表示。

虽然 Isomap,LLE 和 variants 等数据降维和可视化方法,更适合展开单个连续的低维的 manifold。但如果要准确的可视化样本间的相似度关系,如对于下图所示的 S 曲线(不同颜色的图像表示不同类别的数据),t-SNE 表现更好。因为t-SNE 主要是关注数据的局部结构。

通过原始空间和嵌入空间的联合概率的 Kullback-Leibler(KL)散度来评估可视化效果的好坏,也就是说用有关 KL 散度的函数作为 loss 函数,然后通过梯度下降最小化 loss 函数,最终获得收敛结果。

使用 t-SNE 的缺点大概是:

  1. t-SNE 的计算复杂度很高,在数百万个样本数据集中可能需要几个小时,而 PCA 可以在几秒钟或几分钟内完成
  2. Barnes-Hut t-SNE 方法(下面讲)限于二维或三维嵌入。
  3. 算法是随机的,具有不同种子的多次实验可以产生不同的结果。虽然选择 loss 最小的结果就行,但可能需要多次实验以选择超参数。
  4. 全局结构未明确保留。这个问题可以通过 PCA 初始化点(使用init ='pca')来缓解。

其参数及描述如下:

n_components	    int, 默认为 2,嵌入空间的维度(嵌入空间的意思就是结果空间)
perplexity float, 默认为 30,数据集越大,需要参数值越大,建议值位 5-50
early_exaggeration float, 默认为 12.0,控制原始空间中的自然集群在嵌入式空间中的紧密程度以及它们之间的空间。 对于较大的值,嵌入式空间中自然群集之间的空间将更大。 再次,这个参数的选择不是很关键。 如果在初始优化期间成本函数增加,则可能是该参数值过高。
learning_rate float, default:200.0, 学习率,建议取值为 10.0-1000.0
n_iter int, default:1000, 最大迭代次数
n_iter_without_progress int, default:300, 另一种形式的最大迭代次数,必须是 50 的倍数
min_grad_norm float, default:1e-7, 如果梯度低于该值,则停止算法
metric string or callable, 精确度的计量方式
init string or numpy array, default:”random”, 可以是’random’, ‘pca’或者一个 numpy 数组(shape=(n_samples, n_components)。
verbose int, default:0, 训练过程是否可视
random_state int, RandomState instance or None, default:None,控制随机数的生成
method string, default:’barnes_hut’, 对于大数据集用默认值,对于小数据集用’exact’
angle float, default:0.5, 只有method='barnes_hut'时可用

其具有的属性有:

attributes	description
embedding_ 嵌入向量
kl_divergence 最后的 KL 散度
n_iter_ 迭代的次数

其Method有:

Methods	        description
fit 将 X 投影到一个嵌入空间
fit_transform 将 X 投影到一个嵌入空间并返回转换结果
get_params 获取 t-SNE 的参数
set_params 设置 t-SNE 的参数

下列代码详细解释了MNIST数据集可进行的可视化:

# coding='utf-8'
"""t-SNE 对手写数字进行可视化"""
from time import time
import numpy as np
import matplotlib.pyplot as plt from sklearn import datasets
from sklearn.manifold import TSNE def get_data():
digits = datasets.load_digits(n_class=6)
data = digits.data
label = digits.target
n_samples, n_features = data.shape
return data, label, n_samples, n_features def plot_embedding(data, label, title):
x_min, x_max = np.min(data, 0), np.max(data, 0)
data = (data - x_min) / (x_max - x_min) fig = plt.figure()
ax = plt.subplot(111)
for i in range(data.shape[0]):
plt.text(data[i, 0], data[i, 1], str(label[i]),
color=plt.cm.Set1(label[i] / 10.),
fontdict={'weight': 'bold', 'size': 9})
plt.xticks([])
plt.yticks([])
plt.title(title)
return fig def main():
data, label, n_samples, n_features = get_data()
print('Computing t-SNE embedding')
tsne = TSNE(n_components=2, init='pca', random_state=0)
t0 = time()
result = tsne.fit_transform(data)
fig = plot_embedding(result, label,
't-SNE embedding of the digits (time %.2fs)'
% (time() - t0))
plt.show(fig) if __name__ == '__main__':
main()

PyTorch学习笔记6--案例2:PyTorch神经网络(MNIST CNN)的更多相关文章

  1. 莫烦pytorch学习笔记(八)——卷积神经网络(手写数字识别实现)

    莫烦视频网址 这个代码实现了预测和可视化 import os # third-party library import torch import torch.nn as nn import torch ...

  2. 【pytorch】学习笔记(四)-搭建神经网络进行关系拟合

    [pytorch学习笔记]-搭建神经网络进行关系拟合 学习自莫烦python 目标 1.创建一些围绕y=x^2+噪声这个函数的散点 2.用神经网络模型来建立一个可以代表他们关系的线条 建立数据集 im ...

  3. UFLDL深度学习笔记 (六)卷积神经网络

    UFLDL深度学习笔记 (六)卷积神经网络 1. 主要思路 "UFLDL 卷积神经网络"主要讲解了对大尺寸图像应用前面所讨论神经网络学习的方法,其中的变化有两条,第一,对大尺寸图像 ...

  4. 神经网络与深度学习笔记 Chapter 6之卷积神经网络

    深度学习 Introducing convolutional networks:卷积神经网络介绍 卷积神经网络中有三个基本的概念:局部感受野(local receptive fields), 共享权重 ...

  5. 深度学习基础-基于Numpy的卷积神经网络(CNN)实现

    本文是深度学习入门: 基于Python的实现.神经网络与深度学习(NNDL)以及动手学深度学习的读书笔记.本文将介绍基于Numpy的卷积神经网络(Convolutional Networks,CNN) ...

  6. Pytorch学习笔记(二)---- 神经网络搭建

    记录如何用Pytorch搭建LeNet-5,大体步骤包括:网络的搭建->前向传播->定义Loss和Optimizer->训练 # -*- coding: utf-8 -*- # Al ...

  7. Pytorch学习笔记(一)---- 基础语法

    书上内容太多太杂,看完容易忘记,特此记录方便日后查看,所有基础语法以代码形式呈现,代码和注释均来源与书本和案例的整理. # -*- coding: utf-8 -*- # All codes and ...

  8. 【pytorch】pytorch学习笔记(一)

    原文地址:https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html 什么是pytorch? pytorch是一个基于p ...

  9. 【深度学习】Pytorch 学习笔记

    目录 Pytorch Leture 05: Linear Rregression in the Pytorch Way Logistic Regression 逻辑回归 - 二分类 Lecture07 ...

随机推荐

  1. Docker部署Mysql实践

    前言:由于Docker部署容器时,没有指定IP,当机器重启后,容器的IP会变化,所以在创建容器的时候,最好能固定IP:同时,在Ubuntu系统中,每次执行命令,都需要root权限,命令需要加sudo标 ...

  2. 跟我一起写 Makefile(十四)

    使用make更新函数库文件 ----------- 函数库文件也就是对Object文件(程序编译的中间文件)的打包文件.在Unix下,一般是由命令"ar"来完成打包工作. 一.函数 ...

  3. repeatedly function in Clojure

    user=> (doc repeatedly) clojure.core/repeatedly ([f] [n f]) Takes a function of no args, presumab ...

  4. IDEA Error:java: 无效的源发行版: 11错误

    IDEA Error:java: 无效的源发行版: 11错误 今天在网上下载了一个项目到本地运行报错 Error: Java : 无效的源发行版: 11 ,上网查了很多找到问题所在.项目的 JDK(P ...

  5. awk-05-数组

    数组 数组: 存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值 awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标 数组元素的键和值存储在 awk 程序内部 ...

  6. Python语言系列-07-面向对象2

    重构父类__init__方法 #!/usr/bin/env python3 # author:Alnk(李成果) # 需求:Dog类要新增一个实例属性,但是Cat类不需要 class Animal(o ...

  7. Android WorkManager使用入门

    WorkManager使用入门 WorkManager提供了任务调度功能,我们可以对工作进行标记或命名. 我们用一个示例来演示如何使用WorkManager.本文使用Kotlin. 入门示例 grad ...

  8. 【原创】oracle提权执行命令工具oracleShell v0.1

    帮一个兄弟渗透的过程中在内网搜集到了不少oracle连接密码,oracle这么一款强大的数据库,找了一圈发现没有一个方便的工具可以直接通过用户名密码来提权的.想起来自己之前写过一个oracle的连接工 ...

  9. Error running 'Tomcat 9.0.24': port out of range:-1

    修改tomcat安装目录下的conf--server.xml检查一下,端口不能是-1, 一般会选80,或者1-65535之间的任意一个整数

  10. NOIP 模拟 $14\; \text{队长快跑}$

    题解 \(by\;zj\varphi\) 一道很妙的 \(dp\) 题,方程状态不好设置,细节也不少 看到数据范围,直接想离散化 设 \(f_{i,j}\) 表示处理完前 \(i\) 个水晶,其中摧毁 ...