层次——神经网络的基本块
最重要的层之一——线性层
对输入的数据应用线性变换: y = x A T + b y y = xA^T + by y=xAT+by
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor
返回一个由均值为0、方差为1的正态分布(标准正态分布)中的随机数组成的张量。
o u t i ∼ N ( 0 , 1 ) out i ∼N(0,1) outi∼N(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()
上面两种实现线性变换的区别,第一种是自己构建的一个简单的网络模型,需要自己设定权值weight及偏置值bias,而Linear框架中能自己生成weight及bias,并且其内部有很强的关联性。构建一个简单的线性网络模型的步骤:首先需要获取数据预测输入及目标输出,对数据进行处理,获得模型输出,然后计算代价loss,并进行优化,不断迭代上述过程。
线性层由不同的名称调用,例如跨不同框架的密集层或完全连接层。用于解决真实世界用例的深层学习体系结构通常包含一个以上的层,在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
Tanh
ReLU
Leaky ReLU
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])
class 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
上面所示代码中做的是继承父类并在该类中实现两种方法,在Python中,我们将父类作为参数传递给类名,从而实现子类;init方法充当构造函数,super用于将子类的参数传递给父类,在上面的例子中是nn.Module一旦我们定义了我们的网络架构,我们就剩下两个重要的步骤:一个是计算我们的网络在执行特定的回归、分类;下一步就是优化权重。
优化器(梯度下降)通常接受一个标量值,所以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概率是猫,但实际上是熊猫,那么交叉熵损失将更高;如果它预测与实际标签相似,那么交叉熵损失就会降低。
# 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
正确输出如下:
创建一个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)
同一段代码,在两个项目里运行结果不一样