介绍
世界上产生的文本数据量惊人。谷歌每秒处理超过40000次搜索!根据福布斯的报道,我们每分钟发送1600万条短信,在Facebook上发布51000条评论。对于一个门外汉来说,甚至难以掌握数据的绝对大小?
新闻网站和其他网络媒体每小时产生大量的文本内容。如果你没有正确的工具,分析数据中的模式会变得令人畏缩。在这里,我们将讨论一种这样的方法,使用实体识别,称为条件随机场(CRF)。
本文解释了在自注释数据集上的条件随机场的概念和Python实现。这是一个非常有趣的概念,我相信你会喜欢这趟旅程!
目录表
1.什么是实体识别?
2.案例研究目标与理解方法
3.条件随机域(CRFS)的构造
4.训练数据注释
- 使用注解的注释
5.用Python构建和训练CRF模块
什么是实体识别?
随着对自然语言处理(NLP)的兴趣,实体识别最近出现了一次加注。实体通常可以被定义为数据科学家或企业感兴趣的文本的一部分。经常提取的实体的例子是人名、地址、帐户号、位置等。这些只是简单的例子,人们可以针对手头的问题想出自己的实体。
为了实现实体识别的简单应用,如果数据集中有任何带有“伦敦”的文本,则该算法将自动将该文本分类或分类为位置(您必须大致了解我将使用该位置)。
让我们通过一个简单的案例研究来更好地理解我们的话题。
案例研究目标与理解方法
假设您是一家保险公司的分析团队的一员,索赔团队每天都会从客户那里接收数千封关于索赔的电子邮件。索赔操作团队在处理这些邮件之前,先将每一封电子邮件和详细信息更新为在线表格。
要求您与IT团队合作,自动化预填充在线表单的过程。对于这个任务,分析团队需要建立一个自定义实体识别算法。
为了识别文本中的实体,必须能够识别模式。例如,如果我们需要识别索赔号,我们可以查看它周围的单词,例如“我的ID是”或“我的号码是”等等。让我们检查下面提到的几种方法来识别这些模式。
1.正则表达式:正则表达式(正则表达式)是有限状态自动机的一种形式。它们有助于识别遵循某种结构的模式。例如,电子邮件ID,电话号码等可以很好地使用ReGEX识别。然而,这种方法的缺点是需要知道在索赔号之前发生的所有可能的确切单词。这不是一种学习方法,而是一种蛮力的方法。
2.Hidden Markov模型(HMM):这是一种识别和学习模式的序列建模算法。虽然HMM考虑了未来的观察实体周围的学习模式,它假定的特征是相互独立的。这种方法比正则表达式好,因为我们不需要对确切的单词集进行建模。但就性能而言,不知道它是最佳的实体识别方法。
3.MaxEnt Markov模型(MEMM):这也是一个序列建模算法。这并不假定特征是相互独立的,也不考虑将来的观察来学习模式。在性能方面,不知道是识别实体关系的最佳方法。
4.条件随机场(CRF):这也是一个序列建模算法。这不仅假设特征彼此依赖,而且在学习模式时还要考虑将来的观察。这两者结合了HMM和MEMM的优点。在性能方面,它被认为是实体识别问题的最佳方法。
条件随机域(CRF)的构造
词袋(BoW)方法适用于多种文本分类问题。这种方法假设词的存在或不存在比词的顺序更重要。然而,存在诸如实体识别、词性识别之类的问题,其中词序列即使不是更重要,也同样重要。条件随机场(CRF)在这里拯救了它,因为它使用单词序列而不是单词。
现在让我们来理解CRF是如何形成的。
下面是CRF的公式,其中Y是隐藏状态(例如,词性),X是观察变量(在我们的示例中,这是实体或围绕它的其他词)。
广义地说,CRF公式有2个组成部分:
1.规范化:您可能已经观察到,在等式的右侧没有概率,在这里我们有权重和特征。然而,预期输出是概率的,因此需要规范化。归一化常数z(x)是所有可能状态序列的总和,使得总数成为1。您可以在本文的参考部分中找到更多的细节来理解我们是如何到达这个值的。
2.权重和特征:这个成分可以被认为是具有权重的logistic回归公式以及相应的特征。权重估计是通过最大似然估计进行的,特征是由我们定义的。
训练数据注释
现在你知道了CRF模型,让我们来分析训练数据。做这件事的第一步是注释。注释是用相应的标记对单词进行标记的过程。为了简单起见,让我们假设我们只需要两个实体来填充在线表单,即索赔人名称和索赔号码。
以下是收到的电子邮件样本。这样的电子邮件需要注释,以便可以训练CRF模型。注释的文本需要以XML格式。尽管您可以选择以自己的方式对文档进行注释,但我将指导您使用GATE体系结构进行注释。
收到电子邮件:
“Hi,
I am writing this email to claim my insurance amount. My id is abc123 and I claimed it on 1st January 2018. I did not receive any acknowledgement. Please help.
Thanks
randomperson”
释:
“嗨,我写这封电子邮件索取我的保险金额。我的ID是abc123,我在2018年1月1日提出申请的。我没有收到收件复函。请帮忙。谢谢,随机作者”
注释电子邮件:
“Hi, I am writing this email to claim my insurance amount. My id is abc123 and I claimed on 1st January 2018. I did not receive any acknowledgement. Please help. Thanks, randomperson”
释:<文件>嗨,我写这封电子邮件,索取我的保险金额。我的身份证是< Calim_number> abc123,我于2018年1月1日提出申请。我没有收到任何收件复函。请帮忙。谢谢,randomperson
使用注解的注释
让我们了解如何使用通用的文本工程体系结构(GATE)。请按照以下步骤安装GATE。
1.从这个链接下载最新版本:最新版本
2.通过执行下载的安装程序并适当地遵循安装步骤来安装门平台。
3.安装后,运行应用程序可执行文件,如下所示:
4.一旦应用程序打开,通过右击“语言资源”>New>GATE文档,迭代地将电子邮件加载到语言资源中,如下所示。给每封电子邮件起一个名字,将编码设置为“utf-8”,这样我们在Python中就没有问题了,导航到需要通过点击sourceUrl部分中的图标进行注释的电子邮件,如下所示。
a.一次打开一封电子邮件,开始注释练习。构建注释有2种选择。
- 将注释XML加载到GATE中并使用它
- 在飞行中创建注释并使用它们。在本文中,我们将演示这种方法。
b.点击语言资源部分的电子邮件,打开它。点击“注释集”,然后选择单词或单词,并将光标放在上面几秒钟。一个弹出窗口用于注释,然后你可以在注释中键入“x Nexy”并点击回车。下面将创建一个新的注释。为每个电子邮件的所有注释重复此练习
5.一旦所有的培训电子邮件被注释,创建一个语料库,便于导航到语言资源>新>门语料库。
6.给新语料库起一个供参考的名称,单击导航图标,并添加加载到语言语料库中的每个电子邮件,如下所示
7.通过右键单击语料库并导航到“Inline XML(.xml)”,将语料库保存为机器上的文件夹中的内联xml,如下所示
8.在下一个弹出窗口中,选择预填充的注释类型并移除它们。手动键入注释并添加它们以代替预先填充的注释。通过点击它将“包含特征”选项设置为false,并将“文档”键入到“根元素”框中。一旦所有这些更改都通过点击“保存到”图标将文件保存到机器上的文件夹中。以下是屏幕截图以供参考。
9.以上过程将把所有注释电子邮件保存在一个文件夹中。
用Python构建和训练CRF模块
1.首先下载PYCRF模块。对于PIP安装,命令是“pip install python-crf.”,对于conda安装,命令是“conda install-c conda-forge python-crf.”。
2.如果上述安装没有用,请从https://anaconda.org/conda-forge/python-crf./files下载相关的pycrf模块。如果您有一个带有python 2.7版本的Windows OS 64位机器,那么使用以下链接:win-64/python-crf.-0.9.2-py27_vc9_0.tar.bz2
3.提取pycrfsuite和python_crfsuite.-0.9.2-py2.7.egg-info文件,并将它们放置在其他包存在的文件夹中。例如,如果使用Anaconda,那么这些文件可以放置在Anaconda >lib >site>package folder中。
安装完成后,您就可以训练和构建自己的CRF模块。让我们这样做!
#invoke libraries
from bs4 import BeautifulSoup as bs
from bs4.element import Tag
import codecs
import nltk
from nltk import word_tokenize, pos_tag
from sklearn.model_selection import train_test_split
import pycrfsuite
import os, os.path, sys
import glob
from xml.etree import ElementTree
import numpy as np
from sklearn.metrics import classification_report
让我们定义和构建一些函数。
#this function appends all annotated files
def append_annotations(files):
xml_files = glob.glob(files +"/*.xml")
xml_element_tree = None
new_data = ""
for xml_file in xml_files:
data = ElementTree.parse(xml_file).getroot()
#print ElementTree.tostring(data)
temp = ElementTree.tostring(data)
new_data += (temp)
return(new_data)
#this function removes special characters and punctuations
def remov_punct(withpunct):
punctuations = '''!()-[]{};:'"\,<>./?@#$%^&*_~'''
without_punct = ""
char = 'nan'
for char in withpunct:
if char not in punctuations:
without_punct = without_punct + char
return(without_punct)
# functions for extracting features in documents
def extract_features(doc):
return [word2features(doc, i) for i in range(len(doc))]
def get_labels(doc):
return [label for (token, postag, label) in doc]
现在我们将输入注释的训练数据。
files_path = "D:/Annotated/"
allxmlfiles = append_annotations(files_path)
soup = bs(allxmlfiles, "html5lib")
#identify the tagged element
docs = []
sents = []
for d in soup.find_all("document"):
for wrd in d.contents:
tags = []
NoneType = type(None)
if isinstance(wrd.name, NoneType) == True:
withoutpunct = remov_punct(wrd)
temp = word_tokenize(withoutpunct)
for token in temp:
tags.append((token,'NA'))
else:
withoutpunct = remov_punct(wrd)
temp = word_tokenize(withoutpunct)
for token in temp:
tags.append((token,wrd.name))
sents = sents + tags
docs.append(sents) #appends all the individual documents into one list
生成特征。这些是NER算法在NLTK中使用的默认特性。可以修改它进行定制。
data = []
for i, doc in enumerate(docs):
tokens = [t for t, label in doc]
tagged = nltk.pos_tag(tokens)
data.append([(w, pos, label) for (w, label), (word, pos) in zip(doc, tagged)])
def word2features(doc, i):
word = doc[i][0]
postag = doc[i][1]
# Common features for all words. You may add more features here based on your custom use case
features = [
'bias',
'word.lower=' + word.lower(),
'word[-3:]=' + word[-3:],
'word[-2:]=' + word[-2:],
'word.isupper=%s' % word.isupper(),
'word.istitle=%s' % word.istitle(),
'word.isdigit=%s' % word.isdigit(),
'postag=' + postag
]
# Features for words that are not at the beginning of a document
if i > 0:
word1 = doc[i-1][0]
postag1 = doc[i-1][1]
features.extend([
'-1:word.lower=' + word1.lower(),
'-1:word.istitle=%s' % word1.istitle(),
'-1:word.isupper=%s' % word1.isupper(),
'-1:word.isdigit=%s' % word1.isdigit(),
'-1:postag=' + postag1
])
else:
# Indicate that it is the 'beginning of a document'
features.append('BOS')
# Features for words that are not at the end of a document
if i < len(doc)-1:
word1 = doc[i+1][0]
postag1 = doc[i+1][1]
features.extend([
'+1:word.lower=' + word1.lower(),
'+1:word.istitle=%s' % word1.istitle(),
'+1:word.isupper=%s' % word1.isupper(),
'+1:word.isdigit=%s' % word1.isdigit(),
'+1:postag=' + postag1
])
else:
# Indicate that it is the 'end of a document'
features.append('EOS')
return features
现在我们将构建特性并创建火车和测试数据帧。
X = [extract_features(doc) for doc in data]
y = [get_labels(doc) for doc in data]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
让我们来测试一下我们的模型。
tagger = pycrfsuite.Tagger() tagger.open('crf.model') y_pred = [tagger.tag(xseq) for xseq in X_test]
你可以通过选择相应的行号“i”来检查任何预测值。
i = 0
for x, y in zip(y_pred[i], [x[1].split("=")[1] for x in X_test[i]]):
print("%s (%s)" % (y, x))
检查模型的性能。
# Create a mapping of labels to indices
labels = {"claim_number": 1, "claimant": 1,"NA": 0}
# Convert the sequences of tags into a 1-dimensional array
predictions = np.array([labels[tag] for row in y_pred for tag in row])
truths = np.array([labels[tag] for row in y_test for tag in row])
打印分类报告。基于模型的性能,建立更好的特征来提高性能。
print(classification_report(
truths, predictions,
target_names=["claim_number", "claimant","NA"]))
#predict new data
with codecs.open("D:/ SampleEmail6.xml", "r", "utf-8") as infile:
soup_test = bs(infile, "html5lib")
docs = []
sents = []
for d in soup_test.find_all("document"):
for wrd in d.contents:
tags = []
NoneType = type(None)
if isinstance(wrd.name, NoneType) == True:
withoutpunct = remov_punct(wrd)
temp = word_tokenize(withoutpunct)
for token in temp:
tags.append((token,'NA'))
else:
withoutpunct = remov_punct(wrd)
temp = word_tokenize(withoutpunct)
for token in temp:
tags.append((token,wrd.name))
#docs.append(tags)
sents = sents + tags # puts all the sentences of a document in one element of the list
docs.append(sents) #appends all the individual documents into one list
data_test = []
for i, doc in enumerate(docs):
tokens = [t for t, label in doc]
tagged = nltk.pos_tag(tokens)
data_test.append([(w, pos, label) for (w, label), (word, pos) in zip(doc, tagged)])
data_test_feats = [extract_features(doc) for doc in data_test]
tagger.open('crf.model')
newdata_pred = [tagger.tag(xseq) for xseq in data_test_feats]
# Let's check predicted data
i = 0
for x, y in zip(newdata_pred[i], [x[1].split("=")[1] for x in data_test_feats[i]]):
print("%s (%s)" % (y, x))
现在,您已经了解了如何注释训练数据、如何使用Python训练CRF模型,以及最后如何从新文本中识别实体。虽然这个算法提供了一些基本的特性集,但是您可以提出自己的特性集来提高模型的精度。
小结
综上所述,以下是我们在本文中所涉及的要点:
1.实体是当前业务问题感兴趣的文本的一部分。
2.识别实体中的词或令牌的顺序
3.模式识别方法,如正则表达式或基于图的模型,如隐马尔可夫模型和最大熵马尔可夫模型,可以帮助识别实体。然而,条件随机场(CRF)是一个流行的,可以说是一个更好的候选实体识别问题。
4.CRF是一种基于无向图的模型,它不仅考虑在实体之前发生的词,而且考虑在它之后。
5.可以通过使用门结构来注释训练数据。
6.Python代码提供了在训练CRF模型和从文本中提取实体方面的帮助。
7.总之,这篇文章应该为你的商业问题提供一个很好的起点。
工具书类
1.Charles Sutton和Andrew McCallum对条件随机场的介绍(http://homepages.inf.ed.ac.uk/csutton/publications/crftut-fnt.pdf)。
2.概率图形模型:Alexander M.Rush提出的自然语言处理的拉格朗日松弛算法(基于与Michael Collins,Tommi Jaakkola,Terry Koo,David Sontag的联合工作)。(https://people.csail.mit.edu/dsontag/courses/pgm12/slides/lecture3.pdf)。
3.用Albert Au Yeung在Python中使用CRF进行序列标记。(http://www.albertauyeung.com/post/python-sequence-labelling-with-crf/)标记。
4.使用门作为注释工具,由Tom Kenter,Diana Maynard。(https://gate.ac.uk/sale/am/annotationmanual-gate2.pdf)
关于作者
Sidharth Macherla -独立研究员,自然语言处理领域
Sidharth Macherla有超过12年的数据科学经验,他目前的重点领域是自然语言处理。他曾在银行、保险、投资研究和零售领域工作过。
原文链接:Complete tutorial on Text Classification using Conditional Random Fields Model (in Python)
原作者:Sidharth Macherla
翻译:徐大白
注意:本文归作者所有,未经作者允许,不得转载