Torch的封装的还是前馈神经网络最核心的梯度计算,然后迭代实现梯度下降,达到参数学习的结果。本主题通过一个例子说明这种学习模式。当然更复杂的多层结果也不会太难,但是会很繁琐,加上Torch乱七八糟的损失函数与激活函数的混杂,会增加这种繁琐度。
1. 梯度下降的思想;
2. 线性回归的实现;
梯度下降算法
梯度下降思想
- 梯度下降是给定一个函数,找到这个函数的最小值点的方法或者算法:(最小值比较常用:比如损失最小)
- 两个操作
- 使用导数作为下降方向;
- 用户定义一个因子作为梯度下降的速度;
- 反复执行操作,可以接近最小值
- 两个操作
梯度下降的例子
- 求如下函数的极小值
- 最小值点在
使用numpy实现梯度下降
import numpy as np
x = 0 # 最小值点的初始值:肯定不正确,迭代后应该接近1
learn_rate = 0.01 # 学习率,控制梯度的下降速度
grad_fn = lambda x: 2 * x - 2 # 导数函数
epoch = 400 # 梯度下降的迭代次数
x_list = []
for epoch in range(epoch):
x_grad = grad_fn(x) # 计算导数 ( 梯度 )
x -= x_grad * learn_rate # 迭代梯度
x_list.append(x)
# 可视化最小值点逼近过程
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(range(epoch), x_list[0:-1])
plt.show()
使用torch实现梯度下降
import torch
x = torch.Tensor([0.1]) # 最小值点的初始值:肯定不正确,迭代后应该接近1
x.requires_grad=True
learn_rate = 0.01 # 学习率,控制梯度的下降速度
epoch = 400 # 梯度下降的迭代次数
x_list = []
for epoch in range(epoch):
y = x ** 2 - 2 * x + 1
y.backward(retain_graph=True) # 反复计算梯度
# print(x.grad)
with torch.autograd.no_grad(): #
x -= learn_rate * x.grad # 直接修改就不允许做数据运算,否则会被跟踪计算历史
x_list.append(x.detach().clone().numpy()) # 防止公用数据区
x.grad.zero_()
# 可视化最小值点逼近过程
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(range(epoch), x_list[0:-1])
plt.show()
线性回归实现
线性回归模型
预测输出模型
-
- 其中:
- 其中:
损失模型
- 通用表示模型:
- 矩阵内积表示模型: ,其中
- ,
Torch实现
- 使用一个身高与年龄的数据集,训练出他们之间的线性关系,这个数据集是一元的线性关系。
数据集
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
age = np.loadtxt("datasets/ex2x.dat") # age:年龄
height = np.loadtxt("datasets/ex2y.dat") # height:身高
plt.scatter(age, height, color=(0, 0, 1, 1), s=1**2)
plt.show()
forward模型表示
- 不考虑偏置项:
- 考虑偏置项:
import torch
# 随机权重系数
w = torch.randn(1, 1, dtype=torch.float64)
# 随机偏置项
b = torch.randn(1, dtype=torch.float64)
# 把前面加载的numpy数据集转换为Tensor(注意shape)
x = torch.from_numpy(np.mat(age).T)
y = torch.from_numpy(np.mat(height).T)
# forward的输出模型
y_ = x @ w + b
损失模型表示
loss = torch.mean((y - y_) ** 2) # 上面的是平方和,没有使用平均值,这里采用平均值方式
print(loss)
tensor(40.6979, dtype=torch.float64)
损失最小值求解:梯度下降
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import torch
# 1. 数据集加载------------------
age = np.loadtxt("datasets/ex2x.dat") # age:年龄
height = np.loadtxt("datasets/ex2y.dat") # height:身高
# 把前面加载的numpy数据集转换为Tensor(注意shape)
x = torch.from_numpy(np.mat(age).T)
y = torch.from_numpy(np.mat(height).T)
# 2. 模型构建-------------------
# 随机权重系数
w = torch.tensor([[0]], dtype=torch.float64)
w.requires_grad = True
# 随机偏置项
b = torch.tensor([0], dtype=torch.float64)
b.requires_grad = True
# 3. 梯度下降迭代,求解最小值
epoch = 10000 # 反复梯度下降
learn_rate = 0.001 # 学习率(控制梯度下降速度)
list_loss = []
for e in range(epoch):
# forward的输出模型
y_ = x @ w + b
# 损失模型
loss = torch.mean((y- y_) ** 2)
list_loss.append(loss) # 保存损失函数
# backward梯度模型
loss.backward() # 不需要保留图,因为整个计算模型每轮循环都是重新构建的。
# w与b的梯度
w.data -= learn_rate * w.grad.data
b.data -= learn_rate * b.grad.data
with torch.autograd.no_grad(): #
w -= learn_rate * w.grad # 直接修改就不允许做数据运算,否则会被跟踪计算历史
w.grad.zero_()
b -= learn_rate * b.grad # 直接修改就不允许做数据运算,否则会被跟踪计算历史
b.grad.zero_()
# 使用矩阵求解的结果(sklearn的模块计算的结果)
# w: [0.06388117]
# b: 0.7501625370012386
# 4. 可视化
x_p = torch.linspace(1, 9, 100, dtype=torch.float64)
y_p = x_p.view(x_p.shape[0], 1) @ w + b
plt.scatter(age, height, color=(0, 0, 1, 1), s=1**2)
plt.plot(
x_p.detach().numpy(), y_p.detach().numpy(), color=(1, 0, 0, 1),
label=F"w={w.detach().numpy()[0,0]:10.4f}, b={b.detach().numpy()[0]:10.4f}")
plt.legend()
plt.show()
附录
Tensor形状不一致导致的误差
- 当在计算均方差,如果需要的两个张量形状不一致,计算的值会产生误差,因为某些时候,张量的差运算尽管要求形状一致,但实际形状不一致的时候,也会计算,但是计算规则被改变,从而导致计算结果的差异。
- 下面是一个例子说明
import torch
v1 = torch.Tensor([
[1],[2],[3]
])
v2 = torch.Tensor([1,2,3])
print(torch.mean((v1-v1)**2)) # 正确
print(torch.mean((v1-v2)**2)) # 错误(形状不一致,导致的误差)
tensor(0.)
tensor(1.3333)
import torch
v1 = torch.Tensor([
[1],[2],[3]
])
v2 = torch.Tensor([1,2,3])
print(v1-v2) # 形状不一致,计算出来的结果是这样的,所以在torch中形状不一致的运算,一定要小心。
tensor([[ 0., -1., -2.],
[ 1., 0., -1.],
[ 2., 1., 0.]])
说明
- 实际上Torch的核心的梯度计算(本质就是求导)比较容易理解易用。加上动态图的设计技巧,这就是Torch被很多人喜欢的原因吧?从性能上说,Tensorflow还是略胜一筹!
下面贴上一段我实现的多层全链接分类器,来说明Torch的深度学习实现模式
import torch
import sklearn.datasets
from sklearn.model_selection import train_test_split
# 1. 准备数据 --------------------
data, target = sklearn.datasets.load_iris(return_X_y=True)
# data, _, target, _ = train_test_split(data, target, test_size=0.01, random_state=42)
x = torch.Tensor(data) # 全部150个样本,一共三类
y = torch.tensor(target) # 使用交叉熵需要LongTensor
# 2. 准备预测模型 ----------------
w1 = torch.randn(12, 4) # 前面输出特征,后面输入特征
b1 = torch.randn(12) # 输出特征
w1.requires_grad=True
b1.requires_grad=True
w2 = torch.randn(6, 12) # 前面输出特征,后面输入特征
b2 = torch.randn(6) # 输出特征
w2.requires_grad=True
b2.requires_grad=True
w3 = torch.randn(3, 6) # 前面输出特征,后面输入特征
b3 = torch.randn(3) # 输出特征
w3.requires_grad=True
b3.requires_grad=True
# #########################
def forward(input):
o1 = torch.nn.functional.linear(input, w1, b1)
y1 = torch.sigmoid(o1) # torch.nn.functional.sigmoid已经不推荐使用
x1 = y1
# ----
o2= torch.nn.functional.linear(x1, w2, b2)
y2 = torch.sigmoid(o2)
x2 = y2
# ----
o3= torch.nn.functional.linear(x2, w3, b3)
# y3 = torch.sigmoid(o3)
return o3
# 迭代轮数
epoch = 10000
# 学习率
learn_rate = 0.005
for n in range(epoch):
# 计算梯度
y_= forward(x)
loss = torch.nn.functional.cross_entropy(y_, y)
# 3.2 损失优化
loss.backward(retain_graph=True)
with torch.autograd.no_grad():
# 更新梯度
w1 -= learn_rate * w1.grad
b1 -= learn_rate * b1.grad
w2 -= learn_rate * w2.grad
b2 -= learn_rate * b2.grad
w3 -= learn_rate * w3.grad
b3 -= learn_rate * b3.grad
# 清空上一轮的梯度
w1.grad.zero_()
b1.grad.zero_()
w2.grad.zero_()
b2.grad.zero_()
w3.grad.zero_()
b3.grad.zero_()
if n % 1000 == 0:
print(F"损失值:{loss.detach().numpy():8.6f}, ", end="")
y_ = forward(x)
y_ = y_.log_softmax(dim=1) # 有使用交叉熵,其中做了一个log_softmax运算,所以这儿也作为概率使用,并调用了log_softmax运算
predict = y_.argmax(dim=1)
print(F"\t训练集测试准确度:{(predict == y).float().mean()*100:8.2f}%")
# # 4. 测试与评估------------------
# y_ = forward(x)
# y_ = y_.log_softmax(dim=1) # 有使用交叉熵,其中做了一个log_softmax运算,所以这儿也作为概率使用,并调用了log_softmax运算
# predict = y_.argmax(dim=1)
# print(predict)
# print((predict == y).float().mean())
分类效果输出:
损失值:1.289355, 训练集测试准确度: 33.33%
损失值:0.949061, 训练集测试准确度: 75.33%
损失值:0.838661, 训练集测试准确度: 84.00%
损失值:0.727442, 训练集测试准确度: 88.00%
损失值:0.621567, 训练集测试准确度: 91.33%
损失值:0.523153, 训练集测试准确度: 95.33%
损失值:0.444559, 训练集测试准确度: 96.67%
损失值:0.383188, 训练集测试准确度: 97.33%
损失值:0.334608, 训练集测试准确度: 98.00%
损失值:0.295041, 训练集测试准确度: 98.00%
- 对鸢尾花的三类数据集来说,基本上是很好的线性分类效果了。