逻辑回归:ML-From-Scratch二分类问题实战

逻辑回归:ML-From-Scratch二分类问题实战

【免费下载链接】ML-From-Scratch Machine Learning From Scratch. Bare bones NumPy implementations of machine learning models and algorithms with a focus on accessibility. Aims to cover everything from linear regression to deep learning. 【免费下载链接】ML-From-Scratch 项目地址: https://gitcode.com/GitHub_Trending/ml/ML-From-Scratch

引言:你还在为二分类问题烦恼吗?一文掌握从原理到实现的完整路径

在机器学习领域,二分类问题(Binary Classification)是最基础也最常见的任务之一。无论是垃圾邮件检测、欺诈交易识别,还是疾病诊断,都离不开高效可靠的二分类算法。你是否也曾面临以下困境:

  • 调用Scikit-learn时只能调参却不懂内部原理?
  • 面试中被问及逻辑回归推导时支支吾吾?
  • 想自己实现算法却被数学公式吓退?

本文将通过ML-From-Scratch项目,带你从零开始理解并实现逻辑回归算法,彻底解决这些痛点。读完本文后,你将获得:

✅ 逻辑回归的数学原理与推导过程
✅ 纯NumPy实现逻辑回归的核心代码
✅ 完整的二分类项目实战案例(含数据预处理与模型评估)
✅ 梯度下降与批量优化的对比分析
✅ 可视化工具使用技巧

逻辑回归原理:从线性回归到概率预测的华丽转身

1. 什么是逻辑回归?

逻辑回归(Logistic Regression)虽然名称中带有"回归"二字,却是一种强大的二分类算法。它通过Sigmoid函数将线性回归的输出映射到[0,1]区间,从而实现对事件概率的预测。

1.1 Sigmoid函数:概率的桥梁

Sigmoid函数(也称为逻辑函数)是逻辑回归的核心,其数学表达式为:

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

其中,$z = w^T x + b$ 是线性回归的输出。Sigmoid函数具有以下特性:

  • 输出范围严格限制在[0,1]之间
  • 在z=0处的函数值为0.5
  • 导数为 $\sigma'(z) = \sigma(z)(1 - \sigma(z))$,计算高效
# Sigmoid函数的NumPy实现
import numpy as np
def sigmoid(z):
    return 1 / (1 + np.exp(-z))
1.2 决策边界:分类的分水岭

当Sigmoid函数输出大于0.5时,我们将样本分类为正类(1),否则为负类(0)。这对应于:

\begin{cases} 
y=1 & \text{if } w^T x + b \geq 0 \\
y=0 & \text{if } w^T x + b < 0 
\end{cases}

这个边界称为决策边界(Decision Boundary),可以是线性或非线性的,取决于特征的选择。

2. 损失函数:衡量模型的好坏

逻辑回归采用交叉熵损失(Cross-Entropy Loss)作为损失函数,定义如下:

J(w) = -\frac{1}{m} \sum_{i=1}^{m} [y^{(i)} \log(h_w(x^{(i)})) + (1-y^{(i)}) \log(1-h_w(x^{(i)}))]

其中,$h_w(x) = \sigma(w^T x + b)$ 是模型的预测值。

2.1 为什么不使用均方误差?

对比均方误差(MSE)和交叉熵损失的梯度:

损失函数表达式梯度优点
均方误差$J(w) = \frac{1}{2m} \sum (y - h_w(x))^2$$\nabla J(w) = (h_w(x) - y) \cdot h_w(x) \cdot (1 - h_w(x)) \cdot x$数学简单,适用于回归问题
交叉熵$J(w) = -\sum [y \log(h) + (1-y) \log(1-h)]$$\nabla J(w) = (h_w(x) - y) \cdot x$梯度更大,收敛更快,适用于分类问题

交叉熵损失在模型预测错误较大时提供更大的梯度,加速模型收敛,这是它优于均方误差的关键原因。

3. 参数优化:寻找最优解的两种途径

3.1 梯度下降法(Gradient Descent)

梯度下降是一种迭代优化算法,通过不断沿着梯度的反方向更新参数来最小化损失函数。逻辑回归的梯度下降更新公式为:

w := w - \alpha \nabla J(w) = w - \alpha \frac{1}{m} X^T (h_w(X) - y)

其中,$\alpha$ 是学习率(Learning Rate),控制每次更新的步长。

3.2 批量优化(Batch Optimization)

除了梯度下降,ML-From-Scratch还提供了批量优化方法,通过解正规方程组直接求解最优参数:

w = (X^T \cdot diag(g) \cdot X)^{-1} \cdot X^T \cdot (diag(g) \cdot X \cdot w + y - h)

其中,$diag(g)$ 是Sigmoid函数梯度的对角矩阵。

ML-From-Scratch实现:纯NumPy构建逻辑回归

1. 核心代码解析

ML-From-Scratch的逻辑回归实现位于mlfromscratch/supervised_learning/logistic_regression.py,核心代码如下:

from __future__ import print_function, division
import numpy as np
import math
from mlfromscratch.utils import make_diagonal, Plot
from mlfromscratch.deep_learning.activation_functions import Sigmoid


class LogisticRegression():
    """ Logistic Regression classifier.
    Parameters:
    -----------
    learning_rate: float
        The step length that will be taken when following the negative gradient during
        training.
    gradient_descent: boolean
        True or false depending if gradient descent should be used when training. If
        false then we use batch optimization by least squares.
    """
    def __init__(self, learning_rate=.1, gradient_descent=True):
        self.param = None
        self.learning_rate = learning_rate
        self.gradient_descent = gradient_descent
        self.sigmoid = Sigmoid()

    def _initialize_parameters(self, X):
        n_features = np.shape(X)[1]
        # Initialize parameters between [-1/sqrt(N), 1/sqrt(N)]
        limit = 1 / math.sqrt(n_features)
        self.param = np.random.uniform(-limit, limit, (n_features,))

    def fit(self, X, y, n_iterations=4000):
        self._initialize_parameters(X)
        # Tune parameters for n iterations
        for i in range(n_iterations):
            # Make a new prediction
            y_pred = self.sigmoid(X.dot(self.param))
            if self.gradient_descent:
                # Move against the gradient of the loss function with
                # respect to the parameters to minimize the loss
                self.param -= self.learning_rate * -(y - y_pred).dot(X)
            else:
                # Make a diagonal matrix of the sigmoid gradient column vector
                diag_gradient = make_diagonal(self.sigmoid.gradient(X.dot(self.param)))
                # Batch opt:
                self.param = np.linalg.pinv(X.T.dot(diag_gradient).dot(X)).dot(X.T).dot(diag_gradient.dot(X).dot(self.param) + y - y_pred)

    def predict(self, X):
        y_pred = np.round(self.sigmoid(X.dot(self.param))).astype(int)
        return y_pred
代码解析:
  1. 初始化方法:设置学习率和优化方式(梯度下降/批量优化)
  2. 参数初始化:在[-1/√n, 1/√n]范围内随机初始化参数,避免梯度消失
  3. 拟合方法
    • 梯度下降:通过预测值与真实值的误差更新参数
    • 批量优化:使用矩阵运算直接求解最优参数
  4. 预测方法:应用Sigmoid函数并四舍五入得到类别标签

2. 关键组件解析

2.1 Sigmoid类实现
class Sigmoid():
    """Sigmoid activation function."""
    def __call__(self, x):
        return 1 / (1 + np.exp(-x))
    
    def gradient(self, x):
        return self.__call__(x) * (1 - self.__call__(x))

Sigmoid类实现了函数本身及其导数,为反向传播提供支持。

2.2 参数初始化技巧
limit = 1 / math.sqrt(n_features)
self.param = np.random.uniform(-limit, limit, (n_features,))

这种初始化方式可以确保前向传播的输出值不会过大或过小,有助于保持数值稳定性。

实战案例:鸢尾花数据集二分类

1. 完整代码实现

下面我们使用鸢尾花(Iris)数据集来演示逻辑回归的完整应用流程:

from __future__ import print_function
from sklearn import datasets
import numpy as np
import matplotlib.pyplot as plt

# Import helper functions
from mlfromscratch.utils import make_diagonal, normalize, train_test_split, accuracy_score
from mlfromscratch.deep_learning.activation_functions import Sigmoid
from mlfromscratch.utils import Plot
from mlfromscratch.supervised_learning import LogisticRegression

def main():
    # Load dataset
    data = datasets.load_iris()
    X = normalize(data.data[data.target != 0])
    y = data.target[data.target != 0]
    y[y == 1] = 0
    y[y == 2] = 1

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, seed=1)

    clf = LogisticRegression(gradient_descent=True)
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)

    accuracy = accuracy_score(y_test, y_pred)
    print ("Accuracy:", accuracy)

    # Reduce dimension to two using PCA and plot the results
    Plot().plot_in_2d(X_test, y_pred, title="Logistic Regression", accuracy=accuracy)

if __name__ == "__main__":
    main()

2. 步骤分解:从数据到模型

2.1 数据准备与预处理
# 加载数据集
data = datasets.load_iris()
# 选择后两个类别(二分类)
X = normalize(data.data[data.target != 0])
y = data.target[data.target != 0]
# 标签转换:将1和2转换为0和1
y[y == 1] = 0
y[y == 2] = 1

鸢尾花数据集原本有3个类别,我们通过简单的索引操作将其转换为二分类问题。

2.2 数据标准化
def normalize(X, axis=-1, order=2):
    """Normalize the dataset (unit norm)"""
    l2 = np.atleast_1d(np.linalg.norm(X, order, axis))
    l2[l2 == 0] = 1
    return X / np.expand_dims(l2, axis)

标准化可以加速梯度下降的收敛速度,是逻辑回归中重要的预处理步骤。

2.3 数据集拆分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, seed=1)

将数据集按67:33的比例拆分为训练集和测试集,确保模型评估的客观性。

2.4 模型训练与预测
clf = LogisticRegression(gradient_descent=True)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

创建逻辑回归分类器实例,使用梯度下降法进行训练,并对测试集进行预测。

2.5 模型评估
accuracy = accuracy_score(y_test, y_pred)
print ("Accuracy:", accuracy)

准确率(Accuracy)是最简单的分类评估指标,计算公式为:

\text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}

其中TP、TN、FP、FN分别表示真正例、真负例、假正例和假负例。

3. 可视化结果

ML-From-Scratch提供了Plot类,可以将高维数据降维到2D空间并可视化分类结果:

Plot().plot_in_2d(X_test, y_pred, title="Logistic Regression", accuracy=accuracy)
可视化原理:主成分分析(PCA)
def plot_in_2d(self, X, y=None, title=None, accuracy=None, legend_labels=None):
    """ Plot the dataset X and the corresponding labels y in 2D using PCA. """
    # PCA dimensionality reduction to 2D
    pca = PCA(n_components=2)
    X_transformed = pca.fit_transform(X)
    
    # Create scatter plot
    plt.figure()
    if y is None:
        plt.scatter(X_transformed[:, 0], X_transformed[:, 1], alpha=0.5)
    else:
        colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
                  '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
        for i, label in enumerate(np.unique(y)):
            plt.scatter(X_transformed[y == label, 0],
                        X_transformed[y == label, 1],
                        alpha=0.8,
                        c=colors[i],
                        label=legend_labels[label] if legend_labels else label)
        if accuracy is not None:
            plt.suptitle(title)
            plt.title("Accuracy: %.2f" % accuracy, fontsize=10)
        plt.legend()
    plt.show()

通过PCA将4维特征降维到2D空间,我们可以直观地看到模型的分类效果。

进阶技巧:优化逻辑回归性能的关键策略

1. 梯度下降 vs 批量优化:如何选择?

优化方法优点缺点适用场景
梯度下降内存占用小,可在线学习需要调学习率,收敛慢大数据集,在线学习
批量优化无需调参,一步到位计算复杂度高,内存消耗大小数据集,离线学习

在ML-From-Scratch实现中,通过gradient_descent参数可以轻松切换两种优化方式:

# 梯度下降
clf_gd = LogisticRegression(gradient_descent=True, learning_rate=0.1)

# 批量优化
clf_batch = LogisticRegression(gradient_descent=False)

2. 学习率选择:收敛的关键

学习率(Learning Rate)是梯度下降中最重要的超参数,直接影响模型的收敛速度和效果:

  • 学习率太小:收敛太慢,需要更多迭代次数
  • 学习率太大:可能导致不收敛,在最优解附近震荡
学习率调整策略:
# 尝试不同学习率
learning_rates = [0.001, 0.01, 0.1, 0.5]
accuracies = []

for lr in learning_rates:
    clf = LogisticRegression(gradient_descent=True, learning_rate=lr)
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    accuracies.append(accuracy_score(y_test, y_pred))

# 绘制学习率-准确率曲线
plt.plot(learning_rates, accuracies, marker='o')
plt.xlabel('Learning Rate')
plt.ylabel('Accuracy')
plt.xscale('log')  # 对数刻度更适合展示学习率
plt.title('Learning Rate vs Accuracy')
plt.show()

通过这种方式,我们可以找到最优的学习率。在鸢尾花数据集上,0.1通常是一个不错的起点。

3. 特征工程:提升模型性能的利器

逻辑回归本身是线性模型,但通过特征工程可以处理非线性关系:

3.1 多项式特征
def polynomial_features(X, degree):
    """
    Create polynomial features for the input data.
    
    Parameters:
    -----------
    X : array-like, shape [n_samples, n_features]
        The input data.
    degree : int
        The degree of the polynomial features.
        
    Returns:
    --------
    X_poly : array-like, shape [n_samples, n_features * degree]
        The polynomial features.
    """
    n_samples, n_features = np.shape(X)
    
    # Create new array with polynomial features
    X_poly = np.zeros((n_samples, n_features * degree))
    
    # Fill the new array with polynomial features
    for i in range(n_samples):
        for d in range(1, degree + 1):
            for j in range(n_features):
                X_poly[i, (d-1)*n_features + j] = X[i, j] ** d
    
    return X_poly

通过添加多项式特征,逻辑回归可以拟合非线性决策边界。

3.2 正则化:防止过拟合的有效手段

过拟合(Overfitting)是机器学习中的常见问题,正则化是解决这一问题的有效手段。ML-From-Scratch虽然没有直接实现正则化,但我们可以轻松扩展:

def fit(self, X, y, n_iterations=4000, lambda_=0.01):
    self._initialize_parameters(X)
    for i in range(n_iterations):
        y_pred = self.sigmoid(X.dot(self.param))
        if self.gradient_descent:
            # 添加L2正则化项
            regularization = lambda_ * self.param
            self.param -= self.learning_rate * (-(y - y_pred).dot(X) + regularization)
        # ... 批量优化类似

正则化通过对参数施加惩罚,防止参数过大,从而提高模型的泛化能力。

项目实践:从零开始搭建逻辑回归分类系统

1. 环境准备

首先,克隆ML-From-Scratch仓库:

git clone https://gitcode.com/GitHub_Trending/ml/ML-From-Scratch
cd ML-From-Scratch

安装必要依赖:

pip install -r requirements.txt

2. 完整工作流实现

# 1. 导入必要的库
import numpy as np
from mlfromscratch.supervised_learning import LogisticRegression
from mlfromscratch.utils import normalize, train_test_split, accuracy_score, Plot
from sklearn import datasets

# 2. 加载并准备数据
data = datasets.load_breast_cancer()  # 使用乳腺癌数据集
X = normalize(data.data)
y = data.target

# 3. 拆分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, seed=42)

# 4. 训练模型
clf = LogisticRegression(learning_rate=0.1, gradient_descent=True)
clf.fit(X_train, y_train, n_iterations=10000)

# 5. 预测与评估
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy:.4f}")

# 6. 可视化结果
Plot().plot_in_2d(X_test, y_pred, title="Breast Cancer Classification", accuracy=accuracy)

3. 常见问题与解决方案

3.1 模型不收敛怎么办?
  • 检查数据是否标准化:逻辑回归对特征尺度敏感
  • 调整学习率:尝试减小学习率(如从0.1调整到0.01)
  • 增加迭代次数:n_iterations参数调大(如从4000增加到10000)
3.2 准确率不高如何改进?
  • 特征工程:添加多项式特征或交互项
  • 正则化:添加L1或L2正则化项
  • 尝试不同优化方法:切换到批量优化或尝试其他优化器(如牛顿法)

总结与展望:逻辑回归的价值与局限

1. 逻辑回归的优势

  • 实现简单,计算高效
  • 输出具有概率意义,可解释性强
  • 内存占用小,适合大规模数据集
  • 可通过特征工程处理非线性问题

2. 逻辑回归的局限性

  • 本质是线性模型,难以处理复杂非线性关系
  • 对异常值敏感,需要预处理
  • 特征相关性高时性能下降,需要特征选择

3. 后续学习路径

  1. 多分类扩展:了解One-vs-Rest和Softmax回归
  2. 高级优化器:学习Adam、RMSprop等现代优化算法
  3. 正则化深入:L1、L2正则化与弹性网络(Elastic Net)
  4. 模型融合:逻辑回归作为基础模型的集成学习方法

结语:从原理到实践的蜕变

通过本文的学习,你已经掌握了逻辑回归的数学原理、ML-From-Scratch实现细节和完整的二分类项目实战。从推导Sigmoid函数到实现梯度下降,从数据预处理到模型评估可视化,我们完成了一次完整的机器学习实践之旅。

逻辑回归作为最基础的分类算法,不仅是面试高频考点,更是理解更复杂模型(如神经网络)的基石。希望你能将本文所学应用到实际项目中,并继续探索机器学习的精彩世界!

如果觉得本文对你有帮助,请点赞、收藏、关注三连,下期我们将深入探讨决策树算法的原理与实现!

【免费下载链接】ML-From-Scratch Machine Learning From Scratch. Bare bones NumPy implementations of machine learning models and algorithms with a focus on accessibility. Aims to cover everything from linear regression to deep learning. 【免费下载链接】ML-From-Scratch 项目地址: https://gitcode.com/GitHub_Trending/ml/ML-From-Scratch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值