deepFM模型pytorch实现

deepFM

deepfm包含两个部分:因子分解机FM和神经网络DNN,分别负责低阶特征和高阶特征的提取。可以处理全是分类特征的数据,或者分类与数值型结合的数据。
FM部分 是对一阶特征和二阶特征(一阶特征之间的交互)的处理。

DNN部分 是对高阶特征进行交叉处理。

思考🤔:为什么要做特征与特征之间的交互呢?

文献中写到男性青少年喜欢射击游戏和RPG游戏,这意味着app类别、用户性别和年龄的之间是存在相互影响的。一般来说,用户点击行为背后的这种特征交互可以是高度复杂的,其中低阶和高阶特征交互都应该起重要作用。

所以FM考虑低阶特征交互,DNN考虑高阶特征交互。

(一)数据处理

将数据的特征主要分为类别型特征和数值型特征两种,还有其他类(label、userid等)标记为igore feature。
类别类特征 :离散、稀疏的,标记为sparse feature;
数值型特征:连续、稠密的,标记为dense feature。

数据预处理代码,需要对sparse feature进行编码处理,对dense feature进行归一化化处理,对于自己的数据集,需要自行分好sparse feature和dense feature。

python 复制代码
#CATEGORICAL_COLS=['性别','职业']等类别特征名称
#NUMERIC_COLS=['时间','次数']等连续性数类特征名称
#IGNORE_COLS=['','']#不参与训练的特征名称
import pandas as pd
data=pd.read_cav("datasets.scv")
sparse_data=data[CATEGORICAL_COLS]
dense_data=train[NUMERIC_COLS]
ignore_data=train[IGNORE_COLS]

对dense fea进行编码处理

python 复制代码
from sklearn.preprocessing import LabelEncoder
from tqdm import tqdm
#训练数据集
## 类别特征labelencoder
for feat in tqdm(CATEGORICAL_COLS):
    lbe = LabelEncoder()
    sparse_data[feat] = lbe.fit_transform(sparse_data[feat])  

对sparse fea进行标准化处理

python 复制代码
for feat in tqdm(NUMERIC_COLS):  #将每一个特征下的数据进行标准化
    mean = dense_data[feat].mean()
    std = dense_data[feat].std()
    dense_data[feat] = (dense_data[feat] - mean) / (std + 1e-12)  # 防止除零

将数据转为模型可处理的tensor类型,划分训练测试集

python 复制代码
from sklearn.model_selection import train_test_split
import torch
import torch.utils.data as Data
data=pd.concat([sparse_data, dense_data,data['label']], axis=1)
train, valid = train_test_split(data, test_size=0.2, random_state=42)
train_dataset = Data.TensorDataset(torch.LongTensor(train[CATEGORICAL_COLS].values),
                                       torch.FloatTensor(train[NUMERIC_COLS].values),
                                       torch.FloatTensor(train['target'].values), )

train_loader = Data.DataLoader(dataset=train_dataset, batch_size=2048, shuffle=True)

valid_dataset = Data.TensorDataset(torch.LongTensor(valid[CATEGORICAL_COLS].values),
                                torch.FloatTensor(valid[NUMERIC_COLS].values),
                                       torch.FloatTensor(valid['target'].values), )
valid_loader = Data.DataLoader(dataset=valid_dataset, batch_size=4096, shuffle=False)

(二)模型搭建

python 复制代码
# 模型结构
import torch
import torch.nn as nn

class DeepFM(nn.Module):
    def __init__(self, cate_fea_nuniqs, nume_fea_size=0, emb_size=128,
                 hid_dims=[256, 128], num_classes=1, dropout=[0.2, 0.2]):
        """
        cate_fea_nuniqs: 类别特征的唯一值个数列表,也就是每个类别特征的vocab_size所组成的列表
        nume_fea_size: 数值特征的个数,该模型会考虑到输入全为类别型,即没有数值特征的情况
        """
        super().__init__()
        self.cate_fea_size = len(cate_fea_nuniqs) #分类特征的数量
        self.nume_fea_size = nume_fea_size

        """FM部分"""
        # 一阶 数值型的利用线性层进行计算得到一个值
        #类别型的数据 先用连续的数值表达类别,例如(s/m/l/xl)用1/2/3/4表达 分别对其embedding,得到一个值
        if self.nume_fea_size != 0:
            self.fm_1st_order_dense = nn.Linear(self.nume_fea_size, 1)  # 数值特征的一阶表示
        self.fm_1st_order_sparse_emb = nn.ModuleList([  #将类别特征分别进行embedding 有几个类别特征,就有几个embedding层
            nn.Embedding(voc_size, 1) for voc_size in cate_fea_nuniqs])  # 类别特征的一阶表示

        # 二阶
        self.fm_2nd_order_sparse_emb = nn.ModuleList([
            nn.Embedding(voc_size, emb_size) for voc_size in cate_fea_nuniqs])  # 类别特征的二阶表示  #将类别特征embedding,输出隐藏层的维度

        """DNN部分"""
        self.all_dims = [self.cate_fea_size * emb_size] + hid_dims #二阶类别特征的维度加上隐藏层
        self.dense_linear = nn.Linear(self.nume_fea_size, self.cate_fea_size * emb_size)  # 数值特征的维度变换到FM输出维度一致
        self.relu = nn.ReLU()
        # for DNN
        for i in range(1, len(self.all_dims)): #all_dims 3维数组 [self.cate_fea_size * emb_size,256,128]
            setattr(self, 'linear_' + str(i), nn.Linear(self.all_dims[i - 1], self.all_dims[i]))  
            #使用setattr函数动态创建线性层。nn.Linear是PyTorch中的线性层(全连接层),
            # 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]))
        # for output
        self.dnn_linear = nn.Linear(hid_dims[-1], num_classes)
        self.sigmoid = nn.Sigmoid()

    def forward(self, X_sparse, X_dense=None):
        """
        X_sparse: 类别型特征输入  [bs, cate_fea_size]
        X_dense: 数值型特征输入(可能没有)  [bs, dense_fea_size]
        """

        """FM 一阶部分"""
        fm_1st_sparse_res = [emb(X_sparse[:, i].unsqueeze(1)).view(-1, 1) #将一维张量转换为二维列向量
                             for i, emb in enumerate(self.fm_1st_order_sparse_emb)]  #对分类特征进行embedding
        fm_1st_sparse_res = torch.cat(fm_1st_sparse_res, dim=1)  # [bs, cate_fea_size]
        fm_1st_sparse_res = torch.sum(fm_1st_sparse_res, 1, keepdim=True)  # [bs, 1] #最后只有一个值,求和

        if X_dense is not None:
            fm_1st_dense_res = self.fm_1st_order_dense(X_dense)
            fm_1st_part = fm_1st_sparse_res + fm_1st_dense_res
        else:
            fm_1st_part = fm_1st_sparse_res  # [bs, 1]  #一阶得到一个值?

        """FM 二阶部分"""
        fm_2nd_order_res = [emb(X_sparse[:, i].unsqueeze(1)) for i, emb in enumerate(self.fm_2nd_order_sparse_emb)]
        
        #torch.cat可以沿指定的维度将两个或多个张量连接在一起。
        #dim=1 竖向堆叠 dim=0 横向拼接
        fm_2nd_concat_1d = torch.cat(fm_2nd_order_res, dim=1)  # [bs, n, emb_size]  n为类别型特征个数(cate_fea_size)

        # 先求和再平方   #求和时不同分类特征 同一维度的值相加。 
        sum_embed = torch.sum(fm_2nd_concat_1d, 1)  # [bs, emb_size]
        square_sum_embed = sum_embed * sum_embed  # [bs, emb_size]
        # 先平方再求和
        square_embed = fm_2nd_concat_1d * fm_2nd_concat_1d  # [bs, n, emb_size]
        sum_square_embed = torch.sum(square_embed, 1)  # [bs, emb_size]
        # 相减除以2
        sub = square_sum_embed - sum_square_embed
        sub = sub * 0.5  # [bs, emb_size]

        fm_2nd_part = torch.sum(sub, 1, keepdim=True)  # [bs, 1]  最后得到一个值?

        """DNN部分"""
        dnn_out = torch.flatten(fm_2nd_concat_1d, 1)  # [bs, n * emb_size]

        if X_dense is not None:
            dense_out = self.relu(self.dense_linear(X_dense))  # [bs, n * emb_size]
            dnn_out = dnn_out + dense_out  # [bs, n * emb_size]

        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)

        dnn_out = self.dnn_linear(dnn_out)  # [bs, 1]
        out = fm_1st_part + fm_2nd_part + dnn_out  # [bs, 1]
        out = self.sigmoid(out)
        return out

其中

关注公式中的后一项

只用计算xi与其他xj之间的乘积之和,所以只需要矩阵x与自身相乘,然后只需要对成矩阵除对角线外的右上角部分。

因为是对成的,所以2A+对角线上的值就等于整个矩阵的内积。

可以算出来A=(x矩阵内积-对角线的乘积)/2

同时






相关推荐
青松@FasterAI15 分钟前
【NLP高频面题 - 分布式训练篇】ZeRO主要为了解决什么问题?
人工智能·深度学习·自然语言处理·分布式训练·nlp面试
lu_rong_qq17 分钟前
【LLM】一文了解 NLP 里程碑模型 BERT
人工智能·自然语言处理·bert
CodeClimb33 分钟前
【华为OD-E卷 - 服务失效判断 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
CodeClimb35 分钟前
【华为OD-E卷 - 九宫格按键输入 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
大霸王龙1 小时前
Python中使用PostgreSQL和Apache AGE扩展来绘制和显示图表
python·postgresql·apache
几两春秋梦_1 小时前
PINN求解偏微分方程
人工智能·pytorch·python
数据小小爬虫1 小时前
淘宝商品详情API返回值说明:Python爬虫代码示例
java·爬虫·python
重剑无锋10241 小时前
【《python爬虫入门教程11--重剑无峰168》】
开发语言·爬虫·python
大学生毕业题目1 小时前
毕业项目推荐:基于yolov8/yolov5的行人检测识别系统(python+卷积神经网络)
python·yolo·cnn
蒸土豆的技术细节2 小时前
vllm源码(一)
人工智能·自然语言处理