one-hot标签转换实验

Nature0927 发布于 5 天前 10 次阅读


什么是one-hot向量

还记得上一次线性回归实验的数据集吗?我们借助pandas库的工具查看了它的属性信息,并发现它的所有特征字段都是float浮点数类型,这种类型的数据可以直接传递到模型中。但是在现实世界中,特征并不都是连续的数值,大部分情况下,特征是离散的数据。比如动物类别这个特征,假设会有如下这些属性:
["猫","狗","鸟"]
很明显,这种分类特征并不能直接传入到我们的模型中,我们需要对其进行一定的数值化处理,怎么处理呢?这时候独热编码one-hot便派上了用场。
one-hot向量是一种用于表示离散数据的编码方式。它是一个仅由0和1构成的向量,并且只有一个元素是1,其余都是0,1的位置表示特定的类别或标签。
对于刚刚的动物类别特征,由于总计有3个取值,所以可以用一个长度为3的向量来表示各种可能的取值:
猫:[1, 0, 0]
狗:[0, 1, 0]
鸟:[0, 0, 1]
是不是很容易理解?但你可能想到了另一种更为简单的方式,对于上述数据,共计3个可能的取值,我们也可以用3个数字来表示它们,最简单地:猫=1,狗=2,鸟=3,这也实现了对离散属性的编码。然而,这样编码意味着模型可能会学习到“猫<狗<鸟”,但是这三个取值并无大小关系,只是不同的类别,而采用one-hot向量进行编码就不会出现这种问题,它们都是向量空间的基向量,模长相等,而且每两个向量之间的距离都是相同的,很好地解决了直接赋值所引入的问题。

使用for循环在列表中填充one-hot向量

下面我们使用Python来基于列表实现离散特征到one-hot向量的转换,仅需一个for循环即可实现对所有的属性取值进行编码。

# 离散特征
animals = ['猫', '狗', '鸟']
# 属性的个数
num_classes = len(animals)
# 记录每一个取值的独热编码
one_hot = {}
for index, animal in enumerate(animals):
    # 对于每一个取值,构建一个长度为取值个数的全0列表, 并在其索引的位置上取值为1
    one_hot[animal] = [0 if i != index else 1 for i in range(num_classes)]
print(one_hot)
输出结果
{'猫': [1, 0, 0], '狗': [0, 1, 0], '鸟': [0, 0, 1]}

可以看到,我们成功地对动物类别这一离散特征实现了one-hot编码。

使用第三方库实现标签与one-hot向量的转换

虽然我们可以手写一个for循环来获取离散特征的one-hot向量,但是真实的数据集可能有多个特征都是离散取值,对于每一个特征都要用for循环手动构建one-hot向量费时费力,而且代码不够高效。这时候我们可以借助之前安装的Python库。以numpy为例:

import numpy as np
# 离散特征
animals = ['猫', '狗', '鸟']
# 属性的个数
num_classes = len(animals)
# 使用numpy生成one-hot向量
one_hot = np.eye(num_classes)
print(one_hot)
# 假如我们需要猫的独热编码
index = animals.index('猫')
print(one_hot[index])
输出结果:
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[1. 0. 0.]

使用eye(),可以生成一个单位矩阵,大小由传入参数决定。单位矩阵是对角线元素全部为1,其余元素为0的矩阵,如果我们需要某个特定的属性的独热编码,只需要根据其索引获得第index行的向量即可,这正是one-hot向量的形式。
如果不想按照索引的顺序进行编码,也可以手动指定:

# 定义属性的顺序
labels = [1, 0, 2]
one_hot = np.eye(num_classes)[labels]
print(one_hot)
输出结果:
[[0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 1.]]

pandas也为我们提供了用于获取one-hot向量的工具:

import pandas as pd
 
# 创建 DataFrame存放数据
df = pd.DataFrame({'animal': animals})
 
# 使用 get_dummies 进行独热编码
one_hot = pd.get_dummies(df, columns=['animal'])
 
print(one_hot)
输出结果:
   animal_狗  animal_猫  animal_鸟
0     False      True     False
1      True     False     False
2     False     False      True

pandas的get_dummies()默认将分类变量转换为布尔值,如果我们想看到数值,可以指定dtype为int类型。

# 使用 get_dummies 进行独热编码
one_hot = pd.get_dummies(df, columns=['animal'], dtype=int)
 
print(one_hot)
输出结果:
   animal_狗  animal_猫  animal_鸟
0         0         1         0
1         1         0         0
2         0         0         1

sklearn也提供了转换为one-hot向量的方式:

from sklearn.preprocessing import OneHotEncoder
 
# 将类别转换为二维数组(Scikit-learn 需要输入为二维数组)
animals_2d = np.array(animals).reshape(-1, 1)
 
# 初始化 OneHotEncoder
encoder = OneHotEncoder(sparse_output=False)
 
# 对类别进行独热编码
one_hot = encoder.fit_transform(animals_2d)
 
print("Scikit-learn 独热编码结果:")
print(one_hot)
输出结果:
Scikit-learn 独热编码结果:
[[0. 1. 0.]
 [1. 0. 0.]
 [0. 0. 1.]]

最后,我们通过一个对真实数据集的处理,来加深对one-hot向量的理解。
我们选取Adults数据用于ont-hot向量转换的实战。该数据集来自美国1994年人口普查数据,因此也称作“人口普查收入”数据集,共包含48842条记录。该数据集有14个属性变量,其中有8个是类别离散型变量,可以很好地用于one-hot编码的实战。
首先,我们导入数据并查看基本信息。

from sklearn.datasets import fetch_openml
 
# 加载 Adult 数据集
adult = fetch_openml(name='adult', version=2, as_frame=True)
 
# 获取特征和标签
X = adult.data  # 特征
y = adult.target  # 标签
 
# 查看数据集的基本信息
print("特征名称:", adult.feature_names)
print("标签名称:", adult.target_names)
print(X.info())
print(y.info())

数据集的基本信息如下:

输出结果:
特征名称: ['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country']
标签名称: ['class']
特征信息:

RangeIndex: 48842 entries, 0 to 48841
Data columns (total 14 columns):
 #   Column          Non-Null Count  Dtype   
---  ------          --------------  -----   
 0   age             48842 non-null  int64   
 1   workclass       46043 non-null  category
 2   fnlwgt          48842 non-null  int64   
 3   education       48842 non-null  category
 4   education-num   48842 non-null  int64   
 5   marital-status  48842 non-null  category
 6   occupation      46033 non-null  category
 7   relationship    48842 non-null  category
 8   race            48842 non-null  category
 9   sex             48842 non-null  category
 10  capital-gain    48842 non-null  int64   
 11  capital-loss    48842 non-null  int64   
 12  hours-per-week  48842 non-null  int64   
 13  native-country  47985 non-null  category
dtypes: category(8), int64(6)
memory usage: 2.6 MB
标签信息:

RangeIndex: 48842 entries, 0 to 48841
Series name: class
Non-Null Count  Dtype   
--------------  -----   
48842 non-null  category
dtypes: category(1)
memory usage: 47.9 KB

由于部分数据存在缺失值,我们有必要进行数据的预处理,从上面输出结果中可以发现,workclass、occupation和native-country存在缺失值,但由于缺失的数据占比不大,我们可以去除这些缺失值,留下不存在缺失属性的数据。不过这并不是本次实验的重点,不必过多关注去除缺失值数据的代码。

import pandas as pd
 
# 去除掉存在缺失值的数据
X = X[X['workclass'] != ' ?']
X = X[X['occupation'] != ' ?']
X = X[X['native-country'] != ' ?']
# 获取分类数据
cat_cols = ['workclass', 'education', 'marital-status',
            'occupation', 'relationship', 'race', 'sex', 'native-country']
cat_df = X[cat_cols]
# 利用pandas转换为one-hot向量
cat_one_hot = pd.get_dummies(cat_df, dtype=int)
data_all = pd.concat([cat_one_hot, X[['age', 'fnlwgt', 'education-num',
                                      'capital-gain', 'capital-loss',
                                      'hours-per-week']]], axis=1)
print(data_all.shape)
print(data_all.head())

这里我们以pandas的get_dummies()为例进行独热编码,对于非类别数据,我们不需要进行编码,所以我们取出所有数据的分类属性的部分,然后转换成独热编码表示。最后将数值特征与分类特征独热编码拼接,就得到了新的数据集,此数据集将所有分类数据进行了独热编码,便于后续传入模型进行训练。
我们可以打印编码后的数据集尺寸,并获取前5行查看:

输出结果:
(48842, 105)
   workclass_Federal-gov  workclass_Local-gov  workclass_Never-worked  \
0                      0                    0                       0   
1                      0                    0                       0   
2                      0                    1                       0   
3                      0                    0                       0   
4                      0                    0                       0   
 
   workclass_Private  workclass_Self-emp-inc  workclass_Self-emp-not-inc  \
0                  1                       0                           0   
1                  1                       0                           0   
2                  0                       0                           0   
3                  1                       0                           0   
4                  0                       0                           0   
 
   workclass_State-gov  workclass_Without-pay  education_10th  education_11th  \
0                    0                      0               0               1   
1                    0                      0               0               0   
2                    0                      0               0               0   
3                    0                      0               0               0   
4                    0                      0               0               0   
 
   ...  native-country_Trinadad&Tobago  native-country_United-States  \
0  ...                               0                             1   
1  ...                               0                             1   
2  ...                               0                             1   
3  ...                               0                             1   
4  ...                               0                             1   
 
   native-country_Vietnam  native-country_Yugoslavia  age  fnlwgt  \
0                       0                          0   25  226802   
1                       0                          0   38   89814   
2                       0                          0   28  336951   
3                       0                          0   44  160323   
4                       0                          0   18  103497   
 
   education-num  capital-gain  capital-loss  hours-per-week  
0              7             0             0              40  
1              9             0             0              50  
2             12             0             0              40  
3             10          7688             0              40  
4             10             0             0              30  
 
[5 rows x 105 columns]

实验总结与反思

这一小节,我们了解了什么是one-hot向量,为什么要有one-hot向量,完成了手动构建one-hot向量,并利用第三方库工具进行one-hot编码。最后对一个真实的数据集完成了one-hot编码,相信你对于one-hot独热编码现在有了更加深入的理解!
不过,one-hot编码并不是万能的,它仍然有着一定的缺陷。比如,我们最后对Adult数据集进行了独热编码,编码后的数据集维度来到了105维,这就是one-hot编码的最明显的一个问题,它会导致编码后的特征矩阵维度很高并且非稀疏。我们查看编码后的数据就会发现绝大部分的值都是0,这会导致训练模型时占用更多的内存,且降低计算的效率。独热编码遇到缺失值也会增加特征的维度,这无疑更进一步加大计算和存储开销,且意义不大,甚至可能影响模型最后的性能。
同时,独热编码不能捕捉到相关类别之间的关系,因为它将每个类别视为完全独立的。拿动物类别举例:“猫、狗、鸟”,按照我们的常识和直觉,猫和狗都不会飞,且有更多的相似特征,编码向量之间的距离应该要小一些,而鸟与猫、狗之间的距离应该要大一些,但是独热编码并不能体现出这种关系。要想解决这种问题,我们可以使用嵌入(Embedding)方法来捕捉语义之间的关系,大家感兴趣的可以自行搜索。

此作者没有提供个人介绍。
最后更新于 2026-05-28