用神经网络实现线性拟合,代码源自《深度学习入门之pytorch》,本人根据新版本的PyTorch做了挺大的修改。
《深度学习入门之pytorch》的使用体验
关于这个书好不好,我也不好评价,但总体体验不是太好。
书中的代码有些不全,新手自己运行不出结果,就不利于新手理解代码。
另外GitHub上的代码版本比较旧。我用的pytorch版本为1.0.1,在运行示例代码的时候会报错,需要改动细节才能正常运行。
尤其尤其是源代码没有根据“tensor
和Variable
合并”的大改动而更新(此次改动出现在0.4.0的版本)!!!
代码解析
导入需要的库
import torch
import numpy as np
# 可调用网络结构的库
import torch.nn as nn
# 优化器的库
import torch.optim as optim
# 画图函数的库
import matplotlib.pyplot as plt
给出待拟合的原始数据
原始数据类型为 numpy
的 ndarray
,变量类型定为 float
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)
如果不增加 dtype=np.float32
将元素定为float
,则元素的数据类型就为默认的 double
。
在 out = self.linear(x)
报 RuntimeError: Expected object of scalar type Float but got scalar type Double for argument #4 'mat1'
的错,该错的原因是类型不符(要得到 float
但得到 double
)。
定义模型并确定CPU或GPU
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(1, 1) # 输入输出都是一维
def forward(self, x):
out = self.linear(x)
return out
# 判断cuda加速是否可用
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = LinearRegression().to(DEVICE)
当网络搭建完成后,通过 model = LinearRegression().to(DEVICE)
将网络实例化,即带有了输入输出接口。
其中DEVICE
是一个人为定义的超参数,通过if
来决定tensor
是在GPU
上运算还是CPU
上运算。
定义优化器和损失函数
在优化器中设定学习率(此处没有用到学习率的衰减)。
# 损失函数
criterion = nn.MSELoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=1e-3)
数据预处理
numpy
的 ndarray
转为 tensor
# array 变 Tensor
x_train = torch.from_numpy(x_train)
y_train = torch.from_numpy(y_train)
确定使用CPU
或GPU
# 判断GPU是否可用
if torch.cuda.is_available():
inputs = x_train.cuda()
target = y_train.cuda()
else:
inputs = x_train
target = y_train
训练网络
前向传播 + 反向传播 + 输出结果
1. 设定循环次数
# 循环100次
num_epochs = 100
2. 前向传播
- 由网络计算输出 :
out = model(inputs)
- 计算实际值和预测值的损失函数:
loss = criterion(out, target)
3. 反向传播
- 梯度清零:
optimizer.zero_grad()
- 损失函数反向传播:
loss.backward()
- 优化器优化:
optimizer.step()
4. 每20个epoch输出一次
该部分整体代码如下:
# 循环100次
num_epochs = 100
for epoch in range(num_epochs):
# forward
out = model(inputs)
loss = criterion(out, target)
# backward
optimizer.zero_grad() # 梯度清零
loss.backward()
optimizer.step()
# 输出循环结果(每20个epoch输出一次)
if(epoch+1) % 20 == 0:
print('Epoch[{}/{}], loss:{:.6f}'
.format(epoch+1, num_epochs, loss.data))
绘图
1. 将模型改回CPU
model.cpu()
2. 计算最终的预测值(并转换为ndarray
类型)
predict = model(x_train)
predict = predict.detach().numpy()
对于predict = predict.detach().numpy()
中为什么要加入.detach()
。因为predict
在Variable的计算图中,需要从中取出tensor
,再通过.numpy()
转为ndarray
。
不过也不用太担心。因为如果没加的话,会对predict = predict.numpy()
报出RuntimeError: Can't call numpy() on Variable that requires grad. Use var.detach().numpy() instead
的错,告诉你用.detach().numpy()
。(记住错误原因是Can't call numpy() on Variable that requires grad
,在下下一个部分讨论tensor
和Variable
合并的影响的时候会再提到)
关于tensor
和Variable
的关系。详见本人的另一篇博客深度学习框架PyTorch中的概念和函数(待填坑),并不难理解。
关于.data()
的.detach()
的区别,详见关于 pytorch inplace operation, 需要知道的几件事。如果懒得看,也可以直接记下结论:都用.detach()
就好了。
这部分的整体代码如下:
model.cpu()
predict = model(x_train)
predict = predict.numpy()
plt.plot(x_train.numpy(), y_train.numpy(), 'ro', label='Original data')
plt.plot(x_train.numpy(), predict, label='Fitting line')
plt.show()
源代码
源代码如下
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
# 原始数据为 ndarray,变量类型为 float
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)
# ----------------定义模型------------------
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.linear = nn.Linear(1, 1) # 输入输出都是一维
def forward(self, x):
out = self.linear(x)
return out
# 判断cuda加速是否可用
if torch.cuda.is_available():
model = LinearRegression().cuda()
else:
model = LinearRegression()
# ----------------定义模型完毕--------------
# ------------定义损失函数和优化函数--------------
criterion = nn.MSELoss() # 损失函数
optimizer = optim.SGD(model.parameters(), lr=1e-3)
# array 变 Tensor
x_train = torch.from_numpy(x_train)
y_train = torch.from_numpy(y_train)
# 判断GPU是否可用
if torch.cuda.is_available():
inputs = x_train.cuda()
target = y_train.cuda()
else:
inputs = x_train
target = y_train
# 循环100次
num_epochs = 100
for epoch in range(num_epochs):
# forward
out = model(inputs)
loss = criterion(out, target)
# backward
optimizer.zero_grad() # 梯度归零
loss.backward()
optimizer.step()
# 输出循环结果(每20个epoch输出一次)
if(epoch+1) % 20 == 0:
print('Epoch[{}/{}], loss:{:.6f}'
.format(epoch+1, num_epochs, loss.data))
model.cpu()
predict = model(x_train)
predict = predict.detach().numpy()
plt.plot(x_train.numpy(), y_train.numpy(), 'ro', label='Original data')
plt.plot(x_train.numpy(), predict, label='Fitting line')
plt.show()
梳理
1. 如何简单地将tensor从CPU,简单地转移到GPU上?
.cuda()
即可。
需要用.cuda()
的有:
- 网络实例化的
model
:model = LinearRegression().cuda()
- 所有用到的数据
inputs
和target
:inputs = x_train.cuda()
和target = y_train.cuda()
2. 关于tensor
和Variable
,新版的PyTorch
已经将两者合并。而本次参考的代码比较老旧,故看看有没有可以改进的地方?
一个最明显的改变就是,不用再把tensor
转成Variable
,即:tensor
已经承载了Variable
的功能。
另外需要注意的是,numpy
并不能处理tensor
。所以如果要对最终的输出结果,就需要将输出结果从tensor
转为numpy
的ndarray
,需要用到的函数是.detach().numpy()
。
回想上上一部分提到的,不加.detach()
的报错Can't call numpy() on Variable that requires grad
,再联系深度学习框架PyTorch中的概念和函数(待填坑)中对Variable
的三个组件.data
、.grad
、.grad_fn
。
报错说明了tensor
在与Variable
合并后也有了这三个组件,而能转回ndarray
必须是不带grad
的tensor
(require_grad=False
),而这可以通过.detach()
函数提取出tensor
的.data
组件来实现。
3. 承接第2点,tensor、Variable、CUDA、CPU、ndarray,数据类型太过于复杂,有没有减少数据类型的方法?
首先tensor
和Variable
合并,就只剩下了ndarray
和tensor
。
默认tensor是在CPU上,需要转到GPU需要调用.cuda()
。