保姆级教程:用Python手写逻辑回归,从零搞定西瓜书3.0α数据集分类
从零实现逻辑回归用Python解析西瓜数据集分类任务当翻开周志华教授的《机器学习》第三章许多初学者会被对率回归即逻辑回归的数学推导劝退。本文将以纯手工代码实现的方式带你用Python从零构建逻辑回归模型并用经典的西瓜数据集3.0α验证模型效果。不同于直接调用sklearn我们将深入算法核心亲手实现数据预处理与自定义DataLoaderSigmoid函数与梯度下降的数学实现留一法交叉验证策略决策边界可视化1. 理解逻辑回归与西瓜数据集逻辑回归虽然名字带回归实则是解决二分类问题的利器。它通过Sigmoid函数将线性回归结果映射到(0,1)区间表示样本属于正类的概率。西瓜数据集3.0α包含17个样本每个样本有密度和含糖率两个特征标签为好瓜(1)或坏瓜(0)。数据集关键特征分析统计量密度含糖率均值0.5180.191最大值0.7740.460最小值0.2430.026观察数据可发现好瓜普遍具有较高含糖率0.2但仅凭单一特征无法完美分类这正是逻辑回归的用武之地。2. 构建数据加载器我们先实现一个轻量级DataLoader模仿PyTorch的接口设计import numpy as np import pandas as pd class WatermelonLoader: def __init__(self, data_path, features, label): raw_data pd.read_csv(data_path) self.features raw_data[features].values self.labels raw_data[label].values.reshape(-1, 1) self._n_samples len(raw_data) def __len__(self): return self._n_samples def __getitem__(self, idx): return self.features[idx], self.labels[idx] def get_batch(self, batch_size): indices np.random.choice(self._n_samples, batch_size) return self.features[indices], self.labels[indices]使用示例loader WatermelonLoader(watermelon3.0.csv, [密度,含糖率], 好瓜) for x, y in loader: print(f特征:{x}, 标签:{y})3. 实现逻辑回归核心逻辑回归的核心在于Sigmoid函数和梯度计算。我们创建一个LogisticRegression类class LogisticRegression: def __init__(self, lr0.01, n_iter1000): self.lr lr # 学习率 self.n_iter n_iter # 迭代次数 self.weights None # 模型参数 self.bias None # 偏置项 def _sigmoid(self, z): return 1 / (1 np.exp(-z)) def fit(self, X, y): n_samples, n_features X.shape self.weights np.zeros(n_features) self.bias 0 # 梯度下降 for _ in range(self.n_iter): linear np.dot(X, self.weights) self.bias predictions self._sigmoid(linear) # 计算梯度 dw (1/n_samples) * np.dot(X.T, (predictions - y).flatten()) db (1/n_samples) * np.sum(predictions - y) # 参数更新 self.weights - self.lr * dw self.bias - self.lr * db def predict(self, X, threshold0.5): linear np.dot(X, self.weights) self.bias y_pred self._sigmoid(linear) return (y_pred threshold).astype(int)关键点解析Sigmoid函数将线性输出压缩到(0,1)使用交叉熵损失函数的梯度下降预测时默认以0.5为分类阈值4. 模型训练与评估由于西瓜数据集样本较少我们采用留一法交叉验证def leave_one_out_eval(data_path): raw_data pd.read_csv(data_path) features [密度, 含糖率] label 好瓜 correct 0 total len(raw_data) for i in range(total): # 留一法分割 test_data raw_data.iloc[i:i1] train_data raw_data.drop(i) # 数据准备 X_train train_data[features].values y_train train_data[label].values.reshape(-1, 1) X_test test_data[features].values y_test test_data[label].values # 训练模型 model LogisticRegression(lr0.1, n_iter1000) model.fit(X_train, y_train) # 预测验证 pred model.predict(X_test) if pred y_test: correct 1 print(f留一法准确率: {correct/total:.2%})运行结果示例留一法准确率: 82.35%5. 决策边界可视化理解模型如何做出决策至关重要我们绘制决策边界import matplotlib.pyplot as plt def plot_decision_boundary(model, loader): # 创建网格点 x_min, x_max loader.features[:,0].min()-0.1, loader.features[:,0].max()0.1 y_min, y_max loader.features[:,1].min()-0.1, loader.features[:,1].max()0.1 xx, yy np.meshgrid(np.linspace(x_min, x_max, 100), np.linspace(y_min, y_max, 100)) # 预测网格点 Z model.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) # 绘制 plt.contourf(xx, yy, Z, alpha0.4) plt.scatter(loader.features[:,0], loader.features[:,1], cloader.labels.flatten(), s20, edgecolork) plt.xlabel(密度) plt.ylabel(含糖率) plt.title(逻辑回归决策边界)可视化解读背景色表示模型预测的区域散点表示真实样本分布边界线即为决策边界概率0.5的位置6. 常见问题与调试技巧在实际实现过程中可能会遇到以下典型问题问题1梯度爆炸/消失现象损失值剧烈波动或长期不下降解决调整学习率通常尝试0.01, 0.1, 0.3等问题2特征尺度不一致现象密度值范围(0.2-0.7)与含糖率(0.02-0.4)差异大解决添加特征标准化from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)问题3类别不平衡检查查看正负样本比例print(f正样本比例: {np.mean(y_train):.1%})7. 进阶优化方向基础实现后可以考虑以下改进正则化添加L1/L2正则项防止过拟合# 在fit方法中添加 self.weights - self.lr * (dw lambda * np.sign(self.weights)) # L1 self.weights - self.lr * (dw 2 * lambda * self.weights) # L2优化器升级实现动量法或Adam# 动量法示例 velocity 0.9 * velocity self.lr * dw self.weights - velocity多分类扩展改造为softmax回归def _softmax(self, z): exp np.exp(z - np.max(z)) return exp / exp.sum(axis1, keepdimsTrue)完整项目代码已托管在GitHub包含详细注释和Jupyter Notebook示例。在实际项目中当数据量较大时建议使用scikit-learn的优化实现但这次手写实践让我们真正理解了逻辑回归的每一个计算细节。