机器学习基础(8):逻辑回归

一、引言

在前面的学习中,我们已经掌握了线性回归、损失函数、反向传播以及激活函数等核心概念。但线性回归有一个明显的局限性:

它只能预测连续值,无法直接处理分类问题。

而在现实世界中,分类问题无处不在:

  • 邮件是垃圾邮件还是正常邮件?
  • 肿瘤是良性还是恶性?
  • 客户是否会购买产品?

这些问题都需要模型输出离散类别,而非连续数值。

逻辑回归(Logistic Regression) 正是为解决这类问题而生。虽然名字里有"回归",但它实际上是一种分类算法


二、从线性回归到分类问题

1. 为什么不能直接用线性回归做分类?

假设我们要预测"考试是否及格"(及格=1,不及格=0):

如果用线性回归:

\[\hat{y} = w_1x_1 + w_2x_2 + b \]

问题很明显:

  • 输出范围是 \((-\infty, +\infty)\),但分类结果只能是 0 或 1
  • 预测值可能大于1或小于0,没有概率意义

2. 解决方案:引入 Sigmoid 函数

我们需要一个函数,将任意实数映射到 \((0, 1)\) 区间:

\[\sigma(z) = \frac{1}{1 + e^{-z}} \]

其中 \(z = w^Tx + b\) 是线性部分的结果。

Sigmoid 函数的特点:

  • \(z \to +\infty\) 时,\(\sigma(z) \to 1\)
  • \(z \to -\infty\) 时,\(\sigma(z) \to 0\)
  • \(z = 0\) 时,\(\sigma(z) = 0.5\)

这样,输出就可以解释为概率

\[P(y=1|x) = \sigma(w^Tx + b) = \frac{1}{1 + e^{-(w^Tx + b)}} \]


三、逻辑回归的数学原理

1. 模型定义

逻辑回归的完整表达式:

\[h_\theta(x) = \frac{1}{1 + e^{-\theta^Tx}} \]

其中 \(\theta = [b, w_1, w_2, ..., w_n]^T\) 包含偏置项和所有权重。

2. 决策边界

通常设定阈值为 0.5:

  • 如果 \(h_\theta(x) \geq 0.5\),预测为类别 1
  • 如果 \(h_\theta(x) < 0.5\),预测为类别 0

这等价于:

\[\theta^Tx \geq 0 \Rightarrow \text{预测为 1} \]

\[\theta^Tx < 0 \Rightarrow \text{预测为 0} \]

决策边界就是 \(\theta^Tx = 0\) 这条线(或超平面)。


四、损失函数:交叉熵损失

1. 为什么不用均方误差(MSE)?

如果直接用 MSE:

\[L = \frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})^2 \]

由于 Sigmoid 函数的非线性,损失函数会变成非凸函数,存在很多局部最优,梯度下降容易陷入局部最小值。

2. 交叉熵损失(Cross-Entropy Loss)

对于二分类问题,定义:

\[L(\theta) = -\frac{1}{m}\sum_{i=1}^{m}\left[y^{(i)}\log(h_\theta(x^{(i)})) + (1-y^{(i)})\log(1-h_\theta(x^{(i)}))\right] \]

直观理解:

  • 当真实标签 \(y=1\) 时,损失为 \(-\log(h_\theta(x))\),预测越接近1,损失越小
  • 当真实标签 \(y=0\) 时,损失为 \(-\log(1-h_\theta(x))\),预测越接近0,损失越小

交叉熵损失是凸函数,保证梯度下降能找到全局最优。


五、梯度下降与参数更新

1. 梯度推导

对交叉熵损失求导(过程略),得到:

\[\frac{\partial L}{\partial \theta_j} = \frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})x_j^{(i)} \]

神奇的是:这个形式和线性回归的梯度公式完全一样!

2. 参数更新

\[\theta_j := \theta_j - \alpha \cdot \frac{\partial L}{\partial \theta_j} \]

其中 \(\alpha\) 是学习率。


六、Scikit-learn 简介

在深入代码实现之前,先介绍一下 Scikit-learn(简称 sklearn)—— Python 中最流行的机器学习库之一。

1. 什么是 Scikit-learn?

Scikit-learn 是基于 NumPy、SciPy 和 Matplotlib 的开源机器学习库,提供了简单且高效的数据挖掘和数据分析工具。

主要特点:

  • 简单易用:统一的 API 设计,所有模型都遵循 fitpredictevaluate 的流程
  • 文档丰富:详尽的官方文档和示例
  • 算法全面:涵盖分类、回归、聚类、降维等主流算法
  • 集成性好:与 NumPy、Pandas、Matplotlib 无缝衔接

2. 安装方法

pip install scikit-learn

3. 基本使用流程

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 1. 准备数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 2. 创建模型
model = LogisticRegression()

# 3. 训练模型
model.fit(X_train, y_train)

# 4. 预测
predictions = model.predict(X_test)

# 5. 评估
accuracy = accuracy_score(y_test, predictions)

七、代码实现

1. NumPy 实现逻辑回归

import numpy as np
import matplotlib.pyplot as plt

class LogisticRegression:
    def __init__(self, learning_rate=0.01, n_iterations=1000):
        self.lr = learning_rate
        self.n_iter = n_iterations
        self.weights = None
        self.bias = None
        self.losses = []
    
    def _sigmoid(self, z):
        """Sigmoid激活函数"""
        return 1 / (1 + np.exp(-z))
    
    def _compute_loss(self, y_true, y_pred):
        """计算交叉熵损失"""
        # 防止log(0)数值不稳定
        epsilon = 1e-15
        y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
        
        loss = -np.mean(y_true * np.log(y_pred) + 
                        (1 - y_true) * np.log(1 - y_pred))
        return loss
    
    def fit(self, X, y):
        """训练模型"""
        n_samples, n_features = X.shape
        
        # 初始化参数
        self.weights = np.zeros(n_features)
        self.bias = 0
        
        # 梯度下降
        for i in range(self.n_iter):
            # 前向传播
            linear_model = np.dot(X, self.weights) + self.bias
            y_pred = self._sigmoid(linear_model)
            
            # 计算损失
            loss = self._compute_loss(y, y_pred)
            self.losses.append(loss)
            
            # 计算梯度
            dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
            db = (1 / n_samples) * np.sum(y_pred - y)
            
            # 更新参数
            self.weights -= self.lr * dw
            self.bias -= self.lr * db
            
            # 每100次迭代打印一次损失
            if (i + 1) % 100 == 0:
                print(f"Iteration {i+1}/{self.n_iter}, Loss: {loss:.4f}")
    
    def predict_proba(self, X):
        """预测概率"""
        linear_model = np.dot(X, self.weights) + self.bias
        return self._sigmoid(linear_model)
    
    def predict(self, X, threshold=0.5):
        """预测类别"""
        probabilities = self.predict_proba(X)
        return (probabilities >= threshold).astype(int)
    
    def accuracy(self, X, y):
        """计算准确率"""
        predictions = self.predict(X)
        return np.mean(predictions == y)


# ==================== 示例:使用逻辑回归进行分类 ====================

# 生成示例数据
np.random.seed(42)

# 类别0的数据(左下角)
X_class0 = np.random.randn(50, 2) + np.array([-2, -2])
y_class0 = np.zeros(50)

# 类别1的数据(右上角)
X_class1 = np.random.randn(50, 2) + np.array([2, 2])
y_class1 = np.ones(50)

# 合并数据
X = np.vstack([X_class0, X_class1])
y = np.hstack([y_class0, y_class1])

# 划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建并训练模型
model = LogisticRegression(learning_rate=0.1, n_iterations=1000)
model.fit(X_train, y_train)

# 评估模型
train_acc = model.accuracy(X_train, y_train)
test_acc = model.accuracy(X_test, y_test)
print(f"\n训练集准确率: {train_acc:.4f}")
print(f"测试集准确率: {test_acc:.4f}")

# 可视化结果
plt.figure(figsize=(12, 5))

# 子图1:数据分布和决策边界
plt.subplot(1, 2, 1)
plt.scatter(X_class0[:, 0], X_class0[:, 1], c='red', label='Class 0', alpha=0.6)
plt.scatter(X_class1[:, 0], X_class1[:, 1], c='blue', label='Class 1', alpha=0.6)

# 绘制决策边界
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
                     np.linspace(y_min, y_max, 100))
Z = model.predict_proba(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, levels=50, alpha=0.3, cmap='RdBu')
plt.contour(xx, yy, Z, levels=[0.5], colors='black', linestyles='--')

plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('Logistic Regression Decision Boundary')
plt.legend()

# 子图2:损失曲线
plt.subplot(1, 2, 2)
plt.plot(model.losses)
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('logistic_regression_demo.png', dpi=150)
plt.show()

2. 使用 Scikit-learn

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# 生成更复杂的数据集
X, y = make_classification(n_samples=1000, n_features=4, n_redundant=0, 
                           n_informative=4, n_classes=2, random_state=42)

# 划分数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建模型(使用L2正则化)
model = LogisticRegression(penalty='l2', C=1.0, max_iter=1000, random_state=42)

# 训练
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)
y_pred_proba = model.predict_proba(X_test)

# 评估
print("准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:")
print(classification_report(y_test, y_pred))
print("\n混淆矩阵:")
print(confusion_matrix(y_test, y_pred))

# 查看特征重要性(系数)
print("\n特征系数:")
for i, coef in enumerate(model.coef_[0]):
    print(f"Feature {i+1}: {coef:.4f}")

3. 多分类逻辑回归(Softmax 回归)

import numpy as np

class SoftmaxRegression:
    """Softmax回归(多分类逻辑回归)"""
    
    def __init__(self, learning_rate=0.01, n_iterations=1000):
        self.lr = learning_rate
        self.n_iter = n_iterations
        self.weights = None
        self.bias = None
    
    def _softmax(self, z):
        """Softmax函数"""
        # 减去最大值防止数值溢出
        exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)
    
    def fit(self, X, y):
        """训练模型"""
        n_samples, n_features = X.shape
        n_classes = len(np.unique(y))
        
        # 将标签转换为one-hot编码
        y_onehot = np.eye(n_classes)[y]
        
        # 初始化参数
        self.weights = np.random.randn(n_features, n_classes) * 0.01
        self.bias = np.zeros((1, n_classes))
        
        for i in range(self.n_iter):
            # 前向传播
            linear_model = np.dot(X, self.weights) + self.bias
            y_pred = self._softmax(linear_model)
            
            # 计算梯度
            dw = (1 / n_samples) * np.dot(X.T, (y_pred - y_onehot))
            db = (1 / n_samples) * np.sum(y_pred - y_onehot, axis=0, keepdims=True)
            
            # 更新参数
            self.weights -= self.lr * dw
            self.bias -= self.lr * db
    
    def predict(self, X):
        """预测类别"""
        linear_model = np.dot(X, self.weights) + self.bias
        y_pred = self._softmax(linear_model)
        return np.argmax(y_pred, axis=1)


# 使用示例
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 生成三分类数据
X, y = make_classification(n_samples=300, n_features=2, n_redundant=0, 
                           n_informative=2, n_classes=3, n_clusters_per_class=1, 
                           random_state=42)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练Softmax回归
model = SoftmaxRegression(learning_rate=0.1, n_iterations=1000)
model.fit(X_train, y_train)

# 预测并评估
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Softmax回归准确率: {accuracy:.4f}")

八、逻辑回归的优缺点

优点

优点 说明
可解释性强 系数直接反映特征对结果的影响方向和程度
计算高效 训练和预测速度快,适合大规模数据
输出概率 不仅给出分类结果,还能提供置信度
不易过拟合 模型简单,配合正则化效果更好
扩展性好 容易扩展到多分类问题(Softmax)

缺点

缺点 说明
线性决策边界 只能处理线性可分问题,复杂边界需要特征工程
对异常值敏感 异常值会显著影响模型参数
特征相关性 多重共线性会影响系数估计

九、应用场景

逻辑回归广泛应用于:

  • 医疗诊断:疾病风险评估、肿瘤良恶性判断
  • 金融风控:信用评分、欺诈检测
  • 营销预测:客户流失预测、购买意愿分析
  • 自然语言处理:情感分析、垃圾邮件识别

十、总结

逻辑回归是机器学习中最基础、最重要的分类算法之一:

核心思想:用 Sigmoid 函数将线性输出映射为概率,实现分类预测。

关键点回顾:

要点 内容
模型 \(h_\theta(x) = \sigma(\theta^Tx) = \frac{1}{1+e^{-\theta^Tx}}\)
损失函数 交叉熵损失(凸函数,优化友好)
优化方法 梯度下降
扩展 Softmax 回归处理多分类

逻辑回归虽然简单,但它是理解更复杂模型(如神经网络)的重要基础。掌握它,你就迈出了走向深度学习的关键一步。

posted @ 2026-04-01 21:35  YZG5N  阅读(56)  评论(0)    收藏  举报