4、类别列:categorical_column_with_vocabulary_list、词汇表构建、OOV处理

好,咱们接着聊特征列。前面几章我们把数值特征处理得差不多了,现在该聊聊「类别特征」了。

说实话,在真实项目中,类别特征比数值特征要麻烦得多。我最早做推荐系统时,光处理用户ID和商品ID就折腾了好几天。你想想看,用户ID是字符串,模型又不认识字符串,怎么办?

答案就是:把类别映射成整数ID。这就是 categorical_column_with_vocabulary_list 干的事。

4.1 什么是词汇表?

说白了,词汇表就是一个「字符串 → 整数」的映射字典。

比如你有一个特征叫「城市」,取值是 北京、上海、广州、深圳。那词汇表就是:

北京 → 0
上海 → 1
广州 → 2
深圳 → 3

模型拿到「北京」,就知道它对应ID 0。就这么简单。

但这里有个坑——词汇表必须提前定义好。你不能训练时突然冒出一个「杭州」,模型会懵掉的。

核心要点:词汇表是静态的,训练前就要确定。所有可能的取值,都得列进去。

4.2 实战:用 vocabulary_list 构建类别列

咱们直接上代码。假设你有一个数据集,里面有个 color 特征:

import tensorflow as tf

# 定义词汇表
vocab_list = ['red', 'green', 'blue']

# 构建类别列
color_column = tf.feature_column.categorical_column_with_vocabulary_list(
    key='color',
    vocabulary_list=vocab_list
)

# 看看效果
feature_dict = {'color': ['red', 'green', 'blue', 'yellow']}
input_layer = tf.feature_column.input_layer(feature_dict, [color_column])

print(input_layer)

运行一下,你会发现 red 变成了 [1. 0. 0.]green 变成了 [0. 1. 0.]blue 变成了 [0. 0. 1.]

等等,yellow 呢?

嗯,这里要注意——yellow 不在词汇表里,默认会被映射成 [0. 0. 0.],也就是全零向量。这在很多场景下是有问题的。

警告:默认情况下,OOV(Out-Of-Vocabulary)的取值会被忽略,变成全零向量。这可能导致模型学到错误的信息。

4.3 OOV 处理:别让未知值搞崩你的模型

我在项目中遇到过这么一件事:有个电商推荐模型,上线后CTR突然暴跌。查了半天,发现是双十一期间出现了大量新商品类别,这些类别不在词汇表里,全被映射成了全零向量。模型直接懵了。

从那以后,我养成了一个习惯——永远给 OOV 留一个坑位

怎么做?加一个 default_value 参数:

color_column = tf.feature_column.categorical_column_with_vocabulary_list(
    key='color',
    vocabulary_list=vocab_list,
    default_value=len(vocab_list)  # 把 OOV 映射到最后一个ID
)

这样,yellow 就会被映射成 ID 3,而不是被忽略。模型至少能知道「这是个没见过的东西」,而不是「这是个不存在的东西」。

我的建议:default_value 设成词汇表长度,也就是新开一个ID给OOV。这样词汇表大小就是 len(vocab_list) + 1

4.4 词汇表构建的三种方式

实际项目中,词汇表不会只有三个值。我见过最大的词汇表有上百万个商品ID。那怎么构建?

三种常见方式:

方式 适用场景 示例
手动列表 取值少且固定 ['男', '女']
从数据中提取 取值多但可控 df['city'].unique()
从文件加载 取值非常多 pd.read_csv('vocab.csv')

我个人最常用的是第二种——从训练数据中提取。但要注意,一定要用训练集提取,不能用测试集。否则就是数据泄露。

# 从训练数据中提取词汇表
train_colors = train_df['color'].unique().tolist()

# 构建类别列
color_column = tf.feature_column.categorical_column_with_vocabulary_list(
    key='color',
    vocabulary_list=train_colors,
    default_value=len(train_colors)  # 给OOV留位置
)

4.5 词汇表大小与性能

这里有个权衡:词汇表越大,模型参数越多,训练越慢。

我曾经处理过一个用户ID特征,词汇表有500万。结果模型训练速度慢得像蜗牛。后来我用了哈希技巧(后面会讲),才把问题解决。

所以我的经验是:

  • 词汇表 < 1000:直接用 vocabulary_list,简单粗暴
  • 词汇表 1000 ~ 10000:可以用,但注意内存
  • 词汇表 > 10000:建议考虑 categorical_column_with_hash_bucket

一句话总结:词汇表是类别特征的基础,OOV处理是避坑的关键。永远给未知值留一个位置,别让模型在推理时手足无措。

好,这一章就到这里。下一章我们聊聊哈希列——当词汇表大到离谱时,怎么用哈希技巧来救场。