ps:虽然初恋是Tensorflow,但是在用tensorflow-gpu跑代码的时候总是遇到问题;虽然keras框架确实好用,但是pytorch的风格确实相对而言更有pythonic的味道,而且学术界大多在用pytorch,所以果断学习一波pytorch。
本文基于Deep-Learning-with-PyTorch-Chinese
原网址如下:
Deep-Learning-with-PyTorch-Chinese
PyTorch是极容易入门的深度学习工具(貌似与Python有关的工具库都说自己极容易入门。。)
它是一个深度学习库,可以创建Tensor(张量)这种数据结构(类似于Numpy中的矩阵,但通常tensor指的是3维以上的矩阵),然后可以调用其中的深度学习方法,构建模型和训练模型。
学习PyTorch只需要具备两个条件:
1.一丢丢Python的编程经验
2.不断实践的态度(个人认为任何与编程有关的活动都需要不断实践)
附:
加一个条件:
一定要有恒心,要坚持!!!!
静态图模式:延迟执行,类似于在高级语言编程中,写一个函数,然后传递参数,函数中包含了一些操作,直到你传递参数时再对参数进行操作得到结果。也就是计算过程已经写好了,程序知道了这些计算操作之间的关联。函数定义在此处,执行时在别处。
动态图模式:即时执行,计算操作被分解为独立的表达式,执行到这些表达式我再立即运算,程序对这些表达式之间的内在关联没有预先概念。
静态图模式的调试比较困难,因为异常通常会缺失关于错误的具体信息,并且Python的调试工具也看不到数据的中间状态。另外,静态图通常不能与标准Python控制流很好的融合:它们是事实上的特定于域的语言,是在宿主语言之上实现的
tensorflow1.x版本采用静态图模式
tensorflow2.x版本提出了eager mode模式,弥补了静态图的一些不足
pytorch一直采用动态图模式。但也增加了称为TorchScript的延迟执行图模式运行引擎。
实际上python编写的部分占少数,基于性能考虑大多数是用C++,CUDA编写。
CUDA 是NVIDIA提供的类似C++的语言,用于在NVIDIA系列的GPU上大规模并行运行。(基本上用GPU跑深度学习最好用NVIDIA,AMD的GPU在深度学习方面还有所欠缺)
1.提供tensor的库
PyTorch的核心是提供多维数组的库,在PyTorch术语中这些多维数组称为张量(tensor),而torch模块则提供了可对其进行扩展操作的库。张量和相关操作都可以在CPU或GPU上运行。相比于CPU,在GPU上运行可以显著的提高速度,而且使用PyTorch最多需要一到两个额外的函数来调用GPU。
2.允许张量跟踪对其所执行的操作,并通过反向传播来计算输出相对于其任何输入的导数。此功能由张量自身提供,并通过torch.autograd进一步扩展完善。
3.PyTorch中用于构建神经网络的核心模块位于torch.nn中,该模块提供了常见的神经网络层和其他架构组件。全连接层、卷积层、激活函数和损失函数都能在该模块找到
深度学习模型会自动地学习将样本中的输入和期望输出建立联系。
PyTorch等深度学习库可以使你有效地构建和训练神经网络模型。
PyTorch在专注于灵活性和速度的同时最大程度地降低了认知成本。它默认使用即时执行模式。
TorchScript是一种可以在C++中调用的预编译的延迟执行模型。
自2017年初PyTorch问世以来,深度学习工具的生态系统已显着巩固完善。
PyTorch提供了多个实用的功能库来帮助深度学习项目开发。
总所周知,电路只认识0和1,因此电路要想认识其他事物,就需要编码器,而要将编码的内容翻译成我们所知道的事物,就需要译码器。
同样的,神经网络也是如此,它只认识矩阵,也需要将事物进行编码和解码。将具体事物编码输入进网络处理然后再得到输出,再对输出进行解码得到我们期望的结果。而编码,解码并且要让网络能够理解具体事物意味着需要一种数据结构来存储和处理数据,这个数据结构就是Tensor。
深度学习有许多应用,这些应用往往包括以某种形式获取数据(例如图像或文本),并以另一种形式生成数据(例如标签,数字或更多文本)。从这个角度来看,深度学习包括构建一个将数据从一种表示转换为另一种表示的系统。这种转换是通过从一系列样本中提取的共性来驱动的,这些共性能够反映期望的映射关系。
这个过程的第一步是将输入转换为浮点数(也可以是其他类型的数据)。因为网络使用浮点数来处理信息,所以我们需要对真实世界的数据进行编码,使其成为网络可理解的形式,然后再将输出解码回我们可以理解并用于某种用途的形式。
从一种数据形式到另一种数据形式的转换通常是由深度神经网络分层次学习的,这意味着我们可以将层次之间转换得到的数据视为一系列中间表示(intermediate representation)。以图像识别为例,浅层的表示可以是特征(例如边缘检测)或纹理(例如毛发),较深层次的表征可以捕获更复杂的结构(例如耳朵、鼻子或眼睛)。
通常,这种中间表示形式是浮点数的集合,这些浮点数表征输入并捕获数据中的结构,从而有助于描述输入如何映射到神经网络的输出。这种表征是特定于当前任务的,可以从相关示例中学习。这些浮点数的集合及其操作是现代AI的核心。请务必牢记,这些中间表示是将输入与前一层神经元权重相结合的结果,每个中间表示对于之前的输入都是唯一的。
在开始将数据转换为浮点输入之前,我们必须对PyTorch如何处理和存储数据(输入、中间表示以及输出)有深刻的了解。为此,PyTorch引入了一个基本的数据结构:张量(tensor)。对于来自数学、物理学或工程学的人来说,张量一词是与空间、参考系以及它们之间的转换的概念是捆绑在一起的。对于其他人来说,张量是指将向量(vector)和矩阵(matrix)推广到任意维度。与张量相同概念的另一个名称是多维数组(multidimensional array)。张量的维数与用来索引张量中某个标量值的索引数一致。
import torch
torch.zeros(3,2) #传递张量维度,创建一个全0的张量,与numpy中的zeros差不多
torch.ones(3,3) #传递张量维度,创建一个全1的张量,与numpy中的ones差不多
points = torch.tensor([[1,2],[3,4],[5,6]) #将列表传递给张量构造函数tensor() , 创建一个张量
pytorch中tensor中的数据时连续分配在内存中,由torch.Storage实例管理
points_storage = points.storage() #points的存储实例是一维的连续数组,大小为2*3 = 6
points_storage[0] = 2 #通过更改存储的值来更改引用它的张量的内容
除了存放存储外,为了索引存储,张量依赖于几条明确定义它们的信息:尺寸(size)、存储偏移(storage offset)和步长(stride)。
尺寸(或按照NumPy中的说法:形状shape)是一个元组,表示张量每个维度上有多少个元素。
存储偏移是存储中与张量中的第一个元素相对应的索引。
步长是在存储中为了沿每个维度获取下一个元素而需要跳过的元素数量。
用下标i和j访问二维张量等价于访问存储中的 storage_offset + stride[0] * i + stride[1] * j元素。偏移通常为0,但如果此张量是一个可容纳更大张量的存储的视图,则偏移可能为正值。
通过dtype查看数据类型,这与numpy一样
torch.float32或torch.float —— 32位浮点数
torch.float64或torch.double —— 64位双精度浮点数
torch.float16或torch.half —— 16位半精度浮点数
torch.int8 —— 带符号8位整数
torch.uint8 —— 无符号8位整数
torch.int16或torch.short —— 带符号16位整数
torch.int32或torch.int —— 带符号32位整数
torch.int64或torch.long —— 带符号64位整数
每个torch.float、torch.double等等都有一个与之对应的具体类:torch.FloatTensor、torch.DoubleTensor等等。torch.int8对应的类是torch.CharTensor,而torch.uint8对应的类是torch.ByteTensor。torch.Tensor(最常用)是torch.FloatTensor的别名,即默认数据类型为32位浮点型。
同时,使用.方法和to()进行类型转换
double_points = torch.zeros(10, 2).to(torch.double)
short_points = torch.ones(10, 2).to(dtype=torch.short)
大致与numpy中的一样,不再详细介绍。
要从points张量创建NumPy数组,请调用
points = torch.ones(3, 4)
points_np = points.numpy()
points_np
从NumPy数组创建Tensor张量,可以这么写
points_numpy = np.ones(3,4)
points_tensor = torch.from_numpy(points_numpy)
动态创建张量是很不错的,但是如果其中的数据对你来说具有价值,那么你可能希望将其保存到文件中并在某个时候加载回去。毕竟你可不想每次开始运行程序时都从头开始重新训练模型!PyTorch内部使用pickle来序列化张量对象和实现用于存储的专用序列化代码。下面展示怎样将points张量保存到ourpoints.t文件中:
torch.save(points, '../../data/chapter2/ourpoints.t')
或者
传递文件描述符代替文件
with open('../../data/chapter2/ourpoints.t','wb') as f:
torch.save(points, f)
要注意的是.t文件只能通过pytorch打开,与其他软件并不互通。
互通的格式采用HDF5,HDF5是一种可移植的、广泛支持的格式,用于表示以嵌套键值字典形式组织的序列化多维数组。Python通过h5py库支持HDF5,该库以NumPy数组的形式接收和返回数据。
此时,你可以通过将points张量转换为NumPy数组(如前所述,此操作几乎零花费)并将其传递给create_dataset函数来保存points张量:
import h5py
f = h5py.File('../../data/chapter2/ourpoints.hdf5', 'w')
dset = f.create_dataset('coords', data=points.numpy())
f.close()
除了dtype之外,PyTorch张量还具有设备(device)的概念,这是在设置计算机上放张量(tensor)数据的位置。 通过为构造函数指定相应的参数,可以在GPU上创建张量:
points_gpu = torch.tensor([[1.0, 4.0], [2.0, 1.0], [3.0, 4.0]], device='cuda')
或者使用to()方法
points_gpu = points.to(device='cuda')
现在数据已经存放在本地的GPU中,当在张量上运行数字运算时,你可以看见很好的加速效果。并且,这个新GPU支持的张量的类也更改为torch.cuda.FloatTensor(一开始输入的类型为torch.FloatTensor;torch.cuda.DoubleTensor等等也存在对应关系)。在大部分样例中,基于CPU和GPU的张量都公开面向用户相同的API,这使得与繁琐数字运算过程中无关的代码的编写更加容易。
如果你的机器拥有多个GPU,你可以通过传递从零开始的整数来确定张量分配给哪个GPU,该整数标志着机器上的GPU下标:
points_gpu = points.to(device='cuda:0')
值得一提的是,使用to方法时,可以通过提供device和dtype参数来同时更改位置和数据类型。
需要注意的是:有少量的操作仅作为张量对象的方法存在。你可以通过名称中的下划线来识别它们,例如zero_,下划线标识表明该方法是就地(inplace)运行的,即直接修改输入而不是创建新的输出并返回。例如,zero_方法会将输入的所有元素清零。任何不带下划线的方法都将保持源张量不变并返回新的张量:
a = torch.ones(3, 2)
a.zero_()
a
官方文档将张量API分为如下几组:
创建操作 —— 构造张量的函数,例如ones和from_numpy;
索引、切片、联接和变异操作 —— 更改形状、步长或张量内容,例如transpose;
数学操作 —— 通过计算来操纵张量内容的函数:
按点(pointwise)操作 —— 将函数分别应用于每个元素(例如abs和cos)的函数
简化(reduction)操作 —— 通过张量迭代计算合计值的函数,例如mean、std和norm;
比较操作 —— 用于比较张量的函数,例如equal和max;
频谱操作 —— 在频域中转换和运行的函数,例如stft和hamming_window;
其他操作 —— 一些特殊函数,例如对于向量的cross,对于矩阵的trace;
BLAS和LAPACK操作 —— 遵循BLAS(Basic Linear Algebra Subprograms)规范的函数,用于标量、向量与向量、矩阵与向量和矩阵与矩阵的运算。
随机采样操作 —— 从概率分布中随机采样值的函数,例如randn和normal;
序列化操作 —— 用于保存和加载张量的函数,例如save和load;
并行操作 —— 控制并行CPU执行的线程数的函数,例如set_num_threads;
先简单介绍一下常用的tensor运算API
输入参数:Tensor数据类型的变量
输出:输入参数的绝对值
输入参数:既可以全部是tensor数据类型的变量,也可以一个是tensor数据类型的变量,另一个是标量
输出:返回输入参数的求和结果
与numpy中的矩阵加法运算一样
对输入参数按照自定义的范围进行裁剪,最后将参数裁剪的结果作为输出
输入参数:3个,分别是需要进行裁剪的tensor数据类型变量,裁剪的上边界以及下边界
具体裁剪过程:使用变量中的每一个元素分别和裁剪的上边界和裁剪的下边界的值进行比较,如果元素的值小于裁剪的下边界的值,该元素就被重写成裁剪下边界的值;同理,如果元素的值大于裁剪上边界的值,该元素就被重写成裁剪的上边界的值
输出:将裁剪结果输出
输入:既可以全部是tensor数据类型的变量,也可以一个是tensor数据类型的变量,另一个是标量
输出:返回输入参数的求商结果
输入:既可以全部是tensor数据类型的变量,也可以一个是tensor数据类型的变量,另一个是标量
输出:返回输入参数的求积结果
输入:既可以全部是tensor数据类型的变量,也可以一个是tensor数据类型的变量,另一个是标量
输出:返回输入参数的求幂结果
返回输入参数的求积结果作为输出,但求积方式与torch.mul不同,torch.mm()采用矩阵乘法,因此被传入的参数被当做矩阵进行处理,参数的维度需要满足矩阵乘法的条件,否则不能计算
返回输入参数的求积结果作为输出,torch.mv()运用矩阵与向量之间的乘法规则运算,被传入的参数中第1个参数代表矩阵,第2个采纳数代表向量,顺序不能颠倒。
神经网络将浮点表示形式转换为其他浮点表示形式,起始和结束的表示形式通常是人类可以理解的,但中间表示则不是这样。
这些浮点表示存储在张量中。
张量是多维数组,它是PyTorch中的基本数据结构。
PyTorch有一个全面的标准库,用于张量创建、操作和数学运算。
张量可以序列化存储到磁盘上也可以加载回来。
PyTorch中的所有张量操作都可以在CPU和GPU上执行,无需更改代码。
PyTorch使用结尾下划线标识来表示就地操作(例如Tensor.sqrt_)
张量是PyTorch中数据的基础。神经网络将张量输入并产生张量作为输出。实际上,神经网络内部和优化期间的所有操作都是张量之间的操作,而神经网络中的所有参数(例如权重和偏差)也都是张量。掌握如何在张量上执行操作并对其进行有效索引是成功使用PyTorch等工具的关键。现在你已经了解了张量的基本知识,你对它们灵活性的理解将会大大增强。
误差反向传播中用到的求导链式法则是BP神经网络的精髓。因此熟悉pytorch求导的方法将有助于我们迅速构建神经网络。
torch.autograd 主要功能是完成神经网络反向传播中的链式求导,减少手动求导中的不必要的麻烦。tensor可以记住它们来自什么运算以及其起源的父张量,并且提供相对于输入的导数链。你无需手动对模型求导:不管如何嵌套,只要你给出前向传播表达式,PyTorch都会自动提供该表达式相对于其输入参数的梯度。
实现auto compute grad 的大致过程:
1.通过输入的tensor数据类型的变量在神经网络中的前向传播过程生成一张计算图
2.根据这个计算图和输出结果准确计算出每个参数需要更新的梯度,并通过完成反向传播完成对参数的梯度更新
params = torch.tensor([1.0, 0.0], requires_grad=True)
require_grad = True,这个参数告诉PyTorch需要追踪在params上进行运算而产生的所有张量。换句话说,任何以params为祖先的张量都可以访问从params到该张量所调用的函数链。如果这些函数是可微的(大多数PyTorch张量运算都是可微的),则导数的值将自动存储在参数张量的grad属性中。
一般来讲,所有PyTorch张量都有一个初始为空的名为grad的属性:
params.grad is None # True
你可以将包含任意数量的张量的require_grad设置为True以及组合任何函数。在这种情况下,PyTorch会在沿着整个函数链(即计算图)计算损失的导数,并在这些张量(即计算图的叶节点)的grad属性中将这些导数值累积(accumulate)起来。
警告:PyTorch的新手(以及很多经验丰富的人)经常忽视的事情:是积累(accumulate)而不是存储(store)。
警告:调用backward会导致导数值在叶节点处累积。所以将其用于参数更新后,需要将梯度显式清零。
如果不置零,重复调用backward会导致导数在叶节点处累积,影响后续计算。因此,如果提前调用了backward,然后再次计算损失并再次调用backward(如在训练循环中一样),那么在每个叶节点上的梯度会被累积(即求和)在前一次迭代计算出的那个叶节点上,导致梯度值不正确。
为防止这种情况发生,你需要在每次迭代时将梯度显式清零。可以使用就地方法zero_轻松地做到这一点:
if params.grad is not None:
params.grad.zero_()
注:你可能会很好奇为什么在每次调用backward后将梯度清零是必需的步骤而不是自动进行的步骤。原因是为复杂模型中的梯度提供更大的灵活性和可控制性。
使用优化策略,以使你免于繁琐地更新模型中的每个参数。torch模块有一个optim子模块,你可以在其中找到实现不同优化算法的类。这里有一个简短的清单:
from torch import optim
dir(optim)
'''
['ASGD',
'Adadelta',
'Adagrad',
'Adam',
'AdamW',
'Adamax',
'LBFGS',
'Optimizer',
'RMSprop',
'Rprop',
'SGD',
'SparseAdam',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'_functional',
'_multi_tensor',
'lr_scheduler',
'swa_utils']
'''
每个优化器构造函数都将参数(通常是将require_grad设置为True的PyTorch张量)作为第一个输入。传递给优化器的所有参数都保留在优化器对象内,以便优化器可以更新其值并访问其grad属性
每个优化器都有两个方法:zero_grad和step。前者将构造时传递给优化器的所有参数的grad属性归零;后者根据特定优化器实施的优化策略更新这些参数的值。
现在创建参数并实例化一个梯度下降优化器:
params = torch.tensor([1.0, 0.0], requires_grad=True)
learning_rate = 1e-5
optimizer = optim.SGD([params], lr=learning_rate)
使用优化器:
t_p = model(t_u, *params)
loss = loss_fn(t_p, t_c)
loss.backward()
optimizer.step()
params
调用step后params的值就会更新,无需亲自更新它!调用step发生的事情是:
优化器通过将params减去learning_rate与grad的乘积来更新的params。
优化器并不是训练循环中唯一灵活的部分。现在将注意力转移到模型上。为了在相同的数据上使用相同的损失训练神经网络,你只需更改函数model即可
适应性强的模型倾向于使用其许多参数来确保在训练数据上的损失最小,但是你无法保证该模型在远离训练数据或在训练数据之间的数据上表现良好。
优化器的作用是最大限度的减少训练数据的损失。而在这个过程中容易发生过拟合(over fitting)
训练损失告诉模型是否完全适合训练集,换句话说,模型是否具有足够的能力来处理训练数据中的相关信息。
深度神经网络可以近似复杂的函数,前提是神经元的数量(即参数量)足够高。参数越少,网络能够近似的函数越简单。
因此,这里有一条规律:如果训练损失没有减少,则该模型对于数据来说太简单了。另一种可能性是训练数据中不包含有意义的信息以用于预测输出。
如果在验证集中评估的损失没有随训练集一起减少,则你的模型正在改善其在训练过程中看到的样本的拟合度,但并没有将其泛化(generalize)到训练集之外的样本,当你在新的(训练时没见过的)数据上评估模型时,损失值就很高。这是第二条规律:如果训练损失和验证损失分道扬镳(diverge)了,则说明模型过拟合了。
有什么办法防止过拟合?好问题。过拟合似乎是这样一个问题:确保模型在数据点之间的行为对于你尝试近似的过程是明智的(原句:Overfitting looks like a problem of making sure that the behavior of the model in between data points is sensible for the process you’re trying approximate.)。首先,你应确保为该过程获取了足够多的数据。如果你以很低频率从正弦过程中采样来收集数据,那么你很难让模型拟合这些数据。
假设你有足够多的数据,则应确保能够拟合训练数据的模型在数据点之间尽可能正则化(regular)。你有几种方法可以实现此目标:一种方法是在损失函数中添加所谓的惩罚项,以使模型的行为更平稳,变化更慢(到一定程度);另一种方法是向输入样本添加噪声,在训练数据样本之间人为地创建新的数据,并迫使模型也尝试拟合它们。还有几种与这两种方法有些相关的方式。不过你可以为自己做的最大努力,至少作为第一步,就是简化你的模型。从直观的角度来看,较为简单的模型可能无法像较复杂的模型那样完美地拟合训练数据而是在训练数据上表现得更加正则。
1.线性模型是用于拟合数据的合理的最简单的模型;
2.凸优化技术可以用于线性模型,但不能推广到神经网络,因此本章重点介绍参数估计。
3.深度学习可用于通用模型,这些通用模型不是为解决特定任务而设计的,而是可以自动调整以专门解决眼前的问题。
4.学习算法等于根据观察结果优化模型的参数。损失函数是对执行任务中的错误的一种度量,例如预测输出值和测量值之间的误差。目标就是使损失函数值尽可能低。
5.损失函数关于模型参数的变化率可用于在减少损失的方向上更新该参数。
6.PyTorch中的optim模块提供了一组现成的优化器,用于更新参数和最小化损失函数。
7.优化器使用PyTorch的autograd来计算每个参数的梯度,而梯度具体取决于该参数对最终输出的贡献程度。autograd允许用户在复杂的前向通过过程中依赖于动态计算图。
8.诸如torch.no_grad()的上下文管理器可用于控制是否需要自动求导。
9.数据通常划分为独立的训练集和验证集,从而可以在未训练的数据(即验证集)上进行模型评估。
10.当模型的性能在训练集上继续提高但在验证集上下降时,模型就发生了过拟合。这种情况通常发生在模型无法泛化(到训练集之外的数据)而是记住了训练集所需的输出时。
从本质上讲,神经元不过是输入的线性变换(例如,输入乘以一个数[weight,权重],再加上一个常数[偏置,bias]),然后再经过一个固定的非线性函数(称为激活函数)。
数学上,你可以将其写为 o=f(wx+b)o=f(wx+b),其中 xx 为输入,ww 为权重或缩放因子,bb 为偏置或偏移。ff 是激活函数,在此处设置为双曲正切( tanh)函数。通常,xx 以及 oo 可以是简单的标量,也可以是向量(包含许多标量值)。类似地,ww 可以是单个标量或矩阵,而 bb 是标量或向量(输入和权重的维度必须匹配)。在后一种情况下,该表达式被称为神经元层,因为它通过多维度的权重和偏差表示许多神经元。
之前的线性模型与你将要使用的深度学习模型之间的重要区别是误差函数的形状。线性模型和误差平方损失函数具有凸的具有明确定义的最小值的误差曲线。如果你要使用其他方法(译者注:即非梯度下降的方法),则可以自动地求出这个明确的最小值。而(译者注:使用梯度下降)参数更新则试图尽可能地估计出这个最小值。
即使使用相同的误差平方损失函数,神经网络也不具有凸误差曲面这个属性。你尝试优化的每个参数都没有一个明确正确的答案。相反,你尝试优化所有协同工作的参数以产生有用的输出。由于有用的输出只会逼近真实值,因此会有一定程度的不完美。这种不完美在何处以及如何表现是任意的,引起这种不完美的控制输出的参数在某种程度上也是任意的。从机械角度来看,神经网络训练的输出结果看起来很像参数估计,但是请记住,理论基础是完全不同的。
神经网络具有非凸误差曲面主要是因为激活函数。组合神经元来逼近各种复杂函数的能力取决于每个神经元固有的线性和非线性行为的组合
定义 激活函数:
1.是非线性的。在没有激活函数的情况下重复应用 wx+bwx+b 会产生多项式。非线性的激活函数允许整个网络能近似更复杂的函数。
2.是可微的。激活函数是可微的这样就可以计算穿过它们的梯度。不可微的离散点是无伤大雅的,例如Hardtanh和ReLU。
如果没有上述两个要求,网络要么退回到复杂的多项式,要么变得难以训练。
激活函数还通常(尽管并非总是如此)
1.具有至少一个敏感范围,其中输入的轻微变化会导致输出中相应的变化。
2.具有至少一个不敏感(或饱和)范围,其中输入的变化导致输出的变化很小甚至没有变化。
通常(但并非普遍如此),激活函数至少具有以下特点之一:
1.当输入变为负无穷大时接近(或达到)下限
2.当输入变为正无穷大时接近(或达到)上限
思考一下反向传播的工作原理,你可以发现,当输入处于响应范围内时,误差将通过激活更有效地向后传播,而误差不会严重影响输入饱和的神经元(因为由于输出周围区域很平坦,梯度将接近零)。
综上所述,此机制非常强大。我们要说的是,在由线性+激活单元构成的网络中,当向网络提供不同的输入时,(a)不同的单元对于相同的输入会在不同的范围内做出响应,并且(b)与这些输入相关的误差将主要影响在敏感范围内运行的神经元,而其他单元或多或少不受学习过程的影响。此外,由于激活函数相对于其输入的导数通常在敏感范围内接近1,因此通过梯度下降在该范围内估计线性变换的参数看起来很像线性拟合。
你开始对如何将多个线性+激活函数并行连接并一个接一个地堆叠到一个能够近似复杂函数的数学对象上有了更深入的了解。不同的组合会响应不同范围内的输入,并且对于这些参数,通过梯度下降相对容易优化,因为学习过程将非常类似于线性拟合,直到输出饱和为止。
深度神经网络可让你近似高度非线性的过程,而无需为它们建立明确的模型。 相反,从未经训练的通用模型开始,你可以通过为它提供一组输入和输出以及一个从中进行反向传播的损失函数,将其专门用于某个任务。通过训练样本将通用模型专门用于某个任务就是我们所谓的学习,因为模型并不是在考虑特定任务的情况下构建的;模型中没有编码描述该任务如何工作的规则。
我们对输入/输出函数的形状进行了硬编码;我们无法近似除了围绕一条线的数据点外的数据。随着问题的维数增长(许多输入到许多输出)以及输入/输出关系变得复杂,假设输入/输出函数的形状变得不太可能。物理学家或应用数学家的工作通常是根据理论原理对现象进行函数描述,以便可以通过测量来估算未知参数并获得准确的模型。而另一方面,深度神经网络是一系列函数,可以近似各种输入/输出关系,而不必要求提供一种现象的解释模型。在某种程度上,你需要放弃可解释性来解决日益复杂的问题。换句话说,有时您缺乏能力、信息或者计算资源来为你遇到的问题建立显式模型,因此数据驱动方法是你前进的唯一方法。
PyTorch有一个专门用于神经网络的完整子模块:torch.nn。该子模块包含创建各种神经网络体系结构所需的构建块。这些构建块在PyTorch术语中称为module(模块),在其他框架中称为layer(层)。
从面向对象的角度看torch.nn包提供的是很多与实现神经网络中的具体功能相关的类,这些类涵盖了深度神经网络模型在搭建和参数优化过程中的常用内容。包括但不限于如下内容:
卷积层,池化层,全连接层这类层次构造方法,防止过拟合的参数归一法,Dropout方法,还有激活函数部分的线性激活函数,非线性激活函数相关方法。
而torch.nn中的模块(类)都是从基类nn.Module继承而来的Python类。模块可以具有一个或多个参数(Parameter)实例作为属性,这些参数就是在训练过程中需要优化的张量。模块还可以具有一个或多个子模块(nn.Module的子类)属性,并且也可以追踪其参数。
注:子模块必须是顶级属性(top-level attributes),而不能包含在list或dict实例中!否则,优化器将无法找到子模块(及其参数)。对于需要子模块列表或字典的情况,PyTorch提供有nn.ModuleList和nn.ModuleDict。
在学会使用torch.nn进行神经网络模型的搭建和参数优化后,就会发现实现一个神经网络应用并没有我们想象中那么难
torch.nn.Sequential() 是一个类,是torch.nn中的序列容器,通过在容器中嵌套各种实现神经网络中的具体功能相关的类,来完成对神经网络的搭建,最主要的是,参数会按照我们定义好的序列自动传递下去。我们可以将嵌套在容器中的各个部分看作各种不同的模块,这些模块可以自由组合。
模块的加入方式一般有两种:1.直接嵌套,2.以orderdict有序字典的方式进行传入。这两种方式的唯一区别是,使用后者搭建的模型的每一个模块都有我们自定义的名字,而前者默认使用从零开始的数字序列作为每个模块的名字。
hidden_layer = 100
input_data = 1000
output_data = 10
models = torch.nn.Sequential(
torch.nn.Linear(input_data,hidden_layer),
torch.nn.ReLU(),
torch.nn.Linear(hidden_layer,output_data)
)
print(models)
'''
Sequential(
(0): Linear(in_features=1000, out_features=100, bias=True)
(1): ReLU()
(2): Linear(in_features=100, out_features=10, bias=True)
)
'''
hidden_layer = 100
input_data = 1000
output_data = 10
from collections import OrderedDict
models = torch.nn.Sequential(OrderedDict([
('Linel',torch.nn.Linear(input_data,hidden_layer)),
('Relu1',torch.nn.ReLU()),
('Line2',torch.nn.Linear(hidden_layer,output_data))
]))
print(models)
'''
Sequential(
(Linel): Linear(in_features=1000, out_features=100, bias=True)
(Relu1): ReLU()
(Line2): Linear(in_features=100, out_features=10, bias=True)
)
'''
对模块使用自定义的名称可以让我们更加便捷地找到模型中相应模块并进行操作
torch.nn.Linear():这个类用于定义模型的线性层,即完成前面提到的不同层之间的线性变换。
接收三个参数,分别是输入特征数,输出特征数和是否使用偏置(bool类型,默认为True)。生成对应维度的权重参数和偏置,而且对于生成的权重参数和偏置,模型默认使用一种比之前的简单随机方式更好的参数初始化方法
torch.nn.ReLU():这个类属于非线性激活分类,在定义时默认不需要传入参数。
可供选择的非线性激活函数还有:PReLU,LeakyReLU,Tanh,Sigmoid,Softmax等
torch.nn.MSELoss():这个类使用均方误差函数对损失值进行计算,在定义类的对象时不用传入任何参数,但在使用实例时需要输入两个维度一样的参数方可进行计算,示例如下:
import torch
from torch.autograd import Variable
loss_f = torch.nn.MSELoss()
x = Variable(torch.randn(100,100))
y = Variable(torch.randn(100,100))
loss = loss_f(x,y)
print(loss.data)
torch.nn.L1Loss():这个类使用L1范式作为误差函数,也就是平均绝对误差,与MSELoss的使用方式一样
import torch
from torch.autograd import Variable
loss_f = torch.nn.L1Loss()
x = Variable(torch.randn(100,100))
y = Variable(torch.randn(100,100))
loss = loss_f(x,y)
print(loss.data)
torch.nn.CrossEntropyLoss():这个类用于计算交叉熵,在定义类对象时不需要传入任何参数,在使用实例时需要输入两个满足交叉熵计算条件的参数
import torch
from torch.autograd import Variable
loss_f = torch.nn.CrossEntropyLoss()
x = Variable(torch.randn(3,5))
y = Variable(torch.LongTensor(3).random_(5))
loss = loss_f(x,y)
print(loss.data)
除了采用pytorch自动梯度的方法来搭建神经网络,还可以通过构建一个继承了torch.nn.Module的新类,来完成forward和backward的重写。
# 神经网络搭建
import torch
from torch.autograd import Varible
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
def forward(self,input,w1,w2):
x = torch.mm(input,w1)
x = torch.clamp(x,min = 0)
x = torch.mm(x,w2)
def backward(self):
pass
model = Model()
#训练
x = Variable(torch.randn(batch_n,input_data))