个人博客原文:https://blog.gutaicheng.top

分治法求取多维权重(被舍弃的方法)

  • 当涉及的权重有多个时,以二维为例,假设每个权重取100个值,则一共有100^2种组合(维度的诅咒)
  • 首先取一个稀疏点阵,比如 4 x 4 的稀疏点阵,求取这16个点的MSE值
  • 再选取MSE最小的那个点,在它的周围再次取一个 4 x 4 的稀疏点阵,重复计算
  • 以此试图找到使得MSE最小的权重组合

[!TIP]

弊端:为什么被舍弃

  • 机器学习中的优化问题(如线性回归、神经网络训练)通常是连续可导的,不适合用分治法分成若干个小问题

梯度下降算法(推荐)

  • 先随机取一个权重组合
  • 计算取该组合时,损失函数在此对权重的导数(也叫梯度);
    • 导数为正,代表往右递增,往左递减
    • 导数为负,代表往右递减,往左递增
  • 以导数为基准,对权重进行迭代,而不是像穷举法那样以相同的步长进行迭代

一维权重求导过程

权重迭代公式

  • 权重迭代公式中的 α 的名称叫做学习率,是一个很重要的参数,相当于穷举法中的步幅
    • 它不是通过训练学习出来的,而是需要我们在训练前手动设置。选择一个合适的学习率是训练一个高性能模型的关键一步。
    • 步子迈得太大,可能会直接跳过损失函数的最小值点,甚至在“山谷”的两侧来回震荡,导致损失值越来越大,永远无法收敛。这就像你下山时,每一步都跨得太大,结果不是跑到山脚下,而是跳到了对面的悬崖上。
    • 步子迈得太小,虽然能够保证每一步都朝着正确的方向前进,但收敛速度会非常慢,需要进行大量的迭代才能到达最小值。这会极大地增加训练所需的时间和计算资源。这就像你在下山时,每一步都只挪动一小段距离,要花很长时间才能到达山脚。

梯度下降算法的问题

  • 同样是局部最优解,但是比分治法处理的更平滑,也更容易克服,比如我采用多轮次,每轮选取不同的初始点。
  • 鞍点问题:
    • 一维权重时,也就是驻点,即导数为零的点,会导致迭代停止
    • 多维权重时,可能会形成像马鞍(薯片)一样的曲面,从某个方向看,这个点是局部最高点(比如马鞍的前后方向),从另一个方向看,这个点又是局部最低点(比如马鞍的左右方向)。

简化模型使用梯度下降算法预测权重(无截距)

# 导入计算和绘图用的包
import matplotlib.pyplot as plt

# 定义数据集
x_data = [1.0, 2.0, 3.0] # 输入
y_data = [2.0, 4.0, 6.0] # 输出

def forward(x, w):
    return x*w

# 单个样本的损失loss
def loss(x, y, w):
    y_pred = forward(x, w)
    return (y_pred - y) * (y_pred - y)

# MSE损失函数
def MSE(xd, yd, w):
    loss_sum = 0
    for x_val, y_val in zip(xd, yd):
        loss_sum += loss(x_val, y_val, w)
    return loss_sum / len(xd)

# 定义梯度函数
def gradient(xd, yd, w):
    grad_sum = 0
    for x_val, y_val in zip(xd, yd):
        grad_sum += 2 * x_val * (x_val * w - y_val)
    return grad_sum / len(xd)

# 初始随机权重
w = 1.0
# 定义学习率
a = 0.01
# 轮次值列表
epoch_list = []
# MSE误差列表
mse_list = []

print('训练前的预测值 x =', 4, 'y =',  forward(4, w))
for epoch in range(100):
    mes_val = MSE(x_data, y_data, w)
    mse_list.append(mes_val)
    grad_val = gradient(x_data, y_data, w)
    w -= a * grad_val
    epoch_list.append(epoch)
    # 详细值
    print(f"epoch: {epoch}, w: {w}, mes: {mes_val}")
    # 保留两位小数的值
    # print(f"epoch: {epoch}, w: {round(w, 2)}, mes: {round(mes_val, 2)}")
print('训练后的预测值 x =', 4, 'y =', forward(4, w))

plt.plot(epoch_list, mse_list)
plt.xlabel('epoch')
plt.ylabel('MES')
plt.show()

image-20250918204114132

随机梯度下降算法(SGD)

  • 上述的梯度算法,对于每次权重迭代的大小是由所有数据的损失汇总取平均值得到的误差来计算的,这样的方式在遇到鞍点时可能会停滞不前

  • 而随机梯度算法,权重的迭代大小是由随机取一个样本(也可以按照顺序随机,也叫顺序随机梯度算法),用其损失对权重求导,这样的方式可能可以跨过鞍点,使权重继续前进

    image-20250918213038164

# 导入计算和绘图用的包
import random
import matplotlib.pyplot as plt

# 定义数据集
x_data = [1.0, 2.0, 3.0]  # 输入
y_data = [2.0, 4.0, 6.0]  # 输出

# 用于计算 y 的预测值
def forward(x, w):
    return x * w

# 单个样本的损失loss
def loss(x, y, w):
    y_pred = forward(x, w)
    return (y_pred - y) * (y_pred - y)

# 定义梯度函数
def gradient(x, y, w):
    return 2 * x * (x * w - y)

# 初始化权重和学习率
w = 1.0
a = 0.01

# 用于记录每个轮次的平均损失
epochs_list = []
costs_list = []

print('训练前的预测值 x = 4, y =', forward(4, w))

# 训练循环
for epoch in range(100):
    # 在每个轮次开始时,初始化总损失
    epoch_loss_sum = 0

    # 随机选择一个样本进行训练
    index = random.randint(0, 2)# 这里是真随机
    x_val = x_data[index]
    y_val = y_data[index]

    # 计算梯度并更新权重
    grad = gradient(x_val, y_val, w)
    w -= a * grad

    # 计算当前样本的损失并累加到总损失中
    current_loss = loss(x_val, y_val, w)

    # 因为是SGD,一个轮次只训练一个样本,所以直接记录当前损失即可
    epoch_loss = current_loss / 1 # loss的计算只是为了输出体现为0而已

    # 打印当前轮次的信息
    print(f'Epoch: {epoch}, w = {w:.2f}, loss = {epoch_loss:.2f}')

    # 记录每个轮次的平均损失和轮次数,用于绘图
    epochs_list.append(epoch)
    costs_list.append(epoch_loss)

print('训练后的预测值 x = 4, y =', forward(4, w))

# 绘制损失曲线
plt.plot(epochs_list, costs_list)
plt.ylabel('Cost')
plt.xlabel('Epoch')
plt.show()
  • 输出结果(由于每次都取单独一个样本进行计算梯度,所以波动会很大,每次图像基本不一样)

    image-20250918221902242

小批量梯度下降算法(Mini-BGD)

  • 现在普遍也称BGD
  • 比如训练集有8对数据,每个小批次为2,则会分为4个批次batch
  • 每个轮次epoch,都会计算这四个批次batch的的小平均损失,并且累加取平均,作为当前轮次epoch的平均损失
    • 简单说就是每个小batch里要计算一次平均,每个轮次也要计算一次平均损失
import numpy as np
import matplotlib.pyplot as plt

# 假设数据集有8组
x_data = np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
y_data = np.array([2.3, 4.1, 5.8, 8.4, 10.3, 11.7, 14.5, 15.9])

# 用于计算 y 的预测值
def forward(x, w):
    return x * w

# 使用 NumPy 计算整个样本的平均损失
def avg_loss(x, y, w):
    y_pred = forward(x, w)
    return np.mean((y_pred - y) ** 2)

# 使用 NumPy 计算整个Batch的平均梯度
def avg_gradient(x, y, w):
    return np.mean(2 * x * (x * w - y))

# 初始化
w = 1.0 # 权重
a = 0.0001 # 学习率
batch_size = 2 # 批次Batch大小
num_samples = len(x_data) # 数据总组数
num_batches = num_samples // batch_size  # 批次Batch数量

# 轮次列表 与 轮次的平均损失列表
epochs_list = []
costs_list = []

print('训练前的预测值 x = 4, y =', forward(4, w))

# 训练循环
for epoch in range(200):
    # 第一步:打乱数据
    indices = np.random.permutation(num_samples) # 生成一个包含从 0 到 num_samples-1 的随机排列数组
    # 每个轮次的 数据顺序都不同
    shuffled_x = x_data[indices]
    shuffled_y = y_data[indices]

    # 第二步:创建批次Batch并循环
    for i in range(num_batches):
        # 得到当前批次Batch的 起始 和 终止 索引
        start_index = i * batch_size
        end_index = start_index + batch_size
        # 获取当前批次Batch的数据
        batch_x = shuffled_x[start_index:end_index]
        batch_y = shuffled_y[start_index:end_index]

        # 计算当前批次Batch的平均梯度
        avg_batch_grad = avg_gradient(batch_x, batch_y, w)

        # 更新权重
        w -= a * avg_batch_grad

    # 计算并记录当前轮次(epoch)的平均损失
    avg_epoch_loss = avg_loss(x_data, y_data, w)

    # 记录 当前轮次 和 当前轮次 的平均损失至数组方便绘图
    epochs_list.append(epoch)
    costs_list.append(avg_epoch_loss)

    print(f'Epoch: {epoch}, w = {w}, avg_loss = {avg_epoch_loss}')

print('训练后的预测值 x = 4, y =', forward(4, w))

# 绘制损失曲线
plt.plot(epochs_list, costs_list)
plt.ylabel('Cost')
plt.xlabel('Epoch')
plt.show()

image-20250919174947709

  • 上述代码在求取平均值时采用了**np.mean()**的方法,使代码更简洁。
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐