简介:这份资源是面向计算机相关专业在校学生、教师及企业员工的期末大作业与课程设计资料包,围绕对数几率回归模型实现西瓜与鸢尾花数据集的识别分类任务展开,适合作为机器学习入门进阶练习,也可直接用于毕设、课程设计或初期项目立项演示。压缩包共30个文件,约544KB,包含6个Python源码文件、4个Jupyter Notebook、2份Word作业报告、2份Markdown项目说明,以及xml、iml等IDE配置文件和pyc缓存文件,源码附带详细注释,便于理解算法实现与调试过程。目前已有296人学习下载。读者可获得完整的对数几率回归建模流程、数据预处理与分类评估代码、可直接提交的作业报告模板,以及清晰的目录结构,既能对照复现实验,也方便在此基础上进行二次开发与功能拓展。
1. 对数几率回归做西瓜鸢尾花分类:期末大作业里最容易被低估的一行代码
很多人拿到「基于对数几率回归模型实现西瓜鸢尾花识别分类」这个题目,第一反应是:逻辑回归太简单了,sklearn 三行代码就能跑完,期末大作业写这个是不是显得没水平?我当年也这么想,直到帮学弟调他的作业报告,才发现真正翻车的地方根本不在模型本身——他把西瓜数据集和鸢尾花数据集混在一起读,特征列对不上,准确率一直在 0.33 附近晃,他还以为是逻辑回归不适合多分类。对数几率回归(Logistic Regression)本质是线性分类器套一个 Sigmoid 映射,二分类是它的主场,多分类要靠 One-vs-Rest 或 Softmax 扩展。西瓜数据集是二分类(好瓜/坏瓜),鸢尾花是三分类(三种花),两个任务放在一份作业里,恰好覆盖了从二分类到多分类的完整链路。这篇笔记面向正在做这个期末大作业、或者想拿它当 Python 入门练手项目的人,把数据读取、模型训练、参数调节、报告撰写和注释规范整条路径讲清楚,让你交上去的东西经得起追问。
2. 先搞清楚两个数据集的结构差异:西瓜二分类和鸢尾花三分类不是一回事
2.1 西瓜数据集 3.0 的字段含义与标签编码
西瓜数据集 3.0 是周志华《机器学习》里的经典数据,共 17 个样本,每个样本有 8 个特征:色泽、根蒂、敲声、纹理、脐部、触感、密度、含糖率。前 6 个是离散字符串,后 2 个是连续数值。标签是「好瓜」和「坏瓜」。很多人直接把这个数据集丢给逻辑回归,会报could not convert string to float的错误,因为 sklearn 不接受字符串特征。
常见做法是先用 pandas 读入,然后对离散列做映射编码。注意不要用 OneHotEncoder 无脑展开,17 个样本展开成几十维反而容易过拟合。我一般用简单的字典映射,把「青绿/乌黑/浅白」映射成 0/1/2,这样既保留了序关系又不会维度爆炸。
import pandas as pd import numpy as np # 读取西瓜数据集,假设文件为 watermelon3.0.csv df = pd.read_csv('watermelon3.0.csv', encoding='utf-8') # 离散特征映射字典,按语义顺序编码 color_map = {'青绿': 0, '乌黑': 1, '浅白': 2} root_map = {'蜷缩': 0, '稍蜷': 1, '硬挺': 2} sound_map = {'浊响': 0, '沉闷': 1, '清脆': 2} texture_map = {'清晰': 0, '稍糊': 1, '模糊': 2} umbilicus_map = {'凹陷': 0, '稍凹': 1, '平坦': 2} touch_map = {'硬滑': 0, '软粘': 1} df['色泽'] = df['色泽'].map(color_map) df['根蒂'] = df['根蒂'].map(root_map) df['敲声'] = df['敲声'].map(sound_map) df['纹理'] = df['纹理'].map(texture_map) df['脐部'] = df['脐部'].map(umbilicus_map) df['触感'] = df['触感'].map(touch_map) # 标签编码:好瓜=1,坏瓜=0 df['好瓜'] = df['好瓜'].map({'是': 1, '否': 0}) # 检查缺失值 print(df.isnull().sum())这段代码的关键在于映射字典的顺序要和数据集里的类别一一对应,如果映射错了,模型学到的就是错的序关系。参数方面,encoding='utf-8'是为了防止中文列名读取乱码,如果你在 Windows 上遇到UnicodeDecodeError,可以换成gbk试试。isnull().sum()用来确认映射过程中有没有产生 NaN,因为如果某个类别没在字典里,map 会返回 NaN,后续训练直接报错。
2.2 鸢尾花数据集的加载与特征标准化
鸢尾花数据集在 sklearn 里自带,150 个样本,4 个连续特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度,3 个类别各 50 个样本。这个数据集干净得多,但有一个坑:三个类别的特征分布范围不同,花瓣长度和花萼宽度的量纲差异会导致逻辑回归的梯度下降收敛慢。我一般会做标准化,把均值归到 0、方差归到 1。
from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 加载鸢尾花数据集 iris = load_iris() X, y = iris.data, iris.target # 标准化:逻辑回归对特征尺度敏感,标准化能加速收敛 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 划分训练集和测试集,stratify 保证类别比例一致 X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, random_state=42, stratify=y ) print(f"训练集样本数: {X_train.shape[0]}, 测试集样本数: {X_test.shape[0]}") print(f"类别分布: {np.bincount(y_train)}")StandardScaler的fit_transform只能在训练集上做,测试集要用训练集的均值和方差来transform,否则就是数据泄露。stratify=y保证划分后每个类别的比例和原始一致,150 个样本按 7:3 划分后,训练集每类 35 个,测试集每类 15 个。random_state=42固定随机种子,保证你每次跑出来的结果一样,报告里写准确率才有意义。
提示:西瓜数据集样本太少,标准化后反而可能放大噪声,建议只对连续特征「密度」和「含糖率」做标准化,离散特征保持原样。
3. 对数几率回归的两种实现路径:手写梯度下降和 sklearn 调包
3.1 手写二分类逻辑回归的梯度下降
期末大作业如果只调包,报告里没什么可写的。我建议至少手写一遍二分类的逻辑回归,把 Sigmoid、交叉熵损失、梯度更新这三个核心公式落到代码里。这样报告里可以画损失曲线,老师一看就知道你真懂了。
def sigmoid(z): # 防止 exp 溢出,对 z 做截断 z = np.clip(z, -500, 500) return 1.0 / (1.0 + np.exp(-z)) def train_logistic_regression(X, y, lr=0.1, epochs=1000): """ 手写二分类逻辑回归训练 X: 特征矩阵 (n_samples, n_features) y: 标签向量 (n_samples,) lr: 学习率 epochs: 迭代次数 返回: 权重 w, 偏置 b, 损失历史 """ n_samples, n_features = X.shape w = np.zeros(n_features) b = 0.0 loss_history = [] for i in range(epochs): # 前向传播 z = np.dot(X, w) + b y_pred = sigmoid(z) # 交叉熵损失,加 1e-9 防止 log(0) loss = -np.mean(y * np.log(y_pred + 1e-9) + (1 - y) * np.log(1 - y_pred + 1e-9)) loss_history.append(loss) # 梯度计算 dw = np.dot(X.T, (y_pred - y)) / n_samples db = np.mean(y_pred - y) # 参数更新 w -= lr * dw b -= lr * db return w, b, loss_history学习率lr是最关键的参数,设 0.1 在标准化后的数据上一般能收敛,如果损失曲线震荡就降到 0.01,如果下降太慢就升到 0.5 试试。epochs设 1000 通常够用,你可以画loss_history看它什么时候趋于平稳。np.clip(z, -500, 500)是防止np.exp溢出,这是血泪经验,不加上去在某些样本上会直接报RuntimeWarning: overflow。
3.2 sklearn 的 LogisticRegression 多分类参数
鸢尾花是三分类,sklearn 的LogisticRegression默认用multi_class='auto',在数据量小的时候会选 OvR,数据量大且类别多的时候选 Softmax。你可以显式指定multi_class='multinomial'来强制用 Softmax,配合solver='lbfgs'或solver='saga'。
from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, classification_report # 多分类逻辑回归,使用 Softmax 回归 clf = LogisticRegression( multi_class='multinomial', solver='lbfgs', C=1.0, max_iter=1000, random_state=42 ) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(f"准确率: {accuracy_score(y_test, y_pred):.4f}") print(classification_report(y_test, y_pred, target_names=iris.target_names))C是正则化强度的倒数,默认 1.0,越小正则化越强。如果报告里发现训练集准确率远高于测试集,说明过拟合,把C降到 0.1 或 0.01。max_iter默认 100,在鸢尾花上可能不收敛,设 1000 比较稳。classification_report会输出每个类别的精确率、召回率和 F1,报告里直接贴这个表比只写一个准确率有说服力得多。
3.3 西瓜数据集上的二分类验证
西瓜数据集只有 17 个样本,用留一法交叉验证比 train_test_split 更合理,因为随机划分可能导致某一类在测试集里一个都没有。
from sklearn.model_selection import LeaveOneOut from sklearn.linear_model import LogisticRegression # 准备西瓜数据 X_wm = df[['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率']].values y_wm = df['好瓜'].values loo = LeaveOneOut() acc_list = [] for train_idx, test_idx in loo.split(X_wm): X_tr, X_te = X_wm[train_idx], X_wm[test_idx] y_tr, y_te = y_wm[train_idx], y_wm[test_idx] model = LogisticRegression(max_iter=500) model.fit(X_tr, y_tr) acc_list.append(model.score(X_te, y_te)) print(f"留一法平均准确率: {np.mean(acc_list):.4f}")留一法每次只留一个样本做测试,17 个样本跑 17 次,最终准确率是 17 次的平均。这个数字在报告里比单次划分更可信。注意西瓜数据集样本少,准确率波动大,0.7 到 0.9 之间都算正常,不要为了追求高准确率去调参数调到过拟合。
4. 避坑与排查:逻辑回归作业里最容易翻车的 5 个地方
4.1 准确率一直上不去,检查标签编码是否反了
现象:模型训练完准确率在 0.3 到 0.5 之间,比随机猜还差。原因:标签映射时把「好瓜」映射成 0、「坏瓜」映射成 1,但预测时又按 1 是好瓜来解读,导致精确率和召回率对调。解决:统一标签编码规范,在代码开头用注释写清楚0=坏瓜, 1=好瓜,所有评估指标都按这个约定解读。
4.2 报错 could not convert string to float
现象:fit的时候直接抛ValueError: could not convert string to float: '青绿'。原因:西瓜数据集的离散列没有做编码,直接传给了 sklearn。解决:在fit之前用df.select_dtypes(include='object').columns检查还有哪些字符串列,逐一映射。如果懒得手动映射,可以用pd.get_dummies做 OneHot 编码,但 17 个样本展开后维度太高,不推荐。
4.3 损失曲线不下降或者震荡
现象:手写梯度下降时loss_history一直在 0.69 附近不动,或者上下大幅震荡。原因:学习率设得太大导致震荡,或者特征没有标准化导致梯度尺度差异大。解决:先把学习率降到 0.01 试,如果下降太慢再逐步升到 0.1、0.3。同时确认连续特征做了标准化,西瓜数据集的「密度」在 0.3 到 0.8 之间,「含糖率」在 0.05 到 0.45 之间,量纲接近,但鸢尾花的花瓣长度在 1 到 7 之间,不标准化会影响收敛。
4.4 多分类时 predict_proba 返回的列数不对
现象:鸢尾花三分类,predict_proba返回的却是两列。原因:multi_class参数设成了'ovr'且某个类别被当成正类、其余合并成负类。解决:显式设multi_class='multinomial',并且确认solver支持多分类,lbfgs和saga都支持,liblinear不支持多分类。
4.5 作业报告里注释太少被扣分
现象:代码能跑,但报告分数不高,老师评语写「注释不足,可读性差」。原因:只写了# 导入库这种无意义注释,没有解释参数含义和设计决策。解决:每个函数写 docstring,说明输入输出和关键参数;每个映射字典旁边写清楚类别顺序;每个超参数写一行注释说明为什么选这个值。参考 Python 文档注释规范,用"""写函数说明,用#写行内说明。
注意:不要用
# -*- coding: utf-8 -*-这种老式编码声明,Python 3 默认就是 UTF-8,写了反而显得不专业。
5. 报告撰写与注释规范:让大作业从及格线拉到优秀
5.1 作业报告的结构模板
期末大作业的报告一般包括:问题描述、数据集说明、方法原理、实验设置、结果分析、结论。我建议在「方法原理」部分把对数几率回归的公式推导写清楚:从线性回归到 Sigmoid 映射,从极大似然估计到交叉熵损失,从梯度推导到参数更新。这部分不用太长,一页纸足够,但公式要自己敲,不要截图。
「实验设置」部分要写清楚:训练集测试集划分比例、随机种子、标准化方法、超参数取值。这些细节决定了你的实验能不能被复现。「结果分析」部分除了准确率,还要放混淆矩阵和分类报告,鸢尾花三分类的混淆矩阵能看出哪个类别容易混。
from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # 绘制混淆矩阵 cm = confusion_matrix(y_test, y_pred) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=iris.target_names, yticklabels=iris.target_names) plt.xlabel('预测类别') plt.ylabel('真实类别') plt.title('鸢尾花分类混淆矩阵') plt.tight_layout() plt.savefig('confusion_matrix.png', dpi=150)fmt='d'表示显示整数,cmap='Blues'是配色方案,dpi=150保证图片清晰度够打印。保存图片而不是只show,是因为报告里要插入图片,直接截图会有白边。
5.2 代码注释的三个层次
第一层是模块级注释,在文件开头写清楚这个脚本做什么、依赖哪些库、数据文件放在哪里。第二层是函数级注释,用 docstring 写清楚输入参数的类型和含义、返回值的结构。第三层是行内注释,只在对非直观操作做解释时写,比如「对 z 做截断防止 exp 溢出」这种。
""" 西瓜鸢尾花分类作业 依赖: pandas, numpy, sklearn, matplotlib, seaborn 数据: watermelon3.0.csv 放在同目录下 运行: python classification.py """这种模块级注释看起来简单,但很多同学交上来的代码连文件名和运行方式都没写,老师想跑都跑不起来。加上这三行,印象分直接拉满。
5.3 参数调优的记录方式
报告里不要只写「最终准确率 0.95」,要写「在 C=1.0 时准确率 0.93,C=0.1 时 0.95,C=0.01 时 0.91,最终选 C=0.1」。用一个表格记录不同参数下的结果,比一段文字描述清楚得多。
| C 值 | 训练集准确率 | 测试集准确率 | 备注 |
|---|---|---|---|
| 0.01 | 0.90 | 0.91 | 正则化过强,欠拟合 |
| 0.1 | 0.96 | 0.95 | 最佳 |
| 1.0 | 0.99 | 0.93 | 轻微过拟合 |
| 10.0 | 1.00 | 0.91 | 过拟合明显 |
这张表放在报告里,老师一眼就能看出你做了调参实验,而不是随便跑了一次就交。注意训练集准确率和测试集准确率的差距,差距越大过拟合越严重。
5.4 用交叉验证替代单次划分
如果想让结果更稳,可以用cross_val_score做 5 折交叉验证,报告里写「5 折交叉验证平均准确率 0.95±0.03」,比单次划分的 0.95 更有说服力。
from sklearn.model_selection import cross_val_score clf_cv = LogisticRegression(multi_class='multinomial', solver='lbfgs', C=0.1, max_iter=1000, random_state=42) scores = cross_val_score(clf_cv, X_scaled, y, cv=5, scoring='accuracy') print(f"5折交叉验证: {scores.mean():.4f} ± {scores.std():.4f}")cv=5表示 5 折,scoring='accuracy'表示用准确率评估。scores.std()是标准差,越小说明模型越稳定。如果标准差超过 0.05,说明数据划分对结果影响大,报告里要说明这一点。
5.5 最后检查一遍这些细节
交作业之前,我一般会过一遍这个清单:代码能不能在干净环境里跑通(把__pycache__删掉重跑)、数据文件路径是不是相对路径、有没有硬编码的绝对路径、图片有没有保存到正确目录、报告里的数字和代码输出是否一致。这些细节看起来琐碎,但每年都有同学因为路径写死在自己电脑上,老师一跑就报FileNotFoundError,直接扣分。
我自己的习惯是,在项目根目录放一个requirements.txt,写清楚依赖库和版本,虽然期末大作业不一定要求,但加上去显得规范。另外,代码里不要出现print调试语句残留,交之前全局搜一下print,该删的删,该改成日志的改成日志。希望帮到你。
本文还有配套的精品资源,点击获取