对数几率回归实战:基于西瓜与鸢尾花数据的手写实现
简介基于对数几率回归模型实现西瓜与鸢尾花识别分类的Python源码包面向高校计算机、数据科学、人工智能等专业需要完成课程设计或期末大作业的学生也适合教师作为教学案例参考。压缩包共计30个文件包含6个py源码、4个ipynb交互式笔记、2个doc作业报告及markdown说明等其中py脚本和笔记均附有详细注释完整覆盖从模型训练到分类预测的实现流程整体仅544KB便于下载与二次开发。资源中的作业报告对对数几率回归原理和实验过程做了梳理可直接对照源码理解关键逻辑源码结构清晰稍作调整即可迁移到其他分类任务。该资源目前已有295人学习下载代码经验证可稳定运行能够作为课程作业提交或项目立项演示的基础版本适合快速上手并在此基础上扩展。1. 期末大作业选对数几率回归为什么“西瓜鸢尾花”这个组合容易拿高分很多同学的期末大作业要求交一份“模型 python源码 作业报告 详细注释”的压缩包。选题时容易被深度学习的名字吸引结果训练时间失控、报告里讲不清原理。对数几率回归模型在这类课程设计里属于“稳稳的幸福”鸢尾花数据集上轻松达到95%以上准确率西瓜数据集也能画出清晰的决策边界整个 python 源码能控制在两三百行内每一行都可以在报告里解释清楚。它解决的是“用可解释的方式做分类识别”的问题适合需要快速出活、又想让评分老师相信你真的调通了模型的学生。这篇文章从数学原理讲到两个数据集的预处理、手写实现最后给你一套作业报告里能直接用的验证方法。2. 对数几率回归的数学底细与代码映射从 sigmoid 到梯度推导一次讲清2.1 从线性回归到对数几率决策边界是一条直线的原因线性回归的输出 z w^T x b 落在整个实数轴上而分类问题的输出需要落在 [0, 1] 区间里表示概率。对数几率回归做了一件很简单的事把 z 塞进 sigmoid 函数得到 p 1 / (1 e^{-z})。当 z 趋向正无穷时 p 趋向 1当 z 趋向负无穷时 p 趋向 0。这样模型的输出天然具备概率语义。“对数几率”这个名字不是白叫的。把 p 换算成几率oddsp / (1 - p)再取自然对数会得到一个漂亮的结果ln(p / (1 - p)) z。也就是说特征与“对数几率”之间是线性关系。这个性质让模型系数 w 变得可解释某个特征增加一个单位对数几率就增加对应系数的大小方向由系数正负决定。评分老师如果在报告里看到你写清楚这一层比单纯贴一行“from sklearn.linear_model import LogisticRegression”要加分得多。决策边界也藏在这个公式里。当模型预测 p 0.5 时判为正类而 p 0.5 对应 z 0也就是 w^T x b 0。这个方程在二维特征空间里是一条直线在更高维空间里是一个超平面。所以对数几率回归本质上是在找一条能把两类数据分开的线性边界。西瓜数据集里的密度和含糖率两个连续特征正好可以用二维散点图直接验证这条直线这也是为什么这个课题适合做期末大作业——数据和模型之间能互相印证。2.2 损失函数为什么是交叉熵梯度形式才是关键抄线性回归的均方误差MSE到对数几率回归里会吃大亏。把 sigmoid 复合进 MSE 之后损失函数不再是凸函数梯度下降很可能停在局部极小点而且梯度表达式中会出现 p(1-p) 这样的因子靠近边界时梯度消失收敛极慢。交叉熵损失 L -[y ln p (1-y) ln(1-p)] 是对数似然函数取负的结果整体是凸函数对参数求导得到的梯度形式异常简洁。从 z 到 p 的链式求导结果是 ∂L/∂z p - y再乘以 ∂z/∂w x得到 ∂L/∂w (p - y) x。这个形式跟线性回归的残差乘以特征如出一辙只是这里的“残差”换成了预测概率与真实标签的差。手写实现时核心代码只需要一行矩阵乘法就能同时算出所有样本的梯度贡献这也是我强烈建议你手写一遍模型的原因——你会直观看到梯度下降到底在“下降”什么而不是把 sklearn 当黑匣子。作业报告里还可以补一句由于交叉熵来自最大似然估计最大化似然等价于最小化交叉熵所以模型给出的概率可以被理解为“在参数下数据的合理程度”。这段话不需要很长但能说明你理解损失函数的来源而不是只会调包。2.3 阈值 0.5 与多分类OvR 和 softmax 怎么选默认情况下 p 0.5 判为正类这在类别均衡时没问题。但西瓜数据集里的正负样本比例并不均衡常见版本里好瓜只有 6 个、坏瓜 11 个此时 0.5 这个阈值会让模型偏向把样本判成数量多的那一类。可以尝试把阈值降到 0.4 或者升到 0.6比较准确率、精确率和召回率的变化。报告里写“我尝试了不同阈值最终选择在验证集上 F1 分数最高的阈值”这是只有真正调过模型的人才会写出来的细节。鸢尾花数据集则是三分类问题二分类的 sigmoid 输出只能回答“是不是某个类”。常见做法有两个一是 OvRone-vs-rest训练三个二分类器分别判断“是不是 setosa”“是不是 versicolor”“是不是 virginica”预测时取概率最大的那个二是直接改成 softmax 回归让输出层变成三个概率加起来等于 1。课程进度只教到二分类 logit 时报告里写 OvR 比较稳妥如果课程里提过 softmax直接上 softmax 会更完整。后面的第 4 章会给二分类核心实现第 6 章说清楚改成 softmax 需要动哪些代码。2.4 sigmoid 数值稳定性指数溢出与 numpy 处理教科书里写的 p 1 / (1 np.exp(-z)) 在真实数据上会翻车。当 z 很大时np.exp(-z) 下溢成 0p 算出来是 1当 z 很小时np.exp(-z) 直接溢出p 变成 nan。手工实现时我一般会用分段函数避免这个坑z 0 时算 1 / (1 np.exp(-z))z 0 时算 np.exp(z) / (1 np.exp(z))。两个分支都不会产生指数爆炸。def sigmoid(z): # z 0 走第一分支避免 exp(-z) 发散 # z 0 走第二分支避免 exp(z) 溢出 return np.where(z 0, 1 / (1 np.exp(-z)), np.exp(z) / (1 np.exp(z)))逻辑说明np.where 会先计算两个分支的所有结果再进行选择但好在 z 是 numpy 数组时逐元素判断分支本身的计算量可以接受如果在循环里逐样本调用建议用 Python 的 if 判断。这个分段函数对后面的损失函数计算很关键否则交叉熵里的 log(p) 遇到 p 等于 0 会得到 -inf让 loss 变成 nan。参数说明z 是线性输出 w^T x b形状是 (n_samples,)函数返回形状相同的概率数组。这段代码放进作业报告的“模型实现”小节里配合一句“为了让模型在极端输入下保持数值稳定我对 sigmoid 做了分段处理”属于典型的加分注释。3. 西瓜与鸢尾花数据预处理CSV 读取、编码映射与数据划分的完整流程3.1 鸢尾花数据集pandas 读 CSV、标准化与数据划分鸢尾花数据集有 150 条样本、4 个数值特征和 3 个类别是分类入门最常用的数据。用 pandas 读进内存后先取出四个特征列和标签列。做二分类演示时可以先只保留 setosa 和 versicolor 两类这样标签只有 0 和 1方便把注意力集中在模型本身。完整三分类的扩展留在第 6 章讨论。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler df pd.read_csv(iris.csv) X df.iloc[:, :4].values.astype(float) y_raw df.iloc[:, 4].values # 二分类演示只保留前两类 mask (y_raw setosa) | (y_raw versicolor) X, y_raw X[mask], y_raw[mask] y np.where(y_raw setosa, 1, 0) # stratify 保证训练/测试类别比例一致random_state 固定随机种子 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train scaler.fit_transform(X_train) # 只用训练集 fit X_test scaler.transform(X_test) # 测试集只做 transform逻辑说明df.iloc[:, :4] 取前四列这是花萼长、花萼宽、花瓣长、花瓣宽四个特征。np.where 把标签字符串转成数值setosa 记为 1、versicolor 记为 0。train_test_split 里的 stratifyy 按类别比例拆分避免某一类全部跑进训练集random_state42 固定随机种子保证每次运行结果一致这也是作业报告里要写明的实验条件。标准化先 fit 训练集再 transform 测试集是铁律fit_transform 用在合并后的全量数据上属于数据泄漏第 5 章会专门讲。参数说明test_size0.2 表示留出 20% 做测试二分类后训练集 80 条、测试集 20 条。StandardScaler 把每个特征变换成均值约 0、标准差约 1梯度下降在这种尺度下收敛速度会快很多。如果你希望报告里展示“标准化前 vs 标准化后”的收敛曲线对比可以在 fit 前先用原始特征跑一遍loss 曲线通常会出现明显的震荡这个对比图很能说明问题。3.2 西瓜数据集中文离散特征的字典编码与连续特征处理西瓜数据集是周志华《机器学习》教材里的经典数据常见版本有 17 条样本特征是 6 个离散属性加 2 个连续属性密度、含糖率。用 pandas 直接读 CSV 时中文列名要指定编码常见是 gbk 或 utf-8。离散属性需要手动映射成数值因为对数几率回归的输入必须是数值矩阵。watermelon pd.read_csv(watermelon.csv, encodinggbk) color_map {青绿: 0, 乌黑: 1, 浅白: 2} root_map {蜷缩: 0, 稍蜷: 1, 硬挺: 2} sound_map {浊响: 0, 沉闷: 1, 清脆: 2} texture_map {清晰: 0, 稍糊: 1, 模糊: 2} navel_map {平坦: 0, 稍凹: 1, 凹陷: 2} touch_map {硬滑: 0, 软粘: 1} for col, mapping in zip( [色泽, 根蒂, 敲声, 纹理, 脐部, 触感], [color_map, root_map, sound_map, texture_map, navel_map, touch_map] ): watermelon[col] watermelon[col].map(mapping) features [色泽, 根蒂, 敲声, 纹理, 脐部, 触感, 密度, 含糖率] X_water watermelon[features].values.astype(float) y_water np.where(watermelon[好瓜] 是, 1, 0)逻辑说明字典 map 把每个离散特征的中文取值替换成整数编码。zip 循环同时遍历列名和映射字典比手写六行赋值更清晰也不容易漏列。“密度”和“含糖率”本身是浮点数值直接保留。最后把训练矩阵转成 float标签“是”映射为 1、“否”映射为 0。参数说明离散编码的顺序会影响特征值与标签的相关方向但对线性模型来说只是系数符号和大小变化。更严谨的做法是独热编码one-hot但 17 条样本做独热会引入很多列对小数据集反而容易过拟合。报告里可以写一句“本实验采用数值编码简化处理实际工程中建议对无序离散特征使用独热编码”评分老师会认可这个边界意识。如果 CSV 里有缺失值可以先检查watermelon.isna().sum()然后用均值填充连续特征、用众数填充离散特征注意填充必须在划分训练测试之后再做。3.3 划分前必查三件事标签分布、缺失值与类别均衡模型代码写得再漂亮数据没检查照样出问题。每次跑训练之前我会先打印三组信息标签分布、缺失值情况、特征数值范围。不要小看这一步很多“模型预测全输出一类”的翻车就是从这里开始的。print(训练集标签分布:, np.bincount(y_train)) print(测试集标签分布:, np.bincount(y_test)) print(训练集是否存在 NaN:, np.isnan(X_train).any()) print(特征均值:, X_train.mean(axis0)) print(特征标准差:, X_train.std(axis0))逻辑说明np.bincount 统计 0 和 1 的样本数量如果某一类数量为 0说明划分失败或原始数据就有问题。NaN 检查要放在标准化之前否则 nan 会顺着均值计算传播到整个矩阵。特征均值接近 0、标准差接近 1说明标准化生效如果标准差出现几百的值说明某个特征没转成 float 或者类别编码混入了字符串。参数说明西瓜数据集只有 17 条样本此时 test_size0.2 会划分出 3 到 4 条测试样本类别比例很容易失衡。如果某一类在测试集里只有 1 条甚至 0 条stratify 会直接报错。遇到这种情况我一般会把 test_size 调到 0.25 但手工控制每类的测试数量或者干脆只留 4 条样本做测试其余 13 条都去训练。期末作业里数据集小不是问题但要在报告里写清楚“由于数据规模有限测试结果仅供参考”这反而能体现你对模型局限性的理解。4. 手写对数几率回归的 Python 源码训练循环、loss 曲线与决策边界4.1 完整模型类初始化、前向、梯度与训练循环手写模型的意义在于当你把梯度公式一行行敲进代码时才发现自己是不是真的理解推导。下面这个类是期末大作业常见做法只依赖 numpy不调用任何机器学习库。import numpy as np class LogisticRegression: 手写对数几率回归批量梯度下降实现 def __init__(self, lr0.01, epochs1000, tol1e-4): self.lr lr # 学习率控制每步更新幅度 self.epochs epochs # 最大迭代轮数 self.tol tol # 提前停止阈值loss 变化小于它则停止 self.w None self.b None self.loss_history [] def _sigmoid(self, z): # 分段计算 sigmoid避免指数溢出 return np.where(z 0, 1 / (1 np.exp(-z)), np.exp(z) / (1 np.exp(z))) def _loss(self, y, p): # 交叉熵损失clip 防止 log(0) p np.clip(p, 1e-12, 1 - 1e-12) return -np.mean(y * np.log(p) (1 - y) * np.log(1 - p)) def fit(self, X, y): n_samples, n_features X.shape self.w np.zeros(n_features) self.b 0.0 for epoch in range(self.epochs): z X self.w self.b # 线性输出 p self._sigmoid(z) # 概率预测 dw (1 / n_samples) * (X.T (p - y)) # (p-y)x 的矩阵形式 db (1 / n_samples) * np.sum(p - y) self.w - self.lr * dw self.b - self.lr * db loss self._loss(y, p) self.loss_history.append(loss) if epoch 0 and abs(self.loss_history[-2] - loss) self.tol: print(fepoch {epoch}: loss 变化小于 {self.tol}提前停止) break if epoch % 100 0: print(fepoch {epoch}, loss {loss:.6f}) def predict_proba(self, X): return self._sigmoid(X self.w self.b) def predict(self, X, threshold0.5): return (self.predict_proba(X) threshold).astype(int)逻辑说明fit 方法里最关键的一行是dw (1 / n_samples) * (X.T (p - y))。p - y是每个样本的预测概率与真实标签之差形状为 (n_samples,)X.T 是特征的转置矩阵相乘后得到每个特征的梯度分量对应公式 ∂L/∂w (p - y)x。b 的梯度是残差求和再除以样本数。参数更新就是沿负梯度方向走一步学习率 lr 控制步长。loss_history 把每一轮的损失都记下来后面画曲线直接取用。参数说明lr0.01 适合标准化后的数据如果特征没标准化这个学习率很容易让 loss 变成 nan。epochs1000 对这两个数据集来说绰绰有余通常两三百轮就收敛。tol1e-4 是提前停止条件当 loss 变化小于该值说明已经收敛继续迭代只会浪费时间。threshold 参数放在 predict 里而不是写死 0.5方便后面做阈值搜索。w 初始化为全零在标准化数据上没问题但如果你发现 loss 曲线长时间不动可以改成用 np.random.randn(n_features) * 0.01 做随机初始化。4.2 loss 曲线与决策边界可视化报告里最有说服力的两张图训练完第一件事不是算准确率而是画 loss 曲线。一条平滑下降的曲线能证明梯度下降在正常工作一条上下震荡的曲线说明学习率太大一条根本不降的曲线说明代码里有 bug。这张图放进作业报告里比任何文字都能说明“我真的训练了一个模型”。import matplotlib.pyplot as plt plt.plot(range(len(model.loss_history)), model.loss_history) plt.xlabel(epoch) plt.ylabel(loss) plt.title(训练损失曲线) plt.grid(True) plt.show()决策边界图更直观。取两个特征做坐标轴在特征范围内铺满网格点让模型对每个网格点预测概率再用等高线填充颜色。数据点按真实标签着色后能看到边界是否正确把两类分开。def plot_decision_boundary(model, X, y, feat_idx(0, 1), feat_namesNone): x_min, x_max X[:, feat_idx[0]].min() - 0.5, X[:, feat_idx[0]].max() 0.5 y_min, y_max X[:, feat_idx[1]].min() - 0.5, X[:, feat_idx[1]].max() 0.5 xx, yy np.meshgrid(np.linspace(x_min, x_max, 300), np.linspace(y_min, y_max, 300)) # 这里假设模型只用了两个特征或用均值填充其余特征 grid np.c_[xx.ravel(), yy.ravel()] probs model.predict_proba(grid).reshape(xx.shape) plt.contourf(xx, yy, probs, levels20, cmapRdBu, alpha0.6) plt.scatter(X[:, feat_idx[0]], X[:, feat_idx[1]], cy, edgecolorsk, cmapRdBu) plt.xlabel(feat_names[0] if feat_names else feature 0) plt.ylabel(feat_names[1] if feat_names else feature 1) plt.title(决策边界与概率等高线) plt.show()逻辑说明meshgrid 在特征范围内生成 300x300 的网格点np.c_ 把它们拼成两列矩阵喂给 predict_proba 得到每个点的正类概率再 reshape 成网格形状contourf 填充颜色区域。概率接近 1 的区域是红色系接近 0 是蓝色系中间渐变部分就是决策边界的“模糊带”。scatter 用真实标签颜色覆盖在概率图上可以肉眼检验边界和样本是否吻合。参数说明feat_idx(0,1) 表示选择第 0 和第 1 个特征画图。如果模型用了全部四个特征网格只有两列直接喂给模型会维度不匹配。常见做法是固定另外两个特征为训练集均值然后只变化画图用的两个特征。在报告里用鸢尾花的 petal length 和 petal width 做坐标轴边界的线性特征会展示得非常清楚因为这两个特征本身区分度就高。4.3 从二分类扩展到三分类softmax 改写核心代码如果作业要求直接对鸢尾花三分类二分类的 sigmoid 不够用。这时改成 softmax 回归输出层的 z 从标量变成长度为 3 的向量softmax 把向量转换成三个类别的概率分布损失函数依然是交叉熵只是从二分类形式变成多分类形式。class SoftmaxRegression: def __init__(self, lr0.01, epochs1000): self.lr lr self.epochs epochs self.W None self.b None def _softmax(self, z): # z 形状 (n_samples, n_classes) z z - np.max(z, axis1, keepdimsTrue) # 减最大值防止 exp 溢出 exp_z np.exp(z) return exp_z / np.sum(exp_z, axis1, keepdimsTrue) def fit(self, X, y_onehot): n_samples, n_features X.shape n_classes y_onehot.shape[1] self.W np.zeros((n_features, n_classes)) self.b np.zeros(n_classes) for epoch in range(self.epochs): z X self.W self.b p self._softmax(z) grad_W (1 / n_samples) * (X.T (p - y_onehot)) grad_b (1 / n_samples) * np.sum(p - y_onehot, axis0) self.W - self.lr * grad_W self.b - self.lr * grad_b def predict(self, X): return np.argmax(self._softmax(X self.W self.b), axis1)逻辑说明多分类的梯度公式和二分类几乎一致只是把残差从 (p - y) 变成了 (p - y_onehot)其中 y_onehot 是独热编码后的标签矩阵形状 (n_samples, n_classes)。softmax 里先减去每行最大值是为了避免指数运算溢出这是多分类实现里最容易忘的一步。预测时取概率最大的类别索引作为输出。参数说明y_onehot 可以用np.eye(3)[y_index]生成。三分类鸢尾花直接用 petal 两个特征也能达到 95% 左右准确率四个特征全用则更高。报告里可以对比二分类 OvR 和 softmax 两种方案的准确率差异这个对比实验本身就是很好的素材。5. 期末大作业常见翻车点排查数据泄漏、梯度爆炸与乱码的 5 个真实案例5.1 数据泄漏测试集标准化泄露导致准确率虚高现象是模型在测试集上准确率高达 98%但换一批新数据就明显变差作业报告的测试结果完全没有参考价值。原因出在预处理顺序很多人用scaler.fit_transform(X)处理完整数据集再划分训练测试集这样标准化时已经“看过”测试集的均值和方差测试集信息提前泄漏进训练过程。解决方法是先 train_test_split再对训练集 fit_transform、对测试集只用 transform。这个坑在期末作业里几乎每年都有人踩老师的提问也常从“你这准确率怎么比 sklearn 还高”开始。检查方法也很简单打印标准化后的测试集均值和标准差如果测试集均值严格为 0、标准差严格为 1说明你很可能在全量数据上 fit 了。正确流程下测试集均值应该接近 0 但不完全等于 0因为它的统计量来自训练集。5.2 梯度爆炸loss 变成 nan 的排查路径现象是训练几轮后 loss 变成 nan或者第一次迭代就直接 nan。原因通常是特征尺度差异太大比如西瓜数据里含糖率在 0.05 到 0.5 之间密度在 0.3 到 0.9 之间如果某个特征被错误地保留成几千的量级梯度数值就会爆炸。另一个常见原因是学习率设得过大步长直接越过山谷。解决路径分三步先检查特征矩阵里有没有 inf 或 nan再用 StandardScaler 标准化最后把学习率降到 0.001 重新试。如果标准化后依然 nan在 sigmoid 和损失函数里打印每一轮的 p 值范围看是不是某一步 p 被算成 0 导致 log(0)。我在实现损失函数时用 np.clip(p, 1e-12, 1 - 1e-12) 兜底这也是作业注释里值得写的细节。5.3 预测全部输出同一类标签编码踩坑现象是模型训练完对测试集所有样本都预测成同一类准确率还停留在 50% 左右。一个典型的低级错误是标签编码用了 1 和 2而不是 0 和 1。sigmoid 的输出范围是 [0, 1]阈值 0.5 相当于把概率大于一半判为类别 1。如果真实标签是 2模型怎么算都不可能输出 2于是全部样本都被判为 0。解决办法是训练前检查np.unique(y)确认只有 0 和 1。另外类别不均衡也会导致模型“偷懒”——把多数类概率整体抬高少数类很难越过阈值。此时不只是改标签还要考虑调整分类阈值或者用 class_weight 给少数类加权。报告里可以写清楚“我检查了标签分布与编码并针对类别不均衡调整了阈值”这个表述比单纯写准确率更有说服力。5.4 训练集准确率高但测试集低过拟合的边界现象是训练集准确率 99%测试集却只有 70%报告里只写训练集指标会直接被老师问住。对数几率回归是线性模型本身不容易过拟合但数据量只有 17 条的西瓜数据集很容易出现这种情况模型把训练样本的噪声也当成了规律。另一个原因是迭代轮数过多损失函数在训练集上无限逼近最优但泛化能力并没有提升。解决方法是先看 loss 曲线是否在测试集上先降后升如果是就提前停止或者给损失函数加上 L2 正则化在梯度更新里加一项self.w * 0.001。报告里应该同时列出训练集与测试集准确率并且主动解释差距原因比如“西瓜数据仅 17 条测试评估波动较大建议结合留一法交叉验证看待结果”。这个解释能让评分老师知道你对模型边界有认知。5.5 matplotlib 中文乱码字体设置与三个备选方案现象是图的标题、坐标轴标签全部显示成方块。原因是 matplotlib 默认字体不含中文字符尤其在 Linux 环境。解决方法是开头设置plt.rcParams[font.sans-serif] [SimHei]同时设置plt.rcParams[axes.unicode_minus] False避免负号异常。Windows 下 SimHei 通常可用Mac 下可以换[Arial Unicode MS]Linux 下需要安装 fonts-noto-cjk 后使用[Noto Sans CJK SC]。还有一个备选方案把图表里所有文案改成英文避免字体依赖但中文作业报告里英文图会显得不统一。我一般建议直接在报告里用中文图并且把那行字体设置写进源码注释里标注“Windows 与 Mac 可能需要替换字体名”。这种边角细节在评分时很加分因为它说明你真的跑通过了而不是只改了几个参数就交差。6. 作业报告加分验证混淆矩阵、sklearn 对照与多分类扩展6.1 用混淆矩阵佐证二分类结果准确率只能告诉读者“对了多少”混淆矩阵能告诉读者“错在哪里”。用 sklearn 的 confusion_matrix 和 classification_report可以直接得到精确率、召回率、F1 分数。哪个类别被误判、误判成什么类别在矩阵里一目了然。from sklearn.metrics import confusion_matrix, classification_report y_pred model.predict(X_test) cm confusion_matrix(y_test, y_pred) print(混淆矩阵:\n, cm) print(classification_report(y_test, y_pred, target_names[versicolor, setosa]))6.2 与 sklearn 官方实现做结果对照手写模型的准确率与 sklearn 自带的 LogisticRegression 做对照是验证梯度实现是否正确的最快方式。注意 sklearn 默认带 L2 正则手写模型没有所以结果会有微小差异用penaltynone可以对齐。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score clf LogisticRegression(max_iter1000, penaltynone) clf.fit(X_train, y_train) print(sklearn 准确率:, accuracy_score(y_test, clf.predict(X_test))) print(手写模型准确率:, accuracy_score(y_test, model.predict(X_test)))如果两者差距在 1% 以内说明梯度实现正确如果手写模型显著更差优先检查数据标准化是否一致、随机种子是否相同。这个对照实验写进报告等于用第三方实现给手写代码做了一次“体检”。6.3 报告结论怎么写把实验现象翻译成评分点我每次做这类期末大作业最深的教训是先画 loss 曲线再谈准确率。你自己能解释每一张图的含义才敢写进报告。结论部分不要只写“准确率达到 95%”而是写清楚在哪个数据集、用了哪两个特征、阈值多少、训练测试如何划分。如果做了 softmax 扩展对比二分类和三分的结果。最后写一句“对数几率回归在鸢尾花和西瓜数据集上均以极小的训练代价取得了可解释的分类效果适合作为小样本线性分类任务的基线模型”。希望帮到你。本文还有配套的精品资源点击获取