简介:这是面向机器学习初学者的课程作业源码包,覆盖分类、回归与聚类三大经典任务,共八次高分作业,可直接用于期末大作业或课程设计参考,也能帮助零基础学生快速建立算法实战认知。压缩包合计四十八个文件,以 Jupyter 笔记(ipynb)为核心,搭配文本数据、Python 脚本及示意图,整体大小仅一点四二 MB,模块划分清晰,便于按作业序号逐次学习。目前已有约一千三百人次学习或下载,适合作为入门参考与课程项目模板。内容涵盖逻辑回归、决策树、支持向量机、感知机、多层感知机、AdaBoost、KMeans、KMedoids 与 GMM 聚类等常用算法,每次作业均包含数据准备、特征处理、模型搭建、训练评估与结果分析,代码注释清晰、步骤完整且易复现。所有项目均获老师指导并通过,纯手打代码、实战导向,可帮助初学者理解算法原理,并将典型案例快速迁移到自己的作业或研究项目中,减少从零搭建的重复工作。
1. 机器学习大作业源码包:八次作业分别考什么,值不值得花力气复现
期末前一周,课程群总有人问有没有现成的机器学习大作业源码。其实大家不是想抄,是想找一个能跑的坐标系——别人交的是能复现的代码、可视化和一份说清因果的报告,自己交的是换个机器就崩的 notebook。标题里这份八次高分作业的 zip 包,拆开看就是回归、分类、聚类三个方向的八次变形。它解决的是课程作业怎么组织、怎么调参、怎么避开那些让分数腰斩的隐藏坑。适合正在修机器学习课、需要交课设作业的学生,也适合期末复习时拿它对照:原来回归和分类的边界在这,聚类和分类的差别在这。我的建议是别急着全跑,先把八次作业的算法名对应到目录结构,再决定哪几次直接复用、哪几次必须换数据集重写。
2. 回归类作业:线性回归与逻辑回归,从损失函数到评估指标
2.1 回归作业的第一性原理:把最小二乘和梯度下降写进 numpy
先说结论:线性回归是所有回归作业的底座。八次作业里只要出现“预测连续值”,最后都能还原成 y = Xw + b 再加一个损失函数。高分作业不是把 sklearn 的 LinearRegression 调包跑完就结束,而是要在报告里写清楚损失函数怎么定义、梯度怎么更新。所以我一般会先手写一遍,再拿 sklearn 的结果做对照,两条线对上了,调参才有底气。
import numpy as np rng = np.random.default_rng(42) X = rng.uniform(-3, 3, size=(200, 1)) y = 2.5 * X.ravel() + 1.0 + rng.normal(0, 0.5, size=200) # 手动加一列偏置项,比单独维护 b 更方便 Xb = np.c_[np.ones((200, 1)), X] w = np.zeros((Xb.shape[1], 1)) lr, epochs = 0.1, 500 for i in range(epochs): grad = Xb.T @ (Xb @ w - y.reshape(-1, 1)) / len(y) # 平均梯度 w -= lr * grad print("w0/bias:", w[0, 0], "w1/slope:", w[1, 0])这里用全批量梯度下降,梯度公式是 X^T(Xw - y)/n,除以 n 是为了让学习率不受样本量影响。lr选 0.1 是因为特征已经做了均匀分布归一化,如果原始数据量纲差很大,这个学习率会让 loss 直接炸掉,第一件事就是先做标准化。epochs取 500 在小数据集上足够收敛,作业里如果画训练 loss 曲线,前 100 轮快速下降、之后渐渐平缓,这条曲线放进报告就比只贴结果高一个档次。
作业里如果要求加正则化,把损失函数换成岭回归的 ||y-Xw||^2 + alpha||w||^2,梯度更新里多一项 alpha*w 即可。这是搜“岭回归”相关题目时最直接的回答。手写代码跑通之后,拿 sklearn 对拍是自查最快的方式:
from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score model = LinearRegression().fit(X, y) r2_sk = r2_score(y, model.predict(X)) r2_manual = r2_score(y, Xb @ w) print("sklearn R2:", r2_sk, "manual R2:", r2_manual)两边 R2 几乎一致时,说明梯度推导和更新没有算错。这里有个很常见的误用:用 R2 比较线性回归和随机森林回归,然后得出随机森林更好的结论。R2 在训练集上对复杂模型天然友好,应该用交叉验证分数来比,不然就是自己骗自己。
2.2 逻辑回归损失函数与二分类任务:头歌式填动作业怎么拿满
逻辑回归是分类作业里最常见的入口。它名字带“回归”,实际做的是二分类。它的损失函数是交叉熵,不是均方误差。很多在线实训平台,比如头歌,经常让你填空实现 sigmoid 和前向传播,填错一个符号就全错。这里给一个带 L2 惩罚的逻辑回归手写骨架。
def sigmoid(z): z = np.clip(z, -250, 250) # 防溢出 return 1.0 / (1.0 + np.exp(-z)) def train_logreg(X, y, lr=0.05, epochs=300, lam=0.01): n, d = X.shape w = np.zeros(d) b = 0.0 for _ in range(epochs): z = X @ w + b p = sigmoid(z) dw = X.T @ (p - y) / n + lam * w db = np.mean(p - y) w -= lr * dw b -= lr * db return w, bsigmoid 里先做 np.clip 是血泪经验,样本少的时候 z 很容易到 ±几百,exp 一算就是 inf,loss 直接 nan。梯度里 dw 的第二项是 L2 正则的导数,lam越大模型越保守。返回的 w 和 b 可以直接拿来预测,p > 0.5 判为正类。lr调 0.05 是因为交叉熵的梯度量级通常比 MSE 小,学习率可以比线性回归稍大,但超过 0.1 也容易来回弹。
分类评估不能只报 accuracy。作业如果给的是不平衡二分类,比如正例只占 5%,模型全预测负类也能有 95% 准确率,这在作业里是一眼假。至少要用 precision、recall、F1,再画 ROC 计算 AUC:
from sklearn.metrics import classification_report, roc_auc_score pred = (sigmoid(X_test @ w + b) > 0.5).astype(int) print(classification_report(y_test, pred, target_names=['neg', 'pos'])) print("AUC:", roc_auc_score(y_test, sigmoid(X_test @ w + b)))classification_report会同时给出 P/R/F1,AUC 用预测概率而不是标签,直接传 p。这个代码块对应作业里的“分类评估”部分,比只打印 accuracy 更能拿分。逻辑回归损失函数的手写推导是这类作业的高频考点,报告里建议附上损失函数公式和梯度推导简图,老师一眼就能看出你是真懂。
2.3 回归和分类共用的数据检查:先看分布再跑模型
回归作业里“数据处理是什么”这个问题,很多人理解成标准化。但更关键的是先看特征分布。如果特征范围从 0.001 到 10000,线性回归的梯度下降会非常慢;如果不平衡分类里的正例只有零星几个,逻辑回归学不到任何规律。所以拿到数据的第一件事永远是df.describe()和df['label'].value_counts()。
import pandas as pd df = pd.read_csv('dataset.csv') print(df.describe()) print(df['label'].value_counts())这段代码在报告里写一句话解释:量纲差异大,所以需要 StandardScaler;类别分布极不均衡,所以评估指标选 F1 和 AUC。数据检查的三行输出,能把后面所有模型调参的合理性串起来,这是八次作业通用的地基。
3. 树模型与集成:决策树、随机森林回归与分类,把过拟合压回去
3.1 为什么回归树比线性模型更耐噪声
作业数据如果不是线性、有异常值、特征之间交互复杂,线性回归会很难受。回归树用递归划分把特征空间切成矩形,对异常值不那么敏感,也不需要标准化。这是它在课设里被频繁选用的原因。但决策树单独用很容易过拟合:树的深度一深,叶子节点样本太少,测试分数大跌。所以集成树才是作业主力,随机森林回归算法是默认起点。
决策树和随机森林的分类版本也遵循同一套逻辑。分类作业里如果样本量不大,随机森林往往比 SVM 更稳,因为它不需要精细的核函数调参,默认参数跑出来就有个还过得去的分数,再微调就能进报告。但要注意,随机森林在训练集上的分数很容易虚高,必须用交叉验证或者单独留出测试集来证明它真的能泛化。
3.2 随机森林回归算法的必调参数:n_estimators、max_depth、min_samples_split
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import cross_val_score rf = RandomForestRegressor( n_estimators=200, max_depth=8, min_samples_split=5, max_features='sqrt', random_state=42 ) scores = cross_val_score(rf, X_train, y_train, cv=5, scoring='neg_mean_squared_error') print("CV RMSE:", (-scores.mean()) ** 0.5)n_estimators决定树的数量,200 棵在课设规模下足够,再大收益很小还慢。max_depth是最大的坑,默认 None 会无限生长,我一般先在 4~10 之间网格搜。min_samples_split=5限制内部节点最少样本数,能有效抑制过拟合。cross_val_score用负 MSE,取负号开方变成 RMSE,这样能跟原始 y 的量纲对齐。random_state必须固定,否则两次运行结果不同,作业报告没法解释。
如果作业要求展示调参过程,用 GridSearchCV 更直观:
from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [100, 200], 'max_depth': [5, 8, 12], 'min_samples_split': [2, 5, 10] } grid = GridSearchCV(RandomForestRegressor(random_state=42), param_grid, cv=5, scoring='neg_mean_squared_error', n_jobs=-1) grid.fit(X_train, y_train) print(grid.best_params_, (-grid.best_score_) ** 0.5)n_jobs=-1是加速操作,数据量小的时候用不用无所谓;机房共享内存不足时,设 n_jobs=2 更稳。参数组合 2×3×3=18,每折 5 次,90 次训练,课设数据一般几十秒能跑完。报告里把best_params_和对应 RMSE 打出来,这就是一个完整的调参闭环。
3.3 机器学习中的数据处理是什么:树模型和 SVM 要的是同一套预处理吗
机器学习中的数据处理是什么,这个问题在课程群里反复出现。树模型对特征尺度不敏感,这是它的优点;但类别特征编码和缺失值处理仍然绕不开。常见流程是:数值特征直接喂;类别特征用 OneHotEncoder,高基数类别如果用 OrdinalEncoder,树容易按编号顺序硬切;缺失值用 SimpleImputer 填均值,缺失率超过 40% 就直接扔特征。
from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.compose import ColumnTransformer preprocessor = ColumnTransformer([ ('num', StandardScaler(), ['age', 'income']), ('cat', OneHotEncoder(handle_unknown='ignore'), ['city', 'job']) ]) X_processed = preprocessor.fit_transform(X_train)这里ColumnTransformer把数值列和类别列分开处理,是现在课设里最常用的写法。注意fit_transform只能用在训练集,测试集要用transform,一旦混用就是数据泄漏。树模型不需要 StandardScaler,但这里仍然对数值列做了标准化,是为了让同一个流程后面接 SVM 或逻辑回归时不冲突。推荐在报告里直接贴预处理管线截图,比贴一堆df.fillna()好看得多。
特征重要性是随机森林作业里最受欢迎的输出,拿到它,报告的分析部分就有话说:
importances = rf.feature_importances_ for name, imp in zip(feature_names, importances): print(f"{name}: {imp:.3f}")重要性分数会偏向高基数或强相关的特征,两个强相关特征同时存在时,重要性会在两者之间分摊,不代表其中一个真的没用。作业里发现模型分数不高时,先看特征重要性分布,往往能找到方向。
4. 聚类算法作业:KMeans与高斯混合模型GMM,从硬分配到软分配
4.1 KMeans聚类算法的肘部法则与轮廓系数:K怎么选
聚类是无监督,没有标签,评分方式完全不一样。作业里常见做法是先用 KMeans,然后画肘部曲线选 K,再算轮廓系数佐证。
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score inertia = [] for k in range(2, 11): km = KMeans(n_clusters=k, n_init=10, random_state=42) km.fit(X_scaled) inertia.append(km.inertia_) sil = silhouette_score(X_scaled, km.labels_) print(f"k={k}: inertia={inertia[-1]:.1f}, silhouette={sil:.3f}")inertia是样本到最近簇中心的平方距离和,K 越大它越小,下降变缓的“肘部”就是合理 K。silhouette_score范围是 [-1,1],越接近 1 说明簇内紧凑、簇间分离。这里的X_scaled必须先标准化,否则量纲大的特征直接主导距离计算,这是 KMeans 作业里最常见的翻车点。n_init=10表示从 10 个随机中心点里选最优,默认 KMeans++ 初始化;如果改成 n_init=1,每次结果都可能不同。
4.2 高斯混合模型GMM:聚类不是圆形时换一下
KMeans 的簇是凸的、等方差的,遇到长条形或椭圆形的数据会切得很难看。高斯混合模型用一组高斯分布去拟合数据,每个样本属于每个簇是一个概率,做的是软分配。GMM 的协方差类型有 full、tied、diag、spherical 四种,作业里选 full 最能拟合任意形状,但参数多、数据少会过拟合。
from sklearn.mixture import GaussianMixture bic_scores = [] for k in range(2, 10): gmm = GaussianMixture(n_components=k, covariance_type='full', random_state=42, n_init=5) gmm.fit(X_scaled) bic_scores.append(gmm.bic(X_scaled)) print(f"k={k}, BIC={bic_scores[-1]:.1f}")BIC 是模型复杂度和拟合优度的折中,选 BIC 最低的 K。和 KMeans 的肘部相比,BIC 有明确最小值,不用肉眼找拐点。n_init=5是因为 EM 算法对初始值敏感,多跑几次取最优。如果数据量大,先用 KMeans 的结果初始化 GMM 的均值会更稳:
km_init = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X_scaled) gmm = GaussianMixture(n_components=k, means_init=km_init.cluster_centers_, random_state=42)另外,有人用 orange3 的拖拽式聚类算法做分析,但作业报告要写参数和结果,代码版 sklearn 的可复现性比界面截图强得多,建议还是用代码。
4.3 聚类作业的常见丢分点:归一化、随机种子、聚类评估指标
聚类作业最容易丢分的地方有三个。第一是不归一化,欧氏距离会被量纲大的特征主导;第二是不固定随机种子,KMeans 和 GMM 每次跑出来簇标签都不一样;第三是用准确率评估聚类。聚类给的是簇编号,编号顺序是任意的,直接和真实标签比对准确率毫无意义,要用调整兰德指数 ARI:
from sklearn.metrics import adjusted_rand_score ari = adjusted_rand_score(y_true, km.labels_) print("ARI:", ari)ARI 随机聚类接近 0,完全一致接近 1。如果作业有真实标签,ARI 是比 accuracy 合理得多的评估指标。没有真实标签时,轮廓系数和 BIC 就是主要依据,报告里要写清楚“因为是无监督,所以用轮廓系数而不是准确率”。
5. 常见问题排查:八次作业源码里最容易翻车的五个细节
5.1 数据泄漏与随机种子:先看这两个再谈调参
现象:训练集 R2 0.98,交叉验证只有 0.6;或者同一个代码连续跑两次,结果完全不一样。
原因:前者是在 fit 之前用了全量数据的 StandardScaler,或者 GridSearchCV 里的预处理步骤包含了测试集统计量,这属于数据泄漏。后者是没设 random_state,树模型和 KMeans 的初始化每次不同。
解决:把预处理放进 Pipeline,确保每一折交叉验证只看到训练折的统计量;在模型和数据集划分处统一设 random_state=42。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe = Pipeline([ ('scaler', StandardScaler()), ('model', RandomForestRegressor(random_state=42)) ]) cv_score = cross_val_score(pipe, X_train, y_train, cv=5) print(cv_score.mean())Pipeline 是防止数据泄漏最省心的手段,比手动三步更不容易写错。建议把SEED = 42写在一个 config.py 里,所有文件统一引用,避免不同作业出现十几个不同的随机种子。
5.2 维度爆炸与类别不平衡:SVM和KMeans集体失灵的真凶
现象:特征有几十个,其中一个是“ID”列;或者变量范围一列是 0~1,另一列是 0~1 亿。SVM 跑出来准确率极低,KMeans 的轮廓系数为负。
原因:ID 列每个样本几乎都不同,树模型还能勉强用,但 SVM 的核函数距离计算直接被这种高基数特征主导;量纲差异让距离集中在数值大的特征上,KMeans 的簇结构被压扁。
解决:先删掉 ID、日期这类唯一性特征;数值特征用 StandardScaler。高基数类别特征做 OneHot 后维度爆炸,可以先 PCA 再喂模型,或者直接换随机森林。
df.describe(include='all').T[['count', 'unique', 'std', 'min', 'max']]用这段代码快速扫一遍,发现某列 unique 数量接近样本数,且 std 极大,基本可以判定它在污染模型。这个检查在每份作业跑模型前做一次,能省掉一半调参时间。
5.3 类别不平衡与评估指标:二分类作业里的“准确率陷阱”
现象:正类只有 3%,模型全部预测负类,准确率 97%,报告里写“模型表现优秀”。
原因:没看数据分布,直接用 accuracy 作为评估标准。准确率被大量负类样本拉高了,模型什么都没学到。
解决:用 class_weight='balanced',或者在损失函数里给样本加权重;报告里同时给混淆矩阵、precision、recall、F1、AUC。
from sklearn.utils.class_weight import compute_class_weight weights = compute_class_weight('balanced', classes=np.unique(y), y=y) print(dict(zip(np.unique(y), weights)))compute_class_weight返回每个类别的权重,放进模型样本权重就能缓解不平衡。更简单的做法是直接设class_weight='balanced'。重点不是用哪种,而是要在报告里写清楚“因为数据不平衡,所以用 F1 和 AUC 而不是准确率”,这句话比调参本身更值分。
5.4 手写代码和 sklearn 对不上:先对预测再对权重
现象:自己手写的线性回归权重和 sklearn 算出来差距很大,但 loss 看起来在下降。
原因:可能特征处理不一致,sklearn 默认不标准化,手写代码如果先做了标准化,权重尺度就不同;或者手写代码漏了偏置项,梯度更新符号反了。
解决:对比时统一用同一份数据、同一套预处理,比较预测值而不是权重。权重尺度可以不同,预测值一致就行。
pred_sk = model.predict(X_test) pred_manual = Xb_test @ w print(np.allclose(pred_sk, pred_manual, atol=1e-2))np.allclose返回 True 说明两者预测基本一致。如果 False,大概率是预处理不一致或者梯度方向写反了。这个排查技巧在作业一对拍阶段特别有用。
5.5 模型保存与报告生成:提交作业前最后一道坎
现象:跑完作业忘了保存模型,关闭 notebook 后特征工程全没了,重新跑一遍又发现数据路径不对。
原因:没把数据路径、随机种子、特征处理封装成函数,全部堆在一个单元格里。
解决:用 joblib 保存模型,把公共配置放 config.py。
import joblib joblib.dump(pipe, 'model_rf.pkl') loaded = joblib.load('model_rf.pkl')这一步是“后悔药”。作业提交时,老师要的是能复现的工程,不是只有一个结果文档。把 pipeline 和模型一起 dump,换台机器 load 就能直接预测。保存下来的模型文件还可以拿到报告附件里,证明“这个分数是我亲手跑出来的”。
6. 进阶:把八次作业源码整理成一个能复现的工程模板
八次作业如果能共用一套工程骨架,每次作业只需要替换数据和模型文件,效率和稳定性都会明显提升。我自己的习惯是给每份作业建一个固定目录:config.py统一放 SEED、路径、测试集比例;preprocess.py放数据处理管线;train.py跑训练和交叉验证;report/放生成的图表和指标文本。八次作业共用这套结构,代码量直接砍掉一半,而且每次交作业前只要检查 config 里的随机种子和路径有没有改对就行。
验证方法上,我固定跑“三件套”:第一,训练 loss 曲线确认收敛,没收敛就把学习率调小、轮数调大;第二,交叉验证分数和测试集分数差距不超过 5%,超过就回退参数或者加正则;第三,检查模型保存文件是否有对应的预处理管线,确保下次也能复现。如果作业允许用现代库,可以试试 xgboost 二分类模型和 lightgbm 回归模型,它们内置了正则化和早停,调参压力比随机森林小,但报告里要写清楚训练轮数和树深度,不然老师会认为你在“黑匣子”里跑出来的结果。
有一次我把随机森林的 max_depth 设成 None,训练集 R2 0.995,测试集只有 0.4,从那以后我每次训练完都先打印交叉验证分数,再碰测试集。这个习惯让“高分作业”从玄学变成了按流程走。希望帮到你。
本文还有配套的精品资源,点击获取