本文主要介绍如何在昇腾上使用 pytorch 对推荐系统中经典的网络模型 Din 进行训练的实战讲解,使用数据集是 Amazon 中 book 数据集,主要内容分为以下几个模块:
CTR(Click-through rate prediction)是工业应用(如在线广告)中的一项重要任务,处理这一类任务较为常用的方法是基于深度学习的方式,通过类似多层感知机的方式,将大规模的稀疏输入特征向量映射为低维的嵌入向量后以组为单位将其转化为固定长度的向量,最后将这些向量进行拼接输入到多层感知机中来学习特征之间的非线性关系。
在 embedding 阶段,所有的离散特征都要被映射成一个固定长度的低维稠密向量。离散特征一般有单值特征和多值特征,分别要做 one-hot 编码和 multi-hot 编码,单值特征没什么问题,直接映射成 embedding 向量即可,而对于多值特征,比如:用户点击过的 item 序列,用户点击过 item 类目的序列,通常的操作都是每个 item 映射成一个 embedding 向量,然后做一个 sum/average pooling,最终得到一个 embedding 向量。
从图中可以看出在做多值特征 pooling 的时候并不会考虑每个 item 的重要性,但是在实际的应用过程中我们知道一个爱游泳的人,之前购买书籍、冰淇淋、薯片、游泳镜。给他推荐护目镜后他是否会点击这次推荐广告,跟之前购买的书籍、薯片与冰淇淋无关,而是跟他以前购买的游泳帽相关。也就是在这一次 CTR 预估中,部分历史数据(游泳镜)起到了决定作用,而其他的基本都无用,所以最好的方式就是给每个 item 一个权重。
DIN 网络给每个 item 的权重是通过目标广告与该商品 item 之间的相关性决定,比如在上述例子中,候选广告推荐用户是否点击是护目镜,与用户购买的书籍、冰淇淋与薯片相关性较少,与游泳镜的相关性更大。
Din 网络创新点介绍 将注意力机制应用于用户兴趣建模,可以更好评估不同项的重要性差异,自适应的学习用户每个历史行为项的权重,使能模型可以更好的捕捉用户对不同项的兴趣。
通过使用局部激活单元方式,来计算用户对每个历史行为的兴趣权重,是注意力机制中有效实现方式。
采用 Mini-batch Perceptron Regularizer 节省参数计算量,避免过拟合。
使用 Data-dependent Activation Function 也就是自适用的数据激活函数
Din 网络架构剖析及搭建 当前广告模型基本上遵循的都是 embedding&MLP 组合模式(图中 basemodel 部分),而 Din 网络通过在 embedding 层与 MLP 之间加入 Attention 机制有效的解决了推荐广告与用户历史购买行为之间相关性的问题,其网络架构重点由两部分组成,一个是激活模块(图中的 Activation Unit),另一个是 Attention 模块(图中的 Sumpooling),接下来将分别介绍这两部分的原理及如何使用 pytorch 搭建这两部分从而构建整个 Din 网络。
Activation Unit 介绍 Activation Unit 作为 Din 网络核心模块,通过将用户历史点击过的商品 Id(Inputs from User)与候选广告商品的 Id(Inputs from Ad)通过 Out Product 的方式计算相关性。计算相关性后通过 concat 的方式将原始输入与相关性计算后的输入进行 shortcut 连接载通过激活与线性层进行输出。关于'PRelu'与'Dice'模块也就是 Din 网络中引入的自适适应的数据激活函数。
Prelu 激活函数计算公式:
f ( x ) = { s if s> 0 \1 if s <= 0 = p ( s ) . s + ( 1 − p ( s ) ) . a s
Dice 激活函数计算公式:
两者计算表达式一致,其通过 p(s)的计算公式不一样来控制激活的取值,关于两者 p(s)的取值如下所示:
Dice 中 p(s)计算公式:
p ( s ) = ( 1 + e − V a r [ s ] + e p s i l o n s − E [ s ] ) 1
式中 E[s]和 Var[s]分别是均值与方差,epsilon 是一个常量,默认取值是 10^-8。
Dice 是 PReLu 的推广,其核心思想是根据输入数据的分布自适应地调整校正点。Dice 可以平滑地在两个通道之间切换,并且当 E[s] = 0 并 Var[s]=0 时,Dice 退化为 PReLU,Dice 通过定义一个 Dice 类实现,'forward()'函数中实现了上述定义公式的功能。
# python自定义包
import os
import pandas as pd
import numpy as np
复制代码
# 导入pytorch相关包
import torch
import torch.nn as nn
复制代码
# 根据公式计算p(s)
class Dice(nn.Module):
def __init__(self):
super(Dice, self).__init__()
self.alpha = nn.Parameter(torch.zeros((1,)))
self.epsilon = 1e-9
def forward(self, x):
# 公式中分母部分e的指数部分计算
norm_x = (x - x.mean(dim=0)) / torch.sqrt(x.var(dim=0) + self.epsilon)
p = torch.sigmoid(norm_x)
x = self.alpha * x.mul(1-p) + x.mul(p)
return x
复制代码
整个激活模块通过 ActivationUnit 类实现,该模块功能是计算用户购买行为与推荐目标之间的注意力系数,比如说用户虽然用户买了这个东西,但是这个东西实际上和推荐目标之间没啥关系,也不重要,所以要乘以一个小权重,如果购买的东西跟推荐目标之间有直接因果关系,则需要乘以一个较大的权重。
class ActivationUnit(nn.Module):
def __init__(self, embedding_dim, dropout=0.2, fc_dims = [32, 16]):
super(ActivationUnit, self).__init__()
# 1.初始化fc层
fc_layers = []
# 2.输入特征维度这里将输入的embedding_dim乘以4是将中间层的节点数放大了4倍,因为将4个模块的向量进行了concat
input_dim = embedding_dim*4
# 3.fc层内容:全连接层(4*embedding, 32)--->激活函数->dropout->全连接层(32,16)->.....->全连接层(16,1)
for fc_dim in fc_dims:
fc_layers.append(nn.Linear(input_dim, fc_dim))
fc_layers.append(Dice())
fc_layers.append(nn.Dropout(p = dropout))
input_dim = fc_dim
fc_layers.append(nn.Linear(input_dim, 1))
# 4.将上面定义的fc层,整合到sequential中
self.fc = nn.Sequential(*fc_layers)
def forward(self, query, user_behavior):
"""
query:targe目标的embedding ->(输入维度) batch*1*embed
user_behavior:行为特征矩阵 ->(输入维度) batch*seq_len*embed
out:预测目标与历史行为之间的注意力系数
"""
# 1.获取用户历史行为序列长度
seq_len = user_behavior.shape[1]
# 2.序列长度*embedding
queries = torch.cat([query] * seq_len, dim=1)
# 3.前面的把四个embedding合并成一个(4*embedding)的向量,
# 第一个向量是目标商品的向量,第二个向量是用户行为的向量,
# 至于第三个和第四个则是他们的相减和相乘(这里猜测是为了添加一点非线性数据用于全连接层,充分训练)
attn_input = torch.cat([queries, user_behavior, queries - user_behavior,
queries * user_behavior], dim = -1)
out = self.fc(attn_input)
return out
复制代码
Attention 模块 Attention 模块是 Din 网络中注意力序列层,其功能是计算用户行为与预测目标之间的系数,并将所有的向量进行相加,这里的目的是计算出用户的兴趣的向量。通过'AttentionPoolingLayer'这个类实现该功能。主要通过'active_unit()'引入了目标和历史行为之间的相关性。
class AttentionPoolingLayer(nn.Module):
def __init__(self, embedding_dim, dropout):
super(AttentionPoolingLayer, self).__init__()
self.active_unit = ActivationUnit(embedding_dim = embedding_dim,
dropout = dropout)
def forward(self, query_ad, user_behavior, mask):
"""
query_ad:targe目标x的embedding -> (输入维度) batch*1*embed
user_behavior:行为特征矩阵 -> (输入维度) batch*seq_len*embed
mask:被padding为0的行为置为false -> (输入维度) batch*seq_len*1
output:用户行为向量之和,反应用户的爱好
"""
# 1.计算目标和历史行为之间的相关性
attns = self.active_unit(query_ad, user_behavior)
# 2.注意力系数乘以行为
output = user_behavior.mul(attns.mul(mask))
# 3.历史行为向量相加
output = user_behavior.sum(dim=1)
return output
复制代码
Din 网络构建 基于上述搭建好的 Activation Unit 与 AttentionPoolingLayer 模块,可以构建 DeepInterestNet 网络,本文实现的网络主要是用在 Amazon-book 数据集上进行测试,其主要功能是用户最近的历史 40 个购买物品是 xxx 时,购买 y 的概率是多少?
class DeepInterestNet(nn.Module):
def __init__(self, feature_dim, embed_dim, mlp_dims, dropout):
super(DeepInterestNet, self).__init__()
# 1.特征维度,就是输入的特征有多少个类
self.feature_dim = feature_dim
# 2.embeding层,将特征数值转化为向量
self.embedding = nn.Embedding(feature_dim+1, embed_dim)
# 3.注意力计算层(论文核心)
self.AttentionActivate = AttentionPoolingLayer(embed_dim, dropout)
# 4.定义fc层
fc_layers = []
# 5.该层的输入为历史行为的embedding,和目标的embedding,所以输入维度为2*embedding_dim
# 全连接层(2*embedding,fc_dims[0])--->激活函数->dropout->全连接层(fc_dims[0],fc_dims[1])->.....->全连接层(fc_dims[n],1)
input_dim = embed_dim * 2
for fc_dim in mlp_dims:
fc_layers.append(nn.Linear(input_dim, fc_dim))
fc_layers.append(nn.ReLU())
fc_layers.append(nn.Dropout(p = dropout))
input_dim = fc_dim
fc_layers.append(nn.Linear(input_dim, 1))
# 6.将所有层封装
self.mlp = nn.Sequential(*fc_layers)
def forward(self, x):
"""
x输入(behaviors*40,ads*1) ->(输入维度) batch*(behaviors+ads)
"""
# 1.排除掉推荐目标
behaviors_x = x[:,:-1]
# 2.记录之前填充为0的行为位置
mask = (behaviors_x > 0).float().unsqueeze(-1)
# 3.获取推荐的目标
ads_x = x[:,-1]
# 4.对推荐目标进行向量嵌入
query_ad = self.embedding(ads_x).unsqueeze(1)
# 5.对用户行为进行embeding,注意这里的维度为(batch*历史行为长度*embedding长度)
user_behavior = self.embedding(behaviors_x)
# 6.矩阵相乘,将那些行为为空的地方全部写为0
user_behavior = user_behavior.mul(mask)
# 7.将用户行为乘上注意力系数,再把所有行为记录向量相加
user_interest = self.AttentionActivate(query_ad, user_behavior, mask)
# 8.将计算后的用户行为行为记录和推荐的目标进行拼接
concat_input = torch.cat([user_interest, query_ad.squeeze(1)], dim = 1)
# 9.输入用户行为和目标向量,计算预测得分
out = self.mlp(concat_input)
# 10.sigmoid激活函数
out = torch.sigmoid(out.squeeze(1))
return out
复制代码
使用 Amazon-book 数据集训练 Din 网络实战 综上所述,我们已经构建好了一个 Din 模型,现在可以尝试使用 Amazon 数据集来训练该网络,首先我们先料了解一下什么 Amazon 数据集是什么?
Amazon-book 数据集介绍 Amazon-Book 数据集是由亚马逊公司提供的大规模图书评论数据集,包含了亚马逊 2000 年至 2014 年之间大量的图书评论信息。是当前最大、最全面的图书评论数据集之一。
Amazon-Book 数据集主要包括以下内容:
这些数据提供了丰富的信息,可以用于分析用户行为、优化产品设计、改进营销策略等
# 使用padas库加载amazon-book数据集
data = pd.read_csv('/home/pengyongrong/workspace/DinModel/DIN-CODE/amazon-books-100k.txt')
复制代码
将加载好的数据集进行打印,数据总共包含 89999 个样例 ,'label'表示用户是否会点击购买推荐的广告商品,0 表示不点击、1 表示点击;'userID'是用来标记用户的;'itemID'用来标识书籍;'cateID'标识书籍属的类别;'hit_item_list'指的是推荐给用户的商品或内容列表;hit_cate_list 指的是推荐给用户的商品内容所述的类别。
| | label | userID | itemID | cateID | hist_item_list | hist_cate_list || 0 | 0 | AZPJ9LUT0FEPY | B00AMNNTIA | Literature & Fiction | 0307744434|0062248391|0470530707|0978924622|15... | Books|Books|Books|Books|Books || 1 | 1 | AZPJ9LUT0FEPY | 0800731603 | Books | 0307744434|0062248391|0470530707|0978924622|15... | Books|Books|Books|Books|Books || 2 | 0 | A2NRV79GKAU726 | B003NNV10O | Russian | 0814472869|0071462074|1583942300|0812538366|B0... | Books|Books|Books|Books|Baking|Books|Books || 3 | 1 | A2NRV79GKAU726 | B000UWJ91O | Books | 0814472869|0071462074|1583942300|0812538366|B0... | Books|Books|Books|Books|Baking|Books|Books || 4 | 0 | A2GEQVDX2LL4V3 | 0321334094 | Books | 0743596870|0374280991|1439140634|0976475731 | Books|Books|Books|Books || ... | ... | ... | ... | ... | ... | ... || 89994 | 0 | A3CV7NJJC20JTB | 098488789X | Books | 034545197X|0765326396|1605420832|1451648448 | Books|Books|Books|Books || 89995 | 1 | A3CV7NJJC20JTB | 0307381277 | Books | 034545197X|0765326396|1605420832|1451648448 | Books|Books|Books|Books || 89996 | 0 | A208PSIK2APSKN | 0957496184 | Books | 0515140791|147674355X|B0055ECOUA|B007JE1B1C|B0... | Books|Books|Bibles|Literature & Fiction|Litera... || 89997 | 1 | A208PSIK2APSKN | 1480198854 | Books | 0515140791|147674355X|B0055ECOUA|B007JE1B1C|B0... | Books|Books|Bibles|Literature & Fiction|Litera... |
89999 rows × 6 columns
通过对数据集分析发现,许多商品 id 只出现了一次,因此在编码的时候以类别作为编码和预测的目标输入给模型进行训练。由于每一个用户的历史购买行为均不一致,因此会导致长度也会不一样,这里截取了 40 个历史行为作为标准,用来获取用户历史购买信息与推荐广告商品的相关性,如果不足 40 则填充 0 到 40,若大于 40 个行为,则截取最近 40 个行为(也就是后 40 个行为)。
'cate_encoder'是全局变量,用来编码类别标签的,在后续测试过程中也会用到。整个预处理过程分为 8 个步骤均在代码出注解。
Amazon-book 数据集预处理 #导入sklearn相关包,主要用到LabelEncoder用来自动编码类别
from sklearn.preprocessing import LabelEncoder
cate_encoder = None
def AmazonBookPreprocess(dataframe, seq_len=40):
"""
数据集处理
dataframe: 未处理的数据集
seq_len: 数据序列长度
data: 处理好的数据集
"""
# 1.按'|'切割,用户历史购买数据,获取item的序列和类别的序列
data = dataframe.copy()
data['hist_item_list'] = dataframe.apply(lambda x: x['hist_item_list'].split('|'), axis=1)
data['hist_cate_list'] = dataframe.apply(lambda x: x['hist_cate_list'].split('|'), axis=1)
# 2.获取cate的所有种类,为每个类别设置一个唯一的编码
cate_list = list(data['cateID'])
_ = [cate_list.extend(i) for i in data['hist_cate_list'].values]
# 3.将编码去重,'0' 作为padding的类别
cate_set = set(cate_list + ['0'])
# 4.截取用户行为的长度,也就是截取hist_cate_list的长度,生成对应的列名
cols = ['hist_cate_{}'.format(i) for i in range(seq_len)]
# 5.截取前40个历史行为,如果历史行为不足40个则填充0
def trim_cate_list(x):
if len(x) > seq_len:
# 5.1历史行为大于40, 截取后40个行为
return pd.Series(x[-seq_len:], index=cols)
else:
# 5.2历史行为不足40, padding到40个行为
pad_len = seq_len - len(x)
x = x + ['0'] * pad_len
return pd.Series(x, index=cols)
# 6.预测目标的类别
labels = data['label']
data = data['hist_cate_list'].apply(trim_cate_list).join(data['cateID'])
# 7.生成类别对应序号的编码器,如book->1,Russian->2这样
global cate_encoder
cate_encoder = LabelEncoder().fit(list(cate_set))
print("cate_encoder is: ", cate_encoder)
# 8.这里分为两步,第一步为把类别转化为数值,第二部为拼接上label
data = data.apply(cate_encoder.transform).join(labels)
return data
复制代码
# 对数据执行预处理操作,该操作由于是对表格逐行进行处理,因此可能会需要一点时间
data = AmazonBookPreprocess(data)
复制代码
cate_encoder is: LabelEncoder()
复制代码
将 data 信息进行打印可以看到用户历史购买的序列与 label 数据集存储在 data 中总共包含 40 个用户历史行为类别信息,大部分用户历史购买数据长度均未达到 40,因此在每一个用户后填充 0。
每一行中具体的数字表示表示用户购买物品所属的类别,比如 0 行中 138、138、138、138、138 表示用户 0 购买 5 个历史物品均属于 138 这个类别,推荐的商品类别是 734 ,因此 label 值=0,表示用户不会点击购买推荐的 734 商品。
而第 1 行恰恰相反,138、138、138、138、138 表示用户 1 购买 5 个历史物品均属于 138 这个类别,推荐的商品类别是 138 ,因此 label 值=1,表示用户很大概率会点击购买推荐的 734 商品。
| | hist_cate_0 | hist_cate_1 | hist_cate_2 | hist_cate_3 | hist_cate_4 | hist_cate_5 | hist_cate_6 | hist_cate_7 | hist_cate_8 | hist_cate_9 | ... | hist_cate_32 | hist_cate_33 | hist_cate_34 | hist_cate_35 | hist_cate_36 | hist_cate_37 | hist_cate_38 | hist_cate_39 | cateID | label || 0 | 138 | 138 | 138 | 138 | 138 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 734 | 0 || 1 | 138 | 138 | 138 | 138 | 138 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 1 || 2 | 138 | 138 | 138 | 138 | 95 | 138 | 138 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1071 | 0 || 3 | 138 | 138 | 138 | 138 | 95 | 138 | 138 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 1 || 4 | 138 | 138 | 138 | 138 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 0 || ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... || 89994 | 138 | 138 | 138 | 138 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 0 || 89995 | 138 | 138 | 138 | 138 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 1 || 89996 | 138 | 138 | 115 | 734 | 734 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 0 || 89997 | 138 | 138 | 115 | 734 | 734 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 138 | 1 |
89999 rows × 42 columns
训练集与测试集划分 通过上述预处理后,我们得到了用户的历史输入数据与推商品的关联数据集,接下来我们需要将上述数据集进行划分以训练集、验证集与测试集。以便模型在训练好的能拥有更好泛化性与可靠性。
训练集与测试集的划分采用'train_test_split',该方法集成在'sklearn.model_selection'库中,提供了数据切分、交叉验证与网格搜索等功能,本文用到的是数据划分功能。
将模型输入与输出进行组合划分训练集、测试集与验证集三个集合,首先从总体数据量上将训练集与测试集以 8:2(test_size = 0.2)的比例进行划分,然后再讲训练集中数据按照 7.5:2.5(test_size = 0.25)比例划分训练集与验证集。
import torch.utils.data as Data
from sklearn.model_selection import train_test_split
#模型输入
data_X = data.iloc[:,:-1]
#模型输出
data_y = data.label.values
#划分训练集,测试集,验证集
tmp_X, test_X, tmp_y, test_y = train_test_split(data_X, data_y, test_size = 0.2, random_state=42, stratify=data_y)
train_X, val_X, train_y, val_y = train_test_split(tmp_X, tmp_y, test_size = 0.25, random_state=42, stratify=tmp_y)
dis_test_x = test_X
dis_test_y = test_y
# numpy转化为torch
train_X = torch.from_numpy(train_X.values).long()
val_X = torch.from_numpy(val_X.values).long()
test_X = torch.from_numpy(test_X.values).long()
train_y = torch.from_numpy(train_y).long()
val_y = torch.from_numpy(val_y).long()
test_y = torch.from_numpy(test_y).long()
# 设置dataset
train_set = Data.TensorDataset(train_X, train_y)
val_set = Data.TensorDataset(val_X, val_y)
test_set = Data.TensorDataset(test_X, test_y)
# 设置数据集加载器,用于模型训练,按批次输入数据
train_loader = Data.DataLoader(dataset=train_set, batch_size=32, shuffle=True)
val_loader = Data.DataLoader(dataset=val_set, batch_size=32, shuffle=False)
test_loader = Data.DataLoader(dataset=test_set, batch_size=32, shuffle=False)
复制代码
模型训练过程定义 上述步骤已经分别将模型构建与数据集处理完成,接下来就是需要对模型进行训练。由于本文实验数据与模型需要运行在 npu 上,因此需要指定'device'是'npu',此外,该模型实现使用 pytorch,目前 npu 对 pytorch 适配度非常高,可以使用'transfer_to_npu'将 pytorch 模型无感迁移到 npu 上运行。
import torch_npu
from torch_npu.contrib import transfer_to_npu
device="npu"
复制代码
通常来说,训练一个模型通常需要定义损失函数、定义优化器、定义模型参数可更新及遍历数据集训练模型四个步骤。前三个步骤比较直接,最后一个步骤在使用数据集训练模型过程中,需要根据输入数据给到模型获得预测的结果,计算损失、反向传播后进行参数更新。
据上述分析,我们定义了'train(model)'函数来实现整个模型的训练过程,入参为'model'表示需要训练的模型,具体的步骤解释均在代码中注解。
import torch.optim as optim
import torch.nn.functional as F
from sklearn.metrics import roc_auc_score
复制代码
def train(model):
# 1.设置迭代次数训练模型
for epoch in range(epoches):
train_loss = []
# 1.1设置二分类交叉熵损失函数
criterion = nn.BCELoss()
# 1.2设置adam优化器
optimizer = optim.Adam(model.parameters(), lr = 0.001)
# 1.3设置模型训练,此时模型参数可以更新
model.train()
# 1.4遍历训练数据集,获取每个梯度的大小,输入输出
for batch, (x, y) in enumerate(train_loader):
# 1.4.1如果有gpu则把数据放入显存中计算,没有的话用cpu计算
x=x.to(device)
y=y.to(device)
# 1.4.2数据输入模型
pred = model(x)
# 1.4.3计算损失
loss = criterion(pred, y.float().detach())
# 1.4.4优化器梯度清空
optimizer.zero_grad()
# 1.4.5方向传播,计算梯度
loss.backward()
# 1.4.6优化器迭代模型参数
optimizer.step()
# 1.4.7记录模型损失数据
train_loss.append(loss.item())
# 1.5模型固化,不修改梯度
model.eval()
val_loss = []
prediction = []
y_true = []
with torch.no_grad():
# 1.6遍历验证数据集,获取每个梯度的大小,输入输出
for batch, (x, y) in enumerate(val_loader):
# 1.6.1如果有gpu则把数据放入显存中计算,没有的话用cpu计算
x=x.to(device)
y=y.to(device)
# 1.6.2模型预测输入
pred = model(x)
# 1.6.3计算损失函数
loss = criterion(pred, y.float().detach())
val_loss.append(loss.item())
prediction.extend(pred.tolist())
y_true.extend(y.tolist())
# 1.7计算auc得分
val_auc = roc_auc_score(y_true=y_true, y_score=prediction)
# 1.8输出模型训练效果
print ("EPOCH %s train loss : %.5f validation loss : %.5f validation auc is %.5f" % (epoch, np.mean(train_loss), np.mean(val_loss), val_auc))
return train_loss, val_loss, val_auc
复制代码
使用 Amazon-book 训练 wideWeep 模型 # 计算出现的最大类别编码是多少,目的为统计一共有多少个商品类别
fields = data.max().max()
# 定义din模型
model = DeepInterestNet(feature_dim=fields, embed_dim=8, mlp_dims=[64,32], dropout=0.2).to(device)
# 迭代次数
epoches = 5
# 模型训练
_= train(model)
复制代码
EPOCH 0 train loss : 0.69174 validation loss : 0.68478 validation auc is 0.53995
EPOCH 1 train loss : 0.68359 validation loss : 0.67958 validation auc is 0.57892
EPOCH 2 train loss : 0.67868 validation loss : 0.67763 validation auc is 0.58612
EPOCH 3 train loss : 0.67605 validation loss : 0.67401 validation auc is 0.59358
EPOCH 4 train loss : 0.67428 validation loss : 0.67516 validation auc is 0.59542
复制代码
评估模型性能 通过上述步骤我们将整个模型在 Amazon-book 数据集上进行了训练,并且得到了较不错的准确率,接下来我们使用测试集中的某个样例对模型进行测试。
#从换分的测试数据集中取出一个数据,从该数据可以看出用户感兴趣的类别是Books,推荐的类别也是Books因此,用户很有可能会点击购买该商品对应label值是1。
dis_test_x.apply(cate_encoder.inverse_transform).reset_index().head(1)
复制代码
| | index | hist_cate_0 | hist_cate_1 | hist_cate_2 | hist_cate_3 | hist_cate_4 | hist_cate_5 | hist_cate_6 | hist_cate_7 | hist_cate_8 | ... | hist_cate_31 | hist_cate_32 | hist_cate_33 | hist_cate_34 | hist_cate_35 | hist_cate_36 | hist_cate_37 | hist_cate_38 | hist_cate_39 | cateID |
1 rows × 42 columns
#由于本轮实验需要运行在npu上,因此需要将模型输入的向量加载到npu设备上
inputTensor = test_X[0].to(device)
inputTensor
复制代码
tensor([138, 138, 138, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138],
device='npu:0')
复制代码
#将输入给到模型,得到预测购买的概率为0.4946,该值与标签label=1,有较大差距是因为模型只迭代训练5次,可以增加训练迭代次数以后再进行测试。
model(torch.unsqueeze(inputTensor, 0))
复制代码
内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个 npu 卡(也就是 chip 0),内存占用约 141M,对内存、精度或性能优化有兴趣的可以自行尝试进行优化,这里运行过程中也有其他程序在运行,因此本实验用到的网络所需要的内存已单独框出来。
Reference [1] Zhou, Guorui , et al. "Deep Interest Network for Click-Through Rate Prediction." (2017).
评论