使用Scikit-Learn进行命名实体识别和分类(NERC)

编译:yxy

出品:ATYUN订阅号

命名实体识别和分类(NERC)是识别名称等信息单元的过程(包括人员,组织和位置名称),以及包括非结构化文本中的时间,日期,钱和百分比表达式等数值表达式。目标是开发实用且与域无关的技术,以便自动高精度地检测命名实体。

上周,我们介绍了NLTK和SpaCy中的命名实体识别(NER)。今天,我们更进一步,使用Scikit-Learn的一些库训练NER的机器学习模型。让我们开始吧!

数据

数据是IOB和POS标签注释的特征设计语料库(底部链接给出)。我们可以快速浏览前几行数据。

有关实体的基本信息

  • geo =区域实体(Geographical Entity)
  • org =组织(Organization)
  • per =人(Person)
  • gpe =地缘政治实体(Geopolitical Entity)
  • tim =时间指示器(Time indicator)
  • art =人工制品(Artifact)
  • eve =事件(Event)
  • nat =自然现象(Natural Phenomenon)

Inside–outside–beginning(标记)

IOBInside–outside–beginning)是用于标记标志的通用标记格式。

  • I-标签前的前缀表示标签位于块内。
  • B-标签前的前缀表示标签是块的开头。
  • O标记表示标志不属于任何块(outside)。
import pandas as pd
import numpy as np
from sklearn.feature_extractionimport DictVectorizer
from sklearn.feature_extraction.textimport HashingVectorizer
from sklearn.linear_modelimport Perceptron
from sklearn.model_selectionimport train_test_split
from sklearn.linear_modelimport SGDClassifier
from sklearn.linear_modelimport PassiveAggressiveClassifier
from sklearn.naive_bayesimport MultinomialNB
from sklearn.metricsimport classification_report

整个数据集不能装入一台计算机的内存中,因此我们选择前100,000个记录,并使用外存学习算法(Out-of-core learning algorithm)来有效地获取和处理数据。

df= pd.read_csv('ner_dataset.csv',encoding=“ISO-8859-1”)
df= df [:100000]
df.head()
df.isnull().sum()

数据预处理

我们注意到“Sentence#”列中有很多NaN值,我们用前面的值填充NaN。

df= df.fillna(method='ffill')
df ['Sentence#']。nunique(),df.Word.nunique(),df.Tag.nunique()

(4544,10922,17)

我们有4,544个句子,其中包含10,922个独特单词并标记为17个标签。

标签分布不均匀。

df.groupby('Tag').size().reset_index(name='counts')

以下代码使用DictVectorizer将文本转换为向量,然后拆分为训练和测试集。

X= df.drop('Tag',axis= 1)
v= DictVectorizer(sparse= False)
X= v.fit_transform(X.to_dict('records'))
y= df.Tag.values
classes= np.unique(y)
classes= classes.tolist()
X_train,X_test,y_train,y_test= train_test_split(X,y,test_size= 0.33,random_state= 0)
X_train.shape,y_train.shape

((67000,15507),(67000,))

外存算法

我们将尝试一些外存算法,这些算法旨在处理太大而无法存入的单个计算机内存的数据(partial_fit 方法)。

感知机

per= Perceptron(verbose=10, n_jobs=-1, max_iter=5)
per.partial_fit(X_train, y_train, classes)

因为标签“O”(outside)是最常见的标签,它会使我们的结果看起来比实际更好。因此,当我们评估分类指标时,我们会删除标记“O”。

new_classes= classes.copy()
new_classes.pop()
new_classes
print(classification_report(y_pred= per.predict(X_test),y_true= y_test,labels= new_classes))

具有SGD训练的线性分类器

sgd= SGDClassifier()
sgd.partial_fit(X_train,y_train,classes)
print(classification_report(y_pred= sgd.predict(X_test),y_true= y_test,labels= new_classes))

用于多项模型的朴素贝叶斯分类器

nb=
MultinomialNB (alpha= 0.01)nb.partial_fit(X_train,y_train,classes)
print(classification_report(y_pred= nb.predict(X_test),y_true= y_test,labels= new_classes))

Passive Aggressive分类器

pa = PassiveAggressiveClassifier()
pa.partial_fit(X_train,y_train,classes)
print(classification_report(y_pred= pa.predict(X_test),y_true= y_test,labels= new_classes))

上述分类器均未产生令人满意的结果。显然,使用常规分类器对命名实体进行分类并不容易。

条件随机场(CRF)

CRF通常用于标记或解析序列数据,例如自然语言处理,并且CRF查找POS标记、命名实体识别等应用。

SKLEARN-CRFSUITE

我们将使用sklearn-crfsuite在我们的数据集上训练用于命名实体识别的CRF模型。

import sklearn_crfsuite
from sklearn_crfsuiteimport scorers
from sklearn_crfsuiteimport metrics
from collectionsimport Counter

以下代码用于检索带有POS和标签的句子。

class SentenceGetter(object):

    def __init__(self, data):
        self.n_sent= 1
        self.data= data
        self.empty= False
        agg_func= lambda s: [(w, p, t)for w, p, tin zip(s['Word'].values.tolist(),
                                                           s['POS'].values.tolist(),
                                                           s['Tag'].values.tolist())]
        self.grouped= self.data.groupby('Sentence #').apply(agg_func)
        self.sentences= [sfor sin self.grouped]

    def get_next(self):
        try:
            s= self.grouped['Sentence: {}'.format(self.n_sent)]
            self.n_sent+= 1
            return s
        except:
            return None
getter= SentenceGetter(df)
sentences= getter.sentences

特征提取

接下来,我们提取更多特征(单词构成,简化的POS标签,下部/标题/上部标志,附近词的特征)并将它们转换为sklearn-crfsuite格式 – 每个句子应转换为词典列表。以下代码取自sklearn-crfsuites官网。

def word2features(sent, i):
    word= sent[i][0]
    postag= sent[i][1]

    features= {
        'bias':1.0,
        'word.lower()': word.lower(),
        'word[-3:]': word[-3:],
        'word[-2:]': word[-2:],
        'word.isupper()': word.isupper(),
        'word.istitle()': word.istitle(),
        'word.isdigit()': word.isdigit(),
        'postag': postag,
        'postag[:2]': postag[:2],
    }
    if i >0:
        word1= sent[i-1][0]
        postag1= sent[i-1][1]
        features.update({
            '-1:word.lower()': word1.lower(),
            '-1:word.istitle()': word1.istitle(),
            '-1:word.isupper()': word1.isupper(),
            '-1:postag': postag1,
            '-1:postag[:2]': postag1[:2],
        })
    else:
        features['BOS']= True
    if i <len(sent)-1:
        word1= sent[i+1][0]
        postag1= sent[i+1][1]
        features.update({
            '+1:word.lower()': word1.lower(),
            '+1:word.istitle()': word1.istitle(),
            '+1:word.isupper()': word1.isupper(),
            '+1:postag': postag1,
            '+1:postag[:2]': postag1[:2],
        })
    else:
        features['EOS']= True
return features
def sent2features(sent):
    return [word2features(sent, i)for iin range(len(sent))]
def sent2labels(sent):
    return [labelfor token, postag, labelin sent]
def sent2tokens(sent):
    return [tokenfor token, postag, labelin sent]

拆分训练和测试集

X= [sent2features(s)for sin sentences]
y= [sent2labels(s)for sin sentences]
X_train, X_test, y_train, y_test= train_test_split(X, y, test_size=0.33, random_state=0)

训练CRF模型

crf= sklearn_crfsuite.CRF(
    algorithm='lbfgs',
    c1= 0.1,
    c2= 0.1,
    max_iterations= 100,
    all_possible_transitions= True
)
crf.fit(X_train,y_train)

评估

y_pred= crf.predict(X_test)
print(metrics.flat_classification_report(y_test,y_pred,labels= new_classes))

这种方式更好!我们将坚持sklearn-crfsuite并继续探索!

我们的分类器学到了什么?

def print_transitions(trans_features):
    for (label_from, label_to), weightin trans_features:
        print("%-6s -> %-7s %0.6f" % (label_from, label_to, weight))
print("Top likely transitions:")
print_transitions(Counter(crf.transition_features_).most_common(20))
print("\nTop unlikely transitions:")
print_transitions(Counter(crf.transition_features_).most_common()[-20:])

解释:很可能区域实体开头(B-geo)后面跟着内部区域实体(I-geo)的标志,但是从带有其他标签的标志转移到组织名称内部(I-org)会受到严厉惩罚。

检查状态特征

def print_state_features(state_features):
    for (attr, label), weightin state_features:
        print("%0.6f %-8s %s" % (weight, label, attr))
print("Top positive:")
print_state_features(Counter(crf.state_features_).most_common(30))
print("\nTop negative:")
print_state_features(Counter(crf.state_features_).most_common()[-30:])

观察:

1)5.183603 B-tim word[-3]:day该模型得知如果附近的单词是“day”,则该标志可能是时间指示器的一部分。

2)3.370614 B-per word.lower():president该模型学到标志“president”可能位于人名的开头。

3)-3.521244 O postag:NNP该模型了解到专有名词通常是实体。

4)-3.087828 O word.isdigit()数字可能是实体。

5)-3.233526 O word.istitle()大写单词可能是实体。

ELI5

ELI5是一个Python包,可以检查sklearn_crfsuite.CRF模型的权重。

检查模型重量

import eli5
eli5.show_weights(crf,top= 10)

观察

  1. I-entity必须跟着B-entity,例如I-geo跟着B-geo,I-org跟着B-org,I-per跟着B-per等等。
  2. 我们还可以看到,在这个数据集中,在组织名称后面有一个人并不常见(B-org – > I-per具有较大的负权重)。
  3. 该模型为不可能的过度学习了大的负权重,如O – > I-geo,O – > I-org和O – > I-tim等等。

为便于阅读,我们只检查一部分标签。

eli5.show_weights(crf,top= 10,targets= ['O','B-org','I-per'])

或者只检查所有标签的部分特征。

eli5.show_weights(crf,top= 10,feature_re='^ word \ .is',
                  horizontal_layout= False,show= ['targets'])

源代码可以在Github找到。

Github:https://github.com/susanli2016/NLP-with-Python/blob/master/NER_sklearn.ipynb

数据:https://www.kaggle.com/abhinavwalia95/how-to-loading-and-fitting-dataset-to-scikit/data

原文发布于微信公众号 - ATYUN订阅号(atyun_com)

原文发表时间:2018-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏祁旭翔的专栏

【 SPA 大赛】win10 python3.5.X 下开启 lightgbm 支持

GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型,其主要思想是利用弱分类器(决策树)迭代训练以得到最...

3.3K0
来自专栏大数据挖掘DT机器学习

R语言vs Python:数据分析哪家强?

本文章旨在更客观地看待这两门语言。我们会平行使用Python和R分析一个数据集,展示两种语言在实现相同结果时需要使用什么样的代码。这让我们了解每种语言的优缺点,...

1.2K11
来自专栏一名叫大蕉的程序员

大数据计数原理1+0=1这你都不会算(十)No.77

大数据计数原理1+0=1这你都不会算(一)No.47 <- HashSet 大数据计数原理1+0=1这你都不会算(二)No.50 ...

19710
来自专栏Petrichor的专栏

深度学习: 局部响应归一化 (Local Response Normalization,LRN)

局部响应归一化(Local Response Normalization,LRN):

8784
来自专栏华章科技

R语言数据可视化之五种数据分布图制作

网址:http://www.cnblogs.com/muchen/p/5430536.html

2061
来自专栏北京马哥教育

Numpy 隐含的四大陷阱,千万别掉进去了!

陷阱一:数据结构混乱 array 和 matrix 都可以用来表示多维矩阵: ? 看起来效果不错。假设我们要对数据进行筛选,取第 1 列的第 1 行和第 3 ...

4406
来自专栏Java与Android技术栈

App基于手机壳颜色换肤?先尝试一下用 KMeans 来提取图像中的主色

上周,某公司的产品经理提了一个需求:根据用户手机壳颜色来改变 App 主题颜色。可能是由于这天马行空的需求激怒了程序员,导致程序员和产品经理打了起来,最后双双被...

1012
来自专栏北京马哥教育

Numpy 隐含的四大陷阱,千万别掉进去了!

看起来效果不错。假设我们要对数据进行筛选,取第 1 列的第 1 行和第 3 行数据构成一个 2 x 1 的列向量。先看对 array 的做法:

2602
来自专栏mathor

第四届蓝桥杯决赛B组C/C++——格子刷油漆

1033
来自专栏微信模式识别中心技术分享

“变形金刚”为何强大:从模型到代码全面解析Google Tensor2Tensor系统

      Tensor2Tensor(T2T)是Google Brain Team在Github上开源出来的一套基于TensorFlow的深度学习系统。该系统...

2.9K9

扫码关注云+社区

领取腾讯云代金券