基于昇腾用PyTorch实现传统CTR模型WideDeep网络
本文主要介绍如何在昇腾上使用pytorch对推荐系统中经典的网络模型WideDeep网络进行训练的实战讲解,使用数据集是criteo,主要内容分为以下几个模块:
推荐系统概述
WideDeep网络创新点介绍
WideDeep的网络架构剖析及搭建
使用criteo数据集训练WideDeep网络实战
- criteo数据集介绍 - 模型训练过程定义 - 评估模型性能 - 使用criteo训练wideWeep模型
AI 代码解读
推荐系统概述
推荐系统可以看作是搜索排名系统,其中输入查询是一组用户和上下文信息,输出是条目的排名列表。给出一个查询,推荐任务就是在数据库中找到相关的商品,然后根据特定的目标, 如点击或购买,对这些商品进行排名。经典的推荐系统架构图如下所示:
当用户访问app store的时候会生成一个请求Query,这个请求当中会包含用户以及上下文的特征。推荐系统会返回一系列的app,这些app都是模型筛选出来用户可能会点击或者是购买的app。当用户看到这些信息之后,会产生一些行为也就是user action,比如浏览(没有行为)、点击、购买,产生行为之后,这些数据会被记录在Logs当中,成为训练数据。
DataBase到Retrieval也就是召回操作,由于DataBase中数据非常大,通常都是上百万,若想要在规定时间内(一般是毫秒级)给所有app调用的模型都打一个分然后进行排序显然是不可能的,因此需要对请求进行召回也就是Retrieval。Retrieval操作会对用户的请求进行召回,一般召回的方式是先基于规则快速筛选,然后再用机器学习模型进行过滤。在进行筛选与过滤以后,再调用Wide&Deep模型进行CTR预估,根据预测出来的CTR对这些APP进行排序。
WideDeep网络创新点介绍
WideDeep模型是Google在2016年提出的基于TensorFlow的深度学习模型,用于处理稀疏的类别型特征和数值型特征,在推荐系统和自然语言处理等领域有广泛的应用。WideDeep模型的创新点主要包括以下几点:
结合了宽度学习(如线性模型)和深度学习(如神经网络)的优点,能够捕捉特征之间的交叉和组合,同时也能处理高阶特征,首次将神经网络引入到CTR模型。
引入了交叉元素(crossed features)的概念,这是通过将两个类别特征进行交叉来创建新特征,以此来捕捉到更高阶的特征组合。
使用了深度学习中的embedding技术,即将类别特征映射到向量空间,并在模型中进行学习。
使用了深度学习中的深度结构,如神经网络,来捕捉更复杂的特征交互。
使用了FTRL算法作为优化器,它结合了在线学习和参数更新的优点,适合大规模数据和高维度特征空间。
WideDeep的网络架构剖析及搭建
WideDeep网络是由两部分组成,一部分是Wide模块,另一部分是Deep模块。其中Wide部分是一个广义线性模型(下图中左侧部分,已框出来)即y = W\^Tx + \hat{b} ,其中,y 是需要预测的结果,x 是一个d维度的特征向量。w 是模型参数,b是偏置。特征集包含原始输入特征和转换特征。
WideDeep网络中的Deep部分则是一个前馈神经网络(下图右侧部分,已框出来),其输入是一个sparse的feature,该输入通过神经网络第一层后将被转化成一个低维度的embedding向量,网络训练的是这个embedding,主要是用来处理类别特征,比如item的类目、用户的性别等。
与传统的one-hot方法相比而言,使用embedding方式使用一个向量表示一个离散型的变量,其表达能力更强。此外,由于该向量的取值是作为模型参数可以自动的学习,因此具有更好的泛化性。
综上,Wide部分与Deep部分定义好后,wideDeep网络通过加权的方式将其合并在一起输出对数的加权和作为预测值,然后输入到一个常用的逻辑损失函数来联合训练。
网络中最上层输出之前其实是一个sigmoid层或者是一个linear层,就是一个简单的线性累加,英文叫做joint。网络原始论文中还列举了joint和ensemble的区别,对于ensemble模型来说,它的每一个部分是独立训练的,而joint模型当中的不同部分是联合训练的。ensemble模型当中的每一个部分的参数是互不影响的,但是对于joint模型而言,它当中的参数是同时训练的。这样带来的结果是,由于训练对于每个部分是分开的,所以每一个子模型的参数空间都很大,这样才能获得比较好的效果。而joint训练的方式则没有这个问题,我们把线性部分和深度学习的部分分开,可以互补它们之间的缺陷,从而达到更好的效果,并且也不用人为地扩大训练参数的数量。
从app推荐应用的wideDeep模型结构图可以看出,左侧是Continous特征(年龄、安装App的数量等等),右边是一个离散型的特征(比如设备信息、安装过的App等等),这些离散型的特征均会被转化为embedding向量(论文中使用的是32维,本文使用的是默认的8,),之后将连续与离散的特征向量进行concat组合送入神经网络进行学习。
接下来将介绍如何通过pytorch实现WideDeep网络构建,由于定义过程中需要继承torch.nn中的Module模块,同时在网络堆叠过程中也需要用到'torch.nn.functional'相关函数,因此需要将这两个库导入。
from torch import nn
import torch.nn.functional as F
AI 代码解读
整个WideDeep网络通过一个WideDeep类来实现,该类中定义了'init'与'forward'方法分别用来初始化变量与定义网络的前向传播过程。
class WideDeep(nn.Module):
def __init__(self, cate_fea_uniques,
num_fea_size=0,
emb_size=8,
hidden_dims=[256, 128],
num_classes=1,
dropout=[0.2, 0.2]):
'''
cate_fea_uniques: 分类特征,网络架构图中的Categorical Features
num_fea_size: 数字特征,也就是连续特征向量维度
emb_size: 分类特征通过emb_size后的向量大小,默认是8
hidden_dims: 定义Embedding到Out层之间隐藏层的网络节点数,也就是deep网络部分
num_classes: 类别数
dropout: 以x概率丢弃掉某些节点,减少过拟合现象,增强模型泛化能力
'''
super(WideDeep, self).__init__()
self.cate_fea_size = len(cate_fea_uniques)
self.num_fea_size = num_fea_size
self.n_layers = 3
self.n_filters = 12
self.k = emb_size
# sparse特征二阶表示,需要离散型的特征通过Embedding成向量,emb_size是向量大小。
self.sparse_emb = nn.ModuleList([
nn.Embedding(voc_size, emb_size) for voc_size in cate_fea_uniques
])
# 定义连续型特征向量全连接层
self.linear = nn.Linear(self.num_fea_size, 1)
# Deep network部分,all_dims表示全连接网络层数
self.all_dims = [self.cate_fea_size * emb_size + self.num_fea_size] + hidden_dims
# Deep network部分,根据all_dims数值构建对应层数的全连接层
for i in range(1, len(self.all_dims)):
setattr(self, 'linear_' + str(i), nn.Linear(self.all_dims[i - 1], self.all_dims[i]))
setattr(self, 'batchNorm_' + str(i), nn.BatchNorm1d(self.all_dims[i]))
setattr(self, 'activation_' + str(i), nn.ReLU())
setattr(self, 'dropout_' + str(i), nn.Dropout(dropout[i - 1]))
# Deep network部分,输出全连接层最后为1个数值,表示是否给用户进行推荐或其他二分类0或1选择任务
self.output = nn.Linear(self.all_dims[-1], 1)
self.sigmoid = nn.Sigmoid()
def forward(self, X_sparse, X_dense=None):
"""
X_sparse: sparse_feature [batch_size, sparse_feature_num]
X_dense: dense_feature [batch_size, dense_feature_num]
"""
# X_sparse算子从int64转换成int32,因为int32可以走到aicore上
X_sparse = X_sparse.to(torch.int32)
sparse_embed = [emb(X_sparse[:, i]) for i, emb in enumerate(self.sparse_emb)]
# batch_size, sparse_feature_num * emb_dim
sparse_embed = torch.cat(sparse_embed, dim=-1)
# print(sparse_embed.size()) # torch.Size([2, 208])
x = torch.cat([sparse_embed, X_dense], dim=-1)
"""Wide部分构建"""
wide_out = self.linear(X_dense)
"""DNN部分构建"""
dnn_out = x
for i in range(1, len(self.all_dims)):
dnn_out = getattr(self, 'linear_' + str(i))(dnn_out)
dnn_out = getattr(self, 'batchNorm_' + str(i))(dnn_out)
dnn_out = getattr(self, 'activation_' + str(i))(dnn_out)
dnn_out = getattr(self, 'dropout_' + str(i))(dnn_out)
deep_out = self.output(dnn_out)
out = self.sigmoid(0.5 * wide_out + 0.5 * deep_out)
return out
AI 代码解读
使用criteo数据集训练WideDeep网络实战
# 导入torch与torch_npu库,使能模型运行在npu上
import torch
import torch_npu
AI 代码解读
'os.getenv()'用来获取系统环境变量,用户可以在设置环境变量'NPU_CALCULATE_DEVICE'指定用服务器上的指定编号的Npu卡,默认使用0号device。
在多卡服务情况下,使用'torch_npu.npu.current_device()'函数用于获取当前NPU设备的索引。函数返回一个整数,表示当前选中的NPU设备编号。这对于在多NPU环境下管理和切换不同的NPU设备非常有用。与上述os.getenv()搭配使用判定与设置选中的npu设备编号。
本实验用到的'NPU_CALCULATE_DEVICE'没有在环境变量中进行设置,因此这里选中的是默认的0号npu设备,可以令NPU_CALCULATE_DEVICE = 1,表示选中1号npu设备,可以看到打印的设备信息显示选中的是1号npu设备。
import os
NPU_CALCULATE_DEVICE = 1
if os.getenv('NPU_CALCULATE_DEVICE') and str.isdigit(os.getenv('NPU_CALCULATE_DEVICE')):
NPU_CALCULATE_DEVICE = int(os.getenv('NPU_CALCULATE_DEVICE'))
if torch_npu.npu.current_device() != NPU_CALCULATE_DEVICE:
torch_npu.npu.set_device(f'npu:{NPU_CALCULATE_DEVICE}')
print("NPU_CALCULATE_DEVICE is: ", NPU_CALCULATE_DEVICE)
AI 代码解读
NPU_CALCULATE_DEVICE is: 1
criteo 数据集介绍
criteo数据集是一个关于展示广告点击率预测的经典数据集,由广告技术公司Criteo实验室提供,被用于许多CTR比赛中。该数据集训练集庞大约为4千万行 ,测试集大约有6百万行 ,特征连续型有13个(下图中I1~I13) ,类别离散型有26个 ,。其中label表示用户是否点击,0 表示未点击,1 表示点击,本文选取了部分数据集(约324行)用作本次实验的训练与测试数据集,具体训练与测试的划分下文会详细介绍。
criteo数据集预处理过程
本文实验数据集需要用到表格加载操作,因此需要导入pandas库,没有安装该库的需要通过pip install pandas方式安装好该库,此外,我们需要对每一次迭代时间进行统计,因此还需要导入系统time库用来统计和打印迭代时间
import time
import pandas as pd
# 指定数据集路径
data_path="./data/widedeepDataset/criteo_sampled_data.csv"
# 通过pandas加载数据集到data,因此表格中的数值将会变成二维矩阵的形式存储在data中
data = pd.read_csv("./data/widedeepDataset/criteo_sampled_data.csv")
# 打印数据集的shape可以看到,总共324个样例,每个样例包含一个label,13个连续型特征、26个离散型特征,因此总数是1+13+26=40
print(data.shape)
# (324, 40)
# 数据预处理,读取表格中连续型I列与离散型C列的列标记,存在dense_features于sparse_features中,便于表格读取索引与查找
dense_features = [f for f in data.columns.tolist() if f[0] == "I"]
sparse_features = [f for f in data.columns.tolist() if f[0] == "C"]
print("dense_features is: ", dense_features)
print("sparse_features is: ", sparse_features)
# dense_features is: ['I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9', 'I10', 'I11', 'I12', 'I13']
# sparse_features is: ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10', 'C11', 'C12', 'C13', 'C14', 'C15', 'C16', 'C17', 'C18', 'C19', 'C20', 'C21', 'C22', 'C23', 'C24', 'C25', 'C26']
AI 代码解读
表格中有空缺值或者异常值NAN需要进行处理, fillna函数最简单的用法就是将缺失值填充为指定值,通过将一个标量传递给fillna函数,可以将所有的缺失值替换为这个标量。
data[sparse_features] = data[sparse_features].fillna('-10086',)
data[dense_features] = data[dense_features].fillna(0,)
# 打印稀疏的取值可以看到其中已经将全部异常处替换成了-10086
print(data[sparse_features])
C1 C2 C3 C4 C5 C6 C7 \
0 68fd1e64 80e26c9b fb936136 7b4723c4 25c83c98 7e0ccccf de7995b8
1 68fd1e64 f0cf0024 6f67f7e5 41274cd7 25c83c98 fe6b92e5 922afcc0
2 287e684f 0a519c5c 02cf9876 c18be181 25c83c98 7e0ccccf c78204a1
3 68fd1e64 2c16a946 a9a87e68 2e17d6f6 25c83c98 fe6b92e5 2e8a689b
4 8cf07265 ae46a29d c81688bb f922efad 25c83c98 13718bbd ad9fa255
.. ... ... ... ... ... ... ...
319 05db9164 38d50e09 c86b2d8d 657dc3b9 384874ce 7e0ccccf 7dab17c2
320 05db9164 80e26c9b 4d88f82c 169ffff5 43b19349 -10086 197b4575
321 05db9164 68aede49 4c78bddc 792bc2c0 25c83c98 fbad5c96 841f6a9d
322 8cf07265 c5c1d6ae c7a36fba 94fb8c54 25c83c98 13718bbd 3b93bd7b
323 05db9164 80e26c9b 5c7d8ff6 902872c9 384874ce 7e0ccccf 92ce5a7d
C8 C9 C10 ... C17 C18 C19 \
0 1f89b562 a73ee510 a8cd5504 ... e5ba7672 f54016b9 21ddcdc9
1 0b153874 a73ee510 2b53e5fb ... 07c540c4 b04e4670 21ddcdc9
2 0b153874 a73ee510 3b08e48b ... 8efede7f 3412118d -10086
3 0b153874 a73ee510 efea433b ... 1e88c74f 74ef3502 -10086
4 0b153874 a73ee510 5282c137 ... 1e88c74f 26b3c7a7 -10086
.. ... ... ... ... ... ... ...
319 0b153874 a73ee510 f6f942d1 ... e5ba7672 fffe2a63 21ddcdc9
320 0b153874 a73ee510 6c47047a ... d4bb7bd8 1f9656b8 21ddcdc9
321 0b153874 a73ee510 3b08e48b ... 776ce399 262c8681 -10086
322 0b153874 a73ee510 36bde1a1 ... e5ba7672 836a67dd 21ddcdc9
323 0b153874 a73ee510 d1a1d478 ... 8efede7f f54016b9 21ddcdc9
C20 C21 C22 C23 C24 C25 C26
0 b1252a9d 07b5194c -10086 3a171ecb c5c50484 e8b83407 9727dd16
1 5840adea 60f6221e -10086 3a171ecb 43f13e8b e8b83407 731c3655
2 -10086 e587c466 ad3062eb 3a171ecb 3b183c5c -10086 -10086
3 -10086 6b3a5ca6 -10086 3a171ecb 9117a34a -10086 -10086
4 -10086 21c9516a -10086 32c7478e b34f3128 -10086 -10086
.. ... ... ... ... ... ... ...
319 b1252a9d eb0fc6f8 c9d4222a 3a171ecb df487a73 001f3601 c27f155b
320 b1252a9d 602ce342 -10086 3a171ecb 1793a828 e8b83407 70b6702c
321 -10086 eabe9cb9 -10086 32c7478e 1370c56e -10086 -10086
322 5840adea ba1b0dbb -10086 423fab69 916f4113 7a402766 6527ade9
323 b1252a9d 4063500f -10086 3a171ecb 54baf4d1 e8b83407 98f9ccac
[324 rows x 26 columns]
# 打印连续特征取值可以看到其中已经将全部异常处替换成了0.0
print(data[dense_features])
I1 I2 I3 I4 I5 I6 I7 I8 I9 I10 I11 I12 \
0 1.0 1 5.0 0.0 1382.0 4.0 15.0 2.0 181.0 1.0 2.0 0.0
1 2.0 0 44.0 1.0 102.0 8.0 2.0 2.0 4.0 1.0 1.0 0.0
2 2.0 0 1.0 14.0 767.0 89.0 4.0 2.0 245.0 1.0 3.0 3.0
3 0.0 893 0.0 0.0 4392.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 3.0 -1 0.0 0.0 2.0 0.0 3.0 0.0 0.0 1.0 1.0 0.0
.. ... ... ... ... ... ... ... ... ... ... ... ...
319 0.0 2 1.0 2.0 1441.0 26.0 4.0 20.0 32.0 0.0 2.0 0.0
320 0.0 62 18.0 6.0 90.0 0.0 0.0 6.0 6.0 0.0 0.0 0.0
321 0.0 -1 0.0 0.0 7954.0 0.0 0.0 3.0 16.0 0.0 0.0 0.0
322 6.0 2 17.0 13.0 18.0 12.0 6.0 13.0 13.0 1.0 1.0 0.0
323 3.0 -1 0.0 0.0 666.0 4.0 14.0 0.0 301.0 1.0 6.0 3.0
I13
0 2.0
1 4.0
2 45.0
3 0.0
4 0.0
.. ...
319 2.0
320 6.0
321 0.0
322 12.0
323 0.0
[324 rows x 13 columns]
AI 代码解读
使用sklearn库中的LabelEncoder将数据集中的label规范化标签,这里如果没有安装sklearn库,需要通过pip install scikit-learn安装。
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder
# 将类别数据转为数字
for feat in tqdm(sparse_features):
lbe = LabelEncoder()
data[feat] = lbe.fit_transform(data[feat])
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 26/26 [00:00<00:00, 1567.60it/s]
# 将连续特征值归一化
for feat in tqdm(dense_features):
mean = data[feat].mean()
std = data[feat].std()
data[feat] = (data[feat] - mean) / (std + 1e-12)
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 13/13 [00:00<00:00, 1122.60it/s]
AI 代码解读
划分训练集与验证集,通过'sklearn.model_selection'函数中'train_test_split()'函数对数据集data进行训练与验证集划分,可以看到训练集与验证集分别为291与33(约 9 :1)。
from sklearn.model_selection import train_test_split
train, valid = train_test_split(data, test_size=0.1, random_state=42)
print("train is: ", train.shape)
print("test is: ", valid.shape)
train is: (291, 40)
test is: (33, 40)
AI 代码解读
定义超参数:迭代次数、送入训练的batch_size、验证的batch_size、学习率、weight_decay、n_gpu主要是用来分布式训练统计设备npu卡数、graph_mode表示训练过程中是否使用图模型进行训练。
Epochs=10
train_batch_size=2
eval_batch_size=2
learning_rate=0.005
weight_decay=0.001
n_gpu=0
graph_mode=False
from torch.utils.data import DataLoader, TensorDataset
# 数据集打包操作,将划分好的训练集与验证集通过DataLoader与TensorDataset方法打包成批训练格式便于训练过程中迭代进行训练与验证
train_dataset = TensorDataset(torch.LongTensor(train[sparse_features].values),
torch.FloatTensor(train[dense_features].values),
torch.FloatTensor(train['label'].values))
train_loader = DataLoader(dataset=train_dataset, batch_size=train_batch_size, shuffle=True, drop_last=True,
pin_memory=True)
valid_dataset = TensorDataset(torch.LongTensor(valid[sparse_features].values),
torch.FloatTensor(valid[dense_features].values),
torch.FloatTensor(valid['label'].values))
valid_loader = DataLoader(dataset=valid_dataset, batch_size=eval_batch_size, shuffle=False)
# 稀疏特征,nunique函数返回不同值的个数也就是统计每一个稀疏维度的不同取值个数
cat_fea_unique = [data[f].nunique() for f in sparse_features]
# 打印cat_fea_unique可以看到264个sparse特征不同元素取值个数情况均已统计出来
print(cat_fea_unique)
[28, 111, 230, 199, 14, 7, 265, 22, 2, 196, 240, 226, 223, 12, 226, 220, 9, 171, 57, 4, 223, 6, 10, 158, 19, 115]
AI 代码解读
定义模型训练过程
from sklearn.metrics import roc_auc_score
# 评估模型性能,每迭代一轮后,对模型性能与效果进行评估
def evaluate_model(model):
model.eval()
with torch.no_grad():
valid_labels, valid_preds = [], []
for step, x in tqdm(enumerate(valid_loader)):
cat_fea, num_fea, label = x[0], x[1], x[2]
if torch.npu.is_available():
cat_fea, num_fea, label = cat_fea.npu(), num_fea.npu(), label.npu()
logits = model(cat_fea, num_fea)
logits = logits.view(-1).data.cpu().numpy().tolist()
valid_preds.extend(logits)
valid_labels.extend(label.cpu().numpy().tolist())
cur_auc = roc_auc_score(valid_labels, valid_preds)
return cur_auc
from torch import optim
def train_model(model):
# 指定npu运行需要判断是否安装好torch_npu
if torch.npu.is_available():
model.npu()
# 定义损失函数BCE,二元交叉熵损失函数(Binary Cross Entropy Loss)的简写。它的输入是经过 Sigmoid 函数激活后的预测概率和对应的真实标签(一般使用one-hot表示)
loss_fct = nn.BCELoss()
# 定义优化器,定义学习率参数,weight_decay是一种常用的正则化技术,主要用于防止模型过拟合,通过在损失函数中添加一个惩罚项来限制模型的复杂度。
optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
#定义学习率调度器用来动态的调整学习率,StepLR表示在每个阶段结束时降低学习率,没经过step_size将学习率乘以gamma
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=train_batch_size, gamma=0.8)
# 用于统计模型训练过程中最好参数情况下最好的预测准确率便于权重保存
best_auc = 0.0
# 开始训练迭代
for epoch in range(Epochs):
model.train()
train_loss_sum = 0.0
start_time = time.time()
for step, x in enumerate(train_loader):
#图模式
if graph_mode:
print("graph mode on")
torch.npu.enable_graph_mode()
# 获取训练集输入及标签
cat_fea, num_fea, label = x[0], x[1], x[2]
if torch.npu.is_available():
cat_fea, num_fea, label = cat_fea.npu(non_blocking=True), num_fea.npu(non_blocking=True), label.npu(non_blocking=True)
# 将输入传递给模型进行前向传播
pred = model(cat_fea, num_fea)
# 计算loss
pred = pred.view(-1)
loss = loss_fct(pred, label)
optimizer.zero_grad()
# 根据loss进行反向传播
loss.backward()
optimizer.step()
#图模式下loss计算
if graph_mode:
torch.npu.launch_graph()
if step == len(train_loader):
torch.npu.synchronize()
else:
#非图模式下loss计算
train_loss_sum += loss
# 打印迭代的loss与时间每隔50次迭及将最后一次损失打印
if (step + 1) % 50 == 0 or (step + 1) == len(train_loader):
print("Epoch {:04d} | Step {:04d} / {} | Loss {:.4f} | Time {:.4f}".format(
epoch+1, step+1, len(train_loader), train_loss_sum/(step+1), time.time() - start_time))
# 重新计时方便计算相邻迭代间训练迭代用时
start_time = time.time()
#图模式
if graph_mode:
print("graph mode off")
torch.npu.disable_graph_mode()
scheduler.step()
cur_auc = evaluate_model(model)
print("cur_auc", cur_auc)
if cur_auc > best_auc:
best_auc = cur_auc
os.makedirs('./save_model', exist_ok=True)
torch.save(model.state_dict(), './save_model/widedeep.bin')
AI 代码解读
主调用入口(开始使用criteo训练wideWeep模型)
构造WideDeep模型,并调用'train_model()'函数开始训练,训练完成后会调用'evaluate_model()'对模型效果进行评估。从打印10轮次迭代日志可以看出loss在逐渐下降,准确率在逐步提升,说明模型训练在逐渐收敛,训练完成后将迭代中具有最好表现的权重信息保存在save_model下的widedeep.bin,后续可以作为checkpoint文件进行预训练或者是断点续训。
model = WideDeep(cat_fea_unique, num_fea_size=len(dense_features))
train_model(model)
Epoch 0001 | Step 0050 / 145 | Loss 0.5662 | Time 0.7970
Epoch 0001 | Step 0100 / 145 | Loss 0.4876 | Time 0.7937
Epoch 0001 | Step 0145 / 145 | Loss 0.5027 | Time 0.7139
17it [00:00, 341.69it/s]
cur_auc 0.6304347826086957
Epoch 0002 | Step 0050 / 145 | Loss 0.5267 | Time 0.8777
Epoch 0002 | Step 0100 / 145 | Loss 0.5180 | Time 0.9393
Epoch 0002 | Step 0145 / 145 | Loss 0.4869 | Time 0.8331
17it [00:00, 300.42it/s]
cur_auc 0.7
Epoch 0003 | Step 0050 / 145 | Loss 0.5158 | Time 0.9160
Epoch 0003 | Step 0100 / 145 | Loss 0.4661 | Time 1.0015
Epoch 0003 | Step 0145 / 145 | Loss 0.4600 | Time 0.8986
17it [00:00, 331.88it/s]
cur_auc 0.7608695652173914
Epoch 0004 | Step 0050 / 145 | Loss 0.4149 | Time 0.9968
Epoch 0004 | Step 0100 / 145 | Loss 0.4631 | Time 1.1071
Epoch 0004 | Step 0145 / 145 | Loss 0.4706 | Time 0.8446
17it [00:00, 320.08it/s]
cur_auc 0.7565217391304349
Epoch 0005 | Step 0050 / 145 | Loss 0.4631 | Time 1.0572
Epoch 0005 | Step 0100 / 145 | Loss 0.4493 | Time 1.1320
Epoch 0005 | Step 0145 / 145 | Loss 0.4603 | Time 0.9850
17it [00:00, 316.79it/s]
cur_auc 0.7391304347826086
Epoch 0006 | Step 0050 / 145 | Loss 0.4753 | Time 1.0911
Epoch 0006 | Step 0100 / 145 | Loss 0.4594 | Time 0.9665
Epoch 0006 | Step 0145 / 145 | Loss 0.4581 | Time 0.8289
17it [00:00, 346.39it/s]
cur_auc 0.7956521739130435
Epoch 0007 | Step 0050 / 145 | Loss 0.5164 | Time 0.9187
Epoch 0007 | Step 0100 / 145 | Loss 0.5095 | Time 0.9308
Epoch 0007 | Step 0145 / 145 | Loss 0.4688 | Time 0.8930
17it [00:00, 329.69it/s]
cur_auc 0.7
Epoch 0008 | Step 0050 / 145 | Loss 0.4554 | Time 0.9604
Epoch 0008 | Step 0100 / 145 | Loss 0.4554 | Time 0.9272
Epoch 0008 | Step 0145 / 145 | Loss 0.4470 | Time 0.8281
17it [00:00, 336.85it/s]
cur_auc 0.791304347826087
Epoch 0009 | Step 0050 / 145 | Loss 0.3706 | Time 0.9476
Epoch 0009 | Step 0100 / 145 | Loss 0.4304 | Time 0.9538
Epoch 0009 | Step 0145 / 145 | Loss 0.4617 | Time 0.8337
17it [00:00, 333.07it/s]
cur_auc 0.7478260869565218
Epoch 0010 | Step 0050 / 145 | Loss 0.5268 | Time 0.9196
Epoch 0010 | Step 0100 / 145 | Loss 0.4803 | Time 0.9228
Epoch 0010 | Step 0145 / 145 | Loss 0.4691 | Time 0.8248
17it [00:00, 338.84it/s]
cur_auc 0.791304347826087
AI 代码解读
内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个npu卡(也就是chip 1),前面指定device=1,内存占用约135M,对内存、精度或性能优化有兴趣的可以自行尝试进行优化,这里运行过程中也有其他程序在运行,因此本实验用到的网络所需要的内存已单独框出来。
问题汇总:
torch与torch版本不一致会出现以下问题,torch是2.2.0,torch_npu是2.1.0,需要将torch与torch_npu版本匹配一致。
IDE当中代码使用了parser.parse_args(),当在jupyter下调用时出错,这是因为调用parser.parse_args()会读取系统参数:sys.argv[ ],命令行调用时是正确参数,而在jupyter notebook中调用时,sys.argv的值为ipykrnel_launcher.py。
Reference
[1] Cheng, Heng Tze , et al. "Wide \& Deep Learning for Recommender Systems." ACM (2016).