Python机器学习入门实战:线性回归、KNN与决策树全流程手把手

发布时间:2026/6/26 0:58:35
Python机器学习入门实战:线性回归、KNN与决策树全流程手把手 1. 这不是“算法大全”而是一份能让你真正跑通第一个模型的Python实战手记我带过几十期机器学习入门训练营最常听到的一句话是“看了十篇‘十大算法详解’连iris数据集都跑不起来。”问题不在人而在绝大多数所谓“入门教程”把重点放在了名词解释和公式推导上却跳过了最关键的环节如何让代码在你自己的电脑上动起来看到第一行预测结果理解每一个参数改动带来的真实变化。这篇内容就是为解决这个卡点而写的。它聚焦于Machine Learning (ML) Algorithms For Beginners with Code Examples in Python这个核心命题不讲抽象理论只讲你打开Jupyter Notebook后从import numpy as np开始到print(Accuracy:, accuracy_score(y_test, y_pred))结束的完整闭环。你会用到的工具只有scikit-learn、pandas、matplotlib这三驾马车所有代码都经过2024年最新版库的实测验证没有一行是“理论上可行”。它适合零基础但会写几行Python的转行者也适合学过统计但没碰过sklearn的分析师——只要你需要在接下来三个月内独立完成一个能放进简历里的小项目这篇就是你的操作手册。它不承诺让你成为算法专家但能确保你亲手把线性回归、决策树、KNN这三个最常用、最能体现ML思维的算法从数据加载、特征处理、模型训练、评估到结果可视化全流程走通一遍并且清楚知道每一步背后“为什么必须这么做”。2. 整体设计思路用“最小可行闭环”代替“知识图谱”2.1 为什么只选这3个算法而不是10个很多教程一上来就列“监督学习、无监督学习、强化学习”再分“分类、回归、聚类、降维”最后塞进SVM、XGBoost、LSTM……这就像教人骑自行车先发一本《空气动力学原理》和《金属疲劳分析》再让人自己组装车架。对初学者而言算法数量不是关键理解“建模流程”的肌肉记忆才是核心资产。我们只选三个算法是因为它们完美覆盖了ML最基础的三种范式且实现门槛极低线性回归Linear Regression代表“数值预测”范式。它结构最简单参数含义最直观斜率、截距误差计算MSE一目了然。它是所有复杂回归模型的“地基”不理解它后续的梯度下降、正则化都是空中楼阁。K近邻K-Nearest Neighbors, KNN代表“基于实例”的懒惰学习范式。它没有“训练”过程只有“预测”时才计算距离。这彻底打破了“模型必须先训练”的思维定式让你明白ML的本质是寻找数据间的相似性而非拟合一个函数。决策树Decision Tree代表“可解释性”范式。它的结构就是一棵树每个节点是一个if-else判断最终叶子节点给出预测结果。你能直接画出来、讲给别人听这是其他黑箱模型做不到的。它也是随机森林、XGBoost等强大集成模型的“砖块”。提示选择这三个不是因为它们“最好”而是因为它们“最能教学”。它们像三把不同形状的钥匙分别打开了“预测数值”、“寻找相似”、“做出可解释决策”这三扇门。掌握这三把钥匙后面开任何锁都只是组合与升级。2.2 为什么所有代码都基于scikit-learn而不是从零手写有人会问“不手写梯度下降怎么能叫懂机器学习”这个问题很深刻但答案也很务实初学者的第一目标不是造轮子而是学会开车。手写一个完整的逻辑回归你需要处理矩阵运算、求导、迭代收敛、学习率调优……这些细节会瞬间淹没你对“特征工程”、“过拟合”、“交叉验证”等更高阶概念的理解。scikit-learn是工业界事实标准它的API设计遵循“fit-predict-transform”这一黄金法则简洁、一致、健壮。用它你能把90%的精力放在“数据怎么准备”、“模型怎么评估”、“结果怎么看”上这才是业务场景中的真实工作流。等你用scikit-learn跑通了10个项目再回过头手写一个线性回归那种“原来如此”的顿悟感远比一开始就硬啃数学推导来得深刻。2.3 为什么数据集只用内置的不搞网络爬虫或复杂清洗新手最大的挫败感往往来自“环境配置失败”和“数据加载报错”。一个FileNotFoundError: [Errno 2] No such file or directory: data.csv就能让一个下午的努力付诸东流。因此本篇所有示例均使用scikit-learn内置的经典数据集make_regression生成回归数据、make_classification生成分类数据、load_iris鸢尾花。它们无需下载调用即得格式统一X为二维数组y为一维数组完美规避了路径、编码、缺失值等一切外部干扰。这不是偷懒而是把认知带宽精准地分配给最该学习的地方——算法逻辑本身。等你建立起信心和手感再挑战真实世界的数据会事半功倍。3. 核心细节解析从“能跑”到“跑得明白”的关键参数与陷阱3.1 线性回归别被“线性”二字骗了它也能拟合曲线很多人以为线性回归只能画一条直线这是最大的误解。它的“线性”指的是参数权重w和偏置b是线性的而不是指输入特征x必须是线性的。这意味着我们可以通过构造新的特征来让它拟合复杂的非线性关系。比如想预测房价除了面积x1我们还可以手动添加面积的平方x1²、房间数x2、房龄x3等。模型依然是y w1*x1 w2*x1² w3*x2 w4*x3 b所有w都是线性的所以它还是线性回归。# 实操示例用线性回归拟合一个抛物线 from sklearn.linear_model import LinearRegression from sklearn.preprocessing import PolynomialFeatures import numpy as np import matplotlib.pyplot as plt # 生成一个带噪声的抛物线数据y x² 2x 1 noise np.random.seed(42) X np.linspace(-3, 3, 100).reshape(-1, 1) y X.ravel()**2 2*X.ravel() 1 np.random.normal(0, 2, 100) # 关键点1PolynomialFeatures将原始特征X转换为[X, X²] poly PolynomialFeatures(degree2, include_biasFalse) X_poly poly.fit_transform(X) # X_poly 的形状是 (100, 2)列分别是 x 和 x² # 关键点2LinearRegression 拟合的是 w1*x w2*x² b model LinearRegression() model.fit(X_poly, y) # 预测并绘图 y_pred model.predict(X_poly) plt.scatter(X, y, alpha0.6, labelData) plt.plot(X, y_pred, r-, labelFitted Parabola) plt.legend() plt.show() print(f模型系数: w1 (x) {model.coef_[0]:.2f}, w2 (x²) {model.coef_[1]:.2f}, b {model.intercept_:.2f}) # 输出w1 (x) 2.05, w2 (x²) 1.00, b 1.02 —— 完美逼近真实参数注意PolynomialFeatures(degree2)这一步是“特征工程”的典型操作。它没有改变模型而是改变了输入。初学者常犯的错误是看到拟合效果不好就去调LinearRegression的参数却忘了先检查特征是否足够表达数据的内在规律。这就是为什么我们强调数据和特征永远比模型选择更重要。3.2 KNNK值不是越大越好也不是越小越好它是个“平衡术”KNN的唯一超参数K决定了预测时参考几个邻居。K1就是“最近的那个人是谁我就跟谁一样”这会导致模型对噪声极度敏感决策边界非常锯齿化过拟合风险极高。K很大比如K100那预测结果就变成了整个训练集的“平均意见”过于平滑把所有细微差别都抹平了导致欠拟合。那么K该怎么选没有银弹只有交叉验证。我们不能凭感觉而要用数据说话。具体做法是把训练集再分成几份比如5份轮流用其中4份训练1份验证记录每次的准确率最后取平均。对不同的K值重复这个过程画出“K值 vs 验证准确率”的曲线那个让验证准确率最高的K就是我们要找的“甜点”。from sklearn.model_selection import cross_val_score, StratifiedKFold from sklearn.neighbors import KNeighborsClassifier from sklearn.datasets import make_classification # 生成一个二分类数据集 X, y make_classification(n_samples1000, n_features2, n_redundant0, n_informative2, n_clusters_per_class1, random_state42) # 尝试K从1到20 k_range range(1, 21) cv_scores [] # 使用分层K折交叉验证保证每一折里正负样本比例一致 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) for k in k_range: knn KNeighborsClassifier(n_neighborsk) # cross_val_score 返回一个包含5个分数的数组我们取平均 scores cross_val_score(knn, X, y, cvcv, scoringaccuracy) cv_scores.append(scores.mean()) # 找到最佳K best_k k_range[np.argmax(cv_scores)] print(f最佳K值: {best_k}, 对应的平均交叉验证准确率: {max(cv_scores):.3f}) # 绘图 plt.plot(k_range, cv_scores, bo-) plt.axvline(xbest_k, colorr, linestyle--, labelfBest K{best_k}) plt.xlabel(K Value) plt.ylabel(Cross-Validated Accuracy) plt.title(KNN: Varying Number of Neighbors) plt.legend() plt.grid(True) plt.show()实操心得我见过太多人把K设为1然后抱怨“KNN太不稳定”。其实K1只是KNN的一个极端特例它更像是一个“最近邻搜索”工具而不是一个稳健的分类器。在实际项目中K值通常在3-15之间。记住一个经验法则K值一般取训练样本数的平方根再向下取整。比如你有100个训练样本√10010那么K7或9就是不错的起点。3.3 决策树深度不是越深越好“剪枝”才是真功夫决策树最大的魅力是可解释性但最大的陷阱是过拟合。一棵深度无限的树可以把训练集的每一个样本都完美分类甚至记住每一个噪声点。结果就是在训练集上准确率100%在测试集上惨不忍睹。解决之道就是“剪枝”Pruning。scikit-learn提供了多种剪枝策略最常用、最直观的是max_depth: 树的最大深度。设为3意味着从根节点开始最多只能分3次叉。min_samples_split: 内部节点再划分所需最小样本数。设为10意味着一个节点如果少于10个样本就不再往下分了。min_samples_leaf: 叶子节点最少样本数。设为5意味着任何一个叶子节点里至少要有5个样本。这些参数不是孤立的它们相互影响。比如max_depth3和min_samples_split10同时设置模型会优先满足max_depth的限制。选择哪个参数作为主控取决于你的数据规模和业务需求。from sklearn.tree import DecisionTreeClassifier, plot_tree from sklearn.datasets import load_iris import matplotlib.pyplot as plt # 加载经典鸢尾花数据集 iris load_iris() X, y iris.data, iris.target # 创建两个不同复杂度的树进行对比 tree_shallow DecisionTreeClassifier(max_depth2, random_state42) tree_deep DecisionTreeClassifier(max_depth5, random_state42) tree_shallow.fit(X, y) tree_deep.fit(X, y) # 计算它们在训练集和测试集上的表现这里用留出法简单起见 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42, stratifyy) y_pred_shallow_train tree_shallow.predict(X_train) y_pred_shallow_test tree_shallow.predict(X_test) y_pred_deep_train tree_deep.predict(X_train) y_pred_deep_test tree_deep.predict(X_test) print(浅层树 (max_depth2):) print(f 训练集准确率: {sum(y_pred_shallow_train y_train)/len(y_train):.3f}) print(f 测试集准确率: {sum(y_pred_shallow_test y_test)/len(y_test):.3f}) print(\n深层树 (max_depth5):) print(f 训练集准确率: {sum(y_pred_deep_train y_train)/len(y_train):.3f}) print(f 测试集准确率: {sum(y_pred_deep_test y_test)/len(y_test):.3f}) # 可视化浅层树更清晰 plt.figure(figsize(12, 8)) plot_tree(tree_shallow, feature_namesiris.feature_names, class_namesiris.target_names, filledTrue, roundedTrue, fontsize10, max_depth2) plt.title(Decision Tree (max_depth2)) plt.show()注意运行这段代码你会看到一个惊人的现象深层树在训练集上准确率接近1.000但在测试集上可能反而低于浅层树。这就是过拟合的铁证。模型的终极目标不是在已知数据上表现多好而是在未知数据上预测多准。所以永远要同时关注训练集和测试集或验证集的指标。一个只看训练集准确率的模型就像一个只会背答案、不会解题的学生毫无价值。4. 完整实操流程从零开始构建你的第一个端到端ML项目4.1 项目背景与数据准备用“波士顿房价”理解真实回归任务我们将以经典的“波士顿房价”数据集为例构建一个完整的房价预测项目。这个数据集包含506个房屋样本每个样本有13个特征如犯罪率、房间数、高速公路可达性等目标是预测房价中位数单位千美元。虽然该数据集因伦理问题已被scikit-learn弃用但其结构和挑战对初学者依然极具教学价值。我们将使用一个功能完全相同的替代品fetch_california_housing。# 步骤1导入所有必需的库 import numpy as np import pandas as pd from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error import matplotlib.pyplot as plt import seaborn as sns # 步骤2加载数据 # fetch_california_housing 是波士顿的现代替代品数据更干净无伦理争议 housing fetch_california_housing() X, y housing.data, housing.target # 步骤3探索性数据分析EDA—— 这步绝不能跳 print(数据集形状:, X.shape) print(特征名称:, housing.feature_names) print(目标变量范围:, y.min(), to, y.max()) print(\n目标变量房价分布:) print(pd.Series(y).describe()) # 可视化目标变量分布 plt.figure(figsize(10, 6)) sns.histplot(y, kdeTrue, bins50) plt.title(Distribution of House Prices (in $1000s)) plt.xlabel(Price) plt.show()实操心得这一步看似简单却是项目成败的关键。我曾帮一个学员debug他花了两天时间调参最后发现问题是目标变量y里有大量0值代表数据缺失而他直接把这些0当成了真实的低价房。EDA不是形式主义它是和数据对话的过程。通过describe()你能一眼看出数据是否有异常值、是否严重偏态通过直方图你能判断目标变量是否需要做对数变换比如房价通常右偏log(y)后更接近正态分布有利于线性模型。4.2 特征工程与数据预处理标准化不是“锦上添花”而是“雪中送炭”线性回归和KNN对特征的量纲极其敏感。想象一下一个特征是“房间数”范围1-10另一个是“人口”范围1000-50000。在计算KNN的距离时人口这个大数字会完全主导距离计算房间数的微小差异变得毫无意义。同样线性回归的梯度下降过程也会因为量纲差异巨大而变得极其缓慢甚至不收敛。解决方案就是标准化Standardization将每个特征减去其均值再除以其标准差使其均值为0标准差为1。# 步骤4划分训练集和测试集70%训练30%测试 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) # 步骤5标准化——注意必须只用训练集的均值和标准差来转换测试集 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # fit AND transform on training set X_test_scaled scaler.transform(X_test) # ONLY transform on test set print(标准化前训练集第一个特征的均值/标准差:, X_train[:, 0].mean(), X_train[:, 0].std()) print(标准化后训练集第一个特征的均值/标准差:, X_train_scaled[:, 0].mean(), X_train_scaled[:, 0].std()) print(标准化后测试集第一个特征的均值/标准差:, X_test_scaled[:, 0].mean(), X_test_scaled[:, 0].std()) # 输出显示训练集均值≈0标准差≈1测试集均值≈0但不精确为0标准差≈1。这正是我们想要的。提示scaler.fit_transform(X_train)和scaler.transform(X_test)的区别是初学者最容易混淆的点。fit_transform是学习训练集的统计量均值、标准差并立即应用transform是用之前学到的统计量去处理新数据。如果你对测试集也用fit_transform那就等于“偷看了答案”因为你在测试集上重新计算了均值和标准差这会导致模型在真实部署时表现失真。永远记住所有预处理步骤的“fit”动作只能在训练集上发生一次。4.3 模型训练、预测与评估不止要看准确率更要懂指标背后的业务含义训练完模型得到预测结果这只是开始。如何评价这个结果的好坏不能只看一个数字而要结合多个指标从不同角度审视。均方误差MSE预测值与真实值之差的平方的平均值。它对大误差非常敏感因为平方放大了误差是优化目标。均方根误差RMSEMSE的平方根。它的单位和目标变量一致这里是千美元所以业务解读更直观。RMSE3.0意味着平均每个预测偏差约3000美元。平均绝对误差MAE预测值与真实值之差的绝对值的平均值。它对异常值不敏感更稳健。R²分数决定系数表示模型解释了目标变量多少比例的方差。0表示模型不比用均值预测更好1表示完美预测。# 步骤6创建、训练并预测 model LinearRegression() model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) # 步骤7计算并打印所有关键评估指标 mse mean_squared_error(y_test, y_pred) rmse np.sqrt(mse) mae mean_absolute_error(y_test, y_pred) r2 r2_score(y_test, y_pred) print( 模型评估报告 ) print(f均方误差 (MSE): {mse:.3f}) print(f均方根误差 (RMSE): {rmse:.3f} (千美元)) print(f平均绝对误差 (MAE): {mae:.3f} (千美元)) print(fR² 分数: {r2:.3f}) # 步骤8可视化预测结果——这是最有力的沟通方式 plt.figure(figsize(12, 5)) # 子图1预测值 vs 真实值散点图 plt.subplot(1, 2, 1) plt.scatter(y_test, y_pred, alpha0.6) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--, lw2) plt.xlabel(True Values (1000$)) plt.ylabel(Predictions (1000$)) plt.title(Predictions vs True Values) # 子图2残差图预测误差 plt.subplot(1, 2, 2) residuals y_test - y_pred plt.scatter(y_pred, residuals, alpha0.6) plt.axhline(y0, colorr, linestyle--) plt.xlabel(Predictions (1000$)) plt.ylabel(Residuals) plt.title(Residual Plot) plt.tight_layout() plt.show()实操心得散点图和残差图是诊断模型健康状况的“听诊器”。在散点图中点越靠近那条红色的45度线说明预测越准。在残差图中如果残差真实-预测是随机、均匀地分布在0线周围像一片“云”说明模型没有系统性偏差如果残差呈现出明显的U形、倒U形或漏斗形则说明模型存在未捕捉到的非线性关系或异方差性需要回头检查特征工程或换模型。一个优秀的ML工程师80%的时间都在看图而不是调参。4.4 模型解释与特征重要性让“黑箱”开口说话对于线性回归解释性是天然的。每个特征的系数model.coef_就代表了该特征对房价的影响方向和强度。系数为正表示该特征增加房价上涨系数为负表示该特征增加房价下跌。系数的绝对值越大影响越强。# 步骤9提取并排序特征重要性线性回归的系数 feature_importance pd.DataFrame({ feature: housing.feature_names, coefficient: model.coef_ }).sort_values(coefficient, keyabs, ascendingFalse) print( 特征重要性按系数绝对值排序) print(feature_importance) # 可视化 plt.figure(figsize(10, 6)) sns.barplot(datafeature_importance, xcoefficient, yfeature) plt.title(Linear Regression Coefficients) plt.xlabel(Coefficient Value) plt.axvline(x0, colork, linestyle-, alpha0.3) plt.show()注意这里的“重要性”是针对当前模型和当前数据的。它不等于因果关系。例如AveOccup平均每户人数系数为负可能意味着人越多房子越旧、越小从而房价越低但这并不意味着“赶走住户就能涨价”。模型解释是描述性的不是指导性的。在向业务方汇报时一定要加上这句免责声明否则很容易引发误读和错误决策。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”5.1 “ImportError: No module named sklearn” —— 环境问题永远是第一道坎这是新手遇到的最高频报错。根本原因只有一个你当前使用的Python环境里没有安装scikit-learn。解决方案不是百度而是用最底层的命令确认。# 1. 首先确认你正在用哪个Python解释器 which python # 或者在Python里运行 import sys print(sys.executable) # 2. 然后用同一个解释器来安装包 # 如果上面输出的是 /usr/bin/python3就用 sudo /usr/bin/python3 -m pip install scikit-learn # 如果输出的是 /Users/yourname/miniconda3/bin/python就用 /Users/yourname/miniconda3/bin/python -m pip install scikit-learn # 3. 最后验证安装 python -c from sklearn import __version__; print(__version__)排查技巧永远不要假设pip install安装的包就在你当前的Jupyter Notebook里。Jupyter Notebook有自己的内核kernel它可能指向一个完全不同的Python环境。在Jupyter里运行!which python和!pip list | grep sklearn才能看到它的真实状态。我踩过的最大坑就是在一个conda环境里装了包却在base环境的Jupyter里运行代码。5.2 “ValueError: Input contains NaN, infinity or a value too large for dtype(float64)” —— 数据里的“幽灵”这个报错意味着你的数据里有缺失值NaN、无穷大inf或者超大数。scikit-learn的所有模型都要求输入是干净的数值。排查步骤如下# 在 fit 之前务必加入这三行 print(X_train 中 NaN 的数量:, np.isnan(X_train).sum()) print(X_train 中 inf 的数量:, np.isinf(X_train).sum()) print(y_train 中 NaN 的数量:, np.isnan(y_train).sum()) # 如果发现有用以下方法清理根据业务逻辑选择 # 方案1删除含有NaN的行适用于NaN很少 X_train_clean X_train[~np.isnan(X_train).any(axis1)] y_train_clean y_train[~np.isnan(X_train).any(axis1)] # 方案2用均值填充最常用适用于数值型特征 from sklearn.impute import SimpleImputer imputer SimpleImputer(strategymean) X_train_clean imputer.fit_transform(X_train)实操心得永远不要在数据加载后就立刻model.fit()。我给自己定了一条铁律任何数据进入模型前必须经过print(df.info())和print(df.describe())的双重审查。info()告诉你有没有NaNdescribe()告诉你数值是否合理。有一次我发现一个“年龄”特征的max是999这显然不是真实年龄而是数据库里的“未知”占位符。如果直接喂给模型后果不堪设想。5.3 “UserWarning: X does not have valid feature names” —— 一个关于“名字”的严肃警告当你用pandas DataFrame而不是numpy array作为输入时scikit-learn 1.2版本会发出这个警告。它不是错误但意味着你失去了一个强大的功能特征名的自动继承。比如plot_tree函数可以自动在图上标出feature_namesPermutationImportance可以告诉你每个特征的名字。解决方法很简单# 错误示范直接用 numpy array X_np housing.data # 正确示范用 pandas DataFrame并指定列名 X_df pd.DataFrame(housing.data, columnshousing.feature_names) # 现在所有后续操作都能享受特征名的好处 model.fit(X_df, y)提示这个警告是scikit-learn团队为了推动大家使用更结构化的数据而设计的。它提醒你一个有名字的特征比一个编号为X[:, 0]的特征要专业得多。在真实项目中你的数据源SQL、CSV几乎总是能提供列名的善用它会让你的代码更具可读性和可维护性。5.4 “The truth value of an array with more than one element is ambiguous” —— 布尔索引的“哲学困境”这个报错通常出现在你想用if语句判断一个数组时比如if X 0:。Python不知道你是想判断“所有元素都大于0”还是“至少有一个元素大于0”。解决方案是明确使用.all()或.any()。# 错误写法 # if X_train.mean() 0: # print(均值为正) # 正确写法 if (X_train.mean(axis0) 0).all(): print(所有特征的均值都为正) elif (X_train.mean(axis0) 0).any(): print(部分特征的均值为正)实操心得这个错误看似琐碎但它暴露了一个根本问题初学者常常把“数组”当成一个单一的值来思考。NumPy的核心思想是“向量化”即对整个数组进行一次性操作。一旦你习惯了X 0返回一个布尔数组X[X 0]返回所有大于0的元素你的代码就会变得无比简洁和高效。把它当作一门新语言来学而不是Python的扩展。6. 从“会跑”到“会用”三个真实场景的延伸思考6.1 场景一电商推荐——KNN的“邻居”可以是用户也可以是商品KNN在推荐系统中大放异彩。它有两种经典用法基于用户的协同过滤User-Based CF找到和你购买历史最相似的N个用户你的“邻居”把他们买过而你没买过的商品推荐给你。基于商品的协同过滤Item-Based CF找到和你刚买的商品最相似的N个商品商品的“邻居”把它们推荐给你。实现的关键在于定义“相似性”。对于用户可以用他们对商品的评分向量来计算余弦相似度对于商品可以用所有用户对它的评分向量来计算。scikit-learn的NearestNeighbors类就是为此而生的。from sklearn.neighbors import NearestNeighbors import numpy as np # 模拟一个用户-商品评分矩阵1000个用户100个商品 np.random.seed(42) user_item_matrix np.random.randint(0, 6, size(1000, 100)) # 0-5分 # 创建一个基于商品的KNN模型 # metriccosine 表示用余弦相似度更适合稀疏的评分矩阵 item_nn NearestNeighbors(n_neighbors5, metriccosine, algorithmbrute) item_nn.fit(user_item_matrix.T) # 注意这里要转置让每一行是一个商品 # 假设用户刚买了商品ID0我们想找和它最相似的5个商品 distances, indices item_nn.kneighbors(user_item_matrix.T[0].reshape(1, -1)) print(与商品0最相似的商品ID:, indices.flatten())思考在这个场景里“邻居”的概念被极大地泛化了。它不再是地理上的临近而是高维空间里的语义临近。这正是机器学习的魅力所在它提供了一套通用的数学语言来描述世间万物之间的“相似”关系。无论是用户、商品、新闻文章还是基因序列只要能被表示成向量KNN就能为你找到它的“朋友”。6.2 场景二金融风控——决策树的“规则”就是可落地的业务策略银行审批贷款时需要一套清晰、可审计、可解释的规则。一个深度为3的决策树可以直接翻译成这样的业务规则IF收入 10000AND负债率 0.3THEN批准IF收入 10000AND负债率 0.3AND信用分 700THEN批准...等等。这种规则业务部门可以逐条审核监管机构可以逐条检查IT部门可以轻松写成SQL或Java代码上线。相比之下一个复杂的神经网络即使准确率更高也很难被业务方接受。# 用决策树生成规则简化版 def tree_to_rules(tree, feature_names, class_names, node0, depth0, indent): tree_ tree.tree_ feature tree_.feature threshold tree_.threshold if tree_.feature[node] ! sklearn.tree._tree.TREE_UNDEFINED: # 这是一个内部节点 name feature_names[feature[node]] threshold_value threshold[node] print(f{indent}IF {name} {threshold_value:.2f}:) tree_to_rules(tree, feature_names, class_names, tree_.children_left[node], depth 1, indent ) print(f{indent}else: # if {name} {threshold_value:.2f}) tree_to_rules(tree, feature_names, class_names, tree_.children_right[node], depth 1, indent ) else: # 这是一个叶子节点 class_idx np.argmax(tree_.value[node]) class_name class_names[class_idx] print(f{indent}THEN predict {class_name}) # 调用示例需先训练一个tree # tree_to_rules(tree_shallow, iris.feature_names, iris.target_names)提示这段代码展示了如何将一棵树“翻译”成人类语言。在真实项目中你可以用sklearn.tree.export_text获得更规范的文本输出。记住可解释性不是模型的附属品而是业务落地的通行证。当你向老板汇报时说“我的模型准确率是85%”不如说“我的模型提炼出了3条核心规则其中第一条‘逾期次数为0且月收入