第5篇:逻辑回归——不只是预测数字,还能分类

逻辑回归:分类与概率预测

🤔 从线性回归的困惑说起

还记得我们用线性回归预测房价吗?它能告诉我们"120㎡的房子大概值180万"。

但是,如果我们想问:

  • "这套房子值得买还是不值得买​?"
  • "这个用户会点击广告还是不会点击​?"
  • "这封邮件是垃圾邮件还是正常邮件​?"

这些问题需要的答案是类别,而不是具体数字!

🎯 线性回归的困境

让我们试试直接用线性回归做分类:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

# 模拟考试成绩数据:学习时间 vs 是否及格(1=及格,0=不及格)
np.random.seed(42)
study_hours = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
pass_exam = np.array([0, 0, 0, 1, 1, 1, 1, 1, 1, 1])  # 及格=1,不及格=0

# 用线性回归拟合
lr_model = LinearRegression()
lr_model.fit(study_hours.reshape(-1, 1), pass_exam)

# 预测
hours_for_prediction = np.array([0.5, 1.5, 2.5, 3.5, 4.5, 5.5]).reshape(-1, 1)
predictions = lr_model.predict(hours_for_prediction)

print("学习时间 -> 线性回归预测值:")
for hour, pred in zip([0.5, 1.5, 2.5, 3.5, 4.5, 5.5], predictions):
    print(f"{hour}小时 -> {pred:.3f}")

# 可视化
plt.figure(figsize=(10, 6))
plt.scatter(study_hours, pass_exam, color='blue', s=80, label='实际数据')
plt.plot(study_hours, lr_model.predict(study_hours.reshape(-1, 1)), 
         color='red', linewidth=2, label='线性回归')

# 添加分类边界线(y=0.5)
plt.axhline(y=0.5, color='green', linestyle='--', alpha=0.7, label='分类边界(0.5)')

plt.xlabel('学习时间(小时)')
plt.ylabel('是否及格(0=不及格, 1=及格)')
plt.title('线性回归做分类:预测值在0-1之间,但可能超出范围')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

输出结果:

学习时间 -> 线性回归预测值:
0.5小时 -> -0.050
1.5小时 -> 0.167  
2.5小时 -> 0.383
3.5小时 -> 0.600
4.5小时 -> 0.817
5.5小时 -> 1.033

发现问题了!​

  • 预测值出现了负数(-0.050)和大于1的数(1.033)
  • 但我们想要的是明确的0或1!

这就是我们需要逻辑回归的原因!

🎯 逻辑回归的核心思想

🔄 从直线到S曲线

逻辑回归不直接预测0或1,而是预测概率​(0到1之间的数)。

关键洞察​:用Sigmoid函数把直线"压缩"到0-1之间!

def sigmoid(z):
    """Sigmoid函数:把任何数映射到0-1之间"""
    return 1 / (1 + np.exp(-z))

# 可视化Sigmoid函数
z_values = np.linspace(-10, 10, 100)
sigmoid_values = sigmoid(z_values)

plt.figure(figsize=(10, 4))

plt.subplot(1, 2, 1)
plt.plot(z_values, sigmoid_values, 'b-', linewidth=2)
plt.axvline(x=0, color='red', linestyle='--', alpha=0.7)
plt.axhline(y=0.5, color='red', linestyle='--', alpha=0.7)
plt.xlabel('z (线性组合)')
plt.ylabel('σ(z) (概率)')
plt.title('Sigmoid函数:把直线变成S曲线')
plt.grid(True, alpha=0.3)

plt.subplot(1, 2, 2)
plt.plot(z_values, sigmoid_values, 'b-', linewidth=2)
plt.fill_between(z_values, sigmoid_values, where=(z_values >= 0), 
                 alpha=0.3, color='green', label='P≥0.5 (预测为1)')
plt.fill_between(z_values, sigmoid_values, where=(z_values < 0), 
                 alpha=0.3, color='red', label='P<0.5 (预测为0)')
plt.axvline(x=0, color='black', linestyle='-', alpha=0.7)
plt.xlabel('z (线性组合)')
plt.ylabel('P (概率)')
plt.title('分类决策边界:z=0处')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("Sigmoid函数的一些值:")
print(f"sigmoid(-2) = {sigmoid(-2):.3f}")
print(f"sigmoid(-1) = {sigmoid(-1):.3f}")  
print(f"sigmoid(0)  = {sigmoid(0):.3f}")
print(f"sigmoid(1)  = {sigmoid(1):.3f}")
print(f"sigmoid(2)  = {sigmoid(2):.3f}")

📐 逻辑回归的数学表达

z = w₁x₁ + w₂x₂ + ... + b    (还是线性组合)
P = σ(z) = 1/(1+e^(-z))       (Sigmoid转换)

预测规则​:

  • 如果 P ≥ 0.5,预测为类别1
  • 如果 P < 0.5,预测为类别0

🛠️ 用Python实现逻辑回归

方法1:手工实现(理解原理)

class SimpleLogisticRegression:
    def __init__(self, learning_rate=0.01, n_iterations=1000):
        self.learning_rate = learning_rate
        self.n_iterations = n_iterations
        self.w = None
        self.b = 0
        self.loss_history = []
    
    def sigmoid(self, z):
        """Sigmoid函数"""
        # 防止溢出
        z = np.clip(z, -250, 250)
        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)
        return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
    
    def fit(self, X, y):
        """训练模型"""
        X = np.array(X)
        y = np.array(y)
        
        # 如果是单特征,转换成二维数组
        if X.ndim == 1:
            X = X.reshape(-1, 1)
        
        n_samples, n_features = X.shape
        self.w = np.zeros(n_features)
        
        # 梯度下降
        for i in range(self.n_iterations):
            # 前向传播
            linear_output = np.dot(X, self.w) + self.b
            predictions = self.sigmoid(linear_output)
            
            # 计算损失
            loss = self.compute_loss(y, predictions)
            self.loss_history.append(loss)
            
            # 计算梯度
            dw = (1/n_samples) * np.dot(X.T, (predictions - y))
            db = (1/n_samples) * np.sum(predictions - y)
            
            # 更新参数
            self.w -= self.learning_rate * dw
            self.b -= self.learning_rate * db
            
            # 打印进度
            if i % 200 == 0:
                print(f"迭代 {i}: 损失 = {loss:.4f}")
    
    def predict_proba(self, X):
        """预测概率"""
        X = np.array(X)
        if X.ndim == 1:
            X = X.reshape(-1, 1)
        linear_output = np.dot(X, self.w) + self.b
        return self.sigmoid(linear_output)
    
    def predict(self, X, threshold=0.5):
        """预测类别"""
        probabilities = self.predict_proba(X)
        return (probabilities >= threshold).astype(int)

# 用考试成绩数据训练逻辑回归
X_study = study_hours.reshape(-1, 1)  # 学习时间
y_pass = pass_exam                    # 是否及格

log_reg = SimpleLogisticRegression(learning_rate=0.1, n_iterations=1000)
log_reg.fit(X_study, y_pass)

print(f"\n训练完成!")
print(f"权重 w: {log_reg.w[0]:.3f}")
print(f"偏置 b: {log_reg.b:.3f}")
print(f"决策边界: {log_reg.w[0]:.3f} × 学习时间 + {log_reg.b:.3f} = 0")
print(f"临界学习时间: {-log_reg.b/log_reg.w[0]:.2f}小时")

# 预测
predictions = log_reg.predict(hours_for_prediction)
probabilities = log_reg.predict_proba(hours_for_prediction)

print(f"\n逻辑回归预测结果:")
for hour, pred, prob in zip([0.5, 1.5, 2.5, 3.5, 4.5, 5.5], predictions, probabilities):
    status = "及格" if pred == 1 else "不及格"
    print(f"{hour}小时 -> 概率:{prob:.3f}, 预测:{status}")

方法2:使用sklearn(实际应用)

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

# 使用sklearn的逻辑回归
sk_log_reg = LogisticRegression()
sk_log_reg.fit(X_study, y_pass)

print("=== sklearn逻辑回归结果 ===")
print(f"系数: {sk_log_reg.coef_[0][0]:.3f}")
print(f"截距: {sk_log_reg.intercept_[0]:.3f}")

# 预测
sk_predictions = sk_log_reg.predict(hours_for_prediction)
sk_probabilities = sk_log_reg.predict_proba(hours_for_prediction)[:, 1]  # 取第二类的概率

print(f"\nsklearn预测结果:")
for hour, pred, prob in zip([0.5, 1.5, 2.5, 3.5, 4.5, 5.5], sk_predictions, sk_probabilities):
    status = "及格" if pred == 1 else "不及格"
    print(f"{hour}小时 -> 概率:{prob:.3f}, 预测:{status}")

# 模型评估
train_predictions = sk_log_reg.predict(X_study)
accuracy = accuracy_score(y_pass, train_predictions)

print(f"\n模型准确率: {accuracy:.3f}")
print(f"分类报告:\n{classification_report(y_pass, train_predictions)}")

# 可视化决策边界
plt.figure(figsize=(10, 6))

# 生成平滑的预测线
hours_smooth = np.linspace(0, 11, 100).reshape(-1, 1)
prob_smooth = sk_log_reg.predict_proba(hours_smooth)[:, 1]

plt.scatter(study_hours, pass_exam, color='blue', s=80, label='实际数据')
plt.plot(hours_smooth, prob_smooth, 'red', linewidth=2, label='逻辑回归概率曲线')
plt.axhline(y=0.5, color='green', linestyle='--', alpha=0.7, label='决策边界(P=0.5)')
plt.axvline(x=-sk_log_reg.intercept_[0]/sk_log_reg.coef_[0][0], 
           color='orange', linestyle='--', alpha=0.7, 
           label=f'临界学习时间({(-sk_log_reg.intercept_[0]/sk_log_reg.coef_[0][0]):.2f}h)')

plt.xlabel('学习时间(小时)')
plt.ylabel('及格概率')
plt.title('逻辑回归:从线性到概率')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

🔍 深入理解:损失函数

📉 为什么不用均方误差?

如果用线性回归的均方误差来训练逻辑回归,会遇到问题:

def compare_loss_functions():
    """比较不同损失函数在逻辑回归中的表现"""
    # 模拟数据
    z = np.linspace(-3, 3, 100)
    y_true = (z >= 0).astype(float)  # 真实标签
    
    # Sigmoid预测概率
    y_pred = 1 / (1 + np.exp(-z))
    
    # 均方误差
    mse_loss = (y_true - y_pred)**2
    
    # 交叉熵损失
    epsilon = 1e-15
    y_pred_clipped = np.clip(y_pred, epsilon, 1 - epsilon)
    cross_entropy_loss = - (y_true * np.log(y_pred_clipped) + 
                           (1 - y_true) * np.log(1 - y_pred_clipped))
    
    plt.figure(figsize=(12, 4))
    
    plt.subplot(1, 2, 1)
    plt.plot(z, mse_loss, 'b-', linewidth=2, label='均方误差')
    plt.xlabel('z (线性输出)')
    plt.ylabel('损失')
    plt.title('均方误差在逻辑回归中的问题')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    plt.subplot(1, 2, 2)
    plt.plot(z, cross_entropy_loss, 'r-', linewidth=2, label='交叉熵损失')
    plt.xlabel('z (线性输出)')
    plt.ylabel('损失')
    plt.title('交叉熵损失:完美的凸函数')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

compare_loss_functions()

交叉熵损失的优点​:

  1. 凸函数​:只有一个最小值,容易优化
  2. 概率意义明确​:直接衡量概率分布的差异
  3. 梯度更大​:当预测错误时,梯度更大,学习更快

🎯 多变量逻辑回归

现实中的分类问题通常有多个特征:

📊 乳腺癌分类示例

from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler

# 加载乳腺癌数据集
data = load_breast_cancer()
X = data.data  # 特征:肿瘤的各个医学指标
y = data.target  # 标签:0=恶性,1=良性
feature_names = data.feature_names

print(f"数据集信息:")
print(f"样本数: {X.shape[0]}")
print(f"特征数: {X.shape[1]}")
print(f"良性样本: {np.sum(y==1)}, 恶性样本: {np.sum(y==0)}")

# 标准化特征(逻辑回归对特征尺度敏感)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 分割训练和测试集
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# 训练逻辑回归
multi_log_reg = LogisticRegression(random_state=42)
multi_log_reg.fit(X_train, y_train)

# 预测和评估
y_pred = multi_log_reg.predict(X_test)
y_pred_proba = multi_log_reg.predict_proba(X_test)[:, 1]  # 良性概率

accuracy = accuracy_score(y_test, y_pred)
print(f"\n多变量逻辑回归结果:")
print(f"测试集准确率: {accuracy:.3f}")

# 查看最重要的特征
feature_importance = abs(multi_log_reg.coef_[0])
top_features_idx = np.argsort(feature_importance)[-5:]  # 最重要的5个特征

print(f"\n最重要的5个特征:")
for idx in reversed(top_features_idx):
    print(f"{feature_names[idx]}: {feature_importance[idx]:.3f}")

# 单个样本的预测解释
sample_idx = 0
sample_features = X_test[sample_idx]
sample_true = y_test[sample_idx]
sample_pred = y_pred[sample_idx]
sample_prob = y_pred_proba[sample_idx]

print(f"\n样本预测解释:")
print(f"真实标签: {'良性' if sample_true == 1 else '恶性'}")
print(f"预测标签: {'良性' if sample_pred == 1 else '恶性'}")
print(f"良性概率: {sample_prob:.3f}")
print(f"预测可信度: {'高' if max(sample_prob, 1-sample_prob) > 0.8 else '低'}")

if sample_prob > 0.5:
    confidence = sample_prob
    print(f"模型很有信心这是良性肿瘤 (置信度: {confidence:.1%})")
else:
    confidence = 1 - sample_prob
    print(f"模型很有信心这是恶性肿瘤 (置信度: {confidence:.1%})")

🧠 逻辑回归的决策边界

🎨 可视化二维特征的决策边界

from sklearn.datasets import make_classification

# 生成二维分类数据
X_2d, y_2d = make_classification(n_samples=200, n_features=2, n_redundant=0, 
                               n_informative=2, n_clusters_per_class=1, 
                               random_state=42)

# 训练逻辑回归
log_reg_2d = LogisticRegression()
log_reg_2d.fit(X_2d, y_2d)

# 可视化决策边界
plt.figure(figsize=(10, 8))

# 创建网格点
x_min, x_max = X_2d[:, 0].min() - 1, X_2d[:, 0].max() + 1
y_min, y_max = X_2d[:, 1].min() - 1, X_2d[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

# 预测网格点
Z = log_reg_2d.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 绘制决策边界和数据点
plt.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.RdYlBu)
plt.contour(xx, yy, Z, colors='black', linewidths=0.5, alpha=0.5)

scatter = plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y_2d, cmap=plt.cm.RdYlBu, 
                     edgecolors='black', s=50)

plt.xlabel('特征1')
plt.ylabel('特征2')
plt.title('逻辑回归的决策边界\n(不同颜色代表不同类别,黑色线条是决策边界)')
plt.colorbar(scatter)
plt.grid(True, alpha=0.3)
plt.show()

print("决策边界方程:")
print(f"{log_reg_2d.coef_[0][0]:.3f} × 特征1 + {log_reg_2d.coef_[0][1]:.3f} × 特征2 + {log_reg_2d.intercept_[0]:.3f} = 0")

⚠️ 逻辑回归的局限性

1️⃣ 只能处理线性可分问题

# 线性不可分的例子:螺旋数据
from sklearn.datasets import make_moons

X_moons, y_moons = make_moons(n_samples=200, noise=0.1, random_state=42)

# 训练逻辑回归
log_reg_moons = LogisticRegression()
log_reg_moons.fit(X_moons, y_moons)

# 可视化
plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.scatter(X_moons[:, 0], X_moons[:, 1], c=y_moons, cmap=plt.cm.RdYlBu, 
           edgecolors='black', s=30)
plt.title('螺旋数据(线性不可分)')

# 决策边界
x_min, x_max = X_moons[:, 0].min() - 0.5, X_moons[:, 0].max() + 0.5
y_min, y_max = X_moons[:, 1].min() - 0.5, X_moons[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

Z = log_reg_moons.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.RdYlBu)
plt.contour(xx, yy, Z, colors='black', linewidths=0.5)
plt.title('逻辑回归决策边界(无法很好分割螺旋数据)')

plt.tight_layout()
plt.show()

moons_accuracy = accuracy_score(y_moons, log_reg_moons.predict(X_moons))
print(f"螺旋数据分类准确率: {moons_accuracy:.3f} (不理想!)")

2️⃣ 概率校准问题

逻辑回归给出的概率有时不够准确,特别是在数据分布不均匀时。

🤖 逻辑回归的实际应用

📧 垃圾邮件分类器

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline

# 模拟邮件数据
emails = [
    "恭喜您中奖了!点击链接领取100万大奖",
    "明天上午10点开会,请准时参加",
    "限时优惠!全场商品5折,仅此一天",
    "项目进度报告已完成,请查收附件",
    "免费领取iPhone!无需付款直接邮寄",
    "会议纪要:讨论了Q4的销售策略"
]

labels = [1, 0, 1, 0, 1, 0]  # 1=垃圾邮件,0=正常邮件

# 创建文本分类管道
spam_pipeline = Pipeline([
    ('tfidf', TfidfVectorizer()),  # 文本转换为数字特征
    ('classifier', LogisticRegression())  # 逻辑回归分类器
])

spam_pipeline.fit(emails, labels)

# 测试新邮件
test_emails = [
    "周末聚餐,地点在公司楼下餐厅",
    "紧急通知:您的账户异常,立即点击验证",
    "周报已发送,请各部门负责人查阅"
]

predictions = spam_pipeline.predict(test_emails)
probabilities = spam_pipeline.predict_proba(test_emails)

print("垃圾邮件分类结果:")
for email, pred, prob in zip(test_emails, predictions, probabilities):
    category = "垃圾邮件" if pred == 1 else "正常邮件"
    confidence = max(prob) * 100
    print(f"邮件: {email}")
    print(f"分类: {category} (置信度: {confidence:.1f}%)")
    print("-" * 50)

📝 本篇小结

  1. 逻辑回归解决分类问题​:预测类别而不是连续数值
  2. Sigmoid函数是核心​:把线性输出压缩到0-1之间变成概率
  3. 决策边界​:当线性组合等于0时,概率为0.5,这是分类的分界线
  4. 交叉熵损失​:专门为分类问题设计的损失函数
  5. 概率输出​:不仅告诉类别,还告诉把握有多大
  6. 可解释性强​:权重告诉我们每个特征的重要性
  7. 局限性​:只能处理线性可分问题,复杂模式需要神经网络

🎯 练习题

  1. 手推公式​:给定数据点 (1,0), (2,0), (3,1), (4,1),试着手工推导逻辑回归的参数
  2. 实际应用​:收集一些电影评论,用逻辑回归判断评论是正面还是负面
  3. 阈值调整​:在医疗诊断中,我们可能希望更严格的标准(比如P>0.8才算患病),试试调整决策阈值
  4. 特征工程​:为逻辑回归创造新的特征组合,看看能否提升分类效果

🔮 下一篇预告

第6篇:决策树——像做选择题一样做预测

逻辑回归虽然强大,但它假设特征和结果之间是线性关系。现实中很多决策过程更像是一连串的"如果...那么...":

  • 如果年龄>30岁 AND 收入>50万 → 高价值客户
  • 如果肿瘤大小>5cm OR 形状不规则 → 疑似恶性

决策树就是模仿人类这种做选择题的思维方式的算法!它将学习出一套"决策规则",让我们像剥洋葱一样一步步得到答案。

准备好学习AI如何像人类专家一样做决策了吗? 🌳

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值