赞
踩
设两向量为: 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θ
通过内积可以判断两个数据信息之间的相关性
如果两个向量的点积为0,那么称这两个向量互为正交向量
几何意义上,正交向量之间是垂直关系
如果两个或多个向量,他们的点积均为0,那么它们之间称为正交向量
如果有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
设 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} A−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α=λα⇔(αE−A)α=0⇔(αE−A)x=0有非零解
由
∣
λ
E
−
A
∣
=
0
|\lambda E - A| =0
∣λE−A∣=0得到特征向量
λ
\lambda
λ,由
(
λ
E
−
A
)
x
=
0
(\lambda E - A)x=0
(λE−A)x=0得到特征向量
α
\alpha
α
特征值和特征向量的定义如下:
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=W∑W−1
其中 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=W∑WT
假设有两个样本点
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=1∑n∣xi(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=1∑n∣xi(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=1∑n(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)=lmax∣xi(l)−xj(l)∣,l=1,2,,,n
假设有
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
数据预处理的方法主要包括重复观测处理、去除唯一属性、处理缺失值、属性编码、数据标准化、正则化、特征选择、主成分分析等。
查看行列: 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%分位数,上边界,下边界等统计量来来描述数据的整体分布情况。通过计算这些统计量,生成一个箱体图,箱体包含了大部分的正常数据,而在箱体上边界和下边界之外的,就是异常数据。
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)
唯一属性通常是一些id属性,这些属性并不能刻画样本自身的分布规律,所以简单地删除这些属性即可。
1)缺失值的分类
2)缺失值处理的三种方法
删除含有缺失值的特征:若变量的缺失率较高(大于80%),覆盖率较低,且重要性较低,可以直接将变量删除。
dropna
函数作用:删除含有空值的行或列
DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
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)
方法二:
方法三:
使用机器学习时,会存在不同的特征类型:连续型特征和离散型特征。 针对连续性特征,我们通常将其线性缩放到[-1, 1]区间或者缩放到均值为0,方差为1的范围。 但是,特征并不总是连续值,而有可能是分类值、离散值。因此,我们也需要对离散值进行特征编码数据预处理。 离散特征的编码按照不同的划分标准,类别型变量有:
标签编码(Label Encoding)是使用字典的方式,将每个类别标签与不断增加的整数相关联,即生成一个名为class_的实例数组的索引。
Scikit-learn中的LabelEncoder是用来对分类型特征值进行编码,即对不连续的数值或文本进行编码。其中包含以下常用方法:
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”,”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 Encoding的使用场景
基于Scikit-learn 的one hot encoding
LabelBinarizer:将对应的数据转换为二进制型,类似于onehot编码,这里有几点不同:
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的优势在于:
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地址、电子邮件域名、城市名、家庭住址、街道、产品号码。
主要原因:
如果某个类别型特征基数比较低(low-cardinality features),即该特征的所有值去重后构成的集合元素个数比较少,一般利用One-hot编码方法将特征转为数值型。One-hot编码可以在数据预处理时完成,也可以在模型训练的时候完成,从训练时间的角度,后一种方法的实现更为高效,CatBoost对于基数较低的类别型特征也是采用后一种实现。
显然,在高基数类别型特征(high cardinality features) 当中,比如 user ID,这种编码方式会产生大量新的特征,造成维度灾难。一种折中的办法是可以将类别分组成有限个的群体再进行One-hot编码。一种常被使用的方法是根据目标变量统计(Target Statistics,以下简称TS)进行分组,目标变量统计用于估算每个类别的目标变量期望值。甚至有人直接用TS作为一个新的数值型变量来代替原来的类别型变量。重要的是,可以通过对TS数值型特征的阈值设置,基于对数损失、基尼系数或者均方差,得到一个对于训练集而言将类别一分为二的所有可能划分当中最优的那个。在LightGBM当中,类别型特征用每一步梯度提升时的梯度统计(Gradient Statistics,以下简称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∗(1−s)+s∗count(k)sum(posk)
此方法同样容易引起过拟合,以下方法用于防止过拟合:
目标编码属于有监督的编码方式,如果运用得当则能够有效地提高预测模型的准确性 (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,思路如下:
比如划分为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)
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)
同时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(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)=lnGoodiBadi−lnGoodtotalBadtotal
其中,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 的意义同理。
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也是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总结:
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)+prior∗m
其中
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)+prior∗m可以换成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
连续特征经常是用户或者事物对应一些行为的统计值,常见的处理方法包括:
归一化一般是将数据映射到指定的范围,用于去除不同维度数据的量纲以及量纲单位,归一化方法有:
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′=xmax−xx−xmin
式中: x x x为原始数据, x ′ x′ x′ 为Min-Max 归一化后的数据。 x m i n x_{min} xmin原始数据中最小值, x m a x x_{max} xmax 原始数据中最大值
均值归一化 :
均值归一化是指通过原始数据中的均值、最大值和最小值来进行数据的标准化。均值归 一化法计算公式为:
x
′
=
x
−
μ
x
m
a
x
−
x
m
i
n
x'=\frac{x-\mu}{x_{max}-x_{min}}
x′=xmax−xminx−μ
式中:
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
σ表示标准差。
归一化改变数据分布,标准化不会改变数据分布。特征经过归一化或者标准化处理之后对于模型训练的好处有:
数据离散化(也叫数据分组)是指将连续的数据进行分组,使其变为一段段离散化的区间,离散化后的特征根据其所在的组进行One-Hot编码。
根据离散化过程中是否考虑类别属性,可以将离散化算法分为有监督算法和无监督算法两 类。由于有监督算法(如基于熵进行数据的离散化)充分利用了类别属性的信息,所以在分类中能获得较高的正确率。
常见的数据离散化方法有以下几种(以下介绍的数据分组方法均需要对数据进行排序,且假设待离散化的数据按照升序排列):
二值化分组
二值化(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,7,12,12,22,30,34,38,46)分成三组。
(1,16]表示半开半闭区间,即大于 1 且小于或等于 16。另外,采用半开半闭区间时,最 小值不能进行有效分组,这里默认将其归为第一组。
等频分组
等频分组也叫分位数分组,即分组后每组的变量个数相同。
例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组。
变量的总个数为 9,所以每组的变量为 3 个。分组后的结果为:(1,7,12)、(12,22,30)、 (34,38,46)。
等宽分组和等频分组实现起来比较简单,但都需要人为地指定分组个数。 等宽分组的缺点是:对离群值比较敏感,将属性值不均匀地分布到各个区间。有些区间 包含的变量较多,有些区间包含的变量较少。 等频分组虽然能避免等宽分组的缺点,但是会将相同的变量值分到不同的组(以满足每 个组内的变量个数相同)。
单变量分组
单变量分组也叫秩分组。其原理是:将所有变量按照降序或升序排序,排序名次即为排序结果,即将值相同的变量划分到同一组。
例如,将一组变量(1,7,12,12,22,30,34,38,46)分成三组,去重后,变量个数为 8,所以该组变量的分组数目为 8。
结果为:(1)、(7)、(12,12)、(22)、(30)、(34)、(38)、(46)。
基于信息熵分组
对属性 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中的划分
平时在一些数据处理中,经常会把原始数据取对数后进一步处理。之所以这样做是基于对数函数在其定义域内是单调增函数,取对数后不会改变数据的相对关系,取对数作用主要有:
缩小数据的绝对数值,方便计算
例如,每个数据项的值都很大,许多这样的值进行计算可能对超过常用数据类型的取值范围,这时取对数,就把数值缩小了,例如TF-IDF
计算时,由于在大规模语料库中,很多词的频率是非常大的数字。
取对数后,可以将乘法计算转换称加法计算
不确定性的分析也就变成了信息量的分析。传统的概率论和信息论的桥梁就是对数 log ( a b ) = log ( a ) + log ( b ) \log(ab)=\log(a)+\log(b) log(ab)=log(a)+log(b)
某些情况下,在数据的整个值域中的在不同区间的差异带来的影响不同:
类别不平衡(class-imbalance)就是指分类任务中不同类别的训练样例数目差别很大的情况。在现实的分类学习任务中,我们经常会遇到类别不平衡,以下是一些例子:
如果不同类别的训练样例数目稍有差别,通常影响不大,但若差别很大,则会对学习过程造成困扰。例如有998个反例,但是正例只有2个,那么学习方法只需要返回一个永远将新样本预测为反例的学习器,就能达到99.8%的精度;然而这样的学习器往往没有价值,因为它不能预测出任何正例。
对训练集里的少数类进行“过采样”(over sampling),即增加一些少数类样本使得正、反例数目接近,然后再进行学习。即是过采样。
随机过抽样是增加少数类样本数量,可以事先设置多数类与少数类最终的数量比例,在保留多数类样本不变的情况下,根据比例随机复制少数类样本,在使用的过程中为了保证所有的少数类样本信息都会被包含,可以先完全复制一份全量的少数类样本,再随机复制少数样本使得满足数量比例,具体步骤如下:
缺点:
对于随机过采样,由于需要对少数类样本进行复制来扩大数据集,造成模型训练复杂度加大。另一方面也容易造成模型的过拟合问题,因为随机过采样是简单的对初始样本进行复制采样,这就使得学习器学得的规则过于具体化,不利于学习器的泛化性能,造成过拟合问题。
为了解决随机过采样中造成模型过拟合问题,又能保证实现数据集均衡的目的,出现了过采样法代表性的算法SMOTE和Borderline-SMOTE算法
SMOTE全称是Synthetic Minority Oversampling即合成少数类过采样技术。
SMOTE的主要思想是利用特征空间中少数类样本之间的相似性来建立人工数据,特别是,对于子集 S m i n ⊂ S S_{min}⊂ S Smin⊂S,对于每一个样本 x i ⊂ S m i n x_i\subset S_{min} xi⊂Smin使用K-近邻法,其中K-近邻被定义为考虑 S m i n S_{min} Smin中的 K K K个元素本身与 x i x_i xi 的欧氏距离在 n n n维特征空间 X X X中表现为最小幅度值的样本。由于不是简单地复制少数类样本,因此可以在一定程度上避免分类器的过度拟合,实践证明此方法可以提高分类器的性能。算法流程如下:
缺点:
1)由于对每个少数类样本都生成新样本,因此容易发生生成样本重叠的问题。
2)在SMOTE算法中,出现了过度泛化的问题,主要归结于产生合成样本的方法。特别是,SMOTE算法对于每个原少数类样本产生相同数量的合成数据样本,而没有考虑其邻近样本的分布特点,这就使得类间发生重复的可能性增大。
解释缺点2)的原因:结合前面所述的SMOTE算法的原理,SMOTE算法产生新的人工少数类样本过程中,只是简单的在同类近邻之间插值,并没有考虑少数类样本周围多数类样本的分布情况。如下图所示,绿色正号1、2分布在多数类样本周围,它们离多数类样本最近,这就导致它们有可能被划分成多数类样本。因此,SMOTE算法的样本生成机制存在一定的盲目性。
原始的SMOTE算法对所有的少数类样本都是一视同仁的,但实际建模过程中发现那些处于边界位置的样本更容易被错分,因此利用边界位置的样本信息产生新样本可以给模型带来更大的提升。Borderline-SMOTE便是将原始SMOTE算法和边界信息算法结合的算法。算法流程如下:
上面式子表明,只有最近邻样本集中多数类多于少数类的那些 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 ∣Si−KNN∩Smax∣=k, 即: x i x_{i} xi的所有k个最近邻样本都属于多类。
直接对训练集中多数类样本进行“欠采样”(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 Snew−max=Smax−E 。
缺点:
随机欠采样方法通过改变多数类样本比例以达到修改样本分布的目的,从而使样本分布较为均衡,但是这也存在一些问题。对于随机欠采样,由于采样的样本集合要少于原来的样本集合,因此会造成一些信息缺失,即将多数类样本删除有可能会导致分类器丢失有关多数类的重要信息。
Informed欠抽样算法可以解决传统随机欠采样造成的数据信息丢失问题,且表现出较好的不均衡数据分类性能。其中有一些集成(ensemble)的想法,主要有两种方法,分别是EasyEnsemble算法和BalanceCascade算法。
它把数据划分为两部分,分别是多数类样本和少数类样本。算法流程如下:
可以看到,EasyEnsemble的想法是多次随机欠抽样,尽可能全面地涵盖所有信息,算法特点是利用boosting减小偏差(Adaboost)、bagging减小方差(集成分类器)。实际应用的时候可以尝试选用不同的分类器来提高分类的效果。
BalanceCascade算法基于Adaboost,将Adaboost作为基分类器,其核心思路是:
在每一轮训练时都使用多数类与少数类数量相等的训练集,训练出一个Adaboost基分类器。
然后使用该分类器对全体多数类进行预测,通过控制分类阈值来控制假正例率(False Positive Rate),将所有判断正确的类删除。
最后,进入下一轮迭代中,继续降低多数类数量。
目前为止我们使用的重采样方法几乎都是只针对某一类样本:对多数类样本欠采样,对少数类样本过采样。也有人提出将欠采样和过采样综合的方法,解决样本类别分布不平衡和过拟合问题,本部分介绍其中的SMOTE+Tomek Links和SMOTE+ENN。
定义: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} xi∈Smax, x j ∈ S m i n x_j \in S_{min} xj∈Smin,记 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方法的算法流程非常简单:
Tomek Links对寻找的是那种噪声点或者边界点,可以很好地解决“入侵”的问题,下图红色加号为SMOTE产生的少数类样本,可以看到,红色样本“入侵”到原本属于多数类样本的空间,这种噪声数据问题可以通过Tomek Links很好地解决。 由于第一步SMOTE方法已经很好地平衡了类别分布,因此在使用Tomek Links对的时候考虑剔除所有的Tomek Links对。
SMOTE+ENN方法和SMOTE+Tomek Links方法的想法和过程都是很类似的:
LightGBM解决类不平衡的思路类似于代价敏感学习。
在LGBM的文档中,可以看到有两个参数来处理类别不平衡,分别是is_unbalance和scale_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。
一共提到了以下几点:
在数据分析或者推荐算法的应用场景下,注定会使用更多的特征去描述数据,以便收集大量的数据去寻找规律。
更多的特征注定会增加研究准确性。但是在大多数情况下,许多特征之间存在关系,从而增加了模型的复杂性,对数据分析带来不便。如果对每个特征进行分析,信息会被孤立。而且减少特征信息,也很容易产生错误的结论。
为了能够减少分析特征的同时,又尽量减少原指标的信息损失,对收集的数据进行全面的分析。由于各变量间存在一定的相关关系,因此有可能用较少的综合指标分别综合存在于各变量中的各类信息。主成分分析与因子分析就属于这类降维的方法。
分析一下表中的主成分是什么?
再分析它的主成分?
因此,使用降维技术,将维度降低,可以更好的分析出想要的内容。
线性方法进行数据降维,同时保留最重要的信息。它们通过寻找一种在较低维空间中表示数据的方式来捕捉数据中的原始模式和线性关系。主要线性方法如下:
非线性降维技术尝试捕捉数据中更复杂的非线性关系,并将其表示为较低维度的空间。三种最常用的三种非线性的降维方案:
SVD原理如下:
A = W ∑ W T A=W \sum W^{T} A=W∑WT
这里应运而生SVD(奇异值)分解,可以解决非方阵使用特征值分解的方法,这种方法被称作奇异值分解。
矩阵的奇异值分解是指,将一个非零的m×n实矩阵 A A A, A ∈ R m × n A∈R^{m×n} A∈Rm×n,表示为以下三个实矩阵乘积形式的运算,即进行矩阵的因子分解: A = U ∑ V T A=U \sum V^{T} A=U∑VT
其中
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≥...≥σp≥0p=min(m,n)
∑
=
[
σ
1
0
0
0
0
σ
2
0
0
0
0
σ
3
0
0
0
0
⋱
]
m
×
n
\sum = \left[σ10000σ20000σ30000⋱
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}
ΣΣT∈Rm×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[σ10000σ20000σ30000⋱
奇异值分解是一种矩阵因子分解方法,是线性代数概念,但在统计学习中被广泛使用,成为其重要工具。
应用:主成分分析、潜在语义分析
矩阵的奇异值分解一定存在,但不唯一。奇异值分解可以看做矩阵数据压缩的一种方法。
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),迭代次数from sklearn.decomposition import TruncatedSVD
tsvd2D = TruncatedSVD(n_components=2)
tsvd_data2D = tsvd2D.fit_transform(red_wine_df)
PCA 的原理: 通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。主成分是原特征的线性组合。(前提是变换后的矩阵需要保留变换前矩阵的主要信息,不然降维没有意义)
样本点
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(xi−x−)]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[ {####
扩展到所有样本 :
∑
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[ {####
矩阵的迹就是方差和,对其最大化:
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}
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[ {w(1)1w(2)1w(1)2w(2)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′维
从矩阵角度看:
因此,有 m 条 n 维数据, PCA的步骤总结为:
代码如下:
from sklearn.decomposition import PCA
pca2D = PCA(n_components=2)
# 将数据降维到2维(dimensions)
pca_2D = pca2D.fit_transform(red_wine_df)
独立成分分析(ICA),动机源自于cocktail party problem(鸡尾酒会问题),ICA与被称为盲源分离(Blind Source Separation,BSS)或盲信号分离的方法具有非常密切的关系。“源”在此处的意思是指原始信号,即独立成分,如鸡尾酒会中的说话者;而“盲”表示我们对于混合矩阵所知甚少,仅仅对源信号做非常弱的假定。
ICA假设的三个条件 :
ICA算法步骤
观测信号构成一个混合矩阵,通过数学算法进行对混合矩阵A的逆进行近似求解分为三个步骤:
以上有较多的数学推导,这里就省略,下面给出fastICA的算法流程:
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对应着两种不同的模型,其中一个模型就是这一小节要讲的,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μ1∣∣22 ,同时我们希望同一种类别数据的投影点尽可能的接近,也就是要同类样本投影点的协方差
ω
T
∑
0
ω
ω^T\sum_0ω
ωT∑0ω 和
ω
T
∑
1
ω
ω^T\sum_1ω
ωT∑1ω 尽可能的小,即最小化
ω
T
∑
0
ω
+
ω
T
∑
1
ω
ω^T\sum_0ω+ω^T\sum_1ω
ωT∑0ω+ωT∑1ω 。综上所述,优化目标为:
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(ω)=ωT∑0ω+ωT∑1ω∣∣ωTμ0−ωTμ1∣∣22=ω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算法流程:
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(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(TSNE)将数据点之间的相似度转换为概率。原始空间中的相似度由高斯联合概率表示,嵌入空间的相似度由“t分布”表示。
SNE是通过仿射(affinitie)变换将数据点映射到概率分布上,主要包括两个步骤:
t-SNE模型是非监督的降维,他跟kmeans等不同,他不能通过训练得到一些东西之后再用于其它数据(比如kmeans可以通过训练得到k个点,再用于其它数据集,而t-SNE只能单独的对数据做操作,也就是说他只有fit_transform,而没有fit操作)
尽管SNE提供了很好的可视化方法,但是他很难优化,而且存在”crowding problem”(拥挤问题)。后续中,Hinton等人又提出了t-SNE的方法。与SNE不同,主要如下:
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()
均匀流形近似和投影(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()
线性回归的局限性是只能应用于存在线性关系的数据中,但是在实际生活中,很多数据之间是非线性关系,也可以用线性回归拟合非线性回归,但效果很差,此时就需要对线性回归模型进行改进,使之能够拟合非线性数据。
目标:将数据进行升维处理(特征拓展),可以更好的适应模型
1)处理目的:
2) 常见处理方法,多项式拓展:
(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()
过拟合指的是数据进行过度训练,得出来的训练模型虽然对于训练数据来说,拟合地非常好,但是对于测试数据,将会有糟糕的表现,原因是过度地拟合将会把噪声也极大地引入。
欠拟合指的是数据没有训练完成,得出来的训练模型对训练集拟合效果不好。对于测试集的操作效果也不高 。
优化: 是指调节模型以在训练数据上得到最佳性能。
泛化: 是指训练好的模型在前所未见的数据(测试集)上的性能好坏。
偏差描述样本偏离实际值的情况,方差描述样本的分布疏密情况。下图中红色点为真值,蓝色点为样本点,描绘了不同偏差和方差分布情况。
导致偏差的原因,可能是对模型的假设是错误的,例如用线性的假设去预测非线性数据,或者采用的特征和预测其实没有关系。在机器学习中,一般不会出现采取的特征和预测毫无相关的情况,事实上导致高偏差的原因主要是欠拟合。导致方差的原因,通常是因为模型太过复杂,一点的数据抖动都将会影响到结果,过拟合是导致高方差的主要原因。
现象 | 原因 | 解决方法 |
---|---|---|
欠拟合 | 数据特征少 | 升维(特征扩展)、减少正则化参数、非线性模型、集成学习 |
数据量少 | 获取更多的数据 | |
过拟合 | 数据特征多 | 降维(PCA、SVD)、筛选特征、增加正则化、Dropout |
数据特征范围差距大 | 特征缩放(归一化、标准化) |
获取更多的训练数据。只要给足够多的数据,让模型学习尽可能多的情况,它就会不断修正自己,从而得到更好的结果。如何获取更多数据,可以有以下几个方法:
- 从数据源头获取更多数据。
- 根据当前数据集估计数据分布参数,使用该分布产生更多数据:这个一般不用,因为估计分布参数的过程也会代入抽样误差。
- 数据增强(Data Augmentation):通过一定规则扩充数据。如在物体分类问题里,物体在图像中的位置、姿态、尺度,整体图片明暗度等都不会影响分类结果。我们就可以通过图像平移、翻转、缩放、切割等手段将数据库成倍扩充。
但是获取到有效的数据往往是非常困难的,代价很大(所以在多数情况下不使用此方案)。那么有没有代价适中,又可以解决过拟合的方案呢?
调节模型允许存储的信息量或者对模型允许存储的信息加以约束,该类方法也称为正则化。即:
经验风险最小化可以理解为:最小化代价函数
结构风险最小化可以理解为:最小化目标函数(代价函数+正则化项)
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=1∑N(w∗x−yi)2+λR(w)
L1正则化(LASSO回归 )
权值向量w中各个元素的绝对值之和:
R
(
w
)
=
∣
∣
w
∣
∣
1
=
∣
w
1
∣
+
∣
w
2
∣
R(w)=||w||_1=|w_1|+|w_2|
R(w)=∣∣w∣∣1=∣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∣∣w∣∣22=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的情况?
例:
假设某一层对给定输入样本的返回值应该是向量:[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每次输入一个样本,就相当于该神经网络就尝试了一个新的结构,但是所有这些结构之间共享权重。因为神经元不能依赖于其他特定神经元而存在,所以这种技术降低了神经元复杂的互适应关系。正因如此,网络需要被迫学习更为鲁棒的特征(泛化性更强)。
欠拟合的情况比较容易克服,解决方法有:
- 增加新特征,可以考虑加入进特征组合、高次特征,来增大假设空间。
- 添加多项式特征,这个在机器学习算法里面用的很普遍,例如将线性模型通过添加二次项或者三次项使模型泛化能力更强。
- 减少正则化参数,正则化的目的是用来防止过拟合的,但是模型出现了欠拟合,则需要减少正则化参数。
- 使用非线性模型,比如核SVM 、决策树、深度学习等模型 。
- 调整模型的容量(capacity),通俗地,模型的容量是指其拟合各种函数的能力。
- 容量低的模型可能很难拟合训练集;使用集成学习方法,如Bagging,将多个弱学习器Bagging。
包括留出法、交叉验证法、留一法、自助法等。
(1)留出法
留出法(hold-out):一部分为训练集,一部分为测试集。
应尽量保证数据分布的一致性。
划分比例:7:3左右
(2)交叉验证法(k-fold cross validation)
划分为 k k k个互斥子集,用 k − 1 k-1 k−1作为训练集,剩下一个为测试集,最终每一个子集都会作为测试集,其余子集作为训练集,共进行 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) (1−1/m),这样一个样本在训练集中没出现的概率就是 m m m次都未被选中的概率,即 ( 1 − 1 / m ) m (1-1/m)^m (1−1/m)m。当 m m m趋于无穷大时,这一概率就将趋近于 e − 1 = 0.368 e^{-1}=0.368 e−1=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
训练/测试数据的生成器回归常见指标:均方误差、均方根误差、绝对平均误差等
均方误差(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=N∑i=0N−1(yi−yi^)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=N∑i=0N−1(yi−yi^)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=0∑N−1∣yi−yi^∣
相关系数(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=0N−1(xi−x)2∑i=0N−1(yi−y)∑i=0N−1(xi−x)(yi−y)
决定系数(
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==1−∑i=0N−1(yi−y)2∑i=0N−1(yi−yi^)2=1−SSTSSE=SSTSSR
模型 R 2 R^2 R2拟合优度的计算共涉及3个指标,分别是:
S S T = ∑ ( y i − y ˉ ) 2 SST= \sum\left(y_{i}-\bar{y}\right)^{2} SST=∑(yi−yˉ)2 ——总体平方和,它的大小描述了数据集中的数的分散程度
S S E = ∑ ( y i − y ^ i ) 2 SSE= \sum\left(y_{i}-\hat{y}_{i}\right)^{2} SSE=∑(yi−y^i)2 ——残差平方和,SSE越大,模型不能解释的部分越多,则拟合效果越差。
S S R = ∑ ( y ^ i − y ˉ ) 2 SSR= \sum\left(\hat{y}_{i}-\bar{y}\right)^{2} SSR=∑(y^i−yˉ)2 ——回归平方和,拟合数据的分散情况
三者的关系是: S S T = S S E + S S R SST=SSE+SSR SST=SSE+SSR
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+R2P∗R
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。