处理分类特征时,特别是那些拥有大量独特值(高基数)的特征,独热编码等方法可能导致特征数量急剧增加,从而使模型计算成本高昂且容易过拟合。类似地,目标编码需要谨慎处理以防止数据泄露。哈希编码器,通常被称为“哈希技巧”,提供了一种替代方法,能够有效处理高基数特征,适用于在线学习场景,同时保持输出维度固定。哈希编码的核心思想是使用哈希函数将可能众多的类别值映射到预定义、固定数量的维度(输出特征)中。哈希编码不是为每个类别分配一个唯一的列(如独热编码),也不是基于目标变量计算统计量(如目标编码),而是将哈希函数应用于类别名称(通常表示为字符串),然后使用生成的哈希值来决定哪个输出列将代表该类别。哈希的工作方式哈希处理: 每个独特的类别值都由哈希函数(例如,MurmurHash3、sha256)处理。哈希函数接受输入(类别字符串),并生成一个数值输出(哈希值),通常是一个大整数。哈希函数旨在将输入伪随机地分布在其输出范围内。取模运算: 整数哈希值随后使用取模运算符(%)被缩减到所需的输出维度数量。如果您想要 $k$ 个输出特征,您就计算 hash_value % k。这个操作将哈希值映射到 0 和 $k-1$ 之间的索引。特征向量创建: 创建一个包含 $k$ 列(维度)的新特征矩阵。对于给定的类别,其计算索引(hash_value % k)对应的列中的值通常设为 1。其他实现可能采用不同方案,例如根据哈希值的另一个位使用 +1 或 -1 来轻微减轻冲突影响,甚至使用特征计数。考虑一个分类特征“城市”,其值包括“伦敦”、“巴黎”、“东京”、“纽约”。如果我们的哈希编码器选择 n_components=3:'London' -> 哈希函数 -> 1234567890 -> 1234567890 % 3 -> 索引 0'Paris' -> 哈希函数 -> 9876543210 -> 9876543210 % 3 -> 索引 0'Tokyo' -> 哈希函数 -> 5555555555 -> 5555555555 % 3 -> 索引 1'New York' -> 哈希函数 -> 1122334455 -> 1122334455 % 3 -> 索引 2生成的编码特征可能如下所示:原始值哈希特征 0哈希特征 1哈希特征 2London100Paris100Tokyo010New York001请注意这里的一个重要问题:'London' 和 'Paris' 被映射到了相同的输出特征(索引 0)。这被称为哈希冲突。哈希编码的优点维度可控: 您明确定义输出特征的数量(n_components),而不受独特类别数量的影响。这避免了独热编码在高基数特征上导致的维度激增问题。内存效率高: 由于输出维度是固定的,并且通常远小于独特类别的数量,因此它通常比独热编码更节省内存,尤其是在使用稀疏矩阵实现时。兼容在线学习: 哈希是一种“无状态”转换。它独立处理每个类别,无需维护所有已见过类别的全局映射。这使其适用于流式数据或事先不知道完整类别集的情况。预测期间新出现的、未见过的类别会使用相同的哈希函数处理。无需预先扫描数据: 与独热编码或序数编码不同,您无需首先扫描整个数据集来识别所有独特的类别。缺点和注意事项哈希冲突: 最主要的不足之处。当多个不同的类别由于哈希函数和取模运算映射到相同的输出特征索引时,模型会接收到相同信号,尽管它们对应着不同的原始值。如果冲突频繁发生,或者涉及的类别与目标变量的关系差异很大,这会引入噪声,并可能降低模型性能。随着独特类别数量相对于输出维度数量(n_components)的增加,冲突的可能性也会增加。可解释性丧失: 生成的特征没有直接的语义含义。特征 hash_0 并非特指“伦敦”或“巴黎”,而是指恰好哈希到该索引的类别集合。这使得解释与哈希特征相关的模型系数或特征重要性变得困难。信息丢失: 冲突本身意味着在编码过程中会丢失一些区分冲突类别的信息。选择 n_components: 选择输出维度数量是一个重要的超参数。维度过少会增加冲突风险,可能损害性能。维度过多可能会抵消部分降维优势,尽管对于高基数特征来说,它仍会远少于独热编码。这通常需要根据模型验证性能进行调整。使用 category_encoders 实现虽然 Scikit-learn 有一个常用于文本的 HashingVectorizer,但 category_encoders 库提供了一个方便的 HashingEncoder,专门用于 DataFrame 中的分类特征。import pandas as pd import category_encoders as ce # 示例数据 data = {'Color': ['Red', 'Blue', 'Green', 'Red', 'Yellow', 'Blue', 'Black'], 'Value': [10, 20, 15, 12, 25, 18, 5]} df = pd.DataFrame(data) # 定义输出维度(特征)的数量 n_output_features = 4 # 初始化哈希编码器 # 我们指定要编码的列和所需的组件数量 encoder = ce.HashingEncoder(cols=['Color'], n_components=n_output_features) # 拟合并转换数据 df_encoded = encoder.fit_transform(df) print("原始DataFrame:") print(df) print("\n哈希编码后的DataFrame (n_components=4):") print(df_encoded) # 示例说明如何处理新类别 new_data = pd.DataFrame({'Color': ['Red', 'Orange', 'Blue'], 'Value': [11, 30, 22]}) df_new_encoded = encoder.transform(new_data) # 使用 transform,而不是 fit_transform print("\n编码新数据(包含 'Orange'):") print(df_new_encoded)输出的 df_encoded 会将被“Color”列替换为 n_output_features(本例中为 4)个新列,命名为 col_0、col_1、col_2、col_3。每行将根据原始“Color”值的哈希结果,将值(通常是 1,但实现细节有所不同)分布在这些列中。请注意,transform 步骤中如何无误地处理了 new_data 中未见过的类别“Orange”。何时使用哈希编码哈希编码特别有用时:处理高基数分类特征,当独热编码因内存或计算限制而不可行时。处理在线学习系统或流式数据,其中事先不知道完整的类别词汇。编码特征的可解释性不是主要考虑事项时。内存效率是一个重要考量时。当简单方法难以处理时,它通常是一个实用的选择,但请注意哈希冲突的潜在影响。通常需要尝试 n_components 的不同值,并评估其对后续模型性能的影响。