前言

论文进行到了深度学习的相关部分,尤其是当下火热的 CV(计算机视觉)的部分,写到如此我的论文部分也就是开始的地方了,这部分并不会涉及 Pytorch 框架的安装以及基础的 Python 问题,关于这个问题可以参考 【sklearn】机器学习环境搭建,Python 的部分可以参考 Python

深度学习框架

深度学习框架可以理解为一种工具,就像是现实中的锤子一样,它可以帮助我们更快速方便的捶打一些东西,当然你也可以选择使用别的什么东西来捶打,而这个使用的工具就是不同的深度学习框架,你甚至可以不使用深度学习的框架,完全自己弄的话也是可以的,只不过这个效率上来说会慢很多。

深度学习框架帮助我们封装了很多方法,屏蔽了很多我们不需要关注的细节,例如,求偏导,反向传播等。也许你看到这里并不是很明白这些词是什么意思,并不要紧,慢慢来做下去就会明白了。

全世界最为流行的深度学习框架有PaddlePaddle、Tensorflow、Caffe、Theano、MXNet、Torch和PyTorch。

斜体内容来源:百度百科

在现在来说,主流的只有两大框架:Tensorflow 和 PyTorch 。在工业界 Tensorflow 使用会比较多,而 PyTorch 使用在学术界的相对会多一些。如果你是研究生的生活中,可以先学习 PyTorch 然后再学习 Tensorflow,两个框架各有优势和缺点,

  1. TensorFlow 的优势:

    • 丰富的生态系统:其全面的库和工具将 TensorFlow 定位为机器学习任务的整体解决方案。
    • 多功能性:它的兼容性扩展到各种语言,包括 C++、JavaScript 和 Python。
    • TensorBoard:一个直观的工具,提供可视化,简化了神经网络的检查和调试。
  2. PyTorch 的优势:

    • 以用户为中心:其以 Python 为中心的特性确保了与Python 代码的无缝集成,使其对数据科学家极具吸引力。
    • 动态计算图:PyTorch 对动态计算图的支持允许对模型进行实时调整。
    • 轻松的多 GPU 支持:使用 PyTorch,数据并行和计算任务的分配变得简单。

接下来将使用一元线性回归来举例,介绍和说明一些深度学习的基础思想和概念说明;

一元线性回归

阅读这部分内容默认你已经有了高等数学基础Python基础

首先,如果你一时间想不起来什么是一元线性回归,如下图所示,你会发现这是你经常接触且熟悉的例子:

image-20231128105945147

图片来源:百度百科-解释变量

好的,现在你应该大概理解了什么是一元线性回归,简单来说,使用一条直线来描述一些数据的趋势。现在我们来假设拟合后的直线为:$\Large z(x_1) = w \cdot x_1 + b$ 。现在我们要使得,$\Large Z(x_1) \cong y_1$ 。

其中,$Z(x_1)$ 表示预测值,$x_1$ 表示已知数据,$y_1$ 表示已知数据的实际值。

在一元线性回归的情况下,我们需要通过大量的已知数据,即 $(x_1,y_1)$ 来学习得到 $w$ 和 $b$ 并最终得到预测值 $Z(x_1)$ 。

损失函数

在开始学习之前,我们需要一个函数来衡量我们是否逼近了我们期望的预测值,否则我们并不知道我们有没有达到我们期望的结果,这个函数在深度学习中称为:损失函数Loss Function:是定义在单个样本上的,是指一个样本的误差,度量模型一次预测的好坏)。损失函数的类型有很多种,例如:

  • 绝对值损失函数:$\large L(y,f(x))=|y-f(x)|$
  • 对数损失函数,又称为交叉熵损失函数:$\large loss = -[y \log_a + (1-y) \log_{1-a}]$
  • 均方差损失函数:$\large loss = (a-y)^2$

当然,还有很多其他的损失函数不再一一列举,在一元线性回归中我们使用均方差函数(使用常规的数学方法最小二乘法):$\Large J= \sum_{i=1}^{m} (z(x_1) - y_1)^2 = \sum_{i=1}^{m} (y_i - wx_i -b)^2$ 。该损失函数,尝试找到一条直线使得直线上下的数据到直线的残差平方和最小

梯度下降

现在我们来采用机器学习的思想来计算这个最优的值,在机器学习的思想下也是有均方差函数,但是会稍微有些不同:$\large loss(w,b) = \frac{1}{2} (z_i - y_i)^2$ 。或许你会好奇为什么要加个 $\large \frac{1}{2}$ ,答案上来说是因为在我们求导的时候将会把平方项导数的系数 2 给消去,是为了方便我们计算

现在你或许会好奇为什么要求导,我们来梳理一下整个过程的思路,我们有了能够衡量预测值是否符合我们预期的损失函数,我们有大量的 $(x,y)$ ,现在我们要求参数 $(w,b)$,这个时候其实换一个思想来说,其实 $(w,b)$ 就是我们的变量,而 $(x,y)$ 是我们的参数,我们希望能够将损失函数降低最低,我们需要求损失函数的最小值,而这个最小值是和参数 $(w,b)$ 相关的,也就是求损失函数关于参数 $(w,b)$ 的偏导,来获得损失函数的极值

现在我们来求损失函数关于 $Z$ 的偏导,也就是求 $Z$ 的梯度:$\Large \frac{\part{loss}}{\part{z_{i}}} = z_i - y_i$ 。

现在我们来求损失函数关于 $(w,b)$ 的偏导,来查看 $(w,b)$ 对于损失函数 $loss$ 的影响。

通过求导的链式法则,可以得到 $w$ 的梯度为:$\Large \frac{\part loos}{\part w} = \frac{\part loss}{\part z_i} \frac{\part z_i}{\part w} = (z_i - y_i)x_i$ 。

计算得到 $b$ 的梯度为:$\Large \frac{\part loos}{\part b} = \frac{\part loss}{\part z_i} \frac{\part z_i}{\part b} = z_i - y_i$ 。

反向传播

现在来看剩下的代码片段就可以理解整个过程:

1
2
3
4
5
6
7
8
9
10
//学习率,或者说梯度下降的每次步长
eta = 0.1
//初始化w,b的值
w,b = 0.0,0.0
//按迭代次数进行训练
for i in range(num_train):
//省略计算求导的方法
//更新 w,b 的参数,反向传播
w = w - eta * dw
b = b - eta * db

现在你应该可以理解整个公式过程,通过很多次的迭代来逼近最优解,即损失函数的最小值

使用 Pytorch 实现

在使用这部分示例代码之前,请确保已经成功安装好了 Pytorch 框架和 CUDA 环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import numpy as np

# torch里要求数据类型必须是float
x = np.arange(1, 12, dtype=np.float32).reshape(-1, 1)
y = 2 * x + 3

import torch
import torch.nn as nn


# 继承nn.module,实现前向传播,线性回归直接可以看做是全连接层
class LinearRegressionModel(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__() # 继承父类方法
self.linear = nn.Linear(input_dim, output_dim) # 定义全连接层,其中input_dim和output_dim是输入和输出数据的维数

# 定义前向传播算法
def forward(self, inp):
out = self.linear(inp) # 输入x后,通过全连接层得到输入出结果out
return out # 返回被全连接层处理后的结果


# 定义线性回归模型
regression_model = LinearRegressionModel(1, 1) # x和y都是一维的

# 可以通过to()或者cuda()使用GPU进行模型的训练,需要将模型和数据都转换到GPU上,也可以指定具体的GPU,如.cuda(1)
device = torch.device("cuda:0")
regression_model.to(device)

epochs = 1000 # 训练次数
learning_rate = 0.01 # 学习速率
optimizer = torch.optim.SGD(regression_model.parameters(), learning_rate) # 优化器,这里使用随机梯度下降算法(SGD)
criterion = nn.MSELoss() # 使用均方误差定义损失函数
criterion.to(device)

for epoch in range(epochs):
# 数据类型转换
inputs = torch.from_numpy(x).to(device) # 由于x是ndarray数组,需要转换成tensor类型,如果用gpu训练,则会通过to函数把数据传入gpu
labels = torch.from_numpy(y).to(device)

# 训练
optimizer.zero_grad() # 每次求偏导都会清零,否则会进行叠加
outputs = regression_model(inputs) # 把输入传入定义的线性回归模型中,进行前向传播
loss = criterion(outputs, labels) # 通过均方误差评估预测误差
loss.backward() # 反向传播
optimizer.step() # 更新权重参数

# 每50次循环打印一次结果
if epoch % 50 == 0:
print("epoch:", epoch, "loss:", loss.item())

predict = regression_model(torch.from_numpy(x).to(device).requires_grad_()).cpu().data.numpy() # 通过训练好的模型预测结果

# torch.save(regression_model.state_dict(), "model.pk1") # 保存模型
# result = regression_model.load_state_dict(torch.load("model.pk1")) # 加载模型

代码来源:0302-利用pytorch解决线性回归问题,做了小部分修改,修复了一个报错问题

运行结果:

image-20231128172304103

End

这部分内容会让你对深度学习的基本概念和 Pytroch 的使用有一些基本的了解,你或许看不懂相关代码,但是这并不重要,重要的是理解和适应 Pytorch 以及深度学习。

最重要的是要时刻动手去做,才能更深刻的理解。

参考文章