基于双视角图表示算法的双向人职匹配偏好建模推荐系统构建

基于双视角图表示算法的双向人职匹配偏好建模推荐系统构建

文章目录

  • 基于双视角图表示算法的双向人职匹配偏好建模推荐系统构建
    • 1. 传统推荐系统模型
    • 2. 协同过滤算法
    • 3. 基于双视角图表示学习算法的模型构建
      • 3.1 数据输入
      • 3.2 双视角交互图的构建
      • 3.3 混合偏好传播策略
      • 3.4 对于双向意图预测的评价
      • 3.5 自监督增强的双视角排序优化
    • 4. 代码工程实践
    • 参考论文

1. 传统推荐系统模型

基于内容的推荐算法(Content-Based Recommendations,CB)是一种经典推荐算法,一般只依赖于用户及物品自身的内容属性和行为属性,而不涉及其他用户的行为,在冷启动的情况下(即新用户或者新物品)依然可以做出推荐。在本课题的题设条件下,我们难以找到显式反馈,在这种情况下,可以直接使用求职者的特征信息以及岗位信息的特征信息进行相似度计算,按照相似度度量降序排列后,对评分高的几项进行选取推荐。
基于内容的推荐算法使用最广泛的是K近邻算法(K-Nearest Neighbor,KNN),算法假设在同一个特征空间中,如果与目标样本最相似的K个样本(近邻)大多属于同一种类别,则目标样本属于这个类别的可能性也会很高。设 S m , n S_{m,n} Sm,n为求职者m与招聘信息n的相似度,一般相似度的度量有皮尔逊相似度、余弦相似度、杰卡德相似度和欧氏距离等。
在这里插入图片描述
基于内容的推荐算法是一个简单有效的推荐算法模型,原理质朴且易于复现,但是其有一定的局限性,该算法模型仅仅考虑了文本内容的相似度,对于信息的挖掘局限于文本信息本身,所以基于内容的推荐算法的思想并不能很好的解决本课题的重点与难点。

2. 协同过滤算法

在传统的推荐算法领域中,为了充分挖掘相似人群的潜在物料的偏好信息,绝大部分情况下都采用基于用户的协同过滤算法(user -based collaborative filtering),协同是指通过用户的持续协同作用,使得物料对用户整个群体的推荐作用越来越明显,所谓过滤,指的是从可行的决策方案中将用户喜欢的方案挑出,在实际问题表现为从推荐方案中将用户匹配的物料从物料的集合中找出。
在这里插入图片描述
基于用户的协同过滤算法在对用户建模时通常使用一个用户对物料的评分矩阵。

Item1Item2Item3Item4
User14022
User20556
User36212
User41700

User-CF中,核心的考量是物料群体以及用户群体之间的相似度问题,一般取用cosine余弦相似度对行为向量衡量,设 v 1 v_1 v1为用户1在n维项目空间中的评分向量,设 v 2 v_2 v2为用户2在n维向量空间中的评分,余弦相似度如下式所示:
s i m ( v 1 , v 2 ) = v 1 ⋅ v 2 ∣ ∣ v 1 ∣ ∣ × ∣ ∣ v 2 ∣ ∣ sim\left(v_1,v_2\right)=\frac{v_1\cdot v_2}{\left|\left|v_1\right|\right|\times\left|\left|v_2\right|\right|} sim(v1,v2)=v1×v2v1v2
产生推荐项目的过程也是推荐系统中的排序系统的任务,上述任务是召回任务,设用户i对物料d的评分为 R i , d R_{i,d} Ri,d,设用户j对所有物料的评分均值为 R j ˉ \bar{R_j} Rjˉ,设用户i对所有物料的评分均值为 R i ˉ \bar{R_i} Riˉ S i S_i Si为用户i的邻用户集,那么加权平均后的评分值如下式所示,对于推荐项目的生成过程应用下式对未进行评分的用户进行评分预测,评分降序排列后取评分高的N个物料推荐给目标用户。
P i , d = R i ˉ + ∑ j ∈ S i s i m ( i , j ) ∗ ( R j , d − R j ˉ ) ∑ j ∈ S i ( ∣ s i m ( i , j ) ∣ ) P_{i,d}=\bar{R_i}+\frac{\sum_{j\in S_i}{sim\left(i,j\right)\ast\left(R_{j,d}-\bar{R_j}\right)}}{\sum_{j\in S_i}\left(\left|sim\left(i,j\right)\right|\right)} Pi,d=Riˉ+jSi(sim(i,j))jSisim(i,j)(Rj,dRjˉ)
在本题中可以建模为用户与招聘职位之间的协同算法,但是由于我们的爬取数据缺少用户与招聘信息之间的反馈交互信息,所以需要我们自行在用户关于岗位的描述信息以及岗位对于求职者的期望信息中挖掘关联度,进而间接进行评分以适应基于用户的协同过滤算法。
在这里插入图片描述
但是协同过滤算法具有一些局限性,其聚焦于相似群体的发现,不能关注到个性化的泛化结果,同时无论是基于用户的协同过滤算法还是基于物料(在本课题中反映为招聘信息)的协同过滤算法,都有其侧重的群体,这样的方式使得求职者与招聘岗位之间的推荐算法呈现了匹配分布的不对等性,即对于招聘岗位的抽样服从于求职者的随机变量取值为评分机制结果条件概率模型下的分布,而非具有两种约束的联合分布。对于这个问题在本课题中的考量只能靠再次使用基于物品的协同过滤算法,也即同时用基于用户和基于物品的协同过滤算法两个算法模型组合推荐评分,基于招聘信息的协同过滤算法偏好分析如下:
在这里插入图片描述
在评分的分布上寻找最近点以确定协同过滤算法的推荐项,保证对于求职者和岗位信息都有相似群体偏好的发现。
但是这种方法没有从根本上解决同时考虑求职者和招聘方的双向推荐的问题,只是对两个协同过滤算法的推荐方案进行了折中抽取,设k为协同过滤算法推荐项数,也即推荐匹配项生成后取排序靠前的k个项作为算法推荐方案,不能确定一个统一的k值保证重合匹配项的出现。

3. 基于双视角图表示学习算法的模型构建

推荐系统一般分为召回系统和排序系统,向量化的神经网络参与的架构一般有两种形式,第一种是通过处理分别将求职者和招聘岗位的信息输入神经网络,输出求职者和招聘岗位的表征向量,经过混合运算,得到匹配分数,此种方式的架构如下图:
在这里插入图片描述
另外一种是将求职者信息和岗位招聘信息整体输入神经网络处理,直接预测出匹配得分结果。
在这里插入图片描述
双视角图表示学习算法属于第一种算法模型的范畴,由于算法需要一定的交互信息,所以在数据输入算法前需要对信息进行假设处理,我们在问题三中已经计算了岗位匹配度,岗位匹配度可以作为求职者与招聘信息发生互动的参考指标,即岗位匹配度能够间接反映用户与招聘信息发生互动的个人意愿,而用户满意度更多的是体现求职者对于互动意愿的期望。

双视角图表示学习算法对于求职者和岗位招聘信息的匹配使用双视角图神经网络(Duel-perspective Graph Neural Network),设 C = c 1 , c 2 , … , c n C=c_1,c_2,\ldots,c_n C=c1,c2,,cn为求职者的集合, J = j 1 , j 2 , … , j m J=j_1,j_2,\ldots,j_m J=j1,j2,,jm为招聘信息的集合, n n n表示求职者的数量, m m m表示招聘信息的数量,设匹配集 M = ( c i , j k ) ∣ c k ∈ C , j k ∈ J M=\left(c_i,j_k\right)|c_k\in C,j_k\in J M=(ci,jk)ckC,jkJ表示求职者与招聘信息条目的双向匹配成功的集合,在算法中求职者和招聘信息都与其相关职位或者技能等相关描述信息关联,同时其与一组定向交互记录关联(即浏览、投递、满意三个指标),设交互集 A c i = c i → j ′ ∣ c i ∈ C , j ′ ∈ J A_{c_i}={c_i\rightarrow j^\prime|c_i\in C,j^\prime\in J} Aci=cijciC,jJ与分别为表示求职者i与招聘信息以及招聘信息k与求职者的直接交互行为,在交互行为的指标中,浏览可以转换交互行为建模语言为 c i → j k c_i\rightarrow j_k cijk,投递可以转换交互行为建模语言为 j k → c i j_k\rightarrow c_i jkci,而指标满意可以转换交互行为建模语言为 ( j k → c i ) ∪ ( c i → j k ) \left(j_k\rightarrow c_i\right)\cup\left(c_i\rightarrow j_k\right) (jkci)(cijk),设 为求职者 c i c_i ci对招聘岗位 j k j_k jk的选择偏好建模, s k → i s_{k\rightarrow i} ski为招聘岗位 j k j_k jk对求职者 c i c_i ci的选择偏好建模,算法的整体处理流程如图
在这里插入图片描述

3.1 数据输入

根据算法流程以及结构,数据集要以求职者和招聘方相独立的形式逐条输入BERT进行编码工作,BERT的本质是一种文本表征(context representation),BERT的直观作用是将文本表达为矩阵或者向量,word2vec等工具也能够达到同样的效果,但是word2vec是静态的,而BERT是动态的,因为BERT是将输入考虑语序后经过transformer输出的,能够挖掘文本信息数据中的更多语义信息,对于每条的文本描述信息进行原有序列信息的保留的同时对文本信息进行信息首部 [ C L S ] t o k e n \left[CLS\right]token [CLS]token的添加,BERT结构的embedding过程如下图所示,每条求职者或者招聘方的信息都会经过BERT处理生成文本信息向量 (我们设BERT的输出向量维度为 d T d_T dT),为充分提取文本特征,达到反向传播机器学习的目的。对文本信息向量加入线性层得到输出维度为 d T d_T dT向量,同时对偏好ID(首次运行随机生成)使用embedding_lookup的方式生成编码向量 (我们设lookup方式生成的embedding维度为 ),将偏好ID的编码向量以及BERT处理经线性映射的向量进行连接操作从而得到神经网络处理的向量,由于输入BERT之前的数据文本是经过预处理过的,形式相对来说都是短文本的(低于512字),所以对于BERT的配置使用默认配置,embedding的大小使用768维,隐单元的个数配置为128个,可以满足本课题的模型效果要求。
在这里插入图片描述

3.2 双视角交互图的构建

双视角图表示学习算法的核心运作模块是图神经网络的模块,而图的数据结构必然有节点和边以及权重值的表达[31],对于节点,每个求职者或者招聘方都由两个节点组成,对于求职者c,其偏好有主动选择偏好(active selection preference)和被动选择偏好(active selection preference),记ca为求职者c的主动选择偏好、cp为求职者c的被动选择偏好,同样地,对于招聘方j也按照选择偏好分为两个节点ja和jp,两种节点代表了求职者(招聘方)在主动视角(对意向岗位或者意向求职者的主动选择)和被动视角(来自意向岗位或者意向求职者的被动选择),因此,在主动视角和被动视角的意愿同样强烈的情况下,能够生成最终的匹配。
数据输入章节所述的对于文本和偏好ID的向量化,是节点初始化工作的必要准备输入,对于节点n的初始化,需要将偏好ID编码向量与文本描述编码向量混合计算,节点n的初始化表征如下式:
z ( 0 ) n = [ e n ; W ⋅ t n ] ∈ R d {z^{\left(0\right)}}_n=\left[e_n;W\cdot t_n\right]\in\mathbb{R}^d z(0)n=[en;Wtn]Rd
其中 W ∈ R d T × d O W\in\mathbb{R}^{d_T\times d_O} WRdT×dO为可传播学习的变换矩阵,运算符 [ ; ] [;] [;]表示连接操作(concatenation), d = d E + d T d=d_{E}+d_{T} d=dE+dT 为节点的初始维度。
节点之间的边,是由不同节点的交互作用和同一求职者(招聘信息)节点的关联产生的,其特征是对称的,因此边的生成由以下三种情况组成:
(1)求职者投递简历但是未被录用,这反映了求职者的主动偏好,在 c a c^a ca和j p ^p p之间添加边。
(2)招聘岗位主动与求职者联系,但是求职者拒绝,这反映了招聘方的主动偏好,这种情况下在 j a j^a ja c p c^p cp之间加边。
(3)当求职者与招聘方达成协议,这反映了双向的主动偏好意向,增加 c a c^a ca j p j^p jp j a j^a ja c p c^p cp两条边。

3.3 混合偏好传播策略

基于双视角交互图的构建,我们采用图卷积网络对图结构数据建模进行处理,由于双视角交互图的求职者和招聘信息节点都有主动选择偏好视角节点和被动选择偏好视角节点两个节点,所以与通常的图卷积网络的构建方式有差异性[32]。而这种差异性来自于边集,与节点类型无关,所以在建模时可以忽略差异性进行统一建模,混合偏好传播的偏好只考虑匹配集M以及交互集A,对于节点n,由于统一建模的假设成立,所以忽略节点n的类型是求职者节点还是招聘信息节点、是主动选择偏好还是被动选择偏好,只考虑匹配集M_n以及交互集A_n,混合偏好传播的更新策略定义如下式:
z n ( l ) = ∑ u ∈ M n 1 ∣ N n ∣ ∣ N n ∣ z u ( l − 1 ) + ω ∑ v ∈ A n 1 ∣ N n ∣ ∣ N n ∣ z v ( l − 1 ) z_n^{\left(l\right)}=\sum_{u\in M_n}\frac{1}{\sqrt{\left|N_n\right|\left|N_n\right|}}z_u^{\left(l-1\right)}+\omega\sum_{v\in A_n}{\frac{1}{\sqrt{\left|N_n\right|\left|N_n\right|}}z_v^{\left(l-1\right)}} zn(l)=uMnNnNn 1zu(l1)+ωvAnNnNn 1zv(l1)
其中, 为节点n在匹配集 M n M_n Mn的邻节点, 为节点 n n n在交互集 A n A_n An中的邻节点,超参数 ω \omega ω为传达不同级别偏好的作用参数,用以平衡来自匹配集和交互集的传播更新影响力。
在图卷积网络中,第L+1层的节点n的最终更新策略结果如下式:
z n = ∑ l = 0 L 1 L + 1 z n l z_n=\sum_{l=0}^{L}{\frac{1}{L+1}z_n^l} zn=l=0LL+11znl

3.4 对于双向意图预测的评价

基于交互图的构建和混合偏好传播策略的确定,我们提出一种对于双向意图预测的评价得分指标,这个指标也是问题三中的求职者满意度的指标。
对于求职者 c i c_i ci选择招聘信息 j k j_k jk的偏好意向得分 定义为内积形式 ,对于招聘信息 j k j_k jk选择求职者 c i c_i ci的偏好意向得分 同样定义为内积形式 ,出于对求职者和招聘方的意图同等重要的看待,双向意图预测的评价得分的定义如下式:
y ^ = 1 2 r i → k + 1 2 s k → i \hat{y}=\frac{1}{2}r_{i\rightarrow k}+\frac{1}{2}s_{k\rightarrow i} y^=21rik+21ski

3.5 自监督增强的双视角排序优化

为了优化求职者与招聘信息的匹配预测结果过拟合和发散问题,采用基于排序优化的方法优化整个模型的过程,通过定义四元损失函数的方式优化正样本和负样本距离的偏阶;通过采用双视角对比学习的方式,为求职者和招聘信息的两种表示设计了双视角对比学习的优化函数,通过最大化一致性和最小化差异性的方式建立了 I n f o N C E InfoNCE InfoNCE损失的定义,结合求职者和招聘信息的 I n f o N C E L o s s InfoNCE Loss InfoNCELoss,最终实现了自监督的效果。
在给定一个匹配记录 < c i , j k > <c_i,j_k> <ci,jk>和不匹配记录 < c i , j k ′ > <c_i,j_{k^\prime}> <ci,jk> < c i ′ , j k > <c_{i^\prime},j_k> <ci,jk>的情况下,设匹配分数的生成算法为f,匹配分数应该满足下式:
f ( c i , j k ) > f ( c i , j k ′ ) f ( c i , j k ) > f ( c i ′ , j k ) f\left(c_i,j_k\right)>f\left(c_i,j_{k^\prime}\right)\\ f\left(c_i,j_k\right)>f\left(c_{i^\prime},j_k\right) f(ci,jk)>f(ci,jk)f(ci,jk)>f(ci,jk)
基于上述思想,使用BRP损失扩展,对四元组 < i , k , i ′ , k ′ > <i,k,i^\prime,k^\prime> <i,k,i,k>建模损失如下式:
L m a i n = − 1 ∣ D ∣ ∑ ( i , k , i ′ , k ′ ) ∈ D l o g ( σ ( y ^ i , k − 1 2 y ^ i , k ′ − 1 2 y ^ i ′ , k ) ) L_{main}=-\frac{1}{\left|D\right|}\sum_{\left(i,k,i^\prime,k^\prime\right)\in D} l o g\left(\sigma\left({\hat{y}}_{i,k}-\frac{1}{2}{\hat{y}}_{i,k^\prime}-\frac{1}{2}{\hat{y}}_{i^\prime,k}\right)\right) Lmain=D1(i,k,i,k)Dlog(σ(y^i,k21y^i,k21y^i,k))
其中 为 函数,设 M − M^- M为不匹配集, D D D为定义在匹配集 M M M和交互集 A A A上的四元组 , s i g m o i d sigmoid sigmoid函数在处理来自匹配集 M M M中的分数 与来自不匹配集 的分数 y ^ i , k ′ {\hat{y}}_{i,k^\prime} y^i,k、 的均值的偏差,通过反向传播使得损失减小从而达到优化生成算法 f f f不等式的偏阶。
在考虑求职者与招聘信息的两种表示时,应当在性质上相似的自然约束条件下,通过上述思路设计了一个双视角对比学习优化函数,考虑将 与 、 与 作为正对(positive pairs),考虑将 与 、 与 作为负对(negative pairs),正对促进了不同视角的一致性,而负对扩大了不同视角的差异性,应用 I n f o N C E InfoNCE InfoNCE损失来最大化一致性、最小化差异,求职者的 I n f o N C E L o s s InfoNCE\quad Loss InfoNCELoss定义如下式:
L s s l C = − ∑ c i ∈ C l o g ( e x p ( ( c i a ⋅ c i p ) / τ ) ∑ c i ∈ C ( e x p ( ( c i a ⋅ c i ′ p ) / τ ) + e x p ( ( c i ′ a ⋅ c i p ) / τ ) ) ) L_{ssl}^C=-\sum_{c_i\in C} l o g\left(\frac{exp\left({(c}_i^a\cdot c_i^p)/\tau\right)}{\sum_{c_i\in C}\left(exp\left(\left(c_i^a\cdot c_{i\prime}^p\right)/\tau\right)+exp\left(\left(c_{i\prime}^a\cdot c_i^p\right)/\tau\right)\right)}\right) LsslC=ciClog(ciC(exp((ciacip)/τ)+exp((ciacip)/τ))exp((ciacip)/τ))
其中, τ \tau τ为温度超参数,式是求职者的 I n f o N C E InfoNCE InfoNCE损失定义,那么同样可以得到招聘信息的 损失,将两者结合,就得到了最终的自监督任务的 I n f o N C E InfoNCE InfoNCE损失,如下式:
L s s l = L s s l C + L s s l J L_{ssl}=L_{ssl}^C+L_{ssl}^J Lssl=LsslC+LsslJ
在图神经网络的训练阶段,设\lambda为个体层面的对比学习任务强度超参数,同时优化上述所有损失,训练阶段的损失如下式所示:
L = L m a i n + λ L s s l L=L_{main}+\lambda L_{ssl} L=Lmain+λLssl

4. 代码工程实践

作者开源项目地址https://github.com/RUCAIBox/DPGNN
DPGNN参数配置

# Model
embedding_size: 128
n_layers: 2
reg_weight: 1e-05
mutual_weight: 0.05
temperature: 0.2

ADD_BERT: True
pretrained_bert_model: ~
BERT_embedding_size: 768
BERT_output_size: 32

数据预处理转换

# .udoc

import re
def split_sent(text):
    text = re.split('(?:[0-9][.;。:.•)\)])', text)  # 按照数字分割包括  1.  1;  1。  1:  1) 等
    ans = []
    for t in text:
        for tt in re.split('(?:[\ ][0-9][、,])', t):  #
            for ttt in re.split('(?:^1[、,])', tt):   # 1、
                for tttt in re.split('(?:\([0-9]\))', ttt):   # (1)
                    ans += re.split('(?:[。;…●])', tttt)

    return [_.strip() for _ in ans if len(_.strip()) > 0]


def cut_sent(text):
    wds = [_.strip() for _ in text.split(' ') if len(_.strip()) > 0]  # 分词,返回分词后的 list
    return wds


def clean_text(text):
    illegal_set = ',.;?!~[]\'"@#$%^&*()-_=+{}`~·!¥()—「」【】|、“”《<》>?,。…:'   # 定义非法字符

    for c in illegal_set:
        text = text.replace(c, ' ')     # 非法字符 替换为 空格
    text = ' '.join([_ for _ in text.split(' ') if len(_) > 0])
    
    return text    # 空格间隔


def raw2token_seq(s):
    sents = split_sent(s)
    sent_wds = []
    sent_lens = []
    for sent in sents:
        if len(sent) < 2:
            continue
        sent = clean_text(sent)
        if len(sent) < 1:
            continue
        wds = cut_sent(sent)
        sent_wds.extend(wds)
        sent_lens.append(len(wds))
    if len(sent_wds) < 1:
        return None, None, None
    assert sum(sent_lens) == len(sent_wds)
    # 返回3个值,第一个是用空格连接的词,第二个是各句子长度,第三个是总词数
    return ' '.join(sent_wds), ' '.join(map(str, sent_lens)), len(sent_wds)

u_doc_index = [3, 4, 5, 7, 8]
f = open('users.tsv', 'r', encoding='utf-8')
f.readline()
f_his = open('user_history.tsv', 'r', encoding='utf-8')
f_his.readline()

f_udoc = open('jobrec.udoc', 'w', encoding='utf-8')
head = ['user_id:token', 'user_doc:token_seq']
f_udoc.write('\t'.join(head) + '\n')


for line in f_his:
    lines = line[:-1].split('\t')
    sents = lines[4]
    try:
        sent_wds, sent_lens, _ = raw2token_seq(sents)
        sent_wds = sent_wds.split(' ')
        sent_lens = sent_lens.split(' ')
        a = -(int)(sent_lens[-1])
        for j in range(len(sent_lens)):
            a += (int)(sent_lens[j - 1])
            s_word_line = ' '.join(sent_wds[a:a + (int)(sent_lens[j])])
            if s_word_line == '':
                continue
            s_new_line = lines[0] + '\t' + s_word_line + '\n'
            f_udoc.write(s_new_line)
    except:
        if sents == '':
            continue
        f_udoc.write(lines[0] + '\t' + sents + '\n')


for line in f:
    lines = line[:-1].split('\t')
    for i in u_doc_index:
        if lines[i] and lines[i] != '-':
            sents = lines[i]
            try:
                sent_wds, sent_lens, _ = raw2token_seq(sents)
                sent_wds = sent_wds.split(' ')
                sent_lens = sent_lens.split(' ')
                a = -(int)(sent_lens[-1])
                for j in range(len(sent_lens)):
                    a += (int)(sent_lens[j - 1])
                    s_word_line = ' '.join(sent_wds[a:a + (int)(sent_lens[j])])
                    s_new_line = lines[0] + '\t' + s_word_line + '\n'
                    f_udoc.write(s_new_line)
            except:
                f_udoc.write(lines[0] + '\t' + sents + '\n')
                
f.close()
f_his.close()
f_udoc.close()


# .idoc
import re

i_doc_index = [2, 3, 4]
f = open('jobs.tsv', 'r', encoding='utf-8')
f.readline()

f_idoc = open('jobrec.idoc', 'w', encoding='utf-8')
head = ['item_id:token', 'item_doc:token_seq']
f_idoc.write('\t'.join(head) + '\n')


for line in f:
    lines = line[:-1].split('\t')
    for i in i_doc_index:
        if lines[i] and lines[i] != '-':
            sents = lines[i]
            sents = re.sub('\\\\r', ' ', sents)
            sents = re.sub('<[^>]+>', ' ', sents)
            sents = re.sub('&nbsp;', ' ', sents)
            sents = sents.split('   ')
            for sent in sents:
                sent = sent.strip()
                if len(sent) < 20:
                    continue
                s_new_line = lines[0] + '\t' + sent + '\n'
                f_idoc.write(s_new_line)
                
f.close()
f_idoc.close()

from collections import defaultdict
f = open('jobrec.udoc', encoding='utf-8')
f.readline()
u_doc_num = defaultdict(int)
for l in f:
    lines = l.split('\t')
    u_doc_num[lines[0]] += 1
u_doc = sorted(u_doc_num.items(), key=lambda x: x[1])
count = 1
for i in u_doc:
    if i[1] < 10:
        count += 1
print(count)

from collections import defaultdict
f = open('jobrec.idoc', encoding='utf-8')
f.readline()
i_doc_num = defaultdict(int)
for l in f:
    lines = l.split('\t')
    i_doc_num[lines[0]] += 1
i_doc = sorted(i_doc_num.items(), key=lambda x: x[1])
count = 1
for i in i_doc:
    if i[1] < 10:
        count += 1
print(count)

USER_THRESHOLD = 10
JOB_THRESHOLD = 20


# .inter
import time
def get_timestamp(timeStr):
    timeArray = time.strptime(timeStr, '%Y-%m-%d %H:%M:%S')
    timeStamp = int(time.mktime(timeArray))
    return timeStamp


f_inter = open('apps.tsv', 'r', encoding='utf-8')
f_inter_target = open('jobrec.inter', 'w', encoding='utf-8')
f_inter_target.write('user_id:token\titem_id:token\ttimestamp:float\n')
f_inter.readline()
user = set()
job = set()
l = f_inter.readline()
while l:
    lines = l.split('\t')
    uid = lines[0]
    iid = lines[-1][:-1]
    new_line = lines[0] + '\t' + lines[-1][:-1] + '\t' + str(get_timestamp(lines[-2][:19])) + '\n'
    if u_doc_num[lines[0]] > USER_THRESHOLD and i_doc_num[lines[0]] > JOB_THRESHOLD:
        user.add(uid)
        job.add(iid)
        f_inter_target.write(new_line)
    l = f_inter.readline()

f_inter.close()
f_inter_target.close()

# .user
u_index = [0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
f_user = open('users.tsv', 'r', encoding='utf-8')
f_user_target = open('jobrec.user', 'w', encoding='utf-8')
head = f_user.readline()[:-1].split('\t')
head = [head[i] for i in u_index]
head = [i + ':token' for i in head]
head[0] = 'user_id:token'
f_user_target.write('\t'.join(head) + '\n')
l = f_user.readline()

while l:
    lines = l[:-1].split('\t')
    lines = [lines[i] for i in u_index]
    new_line = '\t'.join(lines) + '\n'
    if lines[0] in user and u_doc_num[lines[0]] > USER_THRESHOLD:
        f_user_target.write(new_line)
    l = f_user.readline()

f_user.close()
f_user_target.close()

# .item
i_index = [0, 5, 6, 7, 8]
f_item = open('jobs.tsv', 'r', encoding='utf-8')
f_item_target = open('jobrec.item', 'w', encoding='utf-8')

head = f_item.readline()[:-1].split('\t')
head = [head[i] for i in i_index]
head = [i + ':token' for i in head]
head[0] = 'item_id:token'
f_item_target.write('\t'.join(head) + '\n')
l = f_item.readline()

while l:
    lines = l.split('\t')
    lines = [lines[i] for i in i_index]
    new_line = '\t'.join(lines) + '\n'
    if lines[0] in job and i_doc_num[lines[0]] > JOB_THRESHOLD:
        f_item_target.write(new_line)
    l = f_item.readline()

f_item.close()
f_item_target.close()


# .udoc
import re

def split_sent(text):
    text = re.split('(?:[0-9][.;。:.•)\)])', text)  # 按照数字分割包括  1.  1;  1。  1:  1) 等
    ans = []
    for t in text:
        for tt in re.split('(?:[\ ][0-9][、,])', t):  #
            for ttt in re.split('(?:^1[、,])', tt):   # 1、
                for tttt in re.split('(?:\([0-9]\))', ttt):   # (1)
                    ans += re.split('(?:[。;…●])', tttt)

    return [_.strip() for _ in ans if len(_.strip()) > 0]


def cut_sent(text):
    wds = [_.strip() for _ in text.split(' ') if len(_.strip()) > 0]  # 分词,返回分词后的 list
    return wds


def clean_text(text):
    illegal_set = ',.;?!~[]\'"@#$%^&*()-_=+{}`~·!¥()—「」【】|、“”《<》>?,。…:'   # 定义非法字符

    for c in illegal_set:
        text = text.replace(c, ' ')     # 非法字符 替换为 空格
    text = ' '.join([_ for _ in text.split(' ') if len(_) > 0])
    
    return text    # 空格间隔


def raw2token_seq(s):
    sents = split_sent(s)
    sent_wds = []
    sent_lens = []
    for sent in sents:
        if len(sent) < 2:
            continue
        sent = clean_text(sent)
        if len(sent) < 1:
            continue
        wds = cut_sent(sent)
        sent_wds.extend(wds)
        sent_lens.append(len(wds))
    if len(sent_wds) < 1:
        return None, None, None
    assert sum(sent_lens) == len(sent_wds)
    # 返回3个值,第一个是用空格连接的词,第二个是各句子长度,第三个是总词数
    return ' '.join(sent_wds), ' '.join(map(str, sent_lens)), len(sent_wds)

u_doc_index = [3, 4, 5, 7, 8]
f = open('users.tsv', 'r', encoding='utf-8')
f.readline()
f_his = open('user_history.tsv', 'r', encoding='utf-8')
f_his.readline()

f_udoc = open('jobrec.udoc', 'w', encoding='utf-8')
head = ['user_id:token', 'user_doc:token_seq']
f_udoc.write('\t'.join(head) + '\n')


for line in f_his:
    lines = line[:-1].split('\t')
    sents = lines[4]
    try:
        sent_wds, sent_lens, _ = raw2token_seq(sents)
        sent_wds = sent_wds.split(' ')
        sent_lens = sent_lens.split(' ')
        a = -(int)(sent_lens[-1])
        for j in range(len(sent_lens)):
            a += (int)(sent_lens[j - 1])
            s_word_line = ' '.join(sent_wds[a:a + (int)(sent_lens[j])])
            if s_word_line == '':
                continue
            s_new_line = lines[0] + '\t' + s_word_line + '\n'
            f_udoc.write(s_new_line)
    except:
        if sents == '':
            continue
        f_udoc.write(lines[0] + '\t' + sents + '\n')


for line in f:
    lines = line[:-1].split('\t')
    if u_doc_num[lines[0]] > USER_THRESHOLD:
        for i in u_doc_index:
            if lines[i] and lines[i] != '-':
                sents = lines[i]

                try:
                    sent_wds, sent_lens, _ = raw2token_seq(sents)
                    sent_wds = sent_wds.split(' ')
                    sent_lens = sent_lens.split(' ')
                    a = -(int)(sent_lens[-1])
                    for j in range(len(sent_lens)):
                        a += (int)(sent_lens[j - 1])
                        s_word_line = ' '.join(sent_wds[a:a + (int)(sent_lens[j])])
                        s_new_line = lines[0] + '\t' + s_word_line + '\n'
                        f_udoc.write(s_new_line)
                except:
                    f_udoc.write(lines[0] + '\t' + sents + '\n')
                
f.close()
f_his.close()
f_udoc.close()


# .idoc
import re

i_doc_index = [2, 3, 4]
f = open('jobs.tsv', 'r', encoding='utf-8')
f.readline()

f_idoc = open('jobrec.idoc', 'w', encoding='utf-8')
head = ['item_id:token', 'item_doc:token_seq']
f_idoc.write('\t'.join(head) + '\n')


for line in f:
    lines = line[:-1].split('\t')
    if i_doc_num[lines[0]] > JOB_THRESHOLD:
        for i in i_doc_index:
            if lines[i] and lines[i] != '-':
                sents = lines[i]
                sents = re.sub('\"', '', sents)
                sents = re.sub('\\\\r', ' ', sents)
                sents = re.sub('<[^>]+>', ' ', sents)
                sents = re.sub('&nbsp;', ' ', sents)
                sents = sents.split('   ')
                for sent in sents:
                    sent = sent.strip()
                    if len(sent) < 20:
                        continue
                    s_new_line = lines[0] + '\t' + sent + '\n'

                    f_idoc.write(s_new_line)
                
f.close()
f_idoc.close()

DPGNN网络结构定义、网络传播、损失定义:

# @Time   : 2022/3/21
# @Author : Chen Yang
# @Email  : flust@ruc.edu.cn

"""
pjfbole
"""

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import xavier_normal_
from recbole.model.abstract_recommender import GeneralRecommender
from recbole.model.loss import BPRLoss, EmbLoss
from recbole.utils import InputType


class DPGNN(GeneralRecommender):
    input_type = InputType.PAIRWISE

    def __init__(self, config, dataset):
        super(DPGNN, self).__init__(config, dataset)

        from recbole_pjf.model.layer import GCNConv

        self.NEG_USER_ID = config['NEG_PREFIX'] + self.USER_ID
        self.NEG_ITEM_ID = config['NEG_PREFIX'] + self.ITEM_ID
        # load parameters info
        self.embedding_size = config['embedding_size']

        # load dataset info
        self.interaction_matrix = dataset.inter_matrix(form='coo').astype(np.float32)
        self.user_add_matrix = dataset.user_single_inter_matrix(form='coo').astype(np.float32)
        self.job_add_matrix = dataset.item_single_inter_matrix(form='coo').astype(np.float32)

        # load parameters info 
        self.latent_dim = config['embedding_size']  # int type:the embedding size of lightGCN
        self.n_layers = config['n_layers']  # int type:the layer num of lightGCN
        self.reg_weight = config['reg_weight']  # float32 type: the weight decay for l2 normalization
        self.mul_weight = config['mutual_weight']
        self.temperature = config['temperature']

        # layers
        self.user_embedding_a = nn.Embedding(self.n_users, self.latent_dim)
        self.item_embedding_a = nn.Embedding(self.n_items, self.latent_dim)
        self.user_embedding_p = nn.Embedding(self.n_users, self.latent_dim)
        self.item_embedding_p = nn.Embedding(self.n_items, self.latent_dim)
        self.gcn_conv = GCNConv(dim=self.latent_dim)
        self.mf_loss = BPRLoss()
        self.reg_loss = EmbLoss()
        self.mutual_loss = nn.CrossEntropyLoss()
        self.loss = 0

        # bert part
        self.ADD_BERT = config['ADD_BERT']
        self.BERT_e_size = config['BERT_output_size'] or 1
        self.bert_lr = nn.Linear(config['BERT_embedding_size'], self.BERT_e_size)
        if self.ADD_BERT:
            self.bert_user = dataset.bert_user.to(config['device'])
            self.bert_item = dataset.bert_item.to(config['device'])

        # generate intermediate data
        self.edge_index, self.edge_weight = self.get_norm_adj_mat()
        self.edge_index = self.edge_index.to(config['device'])
        self.edge_weight = self.edge_weight.to(config['device'])

        self.apply(self._init_weights)

    def _init_weights(self, module):
        if isinstance(module, nn.Embedding):
            xavier_normal_(module.weight.data)

    def get_norm_adj_mat(self):
        r"""Get the normalized interaction matrix of users and items.
        Construct the square matrix from the training data and normalize it
        using the laplace matrix.
        .. math::
            A_{hat} = D^{-0.5} \times A \times D^{-0.5}
        Returns:
            The normalized interaction matrix in Tensor.
        """
        #   user a node: [0 ~~~ n_users] (len: n_users)
        #   item p node: [n_users + 1 ~~~ n_users + 1 + n_items] (len: n_users)
        #   user p node: [~] (len: n_users)
        #   item a node: [~] (len: n_items)
        from torch_geometric.utils import degree
        n_all = self.n_users + self.n_items

        # success edge
        row = torch.LongTensor(self.interaction_matrix.row)
        col = torch.LongTensor(self.interaction_matrix.col) + self.n_users
        edge_index1 = torch.stack([row, col])
        edge_index2 = torch.stack([col, row])
        edge_index3 = torch.stack([row + n_all, col + n_all])
        edge_index4 = torch.stack([col + n_all, row + n_all])
        edge_index_suc = torch.cat([edge_index1, edge_index2, edge_index3, edge_index4], dim=1)

        # user_add edge
        row = torch.LongTensor(self.user_add_matrix.row)
        col = torch.LongTensor(self.user_add_matrix.col) + self.n_users
        edge_index1 = torch.stack([row, col])
        edge_index2 = torch.stack([col, row])
        edge_index_user_add = torch.cat([edge_index1, edge_index2], dim=1)

        # job_add edge
        row = torch.LongTensor(self.job_add_matrix.row)
        col = torch.LongTensor(self.job_add_matrix.col) + self.n_users
        edge_index1 = torch.stack([row + n_all, col + n_all])
        edge_index2 = torch.stack([col + n_all, row + n_all])
        edge_index_job_add = torch.cat([edge_index1, edge_index2], dim=1)

        # self edge
        geek = torch.LongTensor(torch.arange(0, self.n_users))
        job = torch.LongTensor(torch.arange(0, self.n_items) + self.n_users)
        edge_index_geek_1 = torch.stack([geek, geek + n_all])
        edge_index_geek_2 = torch.stack([geek + n_all, geek])
        edge_index_job_1 = torch.stack([job, job + n_all])
        edge_index_job_2 = torch.stack([job + n_all, job])
        edge_index_self = torch.cat([edge_index_geek_1, edge_index_geek_2, edge_index_job_1, edge_index_job_2], dim=1)

        # all edge
        edge_index = torch.cat([edge_index_suc, edge_index_user_add, edge_index_job_add, edge_index_self], dim=1)

        deg = degree(edge_index[0], (self.n_users + self.n_items) * 2)
        norm_deg = 1. / torch.sqrt(torch.where(deg == 0, torch.ones([1]), deg))

        edge_weight = norm_deg[edge_index[0]] * norm_deg[edge_index[1]]

        return edge_index, edge_weight

    def get_ego_embeddings(self):
        r"""Get the embedding of users and items and combine to an embedding matrix.

        Returns:
            Tensor of the embedding matrix. Shape of [n_items+n_users, embedding_dim]
        """
        user_embeddings_a = self.user_embedding_a.weight
        item_embeddings_a = self.item_embedding_a.weight
        user_embeddings_p = self.user_embedding_p.weight
        item_embeddings_p = self.item_embedding_p.weight
        ego_embeddings = torch.cat([user_embeddings_a,
                                    item_embeddings_p,
                                    user_embeddings_p,
                                    item_embeddings_a], dim=0)

        if self.ADD_BERT:
            self.bert_u = self.bert_lr(self.bert_user)
            self.bert_i = self.bert_lr(self.bert_item)

            bert_e = torch.cat([self.bert_u,
                                self.bert_i,
                                self.bert_u,
                                self.bert_i], dim=0)
            return torch.cat([ego_embeddings, bert_e], dim=1)

        return ego_embeddings

    def info_nce_loss(self, index, is_user):
        all_embeddings = self.get_ego_embeddings()
        user_e_a, item_e_p, user_e_p, item_e_a = torch.split(all_embeddings,
                                                             [self.n_users, self.n_items, self.n_users, self.n_items])
        if is_user:
            u_e_a = F.normalize(user_e_a[index], dim=1)
            u_e_p = F.normalize(user_e_p[index], dim=1)
            similarity_matrix = torch.matmul(u_e_a, u_e_p.T)
        else:
            i_e_a = F.normalize(item_e_a[index], dim=1)
            i_e_p = F.normalize(item_e_p[index], dim=1)
            similarity_matrix = torch.matmul(i_e_a, i_e_p.T)

        mask = torch.eye(index.shape[0], dtype=torch.bool).to(self.device)

        positives = similarity_matrix[mask].view(index.shape[0], -1)
        negatives = similarity_matrix[~mask].view(index.shape[0], -1)

        logits = torch.cat([positives, negatives], dim=1)
        labels = torch.zeros(logits.shape[0], dtype=torch.long).to(self.device)
        logits = logits / self.temperature

        return logits, labels

    def forward(self):
        all_embeddings = self.get_ego_embeddings()
        embeddings_list = [all_embeddings]

        for layer_idx in range(self.n_layers):
            all_embeddings = self.gcn_conv(all_embeddings, self.edge_index, self.edge_weight)
            embeddings_list.append(all_embeddings)
        lightgcn_all_embeddings = torch.stack(embeddings_list, dim=1)
        lightgcn_all_embeddings = torch.mean(lightgcn_all_embeddings, dim=1)

        user_e_a, item_e_p, user_e_p, item_e_a = torch.split(lightgcn_all_embeddings,
                                                             [self.n_users, self.n_items, self.n_users, self.n_items])
        return user_e_a, item_e_p, user_e_p, item_e_a

    def calculate_loss(self, interaction):
        user = interaction[self.USER_ID]
        item = interaction[self.ITEM_ID]
        neg_user = interaction[self.NEG_USER_ID]
        neg_item = interaction[self.NEG_ITEM_ID]

        user_e_a, item_e_p, user_e_p, item_e_a = self.forward()

        # user active
        u_e_a = user_e_a[user]
        n_u_e_a = user_e_a[neg_user]
        # item negative
        i_e_p = item_e_p[item]
        n_i_e_p = item_e_p[neg_item]

        # user negative
        u_e_p = user_e_p[user]
        n_u_e_p = user_e_p[neg_user]
        # item active
        i_e_a = item_e_a[item]
        n_i_e_a = item_e_a[neg_item]

        r_pos = torch.mul(u_e_a, i_e_p).sum(dim=1)
        s_pos = torch.mul(u_e_p, i_e_a).sum(dim=1)

        r_neg1 = torch.mul(u_e_a, n_i_e_p).sum(dim=1)
        s_neg1 = torch.mul(u_e_p, n_i_e_a).sum(dim=1)

        r_neg2 = torch.mul(n_u_e_a, i_e_p).sum(dim=1)
        s_neg2 = torch.mul(n_u_e_p, i_e_a).sum(dim=1)

        mf_loss_u = self.mf_loss(2 * r_pos + 2 * s_pos, r_neg1 + s_neg1 + r_neg2 + s_neg2)

        # calculate Emb Loss
        u_ego_embeddings_a = self.user_embedding_a(user)
        u_ego_embeddings_p = self.user_embedding_p(user)
        pos_ego_embeddings_a = self.item_embedding_a(item)
        pos_ego_embeddings_p = self.item_embedding_p(item)
        neg_ego_embeddings_a = self.item_embedding_a(neg_item)
        neg_ego_embeddings_p = self.item_embedding_p(neg_item)
        neg_u_ego_embeddings_a = self.user_embedding_a(neg_user)
        neg_u_ego_embeddings_p = self.user_embedding_p(neg_user)

        reg_loss = self.reg_loss(u_ego_embeddings_a, u_ego_embeddings_p,
                                 pos_ego_embeddings_a, pos_ego_embeddings_p,
                                 neg_ego_embeddings_a, neg_ego_embeddings_p,
                                 neg_u_ego_embeddings_a, neg_u_ego_embeddings_p)

        loss = mf_loss_u + self.reg_weight * reg_loss

        logits_user, labels = self.info_nce_loss(user, is_user=True)
        loss += self.mul_weight * self.mutual_loss(logits_user, labels)

        logits_job, labels = self.info_nce_loss(item, is_user=False)
        loss += self.mul_weight * self.mutual_loss(logits_job, labels)

        return loss

    def predict(self, interaction):
        user = interaction[self.USER_ID]
        item = interaction[self.ITEM_ID]

        user_e_a, item_e_p, user_e_p, item_e_a = self.forward()

        # user activate
        u_e_a = user_e_a[user]
        # item negative
        i_e_p = item_e_p[item]

        # user negative
        u_e_p = user_e_p[user]
        # item negative
        i_e_a = item_e_a[item]

        I_geek = torch.mul(u_e_a, i_e_p).sum(dim=1)
        I_job = torch.mul(u_e_p, i_e_a).sum(dim=1)
        # calculate BPR Loss
        scores = I_geek + I_job

        return scores



参考论文

Yang C, Hou Y, Song Y, et al. Modeling Two-Way Selection Preference for Person-Job Fit[C]//Proceedings of the 16th ACM Conference on Recommender Systems. 2022: 102-112.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git使用X篇_2_Git全套教程IDEA版(git、GitHub、Gitee码云、搭建公司内部GitLab、与IDEA集成等内容)

本文是根据以下视频及网上总结进行更新后的介绍git使用的博文。包含了git、GitHub、Gitee码云、搭建公司内部GitLab、与IDEA集成等内容。 笔记来源&#xff1a;【尚硅谷】5h打通Git全套教程IDEA版&#xff08;涵盖GitHub\Gitee码云\GitLab&#xff09; 文章目录 初识 Git0、内容…

ReadDataByIdentifier(0x22)服务

ReadDataByIdentifier&#xff08;0x22&#xff09;服务 ReadDataByIdentifier服务允许客户端从一个或多个dataIdentifiers标识的服务器请求数据记录值。 客户端请求消息包含一个或多个两字节的dataIdentifier值&#xff0c;用于标识服务器维护的数据记录 允许的dataIdentifie…

Vue3小兔鲜:组合式写法入门

Vue3&#xff1a;组合式写法入门 Date: May 11, 2023 认识Vue3 1. Vue3组合式API体验 通过 Counter 案例 体验Vue3新引入的组合式API <script> export default {data(){return {count:0}},methods:{addCount(){this.count}} } </script><script setup> imp…

Linux 高级篇-定制自己的Linux 系统

Linux 高级篇-定制自己的Linux 系统 基本介绍 通过裁剪现有Linux 系统(CentOS7.6)&#xff0c;创建属于自己的min Linux 小系统&#xff0c;可以加深我们对linux 的理解。利用centos7.6&#xff0c;搭建一个小小linux 系统, 很有趣。 基本原理 启动流程介绍&#xff1a; 制…

(转载)基于混合粒子群算法的TSP问题求解(matlab实现)

1 理论基础 标准粒子群算法通过追随个体极值和群体极值完成极值寻优&#xff0c;虽然操作简单&#xff0c;且能够快速收敛&#xff0c;但是随着迭代次数的不断增加&#xff0c;在种群收敛集中的同时&#xff0c;各粒子也越来越相似&#xff0c;可能在局部最优解周边无法跳出。…

2023/5/29总结

abstract修饰符 抽象类就是当类和类之间有相同特征时&#xff0c;将这些共同的特征提取出来&#xff0c;形成的就是抽象类。 抽象类的特点&#xff1a; 抽象类和抽象方法必须使用abstract 关键字修饰&#xff1a;publicabstract class 类名 / public abstract void eat();抽象…

测评补单操作在美客多店铺及产品优化中的决定性角色:深度解读

许多经营美客多平台的商家有一种观念&#xff0c;他们认为美客多平台的规则与亚马逊有所区别。在美客多上&#xff0c;店铺比产品更重要&#xff0c;而且平台的竞争相对较小。因此&#xff0c;他们认为在美客多平台进行补单操作是不必要的。 然而&#xff0c;是否真的如此呢&a…

FM实现F4帮助系列三:弹出框多筛选条件的搜索帮助(根据搜索帮助筛选字段)...

函数&#xff1a;F4IF_GET_SHLP_DESCR F4IF_START_VALUE_REQUEST 效果图&#xff1a; 本例子代码&#xff1a; 找到需要的帮助: *& Report ZLM_TEST_045 REPORT zlm_test_045. TABLES makt. DATA: BEGIN OF str_f4, matnr TYPE matnr, maktx TYPE maktx, END OF str_f4.…

react antd Modal里Form设置值不起作用

问题描述&#xff1a; react antd Modal里Form设置值不起作用&#xff0c;即使用form的api。比如&#xff1a;编辑时带出原有的值。 造成的原因&#xff1a;一般设置值都是在声明周期里设置&#xff0c;比如&#xff1a;componentDidMounted里设置&#xff0c;hook则在useEff…

国际植物命名数据库(International Plant Names Index)

功能介绍 https://www.ipni.org/ 是国际植物命名数据库&#xff08;International Plant Names Index&#xff09;的官方网站。国际植物命名数据库是一个全球性的植物命名和分类资源&#xff0c;旨在提供植物命名信息的权威来源。以下是该网站的一些特点和功能&#xff1a; 植…

Web Scoket简述

Web Socket 简介 初次接触 Web Socket 的人&#xff0c;我们已经有了 HTTP 协议&#xff0c;为什么还需要另一个协议&#xff1f;它能带来什么好处&#xff1f; 因为 HTTP 协议有一个缺陷&#xff1a;通信只能由客户端发起。http基于请求响应实现。 &#xff08;准确来说HTTP…

使用Python进行接口性能测试:从入门到高级

前言&#xff1a; 在今天的网络世界中&#xff0c;接口性能测试越来越重要。良好的接口性能可以确保我们的应用程序可以在各种网络条件下&#xff0c;保持流畅、稳定和高效。Python&#xff0c;作为一种广泛使用的编程语言&#xff0c;为进行接口性能测试提供了强大而灵活的工…

尚硅谷大数据hadoop教程_mapReduce

p67 课程介绍 p68概述 p69 mapreduce核心思想 p70 wordcount源码 序列化类型 mapReduce三类进程 p71 编程规范 用户编写的程序分成三个部分&#xff1a;Mapper、Reducer和Driver。 P72 wordcount需求案例分析 p 73 -78 案例环境准备 &#xff08;1&#xff09;创建maven…

基于Html5的在线资料库的设计与实现(asp.NET,SQLServer)

在线资料库系统采用.NET开发平台进行开发&#xff0c;开发工具采用Microsoft Visual Studio 2010集成开发环境&#xff0c;后台编程语言采用C#编程语言来进行编程开发&#xff0c;数据库我们采用当下流行的SQL Server 2008数据库管理系统来存放平台中的数据信息&#xff0c;整个…

Nginx 之 Tomcat 负载均衡、动静分离

一.详细安装及操作实例&#xff08;Nginx 七层代理&#xff09; 首先至少准备三台服务器 Nginx 服务器&#xff1a;192.168.247.131:80 Tomcat服务器1&#xff1a;192.168.247.133:80 Tomcat服务器2&#xff1a;192.168.247.134:8080 192.168.247.134:80811.部署Nginx 负载均…

windows下编译roadrunner和作为laravel服务器实践

roadrunner源码地址&#xff1a;https://gitee.com/mirrors/RoadRunner?_fromgitee_search windows下编译roadrunner源码获得rr.exe可执行文件 将rr.exe拷贝到laravel目录下 .rr.yaml配置文件内容&#xff1a; version: 3 server: command: "php vendor/spiral/road…

【Jmeter】生成html格式接口自动化测试报告

jmeter自带执行结果查看的插件&#xff0c;但是需要在jmeter工具中才能查看&#xff0c;如果要向领导提交测试结果&#xff0c;不够方便直观。 笔者刚做了这方面的尝试&#xff0c;总结出来分享给大家。 这里需要用到ant来执行测试用例并生成HTML格式测试报告。 一、ant下载安…

vue methods 互相调用的方法

methods是一个内置的函数&#xff0c;主要用于两个组件之间的数据传递&#xff0c;也就是调用方法。下面给大家介绍一个在 vue中互相调用的方法&#xff0c;在使用过程中可以参考一下。 methods实现了两个组件之间数据的传递&#xff0c;我们先来看一下 Methods是如何实现数据传…

Kafka

Kafka 概述 基于Scala语言&#xff0c;是一个分布式&#xff0c;分区的&#xff0c;多副本的&#xff0c;多订阅者的消息队列系统。 优势 可靠性&#xff1a;分布式的&#xff0c;分区&#xff0c;复制和容错的。可扩展性&#xff1a;kafka消息传递系统轻松缩放&#xff0c…

ROS:订阅者Subscriber的编程实现(C++)

目录 一、话题模型二、创建功能包三、创建Subscriber代码四、编译代码五、运行 一、话题模型 图中&#xff0c;我们使用ROS Master管理节点。 有两个主要节点&#xff1a; Publisher&#xff0c;名为Turtle Velocity&#xff08;即海龟的速度&#xff09; Subscriber&#xff0…