Octave/Matlab教程
6 / 18
正则化
自在学
首页课程创意工坊价格
首页课程创意工坊价格
编程机器学习逻辑回归

逻辑回归

到目前为止,我们学习的都是回归问题——预测连续的数值。但现实世界中,很多问题的答案是离散的类别:这封邮件是不是垃圾邮件?这个肿瘤是良性还是恶性?用户会不会点击这个广告?这些都是分类问题。

逻辑回归(Logistic Regression)是处理分类问题的经典算法。虽然名字中有“回归”二字,但它实际上是分类算法。逻辑回归不仅简单高效,而且是理解更复杂分类器(包括神经网络)的基础。事实上,深度神经网络的每个神经元本质上就是一个逻辑回归单元。

所以这节课我们将学习如何把线性回归的思想扩展到分类问题,如何设计适合分类的代价函数,以及如何将逻辑回归应用到多分类任务。


分类问题的特点

分类与回归的根本区别在于输出的性质。回归预测的是连续值,可以是任意实数;分类预测的是离散的类别标签。

最简单的是二分类(Binary Classification)问题,输出只有两个可能的类别。我们通常用0和1来表示这两个类别:

  • y=0y = 0y=0:负类(Negative Class),比如"不是垃圾邮件"、"良性肿瘤"
  • y=1y = 1y=1:正类(Positive Class),比如"是垃圾邮件"、"恶性肿瘤"

选择哪个类别作为正类是人为规定的,通常我们把想要检测的、比较罕见的那一类作为正类。

如果我们直接用线性回归来做分类会怎样?比如预测肿瘤是否恶性,用肿瘤大小作为特征。我们可能会得到一个线性模型 hθ(x)=θ0+θ1xh_\theta(x) = \theta_0 + \theta_1 xhθ​(x)=θ0​+θ1​x。但这有几个问题:

线性回归的输出可以是任意实数,而我们需要的是0或1。我们可以设定一个阈值,比如0.5,大于0.5就预测为1,否则预测为0。但这很不自然,而且容易受异常值影响。

假设我们的训练数据中,肿瘤大小从1cm到10cm,用线性回归拟合得还不错。突然来了一个极端案例,肿瘤大小30cm,当然是恶性的(y=1y=1y=1)。加入这个点后,线性回归的直线会被"拉"向它,导致原来分类正确的点现在分类错了。

分类问题的特点

我们需要一个更适合分类的模型,它的输出应该自然地落在0和1之间,可以解释为“属于正类的概率”。这就是逻辑回归。

逻辑回归输出的是概率。输出0.8意味着模型认为有80%的可能性属于正类,20%的可能性属于负类。这比简单的0/1分类提供了更丰富的信息,我们可以根据应用需求选择不同的决策阈值。


逻辑回归的假设函数

逻辑回归的关键是引入S型函数(Sigmoid Function),也称为逻辑函数(Logistic Function):

g(z)=11+e−zg(z) = \frac{1}{1 + e^{-z}}g(z)=1+e−z1​

这个函数有很好的性质:

  • 当 z→∞z \to \inftyz→∞ 时,g(z)→1g(z) \to 1g(z)→1
  • 当 z→−∞z \to -\inftyz→−∞ 时,g(z)→0g(z) \to 0g(z)→0
  • 当 z=0z = 0z=0 时,g(z)=0.5g(z) = 0.5g(z)=0.5
  • g(z)g(z)g(z) 总是在0和1之间
  • 函数连续且光滑,处处可导

逻辑回归的假设函数是:

hθ(x)=g(θTx)=11+e−θTxh_\theta(x) = g(\theta^T x) = \frac{1}{1 + e^{-\theta^T x}}hθ​(x)=g(θTx)=1+e−θTx1​

我们先用线性组合 θTx\theta^T xθTx 计算一个实数值,然后用sigmoid函数把它映射到(0, 1)区间。hθ(x)h_\theta(x)hθ​(x) 可以解释为:给定输入 xxx 和参数 θ\thetaθ,y=1y=1y=1 的概率。

hθ(x)=P(y=1∣x;θ)h_\theta(x) = P(y=1 | x; \theta)hθ​(x)=P(y=1∣x;θ)

相应地,y=0y=0y=0 的概率是 1−hθ(x)1 - h_\theta(x)1−hθ​(x)。

举个例子,假设我们预测肿瘤是否恶性,特征是肿瘤大小。如果对于某个肿瘤,θTx=2\theta^T x = 2θTx=2,那么:

hθ(x)=11+e−2≈0.88h_\theta(x) = \frac{1}{1 + e^{-2}} \approx 0.88hθ​(x)=1+e−21​≈0.88

模型认为这个肿瘤有88%的概率是恶性的。如果我们的决策阈值是0.5,我们会预测它是恶性。

决策边界(Decision Boundary)是使 hθ(x)=0.5h_\theta(x) = 0.5hθ​(x)=0.5 的点的集合。由于当 θTx=0\theta^T x = 0θTx=0 时 hθ(x)=0.5h_\theta(x) = 0.5hθ​(x)=0.5,决策边界就是 θTx=0\theta^T x = 0θTx=0。

  • 当 θTx≥0\theta^T x \geq 0θTx≥0 时,hθ(x)≥0.5h_\theta(x) \geq 0.5hθ​(x)≥0.5,预测 y=1y = 1y=1
  • 当 θTx<0\theta^T x < 0θTx<0 时,hθ(x)<0.5h_\theta(x) < 0.5hθ​(x)<0.5,预测 y=0y = 0y=0

对于二维特征 x1x_1x1​ 和 x2x_2x2​,如果 θ=[−3,1,1]T\theta = [-3, 1, 1]^Tθ=[−3,1,1]T(包括截距项),决策边界是:

−3+x1+x2=0-3 + x_1 + x_2 = 0−3+x1​+x2​=0

即 x1+x2=3x_1 + x_2 = 3x1​+x2​=3,这是一条直线。在这条线的一边,预测为正类;另一边预测为负类。

逻辑回归的决策边界是线性的。但通过引入多项式特征(就像我们在线性回归中做的),可以得到非线性的决策边界。比如使用特征 [1,x1,x2,x12,x22][1, x_1, x_2, x_1^2, x_2^2][1,x1​,x2​,x12​,x22​],参数 θ=[−1,0,0,1,1]T\theta = [-1, 0, 0, 1, 1]^Tθ=[−1,0,0,1,1]T,决策边界是:

−1+x12+x22=0-1 + x_1^2 + x_2^2 = 0−1+x12​+x22​=0

即 x12+x22=1x_1^2 + x_2^2 = 1x12​+x22​=1,这是一个圆!圆内预测为一类,圆外预测为另一类。


逻辑回归的代价函数

为什么不能用线性回归的均方误差作为逻辑回归的代价函数?因为逻辑回归的假设函数是非线性的(包含sigmoid),如果用均方误差,代价函数会是非凸的,有很多局部最小值,梯度下降可能无法找到全局最优解。

我们需要设计一个凸函数作为代价函数。对于单个样本,我们定义损失函数(Loss Function):

Loss(hθ(x),y)={−log⁡(hθ(x))if y=1−log⁡(1−hθ(x))if y=0\text{Loss}(h_\theta(x), y) = \begin{cases} -\log(h_\theta(x)) & \text{if } y = 1 \\ -\log(1 - h_\theta(x)) & \text{if } y = 0 \end{cases}Loss(hθ​(x),y)={−log(hθ​(x))−log(1−hθ​(x))​if y=1if y=0​

理解这个定义:

  • 当真实标签 y=1y=1y=1 时,如果我们预测 hθ(x)→1h_\theta(x) \to 1hθ​(x)→1(正确),损失接近0;如果预测 hθ(x)→0h_\theta(x) \to 0hθ​(x)→0(错误),损失趋向无穷大,给予严厉惩罚。
  • 当真实标签 y=0y=0y=0 时,如果预测 hθ(x)→0h_\theta(x) \to 0hθ​(x)→0(正确),损失接近0;如果预测 hθ(x)→1h_\theta(x) \to 1hθ​(x)→1(错误),损失趋向无穷大。

这个损失函数巧妙地实现了:预测正确时损失小,预测错误时损失大,而且是凸函数,便于优化。

对于整个训练集,代价函数是所有样本损失的平均:

J(θ)=−1m∑i=1m[y(i)log⁡(hθ(x(i)))+(1−y(i))log⁡(1−hθ(x(i)))]J(\theta) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(h_\theta(x^{(i)})) + (1 - y^{(i)}) \log(1 - h_\theta(x^{(i)}))]J(θ)=−m1​∑i=1m​[y(i)log(hθ​(x(i)))+(1−y(i))log(1−hθ​(x(i)))]

这个公式巧妙地把两种情况合并了:当 y(i)=1y^{(i)} = 1y(i)=1 时,第二项为0;当 y(i)=0y^{(i)} = 0y(i)=0 时,第一项为0。

用向量化表示更简洁:

J(θ)=−1m[yTlog⁡(h)+(1−y)Tlog⁡(1−h)]J(\theta) = -\frac{1}{m} [y^T \log(h) + (1-y)^T \log(1-h)]J(θ)=−m1​[yTlog(h)+(1−y)Tlog(1−h)]

其中 h=g(Xθ)h = g(X\theta)h=g(Xθ) 是所有样本的预测概率向量。

逻辑回归的代价函数来源于最大似然估计。从概率的角度,它衡量的是“在当前参数下,观测到训练数据的可能性有多大”。最小化这个代价函数,等价于最大化数据的似然,这是统计学中估计参数的经典方法。


梯度下降与优化

逻辑回归的代价函数看起来复杂,但求梯度后形式却很简洁。通过求导(这里省略推导过程),我们得到:

∂J∂θj=1m∑i=1m(hθ(x(i))−y(i))xj(i)\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)}) x_j^{(i)}∂θj​∂J​=m1​∑i=1m​(hθ​(x(i))−y(i))xj(i)​

用向量化表示:

∇J=1mXT(g(Xθ)−y)\nabla J = \frac{1}{m} X^T (g(X\theta) - y)∇J=m1​XT(g(Xθ)−y)

这个形式和线性回归的梯度惊人地相似!唯一的区别是这里的 hθ(x)h_\theta(x)hθ​(x) 是sigmoid函数,而线性回归中是线性函数。

梯度下降的更新规则:

θ:=θ−α1mXT(g(Xθ)−y)\theta := \theta - \alpha \frac{1}{m} X^T (g(X\theta) - y)θ:=θ−αm1​XT(g(Xθ)−y)

实现代码:

|
import numpy as np def sigmoid(z): """Sigmoid函数""" return 1 / (1 + np.exp(-z)) def computeCost(X, y, theta): """计算逻辑回归的代价函数""" m = len(y) h = sigmoid(X @ theta) # 避免log(0)的情况,加一个很小的数 epsilon = 1e-10 cost = (-1/m) * (y.T @ np.log(h + epsilon) + (1-y).T @ np.log(1-h + epsilon)) return cost def gradientDescent(X, y, theta, alpha, numIter): """逻辑回归的梯度下降""" m = len(y) J_history = [] for i in range(numIter): h = sigmoid(X @ theta) gradient = (1/m) * X.T @ (h - y) theta = theta - alpha * gradient J_history.append(computeCost(X, y, theta)) return theta, J_history # 使用示例 X = np.array([[1, 80, 5], [1, 60, 3], [1, 100, 8], [1, 70, 4]]) y = np.array([1, 0, 1, 0]) # 二分类标签 theta = np.zeros(3) alpha = 0.01 iterations = 1000 theta_opt, J_history = gradientDescent(X, y, theta, alpha, iterations) print("最优参数:", theta_opt)

逻辑回归也可以使用高级优化算法,它们通常比梯度下降更快:

  • 共轭梯度法(Conjugate Gradient):不需要手动选择学习率,收敛更快。
  • BFGS和L-BFGS:拟牛顿法,利用二阶导数信息,收敛速度快,适合中大规模问题。

这些算法的原理比较复杂,但在实践中我们可以直接调用库函数。在Octave中:

|
% 定义代价函数和梯度函数 function [J, grad] = costFunction(theta, X, y) m = length(y); h = sigmoid(X * theta); J = (-1/m) * (y' * log(h) + (1-y)' * log(1-h)); grad = (1/m) * X' * (h - y); end % 使用高级优化算法 options = optimset('GradObj', 'on', 'MaxIter', 400); [theta, cost] = fminunc(@(t)(costFunction(t, X, y)), initial_theta, options);

fminunc 是无约束优化函数,它会自动选择优化算法并调整参数,我们只需要提供代价函数和梯度的计算方法。


多元分类:一对多策略

到目前为止我们处理的都是二分类。如果有多个类别怎么办?比如手写数字识别,有0-9共10个类别;邮件分类,可能有工作、私人、促销、垃圾邮件等多个类别。

一种自然的扩展是一对多(One-vs-All或One-vs-Rest)策略。基本思想是:对于K个类别,训练K个二分类器,第i个分类器把类别i作为正类,其他所有类别作为负类。

具体步骤:

  1. 对每个类别 i=1,2,...,Ki = 1, 2, ..., Ki=1,2,...,K,训练一个逻辑回归分类器 hθ(i)(x)h_\theta^{(i)}(x)hθ(i)​(x),预测 y=iy=iy=i 的概率
  2. 对于新样本 xxx,计算所有K个分类器的输出
  3. 选择输出最大的那个类别作为预测:y^=arg⁡max⁡ihθ(i)(x)\hat{y} = \arg\max_i h_\theta^{(i)}(x)y^​=argmaxi​hθ(i)​(x)

多元分类:一对多策略

代码实现:

|
def oneVsAll(X, y, numLabels, lambda_reg): """ 训练多个一对多分类器 X: 特征矩阵 y: 标签(1, 2, ..., numLabels) numLabels: 类别数量 lambda_reg: 正则化参数 返回:所有分类器的参数矩阵 """ m, n = X.shape all_theta = np.zeros((numLabels, n)) for i in range(numLabels): # 创建二分类标签:类别i为1,其他为0 binary_y = (y == i).astype(int) # 训练第i个分类器 initial_theta = np.zeros(n) theta = gradientDescent(X, binary_y, initial_theta, 0.01, 1000)[0] all_theta[i, :] = theta return all_theta def predictOneVsAll(all_theta, X): """ 使用训练好的分类器进行预测 """ # 计算所有分类器的输出 scores = sigmoid(X @ all_theta.T) # m x numLabels # 选择得分最高的类别 predictions = np.argmax(scores, axis=1) return predictions

一对多策略简单有效,适用于类别不太多的情况。但当类别数量很大时,需要训练很多分类器,计算和存储开销会增大。这时可以考虑其他方法,比如softmax回归(多分类逻辑回归的直接扩展)或多类支持向量机。

使用一对多策略时,要注意类别不平衡的问题。如果某个类别的样本很少,对应的分类器可能训练不好。这时可以考虑重采样(过采样少数类或欠采样多数类)或调整分类阈值。


接下来

在下一个部分,我们将学习正则化技术。我们会看到,过于复杂的模型容易过拟合,而正则化可以控制模型复杂度,提高泛化能力。正则化不仅适用于线性回归,也适用于逻辑回归和其他算法,是机器学习实践中的重要技术。

逻辑回归虽然简单,但它的思想深刻而广泛。sigmoid函数、最大似然估计、概率解释、一对多策略,这些概念会在后续的神经网络、深度学习中反复出现。深度神经网络可以看作是多层逻辑回归的堆叠和组合。掌握逻辑回归,你就为理解更复杂的模型打下了坚实基础。


小练习

  1. 计算逻辑回归的代价函数:给定训练数据,手动计算逻辑回归的代价函数值。

    数据:

    xxxyyy (真实标签)
    21
    31
    10

    模型参数:θ0=−3\theta_0 = -3θ0​=−3,θ1=1\theta_1 = 1θ1​=1

    逻辑回归假设函数:hθ(x)=11+e−(θ0+θ1x)h_\theta(x) = \frac{1}{1 + e^{-(\theta_0 + \theta_1 x)}}hθ​(x)=1+e−(θ0​+θ1​x)1​

    代价函数:J(θ)=−1m∑i=1m[y(i)log⁡(hθ(x(i)))+(1−y(i))log⁡(1−hθ(x(i)))]J(\theta) = -\frac{1}{m}\sum_{i=1}^{m}[y^{(i)}\log(h_\theta(x^{(i)})) + (1-y^{(i)})\log(1-h_\theta(x^{(i)}))]J(θ)=−m1​∑i=1m​[y(i)log(hθ​(x(i)))+(1−y(i))log(1−hθ​(x(i)))]

答案:

步骤1:计算每个样本的预测值 hθ(x)h_\theta(x)hθ​(x)

样本1:x=2,y=1x=2, y=1x=2,y=1 z=θ0+θ1x=−3+1×2=−1z = \theta_0 + \theta_1 x = -3 + 1 \times 2 = -1z=θ0​+θ1​x=−3+1×2=−1 hθ(2)=11+e−(−1)=11+e1=11+2.718≈0.269h_\theta(2) = \frac{1}{1+e^{-(-1)}} = \frac{1}{1+e^1} = \frac{1}{1+2.718} \approx 0.269hθ​(2)=1+e−(−1)1​=1+e11​=1+2.7181​≈0.269

样本2:x=3,y=1x=3, y=1x=3,y=1 z=−3+1×3=0z = -3 + 1 \times 3 = 0z=−3+1×3=0 hθ(3)=11+e0=12=0.5h_\theta(3) = \frac{1}{1+e^0} = \frac{1}{2} = 0.5hθ​(3)=1+e01​=21​=0.5

样本3:x=1,y=0x=1, y=0x=1,y=0 z=−3+1×1=−2z = -3 + 1 \times 1 = -2z=−3+1×1=−2 hθ(1)=11+e−(−2)=11+e2≈18.389≈0.119h_\theta(1) = \frac{1}{1+e^{-(-2)}} = \frac{1}{1+e^2} \approx \frac{1}{8.389} \approx 0.119hθ​(1)=1+e−(−2)1​=1+e21​≈8.3891​≈0.119

步骤2:计算每个样本的代价

样本1(y=1y=1y=1): cost1=−[1×log⁡(0.269)+0×log⁡(0.731)]=−log⁡(0.269)≈1.313\text{cost}_1 = -[1 \times \log(0.269) + 0 \times \log(0.731)] = -\log(0.269) \approx 1.313cost1​=−[1×log(0.269)+0×log(0.731)]=−log(0.269)≈1.313

样本2(y=1y=1y=1): cost2=−[1×log⁡(0.5)+0×log⁡(0.5)]=−log⁡(0.5)≈0.693\text{cost}_2 = -[1 \times \log(0.5) + 0 \times \log(0.5)] = -\log(0.5) \approx 0.693cost2​=−[1×log(0.5)+0×log(0.5)]=−log(0.5)≈0.693

样本3(y=0y=0y=0): cost3=−[0×log⁡(0.119)+1×log⁡(0.881)]=−log⁡(0.881)≈0.127\text{cost}_3 = -[0 \times \log(0.119) + 1 \times \log(0.881)] = -\log(0.881) \approx 0.127cost3​=−[0×log(0.119)+1×log(0.881)]=−log(0.881)≈0.127

步骤3:计算平均代价 J(θ)=13(1.313+0.693+0.127)=2.1333≈0.711J(\theta) = \frac{1}{3}(1.313 + 0.693 + 0.127) = \frac{2.133}{3} \approx 0.711J(θ)=31​(1.313+0.693+0.127)=32.133​≈0.711

代价函数值约为 0.711

理解代价函数:

  • 当 y=1y=1y=1 时,如果 hθ(x)h_\theta(x)hθ​(x) 接近1,代价接近0(预测正确)
  • 当 y=1y=1y=1 时,如果 hθ(x)h_\theta(x)hθ​(x) 接近0,代价趋向无穷(预测错误)
  • 当 y=0y=0y=0 时,如果 hθ(x)h_\theta(x)hθ​(x) 接近0,代价接近0(预测正确)
  • 当 y=0y=0y=0 时,如果 hθ(x)h_\theta(x)hθ​(x) 接近1,代价趋向无穷(预测错误)

Python验证代码:

|
import numpy as np X = np.array([2, 3, 1]) y = np.array([1, 1, 0]) theta0, theta1 = -3, 1 z = theta0 + theta1 * X h = 1 / (1 + np.exp(-z)) cost = -np.mean(y * np.log(h) + (1-y) * np.log(1-h)) print(f"代价函数值: {cost:.3f}")
  1. 实现一对多分类:假设你要实现一个识别数字0-2的分类器(3个类别)。

    给定一个样本 x=[2,3]x = [2, 3]x=[2,3],三个二分类器的输出分别为:

    • 分类器1(类别0 vs 其他):hθ(1)(x)=0.1h_{\theta^{(1)}}(x) = 0.1hθ(1)​(x)=0.1
    • 分类器2(类别1 vs 其他):hθ(2)(x)=0.7h_{\theta^{(2)}}(x) = 0.7hθ(2)​(x)=0.7
    • 分类器3(类别2 vs 其他):hθ(3)(x)=0.2h_{\theta^{(3)}}(x) = 0.2hθ(3)​(x)=0.2

    请问应该将这个样本分类为哪个类别?为什么?

答案:

一对多(One-vs-All)分类策略:

在一对多分类中,我们训练 K 个二分类器(K是类别数量)。每个分类器负责识别一个类别:

  • 分类器1:判断是否为类别0
  • 分类器2:判断是否为类别1
  • 分类器3:判断是否为类别2

分类规则: 选择输出概率最高的分类器对应的类别: 预测类别=arg⁡max⁡ihθ(i)(x)\text{预测类别} = \arg\max_i h_{\theta^{(i)}}(x)预测类别=argmaxi​hθ(i)​(x)

本题分析:

比较三个分类器的输出:

  • hθ(1)(x)=0.1h_{\theta^{(1)}}(x) = 0.1hθ(1)​(x)=0.1 (类别0的概率)
  • hθ(2)(x)=0.7h_{\theta^{(2)}}(x) = 0.7hθ(2)​(x)=0.7 (类别1的概率)← 最大
  • hθ(3)(x)=0.2h_{\theta^{(3)}}(x) = 0.2hθ(3)​(x)=0.2 (类别2的概率)

因为分类器2的输出最高,所以应该将样本分类为类别1。

直观理解:

  • 分类器2给出70%的置信度认为这是类别1
  • 分类器3给出20%的置信度认为这是类别2
  • 分类器1只给出10%的置信度认为这是类别0
  • 我们选择最有"信心"的分类器

Python实现:

|
import numpy as np # 三个分类器的输出 outputs = np.array([0.1, 0.7, 0.2]) # 找到最大值的索引 predicted_class = np.argmax(outputs) print(f"预测类别: {predicted_class}") # 输出: 1 print(f"置信度: {outputs[predicted_class]}") # 输出: 0.7

完整的一对多分类流程:

|
def one_vs_all_predict(X, all_theta): """ X: (m, n) 样本特征矩阵 all_theta: (K, n+1) K个分类器的参数 返回: (m,) 每个样本的预测类别 """ # 添加偏置项 m = X.shape[0] X_with_bias = np.hstack([np.ones((m, 1)), X]) # 计算所有分类器的概率 # probs: (m, K),每个样本在每个类别上的概率 probs = sigmoid(X_with_bias @ all_theta.T) # 选择概率最高的类别 predictions = np.argmax(probs, axis=1) return predictions # 测试 x = np.array([[2, 3]]) # 一个样本 all_theta = np.array([ [-1, 0.1, 0.1], # 类别0的参数 [0, 0.5, 0.3], # 类别1的参数 [-2, 0.2, 0.2] # 类别2的参数 ]) prediction = one_vs_all_predict(x, all_theta) print(f"预测类别: {prediction[0]}")

注意事项:

  • 虽然逻辑回归输出"概率",但在一对多设置中,这些"概率"并不保证和为1
  • 它们只是相对的置信度分数
  • 如果所有输出都很低(比如都小于0.3),可能表示样本不属于任何已知类别
  • 分类问题的特点
  • 逻辑回归的假设函数
  • 逻辑回归的代价函数
  • 梯度下降与优化
  • 多元分类:一对多策略
  • 接下来
  • 小练习

目录

  • 分类问题的特点
  • 逻辑回归的假设函数
  • 逻辑回归的代价函数
  • 梯度下降与优化
  • 多元分类:一对多策略
  • 接下来
  • 小练习
自在学

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1