『NLP学习笔记』NER任务的CRF-layer的原理

news/2024/11/18 2:32:15/
NER任务的CRF-layer的原理

文章目录

  • 一. 预备工作
  • 二. BILSTM-CRF模型
    • 2.1. BiLSTM层输出
    • 2.2. 如果没有CRF层会怎么样
    • 2.3. CRF层可以从训练数据中学到约束
  • 三. CRF层
    • 3.1. 发射(Emission)分数
    • 3.2. 转移(Transition)分数
    • 3.3. CRF损失函数
    • 3.4. 实际路径得分
    • 3.5. 所有可能的路径的得分
      • 3.5.1. 步骤1: 回想一下CRF损失函数
      • 3.5.2. 步骤2: 回忆一下Emission和Transition得分
      • 3.5.3. 步骤3: 开始战斗(准备好纸笔)
    • 3.6. 为新的句子推理标签
  • 四. 参考文献

  • 用命名实体识别任务来解释CRF:https://arxiv.org/abs/1603.01360,该文提出了一个使用词和字嵌入的 BiLSTM-CRF 命名实体识别模型。下将以本文中的模型为例来解释CRF层是如何工作的。
  • 更详细的文章请参考该系列:https://createmomo.github.io/2017/09/12/CRF_Layer_on_the_Top_of_BiLSTM_1/

一. 预备工作

  • 假设有一个数据集,有两个实体类型,Person和Organization。但是,事实上,在我们的数据集中,我们有5个实体标签:
  • B-Person
  • I- Person
  • B-Organization
  • I-Organization
  • O
  • 此外,x\mathbf xx 是一个包含5个单词的句子,w0,w1,w2,w3,w4w_0,w_1,w_2,w_3,w_4w0w1w2w3w4。更重要的是,在句子 x\mathbf xx 中,[w0,w1][w_0,w_1][w0w1] 是一个Person实体,[w3][w_3][w3] 是一个Organization实体,其他都是“O”

二. BILSTM-CRF模型

  • 如下图所示:
  • 首先,将句子 x\mathbf xx 中的每个单词表示为一个向量,其中包括 单词的嵌入和字符的嵌入。字符嵌入是随机初始化的。词嵌入通常是从一个预先训练的词嵌入文件导入的。所有的嵌入将在训练过程中进行微调。
  • 第二,BiLSTM-CRF模型的输入是这些嵌入输出是句子 x\mathbf xx 中的单词的预测标签

2.1. BiLSTM层输出

  • 虽然不需要知道BiLSTM层的细节,但是为了更容易的理解CRF层,我们需要知道BiLSTM层输出的意义是什么。
  • 上图说明BiLSTM层的输出是每个标签的分数。例如,对于 w0w_0w0, BiLSTM节点的输出为1.5 (B-Person)、0.9 (I-Person)、0.1 (B-Organization)、0.08 (I-Organization)和0.05 (O),这些分数将作为 CRF层的输入
  • 然后,将BiLSTM层预测的所有分数输入CRF层。在CRF层中,选择预测得分最高的标签序列作为最佳答案。

2.2. 如果没有CRF层会怎么样

  • 你可能已经发现,即使没有CRF层,也就是说,我们可以训练一个BiLSTM命名实体识别模型,如下图所示。
  • 因为每个单词的BiLSTM的输出是标签分数。我们可以选择每个单词得分最高的标签
  • 例如,对于w0w_0w0,“B-Person”得分最高(1.5),因此我们可以选择“B-Person”作为其最佳预测标签。同样,我们可以为w0w_0w0选择“I-Person”,为w2w_2w2选择“O”,为w3w_3w3选择“B-Organization”,为w4w_4w4选择“O”。
  • 虽然在这个例子中我们可以得到正确的句子 x\mathbf xx的标签,但是并不总是这样。再试一下下面图片中的例子。
  • 显然,这次的输出是无效的,“I-Organization I-Person”和“B-Organization I-Person”。

2.3. CRF层可以从训练数据中学到约束

  • CRF层可以向最终的预测标签添加一些约束,以确保它们是有效的。这些约束可以由CRF层在训练过程中从训练数据集自动学习。
  • 约束条件可以是:
  • 句子中第一个单词的标签应该以“B-”或“O”开头,而不是“I-”
  • “B-label1 I-label2 I-label3 I-…”,在这个模式中,label1、label2、label3…应该是相同的命名实体标签。例如,“B-Person I-Person”是有效的,但是“B-Person I-Organization”是无效的。
  • “O I-label”无效。一个命名实体的第一个标签应该以“B-”而不是“I-”开头,换句话说,有效的模式应该是“O B-label”
  • 有了这些有用的约束,无效预测标签序列的数量将显著减少。

三. CRF层

  • 下面了解为什么CRF层可以学习这些约束。 在CRF层的损失函数中,我们有两种类型的分数。这两个分数是CRF层的关键概念。

3.1. 发射(Emission)分数

  • 第一个是emission分数。这些emission分数来自BiLSTM层。例如,如下图所示,标记为B-Person的 w0w_0w0 的分数为1.5。
  • 为了方便起见,我们将 给每个标签一个索引号,如下表所示。
Labelindex
B-Person0
I-Person1
Organization2
I-Organization3
O4
  • 我们用 xiyjx_{iy_j}xiyj来表示emission分数iii 是word的索引,yjy_jyj 是label的索引。如上一节图中所示,xi=1,yj=2=xw1,B−Organization=0.1x_{i=1, y_j=2}=x_{w_1, B-Organization}=0.1xi=1,yj=2=xw1,BOrganization=0.1,即w1w_1w1作为B-Organization的得分为0.1。

3.2. 转移(Transition)分数

  • 转移概率矩阵(Transition Probability Matrix):矩阵各元素都是非负的,并且各行元素之和等于 111,各元素用概率表示,在一定条件下是互相转移的,故称为转移概率矩阵。P(k)P^{(k)}P(k) 表示 kkk 步转移概率矩阵。转移概率矩阵有如下特征:
  • 0≤Pij≤10 \leq P_{ij} \leq 10Pij1,各元素值都是处于0到1之前。
  • ∑j=1nPij=1\displaystyle\sum^{n}_{j=1}P_{ij}=1j=1nPij=1,即矩阵中每一行转移概率之和等于1。
  • 我们使用 tyiyjt_{y_iy_j}tyiyj 来表示transition分数。例如,tB−Person,I−Person=0.9t_{B-Person, I-Person}=0.9tBPerson,IPerson=0.9 表示标签的transition,B−Person→I−PersonB-Person \rightarrow I-PersonBPersonIPerson 得分为0.9。因此,我们有一个transition得分矩阵,它存储了所有标签之间的所有得分。
  • 为了使transition评分矩阵更健壮,我们将添加另外两个标签,START和END。START是指一个句子的开头,而不是第一个单词。END表示句子的结尾。
  • 下面是一个transition得分矩阵的例子,包括额外添加的START和END标签。
  • 如上表所示,我们可以发现transition矩阵已经学习了一些有用的约束。
  • 句子中第一个单词的标签应该以“B-”或“O”开头,而不是“I-”开头 (从“START”到“I- person或I- organization”的transition分数非常低)
  • “B-label1 I-label2 I-label3 I-…”,在这个模式中,label1、label2、label3…应该是相同的命名实体标签。例如,“B-Person I-Person”是有效的,但是“B-Person I-Organization”是无效的。(例如,从“B-Organization”到“I-Person”的分数只有0.0003,比其他分数低很多)
  • “O I-label”无效。一个被命名实体的第一个标签应该以“B-”而不是“I-”开头,换句话说,有效的模式应该是“O B-label”(同样,tO,I−Persont_{O, I-P e r s o n}tO,IPerson的分数非常小)
  • 你可能想问一个关于矩阵的问题。在哪里或如何得到transition矩阵?
  • 实际上,该矩阵是BiLSTM-CRF 模型的一个参数。在训练模型之前,可以随机初始化矩阵中的所有transition分数。所有的随机分数将在你的训练过程中自动更新。换句话说,CRF层可以自己学习这些约束。我们不需要手动构建矩阵。随着训练迭代次数的增加,分数会逐渐趋于合理。

3.3. CRF损失函数

  • CRF损失函数由 真实路径得分所有可能路径的总得分 组成。在所有可能的路径中,真实路径的得分应该是最高的。 例如,如果我们的数据集中有如下表所示的这些标签:
  • 例如,如果我们的数据集中有如下表所示的这些标签:
  • 我们还是有一个5个单词的句子。可能的路径是:
  • ①START B-Person B-Person B-Person B-Person B-Person END
  • ②START B-Person I-Person B-Person B-Person B-Person END
  • 101010 START B-Person I-Person O B-Organization O END
  • NNN
  • 假设每条可能的路径都有一个分数 PiP_iPi,并且总共有NNN条可能的路径,所有路径的总分数是 Ptotal =P1+P2+…+PN=eS1+eS2+…+eSNP_{\text {total }}=P_1+P_2+\ldots+P_N=e^{S_1}+e^{S_2}+\ldots+e^{S_N}Ptotal =P1+P2++PN=eS1+eS2++eSN。(在第3.4节中,我们将解释如何计算,你也可以把它当作这条路径的分数)
  • 如果我们说第10条路径是真正的路径,换句话说,第10条路径是我们的训练数据集提供的黄金标准标签。在所有可能的路径中,得分 P10P_{10}P10 应该是百分比最大的。
  • 在训练过程中,我们的BiLSTM-CRF模型的参数值将会一次又一次的更新,以保持增加真实路径的分数百分比。
    LossFunction=PRealPathP1+P2+…+PN(1)LossFunction=\frac{P_{RealPath}}{P_1+P_2+\ldots+P_N} \tag{1} LossFunction=P1+P2++PNPRealPath(1)
  • 现在的问题是①如何定义一个路径的分数?②如何计算所有可能路径的总分?③当我们计算总分时,我们需要列出所有可能的路径吗?(这个问题的答案是否定的)

3.4. 实际路径得分

  • 在3.3节中,我们假设每条可能的路径都有一个得分,并且有N条可能的路径,所有路径的总得分为 Ptotal=P1+P2+…+PN=eS1+eS2+…+eSNP_{total}=P_1+P_2+\ldots+P_N=e^{S_1}+e^{S_2}+\ldots+e^{S_N}Ptotal=P1+P2++PN=eS1+eS2++eSN
  • 显然,在所有可能的路径中,一定有一条是真实路径。对于这个例子来说,前面中句子的实际路径是 “START B-Person I-Person O B-Organization O END”。其他的是不正确的,如“START B-Person B-Organization O I-Person I-Person B-Person”。eSie^{S_i}eSi是第iii条路径的得分
  • 在训练过程中,CRF损失函数只需要两个分数: 真实路径的分数和所有可能路径的总分数。所有可能路径的分数中,真实路径分数所占的比例会逐渐增加。
  • 计算实际路径分数eSie^{S_i}eSi非常简单。这里我们主要关注的是Si{S_i}Si的计算。
  • 选取真实路径“START B-Person I-Person O B-Organization O END”,我们以前用过,例如:
  • ①我们有一个5个单词的句子,w1,w2,w3,w4,w5w_1,w_2,w_3,w_4, w_5w1,w2,w3,w4,w5
  • ②我们增加了两个额外的单词来表示一个句子的开始和结束,w0,w6w_0,w_6w0,w6
  • Si{S_i}Si 由两部分组成:Si=S_i=Si= EmissionScore +++ TransitionScore
  • Emission得分:
    EmissionScore =x0,START+x1,B−Person+x2,I−Person+x3,O+x4,B−Organization+x5,O+x6,END(2)\text { EmissionScore }=\\ x_{0, START}+x_{1, B-Person}+x_{2, I-Person}+x_{3, O}+x_{4, B-Organization }+x_{5, O}+x_{6, E N D}\tag{2}  EmissionScore =x0,START+x1,BPerson+x2,IPerson+x3,O+x4,BOrganization+x5,O+x6,END(2)
  • xindex,labelx_{index, label}xindex,label 是第index个单词被label标记的分数
  • 这些得分x1,B−Persan;x2,I−Person⁡;x3,O;x4,B−Organization;x5,Ox_{1, B-P e r s a n};x_{2, I-P e r \operatorname{son}}; x_{3, O} ;x_{4, B-Organization}; x_{5, O}x1,BPersanx2,IPersonx3,Ox4,BOrganizationx5,O 来自之前的BiLSTM输出。
  • 对于x0,STARTx_{0, START}x0,STARTx6,ENDx_{6, E N D}x6,END,我们可以把它们设为0。
  • Transition得分:
    TransitionScore =tSTART−>B−Person+tB−Person−>I−Person+tI−Person−>O+tO−>B−Orgnization+tB−Orgnization−>O+tO−>END(3)\text { TransitionScore }= \\t_{S T A R T->B- Person}+t_{B-Person->I-Person} +t_{I-Person-> O}+\\ t_{O->B-Orgnization}+t_{B-Orgnization->O}+t_{O-> END}\tag{3}  TransitionScore =tSTART>BPerson+tBPerson>IPerson+tIPerson>O+tO>BOrgnization+tBOrgnization>O+tO>END(3)
  • 综上所述,现在我们可以计算出Si{S_i}Si以及路径得分eSie^{S_i}eSi

3.5. 所有可能的路径的得分

  • 如何逐步计算一个toy例子一个句子的所有可能的路径的总分。
  • 在上一节中,我们学习了如何计算一个路径(即)的标签路径得分。到目前为止,我们还有一个需要解决的问题,就是如何得到所有路径的总分(Ptotal=P1+P2+…+PN=eS1+eS2+…+eSNP_{total}=P_1+P_2+\ldots+P_N=e^{S_1}+e^{S_2}+\ldots+e^{S_N}Ptotal=P1+P2++PN=eS1+eS2++eSN)。
  • 衡量总分最简单的方法是:列举所有可能的路径并将它们的分数相加。是的,你可以用这种方法计算总分。然而,这是非常低效的。训练的时间将是难以忍受的。
  • 在探索以下内容之前,我建议你先准备一张白纸和一支笔,并按照示例中列出的步骤进行操作。我相信这将有助于你更好地理解算法的细节。此外,你应该知道如何用你喜欢的编程语言实现它。

3.5.1. 步骤1: 回想一下CRF损失函数

  • 在3.3中,我们将CRF损失函数定义为:
    LossFunction=PRealPathP1+P2+…+PNLossFunction=\frac{P_{RealPath}}{P_1+P_2+\ldots+P_N} LossFunction=P1+P2++PNPRealPath
  • 现在我们 把loss函数变成log loss函数:
    LogLossFunction=log⁡PRealPathP1+P2+…+PN(4)LogLossFunction=\log\frac{P_{RealPath}}{P_1+P_2+\ldots+P_N}\tag{4} LogLossFunction=logP1+P2++PNPRealPath(4)
  • 因为当我们训练一个模型时,通常我们的目标是 最小化 我们的损失函数,我们加上一个负号:

    LogLossFunction=−log⁡PRealPathP1+P2+…+PN=−log⁡eSRealPatheS1+eS2+…+eSN这里根据前面的定义=−[log⁡(eSRealPath)−log⁡(eS1+eS2+…+eSN)]=−[SRealPath−log⁡(eS1+eS2+…+eSN)]=−[∑i=1Nxiyi+∑i=1N−1tyiyi+1−log⁡(eS1+eS2+…+eSN)]前面两项就是真实(5)\begin{aligned}LogLossFunction & =-\log \frac{P_{RealPath}}{P_1+P_2+\ldots+P_N} \\ & =-\log \frac{e^{S_{RealPath}}}{e^{S_1}+e^{S_2}+\ldots+e^{S_N}} \text{这里根据前面的定义}\\ & =-\left[\log \left(e^{S_{RealPath}}\right)-\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)\right] \\ & =-\left[S_{RealPath}-\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)\right] \\ & =-\left[\sum_{i=1}^N x_{i y i}+\sum_{i=1}^{N-1} t_{y_i y_{i+1}}-\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)\right] \text{前面两项就是真实} \end{aligned}\tag{5} LogLossFunction=logP1+P2++PNPRealPath=logeS1+eS2++eSNeSRealPath这里根据前面的定义=[log(eSRealPath)log(eS1+eS2++eSN)]=[SRealPathlog(eS1+eS2++eSN)]=[i=1Nxiyi+i=1N1tyiyi+1log(eS1+eS2++eSN)]前面两项就是真实(5)
  • 在上一节中,我们已经知道如何计算实际路径得分,现在我们需要找到一个有效的解决方案来计算log⁡(eS1+eS2+…+eSN)\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)log(eS1+eS2++eSN)

3.5.2. 步骤2: 回忆一下Emission和Transition得分

  • 为了简化,我们假设我们从这个句子中训练我们的模型,它的 长度只有3:
    x=[w0,w1,w2]\mathbf x=[w_0, w_1, w_2]x=[w0,w1,w2]
  • 此外,在我们的数据集中,我们有 两个标签:
    LabelSet={l1,l2}LabelSet=\{l_1, l_2\}LabelSet={l1,l2}
  • 我们还有Bi-LSTM层 输出的Emission分数: xijx_{ij}xij表示 wiw_iwi 被标记为 ljl_jlj 的得分。参考3.1
l1l_1l1l2l_2l2
w0w_0w0x01x_{01}x01x02x_{02}x02
w1w_1w1x11x_{11}x11x12x_{12}x12
w2w_2w2x21x_{21}x21x22x_{22}x22
  • 此外,Transition分数来自CRF层:tijt_{ij}tij表示标签 iii 到标签 jjj 的Transition得分。参考3.2
l1l_1l1l2l_2l2
l1l_1l1t11t_{11}t11t12t_{12}t12
l2l_2l2t21t_{21}t21t22t_{22}t22

3.5.3. 步骤3: 开始战斗(准备好纸笔)

  • 记住:我们的目标是: log⁡(eS1+eS2+…+eSN)\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)log(eS1+eS2++eSN)
  • 这个过程就是分数的累加。其思想与动态规划相似(如果你不知道什么是动态编程,也可以继续阅读本文)。我将逐步解释这个例子。但我强烈建议你学习动态规划算法。
  • 简而言之,计算 w0w_0w0 的所有可能路径的总分。然后,我们用总分来计算 w0→w1w_0→w_1w0w1。最后,我们使用最新的总分来计算w0→w1→w2w_0→w_1→w_2w0w1w2。我们需要的是最后的总分。
  • 在接下来的步骤中,你将看到两个变量:obs和previous。previous存储前面步骤的最终结果。obs表示当前单词的信息。


  • 实际上,第二次迭代已经完成。如果有人想知道如何计算所有可能路径的总分 (label1→label1, label1→label2, label2→label1, label2→label2),从 w0到w1w_0到w_1w0w1,可以做如下计算。
  • 我们使用新的previous中的元素:TotalScore⁡(w0→w1)=log⁡(eprevious [0]+eprevious [1])=log⁡(elog⁡(ex01+x11+t11+ex02+x11+t21)+elog⁡(ex01+x12+t12+ex02+x12+t22))=log⁡(ex01+x11+t11+ex02+x11+t21+ex01+x12+t12+ex02+x12+t22)\begin{aligned} & \operatorname{TotalScore}\left(w_0 \rightarrow w_1\right) \\ & =\log \left(e^{\text {previous }[0]}+e^{\text {previous }[1]}\right) \\ & =\log \left(e^{\log \left(e^{x_{01}+x_{11}+t_{11}}+e^{x_{02}+x_{11}+t_{21}}\right)}+e^{\log \left(e^{x 01+x 12+t 12}+e^{x 02+x 12+t_{22}}\right)}\right) \\ & =\log \left(e^{x_{01}+x_{11}+t_{11}}+e^{x_{02}+x_{11}+t_{21}}+e^{x_{01}+x_{12}+t_{12}}+e^{x_{02}+x_{12}+t_{22}}\right) &\end{aligned}TotalScore(w0w1)=log(eprevious [0]+eprevious [1])=log(elog(ex01+x11+t11+ex02+x11+t21)+elog(ex01+x12+t12+ex02+x12+t22))=log(ex01+x11+t11+ex02+x11+t21+ex01+x12+t12+ex02+x12+t22)
  • 你发现了吗?这正是我们的目标:log⁡(eS1+eS2+…+eSN)\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)log(eS1+eS2++eSN)
  • 在这个等式中,我们可以看到:
    S1=x01+x11+t11(label1→label1)S2=x02+x11+t21(label2→label1)S3=x01+x12+t12(label1→label2)S4=x02+x12+t22(label2→label2)\begin{aligned} & S_1=x_{01}+x_{11}+t_{11}\left(\text {label}_1 \rightarrow \text {label}_1\right) \\ & S_2=x_{02}+x_{11}+t_{21}\left(\text {label}_2 \rightarrow \text {label}_1\right) \\ & S_3=x_{01}+x_{12}+t_{12}\left(\text {label}_1 \rightarrow \text {label}_2\right) \\ & S_4=x_{02}+x_{12}+t_{22}\left(\text {label}_2 \rightarrow \text {label}_2\right) \end{aligned} S1=x01+x11+t11(label1label1)S2=x02+x11+t21(label2label1)S3=x01+x12+t12(label1label2)S4=x02+x12+t22(label2label2)


  • 恭喜!
  • 我们达到了目标,log⁡(eS1+eS2+…+eSN)\log \left(e^{S_1}+e^{S_2}+\ldots+e^{S_N}\right)log(eS1+eS2++eSN),我们的toy句子有三个单词,label set有两个label,所以一共应该有8种可能的label path虽然你发现这个过程相当复杂,但是实现这个算法要容易得多。使用计算机的优点之一是可以完成一些重复性的工作。现在你可以自己实现CRF损失函数,并开始训练自己的模型。

  • 详细可以参考该链接:BiLSTM上的CRF,用命名实体识别任务来解释CRF(2)损失函数

3.6. 为新的句子推理标签

  • 在前面的章节中,我们学习了BiLSTM-CRF模型的结构和CRF损失函数的细节。你可以通过各种开源框架(Keras、Chainer、TensorFlow等)实现自己的BiLSTM-CRF模型。最重要的事情之一是模型的反向传播是在这些框架上自动计算的,因此你不需要自己实现反向传播来训练你的模型(即计算梯度和更新参数)。此外,一些框架已经实现了CRF层,因此将CRF层与你自己的模型结合起来非常容易,只需添加一行代码即可。
  • 这部分直接参考文章:BiLSTM上的CRF,用命名实体识别任务来解释CRF(3)推理

四. 参考文献

  • https://createmomo.github.io/2017/09/12/CRF_Layer_on_the_Top_of_BiLSTM_1/
  • https://createmomo.github.io/2017/10/08/CRF-Layer-on-the-Top-of-BiLSTM-3/
  • 详细可以参考该链接:BiLSTM上的CRF,用命名实体识别任务来解释CRF(2)损失函数
  • https://github.com/createmomo/CRF-Layer-on-the-Top-of-BiLSTM

http://www.ppmy.cn/news/3831.html

相关文章

2022世界杯感悟

前言: 世界杯转眼之间已经到了尾声,总共48场比赛,也只剩下了最后的两场。 都说这一届是诸神黄昏,再过三天,也就到了真正封神的时候。 回想2002 回想第一次看世界杯,恰逢2002年中国队首次挤进世界杯&…

Android 相机预览 横屏竖屏 -- 显示

相机在设备上处于固定位置,无论设备是否 是手机、平板电脑或计算机。当设备方向更改时, 相机方向更改。常见的布局显示比率是 4:3。 对于前置摄像头,图像缓冲区逆时针旋转(从 传感器的自然方向)对于后置摄像头&#xf…

SpringBoot应用瘦身记,兼从maven迁移到gradle手记

目录前言步骤选择合适的JDK版本从maven切换到gradle运行thinjar问题参考前言 线上的SpringBoot应用已经达到350M,每次更新都花费半小时,虽然每天只更新一次。于是决定来一次瘦身。但是为了更方便的打包,例如既能打出fatjar,也能打…

音视频- iOS使用metal渲染图像(一)

概要 本文主要总结一下Metal的基本使用,用来渲染拍照的到的图像,其中涉及到的有UIKit中的MTKView,Metal中负责渲染的几个类,使用MSL(Metal Shading Language)编写着色器,最终将图片渲染出来…

三面美团 Java 岗,HR 现场直接发 offer,他是横着走出来的

前情提要 这是一个发生在我朋友身上的真实事情: 这里就叫他程序员 Y 吧。 程序员 Y 工作不到两年,周末在朋友圈发了个喜报,准备入职美团。 之后,我就带着祝福跟 Y 聊了许久,聊天的内容就是具体了解一下他面试的过程…

Python实现批量采集美女视*频 <无水印>

前言 大家早好、午好、晚好吖 ❤ ~ 我给大家准备了一些资料,包括: 2022最新Python视频教程、Python电子书10个G (涵盖基础、爬虫、数据分析、web开发、机器学习、人工智能、面试题)、Python学习路线图等等 直接在文末名片自取即可&#x…

python大作业高分项目--射击闯关游戏

项目功能: 地图编辑器:可以实现玩家自己定义每一关卡的样式和难易程度 运行界面:实现了玩家的移动,跳跃,发射子弹,投掷手雷,以及敌人的AL(移动,发射子弹,扔…

【大数据技术Hadoop+Spark】MapReduce概要、思想、编程模型组件、工作原理详解(超详细)

MapReduce是Hadoop系统核心组件之一,它是一种可用于大数据并行处理的计算模型、框架和平台,主要解决海量数据的计算,是目前分布式计算模型中应用较为广泛的一种。 一、MapReduce核心思想 MapReduce的核心思想是“分而治之”。所谓“分而治之…