1. 从西瓜数据到决策边界初识对率回归第一次翻开周志华老师的《机器学习》时我被对率回归这个名词搞得一头雾水。听起来像是要做对数运算的回归分析后来才发现这其实就是我们常说的逻辑回归Logistic Regression只不过教材采用了更严谨的学术命名。对率回归最神奇的地方在于它能把线性回归的输出压缩到0到1之间正好对应概率的概念。西瓜数据集3.0α是个绝佳的入门案例。这个数据集记录了17个西瓜样本的两个关键特征密度和含糖率以及它们是否是好瓜的标签。我刚开始学的时候总在想为什么不用简单的阈值判断呢比如含糖率高于0.3就是好瓜。但实际数据会打脸——编号15的西瓜含糖率0.37却被标记为坏瓜这说明单一特征判断会出错。对率回归的优势这时候就显现出来了。它通过Sigmoid函数将线性组合wTxb映射到(0,1)区间相当于用概率的方式表达分类结果。比如输出0.7表示有70%概率属于正类。这种处理方式既考虑了特征间的线性组合又保证了输出符合概率定义比简单阈值法靠谱多了。2. 数据准备与自定义DataLoader2.1 理解西瓜数据集的结构西瓜数据集3.0α虽然只有17条数据但结构非常清晰前两列是数值特征密度(0.243~0.774)、含糖率(0.0267~0.46)最后一列是分类标签1表示好瓜0表示坏瓜我建议先用pandas的describe()看看数据分布import pandas as pd data pd.read_csv(watermelon_3.0a.csv) print(data.describe())输出会显示两个特征的均值、标准差等信息。观察发现好瓜的平均含糖率(0.32)确实高于坏瓜(0.13)但存在交叉区域这就是需要用模型学习的地方。2.2 仿PyTorch实现DataLoader虽然可以直接用numpy数组但模仿PyTorch的DataLoader接口会让代码更规范。我实现的版本主要包含三个关键方法__init__读取CSV文件并提取特征矩阵和标签矩阵__len__返回数据集样本数__getitem__支持索引访问和迭代class WatermelonLoader: def __init__(self, data_path, x_cols, y_col): self.data pd.read_csv(data_path) self.x self.data[x_cols].values self.y self.data[y_col].values.reshape(-1,1) def __len__(self): return len(self.x) def __getitem__(self, idx): return self.x[idx], self.y[idx]使用时就像这样loader WatermelonLoader(watermelon_3.0a.csv, [密度,含糖率], 好瓜) for x, y in loader: print(f特征:{x}, 标签:{y})3. 对率回归模型的数学原理与实现3.1 Sigmoid函数与决策边界对率回归的核心是Sigmoid函数 σ(z) 1/(1e⁻ᶻ)这个S型曲线将任意实数映射到(0,1)区间。当zwTxb0时σ(z)0.5我们预测为正类反之预测为负类。决策边界就是wTxb0这个超平面。在西瓜数据集的二维情况下决策边界是一条直线 w₁·密度 w₂·含糖率 b 03.2 极大似然估计推导与线性回归用最小二乘法不同对率回归使用极大似然估计。对于单个样本其似然函数为 L(w,b) ŷʸ(1-ŷ)⁽¹⁻ʸ⁾ 其中ŷσ(wTxb)对所有样本取对数似然 ℓ(w,b) Σ[yⁱlog(ŷⁱ)(1-yⁱ)log(1-ŷⁱ)]我们的目标就是最大化这个对数似然函数。通过求导可以得到梯度 ∂ℓ/∂w Σ(yⁱ-ŷⁱ)xⁱ3.3 Python实现细节我实现的LogisticRegression类包含三个关键方法import numpy as np class LogisticRegression: def __init__(self, lr0.1): self.w None self.lr lr def sigmoid(self, z): return 1 / (1 np.exp(-z)) def fit(self, X, y, epochs100): # 添加偏置项 X np.c_[X, np.ones(X.shape[0])] self.w np.zeros(X.shape[1]) for _ in range(epochs): z np.dot(X, self.w) y_pred self.sigmoid(z) grad np.dot(X.T, (y - y_pred)) self.w self.lr * grad def predict(self, X): X np.c_[X, np.ones(X.shape[0])] return (self.sigmoid(np.dot(X, self.w)) 0.5).astype(int)注意几个关键点在特征矩阵X最后添加一列1相当于把偏置b并入权重向量使用向量化实现避免低效的循环学习率lr不宜过大否则容易震荡4. 模型训练与评估实战4.1 留一法交叉验证由于数据集只有17个样本我采用留一法(Leave-One-Out)进行验证from sklearn.metrics import accuracy_score def loo_validation(data, epochs100): accuracies [] for i in range(len(data)): train np.delete(data, i, axis0) test data[i:i1] model LogisticRegression() model.fit(train[:,:2], train[:,2], epochs) pred model.predict(test[:,:2]) accuracies.append(pred test[:,2]) return np.mean(accuracies) print(f留一法准确率{loo_validation(data.values):.2%})4.2 训练过程可视化观察权重变化能更好理解模型学习过程plt.figure(figsize(10,4)) for epoch in [10,50,100]: model LogisticRegression() model.fit(X, y, epochsepoch) # 绘制决策边界 x1 np.linspace(0.2,0.8,100) x2 -(model.w[0]*x1 model.w[2])/model.w[1] plt.plot(x1,x2, labelfepoch{epoch}) plt.scatter(X[y0,0],X[y0,1], cblue, label坏瓜) plt.scatter(X[y1,0],X[y1,1], cred, label好瓜) plt.legend()可以看到随着训练轮次增加决策边界逐渐移动到更合理的位置将多数样本正确分类。5. 决策边界与Sigmoid函数可视化5.1 绘制二维决策边界最终的决策边界可视化def plot_decision_boundary(model, X, y): # 创建网格点 x1_min, x1_max X[:,0].min()-0.1, X[:,0].max()0.1 x2_min, x2_max X[:,1].min()-0.1, X[:,1].max()0.1 xx1, xx2 np.meshgrid(np.linspace(x1_min,x1_max,100), np.linspace(x2_min,x2_max,100)) # 预测每个网格点 Z model.predict(np.c_[xx1.ravel(),xx2.ravel()]) Z Z.reshape(xx1.shape) # 绘制 plt.contourf(xx1,xx2,Z,alpha0.3) plt.scatter(X[y0,0],X[y0,1], cblue, label坏瓜) plt.scatter(X[y1,0],X[y1,1], cred, label好瓜) plt.xlabel(密度) plt.ylabel(含糖率)5.2 Sigmoid函数与样本分布理解Sigmoid如何将线性输出转为概率z np.linspace(-10,10,100) y 1/(1np.exp(-z)) plt.figure(figsize(10,4)) plt.subplot(121) plt.plot(z,y) plt.title(Sigmoid函数) # 绘制样本在Sigmoid曲线上的位置 z_samples X model.w[:-1] model.w[-1] y_samples 1/(1np.exp(-z_samples)) plt.subplot(122) plt.scatter(z_samples[y0], y_samples[y0], cblue) plt.scatter(z_samples[y1], y_samples[y1], cred) plt.plot(z,y,k--)右图显示好瓜样本(红色)大多位于Sigmoid曲线右侧(z0)而坏瓜样本(蓝色)多在左侧。这正是我们希望看到的分布。6. 完整代码实现与优化建议6.1 完整代码结构建议按以下结构组织代码/logistic_regression │── data/ │ └── watermelon_3.0a.csv │── utils.py # DataLoader等工具类 │── model.py # LogisticRegression实现 │── train.py # 训练与评估脚本 │── visualize.py # 可视化代码6.2 性能优化技巧在实际项目中我总结了几个优化点添加L2正则化防止过拟合def fit(self, X, y, epochs100, reg0.1): # 在梯度更新步骤添加 self.w self.lr * (grad - reg*self.w)使用随机梯度下降(SGD)加速收敛for epoch in range(epochs): idx np.random.permutation(len(X)) for i in idx: xi, yi X[i:i1], y[i:i1] # 计算单个样本梯度并更新添加早停机制(Early Stopping)best_loss float(inf) for epoch in range(epochs): # ...训练代码... current_loss compute_loss() if current_loss best_loss: break best_loss current_loss对率回归虽然简单但包含了许多机器学习的关键思想。通过这个西瓜数据集的实践我深刻理解了从数据准备、模型实现到评估可视化的完整流程。特别是在实现梯度下降时手动推导并验证梯度的正确性让我对反向传播有了更直观的认识。建议初学者一定要亲手实现一遍这比直接调用sklearn的LogisticRegression收获大得多。