ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

机器学习入门实战:用逻辑回归完成手写数字分类全流程解析

机器学习入门实战:用逻辑回归完成手写数字分类全流程解析 1. 为什么第三次打卡是一个分水岭先说结论机器学习入门的前两次打卡基本都在跟环境和概念较劲。到了第三次你才真正开始“训练一个模型”并且第一次清晰地看见“数据—特征—模型—评估”这条链路的完整闭环。很多人在这一步放弃不是因为它难而是因为不知道自己在做什么报了错也不知道从哪查起。我自己的第三次打卡正好赶上学期的机器学习课进度——前两周讲完了线性回归和梯度下降第三周开始接触分类问题。打卡任务是用逻辑回归对手写数字数据集做分类并提交准确率报告。这听起来很常规但真正动手才发现课程里讲的数学公式和实际调库之间有一段不小的“翻译”距离。比如损失函数长什么样、为什么训练集和测试集要分开、准确率到多少算合格这些问题不亲手跑一遍代码很难真正的理解。这篇文章就按照我第三次打卡的完整经历来写从环境准备到模型训练再到调参和踩坑记录。如果你是正在上机器学习课、或者自学走到分类这一关那这份记录可以直接拿来当参考。2. 环境准备与工具链的第一次“排雷”第一次打卡时大家普遍因为装环境折腾了一整天第二次打卡学会了用Anaconda管理虚拟环境第三次打卡就会面临一个更实际的问题到底哪些库是必须装的哪些版本是匹配的2.1 用Anaconda管理环境避免污染基础环境我第三次打卡用的工具链是Python 3.9 NumPy Pandas Matplotlib Scikit-learn。这几乎是机器学习入门阶段最通用的组合。Python版本不需要太新3.9 足够稳定许多课程的示例代码在3.8到3.11之间都能跑通。Scikit-learn版本我装的是 1.2.2后来遇到过一次 API 变化的问题下面会详细说。一个非常值得养成的习惯是为这门课单独建一个虚拟环境。conda create -n ml-crash python3.9 conda activate ml-crash conda install numpy pandas matplotlib scikit-learn jupyter这么做的好处在于你在实验其他项目时不会因为某个依赖库升级而导致这门课的代码突然跑不了。很多同学图省事直接在 base 环境里装了一堆包等到做期末项目时发现版本冲突哭都来不及。2.2 Jupyter Notebook 还是 PyCharm我的建议是打卡和实验阶段用 Jupyter Notebook写正式的作业报告或项目脚本时用 PyCharm 或 VS Code。Jupyter 的交互式体验非常适合“一边看数据、一边试代码”的探索过程尤其是当你想快速看某列的分布、立刻验证某个特征工程的想法时单元格执行的优势是其他编辑器比不了的。但 Jupyter 有一个致命缺点变量作用域太随意容易让人忽略“代码运行顺序”这件事。我这次打卡就踩过这个坑某个单元格改了一个变量后面的结果全变了但自己毫无察觉。所以建议在每个单元格开头加注释或者干脆养成“每次改动后从头到尾重新跑一遍 kernel”的习惯看似笨拙却能免掉很多诡异的问题。3. 核心理论分类任务与逻辑回归的“为什么”第三次打卡之前我对逻辑回归的理解仅限于“sigmoid函数把输出压到0到1之间”。但真正上手才意识到这背后涉及几个关键问题需要彻底搞清楚否则根本不知道自己在调什么参数。3.1 为什么分类问题不能用线性回归这是课程里的经典问题但真的值得自己再推一遍。如果用线性回归做二分类标签是0和1预测值可以大于1或小于0这种输出没法直接解释成“概率”。更麻烦的是离群值会严重影响拟合结果把整个决策边界拉偏。逻辑回归的做法是在线性回归的输出上套一层 sigmoid 函数得到介于0和1之间的概率值。公式上长这样import numpy as np def sigmoid(z): return 1 / (1 np.exp(-z))到这里为止很多同学都能看懂但有一个细节容易忽视逻辑回归的“回归”二字指的是决策边界是线性的而不是说它在做回归任务。当你看到决策边界是一条直线在二维情况下时就说明模型是在“学习”一个线性方程来区分类别。明白这一点你才能解释为什么逻辑回归对于线性可分的数据效果不错但对于复杂数据需要引入特征交叉或者换用其他模型。3.2 损失函数换成对数损失不是拍脑袋决定的线性回归用均方误差MSE作为损失函数逻辑回归如果继续用 MSE会因为 sigmoid 的非线性导致损失函数变成一个非凸函数用梯度下降很容易陷入局部最优。所以逻辑回归用对数损失Log Loss / Cross-Entropy Loss它在数学上是凸的能保证梯度下降找到全局最优。公式虽然不用手动实现Scikit-learn 封装好了但理解它很重要# 二分类对数损失 def log_loss(y_true, y_pred): eps 1e-15 y_pred np.clip(y_pred, eps, 1 - eps) return -np.mean(y_true * np.log(y_pred) (1 - y_true) * np.log(1 - y_pred))这个损失函数的直觉是当你预测错误且置信度很高时惩罚会呈指数级增大。这跟实际业务是一致的——一个模型如果“很自信地犯错”比“模棱两可”更危险。3.3 评估指标不是只有准确率一个维度第三次打卡如果只报准确率其实是偷懒了。手写数字数据集比如 MNIST 或 Scikit-learn 自带的 digits的类别分布相对均衡准确率还能说明问题但放到真实应用里很多数据集的类别是不均衡的。比如“检测用户是否流失”流失用户可能只占5%模型全预测成“不流失”也有95%的准确率但这个模型毫无用处。所以我这次打卡除了准确率还额外计算了精确率Precision、召回率Recall和 F1-score。这里有一个值得记住的思路精确率关注“你预测的正样本里有多少是对的”召回率关注“真实的正样本里有多少被找出来了”。不同业务场景对这两个指标的侧重完全不同——垃圾邮件过滤更看重精确率疫情筛查更看重召回率。4. 实战用 Scikit-learn 训练逻辑回归模型理论部分过完接下来就是真正的代码环节。这一节我从数据加载开始完整记录整个流程。4.1 加载数据并做探索性分析我在打卡里用的是 Scikit-learn 自带的 digits 数据集它包含1797张8x8的手写数字图像。相比 MNIST这个数据集更小、更干净特别适合课程作业因为不需要下载超大文件CPU 跑起来也不吃力。from sklearn.datasets import load_digits import pandas as pd import matplotlib.pyplot as plt X, y load_digits(return_X_yTrue) print(数据形状:, X.shape) # (1797, 64) print(类别分布:, pd.Series(y).value_counts().sort_index())加载之后一定先看一眼数据长什么样这是机器学习流程里最不应该跳过的环节。我习惯用 Matplotlib 画出前20张图直观感受一下图像的清晰度和噪声水平。fig, axes plt.subplots(2, 10, figsize(12, 3)) for i, ax in enumerate(axes.ravel()): ax.imshow(X[i].reshape(8, 8), cmapgray) ax.set_title(flabel: {y[i]}) ax.axis(off) plt.tight_layout() plt.show()这一步看起来简单但真的能及时发现数据问题。比如有的同学加载数据后直接开训结果画图时发现标签和图像对不上原因是在预处理时发生了错位排查半天才发现是索引写错了。4.2 数据划分训练集、验证集、测试集要分清楚第三次打卡最容易被扣分的地方就是“没有留出测试集”。很多初学者把全部数据拿去训练然后在同样的数据上评估得到一个虚高的准确率这是完全没有意义的行为。因为模型已经“见过”这些样本了评估结果只能反映它对训练数据的记忆程度而不是泛化能力。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(f训练集大小: {X_train.shape}) print(f测试集大小: {X_test.shape})这里有两个参数需要重点说明。random_state固定为某个整数是为了让每次运行得到相同的划分结果方便实验结果可复现。stratifyy的含义是按标签比例进行分层抽样确保训练集和测试集中每个数字的比例与原数据集一致。如果类别不平衡还不管 stratify那是真的会在评估阶段被坑。4.3 训练模型并输出完整评估报告正式训练逻辑回归其实只要两行代码但这背后的 pipeline 值得认真对待。from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, ConfusionMatrixDisplay model LogisticRegression(max_iter5000) model.fit(X_train, y_train) y_pred model.predict(X_test) print(classification_report(y_test, y_pred)) ConfusionMatrixDisplay.from_predictions(y_test, y_pred, cmapBlues) plt.show()有一个细节Scikit-learn 的 LogisticRegression 默认max_iter100对于 digits 这种64维特征的数据有时候可以收敛但偶尔也会警告 “ConvergenceWarning”。我习惯直接设置max_iter5000省得训练到一半收到警告还要返工。第一次跑出来的准确率大约在0.96到0.97之间不同的 random_state 略有浮动。这个数字对于一个64维的简单特征已经不错了说明手写数字在这个分辨率下类别之间的可分性比较强。但只看准确率报告还不够我打印了混淆矩阵发现5和8、3和8之间的混淆次数比较多这其实是个合理的现象——这些数字在8x8分辨率下确实长得像。4.4 特征缩放的影响为什么必须做标准化这是那次打卡中让我印象最深的一个实验。分组练习的时候有同学没有做数据标准化就直接训练逻辑回归准确率也没差多少。这让我产生了一个疑问既然结果差异不大为什么课程里反反复复强调标准化后来我做了一组对比实验才想明白。对于逻辑回归这种线性模型特征缩放不直接影响“能不能收敛”但会影响收敛速度和最终权重的解释性。当特征量纲差异很大时梯度下降会沿着某个特征方向来回震荡需要更多次迭代才能收敛而标准化之后梯度下降的路径更短损失函数下降得更平滑。更重要的是如果后续要做正则化L1/L2特征缩放会影响正则化惩罚的公平性。假设一个特征取值范围在0到1另一个特征取值范围在0到1000如果不缩放后者会被惩罚得更厉害模型就倾向于忽略那些数值小的特征这显然不是我们想要的。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)注意标准化需要先在训练集上fit再对测试集直接transform。也就是说fit_transform的对象必须只有训练集。这个细节很隐蔽但极其重要因为如果先对整个数据集做标准化测试集的信息就“泄露”进了训练过程评估结果就不再可靠。5. 从“能跑”到“跑得漂亮”调参和优化打卡只求能跑通拿的是60分把模型调优并真正理解每个参数的含义才算是入门。这一节记录我做过的几种优化实验以及结果差异背后的原因。5.1 正则化参数 C 到底在控制什么逻辑回归的损失函数可以被扩展成“原始损失 正则化项”。C是正则化强度的倒数C越小正则化越强模型越倾向于简单的决策边界C越大正则化越弱模型越容易过拟合。我在实操中跑了一组简单的网格搜索用交叉验证找到最优的 Cimport numpy as np from sklearn.model_selection import GridSearchCV param_grid {C: [0.01, 0.1, 1, 10, 100]} grid GridSearchCV( LogisticRegression(max_iter5000), param_grid, cv5, scoringaccuracy ) grid.fit(X_train_scaled, y_train) print(最佳参数:, grid.best_params_) print(最佳交叉验证准确率:, grid.best_score_)当我惊讶地发现自己第一次竟然在代码里少传了X_train_scaled拿原始特征直接跑了网格搜索时看到最优 C1交叉验证分数和之前差不多。后来用标准化特征重新跑C 的最优值变成了0.1到1之间验证分数稍微高了一点点。这个细节说明了一个道理在特征没有标准化的前提下C 给我们带来的结果差异是有限的因为所有特征都缩放在同一量级附近而标准化之后模型可以更高效地找到最优边界。换句话说标准化不是一定能让成绩飞跃但它是保证模型真正在“学习规律”而不是“迁就特征量纲”的前提。5.2 尝试 SVM 和随机森林作为对比课程要求里提到“可以使用其他模型进行对比分析”。为了把打卡做得更扎实一些我额外跑了 SVM支持向量机和随机森林。这一部分带来的收获出乎意料。from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier svm_model SVC(C1, kernelrbf, gammascale) svm_model.fit(X_train_scaled, y_train) svm_acc svm_model.score(X_test_scaled, y_test) rf_model RandomForestClassifier(n_estimators100, random_state42) rf_model.fit(X_train, y_train) rf_acc rf_model.score(X_test, y_test) print(f逻辑回归准确率: {model.score(X_test_scaled, y_test):.4f}) print(fSVM准确率: {svm_acc:.4f}) print(f随机森林准确率: {rf_acc:.4f})结果排序大致是SVM 和随机森林都略高于逻辑回归SVM 在标准化特征上优势更明显。原因是 digits 数据在高维空间中相对线性可分但边界并不是绝对直线的SVM 用 RBF 核可以学到更复杂的决策面。随机森林则靠多棵树的投票把一些小噪声“平均”掉了。但注意不能因为准确率高就无脑选择 SVM。逻辑回归的最大优势在于“可解释性”——你可以直接看到某个特征对于预测结果的正负贡献。在工业场景里特别是金融、医疗等需要解释模型判断依据的领域逻辑回归的地位不是其他黑盒模型能替代的。这个取舍是每次做模型选型时都必须思考的问题而不只是盯着准确率一个数字。5.3 学习曲线判断过拟合和欠拟合的实用工具另一个值得学习的诊断工具是学习曲线Learning Curve。它绘制的是“训练集大小 vs 训练/验证得分”的变化趋势能直观地告诉你当前模型是欠拟合、过拟合还是已经趋于稳定。from sklearn.model_selection import learning_curve train_sizes, train_scores, val_scores learning_curve( LogisticRegression(max_iter5000), X_train_scaled, y_train, cv5, train_sizesnp.linspace(0.1, 1.0, 10) ) train_mean train_scores.mean(axis1) val_mean val_scores.mean(axis1) plt.plot(train_sizes, train_mean, labelTrain) plt.plot(train_sizes, val_mean, labelValidation) plt.xlabel(Training Set Size) plt.ylabel(Accuracy) plt.legend() plt.show()实际跑出来训练得分和验证得分之间的差距很小而且随着训练集增大逐渐收敛这个状态说明模型没有明显的过拟合。如果你发现训练得分很高、验证得分很低说明过拟合了可以考虑增加正则化强度减小 C或者收集更多数据如果两个得分都低说明模型欠拟合需要考虑换用更复杂的模型或者更好的特征。6. 常见报错与排查技巧实录这一节是整篇打卡里最“实用”的部分很多坑不看一眼真的想不到。6.1 “ConvergenceWarning” 收敛警告逻辑回归训练时最容易出现的警告内容大致是ConvergenceWarning: Maximum number of iterations reached before convergence.原因很简单默认max_iter不够模型在迭代终止前还没有完全收敛。解决办法是把max_iter调大或者对特征做标准化。两者叠加使用效果最好。但如果你调到了1万步还报这个警告那要考虑特征里是不是有异常值或缺失值检查一下数据预处理。6.2 数据泄露先标准化还是先划分我见过同学犯了这样的错误先对整个数据集做StandardScaler然后才划分训练集和测试集。从表面上这没什么问题但实际上是数据泄露。因为标准化用了测试集的均值和方差这些统计信息在真实预测场景中是拿不到的所以测试集不再足够“陌生”评估结果会偏乐观。正确顺序一定是先train_test_split再在训练集上fit_transform、在测试集上transform。这不仅适用于标准化也适用于其他所有需要“学习数据统计规律”的预处理步骤比如 PCA 降维、缺失值填充中的均值填充。6.3 标签与图像错位当你在 Jupyter 里反复读取数据、筛选数据时很容易因为混淆索引而让标签和图像错位。这种情况一般发生在人工筛选数据或者手动构建子集时。避免方法是用row X[i]和label y[i]这种写法时保持索引一致或者直接用 DataFrame 维护数据和标签减少出错的可能。6.4 Scikit-learn 版本升级导致的 API 兼容问题我用的是 1.2.2但有同学装了 1.4 以上的新版本某些旧教程里的传参方式会触发 DeprecationWarning。比如LogisticRegression的多分类策略在新版本里默认行为有了一些调整准确率波动不大但警告信息会刷屏。建议固定课程使用的版本不要盲目升级。一个快速验证环境是否正常的方法是新建一个 Notebook跑一遍“加载数据—训练逻辑回归—输出准确率”全流程如果能无警告跑通说明环境没问题如果这一步就报错直接定位环境问题不要在项目代码里瞎找。6.5 混淆矩阵的可视化文字太小当类别是10个0~9时ConfusionMatrixDisplay.from_predictions画出来的图在 8x8 的窗口中标签会挤在一起。解决方法是调节画布大小fig, ax plt.subplots(figsize(8, 8)) ConfusionMatrixDisplay.from_predictions(y_test, y_pred, axax, cmapBlues) plt.show()7. 打卡之外的延伸这些经验能用到哪里第三次打卡的练习虽然只在一个小数据集上完成但把整个流程抽象出来它的底层逻辑可以迁移到很多真实场景中。7.1 从“调库”到“看懂损失曲线”我第一次打卡的时候觉得调库很“虚”因为不知道模型内部发生了什么。第三次打卡让我真正开始关注训练曲线的含义。当损失函数下降得越来越慢说明模型找到了一个局部较好的区域当训练集和验证集准确率开始分叉说明过拟合正在发生。这些信号对真实业务非常有用——你可以直接在模型部署前判断它是否可靠而不是上线后等用户反馈再补救。7.2 模型评估和“业务指标”怎么挂钩课程里评估用的是准确率实际工作中更常见的可能是 A/B 测试、转化率、留存率等指标。逻辑回归输出的概率值可以当作排序依据配合业务方设定阈值得到高于/低于阈值才能通过的结果。这个“阈值选择”本质上是精确率-召回率的权衡跟我在打卡里看的 classification_report 其实是一个故事的多面版本。7.3 课程项目怎么做得更出彩如果你正在准备期末项目或者慕课大作业我的建议是不要满足于跑通一个标准数据集。可以自己找一个真实的小数据集哪怕只是把本书或网上搜集的手写数字图片用 OpenCV 预处理成 8x8 的灰度图再做分类也远比直接加载自带数据集更有学习价值。因为真实数据天然有噪声、有倾斜角度、有模糊这些都不存在时会让你在“预处理”环节缺少必要的训练。等到毕业设计或者实习时80%的时间恰恰花在数据清洗上早早养成“先看数据、再选模型”的习惯真的能拉开差距。8. 给第二次打卡同学的特别提醒如果你正在准备自己的第三次打卡以下几个问题建议在提交前逐条自查训练集、测试集是否分开测试集的划分是否用了random_state固定方便复现标准化是在划分之后才做的吗有没有在测试集上不小心用了fit_transform评估指标是否只有准确率有没有混入精确率、召回率或 F1-score训练过程中有没有出现过警告这个警告你理解它是什么意思吗你能否向别人解释清楚“为什么选逻辑回归而不是随机森林”而不是回答“因为老师教了这个”这些问题其实都没有标准答案但会引导你把“能跑起来的代码”升级为“知道为什么这么写的代码”。机器学习的入门门槛不高高的是你踩过坑之后是否积累出属于自己的判断力。
返回列表