机器学习概述(一)

sklearn.datasets

sklearn.datasets 模块用于获取较为经典的数据集。

  • sklearn.datasets.load_*() # 获取小规模数据集,直接从本地获取
  • sklearn.datasets.fetch_*(data_home=None, subset=“train”, shuffle=True, random_state=42) # 获取大规模数据集,需要从网络上下载;data_home 表示用于存储数据集的路径,默认存储在 ~/scikit_learn_data/ 目录下;subset 表示要下载的数据集,可选 “train”、“test”、“all”
  • sklearn.datasets.load_iris() # 从本地获取鸢尾花数据集
    sklearn.datasets.fetch_20newsgroups(data_home=None) # 从网络上下载 20newsgroups 数据集的训练集

load_*()fetch_*() 返回的数据类型为 sklearn.utils.Bunch,是一种字典格式。其中几个主要的键值对如下:

  • data: array([···]),特征数据数组,形如 (n_samples, n_features)
  • target: array([···]),标签数据数组,形如 (n_samples,)
  • feature_names: array([···]),特征名称
  • target_names: array([···]),标签名称
  • DESCR: str(),数据集描述

Bunch 类型可以通过 iris[‘data’] 或 iris.data 获取对应的值。

from sklearn.datasets import load_iris

iris = load_iris()
print(iris['data'])
print(iris.data)

一个数据集通常会被划分为训练集和测试集,划分比例通常为 8:2 或 7:3,可以通过 sklearn.model_selection.train_test_split(arrays, *options) 进行划分。

  • x:数据集的特征值,可以以 ndarray、list 等类型结构传入
  • y:数据集的标签值,可以以 ndarray、list 等类型结构传入
  • test_size:测试集的大小,float,默认为 0.25
  • train_size:训练集的大小,float
  • random_state:随机数种子,不同的种子会产生不同的随机采样结果,默认为 None
  • 返回训练集的特征数据、测试集的特征数据、训练集的标签数据、测试集的标签数据;传入的是 ndarray,返回的也是 ndarray;传入的是 list,返回的也是 list
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
x_train, x_test, y_train, y_test = train_test_split(iris.data, list(iris.target), test_size=0.2)

print(type(x_train))  # <class 'numpy.ndarray'>
print(type(x_test))  # <class 'numpy.ndarray'>
print(type(y_train))  # <class 'list'>
print(type(y_test))  # <class 'list'>
print(x_train.shape)  # (120, 4)
print(x_test.shape)  # (30, 4)

特征工程

数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限。

特征工程旨在将原始特征转换为更能表示预测模型潜在问题的特征,从而提高对未知数据预测的准确性。

特征工程包括特征提取、特征预处理、特征降维等。

特征提取

特征提取旨在将任意类型的数据(如文本或图像)转换为可用于机器学习的数值型数据,这一转换过程也叫做特征值化。

可以通过 sklearn.feature_extraction.DictVectorizer(sparse=True, …) 对字典数据进行特征值化:

  • DictVectorizer.fit_transform(X):X 为一个字典或一个包含字典的迭代器,返回 sparse 矩阵
  • DictVectorizer.inverse_transform(X):X 为一个 sparse 矩阵,返回转换之前的数据格式
  • DictVectorizer.get_feature_names():返回一个列表,包含所有类别
from sklearn.feature_extraction import DictVectorizer

# 创建一个字典列表作为示例数据
data = [{'city': '北京', 'temperature': 100},
        {'city': '上海', 'temperature': 60},
        {'city': '深圳', 'temperature': 30}]

# 创建一个 DictVectorizer 实例
vec = DictVectorizer(sparse=True)  # sparse=True 表示输出稀疏矩阵

# 对示例数据进行向量化
X = vec.fit_transform(data)

# 将 sparse 矩阵转换回之前的格式
y = vec.inverse_transform(X)

# 输出转换后的特征矩阵
print(X)
print(y)
print(vec.get_feature_names())
---------
(0, 1)	1.0
(0, 3)	100.0
(1, 0)	1.0
(1, 3)	60.0
(2, 2)	1.0
(2, 3)	30.0
[{'city=北京': 1.0, 'temperature': 100.0}, {'city=上海': 1.0, 'temperature': 60.0}, {'city=深圳': 1.0, 'temperature': 30.0}]
['city=上海', 'city=北京', 'city=深圳', 'temperature']


"""
vec.fit_transform(data) 会先分析字典的所有类别,['city=上海', 'city=北京', 'city=深圳', 'temperature'] 分别表示为 [0, 1, 2, 3];
因此在上述结果中的小括号内,第一个值表示第几个样本,第二个值表示类别,这两个值就构成了 sparse 矩阵的行列坐标,并使用独热编码对其进行特征值化。
"""


vec = DictVectorizer(sparse=False)  # sparse=False 表示输出稀疏矩阵的二维数组
X = vec.fit_transform(data)
print(X)
---------
[[  0.   1.   0. 100.]
 [  1.   0.   0.  60.]
 [  0.   0.   1.  30.]]

可以通过 sklearn.feature_extraction.text.CountVectorizer(stop_words=None) 对文本数据进行特征值化:

  • CountVectorizer.fit_transform(X):X 为一个字典或一个包含字典的迭代器,返回 sparse 矩阵(词频矩阵)
  • CountVectorizer.inverse_transform(X):X 为一个 sparse 矩阵,返回转换之前的数据格式
  • CountVectorizer.get_feature_names():返回一个列表,包含所有单词
from sklearn.feature_extraction.text import CountVectorizer

# 创建一个字符串列表作为示例数据
data = ['life is short, i like like like python',
        'life is too long, i dislike python']

# 创建一个 CountVectorizer 实例
vec = CountVectorizer()  # 输出词频稀疏矩阵

# 对示例数据进行向量化
X = vec.fit_transform(data)

# 将 sparse 矩阵转换回之前的格式
y = vec.inverse_transform(X)

# 输出转换后的特征矩阵
print(X)
print(y)
print(vec.get_feature_names())  # 获取单词特征列表,标点符号与单个字母不计入列表
print(type(X))
print(X.toarray())
---------
(0, 2)	1
(0, 1)	1
(0, 6)	1
(0, 3)	3
(0, 5)	1
(1, 2)	1
(1, 1)	1
(1, 5)	1
(1, 7)	1
(1, 4)	1
(1, 0)	1
[array(['life', 'is', 'short', 'like', 'python'], dtype='<U7'), array(['life', 'is', 'python', 'too', 'long', 'dislike'], dtype='<U7')]
['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too']
<class 'scipy.sparse.csr.csr_matrix'>
[[0 1 1 3 0 1 1 0]
 [1 1 1 0 1 1 0 1]]


"""
vec.fit_transform(data) 会先分析文本的所有单词,['dislike', 'is', 'life', 'like', 'long', 'python', 'short', 'too'] 分别表示为 [0, 1, 2, 3, 4, 5, 6, 7];
因此在上述结果中的小括号内,第一个值表示第几个样本,第二个值表示不同单词,这两个值就构成了 sparse 矩阵的行列坐标,并使用独热编码对其进行特征值化。
"""


vec = CountVectorizer(stop_words=['is', 'too'])  # 停用参数,其中的单词将不计入单词特征列表
X = vec.fit_transform(data)
print(X.toarray())
print(vec.get_feature_names())
---------
[[0 1 1 0 1 1]
 [1 1 0 1 1 0]]
['dislike', 'life', 'like', 'long', 'python', 'short']
data = ['我 爱 北京 天安门',
        '天安门 上 太阳 升']

vec = CountVectorizer()  # 输出词频稀疏矩阵

X = vec.fit_transform(data)

print(X)
print(vec.get_feature_names())
print(X.toarray())
---------
(0, 0)	1
(0, 1)	1
(1, 1)	1
(1, 2)	1
['北京', '天安门', '太阳']
[[1 1 0]
 [0 1 1]]

TF-IDF 的主要思想是如果某个词语或短语在一篇文章中出现的频率很高,而在其他文章中很少出现,则认为该词语或短语具有很好的类别区分能力,适合用于分类。因此,TF-IDF 的主要作用是用于评估一个词语对于一个文件集或语料库中一份文件的重要程度。

词频(term frequency,tf)指的是某一个词语在该文件中出现的频率。
逆向文档频率(inverse document frequency,idf)是对某一个词语普遍重要性的度量。某一词语的 idf 可由总文件数除以包含该词语的文件数,再对其取以十为底的对数得到。
t f i d f = t f ∗ i d f tfidf = tf * idf tfidf=tfidf
假设语料库中有 1000 篇文章,其中有 100 篇文章包含”经济“一词,现在有一篇新的文章,该文章总共有 100 个词语,其中”经济“一词出现了 10次,tfidf 的计算如下:

  1. t f = 10 / 100 = 0.1 tf = 10 / 100 = 0.1 tf=10/100=0.1
  2. i d f = l o g 10 1000 / 100 = 1 idf = log_{10}1000 / 100 = 1 idf=log101000/100=1
  3. t f i d f = 0.1 ∗ 1 = 0.1 tfidf = 0.1 * 1 = 0.1 tfidf=0.11=0.1

也就是说,”经济“这个词对于该文章的重要程度为 0.1。

可以通过 sklearn.feature_extraction.text.TfidfVectorizer(stop_words=None) 对文本数据进行特征值化:

  • TfidfVectorizer.fit_transform(X):X 为一个字典或一个包含字典的迭代器,返回 sparse 矩阵(权值矩阵)
  • TfidfVectorizer.inverse_transform(X):X 为一个 sparse 矩阵,返回转换之前的数据格式
  • TfidfVectorizer.get_feature_names():返回一个列表,包含所有单词

特征预处理

特征预处理旨在通过一些转换函数,将特征数据转换成更加适合算法模型的特征数据。

数值型数据的无量纲化是指将不同维度的数值型数据统一到同一标准的过程,使得不同维度的特征具有相同的量纲。这个过程可以消除不同维度特征之间的量纲和单位差异,有助于提高模型训练的收敛速度,避免某些特征对模型产生过大影响。

常见的数值型数据的无量纲化方法有:

  • 归一化

    最小-最大缩放(Min-Max Scaling),将数据线性地缩放到一个固定范围内,通常是 [0, 1] 或 [-1, 1];公式如下:
    x ′ = x − m i n ( x ) m a x ( x ) − m i n ( x ) x' = \frac{x - min(x)}{max(x) - min(x)} x=max(x)min(x)xmin(x)
    如果需要将数据映射到指定范围(映射到 [0, 1] 时不需要),还需要进行如下计算:
    x ′ ′ = x ′ ∗ ( u p p e r − l o w e r ) + l o w e r x'' = x' * (upper - lower) + lower x′′=x(upperlower)+lower
    其中, u p p e r upper upper 表示区间的上限, l o w e r lower lower 表示区间的下限;例如我想将数据映射到 [-1, 1] 范围,那么 1 就是区间上限,-1 就是区间下限。

  • 标准化

    Z-Score 标准化(Z-Score Normalization),通过减去均值并除以标准差,将数据转换为均值为 0 标准差为 1 的标准正态分布数据;公式如下:
    x ′ = x − μ σ x' = \frac{x - \mu}{\sigma} x=σxμ

无量纲化可以使不同特征之间的数值范围相近,有利于加快模型训练时的收敛速度,提高模型的精确度和稳定性。

Min-Max Scaling 和 Z-Score Normalization 都属于归一化操作,如果不对数据进行归一化处理,会导致什么问题:

  • 特征尺度不一致:不同特征的数值范围可能相差很大,比如一个特征的取值范围在 0-1 之间,而另一个特征的取值范围在 100-1000 之间,这种情况下,模型可能会更关注数值范围较大的特征,而忽略数值范围较小的特征;归一化处理可以将所有特征的数值范围缩放到相同的区间,以避免此问题发生。
  • 梯度下降速度不均衡:在训练过程中,模型通过梯度下降来更新参数,如果不进行归一化处理,不同特征的梯度大小可能会相差很大,从而导致梯度下降的速度不均衡,这可能会导致训练过程收敛缓慢或不稳定;归一化处理可以使不同特征的梯度大小更加均衡,有助于提高训练的效率和稳定性。
  • 模型对异常值敏感:如果某个特征包含异常值(比如极大或极小的离群值),而其他特征没有异常值,那么模型可能会过度依赖这个特征,导致模型的泛化能力下降;归一化处理可以减小异常值对模型的影响,提高模型的鲁棒性。
  • 模型解释性差:在某些模型中,特征的数值范围会影响模型参数的解释性,如果不进行归一化处理,模型参数的大小和解释性可能会受到特征数值范围的影响,使得模型的解释性变得困难;归一化处理可以消除这种影响,使得模型参数更容易解释。

最小-最大缩放的优缺点:

  • 优点
    • 能够保留原始数据的分布形状,不会改变数据的相对大小关系
    • 公式简单,易于理解和实现
    • 适用性广泛,在数据范围已知的情况下,能够应用于各种类型的数值型数据
  • 缺点
    • 对异常值敏感,非常容易受到异常点的影响(当最小值或最大值是异常值或离群值时,该特征列必定受影响),异常值可能会导致归一化后的数据失去一部分区分度
    • 数据范围受限,归一化后的数据范围受到原始数据最大值和最小值的限制,当新数据超出原始数据范围时,可能需要重新计算最大值和最小值,以确保新数据也在 [0, 1] 或 [-1, 1] 的范围内

综上来看,最小最大缩放这种归一化方法的鲁棒性相对较差,适用于范围已知且数据分布相对均匀的数据集。如果数据的分布非常不均匀或者范围未知,使用最小-最大缩放进行归一化可能会导致数据失真,不利于模型的训练和预测。在这种情况下,使用其他归一化方法,如标准化(Z-score normalization)更为合适。

Z-Score Normalization 的优缺点:

  • 优点
    • Z-score normalization 不受异常值的影响,因为它是基于数据的均值和标准差进行归一化,而不是最大值和最小值
    • Z-score normalization 适用于各种类型的数值型数据,而且不需要知道数据的范围
    • Z-score normalization 保留了原始数据的分布形状,不会改变数据的相对大小关系
  • 缺点
    • Z-score normalization 可能导致数据密度变化,使得数据分布变得更加集中或分散

Z-score normalization 方法相较于最小-最大缩放方法而言,在处理异常值时更具鲁棒性,归一化后的数据相对更稳定。Z-score normalization 基于样本统计量(均值和标准差)进行计算,而不是依赖于固定范围,因此能更好地适应数据集的变化。Z-score normalization 适用于符合正态分布假设的数据集。

可以通过 sklearn.preprocessing.MinMaxScaler(feature_range=(0, 1)) 对数据进行最小-最大缩放:

  • MinMaxScaler.fit_transform(X):X 需为一个形如 (n_samples, n_features) 的 ndarray 或 list
  • 返回一个各特征列经过最小-最大缩放的与输入具有相同形状的 ndarray(不论输入是 ndarray 还是 list,都返回 ndarray)
from sklearn.preprocessing import MinMaxScaler


data = [[90, 5, 145],
        [85, 30, 189],
        [53, 8, 159],
        [98, 15, 110]]

scale = MinMaxScaler(feature_range=(0, 1))  # 实例化 MinMaxScaler 类对象,映射范围为 [0, 1]
X = scale.fit_transform(np.array(data))  # 进行最小-最大缩放计算

print(X)
print(type(X))
---------
[[0.82222222 0.         0.44303797]
 [0.71111111 1.         1.        ]
 [0.         0.12       0.62025316]
 [1.         0.4        0.        ]]
<class 'numpy.ndarray'>

可以通过 sklearn.preprocessing.StandardScaler() 对数据进行 Z-score normalization:

  • StandardScaler.fit_transform(X):X 需为一个形如 (n_samples, n_features) 的 ndarray 或 list
  • 返回一个各特征列经过 Z-score normalization 的与输入具有相同形状的 ndarray(不论输入是 ndarray 还是 list,都返回 ndarray)
from sklearn.preprocessing import StandardScaler


data = [[90, 5, 145],
        [85, 30, 189],
        [53, 8, 159],
        [98, 15, 110]]

scale = StandardScaler()  # 实例化 StandardScaler 类对象,均值为 0,标准差为 1
X = scale.fit_transform(data)  # 进行 Z-score normalization 计算

print(X)
print(type(X))
---------
[[ 0.49721207 -0.98378271 -0.20251156]
 [ 0.20473438  1.60511916  1.3471421 ]
 [-1.66712283 -0.67311449  0.29056006]
 [ 0.96517638  0.05177804 -1.43519061]]
<class 'numpy.ndarray'>

特征降维

特征降维是指在某些限定条件下,降低随机变量(特征)个数,以得到一组不相关主变量的过程。

特征降维的方式主要有:

  • 特征选择
  • 主成分分析(可以理解成一种特征提取的方式)

数据集中往往包含冗余特征或相关性较强的特征,特征选择旨在从原有特征中找出主要特征。

特征选择的方法主要有:

  • Filter(过滤式):主要探究特征本身的特点、特征与特征及特征与目标之间的关联
    • 方差选择法,将低方差特征过滤掉
      • 特征方差小,说明多数样本在该特征上的值较为相近,集中程度较高
      • 特征方差大,说明多数样本在该特征上的值较为疏远,离散程度较高
    • 相关系数法,衡量特征与特征之间的相关程度
      • 如果特征与特征之间的相关性很高,可以选取其中一个,或者加权求和,或者进行主成分分析
  • Embedded(嵌入式):算法自动选择特征(特征与目标之间的关联)
    • 决策树(信息熵、信息增益)
    • 正则化(L1、L2)
    • 深度学习(卷积等)

方差选择法可以通过 sklearn.feature_selection.VarianceThreshold(threshold=0.0) 实现:

  • threshold=0.0 表示删除所有方差小于等于 0.0 的特征列,方差为非负值,因此会删除所有方差等于 0.0 的特征,也就是删除全部值都相同的特征列
  • Variance.fit_transform(X):X 需为一个形如 (n_samples, n_features) 的 ndarray 或 list
  • 返回一个各特征列经过方差计算并删除了方差小于等于阈值的特征列的与输入具有相同形状的 ndarray(不论输入是 ndarray 还是 list,都返回 ndarray)
from sklearn.feature_selection import VarianceThreshold


data = [[50, 1, 3],
        [40, 1, 2],
        [30, 1, 1]]

var = VarianceThreshold()  # 实例化 VarianceThreshold 类对象,threshold 默认为 0.0
X = var.fit_transform(data)  # 进行方差计算,并根据阈值选择特征

print(X)
print(type(X))
---------
[[50  3]
 [40  2]
 [30  1]]
<class 'numpy.ndarray'>

常用的相关系数计算方法有:

  • 皮尔逊相关系数( pearson correlation coefficient),衡量了两个变量(特征)之间的线性相关程度,取值范围为 [-1, 1],-1 表示完全负相关,1 表示完全正相关,0 表示无相关性。

    皮尔逊相关系数只能衡量线性关系,对于非线性关系无法准确刻画。此外,它对异常值敏感,当数据中存在异常值时,相关系数的计算结果可能会受到影响。

    假设有两个变量 X X X Y Y Y,它们的观测值分别为 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn y 1 , y 2 , . . . , y n y_1, y_2, ..., y_n y1,y2,...,yn,则它们的皮尔逊相关系数计算过程如下:

    • 计算 X X X Y Y Y 的均值: x _ m e a n = ∑ i = 1 n x i n x\_mean = \frac{\displaystyle\sum_{i=1}^{n}x_i}{n} x_mean=ni=1nxi y _ m e a n = ∑ i = 1 n y i n y\_mean = \frac{\displaystyle\sum_{i=1}^{n}y_i}{n} y_mean=ni=1nyi
    • 计算 X X X Y Y Y 的协方差: c o v ( X , Y ) = ∑ i = 1 n ( ( x i − x _ m e a n ) ⋅ ( y i − y _ m e a n ) ) n cov(X, Y) = \frac{\displaystyle\sum_{i=1}^{n}((x_i - x\_mean)·(y_i - y\_mean))}{n} cov(X,Y)=ni=1n((xix_mean)(yiy_mean))
    • 计算 X X X Y Y Y 的标准差: x _ s t d = ∑ i = 1 n ( x i − x _ m e a n ) 2 n x\_std = \sqrt \frac{\displaystyle\sum_{i=1}^{n}(x_i - x\_mean)^2}{n} x_std=ni=1n(xix_mean)2 y _ s t d = ∑ i = 1 n ( y i − y _ m e a n ) 2 n y\_std = \sqrt \frac{\displaystyle\sum_{i=1}^{n}(y_i - y\_mean)^2}{n} y_std=ni=1n(yiy_mean)2
    • 计算皮尔逊相关系数: r = c o v ( X , V ) x _ s t d ⋅ y _ s t d r = \frac{cov(X, V)}{x\_std·y\_std} r=x_stdy_stdcov(X,V)
  • 斯皮尔曼相关系数(spearman correlation coefficient),衡量两个变量之间的单调关系强度的非参数统计指标,取值范围为 [-1, 1],-1 表示完全负相关,1 表示完全正相关,0 表示无相关性。

    与皮尔逊相关系数不同,斯皮尔曼相关系数不需要假设数据服从正态分布,并且对非线性关系也能够比较好地描述。此外,当数据中存在异常值时,斯皮尔曼相关系数的计算结果会更加稳健。但是,当数据中存在重复值时,斯皮尔曼相关系数的计算结果可能会失真,因此需要对数据进行去重处理。

    斯皮尔曼相关系数的计算方法如下:

    • 对每个变量的观测值按照大小排序,得到排名(rank)。如果有多个值相同,则它们的排名取平均值
    • 计算每个观测值的排名差(rank difference),即 x i x_i xi 的排名减去 y i y_i yi 的排名,记为 d i d_i di
    • 计算排名差的平方和(sum of squared rank differences),记为 S S S S = Σ ( d i ) 2 S = Σ(d_i)^2 S=Σ(di)2
    • 使用公式 ρ = 1 − ( 6 S / n ( n 2 − 1 ) ) ρ = 1 - (6S / n(n^2-1)) ρ=1(6S/n(n21)),其中 n n n 是观测值的个数

皮尔逊相关系数法可以通过 scipy.stats.pearsonr(x, y) 实现:

  • x、y 需为 array 或 list,它们的维度需相同
  • 返回一个类型为 numpy.float64 的皮尔逊相关系数(不论输入是 array 还是 list,都返回 numpy.float64)
from scipy.stats import pearsonr


data = [[1, 2, 3, 4, 5],
        [10, 23, 32, 48, 54]]

r, _ = pearsonr(data[0], data[1])  # 计算皮尔逊相关系数

print(r)
print(type(r))
---------
0.9929103222184174
<class 'numpy.float64'>

主成分分析可以把具有相关性的高维变量转换为线性无关的低维变量,称为主成分。主成分能够尽可能保留原始数据的信息。

可以通过 sklearn.decomposition.PCA(n_components=None) 实现主成分分析:

  • n_components 为小数,表示保留百分之多少的信息;n_components 为整数,表示减少到多少个特征
  • PCA.fit_transform(X):X 需为一个形如 (n_samples, n_features) 的 ndarray 或 list
  • 返回一个形如 (n_samples, n_components) 的 ndarray(不论输入是 ndarray 还是 list,都返回 ndarray)
from sklearn.decomposition import PCA


data = [[1, 2, 3, 4, 5],
        [10, 23, 32, 48, 54],
        [4, 56, 78, 23, 17]]

pca = PCA(n_components=2)
X = pca.fit_transform(data)

print(X)
print(type(X))
---------
[[ 49.07977129 -16.9822929 ]
 [ -3.07160134  37.60922964]
 [-46.00816996 -20.62693674]]
<class 'numpy.ndarray'>