提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
最近在一个项目中需要使序列标注的方法来进行命名实体识别,目前使用序列标注方法进行命名实体识别主要有两种实现方法:一是基于统计的模型:HMM、MEMM、CRF,这类方法需要关注特征工程;二是深度学习方法:RNN、LSTM、GRU、CRF、RNN+CRF…
本篇记录了如何使用sklearn_crfsuite工具进行中文命名实体识别。
条件随机场这个模型属于概率图模型中的无向图模型,这里我们不做展开,只直观解释下该模型背后考量的思想。一个经典的链式 CRF 如下图所示,

CRF 本质是一个无向图,其中绿色点表示输入,红色点表示输出。点与点之间的边可以分成两类,一类是 x 与 y 之间的连线,表示其相关性;另一类是相邻时刻的 y之间的相关性。也就是说,在预测某时刻 y时,同时要考虑相邻的标签解决。当 CRF 模型收敛时,就会学到类似 P-B 和 T-I 作为相邻标签的概率非常低。
对于 CRF,我们给出准确的数学语言描述:设 X 与 Y 是随机变量,P(Y|X) 是给定 X 时 Y 的条件概率分布,若随机变量 Y 构成的是一个马尔科夫随机场,则称条件概率分布 P(Y|X) 是条件随机场。
本文使用sklearn_crfsuite进行命名实体识别模型的训练,官方教程的链接如下:https://sklearn-crfsuite.readthedocs.io/en/latest/tutorial.html
首先需要确保你已安装了scikit-learn这个库,然后通过pip安装sklearn-crfsuite
pip install sklearn-crfsuite
注意sklearn-crfsuite 需要 Python 2.7+ 或 3.3+
这里我使用的是清华大学开源的cluner数据集。数据集的下载地址:https://www.cluebenchmarks.com/introduce.html
CLUENER2020共有10个不同的类别,包括:
组织(organization)
人名(name)
地址(address)
公司(company)
政府(government)
书籍(book)
游戏(game)
电影(movie)
职位(position)
景点(scene)
在拿到数据之后,我将数据处理成了下面的格式:
浙 B-company
商 I-company
银 I-company
行 I-company
企 O
业 O
信 O
贷 O
部 O
字与标签之间以空格隔开,每句话中间以换行分割
确定好所需要训练的数据集之后,最重要的步骤就是定义特征模板。
在本示例中主要使用了词的上下文信息来构造特征模板。当然更多特征可以参考官方的示例进行设置。
这里使用的特征字典如下:
def word2features(sent, i):
word = sent[i][0]
#构造特征字典,我这里因为整体句子长度比较长,滑动窗口的大小设置的是6 在特征的构建中主要考虑了字的标识,是否是数字和字周围的特征信息
features = {
'bias': 1.0,
'word': word,
'word.isdigit()': word.isdigit(),
}
#该字的前一个字
if i > 0:
word1 = sent[i-1][0]
words = word1 + word
features.update({
'-1:word': word1,
'-1:words': words,
'-1:word.isdigit()': word1.isdigit(),
})
else:
#添加开头的标识 BOS(begin of sentence)
features['BOS'] = True
#该字的前两个字
if i > 1:
word2 = sent[i-2][0]
word1 = sent[i-1][0]
words = word1 + word2 + word
features.update({
'-2:word': word2,
'-2:words': words,
'-3:word.isdigit()': word2.isdigit(),
})
#该字的前三个字
if i > 2:
word3 = sent[i - 3][0]
word2 = sent[i - 2][0]
word1 = sent[i - 1][0]
words = word1 + word2 + word3 + word
features.update({
'-3:word': word3,
'-3:words': words,
'-3:word.isdigit()': word3.isdigit(),
})
#该字的后一个字
if i < len(sent)-1:
word1 = sent[i+1][0]
words = word1 + word
features.update({
'+1:word': word1,
'+1:words': words,
'+1:word.isdigit()': word1.isdigit(),
})
else:
#若改字为句子的结尾添加对应的标识end of sentence
features['EOS'] = True
#该字的后两个字
if i < len(sent)-2:
word2 = sent[i + 2][0]
word1 = sent[i + 1][0]
words = word + word1 + word2
features.update({
'+2:word': word2,
'+2:words': words,
'+2:word.isdigit()': word2.isdigit(),
})
#该字的后三个字
if i < len(sent)-3:
word3 = sent[i + 3][0]
word2 = sent[i + 2][0]
word1 = sent[i + 1][0]
words = word + word1 + word2 + word3
features.update({
'+3:word': word3,
'+3:words': words,
'+3:word.isdigit()': word3.isdigit(),
})
return features
import re
import sklearn_crfsuite
from sklearn_crfsuite import metrics
import joblib
import yaml
import warnings
warnings.filterwarnings('ignore')
def load_data(data_path):
data_read_all = list()
data_sent_with_label = list()
with open(data_path, mode='r', encoding="utf-8") as f:
for line in f:
if line.strip() == "":
data_read_all.append(data_sent_with_label.copy())
data_sent_with_label.clear()
else:
data_sent_with_label.append(tuple(line.strip().split(" ")))
return data_read_all
def word2features(sent, i):
word = sent[i][0]
#构造特征字典,我这里因为整体句子长度比较长,滑动窗口的大小设置的是6 在特征的构建中主要考虑了字的标识,是否是数字和字周围的特征信息
features = {
'bias': 1.0,
'word': word,
'word.isdigit()': word.isdigit(),
}
#该字的前一个字
if i > 0:
word1 = sent[i-1][0]
words = word1 + word
features.update({
'-1:word': word1,
'-1:words': words,
'-1:word.isdigit()': word1.isdigit(),
})
else:
#添加开头的标识 BOS(begin of sentence)
features['BOS'] = True
#该字的前两个字
if i > 1:
word2 = sent[i-2][0]
word1 = sent[i-1][0]
words = word1 + word2 + word
features.update({
'-2:word': word2,
'-2:words': words,
'-3:word.isdigit()': word2.isdigit(),
})
#该字的前三个字
if i > 2:
word3 = sent[i - 3][0]
word2 = sent[i - 2][0]
word1 = sent[i - 1][0]
words = word1 + word2 + word3 + word
features.update({
'-3:word': word3,
'-3:words': words,
'-3:word.isdigit()': word3.isdigit(),
})
#该字的后一个字
if i < len(sent)-1:
word1 = sent[i+1][0]
words = word1 + word
features.update({
'+1:word': word1,
'+1:words': words,
'+1:word.isdigit()': word1.isdigit(),
})
else:
#若改字为句子的结尾添加对应的标识end of sentence
features['EOS'] = True
#该字的后两个字
if i < len(sent)-2:
word2 = sent[i + 2][0]
word1 = sent[i + 1][0]
words = word + word1 + word2
features.update({
'+2:word': word2,
'+2:words': words,
'+2:word.isdigit()': word2.isdigit(),
})
#该字的后三个字
if i < len(sent)-3:
word3 = sent[i + 3][0]
word2 = sent[i + 2][0]
word1 = sent[i + 1][0]
words = word + word1 + word2 + word3
features.update({
'+3:word': word3,
'+3:words': words,
'+3:word.isdigit()': word3.isdigit(),
})
return features
def sent2features(sent):
return [word2features(sent, i) for i in range(len(sent))]
def sent2labels(sent):
return [ele[-1] for ele in sent]
train=load_data('data/cluner_train.txt')
valid=load_data('data/cluner_dev.txt')
print('训练集规模:',len(train))
print('验证集规模:',len(valid))
sample_text=''.join([c[0] for c in train[0]])
sample_tags=[c[1] for c in train[0]]
X_train = [sent2features(s) for s in train]
y_train = [sent2labels(s) for s in train]
X_dev = [sent2features(s) for s in valid]
y_dev = [sent2labels(s) for s in valid]
print(X_train[0])

crf_model = sklearn_crfsuite.CRF(algorithm='lbfgs',c1=0.25,c2=0.018,max_iterations=300,
all_possible_transitions=True,verbose=True)
crf_model.fit(X_train, y_train)
labels=list(crf_model.classes_)
labels.remove("O") #对于O标签的预测我们不关心,就直接去掉
y_pred = crf_model.predict(X_dev)
metrics.flat_f1_score(y_dev, y_pred,
average='weighted', labels=labels)
sorted_labels = sorted(labels,key=lambda name: (name[1:], name[0]))
print(metrics.flat_classification_report(
y_dev, y_pred, labels=sorted_labels, digits=3
))

import joblib
joblib.dump(crf_model, "checkpoint/crf_model.joblib")
本篇记录了如何借助sklearn_crfsuite这个库实现crf模型进行中文命名实体识别,当然实现的方法有很多,比如使用CRF++开源工具做文本序列标注.
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h