推荐系统(十九):优势特征蒸馏(Privileged Features Distillation)在商品推荐中的应用(二)
在上一篇文章《推荐系统(十八):优势特征蒸馏(Privileged Features Distillation)在商品推荐中的应用》中,笔者实现了一个基于 PFD 思想的 Demo。其中,Teacher 模型和 Student 模型都是简单的单任务(CTR)模型,在本节,笔者将基于 PFD 思想实现一个多任务模型:其中,Teacher 模型采用 Wide&Deep 模型,而 Student 模型则采用 ESMM 模型。
1.知识蒸馏实现
1.1模拟数据构造
"""
Part-1:模拟数据构造
本部分模拟真实场景,人工构造用户数据、商品数据、用户-商品交互数据(点击、转化),并进行必要的预处
"""
# 设置随机种子保证可复现性
np.random.seed(42)
tf.random.set_seed(42)
# 生成用户、商品和交互数据
num_users = 100
num_items = 200
num_interactions = 1000
# 用户特征
user_data = {
'user_id': np.arange(1, num_users + 1),
'user_age': np.random.randint(18, 65, size=num_users),
'user_gender': np.random.choice(['male', 'female'], size=num_users),
'user_occupation': np.random.choice(['student', 'worker', 'teacher'], size=num_users),
'city_code': np.random.randint(1, 2856, size=num_users),
'device_type': np.random.randint(0, 5, size=num_users)
}
# 商品特征
item_data = {
'item_id': np.arange(1, num_items + 1),
'item_category': np.random.choice(['electronics', 'books', 'clothing'], size=num_items),
'item_brand': np.random.choice(['brandA', 'brandB', 'brandC'], size=num_items),
'item_price': np.random.randint(1, 199, size=num_items)
}
# 交互数据
# 包括:点击和转化(购买)数据
interactions = []
for _ in range(num_interactions):
user_id = np.random.randint(1, num_users + 1)
item_id = np.random.randint(1, num_items + 1)
# 点击标签。0: 未点击, 1: 点击。在真实场景中可通过客户端埋点上报获得用户的点击行为数据
click_label = np.random.randint(0, 2)
# 转化标签。由于转化的前提是点击,因此点击和转化之间是一个漏斗关系——转化显著低于点击
conversion_label = 0
if click_label == 1:
conversion_label = np.random.binomial(1, 0.3) # 假设点击后30%转化率
interactions.append([user_id, item_id, click_label, conversion_label])
# 合并用户特征、商品特征和交互数据
interaction_df = pd.DataFrame(interactions, columns=['user_id', 'item_id', 'click_label', 'conversion_label'])
user_df = pd.DataFrame(user_data)
item_df = pd.DataFrame(item_data)
df = interaction_df.merge(user_df, on='user_id').merge(item_df, on='item_id')
df['ctcvr_label'] = df['click_label'] * df['conversion_label']
# 划分数据集
labels = df[['click_label', 'conversion_label', 'ctcvr_label']]
features = df.drop(['click_label', 'conversion_label', 'ctcvr_label'], axis=1)
train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.2,
random_state=42)
1.2 特征工程
"""
Part-2:特征工程
本部分对原始用户数据、商品数据、用户-商品交互数据进行分类处理,加工为模型训练需要的特征
1.数值型特征:如用户年龄、价格,少数场景下可直接使用,但最好进行标准化,从而消除量纲差异
2.类别型特征:需要进行 Embedding 处理
3.交叉特征:由于维度高,需要哈希技巧处理高维组合特征
"""
# 用户特征处理
user_id = feature_column.categorical_column_with_identity('user_id', num_buckets=num_users + 1)
user_id_emb = feature_column.embedding_column(user_id, dimension=8)
scaler_age = StandardScaler()
df['user_age'] = scaler_age.fit_transform(df[['user_age']])
user_age = feature_column.numeric_column('user_age')
user_gender = feature_column.categorical_column_with_vocabulary_list('user_gender', ['male', 'female'])
user_gender_emb = feature_column.embedding_column(user_gender, dimension=2)
user_occupation = feature_column.categorical_column_with_vocabulary_list('user_occupation',['student', 'worker', 'teacher'])
user_occupation_emb = feature_column.embedding_column(user_occupation, dimension=2)
city_code_column = feature_column.categorical_column_with_identity(key='city_code', num_buckets=2856)
city_code_emb = feature_column.embedding_column(city_code_column, dimension=8)
device_types_column = feature_column.categorical_column_with_identity(key='device_type', num_buckets=5)
device_types_emb = feature_column.embedding_column(device_types_column, dimension=8)
# 商品特征处理
item_id = feature_column.categorical_column_with_identity('item_id', num_buckets=num_items + 1)
item_id_emb = feature_column.embedding_column(item_id, dimension=8)
scaler_price = StandardScaler()
df['item_price'] = scaler_price.fit_transform(df[['item_price']])
item_price = feature_column.numeric_column('item_price')
item_category = feature_column.categorical_column_with_vocabulary_list('item_category',['electronics', 'books', 'clothing'])
item_category_emb = feature_column.embedding_column(item_category, dimension=2)
item_brand = feature_column.categorical_column_with_vocabulary_list('item_brand', ['brandA', 'brandB', 'brandC'])
item_brand_emb = feature_column.embedding_column(item_brand, dimension=2)
"""
交叉特征预处理
"""
# 使用TensorFlow的交叉特征(crossed_column)定义了Wide部分的特征列,主要用于捕捉用户与商品特征之间的组合效应
# 将用户ID(user_id)和商品ID(item_id)组合成一个新特征,捕捉**“特定用户对特定商品的偏好”**
# 用户ID和商品ID的组合总数可能非常大(num_users * num_items),直接编码会导致维度爆炸。
# hash_bucket_size=10000:使用哈希函数将组合映射到固定数量的桶(10,000个),控制内存和计算开销,适用于稀疏高维特征(如用户-商品对)
user_id_x_item_id = feature_column.crossed_column(
[user_id, item_id], hash_bucket_size=10000)
user_id_x_item_id = feature_column.indicator_column(user_id_x_item_id)
user_gender_x_item_category = feature_column.crossed_column(
[user_gender, item_category], hash_bucket_size=1000)
user_gender_x_item_category = feature_column.indicator_column(user_gender_x_item_category)
user_occupation_x_item_brand = feature_column.crossed_column(
[user_occupation, item_brand], hash_bucket_size=1000)
user_occupation_x_item_brand = feature_column.indicator_column(user_occupation_x_item_brand)
"""
特征列定义
"""
# ESMM 模型相关特征列定义
user_tower_columns = [user_id_emb, user_age, user_gender_emb, user_occupation_emb, city_code_emb, device_types_emb]
item_tower_columns = [item_id_emb, item_category_emb, item_brand_emb, item_price]
# Wide&Deep 模型相关特征列定义
deep_feature_columns = [
user_id_emb,
user_age,
user_gender_emb,
user_occupation_emb,
item_id_emb,
item_category_emb,
item_brand_emb,
item_price
]
wide_feature_columns = [
user_id_x_item_id,
user_gender_x_item_category,
user_occupation_x_item_brand
]
1.3 模型架构设计
- Teacher 模型:Wide&Deep 模型,多任务(CTR,CTCVR);
- Student 模型:ESMM 模型,多任务(CTR,CTCVR);
"""
Part-3:模型架构设计
"""
# 教师模型:采用 Wide&Deep 模型
class WideDeepModel(tf.keras.Model):
"""
Wide部分:线性模型,擅长记忆(Memorization),通过交叉特征捕捉明确的特征组合模式(如用户A常点击商品B)。
Deep部分:深度神经网络,擅长泛化(Generalization),通过嵌入向量学习特征的潜在关系(如女性用户与服装品类的关联)。
结合优势:同时处理稀疏特征(如用户ID、商品ID)和密集特征(如价格、年龄),平衡记忆与泛化能力
"""
def __init__(self, wide_feature_columns, deep_feature_columns):
super(WideDeepModel, self).__init__()
# Wide部分(线性模型)
self.linear_features = tf.keras.layers.DenseFeatures(wide_feature_columns)
self.wide_out = tf.keras.layers.Dense(1, activation='sigmoid')
# Deep部分(深度神经网络)
self.dnn_features = tf.keras.layers.DenseFeatures(deep_feature_columns)
self.dnn_layer = tf.keras.Sequential([
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dense(32, activation='relu')
])
self.deep_out = tf.keras.layers.Dense(1, activation='sigmoid')
def call(self, inputs):
# Wide部分:预测CTR
linear_features = self.linear_features(inputs)
ctr_wide_logits = self.wide_out(linear_features)
# Deep部分:预测CTR和CTCVR
dnn_features = self.dnn_features(inputs)
dnn_layer = self.dnn_layer(dnn_features)
ctr_deep_logits = self.deep_out(dnn_layer)
# 在共享的Deep网络基础上,通过单独的Dense(1)层生成CTCVR logits,再通过Sigmoid输出转化概率
ctcvr_logits = self.deep_out(dnn_layer)
# 将Wide和Deep的logits相加,通过Sigmoid输出点击概率
ctr_logits = ctr_wide_logits + ctr_deep_logits
ctr_logits = tf.sigmoid(ctr_logits)
# 返回带名称的双输出
return {
'ctr_logits': ctr_logits, 'ctcvr_logits': ctcvr_logits}
# 学生模型:采用 ESMM 模型
class ESMMStudent(tf.keras.Model):
"""
ESMM 通过引入全样本空间建模解决CVR样本稀疏问题,核心包含两个子任务:
1.CTR任务:预测点击率(全量样本参与训练)
2.CTCVR任务:预测点击后转化率(CTR * CVR,全量样本参与训练)
通过CTCVR任务间接训练CVR模型,使得CVR模型能利用全量曝光样本而非仅点击样本
"""
def __init__(self