文章目录
深度学习本质上是一个数据驱动的参数优化过程,通过"准备数据→设计模型→训练优化→预测评估"四个步骤,让机器从历史数据中学习规律,从而对未来进行预测。
这个过程就像人类学习一样:我们先观察大量历史现象(数据准备),建立对世界的理解模型(模型设计),通过不断试错和调整来完善认知(训练优化),最终能够对新情况做出合理判断(预测评估)。房价预测这个经典问题完美诠释了这一过程。
基础概念与原理
在深入四个步骤之前,我们需要理解几个核心概念。机器学习与传统编程的根本区别在于:传统编程是"规则驱动",我们告诉计算机具体的计算步骤;而机器学习是"数据驱动",我们让计算机从数据中自动发现规律。
具体来说,我们定义了一个参数化的模型(如线性函数 y = ax + b),然后通过优化算法自动找到最优的参数组合,使得模型能够最好地拟合历史数据。这个过程的核心是损失函数和梯度下降:损失函数衡量模型预测与真实值的差距,梯度下降则通过计算损失函数对参数的导数,指导参数向减小损失的方向更新,逐渐求出最佳参数。
完整代码见:【初识pytorch】从房价预测来学习深度学习的四个步骤
第一步:准备数据 - 构建学习的基础
数据是机器学习的燃料,没有高质量的数据,再先进的算法也无法发挥作用。 在房价预测问题中,我们需要收集时间序列的房价数据,这些数据构成了模型学习的"经验"。
数据生成与特征工程(选择对预测有用的特征)
import torch
import matplotlib.pyplot as plt
import numpy as np
# 生成时间特征:0-100个月
# torch.linspace(start, end, steps) 生成从start到end的steps个均匀分布的数字
# start=0: 起始时间(第0个月)
# end=100: 结束时间(第100个月)
# steps=100: 生成100个时间点
x = torch.linspace(0, 100, 100).type(torch.FloatTensor)
# 生成房价数据:y = x + 噪声(模拟真实房价趋势)
# torch.randn(size) 生成服从标准正态分布N(0,1)的随机数
# size=100: 生成100个随机数
# * 10: 将标准差从1放大到10,模拟房价的波动性
rand = torch.randn(100) * 10 # 添加随机噪声
y = x + rand # 房价 = 时间趋势 + 随机波动
特征工程的基本原理: 特征工程是机器学习中的关键步骤,它涉及从原始数据中提取、转换和选择对预测任务有用的特征。在房价预测中,我们选择时间作为特征,这基于以下假设:
- 时间趋势假设:房价通常随时间呈现某种趋势(上涨、下跌或平稳)
- 连续性假设:相邻时间点的房价变化是连续的,不会出现剧烈跳跃
- 可预测性假设:历史房价模式对未来房价有预测价值
数学表达式: 我们假设房价与时间的关系为:
y i = x i + ϵ i y_i = x_i + \epsilon_i yi=xi+ϵi
其中:
- y i y_i yi 是第i个月的房价
- x i x_i xi 是第i个月的时间点
- ϵ i ∼ N ( 0 , 10 2 ) \epsilon_i \sim N(0, 10^2) ϵi∼N(0,102) 是随机噪声项
这个模型假设房价随时间线性增长,但受到随机因素影响。
数据集划分的重要性
# 训练集:用于模型学习(前90个月)
# x[: -10] 表示从开始到倒数第11个元素(包含)
# 即索引0到89,共90个数据点
x_train = x[: -10]
y_train = y[: -10]
# 测试集:用于评估泛化能力(后10个月)
# x[-10 :] 表示从倒数第10个元素到末尾
# 即索引90到99,共10个数据点
x_test = x[-10 :]
y_test = y[-10 :]
数据集划分的核心目的是模拟真实预测场景。 训练集相当于历史经验,测试集相当于未来情况。模型只能看到训练数据,在测试集上的表现才能真正反映其预测能力。这种划分方式确保了模型评估的客观性,避免了"作弊"现象。
第二步:设计模型 - 建立数学表达
模型是对现实世界的数学抽象,它决定了机器如何理解和预测数据。 在房价预测中,我们假设房价与时间存在线性关系,这是一个简化的假设,但为理解机器学习原理提供了清晰的框架。
线性回归模型的数学表达
# 初始化模型参数(需要梯度计算)
# torch.rand(size) 生成[0,1)之间的均匀分布随机数
# requires_grad=True 告诉PyTorch需要计算这个张量的梯度
a = torch.rand(1, requires_grad = True) # 斜率参数,初始化为随机值
b = torch.rand(1, requires_grad = True) # 截距参数,初始化为随机值
# 线性模型:y = ax + b
# expand_as(x_train) 将a和b的维度扩展到与x_train相同
# 这样可以进行元素级别的乘法运算
predictions = a.expand_as(x_train) * x_train + b.expand_as(x_train)
模型设计的核心是参数化表达。 我们将房价预测问题转化为寻找最优参数 a 和 b 的问题。这两个参数控制了直线的斜率和位置,通过调整它们,我们可以拟合不同的线性关系。
数学表达式: 线性回归模型可以表示为:
y ^ i = a x i + b \hat{y}_i = ax_i + b y^i=axi+b
其中:
- y ^ i \hat{y}_i y^i 是模型对第i个月房价的预测值
- a a a 是斜率参数,表示房价随时间的变化率
- b b b 是截距参数,表示时间起点(x=0)时的房价
- x i x_i xi 是第i个月的时间点
损失函数的设计哲学
# 均方误差损失函数
# predictions: 模型预测值
# y_train: 真实标签值
# (predictions - y_train) ** 2: 计算每个样本的平方误差
# torch.mean(): 计算所有样本的平均误差
loss = torch.mean((predictions - y_train) ** 2)
损失函数是模型优化的"指南针",它量化了模型预测的准确性。 均方误差(Mean Squared Error, MSE)的数学表达式为:
L ( a , b ) = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 = 1 n ∑ i = 1 n ( y i − ( a x i + b ) ) 2 L(a, b) = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 = \frac{1}{n}\sum_{i=1}^{n}(y_i - (ax_i + b))^2 L(a,b)=n1i=1∑n(yi−y^i)2=n1i=1∑n(yi−(axi+b))2
其中:
- L ( a , b ) L(a, b) L(a,b) 是损失函数,它是参数a和b的函数
- n n n 是训练样本数量(这里是90)
- y i y_i yi 是第i个样本的真实房价
- y ^ i = a x i + b \hat{y}_i = ax_i + b y^i=axi+b 是第i个样本的预测房价
均方误差之所以被广泛使用,是因为它具有几个重要性质:
- 可微性:MSE是连续可微的,便于梯度计算
- 对称性:正负误差同等对待,符合直觉
- 惩罚性:大误差被平方放大,小误差相对减小,鼓励模型关注大误差
- 统计意义:MSE是方差的无偏估计,具有统计学意义
更重要的是,损失函数的选择反映了我们对"什么是好的预测"的理解。在房价预测中,我们关心的是预测值与真实值的平均差距,因此选择均方误差是合理的。
第三步:训练优化 - 自动参数学习
训练过程是机器学习的核心,它体现了"从数据中学习"的本质。 通过梯度下降算法,模型能够自动调整参数,逐步接近最优解。
梯度下降的直观理解
# 初始化参数和学习率
a = torch.rand(1, requires_grad = True) # 斜率参数,随机初始化
b = torch.rand(1, requires_grad = True) # 截距参数,随机初始化
learning_rate = 0.0001 # 学习率,控制参数更新步长
for i in range(1000): # 训练1000轮
# 前向传播:计算预测值
predictions = a.expand_as(x_train) * x_train + b.expand_as(x_train)
# 计算损失
loss = torch.mean((predictions - y_train) ** 2)
# 反向传播:计算梯度
# backward() 自动计算损失函数对所有requires_grad=True的张量的梯度
loss.backward()
# 参数更新:梯度下降
# a.data.add_(-learning_rate * a.grad.data) 等价于 a = a - learning_rate * ∂L/∂a
# 负号表示向梯度相反方向更新(下山方向)
a.data.add_(- learning_rate * a.grad.data)
b.data.add_(- learning_rate * b.grad.data)
# 清空梯度(防止累积)
# 每次迭代后必须清零梯度,否则梯度会累积
a.grad.data.zero_()
b.grad.data.zero_()
梯度下降算法可以类比为"盲人下山"的过程。 想象一个盲人站在山上,想要找到最低点。他会在原地转一圈,感受哪个方向下降最快(梯度方向),然后朝那个方向迈出一步(参数更新)。重复这个过程,最终会到达山谷的最低点(损失函数的最小值)。
梯度下降的数学原理: 对于我们的线性回归问题,梯度下降的更新公式为:
a t + 1 = a t − α ∂ L ∂ a a_{t+1} = a_t - \alpha \frac{\partial L}{\partial a} at+1=at−α∂a∂L
b t + 1 = b t − α ∂ L ∂ b b_{t+1} = b_t - \alpha \frac{\partial L}{\partial b} bt+1=bt−α∂b∂L
其中:
- a t , b t a_t, b_t at,bt 是第t次迭代的参数值
- α \alpha α 是学习率(learning_rate)
- ∂ L ∂ a , ∂ L ∂ b \frac{\partial L}{\partial a}, \frac{\partial L}{\partial b} ∂a∂L,∂b∂L 是损失函数对参数的偏导数
偏导数的计算: 对于MSE损失函数:
L ( a , b ) = 1 n ∑ i = 1 n ( y i − ( a x i + b ) ) 2 L(a, b) = \frac{1}{n}\sum_{i=1}^{n}(y_i - (ax_i + b))^2 L(a,b)=n1i=1∑n(yi−(axi+b))2
偏导数为:
∂ L ∂ a = − 2 n ∑ i = 1 n ( y i − ( a x i + b ) ) x i \frac{\partial L}{\partial a} = -\frac{2}{n}\sum_{i=1}^{n}(y_i - (ax_i + b))x_i ∂a∂L=−n2i=1∑n(yi−(axi+b))xi
∂ L ∂ b = − 2 n ∑ i = 1 n ( y i − ( a x i + b ) ) \frac{\partial L}{\partial b} = -\frac{2}{n}\sum_{i=1}^{n}(y_i - (ax_i + b)) ∂b∂L=−n2i=1∑n(yi−(axi+b))
这些偏导数告诉我们在当前参数值下,损失函数对每个参数的敏感度,即参数微小变化时损失函数的变化率。
学习率的重要性
学习率是训练过程中的关键超参数,它决定了参数更新的步长。 学习率太大,可能导致震荡或发散;学习率太小,收敛速度会很慢。选择合适的学习率需要在收敛速度和稳定性之间找到平衡。
学习率的影响机制:
学习率过大(如0.1):
- 参数更新步长过大
- 可能在最优解附近震荡
- 甚至可能发散,损失函数不收敛
学习率过小(如0.00001):
- 参数更新步长过小
- 收敛速度很慢
- 需要更多迭代次数才能达到最优解
合适的学习率(如0.0001):
- 在稳定性和收敛速度之间取得平衡
- 能够平滑地收敛到最优解
学习率选择经验法则: 通常从0.01开始尝试,如果震荡则减小,如果收敛太慢则增大。在我们的房价预测问题中,0.0001是一个相对保守的选择,确保训练过程稳定。
梯度清零的必要性
梯度清零是PyTorch训练中的关键步骤,它防止了梯度累积导致的错误。 每次反向传播后,梯度信息会累积在参数的 .grad
属性中。如果不清零,下一次迭代的梯度会与之前的梯度相加,导致参数更新错误。
梯度累积的数学解释: 如果不清零梯度,第t次迭代的参数更新实际上是:
a t + 1 = a t − α ∑ k = 1 t ∂ L k ∂ a a_{t+1} = a_t - \alpha \sum_{k=1}^{t} \frac{\partial L_k}{\partial a} at+1=at−αk=1∑t∂a∂Lk
这会导致参数更新过大,训练不稳定。正确的做法是每次只使用当前批次的梯度:
a t + 1 = a t − α ∂ L t ∂ a a_{t+1} = a_t - \alpha \frac{\partial L_t}{\partial a} at+1=at−α∂a∂Lt
第四步:预测评估 - 验证学习效果
预测评估是检验模型学习效果的关键环节,它告诉我们模型是否真正学到了有用的规律。 在房价预测中,我们不仅要看模型在历史数据上的表现,更要看它对新数据的预测能力。
模型预测与可视化
# 在测试集上进行预测
# 使用训练好的参数a和b对测试数据进行预测
predictions = a.expand_as(x_test) * x_test + b.expand_as(x_test)
# 可视化预测结果
plt.figure(figsize = (10, 7)) # 设置图形大小
# 绘制训练数据点
plt.plot(x_train.numpy(), y_train.numpy(), 'o', label='Training Data')
# 绘制测试数据点
plt.plot(x_test.numpy(), y_test.numpy(), 's', label='Test Data')
# 绘制拟合直线(在训练数据范围内)
plt.plot(x_train.numpy(), a.data.numpy() * x_train.numpy() + b.data.numpy(), label='Fitted Line')
# 绘制预测点
plt.plot(x_test.numpy(), predictions.numpy(), 'o', label='Predictions')
plt.xlabel('Time (months)') # x轴标签
plt.ylabel('House Price') # y轴标签
plt.legend() # 显示图例
plt.show() # 显示图形
预测结果的可视化帮助我们直观理解模型的学习效果。 通过比较训练数据、测试数据、拟合直线和预测点,我们可以判断模型是否真正学到了房价随时间变化的规律,以及这种规律是否具有泛化能力。
模型评估指标: 除了可视化,我们还可以计算定量指标:
- 训练集MSE:衡量模型在训练数据上的拟合程度
- 测试集MSE:衡量模型的泛化能力
- R²决定系数:衡量模型解释数据变异性的比例
泛化能力的评估
泛化能力是衡量模型质量的核心指标,它反映了模型对新数据的适应能力。 一个好的模型不仅要在训练数据上表现良好,更要能够准确预测未见过的数据。这正是我们划分训练集和测试集的原因。
过拟合与欠拟合:
欠拟合(Underfitting):
- 模型过于简单,无法捕捉数据中的规律
- 训练集和测试集误差都很高
- 解决方案:增加模型复杂度或特征
过拟合(Overfitting):
- 模型过于复杂,记住了训练数据的噪声
- 训练集误差很低,但测试集误差很高
- 解决方案:正则化、增加数据、减少模型复杂度
良好拟合:
- 模型复杂度适中,能够泛化到新数据
- 训练集和测试集误差都较低且接近
核心概念总结与延伸
通过房价预测这个实例,我们不仅学习了深度学习的四个核心步骤,更重要的是理解了机器学习的本质思想。机器学习不是简单的数据拟合,而是一种从数据中自动发现规律的方法论。
关键术语的深层含义
模型:不仅是一个数学函数,更是对现实世界的假设和抽象。它反映了我们对数据生成过程的理解,决定了机器如何"思考"问题。
参数:模型中的可调节变量,通过优化这些参数来改善预测效果。参数的数量和类型决定了模型的表达能力,但过多的参数可能导致过拟合。
损失函数:量化模型质量的指标,指导优化方向。损失函数的选择直接影响模型的学习目标,不同的损失函数适用于不同的任务类型。
梯度下降:自动寻找最优参数的算法,体现了"学习"的本质。它通过计算损失函数对参数的导数,指导参数向减小损失的方向更新,是深度学习中最核心的优化算法。
泛化能力:模型的核心价值,决定了其实际应用效果。泛化能力强的模型能够在未见过的数据上表现良好,这是机器学习模型的终极目标。
从线性回归到深度学习的演进
线性回归虽然简单,但它包含了深度学习的所有核心要素。当我们把线性函数 y = ax + b 替换为复杂的神经网络时,基本原理保持不变:我们仍然需要准备数据、设计模型、训练优化和预测评估。唯一的区别是,神经网络的参数更多,模型更复杂,能够学习更复杂的非线性关系。
神经网络与线性回归的关系: 神经网络可以看作是多个线性变换和非线性激活函数的组合:
f ( x ) = σ ( W 2 σ ( W 1 x + b 1 ) + b 2 ) f(x) = \sigma(W_2 \sigma(W_1 x + b_1) + b_2) f(x)=σ(W2σ(W1x+b1)+b2)
其中:
- W 1 , W 2 W_1, W_2 W1,W2 是权重矩阵(相当于线性回归中的参数a)
- b 1 , b 2 b_1, b_2 b1,b2 是偏置向量(相当于线性回归中的参数b)
- σ \sigma σ 是非线性激活函数(如ReLU、sigmoid等)
因此,理解线性回归不仅是为了解决简单的预测问题,更是为了建立对机器学习本质的深刻认识,为后续学习更复杂的深度学习模型奠定坚实的基础。 线性回归教会了我们参数优化、损失函数设计、梯度下降等核心概念,这些概念在深度学习中同样适用,只是规模更大、复杂度更高。