深度学习推荐系统(六)DeepFM模型及其在Criteo数据集上的应用

news2024/11/23 7:30:20

深度学习推荐系统(六)DeepFM模型及其在Criteo数据集上的应用

在2016年, 随着微软的Deep Crossing, 谷歌的Wide&Deep以及FNN、PNN等一大批优秀的深度学习模型被提出, 推荐系统全面进入了深度学习时代, 时至今日, 依然是主流。 推荐模型主要有下面两个进展:

  • 与传统的机器学习模型相比, 深度学习模型的表达能力更强, 能够挖掘更多数据中隐藏的模式

  • 深度学习模型结构非常灵活, 能够根据业务场景和数据特点, 灵活调整模型结构, 使模型与应用场景完美契合

深度学习推荐模型,以多层感知机(MLP)为核心, 通过改变神经网络结构进行演化。

在这里插入图片描述

在进入深度学习时代后,FM的演化过程并没有停止,FNN、DeepFM 及 NFM模型,使用不同的方式应用或改进了FM模型,并融合进深度学习模型中,持续发挥着其在特征组合上的优势。

1 FNN模型原理

用一句话来说明FNN的主要思想:用FM的隐向量完成Embedding层初始化

1.1 FNN的网络架构及原理

  • 神经网络的参数往往采用随机初始化这种不包含任何先验信息的初始化方法。由于 Embedding 层的输入极端稀疏化,导致 Embedding 层的收敛速度非常缓慢,再加上Embedding层的参数数量往往占整个神经网络参数数量的大半以上,因此模型的收敛速度往往受限于Embedding 层。

  • FNN模型架构类似于Deep Crossing模型的经典深度神经网络

    • 从稀疏输入向量到稠密向量的转换依然是embedding结构,但是针对Embedding层收敛速度慢的问题,FNN模型用FM模型替换了下面的Embedding层
    • 并且在模型的正式训练之前,先提前训练好FM,然后用FM训练好的特征隐向量对正式训练的模型进行Embedding层的初始化操作。这样在正式训练的时候,就加速了模型的收敛过程。
    • 也就是说,神经网络训练的起点更接近目标最优点,自然加速了整个神经网络的收敛过程。
  • 所以FNN采用了两阶段训练方式,这种两阶段方式的应用,是为了将FM作为先验知识加入到模型中,防止因为数据稀疏带来的歧义造成模型参数偏差。这样既可以加速模型收敛,也可以充分利用FM的特征表达。

在这里插入图片描述

1.2 利用FM初始化Embedding层的过程

  • 虽然在下图中,把FM中的参数指向了 Embedding层各神经元,但其具体意义是初始化Embedding神经元与输入神经元之间的连接权重。

    在这里插入图片描述

  • 在训练 FM的过程中,并没有对特征域进行区分,但在 FNN模型中,特征被分成了不同特征域,因此每个特征域具有对应的 Embedding层,并且每个特征域Embedding的维度都应与FM隐向量维度保持一致

在这里插入图片描述

1.3 FNN模型的缺点

  • FNN模型能力受限于FM表征能力的上限

  • FNN模型只关注于高阶组合特征的交叉

  • 两阶段训练方式也存在问题

    • FM中特征组合,使用的隐向量的点积运算。将FM得到的隐向量移植到DNN中作为DNN的输入, 全连接层这时候会将输入向量的所有元素加权求和,且不会对Field进行区分。 这个其实又回到了之前说Deep Crossing时的问题,全连接隐层把所有特征进行了统一的交叉学习, 这样可能会使的即使没有关系的特征在这种情况下也会发生点关系。

    • 这种两阶段的训练方式给神经网络的调参带来了难题, 因为FNN的底层参数是预训练得到的, 所以在反向传播更新参数的时候,不能学习率过大,否则会将FM得到的信息给抹去。所以底层的学习率要小一些。

2 DeepFM模型原理

  • 简单的线性模型不能充分挖掘数据中的隐含信息,且忽略了特征间的交互,如果想交互,需要复杂的特征工程。

  • FM模型考虑了特征的二阶交叉,但是这种交叉仅停留在了二阶层次。

  • DNN,适合天然的高阶交叉信息的学习,但是低阶的交叉会忽略掉。

  • Wide&Deep模型把简单的LR模型和DNN模型进行了组合, 使得模型既能够学习高阶组合特征,又能够学习低阶的特征模式。但是W&D的wide部分是用了LR模型, 这一块依然是需要一些经验性的特征工程的,且Wide部分和Deep部分需要两种不同的输入模式, 这个在具体实际应用中需要很强的业务经验。

  • FNN把FM的训练结果作为初始化权重,并没有对神经网络的结构进行调整。

  • 2017年由哈尔滨工业大学和华为联合提出的DeepFM模型,则将FM的模型结构与Wide&Deep 模型进行了整合,用FM替代Wide部分。模型也是两部分组成, 左边的FM+右边的DNN,FM能够考虑低阶交叉, 而DNN能够学习高阶交叉,另外FM作为wide部分也具备了自动特征组合能力,所以说DeepFM模型是一个经典的模型。

2.1 DeepFM模型的网络架构及原理

  • DeepFM 对 Wide&Deep 模型的改进之处在于,它用FM替换了原来的 Wide 部分,加强了浅层网络部分特征组合的能力

  • 如图所示,左边的FM部分与右边的深度神经网络部分共享相同的 Embedding 层。左侧的FM部分对不同的特征域的Embedding进行了两两交叉,也就是将Embedding向量当作原 FM 中的特征隐向量。

    • FM模型负责特征之间的低阶交互过程,FM的输出是Addition单元和Inner Product units的加和, Addition单元反映1阶特征各自的影响, 而Inner product代表2阶特征交互的影响。
    • 与FNN不同, FM这里的隐向量参数是直接和神经网络的参数一样,都是当做学习参数一块学习的,这样就省去了FM的预训练过程,而是以端到端方式训练整个网络。
    • 与Wide&Deep不同的是,DeepFM中的Wide部分与Deep部分共享了输入特征,即Embedding向量。
  • 最后将 FM 的输出与 Deep 部分的输出一同输入最后的输出层,参与最后的目标拟合。

在这里插入图片描述

2.2 DeepFM模型的优点

  • FNN模型: 预训练的方式增加了开销,模型能力受限于FM表征能力的上限,且只考虑了高阶交互

  • PNN模型:IPNN的内积计算非常复杂, OPNN的外积近似计算损失了很多信息,结果不稳定, 且同样忽视了低阶交互

  • W&D模型:虽然是考虑到了低阶和高阶交互,兼顾了模型的泛化和记忆,但是Wide部分输入需要专业的特征工程经验

  • DeepFM同时考虑了上面的这些问题, 用FM换掉了W&D的LR,并Wide部分和Deep部分通过低阶和高阶特征交互来影响特征表示,从而更精确地对特征表示进行建模的策略共享了特征Embedding。

在这里插入图片描述

2.3 DeepFM模型的代码复现

2.3.1 FM的公式推导

其实,DeepFM代码复现,主要在于FM公式在pytorch中的实现

在这里插入图片描述

化简后FM的公式如下:

在这里插入图片描述

我们定义三个矩阵,

一个是全局偏置,w0的shape为(1,)

一个是一阶权重矩阵,w1的shape为(总特征数, 1)

一个是二阶交叉矩阵,w2的shape为(总特征数, embedding的维度)

那么,对于1阶交叉,代码如下

# 一阶交叉
# inputs的shape为(批次大小,总特征数)
first_order = w0 + torch.mm(inputs, w1)      # 输出的shape为 (批次大小, 1)

对于2阶交叉,代码如下

# 二阶交叉  这个用最终的化简公式进行计算
# inputs为公式中X_i,w2为公式中v_if
second_order = 1/2 * torch.sum(
            torch.pow(torch.mm(inputs, w2), 2) - torch.mm(torch.pow(inputs,2), torch.pow(w2, 2)),
            dim = 1,
            keepdim = True  # 求和保持纬度不变
)    

2.3.2 DeepFM代码复现

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


class FM(nn.Module):
    """FM part"""
    def __init__(self, latent_dim, fea_num):
        """
        latent_dim: 各个离散特征隐向量的维度
        fea_num: 这个最后离散特征embedding之后的拼接和dense拼接的总特征个数
        """
        super(FM, self).__init__()

        self.latent_dim = latent_dim
        # 定义三个矩阵, 一个是全局偏置,一个是一阶权重矩阵, 一个是二阶交叉矩阵
        # 注意这里的参数由于是可学习参数,需要用nn.Parameter进行定义
        self.w0 = nn.Parameter(torch.zeros([1, ]))
        self.w1 = nn.Parameter(torch.rand([fea_num, 1]))
        self.w2 = nn.Parameter(torch.rand([fea_num, latent_dim]))

    def forward(self, inputs):
        # 一阶交叉
        first_order = self.w0 + torch.mm(inputs, self.w1)  # (samples_num, 1)
        # 二阶交叉  这个用FM的最终化简公式
        second_order = 1 / 2 * torch.sum(
            torch.pow(torch.mm(inputs, self.w2), 2) - torch.mm(torch.pow(inputs, 2), torch.pow(self.w2, 2)),
            dim=1,
            keepdim=True
        )  # (samples_num, 1)

        return first_order + second_order



class Dnn(nn.Module):
    """
    Dnn part
    """

    def __init__(self, hidden_units, dropout=0.):
        """
        hidden_units: 列表, 每个元素表示每一层的神经单元个数, 比如[256, 128, 64], 两层网络, 第一层神经单元128, 第二层64, 第一个维度是输入维度
        dropout: 失活率
        """
        super(Dnn, self).__init__()

        self.dnn_network = nn.ModuleList(
            [nn.Linear(layer[0], layer[1]) for layer in list(zip(hidden_units[:-1], hidden_units[1:]))])

        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x):
        for linear in self.dnn_network:
            x = linear(x)
            x = F.relu(x)

        x = self.dropout(x)
        return x


'''
WideDeep模型:
   主要包括Wide部分和Deep部分
'''
class DeepFM(nn.Module):

    def __init__(self, feature_info, hidden_units, embed_dim=8):
        """
               DeepCrossing:
                   feature_info: 特征信息(数值特征, 类别特征, 类别特征embedding映射)
                   hidden_units: 列表, 隐藏单元
                   dropout: Dropout层的失活比例
                   embed_dim: embedding维度
               """
        super(DeepFM, self).__init__()

        self.dense_features, self.sparse_features, self.sparse_features_map = feature_info

        # embedding层, 这里需要一个列表的形式, 因为每个类别特征都需要embedding
        self.embed_layers = nn.ModuleDict(
            {
                'embed_' + str(key): nn.Embedding(num_embeddings=val, embedding_dim=embed_dim)
                for key, val in self.sparse_features_map.items()
            }
        )

        # 统计embedding_dim的总维度
        # 一个离散型(类别型)变量 通过embedding层变为10纬
        embed_dim_sum = sum([embed_dim] * len(self.sparse_features))
        # 总维度 = 数值型特征的纬度 + 离散型变量经过embedding后的纬度
        dim_sum = len(self.dense_features) + embed_dim_sum
        hidden_units.insert(0, dim_sum)

        # dnn网络
        self.dnn_network = Dnn(hidden_units)

        # dnn的线性层
        self.dnn_final_linear = nn.Linear(hidden_units[-1], 1)

        # fm
        self.fm = FM(embed_dim, dim_sum)



    def forward(self, x):
        # 1、先把输入向量x分成两部分处理、因为数值型和类别型的处理方式不一样
        dense_input, sparse_inputs = x[:, :len(self.dense_features)], x[:, len(self.dense_features):]
        # 2、转换为long形
        sparse_inputs = sparse_inputs.long()

        # 2、不同的类别特征分别embedding
        sparse_embeds = [
            self.embed_layers['embed_' + key](sparse_inputs[:, i]) for key, i in
            zip(self.sparse_features_map.keys(), range(sparse_inputs.shape[1]))
        ]
        # 3、把类别型特征进行拼接,即emdedding后,由3行转换为1行
        sparse_embeds = torch.cat(sparse_embeds, axis=-1)
        # 4、数值型和类别型特征进行拼接
        x = torch.cat([sparse_embeds, dense_input], axis=-1)

        # FM部分
        wide_out = self.fm(x)

        # Deep部分,使用全部特征
        deep_out = self.dnn_final_linear(self.dnn_network(x))
        
        # out  将FM部分的输出和Deep部分的输出进行合并
        outputs = torch.sigmoid(torch.add(wide_out, deep_out))

        return outputs

if __name__ == '__main__':
    x = torch.rand(size=(1, 5), dtype=torch.float32)
    feature_info = [
        ['I1', 'I2'],  # 连续性特征
        ['C1', 'C2', 'C3'],  # 离散型特征
        {
            'C1': 20,
            'C2': 20,
            'C3': 20
        }
    ]
    # 建立模型
    hidden_units = [128, 64, 32]

    net = DeepFM(feature_info, hidden_units)
    print(net)
    print(net(x))
DeepFM(
  (embed_layers): ModuleDict(
    (embed_C1): Embedding(20, 8)
    (embed_C2): Embedding(20, 8)
    (embed_C3): Embedding(20, 8)
  )
  (dnn_network): Dnn(
    (dnn_network): ModuleList(
      (0): Linear(in_features=26, out_features=128, bias=True)
      (1): Linear(in_features=128, out_features=64, bias=True)
      (2): Linear(in_features=64, out_features=32, bias=True)
    )
    (dropout): Dropout(p=0.0, inplace=False)
  )
  (dnn_final_linear): Linear(in_features=32, out_features=1, bias=True)
  (fm): FM()
)
tensor([[1.]], grad_fn=<SigmoidBackward0>)

3 DeepFM模型在Criteo数据集的应用

数据的预处理可以参考

深度学习推荐系统(二)Deep Crossing及其在Criteo数据集上的应用_undo_try的博客-CSDN博客

3.1 准备训练数据

import pandas as pd

import torch
from torch.utils.data import TensorDataset, Dataset, DataLoader

import torch.nn as nn
from sklearn.metrics import auc, roc_auc_score, roc_curve

import warnings
warnings.filterwarnings('ignore')
# 封装为函数
def prepared_data(file_path):
    # 读入训练集,验证集和测试集
    train_set = pd.read_csv(file_path + 'train_set.csv')
    val_set = pd.read_csv(file_path + 'val_set.csv')
    test_set = pd.read_csv(file_path + 'test.csv')

    # 这里需要把特征分成数值型和离散型
    # 因为后面的模型里面离散型的特征需要embedding, 而数值型的特征直接进入了stacking层, 处理方式会不一样
    data_df = pd.concat((train_set, val_set, test_set))

    # 数值型特征直接放入stacking层
    dense_features = ['I' + str(i) for i in range(1, 14)]
    # 离散型特征需要需要进行embedding处理
    sparse_features = ['C' + str(i) for i in range(1, 27)]

    # 定义一个稀疏特征的embedding映射, 字典{key: value},
    # key表示每个稀疏特征, value表示数据集data_df对应列的不同取值个数, 作为embedding输入维度
    sparse_feas_map = {}
    for key in sparse_features:
        sparse_feas_map[key] = data_df[key].nunique()


    feature_info = [dense_features, sparse_features, sparse_feas_map]  # 这里把特征信息进行封装, 建立模型的时候作为参数传入

    # 把数据构建成数据管道
    dl_train_dataset = TensorDataset(
        # 特征信息
        torch.tensor(train_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(train_set['Label'].values).float()
    )

    dl_val_dataset = TensorDataset(
        # 特征信息
        torch.tensor(val_set.drop(columns='Label').values).float(),
        # 标签信息
        torch.tensor(val_set['Label'].values).float()
    )
    dl_train = DataLoader(dl_train_dataset, shuffle=True, batch_size=16)
    dl_vaild = DataLoader(dl_val_dataset, shuffle=True, batch_size=16)
    return feature_info,dl_train,dl_vaild,test_set
file_path = './preprocessed_data/'

feature_info,dl_train,dl_vaild,test_set = prepared_data(file_path)

3.2 建立DeepFM模型

from _01_deep_fm import DeepFM

hidden_units = [128, 64, 32]
net = DeepFM(feature_info, hidden_units)
# 测试一下模型
for feature, label in iter(dl_train):
    out = net(feature)
    print(feature.shape)
    print(out.shape)
    print(out)
    break

3.3 模型的训练

from AnimatorClass import Animator
from TimerClass import Timer


# 模型的相关设置
def metric_func(y_pred, y_true):
    pred = y_pred.data
    y = y_true.data
    return roc_auc_score(y, pred)


def try_gpu(i=0):
    if torch.cuda.device_count() >= i + 1:
        return torch.device(f'cuda:{i}')
    return torch.device('cpu')


def train_ch(net, dl_train, dl_vaild, num_epochs, lr, device):
    """⽤GPU训练模型"""
    print('training on', device)
    net.to(device)
    # 二值交叉熵损失
    loss_func = nn.BCELoss()
    optimizer = torch.optim.Adam(params=net.parameters(), lr=lr)

    animator = Animator(xlabel='epoch', xlim=[1, num_epochs],legend=['train loss', 'train auc', 'val loss', 'val auc']
                        ,figsize=(8.0, 6.0))
    timer, num_batches = Timer(), len(dl_train)
    log_step_freq = 10

    for epoch in range(1, num_epochs + 1):
        # 训练阶段
        net.train()
        loss_sum = 0.0
        metric_sum = 0.0

        for step, (features, labels) in enumerate(dl_train, 1):
            timer.start()
            # 梯度清零
            optimizer.zero_grad()

            # 正向传播
            predictions = net(features)
            loss = loss_func(predictions, labels.unsqueeze(1) )
            try:          # 这里就是如果当前批次里面的y只有一个类别, 跳过去
                metric = metric_func(predictions, labels)
            except ValueError:
                pass

            # 反向传播求梯度
            loss.backward()
            optimizer.step()
            timer.stop()

            # 打印batch级别日志
            loss_sum += loss.item()
            metric_sum += metric.item()

            if step % log_step_freq == 0:
                animator.add(epoch + step / num_batches,(loss_sum/step, metric_sum/step, None, None))

        # 验证阶段
        net.eval()
        val_loss_sum = 0.0
        val_metric_sum = 0.0


        for val_step, (features, labels) in enumerate(dl_vaild, 1):
            with torch.no_grad():
                predictions = net(features)
                val_loss = loss_func(predictions, labels.unsqueeze(1))
                try:
                    val_metric = metric_func(predictions, labels)
                except ValueError:
                    pass

            val_loss_sum += val_loss.item()
            val_metric_sum += val_metric.item()

            if val_step % log_step_freq == 0:
                animator.add(epoch + val_step / num_batches, (None,None,val_loss_sum / val_step , val_metric_sum / val_step))

        print(f'final: loss {loss_sum/len(dl_train):.3f}, auc {metric_sum/len(dl_train):.3f},'
              f' val loss {val_loss_sum/len(dl_vaild):.3f}, val auc {val_metric_sum/len(dl_vaild):.3f}')
        print(f'{num_batches * num_epochs / timer.sum():.1f} examples/sec on {str(device)}')
lr, num_epochs = 0.01, 10
train_ch(net, dl_train, dl_vaild, num_epochs, lr, try_gpu())

在这里插入图片描述

3.4 模型的预测

y_pred_probs = net(torch.tensor(test_set.values).float())
y_pred = torch.where(
    y_pred_probs>0.5,
    torch.ones_like(y_pred_probs),
    torch.zeros_like(y_pred_probs)
)
y_pred.data[:10]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/979548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp项目实践总结(十一)自定义网络检测组件

导语&#xff1a;很多时候手机设备会突然没网,这时候就需要一个网络检测组件,在没网的时候显示提示用户,提供用户体验。 目录 准备工作原理分析组件实现实战演练案例展示 准备工作 在components新建一个q-online文件夹&#xff0c;并新建一个q-online.vue的组件&#xff1b;…

华为云云服务器评测|安装Java8环境 配置环境变量 spring项目部署 【!】存在问题未解决

目录 引出安装JDK8环境查看是否有默认jar上传Linux版本的jar包解压压缩包配置环境变量 上传jar包以及运行问题上传Jar包运行控制台开放端口访问失败—见问题记录关闭Jar的方式1.进程kill -92.ctrl c退出 问题记录&#xff1a;【!】未解决各种方式查看端口情况联系工程师最后排查…

【C++基础】5. 变量作用域

文章目录 【 1. 局部变量 】【 2. 全局变量 】【 3. 局部变量和全局变量的初始化 】 作用域是程序的一个区域&#xff0c;一般来说有三个地方可以定义变量&#xff1a; 在函数或一个代码块内部声明的变量&#xff0c;称为局部变量。 在函数参数的定义中声明的变量&#xff0c;称…

关于HarmonyOS元服务的主题演讲与合作签约

一、感言 坚持中&#xff0c;总会有很多意想不到的收获。 前几次参与HDC时更多的是观众、开发者、专家的身份&#xff0c;以参观、学习、交流为主。 通过几年的努力&#xff0c;和HarmonyOS功能成长&#xff0c;在2023年的HDC大会中&#xff0c;有了我的演讲&#xff0c;并带领…

永安通配符和泛域名SSL证书的区别

随着互联网的快速发展&#xff0c;现在大多数人都已经习惯在网上交流、购物、学习&#xff0c;因此互联网上的各种类型的网站越来越多&#xff0c;不仅是企事业单位创建各种类型的网站&#xff0c;个人开发者创建的网站也越来越多&#xff0c;一张单域名SSL就不能满足个人或者企…

pdf怎么合并在一起?几种方法快速合并

pdf怎么合并在一起&#xff1f;在处理PDF文件时&#xff0c;有时需要将多个PDF文件合并成一个文件。这种操作在日常学习、工作和生活中很常见。但是&#xff0c;如果没有专业的PDF工具&#xff0c;这项任务可能会变得非常繁琐、耗时和费力。因此&#xff0c;我们需要一款功能强…

java+ssm+mysql电梯管理系统

项目介绍&#xff1a; 使用javassmmysql开发的电梯管理系统&#xff0c;系统包含管理员&#xff0c;监管员、安全员、维保员角色&#xff0c;功能如下&#xff1a; 管理员&#xff1a;系统用户管理&#xff08;监管员、安全员、维保员&#xff09;&#xff1b;系统公告&#…

哈弗猛龙,年轻人的新能源越野车

在 2023 成都车展上&#xff0c;哈弗汽车正式公布猛龙的预售价格&#xff0c;新车的预售价格区间为 16-19 万元&#xff0c;作为一台插电式混合动力车型&#xff0c;新车搭载 1.5THi4 的混动系统&#xff0c;提供纯电续航 102km 和 145km 两个纯电续航版本。 外观造型方面&…

数据库性能测试实践:慢查询统计分析

01、慢查询 查看是否开启慢查询 mysql> show variables like %slow%’; 如图所示&#xff1a; 系统变量log_slow_admin_statements 表示是否将慢管理语句例如ANALYZE TABLE和ALTER TABLE等记入慢查询日志启用log_slow_extra系统变量 &#xff08;从MySQL 8.0.14开始提供&a…

Object —— Guide Groom

从skin几何体生成引导线&#xff0c;并在其节点内可进一步处理引导线&#xff1b;内嵌Hair Generate节点&#xff1b; 注&#xff1a;skin几何体应是静态的&#xff0c;使用Guide Deform使引导线跟随动态skin&#xff1b; Groom Source 可使用第二端口输入的Groom&#xff1b;可…

国内首个侧重能源金融交易的中国社科院-美国杜兰大学能源管理硕士

国内首个侧重能源金融交易的中国社科院-美国杜兰大学能源管理硕士 作为国内首个且唯一侧重能源金融交易的硕士项目&#xff0c;中国社科院与美国杜兰大学合作举办的能源管理硕士&#xff08;Master of Management in Energy&#xff09;项目旨在培养具备国际视野的高级能源金融…

微信小程序给 thinkphp后端发送请求出现错误 Wrong number of segments 问题的解决 【踩坑记录】

微信小程序给 thinkphp后端发送请求出现错误 Wrong number of segments 问题的解决 【踩坑记录】 微信小程序代码部分PHP后端部分错误显示解决方案及步骤&#xff08;总结&#xff09; 微信小程序代码部分 //给后端接口发送一个json请求,并且得通过token鉴权ToUpdatePwd(){wx.r…

【数据结构篇】线性表2 —— 栈和队列

前言&#xff1a;上一篇我们介绍了顺序表和链表 &#xff08;https://blog.csdn.net/iiiiiihuang/article/details/132615465?spm1001.2014.3001.5501&#xff09;&#xff0c; 这一篇我们将介绍栈和队列&#xff0c;栈和队列都是基于顺序表和链表来实现的 目录 栈&#xff…

Metinfo4.0逻辑漏洞

搭建网站 MetInfo历史版本与文件&#xff0c;这里下载 需要进行安装 漏洞复现 点击会员中心进行注册 点击找回密码&#xff0c;输入刚刚创建的账号&#xff0c;然后抓包 登录后修改基本信息 直接抓包修改admin用户的密码 使用admin用户和密码654321&#xff0c;发现登录成功…

波奇学C++:继承

继承是为了复用代码&#xff0c;成员的变量或者成员函数 class Person { public:protected:string _name"li";int _age1; }; class Student :public Person { public:void print(){cout << _age;} protected:int _stuid2; }; 子类student公有继承基类Person,…

可视化工具Datart踩(避)坑指南(1)——不可复用的图表

作为目前国内开源版本最好用的可视化工具&#xff0c;Datart无疑是低成本高效率可供二开的可视化神兵利器。当然&#xff0c;免费的必然要付出一些踩坑的代价。本篇我们来讲一讲可视化工具Datart踩&#xff08;避&#xff09;坑指南&#xff08;1&#xff09;——不可复用的图表…

【小吉测评】高效简洁的数据库管控平台—CloudQuery

文章目录 &#x1f384;CloudQuery是什么&#x1f6f8;CloudQuery支持的数据源类型&#x1f354;CloudQuery社区地址&#x1f33a;如何使用&#x1f6f8;参考官方文档&#x1f6f8;参考视频教程&#x1f388;点击免费下载&#x1f388;立即下载即可&#x1f388;使用服务器完成…

基于Mendix移动原生的离线应用

一、前言 不同行业的企业会有特殊的业务场景&#xff0c;比如某些制造业的企业的工厂是物理隔离的&#xff0c;但工程师需要拿着平板输入很多生产数据&#xff1b;某些煤炭和矿业企业&#xff0c;在实际的工作区都是比较偏远&#xff0c;信号比较差&#xff0c;但是又需要用手…

gt基础教程

每日练习 吉他-结构 吉他-品 吉他谱 六线谱 调音器 试炼导航 每日练习 流程1&#xff1a; 右手拨弦: 大拇指单独练习 控制654弦&#xff0c;每根弦拨4下 6弦4下 5弦4下 4弦4下食指无名指练习 控制321弦&#xff0c;321、321练习&#xff0c; 然后321、123练习&#xff0…

指令系统(408)

一、拓展操作码指令格式 【2017 统考】某计算机按字节编址&#xff0c;指令字长固定且只有两种指令格式&#xff0c;其中三地址指令29条、二地址指令107条&#xff0c;每个地址字段6位&#xff0c;则指令字长至少应该是&#xff08; A&#xff09; A、24位 B、26位 …