简介:一份面向希望快速上手机器学习算法的学生与开发者的入门代码包,覆盖BP神经网络(MLP)、KNN回归、SVM超平面、决策树、朴素贝叶斯与逻辑回归六类经典模型,同时涉及回归与分类两大任务,能帮助零基础读者理解算法适用场景并快速搭建可运行的代码框架。压缩包共14个文件,以13个Python脚本为主,另含1个CSV企鹅数据集,整体仅24KB,轻量便携,适合下载后直接练习。已有343人学习,内容不是简单的调用演示,而是包含模型实现与对比:KNN回归展示邻居数量对预测的影响,SVM超平面与软间隔直观体现间隔最大化思想,决策树基于企鹅数据集做多分类,朴素贝叶斯与逻辑回归则在Iris鸢尾花数据上比较效果,还附带LDA降维脚本便于延伸学习。通过亲手运行、修改参数,读者能直观看到不同算法的行为差异,掌握核心概念,适合作为机器学习课程配套练习或自学起步资料。
1. 六个经典模型代码,为什么是机器学习入门的最短路径
把一份机器学习入门代码下载下来,里面有BP神经网络(MLP)、KNN回归、SVM超平面、决策树企鹅数据集、朴素贝叶斯、逻辑回归六个可运行脚本,这大概是很多人第一次接触机器学习的真实状态:跑完了,打印出一个准确率,关掉窗口,什么都没留下。其实这六个模型代码恰好覆盖了监督学习的主线——线性模型、距离模型、间隔模型、树模型、概率模型、神经网络,把它们拆开看,每一段代码都对应一个独立的建模思路。
对刚入门的人来说,最要紧的不是背公式,而是先把「训练—预测—评估」这个应用流程在手里过一遍:数据怎么切分,模型怎么 fit,结果怎么量化。对期末复习、项目速成和转行自学的人,这六个代码是最合适的起步材料,下面我按四条线来拆,每条线都能直接跑、直接改、直接复现。
2. 逻辑回归与 KNN 回归:跑通训练—预测—评估的最小闭环
2.1 逻辑回归:先搞清楚「回归」为什么会出现在分类任务里
逻辑回归是入门代码里最反直觉的一个名字:它明明在解决分类问题,为什么叫回归?因为它本质上是线性回归的方程再加一层 sigmoid 压缩,把实数值压到 0 和 1 之间,变成「属于某个类别的概率」。新手第一个任务不是把准确率刷高,而是理解这个「从连续值到概率」的切换,它决定了整个监督学习的建模视角。
先跑一段最小代码,用鸢尾花的前两个特征、只保留两个类别,这样既快又能直接看到边界:
import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score data = load_iris() X = data.data[:, :2] # 只取前两个特征,方便后面做可视化 y = data.target mask = y < 2 # 只保留 setosa 和 versicolor 两类 X, y = X[mask], y[mask] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42) model = LogisticRegression(C=1.0, solver="lbfgs", max_iter=200) model.fit(X_train, y_train) y_pred = model.predict(X_test) print("acc:", accuracy_score(y_test, y_pred)) print("prob:\n", model.predict_proba(X_test[:3]))这段代码里 fit 做的事情就是在找那条把两类样本分开的直线 wx+b=0,只不过输出时用了 sigmoid 把距离转成概率。predict_proba 输出的每一行是两个概率,比如 [0.12, 0.88] 表示当前样本属于第二类的概率是 0.88,两数相加恒等于 1。这个概率输出很值钱,因为实际业务里你要的不总是「是或否」,而是「有多大的把握」。
三个参数值得第一次就记住:C是正则强度的倒数,C 越小惩罚越强,系数越往 0 收缩,默认 1.0 在大部分小数据集上够用;solver是优化器,小规模数据用 lbfgs 通常比 liblinear 更稳,不用刻意去背优化原理;max_iter决定迭代上限,如果看到 ConvergenceWarning 就把它调到 500 或 1000,而不是直接忽略告警。常见误用是有人把标签编成 0/1 后直接用 LinearRegression 跑,预测值会落到 0 和 1 之外,逻辑回归就是用来修正这个问题的。
| 参数 | 作用 | 翻车场景 |
|---|---|---|
| C | 正则强度倒数,越小收缩越狠 | 调了没效果,因为数据量太小 |
| solver | 优化器选择 | 多分类时不同 solver 收敛速度差很多 |
| max_iter | 最大迭代次数 | 默认 200 不够时打印告警而非报错 |
2.2 KNN 回归:同一个模型换个后缀,就从分类跨界到回归
KNN 是入门代码里最「没脑子」的模型:它根本没有训练参数,fit 的时候只是把训练数据存起来,predict 的时候把测试样本放到特征空间里,找最近的 k 个邻居投票或者取平均。所以你会看到 sklearn 里同时存在 KNeighborsClassifier 和 KNeighborsRegressor,两者机制完全一样,差别只在最后一步——分类数票,回归取均值。
标题里的「KNN回归」可以直接跑在糖尿病数据集上,这个数据是 sklearn 内置的,不需要额外下载。我一般会用不同的 k 值做一次快速扫描,看 R² 和 MAE 怎么变化:
from sklearn.datasets import load_diabetes from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsRegressor from sklearn.metrics import r2_score, mean_absolute_error X, y = load_diabetes(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) for k in [3, 5, 15, 50]: model = KNeighborsRegressor(n_neighbors=k, weights="distance") model.fit(X_train, y_train) y_pred = model.predict(X_test) print(k, "R2=", round(r2_score(y_test, y_pred), 3), "MAE=", round(mean_absolute_error(y_test, y_pred), 3))R² 越接近 1 说明模型解释的方差越大,MAE 是平均绝对误差,两个指标配合看才不会偏信一个。一般 k=5 起步,k 太小模型跟着单个样本抖动,偏差低但方差高;k 太大又会把远处的样本拉进来,边界被抹平。你需要在这四个温度之间找平衡点,而 KNN 是最好的练习材料。
参数方面,n_neighbors是核心;weights默认 uniform,改成 distance 可以让近的邻居话语权更大,回归任务里通常略好;p是距离范数,默认 2 是欧氏距离,p=1 是曼哈顿距离。这里埋一个伏笔:KNN 对特征尺度极敏感,两个特征单位差一个量级,距离计算就被大尺度特征主导,所以后面第 5 章专门写标准化时机的问题。KNN 这种惰性学习的代价是预测时要把样本和全量训练集算一遍距离,数据量过万之后,预测速度会肉眼可见地变慢。
3. SVM 超平面与企鹅数据集决策树:两种边界的画法
3.1 用 SVM 把超平面画出来:线性核背后的间隔逻辑与两个调参重点
SVM 的入门代码最有画面感,因为它找的不是随便一条能分开两类的线,而是间隔最大的那条。线性核的决策边界写出来就是 wx+b=0,超平面两侧各有一片间隔带,落在间隔带边缘上的那些训练样本叫支持向量,去掉它们,边界就会变动;因为它们才是真正决定边界位置的样本,其他样本离得再远也不影响。
入门时我建议把决策边界画出来,而不是只看准确率数字。下面这段代码用了鸢尾花的后两个特征,先标准化再训练,然后在二维网格上把整个平面的预测结果铺出来:
import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.datasets import load_iris X = load_iris().data[:, 2:] # petal length / petal width y = load_iris().target X, y = X[y != 0], y[y != 0] # 只留两类,让线性边界有意义 scaler = StandardScaler().fit(X) # 演示用全量数据,正确流程见第5章 X_s = scaler.transform(X) model = SVC(kernel="linear", C=0.5) model.fit(X_s, y) xx, yy = np.meshgrid( np.linspace(X_s[:, 0].min() - 0.5, X_s[:, 0].max() + 0.5, 200), np.linspace(X_s[:, 1].min() - 0.5, X_s[:, 1].max() + 0.5, 200)) Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape) plt.contourf(xx, yy, Z, alpha=0.3) plt.scatter(X_s[:, 0], X_s[:, 1], c=y, edgecolor="k") plt.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=100, facecolors="none", edgecolors="r") plt.show()画出来之后你会第一次直观看到「超平面」长什么样:contourf 铺出两个颜色的区域,红色空心圈标出支持向量。SVC 默认的 kernel 其实是 rbf,但线性核适合作为第一个版本,因为它的边界就是一条直线,支持向量的概念也最清楚。把 C 从 0.01 调到 100,你会看到间隔带宽度在变:C 越小,模型越能容忍分错的点,间隔越宽;C 越大,越追求把所有点都分对,间隔变窄,过拟合风险上升。
两个调参重点务必记住。第一是标准化对 SVM 来说是必选项,不是可选项:间隔大小和权向量长度直接相关,特征 A 如果单位是毫米、特征 B 是千克,算出来的间隔会被大数特征带偏,模型就像瞎了一只眼。第二是 rbf 核还有一个 gamma 参数,控制单一样本的影响半径,gamma 过大容易把决策边界切成一个个小岛,过小则边界过于平滑。在 sklearn 的默认设置里 gamma 通常按特征数自动缩放,新手阶段不要急着动它,先把 C 和标准化这两件事做对。
3.2 企鹅数据集跑决策树:处理缺失值和离散特征,画一棵能读的树
企鹅数据集这几年几乎取代鸢尾花成了入门标配,原因很实际:它自带缺失值、自带类别特征、自带三分类不平衡,比鸢尾花更接近真实表格数据。决策树在它上面的表现也很有讲解价值——树模型不要求标准化,尺度不敏感,这让它和 SVM 形成了鲜明对比。
完整代码里有一个隐藏的步骤,是把 island 和 sex 这两个离散列编码成数值,再用 plot_tree 把树画出来:
import seaborn as sns import pandas as pd from sklearn.tree import DecisionTreeClassifier, plot_tree from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score import matplotlib.pyplot as plt df = sns.load_dataset("penguins").dropna() df["island"] = df["island"].astype("category").cat.codes df["sex"] = df["sex"].astype("category").cat.codes features = ["island", "bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g", "sex"] X = df[features] y = df["species"].astype("category").cat.codes X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=0) tree = DecisionTreeClassifier(max_depth=3, min_samples_leaf=5, random_state=0) tree.fit(X_train, y_train) print("train acc:", accuracy_score(y_train, tree.predict(X_train))) print("test acc:", accuracy_score(y_test, tree.predict(X_test))) plt.figure(figsize=(14, 6)) plot_tree(tree, feature_names=features, class_names=df["species"].unique(), filled=True) plt.show()提示:seaborn 首次加载企鹅数据集会从远端拉取,网络不通时可以先手动保存一份 CSV,再用 pandas 的 read_csv 读进来,后续逻辑完全一致。
代码里两个参数是关键。max_depth=3 限制树的层数,让整棵树不超过 8 个叶子,画出来能一眼看完整;如果完全不设,决策树会一直长到每个叶子都是纯类,训练集准确率奔着 100% 去,测试集却一团糟。min_samples_leaf=5 要求每个叶子至少 5 个样本,这能拦住那些只覆盖一两个异常点的分裂路径。树模型还有一个额外好处:训练后直接看 tree.feature_importances_,就能知道 bill_length_mm 和 body_mass_g 谁对区分企鹅种类贡献更大,这种可解释性是 SVM 给不了的。
决策树对噪声数据敏感这一条,需要在新手阶段就建立认知:它每次分裂只在单一特征上找一个切分点,这种「贪婪」策略意味着样本一抖动、顺序一改变,树结构就可能大变。这也是为什么第 5 章里我会单独写它的过拟合问题,真实项目里很少有人直接拿单棵决策树上线,但理解它会让你后面学随机森林轻松很多。
4. 朴素贝叶斯与 MLP 神经网络:概率假设和特征学习,入门代码里的两个极端
4.1 朴素贝叶斯:用条件独立假设把一个分类问题算到底
朴素贝叶斯是入门代码里最不像「学习」的模型:它没有梯度下降,也没有损失函数,训练过程本质上就是统计每个类别下每个特征的均值和方法。 GaussianNB 假设特征在给定类别下服从正态分布,然后用贝叶斯定理把每个类别的后验概率算出来,哪个大就分到哪一类。
代码短的出乎意料:
from sklearn.naive_bayes import GaussianNB from sklearn.model_selection import train_test_split from sklearn.datasets import load_iris from sklearn.metrics import accuracy_score X, y = load_iris(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42) model = GaussianNB() model.fit(X_train, y_train) print("acc:", accuracy_score(y_test, model.predict(X_test))) print("class prior:", model.class_prior_) print("theta (每个类别特征均值):\n", model.theta_)训练完看 model.theta_,那是一个 3×4 的矩阵,每一行是某个鸢尾花类别下四个特征的均值,这就是「生成式模型」的含义:它先估计每个类别长什么样,再反过来判断新样本属于谁。对比之下,第 2 章的逻辑回归是判别式模型,直接学类别之间的边界,不关心每一类内部的特征分布。这个差别在入门阶段容易被忽略,但它决定了两个模型在不同数据上的命运。
朴素贝叶斯的核心假设是条件独立,即「在已知类别的前提下,各特征之间互不影响」。真实数据里这个假设几乎总是被违反,但模型往往还能给出不错的分类结果,尤其在文本分类这种高维稀疏场景里。如果你在跑新闻分类、垃圾邮件识别这类任务,留意 MultinomialNB,它是专门处理词频计数的变体。
部分同学会在这里把朴素贝叶斯和贝叶斯网络混为一谈:贝叶斯网络属于概率图模型,特征是图的节点,节点之间可以显式连边表达依赖关系,常用于因果推断;而朴素贝叶斯只有一个「类别节点指向所有特征节点」的放射状结构,名字里的「朴素」就是指那条强到站不住脚的独立假设。所以严格回答「贝叶斯网络是机器学习吗」:它属于更宽的概率图模型领域,但经常作为机器学习的进阶内容出现,和入门代码里的朴素贝叶斯不是同一个算法。
4.2 BP 神经网络(MLP):从网络结构图到 sklearn 的三层参数
很多人第一次对神经网络产生兴趣,是因为看到了一张 BP 网络结构图:输入层、两个隐藏层、输出层,箭头密密麻麻连成网。当年看这类结构图时,我最关心的就是「这张图怎么变成能跑的代码」。在 sklearn 里,MLPClassifier 用 hidden_layer_sizes 这一个参数就把图的结构定义了,剩下的事情交给优化器。
用内置的手写数字数据集跑一个三层网络,顺带把训练损失曲线画出来,这是验证「黑匣子」有没有在工作最直接的办法:
from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.neural_network import MLPClassifier from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt X, y = load_digits(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) scaler = StandardScaler().fit(X_train) # 只拟合训练集,原因见第5章 X_train = scaler.transform(X_train) X_test = scaler.transform(X_test) mlp = MLPClassifier(hidden_layer_sizes=(64, 32), activation="relu", solver="adam", max_iter=500, random_state=42) mlp.fit(X_train, y_train) print("acc:", mlp.score(X_test, y_test)) plt.plot(mlp.loss_curve_) plt.xlabel("iteration") plt.ylabel("loss") plt.show()hidden_layer_sizes=(64, 32) 翻译成结构图就是两个隐藏层,第一层 64 个神经元,第二层 32 个,数字从 64 到 32 是一种刻意设计的「压缩」,让网络被迫寻找更紧凑的特征表达。反向传播在这里由 solver="adam" 自动完成,sklearn 把梯度计算、权值更新、学习率调度全部包在 fit 里面了,入门代码不需要你手写反传,但你要知道 loss_curve_ 这个东西:训练损失下降越快,说明网络学得越顺利;如果是一条平线或者剧烈震荡,先检查是不是忘了标准化,再考虑调学习率。
参数上有几个值得记的默认值:max_iter 默认 200,对 digits 这类数据常常不够,我一般先给 500;learning_rate_init 默认 0.001,损失曲线震荡得厉害时可以降到 0.0001;activation 默认 relu,除非做某些特定任务,否则不用换。最后一个认知层面的提醒:MLP 在小数据集上未必能赢过逻辑回归或 SVM,入门阶段别把神经网络当银弹,它只是证明「当特征被自动学习而不是手工设计」时,模型能走多远的一个起点,也因为这个特性,它调整参数时最容易变成玄学——我的做法是先固定标准化、结构、随机种子这三件事,再去看曲线说话。
5. 入门代码避坑指南:标准化时机、数据泄漏、随机种子与高维失效
5.1 标准化时机错了:跨模型对比全变虚高
现象:在跑 SVM 和 MLP 的时候,习惯性地把 StandardScaler 拿到 train_test_split 之前对全量数据 fit 了一次,然后发现测试集准确率高得可疑,跨模型对比时所有模型不分高下,全都虚高。
原因:scaler 在全量数据上拟合时,已经「看过」测试集的特征分布,测试集的信息通过均值和方法提前流进了训练环节,这叫数据泄漏。它不会让 fit 报错,因为 sklearn 不在乎数据来源,它只会让分数变得不真实,等模型上线面对新数据时,性能立刻回到地面。
解决:严格遵循先切分、再标准化的顺序,更好的做法是把 StandardScaler 塞进 Pipeline,这样每一折交叉验证里它都只在训练子集上拟合:
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC pipe = make_pipeline(StandardScaler(), SVC(C=1.0)) pipe.fit(X_train, y_train) y_pred = pipe.predict(X_test)逻辑说明:make_pipeline 把标准化和模型绑成一个整体,fit 的时候先算训练集均值和方差,再用同一组数值去 transform 测试集,整个过程不会让测试集参与 scaler 的拟合。参数不需要额外设置,scaler 跟随管道的 cv 折叠自动隔离。要注意的是目标变量 y 不要做标准化,分类标签编码后直接使用,回归任务如果对 y 做标准化,记得预测完要反变换回去。
5.2 随机种子不固定:调参结果凭什么复现
现象:同一段代码连续跑两遍,准确率不一样;调了一个参数,分数涨了 0.02,但换个机器跑又掉回去,最后分不清是模型变好还是运气变好。
原因:train_test_split 默认会打乱数据,模型初始化也带随机性,MLP 的权值初始化和数据 shuffle 尤其明显。所谓「机器学习模型的随机性」,有相当一部分不是算法带来的,而是数据划分顺序带来的。
解决:把随机种子当成后悔药来用。train_test_split 里固定 random_state=42;模型构造时也固定 random_state;做交叉验证时用 KFold(shuffle=True, random_state=42) 或 StratifiedKFold 保持每一折划分稳定。这样做的最大价值不是消除随机性,而是让调参实验变得可对比——你改一个参数,分数变化才真正来自这个参数。建议把所有固定种子的代码放到统一配置区,不要藏在各段 demo 里。
5.3 决策树不设深度上限:训练集满分背后的过拟合
现象:决策树在训练集上准确率 100%,测试集掉到八成以下,plot_tree 画出来的图又宽又深,一个树叶子上只有一个样本。
原因:决策树默认会一直分裂到所有叶子都纯为止,它对训练数据里的每一个噪声都「鞠躬尽瘁」。决策树对噪声数据敏感这件事在入门阶段尤其明显:一个异常样本的存在,就可能让某条分裂路径彻底改变,因为树每次只在一个特征上找最优切分点,局部最优很容易被噪声带偏。
解决:先把 max_depth 控制在 3 到 5,把 min_samples_leaf 设置在 5 到 10,然后再谈调参。更系统的做法是用 GridSearchCV 对这两个参数做网格搜索,评分用交叉验证。如果一个数据集上决策树被限定深度后分数明显下滑,通常是特征本身噪声太大,而不是树不够深——这时候优先考虑特征筛选,而不是通过加深树叶来硬顶分数。
5.4 KNN 在高维下距离失真:特征越多,邻居越远
现象:KNN 回归在 8 个特征时效果尚可,把特征加到 40 个之后,R² 反而一路下滑,而且不管怎么调 n_neighbors 都没有起色。
原因:这是维度灾难的典型表现。高维空间里所有样本之间的距离趋向于均匀分布,最近的邻居和最远的邻居差距变得很小,「最近邻」这个概念本身失去意义。KNN 这类完全依赖距离度量的算法,是受维度灾难冲击最明显的模型。
解决:先做特征选择,用 SelectKBest 按方差或互信息筛掉无关特征,或者用 PCA 降维后再喂给 KNN。把距离度量从默认的欧氏距离 p=2 改成曼哈顿距离 p=1,能在部分场景下延缓退化,但治标不治本。我的习惯是:任何新数据集跑 KNN 之前,先看一眼特征数量和量纲,如果特征超过 20 个,优先换模型或者先降维。这也是为什么整个入门代码集合里,KNN 永远只在小规模低维数据上表现稳定。
6. 把六个模型收进同一份工作流:用 Pipeline 统一评估与选型
6.1 一份脚本对比五个分类器,回归评估单独换指标
跑完六个模型代码之后,最容易陷入的误区是拿六组分数直接比大小,但每段代码的数据切分、随机种子、预处理方式都不一样,分数根本没有可比性。我现在的习惯是把所有分类模型收进同一份评估脚本,用一致的交叉验证和一致的标准化管道去测:
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import cross_val_score, StratifiedKFold from sklearn.linear_model import LogisticRegression from sklearn.neighbors import KNeighborsClassifier from sklearn.svm import SVC from sklearn.tree import DecisionTreeClassifier from sklearn.naive_bayes import GaussianNB from sklearn.neural_network import MLPClassifier from sklearn.datasets import load_iris X, y = load_iris(return_X_y=True) models = { "LogisticRegression": LogisticRegression(max_iter=500), "KNN": KNeighborsClassifier(n_neighbors=5), "SVM": SVC(kernel="rbf", C=1.0), "DecisionTree": DecisionTreeClassifier(max_depth=3, min_samples_leaf=5, random_state=0), "NaiveBayes": GaussianNB(), "MLP": MLPClassifier(hidden_layer_sizes=(32,), max_iter=500, random_state=0), } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) for name, model in models.items(): pipe = make_pipeline(StandardScaler(), model) scores = cross_val_score(pipe, X, y, cv=cv, scoring="accuracy") print(f"{name}: {scores.mean():.3f} ± {scores.std():.3f}")make_pipeline 在这里的核心价值是保证标准化只在每一折的训练子集上拟合,彻底规避第 5 章的数据泄漏;StratifiedKFold 保证每折里三个类别的比例和全量数据一致,避免因为某折恰好缺了一个类别导致分数跳水。单次 train_test_split 只能说明这一种划分下的表现,5 折均值的方差一出来,哪个模型是碰巧走运、哪个模型是真的稳健,一目了然。
如果你要验证的是标题里的 KNN 回归,只需要把 KNeighborsClassifier 换成 KNeighborsRegressor,把 scoring 换成 "r2",模型字典里再放一个 Ridge 做参照,回归对比就成立了——本质上还是同一套邻居机制,只是预测值从类别变成连续数。我最早学的时候是六个文件分开跑,跑完感觉什么都会了,后来把模型放进统一工作流,才发现逻辑回归在这个数据上一点都不比神经网络差;把评估流程固定下来,比调任何一个模型的参数都重要。希望帮到你。
本文还有配套的精品资源,点击获取