学习笔记——成功解决了跑CRF框架

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

一,介绍需要用到的库

(1), sklearn_crfsuite这个库的安装导入看我之前的博客https://mp.csdn.net/mp_blog/creation/editor/119104841

import scipy
import sklearn_crfsuite
from sklearn.metrics import make_scorer
from sklearn.model_selection import train_test_split
from sklearn_crfsuite import scorers
from sklearn_crfsuite import metrics
from collections import Counter
from sklearn. model_selection import cross_val_score#交叉验证,只需将 cross_validation 改为 model_selection 即可
from sklearn.model_selection import GridSearchCV#sklearn.grid_search在版本0.18就被弃用了,
# 版本0.2用sklearn.model_selection模块代替
# from sklearn.model_selection import GridSearchCV 就对了

(2)更多详细内容请康康我其他博客

https://blog.csdn.net/weixin_51130521/article/details/119205748

二,数据

(1)数据介绍

数据是IOB和POS标签注释的特征设计语料库在如下链接

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

数据大概是这样式的

有关实体的基本信息

  • 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)

(2)数据读取

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

df= pd.read_csv('./ner_dataset.csv',encoding="ISO-8859-1")##ISO-8859-1默认为英文编码ISO-8859-1改成gbk支持中文
df= df [:10000]
df.head()#将excel表格中的第一行看作列名,默认输出之后的5行
  1. df= pd.read_csv('./ner_dataset.csv',encoding="ISO-8859-1") 
  • './ner_dataset.csv' ——表示一个相对路径,需要把数据和代码放到同一个路径的目录下边就可以读取数据了。
  •  encoding="ISO-8859-1"——ISO-8859-1默认为英文编码ISO-8859-1改成gbk支持中文

        2. df= df [:10000] 

  •    整个数据集不能装入一台计算机的内存中,因此我们选择前100,000个记录,

        3. df.head()#将excel表格中的第一行看作列名,默认输出之后的5行 

 其实跟excel表格的差别不大

 (3)数据统计

         判断缺失值然后将列中为空的个数统计出来

df.isnull().sum()#判断缺失值然后将列中为空的个数统计出来

详见https://blog.csdn.net/weixin_51130521/article/details/119220708

(4)数据预处理

df= df.fillna(method='ffill')#我们注意到“Sentence#”列中有很多NaN值,我们用前面的值填充NaN。
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')#获取标签的行数

1.  df= df.fillna(method='ffill')#我们注意到“Sentence#”列中有很多NaN值,我们用前面的值填充NaN。

2. df['Sentence #'].nunique(), df.Word.nunique(), df.Tag.nunique()
  • nuinque()是查看该序列(axis=0/1对应着列或行)的不同值的数量。用这个函数可以查看数据有多少个不同值。 后边同理。
  • 的出来的结果是我们有4,544个句子,其中包含10,922个独特单词并标记为17个标签。

3.  df.groupby('Tag').size().reset_index(name='counts')#获取标签的行数

  • 我们发现标签分布不均匀

 (5)使用DictVectorizer将文本转换为向量

X= df.drop('Tag',axis= 1)#代表将'Tag'对应的列标签(们)沿着水平的方向依次删掉。
v= DictVectorizer(sparse= False)#特征转换
X= v.fit_transform(X.to_dict('records'))
y= df.Tag.values
classes= np.unique(y)#该函数是去除数组中的重复数字,并进行排序之后输出。
classes= classes.tolist()#将数组或者矩阵转化成列表

1.  X= df.drop('Tag',axis= 1)#代表将'Tag'对应的列标签(们)沿着水平的方向依次删掉。

2.  v= DictVectorizer(sparse= False)#特征转换
3. X= v.fit_transform(X.to_dict('records'))

参考连接https://blog.csdn.net/qq_27328197/article/details/113807051

4. X= v.fit_transform(X.to_dict('records'))

 参考连接https://blog.csdn.net/weixin_38278334/article/details/82971752

5.  classes= np.unique(y)#该函数是去除数组中的重复数字,并进行排序之后输出。
6.  classes= classes.tolist()#将数组或者矩阵转化成列表

  • 该函数是去除数组中的重复数字,并进行排序之后输出。
    
  • 将数组或者矩阵转化成列表

 参考连接https://blog.csdn.net/qq_35290785/article/details/96165472

三,检索带有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, 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)  # 实例化
sent = getter.get_next()  # 输出元组中每一个单词对应得B-I-O
sentences = getter.sentences  # ”拿到句子“,比如len(sentences)就是句子得个数

 四,特征处理

"""
特征处理流程,主要选择处理了如下几个特征:
 - 当前词的小写格式
 - 当前词的后缀
 - 当前词是否全大写 isupper
 - 当前词的首字母大写,其他字母小写判断 istitle
 - 当前词是否为数字 isdigit
 - 当前词的词性
 - 当前词的词性前缀
 - 还有就是与之前后相关联的词的上述特征(类似于特征模板的定义)
"""
def word2features(sent, i):  # sent作为一个句子单位(字+标签)
    ''' 特征提取器 '''
    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(),
        # 如果字符串是以标题格式开头的字符串,并且至少有一个字符,则返回 True,否则返回 False。
        '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):
    '''
        提取句子 label
    '''
    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)

七,剩下代码自己康康叭

def print_transitions(trans_features):
    for (label_from, label_to), weight in trans_features:
        print("%-6s -> %-7s %.6f" % (label_from, label_to, weight))


def print_state_features(state_features):
    for (attr, label), weight in state_features:
        print("%.6f %-8s %s" % (weight, label, attr))


if __name__ == "__main__":
    # 注意,迭代取最优模型时一定要在 if __name__ == "__main__" 中
    # 设定模型和超参数
    crf = sklearn_crfsuite.CRF(
        algorithm='lbfgs',
        c1=0.1,
        c2=0.1,  # L2 正则化的系数。
        max_iterations=100,
        all_possible_transitions=True)

    # 开始训练
    crf.fit(X_train, y_train)
    labels = list(crf.classes_)
    print
    labels  # ['B-LOC','O','B-ORG','B-PER','I-PER','B-MISC','I-ORG','I-LOC','I-MISC']
    labels.remove('O')  # 标签“O”(outside)是最常见的标签,它会使我们的结果看起来比实际更好。
    # 因此,当我们评估分类指标时,我们会删除标记“O”

    # 使用测试集评测
    Y_pred = crf.predict(X_test)
    metrics.flat_f1_score(y_test, Y_pred, average='weighted', labels=classes)
    # 获得标记是 B 或者 I 的结果
    sorted_labels = sorted(labels,
                           key=lambda x: (x[1:], x[0]))

    print("初始模型效果如下...")
    print(metrics.flat_classification_report(y_test,
                                             Y_pred,
                                             labels=sorted_labels,
                                             digits=3))  # digits 表示保留几位小数
    # 定义超参数和参数查找空间
    crf = sklearn_crfsuite.CRF(
        algorithm='lbfgs',
        max_iterations=100,
        all_possible_transitions=True)
    params_space = {'c1': [scipy.stats.expon(scale=0.5)],
                    'c2': [scipy.stats.expon(scale=0.05)]}#概率

    # 使用相同的基准评估数据
    f1_scorer = make_scorer(metrics.flat_f1_score, average='weighted', labels=labels)
    # 查询最佳模型
    rs = GridSearchCV(crf, params_space,
                      cv=3,
                      verbose=1,
                      n_jobs=-1,
                      # n_iter=50,
                      scoring=f1_scorer)
    rs.fit(X_train, y_train)

    # 输出最佳模型参数
    print("The Best Params:", rs.best_params_)
    print("The Best CV score:", rs.best_score_)
    print("Model Size:{:.2f}M".format(rs.best_estimator_.size_ / 1000000))

    crf = rs.best_estimator_
    Y_pred = crf.predict(X_test)
    print("最佳模型效果如下...")
    print(metrics.flat_classification_report(y_train,
                                             Y_pred,
                                             labels=sorted_labels,
                                             digits=3))

    print("\n最大转移概率")
    print_transitions(Counter(crf.transition_features_).most_common(20))

    print("\n最低转移概率")
    print_transitions(Counter(crf.transition_features_).most_common()[-20:])

    print("\nTop 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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值