简介:一套面向西电机器学习课程的大作业资料包,适合计算机、电子信息、数学等专业学生完成期末大作业或课程设计时参考。内容覆盖10个实验,包括逻辑与二分类(C2-1)、带噪声的线性回归(C3-1)、神经网络预测森林火灾面积(C4-2)、类别不平衡二分类(C5-1)等典型任务,并附带详细文档说明与完整实验报告。包内含16个文件,以10个Python程序为主,配合csv/data数据集、txt说明、docx实验报告及license文件,整体压缩包仅2.95MB,轻量但结构完整。所有代码均经过运行测试,采用参数化编程,注释详细,便于修改和复现;已上传运行结果,遇到问题可联系博主交流。目前已有2174人学习下载,是快速梳理实验思路、借鉴代码实现和报告写法的实用参考。
1. 西电机器学习大作业到底包含什么
一份完整的机器学习大作业,往往比单独的算法实现更有价值。这套资源来自西安电子科技大学的机器学习课程作业,整理成了MachineLearningHomework-master工程包,里面包含 10 个实验的源代码、实验数据、机器学习实验报告.docx文档说明,以及一份作者自己写的运行说明。实验从最基础的逻辑与(AND)二分类开始,覆盖线性回归、神经网络预测、类别不平衡分类、树模型或支持向量机等知识点,每个代码文件都带有注释和运行结果,属于那种拿过来就能跑、跑完能写报告、写完能讲清楚原理的完整闭环。
适合的人群很明确:正在做课程设计、期末大作业的计算机、电子信息工程、数学专业学生,以及希望快速回顾机器学习基础算法的从业者。对于有经验的工程师,这份材料的价值在于看作者如何把“参数化编程”的思路塞进每个实验里,以及如何组织文档说明和实验报告,这些反而是平时写代码最容易忽略的部分。下面按实验顺序拆开讲,从理论到可复现的实操,把每个实验的关键代码和参数设计都过一遍。
2. 逻辑回归与线性回归:从 AND 到噪声数据
2.1 C2-1 逻辑与 AND 问题:二分类器的第一课
C2-1 实验要求构造一个“逻辑与”的二分类问题,也就是典型的 AND 门:输入两个布尔变量,输出为 1 当且仅当两个输入都为 1。这个实验虽然简单,但它是理解逻辑回归、感知机、决策边界的最佳起点。常见做法是先用numpy生成四个样本点,然后送入逻辑回归模型训练,最后把决策边界画出来。
import numpy as np from sklearn.linear_model import LogisticRegression # AND 数据:两个特征 x1, x2,标签 y X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]], dtype=float) y = np.array([0, 0, 0, 1]) # 逻辑回归模型,C 控制正则化强度 model = LogisticRegression(C=1.0, solver='liblinear') model.fit(X, y) # 打印学到的系数和截距 print("w:", model.coef_) print("b:", model.intercept_)逻辑回归的决策边界由w1*x1 + w2*x2 + b = 0决定。对于 AND 问题,学到的权重通常是w1 ≈ w2 ≈ 1,b ≈ -1.5。这里C=1.0是正则化强度的倒数,C越小正则化越强,如果训练数据存在共线性或离群点,可以适当调低C。solver='liblinear'适合小数据集,如果换成大数据集可以用'lbfgs'。
这个实验容易踩的坑是:直接拿原始特征做逻辑回归,特征尺度差异大时收敛慢。虽然 AND 问题只有 0/1 特征,但后续实验里如果有连续特征,一定要先做标准化。另外,predict_proba输出的概率不是“信心值”,它反映的是模型对决策边界的距离,写报告时不要过度解读。
2.2 C3-1 线性回归:反过来回归会怎样
C3-1 实验生成 500 个数据点(x, y),其中y = x + n,n是均值为 0、标准差为delta的正态分布。要求从x估计y,然后再做一次反方向:从y估计x,最后比较两条回归曲线。这个实验的深层目的是展示“回归稀释效应”(regression dilution)——当自变量有噪声时,斜率会被低估。
import numpy as np from sklearn.linear_model import LinearRegression delta = 0.5 rng = np.random.default_rng(42) x = rng.uniform(0, 10, 500) n = rng.normal(0, delta, 500) y = x + n # 正向回归:用 x 预测 y reg_xy = LinearRegression().fit(x.reshape(-1, 1), y) print("正向斜率:", reg_xy.coef_[0], "截距:", reg_xy.intercept_) # 反向回归:用 y 预测 x reg_yx = LinearRegression().fit(y.reshape(-1, 1), x) print("反向斜率:", reg_yx.coef_[0], "截距:", reg_yx.intercept_)按理论推导,正向回归的斜率约为σ_x^2 / (σ_x^2 + δ^2),反向回归的斜率约为σ_x^2 / (σ_x^2 + δ^2)的倒数关系,但直接用最小二乘拟合时,反向回归的斜率会比 1 小。真实数据中如果两个变量都带噪声,两方向的斜率会形成“剪刀差”,这在仪器校准、测量系统分析中非常常见。实验报告里可以把两条回归曲线画在一张图上,配上原始散点,能清晰看出差异。
参数delta控制噪声标准差,建议在0.1到1.0之间多试几个值,观察斜率变化趋势。如果把delta设得过大,比如50,数据点会完全散开,回归结果失去意义。这也是一个很好的调参切入点:报告里画一张“delta 与斜率”的曲线图,比单纯贴代码更有说服力。
下面用表格对比两种回归的关键差异:
| 回归方向 | 自变量 | 因变量 | 斜率变化 | 实际含义 |
|---|---|---|---|---|
| 正向 | x(无噪声) | y = x + n | 接近 1 | 标准最小二乘 |
| 反向 | y(含噪声) | x | 小于 1 | 因变量噪声被“稀释” |
3. 神经网络实战:森林火灾面积预测
3.1 C4 系列实验的输入与输出
C4 系列实验(C4-1、C4-2、C4-3)要求训练神经网络,预测森林火灾面积。这个问题比前两个实验复杂得多,典型的数据集来自 UCI 的 Forest Fires,特征包括月份、星期、FFMC、DMC、DC、ISI、温度、相对湿度、雨量、风速,目标值是火灾面积(单位为公顷)。由于面积分布极度偏斜,直接回归往往效果很差,常见做法是将目标值取对数log1p,让分布更接近高斯分布。
import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense # 读取数据 df = pd.read_csv('forest_fires.csv') # 将月份和星期做简单数值映射 df['month'] = df['month'].map({'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12}) df['day'] = df['day'].map({'mon':1, 'tue':2, 'wed':3, 'thu':4, 'fri':5, 'sat':6, 'sun':7}) # 特征和标签,对面积做 log1p X = df.drop('area', axis=1).values y = np.log1p(df['area'].values) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) # 标准化特征 scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test)这里用log1p而不是log,是因为面积可能为 0,log1p保证输入大于等于 0 时输出依然有定义。train_test_split的random_state固定后,报告里的结果可以复现。标准化用的是训练集的均值和方差去转换测试集,这是防止数据泄露的关键一步,很多新手会不小心对整个数据集做标准化,导致测试集信息提前进入训练过程。
3.2 网络结构设计与训练参数
对于这种小规模表格数据,全连接网络就够用。我一般会设计三层网络:输入维度对应特征数量,中间层用 ReLU,输出层用线性激活。损失函数用均方误差MSE,优化器用 Adam。下面是一个可复现的 Keras 实现:
from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping model = Sequential([ Dense(64, activation='relu', input_shape=(X_train.shape[1],)), Dense(32, activation='relu'), Dense(1, activation='linear') ]) model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae']) # 早停:验证集 loss 不再下降就停止 early_stop = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True) history = model.fit( X_train, y_train, validation_split=0.2, epochs=200, batch_size=16, callbacks=[early_stop], verbose=1 )参数说明:第一层64个神经元,第二层32个,层数不要太多,否则过拟合。learning_rate=0.001是 Adam 默认值,如果损失不下降,可以先调大到0.01,如果震荡就调小到0.0001。batch_size=16对小数据集较稳定,数据量增大后可改为 32 或 64。EarlyStopping的patience=20表示连续 20 个 epoch 验证 loss 不下降就停止,并用验证 loss 最小的那组权重。
3.3 训练中的坑
森林火灾面积预测最大的坑是目标值极度偏斜,即使做了log1p,预测结果还原为实际面积时误差仍然很大。另一个坑是月份、星期这类循环特征被直接作为数值输入,模型会认为 12 月比 1 月大,但实际它们是循环关系。更合理的做法是用 one-hot 编码或正弦/余弦变换。
表格列出我实际训练中遇到的问题及对策:
| 问题 | 现象 | 对策 |
|---|---|---|
| 损失不下降 | 训练和验证 loss 均不变化 | 检查特征是否标准化,学习率是否过大或过小 |
| 过拟合 | 训练 loss 低,验证 loss 高 | 增加 Dropout,或减少网络层数 |
| 预测值为负 | 还原后面积出现负数 | 输出层改用 softplus 或直接加max(0, x) |
| 数据泄露 | 验证集效果异常好 | 检查是否在fit前对全量数据做标准化 |
4. 类别不平衡与其余实验:分类器的边界
4.1 C5-1 类别不平衡二分类问题
C5-1 实验要求构建一个类别不平衡的二分类问题。在真实业务中,比如欺诈检测、异常流量识别,正样本占比可能只有 1%,此时准确率没有意义,模型需要优化 Precision、Recall 和 AUC。常见做法有三种:重采样、修改损失函数、使用集成方法。
from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, roc_auc_score # 生成 1000 个样本,正类占比 5% X, y = make_classification( n_samples=1000, n_features=20, n_informative=15, n_redundant=5, weights=[0.95, 0.05], random_state=42 ) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42) # 使用 class_weight 给少数类更高权重 model = RandomForestClassifier( n_estimators=100, class_weight='balanced', random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred)) print("AUC:", roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]))class_weight='balanced'会自动根据类别频率调整权重,少数类样本的误分类代价更高。stratify=y保证训练集和测试集的正负样本比例一致,这是不平衡实验里必须做的,否则测试集可能全是负样本,评估失真。n_estimators=100是随机森林中树的数量,树越多越稳定,但训练时间也越长。
除了class_weight,还可以用imbalanced-learn库做 SMOTE 过采样。但要注意,SMOTE 必须在划分训练集之后再使用,而且只能对训练集做,否则会生成重叠样本导致验证结果虚高。实验报告里建议同时给出class_weight='balanced'和 SMOTE 两种方案的对比,这会让报告厚度明显提升。
4.2 剩余实验(C6、C7、C8)快速拆解
除了前面重点讲的 C2、C3、C4、C5,资源里还有 C6-1、C6-2、C7-1、C8-1 等实验。这些实验的定位各不相同,我用下面的表格总结每个实验对应的核心知识点和实现要点,方便你有针对性地看源代码:
| 实验编号 | 核心知识点 | 代码关键点 | 可能的扩展方向 |
|---|---|---|---|
| C6-1 | 决策树 / 集成学习 | 特征重要性排序、树深度限制 | 换成梯度提升树对比 |
| C6-2 | 聚类 / 降维 | KMeans 的肘部法则、PCA 可视化 | 用 t-SNE 观察聚类结构 |
| C7-1 | 支持向量机 | 核函数选择、惩罚系数 C | 对比线性核与 RBF 核 |
| C8-1 | 模型评估 | 交叉验证、混淆矩阵 | 加入 ROC 曲线与 PR 曲线 |
这些实验我建议不要只看单个代码,而是把它们按“预处理、建模、评估”三个阶段拆开。同一个数据集,先用 C6 的树模型跑一遍,再用 C7 的 SVM 跑一遍,最后用 C8 的评估方法做对比,这样就能自然形成一组对照实验。很多同学在期末大作业里只贴孤立的代码,缺少“为什么用这个模型”的论证,这是拿不到高分的主要原因。
4.3 从实验到期末大作业的衔接
把这 10 个实验串联起来看,刚好对应机器学习课程的主线:分类、回归、神经网络、不平衡学习、模型评估。做期末大作业时,不建议直接堆砌所有实验,而是挑一条线深入。比如选“森林火灾面积预测”作为主题,把 C3 的线性回归当作基线,C4 的神经网络作为主模型,C5 的不平衡处理思路用在面积大于阈值的二分类延伸上,最后用 C8 的评估方法收尾。这样既有基础又有深度,还能展示你对多个知识点的控制能力。
源代码里的注释风格是“参数化编程”,这意味着很多超参数都提取到了文件顶部或配置区域,修改起来不需要在代码里到处找。这个习惯非常值得借鉴,写大作业时我也建议把LEARNING_RATE、BATCH_SIZE、EPOCHS等变量集中管理,这样报告里写参数分析时,直接引用变量名就行。
5. 实验报告的写法与调试技巧
5.1 报告结构:让代码可复现
拿到这套资源后,最容易忽视的是机器学习实验报告.docx。这份报告的价值在于告诉你老师的评分点在哪里:一般包括实验目的、算法原理、实验步骤、结果分析、参数调优、结论和参考文献。但很多人的报告只写了“实验步骤”和“代码”,缺少“为什么这么设计”的说明。我的建议是每一章都加一小节“关键参数说明”,用表格列出参数名、默认值、修改影响,例如:
| 参数 | 默认值 | 调高效果 | 调低效果 |
|---|---|---|---|
C(逻辑回归) | 1.0 | 过拟合风险降低,决策边界更平滑 | 对离群点更敏感 |
delta(线性回归噪声) | 0.5 | 数据更散乱,回归斜率下偏 | 接近标准直线 |
learning_rate | 0.001 | 收敛快但可能震荡 | 收敛慢但稳定 |
n_estimators | 100 | 模型更稳,训练更慢 | 方差增大 |
报告里如果能附上每次实验的运行截图或损失曲线,说服力会强很多。源代码文件里已经包含运行结果,直接截图即可,不用重新跑。
5.2 参数化编程与运行排错
“参数化编程”是这套代码的一个特点,但很多人拿到后不知道如何改参数。常见做法是把所有可调参数集中到一个字典或argparse配置里,这样既方便命令行调试,也方便写报告时记录实验记录。下面是一个小型示例:
import argparse def parse_args(): parser = argparse.ArgumentParser(description='ML Homework Experiment') parser.add_argument('--delta', type=float, default=0.5, help='noise std for data generation') parser.add_argument('--lr', type=float, default=0.001, help='learning rate') parser.add_argument('--batch_size', type=int, default=16, help='batch size for training') return parser.parse_args() if __name__ == '__main__': args = parse_args() print(f'delta={args.delta}, lr={args.lr}, batch_size={args.batch_size}')这样改参数时不需要动核心代码,只需在命令行传入--delta 0.8。运行报错时,先看是不是路径问题,data文件夹和代码同级时用相对路径,但如果从其他目录调用脚本,open('data.csv')会找不到文件,建议在代码开头用os.path.dirname(__file__)拼绝对路径。另一个高频错误是sklearn或tensorflow版本不兼容,比如新版本里LogisticRegression的solver参数默认值变了,报错时先检查依赖版本。
5.3 快速验证模型效果的小技巧
在提交前,我习惯用一个小脚本快速验证所有实验是否都能跑通。不要直接运行每个文件,而是按实验顺序逐个执行,并设置一个简单的断言检查输出范围。例如线性回归的斜率应该在0.5到1.5之间,逻辑回归的准确率应该大于0.9,神经网络预测的log1p值不应出现NaN。跑完后再检查data目录下是否有缺失文件,最后统一查看实验报告里的截图是否与当前代码运行结果一致。调参时优先用matplotlib画出损失曲线,如果前 10 个 epoch 没有明显下降,先调学习率而不是调网络结构。这套排查顺序能帮你省下大量重复训练的时间。
本文还有配套的精品资源,点击获取