在上一篇文章《推薦系統(十八):優勢特征蒸餾(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 = 0if 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部分:預測CTRlinear_features = self.linear_features(inputs)ctr_wide_logits = self.wide_out(linear_features)# Deep部分:預測CTR和CTCVRdnn_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_logitsctr_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