☰
期末大作业实战:对数几率回归实现西瓜与鸢尾花分类
2026/10/18 23:24:46 网站建设 项目流程

简介:这份资源是面向计算机、人工智能、数据科学等专业在校学生的期末大作业与课程设计参考包,围绕对数几率回归模型完成西瓜与鸢尾花数据集的识别分类任务。压缩包共30个文件,约544KB,包含6个Python源码文件、4个Jupyter Notebook、2份Word作业报告及2份Markdown说明,另有xml、iml等IDE配置与pyc缓存文件,覆盖从模型实现到报告撰写的完整流程。源码附带详细注释,便于理解对数几率回归的数学原理与代码实现,报告文档可直接参考写作思路。目前已有296人学习下载,适合作为入门进阶练习、课程设计提交或初期项目立项演示,也鼓励在此基础上进行二次开发与拓展实验。

1. 期末大作业选对数几率回归做西瓜鸢尾花分类:为什么它比你想的更能打

期末大作业选题,很多人第一反应是上神经网络,觉得模型越深越有面子。但如果你带过几届学生就会发现,真正能在两周内跑通、报告写满、答辩不被问穿的方案,往往是最朴素的那个。对数几率回归(Logistic Regression)就是这样一个被低估的选手——它名字里带“回归”,干的却是分类的活,而且干得相当稳。西瓜数据集二分类、鸢尾花数据集三分类,这两个经典任务用它做基线,准确率能轻松上到 95% 以上,代码量却不到一百行。更关键的是,它的数学推导清晰、参数可解释、训练秒级完成,特别适合作为大作业的“骨架模型”。这篇文章面向正在赶期末大作业的本科生和刚入门机器学习的开发者,把从数据加载、模型推导、代码实现到报告撰写的完整路径拆开讲,让你不仅能交差,还能在答辩时讲出所以然。

2. 对数几率回归做分类:从西瓜的“好瓜概率”到鸢尾花的软分类

2.1 为什么叫“回归”却用来分类

对数几率回归的核心思路是:先算一个线性得分,再用 Sigmoid 函数把它压到 0 到 1 之间,当作样本属于正类的概率。西瓜数据集里,我们关心的是“这个瓜是好瓜的概率是多少”。线性得分 z = w·x + b 可以取任意实数,Sigmoid 把它映射成概率:

import numpy as np def sigmoid(z): # 将线性得分压缩到 (0, 1) 区间,作为概率输出 # 当 z 很大时趋近 1,很小时趋近 0 return 1.0 / (1.0 + np.exp(-z))

这个函数的妙处在于它的导数可以用自身表示:σ'(z) = σ(z)(1 - σ(z))。这让梯度下降的推导变得非常干净。逻辑说明:输入 z 是线性组合,输出是概率。参数说明:z 可以是标量或 NumPy 数组,函数会自动做逐元素运算。很多同学在这里翻车,是因为把 Sigmoid 的输出直接当类别标签用,忘了还需要一个阈值(通常 0.5)来划分类别。

2.2 从二分类到多分类:鸢尾花任务怎么扩展

西瓜是二分类,鸢尾花有三个品种,直接套 Sigmoid 就不行了。常见做法有两种:一对多(One-vs-Rest)和 Softmax 回归。一对多是训练三个二分类器,每个负责区分“是某品种”和“不是某品种”,预测时取概率最大的那个。Softmax 则是把 Sigmoid 推广到多类,输出每个类别的概率,且和为 1。对于鸢尾花这种类别边界比较清晰的数据,两种方法效果差不多,但 Softmax 的代码更紧凑,报告里也更好写公式。

def softmax(z): # z 的形状为 (样本数, 类别数) # 减去每行最大值防止指数溢出,这是血泪经验 z_shifted = z - np.max(z, axis=1, keepdims=True) exp_z = np.exp(z_shifted) return exp_z / np.sum(exp_z, axis=1, keepdims=True)

逻辑说明:先对每行做数值稳定处理,再计算指数归一化。参数说明:axis=1 表示按类别维度操作,keepdims=True 保持维度以便广播。如果你不做减最大值这一步,当 z 稍微大一点(比如 800),np.exp 就会溢出成 inf,训练直接崩掉。这个坑我在第一次写多分类时就踩过,loss 变成 nan,排查了半天才发现是数值溢出。

2.3 损失函数与梯度推导:为什么用交叉熵而不是均方误差

对数几率回归的损失函数是负对数似然,也叫交叉熵损失。二分类时:

L(w) = -1/m * Σ [y·log(σ(z)) + (1-y)·log(1-σ(z))]

多分类时:

L(W) = -1/m * Σ Σ y_ik·log(softmax(z)_ik)

为什么不用均方误差?因为均方误差配合 Sigmoid 会导致梯度里多出一个 σ'(z) 因子,当预测值接近 0 或 1 时梯度会消失,训练极慢。交叉熵的梯度恰好是 (σ(z) - y)·x,形式简洁且没有饱和问题。这个推导过程建议在报告里写清楚,答辩时老师很可能会问。

def cross_entropy_loss(y_true, y_pred): # y_true: one-hot 编码,形状 (m, k) # y_pred: softmax 输出,形状 (m, k) m = y_true.shape[0] # 加一个极小值 eps 防止 log(0) eps = 1e-12 loss = -np.sum(y_true * np.log(y_pred + eps)) / m return loss

逻辑说明:逐元素相乘后求和,再取负平均。参数说明:eps 是数值稳定项,一般取 1e-12 到 1e-8 之间。注意 y_pred 必须已经过 softmax,不能是原始得分。

2.4 梯度下降更新参数:批量、随机还是小批量

参数更新公式很直观:W = W - lr * dW,其中 dW = 1/m * X^T·(y_pred - y_true)。这里 lr 是学习率,控制每步走多大。批量梯度下降用全部样本算梯度,稳定但慢;随机梯度下降每次用一个样本,快但震荡;小批量介于两者之间,是实践中最常用的。对于西瓜和鸢尾花这种小数据集(西瓜 17 条,鸢尾花 150 条),直接用批量梯度下降就够了,迭代 500 到 1000 次就能收敛。

def train(X, y, lr=0.1, epochs=1000): m, n = X.shape k = y.shape[1] W = np.zeros((n, k)) # 权重初始化 b = np.zeros((1, k)) # 偏置初始化 losses = [] for i in range(epochs): z = np.dot(X, W) + b y_pred = softmax(z) loss = cross_entropy_loss(y, y_pred) losses.append(loss) # 计算梯度 dW = np.dot(X.T, (y_pred - y)) / m db = np.sum(y_pred - y, axis=0, keepdims=True) / m # 更新参数 W -= lr * dW b -= lr * db return W, b, losses

逻辑说明:前向计算得分和概率,反向计算梯度并更新。参数说明:lr 建议从 0.1 开始试,太大震荡,太小收敛慢;epochs 根据 loss 曲线判断,一般 500 到 2000。注意权重初始化用全零在逻辑回归里是安全的,因为损失函数是凸的,但在神经网络里全零初始化会导致对称性问题。

3. 西瓜与鸢尾花数据准备:加载、划分与特征标准化的三个关键操作

3.1 西瓜数据集的手工构造与编码

西瓜数据集通常以表格形式给出,包含色泽、根蒂、敲声等离散特征,以及密度、含糖率两个连续特征。做逻辑回归前需要把离散特征做独热编码或序数编码。比如色泽有“青绿、乌黑、浅白”三个取值,可以编码成 [1,0,0]、[0,1,0]、[0,0,1]。连续特征直接保留数值。标签“好瓜”编码为 1,“坏瓜”编码为 0。

import pandas as pd # 模拟西瓜数据集,实际作业中可从 csv 读取 data = { '色泽': ['青绿', '乌黑', '浅白', '青绿', '乌黑'], '根蒂': ['蜷缩', '稍蜷', '硬挺', '蜷缩', '稍蜷'], '密度': [0.697, 0.774, 0.634, 0.608, 0.556], '含糖率': [0.460, 0.376, 0.264, 0.318, 0.215], '好瓜': [1, 1, 0, 0, 0] } df = pd.DataFrame(data) # 对离散特征做独热编码 df_encoded = pd.get_dummies(df, columns=['色泽', '根蒂']) print(df_encoded.head())

逻辑说明:用 pandas 的 get_dummies 自动完成独热编码。参数说明:columns 指定需要编码的列,其余列保持不变。注意编码后特征维度会增加,如果样本量很小(西瓜只有 17 条),维度太高容易过拟合,可以考虑先做特征选择。

3.2 鸢尾花数据集加载与训练集划分

鸢尾花数据集在 sklearn 里可以直接加载,非常方便。但大作业通常要求自己实现算法,所以我们可以用 sklearn 只做数据加载和划分,模型部分手写。

from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler iris = load_iris() X = iris.data # 形状 (150, 4) y = iris.target # 取值 0, 1, 2 # 划分训练集和测试集,固定随机种子保证可复现 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 特征标准化:均值为 0,方差为 1 scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test)

逻辑说明:stratify=y 保证训练集和测试集的类别比例一致,避免某个类别在测试集中缺失。参数说明:test_size=0.3 表示 30% 做测试,random_state 固定后每次运行结果相同。注意标准化时只能用训练集的均值和方差去变换测试集,不能把测试集的信息泄露进来,这是常见的翻车点。

3.3 标签独热编码与维度对齐

多分类任务中,标签需要转成独热编码才能和 softmax 输出做交叉熵。比如标签 2 转成 [0,0,1]。

def one_hot(y, num_classes): m = len(y) one_hot_y = np.zeros((m, num_classes)) one_hot_y[np.arange(m), y] = 1 return one_hot_y y_train_onehot = one_hot(y_train, 3) y_test_onehot = one_hot(y_test, 3)

逻辑说明:先创建全零矩阵,再把对应位置置 1。参数说明:num_classes 是类别总数,鸢尾花为 3。注意 np.arange(m) 生成行索引,y 作为列索引,这种花式索引写法比循环快很多。

4. 避坑与排查:逻辑回归大作业里最容易翻车的五个地方

4.1 现象:loss 变成 nan 或 inf

原因:学习率太大导致参数爆炸,或者 softmax 计算时指数溢出。解决:把学习率降到 0.01 或 0.001 再试;在 softmax 里减去每行最大值;检查输入数据是否有异常大的值,必要时先做标准化。

4.2 现象:准确率一直卡在 33% 左右(三分类)

原因:权重初始化不当或梯度计算错误,模型实际上在随机猜。解决:检查梯度公式是否漏了除以 m;确认 y_pred 和 y_true 的形状一致;打印前几次迭代的 loss,正常应该从 1.1 左右开始下降。

4.3 现象:训练集准确率很高但测试集很低

原因:过拟合,尤其是西瓜数据集样本极少时。解决:加 L2 正则化项,或者在报告里说明用交叉验证来评估;减少特征数量,比如对西瓜数据只保留密度和含糖率两个连续特征。

4.4 现象:标准化后结果反而变差

原因:标准化时用了全部数据,导致测试集信息泄露。解决:严格用训练集的均值和方差去变换测试集;如果特征里有离散变量,不要对它做标准化。

4.5 现象:报告里公式和代码对不上

原因:推导时用了矩阵形式,代码里却按单个样本循环写。解决:统一用矩阵形式描述,代码里也尽量向量化;在报告附录里贴出关键代码片段并标注对应公式编号。

5. 报告撰写与答辩加分:把对数几率回归讲出深度的一个技巧

大作业报告最忌讳只贴代码和截图。老师想看到的是你理解了多少。一个能让答辩加分的小技巧是:在报告里加入决策边界的可视化。对于西瓜数据集,你可以画出密度-含糖率平面上的决策边界;对于鸢尾花,可以选两个特征画散点图和分类区域。这比单纯列准确率更有说服力。

import matplotlib.pyplot as plt def plot_decision_boundary(X, y, model, feature_idx=(0, 1)): # 只取两个特征用于二维可视化 X_2d = X[:, feature_idx] x_min, x_max = X_2d[:, 0].min() - 0.5, X_2d[:, 0].max() + 0.5 y_min, y_max = X_2d[:, 1].min() - 0.5, X_2d[:, 1].max() + 0.5 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) grid = np.c_[xx.ravel(), yy.ravel()] # 这里需要把网格点补齐到原始特征维度,简单起见用零填充 grid_full = np.zeros((grid.shape[0], X.shape[1])) grid_full[:, feature_idx[0]] = grid[:, 0] grid_full[:, feature_idx[1]] = grid[:, 1] Z = np.argmax(softmax(np.dot(grid_full, W) + b), axis=1) Z = Z.reshape(xx.shape) plt.contourf(xx, yy, Z, alpha=0.3) plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y, edgecolors='k') plt.xlabel('特征 1') plt.ylabel('特征 2') plt.show()

逻辑说明:生成网格点,对每个点预测类别,画出填充等高线。参数说明:feature_idx 指定用哪两个特征做可视化,网格步长 0.02 控制平滑度。注意网格点补齐时用了零填充,实际报告中应说明这只是一种简化处理。

另一个加分点是讨论对数几率回归的局限性:它只能学到线性决策边界,如果数据非线性可分,效果会受限。你可以提一句“可以通过特征交叉或核方法扩展”,但不必实现,这样既展示了知识面,又不会给自己挖坑。最后,报告里的实验对比表要规范:列出不同学习率、不同迭代次数下的训练准确率和测试准确率,用表格呈现,比大段文字描述清晰得多。

我自己带大作业时最深的教训是:不要等到最后一天才跑代码。逻辑回归虽然简单,但数据预处理和调试往往占掉七成时间。提前把数据加载和标准化跑通,后面模型部分就是水到渠成的事。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询