深度学习笔记
1. 深度学习简介
一个学校有 门考试,每一门考试所占权重为 , 对于一个学生,他的每一门考试的分数为 , 再加上一个固定的常量 , 学校用于判断是否录取这个学生的逻辑为:
缩写为:
由于最终所期望的计算结果 为一个数值,所以 是一个 的矩阵:
而 则是一个 的矩阵(如下所示),这样进行矩阵乘法 才会得到一个 的矩阵,即 。
的值则被称之为预测值,而 则被称之为真实值,学校会根据这个值来判断是否录取这个学生。深度学习的目的就是找到一组 和 的值,使得预测值 与真实值 之间的差距最小。
2. 感知器 (Perceptron)
在一个坐标系中有两类点,一类是红色,一类是蓝色,每个点都有两个坐标 ,现在需要找到一条直线,将这两类点分开。
首先,我们假设这条直线的方程为:
其中 和 是直线的斜率, 是直线的截距。
最开始,我们先随机初始化 , 和 的值,然后根据这个方程计算出每个点的 值,如果 值大于 0,则将这个点归为红色,如果 值小于 0,则将这个点归为蓝色。
然后我们对每个点进行分类,如果分类错误,则调整 , 和 的值,直到所有点都被正确分类。调整的逻辑为:
其中 是一个学习率,用来控制每次调整的幅度。在课程种提供了代码,可以参考代码来实现感知器。
首先我们有一个 csv 数据集,数据集的格式为:
0.78051,-0.063669,1
0.28774,0.29139,1
0.40714,0.17878,0
0.2923,0.4217,0
...
其中第一列和第二列是特征 和 ,第三列是标签 s,标签为 1 表示红色,标签为 0 表示蓝色。
然后我们读取数据集,并进行处理,代码如下:
感知器训练
import matplotlib.pyplot as plt
import numpy as np
import pandas
def stepFunction(t):
return 1 if t >= 0 else 0
def prediction(X: np.ndarray, W: np.ndarray, b: float) -> int:
return stepFunction((np.matmul(X, W) + b)[0])
def perceptronStep(
X: np.ndarray,
y: np.ndarray,
W: np.ndarray,
b: float,
learn_rate: float = 0.01,
) -> tuple[np.ndarray, float]:
for i, x_n in enumerate(X):
y_hat = prediction(x_n, W, b)
match y[i] - y_hat:
case 1:
W[0] += x_n[0] * learn_rate
W[1] += x_n[1] * learn_rate
b += learn_rate
case -1:
W[0] -= x_n[0] * learn_rate
W[1] -= x_n[1] * learn_rate
b -= learn_rate
return W, b
def trainPerceptronAlgorithm(
X: np.ndarray,
y: np.ndarray,
learn_rate: float = 0.01,
num_epochs: int = 25,
) -> list[tuple[float, float]]:
x_min, x_max = min(X.T[0]), max(X.T[0])
y_min, y_max = min(X.T[1]), max(X.T[1])
weights = np.array(np.random.rand(2, 1))
bias = np.random.rand(1)[0] + x_max
boundary_lines = []
for i in range(num_epochs):
weights, b = perceptronStep(X, y, weights, bias, learn_rate)
boundary_lines.append((-weights[0] / weights[1], -b / weights[1]))
# draw all the boundary lines
x_vals = np.linspace(x_min, x_max, 100)
line_n = 10
for i, (m, b) in enumerate(boundary_lines[-line_n:]):
y_vals = m * x_vals + b
color = "b" if i < line_n - 1 else "r"
line_style = "--" if i < line_n - 1 else "-"
plt.plot(x_vals, y_vals, color=color, linestyle=line_style)
# plot the data points
plt.scatter(X[:, 0], X[:, 1], c=["b" if y_n else "r" for y_n in y])
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.show()
return boundary_lines
def load_data() -> tuple[np.ndarray, np.ndarray]:
df = pandas.read_csv("data.csv")
X = df[["x", "y"]].to_numpy()
y = df["label"].to_numpy()
return X, y
def main():
X, y = load_data()
trainPerceptronAlgorithm(X, y, num_epochs=25)
其中,最核心的函数是 perceptronStep
,这个函数会调用 prediction
函数来计算每个点的预测值,然后根据预测值和真实值的差距来调整 , 和 的值。而 trainPerceptronAlgorithm
函数则连续调用 perceptronStep
函数,num_epochs
参数用来控制迭代次数,learn_rate
参数用来控制每次调整的幅度,最终会绘制出每次迭代(训练)后的直线,并展示出来。此时我们得到的 , 和 的值就是最终的模型参数。
3. 激活函数 (Activation Function) 与误差函数 (Error Function)
上面的感知器图像是一条直线,但实际场景中,直线很可能无法对所有点进行分类。此时我们需要更加复杂的函数来告诉我们当前距离目标还有多远(误差的大小)。为了进行梯度下降 (Gradient Descent),我们需要一个连续、可微分 (Differentiable) 的函数来衡量误差。当输入变化时,其输出也应该变化,否则就像在阿兹台克金字塔上一样,当我们水平方向移动时,并没有任何高度变化,无法对训练方向进行优化。
对于非线性的误差函数,值只有 0 或 1 ,当输入值在某个零界点变化时,输出从 0 跃迁到 1 ,这对于预测来说并不友好,我们需要的是当输入一个很大的负数时,输出接近 0 ,当输入一个很大的正数时,输出接近 1 ,所以采用 sigmoid 函数作为激活函数。
Sigmoid 函数的表达式为:
当分类的对象不止是 2 种,而是 种时,可以使用 Softmax 函数来进行分类。对于一组输入 ,Softmax 函数的表达式为:
最大似然估计(Maximum Likelihood Estimation, MLE)是深度学习训练中常用的目标函数,其核心思想是:通过优化模型参数,使模型对真实标签的预测概率最大化。
假设我们有两个模型,想比较它们的优劣,可以将同一组带有真实标签的数据输入两个模型,观察每个模型对这些标签的预测概率。将所有样本的预测概率相乘,得到整体的似然值,用以衡量模型对数据的拟合程度。
然而,由于概率值通常较小,多个样本的概率乘积会迅速趋近于零,且对单个样本概率的微小变化会导致整体似然值产生较大波动。因此,在实际计算中,我们通常对似然函数取 对数 ,将乘积变为求和,从而提高数值稳定性并便于优化。
由于概率都是小于 1 的,所以对数的值都为负数,将它们的和乘以 -1 得到一个正数 (按照上面的公式为 4.78) ,这个数则被称为交叉熵 (Cross Entropy),交叉熵越大则误差越大,我们的目标就是最小化交叉熵。
上面两个交叉熵公式,分别是:
- 二分类交叉熵: 是样本数, 是样本 的真实标签, 是样本 的预测概率。
- 多分类交叉熵: 是样本数, 是类别数, 是样本 的真实标签为 的概率, 是样本 的预测概率。
对于多分类交叉熵,当 时,可以进一步简化为二分类交叉熵。
误差函数则为交叉熵除以样本数,其公式为
其中 是样本数, 是类别数, 是样本 的真实标签为 的概率, 是样本 的预测概率。
在误差函数中, 是将第 个样本在第 个类别上的线性输出(logit)经由激活函数(sigmoid 或 softmax)转换后得到的预测概率。
总结:我们的最终目标是要找到 中的 和 。于是我们先随机选择一组 和 ,用已有的样本代入进去,得到一个值 ,再把这个 代入到激活函数(根据任务类别选择 sigmoid 或 softmax),得到预测值 。接着将多个 拿来计算误差(交叉熵)。然后通过梯度下降(Gradient Descent)来优化 和 ,重复上面的步骤,并不断寻找让交叉熵变小的方向。经过多次尝试,最终找到一组能使预测结果与实际标签值相似度最大的 和 ,这就是训练的过程。
4. 梯度下降 (Gradient Descent)
对于只有一个参数的误差函数,它的图像是一条二维坐标系中的曲线。梯度下降的过程可以理解为:从某个初始点出发,计算该点的导数(即梯度),从而确定函数值减小最快的方向——也就是负梯度方向。然后沿着这个方向更新参数,使函数值逐步降低,最终逼近最小值。
在单变量函数中,导数表示函数图像在某点的切线斜率。梯度下降并不是沿着切线方向前进,而是沿着负导数方向移动,因为我们希望让函数值变小。
在实际应用中,误差函数通常是由多个参数构成的多维函数。为了使用梯度下降法优化模型,需要对该函数对所有参数求偏导,得到梯度向量,并沿着负梯度方向更新参数,从而逐步减小误差。
例如 sigmoid 函数为
其导数为
梯度下降法通常用于优化连续可导的损失函数(如交叉熵),其输出是概率值。当模型预测正确时,损失函数的值可能仍然偏离最优,因此梯度仍然存在,可以继续更新参数 和 ,使模型更加稳健。而感知器算法的输出是离散的 0 或 1,使用的是硬阈值函数。当预测正确时,它不会更新参数,因此无法进一步优化模型。