在前面的部分,我们学习了单变量线性回归,并复习了线性代数的基础知识。现在是时候把这两部分知识结合起来,处理现实世界中更常见的情况——多元线性回归。
现实中的预测问题很少只涉及一个特征。当我们预测房价时,不仅要考虑面积,还要考虑位置、房龄、楼层、学区等因素。当医生评估疾病风险时,需要综合患者的年龄、血压、血糖、胆固醇等多个指标。多元线性回归让我们能够同时使用多个特征进行预测,构建更准确、更实用的模型。
好消息是,有了线性代数的语言,多元线性回归的数学表达与单变量情况几乎一模一样。我们不需要学习全新的理论,只需要把标量换成向量,单个变量换成变量向量。
让我们从一个具体的例子开始。假设我们要预测房价,现在有四个特征:
对应的房价记为 。一个样本可以表示为 ,比如 表示一套80平米、3卧室、2楼、房龄5年的房子,售价200万。
在单变量线性回归中,我们的模型是 。现在有了多个特征,自然的推广是:
每个特征都有一个对应的参数 ,表示该特征对预测结果的影响程度。 仍然是截距项(基础房价)。如果 ,意味着面积每增加1平方米,房价平均增加2.5万;如果 ,意味着房龄每增加1年,房价平均降低1万。
为了简化符号,我们引入一个约定:设 ,这样截距项可以统一到求和式中:
这里 是特征的数量(不包括 )。用向量符号表示更加简洁。设:
那么假设函数可以简洁地写成:
这就是向量的内积!多元线性回归的假设函数与单变量情况在形式上完全一样,只不过 和 从标量变成了向量。
在机器学习文献中,特征数量常用 表示,样本数量用 表示。每个样本 的第 个特征记为 。注意上标 是样本索引,下标 是特征索引。
当我们有 个训练样本时,可以把所有样本组织成一个矩阵。设计矩阵(Design Matrix) 是一个 的矩阵,第 行是第 个样本的特征向量(包括 ):
所有样本的标签组成向量 。那么所有样本的预测值可以一次性计算:
这是一个 矩阵乘以 向量,结果是 向量,正好是 个预测值。
代价函数仍然是均方误差:
梯度下降的更新规则:
看,从单变量到多元,我们只是把标量换成了向量,其他都没变!这就是用线性代数表达机器学习算法的优雅之处。
理论上,多元梯度下降与单变量情况完全一样。但在实践中,有一些技巧可以让算法更快收敛、更稳定。最重要的技巧之一就是特征缩放。
想象我们的特征包括房屋面积(范围从30到300平方米)和卧室数量(范围从1到5)。面积的数值远大于卧室数量的数值。在这种情况下,参数 (面积的系数)会很小,而 (卧室数的系数)会相对较大,这样才能让两项对预测值的贡献相当。
但这会导致一个问题:代价函数在参数空间中会呈现细长的椭圆形。在某些方向上(对应数值范围大的特征),代价函数变化很快;在其他方向上(对应数值范围小的特征),代价函数变化很慢。这使得梯度下降的路径曲折迂回,收敛速度变慢。

特征缩放的目的是让所有特征的数值范围大致相同,通常缩放到 或 的范围。常用的特征缩放方法有两种:
均值归一化(Mean Normalization):
其中 是特征 的平均值, 是范围(最大值减最小值)或标准差。这样处理后,特征的平均值是0,范围大致在 。
标准化(Standardization):
其中 是特征 的标准差。这样处理后,特征的均值是0,标准差是1,服从标准正态分布。
举个例子,假设房屋面积的平均值是80平方米,标准差是20平方米。一套100平方米的房子,标准化后的特征值是 。一套60平方米的房子,标准化后是 。
特征缩放不会改变模型的本质,只是改变了参数的尺度。缩放后训练得到的参数 与原始参数 之间有简单的换算关系。重要的是,缩放后梯度下降会快很多。
特征缩放时,不要缩放截距项 (它总是1)。另外,训练时使用的缩放参数(均值和标准差)要保存下来,在预测新数据时也要用相同的参数进行缩放,否则模型会失效。
|import numpy as np def featureNormalize(X): """ 对特征进行归一化处理 X: 特征矩阵(不包括x0=1的列) 返回:归一化后的X,均值向量,标准差向量 """ mu = np.mean(X, axis=0) # 每个特征的均值 sigma = np.std(X, axis=0) # 每个特征的标准差 X_norm = (X - mu) / sigma # 标准化 return
学习率 是梯度下降中最重要的超参数之一。选择合适的学习率,可以让算法快速收敛到最优解;选择不当,可能导致收敛很慢,甚至根本不收敛。
如何判断学习率是否合适?最直观的方法是画出代价函数随迭代次数变化的曲线(学习曲线)。
如果学习率合适,代价函数应该在每次迭代后都下降,曲线平滑地下降并最终趋于平稳。收敛的速度取决于学习率的大小——越大收敛越快,但也更容易不稳定。
一个好的策略是尝试多个学习率,比如 (每次乘以3),观察它们的学习曲线,选择既收敛快又稳定的那个。
|def gradientDescentWithCurve(X, y, theta, alpha, numIter): """ 梯度下降,返回参数和代价函数历史 """ m = len(y) J_history = [] for i in range(numIter): # 计算梯度 h = X @ theta gradient = (1/m) * X.T @ (h - y) # 更新参数
除了固定的学习率,还可以使用自适应学习率。随着迭代进行,逐渐减小学习率,这样在初期可以快速接近最优解,在后期可以更精确地收敛。常见的策略包括:
现代的优化算法(如Adam、RMSprop)自动调整学习率,每个参数有自己的学习率,根据梯度的历史信息动态调整。这些算法在深度学习中非常流行。
判断算法是否收敛,可以设置一个阈值 (比如0.001)。如果连续几次迭代,代价函数的改变量小于 ,就认为已经收敛,可以提前停止。这样可以避免不必要的计算。
线性回归假设输出与输入特征之间是线性关系。但现实世界的关系往往不是线性的。比如,房价与面积的关系可能不是严格线性的——小户型每平米单价可能更高,超大户型每平米单价可能反而下降。
这时候我们可以使用多项式回归。虽然叫"多项式"回归,但它实际上仍然是线性回归,只是特征是原始特征的多项式。
比如,对于单个特征 ,我们可以构造二次模型:
或者三次模型:
在实现上,我们只需要创建新特征:,,,然后用这些新特征进行线性回归。
对于多个原始特征,我们可以构造它们的各种组合和幂次。比如对于特征 和 ,可以构造:
这样就能捕捉特征之间的相互作用( 项)和非线性关系(平方项)。

但要注意,使用多项式特征时,特征缩放变得更加重要。如果 的范围是1-1000,那么 的范围是1-1000000, 的范围更大。不进行缩放,梯度下降几乎无法工作。
另一个问题是模型复杂度的选择。多项式的次数越高,模型越复杂,拟合训练数据的能力越强。但过于复杂的模型会过拟合——在训练集上表现很好,在新数据上表现很差。如何选择合适的复杂度,我们会在后续学习讨论正则化和模型选择时详细探讨。
特征工程不仅限于多项式。根据对问题的理解,我们可以构造各种有意义的特征。比如预测房价时,除了面积和卧室数,我们可以构造“人均面积”(面积除以卧室数)这个新特征。预测贷款违约时,可以用“收入负债比”而不是单独的收入和负债。
好的特征工程往往比复杂的模型更重要。
Andrew Ng有句名言:“Coming up with features is difficult, time-consuming, requires expert knowledge. 'Applied machine learning' is basically feature engineering.”(构造特征是困难的、耗时的、需要专业知识的。“应用机器学习”基本上就是特征工程。)
|def polyFeatures(X, degree): """ 生成多项式特征 X: 原始特征矩阵 degree: 多项式的次数 返回:扩展后的特征矩阵 """ m, n = X.shape X_poly = X.copy() # 添加高次项 for d in range(2, degree + 1): X_poly = np.column_stack([X_poly, X ** d]) return X_poly # 使用示例
前面我们用梯度下降来优化参数。梯度下降是一个迭代算法,需要多次更新才能收敛。对于线性回归,还有另一种方法可以直接计算出最优参数,不需要迭代。这就是正规方程(Normal Equation)。
通过微积分,我们可以推导出:使代价函数 最小的参数 满足:
这个公式直接给出了最优解!我们只需要进行矩阵运算就能得到参数,不需要设置学习率,不需要迭代,不需要特征缩放。
让我们理解这个公式。 是一个 的方阵, 是它的逆矩阵。 是一个 的向量。整个表达式的结果是 的向量,正好是我们的参数向量。
正规方程来自于优化理论中的一个基本结果:对于二次函数,最小值点在导数为零的地方。线性回归的代价函数对参数来说是二次的,对它求导并令导数为零,就得到了正规方程。
|def normalEquation(X, y): """ 使用正规方程计算线性回归的最优参数 """ # theta = (X^T X)^(-1) X^T y theta = np.linalg.inv(X.T @ X) @ X.T @ y return theta # 使用示例 # 添加x0=1的列 X_with_intercept = np.column_stack([np.ones(len(X)), X]) theta = normalEquation(X_with_intercept, y) print("正规方程求解的参数:", theta)
正规方程看起来很美好,为什么我们还要用梯度下降?因为正规方程有一些局限:
在实践中,如果特征数量不太多(比如小于10000),正规方程是一个好选择——简单、准确、不需要调参。如果特征很多,或者样本数非常大,梯度下降(或其变种)更合适。
当 不可逆时,我们不能直接使用正规方程。什么时候会出现这种情况?
遇到这种情况怎么办?有几种解决方案:
np.linalg.pinv() 函数可以计算伪逆。|def normalEquationRobust(X, y): """ 使用伪逆的稳健版本正规方程 """ # 使用伪逆代替逆矩阵 theta = np.linalg.pinv(X.T @ X) @ X.T @ y # 或者更直接地: # theta = np.linalg.pinv(X) @ y return theta
在实践中,当遇到不可逆问题时,最好的做法通常是重新审视特征。特征过多或相关性太强往往意味着我们的特征选择有问题,这时候应该做特征工程,而不是单纯依赖数值技巧。
现代的机器学习实践中,我们很少直接使用正规方程。对于小规模问题,正规方程很好用;对于大规模问题,我们用随机梯度下降或其变种;对于需要正则化的问题,我们用正则化版本的优化算法。但理解正规方程对于理解线性回归的数学本质很重要。
多元线性回归是一个强大而灵活的工具。虽然它假设线性关系,但通过特征工程,我们可以用它建模复杂的非线性关系。虽然它的理论简单,但在实践中需要注意很多细节。掌握这些技巧,你就能有效地应用线性回归解决实际问题。
在下一部分中,我们将学习Octave/Matlab的使用。这些工具让我们能够快速实现和测试机器学习算法,是学习和实践的好帮手。掌握向量化编程,你会发现实现复杂算法其实只需要几行代码。
特征缩放的必要性:假设你在训练一个房价预测模型,有两个特征:
初始参数为 ,,,学习率 。
答案:
为什么需要特征缩放?
量纲差异导致的问题:
对梯度下降的影响:
正规方程求解:使用正规方程直接求解多元线性回归的参数。给定数据:
答案:
正规方程公式为:
步骤1:计算
请解释为什么在梯度下降中需要进行特征缩放,并说明如何对这两个特征进行Z-score标准化。
Z-score标准化步骤:
假设有训练数据:
步骤1:计算均值和标准差
特征1(面积):
特征2(卧室):
步骤2:标准化公式
步骤3:转换后的取值范围
标准化后的优势:
Python实现:
|import numpy as np # 原始数据 X = np.array([[50, 1], [100, 2], [150, 3], [200, 4], [250, 5]]) # 计算均值和标准差 mu = np.mean(X, axis=0) # [150, 3] sigma = np.std(X, axis=0) # [70.71, 1.41] # 标准化 X_norm = (X - mu) / sigma print(X_norm)
使用正规方程 计算参数 。
步骤2:计算
计算每个元素:
步骤3:计算
步骤4:计算
使用高斯消元法或计算器求逆矩阵(手算过程较复杂,这里直接给出结果):
步骤5:计算
最终模型:
验证:代入第一个样本 :
(实际计算中可能需要更精确的矩阵求逆,这里为简化计算做了近似)
Python实现:
|import numpy as np X = np.array([[1, 2, 1], [1, 3, 2], [1, 4, 3], [1, 5, 4]]) y = np.array([7, 10, 13, 16]) # 正规方程 theta = np.linalg.inv(X.T @ X) @ X.T @ y print("参数theta:", theta) # 预测 predictions = X @ theta print("预测值:", predictions) print("真实值:", y)
正规方程 vs 梯度下降: