DeepFM模型代码详解

直到看到这篇文章,我才搞明白类别特征怎么做lookup的,也看明白了代码逻辑。如果你看完没懂,私信留下wx,给你讲懂。

1、Deepfm 的原理,DeepFM 是一个模型还是代表了一类模型,DeepFM 对 FM 做了什么样的改进,FM 的公式如何化简并求解梯度(滴滴) 2、FM、DeepFM 介绍一下(猫眼) 3、DeepFm 模型介绍一下(一点资讯) 4、DeepFM 介绍下 & FM 推导(一点资讯)

1、DeepFM 原理回顾

先来回顾一下 DeepFM 的模型结构:

DeepFM 包含两部分:因子分解机部分与神经网络部分,分别负责低阶特征的提取和高阶特征的提取。这两部分共享同样的嵌入层输入。DeepFM 的预测结果可以写为:

1.1 嵌入层

嵌入层 (embedding layer) 的结构如上图所示。通过嵌入层,尽管不同 field 的长度不同(不同离散变量的取值个数可能不同),但是 embedding 之后向量的长度均为 K(我们提前设定好的 embedding-size)。通过代码可以发现,在得到 embedding 之后,我们还将对应的特征值乘到了 embedding 上,这主要是由于 fm 部分和 dnn 部分共享嵌入层作为输入,而 fm 中的二次项如下:

1.2 FM 部分

FM 部分的详细结构如下:

FM 部分是一个因子分解机。关于因子分解机可以参阅文章 [Rendle, 2010] Steffen Rendle. Factorization machines. In ICDM, 2010.。因为引入了隐变量的原因,对于几乎不出现或者很少出现的隐变量,FM 也可以很好的学习。

FM 的输出公式为:

1.3 深度部分

深度部分是一个多层前馈神经网络。我们这里不再赘述。

1.4 与其他神经网络的关系

1.5 模型效果

2、代码实现

2.1 Embedding 介绍

DeepFM 中,很重要的一项就是 embedding 操作,所以我们先来看看什么是 embedding,可以简单的理解为,将一个特征转换为一个向量。在推荐系统当中,我们经常会遇到离散变量,如 userid、itemid。对于离散变量,我们一般的做法是将其转换为 one-hot,但对于 itemid 这种离散变量,转换成 one-hot 之后维度非常高,但里面只有一个是 1,其余都为 0。这种情况下,我们的通常做法就是将其转换为 embedding。

embedding 的过程是什么样子的呢?它其实就是一层全连接的神经网络,如下图所示:

假设一个离散变量共有 5 个取值,也就是说 one-hot 之后会变成 5 维,我们想将其转换为 embedding 表示,其实就是接入了一层全连接神经网络。由于只有一个位置是 1,其余位置是 0,因此得到的 embedding 就是与其相连的图中红线上的权重。

2.2 tf.nn.embedding_lookup 函数介绍

在 tf1.x 中,我们使用 embedding_lookup 函数来实现 embedding,代码如下:

#embedding
embedding = tf.constant([[0.21,0.41,0.51,0.11]],[0.22,0.42,0.52,0.12],[0.23,0.43,0.53,0.13],[0.24,0.44,0.54,0.14]],dtype=tf.float32)feature_batch = tf.constant([2,3,1,0])get_embedding1 = tf.nn.embedding_lookup(embedding,feature_batch)

在 embedding_lookup 中,第一个参数相当于一个二维的词表,并根据第二个参数中指定的索引,去词表中寻找并返回对应的行。上面的过程为:

注意这里的维度的变化,假设我们的 feature_batch 是 1 维的 tensor,长度为 4,而 embedding 的长度为 4,那么得到的结果是 4 * 4 的,同理,假设 feature_batch 是 2 *4 的,embedding_lookup 后的结果是 2 * 4 * 4。每一个索引返回 embedding table 中的一行,自然维度会 + 1。

上文说过,embedding 层其实是一个全连接神经网络层,那么其过程等价于:

可以得到下面的代码:

embedding = tf.constant([[0.21,0.41,0.51,0.11],[0.22,0.42,0.52,0.12],[0.23,0.43,0.53,0.13],[0.24,0.44,0.54,0.14]],dtype=tf.float32)feature_batch = tf.constant([2,3,1,0])
feature_batch_one_hot = tf.one_hot(feature_batch,depth=4)
get_embedding2 = tf.matmul(feature_batch_one_hot,embedding)

二者是否一致呢?我们通过代码来验证一下:

with tf.Session() as sess:sess.run(tf.global_variables_initializer())embedding1,embedding2 = sess.run([get_embedding1,get_embedding2])print(embedding1)print(embedding2)

得到的结果为:

二者得到的结果是一致的。

因此,使用 embedding_lookup 的话,我们不需要将数据转换为 one-hot 形式,只需要传入对应的 feature 的 index 即可。

2.3 数据处理

接下来进入代码实战部分。

首先我们来看看数据处理部分,通过刚才的讲解,想要给每一个特征对应一个 k 维的 embedding,如果我们使用 embedding_lookup 的话,需要得到连续变量或者离散变量对应的特征索引 feature index。听起来好像有点抽象,咱们还是举个简单的例子:

这里有三组特征,或者说 3 个 field 的特征,分别是性别、星期几、职业。对应的特征数量分别为 2、7、2。我们总的特征数量 feature-size 为 2 + 7 + 2=11。如果转换为 one-hot 的话,每一个取值都会对应一个特征索引 feature-index。

这样,对于一个实例男/二/学生来说,将其转换为对应的特征索引即为 0、3、9。在得到特征索引之后,就可以通过 embedding_lookup 来获取对应特征的 embedding。

当然,上面的例子中我们只展示了三个离散变量,对于连续变量,我们也会给它一个对应的特征索引,如:

可以看到,此时共有 5 个 field,一个连续特征就对应一个 field。

但是在 FM 的公式中,不光是 embedding 的内积,特征取值也同样需要。对于离散变量来说,特征取值就是 1,对于连续变量来说,特征取值是其本身,因此,我们想要得到的数据格式如下:

定好了目标之后,咱们就开始实现代码。先看下对应的数据集:

import pandas as pdTRAIN_FILE = "data/train.csv"
TEST_FILE = "data/test.csv"NUMERIC_COLS = ["ps_reg_01", "ps_reg_02", "ps_reg_03","ps_car_12", "ps_car_13", "ps_car_14", "ps_car_15"
]IGNORE_COLS = ["id", "target","ps_calc_01", "ps_calc_02", "ps_calc_03", "ps_calc_04","ps_calc_05", "ps_calc_06", "ps_calc_07", "ps_calc_08","ps_calc_09", "ps_calc_10", "ps_calc_11", "ps_calc_12","ps_calc_13", "ps_calc_14","ps_calc_15_bin", "ps_calc_16_bin", "ps_calc_17_bin","ps_calc_18_bin", "ps_calc_19_bin", "ps_calc_20_bin"
]dfTrain = pd.read_csv(TRAIN_FILE)
dfTest = pd.read_csv(TEST_FILE)
print(dfTrain.head(10))

数据集如下:

我们定义了一些不考虑的变量列、一些连续变量列,剩下的就是离散变量列,接下来,想要得到一个 feature-map。这个 featrue-map 定义了如何将变量的不同取值转换为其对应的特征索引 feature-index。

df = pd.concat([dfTrain,dfTest])feature_dict = {}
total_feature = 0
for col in df.columns:if col in IGNORE_COLS:continueelif col in NUMERIC_COLS:feature_dict[col] = total_featuretotal_feature += 1else:unique_val = df[col].unique()feature_dict[col] = dict(zip(unique_val,range(total_feature,len(unique_val) + total_feature)))total_feature += len(unique_val)
print(total_feature)
print(feature_dict)

这里,我们定义了 total_feature 来得到总的特征数量,定义了 feature_dict 来得到变量取值到特征索引的对应关系,结果如下:

可以看到,对于连续变量,直接是变量名到索引的映射,对于离散变量,内部会嵌套一个二级 map,这个二级 map 定义了该离散变量的不同取值到索引的映射。

下一步,需要将训练集和测试集转换为两个新的数组,分别是 feature-index,将每一条数据转换为对应的特征索引,以及 feature-value,将每一条数据转换为对应的特征值。

"""
对训练集进行转化
"""
print(dfTrain.columns)
train_y = dfTrain[['target']].values.tolist()
dfTrain.drop(['target','id'],axis=1,inplace=True)
train_feature_index = dfTrain.copy()
train_feature_value = dfTrain.copy()for col in train_feature_index.columns:if col in IGNORE_COLS:train_feature_index.drop(col,axis=1,inplace=True)train_feature_value.drop(col,axis=1,inplace=True)continueelif col in NUMERIC_COLS:train_feature_index[col] = feature_dict[col]else:train_feature_index[col] = train_feature_index[col].map(feature_dict[col])train_feature_value[col] = 1"""
对测试集进行转化
"""
test_ids = dfTest['id'].values.tolist()
dfTest.drop(['id'],axis=1,inplace=True)test_feature_index = dfTest.copy()
test_feature_value = dfTest.copy()for col in test_feature_index.columns:if col in IGNORE_COLS:test_feature_index.drop(col,axis=1,inplace=True)test_feature_value.drop(col,axis=1,inplace=True)continueelif col in NUMERIC_COLS:test_feature_index[col] = feature_dict[col]else:test_feature_index[col] = test_feature_index[col].map(feature_dict[col])test_feature_value[col] = 1

来看看此时的训练集的特征索引:

对应的特征值:

此时,我们的训练集和测试集就处理完毕了!

2.4 模型参数及输入

接下来定义模型的一些参数,如学习率、embedding 的大小、深度网络的参数、激活函数等等,还有两个比较重要的参数,分别是 feature 的大小和 field 的大小:

"""模型参数"""
dfm_params = {"use_fm":True,"use_deep":True,"embedding_size":8,"dropout_fm":[1.0,1.0],"deep_layers":[32,32],"dropout_deep":[0.5,0.5,0.5],"deep_layer_activation":tf.nn.relu,"epoch":30,"batch_size":1024,"learning_rate":0.001,"optimizer":"adam","batch_norm":1,"batch_norm_decay":0.995,"l2_reg":0.01,"verbose":True,"eval_metric":'gini_norm',"random_seed":3
}
dfm_params['feature_size'] = total_feature
dfm_params['field_size'] = len(train_feature_index.columns)

而训练模型的输入有三个,分别是刚才转换得到的特征索引和特征值,以及 label:

"""开始建立模型"""
feat_index = tf.placeholder(tf.int32,shape=[None,None],name='feat_index')
feat_value = tf.placeholder(tf.float32,shape=[None,None],name='feat_value')label = tf.placeholder(tf.float32,shape=[None,1],name='label')

比如刚才的两个数据,对应的输入为:

定义好输入之后,再定义一下模型中所需要的 weights:

"""建立weights"""
weights = dict()#embeddings
weights['feature_embeddings'] = tf.Variable(tf.random_normal([dfm_params['feature_size'],dfm_params['embedding_size']],0.0,0.01),name='feature_embeddings')
weights['feature_bias'] = tf.Variable(tf.random_normal([dfm_params['feature_size'],1],0.0,1.0),name='feature_bias')#deep layers
num_layer = len(dfm_params['deep_layers'])
input_size = dfm_params['field_size'] * dfm_params['embedding_size']
glorot = np.sqrt(2.0/(input_size + dfm_params['deep_layers'][0]))weights['layer_0'] = tf.Variable(np.random.normal(loc=0,scale=glorot,size=(input_size,dfm_params['deep_layers'][0])),dtype=np.float32
)
weights['bias_0'] = tf.Variable(np.random.normal(loc=0,scale=glorot,size=(1,dfm_params['deep_layers'][0])),dtype=np.float32
)for i in range(1,num_layer):glorot = np.sqrt(2.0 / (dfm_params['deep_layers'][i - 1] + dfm_params['deep_layers'][I]))weights["layer_%d" % i] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(dfm_params['deep_layers'][i - 1], dfm_params['deep_layers'][i])),dtype=np.float32)  # layers[i-1] * layers[I]weights["bias_%d" % i] = tf.Variable(np.random.normal(loc=0, scale=glorot, size=(1, dfm_params['deep_layers'][i])),dtype=np.float32)  # 1 * layer[I]#final concat projection layerif dfm_params['use_fm'] and dfm_params['use_deep']:input_size = dfm_params['field_size'] + dfm_params['embedding_size'] + dfm_params['deep_layers'][-1]
elif dfm_params['use_fm']:input_size = dfm_params['field_size'] + dfm_params['embedding_size']
elif dfm_params['use_deep']:input_size = dfm_params['deep_layers'][-1]glorot = np.sqrt(2.0/(input_size + 1))
weights['concat_projection'] = tf.Variable(np.random.normal(loc=0,scale=glorot,size=(input_size,1)),dtype=np.float32)
weights['concat_bias'] = tf.Variable(tf.constant(0.01),dtype=np.float32)

介绍两个比较重要的参数,weights['feature_embeddings'] 是每个特征所对应的 embedding,它的大小为 feature-size * embedding-size,另一个参数是 weights['feature_bias'] ,这个是 FM 部分计算时所用到的一次项的权重参数,可以理解为 embedding-size 为 1 的 embedding table,它的大小为 feature-size * 1。

假设 weights['feature_embeddings'] 如下:

2.5 嵌入层

嵌入层,主要根据特征索引得到对应特征的 embedding:

"""embedding"""
embeddings = tf.nn.embedding_lookup(weights['feature_embeddings'],feat_index)reshaped_feat_value = tf.reshape(feat_value,shape=[-1,dfm_params['field_size'],1])embeddings = tf.multiply(embeddings,reshaped_feat_value)

这里注意的是,在得到对应的 embedding 之后,还乘上了对应的特征值,这个主要是根据 FM 的公式得到的。过程表示如下:

2.6 FM 部分

我们先来回顾一下 FM 的公式,以及二次项的化简过程:

yFM=〈w,x〉+∑j1=1d∑j2=j1+1d〈Vi,Vj〉xj1⋅xj2y_{F M}=\langle w, x\rangle+\sum_{j_1=1}^d \sum_{j_2=j_1+1}^d\left\langle V_i, V_j\right\rangle x_{j_1} \cdot x_{j_2}yFM​=〈w,x〉+∑j1​=1d​∑j2​=j1​+1d​〈Vi​,Vj​〉xj1​​⋅xj2​​

上面的式子中有部分同学曾经问我第一步是怎么推导的,其实也不难,看下面的手写过程

因此,一次项的计算如下,我们刚刚也说过了,通过 weights['feature_bias'] 来得到一次项的权重系数:

fm_first_order = tf.nn.embedding_lookup(weights['feature_embeddings'],feat_index)
fm_first_order = tf.reduce_sum(tf.multiply(fm_first_order,reshaped_feat_value),2)

对于二次项,经过化简之后有两部分(暂不考虑最外层的求和),我们先用 excel 来形象展示一下两部分,这有助于你对下面代码的理解。

第一部分过程如下:

第二部分的过程如下:

最后两部分相减:

二次项部分的代码如下:

summed_features_emb = tf.reduce_sum(embeddings,1)
summed_features_emb_square = tf.square(summed_features_emb)squared_features_emb = tf.square(embeddings)
squared_sum_features_emb = tf.reduce_sum(squared_features_emb,1)fm_second_order = 0.5 * tf.subtract(summed_features_emb_square,squared_sum_features_emb)

要注意这里的 fm_second_order 是二维的 tensor,大小为 batch-size * embedding-size,也就是公式中最外层的一个求和还没有进行,这也是代码中与 FM 公式有所出入的地方。我们后面再讲。

2.7 Deep 部分

Deep 部分很简单了,就是几层全连接的神经网络:

"""deep part"""
y_deep = tf.reshape(embeddings,shape=[-1,dfm_params['field_size'] * dfm_params['embedding_size']])for i in range(0,len(dfm_params['deep_layers'])):y_deep = tf.add(tf.matmul(y_deep,weights["layer_%d" %i]), weights["bias_%d"%I])y_deep = tf.nn.relu(y_deep)

2.8 输出部分

最后的输出部分,论文中的公式如下:

y^=sigmoid⁡(yFM+yDNN)\hat{y}=\operatorname{sigmoid}\left(y_{F M}+y_{D N N}\right)y^​=sigmoid(yFM​+yDNN​)

在我们的代码中如下:

"""final layer"""
if dfm_params['use_fm'] and dfm_params['use_deep']:concat_input = tf.concat([fm_first_order,fm_second_order,y_deep],axis=1)
elif dfm_params['use_fm']:concat_input = tf.concat([fm_first_order,fm_second_order],axis=1)
elif dfm_params['use_deep']:concat_input = y_deepout = tf.nn.sigmoid(tf.add(tf.matmul(concat_input,weights['concat_projection']),weights['concat_bias']))

看似有点出入,其实真的有点出入,不过无碍,咱们做两点说明:

1)首先,这里整体上和最终的公式是相似的,看下面的 excel(由于最后一层只有一个神经元,矩阵相乘可以用对位相乘再求和代替):

2)这里不同的地方就是,FM 二次项化简之后最外层不再是简单的相加了,而是变成了加权求和(有点类似 attention 的意思),如果 FM 二次项部分对应的权重都是 1,就是标准的 FM 了。

2.9 Loss and Optimizer

这一块也不再多讲,我们使用 logloss 来指导模型的训练:

"""loss and optimizer"""
loss = tf.losses.log_loss(tf.reshape(label,(-1,1)), out)
optimizer = tf.train.AdamOptimizer(learning_rate=dfm_params['learning_rate'], beta1=0.9, beta2=0.999,epsilon=1e-8).minimize(loss)

至此,我们整个 DeepFM 模型的架构就搭起来了,接下来,我们简单测试一下代码:

"""train"""
with tf.Session() as sess:sess.run(tf.global_variables_initializer())for i in range(100):epoch_loss,_ = sess.run([loss,optimizer],feed_dict={feat_index:train_feature_index,feat_value:train_feature_value,label:train_y})print("epoch %s,loss is %s" % (str(i),str(epoch_loss)))

3.实战案例参考

搜索推荐算法挑战赛OGeek-完整方案及代码(亚军):https://cloud.tencent.com/developer/article/1479464

  • 参考链接

https://arxiv.org/pdf/1703.04247v1.pdf

http://www.360doc.com/content/21/0720/21/99071_987495812.shtml

https://blog.csdn.net/hero_myself/article/details/117522304

https://cloud.tencent.com/developer/article/1479464

链接:牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网
 

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

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

相关文章

单细胞copyKat分析学习和整理

CopyKAT(肿瘤拷贝数核型分析)是一种使用综合贝叶斯方法的计算工具,能够在单细胞中以5MB分辨率检测全基因组非整倍体,以便从高通量单细胞RNA测序数据中区分肿瘤细胞与正常细胞,并识别肿瘤亚克隆。 (这里提一下,“5MB”是指 5兆碱基对(5 megab…

CTF-PWN方向 栈溢出等基础知识笔记(2)

C语言基本函数补充 write函数 ret2syscall 要求有0x80这种系统调用存在 (0x0A是回车的意思) 案例 通过file查看这个文件 发现是静态编译的文件 所以很多库函数都被编译进去了 但是不存在bin/sh字符串 不存在system和backdoor函数 修改,rea…

【环境搭建】远程服务器搭建ElasticSearch

参考: 非常详细的阿里云服务器安装ElasticSearch过程..._阿里云服务器使用elasticsearch-CSDN博客 服务器平台:AutoDL 注意: 1、切换为非root用户,su 新用户名,否则ES无法启动 2、安装过程中没有出现设置账号密码…

AD9361 在低至 1MHz 的频率下运行

AD9361 在低至 1MHz 的频率下运行 AD -FREQCVT1-EBZ是包含AD9361的FMCOMMS3/4/5板的附加板。虽然完整的芯片级设计包可在此 RF 收发器的ADI产品页面上找到,但有关此卡的信息及其使用方法、围绕它的设计包以及可使其工作的软件可在此处找到。 AD-FREQCVT1-EBZ 模块…

山西农业大学20241015

02-VUE 一. Vue中常用的指令1. Vue指令概述2 Vue中指令的分类3 Vue中指令3.1 内容渲染指令3.2 条件渲染指令3.2.1 v-show3.2.2 v-if3.2.3 v-else 和 v-else-if 3.3 事件绑定指令 v-on--重要3.3.1 内联语句3.3.2 methods中的函数名 一. Vue中常用的指令 1. Vue指令概述 概念: 指…

安装Node.js环境,安装vue工具

一、安装Node.js 去官方网站自行安装自己所需求的安装包 这是下载的官方网站 下载 | Node.js 中文网 给I accept the terms in the License Agreement打上勾然后点击Next 把安装包放到自己所知道的位置,后面一直点Next即可 等待它安装好 然后winr打开命令提示符cmd 二、安装…

MySQL中表的约束

1,概念 表中一定要有各种约束,通过约束,让我们来插入数据库中的数据是符合预期的。 约束本质是通过技术手段,倒逼程序员插入正确的数据;反过来,站在MySQL的角度来单,内部已经插进来的数据&…

Web安全 - 跨站点请求伪造CSRF(Cross Site Request Forgery)

文章目录 OWASP 2023 TOP 10CSRF 导图CSRF的基本概念CSRF的工作原理常见CSRF攻击模式CSRF防御策略补充建议应用场景实战防御策略选择1. CSRF Token(首选)2. SameSite Cookie属性3. 验证Referer和Origin4. 多因素认证 实现方案CSRF Token实现SameSite Coo…

(39)MATLAB生成高斯脉冲及其频谱

文章目录 前言一、MATLAB仿真代码二、仿真结果画图 前言 高斯脉冲在通信中是很重要的调制符号波形,本文使用MATLAB生成高斯脉冲,并使用FFT变换给出其频谱。 一、MATLAB仿真代码 代码如下: % 信号参数 fs 100; % 采样…

【Vercel】Vercel部署项目遇到的问题

部署问题1&#xff1a; 问题详情 部署提示报错&#xff0c;vite的命令找不到 13:18:21.159 13:18:21.160npm error This is an error with npm itself. Please report this error at: 13:18:21.160npm error 13:18:221.16 npm error A complete l <https://github.com/np…

【自动驾驶汽车通讯协议】I2C(IIC)总线通讯技术详解

文章目录 0. 前言1. I2C简介2.I2C的工作原理2.1 硬件要求&#xff1a;2.2 半双工通信&#xff1a; 3. 通信时序4. 其他特性4.1 通信速率4.2 抗干扰措施4.3 注意事项 5. 在自动驾驶汽车中的应用5.1 I2C操作模式5.2 I2C的用途 6. 总结 0. 前言 按照国际惯例&#xff0c;首先声明&…

Java—类和对象习题讲解

如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 目录 习题一&#xff1a; 习题二&#xff1a; 习题三&#xff1a;.import static 能够导入一些静态方法 习题四&#xff1a; 习题五&#xff1a; 习题六&#xff1…

【刷题册】2024.10.13 - 2024.10.15

目录 一、2024.10.131.1 BC153 [NOIP2010]数字统计1.2 NC313 两个数组的交集1.2.1 思路一&#xff1a;暴力O(N^2)1.2.2 思路二&#xff1a;hash 1.3 AB5 点击消除 二、2024.10.142.1 BC64⽜⽜的快递2.2 DP4 最⼩花费爬楼梯2.3 数组中两个字符串的最⼩距离 三、2024.10.153.1 BC…

Visual Studio Code基础:使用debugpy调试python程序

相关阅读 VS codehttps://blog.csdn.net/weixin_45791458/category_12658212.html?spm1001.2014.3001.5482 一、安装调试器插件 在VS code中可以很轻松地调试Python程序&#xff0c;首先需要安装Python调试器插件&#xff0c;如图1所示。 图1 安装调试器插件 Python Debugge…

Redis --- 第四讲 --- 常用数据结构 --- Hash、List

一、Hash哈希类型的基本介绍。 哈希表&#xff1a;之前学过的所有数据结构中&#xff0c;最最重要的。 1、日常开发中&#xff0c;出场频率非常高。 2、面试中&#xff0c;非常重要的考点。 Redis自身已经是键值对结构了。Redis自身的键值对就是通过哈希的方式来组织的。把…

16年408计算机网络

第一题&#xff1a; 解析&#xff1a; 首先我们要清楚R1,R2,R3是路由器&#xff08;网络层&#xff09;&#xff0c;Switch是以太网交换机&#xff08;数据链路层&#xff09;&#xff0c;Hub是集线器&#xff08;物理层&#xff09;。 由此可见路由器实现的最高功能层是3层&am…

如何利用phpstudy创建mysql数据库

phpStudy诞生于2007年&#xff0c;是一款老牌知名的PHP开发集成环境工具&#xff0c;产品历经多次迭代升级&#xff0c;目前有phpStudy经典版、phpStudy V8&#xff08;2019版&#xff09;等等&#xff0c;利用phpstudy可以快速搭建一个mysql环境&#xff0c;接下来我们就开始吧…

【计算机网络 - 基础问题】每日 3 题(三十六)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

Java项目:160 基于springboot物流管理系统(PPT+论文+说明文档)

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 物流管理系统有管理员和用户两个角色。 ​ 管理员功能有个人中心&#xff0c;用户管理&#xff0c;车辆信息管理&#xff0c;公告信息管理&#xff…

实现MySQL异地多活场景

作为现代化的互联网企业 &#xff0c;最怕的是什么 &#xff1f;是意外&#xff01;由各种意外导致的数据库问题&#xff0c;磁盘问题、网络问题、人员误操作问题等等&#xff0c;这些问题都可能导致数据不可用或者丢失&#xff0c;造成重大损失。 因此&#xff0c;很少会有企…