1. 为什么新手入门机器学习,我首推Scikit-learn而不是TensorFlow
经常有人跑来问我:想学机器学习,是不是直接上TensorFlow或者PyTorch?我的回答通常都是:先别急,把Scikit-learn玩明白,你才知道机器学习到底在干嘛。这不是说深度学习框架不好,而是对于一个完全没有经验的人来说,Scikit-learn的学习曲线要友好好几个量级。
先说说Scikit-learn是什么。它是Python生态里最老牌、最稳定的传统机器学习库,简称sklearn。从2007年发布到现在,已经迭代了十几年,功能覆盖了分类、回归、聚类、降维、数据预处理、模型评估、特征选择等几乎所有传统机器学习任务。你只需要会基本的Python语法,就能用十来行代码构建一个逻辑回归、决策树、随机森林或者SVM模型,然后完成对数据的预测。
我见过太多一上来就学深度学习的同学,结果卡在张量、梯度、反向传播这些概念上,学了两三个月还在原地打转,连一个能用的模型都跑不出来。但如果是先学Scikit-learn,从拿到数据集、清洗数据、训练模型、评估效果到调参,整个流程一两周就能理清楚。等你真正理解了机器学习的基本流程,明白了模型训练、验证、过拟合、欠拟合这些概念,再去玩深度学习框架,会发现很多理念是相通的,上手速度会快得多。
而且Scikit-learn的应用场景远比很多人想象的广泛。虽然深度学习在图像识别、自然语言处理这些领域风头正劲,但在日常的表格数据、业务数据、金融风控、用户画像、推荐系统、工业预测性维护等场景中,传统机器学习依然是绝对主力。很多互联网公司的核心业务模型,用的就是Scikit-learn里的逻辑回归、梯度提升树这些算法。自己动手搭建一个完整的机器学习项目,从这个库入门是最好的选择。
2. 万事开头难:环境安装与Scikit-learn版本选择
2.1 安装前的准备工作:哪个Python版本最稳
在装Scikit-learn之前,你得先有一个能用的Python环境。我强烈建议不要直接去Python官网装一个原生Python然后配环境变量,那样后面管理第三方包会很痛苦。
我个人的推荐是装Anaconda。Anaconda是一个开源的Python发行版,自带Python解释器和一大批数据科学常用的包,包括NumPy、Pandas、Matplotlib、Jupyter Notebook这些,更关键的是它自带conda包管理器,可以很方便地创建独立的虚拟环境,避免不同项目之间的依赖冲突。
装好以后,用conda创建一个独立环境,然后激活它:
conda create -n sklearn_env python=3.10 conda activate sklearn_env选择Python 3.10的原因是:它是目前兼容性最稳的版本,各个第三方库的适配都做得比较好。太新的Python版本,有些库的预编译包还没跟上,容易在安装时踩到编译报错的坑。
2.2 正式安装Scikit-learn:pip和conda两种方式对比
环境准备完毕,接下来就是装核心库。两条路可以走,我用一个表格简单对比一下:
| 安装方式 | 命令 | 优点 | 缺点 |
|---|---|---|---|
| pip | pip install scikit-learn | 安装快,包通常比较新 | 依赖管理不如conda严格,偶尔会装到不兼容的依赖版本 |
| conda | conda install scikit-learn | 依赖解析由conda统一管理,很少出现依赖冲突 | 默认源比较慢,需要换国内镜像,包更新可能略滞后 |
我自己现在基本都是用pip,因为pip配好国内镜像后速度很快,而且Scikit-learn的依赖不算复杂,一般就NumPy和SciPy这两个核心库,冲突概率很低。如果你不是做非常复杂的环境管理,直接pip就够用了。
装完后验证一下版本:
python -c "import sklearn; print(sklearn.__version__)"看到类似“1.6.0”这样的输出版本号,就说明装成功了。顺便看一下numpy和scipy的版本号,确保它们也都是比较新的版本。
2.3 现成数据集:新手不需要自己找数据
Scikit-learn自带了一组经典的数据集,对新手来说这是天大的福音。你不需要上网到处找数据、下载数据、清洗数据,直接就能拿来做练习。最常用的几个:
load_iris():鸢尾花数据集,150条样本,4个特征,3个类别,分类入门首选load_boston():波士顿房价数据集,注意新版sklearn已经移除了这个数据集,因为它的字段有伦理问题,可以用fetch_california_housing()替代做回归练习load_digits():手写数字数据集,1797张8x8的灰度图,用来体验图像分类
还有一个make_classification()函数,可以按你的参数需求生成模拟数据,我做演示的时候经常用。新手用自带数据集的好处是,你可以把全部精力放在理解模型调用的逻辑上,而不是被数据清洗绊住脚。
3. 机器学习到底在做什么:先把“特征、标签、训练、预测”这四个词彻底学透
在跑代码之前,我特别想把这一节单拎出来讲清楚,因为太多人代码跑通了,但心里其实完全不知道机器学习在干什么。如果你不理解这些核心概念,后面所有代码都只是复制粘贴,换个真实项目就抓瞎。
3.1 训练一个模型,本质上是在学一个函数
我们小时候都学过函数:给定一个x,通过某种规则得到y。机器学习训练出来的模型,本质上就是干这件事——它学着找到一个从x到y的映射关系。只不过这个函数太复杂,不是用几行公式能表达出来的,而是通过大量数据“算”出来的。
举个例子,你给一个系统输入一张猫的图片的数值化表示,输出是一个标签“猫”或“狗”。在机器学习的世界里,这个输入的数值化表示叫“特征向量”,输出叫“标签”。模型的作用就是从大量已知特征和标签的对应关系中,学到一个足够好的映射函数,以至于下次看到一个从没见过的新样本,也能大概率猜对标签。
3.2 “特征”和“标签”是什么?
我用预测房价来说明白。
假设你要用线性回归预测一套房子的价格。你手里有历史上1000套房子的成交记录。每条记录里有面积、卧室数量、所在楼层、房龄、以及最终成交价。这里“面积”、“卧室数”、“楼层”、“房龄”就是特征,“成交价”就是标签。
数据在交给模型之前,通常要组织成一个二维表格的结构,很像Excel表格。每一行是一个样本,每一列是一个特征,再加上一列标签。Scikit-learn的StandardScaler方法要求数据必须是这种结构——行是样本,列是特征,这是一个非常核心的规则。
3.3 训练和预测的本质区别
训练(fit)是让模型“看”数据“学”规律的过程;预测(predict)是模型利用学到的规律去猜新样本答案的过程。
选几行数据给模型看,然后给它一叠没有答案的试卷让它自己批改,这个过程在Scikit-learn里就是两行代码:
model.fit(X_train, y_train) predictions = model.predict(X_test)在Scikit-learn中,fit函数和predict函数几乎是所有模型类的统一接口,这是这个库最让我喜欢的设计哲学——换一个算法,调用方式一模一样,重点全在你的数据上。这一点后面我会展开细讲。
理解了这四个概念,机器学习的第一步你就迈过去了。实际上大部分机器学习项目的失败,都跟这些基础概念没吃透有关。比如有人把数据预处理做错,把连续值当成离散值输入,模型效果很差还找不到原因;有人把整个数据集拿去训练,没有留测试集,结果评估出来分数高得离谱,一看就是过拟合了。这些问题,都会随着你对基础概念的理解深入而逐渐避免。
4. 第一个完整项目实战:用鸢尾花数据集跑通“训练-评估-预测”全流程
光说不练假把式。在这个部分,我把机器学习里最经典的“Hello World”项目拆开揉碎来讲——鸢尾花分类。我会走完从加载数据到模型评估的完整流程,每一步都讲清楚到底在做什么、为什么这么做。
4.1 加载数据
下面的代码加载Scikit-learn自带的鸢尾花数据集:
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split iris = load_iris() X = iris.data y = iris.target print("特征矩阵形状:", X.shape) # (150, 4) print("标签数量:", len(y)) # 150 print("类别名称:", iris.target_names) # ['setosa' 'versicolor' 'virginica']这里的X是150行4列的二维数组,每一行是一朵鸢尾花的四个特征数值:花萼长度、花萼宽度、花瓣长度、花瓣宽度。y是150个标签值,0、1、2分别对应上面打印的三个品种名称。
4.2 划分训练集和测试集
拿到数据后第一件事不能是直接训练,而是先把数据分成两份:一份用于训练模型,一份用于检验模型在“没见过”的数据上的表现。
还有一个很容易被忽略的关键点:划分数据之前,要先把数据打乱(shuffle)。
Scikit-learn的train_test_split函数很贴心,它默认就会帮我们打乱数据再切分,所以不太用担心这个问题。下面我们按7:3的比例划分:
# test_size=0.3 表示拿出30%的数据做测试集 # random_state=42 固定随机种子,让每次运行结果一致,便于复现 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42 ) print(X_train.shape) # (105, 4) print(X_test.shape) # (45, 4)关于random_state,我要多说两句。很多新手不明白为什么每次要设这个固定值。如果你不设,每次运行程序划分出的训练集和测试集都不一样,模型结果也会有波动。为了做实验对比不同的算法,你就必须保证大家用同一份数据训练和测试。设置一个固定数值,就好比给这次划分盖了个章,之后任何人都可以复现你的结果。
4.3 选择模型并训练
接下来选择一个分类算法来训练。对于这种多分类、特征数量不多的小数据集,逻辑回归是一个很好的起点。它的可解释性强、训练速度快,而且作为最简单的线性分类器,非常适合理解模型训练的过程。
from sklearn.linear_model import LogisticRegression model = LogisticRegression(max_iter=1000) model.fit(X_train, y_train)我注意到很多第一次接触的同学会问:fit到底是在干什么?我们以前写代码都是在执行逻辑,但fit是在“学习参数”。具体来说,对于逻辑回归,fit的过程就是通过一种叫梯度下降的优化方法,不断调整模型内部的权重参数,使得模型在训练数据上的预测结果和真实标签之间的差距越来越小。
在没调整任何参数的情况下,逻辑回归默认就能跑得很不错。但那行max_iter=1000可能会让你踩到第一个坑:如果不设置这个参数,有时候会看到一条“ConvergenceWarning: Maximum iterations reached”的警告,意思是模型在迭代到默认上限时还没收敛。这很常见于数据没有做标准化的情况,为了不让训练过程因为迭代次数不够而半途而废,我习惯显式地调大这个值。
4.4 用测试集做预测
模型训练完成之后,最重要的环节来了——拿从来没有参与过训练的数据去考它,看它能不能举一反三:
predictions = model.predict(X_test) print(predictions[:10])这行代码输出的是测试集中前10个样本的预测结果,可能是0或1或2。
如果你想看模型预测的是每个类别的“置信度”,也就是概率值,还可以用predict_proba方法:
probabilities = model.predict_proba(X_test[:3]) print(probabilities)输出的每一行是3个数字,分别代表模型认为该样本属于类别0、1、2的概率。三个数加起来正好为1。
4.5 评估模型:准确率到底是什么意思
评估分类模型最简单的指标就是准确率——预测正确的样本数除以总样本数。Scikit-learn里可以直接用一行代码算出来:
from sklearn.metrics import accuracy_score print("预测准确率:", accuracy_score(y_test, predictions))在我固定了random_state=42的这套划分下,这个模型通常能拿到0.9778的准确率。也就是说45个测试样本中,模型只把1个样本的分类预测错了。对于一行机器学习代码都没写过的同学来说,第一次看到这个数字会特别有成就感。这种感觉很重要,它会成为你继续学下去的动力。
不过我得提前打个预防针:在真实项目中,准确率并不是越高越好,甚至有时候准确率高反而是个危险信号。比如一个99%样本都是正常用户的风控系统,模型什么都不学,全预测“正常用户”,准确率就有99%。但真正的欺诈用户一个都抓不到,这样的模型没有任何价值。这就引出了我们下一节要讲的更全面的评估方法。
到这里,你已经亲手完成了一个完整的机器学习项目全流程。整个过程其实就干了一件事——用历史数据训练一个函数,然后拿新数据喂给这个函数,得到预测结果。
5. 别只盯着精确率:Scikit-learn里更靠谱的模型评估指标和调参方法
5.1 精确率、召回率和F1,各自的适用场景
上面说过,只有准确率是远远不够的。在分类问题上,Scikit-learn提供了更全面的评估工具。
先说3个最核心的评估指标,我用一个互联网公司识别垃圾邮件的场景来说明:
- 精确率(Precision):你从所有邮件里挑出10封预测为垃圾邮件,其中7封确实是垃圾,那么精确率就是70%。这个指标回答的是“你说是垃圾的,到底有多少真是垃圾”。追求高精确率,是为了减少误伤正常邮件。
- 召回率(Recall):一共有100封真垃圾邮件,你的模型只抓到了60封,那召回率就是60%。这个指标关注的是“真的垃圾邮件,你抓回了多少”。追求高召回率,是为了尽量不漏掉每一封垃圾邮件。
- F1分数:精确率和召回率的调和平均数。当这两个指标出现矛盾时,F1就是一个平衡的折中值。
如果你分类的目标是疾病筛查,你可能要优先看召回率,因为漏诊一个病人的代价远大于误诊;如果你的目标是内容审核,你可能更看重精确率,因为误删用户正常内容会引发大量投诉。
在Scikit-learn里,这些指标全都有现成的函数:
from sklearn.metrics import classification_report print(classification_report(y_test, predictions))一行代码就能输出每个类别的精确率、召回率和F1分数,以及各类别样本数。我几乎每个分类项目都会先跑一次这个函数,快速判断模型在哪些类别上表现弱。在鸢尾花这个例子中你会发现,三个类别的精确率都在0.96以上,说明模型整体很均衡,没有什么严重的偏向。
5.2 把数据标准化:为什么特征尺度不同会坑掉模型
再往前推一步,很多新手第一次训练完模型遇到训练不收敛、效果很差的问题,十有八九是因为特征没有做标准化。我提前说一下这个问题,等到你自己写项目时能少走弯路。
先理解一下什么是“特征尺度不同”:假设你在做一个房价预测,面积这个特征的范围是30到200,房龄的范围是1到50,而小区评分是1到10。你会发现面积的数值普遍偏大,在模型计算距离或者梯度时就会占据决定性地位,其他特征几乎被“淹没”了。很多算法会因此对数据分布非常敏感。
解决方案是缩放特征,让它们的范围大致保持一致。Scikit-learn中最常用的是StandardScaler,它会把每个特征变成均值为0、标准差为1的分布。
一个很典型的流程是这样的:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)注意这两个“坑”,是非常重要的细节:
首先,fit_transform需要用在训练集上,而transform用在测试集上。原因是不允许让测试集的数据统计信息(比如均值和标准差)参与模型训练过程,否则会发生信息泄露。
还有一个是:数据标准化本身就是为了让某些对尺度敏感的模型(比如逻辑回归、SVM、KNN)跑得更稳。但对决策树、随机森林这类基于划分的树模型,有没有标准化影响不大,因为它们不考虑特征之间的距离,而是关注“怎么切”(纯度增益)。我第一次知道这个结论时,感觉一下子明白了为什么有些项目几乎不用做数据预处理。
5.3 交叉验证:比一次性划分更靠谱的评估策略
如果你足够细心,可能会发现一个隐藏的问题:前面我们划分了一次训练集和测试集,这个划分是随机的,那结果会不会因为这次偶然的划分而有失偏颇?
确实有这个问题。这就是为什么在实际项目中普遍会用交叉验证(Cross-Validation)来更稳定地评估模型的表现。
交叉验证的思路非常直白:把原始训练集切成K份,每次拿出其中1份做验证,剩下K-1份做训练,轮流K次,最终得到K个验证分数,再取平均值。这样就大大降低了“这次碰巧划分得好”的运气成分,模型评估结果更可信。
Scikit-learn里的交叉验证简单到令人发指:
from sklearn.model_selection import cross_val_score scores = cross_val_score(model, X_train, y_train, cv=5) print("5折交叉验证得分:", scores) print("平均得分:", scores.mean())一般来说5折或10折是常用选择。如果数据量特别小,也可以用留一法(Leave-One-Out)。新手在做模型对比时,用cross_val_score而不是盲目相信单次准确率,这是一个成熟的信号。
5.4 用网格搜索调参的科学方法
模型训练完后,大家都会想一个问题:能不能调一调参数让效果更好?如果手动一个个去试,效率太低。Scikit-learn提供了网格搜索(GridSearchCV),把参数组合自动排列好,每组都做交叉验证,最后告诉你哪组参数效果最好。
比如对逻辑回归来说,有一个重要的超参数是正则化强度C,在小数据集上手动试5个值就够,我倾向于取对数尺度上的几个点:
from sklearn.model_selection import GridSearchCV param_grid = {'C': [0.01, 0.1, 1, 10, 100]} grid_search = GridSearchCV( LogisticRegression(max_iter=1000), param_grid, cv=5, scoring='accuracy' ) grid_search.fit(X_train, y_train) # 找到的最优参数组合 print("最优参数:", grid_search.best_params_) # 对应的最优分数 print("最优交叉验证分数:", grid_search.best_score_)注意一点:虽然GridSearchCV内部自己会交叉验证,但网格搜索的分数是基于它内部的训练集和验证集评估出来的,不是最终测试集分数。当你用这批“最优参数”重新训练最终模型后,最后还是要拿最开始划分出来的那一份X_test做一次最终评估,才算是诚实的结果。否则网格搜索会过拟合验证集,导致你高估模型能力。
6. 从Scikit-learn毕业之后:新手的常见卡点与实际建议
6.1 为什么代码跑通了,却觉得没学到东西
我观察到很多自学机器学习的同学,出现的最典型的困惑是:跑通了鸢尾花数据集,代码一行一行理解也没问题,接下来就不知道干嘛了。这就是“学了API,却没有建立知识体系”的状态。
我的个人建议是,用一个自己熟悉领域的小数据集做一次完整的项目实践。比如你对电商感兴趣,就用一份关于用户购买行为的数据,去预测用户会不会复购。这样数据是你最熟悉不过的背景知识,你很容易就能理解特征的含义和业务的合理性。从一卷自带数据集到自找数据,这个过程会逼你去处理很多问题:数据有缺失值怎么办、字符串特征怎么编码、样本严重失衡怎么办,所有这些都会让你真正上一个台阶。
Scikit-learn的文档是我见过所有开源库里写得最认真的之一,每个模型都配有实际的例子和详细说明。遇到不熟悉的模型,直接去找官方示例,对着运行看效果,比读一堆二手博客靠谱得多。官方还有一份备忘速查表,专门帮你根据“我有多少标注数据”“数据是否带有标签”“需要解决什么问题”等维度快速定位合适的算法。
6.2 常见坑位索引:我整理的一份避坑清单
| 坑位 | 现象 | 解决方案 |
|---|---|---|
| 一次性把整个数据集拿来训练 | 测试时分数虚高,换到新数据上效果崩盘 | 永远先切分出测试集,只在训练集上做fit |
用fit_transform处理测试集 | 导致信息泄露,评估结果不可信 | 训练集fit_transform,测试集只transform |
| 忽略缺失值 | 部分模型报警告或直接报错 | 用SimpleImputer填充均值/中位数/众数 |
| 把字符串类别直接喂给模型 | 类型报错 | 用OneHotEncoder或OrdinalEncoder编码 |
| 不设置随机种子 | 每次跑结果不一样,无法复现 | 统一设置random_state=42 |
| 所有特征不做标准化就训练SVM/KNN/逻辑回归 | 训练不收敛,效果差 | 用StandardScaler预处理后再训练 |
| 拿网格搜索的结果当作最终测试分数 | 对模型能力盲目乐观 | 网格搜索只在训练集内做,最终保留测试集做验证 |
这几个坑我基本都踩过一遍,尤其是前两个,几乎每隔一段时间就会看到有人犯同样的错误。信息泄露这件事特别隐蔽——表面上代码没报错,评估指标也漂亮,但模型其实已经被“剧透”了,真实环境里根本达不到测试时的效果。
6.3 学完传统机器学习,下一步应该怎么办
Scikit-learn入门以后,回头看TensorFlow和PyTorch,你会发现自己已经具备了理解机器学习全流程的知识框架。这时候再学深度学习,其实就是在已有框架基础上换一种更“重型”的工具。你会发现整个流程还是那几步——数据处理、划分数据集、训练模型、评估效果、调参,只是模型的内部结构变化了。
大多数人的下一步方向,取决于你想做什么:
- 对文本感兴趣,可以尝试用Scikit-learn先做文本分类,先掌握TF-IDF特征表示和朴素贝叶斯模型,再逐步接触更复杂的神经网络文本模型。
- 对推荐系统感兴趣,可以先从协同过滤的思路出发,用Scikit-learn里的最近邻算法做一个简单的“猜你喜欢”。
- 对图像感兴趣,可以先用
load_digits()手写数字识别练手,了解图像如何展平成一维向量,再过渡到深度学习里的卷积神经网络。
我给大多数新手的建议是,一个月的时间把Scikit-learn核心用法掌握扎实,一个月之后你再决定是深入研究传统机器学习算法,还是转向深度学习。这个过程不只是学了一堆函数调用,更是在帮你完成思维训练:从“拿到数据一脸茫然”到“知道先看什么、后做什么、如何评估结果是否有价值”。有了这个基底,后面你无论是继续学算法推导、学工程部署,还是跳去学深度学习框架,都会顺畅得多。机器学习这条路很长,但Scikit-learn绝对是最好的第一个台阶。