One-Hot编码( 虚拟变量)

到目前为止, 表示分类变量最常用的方法就是使用 one-hot 编码 ( onehot-encoding) 或 N 取一编码 ( one-out-of-N encoding) , 也叫虚拟变量( dummy variable) 。 虚拟变量背后的思想是将一个分类变量替换为一个或多个新特征, 新特征取值为 0 和 1。 对于线性二分类( 以及scikit-learn 中其他所有模型) 的公式而言, 0 和 1 这两个值是有意义的, 我们可以像这样对每个类别引入一个新特征, 从而表示任意数量的类别。比如说, workclass 特征的可能取值包括 "Government Employee"、 "Private Employee" 、 "Self Employed" 和 "Self EmployedIncorporated" 。为了编码这 4 个可能的取值, 我们创建了 4 个新特征, 分别叫作 "Government Employee" 、 "Private Employee"、 "Self Employed" 和 "Self Employed Incorporated" 。如果一个人的 workclass 取某个值, 那么对应的特征取值为 1,其他特征均取值为 0。 因此, 对每个数据点来说,4 个新特征中只有一个的取值为1。 这就是它叫作 one-hot 编码或 N 取一编码的原因。其原理如表 4-2 所示。利用 4 个新特征对一个特征进行编码。 在机器学习算法中使用此数据时,我们将会删除原始的 workclass 特征, 仅保留 0-1 特征。表4-2: 利用one-hot编码来编码workclass 特征

workclass Government
Employee
Private
Employee
Self
Employed
Self Employed
Incorporated
Government
Employee
1 0 0 0
Private Employee 0 1 0 0
Self Employed 0 0 1 0
Self Employed
Incorporated
0 0 0 1


我们使用的 one-hot 编码与统计学中使用的虚拟编码( dummy encoding) 非常相似, 但并不完全相同。 为简单起见, 我
们将每个类别编码为不同的二元特征。 在统计学中, 通常将具有 k个可能取值的分类特征编码为 k - 1 个特征( 都等于零表示最后一个可能取值) 。 这么做是为了简化分析( 更专业的说法是, 这可以避免使数据矩阵秩亏) 。将数据转换为分类变量的 one-hot 编码有两种方法: 一种是使用 pandas, 一种是使用 scikit-learn 。 在写作本书时, 使用 pandas 要稍微简单一些, 所以我们选择这种方法。 首先, 我们使用 pandas 从逗号分隔值( CSV) 文件中加载数据:

In[2]:

import pandas as pd
from IPython.display import display
# 文件中没有包含列名称的表头, 因此我们传入header=None
# 然后在"names"中显式地提供列名称
data = pd.read_csv("data/adult.data", header=None, index_col=False,
names=['age', 'workclass', 'fnlwgt', 'education', 'education-num',
'marital-status', 'occupation', 'relationship', 'race', 'gender',
'capital-gain', 'capital-loss', 'hours-per-week', 'native-country',
'income'])
# 为了便于说明, 我们只选了其中几列
data = data[['age', 'workclass', 'education', 'gender', 'hours-per-week','occupation', 'income']]
# IPython.display可以在Jupyter notebook中输出漂亮的格式
display(data.head())

其结果见表 4-3。
表4-3: adult 数据集的前5行

age workclass education gender hours-per
week
occupation income
0 39 State-gov Bachelors Male 40 Adm-clerical <=50K
1 50 Self-emp-not
inc
Bachelors Male 13 Exec-managerial <=50K
Handlers-

 

2 38 Private HS-grad Male 40 cleaners <=50K
3 53 Private 11th Male 40 Handlers
cleaners
<=50K
4 28 Private Bachelors Female 40 Prof-specialty <=50K


01. 检查字符串编码的分类数据
读取完这样的数据集之后, 最好先检查每一列是否包含有意义的分类数据。 在处理人工( 比如网站用户) 输入的数据时, 可能没有固定的类别, 拼写和大小写也存在差异, 因此可能需要预处理。 举个例子, 有人可能将性别填为“male”( 男性) , 有人可能填为“man”( 男人) , 而我们希望能用同一个类别来表示这两种输入。 检查列的内容有一个好方法, 就是使用 pandas Series( Series 是 DataFrame 中单列对应的数据类型) 的value_counts 函数, 以显示唯一值及其出现次数:
In[3]:

print(data.gender.value_counts())

Out[3]:

Male 21790
Female 10771
Name: gender, dtype: int64


可以看到, 在这个数据集中性别刚好有两个值: Male 和 Female, 这说明数据格式已经很好, 可以用 one-hot 编码来表示。 在实际的应用中, 你应该查看并检查所有列的值。 为简洁起见, 这里我们将跳过这一步。用 pandas 编码数据有一种非常简单的方法, 就是使用get_dummies 函数。 get_dummies 函数自动变换所有具有对象类型( 比如字符串) 的列或所有分类的列( 这是 pandas 中的一个特殊概念, 我们还没有讲到) :
In[4]:

print("Original features:\n", list(data.columns), "\n")
data_dummies = pd.get_dummies(data)
print("Features after get_dummies:\n", list(data_dummies.columns))

Out[4]:
Original features:

['age', 'workclass', 'education', 'gender', 'hours-per-week', 'occupation','income']
Features after get_dummies:
['age', 'hours-per-week', 'workclass_ ?', 'workclass_ Federal-gov',
'workclass_ Local-gov', 'workclass_ Never-worked', 'workclass_ Private',
'workclass_ Self-emp-inc', 'workclass_ Self-emp-not-inc',
'workclass_ State-gov', 'workclass_ Without-pay', 'education_ 10th',
'education_ 11th', 'education_ 12th', 'education_ 1st-4th',
...
'education_ Preschool', 'education_ Prof-school', 'education_ Some-college',
'gender_ Female', 'gender_ Male', 'occupation_ ?',
'occupation_ Adm-clerical', 'occupation_ Armed-Forces',
'occupation_ Craft-repair', 'occupation_ Exec-managerial',
'occupation_ Farming-fishing', 'occupation_ Handlers-cleaners',
...
'occupation_ Tech-support', 'occupation_ Transport-moving',
'income_ <=50K', 'income_ >50K']

你可以看到, 连续特征 age 和 hours-per-week 没有发生变化,
而分类特征的每个可能取值都被扩展为一个新特征:
In[5]:

data_dummies.head()

Out[5]:

age hoursperweek workclass_? workclass_Federalgov workclass_Local
gov
... occup
0 39 40 0.0 0.0 0.0 ... 0.0
1 50 13 0.0 0.0 0.0 ... 0.0
2 38 40 0.0 0.0 0.0 ... 0.0
3 53 40 0.0 0.0 0.0 ... 0.0
4 28 40 0.0 0.0 0.0 ... 0.0


ation_Techsupp
5 rows×46 columns
下面我们可以使用 values 属性将 data_dummies 数据框( DataFrame ) 转换为 NumPy 数组, 然后在其上训练一个机器学
习模型。 在训练模型之前, 注意要把目标变量( 现在被编码为两个income 列) 从数据中分离出来。 将输出变量或输出变量的一些导出属性包含在特征表示中, 这是构建监督机器学习模型时一个非常常见的错误。
注意: pandas 中的列索引包括范围的结尾, 因此'age':'occupation_Transport-moving' 中包括occupation_Transport-moving 。 这与 NumPy 数组的切片不同, 后者不包括范围的结尾, 例如 np.arange(11)[0:10]不包括索引编号为 10 的元素。
在这个例子中, 我们仅提取包含特征的列, 也就是从 age 到occupation_ Transport-moving 的所有列。 这一范围包含所有
特征, 但不包含目标:
In[6]:

features = data_dummies.ix[:, 'age':'occupation_ Transport-moving']
# 提取NumPy数组
X = features.values
y = data_dummies['income_ >50K'].values
print("X.shape: {} y.shape: {}".format(X.shape, y.shape))

Out[6]:

X.shape: (32561, 44) y.shape: (32561,)

现在数据的表示方式可以被 scikit-learn 处理, 我们可以像之前
一样继续下一步:
In[7]:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
logreg = LogisticRegression()
logreg.fit(X_train, y_train)
print("Test score: {:.2f}".format(logreg.score(X_test, y_test)))

Out[7]:
Test score: 0.81
在这个例子中, 我们对同时包含训练数据和测试数据的数据框调用 get_dummies 。 这一点很重要, 可以确保训练集和测试
集中分类变量的表示方式相同。假设我们的训练集和测试集位于两个不同的数据框中。 如果workclass 特征的 "Private Employee" 取值没有出现在测试集中, 那么 pandas 会认为这个特征只有 3 个可能的取值,因此只会创建 3 个新的虚拟特征。 现在训练集和测试集的特征个数不相同, 我们就无法将在训练集上学到的模型应用到测试集上。 更糟糕的是, 假设 workclass 特征在训练集中有"Government Employee" 和 "Private Employee" 两个值,而在测试集中有 "Self Employed" 和 "Self Employed
Incorporated" 两个值。 在两种情况下, pandas 都会创建两个新的虚拟特征, 所以编码后的数据框的特征个数相同。 但在训练集和测试集中的两个虚拟特征含义完全不同。 训练集中表示 "Government Employee" 的那一列在测试集中对应的是"Self Employed" 。如果我们在这个数据上构建机器学习模型, 那么它的表现会很差, 因为它认为每一列表示的是相同的内容( 因为位置相同) , 而实际上表示的却是非常不同的内容。 要想解决这个问题, 可以在同时包含训练数据点和测试数据点的数据框上调用get_dummies , 也可以确保调用 get_dummies 后训练集和测试集的列名称相同, 以保证它们具有相同的语义。

你可能感兴趣的:(python)