Deep Learning with Pytorch_002
chapter03_深入研究神经网络的构建块
- 在上一章中,我们使用Py Torch的低级操作来构建模块,如网络体系结构、损失函数和优化器。在本章中,我们将探讨解决现实世界问题所需的神经网络的一些重要组成部分,以及PyTorch如何通过提供大量的高级函数来抽象出大量的复杂性。
本章将讨论以下主题:
- 深入研究神经网络的各种构建块
- 探索PyTorch中的高级功能来构建深度学习体系结构
- 将深度学习应用在一个真实图像分类问题
- 任何深入的学习训练都需要获取数据,构建一个总体上是将一堆层聚集在一起的体系结构。
- 理解PyTorch为构建层、损失函数和优化器提供的更高层次的抽象。
-
层次——神经网络的基本块
-
最重要的层之一——线性层
对输入的数据应用线性变换:
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
返回一个由均值为0、方差为1的正态分布(标准正态分布)中的随机数组成的张量。
torch.from_numpy(ndarray) → Tensor
是将numpy类型转换为Tensor的函数# from_numpy 测试 ,从numpy.ndarray中创建一个张量, # 返回的张量和ndarray共享相同的内存。张量的变化将反映在ndarray中,反之亦然; # 返回的张量是不可调整的(这句话是什么意思) a = numpy.array([1, 2, 3]) t = torch.from_numpy(a) print(t) # tensor([ 1, 2, 3]) print(a) t[0] = -1 print(a) # array([-1, 2, 3]) print(t) # tensor([-1, 2, 3])
完整的线性变换实现代码(还没用linear):
import torch torch.__version__ import numpy as np import matplotlib.pyplot as plt from torch.autograd import Variable # Creating data for our neural network def get_data(): train_X = np.asarray([3.3, 4.4, 5.5, 6.71, 6.93, 4.168, 9.779, 6.182, 7.59, 2.167, 7.042, 10.791, 5.313, 7.997, 5.654, 9.27, 3.1]) train_Y = np.asarray([1.7, 2.76, 2.09, 3.19, 1.694, 1.573, 3.366, 2.596, 2.53, 1.221, 2.827, 3.465, 1.65, 2.904, 2.42, 2.94, 1.3]) dtype = torch.FloatTensor X = Variable(torch.from_numpy(train_X).type(dtype), requires_grad=False).view(17, 1) # 对X进行转置 y = Variable(torch.from_numpy(train_Y).type(dtype), requires_grad=False) return X, y # Creating learnable parameters def get_weights(): w = Variable(torch.randn(1), requires_grad=True) b = Variable(torch.zeros(1), requires_grad=True) return w, b # Network implementation def simple_network(x): y_pred = torch.matmul(x, w) + b return y_pred # loss function def loss_fn(y, y_pred): loss = torch.mean((y - y_pred) ** 2) for param in [w, b]: if not param.grad is None: param.grad.data.zero_() # 第一次时,需要将梯度清零将梯度 loss.backward() # 计算可学习参数w和b的梯度 return loss.data # Optimize the neural network def optimize(learning_rate): w.data -= learning_rate * w.grad.data b.data -= learning_rate * b.grad.data learning_rate = 0.005 x,y = get_data() w,b = get_weights() num_epochs = 100 # 这个数据不能太大,太大会加大误差 for epoch in range(num_epochs): inputs = x targets = y # Forward pass outputs = simple_network(inputs) # Backward and optimize loss = loss_fn(outputs, targets) optimize(learning_rate) if (epoch + 1) % 10 == 0: print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, loss.item())) print('outputs:') print(outputs) print(loss.item()) plt.plot(x.detach().numpy(), y.detach().numpy(), 'ro', label='Original Data') plt.plot(x.detach().numpy(), outputs.detach().numpy(), label='Fitting Line') plt.legend() plt.show()
但在Pytorch中,它的强大之处在于, 对于线性层的线性变换:
Y=Wx+b
,在上面编写的整个函数可以用一行代码编写,如下所示:from torch.nn import Linear, ReLU myLayer = Linear(in_features=10,out_features=5,bias=True) # 输 入张量大小 为10,输出为5
torch.nn.Linear(in_features, out_features, bias=True) #in_feature ——每个输入样本的大小 ;out_feature——每个输出样本的大小;bias——如果设置为False,则该层将不会学习加性偏差,默认值:True。
具体实现代码如下(包含所需的导入包):
import torch from torch.autograd import Variable from torch.nn import Linear, ReLU inp = Variable(torch.randn(1,10)) #创建输入数据 print('inp:') print(inp) myLayer = Linear(in_features=10,out_features=5,bias=True) # 输入张量大小为10,输出为5 myLayer(inp) print('myLayer(inp):') print(myLayer(inp)) print('myLayer.weight:') print(myLayer.weight) print('myLayer.bias:') print(myLayer.bias)
实现线性变换,使用Pytorch的框架Linear:
# 使用Pytorch中的Linear实现线性变换(能实现的代码) import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt # Hyper-parameters input_size = 1 output_size = 1 num_epochs = 60 learning_rate = 0.001 # Toy dataset x_train = np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168],[9.779], [6.182], [7.59], [2.167], [7.042],[10.791], [5.313], [7.997], [3.1]], dtype=np.float32) y_train = np.array([[1.7], [2.76], [2.09], [3.19], [1.694], [1.573],[3.366], [2.596], [2.53], [1.221], [2.827],[3.465], [1.65], [2.904], [1.3]], dtype=np.float32) # Linear regression model model = nn.Linear(input_size, output_size) # Loss and optimizer criterion = nn.MSELoss() optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # Train the model for epoch in range(num_epochs): # Convert numpy arrays to torch tensors inputs = torch.from_numpy(x_train) targets = torch.from_numpy(y_train) # Forward pass outputs = model(inputs) loss = criterion(outputs, targets) # Backward and optimize optimizer.zero_grad() loss.backward() optimizer.step() if (epoch + 1) % 5 == 0: print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, num_epochs, loss.item())) # Plot the graph predicted = model(torch.from_numpy(x_train)).detach().numpy() plt.plot(x_train, y_train, 'ro', label='Original data') plt.plot(x_train, predicted, label='Fitted line') plt.legend() plt.show()
线性层由不同的名称调用,例如跨不同框架的密集层或完全连接层。用于解决真实世界用例的深层学习体系结构通常包含一个以上的层,在PyTorch中,我们可以通过多种方式来实现它,如下所示:
- 一种简单的方法是将一个层的输出传递给另一个层
# 将一个层的输出传递给另一个层 myLayer1 = Linear(10,5) myLayer2 = Linear(5,2) myLayer2(myLayer1(inp)) print('myLayer1(inp):') print(myLayer1(inp)) print('myLayer2(myLayer1(inp)):') print(myLayer2(myLayer1(inp)))
线性层的缺点:具有两个不同层的体系结构可以简单地表示为具有不同层的单层。因此,仅仅叠加多个线性层将无助于算法学习任何新的东西。
- 示例:
Y=2(3X1) # 2 Linear Layers Y=6(X1) #1 Linear Layer
- 一种简单的方法是将一个层的输出传递给另一个层
-
-
非线性激活
非线性激活是接受输入,然后应用数学变换并产生输出的函数。为了解决线性变换中的问题,引进不同的非线性函数来帮助学习不同的关系,而不是只关注线性关系。PyTorch将这些非线性功能作为层提供,能够像使用线性层一样使用它们。
一些常用的非线性函数如下:-
Sigmoid
- Sigmoid函数的表达式如下:
- Sigmoid函数直观地获取一个实数,并输出在0到1之间的一个数字;对于较大的负数,它返回接近于零的值;对于一个较大的正数,它返回接近1的值。
- Sigmoid函数的缺点:当Sigmoid函数的输出接近于零或1时,Sigmoid函数之前各层的梯度接近于零。前一层的可学习参数使得梯度接近于零,权重不被调整,导致神经元死亡。
- Sigmoid函数的表达式如下:
-
Tanh
- tanh非线性函数将实数压缩在-1和1的范围内,但当tanh输出接近-1 和1的极值时,tanh也面临同样的饱和梯度问题;然而,它比Sigmoid更 好,因为tanh的输出是以零为中心的。
-
ReLU
- 在最近几年里,ReLU变得越来越流行;它有一个简单的数学公式:
- ReLU将所有输入为负值的数压缩为0,并留下正数。
- ReLU帮助优化器更快地找到正确的权重集;更严格地说,它使随机梯度下降的收敛速度更快。
- ReLU在计算上很便宜,因为只是在进行阈值处理,而不是像对Sigmoid和切线函数那样计算。
- ReLU有一个缺点:当一个大的梯度在反向传播过程中通过它时。常常变得没有反应;它们被称为神经元死亡,它可以通过仔细选择学习率来控制。
- 在最近几年里,ReLU变得越来越流行;它有一个简单的数学公式:
-
Leaky ReLU
- Leaky ReLU试图解决一个濒死的问题,而不是饱和到零,我们饱和到一个非常小的数字,例如0.001;对于某些使用情况,该激活功能为其他应用提供了卓越的性能,但这并不一致。
-
ReLU(线性整流函数)、Leaky ReLU(带泄露线性整流函数)、PReLU(参数化修正线性单元)和RReLU(随机纠正线性单元)的比较:
-
-
PyTorch 非线性激活
代码:
# A quick example of how to use the ReLU function in PyTorch sample_data = Variable(torch.Tensor([[1,2,-1,-1]])) myReLU = ReLU() print('myReLU(sample_data):') print(myReLU(sample_data)) print('myReLU(sample_data).size():') print(myReLU(sample_data).size())
在上面的代码中,取一个具有两个正值和两个负值的张量,并在其上应用一个ReLU函数处理,它将负数阈值化为0并保留原来的正数。
输出结果如下:myReLU(sample_data): tensor([[1., 2., 0., 0.]]) myReLU(sample_data).size(): torch.Size([1, 4])
- 构建深度学习算法的PyTorch方法
- Pytorch中的所有网络都是由类实现的,而Pytorch中的子类由nn.Module调用
- 类中应该实现__init__方法和__forward__方法
- 在__init__方法中初始化所有层,例如线性层
- 在__forward__方法中,将输入数据传递到在__init__方法中初始化的层中,并返回最终输出。
- 非线性函数通常直接应用于forward函数中,也有一些在init方法中使用。
- 下面的代码片段展示了如何在PyTorch中实现深度学习架构:
上面所示代码中做的是继承父类并在该类中实现两种方法,在Python中,我们将父类作为参数传递给类名,从而实现子类;init方法充当构造函数,super用于将子类的参数传递给父类,在上面的例子中是nn.Moduleclass MyFirstNetwork(nn.Module): def __init__(self,input_size,hidden_size,output_size): super(MyFirstNetwork,self).__init__() self.layer1 = nn.Linear(input_size,hidden_size) self.layer2 = nn.Linear(hidden_size,output_size) def __forward__(self,input): out = self.layer1(input) out = nn.ReLU(out) out = self.layer2(out) return out
- 不同机器学习问题的模型结构
- 我们正在解决的问题将主要决定我们将使用哪些层,从线性层到用于顺序数据的长短期存储器(LSTM),根据所要解决的问题的类型,最后一层是确定的;我们通常使用机器学习或深度学习算法来解决三个问题。
- 1.对于回归问题,例如预测t恤的价格,我们使用最后一层作为一个线性层,具有一个输出,并且输出一个连续的值。
- 2.要将给定的图像分类为t恤或非t恤,可以使用Sigmoid激活函数,因为它输出的值要么接近1,要么接近于0,这通常称为二进制分类问题。
- 3.对于多类别分类,我们必须将给定图像分类为T恤、牛仔裤、衬衫或连衣裙;我们将在网络的末尾使用Softmax层,例如,它接受前一个线性层的输入,并为给定数量的示例输出概率,在我们的例子中,它将被训练来预测每种类型的图像的四个概率,并且这四个概率和为1。
- Loss functions
-
一旦我们定义了我们的网络架构,我们就剩下两个重要的步骤:一个是计算我们的网络在执行特定的回归、分类;下一步就是优化权重。
-
优化器(梯度下降)通常接受一个标量值,所以loss函数应该生成一个标量值,并且该值在训练中必须最小化。而对于某些用例,如预测道路上的障碍物并将其划分为行人或非行人,将需要两个或更多的损失函数。但即使在这种情况下,也需要将损失合并为一个标量,以便优化器最小化。
-
PyTorch中用于回归和分类的loss函数,应用示例:
# Loss functions loss = nn.MSELoss() input = Variable(torch.randn(3,5),requires_grad=True) target = Variable(torch.randn(3,5)) output = loss(input,target) output.backward() print('input:') print(input) print('target:') print(target) print('output:') print(output)
-
交叉熵损失:它计算了一个预测概率的分类网络的损失,这个概率应该加起来总和为1,就像Softmax层一样;当预测概率偏离正确概率时,交叉熵损失增加;例如,如果我们的分类算法预测某图像的0.1概率是猫,但实际上是熊猫,那么交叉熵损失将更高;如果它预测与实际标签相似,那么交叉熵损失就会降低。
- 交叉熵损失函数的原理:
在二分类问题模型:例如逻辑回归「Logistic Regression」、神经网络「Neural Network」等,真实样本的标签为 [0,1],分别表示负类和正类。交叉熵损失模型的最后通常会经过一个 Sigmoid 函数,输出一个概率值,这个概率值反映了预测为正类的可能性:概率越大,可能性越大。
预测输出即 Sigmoid 函数的输出表征了当前样本标签为 1 的概率可表达为:
当前样本标签为 0 的概率就可以表达成: - 单个样本交叉熵损失的公式:
- 交叉熵损失函数定义:
# cross_entropy loss def cross_entropy(true_label,prediction): if true_label == 1: return -log(prediction) # log这块会提示出错,导入了from math import log else: return -log(1 - prediction)
在分类问题中使用交叉熵损失:
# Use a cross-entropy loss in a classification problem loss = nn.CrossEntropyLoss() input = Variable(torch.randn(3,5),requires_grad=True) target = Variable(torch.LongTensor(3).random_(5)) output = loss(input,target) output.backward() print('output.backward():') print(output.backward())
但是在上面的代码中会报如下错误:
解决方法是:将output.backward()
改为output.backward(retain_graph=True)
,该问题是指在默认情况下,网络在反向传播中不允许多个backward()
。需要在第一个backward
设置retain_graph=True
正确输出如下:- Pytorch中的一些loss函数及其应用范围:
- L1 loss——主要用作正则化器
- MSE loss——回归问题的损失函数
- Cross-entropy loss——用于二进制和多级分类问题
- NLL Loss——用于分类问题,并允许我们使用特定的权重来处理不平衡的数据集。
- NLL Loss2d——用于逐像素分类,主要用于与图像分割有关的问题。
- 交叉熵损失函数的原理:
- 优化网络体系结构
一旦我们计算出我们的网络损失,我们将优化权重以减少损失,从而提高算法的精度,为了简单起见,让我们将这些优化器看作黑匣子,它们接受丢失函数和所有可学习的参数,并稍微移动它们以提高性能。Pytorch所提供的一些优化器如下所示:
- ADADELTA
- Adagrad——自适应梯度算法
- Adam
- SparseAdam
- Adamax
- ASGD
- LBFGS
- RMSProp
- Rprop
- SGD——随机梯度下降
-
创建一个SGD优化器,它将网络的所有可学习参数作为第一个参数,并创建了一个学习速率,它决定可以对可学习的参数进行多大比例的更改:
optimizer = optim.SGD(model.parameters(),lr = 0.01) # 这处的调试为什么不会继续往下执行,而且model不管导入哪个包都出错,所有关于model的包都没有这个attribute
loss = nn.MSELoss() for input,target in dataset: optimizer.zero_grad() output = model(input) losss = loss(output,target) loss.backward() optimizer.step() print('loss.backward():') print(loss.backward())
一旦创建了优化器对象,就要在循环中调用zero_grad(),因为参数将累积在上一次优化器调用期间创建的梯度;当调用了loss函数中的backward函数(计算梯度更新权值)中后,就需要调用optimizer.step(),对可学习参数进行实际更改。
-
基于深度学习的图像分类
Dogs vs. Cats数据集
-
调试出现的错误:
import os dir = 'F:\\inner\\kaggle' # 数据集路径 list_img = [] list_label = [] data_size = 0 dir = dir + '/train/' for file in os.listdir(dir): # 遍历dir文件夹 list_img.append(dir + file) # 将图片路径和文件名添加至image list data_size += 1 # 数据集增1 name = file.split(sep='.') # 分割文件名,"cat.0.jpg"将分割成"cat",".","jpg"3个元素 # label采用one-hot编码,"1,0"表示猫,"0,1"表示狗,任何情况只有一个位置 为"1",在采用CrossEntropyLoss()计算Loss情况下,label只需要输入"1"的索引,即猫应输入0,狗应输入1 if name[0] == 'cat': list_label.append(0) # 图片为猫,label为0 else: list_label.append(1) # 图片为狗,label为1,注意:list_img和list_label中的内容是一一配对的 print('data_size:') print(data_size)
同一段代码,在两个项目里运行结果不一样
- 在本地运行时结果:
- 服务器运行结果:
- 在本地运行时结果:
来源:https://blog.csdn.net/qq_33740167/article/details/102660464