一、算法原理

逻辑回归算法是一种用于二元分类的算法,它通过使用逻辑函数将线性回归的结果映射到[0,1]范围内。逻辑回归模型将输入特征与输出类别之间的关系表示为线性回归函数,然后通过逻辑函数将线性回归的结果转换为一个概率值,用于预测目标变量。逻辑回归的优点是计算效率高,但在处理高维数据时可能会过拟合。

二、参考代码

import numpy as np
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import (confusion_matrix, roc_curve, auc, 
                            ConfusionMatrixDisplay, accuracy_score)
import pandas as pd


class LogisticRegression:
    def __init__(self, learning_rate=0.01, n_iterations=1000):
        """
        初始化逻辑回归模型
        
        参数:
        learning_rate -- 学习率 (默认: 0.01)
        n_iterations -- 迭代次数 (默认: 1000)
        """
        self.learning_rate = learning_rate
        self.n_iterations = n_iterations
        self.weights = None
        self.bias = None
    def generate_logistic_data(
        n_samples=100, 
        n_features=2, 
        weights=None, 
        bias=0, 
        noise_std=0.5, 
        random_seed=None
    ):
        """
        生成适合逻辑回归的二分类数据
        
        参数:
            n_samples (int): 样本数量(默认100)
            n_features (int): 特征数量(默认2,便于可视化)
            weights (array): 真实权重向量(默认随机生成)
            bias (float): 偏置项(默认0)
            noise_std (float): 噪声的标准差(默认0.5)
            random_seed (int): 随机种子(默认None)
        
        返回:
            X (ndarray): 特征矩阵,形状 (n_samples, n_features)
            y (ndarray): 标签向量,形状 (n_samples,),取值为0或1
        """
        if random_seed is not None:
            np.random.seed(random_seed)
        
        # 默认权重(随机生成)
        if weights is None:
            weights = np.random.randn(n_features)
        
        # 生成特征矩阵(标准正态分布)
        X = np.random.randn(n_samples, n_features)
        
        # 计算线性组合 z = w·X + b
        z = np.dot(X, weights) + bias
        
        # 通过逻辑函数(sigmoid)将z转换为概率
        prob = 1 / (1 + np.exp(-z))
        
        # 根据概率生成二分类标签(0或1)
        y = (prob > 0.5).astype(int)
        
        # 添加高斯噪声(可选)
        if noise_std > 0:
            X += np.random.normal(scale=noise_std, size=X.shape)
        
        return X, y
    
    def _sigmoid(self, z):
        """Sigmoid激活函数"""
        return 1 / (1 + np.exp(-z))
    
    def fit(self, X, y):
        """
        训练逻辑回归模型
        
        参数:
        X -- 特征矩阵,形状 (n_samples, n_features)
        y -- 目标向量,形状 (n_samples,)
        """
        n_samples, n_features = X.shape
        
        # 初始化参数
        self.weights = np.zeros(n_features)
        self.bias = 0
        
        # 梯度下降
        for _ in range(self.n_iterations):
            # 计算线性预测
            linear_model = np.dot(X, self.weights) + self.bias
            # 应用sigmoid函数
            y_pred = self._sigmoid(linear_model)
            
            # 计算梯度
            dw = (1 / n_samples) * np.dot(X.T, (y_pred - y))
            db = (1 / n_samples) * np.sum(y_pred - y)
            
            # 更新参数
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db
    
    def predict_proba(self, X):
        """
        预测概率
        
        参数:
        X -- 特征矩阵,形状 (n_samples, n_features)
        
        返回:
        proba -- 预测概率,形状 (n_samples,)
        """
        linear_model = np.dot(X, self.weights) + self.bias
        return self._sigmoid(linear_model)
    
    def predict(self, X, threshold=0.5):
        """
        预测类别
        
        参数:
        X -- 特征矩阵,形状 (n_samples, n_features)
        threshold -- 分类阈值 (默认: 0.5)
        
        返回:
        predictions -- 预测类别,形状 (n_samples,)
        """
        probabilities = self.predict_proba(X)
        return [1 if p >= threshold else 0 for p in probabilities]
    
    def score(self, X, y):
        """
        计算模型准确率
        
        参数:
        X -- 特征矩阵,形状 (n_samples, n_features)
        y -- 真实标签,形状 (n_samples,)
        
        返回:
        accuracy -- 准确率
        """
        predictions = self.predict(X)
        return np.mean(predictions == y)

# 归一化
class Normalizer:
    def __init__(self, method='minmax'):
        """
        初始化归一化器
        :param method: 'minmax' (默认) 或 'zscore'
        """
        self.method = method
        self.params = {}  # 存储归一化参数(min, max 或 mean, std)

    def fit(self, X):
        """
        计算归一化参数(不直接修改数据)
        :param X: 输入数据 (n_samples, n_features)
        """
        if self.method == 'minmax':
            self.params['min'] = np.min(X, axis=0)
            self.params['max'] = np.max(X, axis=0)
        elif self.method == 'zscore':
            self.params['mean'] = np.mean(X, axis=0)
            self.params['std'] = np.std(X, axis=0)
        else:
            raise ValueError("method 必须是 'minmax' 或 'zscore'")

    def transform(self, X):
        """
        对数据进行归一化
        :param X: 输入数据 (n_samples, n_features)
        :return: 归一化后的数据
        """
        if not self.params:
            raise ValueError("请先调用 fit() 计算归一化参数")

        if self.method == 'minmax':
            min_vals = self.params['min']
            max_vals = self.params['max']
            # 避免除以零(如果某列所有值相同)
            denominator = np.where(max_vals - min_vals != 0, max_vals - min_vals, 1)
            X_normalized = (X - min_vals) / denominator
        elif self.method == 'zscore':
            mean_vals = self.params['mean']
            std_vals = self.params['std']
            # 避免除以零(如果某列标准差为0)
            denominator = np.where(std_vals != 0, std_vals, 1)
            X_normalized = (X - mean_vals) / denominator

        return X_normalized

    def fit_transform(self, X):
        """
        计算参数并直接归一化数据
        :param X: 输入数据 (n_samples, n_features)
        :return: 归一化后的数据
        """
        self.fit(X)
        return self.transform(X)
class LogisticRegressionVisualizer:
    def __init__(self, model):
        """
        初始化可视化工具
        :param model: 训练好的 LogisticRegression 实例
        """
        self.model = model
    
    def plot_decision_boundary(self, X, y, resolution=0.02, figsize=(8, 6)):
        """
        绘制决策边界和散点图
        :param X: 特征矩阵 (n_samples, 2)
        :param y: 标签 (n_samples,)
        :param resolution: 网格分辨率
        :param figsize: 图像大小
        """
        if X.shape[1] != 2:
            raise ValueError("决策边界仅支持二维特征数据")
        
        # 生成网格点
        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.arange(x_min, x_max, resolution),
                             np.arange(y_min, y_max, resolution))
        
        # 预测每个网格点的类别
        grid = np.c_[xx.ravel(), yy.ravel()]
        probs = self.model.predict_proba(grid).reshape(xx.shape)
        
        # 绘制决策边界和散点
        plt.figure(figsize=figsize)
        plt.contourf(xx, yy, probs, levels=20, cmap=plt.cm.RdBu, alpha=0.6)
        plt.contour(xx, yy, probs, levels=[0.5], colors='black', linewidths=1)
        plt.scatter(X[:, 0], X[:, 1], c=y, edgecolors='k', cmap=plt.cm.Paired)
        
        plt.title("Decision Boundary")
        plt.xlabel("Feature 1")
        plt.ylabel("Feature 2")
        plt.show()
    
    def plot_coefficients(self, feature_names=None, figsize=(6, 4)):
        """
        绘制系数权重条形图
        :param feature_names: 特征名称列表 (可选)
        :param figsize: 图像大小
        """
        if feature_names is None:
            feature_names = [f"Feature {i}" for i in range(len(self.model.weights))]
        
        plt.figure(figsize=figsize)
        sns.barplot(x=self.model.weights, hue=feature_names,legend=False)
        plt.axvline(0, color='k', linestyle='--')
        plt.title("Logistic Regression Coefficients")
        plt.xlabel("Coefficient Value")
        plt.ylabel("Features")
        plt.tight_layout()
        plt.show()
    
    def plot_probability_distribution(self, X, y, figsize=(8, 4)):
        """
        绘制预测概率的分布(按真实类别分组)
        :param X: 特征矩阵
        :param y: 真实标签
        :param figsize: 图像大小
        """
        probs = self.model.predict_proba(X)
        df = pd.DataFrame({"Probability": probs, "True Class": y})
        
        plt.figure(figsize=figsize)
        sns.kdeplot(data=df, x="Probability", hue="True Class", 
                    common_norm=False, palette=["blue", "orange"], fill=True)
        plt.axvline(0.5, color='red', linestyle='--', label="Threshold (0.5)")
        plt.title("Predicted Probability Distribution by Class")
        plt.xlabel("Predicted Probability")
        plt.ylabel("Density")
        plt.legend()
        plt.show()
    
    def plot_roc_curve(self, X, y, figsize=(8, 6)):
        """
        绘制 ROC 曲线和 AUC
        :param X: 特征矩阵
        :param y: 真实标签
        :param figsize: 图像大小
        """
        probs = self.model.predict_proba(X)
        fpr, tpr, thresholds = roc_curve(y, probs)
        roc_auc = auc(fpr, tpr)
        
        plt.figure(figsize=figsize)
        plt.plot(fpr, tpr, color='darkorange', lw=2, 
                 label=f"ROC Curve (AUC = {roc_auc:.2f})")
        plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
        plt.xlim([0.0, 1.0])
        plt.ylim([0.0, 1.05])
        plt.xlabel("False Positive Rate")
        plt.ylabel("True Positive Rate")
        plt.title("Receiver Operating Characteristic (ROC)")
        plt.legend(loc="lower right")
        plt.show()
    
    def plot_confusion_matrix(self, X, y, figsize=(6, 6)):
        """
        绘制混淆矩阵
        :param X: 特征矩阵
        :param y: 真实标签
        :param figsize: 图像大小
        """
        y_pred = self.model.predict(X)
        cm = confusion_matrix(y, y_pred)
        
        plt.figure(figsize=figsize)
        sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", 
                    xticklabels=["Class 0", "Class 1"],
                    yticklabels=["Class 0", "Class 1"])
        plt.title("Confusion Matrix")
        plt.xlabel("Predicted Label")
        plt.ylabel("True Label")
        plt.show()
    
    def plot_all(self, X, y, feature_names=None):
        """一键绘制所有可视化"""
        print("=== Plotting Decision Boundary ===")
        self.plot_decision_boundary(X, y)
        
        print("\n=== Plotting Coefficients ===")
        self.plot_coefficients(feature_names)
        
        print("\n=== Plotting Probability Distribution ===")
        self.plot_probability_distribution(X, y)
        
        print("\n=== Plotting ROC Curve ===")
        self.plot_roc_curve(X, y)
        
        print("\n=== Plotting Confusion Matrix ===")
        self.plot_confusion_matrix(X, y)

三、归一化数据的必要性

数据处理未归一化归一化
训练集准确率0.68370.8363
测试集准确率0.69500.8150
weight-286、-4501.218、1.282
bias-32-1.191

可以看出来,未归化的结果准确率较低,模型权重系数很大,并且未归一化会出现数值溢出的问题。本次实验采用的是具备明显特征尺度差异的数据。

(1) 最小-最大归一化(Min-Max Scaling)

公式:
x ′ = x − m i n ( X ) m a x ( X ) − m i n ( X ) x^{'}=\frac{x-min(X)}{max(X)-min(X)} x=max(X)min(X)xmin(X)
输出范围:[0, 1](或 [-1, 1],如果调整公式)。
特点:保留原始数据的分布形状。
对异常值敏感(若数据存在极端值,缩放后可能集中在较小范围)。
适用场景:
数据分布未知或非高斯分布。
需要将数据固定在特定范围(如图像像素值 [0, 255] → [0, 1])。

(2) 标准化(Z-Score Normalization / Standardization)

公式:
x ′ = x − μ σ x^{'}=\frac{x-\mu}{\sigma} x=σxμ
输出范围:均值为 0,标准差为 1(无固定边界)。
特点:假设数据服从高斯分布(对非高斯分布可能效果不佳)。
对异常值鲁棒性优于 Min-Max(但极端值仍会影响均值和方差)。
适用场景:数据近似高斯分布。
需要消除量纲影响(如逻辑回归、SVM、神经网络等)。

四、逻辑回归的两个核心

1、sigmoid函数

公式:
σ ( x ) = 1 1 + e − x \sigma(x)=\frac{1}{1+e^{-x}} σ(x)=1+ex1
输出范围:严格限制在 (0,1),符合概率解释。
单调性:单调递增,保证概率随 x 增大而增大。
导数易计算: σ ( x ) = σ ( x ) ( 1 − σ ( x ) ) \sigma(x)=\sigma(x)(1-\sigma(x)) σ(x)=σ(x)(1σ(x)),便于梯度下降优化。
概率解释:与伯努利分布的对数似然函数直接关联,理论推导优雅。

2、梯度下降

目标:找到损失函数 J(θ) 的最小值,其中
θ是模型参数(如 w,b)。
数学基础:梯度 ∇J(θ)是损失函数在参数空间中的“斜率”向量,指向函数增长最快的方向。
负梯度方向是函数下降最快的方向,因此参数更新规则为:
θ t + 1 = θ t − η ⋅ ∇ J ( θ ) \theta_{t+1}=\theta_t-\eta \cdot \nabla J(\theta) θt+1=θtηJ(θ)

五、各类模型评价指标

1、测试集准确率和模型结果

结果:model:weight:[ 2.58995708 -1.9043075 ],bias:0.6369472498190414
accuracy:0.8575
生成数据时的权重为weights=np.array([2.0, -1.5]), bias=0.5。
这是因为在生成数据时加入高斯噪声,所以才有权重和偏置值的变化。

2、决策边界

在这里插入图片描述
由此可以看出,数据具备明显的线性特征,但也存在比较明显的噪声。

3、系数权重

在这里插入图片描述
画图表现不同特征的权重比例。

4、概率分布

在这里插入图片描述
蓝色曲线:表示负类(通常是类别0)的预测概率分布。曲线下的区域表示负类样本在不同预测概率下的密度。
橙色曲线:表示正类(通常是类别1)的预测概率分布。曲线下的区域表示正类样本在不同预测概率下的密度。
两条曲线在中间部分有重叠,这表示存在一些样本,模型对其属于正类还是负类的预测概率接近0.5。这些样本的分类结果可能不太稳定,微小的变化可能导致分类结果的改变。
红色虚线(阈值0.5)将图表分为两部分。左侧的样本(预测概率小于0.5)将被分类为负类,右侧的样本(预测概率大于0.5)将被分类为正类。
通过调整阈值,可以改变分类的严格程度。例如,提高阈值(如0.6)会使模型更倾向于将样本分类为负类,而降低阈值(如0.4)会使模型更倾向于将样本分类为正类。

5、ROC 曲线

在这里插入图片描述
横轴:“False Positive Rate”(FPR,假阳性率),表示实际为负类但被模型错误预测为正类的比例。其计算公式为:FPR = FP / (FP + TN),其中FP是假阳性,TN是真阴性。
纵轴:“True Positive Rate”(TPR,真阳性率),也称为召回率(Recall),表示实际为正类且被模型正确预测为正类的比例。其计算公式为:TPR = TP / (TP + FN),其中TP是真阳性,FN是假阴性。
橙色曲线:表示逻辑回归模型的ROC曲线。该曲线展示了在不同分类阈值下,模型的TPR和FPR之间的权衡关系。
虚线对角线:这条线代表随机猜测的情况,即模型的性能与随机猜测无异。如果一个模型的ROC曲线接近这条对角线,说明其分类性能较差。
AUC(Area Under the Curve) 是指ROC曲线下的面积,取值范围在0到1之间。AUC值越接近1,表示模型的性能越好。
AUC = 0.5:相当于随机猜测。
AUC > 0.5:模型有一定的预测能力,且越接近1,性能越好。
ROC曲线越靠近左上角,模型的性能越好。 因为这意味着在较低的假阳性率下,模型能够获得较高的真阳性率。
在这张图中,橙色曲线迅速上升并接近左上角,然后趋于平稳,最终到达右上角。这表明模型在不同的分类阈值下都能保持较好的性能。

6、混淆矩阵

在这里插入图片描述
(1)True Negative (TN):
实际为Class 0,模型预测为Class 0的样本数量。
位于矩阵的左上角,数值为689。
(2)False Positive (FP):
实际为Class 0,但模型错误预测为Class 1的样本数量。
位于矩阵的右上角,数值为159。
(3)False Negative (FN):
实际为Class 1,但模型错误预测为Class 0的样本数量。
位于矩阵的左下角,数值为134。
(4)True Positive (TP):
实际为Class 1,模型预测为Class 1的样本数量。
位于矩阵的右下角,数值为1018。
用混淆矩阵来计算
准确率 (Accuracy):
公式:(TP + TN) / (TP + TN + FP + FN)
表示模型整体预测正确的比例。
精确率 (Precision):
公式:TP / (TP + FP)
表示模型预测为Class 1的样本中,实际为Class 1的比例。
召回率 (Recall):
公式:TP / (TP + FN)
表示实际为Class 1的样本中,模型正确预测为Class 1的比例。
F1分数 (F1 Score):
公式:2 * (Precision * Recall) / (Precision + Recall)
是精确率和召回率的调和平均数,综合反映了模型的性能。

六、算法评价

1、应用场景

主要用于分类问题,尤其是在二分类任务中表现出色。
信用评分:金融机构使用逻辑回归来评估客户的信用风险,预测客户是否会违约。
医疗诊断:用于预测患者是否患有某种疾病,基于各种医疗指标进行分类。
营销领域:预测客户对营销活动的响应,例如是否会购买某个产品。
文本分类:在自然语言处理中,逻辑回归可用于情感分析、垃圾邮件检测等任务。
选举预测:用于预测选民行为或选举结果。

2、优点

简单易用:逻辑回归模型形式简单,易于实现和解释。
计算效率高:训练速度快,适合大规模数据集。
概率输出:输出具有概率解释,便于决策和风险评估。
特征重要性:可以通过权重分析特征对目标变量的影响程度。
正则化支持:支持L1和L2正则化,有助于防止过拟合。

(1)L1和L2正则化

L1和L2正则化是机器学习中常用的技术,用于防止模型过拟合,提高模型的泛化能力。它们通过在损失函数中添加惩罚项来限制模型权重的大小。

1)一、L1正则化(Lasso正则化)

定义:L1正则化在损失函数中添加一个与权重绝对值成比例的惩罚项。
数学表达式:在原始损失函数
L 添加 λ ∑ ∣ w i ∣ ,其中 λ 为正则化强度, w i 为模型权重 L添加\lambda \sum|w_i|,其中\lambda为正则化强度,w_i为模型权重 L添加λwi,其中λ为正则化强度,wi为模型权重
作用:
倾向于产生稀疏的权重矩阵,即一些权重会变为零。
可以用于特征选择,因为零权重意味着对应的特征对模型没有贡献。
优点:
可以减少特征数量,简化模型。
在高维数据中特别有用。
缺点:
可能导致在压缩感知和稀疏编码中丢失一些信息。

2)L2正则化(Ridge正则化)

定义:
L2正则化在损失函数中添加一个与权重平方成比例的惩罚项。
数学表达式:在原始损失函数
L 添加 λ ∑ w i 2 ,其中 λ 为正则化强度, w i 为模型权重 L添加\lambda \sum w_i^{2},其中\lambda为正则化强度,w_i为模型权重 L添加λwi2,其中λ为正则化强度,wi为模型权重
作用:倾向于使权重接近零,但不会完全为零。
通过限制权重的大小,防止模型对训练数据中的噪声过度拟合。
优点:防止过拟合,提高模型的泛化能力。
在存在多个相关特征时,可以平均分配权重,减少对单一特征的依赖。
缺点:不会产生稀疏解,因此不能直接用于特征选择。
需要选择合适的正则化强度 λ \lambda λ

2)选择L1或L2正则化

特征选择需求:如果希望减少特征数量,选择L1正则化。
模型稳定性:如果希望模型在多个相关特征上表现稳定,选择L2正则化。
数据维度:在高维数据中,L1正则化可能更有效;在低维数据中,L2正则化可能更合适。

3、缺点

线性假设:逻辑回归假设特征与对数几率之间是线性关系,对于非线性问题效果不佳。
多重共线性问题:当特征之间存在高度相关性时,模型性能可能受到影响。
数据预处理要求:需要处理缺失值和异常值,特征可能需要标准化或归一化。
类别不平衡敏感:当某一类样本数量远多于另一类时,模型可能偏向多数类。
复杂模式捕捉能力有限:对于复杂的模式和交互作用,可能需要引入多项式特征或使用其他模型。

参考文献

1、【总结】机器学习中的15种分类算法

更多推荐