有关实体的基本信息:
Inside–outside–beginning(标记)
IOB (Inside–outside–beginning)是用于标记标志的通用标记格式。
import pandas as pd
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.linear_model import Perceptron
from sklearn.model_selection import train_test_split
from sklearn.linear_model import SGDClassifier
from sklearn.linear_model import PassiveAggressiveClassifier
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
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()
我们有4,544个句子,其中包含10,922个独特单词并标记为17个标签。
标签分布不均匀。
df.groupby('Tag').size().reset_index(name='counts')
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
per = Perceptron(verbose=10, n_jobs=-1, max_iter=5)
per.partial_fit(X_train, y_train, classes)
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 = 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查找POS标记、命名实体识别等应用。
我们将使用sklearn-crfsuite在我们的数据集上训练用于命名实体识别的CRF模型。
import sklearn_crfsuite
from sklearn_crfsuite import scorers
from sklearn_crfsuite import metrics
from collections import Counter
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, t in zip(s['Word'].values.tolist(),
s['POS'].values.tolist(),
s['Tag'].values.tolist())]
self.grouped = self.data.groupby('Sentence #').apply(agg_func)
self.sentences = [s for s in 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
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 i in range(len(sent))]
def sent2labels(sent):
return [label for token, postag, label in sent]
def sent2tokens(sent):
return [token for token, postag, label in sent]
X = [sent2features(s) for s in sentences]
y = [sent2labels(s) for s in sentences]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=0)
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), weight in 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), weight in 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:])
import eli5
eli5.show_weights(crf,top = 10)
观察:
为便于阅读,我们只检查一部分标签。
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'])