​万字长文详解文本抽取:从算法理论到实践(附“达观杯”官方baseline实现解析及答疑)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
NLP自然语言处理_高级版,每接口累计50万次
全局流量管理 GTM,标准版 1个月
简介: 本文是这两次技术直播的内容总结,包括信息抽取传统算法和前沿算法详解、比赛介绍,以及比赛baseline模型代码分析和改进建议。

文章来源:微信公众号 数据派THU

[ 导读 ]“达观杯”文本智能信息抽取挑战赛已吸引来自中、美、英、法、德等26个国家和地区的2400余名选手参赛,目前仍在火热进行中(点击“阅读原文”进入比赛页面,QQ群见下图或文末二维码)。达观数据目前已经举行过两次围绕比赛的技术直播分享,并开源了baseline模型。本文是这两次技术直播的内容总结,包括信息抽取传统算法和前沿算法详解、比赛介绍,以及比赛baseline模型代码分析和改进建议。

image.png

在前半部分,达观数据的联合创始人高翔详细讲解了自然语言处理中信息抽取算法技术。在后半部分,达观数据的工程师们分享并介绍了“达观杯”文本信息抽取挑战赛的baseline代码以及改进建议。最后,针对参赛选手和其他观众的疑问,三位专家也一一做了解答。

作者介绍:

高翔是达观数据联合创始人,达观数据前端产品组、文本挖掘组总负责人;自然语言处理技术专家,负责文本阅读类产品、搜索引擎、文本挖掘及大数据调度系统的开发工作,在自然语言处理和机器学习等技术方向有着丰富的理论与工程经验。

目录:

第一部分:文本信息抽取详解

第二部分:“达观杯”baseline代码分享

第三部分:问题答疑

第一部分:文本信息抽取详解

文本挖掘简介

下面我们开始介绍一下文本挖掘。下图中,我们可以把人工智能分为三类——图像、文本和语音,达观是一家专注于做文本智能处理的科技公司。文本相对于图像和语言来说更难处理,因为文本数据需要做一些逻辑分析。图像和语音属于感知智能,而文本属于认知智能,所以号称是“人工智能的明珠”,难度很大。

image.png

自然语言处理的任务是什么?简单来说就是让机器知道怎么看、要么写。我们一般把“看”叫自然语言理解(NLU),包括自动化审核、自动文本比对、信息纠错,搜索推荐等等,它可以大幅度减轻人工的负担。自动写作叫自然语言生成(NLG),包括自动填表、生成摘要,文本润色,还有大家看到的“自动生成股市”、“自动生成对联”等等。目前我们主要还是在解决自然语言理解的问题。语言生成因为一些限制,实际落地的效果仍然有待提高的。所以我们今天主要讨论自然语言理解这部分。

image.png

其实自然语言处理的历史非常悠久,甚至出现在“AI”这个概念之前,最早叫“符号主义”。刚开始的时候人们选择了一个很不好的场景:机器翻译。机器翻译是一个难度很大的任务,因为涉及了语义的理解和不同语种语法的规则。所以早期自然语言处理不是很成功。过了20-30年,到上世纪80年代开始,我们使用了语法规则,基于自然语言处理的一些基本原理,再通过人工在这些语法的规则上进行修订,做了一些问答、翻译和搜索方面的尝试。

自然语言处理真正的黄金时期是从上世纪90年代开始,那时候我们搞了统计学,做了很多基于统计机器学习的算法。从下图中我们可以发现,统计模型的效果让自然语言处理的应用领域更加广泛,产生了很大进步。其实在上世纪90年代的时候,自然语言处理已经可以在很多场景表现得很不错了,比之前的技术要先进很多。

image.png

从2006年到现在,深度学习已经开始起步。之前“神经网络”这个概念已经有了,只是当时受限于各种各样的算法和硬件,没法做得很好。但现在各方面都成熟之后,大家发现深度学习是一个神器。其实深度学习最早的时候在图像领域的应用较多,但目前自然语言处理也逐渐开始过渡到深度学习的阶段。尤其是去年像BERT这样的模型出来之后,我们发现自然语言处理的评测经常被屠榜,这说明神经网络非常有效,但也说明数据也很重要,后文中我们会解释数据的重要性。

image.png

我们对比一下人类和计算机之间的差异。其实我们人类短时间内阅读理解文字的能力还不错,但是时间久了很容易遗忘。但计算机基本不会忘,只要硬盘不坏。人脑难以长期记忆,但我们对内容的推理能力比计算机强。因此,我们可以请计算机来做一些比较细节的工作。例如文字比对,我们检查错误要逐字逐句地看,非常累。计算机能做到秒看,却很难做复杂的逻辑和推理。

此外,虽然人类阅读速度很快,但写作速度很慢。大家高考的时候都要留几十分钟来写作。这是因为写的时候,我们手速有限。而且在写的过程中还要进行很多思考。写作本质是把脑中的很多语义信息压缩到一个点,也就是文章的主题。有了主题后我们还要再把作文展开,所以要花很多时间构思大纲、设计章节结构和文章主线,非常耗时。

我们在接受信息时能很快地理解整体,但是难以记住细节。我们看完一个东西立刻能知道它的中心思想。例如,我们浏览了一个企业的信息之后,就能做出“这个企业比较靠谱,愿意投资”的判断。但是企业收入、竞争利润、负债这些具体数字很难全部记清楚。所以人去寻找局部信息的能力和计算机比非常慢。计算机的优点就是找这种局部信息,越细的东西它找得越快。

image.png

什么场景比较适合让计算机去做?基于现阶段的技术,现在大部分场景计算机还是无法取代人。我们可以看到,很多行业,包括法律,包括企业合同、客户意见、产品手册、新闻、问答资料的数据是需要我们亲自来看。虽然这些行业领域不同,但做的事情都类似。审一个企业合同的时候,需要看一些关键的信息,如甲方、乙方,以及这些东西是否合规,总金额是否正确。在法律行业,法官判案时也要看整个案由,包括被告和原告的相关信息,案件的时间、地点等等。这些都是信息抽取,在很多应用场景下都需要信息抽取。无论我们做了什么决策,判断是否投资,是否通过合同,如何进行法律判决,都需要先从文字中提取信息。

其实在一些比较固定的,相对简单,不需要特别复杂的逻辑推理的场景中,机器学习算法已经可以完成信息抽取任务。我们正努力让计算机在这些场景落地,这不仅仅是算法的问题,也是应用的问题。这也是我们一直在思考的问题。

抽取算法概述

现在我们具体讲讲信息抽取的几种最主流的算法。

什么是信息抽取?其实就是从文本中找到指定类型的实体。大家应该听过命名实体识别(NER),其实命名实体识别只是抽取中的一种。广义上的信息抽取,除了命名实体识别之外,还包括关系抽取、事件抽取等。其实在我看来,关系抽取和事件抽取比命名实体识别的应用层次更高级一点。因为这两个抽取同需要做NER,只是在做NER的基础之上,还要做一些其他的工作,来满足场景需求。

我们先从最简单的NER开始。命名实体一般是指人物、地点、机构、时间等内容。现在我们以公司抽取为例详细说明一下。

image.png

如果从历史的角度来说,识别公司的任务就是所谓的“符号主义”任务,简单来说就是穷举所有公司的名称做词典匹配。这样就是一个命名实体。但是,这么做场景其实有限。为什么?因为上市公司的集合是有限的,所以直接拿公司字典可能比训练模型更快。

但是你会发现这种场景并不常见。比如,如果抽取所有公司(不仅限于上市公司)就不能用这种办法,因为公司实在太多了。十年前如果你看到“饿了么”,如果没有上下文,你不会觉得这是一个公司,但因为现在大家经常点“饿了么”,都知道这是一个公司的名字。而且,每天都有大量新公司产生,所以整体的公司是一个没法穷尽的集合。在这种情况下,我们没办法用字典很好地完成绝大多数任务。

image.png

之前我们提到了上下文。那我们现在加入上下文信息,是不是可以知道某个实体是一个公司呢?最直接的方法是通过语法规则来做,例如“A是一家公司”、“B作为一家公司”等等。你会看到这样的一些模板,然后再去分析。如果说得学术/技术一点,相当于把这个任务提炼成一个比较复杂的句法依赖和语法规则。但从代码角度可能会比较简单,比如把模板中间的东西抠掉,然后去做匹配,做完匹配再去做填空,填空的内容就是你要的这些公司。

但这样做也有很大的问题,因为我们语言表述的方法太多了。例如,“我是A公司的”,“我来自B公司”以及很多种其他不同的表述都是一个意思,我们无法穷尽所有的表述方法。甚至周星弛的电影也能增加这种做法的难度。我们以前说“我先走了”,现在会说“我走了先”、“我吃了先”,这其实跟我们传统的语法都不太一样,但现实生活中就有这么多表述。不过,和上面的字典类似,在特定的场合,比如一些特定领域的公文等文书文章,还是有套路或者标准写法,也许可以用这种方法。总的来说这种方法比较简单。

image.png

更高级的是基于统计机器学习的方法,从算法上来说是用序列标注的方式来做。这种方法要求我们标注数据,例如上图中我们标注了一句话:“达观数据是人工智能公司”。现在它会预测“上海的虚拟数据”中的“虚拟数据”也是一家公司。它是怎么做到的?后文会详细介绍。这种做法就跟模板匹配完全不一样了。在图中,可能第一个预测“虚拟数据是人工智能公司”还有模板的性质,但后面两个表述和前面完全不同,所以这种基于统计机器学习的方式有了一定的预测能力。

但问题是什么?它需要两个条件。首先是数据。大部分的机器学习都是监督学习,要做数据标注。而且我们传统机器学习经常要做特征工程。甚至在很多任务中,一个特征工程可能要占到我们项目时间和精力的90%。我们之前参加CIKM评测并拿到冠军的任务中,就耗费了大量时间构建特征。举个例子,我们实际工作中完成文本分类任务的时候,仅仅把文字的长度这个特征加进去,效果一下子提升了很多。这种特征我们很难想到。特征的选择可能有时候还有一定的逻辑推理,但有的时候就是拍脑袋。所以特征工程做好是很难的,需要很多的经验,还需要有扩散性的思维。

此外训练和预测需要很多计算资源。某些机器学习(尤其是传统的机器学习)的训练过程中,特征有时候会特别耗费内存,可能不一定训练得完,所以对机器有一定的限制。当然,现在做深度学习,限制可能是GPU。深度学习相对于传统机器学习,对数据量地要求更高。因为传统的机器学习模型的各种参数没有深度学习这么多。

image.png

虽然深度学习的可解释性经常被人诟病,但也有些模型实际上可以给我们一些解释。尤其是一些基于Attention机制的模型。这里就是一个Attention分类器。图中可以看到它能从句子级别和词级别告诉你,对一个分类模型来说,哪句话最重要,哪个词最重要。这些词和句子都是有权重的。因为有Attention这样的权重,我们就能把它拿出来做可视化。

image.png

所以整体来说还是要通过序列标注来做。上图有一个序列标注的例子:分词。要分词的句子是“它来自达观数据”。我们有一个叫Label Set,也就是标签集。图中我们用的是BMES这个很经典的标签集,这个标签集其实对应的英文Begin、Middle、End、Single,大家一看就知道是什么意思。对于分词来说,每个字可能组成一个词(单字成词),也可能是一个词的开始、的中间或结尾。

上图还可以看到,在分词之外,命名实体我们用另外一个标签集。我们做词性分析可能用不同的标签集。可以看到,不同的标签集可以用来做不同的事情。所以无论是传统的机器学习,还是深度学习,我们都是在解决一个叫做“序列标注”的问题。所以标签集和标注方式都是基础的、几乎是一样的。有什么样不同?后文会具体讨论。

传统抽取算法介绍

其实传统抽取算法有很多,这里会介绍一些大家比较常用,也比较好理解的模型。第一个模型叫生成式模型。生成式模型的一个代表就是隐马尔科夫模型(HMM)。另外一个是判别式模型,代表是条件随机场(CRF)。这两个模型都结合了概率论还有图论的一些内容,也都基于统计机器学习的算法。它们都能根据训练集训练出不同的结果。下面我们详细介绍一下这两个模型。

我人生第一次做序列标注任务的时候,用的就是HMM模型。马尔可夫这个名字一听就像是个数学很厉害的俄国人,但其实HMM模型并不难。大家只要记住两部分内容:两个序列、三个矩阵。如下图所示。我们要做的就就是把这五个部分定义好,整个模型和要解决的问题就定义清楚了。

image.png

首先是观察序列。上图中“他来自达观数据”,就是我们人看得到的观察序列,但它背后隐藏了分词。“他”是一个词,“来自”是一个词,“达观数据”是一个词,这个是我们说“隐藏序列”,没有写到明面上,但需要我们模型预测。怎么预测?下图画了预测模型的示意图。图中,X_1、X_2、X_3就是我们说的隐藏内容,人能看到的是y_1、y_2、y_3、y_4,也就是观察序列。但其实不同状态是可以不停地转换的。比如X_1到X_2之间有一条连线说明X_1和X_2之间可以通过概率a_12做转换;X_2到X_3之间通过概率a_23做转换。所以这个模型其实比链式的HMM还要更复杂一点,因为它有X_2到X_1这样的转换。所有的X都可以转换到y_1、y_2、y_3、y_4这样的观察序列,每对转换关系都有对应的概率。

image.png

这样我们就把模型定义好了。我们只需要求模型的哪几个部分呢?主要是这三个矩阵:初始状态矩阵,发射状态矩阵,以及状态转移矩阵。

第一个是初始状态矩阵。我们现在举的例子都是有序列标注,例如多轮分词。下图是一个真实的多轮分词模型里面的图,这是我们自己训练的一个模型。可以看到,初始状态只可能是S(ingle)或B(egin),因为不可能从代表词结尾的标记开始一个句子。所以我们要从所有的语料中统计,单字词S和多字词B开始的概率是多少。仅仅统计这两个矩阵就可以,因为其他两个标记M(iddle)和E(en)是不可能出现在句首的。图中的概率有负数,是因为经过log和相关处理,从而可以方便后续的计算,但本质的含义还是概率。

image.png

第二个矩阵是发射状态矩阵。什么是发射状态矩阵?简单来说就是我们在分词里每个字变成任何一个标签的概率(如下图所示)。例如“他”这个字如果来自“他来自达观数据”这句话,就是一个单字词S(ingle);但如果在“他”出现在“他们”等多字词里,标签就是B(egin);在“关心你我他”里,“他”的标签可能就是E(end)。所以你会在训练语料看到“他”有不同的标签。发射状态矩阵就是把“他”到每一个标签的概率集合起来。发射状态矩阵非常重要,它说明了每一个字到不同标签的概率。

image.png

第三个是状态转移矩阵。什么是状态转移矩阵?其实状态转移矩阵也是统计出来的,也就是刚才说的X_1和X_2之间的概率。我们训练语料里面已经有了SB、BMME这样的标签。其实我们可以观察到一些现象,例如S(ingle)后面不可能跟E(nd)和M(iddle)。这些就是状态转移矩阵描述的内容,如下图所示。它说明E后面跟着S的概率是多少,E后面跟着B的概率又是多少等等。这些值其实都是从语料库中训练出来的。

image.png

下面讨论两类学习算法:一种是“监督学习”,通过极大似然估计就可以得到这些值,非常好算,简单地说就是统计次数:统计这个标签一共有多少,相关概率又是多少,就可以得出结果了。还有是一个非监督学习Baum-Welch,这个算法我们用得比较少,因为根据我们自己的经验,它的整体效果会比做统计差很多。而且监督学习有个好处是因为有了训练集和相关的数据,所以很容易去查错。

解码算法基本是用Viterbi来做。当然你也可以把当前最好的状态输出来,找到在当前序列下能够输出的最大标签,通过自己的一些解码逻辑(比如B后面一定是M或者E,不可能是S)优化一些内容。但我们经常还是用Viterbi去做整体的解码,取得最优路径的概率。Viterbi解码算法大家一定要掌握,因为后面有有不少算法与它类似。只要把Viterbi学会了,后面的很多东西就很好理解了。

HMM是我个人学的第一个模型,但是我现在基本上不用这个模型。为什么不用?因为它的效果还是相对差一点。但它也有优点。因为做极大似然估计就是简单的统计,速度非常快。所以这个模型的更新可以做到秒级。你做一个数据的修改,跑一遍立刻把数据统计出来,修改矩阵以后很快就对这个模型做一个更新。所以在项目的初始阶段,我们可以快速地用这个方法来做baseline或者动态的修改。尤其在实际业务中,可能客户做了一些修改后他需要实时知道反馈,这时候可以用HMM,虽然可能不能保证有好的效果。

image.png

在实际应用中我们用的最多还是条件随机场(CRF)。因为CRF往往效果更好。下图说明了HMM和CRF的关系是什么,我们可以看到一个HMM是链式传递,但加上一个条件就是我们最常见的链式条件随机场。通用CRF就是下图中右下角的图,但是我们做序列标注的话可能是最下面一行中间的这个图,也就是链式的CRF。它跟上面一行的图的区别是什么?大家可以看到下面一行图中有好多小的黑色正方形,这就是我们说的条件。我们是如何得出条件的?下面我们就来介绍一下如何通过真实训练得到条件。

image.png

我们先看下面这张图。图中nz在词性里表示是一个“其他”类型的实体。这种类型很难归入时间、地点、人物等常见的实体类型,比如“苹果手机”可能就可以算是一个nz。我们把所有不太好分类的实体都归入到nz里。在这里,标签集还是BMES,但是加了一个“O”。标签后面的后缀其实就是类型。刚才提到的“其他”是nz,还可以有其他类型(如地名、时间、机构等)可以用其他字符串表示,比如nr、ns、nt。定义好这套标签集后,我们就开始定义特征函数。

image.png

下图是我们是用CRF++、CRFPP做的特征模板。大家可以看到,图里有U00到U08,最后还有一个字母“B”,B说明它会学习标签间的转移。U00到U08都是特征,U00表示第一个特征,U01是第二个特征。此外还有一个x%,它代表了前面特征的内容。

首先看第一个特征:U00: %X[-3,0]。U00表示把我们要研究的字左边的第三个字作为特征,向量后一个数0表示我们没有添加人工特征。我们把这些拼接起来就是一个最终的特征。

image.png

下图中包括了特征函数的权重(weight)。我们可以看到“U06:径”,这表示当前的字右边第三个字是一个“径”字。我们会给出每个标签的得分。可选的标签就是BEMOS。这里的数字代表得分(不是概率),有正有负。我们最终就是要把训练集所有的数据先通过这个特征模板变成一个特征。对于每个字,都有8个特征,第一个特征就是当前字左边的第三个字,第二个特征是左边第二个字,U03就是当前字本身。

image.png

所以大家可以看到CRF和HMM最大的不同。我们定义了这样一个特征函数(或者特征模板)。我们还可以人工设置一些特征影响特征模板。比如在研究当前字时,如果用了这样的模板,我就知道前三个字和后三个字会对当前这个字的标签的输出产生影响。除此之外,还可以用前一个字和当前字,或者当前字和后一个字的组合作为特征。有了这些特征,我们就要计算特征的结果。这时可以迭代训练模型,CRF使用了L-BFGS来训练。最终训练出来的模型可以告诉我们每个特征值对于不同的标签的值是多少,相当于是一个全局最优的值。

下面这张图代表了标签之间的转移,这跟HMM非常像,也可以算出来。所以CRF最终在一个全局最优的情况下达到了一个最优点。我们可以存储这个最优点情况下每一个特征的值,用来解码。

image.png

CRF的解码较为简单,我们根据当前序列的位置,根据特征的模板生成很多特征函数,直接去查我们的模型,找到其对应的特征函数权重,之后每一个特征函数权重加起来。查到这个特征函数就把相应的权重取出来,加起来,没有查到就是0,就不用去做了,最终有一个得分,这样每一个标签都会有相关的得分。这个字生成的Score会有BEMOS相对应的,最终得到一个图,我们就用Viterbi解码,跟前面一样就能解出来了。

为什么CRF效果好?因为我们可以定义特征模板,包括了很多上下文比较远的特征。CRF的特征是人工选择的,可以选择前两个、前三个,甚至更多,所以可以让模型学到更多上下文,而且是远距离的上下文,辅助我们判断,提升整体效果。但条件随机场需要迭代优化,根据梯度下降的方向去找最优点,所以整体速度相对较慢,算出来的模型也不会小。很多时候必须要筛选或裁剪标签。

以上内容就是HMM和CRF这两个传统的算法。

基于深度学习的抽取算法

经典机器学习的很多算法需要比较强的数学功底,通过数学公式做出优美完整的论证。但现在经典机器学习算法的收益已经没有以前大了。原因如下图所示,图中列出了文本挖掘领域中,经典的机器学习和深度学习的对比。

image.png

最大的区别就是紫色的框:特征工程。其实算法并不多,但特征工程五花八门,包括我们做文本处理时经常遇到的TF-IDF、互信息、信息增益、期望交叉熵等等。其实这些提取特征的方式都有一些科学依据,但很多场景下我们需要靠直觉。特征工程往往占到项目时间的90%。

而深度学习不在乎特征。模型定好之后只管输入,有了输入就能输出一个最好的结果。基本不用改代码的,只需要调参。如果数据小,还需要修改一下过拟合方面的东西就可以了。但是用经典机器学习做特征工程可能要改很多代码才能做出一个非常好的特征,这就是传统机器学习和深度学习最大的区别。

用深度学习做文本处理基本绕不开LSTM。虽然现在有很多模型,但也采用LSTM做baseline。下面是一篇著名的介绍LSTM的文章的截图,建议大家看一下原文。文章中最精华的就是下面四张图,展示了LSTM的工作原理。

第一个步骤是单元状态丢弃(如下图)。图中有两个量x_t和h_t-1。x_t就是当前的输入,h_t-1是上一时刻的隐层的输出。这个公式求出来一个0-1之间的值,决定要留下多少东西。(任何东西乘以0-1其实就是计算要留多少东西,乘以0什么都留不了,乘以1就都留下,乘0.8就留80%。)

image.png

第一步:单元状态丢弃

第二步新信息的选择。当前输入包括上一时刻隐层的输出和当前的输入。这一步骤判断应该留下来多少内容。它还是计算两个系数,一个i_t,这也是一个0-1之间的值。第二个是C_t,表示当前cell的状态。计算完毕后需要把这两个系数的值保存下来。

image.png

第二步:新信息选择

第三步是更新状态。上面一步已经决定可以留下的新内容和老内容。这一步要决定如何组合新老内容。老内容可以乘以第一步计算出的f_t,新内容可以乘以第二步算出来的i_t,然后把新老内容相加,就是最新的状态了。

image.png

第三步:单元状态更新

第四步是得出最后的输出值。Cell不会一股脑输出,而是计算出了系数o_t和状态相关的函数结果相乘后得出输出。

image.png

第四步:确定输出

以上四步定义了LSTM基本的原理。LSTM其实提出来已经很多年了,在很多场景下都经受了考验。所以希望大家一定要把上面介绍的基础原理了解好。

下图显示了基于深度学习的信息抽取技术Bi-LSTM+CRF的原理。这个方法代表了深度学习和传统的机器学习一个很好的结合。传统CRF最大的问题是特征很稀疏,想做一个很好的特征要花费很多时间。我们可能会有几套比较经典的特征,但不一定保证效果最好,特别是训练数据发生变化以后。而词向量和Bi-LSTM可以做很多的特征提取工作。

image.png

为什么要用Bi-LSTM而不是简单的LSTM?举个例子,“华为发布了新一代的麒麟处理X”这句话中,“X”一看就是处理器的“器”。因为我们都知道前文“麒麟处理”后面肯定跟着“器”。类似地,根据“X鲜和美国签订了新一轮的谅解备忘录”很容易猜出X是“朝鲜”的“鲜”,这是根据后文做出的判断。天然的语言中存在前后文的信号,都会影响当前字的选择。Bi-LSTM可以兼顾前后文的影响,所以是从理论上来说是个很符合人类直觉的工具。

如果不用CRF,可能整体效果还不错,但会出现很多badcase。比如B后面出现S,S后面出现O。因为算法只考虑当前的最优输出,没有考虑整个序列的最优结果。而CRF是一个考虑全局的算法,也考虑到标签间的转移概率。所以用CRF会得到一个比较可控的结果。

总得来说,上图介绍的Bi-LSTM+CRF方法,结合了CRF和Bi-LSTM,把“小明去达观数据开会”这几个字变成向量,通过中间的Bi-LSTM隐层,提取出来高维的特征,输入CRF层,CRF最后就会给出标签和结果。

下面我们会介绍这篇文章最重要的部分:预训练模型。深度学习除了不用做大量的特征工程,还可以对文本做非常好的表示。这里的例子是用Word2Vec做出词向量,然后用TensorBoard可视化,如下图所示。

image.png

在图中“威海”、“潍坊”、“枣庄”这三个山东的城市的词汇,被转化成了三个低维向量,向量中的数都是浮点数,有正数也有负数。如果从空间的角度来看这三个向量,可以发现它们距离很近,说明从语义角度来看它们的含义很接近。而且我们还可以直接对这些词向量进行计算,例如山东-威海=广东-佛山,皇帝-皇后+女人=男人,所以词向量是很优秀的自然语言的表征方式。

上图用的是Word2Vec模型。下图还有一些其他的模型,比如Glove。这两个模型都是静态表示。静态表示有天然的缺陷,例如它们很难区分“苹果好吃”和“苹果手机”中的两个“苹果”。就好像我们学技术的时候什么都想学,但因为时间是有限,所以每种技术学得都不够深入。

image.png

所以从2018年开始,出现了很多新的预训练模型,不少模型都用《芝麻街》里怪物的名字命名,比如ELMO、BERT和ERNIE。除此之外还有微软的MASS,Google最新的XLNet等等。这些模型本质上都用深度学习的神经网络做表示,虽然有的用Attention,有的用Transform,但本质差别不大。

这些模型和Word2Vec/Glove最大的区别在于它们是动态模型。下图是一个真实的例子。输入“苹果好吃”和“苹果手机”后,用BERT对每个字建模,发现前两个字的向量很不一样。这说明BERT可以根据不同的上下文语境编码每个字,或者说可以根据上下文语境对同一个字做出不同的表示。

image.png

BERT可以根据上下文,对同一个字做出不同的表示

如何选择预训练模型呢?我建议大家可以都尝试一下。大部分同学都可以训练ELMO,它的结构和LSTM很像,我们可以自己训练一个语言模型。BERT训练的成本就要高很多,但现在已经有一些其他的框架或语言做处理。我们自己用中文维基百科训练BERT只用了几天,也没有用很多显卡,当然我们也做了不少优化工作。可以先试着用Word2Vec看看效果,有可能效果已经很不错。关键在于要找到在能力范围内按时训练完的模型。

抽取算法在达观的具体实践

下面我们分享一下在达观的实践中完成抽取任务的一些经验和教训。

首先我们要注重场景。应用场景一般就是客户提供的文档,包括财务报表、基金合同等等。文档处理的核心是自然语言处理,特别是抽取技术。我们也需要考虑实际应用,结合一些其他的工程技术,比如外部系统、分布式技术、数据库技术等等。

image.png

第二是要解决数据不足的问题。尤其是序列标注比文本分类需要更多的标注成本,所以很可能数据量不够。虽然目前有一些通用的数据(比如《人民日报》的数据),但针对具体的业务场景可能没有足够多的语料和标注数据。这时候我们就要做数据增强。数据增强是一种通用的方法,可以应用于传统的机器学习和深度学习中。

image.png

在上图中,我们可以看到标注数据只有三句话,黄色表示要做机构识别。怎么增加标注数据的量?我们可以直接暴力地把它们两两随机组合。初听起来可能会觉得有点不可理喻,但确实有效果。上图中右边的三段话中,前两段是两两随机组合,最后一段是把三句话全部混合到一起。把这些新生成的数据加入原数据起去做模型,就会发现效果的确好了很多。数据增强为什么有效?从模型的角度简单地说,这样可以看到更多上下文,特别是可以跨句子看到上下文,所以会有帮助。基本上写5-10行代码就能产生一些收益。

还有一种方法是非监督的Embeddin的学习。下图是我们的一个真实的例子。当时登贝莱刚转会到巴塞罗那俱乐部。我们用标准语料去训练,发现“登贝莱”这个名字一定会被切开,无论怎么训练分词都不行。潜在的解决方法之一是增加很多登贝莱相关的标注数据,但是这么做收益不足。所以我们就找了很多外部的语料做嵌入。

image.png

如上图所示,我们在网上找了一些登贝莱的新闻补充到《人民日报》等语料里一起训练。在完全没有修改,只是重新训练了预训练模型的情况下,“登贝莱”就成了一个词。这说明深度学习的预训练模型,可以非常好地捕捉到上下文,而且我们知道大部分的神经网络的语言模型训练是非监督学习,所以不需要很多标注数据。可以有很大数据量。总体来说数据越多,模型会学得越准,效果越好。BERT训练了一两千万的中文后,可以达到非常好的效果,我觉得这是个大力出奇迹的模型。

除了NER,还可以抽取别的内容。例如知识图谱就要做关系抽取。输入一句话,“美国总统特朗普将考察苹果公司,该公司由乔布斯创立”,怎么抽取关系?有两种方法。一种方式是把实体抽出来,然后两两实体做一些分类,分到一些关系里面。另一种依靠序列标注,也就是基于联合标注的方法。这么做的好处是不用修改标注框架。

image.png

我们总结一下本文内容。在实际工作中,到底怎么来用深度学习挖掘文本?最重要的一点是要用预训练模型,通过非监督数据训练向量,提升泛化能力。虽然中间步骤难以分解,但因为深度学习有端到端的能力,所以对中间步骤要求较低。而且,深度学习能克服一些传统模型的缺点,例如LSTM的上下文依赖就比CRF强。

image.png

但是深度学习也有一些缺点,它在小数据集上的效果难以保证,很可能会过拟合或者难以收敛。例如大家看到TensorBoard经常在抖,就是有这样的问题。而且大家现在把深度学习调参的工作叫炼丹室,你也不知道好坏就在反复调。有时候调参的工作量不亚于特征工程,特征工程至少知道在做什么,而想分析调参结果更加困难。另外深度学习对计算资源的要求更高。

所以我们最终的思考是:第一要尽可能地收集数据、理解数据,这是所有做机器学习的同学第一步就应该做的事情。我们应该去分析数据、看数据,而不是一开始就上模型。如果不做数据清洗,好数据、乱数据、脏数据都在里面,模型是做不好的。就像教孩子一样,如果好的坏的都教,他就不知道什么是好坏了。而且我们要分析问题的本质,选择合适的模型。例如,对于已有数据的数据量,选先进模型有用吗?如果没有用,就要赶紧去收集数据。

而且在任务一开始的阶段,我比较推荐大家做传统的机器学习,因为这些模型比较现成,也比较通用。在做了一个非常好的baseline之后,你就知道底线在哪,然后再引用深度学习。去年的达观杯我们就发现很多参赛者一上来就在用深度学习,结果做了各种调参,效果还不如我们自己20行代码的传统的机器学习。所以刚开始的时候一定要让传统机器学习帮助你,这样你更有信心做后面的事情。另外,这句话一定要送给大家:“数据决定效果上限,模型逼近此上限”,所以大家一定要重视数据清理,数据的分析真的比调参调模型收益更大。

如果遇到疑难杂症,端到端技术经常会有惊喜,但不能保证每次都有惊喜。大家在学习的过程中一定要关心最前沿的技术。

做机器学习肯定会遇到失败和挫折,重要的是从挫折中总结规律才是最重要的,不要被同一个坑绊。这样的经验很难依靠别人教会,因为所处的环境、场景、场合、数据不可能完全一致,所以需要有自己的思考。

最后,看完了这篇文章能做什么呢?可以参加我们的“达观杯”文本智能信息抽取挑战赛。这是我们第三次组织“达观杯”比赛。比赛的一等奖有30000元奖金,二等奖2支队伍有10000元的奖金,三等奖有5000元的奖金,优胜奖还有3000元。除此之外,TOP30同学直接直通面试。

image.png

大家学习完以上基础可以用我们介绍的内容做一些实践。比赛的数据很有意思,文字经过了加密,每个字都做了一个随机的映射。这么做的好处是可以更多地关注算法的本身,而不用去想如何补充数据。虽然补充数据在实际工作中很重要,但我们的比赛主要还是考察算法。

比赛数据有两部分,一部分是有标注的数据,另外一部分是一个规模达到上百万的非标注的数据。比赛的关键就是如何利用这些非标注的数据来提升整个模型的效果。而这就是我们最终在实际生活和工作中遇到的问题:只有少量标注数据,但是有大量的未标注数据。欢迎大家在比赛中实际运用一些算法和理论。因为有时候光看别人的分享难以获得深刻的理解,但是经过“达观杯”这样的比赛就能把知识掌握地更好。

image.png

第二部分:“达观杯”baseline代码分享

达观数据工程师:

现在我们着重讲一下basline代码。baseline代码可以在比赛网站的“数据”页面(https://biendata.com/competition/datagrand/data/ )下载。

在前一章中,高翔老师给大家提到过做命名实体识别的几种方式:

  • 基于规则
  • 基于机器学习
  • 基于深度学习

因为这次达观杯比赛的数据经过了特殊处理,所以没法用基于规则的方法做。在这里我们介绍一下后两种方法。

对于传统的机器学习算法来说,特征工程是特别重要的一项,常常会占用我们特别多的时间。而且baseline提供的算法来说,设计特征模板也是一个重要的步骤,它会影响最后出来模型的整体效果。

而如果要用深度学习的方法做信息抽取,就需要比较多的机器资源,可能还需要更多的标注数据,才能在深度学习的算法上获得较好的效果。

下面我们看一下baseline代码。首先需要引入相关的库:

import codecs
import os

整个代码分成以下5个部分:

# 0 install crf++ https://taku910.github.io/crfpp/
# 1 train data in
# 2 test data in
# 3 crf train
# 4 crf test
# 5 submit test

首先我们需要CRF++工具,大家可以到 https://taku910.github.io/crfpp/ 下载工具。然后我们可以分析一下代码:

第一步:处理训练数据:

# step 1 train data in
with codecs.open('train.txt','r', encoding='utf-8')as f:
   lines = f.readlines()
   results =[]
   for line in lines:
      features =[]
      tags =[]
      samples = line.strip().split('  ')
        for sample in samples:
           sample_list = sample[:-2].split('_')
           tag = sample[-1]
           features.extend(sample_list)
           tags.extend(['O']* len(sample_list))if tag =='o'else tags.extend(['B-'+tag]+['I-'+ tag]*(len(sample_list)-1))
        results.append(dict({'features': features,'tags': tags}))
      train_write_list =[]
      with codecs.open('dg_train.txt','w', encoding='utf-8')as f_out:
         for result in results:
            for i in range(len(result['tags'])):
                 train_write_list.append(result['features'][i]+'\t'+ result['tags'][i]+'\n')
            train_write_list.append('\n')
         f_out.writelines(train_write_list)

我们知道做命名识别相当于一个序列标注的问题,所以在这里我们需要把这个数据转化成不同标签集序列标注的样式,这里有不同的几种标签集,这里我是用BIO去做的,大家也可以去尝试其他方式。

在比赛中,我们的训练集格式是几列特征加上一列标签的。我这里用了最基本的特征,就是字符本身的特征。不过比赛使用的不是字符本身,而是我们经过特殊处理之后每一个字的index,以及这个index对应的标签。在比赛中,我们定义了a,b,c三种标签,再通过中划线连接,就是代码中的['B-' + tag]和['I-' +tag]。

在这里大家也可以添加一些其他的特征。对于NLP来说,常见的特征包括词性、词频、词边界、实体的边界等。多加入几个这样的特征,可能会对效果有一些影响。

第二步:处理测试集

代码如下:

       # step 2 test data in
       with codecs.open('test.txt','r', encoding='utf-8')as f:
           lines = f.readlines()
           results =[]
           for line in lines:
               features =[]
               sample_list = line.split('_')
               features.extend(sample_list)
               results.append(dict({'features': features}))
        test_write_list =[]
        with codecs.open('dg_test.txt','w', encoding='utf-8')as f_out:
            for result in results:
                for i in range(len(result['features'])):
                    test_write_list.append(result['features'][i]+'\n')
                test_write_list.append('\n')
            f_out.writelines(test_write_list)

预测集的处理方式和训练集是一样的,只有一点区别,测试集没有最后一列标签。这些都处理好了之后,我们就可以用我们安装的CRF++工具去调用这个命令训练。代码如下:

第三步:CRF++训练

       # 3 crf train
       crf_train ="crf_learn -f 3 template.txt dg_train.txt dg_model"
       os.system(crf_train)

这里需要重点强调一下对模型影响比较大的特征模板(存储在template.txt)。template.txt文件可以在本文最上方的链接[c4] 下载,文件内容如下:


       # Unigram
       U00:%x[-3,0]
       U01:%x[-2,0]
       U02:%x[-1,0]
       U03:%x[0,0]
       U04:%x[1,0]
       U05:%x[2,0]
       U06:%x[3,0]
       U07:%x[-2,0]/%x[-1,0]/%x[0,0]
    U08:%x[-1,0]/%x[0,0]/%x[1,0]
    U09:%x[0,0]/%x[1,0]/%x[2,0]
    U10:%x[-3,0]/%x[-2,0]
    U11:%x[-2,0]/%x[-1,0]
    U12:%x[-1,0]/%x[0,0]
    U13:%x[0,0]/%x[1,0]
    U14:%x[1,0]/%x[2,0]
    U15:%x[2,0]/%x[3,0]     
    # Bigram
    B

这里我们用了一个比较简单的特征模板,取了离当前字最远的前3个字和后3个字,以及它们之间的组合特征(如U07到U09这几行)。我们可以看到这个模板其实特别简单,一共只有16个特征。这种选择和我们标注好的训练集性质有关。如果研究过比赛的训练集,就会发现数据中的实体是比较简单的,不会涉及特别长的文本抽取,所以这里正负3已经可以取到一个比较好的特征了。我也试过用再复杂一点的模板去做,但是效果不如这个简单的模板好。

不过,如果像之前提到的,在第一步训练集处理的过程中加了一些其他的NLP的特征工程(比如词性、词频或者词边界、是否是句子的结尾、是否是实体的结尾等),那么我们就需要在template文件里加入更复杂的特征。

具体来说,因为我们现在可以看U00到U06对应的都只是一个坐标,代表了当前字向前和向后的字,但是我们并没有横向地去取特征。如果大家加了其他的特征工程,那么在每行(如U00:%x[-3,0])后面加上这个字的其他特征,比如这个字本身及前后几个字是否是一个停用词,或者这一个字的前一个字是不是停用词的特征。

除了Unigram,还可以选择Bigram。Bigram和Unigram非常相似。但是它引入了状态转移函数。状态转移函数考虑了当前输出标签的前一个标签是什么。所以它会有全局的概念。

在CRF训练代码crf_train = "crf_learn -f 3 template.txt dg_train.txtdg_model"中,除了template.txt外,还有其他一些参数。例如-f 3,代表Template里设定的特征函数的频率值。如果它低于这个值的话,我是会把它删除。这里大家可以看到我们在template.txt中只尝试取到3个字的组合(例如文件中的U07:%x[-2,0]/%x[-1,0]/%x[0,0]),再多的都没有了。如果大家取到4个字或者5个字的组合,这些特征是非常稀疏,对整个模型没有很大贡献了。所以这里我们设一个最低的值,可以把那些稀疏的特征去掉。

除了这个以外,还会有一个惩罚系数,也一定程度提高这个模型的泛化能力。

第四步:CRF++生成预测结果

      # 4 crf test
      crf_test ="crf_test -m dg_model dg_test.txt -o dg_result.txt"
      os.system(crf_test)

在我们训练好这个模型之后,就可以调用crf_test这个命令生成预测结果。

第五步:生成可提交的文件

最后一步是生成可以提交的文件,代码如下:

       # 5 submit data
       f_write =codecs.open('dg_submit.txt','w', encoding='utf-8')
       with codecs.open('dg_result.txt','r', encoding='utf-8')as f:
           lines = f.read().split('\n\n')
           for line in lines:
               if line =='':
                   continue
               tokens = line.split('\n')
               features =[]
            tags =[]
            for token in tokens:
                feature_tag = token.split()
                features.append(feature_tag[0])
                tags.append(feature_tag[-1])
            samples =[]
            i =0
            while i < len(features):
                sample =[]
                if tags[i]=='O':
                    sample.append(features[i])
                    j = i +1
                    while j < len(features)and tags[j]=='O':
                        sample.append(features[j])
                        j +=1
                    samples.append('_'.join(sample)+'/o')
                else:
                    if tags[i][0]!='B':
                        print(tags[i][0]+'error start')
                        j = i +1
                    else:
                        sample.append(features[i])
                        j = i +1
                        while j < len(features)and tags[j][0]=='I'and tags[j][-1]== tags[i][-1]:
                            sample.append(features[j])
                            j +=1
                        samples.append('_'.join(sample)+'/'+tags[i][-1])
                i = j
            f_write.write('  '.join(samples)+'\n')

这一步也是选手反映出现问题最多的一个步骤,因为可能大家的操作系统不同。如果用的Windows系统,split('nn')应该要换成split('rn'),然后再进行后续处理,不然是会报错的。

除此之外,还有一些选手会反映系统总是会报各种各样的分类错误和异常。我建议大家检查换行符,保证提交的文件是3000条,不能多一行、少一行,也不能在最后一行加换行符。还有一点,虽然我们只评测a,b,c这三个类型的字段,所以/o不参与评分,但我们提交时还是要包含/o,否则也会报错。

第三部分:问答环节

关于比赛:数据,baseline

问:比赛的训练数据都是词向量吗?

达观数据工程师:不是的,由于我们数据的特殊性,我们的数据都是字符级别的。因为我们的数据已经做了脱敏处理,分词的特征已经没有了。

问:Baseline模型可以到多少分?

达观数据工程师:Baseline非常简单,如果装了CRF++工具,能够马上跑出来,大概能有0.85的F1。大家如果选到更好的特征模板,就能把CRF调到一个更高的分数。因为CRF本身也是很有门道的,可能需要花一些时间,不过研究一下会很有收获。

问:比赛中提供的未标注数据有什么用?

达观数据工程师:我的看法是用来训练语言模型,如果你有一些别的特别酷炫的想法也可以尝试。因为提供未标注数据的初衷,是我们看到NLP最近一年的发展,自从ELMo、BERT出现,后面还有XLNet,这些模型都是在告诉我们NLP其实也可以借鉴一下CV超大数据量预训练模型的思路。因为在NLP领域标注的数据非常难获取,其实NLP的数据比CV的数据更难标,但是我们有大量无监督数据,比如神奇的被翻来覆去调教了N次的维基百科,这种未监督数据量足够大,你总能发现里面的语言学规律,这里面的规律可以用来做特征,来增强模型的泛化。特别是现在做文本相似度以及做NER等基础任务,用了BERT以后泛化能力会提高很多,BERT现在经常被作为基础的特征提取模型。

问:为什么我的模型分数只有 0.72?

高翔:其实就如前文所说,应该先研究一下数据,并用传统的方法提出一些baseline模型。否则就不知道这个任务的底线,也可能会出现过拟合。我们自己内部用Python串起来十几行代码,就能跑到0.8+。我觉得不到0.8可能是出现了过拟合,没有使用预训练数据,只是使用了标注数据。所以建议先用标准模型试试,得到一个baseline,获得一些信息,再去做一些比较复杂的模型。

比赛相关技术:调参,BERT,ELMo,LSTM和CRF

问:如何调参?

高翔:这个问题特别深奥。很多情况下我们把一些同学叫“调包侠”,他们没有很好地理解算法本质,只是在瞎调。比如我曾经遇到一个同学在LSTM里把ReLu当成激活函数。但LSTM要求输出为0-1,而ReLu的输出范围是0到无穷大,所以梯度直接就爆炸了。我觉得调参有两个重要的方面,首先是经验的积累,这点只能靠自己;其次需要对模型和数学有一定的了解,这些不会直接告诉你如何调参,但是能给你一些灵感和方向。我也建议大家搜索一些网上的经典套路。

问:BiLSTM+CRF能用于比赛吗?

达观数据工程师:如果用深度模型也可以,但是我们这次比赛的数据量有点少,用深度学习模型的话很容易过拟合。目前已经有不少参赛选手反馈,用BiLSTM+CRF模型调参,不管怎样调,结果甚至还不如CRF随便跑一下好。这是因为我们给的训练集只有17000条,而且并不是每一条都有标注的,所以训练集比较少。所以大家如果要做的话,就需要尝试用训练语言模型的方式去做深度学习模型了。

问:BERT+BiLSTM+CRF呢?

达观数据工程师:如果是用于我们这个比赛,我怀疑你在BERT的训练过程中会过拟合。

问:BERT能不能把比赛数据解密?

达观数据工程师:我觉得逆向解密可能跟BERT关系不大,跟你的统计能力以及侦探小说看得多不多有关系。

问:如何使用ELMo能训练出更好的词向量?

高翔:这个要看与什么比较。我们的实践经验不一定跟你相符。我们发现,ELMo训练出来的东西的确会比Word2Vec会好,而且要好不少,但跟BERT相比还是会差一些。如果你要训练一个不停地迭代的模型,可能花很多时间调参,但是可能没有换掉整个模型框架的收益大。因为BERT效果的确要好很多,无论从机制还是attention思路来说,都会比简单的LSTM这种方式做得更好。

达观数据工程师:虽然BERT很难用于本次比赛,但我觉得ELMo也可以用在比赛里。ELMo有两种方式,一种是自己分词,然后再进LSTM。还有一种是直接用字向量。你其实可以跳过自己分词,直接把每个字当成ELMo中的词向量来训练。

比赛给出的未监督数据不可能到维基中文数据的量级。所以我个人建议是从ELMo这样的Baseline开始,看ELMo会不会在比赛数据量规模上过拟合,如果会的话,可以把ELMo的模型复杂度自己裁剪,或者你用一些更复杂更炫酷的词向量去拼接ELMo。

问:能否再讲解一下BiLSTM+CRF?

达观数据工程师:CRF可以做的东西,BiLSTM+CRF应该都可以做。但是这次比赛没有给分词的信息,所以可以用字向量来做。但是用字向量来做,一是容易过拟合(就像前文提到的数据量不是很多)。如果你们要玩这种深度学习模型,首先要面对的首要问题是过拟合,不过我们已经帮你们排除掉了最容易过拟合的事情,就是词向量。

所以,现在只需要做字向量,而字向量的空间比词向量少了很多。词向量一般来说是几万级的,再乘以常用的200维,这样就容易出现严重的过拟合。

字向量一般是千级别(比如五六千),再乘以一个100维,不过可能还是有可能会过拟合,因为我们的数据量比较少。

这次比赛做的命名实体识别,基本上给的是比较短的文本。你可以做一件很简单的事情,就是做数据增强:随机把2句、3句,甚至4句、5句话拼在一起。拼完以后你的实体还是在原来的位置,这样数据量可以直接变成2倍到3倍。而现在比赛这个量级的数据做BiLSTM+CRF是一定会过拟合的。

当然,你可以有一些更神奇的操作。比如如果你怀疑过拟合主要出现在字向量层或者LSTM层的话,你可以去把字向量层的学习率调低,或者把LSTM层的学习率调低,或者加L2正则化。

另外值得一提的是,我们在比赛中给了一部分未监督语料。未监督语料量级其实也不是特别大,我前两天在比赛群里看到有人在企图训练BERT,我认为这么做不是特别科学。这么小的语料训练BERT一定是训练不出来的。我们也有过BERT的训练,基本上用了整个维基百科的数据。如果数据量少或者质量差的话,都会影响到BERT的训练效果,特别是这个比赛用的数据量级会比维基百科少了太多。

语言模型从ELMo到BERT,都是数据量多大,模型能力就有多大。深度学习一定要记住这样一件事情:数据量多大决定你要用多复杂的模型才不会过拟合。

这个问题回答有点长,但是这是一个相当广泛的,也是一个比较实用的问题。

问:NER除了BiLSTM+CRF,还有其它方法吗?

达观数据工程师:BiLSTM+CRF已经是一个2015年提出来的很老的Baseline了。还有很多很酷炫的方法(比如IDCNN+CRF)也出现了挺久了。它的主要特点是性能高,在上下文关联不是很长,而且对训练速度要求高的情况下可以试一下。

我个人观点,在机器学习这块更好的思路不是唯模型论,而是一个概率编程的思路。也就是说,当你的规则很难去维护时,就说明你的数据量已经足够去做模型了。你可以做一个很小的模型,解决一个很小的问题,然后把这些东西拼起来。

问:数据量规模和神经网络的层数有什么关系,10万样本需要几层网络?

达观数据工程师:这种玄学问题很难简单地概括清楚。我觉得样本质量比数量重要太多,有时你洗洗数据比你调一两层模型有意义多了。

即使不考虑数据质量,相比样本数量来说,你可以做的数据增强以及你样本当中实际的模式也是很重要的一件事情。如果是标准的NER问题(比如抽取人名、地名、机构名这种实体),而且数据量有10万样本,我觉得LSTM层数差不多1-2层,最多3层就可以了。

但这是一个经验值,如果你有足够的时间,可以用先用BERT去微调(finetune)做一个Baseline,供其他模型参考。然后更重要的是,你怎样去发现是否在这个数据上出现了过拟合。如果你在过拟合这个数据,很多时候并不是LSTM层数的关系。

关于隐层维度的上限,假如你说的是BiLSTM+CRF的话,它的前级特征,比如如何得到字向量和词向量,是否有锁定词向量,以及数据是否有增强,比LSTM的层数重要多了。维度一般都在数百维左右。

问:能否再详细介绍一下数据增强?

达观数据工程师:NER的语料基本是识别一句话,比如“小姐姐某某某来到达观数据” 和“孙杨去参加了游泳比赛”。那么现在我们有了两条语料,你可以这两条语料拼在一起,模型理论上还是应该给出正确结果,但是你喂给模型的语料就变成了三条。

其实模型有可能并不是在过拟合你所理解的那种模式,它其实是在记住数据中相同的东西。

问:长度过长的句子需要截断吗?

达观数据工程师:这就看你怎么定义“过长”了。比如你统计下来,99%的句子都在200个字以下,你完全可以选择在200个字截断,或者你做了各种花式拼接以后再统计一下。如果有些特别长尾的东西,就可以在那个长度截断。

问:深度学习和传统的机器学习方法,哪个好?

达观数据工程师:这个问题比较泛,深度学习和传统机器学习方法哪个好一些?机器学习本质上是个实验科学。我认为实验科学的意思就是试一试哪个方法效果好,哪个方法就好。从理论上有时很难推断出来,因为使用的数据经常千奇百怪,存在各种异常。

目录
相关文章
|
15天前
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
28 6
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
50 3
|
2月前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
29天前
|
编解码 前端开发 UED
探索无界:前端开发中的响应式设计深度解析与实践####
【10月更文挑战第29天】 本文深入探讨了响应式设计的核心理念,即通过灵活的布局、媒体查询及弹性图片等技术手段,使网站能够在不同设备上提供一致且优质的用户体验。不同于传统摘要概述,本文将以一次具体项目实践为引,逐步剖析响应式设计的关键技术点,分享实战经验与避坑指南,旨在为前端开发者提供一套实用的响应式设计方法论。 ####
47 4
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
69 4
|
1月前
|
安全 编译器 PHP
PHP 8新特性解析与实践应用####
————探索PHP 8的创新功能及其在现代Web开发中的实际应用
|
1月前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
104 10
|
1月前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
XML JavaScript 前端开发
如何解析一个 HTML 文本
【10月更文挑战第23天】在实际应用中,根据具体的需求和场景,我们可以灵活选择解析方法,并结合其他相关技术来实现高效、准确的 HTML 解析。随着网页技术的不断发展,解析 HTML 文本的方法也在不断更新和完善,
|
1月前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
78 3

推荐镜像

更多