One-Hot

前段时间,在刚实习的时候,无意间查阅资料看到了和我同组并且已经工作多年的航哥的博客,在大致浏览了他的所有目录后,不由的感叹确实很有毅力和真心,大致题目为——每周精选xx。每周他都会记录一篇生活或者学习上自己感兴趣的博客,没有一周懈怠也没有一周有多余文章,这是让我很触动的,虽然已经写博客很长一段时间了,但仔细想想我写博客只是为了沉淀自己,并不是获得多少注视。所以当天我便搭建了自己的博客网站。由于客观时间原因最近才开始写文章(文章题目其实已经积累了很多了。。。),以前 CSDN 上的部分文章我也会做精简重写,时间能力有限目前不能保证一周一篇优质博客,并且内容精简,但是我会尽力的。

————给自己一个开头,从今天起

万壑松风知客来,摇扇抚琴待留声

通过独热编码做一个扩展与延伸

简单描述问题:

这是一个常见的问题,在机器学习建模中数据特征并不都是数值型,文字分类的同样占多数,对于这类特征其算法没法直接使用。如果保留该特征,通常会选择做一个数值映射,如 [‘Cat’,’Dog’,’Pig’] 映射为 [0,1,2],这样便完成了非数字类别到数字类别的转换,对于只关心种类的树类模型,我们的转换其实算法已经能很好的处理了,通过信息增益比、基尼系数等方法来构建树。

决策树只关心类别,独热编码本质是增加树深度,而对于线性回归、逻辑回归、K邻近、支持向量机等需要利用数值推算或计算距离的算法来说,只做类别映射其实是有缺陷的,如上面映射后得到 [0,1,2] 这样连续有大小之分的结果,原本这些类别之间是等同关系,但是如今映射后却出现了非等同关系,直接带入算法便曲解了特征的含义,自然预测结果也是不准确的。所以在数值映射之后需要做独热编码(稀疏矩阵问题)。

独热编码即 One-Hot 编码,又称一位有效编码,其方法是使用 N 为状态寄存器来对 N 个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。简单可以理解为将每一个变量转换为唯一特征值等长的 0、1 表示,其余位置都是 0,只有该变量位置下为 1,这样可以做到变量之间距离等价且值唯一,如 [0,1,2] 中可以通过独热变量转换为 0:[1,0,0],1:[0,1,0],2:[0,0,1],将原来的一列特征转换为三列特征(所以独热编码也就是增加了树的深度)。

Python解决问题:

关于上面的数值映射与独热编码,通过 Python 都可以很方便的处理,对于数值映射可以通过【pandas的Categorical】或者【scikit-learn的LabelEncoder】,对独热编码可以使用【pandas的get_dummies】或者【scikit-learn的OneHotEncoder】,pandas 与 scikit-learn 都可以实现数值映射与独热编码,且效果一样。但两者也是有区别的,pandas 调用方便实现迅速非持久化,scikit-learn 持久化模型可以保存,用于对新数据模型采用同样的方式进行数据处理。两者的参数设置也大不相同,例如get_dummies去除冗余的一列编码。

代码阐述:
1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
import numpy as np
# 构建样例数据集df
df = pd.DataFrame({'a':['cat','cat','dog','cat','dog','dog'],'b':[2,3,5,1,3,1],'c':[100,200,100,300,200,200]})
# a b c
#0 cat 2 100
#1 cat 3 200
#2 dog 5 100
#3 cat 1 300
#4 dog 3 200
#5 dog 1 200
数值映射:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Categorical
categories = pd.Categorical(df['a']).categories # 查看类别标签
codes = pd.Categorical(df['a']).codes # 查看数值映射
print(categories)
print(codes)

# Index(['cat', 'dog'], dtype='object')
# [0 0 1 0 1 1]

# LabelEncoder
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit(df['a']) # 训练模型
df['d'] = le.transform(df['a']) # 转换类别
print(df['d'])

#0 0
#1 0
#2 1
#3 0
#4 1
#5 1
独热编码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# get_dummies
dummies = pd.get_dummies(df['d'])
dummies = pd.get_dummies(df['a'], prefix='a_') # get_dummies方法可以直接对非数值的元素进行独热处理,并且可以添加前缀注释等操作
print(dummies)

# a__cat a__dog
#0 1 0
#1 1 0
#2 0 1
#3 1 0
#4 0 1
#5 0 1


# OneHotEncoder
# 不能直接对非数值型的特征值进行编码,可以通过LabelEncoder等先做数值映射,再做独热
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse=False) # 定义模型,设置参数返回数组,而非稀疏矩阵
ohe.fit(np.array(df['d']).reshape(-1,1)) # 模型训练需要传入二维数据(样本行,数据列)
df_temp = ohe.transform(np.array(df['d']).reshape(-1,1)).astype(int) # 传入相同样式数据训练,并且转换为int型(看起来舒服),返回ndarray数组
# 这里fit的格式为n行1列的二维数组,n取决于原来数据的特征值个数;transform后返回n行m列,m取决于特征值的唯一值数量

df_temp = pd.DataFrame(df_temp, columns=['A','B']) # 将二维ndarray数组转换为DataFrame格式
df = pd.concat([df, df_temp], axis=1)
print(df)

# a b c d A B
#0 cat 2 100 0 1 0
#1 cat 3 200 0 1 0
#2 dog 5 100 1 0 1
#3 cat 1 300 0 1 0
#4 dog 3 200 1 0 1
#5 dog 1 200 1 0 1
简单总结:

唯一可能有点疑问的就是 OneHotEncoder 方法的使用。其实通过上面最后输出的 df 数据来看,应该很容里理解,【特征d】是我们通过对【特征a】进行数值映射后的到的结果,而【特征A】【特征B】这两列则是根据【特征d】独热编码,需要按行整体才能作为结果,【特征d】的唯一值为0、1,所以独热后为两列数据,0:[1,0],1:[0,1]。另外有点难理解的就是 fit 和 transform 方法的传入参数,需要传入二维数据。返回结果需要数组,所以设置参数 sparse=False。