类别型数据:词表与独热编码#
维度(dimension),其实就是特征向量(feature vector)里元素个数的另一个说法。
有些类别型特征的维度很低,低到你可以一眼看完它的全部取值:
| 特征名 | 类别数 | 类别样例 |
|---|---|---|
| snowed_today | 2 | True、False |
| skill_level | 3 | 初学者、熟练者、专家 |
| season | 4 | 冬、春、夏、秋 |
| day_of_week | 7 | 星期一、星期二、星期三…… |
| planet | 8 | 水星、金星、地球…… |
词表#
类别数量不多时,我们就可以把它编码成一份词表(vocabulary)。用词表编码之后,模型会把每一个可能的类别取值,当成一个独立特征来对待;在训练过程中,模型会为每个类别分别学到一组权重。
举个例子。假设你要训练一个模型,根据车漆颜色 car_color 来预测汽车的价格——说不定红色的车就是比绿色的贵。由于各家厂商能提供的车漆颜色是有限的,car_color 就属于一个低维度的类别型特征。
下面这张图给出了 car_color 的一份词表(也就是所有可能的取值):
索引编号#
模型只会处理浮点数。所以我们必须把每个字符串转换成一个独一无二的索引编号,就像下面这张图:
不过,把字符串换成索引编号只是第一步。要真正让模型学到取值之间有意义的关系,还得再往下走一层。因为——如果类别型特征就这么以索引整数的形态被送进模型,模型会把这些编号当成连续的浮点数来看待。换个说法,它会认为 "Purple" 的可能性是 "Orange" 的六倍。
独热编码#
搭好词表之后的下一步,是把每个索引编号转换成它的独热编码(One-hot encoding。
在独热编码里:
每个类别都由一个长度为 N 的向量(数组)表示,N 就是类别的数量。比如
car_color一共有 8 个可能的类别,那么代表它的独热向量就有 8 个元素。一个独热向量里,有且仅有一个元素的值是 1,其余元素的值全是 0。
下面这张表就是 car_color 中每种颜色对应的独热编码:
| 特征 | Red | Orange | Blue | Yellow | Green | Black | Purple | Brown |
|---|---|---|---|---|---|---|---|---|
| “Red” | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| “Orange” | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| “Blue” | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| “Yellow” | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| “Green” | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| “Black” | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| “Purple” | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| “Brown” | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
注意,真正被放进特征向量的是独热向量,而不是字符串本身、也不是索引编号。模型会为特征向量里的每一个元素分别学到一组权重。
提示: 真正的独热编码中,只有一个元素的值是 1.0。在一种被称为多热编码(multi-hot encoding)的变体里,可以有多个元素的值同时是 1.0。
MLWM:关于多热编码,一个典型场景就是垃圾邮件分类场景,我们就需要用一个多热编码向量来表示一封邮件,然后使用模型来对这封邮件进行分类,可以参见「词袋模型原理:文本特征表示与机器学习建模」 中的介绍。
下面这张图展示了词表表示中各种转换的全过程:
稀疏表示#
取值绝大部分是零(或者空)的特征,我们称之为稀疏特征(Sparse feature)。很多类别型特征,比如 car_color,天然就是稀疏特征。
稀疏表示(Sparse representation),指的是只存储那个 1.0 所在的位置。例如,"Blue" 对应的独热向量是:
[0, 0, 1, 0, 0, 0, 0, 0]
因为这个 1 位于第 2 位(从 0 开始数),所以上面这个独热向量的稀疏表示就是:
2
不难看出,稀疏表示占用的内存远小于 8 个元素的独热向量。但要特别强调的是:模型必须用独热向量来训练,而不是稀疏表示。
提示: 多热编码的稀疏表示,存储的是所有非零元素的位置。例如,一辆同时是
"Blue"和"Black"的车,它的稀疏表示是2, 5。
类别型数据中的离群值#
和数值型数据一样,类别型数据里也有离群值。假设 car_color 里除了那些常见颜色之外,还混进了一些极少用到的离群颜色,比如 "Mauve"(藕荷色)或 "Avocado"(牛油果绿)。
我们当然可以给每一种离群颜色都单开一个类别,但更好的做法是:把它们统统塞进一个叫作词表外(out-of-vocabulary, OOV)的「万能」类别里。换句话说,所有离群颜色都被分箱进同一个离群桶里,模型则为这个桶单独学一组权重。
高维类别型特征的编码#
词嵌入#
有些类别型特征的维度非常高,比如下面这张表里的几个:
| 特征名 | 类别数 | 类别样例 |
|---|---|---|
| words_in_english | ~500,000 | “happy”、“walking” |
| US_postal_codes | ~42,000 | “02114”、“90301” |
| last_names_in_Germany | ~850,000 | “Schmidt”、“Schneider” |
当类别数量很多时,独热编码通常是个糟糕的选择。更好的选择是嵌入(Embeddings)——我们在单独的嵌入模块中详细讲过。嵌入能大幅压缩维度,这给模型带来两方面的好处:
模型训练通常会更快。
构建好的模型做预测推断通常也更快,也就是延迟更低。
哈希#
哈希(Hashing),也叫哈希小技巧(hashing trick),则是另一种不那么常用的降维方式。简单说,哈希是把一个类别(比如某种颜色)映射到一个小整数——也就是那个「用来装这个类别的桶」的编号。
具体来说,哈希算法是这样实现的:
把类别向量里的桶数设为 N,其中 N 要小于剩余类别的总数。举一个随意的例子,假设 N = 100。
选定一个哈希函数。(通常你也会一并选定哈希值的取值范围。)
把每个类别(比如某一种具体颜色)送进这个哈希函数,得到一个哈希值,比如 89237。
把输出哈希值对 N 取模,得到每个桶的索引编号。此处 N = 100、哈希值为 89237,取模结果是 37,因为 89237 % 100 = 37。
用这些新的索引编号,为每个桶生成一份独热编码。
关于哈希数据的更多细节,可以参考生产机器学习系统模块中随机化(Randomization)一节的内容。
小测验:检验你的理解#
判断并说明:一个机器学习模型可以直接在 "Red"、"Black" 这样的原始字符串取值上训练,而不必先把这些取值转换成数值向量。
正确(True)
错误(False)
点击查看答案
错误(False)
训练过程中,模型只能操作浮点数。字符串 "Red" 不是浮点数,所以你必须先把 "Red" 这样的字符串转换成浮点数。
关键术语:
- 类别型数据(Categorical data)
- 维度(Dimensions)
- 离散特征(Discrete feature)
- 特征交叉(Feature cross)
- 特征向量(Feature vector)
- 哈希(Hashing)
- 独热编码(One-hot encoding)
- 稀疏特征(Sparse feature)
- 稀疏表示(Sparse representation)