条件随机场是一种无向图模型,与深度网络相比具有许多优势。因此,现在许多研究人员将条件随机场(CRF)与深度网络相结合,以获得更稳健和可解释的模型。本文结合PyTorch从基本概率定义到模型实现,直观地介绍了CRF的基本概念,有助于读者进一步理解完整的理论。假设我们有两个相同的骰子,但其中一个是公平的,每个点出现的概率是相同的;另一个骰子被篡改过,数字6出现的概率是80%,而数字1-5出现的概率是4%。如果我给你一个15次掷骰子的序列,你能预测我每次掷骰子时使用的是哪个骰子吗?为了高精度,一个简单的模型是,每当出现“6”时,我们预测使用有偏向的骰子,当出现其他数字时,我们预测使用公平的骰子。事实上,这个简单的规则是您可以做出的最佳预测,前提是我们在每次掷骰时均等概率地使用其中一个骰子。但是,想象一个情况:如果在使用公平骰子后,下一次使用有偏骰子的概率是90%怎么办?如果下一次掷出“3”,上述模型会预测我们使用了公平骰子,但实际上我们使用有偏骰子是更有可能的选择。我们可以通过贝叶斯定理来验证这个说法:其中随机变量y_i是第i次掷出的骰子类型,x_i是第i次掷出的点数。我们得出结论,在每一步做出最有可能的选择只是一种可能的策略,因为我们可能同时选择其他骰子。更有可能是我之前选择的骰子影响了我未来的选择。为了成功预测,您必须考虑每次抛出之间的相互依赖性。条件随机场(CRF)是用于预测与输入序列对应的标记序列的标准模型。有很多关于条件随机场的教程,但我看到的教程属于以下两种情况之一:1)都是理论,但没有说明如何实现它们2)为复杂的机器学习问题编写的代码缺乏解释不能让读者对代码有一个直观的理解。这些作者之所以选择编写全部是理论性的或包含可读性差的代码的教程,是因为条件随机场属于更广泛和更深入的主题“概率图模型”。因此,深入介绍其理论和实现可能需要写一本书而不是博客文章,这也使得学习条件随机场比原本需要的更加困难。本教程的目标是涵盖足够多的理论,以便您对CRF有一个基本的印象。此外,我们将通过一个简单的问题向您展示如何实现条件随机场,您可以在自己的笔记本电脑上重现该问题。这可能会给您带来使这个简单的条件随机场示例适应更复杂问题所需的直觉。1.理论我们对理论的讨论将分为三个部分:1)指定模型参数2)如何估计这些参数3)使用这些参数进行预测,这三类适用于任何统计机器学习模型。所以从这个意义上说,CRF没有什么特别之处,但这并不意味着CRF就像逻辑回归模型一样简单。一旦我们处理一系列预测而不是单个预测,我们会发现事情变得更加复杂。1.指定模型参数在这个简单的问题中,我们唯一需要担心的参数是与从一次抛掷到下一次抛掷的过渡相关的分布。我们有六个状态要考虑,所以我们将它们存储在一个2*3的“转移矩阵”中。***列对应于使用公平骰子(行***中的值)使用公平骰子转换到当前状态的概率或成本,或者转换到有偏差骰子状态的概率(valuesinrow2value)”。因此,***列中的第一个元素编码了预测下一次掷骰子的概率,假设我为这次掷骰使用了公平骰子。如果数据显示它我不太可能一直使用公平的骰子,模型了解到这个概率应该很低,反之亦然。同样的逻辑也适用于第二列。矩阵的第一列和第二列假设我们知道使用了哪个骰子在前一掷中,所以我们必须将第一次掷作为特例。我们将在第三列中存储相应的概率。2.参数估计假设给定一组掷X**及其对应的骰子标签Y.我们会找到最小化neg的转移矩阵T整个训练数据的对数似然。我将向您展示一系列单独掷骰子的可能性和负对数似然。要在整个数据集上获得它,您需要对所有序列进行平均。P(x_i|y_i)是在给定当前骰子标签的情况下观察到给定骰子掷骰的概率。例如,如果y_i是公平骰子,则P(x_i|y_i)=1/6。另一项T(y_i|y_{i-1})是从之前的骰子标签切换到当前投资标签的概率,我们可以直接从转移矩阵中读取。请注意分母中我们如何对所有可能标签y'的序列求和。在二元分类问题的传统逻辑回归中,分母中有两项。但是现在,我们正在处理标签序列,对于长度为15的序列,有2^15种可能的标签序列,因此分母项很大。CRF的“秘密武器”是它通过假设当前骰子标签仅依赖于先前的骰子标签来有效地计算这个巨大的总和。这个秘诀被称为“前向-后向算法”。对该算法的深入讨论超出了本博文的范围,因此此处不提供详细说明。3.序列预测一旦我们估计了我们的转换矩阵,我们就可以使用它来找到给定投掷序列的最可能的骰子标签序列。最简单的方法是计算所有可能序列的可能性,但即使对于中等长度的序列,这也非常困难。正如我们在参数估计中所做的那样,我们将不得不使用一种特殊的算法来有效地搜索最有可能的序列。该算法与“前向-后向算法”非常相似,被称为“维特比算法”。2.CodePyTorch是为在Python语言环境下训练深度学习模型而编写的深度学习库。虽然我们在这里不是做深度学习,但是PyTorch的自动微分库将帮助我们通过梯度下降训练CRF模型,而不需要我们手动计算任何梯度。使用PyTorch迫使我们实现“前向-后向算法”的前向部分以及“Viterbi算法”,这比我们直接使用用专门的条件随机场构建的Python包更有指导意义。首先,让我们想象一下结果应该是什么样子。我们需要一种方法来计算给定骰子标签的任意掷骰序列的对数似然性。这是一种可能的方式:defneg_log_likelihood(self,rolls,states):"""Computeneglog-likelihoodforagivesequence。输入:rolls:numpyarray,dim[1,n_rolls]。Integer0-5showingvalueondice.states:numpyarray,dim[1,n_rolls]。Integer0,1.0ifdiceisfair."""loglikelihoods=self._data_to_likelihood(rolls)states=torch.LongTensor(states)sequence_loglik=self._compute_likelihood_numerator(loglikelihoods,states)denominator=self._compute_likelihood_denominator(loglikelihoods)returndenominator-sequence_loglik这个方法做了三个主要的事情:1)将骰子的值映射到一个似然函数2)计算对数似然项的分子3)计算对数似然项的分母。让我们首先处理“_data_to_likelihood”方法,它帮助我们执行上面提到的步骤1。我们要做的是创建一个6*2维度的矩阵,其中第一列是用公平骰子掷出1-6的可能性,第二列是用有偏骰子掷出1-6的可能性。自然。本题矩阵的形式为:array([[-1.79175947,-3.21887582],[-1.79175947,-3.21887582],[-1.79175947,-3.21887582],[-1.79175947,-3.21887582],[-1.7918-75.28],[-1.79175947,-0.22314355]])现在,如果我们看到一个值为“4”的折腾,我们可以直接选择矩阵中的第四行。该向量中的第一个元素是用公平骰子得到“4”的对数??似然log(1/6),第二个元素是用有偏骰子得到“4”的对数??似然log(1/6)。0.04)。代码如下:def_data_to_likelihood(self,rolls):"""Convertsanumpyarrayofrolls(integers)tolog-likelihood.self.loglikeligoodisamatrixof6x2inourcase.Inputisone[1,n_rolls]"""log_likelihoods=self.loglikelihood[rolls]returnVariable_atliable(torch.log.likelihood),requires_grad=False)接下来,我们将编写计算对数似然分子和分母的方法。def_compute_likelihood_numerator(self,loglikelihoods,states):"""Computesnumeratoroflikelihoodfunctionforagivensequence.We'lliterateoverthesequenceofstatesandcomputethesumoftherelevanttransitioncostwiththeloglikelihoodoftheobservedroll.Input:loglikelihoods:torchVariable.Matrixofn_obsxn_states.i,jentryisloglikelihoodofobservingrolligivenstatejstates:sequenceoflabelsOutput:score:torchVariable.Scoreofassignment."""prev_state=self.n_statesscore=Variable(torch.Tensor([0]))forindex,stateinenumerate(states):score+=self.transition[state,prev_state]+loglikelihoods[index,state]prev_state=statereturnscoredef_compute_likelihood_denominator(self,loglikelihoods):"""实现前向-后向算法的前向传递。我们使用递归关系高效地遍历所有可能的状态:alpha_t(j)=\sum_ialpha_{t-1}(i)*L(x_t|y_t)*C(y_t|y{t-1}=i)输入:loglikelihoods:torchVariable.Sameinputas_compute_likelihood_numerator.Thisalgorithmefficientlyloopsoverallpossiblestatesequencessonootherimputisneeded.Output:torchVariable."""#Storesthecurrentvalueofalphaattimesteptprev_alpha=self.transition[:,self.n_states]+loglikelihoods[0].view(1,-1)forrollinloglikelihoods[1:]:alpha_t=[]#Loopoverallpossiblestatesrangeinforne(self_t_statesinforne).n_states):#Computeallpossiblecostsofttransitioningtonext_statefeature_function=self.transition[next_state,:self.n_states].view(1,-1)+\roll[next_state].view(1,-1).expand(1,self.n_states)alpha_t_next_state=prev_alpha+feature_functionalalpha_t.append(self.log_sum_exp(alpha_t_next_state))prev_alpha=torch.cat(alpha_t).view(1,-1)returnself.log_sum_exp(prev_alpha)就是这样!我们现在有了学习转换所需的东西矩阵所有代码但是,如果我们想要在训练完模型之后作出预测,我们就必须编写维特比算法:def_viterbi_algorithm(self,loglikelihoods):"""ImplementsViterbialgorithmforfindingmostlikelysequenceoflabels.Verysimilarto_compute_likelihood_denominatorbutnowwetakethemaximumoverthepreviousstatesasopposedtothesum.Input:loglikelihoods:torchVariable.Sameinputas_compute_likelihood_denominator.Output:tuple.Firstentryisthemostlikelysequenceoflabels.Secondistheloglikelihoodofthissequence."""argmaxes=[]#prev_deltawillstorethecurrentscoreofthesequenceforeachstateprev_delta=self.transition[:,self.n_states].view(1,-1)+\loglikelihoods[0].view(1,-1)forrollinloglikelihoods[1:]:local_argmaxes=[]next_delta=[]fornext_stateinrange(self.n_states):feature_function=self.transition[next_state,:self.n_states].view(1,-1)+\roll.view(1,-1)+\prev_deltamost_likely_state=self.argmax(feature_function)score=feature_function[0][most_likely_state]next_delta.append(score)local_argmaxes.append(most_likely_state)prev_delta=torch.cat(next_delta).view(1,-1)argmaxes.append(local_argmaxes)final_state=self.argmax(prev_delta)final_score=prev_delta[0][final_state]path_list=[final_state]#Backtrackthroughtheargmaxestofindmostlikelystateforstatesinreversed(argmaxes):final_state=states[final_state]path_list.append(final_state)returnnp.array(path_list),final_score我们还有很多代码要实现,但这里我只包括我们在理论部分讨论的核心功能。模型评估我使用了从以下概率模拟中获得的数据并评估了模型:P(序列中的第一个骰子是公平的骰子)=0.5P(当前是公平的骰子|上次是公平的骰子)=0.8P(CurrentBiasedDice|LastBiasedDice)=0.35请查看我的笔记本,看看我是如何生成CRF模型并对其进行训练的。笔记本地址:https://github.com/freddyalfonsoboulton/crf_tutorial/blob/master/crf_demo.ipynb我们要做的第一件事就是看看估计的转移矩阵长什么样。该模型了解到,如果上一次使用了公平的骰子,那么这次它更有可能使用公平的骰子滚动(-1.38<-0.87)。该模型还了解到,在使用有偏向的骰子后,我们更有可能使用公平的骰子,但这与使用偏向掷骰的概率(-1.38<-0.87)没有太大区别。该模型在第一次掷骰时将相同的成本(0.51~-0.54)分配给两个骰子。array([[-0.86563134,-0.40748784,-0.54984874],[-1.3820231,-0.59524935,-0.516026]],dtype=float32)接下来,让我们看看特定滚动序列的预测结果如何:#observeddicerollsarray([2,3,4,5,5,5,1,5,3,2,5,5,5,3,5])#correspondinglabels.0meansfairarray([0,0,1,1,1,1,0,1,0,0,1,1,1,0,1])#predictionsarray([0,1,0,1,1,1,0,0,1,0,1,1,1,0,0])模型识别出“6”的长序列(实际上在上面的代码中显示为“5”,因为我们从“0”开始)来自有偏的骰子,这是有道理的。请注意,该模型不会将所有“6”分配给有偏见的骰子,就像第八次掷骰的预测一样。这是因为在这个“6”之前,我们非常确定使用了一个公平的骰子(我们掷出一个“2”)并且不太可能从公平的骰子状态过渡到有偏见的骰子状态。我觉得这种错误是可以接受的,所以这个模型是很成功的。结论我向您展示了CRF背后的少量理论,还展示了如何针对简单问题实施CRF。当然,相关知识远比我在这里所能涵盖的要多得多。所以我建议读者多查看相关资源。原文链接:https://towardsdatascience.com/conditional-random-field-tutorial-in-pytorch-ca0d04499463(id:almosthuman2014)”]点此阅读更多本作者好文
