算法入门手记:从回归分类到聚类的实战决策指南 1. 这不是教科书是我在带新人时反复打磨的“算法入门手记”我带过三十多个刚转行的数据分析和机器学习新人从零基础文科生到有五年Java开发经验的工程师都有。每次开课前我都会被问同一个问题“老师到底该从哪几个算法开始学网上资料太散书又太厚跑通一个例子要花三天结果发现根本没搞懂它在解决什么问题。”这句话我听了太多遍也反思了太久——问题不在学生而在我们这些老手总把“讲清楚”等同于“堆概念”却忘了初学者最需要的是一条能踩着走、不迷路、还能随时回头验证的脚印。这篇内容就是我把过去十年在一线项目里反复验证、在带教中不断迭代、在深夜debug后拍大腿总结出来的“算法入门手记”。它不叫《机器学习导论》也不叫《Python机器学习实战》它就叫“手记”——因为里面没有一句空话全是我在真实项目里用过的、调过的、坑过的、最终跑通并上线的代码和思路。核心关键词只有一个Algorithms。但这个“算法”不是数学公式里的抽象符号而是你明天就能打开Jupyter Notebook、粘贴运行、看到结果、理解原理、知道什么时候该用、什么时候不该用的具体工具。它适合三类人第一类是完全没碰过代码的业务岗同事比如市场、运营、产品想用数据说话但被“算法”二字吓退第二类是写过Python但没做过建模的开发者知道for循环和函数但面对regr.fit()就发懵第三类是已经学过理论但总卡在“为什么我的模型不准”的同学。它不承诺让你三个月成为算法专家但它能确保你在两周内独立完成从数据加载、特征观察、模型选择、训练评估到结果解读的完整闭环。我试过我的学员也试过实测下来很稳。下面所有内容都围绕一个朴素目标展开让算法从黑箱变成你手边的一把可拆解、可调试、可信赖的螺丝刀。2. 算法不是魔法是解决具体问题的“工具箱”与“决策树”很多人一上来就被“监督学习”“无监督学习”“深度学习”这些大词镇住其实大可不必。在我经手的上百个落地项目里80%以上的业务问题用不到五种基础算法就能覆盖。它们不是并列的“知识点”而是一个有逻辑顺序的“工具箱”。我把它画成一张我每天都在用的决策树这张图不是为了好看而是为了告诉你遇到新问题时第一步永远不是查文档而是问自己三个问题。2.1 你的目标是什么——任务类型决定算法家族这是整个决策树的根节点也是最容易被忽略的起点。我见过太多人数据还没看两眼就急着跑RandomForestClassifier结果发现目标压根不是分类。所以请先拿出一张纸写下你手头任务最直白的描述你想预测一个具体的数字吗比如“下个月销售额是多少”、“这台发动机的CO2排放量预计多少克/公里”、“用户未来30天的活跃天数”。如果是你就在回归Regression家族里找答案。回归的核心是建立输入特征X和连续输出值Y之间的数学关系。你想把东西分门别类吗比如“这张图片是猫还是狗”、“这个客户会流失还是留存”、“这笔交易是正常还是欺诈”。如果是你就在分类Classification家族里找答案。分类的核心是学习一个边界可以是直线、曲线、甚至超平面把不同类别的样本区分开。你想发现数据里隐藏的结构或模式吗比如“我们的用户自然分成几群每群有什么特征”、“服务器日志里哪些行为序列总是同时出现”、“销售数据里哪些商品经常被一起购买”。如果是你就在聚类Clustering或关联Association家族里找答案。这类算法不需要你提前告诉它“应该是什么”它自己从数据里“挖”出规律。提示这个判断必须非常严格。比如“预测用户是否会点击广告”表面看是“是/否”是分类但如果你的目标是“预测点击概率是73.5%”那就是回归。前者输出0或1后者输出0到1之间的小数。选错家族后面所有努力都是南辕北辙。2.2 你的数据长什么样——数据形态决定具体算法确定了任务类型下一步是看你的“原材料”。算法不是万能胶它对数据有明确的“胃口”。对于回归任务如果画出你的特征X和目标Y的散点图发现点大致沿着一条直线分布那线性回归Linear Regression就是你的首选。它简单、快、可解释性强就像一把直尺能快速给你一个基准线。但如果点明显弯成一条弧线比如先缓慢上升然后陡峭加速那线性回归就会“拉直”它造成大量误差。这时你就得考虑多项式回归Polynomial Regression——给直尺加个“弯度调节旋钮”或者如果增长趋势是“越往后越猛”像病毒传播、用户增长那就轮到指数回归Exponential Regression出场它自带“加速器”。对于分类任务如果两类数据在图上能用一条直线大致分开比如身高体重区分男女逻辑回归Logistic Regression是最轻量、最透明的选择。它输出的概率值能直接告诉你模型有多“犹豫”。但如果边界是弯曲的、复杂的比如手写数字识别那单靠一条直线肯定不行就得请出K近邻K-Nearest Neighbors或决策树Decision Tree。前者像“问邻居”后者像“做选择题”思路完全不同但都能处理非线性。对于聚类任务如果你的数据没有标签只有一堆坐标点你想知道它们天然聚成几簇。如果簇的形状是圆形的、大小差不多K-Means就像一个高效的“圆规”能快速画出中心点。但如果簇是长条形的、或者有噪声点比如异常用户行为K-Means就容易“误伤”这时DBSCAN就像一个更聪明的“侦探”能识别出核心区域和边缘点。注意这里说的“画图看”不是可选项是必选项。我带的每个新人第一周作业都是用matplotlib画至少10张散点图、直方图、箱线图。很多问题在图上一眼就能看出端倪。比如你画完ENGINESIZEvsCO2EMISSIONS发现点基本在一条线上那还纠结什么神经网络线性回归就是最合适的起点。2.3 你的业务场景需要什么——落地需求决定技术取舍最后一步也是最关键的一步这个模型要用来干什么这决定了你不能只看准确率。需要向老板解释“为什么”比如你要说服市场部为什么某次促销活动对高净值客户效果更好。这时候一个黑盒的深度学习模型哪怕准确率高5%也毫无价值。你必须用线性回归或决策树因为它们的系数或规则可以直接翻译成“引擎排量每增加1升平均多排放XX克CO2”或“如果用户月均消费5000且注册时长3个月则流失风险高”。可解释性是业务沟通的生命线。需要毫秒级响应比如一个实时反欺诈系统每笔交易要在100毫秒内给出风险评分。这时候一个需要10秒推理时间的复杂模型再准也没用。你得选逻辑回归或轻量级决策树它们的预测速度是以微秒计的。数据会持续更新比如一个推荐系统每天都有新用户、新商品、新行为。如果你用的是需要全量重训的模型那每天凌晨都要停服几小时业务肯定炸锅。这时在线学习Online Learning能力就至关重要而SGDRegressor或PassiveAggressiveClassifier这类算法就是为这种场景设计的。我曾经在一个电商项目里为“猜你喜欢”模块选型。团队一开始狂热追捧协同过滤但上线后发现新用户冷启动问题巨大而且模型更新慢导致首页推荐一周都不变。最后我们砍掉80%的复杂度用一个基于用户基础画像性别、地域、年龄和近期浏览行为的加权线性回归配合简单的实时更新逻辑效果反而更稳、更快、更容易排查问题。这就是“业务场景”对技术选型的终极裁决。3. 回归算法精讲从线性到非线性手把手拆解每一个“为什么”回归是机器学习的基石也是新手最容易上手、最能建立信心的入口。它的核心思想极其朴素找一条线或一个面让它尽可能地“靠近”所有已知的数据点。这条线就是你的模型。下面我将用同一份汽车数据集Fuel.csv带你从最简单的线性回归开始一层层揭开它的面纱直到你能亲手写出并理解多项式回归和指数回归的底层逻辑。3.1 线性回归不只是“画一条直线”而是理解“最佳拟合”的数学本质线性回归的目标是找到一个方程Y β₀ β₁ * X。其中Y是你要预测的目标如CO2排放量X是你的特征如引擎排量β₀是截距当X0时Y的值β₁是斜率X每增加1单位Y平均变化多少。听起来简单但“找到最佳的β₀和β₁”这件事背后有坚实的数学支撑。3.1.1 为什么是“最小二乘法”——误差的几何意义我们希望这条线离所有点都“很近”。但怎么定义“近”一个直观的想法是把每个点到线的垂直距离即残差加起来。但距离是绝对值计算麻烦。于是数学家们选择了更优雅的方式把每个点的残差实际值 - 预测值平方然后求和。这个和叫做残差平方和RSS。RSS Σ(yᵢ - (β₀ β₁ * xᵢ))²为什么要平方第一消除正负号干扰一个点在线上一个点在线下距离抵消了第二放大离群点的影响迫使模型更关注那些“特别不准”的预测从而得到一条更稳健的线。你可以把它想象成在玩一个游戏你画一条线每个点都会根据它离线的远近给你扣分离得越远扣分越狠因为是平方。你的目标就是画出一条让总扣分最少的线。这个“总扣分最少”的解就是最小二乘解。3.1.2 代码实现与关键步骤解析下面这段代码是我要求所有新人必须逐行手敲、并修改注释的“黄金模板”。它不是为了炫技而是为了让你看清每一步在做什么。# 1. 导入核心库pandas处理数据numpy做数值计算matplotlib画图sklearn建模 import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn import linear_model from sklearn.model_selection import train_test_split # 更规范的划分方式 from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 2. 加载并探索数据永远不要跳过这一步 data pd.read_csv(Fuel.csv) print(数据集前5行\n, data.head()) print(\n数据集基本信息) print(data.info()) # 查看字段名、数据类型、非空值数量 print(\n关键字段统计) print(data[[ENGINESIZE, CO2EMISSIONS]].describe()) # 查看引擎排量和排放量的均值、标准差等 # 3. 数据清洗与特征选择现实世界的数据永远不干净 # 检查是否有缺失值 print(\n缺失值检查) print(data.isnull().sum()) # 假设我们发现ENGINESIZE有少量缺失我们选择删除这些行简单粗暴但有效 data data.dropna(subset[ENGINESIZE, CO2EMISSIONS]) # 4. 可视化用眼睛“感受”数据 plt.figure(figsize(10, 6)) plt.scatter(data[ENGINESIZE], data[CO2EMISSIONS], alpha0.6, s30, colorsteelblue) plt.xlabel(Engine Size (L)) plt.ylabel(CO2 Emissions (g/km)) plt.title(Engine Size vs CO2 Emissions - Raw Data) plt.grid(True, alpha0.3) plt.show() # 5. 划分训练集和测试集80%训练20%测试这是行业默认比例 # 使用sklearn的train_test_split它比手动切片更可靠能保证随机性 X data[[ENGINESIZE]] # 特征矩阵注意双括号返回DataFrame y data[CO2EMISSIONS] # 目标向量单括号返回Series X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 6. 创建并训练模型这就是“找最佳β₀和β₁”的过程 regr linear_model.LinearRegression() regr.fit(X_train, y_train) # fit()方法内部就是在解最小二乘方程组 # 7. 解读模型参数这才是业务价值所在 print(f\n模型参数) print(f斜率 (β₁): {regr.coef_[0]:.4f}) # 每升引擎排量平均多排放多少克CO2 print(f截距 (β₀): {regr.intercept_:.4f}) # 当引擎排量为0时模型预测的排放量理论上无意义但数学上需要 # 8. 可视化模型把“线”画出来 plt.figure(figsize(10, 6)) plt.scatter(X_train, y_train, alpha0.6, s30, labelTraining Data, colorsteelblue) plt.plot(X_train, regr.predict(X_train), r-, linewidth2, labelFitted Line) plt.xlabel(Engine Size (L)) plt.ylabel(CO2 Emissions (g/km)) plt.title(Linear Regression Fit) plt.legend() plt.grid(True, alpha0.3) plt.show() # 9. 在测试集上评估这才是模型真实能力的试金石 y_pred regr.predict(X_test) print(f\n模型在测试集上的表现) print(f平均绝对误差 (MAE): {mean_absolute_error(y_test, y_pred):.2f} g/km) print(f均方根误差 (RMSE): {np.sqrt(mean_squared_error(y_test, y_pred)):.2f} g/km) print(fR² 分数: {r2_score(y_test, y_pred):.4f}) # R²分数解读0.85意味着模型解释了测试数据中85%的变异这是一个非常好的结果。实操心得我让新人第一次跑这段代码时必须把X_train和y_train打印出来看几行。很多人以为fit()是个黑盒子其实它只是在后台飞快地解一个方程。当你看到X_train是几十行数字y_train是几十行对应的排放量你就会明白fit()就是在找一个β₀和β₁让(yᵢ - (β₀ β₁*xᵢ))²的和最小。这个过程和你用Excel的“添加趋势线”功能背后的数学是一模一样的。3.2 多元线性回归当一个特征不够用时如何让模型“多看几眼”现实中很少有事情只由一个因素决定。一辆车的CO2排放量不仅和引擎排量有关还和气缸数、城市油耗、高速油耗等息息相关。多元线性回归就是把那个简单的方程Y β₀ β₁ * X扩展成Y β₀ β₁ * X₁ β₂ * X₂ ... βₙ * Xₙ。它不再是二维平面上的一条线而是高维空间中的一个“超平面”。3.2.1 关键洞察系数的正负号就是业务逻辑的“开关”在多元回归中每个系数βᵢ都代表在其他所有特征保持不变的前提下该特征每增加1个单位目标变量Y平均变化多少。这个“其他特征保持不变”的前提是理解多元回归的钥匙。# 继续使用上面的数据 X_multi data[[ENGINESIZE, CYLINDERS, FUELCONSUMPTION_CITY, FUELCONSUMPTION_HWY]] y data[CO2EMISSIONS] X_train_multi, X_test_multi, y_train_multi, y_test_multi train_test_split( X_multi, y, test_size0.2, random_state42 ) regr_multi linear_model.LinearRegression() regr_multi.fit(X_train_multi, y_train_multi) # 把系数整理成表格一目了然 coeff_df pd.DataFrame({ Feature: X_multi.columns, Coefficient: regr_multi.coef_, Interpretation: [ 引擎排量每增加1LCO2排放平均增加...g/km, 气缸数每增加1个CO2排放平均增加...g/km, 城市油耗每增加1L/100kmCO2排放平均增加...g/km, 高速油耗每增加1L/100kmCO2排放平均增加...g/km ] }) print(\n多元线性回归系数详解) print(coeff_df.round(4))输出结果可能类似这样FeatureCoefficientInterpretationENGINESIZE38.2157引擎排量每增加1LCO2排放平均增加38.2157g/kmCYLINDERS12.4568气缸数每增加1个CO2排放平均增加12.4568g/kmFUELCONSUMPTION_CITY19.8742城市油耗每增加1L/100kmCO2排放平均增加19.8742g/kmFUELCONSUMPTION_HWY15.3219高速油耗每增加1L/100kmCO2排放平均增加15.3219g/km注意所有系数都是正的这符合常识——排量越大、气缸越多、油耗越高排放自然越高。但如果某个系数是负的比如-5.23那它的业务含义就是“在其他条件不变的情况下这个特征每增加1个单位CO2排放平均会减少5.23g/km”。这可能揭示了一个隐藏的优化点比如某种新型催化转化器技术。3.2.2 为什么多元回归通常比单变量更准——信息的“复利效应”回到我们的数据单变量线性回归的R²可能是0.78而加入四个特征后的多元回归R²提升到了0.92。这不是偶然。单变量模型只看到了“引擎排量”这一个维度它把所有其他影响因素比如一辆小排量但老旧的车油耗奇高都算作了“误差”。而多元模型把这些因素都纳入了考量相当于把原本属于“噪音”的部分变成了“信号”从而大幅提升了预测精度。这就像一个只看身高来预测篮球水平的人和一个既看身高、又看臂展、又看弹跳、又看投篮命中率的人后者当然更准。3.3 多项式回归当世界不是“直”的如何给模型装上“弯曲”的能力线性回归的假设是“世界是直的”。但现实往往充满曲线。比如一个产品的销量在上市初期增长缓慢大家还在观望中期爆发式增长口碑形成后期增速放缓市场饱和。这种“S”形曲线用一条直线去拟合注定失败。多项式回归就是给线性模型加上“弯曲”的能力。它的核心思想是我不直接用X来预测Y而是用X, X², X³, ... 这些X的幂次作为新的特征再用线性回归去拟合它们。本质上它还是一个线性模型对参数β是线性的但对原始特征X是非线性的。3.3.1 手动实现理解“特征工程”的魔力下面这段代码不依赖sklearn.preprocessing.PolynomialFeatures而是手动构造特征矩阵。这是为了让你彻底看清“多项式”是如何被“线性化”的。# 生成一个典型的非线性数据集y x^3 x^2 x noise np.random.seed(42) x np.linspace(-3, 3, 100) y x**3 x**2 x 2 np.random.normal(0, 2, 100) # 加入噪声 # 手动构造多项式特征我们需要 [1, x, x^2, x^3] X_poly np.column_stack([ np.ones(x.shape), # β₀ 的系数恒为1 x, # β₁ * x x**2, # β₂ * x^2 x**3 # β₃ * x^3 ]) # 现在X_poly 是一个 100x4 的矩阵每一行是 [1, x_i, x_i^2, x_i^3] # 我们对这个矩阵用标准的线性回归来求解 β beta np.linalg.inv(X_poly.T X_poly) X_poly.T y # 正规方程解法 # beta 现在是一个包含4个值的向量[β₀, β₁, β₂, β₃] print(f求解出的多项式系数β₀{beta[0]:.4f}, β₁{beta[1]:.4f}, β₂{beta[2]:.4f}, β₃{beta[3]:.4f}) # 用求出的系数绘制拟合曲线 y_pred beta[0] beta[1]*x beta[2]*x**2 beta[3]*x**3 plt.figure(figsize(10, 6)) plt.scatter(x, y, alpha0.6, s30, labelRaw Data, colorsteelblue) plt.plot(x, y_pred, r-, linewidth2, labelPolynomial Fit (Degree3)) plt.xlabel(X) plt.ylabel(Y) plt.title(Manual Polynomial Regression (Degree 3)) plt.legend() plt.grid(True, alpha0.3) plt.show()关键点解析X_poly矩阵的第一列全是1对应截距项β₀第二列是原始的x对应β₁*x第三列是x²对应β₂*x²第四列是x³对应β₃*x³。是numpy的矩阵乘法符号。np.linalg.inv(A) A b就是求解A x b的标准方法。你看我们并没有发明新的算法只是把一个非线性问题“伪装”成了一个线性问题然后扔给线性回归去解。这就是特征工程的精髓用聪明的方式把原始数据转换成模型能轻松处理的形式。3.3.2 如何选择“弯曲”的程度——过拟合与欠拟合的平衡术多项式的阶数degree是一个关键超参数。阶数太低如degree1就是线性回归会欠拟合——模型太简单抓不住数据的内在规律训练和测试误差都很大。阶数太高如degree10模型会变得过于“灵活”它会努力穿过每一个点甚至包括那些由噪声造成的异常点导致在训练集上误差极小但在从未见过的测试集上误差巨大这就是过拟合。我的经验法则从degree2开始逐步增加同时监控训练误差和测试误差。当测试误差开始上升而训练误差还在下降时就是过拟合的信号此时应停止增加阶数。通常degree2或3就能解决绝大多数实际问题。追求更高的阶数带来的收益远小于其带来的不稳定性和计算开销。3.4 指数回归当增长是“爆炸式”的如何捕捉那种“加速度”指数增长的特征是增长率本身是恒定的。比如一个细菌种群每小时分裂一次那么它的数量就是N N₀ * 2^t。这里的2^t就是指数项。在商业中用户增长、病毒式传播、某些金融产品的复利都遵循这种模式。指数回归的模型是Y a * b^X。它看起来不像线性模型但通过一个巧妙的数学变换我们可以把它“线性化”。3.4.1 “线性化”的奥秘对数的力量对模型两边同时取自然对数lnln(Y) ln(a * b^X) ln(a) ln(b^X) ln(a) X * ln(b)令Y ln(Y),a ln(a),b ln(b)那么原式就变成了Y a b * X这就是一个标准的线性回归方程所以指数回归的实现就是先对目标变量Y取对数然后用线性回归去拟合ln(Y)和X最后再把结果“还原”回来。from scipy.optimize import curve_fit import numpy as np # 模拟一个典型的指数衰减数据比如放射性物质的衰变 t np.arange(0, 10) # 时间点 N 1000 * np.exp(-0.3 * t) np.random.normal(0, 20, len(t)) # 真实值 噪声 # 方法一使用curve_fit更通用推荐 def exp_func(x, a, b): return a * np.exp(b * x) # 注意这里是exp(b*x)不是b^x两者等价但exp形式更常用 popt, pcov curve_fit(exp_func, t, N) a_fit, b_fit popt N_pred_curve exp_func(t, a_fit, b_fit) # 方法二手动线性化加深理解 N_log np.log(N) # 对Y取对数 # 现在对 N_log 和 t 做线性回归 regr_log linear_model.LinearRegression() regr_log.fit(t.reshape(-1, 1), N_log) a_prime regr_log.intercept_ b_prime regr_log.coef_[0] # 还原回原始参数 a_manual np.exp(a_prime) b_manual b_prime N_pred_manual a_manual * np.exp(b_manual * t) print(fcurve_fit拟合参数: a{a_fit:.4f}, b{b_fit:.4f}) print(f手动线性化拟合参数: a{a_manual:.4f}, b{b_manual:.4f}) plt.figure(figsize(10, 6)) plt.scatter(t, N, labelRaw Data, colorsteelblue) plt.plot(t, N_pred_curve, r--, labelcurve_fit Fit) plt.plot(t, N_pred_manual, g-., labelManual Linearization Fit) plt.xlabel(Time (t)) plt.ylabel(Quantity (N)) plt.title(Exponential Regression: Two Approaches) plt.legend() plt.grid(True, alpha0.3) plt.show()实操心得curve_fit是SciPy提供的一个强大工具它能自动处理各种非线性函数的拟合内部使用的是迭代优化算法如Levenberg-Marquardt。而手动线性化的方法虽然步骤多但它完美地展示了“为什么指数回归可行”——因为它本质上还是在解一个线性问题。两种方法的结果应该非常接近这验证了数学原理的普适性。4. 分类与聚类算法从“分门别类”到“发现群体”如果说回归是“预测一个数”那么分类就是“预测一个标签”而聚类则是“发现一群相似的人”。它们构成了机器学习应用的另外两大支柱。下面我将用最贴近业务的案例带你掌握其中最核心的两个算法K近邻KNN和K-Means聚类。4.1 K近邻KNN最“懒”的算法也是最直观的智慧KNN的哲学非常朴素“物以类聚人以群分”。它不做任何复杂的模型训练它的“训练”过程就是把所有已知的、带标签的数据点原封不动地存起来。当有一个新点需要分类时它才开始工作找出离这个新点最近的K个已知点然后看这K个点里哪个类别的票数最多就把新点分到那个类别。4.1.1 为什么KNN是“懒”的——它没有“学习”只有“查找”这与线性回归、决策树等“急切学习Eager Learning”算法截然不同。线性回归在fit()时就计算出了β₀和β₁之后的预测就是简单的乘加运算。而KNN的fit()方法几乎什么都不做它只是把数据存进内存。真正的计算全部发生在predict()的那一刻。这意味着优点模型极其简单没有假设能适应任意复杂的决策边界只要K足够大。缺点预测速度慢每次都要计算距离内存占用大要存所有数据对高维数据效果差“维度灾难”。4.1.2 代码实现与距离度量的深意from sklearn.neighbors import KNeighborsClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import numpy as np # 生成一个模拟的二分类数据集比如区分两种类型的客户 X, y make_classification( n_samples1000, n_features2, # 为了可视化只用2个特征 n_redundant0, n_informative2, n_clusters_per_class1, random_state42 ) # 划分数据集 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42) # KNN对特征的尺度非常敏感必须标准化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意用训练集的均值和标准差来转换测试集 # 创建KNN分类器K5 knn KNeighborsClassifier(n_neighbors5) knn.fit(X_train_scaled, y_train) # 预测 y_pred knn.predict(X_test_scaled) print(fKNN (K5) 在测试集上的准确率: {knn.score(X_test_scaled, y_test):.4f}) # 可视化决策边界仅适用于2D from mlxtend.plotting import plot_decision_regions plt.figure(figsize(10, 6)) plot_decision_regions(X_train_scaled, y_train, clfknn, legend2) plt.xlabel(Feature 1 (scaled)) plt.ylabel(Feature 2 (scaled)) plt.title(KNN Decision Boundary (K5)) plt.show()关键细节StandardScaler是必须的。因为KNN计算的是欧氏距离d √[(x₁-x₂)² (y₁-y₂)²]。如果一个特征的取值范围是0-1000如年收入另一个是0-1如是否结婚那么距离几乎完全由年收入决定婚姻状况这个重要特征就被“淹没”了。标准化Z-score把所有特征都缩放到均值为0、标准差为1让它们在距离计算中拥有平等的“话语权”。4.1.3 如何选择K——交叉验证是你的指南针K值的选择是KNN的命门。K太小如K1模型会过度关注局部噪声导致过拟合决策边界会非常“锯齿状”。K太大如K100模型会变得过于“平滑”把不同类别的区域都混在一起导致欠拟合。我的做法是使用交叉验证Cross-Validation来自动寻找最优K。from sklearn.model_selection import cross_val_score import matplotlib.pyplot as plt # 尝试不同的K值 k_range range(1, 31) cv_scores [] for k in k_range: knn_cv KNeighborsClassifier(n_neighborsk) # 使用5折交叉验证评估准确率 scores cross_val_score(knn_cv, X_train_scaled, y_train, cv5, scoringaccuracy) cv_scores.append(scores.mean()) # 找到最高分对应的K optimal_k k_range[np.argmax(cv_scores)] print(f最优K值: {optimal_k}, 对应的交叉验证准确率: {max(cv_scores):.4f}) # 绘制K值与准确率的关系图 plt.figure(figsize(10, 6)) plt.plot(k_range, cv_scores, bo-) plt.axvline(xoptimal_k, colorred, linestyle--, labelfOptimal K{optimal_k}) plt.xlabel(Value of K for KNN) plt.ylabel(Cross-Validated Accuracy) plt.title(KNN: Varying Number of Neighbors