基于昇腾用PyTorch实现传统CTR模型WideDeep网络

简介: 本文介绍了如何在昇腾平台上使用PyTorch实现经典的WideDeep网络模型,以处理推荐系统中的点击率(CTR)预测问题。

基于昇腾用PyTorch实现传统CTR模型WideDeep网络

本文主要介绍如何在昇腾上使用pytorch对推荐系统中经典的网络模型WideDeep网络进行训练的实战讲解,使用数据集是criteo,主要内容分为以下几个模块:

  • 推荐系统概述

  • WideDeep网络创新点介绍

  • WideDeep的网络架构剖析及搭建

  • 使用criteo数据集训练WideDeep网络实战

      - criteo数据集介绍
      - 模型训练过程定义
      - 评估模型性能
      - 使用criteo训练wideWeep模型
    

推荐系统概述

推荐系统可以看作是搜索排名系统,其中输入查询是一组用户和上下文信息,输出是条目的排名列表。给出一个查询,推荐任务就是在数据库中找到相关的商品,然后根据特定的目标, 如点击或购买,对这些商品进行排名。经典的推荐系统架构图如下所示:

001.jpg

当用户访问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是偏置。特征集包含原始输入特征和转换特征。

002.jpg

WideDeep网络中的Deep部分则是一个前馈神经网络(下图右侧部分,已框出来),其输入是一个sparse的feature,该输入通过神经网络第一层后将被转化成一个低维度的embedding向量,网络训练的是这个embedding,主要是用来处理类别特征,比如item的类目、用户的性别等。

与传统的one-hot方法相比而言,使用embedding方式使用一个向量表示一个离散型的变量,其表达能力更强。此外,由于该向量的取值是作为模型参数可以自动的学习,因此具有更好的泛化性。

004.jpg

综上,Wide部分与Deep部分定义好后,wideDeep网络通过加权的方式将其合并在一起输出对数的加权和作为预测值,然后输入到一个常用的逻辑损失函数来联合训练。

003.jpg

网络中最上层输出之前其实是一个sigmoid层或者是一个linear层,就是一个简单的线性累加,英文叫做joint。网络原始论文中还列举了joint和ensemble的区别,对于ensemble模型来说,它的每一个部分是独立训练的,而joint模型当中的不同部分是联合训练的。ensemble模型当中的每一个部分的参数是互不影响的,但是对于joint模型而言,它当中的参数是同时训练的。这样带来的结果是,由于训练对于每个部分是分开的,所以每一个子模型的参数空间都很大,这样才能获得比较好的效果。而joint训练的方式则没有这个问题,我们把线性部分和深度学习的部分分开,可以互补它们之间的缺陷,从而达到更好的效果,并且也不用人为地扩大训练参数的数量。

从app推荐应用的wideDeep模型结构图可以看出,左侧是Continous特征(年龄、安装App的数量等等),右边是一个离散型的特征(比如设备信息、安装过的App等等),这些离散型的特征均会被转化为embedding向量(论文中使用的是32维,本文使用的是默认的8,),之后将连续与离散的特征向量进行concat组合送入神经网络进行学习。

005.jpg

接下来将介绍如何通过pytorch实现WideDeep网络构建,由于定义过程中需要继承torch.nn中的Module模块,同时在网络堆叠过程中也需要用到'torch.nn.functional'相关函数,因此需要将这两个库导入。

from torch import nn
import torch.nn.functional as F

整个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

使用criteo数据集训练WideDeep网络实战

# 导入torch与torch_npu库,使能模型运行在npu上
import torch
import torch_npu

'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)

NPU_CALCULATE_DEVICE is: 1

criteo 数据集介绍

criteo数据集是一个关于展示广告点击率预测的经典数据集,由广告技术公司Criteo实验室提供,被用于许多CTR比赛中。该数据集训练集庞大约为4千万行 ,测试集大约有6百万行 ,特征连续型有13个(下图中I1~I13) ,类别离散型有26个 ,。其中label表示用户是否点击,0 表示未点击,1 表示点击,本文选取了部分数据集(约324行)用作本次实验的训练与测试数据集,具体训练与测试的划分下文会详细介绍。

006.jpg

0061.jpg

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']

表格中有空缺值或者异常值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]

使用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]

划分训练集与验证集,通过'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)

定义超参数:迭代次数、送入训练的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]

定义模型训练过程

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')

主调用入口(开始使用criteo训练wideWeep模型)

构造WideDeep模型,并调用'train_model()'函数开始训练,训练完成后会调用'evaluate_model()'对模型效果进行评估。从打印10轮次迭代日志可以看出loss在逐渐下降,准确率在逐步提升,说明模型训练在逐渐收敛,训练完成后将迭代中具有最好表现的权重信息保存在save_model下的widedeep.bin,后续可以作为checkpoint文件进行预训练或者是断点续训。

007.jpg

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

内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个npu卡(也就是chip 1),前面指定device=1,内存占用约135M,对内存、精度或性能优化有兴趣的可以自行尝试进行优化,这里运行过程中也有其他程序在运行,因此本实验用到的网络所需要的内存已单独框出来。

terminal.jpg

问题汇总:

torch与torch版本不一致会出现以下问题,torch是2.2.0,torch_npu是2.1.0,需要将torch与torch_npu版本匹配一致。

008.jpg

IDE当中代码使用了parser.parse_args(),当在jupyter下调用时出错,这是因为调用parser.parse_args()会读取系统参数:sys.argv[ ],命令行调用时是正确参数,而在jupyter notebook中调用时,sys.argv的值为ipykrnel_launcher.py。

009.jpg

Reference

[1] Cheng, Heng Tze , et al. "Wide \& Deep Learning for Recommender Systems." ACM (2016).

相关文章
|
6天前
|
机器学习/深度学习 算法 PyTorch
昇腾910-PyTorch 实现 ResNet50图像分类
本实验基于PyTorch,在昇腾平台上使用ResNet50对CIFAR10数据集进行图像分类训练。内容涵盖ResNet50的网络架构、残差模块分析及训练代码详解。通过端到端的实战讲解,帮助读者理解如何在深度学习中应用ResNet50模型,并实现高效的图像分类任务。实验包括数据预处理、模型搭建、训练与测试等环节,旨在提升模型的准确率和训练效率。
100 54
|
1月前
|
机器学习/深度学习 算法 PyTorch
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
软演员-评论家算法(Soft Actor-Critic, SAC)是深度强化学习领域的重要进展,基于最大熵框架优化策略,在探索与利用之间实现动态平衡。SAC通过双Q网络设计和自适应温度参数,提升了训练稳定性和样本效率。本文详细解析了SAC的数学原理、网络架构及PyTorch实现,涵盖演员网络的动作采样与对数概率计算、评论家网络的Q值估计及其损失函数,并介绍了完整的SAC智能体实现流程。SAC在连续动作空间中表现出色,具有高样本效率和稳定的训练过程,适合实际应用场景。
138 7
深度强化学习中SAC算法:数学原理、网络架构及其PyTorch实现
|
11天前
|
机器学习/深度学习 算法 PyTorch
昇腾910-PyTorch 实现 GoogleNet图像分类
本实验基于PyTorch在昇腾平台上实现GoogleNet模型,针对CIFAR-10数据集进行图像分类。内容涵盖GoogleNet的创新点(如Inception模块、1x1卷积、全局平均池化等)、网络架构解析及代码实战分析。通过详细讲解模型搭建、数据预处理、训练与测试过程,帮助读者掌握如何使用经典CNN模型进行高效图像分类。实验中还介绍了辅助分类器、梯度传播优化等技术细节,并提供了完整的训练和测试代码示例。
|
11天前
|
机器学习/深度学习 算法 PyTorch
昇腾910-PyTorch 实现 Alexnet图像分类
本文介绍了在昇腾平台上使用PyTorch实现AlexNet对CIFAR-10数据集进行图像分类的实战。内容涵盖AlexNet的创新点、网络架构解析及代码实现,包括ReLU激活函数、Dropout、重叠最大池化等技术的应用。实验中详细展示了如何构建模型、加载数据集、定义训练和测试模块,并通过60个epoch的训练验证模型性能。
|
2月前
|
机器学习/深度学习 人工智能 PyTorch
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
本文探讨了Transformer模型中变长输入序列的优化策略,旨在解决深度学习中常见的计算效率问题。文章首先介绍了批处理变长输入的技术挑战,特别是填充方法导致的资源浪费。随后,提出了多种优化技术,包括动态填充、PyTorch NestedTensors、FlashAttention2和XFormers的memory_efficient_attention。这些技术通过减少冗余计算、优化内存管理和改进计算模式,显著提升了模型的性能。实验结果显示,使用FlashAttention2和无填充策略的组合可以将步骤时间减少至323毫秒,相比未优化版本提升了约2.5倍。
75 3
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
|
2月前
|
机器学习/深度学习 算法 PyTorch
基于Pytorch Gemotric在昇腾上实现GraphSage图神经网络
本文详细介绍了如何在昇腾平台上使用PyTorch实现GraphSage算法,在CiteSeer数据集上进行图神经网络的分类训练。内容涵盖GraphSage的创新点、算法原理、网络架构及实战代码分析,通过采样和聚合方法高效处理大规模图数据。实验结果显示,模型在CiteSeer数据集上的分类准确率达到66.5%。
|
2月前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
128 3
|
2月前
|
监控 安全 BI
什么是零信任模型?如何实施以保证网络安全?
随着数字化转型,网络边界不断变化,组织需采用新的安全方法。零信任基于“永不信任,永远验证”原则,强调无论内外部,任何用户、设备或网络都不可信任。该模型包括微分段、多因素身份验证、单点登录、最小特权原则、持续监控和审核用户活动、监控设备等核心准则,以实现强大的网络安全态势。
154 2
|
3月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
171 2
|
2月前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
78 17