机器学习入门:21步掌握Scikit-learn工具包
机器学习听起来高深,但只要粗通Python和NumPy,你就能快速上手。本文基于业界最流行的机器学习框架Scikit-learn,带你用21个核心知识点,从零搭建完整认知。每一点都配有代码示例,逐行敲完,你就能打开机器学习的大门。
第一部分:机器学习基础概念
1. 机器学习的四种用途
机器学习有四种用途:分类、聚类、回归和降维。更严格地说,机器学习的目标仅包含三大类:分类、聚类和回归,降维是达成目标的手段之一。理解了这句话,你就抓住了机器学习的本质。迷茫时,在心里默念这句话,就会找到前进的方向。
2. 分类与聚类的区别
分类和聚类都是对个体样本归类,看起来很相似,实则相去甚远——分类属于有监督的学习,聚类属于无监督的学习。分类基于过往数据的经验,需要训练;聚类基于当前所有样本的特征,不依赖经验,无需训练。例如:从一堆水果中挑出苹果、橘子和香蕉是分类;将画在纸上的若干个图案按自己的规则分组是聚类。
3. 分类与回归的关系
从字面上看,分类和回归似乎无关,其实二者是亲兄弟,使用的算法几乎完全重合。分类是对个体样本做出定性判定,回归是对个体样本做出定量判定,二者同属有监督的学习,都基于经验。例如:预测某学生考试及格或不及格是分类;预测某学生能考多少分是回归。老师的经验数据和思考方法相同,只是最后表述不同。
4. 数据是机器学习的核心
传统软件开发重在代码,机器学习则重在数据。训练模型时,数据的质量和数量直接影响结果的准确性和有效性。无论学习还是应用,前提都是拥有足够好、足够多的数据集。
小提示: 初学者建议从Scikit-learn内置的小型数据集开始练习,它们已经过清洗和标准化,方便你聚焦算法本身。
常见问题: 为什么说数据比代码更重要?
答案: 因为模型本质上是从数据中学习规律。即使算法再先进,如果数据包含噪声、缺失值或偏差,训练出的模型也会“学到错误”。好的数据能帮你更快、更稳定地逼近真实规律。
第二部分:数据集与特征工程
5. 数据集的结构
数据集通常是一个二维数组,每一行表示一个样本的数据。例如,用性别、年龄、身高、体重、职业、年薪、不动产、有价证券等信息组成的一维数组表示一位征婚者,下面的二维数组就是一个婚介机构收集到的数据集:
>>> import numpy as np
>>> members = np.array([ ['男', '25', 185, 80, '程序员', 35, 200, 30],
['女', '23', 170, 55, '公务员', 15, 0, 80],
['男', '30', 180, 82, '律师', 60, 260, 300],
['女', '27', 168, 52, '记者', 20, 180, 150]])
6. 特征列
数据集的列也被称为特征维或特征列。上面征婚者数据集共有8列(性别、年龄、身高、体重、职业、年薪、不动产、有价证券),即8个特征列。
7. 什么是降维
降维并非将数据集从二维变成一维,而是减少数据集的特征列数。例如征婚者信息远不止8项,但若将所有信息都加入,会增加存储和处理成本,还会让择偶者忽略最重要的信息。降维就是从数据集中剔除对结果无影响或影响微弱的特征列。
8. 标准化
标准化是对样本集的每个特征列减去该特征列的平均值进行中心化,再除以标准差进行缩放。它的意义在于反映个体数据偏离所有样本平均值的程度。下面是对征婚者数据集中“有价证券”特征列标准化的结果:
>>> security = np.float32((members[:,-1])) # 提取有价证券特征列数据
>>> security
array([ 30., 80., 300., 150.], dtype=float32)
>>> (security - security.mean())/security.std() # 减去均值再除以标准差
array([-1.081241, -0.5897678, 1.5727142, 0.09829464], dtype=float32)
9. 归一化
归一化是对样本集的每个特征列减去该特征列的最小值进行中心化,再除以极差(最大值减最小值)进行缩放。结果收敛于[0,1]区间。下面演示对“有价证券”特征列归一化:
>>> security = np.float32((members[:,-1]))
>>> security
array([ 30., 80., 300., 150.], dtype=float32)
>>> (security - security.min())/(security.max() - security.min())
array([0. , 0.18518518, 1. , 0.44444445], dtype=float32)
10. 特征编码
机器学习模型只能处理数值数据,因此需要将性别、职业等非数值数据变成整数,这个过程称为特征编码。对于无序的职业,通常使用独热码(one-of-K):若有n个不同职业,就用n位二进制数字表示,每位只有1位为1。下面使用Scikit-learn的OneHotEncoder对性别和职业编码:
>>> from sklearn import preprocessing as pp
>>> X = [ ['男', '程序员'], ['女', '公务员'], ['男', '律师', ], ['女', '记者', ]]
>>> ohe = pp.OneHotEncoder().fit(X)
>>> ohe.transform(X).toarray()
array([[0., 1., 0., 0., 1., 0.],
[1., 0., 1., 0., 0., 0.],
[0., 1., 0., 1., 0., 0.],
[1., 0., 0., 0., 0., 1.]])
小提示: 标准化和归一化都能消除量纲影响,但标准化更适用于数据近似正态分布的场景;归一化更适用于数据有明确上下界的情况。
常见问题: 什么时候必须做特征编码?
答案: 只要特征列包含类别型文本(如“男”“女”“程序员”),就必须进行编码。独热码是处理无序类别最常用的方法,对于有序类别(如学历“小学、中学、大学”)可以考虑使用标签编码(Label Encoding)。
第三部分:Scikit-learn数据集与模型流程
11. Scikit-learn内置数据集
datasets子模块提供了丰富的内置数据集:函数名以load开头的是小型数据集,以fetch开头的是需要从网络下载的大型数据集。常用列表如下:
datasets.load_boston– 波士顿房价数据集datasets.load_breast_cancer– 威斯康星州乳腺癌数据集datasets.load_diabetes– 糖尿病数据集datasets.load_digits– 数字数据集datasets.load_iris– 鸢尾花数据集datasets.load_linnerud– 体能训练数据集datasets.load_wine– 葡萄酒数据集
12. 数据集与标签集
每个二维数据集对应一个一维的标签集,用于标识每个样本的类别或属性值。习惯上数据集用大写字母X表示,标签集用小写字母y表示。下列代码加载鸢尾花数据集(150个样本,4个特征,3种类型):
>>> from sklearn.datasets import load_iris
>>> X, y = load_iris(return_X_y=True)
>>> X.shape # (150, 4)
>>> y.shape # (150,)
>>> X[0], y[0]
(array([5.1, 3.5, 1.4, 0.2]), 0)
若指定return_X_y=False(默认),可以查看标签名称:
>>> iris = load_iris()
>>> iris.target_names
array(['setosa', 'versicolor', 'virginica'], dtype='>> X = iris.data
>>> y = iris.target
13. 训练集与测试集分割
模型训练时,通常将数据集分成两部分:一部分用于训练,一部分用于测试。train_test_split是其中最简单的方法,可根据比例随机抽取。该函数位于model_selection子模块中:
>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split as tsplit
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = tsplit(X, y, test_size=0.1)
>>> X_train.shape, X_test.shape # ((135,4), (15,4))
>>> y_train.shape, y_test.shape # ((135,), (15,))
小提示: 分割数据时,建议设置random_state参数固定随机种子,以保证每次运行结果可复现。
常见问题: 测试集的比例一般取多少?
答案: 常用的比例是20%~30%(对应test_size=0.2~0.3)。如果数据量很大,测试集比例可以适当减少;如果数据量很小,则要增加测试集比例或采用交叉验证。
第四部分:分类与回归算法
14. k-近邻分类
“近朱者赤,近墨者黑”——k-近邻分类是最简单、最容易的分类方法。对于待分类样本,从训练集中找出k个距离最近的样本,考察它们中哪个标签最多,就给待分类样本贴上该标签。k值越大,抑制噪声效果越强,但分类边界越模糊,通常选不大于20的整数。下面用鸢尾花数据集演示:
>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split as tsplit
>>> from sklearn.neighbors import KNeighborsClassifier
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = tsplit(X, y, test_size=0.1)
>>> m = KNeighborsClassifier(n_neighbors=10)
>>> m.fit(X_train, y_train)
KNeighborsClassifier()
>>> m.predict(X_test)
array([2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 0, 1, 0, 0, 2])
>>> y_test
array([2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 0, 1, 0, 0, 2])
>>> m.score(X_test, y_test) # 准确率约93%
0.9333333333333333
15. k-近邻回归
k-近邻也可用于回归:预测样本的标签由它最近邻标签的均值得到。例如,一辆开了八年的大切诺基值多少钱?可参考k辆同款且使用年限相近的二手车售价的均值。下面以波士顿房价数据集为例,用KNeighborsRegressor预测:
>>> from sklearn.datasets import load_boston
>>> from sklearn.model_selection import train_test_split as tsplit
>>> from sklearn.neighbors import KNeighborsRegressor
>>> X, y = load_boston(return_X_y=True)
>>> X.shape, y.shape, y.dtype # ((506,13), (506,), float64)
>>> X_train, X_test, y_train, y_test = tsplit(X, y, test_size=0.01)
>>> m = KNeighborsRegressor(n_neighbors=10)
>>> m.fit(X_train, y_train)
KNeighborsRegressor(n_neighbors=10)
>>> m.predict(X_test)
array([27.15, 31.97, 12.68, 28.52, 20.59, 21.47])
>>> y_test
array([29.1, 50. , 12.7, 22.8, 20.4, 21.5])
16. 回归模型的评价指标
评价回归结果比分类更复杂,因为要考虑偏离程度。常用评价函数有均方误差(MSE)、中位数绝对误差(MAE)和复相关系数(R²),它们都在metrics子模块中:
>>> from sklearn import metrics
>>> y_pred = m.predict(X_test)
>>> metrics.mean_squared_error(y_test, y_pred) # 60.27
>>> metrics.median_absolute_error(y_test, y_pred) # 1.07
>>> metrics.r2_score(y_test, y_pred) # 0.56(不理想)
复相关系数只有0.56,说明k-近邻不适合预测波士顿房价。改用决策树回归效果更好:
>>> from sklearn.tree import DecisionTreeRegressor
>>> m = DecisionTreeRegressor(max_depth=10)
>>> m.fit(X, y)
>>> y_pred = m.predict(X_test)
>>> y_test # [20.4, 21.9, 13.8, 22.4, 13.1, 7. ]
>>> y_pred # [20.14, 22.33, 14.34, 22.4, 14.62, 7. ]
>>> metrics.r2_score(y_test, y_pred) # 0.98
>>> metrics.mean_squared_error(y_test, y_pred) # 0.47
17. 决策树、SVM、贝叶斯:分类与回归兼用
这些算法既能做分类也能做回归,流程与k-近邻基本相同,但参数不同。例如SVM回归的关键参数是kernel(内核算法)和C(误差惩罚参数)。C值越大,训练集准确率越高,但泛化能力越弱。下面用糖尿病数据集演示不同C值的影响:
>>> from sklearn.datasets import load_diabetes
>>> from sklearn.model_selection import train_test_split as tsplit
>>> from sklearn.svm import SVR
>>> from sklearn import metrics
>>> X, y = load_diabetes(return_X_y=True)
>>> X_train, X_test, y_train, y_test = tsplit(X, y, test_size=0.02)
>>> svr_1 = SVR(kernel='rbf', C=0.1)
>>> svr_2 = SVR(kernel='rbf', C=100)
>>> svr_1.fit(X_train, y_train)
>>> svr_2.fit(X_train, y_train)
>>> z_1 = svr_1.predict(X_test)
>>> z_2 = svr_2.predict(X_test)
>>> y_test
array([ 49., 317., 84., 181., 281., 198., 84., 52., 129.])
>>> z_1 # C=0.1,偏差很大
array([138.10720127, 142.1545034 , 141.25165838, 142.28652449, ...])
>>> z_2 # C=100,偏差明显减小
array([ 54.38891948, 264.1433666 , 169.71195204, 177.28782561, ...])
>>> metrics.r2_score(y_test, z_2) # 0.54 比C=0.1的0.013好很多
18. 随机森林
随机森林是将多棵决策树集成在一起的算法,属于集成学习方法。每棵树的训练样本和特征列都是随机选择的,因此不易过拟合,抗噪能力强。下面用威斯康星州乳腺癌数据集(569个样本,30个特征)比较决策树和随机森林的交叉验证结果:
>>> from sklearn.datasets import load_breast_cancer
>>> from sklearn.tree import DecisionTreeClassifier
>>> from sklearn.ensemble import RandomForestClassifier
>>> from sklearn.model_selection import cross_val_score
>>> ds = load_breast_cancer()
>>> dtc = DecisionTreeClassifier()
>>> rfc = RandomForestClassifier()
>>> dtc_scroe = cross_val_score(dtc, ds.data, ds.target, cv=10)
>>> dtc_scroe.mean() # 0.912
>>> rfc_scroe = cross_val_score(rfc, ds.data, ds.target, cv=10)
>>> rfc_scroe.mean() # 0.961
随机森林(96.1%)明显优于单棵决策树(91.2%)。
小提示: 随机森林的两个关键参数:n_estimators(树的数量,通常100~1000),max_features(每棵树随机选取的特征数)。
常见问题: 什么时候用随机森林而不是单棵决策树?
答案: 当数据特征较多(如几十个以上)且存在噪声时,随机森林的集成效果通常更好。单棵决策树容易过拟合,而随机森林通过随机采样和特征选择有效防止了过拟合。
第五部分:聚类算法
19. 基于质心的聚类(k-means)
k-means是最简单的聚类算法:首先从X中选k个样本作为初始质心,然后重复分配每个样本到最近质心、根据每个类中所有样本平均值更新质心,直到质心不再显著移动。但基于质心的聚类无法处理细长条、环形或交叉的不规则形状。下面用样本生成器演示:
>>> from sklearn import datasets as dss
>>> from sklearn.cluster import KMeans
>>> import matplotlib.pyplot as plt
>>> plt.rcParams['font.sans-serif'] = ['FangSong']
>>> plt.rcParams['axes.unicode_minus'] = False
>>> X_blob, y_blob = dss.make_blobs(n_samples=[300,400,300], n_features=2)
>>> X_circle, y_circle = dss.make_circles(n_samples=1000, noise=0.05, factor=0.5)
>>> X_moon, y_moon = dss.make_moons(n_samples=1000, noise=0.05)
>>> y_blob_pred = KMeans(init='k-means++', n_clusters=3).fit_predict(X_blob)
>>> y_circle_pred = KMeans(init='k-means++', n_clusters=2).fit_predict(X_circle)
>>> y_moon_pred = KMeans(init='k-means++', n_clusters=2).fit_predict(X_moon)
>>> plt.subplot(131)
>>> plt.title('团状簇')
>>> plt.scatter(X_blob[:,0], X_blob[:,1], c=y_blob_pred)
>>> plt.subplot(132)
>>> plt.title('环状簇')
>>> plt.scatter(X_circle[:,0], X_circle[:,1], c=y_circle_pred)
>>> plt.subplot(133)
>>> plt.title('新月簇')
>>> plt.scatter(X_moon[:,0], X_moon[:,1], c=y_moon_pred)
>>> plt.show()

结果显示,k-means仅对团状簇有效,对环状和新月簇无能为力。
20. 基于密度的空间聚类(DBSCAN)
DBSCAN(基于密度的带噪声空间聚类)可以识别任意形状的簇。它将簇视为被低密度区域分隔的高密度区域。两个关键参数:eps(核心样本半径)和min_samples(核心样本邻域内的最少样本数)。下面用不同eps值对新月数据集聚类:
>>> from sklearn import datasets as dss
>>> from sklearn.cluster import DBSCAN
>>> import matplotlib.pyplot as plt
>>> plt.rcParams['font.sans-serif'] = ['FangSong']
>>> plt.rcParams['axes.unicode_minus'] = False
>>> X, y = dss.make_moons(n_samples=1000, noise=0.05)
>>> dbs_1 = DBSCAN() # eps=0.5
>>> dbs_2 = DBSCAN(eps=0.2) # eps=0.2
>>> dbs_3 = DBSCAN(eps=0.1) # eps=0.1
>>> dbs_1.fit(X)
>>> dbs_2.fit(X)
>>> dbs_3.fit(X)
>>> plt.subplot(131)
>>> plt.title('eps=0.5')
>>> plt.scatter(X[:,0], X[:,1], c=dbs_1.labels_)
>>> plt.subplot(132)
>>> plt.title('eps=0.2')
>>> plt.scatter(X[:,0], X[:,1], c=dbs_2.labels_)
>>> plt.subplot(133)
>>> plt.title('eps=0.1')
>>> plt.scatter(X[:,0], X[:,1], c=dbs_3.labels_)
>>> plt.show()

选择合适的eps参数(如0.2),DBSCAN成功将上下弦月分开。
小提示: DBSCAN不需要指定簇的个数,但需要调节eps和min_samples。可以先使用k-distance图来估计合适的eps值。
常见问题: 什么时候用DBSCAN代替k-means?
答案: 当数据呈任意形状(环形、新月形)、簇密度不均匀,或数据中存在大量噪声时,DBSCAN优于k-means。但DBSCAN对参数敏感,且不适用于高维数据(维度灾难)。
第六部分:降维与主成分分析(PCA)
21. 主成分分析(PCA)
PCA通过正交变换将一组可能存在相关性的变量转换为线性不相关的变量(主成分),从而达到降维目的。它并非简单丢掉特征,而是合并相关特征。下面用鸢尾花数据集演示:
>>> from sklearn import datasets as dss
>>> from sklearn.decomposition import PCA
>>> ds = dss.load_iris()
>>> ds.data.shape # (150,4)
>>> m = PCA()
>>> m.fit(ds.data)
>>> m.explained_variance_
array([4.22824171, 0.24267075, 0.0782095 , 0.02383509])
>>> m.explained_variance_ratio_
array([0.92461872, 0.05306648, 0.01710261, 0.00521218])
结果显示:第一个主成分占比超过92%,前两个主成分合计占比超过97.7%。因此可以将4个特征降到2个而不损失太多信息:
>>> m = PCA(n_components=0.97)
>>> m.fit(ds.data)
>>> d = m.transform(ds.data)
>>> d.shape # (150,2)
>>> import matplotlib.pyplot as plt
>>> plt.scatter(d[:,0], d[:,1], c=ds.target)
>>> plt.show()

降维后,仅用2个主成分就能清晰分辨出三种鸢尾花。
通过这21句话和代码练习,你已经掌握了Scikit-learn的核心用法:从数据预处理、特征工程到分类、回归、聚类、降维,每一步都有清晰的模型接口。接下来,你可以尝试用真实数据集解决具体问题,反复调参,在实践中不断深化理解。机器学习的大门已经打开,欢迎进入自由王国。
