1、机器学习基础

1 数学知识

1.1 矩阵计算

  1. 向量内积

设两向量为: a ⃗ = ( x 1 , y 1 ) , b ⃗ = ( x 2 , y 2 ) \vec{a} =(x_1,y_1 ),\vec{b}=(x_2,y_2 ) a =(x1,y1)b =(x2,y2),并且 a ⃗ \vec{a} a b ⃗ \vec{b} b 之间的夹角为: θ θ θ

数量积:两个向量的数量积(内积、点积)是一个数量/实数,记作 a ⃗ . b ⃗ \vec{a}.\vec{b} a .b
a ⃗ . b ⃗ = ∣ a ⃗ ∣ ∗ ∣ b ⃗ ∣ ∗ cos ⁡ θ \vec{a}.\vec{b}=|\vec{a}|*|\vec{b}|*\cos \theta a .b =a b cosθ
在这里插入图片描述

  • 通过内积可以判断两个数据信息之间的相关性

    • a ⃗ . b ⃗ > 0 \vec{a}.\vec{b}>0 a .b >0 方向基本相同,夹角在0°到90°之间
    • a ⃗ . b ⃗ = 0 \vec{a}.\vec{b}=0 a .b =0 正交,相互垂直
    • a ⃗ . b ⃗ < 0 \vec{a}.\vec{b}<0 a .b <0 方向基本相反,夹角在90°到180°之间
  • 如果两个向量的点积为0,那么称这两个向量互为正交向量

  • 几何意义上,正交向量之间是垂直关系

  • 如果两个或多个向量,他们的点积均为0,那么它们之间称为正交向量
    在这里插入图片描述

  1. 实对称矩阵

如果有n阶矩阵A,其矩阵的元素都为实数,且矩阵A的转置等于其本身( a i j = a j i a_{ij}=a_{ji} aij=aji)( i , j i,j i,j为元素的脚标), 而且该矩阵对应的特征值全部为实数,则称A为实对称矩阵。
A = A T A=A^T A=AT

  1. 方阵的逆

A A A是数域上的一个n阶方阵,若在相同的数域上存在另一个n阶方阵 B B B,使得 A B = B A = E AB=BA=E AB=BA=E,那么称B为A的逆矩阵,而 A A A被称为可逆矩阵或非奇异矩阵。如果 A A A不存在逆矩阵,那么 A A A称为奇异矩阵。

A的逆矩阵记作: A − 1 A^{-1} A1

特性如下:

  • 如果矩阵 A A A是可逆的,那么矩阵 A A A的逆矩阵是唯一的
  • A A A的逆矩阵的逆矩阵还是 A A A,记作 ( A − 1 ) − 1 = A (A^{-1} )^{-1}=A (A1)1=A
  • 可逆矩阵 A A A的转置矩阵 A T A^T AT也可逆,并且 ( A T ) − 1 = ( A − 1 ) T (A^T)^{-1}=(A^{-1})^T (AT)1=(A1)T
  • 若矩阵 A A A可逆,则矩阵 A A A满足消去律,即 A B = A C = > B = C AB=AC => B=C AB=AC=>B=C
  • 矩阵 A A A可逆的充要条件是行列式 ∣ A ∣ |A| A不等于0
  1. 特征值、特征向量

A A A是n阶矩阵, λ λ λ是一个数, α α α是n维非零列向量,若
A α = λ α ( α ≠ 0 ) A\alpha=\lambda \alpha (\alpha \ne 0) Aα=λα(α=0)
则称 λ λ λ A A A的特征值, α α α A A A的对应于特征值 λ λ λ的特征向量
A α = λ α ⇔ ( α E − A ) α = 0 ⇔ ( α E − A ) x = 0 有非零解 A\alpha=\lambda \alpha \Leftrightarrow (\alpha E -A)\alpha=0 \Leftrightarrow (\alpha E -A)x=0 有非零解 Aα=λα(αEA)α=0(αEA)x=0有非零解
∣ λ E − A ∣ = 0 |\lambda E - A| =0 λEA=0得到特征向量 λ \lambda λ,由 ( λ E − A ) x = 0 (\lambda E - A)x=0 (λEA)x=0得到特征向量 α \alpha α

  1. 特征值分解

特征值和特征向量的定义如下:
A x = λ x ( x ≠ 0 ) Ax=\lambda x (x \ne 0) Ax=λx(x=0)
​ 其中 A A A是一个n×n的矩阵, x x x是一个n维向量,则我们说 λ λ λ是矩阵A的一个特征值,而 x x x是矩阵 A A A的特征值 λ λ λ所对应的特征向量。

​ 如果我们求出了矩阵 A A A的n个特征值 λ 1 ≤ λ 2 ≤ . . . ≤ λ n λ_1≤λ_2≤...≤λ_n λ1λ2...λn,以及这n个特征值所对应的特征向量 w 1 , w 2 , . . . w n {w_1,w_2,...w_n} w1,w2,...wn,如果这n个特征向量线性无关,那么矩阵 A A A就可以用下式的特征分解表示: A = W ∑ W − 1 A=W \sum W^{-1} A=WW1

​ 其中 W W W是这 n n n个特征向量所张成的n×n维矩阵,并对n个特征向量标准化,而 Σ Σ Σ为这 n n n个特征值为主对角线的 n × n n×n n×n维矩阵。若 A A A为实对称矩阵,另有 A = W ∑ W T A=W \sum W^{T} A=WWT

1.2 距离度量

1.2.1 闵可夫斯基距离

假设有两个样本点 x i x_i xi x j x_j xj,有 n n n维度,它们两者间的闵可夫斯基距离 L p Lp Lp定义为
L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p {L_p}({x_i},{x_j}) = {\left( {{{\sum\limits_{l = 1}^n {|x_i^{(l)} - x_j^{(l)}|} }^p}} \right)^{\frac{1}{p}}} Lp(xi,xj)=(l=1nxi(l)xj(l)p)p1
p = 1 p=1 p=1时,称为曼哈顿距离(Manhattan distance),即
L p ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ {L_p}({x_i},{x_j}) = \sum\limits_{l = 1}^n {|x_i^{(l)} - x_j^{(l)}|} Lp(xi,xj)=l=1nxi(l)xj(l)
p = 2 p=2 p=2时,称为欧氏距离(Euclidean distance),即
L p ( x i , x j ) = ∑ l = 1 n ( x i ( l ) − x j ( l ) ) 2 {L_p}({x_i},{x_j}) = \sqrt {\sum\limits_{l = 1}^n {{{(x_i^{(l)} - x_j^{(l)})}^2}} } Lp(xi,xj)=l=1n(xi(l)xj(l))2
p = ∞ p=∞ p=时,称为切比雪夫距离,即
L p ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ , l = 1 , 2 , , , n {L_p}({x_i},{x_j}) = \mathop {\max }\limits_l |x_i^{(l)} - x_j^{(l)}|,l=1,2,,,n Lp(xi,xj)=lmaxxi(l)xj(l),l=1,2,,,n
在这里插入图片描述
在这里插入图片描述

1.2.2 带权平均值

假设有 a 1 a_1 a1 a 2 a_2 a2 a 3 a_3 a3三个数,对应的权重分别为 p 1 p_1 p1 p 2 p_2 p2 p 3 p_3 p3
a ˉ = p 1 a 1 + p 2 a 2 + p 3 a 3 p 1 + p 2 + p 3 \bar a = \frac{{{p_1}{a_1} + {p_2}{a_2} + {p_3}{a_3}}}{{{p_1} + {p_2} + {p_3}}} aˉ=p1+p2+p3p1a1+p2a2+p3a3

2 数据预处理

数据预处理的方法主要包括重复观测处理、去除唯一属性、处理缺失值、属性编码、数据标准化、正则化、特征选择、主成分分析等。

2.1 数据查看

查看行列: data.shape
查看数据详细信息: data.info(),可以查看是否有缺失值
查看数据的描述统计分析: data.describe(),可以查看到异常数据
获取前/后10行数据: data.head(10)、data.tail(10)
查看列标签: data.columns.tolist()
查看行索引: data.index
查看数据类型: data.dtypes
查看数据维度: data.ndim
查看除index外的值: data.values,会以二维ndarray的形式返回DataFrame的数据
查看数据分布(直方图): seaborn.distplot(data[列名].dropna())

(1)箱线图:一种查看噪音的可视化图表

箱形图可以用来观察数据整体的分布情况,利用中位数,25%分位数,75%分位数,上边界,下边界等统计量来来描述数据的整体分布情况。通过计算这些统计量,生成一个箱体图,箱体包含了大部分的正常数据,而在箱体上边界和下边界之外的,就是异常数据。
在这里插入图片描述

2.2 重复观测处理

import pandas as pd
import numpy as np
 
data=pd.DataFrame([[8.3,6],[9.3,4],[6,8],[3,1],[3,1]])
# 重复观测的检测 
print('数据集中是否存在重复观测:\n',any(data.duplicated()))
# 打印
'''
数据集中是否存在重复观测:
True
'''

# 删除重复项 
data.drop_duplicates(inplace = True)
# 重复观测的检测 
print('数据集中是否存在重复观测:\n',any(data.duplicated()))
print(data)

2.3 去除唯一属性

唯一属性通常是一些id属性,这些属性并不能刻画样本自身的分布规律,所以简单地删除这些属性即可。

2.4 缺失值处理

1)缺失值的分类

  • 完全随机缺失:指的是数据的缺失是完全随机的;
  • 随机缺失:指的是数据的缺失不是完全随机的,和完全变量有关;
  • 完全不随机缺失:指的是数据的缺失与不完全变量自身的取值相关; 缺失值会使得系统丢失了大量的有用信息,系统所表现出来的不确定性更加显著,系统中蕴含的确定性成分更难把握,包含空值的不完全变量会使得挖掘过程陷入混乱。

2)缺失值处理的三种方法

  • 直接使用含有缺失值的特征;
  • 删除含有缺失值的特征(属性含有大量缺失值而仅仅包含极少量有效值时是有效的);
  • 缺失值补全。

删除含有缺失值的特征:若变量的缺失率较高(大于80%),覆盖率较低,且重要性较低,可以直接将变量删除。

dropna函数作用:删除含有空值的行或列

  • DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
  • axis:维度,axis=0表示index行,axis=1表示columns列,默认为0
  • how:"all"表示这一行或列中的元素全部缺失(为nan)才删除这一行或列,"any"表示这一行或列中只要有元素缺失,就删除这一行或列
  • thresh:一行或一列中至少出现了thresh个才删除。
  • subset:在某些列的子集中选择出现了缺失值的列删除,不在子集中的含有缺失值得列或行不会删除(有axis决定是行还是列)
  • inplace:刷选过缺失值得新数据是存为副本还是直接在原数据上进行修改。
import pandas as pd
import numpy as np
 
data=pd.DataFrame([[8.3,6,],[9.3,4,],[6,8,8],[5,6],[3,1,8]],columns=('a','b','c'))
# 缺失观测的检测 
print('数据集中是否存在缺失值:\n',any(data.isnull()))
print(data)
'''
数据集中是否存在缺失值:
 True
 
     a  b    c
0  8.3  6  NaN
1  9.3  4  NaN
2  6.0  8  8.0
3  5.0  6  NaN
4  3.0  1  8.0
''' 
 
# 删除法之变量删除
data.drop(["c"],axis =1 ,inplace=True)
print(data)
'''
     a  b
0  8.3  6
1  9.3  4
2  6.0  8
3  5.0  6
4  3.0  1
'''
# 删除法之记录删除 
data=data.dropna(axis=0,how='any')
'''
     a  b    c
2  6.0  8  8.0
4  3.0  1  8.0
''

如果缺少特征值信息的数据时,可能导致模型无法进行计算,进行缺失值补全有以下方法:

  • 方法一:

    • 用平均值、中值、分位数、众数、随机值等替代

    • 优点:简单 缺点:人为增加了噪声

      # 替换法之前向替换
      #data.fillna(method = 'ffill')
      # 替换法之后向替换
      #data.fillna(method = 'bfill')
      #替换法之补平均数
      #data['c']=data['c'].fillna(data['c'].mean())
      #替换法之补众数
      #data['c']=data['c'].fillna(data['c'].mode())
      #替换法之补中位数
      data['c']=data['c'].fillna(data['c'].median())
      print(data)
      
  • 方法二:

    • 用其他变量做回归、贝叶斯、随机森林、决策树等模型对缺失数据进行预测来算出缺失变量。
    • 看似更符合实际,但是如果其他变量和缺失变量无关,则预测的结果无意义
  • 方法三:

    • 最精确的做法,把变量映射到高维空间。
    • 比如性别,有男、女、缺失三种情况,则映射成3个变量:是否男、是否女、是否缺失。

2.5 分类特征编码处理

使用机器学习时,会存在不同的特征类型:连续型特征和离散型特征。 针对连续性特征,我们通常将其线性缩放到[-1, 1]区间或者缩放到均值为0,方差为1的范围。 但是,特征并不总是连续值,而有可能是分类值、离散值。因此,我们也需要对离散值进行特征编码数据预处理。 离散特征的编码按照不同的划分标准,类别型变量有:

  • 按照类别是否有序:有序无序的类别特征。
  • 按照类别数量:高基类低基类的类别特征。

标签编码

标签编码(Label Encoding)是使用字典的方式,将每个类别标签与不断增加的整数相关联,即生成一个名为class_的实例数组的索引。

Scikit-learn中的LabelEncoder是用来对分类型特征值进行编码,即对不连续的数值或文本进行编码。其中包含以下常用方法:

  • fit(y) :fit可看做一本空字典,y可看作要塞到字典中的词。
  • fit_transform(y):相当于先进行fit再进行transform,即把y塞到字典中去以后再进行transform得到索引值。
  • inverse_transform(y):根据索引值y获得原始数据。
  • transform(y) :将y转变成索引值。
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
city_list = ["paris", "paris", "tokyo", "amsterdam"]
le.fit(city_list)
print(le.classes_)  # 输出为:['amsterdam' 'paris' 'tokyo']
city_list_le = le.transform(city_list)  # 进行Encode
print(city_list_le)  # 输出为:[1 1 2 0]
city_list_new = le.inverse_transform(city_list_le)  # 进行decode
print(city_list_new) # 输出为:['paris' 'paris' 'tokyo' 'amsterdam']

多列数据编码方式:

import pandas as pd
from sklearn.preprocessing import LabelEncoder
df = pd.DataFrame({
    'pets': ['cat', 'dog', 'cat', 'monkey', 'dog', 'dog'],
    'owner': ['Champ', 'Ron', 'Brick', 'Champ', 'Veronica', 'Ron'],
    'location': ['San_Diego', 'New_York', 'New_York', 'San_Diego', 'San_Diego',
                 'New_York']
})
d = {}
le = LabelEncoder()
cols_to_encode = ['pets', 'owner', 'location']
for col in cols_to_encode:
    df_train[col] = le.fit_transform(df_train[col])
    d[col] = le.classes_

Pandas的factorize()可以将Series中的标称型数据映射称为一组数字,相同的标称型映射为相同的数字。factorize函数的返回值是一个tuple(元组),元组中包含两个元素。第一个元素是一个array,其中的元素是标称型元素映射为的数字;第二个元素是Index类型,其中的元素是所有标称型元素,没有重复。

import numpy as np
import pandas as pd
df = pd.DataFrame(['green','bule','red','bule','green'],columns=['color'])
pd.factorize(df['color'])  #(array([0, 1, 2, 1, 0], dtype=int64),Index(['green', 'bule', 'red'], dtype='object'))
pd.factorize(df['color'])[0] #array([0, 1, 2, 1, 0], dtype=int64)
pd.factorize(df['color'])[1]  #Index(['green', 'bule', 'red'], dtype='object')

Label Encoding只是将文本转化为数值,并没有解决文本特征的问题:所有的标签都变成了数字,算法模型直接将根据其距离来考虑相似的数字,而不考虑标签的具体含义。使用该方法处理后的数据适合支持类别性质的算法模型,如LightGBM。

序列编码

序列编码(Ordinal Encoding)即最为简单的一种思路,对于一个具有m个category的Feature,我们将其对应地映射到 [0,m-1] 的整数。当然 Ordinal Encoding 更适用于 Ordinal Feature,即各个特征有内在的顺序。例如对于”学历”这样的类别,”学士”、”硕士”、”博士” 可以很自然地编码成 [0,2],因为它们内在就含有这样的逻辑顺序。但如果对于“颜色”这样的类别,“蓝色”、“绿色”、“红色”分别编码成[0,2]是不合理的,因为我们并没有理由认为“蓝色”和“绿色”的差距比“蓝色”和“红色”的差距对于特征的影响是不同的。

ord_map = {'Gen 1': 1, 'Gen 2': 2, 'Gen 3': 3, 'Gen 4': 4, 'Gen 5': 5, 'Gen 6': 6}
df['GenerationLabel'] = df['Generation'].map(gord_map)

独热编码

在实际的机器学习的应用任务中,特征有时候并不总是连续值,有可能是一些分类值,如性别可分为male和female。在机器学习任务中,对于这样的特征,通常我们需要对其进行特征数字化,比如有如下三个特征属性:

  • 性别:[“male”,”female”]
  • 地区:[“Europe”,”US”,”Asia”]
  • 浏览器:[“Firefox”,”Chrome”,”Safari”,”Internet Explorer”]

对于某一个样本,如[“male”,”US”,”Internet Explorer”],我们需要将这个分类值的特征数字化,最直接的方法,我们可以采用序列化的方式:[0,1,3]。但是,即使转化为数字表示后,上述数据也不能直接用在我们的分类器中。因为,分类器往往默认数据是连续的,并且是有序的。按照上述的表示,数字并不是有序的,而是随机分配的。这样的特征处理并不能直接放入机器学习算法中。

为了解决上述问题,其中一种可能的解决方法是采用独热编码(One-Hot Encoding)。独热编码,又称为一位有效编码。其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候,其中只有一位有效。可以这样理解,对于每一个特征,如果它有m个可能值,那么经过独热编码后,就变成了m个二元特征。并且,这些特征互斥,每次只有一个激活。因此,数据会变成稀疏的。

对于上述的问题,性别的属性是二维的,同理,地区是三维的,浏览器则是四维的,这样,我们可以采用One-Hot编码的方式对上述的样本[“male”,”US”,”Internet Explorer”]编码,male则对应着[1,0],同理US对应着[0,1,0],Internet Explorer对应着[0,0,0,1]。则完整的特征数字化的结果为:[1,0,0,1,0,0,0,0,1]。

在这里插入图片描述

为什么能使用One-Hot Encoding?

  • 使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。在回归,分类,聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算,计算余弦相似性,也是基于的欧式空间。
  • 将离散型特征使用one-hot编码,可以会让特征之间的距离计算更加合理。比如,有一个离散型特征,代表工作类型,该离散型特征,共有三个取值,不使用one-hot编码,计算出来的特征的距离是不合理。那如果使用one-hot编码,显得更合理。

独热编码优缺点

  • 优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
  • 缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且one hot encoding+PCA这种组合在实际中也非常有用。

One-Hot Encoding的使用场景

  • 独热编码用来解决类别型数据的离散值问题。将离散型特征进行one-hot编码的作用,是为了让距离计算更合理,但如果特征是离散的,并且不用one-hot编码就可以很合理的计算出距离,那么就没必要进行one-hot编码,比如,该离散特征共有1000个取值,我们分成两组,分别是400和600,两个小组之间的距离有合适的定义,组内的距离也有合适的定义,那就没必要用one-hot 编码。
  • 基于树的方法是不需要进行特征的归一化,例如随机森林,bagging 和 boosting等。对于决策树来说,one-hot的本质是增加树的深度,决策树是没有特征大小的概念的,只有特征处于他分布的哪一部分的概念。

基于Scikit-learn 的one hot encoding

LabelBinarizer:将对应的数据转换为二进制型,类似于onehot编码,这里有几点不同:

  • 可以处理数值型和类别型数据
  • 输入必须为1D数组
  • 可以自己设置正类和父类的表示方式
from sklearn.preprocessing import LabelBinarizer
 
lb = LabelBinarizer() 
city_list = ["paris", "paris", "tokyo", "amsterdam"] 
lb.fit(city_list)
print(lb.classes_)  # 输出为:['amsterdam' 'paris' 'tokyo']
 
city_list_le = lb.transform(city_list)  # 进行Encode
print(city_list_le)  # 输出为:
# [[0 1 0]
#  [0 1 0]
#  [0 0 1]
#  [1 0 0]]
 
city_list_new = lb.inverse_transform(city_list_le)  # 进行decode
print(city_list_new)  # 输出为:['paris' 'paris' 'tokyo' 'amsterdam']

OneHotEncoder只能对数值型数据进行处理,需要先将文本转化为数值(Label encoding)后才能使用,只接受2D数组:

import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
def LabelOneHotEncoder(data, categorical_features):
    d_num = np.array([])
    for f in data.columns:
        if f in categorical_features:
            le, ohe = LabelEncoder(), OneHotEncoder()
            data[f] = le.fit_transform(data[f])
            if len(d_num) == 0:
                d_num = np.array(ohe.fit_transform(data[[f]]))
            else:
                d_num = np.hstack((d_num, ohe.fit_transform(data[[f]]).A))
        else:
            if len(d_num) == 0:
                d_num = np.array(data[[f]])
            else:
                d_num = np.hstack((d_num, data[[f]]))
    return d_num
df = pd.DataFrame([
    ['green', 'Chevrolet', 2017],
    ['blue', 'BMW', 2015],
    ['yellow', 'Lexus', 2018],
])
df.columns = ['color', 'make', 'year']
df_new = LabelOneHotEncoder(df, ['color', 'make', 'year'])

基于Pandas的one hot encoding

其实如果我们跳出 scikit-learn, 在 pandas 中可以很好地解决这个问题,用 pandas 自带的get_dummies函数即可

import pandas as pd
 
df = pd.DataFrame([
    ['green', 'Chevrolet', 2017],
    ['blue', 'BMW', 2015],
    ['yellow', 'Lexus', 2018],
])
df.columns = ['color', 'make', 'year']
df_processed = pd.get_dummies(df, prefix_sep="_", columns=df.columns[:-1])
print(df_processed)

get_dummies的优势在于:

  • 本身就是 pandas 的模块,所以对 DataFrame 类型兼容很好
  • 不管你列是数值型还是字符串型,都可以进行二值化编码
  • 能够根据指令,自动生成二值化编码后的变量名

get_dummies虽然有这么多优点,但毕竟不是 sklearn 里的transformer类型,所以得到的结果得手动输入到 sklearn 里的相应模块,也无法像 sklearn 的transformer一样可以输入到pipeline中进行流程化地机器学习过程。

哈希编码

哈希编码是使用二进制对标签编码做哈希映射。好处在于哈希编码器不需要维持类别字典,若后续出现训练集未出现的类别,哈希编码(Hash Encoder)也能适用。但按位分开哈希编码,模型学习相对比较困难。

# !pip install category_encoders
import category_encoders as ce
x = pd.DataFrame({'gender':[2, 1, 1]})
ce_encoder = ce.HashingEncoder(cols = ['gender']).fit(x)
x_trans = ce_encoder.transform(x)

>>x_trans
col_0  col_1  col_2  col_3  col_4  col_5  col_6  col_7
0      0      0      0      0      1      0      0      0
1      0      0      0      1      0      0      0      0
2      0      0      0      1      0      0      0      0

计数编码

将类别特征替换为训练集中的计数(一般是根据训练集来进行计数,属于统计编码的一种,统计编码,就是用类别的统计特征来代替原始类别,比如类别A在训练集中出现了100次则编码为100)。这个方法对离群值很敏感,所以结果可以归一化或者转换一下(例如使用对数变换)。未知类别可以替换为1。

频数编码(Count Encoder)使用频次替换类别。有些变量的频次可能是一样的,这将导致碰撞。尽管可能性不是非常大,没法说这是否会导致模型退化,不过原则上我们不希望出现这种情况。

import pandas as pd
data_count = data.groupby('城市')['城市'].agg({'频数':'size'}).reset_index()
data = pd.merge(data, data_count, on = '城市', how = 'left')

目标编码

目标编码(target encoding),亦称均值编码(mean encoding)、似然编码(likelihood encoding)、效应编码(impact encoding),是一种能够对高基数(high cardinality)自变量进行编码的方法 (Micci-Barreca 2001) 。

如果某一个特征是定性的(categorical),而这个特征的可能值非常多(高基数),那么目标编码(Target encoding)是一种高效的编码方式。在实际应用中,这类特征工程能极大提升模型的性能。

一般情况下,针对定性特征,我们只需要使用sklearn的OneHotEncoder或LabelEncoder进行编码。

LabelEncoder能够接收不规则的特征列,并将其转化为从0到n-1的整数值(假设一共有n种不同的类别);OneHotEncoder则能通过哑编码,制作出一个m*n的稀疏矩阵(假设数据一共有m行,具体的输出矩阵格式是否稀疏可以由sparse参数控制)。

定性特征的基数(cardinality)指的是这个定性特征所有可能的不同值的数量。在高基数(high cardinality)的定性特征面前,这些数据预处理的方法往往得不到令人满意的结果。

高基数定性特征的例子:IP地址、电子邮件域名、城市名、家庭住址、街道、产品号码。

主要原因:

  • LabelEncoder编码高基数定性特征,虽然只需要一列,但是每个自然数都具有不同的重要意义,对于y而言线性不可分。使用简单模型,容易欠拟合(underfit),无法完全捕获不同类别之间的区别;使用复杂模型,容易在其他地方过拟合(overfit)。
  • OneHotEncoder编码高基数定性特征,必然产生上万列的稀疏矩阵,易消耗大量内存和训练时间,除非算法本身有相关优化(例:SVM)。

如果某个类别型特征基数比较低(low-cardinality features),即该特征的所有值去重后构成的集合元素个数比较少,一般利用One-hot编码方法将特征转为数值型。One-hot编码可以在数据预处理时完成,也可以在模型训练的时候完成,从训练时间的角度,后一种方法的实现更为高效,CatBoost对于基数较低的类别型特征也是采用后一种实现。

显然,在高基数类别型特征(high cardinality features) 当中,比如 user ID,这种编码方式会产生大量新的特征,造成维度灾难。一种折中的办法是可以将类别分组成有限个的群体再进行One-hot编码。一种常被使用的方法是根据目标变量统计(Target Statistics,以下简称TS)进行分组,目标变量统计用于估算每个类别的目标变量期望值。甚至有人直接用TS作为一个新的数值型变量来代替原来的类别型变量。重要的是,可以通过对TS数值型特征的阈值设置,基于对数损失、基尼系数或者均方差,得到一个对于训练集而言将类别一分为二的所有可能划分当中最优的那个。在LightGBM当中,类别型特征用每一步梯度提升时的梯度统计(Gradient Statistics,以下简称GS)来表示。虽然为建树提供了重要的信息,但是这种方法有以下两个缺点:

  • 增加计算时间,因为需要对每一个类别型特征,在迭代的每一步,都需要对GS进行计算
  • 增加存储需求,对于一个类别型变量,需要存储每一次分离每个节点的类别

为了克服这些缺点,LightGBM以损失部分信息为代价将所有的长尾类别归为一类,作者声称这样处理高基数类别型特征时比One-hot编码还是好不少。不过如果采用TS特征,那么对于每个类别只需要计算和存储一个数字。因此,采用TS作为一个新的数值型特征是最有效、信息损失最小的处理类别型特征的方法。TS也被广泛应用在点击预测任务当中,这个场景当中的类别型特征有用户、地区、广告、广告发布者等。接下来我们着重讨论TS,暂时将One-hot编码和GS放一边。

以下是计算公式:
s = 1 1 + e x p ( − c o u n t ( k ) − m d l a ) c a t e k = p r i o r ∗ ( 1 − s ) + s ∗ s u m ( p o s k ) c o u n t ( k ) s=\frac{1}{1+exp(−\frac{ count(k)−mdl }{a })} \\ cate^k = prior ∗(1−s )+ s∗\frac{sum(pos^k) }{count(k) } \\ s=1+exp(acount(k)mdl)1catek=prior(1s)+scount(k)sum(posk)

  • m d l mdl mdl:为类目 k k k允许最小出现次数,是一个超参数;
  • c o u n t ( k ) count(k) count(k):为类目 k k k的出现次数;
  • a a a:为正则化系数,防止过拟合;
  • s u m ( p o s k ) sum(pos^k) sum(posk):为类目 k k k中正样本个数;
  • p r i o r prior prior:先验值,一般是 总体正样本数 总样本数 \frac{总体正样本数}{总样本数} 总样本数总体正样本数

此方法同样容易引起过拟合,以下方法用于防止过拟合:

  • 增加正则项 a a a的大小
  • 在训练集该列中添加噪声
  • 使用交叉验证

目标编码属于有监督的编码方式,如果运用得当则能够有效地提高预测模型的准确性 (Pargent, Bischl, and Thomas 2019) ;而这其中的关键,就是在编码的过程中引入正则化,避免过拟合问题。

例如类别A对应的标签1有200个,标签2有300个,标签3有500个,则可以编码为:2/10,3/10,3/6。中间最重要的是如何避免过拟合(原始的target encoding直接对全部的训练集数据和标签进行编码,会导致得到的编码结果太过依赖与训练集),常用的解决方法是使用2 levels of cross-validation求出target mean,思路如下:

  • 把train data划分为20-folds (举例:infold: fold #2-20, out of fold: fold #1)
    • 将每一个 infold (fold #2-20) 再次划分为10-folds (举例:inner_infold: fold #2-10, Inner_oof: fold #1)
    • 计算 10-folds的 inner out of folds值 (举例:使用inner_infold #2-10 的target的均值,来作为inner_oof #1的预测值)
    • 对10个inner out of folds 值取平均,得到 inner_oof_mean
    • 计算oof_mean (举例:使用 infold #2-20的inner_oof_mean 来预测 out of fold #1的oof_mean
  • 将train data 的 oof_mean 映射到test data完成编码

比如划分为10折,每次对9折进行标签编码然后用得到的标签编码模型预测第10折的特征得到结果,其实就是常说的均值编码。

目标编码尝试对分类特征中每个级别的目标总体平均值进行测量。这意味着,当每个级别的数据更少时,估计的均值将与“真实”均值相距更远,方差更大。

from category_encoders import TargetEncoder
import pandas as pd
from sklearn.datasets import load_boston
# prepare some data
bunch = load_boston()
y_train = bunch.target[0:250]
y_test = bunch.target[250:506]
X_train = pd.DataFrame(bunch.data[0:250], columns=bunch.feature_names)
X_test = pd.DataFrame(bunch.data[250:506], columns=bunch.feature_names)
# use target encoding to encode two categorical features
enc = TargetEncoder(cols=['CHAS', 'RAD'])
# transform the datasets
training_numeric_dataset = enc.fit_transform(X_train, y_train)
testing_numeric_dataset = enc.transform(X_test)

Beta Target Encoding

kaggle竞赛Avito Demand Prediction Challenge 第14名的solution分享:14th Place Solution: The Almost Golden Defenders。和target encoding 一样,beta target encoding 也采用 target mean value (among each category) 来给categorical feature做编码。不同之处在于,为了进一步减少target variable leak,beta target encoding发生在在5-fold CV内部,而不是在5-fold CV之前:

把train data划分为5-folds (5-fold cross validation)

  • target encoding based on infold data
  • train model
  • get out of fold prediction

同时beta target encoding 加入了smoothing term,用 bayesian mean 来代替mean。Bayesian mean (Bayesian average) 的思路: 某一个category如果数据量较少(<N_min),noise就会比较大,需要补足数据,达到smoothing 的效果。补足数据值 = prior mean。N_min 是一个regularization term,N_min 越大,regularization效果越强。

另外,对于target encoding和beta target encoding,不一定要用target mean (or bayesian mean),也可以用其他的统计值包括 medium, frqequency, mode, variance, skewness, and kurtosis — 或任何与target有correlation的统计值。

# train -> training dataframe
# test -> test dataframe
# N_min -> smoothing term, minimum sample size, if sample size is less than N_min, add up to N_min 
# target_col -> target column
# cat_cols -> categorical colums
# Step 1: fill NA in train and test dataframe
# Step 2: 5-fold CV (beta target encoding within each fold)
kf = KFold(n_splits=5, shuffle=True, random_state=0)
for i, (dev_index, val_index) in enumerate(kf.split(train.index.values)):
    # split data into dev set and validation set
    dev = train.loc[dev_index].reset_index(drop=True) 
    val = train.loc[val_index].reset_index(drop=True)
        
    feature_cols = []    
    for var_name in cat_cols:
        feature_name = f'{var_name}_mean'
        feature_cols.append(feature_name)
        
        prior_mean = np.mean(dev[target_col])
        stats = dev[[target_col, var_name]].groupby(var_name).agg(['sum', 'count'])[target_col].reset_index()           
   
        ### beta target encoding by Bayesian average for dev set 
        df_stats = pd.merge(dev[[var_name]], stats, how='left')
        df_stats['sum'].fillna(value = prior_mean, inplace = True)
        df_stats['count'].fillna(value = 1.0, inplace = True)
        N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
        dev[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
        ### beta target encoding by Bayesian average for val set
        df_stats = pd.merge(val[[var_name]], stats, how='left')
        df_stats['sum'].fillna(value = prior_mean, inplace = True)
        df_stats['count'].fillna(value = 1.0, inplace = True)
        N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
        val[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
        
        ### beta target encoding by Bayesian average for test set
        df_stats = pd.merge(test[[var_name]], stats, how='left')
        df_stats['sum'].fillna(value = prior_mean, inplace = True)
        df_stats['count'].fillna(value = 1.0, inplace = True)
        N_prior = np.maximum(N_min - df_stats['count'].values, 0)   # prior parameters
        test[feature_name] = (prior_mean * N_prior + df_stats['sum']) / (N_prior + df_stats['count']) # Bayesian mean
        
        # Bayesian mean is equivalent to adding N_prior data points of value prior_mean to the data set.
        del df_stats, stats
    # Step 3: train model (K-fold CV), get oof prediction

WOE编码

WOE(Weight of Evidence,证据权重)编码适用于二分类任务,WOE表明自变量相对于因变量的预测能力。WOE特别合适逻辑回归,因为Logit=log(odds)。WOE编码的变量被编码为统一的维度(是一个被标准化过的值),变量之间直接比较系数即可。Weight Of Evidence 同样是基于target的方法,只是其选用odds作为变化底数。
W O E i = ln ⁡ ( B a d i G o o d i B a d t o t a l G o o d t o t a l ) = ln ⁡ B a d i G o o d i − ln ⁡ B a d t o t a l G o o d t o t a l WOE_i=\ln(\frac{\frac{Bad_i}{Good_i}}{\frac{Bad_{total}}{Good_{total}}})=\ln{\frac{Bad_i}{Good_i}}-\ln{\frac{Bad_{total}}{Good_{total}}} WOEi=ln(GoodtotalBadtotalGoodiBadi)=lnGoodiBadilnGoodtotalBadtotal
其中,i 为待编码变量的第 i 个取值(或者说分箱), B a d i Bad_i Badi为第 i 个取值(或者说分箱)中坏样本的数量, B a d t o t a l Bad_{total} Badtotal为总样本中坏样本的数量, G o o d i Good_i Goodi G o o d t o t a l Good_{total} Goodtotal 的意义同理。

  • WOE 可以理解为当前组中正负样本的比值,与所有样本中正负样本比值的差异。这个差异是用这两个比值的比值,再取对数来表示的。
  • WOE > 0 表示当前组正负样本比例大于总体的正负样本比例,值越大表示这个分组里的坏样本的可能性越大;
  • WOE < 0 表示当前组正负样本比例小于总体的正负样本比例,值越小表示这个分组里的坏样本的可能性越小。
  • WOE绝对值越大,对于分类贡献越大。当分箱中正负的比例等于随机(大盘)正负样本的比值时,说明这个分箱没有预测能力,即 WOE = 0。
from category_encoders import WOEEncoder
import pandas as pd

df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 1, 1, 1]})
enc = WOEEncoder(cols=['cat_feat']).fit(df, df['label'])
df_trans = enc.transform(df)

>>df_trans
cat_feat  label
0  0.287682  0
1  0.287682  1
2  -0.405465  0
3  0.287682  1
4  -0.405465  1
5  0.287682  1

CatBoost Encoding

CatBoost encoding也是target encoding的一种, 对于可取值的数量比独热最大量还要大的分类变量,CatBoost 使用了一个非常有效的编码方法,这种方法和均值编码类似,但可以降低过拟合情况。它的具体实现方法如下:

  • 将输入样本集随机排序,并生成多组随机排列的情况。

  • 将浮点型或属性值标记转化为整数。

  • 将所有的分类特征值结果都根据以下公式,转化为数值结果。
    a v g _ t a r g e t = C o u n t I n C l a s s + P r i o r T o t a l C o u n t + 1 avg\_target=\frac{ CountInClass+Prior }{ TotalCount+1} \\ avg_target=TotalCount+1CountInClass+Prior
    其中 CountInClass 表示在当前分类特征值中,有多少样本的标记值是1;Prior 是分子的初始值,根据初始参数确定。TotalCount 是在所有样本中(包含当前样本),和当前样本具有相同的分类特征值的样本数量。

CatBoost处理Categorical features总结:

  • 首先,他们会计算一些数据的statistics。计算某个category出现的频率,加上超参数,生成新的numerical features。这一策略要求同一标签数据不能排列在一起(即先全是0之后全是1这种方式),训练之前需要打乱数据集。
  • 第二,使用数据的不同排列(实际上是4个)。在每一轮建立树之前,先扔一轮骰子,决定使用哪个排列来生成树。
  • 第三,考虑使用categorical features的不同组合。例如颜色和种类组合起来,可以构成类似于blue dog这样的feature。当需要组合的categorical features变多时,catboost只考虑一部分combinations。在选择第一个节点时,只考虑选择一个feature,例如A。在生成第二个节点时,考虑A和任意一个categorical feature的组合,选择其中最好的。就这样使用贪心算法生成combinations。
  • 第四,除非向gender这种维数很小的情况,不建议自己生成one-hot vectors,最好交给算法来处理。

M-Estimate Encoding

M-Estimate Encoding是CatBoost encoding的进化版,主要引入了一个 m m m超参数( m m m的推荐值为1到100)去缓解过拟合,相当于 一个简化版的Target Encoding:
c a t e k = s u m ( p o s k ) + p r i o r ∗ m c o u n t ( k ) + m cate^k =\frac{sum(pos^k) +prior*m }{count(k)+m } \\ catek=count(k)+msum(posk)+priorm
其中 s u m ( p o s k ) sum(pos^k) sum(posk)代表所有正Label的个数, m m m是一个调参的参数( m m m的推荐值为1到100), m m m越大过拟合的程度就会越小;同样的在处理连续值时 s u m ( p o s k ) + p r i o r ∗ m sum(pos^k) +prior*m sum(posk)+priorm可以换成label的求和, c o u n t ( k ) + m count(k)+m count(k)+m换成所有label的求和。

直方图编码

直方图编码(Bin Encoder)属于目标编码的一种,适用于分类任务。它先将类别属性分类,然后在对应属性下,统计不同类别标签的样本****占比进行编码。直方图编码能清晰看出特征下不同类别对不同预测标签的贡献度,缺点在于:使用了标签数据,若训练集和测试集的类别特征分布不一致,那么编码结果容易引发过拟合。此外,直方图编码出的特征数量是分类标签的类别数量,若标签类别很多,可能会给训练带来空间和时间上的负担。直方图编码样例如下图所示:
在这里插入图片描述

import pandas as pd

class hist_encoder:
    '''直方图编码器
    params:
        df         (pd.DataFrame): 待编码的dataframe数据
        encode_feat_name    (str): 编码的类别特征名,当前代码只支持单个特征编码,若要批量编码,请自行实现
        label_name          (str): 类别标签
    '''
    def __init__(self, df, encode_feat_name, label_name):
        self.df = df.copy()
        self.encode_feat_name = encode_feat_name
        self.label_name = label_name

    def fit(self):
        '''用训练集获取编码字典'''
        # 分子:类别特征下给定类别,在不同分类标签下各类别的数量
        self.df['numerator'] = 1
        numerator_df = self.df.groupby([self.encode_feat_name, self.label_name])['numerator'].count().reset_index()

        # 分母:分类标签下各类别的数量
        self.df['denumerator'] = 1
        denumerator_df = self.df.groupby(self.encode_feat_name)['denumerator'].count().reset_index()

        # 类别特征类别、分类标签类别:直方图编码映射字典
        encoder_df = pd.merge(numerator_df, denumerator_df, on = self.encode_feat_name)
        encoder_df['encode'] = encoder_df['numerator'] / encoder_df['denumerator'] 

        self.encoder_df = encoder_df[[self.encode_feat_name, self.label_name, 'encode']]

    def transform(self, test_df):
        '''对测试集编码'''
        # 依次编码出: hist特征1, hist特征2, ...
        test_trans_df = test_df.copy()
        for label_cat in test_trans_df[self.label_name].unique():
            hist_feat = []
            for cat_feat_val in test_trans_df[self.encode_feat_name].values:
                try:
                    encode_val = encoder_df[(encoder_df[self.label_name] == label_cat) & (encoder_df[self.encode_feat_name] == cat_feat_val)]['encode'].item()
                    hist_feat.append(encode_val)
                except:
                    hist_feat.append(0)
            encode_fname = self.encode_feat_name + '_en{}'.format(str(label_cat)) # 针对类别特征-类别label_cat的直方图编码特征名
            test_trans_df[encode_fname] = hist_feat # 将编码的特征加入到原始数据中    
        return test_trans_df

# 初始化数据
df = pd.DataFrame({'cat_feat':['A', 'A', 'B', 'A', 'B', 'A'], 'label':[0, 1, 0, 2, 1, 2]})
encode_feat_name = 'cat_feat'
label_name = 'label'

# 直方图编码
he = hist_encoder(df, encode_feat_name, label_name)
he.fit()
df_trans = he.transform(df)

>>df
cat_feat  label
0  A  0
1  A  1
2  B  0
3  A  2
4  B  1
5  A  2

>>df_trans
cat_feat  label  cat_feat_en0  cat_feat_en1  cat_feat_en2
0  A  0  0.25  0.25  0.5
1  A  1  0.25  0.25  0.5
2  B  0  0.50  0.50  0.0
3  A  2  0.25  0.25  0.5
4  B  1  0.50  0.50  0.0
5  A  2  0.25  0.25  0.5

2.6 连续特征编码处理

连续特征经常是用户或者事物对应一些行为的统计值,常见的处理方法包括:

  • 归一化
  • 标准化
  • 离散化

归一化

归一化一般是将数据映射到指定的范围,用于去除不同维度数据的量纲以及量纲单位,归一化方法有:

  1. Min-Max 归一化

    Min-Max 归一化是指对原始数据进行线性变换,将值映射到[0,1]之间。Min-Max 归一化的计算公式为:

x ′ = x − x m i n x m a x − x x'=\frac{x-x_{min}}{x_{max}-x} x=xmaxxxxmin

​ 式中: x x x为原始数据, x ′ x′ x 为Min-Max 归一化后的数据。 x m i n x_{min} xmin原始数据中最小值, x m a x x_{max} xmax 原始数据中最大值

  1. 均值归一化 :

    均值归一化是指通过原始数据中的均值、最大值和最小值来进行数据的标准化。均值归 一化法计算公式为:
    x ′ = x − μ x m a x − x m i n x'=\frac{x-\mu}{x_{max}-x_{min}} x=xmaxxminxμ
    式中: x x x为原始数据, μ \mu μ为表示原始数据的均值, x ′ x′ x 为均值归一化后的数据。 x m i n x_{min} xmin 原始数据中最小值, x m a x x_{max} xmax 原始数据中最大值

标准化

Z-Score(也叫 Standard Score,标准分数)标准化是指基于原始数据的均值(mean)和 标准差(standard deviation)来进行数据的标准化。Z-Score 标准化的计算公式为:
x ′ = x − μ σ x'=\frac{x-\mu}{\sigma} x=σxμ
其中 σ \sigma σ表示标准差。

归一化改变数据分布,标准化不会改变数据分布。特征经过归一化或者标准化处理之后对于模型训练的好处有:

  • 提升模型精度。 因为使不同量纲的特征处于同一数值量级,减少方差大的特征的影响。在KNN中,我们需要计算待分类点与所有实例点的距离。假设每个实例点(instance)由n个features构成。如果我们选用的距离度量为欧式距离,如果数据预先没有经过归一化,那么那些绝对值大的features在欧式距离计算的时候起了决定性作用。 从经验上说,归一化是让不同维度之间的特征在数值上有一定比较性,可以大大提高分类器的准确性。
  • 提升收敛速度。 对于线性model来说,数据归一化后,最优解的寻优过程明显会变得平缓,更容易正确的收敛到最优解。

离散化

数据离散化(也叫数据分组)是指将连续的数据进行分组,使其变为一段段离散化的区间,离散化后的特征根据其所在的组进行One-Hot编码。

根据离散化过程中是否考虑类别属性,可以将离散化算法分为有监督算法和无监督算法两 类。由于有监督算法(如基于熵进行数据的离散化)充分利用了类别属性的信息,所以在分类中能获得较高的正确率。

常见的数据离散化方法有以下几种(以下介绍的数据分组方法均需要对数据进行排序,且假设待离散化的数据按照升序排列):

  • 二值化分组
  • 等宽分组
  • 等频分组
  • 单变量分组
  • 基于信息熵分组
  1. 二值化分组

    二值化(Binarizer):对于定量的数据根据给定的阈值threshold,将其进行转换,如果大于阈值,那么赋值为1;否则赋值为0

from sklearn.preprocessing import Binarizer
binerize = Binarizer(threshold = 30)
x = np.array([30, 20, 45, 99, 87, 25, 31]) # 提取数据
trans = binerize.fit_transform(x.reshape(-1,1))
trans.tolist()
# 结果
# [[0], [0], [1], [1], [1], [0], [1]]
  1. 等宽分组

    等宽分组的原理是,根据分组的个数得出固定的宽度,分到每个组中的变量的宽度是相等的。

    例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组。

    • 宽度为 15,即用变量中的最大值(46)减去变量中的最小值(1),然后用差除以组数(3)。
    • 每组的范围为:(1,16]、(16,31]、(31,46]
    • 分组后的结果为:(1,7,12,12)、(22,30)、(34,38,46)

    (1,16]表示半开半闭区间,即大于 1 且小于或等于 16。另外,采用半开半闭区间时,最 小值不能进行有效分组,这里默认将其归为第一组。

  2. 等频分组

    等频分组也叫分位数分组,即分组后每组的变量个数相同。

    例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组。

    变量的总个数为 9,所以每组的变量为 3 个。分组后的结果为:(1,7,12)、(12,22,30)、 (34,38,46)。

    等宽分组和等频分组实现起来比较简单,但都需要人为地指定分组个数。 等宽分组的缺点是:对离群值比较敏感,将属性值不均匀地分布到各个区间。有些区间 包含的变量较多,有些区间包含的变量较少。 等频分组虽然能避免等宽分组的缺点,但是会将相同的变量值分到不同的组(以满足每 个组内的变量个数相同)。

  3. 单变量分组

    单变量分组也叫秩分组。其原理是:将所有变量按照降序或升序排序,排序名次即为排序结果,即将值相同的变量划分到同一组。

    例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组,去重后,变量个数为 8,所以该组变量的分组数目为 8。

    结果为:(1)、(7)、(12,12)、(22)、(30)、(34)、(38)、(46)。

  4. 基于信息熵分组

  • 对属性 A A A 的所有取值从小到大排序

  • 遍历属性 A A A 的每个值 V i V_i Vi ,将属性 A A A 的值分为两个区间 S 1 S_1 S1 S 2 S_2 S2 ,使得将其作为分隔点划分数据集后的熵 S S S 最小,熵 S S S 的计算方式见式下边公式

    E ( x ) = − ∑ i = 1 n P ( x i ) log ⁡ 2 P ( x i ) E(x)=-\sum_{i=1}^nP(x_i)\log_2P(x_i) E(x)=i=1nP(xi)log2P(xi),式中, P ( x i ) P(x_i) P(xi)表示 x i x_i xi事件发生的概率, n n n x x x中所有类别的个数。

  • 当划分后的熵大于设置的阈值且小于指定的数据分组个数时,递归对 S 1 S_1 S1 S 2 S_2 S2执行步骤2中的划分

2.7 预测值做对数处理

平时在一些数据处理中,经常会把原始数据取对数后进一步处理。之所以这样做是基于对数函数在其定义域内是单调增函数,取对数后不会改变数据的相对关系,取对数作用主要有:

  • 缩小数据的绝对数值,方便计算

    例如,每个数据项的值都很大,许多这样的值进行计算可能对超过常用数据类型的取值范围,这时取对数,就把数值缩小了,例如TF-IDF计算时,由于在大规模语料库中,很多词的频率是非常大的数字。

  • 取对数后,可以将乘法计算转换称加法计算

    不确定性的分析也就变成了信息量的分析。传统的概率论和信息论的桥梁就是对数 log ⁡ ( a b ) = log ⁡ ( a ) + log ⁡ ( b ) \log(ab)=\log(a)+\log(b) log(ab)=log(a)+log(b)

某些情况下,在数据的整个值域中的在不同区间的差异带来的影响不同:

  • 自变量 x x x的值越小,函数值 y y y的变化越快,例如同样是相差了300,但log500-log200>log800-log500,因为前面一对的比后面一对更小
  • 取对数之后不会改变数据的性质和相关关系,但压缩了变量的尺度,例如800/200=4, 但log800/log200=1.2616,数据更加平稳,也消弱了模型的共线性、异方差性等。

2.8 类别不平衡

类别不平衡(class-imbalance)就是指分类任务中不同类别的训练样例数目差别很大的情况。在现实的分类学习任务中,我们经常会遇到类别不平衡,以下是一些例子:

  • 每年,约 2% 的信用卡账户是伪造的。(多数的欺诈检测领域是极其不平衡的)
  • 针对某一病征的医学筛查通常涵盖了许多没有此病征的人,以检查出少数患者(例:美国的 HIV 感染率约为 0.4%)
  • 每年,硬盘驱动器故障的发生率约为 1%
  • 在线广告的转化率在 1 0 − 3 10^{-3} 103 1 0 − 6 10^{-6} 106 的范围区间内
  • 工厂的产品缺陷率一般在 0.1% 左右

如果不同类别的训练样例数目稍有差别,通常影响不大,但若差别很大,则会对学习过程造成困扰。例如有998个反例,但是正例只有2个,那么学习方法只需要返回一个永远将新样本预测为反例的学习器,就能达到99.8%的精度;然而这样的学习器往往没有价值,因为它不能预测出任何正例。

2.8.1 过采样

对训练集里的少数类进行“过采样”(over sampling),即增加一些少数类样本使得正、反例数目接近,然后再进行学习。即是过采样。

随机过采样

随机过抽样是增加少数类样本数量,可以事先设置多数类与少数类最终的数量比例,在保留多数类样本不变的情况下,根据比例随机复制少数类样本,在使用的过程中为了保证所有的少数类样本信息都会被包含,可以先完全复制一份全量的少数类样本,再随机复制少数样本使得满足数量比例,具体步骤如下:

  1. 首先在少数类 S m i n S_{min} Smin集合中随机选中一些少数类样本
  2. 然后通过复制所选样本生成样本集合 E E E
  3. 将它们添加到 S m i n S_{min} Smin中来扩大原始数据集从而得到新的少数类集合 S m i n − n e w S_{min-new} Sminnew

缺点

对于随机过采样,由于需要对少数类样本进行复制来扩大数据集,造成模型训练复杂度加大。另一方面也容易造成模型的过拟合问题,因为随机过采样是简单的对初始样本进行复制采样,这就使得学习器学得的规则过于具体化,不利于学习器的泛化性能,造成过拟合问题。

为了解决随机过采样中造成模型过拟合问题,又能保证实现数据集均衡的目的,出现了过采样法代表性的算法SMOTE和Borderline-SMOTE算法

SMOTE算法

SMOTE全称是Synthetic Minority Oversampling即合成少数类过采样技术。

SMOTE的主要思想是利用特征空间中少数类样本之间的相似性来建立人工数据,特别是,对于子集 S m i n ⊂ S S_{min}⊂ S SminS,对于每一个样本 x i ⊂ S m i n x_i\subset S_{min} xiSmin使用K-近邻法,其中K-近邻被定义为考虑 S m i n S_{min} Smin中的 K K K个元素本身与 x i x_i xi 的欧氏距离在 n n n维特征空间 X X X中表现为最小幅度值的样本。由于不是简单地复制少数类样本,因此可以在一定程度上避免分类器的过度拟合,实践证明此方法可以提高分类器的性能。算法流程如下:

  1. 对于少数类中的每一个样本 x i x_i xi,以欧氏距离为标准计算它到少数类样本集 S m i n S_{min} Smin中所有样本的距离,得到 K K K近邻;
  2. 根据样本不平衡比例设置一个采样比例以确定采样倍率 N N N,对于每一个少数类样本 x i x_i xi,从其 K K K近邻中随机选择若干个样本,假设选择的近邻为 x ~ \tilde{x} x~
  3. 对于每一个随机选出的近邻 x ~ \tilde{x} x~ ,分别与原样本按照如下的公式构建新的样本 x n e w = x + r a n d ( 0 , 1 ) × ( x ~ − x ) x_{new}=x+rand\left(0,1\right)\times\left(\tilde{x}-x\right) xnew=x+rand(0,1)×(x~x)

在这里插入图片描述

缺点:

  • 1)由于对每个少数类样本都生成新样本,因此容易发生生成样本重叠的问题。

  • 2)在SMOTE算法中,出现了过度泛化的问题,主要归结于产生合成样本的方法。特别是,SMOTE算法对于每个原少数类样本产生相同数量的合成数据样本,而没有考虑其邻近样本的分布特点,这就使得类间发生重复的可能性增大。

  • 解释缺点2)的原因:结合前面所述的SMOTE算法的原理,SMOTE算法产生新的人工少数类样本过程中,只是简单的在同类近邻之间插值,并没有考虑少数类样本周围多数类样本的分布情况。如下图所示,绿色正号1、2分布在多数类样本周围,它们离多数类样本最近,这就导致它们有可能被划分成多数类样本。因此,SMOTE算法的样本生成机制存在一定的盲目性。

在这里插入图片描述

Borderline-SMOTE算法

原始的SMOTE算法对所有的少数类样本都是一视同仁的,但实际建模过程中发现那些处于边界位置的样本更容易被错分,因此利用边界位置的样本信息产生新样本可以给模型带来更大的提升。Borderline-SMOTE便是将原始SMOTE算法和边界信息算法结合的算法。算法流程如下:

  1. 首先,对于每个 x i ⊂ S m i n x_{i}\subset S_{min} xiSmin确定一系列K-近邻样本集,称该数据集为 S i − k N N S_{i-kNN} SikNN,且 S i − k N N ⊂ S S_{i-kNN}\subset S SikNNS
  2. 然后,对每个样本 x i x_i xi,判断出最近邻样本集中属于多数类样本的个数,即: ∣ S i − k N N ∩ S m a x ∣ |S_{i-kNN}\cap S_{max}| SikNNSmax
  3. 最后,选择满足下面不等式的 k 2 ≤ ∣ S i − k N N ∩ S m a x ∣ ≤ k \frac{k}{2}\le |S_{i-kNN} \cap S_{max}|\leq k 2kSikNNSmaxk,将其加入危险集DANGER,

上面式子表明,只有最近邻样本集中多数类多于少数类的那些 x i x_{i} xi才会被选中形成“危险集”(DANGER)。因此,DANGER集中的样本代表少数类样本的边界(最容易被错分的样本)。然后对DANGER集中使用SMOTE算法在边界附近产生人工合成少数类样本。如果 ∣ S i − K N N ∩ S m a x ∣ = k \left| S_{i-KNN} \cap S_{max} \right| = k SiKNNSmax=k, 即: x i x_{i} xi的所有k个最近邻样本都属于多类。

2.8.2 欠采样

直接对训练集中多数类样本进行“欠采样”(under sampling),即去除一些多数类中的样本使得正例、反例数目接近,然后再进行学习。即是欠采样。

随机欠采样

随机欠采样顾名思义即从多数类 S m a x S_{max} Smax中随机选择一些样样本组成样本集 E E E 。然后将样本集 E E E S m a x S_{max} Smax中移除。新的数据集 S n e w − m a x = S m a x − E S_{new-max}=S_{max}-E Snewmax=SmaxE

缺点:

随机欠采样方法通过改变多数类样本比例以达到修改样本分布的目的,从而使样本分布较为均衡,但是这也存在一些问题。对于随机欠采样,由于采样的样本集合要少于原来的样本集合,因此会造成一些信息缺失,即将多数类样本删除有可能会导致分类器丢失有关多数类的重要信息。

Informed欠抽样算法可以解决传统随机欠采样造成的数据信息丢失问题,且表现出较好的不均衡数据分类性能。其中有一些集成(ensemble)的想法,主要有两种方法,分别是EasyEnsemble算法和BalanceCascade算法。

EasyEnsemble算法

它把数据划分为两部分,分别是多数类样本和少数类样本。算法流程如下:

  1. 从多数类中有放回的随机采样 n n n次,每次选取与少数类数目相近的样本个数,那么可以得到 n n n个样本集合记作 { S 1 m a x , S 2 m a x , . . . , S n m a x } \left\{ S_{1max},S_{2max},...,S_{nmax} \right\} {S1max,S2max,...,Snmax}
  2. 然后,将每一个多数类样本的子集与少数类样本合并并训练出一个模型,可以得到 n n n个模型。
  3. 最终将这些模型组合形成一个集成学习系统,最终的模型结果是这 n n n个模型的投票值。

可以看到,EasyEnsemble的想法是多次随机欠抽样,尽可能全面地涵盖所有信息,算法特点是利用boosting减小偏差(Adaboost)、bagging减小方差(集成分类器)。实际应用的时候可以尝试选用不同的分类器来提高分类的效果。

BalanceCascade算法

BalanceCascade算法基于Adaboost,将Adaboost作为基分类器,其核心思路是:

  1. 在每一轮训练时都使用多数类与少数类数量相等的训练集,训练出一个Adaboost基分类器。

  2. 然后使用该分类器对全体多数类进行预测,通过控制分类阈值来控制假正例率(False Positive Rate),将所有判断正确的类删除。

  3. 最后,进入下一轮迭代中,继续降低多数类数量。

    在这里插入图片描述

2.8.3 综合采样

目前为止我们使用的重采样方法几乎都是只针对某一类样本:对多数类样本欠采样,对少数类样本过采样。也有人提出将欠采样和过采样综合的方法,解决样本类别分布不平衡和过拟合问题,本部分介绍其中的SMOTE+Tomek Links和SMOTE+ENN。

SMOTE+Tomek Links
Tomek Links方法

定义:Tomek links被定义为相反类最近邻样本之间的一对连接。

符号约定:给定一个样本对 ( x i , x j ) \left(x_i,x_j\right) (xi,xj),其中 x i ∈ S m a x x_i \in S_{max} xiSmax x j ∈ S m i n x_j \in S_{min} xjSmin,记 d ( x i , x j ) d\left(x_i,x_j\right) d(xi,xj)是样本 x i x_i xi x j x_j xj之间的距离

公式表示:如果不存在任何样本 x k x_k xk,使得 d ( x i , x k ) ≤ d ( x i , x j ) d\left( x_i,x_k \right) \le d\left( x_i,x_j \right) d(xi,xk)d(xi,xj),那么样本对 ( x i , x j ) (x_i,x_j) (xi,xj)被称为Tomek Links

使用这种方法,如果两个样本来自Tomek Links,那么他们中的一个样本要么是噪声要么它们都在两类的边界上。所以Tomek Links一般有两种用途:在欠采样中:将Tomek Links中属于是多数类的样本剔除;在数据清洗中,将Tomek Links中的两个样本都剔除。

SMOTE+Tomek Links

SMOTE+Tomek Links方法的算法流程非常简单:

  1. 利用SMOTE方法生成新的少数类样本,得到扩充后的数据集T
  2. 剔除T中的Tomek Links对普通的SMOTE方法生成的少数类样本是通过线性插值得到的,在平衡类别分布的同时也扩张了少数类的样本空间,产生的问题是可能原本属于多数类样本的空间被少数类“入侵”,容易造成模型的过拟合。

Tomek Links对寻找的是那种噪声点或者边界点,可以很好地解决“入侵”的问题,下图红色加号为SMOTE产生的少数类样本,可以看到,红色样本“入侵”到原本属于多数类样本的空间,这种噪声数据问题可以通过Tomek Links很好地解决。 由于第一步SMOTE方法已经很好地平衡了类别分布,因此在使用Tomek Links对的时候考虑剔除所有的Tomek Links对。

SMOTE+ENN

SMOTE+ENN方法和SMOTE+Tomek Links方法的想法和过程都是很类似的:

  1. 利用SMOTE方法生成新的少数类样本,得到扩充后的数据集T
  2. 对T中的每一个样本使用KNN(一般K取3)方法预测,若预测结果与实际类别标签不符,则剔除该样本。

2.8.4 常见机器学习模型本身处理类不平衡

LightGBM

LightGBM解决类不平衡的思路类似于代价敏感学习。

二分类处理

在LGBM的文档中,可以看到有两个参数来处理类别不平衡,分别是is_unbalancescale_pos_weight

这2个参数只能选其一,不能同时选。这说明了什么呢?这2个参数肯定是起到了相同的作用。这2个参数的关系是什么呢?在issue中找到了答案:

if is_unbalance = true, weight of each positive sample / weight of each negative sample = (num_negative_sample / num_positive_sample) * scale_pos_weight_

如果我们设置is_unbalance为True的话,那么scale_pos_weight就是默认值1,那么:每一个正样本的权重/每一个负样本的权重就等于负样本的样本数/正样本的样本数。

如果我们设置scale_pos_weight的话,那么应该设置成number of negative samples / number of positive samples,因为在上图中已经写明了:scale_pos_weight代表的是正类的权重。这样也就和is_unbalance参数的意思对应上了。(注:很多人对于设置scale_pos_weight是懵逼的,我自己已经明确给出了答案,包括xgb也是一样的。)

那么这2个参数是如何来解决类别不平衡的呢?

很明显,可以看到,通过设置这两个参数,可以得到正样本和负样本的权重,样本的权重是什么意思呢?我们知道,在LGBM进行分裂的过程中,我们求出了样本的一阶导数和二阶导数,在此基础上,就会乘以样本的权重。

多分类处理

LGBM对多分类的不平衡问题也进行了完美地处理。它设计了参数class weight

一共提到了以下几点:

  1. 要不传入是dict,格式是class_weight={1: 1, 0: 2}这样;要不传入"balanced";要不传入是None。
  2. 如果传入的是dict,那么每个类的权重正如你传入的那样。
  3. 如果传入的是"balanced",那么会自动根据标签的值来算出每个类的权重,每个类的权重是与每个类别样本出现的次数成反比的。具体是根据公式: n_samples / (n_classes * np.bincount(y))。np.bincount(y)是numpy的一个很有趣的函数,不了解的可以参考该篇文档。我们更形象地说明,我在这里举一个例子,假如我们有10个样本,标签分别为[0,2,2,1,1,1,1,1,1,1],这典型是不平衡的。那么np.bincount(y)输出的值为[1,7,2],n_samples / (n_classes * np.bincount(y))输出的值分别为 10 3 ∗ 1 \frac{10}{3*1} 3110 10 3 ∗ 7 \frac{10}{3*7} 3710 10 3 ∗ 2 \frac{10}{3*2} 3210 。这显然类别权重是与类别的样本个数成反比的。(注:这里有一个背景,就是lgbm中多类别必须设置为0,1,2,3…这样的形式)
  4. 如果传入的是None,那么每个类别的权重是一样的。
  5. 当然,与二分类一样,如果我们也传入了sample_weight,那么每个类的权重还要再乘以sample_weight。(注:sample_weight是LGBM是很少用到的一个功能,它一般是通过fit方法传入的,关于它的使用需要详细参考官方文档)

2.9 数据降维

在数据分析或者推荐算法的应用场景下,注定会使用更多的特征去描述数据,以便收集大量的数据去寻找规律。

更多的特征注定会增加研究准确性。但是在大多数情况下,许多特征之间存在关系,从而增加了模型的复杂性,对数据分析带来不便。如果对每个特征进行分析,信息会被孤立。而且减少特征信息,也很容易产生错误的结论。

为了能够减少分析特征的同时,又尽量减少原指标的信息损失,对收集的数据进行全面的分析。由于各变量间存在一定的相关关系,因此有可能用较少的综合指标分别综合存在于各变量中的各类信息。主成分分析因子分析就属于这类降维的方法。

分析一下表中的主成分是什么?

在这里插入图片描述

再分析它的主成分?

在这里插入图片描述

因此,使用降维技术,将维度降低,可以更好的分析出想要的内容。

在这里插入图片描述

常见方式

线性方法进行数据降维,同时保留最重要的信息。它们通过寻找一种在较低维空间中表示数据的方式来捕捉数据中的原始模式和线性关系。主要线性方法如下:

  • 奇异值分解(SVD, singular value decomposition)对矩阵最好的分解形式
  • 主成分分析(PCA,Principal components analysis)设定最大方差可以显示数据特征
  • 独立成分分析 (ICA, Independent Component Analysis)
  • 线性判别分析——LDA:有监督学习的降维技术,投影后类内方差最小,类间方差最大

非线性降维技术尝试捕捉数据中更复杂的非线性关系,并将其表示为较低维度的空间。三种最常用的三种非线性的降维方案:

  • 低维嵌入MDS算法:降维前后 保证样本点的距离相等
  • 等度量映射——Isomap:高维空间的分布虽然极为复杂,但是在局部上,仍然具有欧式空间的性质
  • T-SNE
  • UMAP

SVD

SVD原理如下:

  • 特征值分解只可以在方阵中进行处理,不能应用的非方阵中
  • 特征值分解可以利用到降维的处理中,提升使用效率
  • 但是大多数矩阵都是非方阵形式,所以不可能进行特征值分解

A = W ∑ W T A=W \sum W^{T} A=WWT

这里应运而生SVD(奇异值)分解,可以解决非方阵使用特征值分解的方法,这种方法被称作奇异值分解。

矩阵的奇异值分解是指,将一个非零的m×n实矩阵 A A A A ∈ R m × n A∈R^{m×n} ARm×n,表示为以下三个实矩阵乘积形式的运算,即进行矩阵的因子分解: A = U ∑ V T A=U \sum V^{T} A=UVT

其中 U U U是m阶正交矩阵, V V V是n阶正交矩阵, Σ Σ Σ是由降序排列的非负的对角元素组成的m×n矩形对角矩阵,满足:
U U T = I V V T = I ∑ = d i a g ( σ 1 , σ 2 , . . . , σ p ) σ 1 ≥ σ 2 ≥ . . . ≥ σ p ≥ 0 p = min ⁡ ( m , n ) UU^T = I \\ VV^T = I\\ \sum = diag(\sigma_1,\sigma_2,...,\sigma_p) \\ \sigma_1 \geq \sigma_2 \geq ... \geq\sigma_p \geq0 \\ p = \min(m,n) UUT=IVVT=I=diag(σ1,σ2,...,σp)σ1σ2...σp0p=min(m,n)

∑ = [ σ 1 0 0 0 0 σ 2 0 0 0 0 σ 3 0 0 0 0 ⋱ ] m × n \sum = \left[\begin{matrix} \sigma_1&0&0&0 \\ 0&\sigma_2&0&0\\ 0&0&\sigma_3&0\\ 0&0&0&\ddots\\ \end{matrix}\right]_{m\times n} = σ10000σ20000σ30000 m×n

U Σ V T UΣV^T UΣVT称为矩阵 A A A的奇异值分解, σ i \sigma_i σi称为矩阵 A A A的奇异值, U U U的列向量称为左奇异向量 V V V的列向量称为右奇异向量

注意奇异值分解不要求矩阵A是方阵。

正常求上面的 U , V , Σ U,V,Σ U,V,Σ不便于求,我们可以利用如下性质:
A A T = U Σ V T V Σ T U T = U Σ Σ T U T A T A = V Σ T U T U Σ V T = V Σ T Σ V T AA^T=UΣ V^T V Σ^T U^T =UΣ Σ ^T U^T \\ A^TA=VΣ^T U^T U Σ V^T =VΣ^T Σ V^T AAT=UΣVTVΣTUT=UΣΣTUTATA=VΣTUTUΣVT=VΣTΣVT
这里 Σ Σ T ΣΣ^T ΣΣT Σ T Σ Σ^TΣ ΣTΣ在矩阵的角度上来讲,它们是不相等的,因为它们的维数不同 Σ Σ T ∈ R m × m ΣΣ^T∈R_{m×m} ΣΣTRm×m,而 Σ T Σ ∈ R n × n Σ^TΣ∈R_{n×n} ΣTΣRn×n,但是它们在主对角线的奇异值是相等的,因此 A = U Σ V T A=UΣV^T A=UΣVT
∑ T ∑ = [ σ 1 0 0 0 0 σ 2 0 0 0 0 σ 3 0 0 0 0 ⋱ ] m × m ∑ ∑ T = [ σ 1 0 0 0 0 σ 2 0 0 0 0 σ 3 0 0 0 0 ⋱ ] n × n {\sum} ^T\sum = \left[\begin{matrix} \sigma_1&0&0&0 \\ 0&\sigma_2&0&0\\ 0&0&\sigma_3&0\\ 0&0&0&\ddots\\ \end{matrix}\right]_{m\times m} \sum{\sum} ^T = \left[\begin{matrix} \sigma_1&0&0&0 \\ 0&\sigma_2&0&0\\ 0&0&\sigma_3&0\\ 0&0&0&\ddots\\ \end{matrix}\right]_{n\times n} T= σ10000σ20000σ30000 m×mT= σ10000σ20000σ30000 n×n
奇异值分解是一种矩阵因子分解方法,是线性代数概念,但在统计学习中被广泛使用,成为其重要工具。

应用:主成分分析、潜在语义分析

矩阵的奇异值分解一定存在,但不唯一。奇异值分解可以看做矩阵数据压缩的一种方法。
在这里插入图片描述

sklearn库

class sklearn.decomposition.TruncatedSVD(n_components=2,
algorithm='randomized', n_iter=5, random_state=None, tol=0.0)

主要参数:

  • n_components: default = 2,话题数量
  • algorithm: default = “randomized”,算法选择
  • n_iter: optional (default 5),迭代次数
    Number of iterations for randomized SVD solver. Not used by ARPACK.
from sklearn.decomposition import TruncatedSVD

tsvd2D = TruncatedSVD(n_components=2)
tsvd_data2D = tsvd2D.fit_transform(red_wine_df)

PCA

PCA 的原理: 通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。主成分是原特征的线性组合。(前提是变换后的矩阵需要保留变换前矩阵的主要信息,不然降维没有意义)

  • PCA是一种无监督学习的降维技术,其思想:投影后样本越分散,保留的信息越多
  • 做法:将所有的样本点向直线 w w w投影
  • 目标函数:让投影后样本的方差极大

样本点 x i x_i xi在直线 w w w上的投影为 w T x i w^Tx_i wTxi,这是一个实数。我们的目的是让投影后的点的方差越大越好
max ⁡ w ∑ i [ w T ( x i − x − ) ] 2 \max_w \sum_i[w^T(x_i-\stackrel{-}{x})]^2 wmaxi[wT(xix)]2
若样本进行了中心化,即 x − = 0 → \stackrel{-}{x}=\stackrel{\rightarrow}{0} x=0,那么目标函数变为
max ⁡ w ∑ i [ w T ( x i ) ] 2 \max_w \sum_i[w^T(x_i)]^2 wmaxi[wT(xi)]2
若投影到 d d d条线上,则希望方差和最大。先研究一个样本:
W T d × n x i n × 1 x i T 1 × n W n × d = [ # # # # ] [ # # ] [ # # ] [ # # # # ] = [ # # # # ] d × d W T d × n x i n × 1 = [ 线 1 上的投影 线 2 上的投影 ] , x i T 1 × n W n × d = [ 线 1 上的投影 线 2 上的投影 ] W = [ w 1 ( 1 ) w 2 ( 1 ) w 1 ( 2 ) w 2 ( 2 ) ] W T = [ w 1 ( 1 ) w 1 ( 2 ) w 2 ( 1 ) w 2 ( 2 ) ] {\mathop {{W^T}}\limits_{d \times n} \mathop {{x_i}}\limits_{n \times 1} \mathop {x_i^T}\limits_{1 \times n} \mathop W\limits_{n \times d} } = {\left[ {\begin{aligned} \# &\# \\ \# &\# \end{aligned}} \right]} {\left[ {\begin{aligned} \# \\ \# \end{aligned}} \right]} {\left[ {\begin{aligned} \# &\# \end{aligned}} \right]} {\left[ {\begin{aligned} \# &\# \\ \# &\# \end{aligned}} \right]} = {\left[ {\begin{aligned} \# &\# \\ \# &\# \end{aligned}} \right]_{d \times d}} \\ \mathop {{W^T}}\limits_{d \times n} \mathop {{x_i}}\limits_{n \times 1} ={\left[ {\begin{aligned} 线1上的投影\\ 线2上的投影 \end{aligned}} \right]}, \mathop {x_i^T}\limits_{1 \times n} \mathop W\limits_{n \times d}={\left[ {\begin{aligned} 线1上的投影& 线2上的投影 \end{aligned}} \right]} \\ W =\left[ {\begin{aligned} {w_1^{(1)}}&{w_2^{(1)}}\\ {w_1^{(2)}}&{w_2^{(2)}} \end{aligned}} \right] \\ {W^T}=\left[ {\begin{aligned} {w_1^{(1)}}&{w_1^{(2)}}\\ {w_2^{(1)}}&{w_2^{(2)}} \end{aligned}} \right] d×nWTn×1xi1×nxiTn×dW=[####][##][##][####]=[####]d×dd×nWTn×1xi=[线1上的投影线2上的投影],1×nxiTn×dW=[线1上的投影线2上的投影]W= w1(1)w1(2)w2(1)w2(2) WT= w1(1)w2(1)w1(2)w2(2)
扩展到所有样本 :
∑ i N W T d × n x i n × 1 x i T 1 × n W n × d = [ # # # # ] d × d = W T d × n X n × N X T N × n W n × d \sum_i^N {\mathop {{W^T}}\limits_{d \times n} \mathop {{x_i}}\limits_{n \times 1} \mathop {x_i^T}\limits_{1 \times n} \mathop W\limits_{n \times d} } = {\left[ {\begin{aligned} \# &\# \\ \# &\# \end{aligned}} \right]_{d \times d}} = \mathop {{W^T}}\limits_{d \times n} \mathop X\limits_{n \times N} \mathop {{X^T}}\limits_{N \times n} \mathop W\limits_{n \times d} iNd×nWTn×1xi1×nxiTn×dW=[####]d×d=d×nWTn×NXN×nXTn×dW
矩阵的迹就是方差和,对其最大化:
max ⁡ w t r ( W T X X T W ) s . t . W T W = I \begin{aligned} \mathop {\max }\limits_w \begin{aligned} {} \end{aligned}tr({W^T}X{X^T}W)\\ s.t.\begin{aligned} {} \end{aligned}{W^T}W = I \end{aligned} wmaxtr(WTXXTW)s.t.WTW=I

W T W n × d = [ w 1 ( 1 ) w 1 ( 2 ) w 2 ( 1 ) w 2 ( 2 ) ] [ w 1 ( 1 ) w 2 ( 1 ) w 1 ( 2 ) w 2 ( 2 ) ] = [ w 1 ( 1 ) ∗ w 1 ( 1 ) + w 1 ( 2 ) ∗ w 1 ( 2 ) w 1 ( 1 ) ∗ w 2 ( 1 ) + w 1 ( 2 ) ∗ w 2 ( 2 ) w 2 ( 1 ) ∗ w 1 ( 1 ) + w 2 ( 2 ) ∗ w 1 ( 2 ) w 2 ( 1 ) ∗ w 2 ( 1 ) + w 2 ( 2 ) ∗ w 2 ( 2 ) ] {W^T}\mathop W\limits_{n \times d} = \left[ {\begin{aligned} {w_1^{(1)}}&{w_1^{(2)}}\\ {w_2^{(1)}}&{w_2^{(2)}} \end{aligned}} \right]\left[ {\begin{aligned} {w_1^{(1)}}&{w_2^{(1)}}\\ {w_1^{(2)}}&{w_2^{(2)}} \end{aligned}} \right] = \left[ {\begin{aligned} {w_1^{(1)}{\rm{*}}w_1^{(1)} + w_1^{(2)}{\rm{*}}w_1^{(2)}}&{w_1^{(1)}{\rm{*}}w_2^{(1)} + w_1^{(2)}{\rm{*}}w_2^{(2)}}\\ {w_2^{(1)}{\rm{*}}w_1^{(1)} + w_2^{(2)}{\rm{*}}w_1^{(2)}}&{w_2^{(1)}{\rm{*}}w_2^{(1)} + w_2^{(2)}{\rm{*}}w_2^{(2)}} \end{aligned}} \right] WTn×dW= w1(1)w2(1)w1(2)w2(2) w1(1)w1(2)w2(1)w2(2) = w1(1)w1(1)+w1(2)w1(2)w2(1)w1(1)+w2(2)w1(2)w1(1)w2(1)+w1(2)w2(2)w2(1)w2(1)+w2(2)w2(2)

PCA将高维数据集映射到低维空间的做法是:选取方差最大的方向作为第一个主成分,第二个主成分选择方差次大的的方向,并且与第一个主成分正交,然后不断重复这个过程,直到找到N个主成分。简单的来说就是让样本在每一个维度上的投影尽可能的分散。

利用拉格朗日函数,求导令其=0,得到
X X T W = λ W n × d % MathType!MTEF!2!1!+- % feaagKart1ev2aqatCvAUfeBSjuyZL2yd9gzLbvyNv2CaerbuLwBLn % hiov2DGi1BTfMBaeXatLxBI9gBaerbd9wDYLwzYbItLDharqqtubsr % 4rNCHbWexLMBbXgBd9gzLbvyNv2CaeHbl7mZLdGeaGqiVu0Je9sqqr % pepC0xbbL8F4rqqrFfpeea0xe9Lq-Jc9vqaqpepm0xbba9pwe9Q8fs % 0-yqaqpepae9pg0FirpepeKkFr0xfr-xfr-xb9adbaqaceGabiqaci % aabeqaamaabaabauaakeaacaWGybGaamiwamaaCaaaleqabaGaamiv % aaaakiaadEfacqGH9aqpcaaH7oGaam4vamaaBaaaleaacaWGUbGaey % 41aqRaamizaaqabaaaaa!4A0F! X{X^T}W = \lambda {W_{n \times d}} XXTW=λWn×d
因此 W W W就是 X X T XX^T XXT的特征向量组成的矩阵,而 λ λ λ X X T XX^T XXT的若干特征值组成的矩阵,特征值在主对角线上,其余位置为0。

求解特征值的方法:特征值分解

特征值是来描述对应特征向量方向上包含多少信息量的,值越大信息量(方差)越大。因此对特征值排序取前 d ′ d' d个特征值对应的特征向量构成 W = ( w 1 , w 2 , . . . , w d ′ ) W=(w_1,w_2,...,w_{d'}) W=(w1,w2,...,wd)

对于原始数据集,我们只需要用 z = W T x z=W^Tx z=WTx,就可以把原始数据集降维到 d ′ d' d

从矩阵角度看:

  • 两个矩阵相乘的意义是将右边矩阵中的每一列向量 a i a_i ai变换到左边矩阵中以每一行行向量为所表示的空间中去。
  • 如何选择基才是最优的。一种直观的看法是:希望投影后的投影值尽可能分散,因为如果重叠就会有样本消失。当然这个也可以从熵的角度进行理解,熵越大所含信息越多。
  • 数值的分散程度,可以用数学上的方差来表述。于是上面的问题被形式化表述为:寻找一个一维基,使得所有数据变换为这个基上的坐标表示后,方差值最大
  • 在一维空间中我们可以用方差来表示数据的分散程度。对于高维数据,我们用协方差进行约束,协方差可以表示两个变量的相关性。为了让两个变量尽可能表示更多的原始信息,我们希望它们之间不存在线性相关性,因为相关性意味着两个变量不是完全独立,必然存在重复表示的信息。
  • 降维问题的优化目标:将一组 N 维向量降为 K 维,其目标是选择 K 个单位正交基,使得原始数据变换到这组基上后,各变量两两间协方差为 0,而变量方差则尽可能大(在正交的约束下,取最大的 K 个方差)。
  • 优化目标变成了寻找一个矩阵 P,满足 P C P T PCP^T PCPT是一个对角矩阵,并且对角元素按从大到小依次排列,那么 P 的前 K 行就是要寻找的基,用 P 的前 K 行组成的矩阵乘以 X 就使得 X 从 N 维降到了 K 维并满足上述优化条件。

因此,有 m 条 n 维数据, PCA的步骤总结为:

  1. 将原始数据按列组成 n 行 m 列矩阵 X;
  2. 将 X 的每一行进行零均值化,即减去这一行的均值;
  3. 求出协方差矩阵 C = 1 m X X T C=\frac{1}{m}XX^T C=m1XXT
  4. 求出协方差矩阵的特征值及对应的特征向量;
  5. 将特征向量按对应特征值大小从上到下按行排列成矩阵,取前 k k k 行组成矩阵 P;
  6. Y = P X Y=PX Y=PX即为降维到 k 维后的数据 。

代码如下:

from sklearn.decomposition import PCA

pca2D = PCA(n_components=2)
# 将数据降维到2维(dimensions)
pca_2D = pca2D.fit_transform(red_wine_df)

ICA

独立成分分析(ICA),动机源自于cocktail party problem(鸡尾酒会问题),ICA与被称为盲源分离(Blind Source Separation,BSS)或盲信号分离的方法具有非常密切的关系。“源”在此处的意思是指原始信号,即独立成分,如鸡尾酒会中的说话者;而“盲”表示我们对于混合矩阵所知甚少,仅仅对源信号做非常弱的假定。

ICA假设的三个条件 :

  • 独立成分被假设是统计独立。对于这一条可以从概率密度以及其他算法可以判断。我们说随机变量y1,y2…yn独立,是指在i≠j时,有关yi的取值情况对于yj如何取值没有提供任何信息。
  • 独立成分具有非高斯分布。如果观测到的变量具有高斯分布,那么ICA在本质上是不可能实现的。假定S经过混合矩阵A后,他们的联合概率密度仍然不变化,因此我们没有办法在混合中的得到混合矩阵的信息。
  • 假设混合矩阵是方阵。这个条件是为了后续ICA算法求解的便利。当混合矩阵A是方阵时就意味着独立源的个数和监测信号的个数数目是一致。

ICA算法步骤

观测信号构成一个混合矩阵,通过数学算法进行对混合矩阵A的逆进行近似求解分为三个步骤:

  1. 去均值。去均值也就是中心化,实质是使信号X均值是零。
  2. 白化。白化就是去相关性。
  3. 构建正交系统。在常用的ICA算法基础上已经有了一些改进,形成了fastICA算法。fastICA实际上是一种寻找wTz(即Y=wTz)的非高斯最大的不动点迭代方案。

以上有较多的数学推导,这里就省略,下面给出fastICA的算法流程:

  1. 观测数据的中心化
  2. 数据白化
  3. 选择需要估计的分量个数m,设置迭代次数和范围
  4. 随机选择初始权重
  5. 选择非线性函数
  6. 迭代
  7. 判断收敛,是下一步,否则返回步骤6
  8. 返回近似混合矩阵的逆矩阵

ICA降维Python代码如下:

from sklearn.decomposition import FastICA

ica2D = FastICA(n_components=2)
ica_data2D = ica2D.fit_transform(red_wine_df)
ica2D_df = pd.DataFrame(data =  ica_data2D,columns = ['x', 'y'])

ica2D_df['cluster'] = cluster

sns.scatterplot(x='x', y='y', hue='cluster', data=ica2D_df)
plt.title("ICA")
plt.show()

LDA

在机器学习领域,LDA对应着两种不同的模型,其中一个模型就是这一小节要讲的,Linear Discriminant Analysis(线性判别分析),主要用于分类和降维;而另一个模型为 ,Latent Dirichlet Allocation(潜在狄利克雷分布),其在主题模型中有非常重要的地位,主要用于文本分类。

思想:将数据投影到低维空间之后,使得同一类数据尽可能的紧凑,不同类的数据尽可能的分散。因此,LDA算法是一种有监督的机器学习算法。 如下图所示(图片来自周志华老师的《机器学习》):

在这里插入图片描述

而我们需要的就是寻找这样的直线或超平面,对于同样的样本点,投影到不同的直线会得到完全不同的投影点。

在这里插入图片描述

显然,我们更愿意得到右边的投影直线,因为它满足了我们的需求:类内样本点距离近而类间样本点有明显的界限。通过这样一条直线,我们把原本在二维空间的样本点投影到了一维空间,实现了降维。同时,实现了分类。

LDA有如下两个假设:(1)原始数据根据样本均值进行分类。(2)不同类的数据拥有相同的协方差矩阵。

当然,在实际情况中,不可能满足以上两个假设。但是当数据主要是由均值来区分的时候,LDA一般都可以取得很好的效果。LDA降维最多降到类别数k-1的维数。

假设投影直线是向量 ω ω ω,则对任意一个样本 x i x_i xi,它在直线 ω ω ω的投影为 ω T x i ω^Tx_i ωTxi,对于我们的两个类别的中心点 μ 0 μ_0 μ0 μ 1 μ_1 μ1,在在直线 ω ω ω的投影为 ω T μ 0 ω^Tμ_0 ωTμ0 ω T μ 1 ω^Tμ_1 ωTμ1。由于LDA需要让不同类别的数据的类别中心之间的距离尽可能的大,也就是我们要最大化 ∣ ∣ ω T μ 0 − ω T μ 1 ∣ ∣ 2 2 ||ω^Tμ_0-ω^Tμ_1||_2^2 ∣∣ωTμ0ωTμ122 ,同时我们希望同一种类别数据的投影点尽可能的接近,也就是要同类样本投影点的协方差 ω T ∑ 0 ω ω^T\sum_0ω ωT0ω ω T ∑ 1 ω ω^T\sum_1ω ωT1ω 尽可能的小,即最小化 ω T ∑ 0 ω + ω T ∑ 1 ω ω^T\sum_0ω+ω^T\sum_1ω ωT0ω+ωT1ω 。综上所述,优化目标为:
arg ⁡ max ⁡ J ( ω ) = ∣ ∣ ω T μ 0 − ω T μ 1 ∣ ∣ 2 2 ω T ∑ 0 ω + ω T ∑ 1 ω = ω T ( μ 0 − μ 1 ) ( μ 0 − μ 1 ) T ω ω T ( ∑ 0 + ∑ 1 ) ω \arg \max J(ω)=\frac{||ω^Tμ_0-ω^Tμ_1||_2^2}{ω^T\sum_0ω+ω^T\sum_1ω}=\frac{ω^T(μ_0-μ_1)(μ_0-μ_1)^Tω}{ω^T(\sum_0+\sum_1)ω} argmaxJ(ω)=ωT0ω+ωT1ω∣∣ωTμ0ωTμ122=ωT(0+1)ωωT(μ0μ1)(μ0μ1)Tω
类内散度矩阵 S ω S_ω Sω为:
S ω = ∑ 0 + ∑ 1 = ∑ ( x − μ 0 ) ( x − μ 0 ) T + ∑ ( x − μ 1 ) ( x − μ 1 ) T S_ω=\sum_{0}+\sum_{1}=\sum(x-\mu_0)(x-\mu_0)^T+\sum(x-\mu_1)(x-\mu_1)^T Sω=0+1=(xμ0)(xμ0)T+(xμ1)(xμ1)T
类间散度矩阵 S b S_b Sb为:
S b = ( μ 0 − μ 1 ) ( μ 0 − μ 1 ) T S_b=(\mu_0-\mu_1)(\mu_0-\mu_1)^T Sb=(μ0μ1)(μ0μ1)T
优化目标重新定义为:
arg ⁡ max ⁡ J ( ω ) = ω T S b ω ω T S ω ω \arg \max J(ω)=\frac{ω^TS_bω}{ω^TS_ωω} argmaxJ(ω)=ωTSωωωTSbω
LDA算法流程:

  1. 计算类内散度矩阵 S ω S_ω Sω
  2. 计算类间散度矩阵 S b S_b Sb
  3. 计算矩阵 S ω − 1 S b S_ω^{-1}S_b Sω1Sb
  4. 计算矩阵 S ω − 1 S b S_ω^{-1}S_b Sω1Sb 的最大的d个特征值和对应的d个特征向量 ( ω 1 , ω 2 , . . . , ω d , ) (ω_1,ω_2,...,ω_d,) (ω1,ω2,...,ωd,),得到投影矩阵 ω ω ω
  5. 对样本集中的每一个样本 x i x_i xi,转为新的样本 z i = ω T x i z_i=ω^Tx_i zi=ωTxi
  6. 得到输出样本集 D ′ = ( z 1 , y 1 ) , ( z 2 , y 2 ) , . . . , ( z m , y m ) D'=(z_1,y_1),(z_2,y_2),...,(z_m,y_m) D=(z1,y1),(z2,y2),...,(zm,ym)
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
lda = LDA(n_components=2)
lr = LogisticRegression()
x_train_lda = lda.fit_transform(x_train_std, y_train)  # LDA是有监督方法,需要用到标签
x_test_lda = lda.fit_transform(x_test_std, y_test)   # 预测时候特征向量正负问题,乘-1反转镜像
lr.fit(x_train_lda, y_train)
plot_decision_regions(x_train_pca, y_train, classifier=lr)
plt.xlabel('LD1')
plt.ylabel('LD2')
plt.legend(loc='lower left')
plt.show()

MDS

MDS(Multidimensional Scaling)是一种用于可视化数据集中观测之间相似性或不相似性的技术。在这种表示中,相似的观测被放置在彼此更近的位置,而不相似的观测则被分开得更远。多维缩放具有线性和非线性降维的优势,具体取决于所使用的设置和算法。在所有情况下,多维缩放旨在保持数据点之间的距离,确保降维表示保留了这些距离。

from sklearn.manifold import MDS

mds2D = MDS(n_components=2)

mds_data2D = mds2D.fit_transform(red_wine_df)
mds2D_df = pd.DataFrame(data =  mds_data2D, columns = ['x', 'y'])

mds2D_df['cluster'] = cluster

sns.scatterplot(x='x', y='y', hue='cluster', data=mds2D_df)
plt.title("MDS")
plt.show()

T-SNE

t-SNE(TSNE)将数据点之间的相似度转换为概率。原始空间中的相似度由高斯联合概率表示,嵌入空间的相似度由“t分布”表示。

SNE是通过仿射(affinitie)变换将数据点映射到概率分布上,主要包括两个步骤:

  • SNE构建一个高维对象之间的概率分布,使得相似的对象有更高的概率被选择,而不相似的对象有较低的概率被选择。
  • SNE在低维空间里在构建这些点的概率分布,使得这两个概率分布之间尽可能的相似。

t-SNE模型是非监督的降维,他跟kmeans等不同,他不能通过训练得到一些东西之后再用于其它数据(比如kmeans可以通过训练得到k个点,再用于其它数据集,而t-SNE只能单独的对数据做操作,也就是说他只有fit_transform,而没有fit操作)

尽管SNE提供了很好的可视化方法,但是他很难优化,而且存在”crowding problem”(拥挤问题)。后续中,Hinton等人又提出了t-SNE的方法。与SNE不同,主要如下:

  • 使用对称版的SNE,简化梯度公式
  • 低维空间下,使用t分布替代高斯分布表达两点之间的相似度
from sklearn.manifold import TSNE

tsne2D = TSNE(n_components=2)
tsne_data2D = tsne2D.fit_transform(red_wine_df)
tsne2D_df = pd.DataFrame(data =  tsne_data2D, columns = ['x', 'y'])

tsne2D_df['cluster'] = cluster

sns.scatterplot(x='x', y='y', hue='cluster', data=tsne2D_df)
plt.title("T-SNE")
plt.show()

UMAP

均匀流形近似和投影(Uniform Manifold Approximation and Projection,简称UMAP)UMAP可以看作是t-SNE的更强大的亲戚。它同样学习非线性映射以保持簇的完整性,而且它的速度更快。此外,与t-SNE相比,UMAP在保持数据的全局结构方面往往表现更好。在这个上下文中,全局结构指的是相似葡萄酒类型之间的“接近程度”,而局部结构则指的是同一类型的葡萄酒在降维后的空间中的聚类程度。

#pip install umap-learn
from umap.umap_ import UMAP

umap2D = UMAP(n_components=2)
umap_data2D = umap2D.fit_transform(red_wine_df)
umap2D_df = pd.DataFrame(data =  umap_data2D,columns = ['x', 'y'])

umap2D_df['cluster'] = cluster

sns.scatterplot(x='x', y='y', hue='cluster', data=umap2D_df)
plt.title("UMAP")
plt.show()

2.10 特征扩展

​ 线性回归的局限性是只能应用于存在线性关系的数据中,但是在实际生活中,很多数据之间是非线性关系,也可以用线性回归拟合非线性回归,但效果很差,此时就需要对线性回归模型进行改进,使之能够拟合非线性数据。

目标:将数据进行升维处理(特征拓展),可以更好的适应模型

1)处理目的:

  • 解决模型欠拟合
  • 捕捉自变量与应变量之间的非线性关系

2) 常见处理方法,多项式拓展:

  • 假设数据集中包含自变量a、b
  • 如果对自变量做二项式扩展
  • 自变量集从两个变量扩展为5个变量(a、b、a×a、b×b、a×b)

(1)特征处理方法

sklearn.preprocessing.PolynomialFeatures(degree=2, interaction_only=False, include_bias=True)

  • degree:多项式次数(就同几元几次方程中的次数一样)

  • interaction_only:是否包含单个自变量**n(n>1)特征数据标识

  • include_bias:是否包含偏差标识

    from sklearn.preprocessing import PolynomialFeatures
    poly = PolynomialFeatures(degree=2,include bias=False) #设置最多添加几次幂的特征项
    poly.fit(X)
    x2 = poly.transform(X)
    from sklearn.linear_model import LinearRegression #接下来的代码和线性回归一致
    lin_reg2 = LinearRegression()
    lin_reg2.fit(x2,y)
    y_predict2 = lin_reg2.predict(x2)
    

(2)管道机制

管道机制在机器学习算法中得以应用的根源在于,参数集在新数据集(比如测试集)上的重复使用。管道机制实现了对全部步骤的流式化封装和管理。管道机制不是算法,是一种编程方式。

import numpy as np  
import matplotlib.pyplot as plt 
x = np.random.uniform(-3,3, size=100)
X = x.reshape(-1,1)
y = 0.5 * x**2 +x +2 + np.random.normal(0,1,size=100)
 
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

degree = 2
#这里将三个处理步骤进行了封装,将数据传入poly_reg之后,将会智能地沿着该管道进行处理
poly_reg = Pipeline([ 
    ("poly",PolynomialFeatures(degree=degree)),
    ("std_scaler",StandardScaler()),
    ("lin_reg",LinearRegression())    
])
poly_reg.fit(X,y) 
y_predict = poly_reg.predict(X)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],color='r') #效果和之前代码一致
plt.show()
  • 先执行多项式回归的fit_transform,再执行标准化的fit_transform
  • 如果进行模型拟合,执行fit操作,生成预测模型
  • 如果执行predict操作,将数据进行预测操作
    在这里插入图片描述

3 过拟合欠拟合

  • 过拟合指的是数据进行过度训练,得出来的训练模型虽然对于训练数据来说,拟合地非常好,但是对于测试数据,将会有糟糕的表现,原因是过度地拟合将会把噪声也极大地引入。

  • 欠拟合指的是数据没有训练完成,得出来的训练模型对训练集拟合效果不好。对于测试集的操作效果也不高 。

在这里插入图片描述

  • 优化: 是指调节模型以在训练数据上得到最佳性能

  • 泛化: 是指训练好的模型在前所未见的数据(测试集)上的性能好坏

  • 偏差描述样本偏离实际值的情况,方差描述样本的分布疏密情况。下图中红色点为真值,蓝色点为样本点,描绘了不同偏差和方差分布情况。

在这里插入图片描述

​ 导致偏差的原因,可能是对模型的假设是错误的,例如用线性的假设去预测非线性数据,或者采用的特征和预测其实没有关系。在机器学习中,一般不会出现采取的特征和预测毫无相关的情况,事实上导致高偏差的原因主要是欠拟合。导致方差的原因,通常是因为模型太过复杂,一点的数据抖动都将会影响到结果,过拟合是导致高方差的主要原因。

现象原因解决方法
欠拟合数据特征少升维(特征扩展)、减少正则化参数、非线性模型、集成学习
数据量少获取更多的数据
过拟合数据特征多降维(PCA、SVD)、筛选特征、增加正则化、Dropout
数据特征范围差距大特征缩放(归一化、标准化)

应对过拟合

最优方案

获取更多的训练数据。只要给足够多的数据,让模型学习尽可能多的情况,它就会不断修正自己,从而得到更好的结果。如何获取更多数据,可以有以下几个方法:

  1. 从数据源头获取更多数据
  2. 根据当前数据集估计数据分布参数,使用该分布产生更多数据:这个一般不用,因为估计分布参数的过程也会代入抽样误差。
  3. 数据增强(Data Augmentation):通过一定规则扩充数据。如在物体分类问题里,物体在图像中的位置、姿态、尺度,整体图片明暗度等都不会影响分类结果。我们就可以通过图像平移、翻转、缩放、切割等手段将数据库成倍扩充。

但是获取到有效的数据往往是非常困难的,代价很大(所以在多数情况下不使用此方案)。那么有没有代价适中,又可以解决过拟合的方案呢?

次优方案

调节模型允许存储的信息量或者对模型允许存储的信息加以约束,该类方法也称为正则化。即:

  1. 调节模型大小
  2. 约束模型权重,即权重正则化(在机器学习中一般使用 L2正则化)
  3. 随机失活(Dropout)
正则化
  • 经验风险最小化可以理解为:最小化代价函数

  • 结构风险最小化可以理解为:最小化目标函数(代价函数+正则化项)
    o b j ( w ) = 1 2 N ∑ i = 1 N ( w ∗ x − y i ) 2 ⏟ 经验风险最小化 + λ R ( w ) ⏟ 结构风险最小化 obj(w)=\underbrace{\underbrace{\frac{1}{2N}\sum_{i=1}^N(w*x-y_i)^2}_{经验风险最小化}+\lambda R(w)}_{结构风险最小化} obj(w)=结构风险最小化 经验风险最小化 2N1i=1N(wxyi)2+λR(w)

  • L1正则化(LASSO回归

    权值向量w中各个元素的绝对值之和:
    R ( w ) = ∣ ∣ w ∣ ∣ 1 = ∣ w 1 ∣ + ∣ w 2 ∣ R(w)=||w||_1=|w_1|+|w_2| R(w)=∣∣w1=w1+w2

  • L2正则化(岭回归,Ridge回归)

    权值向量w中各个元素的平方和:
    R ( w ) = 1 2 ∣ ∣ w ∣ ∣ 2 2 = 1 2 ( w 1 2 + w 2 2 ) R(w)=\frac{1}{2}||w||_2^2=\frac{1}{2}(w_1^2+w_2^2) R(w)=21∣∣w22=21(w12+w22)

  • L1正则化 VS L2正则化

    相同点: 都是在线性回归的基础上,添加了一个惩罚项,让模型更稳定更简单,泛化能力更强,避免过拟合。

    不同点:LASSO是利用L1-norm来逼近L0-norm的,因为系数可以减小到0,所以可以做特征选择,但是并不是每个点都是可导的,所以计算起来可能会并没有L2-norm方便;Ridge是利用L2-norm来使系数不那么大,同时方便计算,但是不会使系数减小到0,所以不能做特征选择,可解释性方面也没有LASSO高。

    (1)L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择

    (2)L2正则化可以防止模型过拟合(overfitting),计算出来的系数更稳定 ,多重共线性仍然存在

  • 经典面试题

    为什么 L1 正则可以产生稀疏模型(很多参数=0),而 L2 正则不会出现很多参数为0的情况?

Dropout 随机失活
  • 随机失活:让隐层的神经元以一定的概率不被激活。
  • 实现方式:训练过程中,对某一层使用Dropout,就是随机将该层的一些输出舍弃(输出值设置为0),这些被舍弃的神经元就好像被网络删除了一样。
  • 随机失活比率( Dropout ratio):是被设为 0 的特征所占的比例,通常在 0.2~0.5范围内。

例:

假设某一层对给定输入样本的返回值应该是向量:[0.2, 0.5, 1.3, 0.8, 1.1]。
使用Dropout后,这个向量会有几个随机的元素变成:[0, 0.5, 1.3, 0, 1.1]

Dropout是通过遍历神经网络每一层的节点,然后通过对该层的神经网络设置一个 Dropout ratio(随机失活比率),即该层的节点有Dropout ratio的概率失活。以这种方式“dropped out”的神经元既不参与前向传播,也不参与反向传播。

随机失活为什么能够防止过拟合呢?

解释1:
  随机失活使得每次更新梯度时参与计算的网络参数减少了,降低了模型容量,所以能防止过拟合。
解释2:
  随机失活鼓励权重分散,从这个角度来看随机失活也能起到正则化的作用,进而防止过拟合。
总的来说通过Dropout每次输入一个样本,就相当于该神经网络就尝试了一个新的结构,但是所有这些结构之间共享权重。因为神经元不能依赖于其他特定神经元而存在,所以这种技术降低了神经元复杂的互适应关系。正因如此,网络需要被迫学习更为鲁棒的特征(泛化性更强)。

应对欠拟合

欠拟合的情况比较容易克服,解决方法有:

  1. 增加新特征,可以考虑加入进特征组合、高次特征,来增大假设空间。
  2. 添加多项式特征,这个在机器学习算法里面用的很普遍,例如将线性模型通过添加二次项或者三次项使模型泛化能力更强。
  3. 减少正则化参数,正则化的目的是用来防止过拟合的,但是模型出现了欠拟合,则需要减少正则化参数。
  4. 使用非线性模型,比如核SVM 、决策树、深度学习等模型 。
  5. 调整模型的容量(capacity),通俗地,模型的容量是指其拟合各种函数的能力。
  6. 容量低的模型可能很难拟合训练集;使用集成学习方法,如Bagging,将多个弱学习器Bagging。

4 模型选择方法

包括留出法、交叉验证法、留一法、自助法等。

(1)留出法

留出法(hold-out):一部分为训练集,一部分为测试集。

  • 应尽量保证数据分布的一致性。

  • 划分比例:7:3左右

在这里插入图片描述

(2)交叉验证法(k-fold cross validation)

​ 划分为 k k k个互斥子集,用 k − 1 k-1 k1作为训练集,剩下一个为测试集,最终每一个子集都会作为测试集,其余子集作为训练集,共进行 k k k次建模,最终得到测试结果的均值。

  • K K K取值一般为10
  • 随机取 k k k个互斥子集,进行 p p p次,最后对 p p p个k-fold cv进行取平均,叫作 p p p k k k折交叉验证

(3)留一法(Leave-one-out cross validation)

​ 假定数据集D中包含m个样本,若令k=m,则得到了交叉验证法的一个特例:留一法(Leave-One-Out,简称LOO)。 只有一种划分方法,即每个测试集只有一条数据。

  • 优势,每个模型都能很好的反应原始数据集的特性
  • 劣势,计算量在数据量大时会非常大,还不算调参的计算量

(4)自助法(Bootstrapping)

​ 假设给定的数据集包含 d d d个样本。该数据集有放回地抽样 m m m次,产生 m m m个样本的训练集。这样原数据样本中的某些样本很可能在该样本集中出现多次。没有进入该训练集的样本最终形成检验集(测试集)。 显然每个样本被选中的概率是 1 / m 1/m 1/m,因此未被选中的概率就是 ( 1 − 1 / m ) (1-1/m) (11/m),这样一个样本在训练集中没出现的概率就是 m m m次都未被选中的概率,即 ( 1 − 1 / m ) m (1-1/m)^m (11/m)m。当 m m m趋于无穷大时,这一概率就将趋近于 e − 1 = 0.368 e^{-1}=0.368 e1=0.368,所以留在训练集中的样本大概就占原来数据集的63.2%。

(5)网格搜索交叉验证

Grid Search 是一种穷举的调参方法。通过循环遍历的方式,把每一种候选的参数组合,全部调试一遍。最后表现效果最好的参数就是最终的结果。 嵌套循环方式实现:(每次调参时,数据集要保持一致性)。为了避免test data既用于检验模型参数,又用于测试模型好坏,会提高模型结果的评分。

在这里插入图片描述

在这里插入图片描述

  • sklearn.model_selection.GridSearchCV(estimator,param_grid,cv)
    • estimator:选择使用的分类器,并且传入除需要确定最佳的参数之外的其他参数。每一个分类器都需要一个scoring参数,或者score方法
    • param_grid:需要最优化的参数的取值,值为字典或者列表
    • cv:交叉验证参数,默认None,使用五折交叉验证。指定fold数量,默认为5(之前版本为3),也可以是yield训练/测试数据的生成器
  • 方法
    • fit:输入训练数据
    • score:准确率
    • best_score_:交叉验证中测试的最好的结果
    • best_estimator_:交叉验证中测试的最好的参数模型
    • best_params_:交叉验证中测试的最好的参数
    • cv_results_:每次交叉验证的结果

5 模型评估指标

5.1 回归模型评估指标

回归常见指标:均方误差、均方根误差、绝对平均误差等

  • 均方误差(Mean Squared Error,MSE)
    M S E = ∑ i = 0 N − 1 ( y i − y i ^ ) 2 N MSE = \frac{\sum_{i=0}^{N-1}(y_i-\hat{y_i})^2}{N} MSE=Ni=0N1(yiyi^)2

  • 均方根误差(Root Mean Squared Error,RMSE)
    R M S E = ∑ i = 0 N − 1 ( y i − y i ^ ) 2 N RMSE = \sqrt{\frac{\sum_{i=0}^{N-1}(y_i-\hat{y_i})^2}{N}} RMSE=Ni=0N1(yiyi^)2

  • 绝对平均误差(Mean Absolute Error,MAE)
    M A E = 1 N ∑ i = 0 N − 1 ∣ y i − y i ^ ∣ MAE = \frac{1}{N}\sum_{i=0}^{N-1}|y_i-\hat{y_i}| MAE=N1i=0N1yiyi^

  • 相关系数(R-square):用于衡量两个变量之间相关程度的系数。R的取值范围在[-1,1]之间。当相关系数的绝对值越接近1,说明变量之间的相关性越强。如果相关系数大于0,说明二者之间呈正相关;如果相关系数小于0,说明二者之间呈负相关。
    R = ∑ i = 0 N − 1 ( x i − x ‾ ) ( y i − y ‾ ) ∑ i = 0 N − 1 ( x i − x ‾ ) 2 ∑ i = 0 N − 1 ( y i − y ‾ ) R=\frac{{\sum_{i=0}^{N-1}(x_i-\overline{x})(y_i-\overline{y})}}{{\sum_{i=0}^{N-1}(x_i-\overline{x})^2}{\sum_{i=0}^{N-1}(y_i-\overline{y})}} R=i=0N1(xix)2i=0N1(yiy)i=0N1(xix)(yiy)

  • 决定系数( R 2 R^2 R2):决定系数,又称拟合优度,通常用来描述数据对模型拟合程度的好坏,表示X对Y的解释程度。R方的取值在[0,1]之间,越接近1,说明回归拟合效果越好。比如 R 2 R^2 R2=0.5,那么说明自变量Y可以解释因变量X50%的变化原因。
    R 2 = = 1 − ∑ i = 0 N − 1 ( y i − y i ^ ) 2 ∑ i = 0 N − 1 ( y i − y ‾ ) 2 = 1 − S S E S S T = S S R S S T R^2==1-\frac{\sum_{i=0}^{N-1}(y_i-\hat{y_i})^2}{\sum_{i=0}^{N-1}(y_i-\overline{y})^2}=1-\frac{SSE}{SST}=\frac{SSR}{SST} R2==1i=0N1(yiy)2i=0N1(yiyi^)2=1SSTSSE=SSTSSR

    • 模型 R 2 R^2 R2拟合优度的计算共涉及3个指标,分别是:

      S S T = ∑ ( y i − y ˉ ) 2 SST= \sum\left(y_{i}-\bar{y}\right)^{2} SST=(yiyˉ)2 ——总体平方和,它的大小描述了数据集中的数的分散程度

      S S E = ∑ ( y i − y ^ i ) 2 SSE= \sum\left(y_{i}-\hat{y}_{i}\right)^{2} SSE=(yiy^i)2 ——残差平方和,SSE越大,模型不能解释的部分越多,则拟合效果越差。

      S S R = ∑ ( y ^ i − y ˉ ) 2 SSR= \sum\left(\hat{y}_{i}-\bar{y}\right)^{2} SSR=(y^iyˉ)2 ——回归平方和,拟合数据的分散情况

    • 三者的关系是: S S T = S S E + S S R SST=SSE+SSR SST=SSE+SSR

5.2 分类模型评估指标

混淆矩阵

  • TP: 将正类预测为正类数

  • FN: 将正类预测为负类数

  • FP: 将负类预测为正类数

  • TN: 将负类预测为负类数

    confusion_matrix计算得到的是0,1,0,1

    预测值预测值总数
    n‘p’
    真实值n真阴性(TN)伪阳性(FP)N
    真实值p伪阴性(FN)真阳性(TP)P
    总数N’P’

常见指标

  • 准确率(accuracy) = 预测对的/所有 = (TP+TN)/(TP+FN+FP+TN)

  • 精确率(precision)、查准类 ,预测为正例的那些数据里预测正确的数据个数,P= TP/(TP+FP)

  • 召回率(recall)、查全率 ,真实为正例的那些数据里预测正确的数据个数 ,R= TP/(TP+FN)

  • F1值就是精确率和召回率的调和均值
    2 F 1 = 1 P + 1 R F 1 = 2 P ∗ R P + R \frac{2}{F_1}=\frac{1}{P}+\frac{1}{R} \\ F1=\frac{2P*R}{P+R} F12=P1+R1F1=P+R2PR

ROC、AUC

ROC全称是“受试者工作特征”(Receiver Operating Characteristic)。

  • PR曲线

    • 某个模型对一批数据数据进行预测,会为每个样本输出一个概率(属于正例的概率)

    • 我们做分类时如果要严格输出0或1的话,就要使用阈值对概率进行激活。

    • 选择不同的阈值时,精确率(实际和预测为正例的数量/预测为正例的个数)和召回率(实际和预测为正例的数量/实际正例的数量)就会不同。那么就会有pr曲线这种东西

    • 对于精确率来说,根据精确率公式可知,阈值越小那么 T P + F P TP+FP TP+FP 就会越大(因为更多的样本会被预测为正类别),整体上(不是绝对)精确率便会降低;同理,如果阈值越大那么 T P + F P TP+FP TP+FP 就会越小,某些情况下预测出的结果可能都是正样本,则精确率总体上便会提高。因此,如果阈值由小变大,那么便会使得变小,精确率便也会由小变大。

    • 对于召回率来说,根据召回率公式可知, T P + F N TP+FN TP+FN 是一个定值(即所有真实正样本的数量),改变阈值并不会使得 T P + F N TP+FN TP+FN 发生改变。这意味着如果降低阈值,那么召回率便会提高或保持不变,因为 T P TP TP 变得更大(或保持不变)了。因此,如果阈值由小变大,那么便会使得 T P TP TP 变小,召回率便会由大变小。

    • 总结起来就是,随着召回率的增大,那么精确率整体上可能会呈下降趋势,如图所示。因此,Precision-Recall曲线很好的展示了在不同阈值取值下精确率和召回率的平衡情况。同时,从上面的分析可知,最理想的情况便是随着召回率的提升,精确率也逐步保持提升或保持不变。

在这里插入图片描述

ROC曲线的面积就是AUC(Area Under the Curve)。AUC用于衡量“二分类问题”机器学习算法性能(泛化能力)。可以看出,当一个样本被分类器判为正例,若其本身是正例,则TPR增加;若其本身是负例,则FPR增加,因此ROC曲线可以看作是随着阈值的不断移动,所有样本中正例与负例之间的“对抗”。曲线越靠近左上角,意味着越多的正例优先于负例,模型的整体表现也就越好。
T P R = T P P = T P T P + F N TPR=\frac{TP}{P}=\frac{TP}{TP+FN} TPR=PTP=TP+FNTP

F P R = F P N = F P T N + F P FPR=\frac{FP}{N}=\frac{FP}{TN+FP} FPR=NFP=TN+FPFP

在这里插入图片描述

使用场景

  1. ROC曲线由于兼顾正例与负例,所以适用于评估分类器的整体性能,相比而言PR曲线完全聚焦于正例。
  2. 如果有多份数据且存在不同的类别分布,比如信用卡欺诈问题中每个月正例和负例的比例可能都不相同,这时候如果只想单纯地比较分类器的性能且剔除类别分布改变的影响,则ROC曲线比较适合,因为类别分布改变可能使得PR曲线发生变化时好时坏,这种时候难以进行模型比较;反之,如果想测试不同类别分布下对分类器的性能的影响,则PR曲线比较适合。
  3. 如果想要评估在相同的类别分布下正例的预测情况,则宜选PR曲线。
  4. 类别不平衡问题中,ROC曲线通常会给出一个乐观的效果估计,所以大部分时候还是PR曲线更好。
  5. 最后可以根据具体的应用,在曲线上找到最优的点,得到相对应的precision,recall,f1 score等指标,去调整模型的阈值,从而得到一个符合具体应用的模型。

在这里插入图片描述

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

healed萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值