往往我们需要进行训练时,读取的特征是多种多样的,数值类型的变量可能只是其中的一部分(比如年龄),但是还会有一些其他类型的分类变量(比如性别,比如月份)。
而我们这里讨论的分类变量/特征是指任何特征类型,可分为两大类:
- 无序变量: 是指有两个或两个以上类别的变量,这些类别没有任何相关顺序。例如,如果将性别分为两组,即男性和女性,则可将其视为名义变量。
- 有序变量: 则有 “等级 “或类别,并有特定的顺序。例如,一个顺序分类变量可以是一个具有低、中、高三个不同等级的特征。顺序很重要。
计算机无法理解文本数据,因此我们需要将这些类别转换为数字。为了便于理解这里使用kaggle项目的一个数据
| index | id | bin_0 | bin_1 | bin_2 | bin_3 | bin_4 | nom_0 | nom_1 | nom_2 | nom_3 | … | nom_9 | ord_0 | ord_1 | ord_2 | ord_3 | ord_4 | ord_5 | day | month | target |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | T | Y | Green | Triangle | Snake | Finland | … | 2f4cb3d51 | 2 | Grandmaster | Cold | h | D | kr | 2 | 2 | 0 |
| 1 | 1 | 0 | 1 | 0 | T | Y | Green | Trapezoid | Hamster | Russia | … | f83c56c21 | 1 | Grandmaster | Hot | a | A | bF | 7 | 8 | 0 |
| 2 | 2 | 0 | 0 | 0 | F | Y | Blue | Trapezoid | Lion | Russia | … | ae6800dd0 | 1 | Expert | Lava Hot | h | R | Jc | 7 | 2 | 0 |
| 3 | 3 | 0 | 1 | 0 | F | Y | Red | Trapezoid | Snake | Canada | … | 8270f0d71 | 1 | Grandmaster | Boiling Hot | i | D | kW | 2 | 1 | 1 |
| 4 | 4 | 0 | 0 | 0 | F | N | Red | Trapezoid | Lion | Canada | … | b164b72a7 | 1 | Grandmaster | Freezing | a | R | qP | 7 | 8 | 0 |
这份输入数据中有
- 5个二元变量
- 10个无序变量
- 6个有序变量
- 2个循环变量
- 1个目标变量
编码
标签编码
让我们来看看数据集中的 ord_2 特征。它包括6个不同的类别: Cold、Hot、Lava Hot、Boiling Hot、Freezing 等。
计算机无法理解文本数据,因此我们需要将这些类别转换为数字。一个简单的方法是创建一个字典,将这些值映射为从 0 到 N-1 的数字,其中 N 是给定特征中类别的总数。1
2
3
4
5
6
7
8
9# 映射字典
mapping = {
"Freezing": 0,
"Warm": 1,
"Cold": 2,
"Boiling Hot": 3,
"Hot": 4,
"Lava Hot": 5
}
然后我们可以读取数据,并将这些类别转换为数字。1
2
3
4
5import pandas as pd
# 读取数据
df = pd.read_csv("../input/cat_train.csv")
# 取*ord_2*列,并使用映射将类别转换为数字
df.loc[:, "*ord_2*"] = df.*ord_2*.map(mapping)
这种分类变量的编码方式被称为标签编码(Label Encoding)我们将每个类别编码为一个数字标签。
我们也可以使用 scikit-learn 中的 LabelEncoder 进行编码。1
2
3
4
5
6
7
8
9
10import pandas as pd
from sklearn import preprocessing
# 读取数据
df = pd.read_csv("../input/cat_train.csv")
# 将缺失值填充为"NONE" scikit-learn 的 LabelEncoder 无法处理 NaN 值,所以需要提前处理。
df.loc[:, "*ord_2*"] = df.*ord_2*.fillna("NONE")
# LabelEncoder编码
lbl_enc = preprocessing.LabelEncoder()
# 转换数据
df.loc[:, "*ord_2*"] = lbl_enc.fit_transform(df.*ord_2*.values)
我们可以在许多基于树的模型中直接使用它:决策树、随机森林、XGBoost 、GBM、LightGBM等。
但是这种编码方式不能用于线性模型、支持向量机或神经网络,因为它们希望数据是标准化的。
二值化的稀疏矩阵
为了对分类数据的处理能适用于线性模型、支持向量机或神经网络。对于这些类型的模型,我们可以对数据进行二值化(binarize)处理。二值化不是二分类,我们可以通过特征拆解,将一个特征用多个特征表示,实现多分类。
1 | Freezing --> 0 --> 0 0 0 |
这只是将类别转换为数字,然后再转换为二值化表示。这样,我们就把一个特征分成了三个(在本例中)特征(或列)。如果我们有更多的类别,最终可能会分成更多的列。对应的特征 Feature 就可以由 Feature_0、Feature_1、Feature_2 表示。
| Feature | Feature_0 | Feature_1 | Feature_2 |
| ——– | ——— | ——— | ——— |
| Warm | 0 | 0 | 1 |
| Hot | 1 | 0 | 0 |
| Lava Hot | 1 | 0 | 1 |
由于我们只关注其中的非0值(对应特征为1 的值)所以将上述特征格式转换成稀疏矩阵会进一步减少内存使用。用 1 表示矩阵的一种方法是某种字典方法,其中键是行和列的索引,值是 1。1
2
3
4
5(0, 2) 1
(1, 0) 1
(2, 0) 1
(2, 2) 1
# 占用32字节
在大数据矩阵中,稀疏矩阵可以减少内存的效果会更加明显。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import numpy as np
from scipy import sparse
n_rows = 10000
n_cols = 100000
# 生成符合伯努利分布的随机数组,维度为[10000, 100000]
example = np.random.binomial(1, p=0.05, size=(n_rows, n_cols))
print(f"Size of dense array: {example.nbytes}")
# Size of dense array: 8000000000
# 将随机矩阵转换为稀疏矩阵
sparse_example = sparse.csr_matrix(example)
print(f"Size of sparse array: {sparse_example.data.nbytes}")
# Size of sparse array: 399932496
full_size = (
sparse_example.data.nbytes +
sparse_example.indptr.nbytes +
sparse_example.indices.nbytes
)
print(f"Full size of sparse array: {full_size}")
# Full size of sparse array: 599938748
因此,密集阵列需要 ~8000MB 或大约 8GB 内存。而稀疏阵列只占用 399MB 内存。当我们的特征中有越多零时,稀疏阵列对内存的降低效果越显著。
独热编码
二值化特征的稀疏表示比其密集表示所占用的内存要少得多,但对于分类变量来说,还有一种转换所占用的内存更少。这就是所谓的 “独热编码”。
独热编码也是一种二值编码,因为只有 0 和 1 两个值。但必须注意的是,它并不是二值表示法。我们可以通过下面的例子来理解它的表示法。
假设我们用一个向量来表示 ord_2 变量的每个类别。这个向量的大小与 ord_2 变量的类别数相同。在这种特定情况下,每个向量的大小都是 6,并且除了一个位置外,其他位置都是 0。让我们来看看这个特殊的向量表。
| Category | Fea_0 | Fea_1 | Fea_2 | Fea_3 | Fea_4 | Fea_5 |
| ———– | :—: | :—: | :—: | :—: | :—: | :—: |
| Freezing | 0 | 0 | 0 | 0 | 0 | 1 |
| Warm | 0 | 0 | 0 | 0 | 1 | 0 |
| Cold | 0 | 0 | 0 | 1 | 0 | 0 |
| Boiling Hot | 0 | 0 | 1 | 0 | 0 | 0 |
| Hot | 0 | 1 | 0 | 0 | 0 | 0 |
| Lava Hot | 1 | 0 | 0 | 0 | 0 | 0 |
使用热独编码相比二值化编码,在进行稀疏化后,可以更加节省内存,还是以之前的例子,我们表示 Warm、Hot、Lava Hot 这三个类别的稀疏表示如下:1
2
3
4(1, 4) 1
(4, 1) 1
(5, 0) 1
# 直接少了一个非零值,仅占用24字节
二值化中示例的更大的数据,我们来看看变化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
26import numpy as np
from sklearn import preprocessing
# 生成符合均匀分布的随机整数,维度为[1000000, 10000000]
example = np.random.randint(1000, size=1000000)
# 独热编码,非稀疏矩阵
ohe = preprocessing.OneHotEncoder(sparse=False)
# 将随机数组展平
ohe_example = ohe.fit_transform(example.reshape(-1, 1))
print(f"Size of dense array: {ohe_example.nbytes}")
# Size of dense array: 8000000000
# 独热编码,稀疏矩阵
ohe = preprocessing.OneHotEncoder(sparse=True)
# 将随机数组展平
ohe_example = ohe.fit_transform(example.reshape(-1, 1))
print(f"Size of sparse array: {ohe_example.data.nbytes}")
# Size of sparse array: 8000000
full_size = (
ohe_example.data.nbytes +
ohe_example.indptr.nbytes +
ohe_example.indices.nbytes
)
print(f"Full size of sparse array: {full_size}")
# Full size of sparse array: 16000004
这里的密集阵列大小约为 8GB,稀疏阵列在二值化时是300MB,使用热独编码的稀疏矩阵只有 8MB。
罕见类
有时候
罕见类是指在数据集中出现次数少于一定阈值的类别。