基于条件随机场的词性标注方法

基于条件随机场的词性标注方法

词性标注简介

通常我们对一个句子进行语法结构分析时,词性分析是必不可少的步骤,这也使其成为了许多自然语言处理研究者的重点 “关照” 对象。[词性] 指的是根据语言中词本身的特点将不同的词划分为一类。也就是说,对词语按照某种属性进行分类。
对于不同的语言,拥有着不同的词性划分方式。在现代汉语中,主要讲词性分为 12 大类,如下图所示:

image.png

在前面的实验讲解中文分词时,讲到 Python 常用的自然语言处理处理工具:jieba 。jieba 也同样实现了词性标注功能。下面我们先用 jieba 来进行词性标注实验。
jieba 的词性标注接口为 jieba.posseg,下面通过一个实验来说明。

import jieba.posseg as psg
import jieba
str = '自然语言处理是人工智能领域的一个重要分支'
for x in psg.cut(str):
    print(x.word, x.flag)

从上面的输出结果可以看到,词性标注的输出结果是每一个词对应一个词性标记。例如,【处理】被标记成【v】,而【v】是动词的意思,具体每个字母所对应的词性可参考 [国家语委]制定的 [《现代汉语语料库词类标记标准》]。
从输出的结果还可以看到,输出的是每个词。而我们输入的是一句话。这说明了在进行词性分析之前,先要对输入句子进行分词。这也从侧面说明了中文分词的重要性。

词性标注方法

与中文分词一样,词性标注也含有两种主要的方法,基于规则的方法和基于统计的方法。具体的词性标注算法如下:
在这里插入图片描述

image.png

基于字典的方法
基于字典的方法最简单,也是最古老的算法。其核心思想是在字典中构建好每个词对应的词性,然后通过字符串匹配的方法来得出最后的句子中每个词的词性。
基于规则的方法
基于规则的方法主要是在基于字典的方法上建立起来的。基于字典的方法显然有一个缺点,那就是不能解决一词多性问题。
基于规则的方法是先从字典中找到每个词可能对应的词性。然后通过,分析上下文关系来确定最终的词性,而分析上下文就需要制定一个规则来进行。例如,动词后面只能接名词。
早期的规则一般由人编写。然而随着语料库规模的逐步增大,以人工提取规则的方式显然是不现实的,于是人们提出了基于机器学习的规则自动提取方法。如下图所示:
在这里插入图片描述

image.png

基于统计的方法
基于统计的标注方法是最常用的方法之一。基于统计的标注方法的思路与中文分词相似,同样将词性标注看作是一个序列标注任务。如下图所示:
在这里插入图片描述

image.png

看到上图,你是不是感觉很熟悉,这和上一个实验讲解的隐马尔可夫模型不就一回事。一般情况下,使用统计学习的方法都会将自然语言处理中的许多问题转化为序列标注问题。而词性标注就是典型的序列标注问题。因此,词性标注可以使用 [隐马尔可夫模型], [最大熵模型] , [条件随机场] 等方法来进行标注。在上一节,我们已经讲解了隐马尔可夫模型,这一节主要讲解另一个在序列标注中常用的方法:条件随机场。

基于条件随机场的方法

条件随机场与隐马尔可夫模型一样,都是机器学习中一种比较重要的统计模型。而且一般都用于序列标注任务当中。这里为了便于理解,仅讲解线性链条件随机场。条件随机场可用下图进行表示:
在这里插入图片描述

image.png

上图显示了条件随机场的两种表示方法,y 表示状态序列,x 表示观测序列。我们现在来看第二种表示方法。
聪明的你可能已经发现,这跟上一节讲解的隐马尔可夫模型长得差不多嘛。虽然看其来形式差不多,但在事实上,隐马尔可夫和条件随机场是两种截然不同的思路。
与隐马尔可夫模型不同,条件随机场是一种 [判别模型],也就是其建模的对象是P(y∣x) 。也就是在建模时,在给定一组序列x 的条件下,要使得对应输出的一组序列y 的概率值达到最大。同时,条件随机场还假定输出序列y 为一个 [马尔可夫随机场]。其表达式如下:
在这里插入图片描述

image.png

上式公式中的参数对应的解释如下:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

你可能还记得,在上一个实验所讲解的隐马尔科夫模型中,有两个假设,分别是齐次马尔科夫过程假设和观测独立性假设,即:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

你是否感觉隐马尔可夫模型的齐次马尔可夫过程假设与条件随机场的转移特征函数有那么一点像呢。
在这里插入图片描述

image.png

在这里插入图片描述

image.png

在这里插入图片描述

image.png

但在条件随机场中,我们将转移特征函数定义成如下:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

同理,隐马尔可夫模型的发射矩阵在条件随机场中也可以使用同样的方式获得。假设我们有一句话【明天要下雨】,该句话在隐马尔可否模型中所对应的的发射矩阵则如下图所示:
在这里插入图片描述

image.png

同样的方法我们在条件随机场构建一个实现与隐马尔可夫模型的发射矩阵功能类似的状态特征函数。如下图所示:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

从上面的推导可以看出,隐马尔可夫模型只是条件随机场的一种特殊形式。相比于隐马尔可夫模型,条件随机场还可以通过定义其他的特征函数来提取更多的特征。
接下来,我们接着来看条件随机场。
条件随机场的简化形式
为了便于理解,对条件随机场进行相应的简化。对同一个特征函数在各个位置求和,将局部特征函数转化为一个全局特征函数,这样就可以将条件随机场写成权值向量和特征向量的内积形式,即条件随机场的简化形式。具体操作如下:
首先将转移特征和状态特征及其权值用统一的符号表示。设有K1个转移特征函数,K2个状态特征函数,总的特征函数为K=K1+K2
然后,对每个转移特征函数与状态特征函数在观测序列x的各个位置i进行求和,即:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

用一个向量来存放权重,用另一个向量来存放特征函数,即:
在这里插入图片描述

image.png

则条件随机场的向量形式可以表达为:
在这里插入图片描述

image.png

从上面的推导可以看到,其实条件随机场就是一组权重向量w乘以所定义的特征函数F(y,x)。再对其进行归一化得到条件概率。而权重w也正是训练的对象。换句话说,就是要寻找最佳的一组权重向量w ,使得输入序列x时得到对应序列y的概率。
由于条件随机场公式相对复杂,其概念也相对难以理解。但幸运的是,目前已经有许多条件随机场很好的实现工具。一般在使用条件随机场时,也不会从头开始写,而是使用相关工具来完成即可。
不过为了让你对条件随机场有一个更清晰的认识。下面我们来动手实现一个条件随机场。当然下面动手实现的这部分会比较难以理解。你也可以跳过该部分直接去看下文使用 sklearn-crfsuite 来完成的实验。
条件随机场的代码实现
在这里插入图片描述

image.png

在这里插入图片描述

image.png

则对该句话建立的矩阵形状为4×5×5。4 表示观测序列x的 4 个位置,即位置 1:【我】;位置 2:【爱】;位置 3:【中国】;位置 4:【end】。5 则表示观测序列可取的值。即:n、v、r、start、end。5×5 就类似于一个转移矩阵,如下:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

理解了上面所述的矩阵,现在开始动手来实现条件随机场。实验数据选用的是国家语委标注的 [现代汉语语料库]数据。我们先使用下面命令进行下载数据。

!wget -nc "https://labfile.oss-cn-hangzhou.aliyuncs.com/courses/1329/corpus.txt"

下载完数据之后,现在来导入数据,并打印出前 5 个句子。

data = []
with open("corpus.txt", 'rb') as f:
    line = f.readline()
    while line:
        sentence = line.decode("gb18030", "ignore")
        data.append(sentence)
        line = f.readline()
for i in range(5):
    print(data[i])

从上面显示的结果可以看到,在该标注数据集中,每句话前面都会有一个序号和几个空格。而且每个句子的【你】字,都会有 “[]” 这样一对括号。因此,现在进行数据预处理,把每个句子前面的序号和空格都去掉。而且把 “[]” 去掉。

pre_data = []  # 存放处理后的数据
diti = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
for sentence in data:
    for i in range(8):
        if sentence[i] not in diti:
            index = i
            break
    sentence = sentence[index+3:]  # 删除掉每个句子前面的序号和空格
    sentence = sentence.strip()
    sentence = sentence.replace(' [', '')
    sentence = sentence.replace('[', '')
    sentence = sentence.replace('] ', '')
    sentence = sentence.replace(']', '')
    pre_data.append(sentence)
for i in range(5):
    print(pre_data[i])

通过数据预处理,现在得到一个正常的数据集。现在将每个句子的词和词性进行分开。由于条件随机场复杂度高,而且运行速度较慢,所以这里仅读取前 5 个句子来进行实验。

data_X = []  # 存放句子
data_y = []  # 存放句子对应的词性标注
all_labels = set()  # 存放训练集中所有的词性类别
observes = set()  # 存放训练集中所有不同的词
for i in range(5):  # 仅读取前 5 句话
    sentence = pre_data[i]
    sentence = sentence.split()  # 对每句话按空格进行划分成词
    temp_word = []
    temp_label = []
    for sent_word in sentence:
        word = sent_word.split('/')[0]  # 对每个词按斜杠划分出词和词性
        label = sent_word.split('/')[1]
        all_labels.add(label)
        observes.add(word)
        temp_word.append(word)
        temp_label.append(label)
    data_X.append(temp_word)
    data_y.append(temp_label)

打印出第一句话以及对应的词性标注。这里为了方便查看,将数据转换成为 [Pandas]的 DataFrame 格式进行输出。

import pandas as pd
dc={'原句子':data_X[0],'正确标注':data_y[0]}
df=pd.DataFrame(dc)
df.T

接下来定义几个重要的全局变量。

import numpy as np

START = 'start'
END = 'end'
# 所有的词性类别标签
LABELS = [START, END] + list(all_labels)
# 构建一个字典将词性类别标签转化为对应的数字
LABEL_ID = {l: i for i, l in enumerate(LABELS)}
OBSERVES = observes
# 特征函数的个数,也即是权重向量的个数 w
FEAT_FUNC_COUNT = len(LABELS)*len(LABELS)+len(LABELS)*len(OBSERVES)

现在定义特征函数。为了便于理解,这里只定义上文中所讲解的两种特征函数,即模仿隐马尔可夫模型的状态转移矩阵和发射矩阵的两种特征函数。如果你忘记了,可回头去看上文对隐马尔可夫模型进行对比的两张矩阵图。

def get_feature_functions():
    """生成转移特征函数和状态特征函数"""
    # 转移特征函数
    transition_functions = [
        lambda yp, y, x_v, i, _yp=_yp, _y=_y: 1 if yp == _yp and y == _y else 0
        for _yp in LABELS for _y in LABELS
    ]
    # 状态特征函数
    tagval_functions = [
        lambda yp, y, x_v, i, _y=_y, _x=_x: 1 if i < len(
            x_v) and y == _y and x_v[i] == _x else 0
        for _y in LABELS
        for _x in OBSERVES]

    return transition_functions + tagval_functions

定义一个函数来完成所有的特征函数对输入观测序列x进行的特征提取。

def get_all_features(x_vec):
    """对输入句子进行特征提取,并转化为矩阵的形式"""
    ft_fun = get_feature_functions()
    # 创建一个空矩阵来存放输入句子每个位置所提取到的特征
    result = np.zeros((len(x_vec) + 1, len(LABELS),
                       len(LABELS), FEAT_FUNC_COUNT))
    for i in range(len(x_vec) + 1):
        for j, yp in enumerate(LABELS):
            for k, y in enumerate(LABELS):
                for l, f in enumerate(ft_fun):
                    result[i, j, k, l] = f(yp, y, x_vec, i)
    return result

使用上面所定义的特征提取函数,进行实验。使用前面所读取的数据的第一个句子来测试。

sentence = data_X[0]
sent_vec = get_all_features(sentence)
sent_vec.shape

可以看到,输出结果矩阵的形状为(33,18,18,1800)。33 为句子的长度加1。也就是加上我们在句子末尾所添加的【end】,因为加上该值才能计算出句子的最后一个词转移到【end】的特征。18 表示训练集的所有词性类别加上【start】和【end】。1800 则表示特征函数的个数。且 1800=18×18+18×82。18 表示总的词性类别,18×18 则表示总的转移特征函数。18×82 表示总的状态特征函数,82 表示训练集不重复的总词数。
现在定义一个函数来完成所有的转换。并对每个句子对应的词性添加【start】和【end】和将词性转换成对应的数字。

import copy
from tqdm.notebook import tqdm
def create_vector_list(x_vecs, y_vecs):
    """
    数据预处理
    """
    print("提取特征,创建矩阵形式")
    print("总共含有 {} 个句子".format(len(x_vecs)))
    # 完成所有句子的转换
    observations = [get_all_features(x_vec) for x_vec in tqdm(x_vecs)]
    label = len(y_vecs) * [None]
    # 在词性序列的前面加上【start】和末尾加上【end】,然后转化为对应的数字

    for i in range(len(y_vecs)):
        assert (len(y_vecs[i]) == len(x_vecs[i]))
        temp_y = copy.copy(y_vecs[i])
        temp_y.insert(0, START)
        temp_y.append(END)
        label[i] = np.array([LABEL_ID[y] for y in temp_y], dtype=np.int)
    return observations, label

对所有的句子进行特征提取,并打印出每个句子的矩阵形状。下方代码单元格执行耗时约 3 分钟,请耐心等待。

x_vecs, y_vecs = create_vector_list(data_X, data_y)
for x in x_vecs:
    print(x.shape)

可以看到,每个句子提取的矩阵的第一个维度是不同的。当然,这是因为每个句子的长度不同。
上面主要是将输入句子转化成为相应的矩阵形式。下面接着来看条件随机场是如何进行训练的。
条件随机场的训练
在这里插入图片描述

image.png

模型训练就是要找到w使得L(w)达到最大。但可以对L(w)取负数来对其进行最小优化。所以优化函数等价于:
在这里插入图片描述

image.png

从上式的表达式可以看出,梯度就是特征函数关于模型的期望减去特征函数关于训练数据的期望。
特征函数关于训练数据的期望指的就是,当输入一组观测序列x和对应的状态序列y时,得到的一组特征函数 f(y,x) 的值。
而特征函数关于模型的期望就是,当输入一组观测序列x。通过模型的计算得到对应不同状态序列y的值,然后得到一组特征函数f(y,x) 的值。
在这里插入图片描述

image.png

先来看前向递推公式:
在这里插入图片描述

image.png

同理来看后向递推公式:
在这里插入图片描述

image.png

在这里插入图片描述

image.png

在这里插入图片描述

image.png

完成上面的数学推导,则现在可以写代码来实现。首先来定义前向运算和后向运算函数。这里需要注意的是,为了防止计算得出的数值太小,导致溢出。使用 log 函数来解决。就是对一些值取对数来防止因其值过小而向下溢出。

from scipy import special

def forward(log_M_s, start):
    """前向运算函数"""
    T = log_M_s.shape[0]
    Y = log_M_s.shape[1]
    alphas = np.NINF * np.ones((T+1, Y))  # log0 = ninf
    alpha = alphas[0]
    alpha[start] = 0  # log1 = 0
    for t in range(1, T+1):
        # logsumexp 表示先指数化exp,再求和sum,最后取对数log
        alphas[t] = special.logsumexp(np.expand_dims(
            alpha, axis=1) + log_M_s[t - 1], axis=0)
        alpha = alphas[t]
    return alphas

def backward(log_M_s, end):
    """后向运算函数"""
    T = log_M_s.shape[0]
    Y = log_M_s.shape[1]
    betas = np.NINF * np.ones((T+1, Y))  # log0 = ninf
    beta = betas[-1]
    beta[end] = 0  # log1 = 0
    for t in reversed(range(T)):
        # logsumexp 表示先指数化exp,再求和sum,最后取对数log
        betas[t] = special.logsumexp(
            log_M_s[t] + np.expand_dims(beta, axis=0), axis=1)
        beta = betas[t]
    return betas

现在来定义一个函数来求出特征函数关于模型的期望和归一化因子。

def comp_Ep_f_and_Z(log_M_s, x_vec):
    """
    计算特征函数关于模型的期望Ep(f)和归一化因子Z(x)
    log_M_s:条件随机场的矩阵形式,形状为 (n+1,Y,Y),Y 为标签数
    x_vec:用于特征函数提取特征后所得到的矩阵形式,形状为 (n+1,Y,Y,K) K为特征函数的个数
    """
    log_alphas = forward(log_M_s, LABEL_ID[START])  # 计算 alpha
    last = log_alphas[-1]
    log_Z = special.logsumexp(last)  # 计算 Z(x)
    log_betas = backward(log_M_s, LABEL_ID[END])  # 计算 beta

    log_alphas1 = np.expand_dims(log_alphas[1:], axis=2)
    log_betas1 = np.expand_dims(log_betas[:-1], axis=1)
    log_probs = log_alphas1 + log_M_s + log_betas1 - log_Z
    log_probs = np.expand_dims(log_probs, axis=3)

    exp_features = np.sum(np.exp(log_probs) *      # 计算特征函数关于模型的期望
                          x_vec, axis=(0, 1, 2))
    return exp_features, log_Z

定义一个函数来计算极大似然损失和梯度。

def likelihood_and_deriv(x_vec_list, y_vec_list, w, debug=False):
    """计算极大似然损失和梯度"""
    likelihood = 0
    derivative = np.zeros(len(w))
    # 对观测序列x的每一个位置
    for x_vec, y_vec in zip(x_vec_list, y_vec_list):
        # x_vec:形状为 (n+1,Y,Y,K),Y为词性类别数量, K为特征函数的个数
        length = x_vec.shape[0]
        # 计算条件随机场的 M 矩阵,得到的形状为 (n+1,Y,Y),Y 为标签数
        log_M_s = np.dot(x_vec, w)
        # 计算特征函数关于模型的期望和归一化因子
        exp_features, log_Z = comp_Ep_f_and_Z(log_M_s, x_vec)
        # 计算特征函数关于训练数据的期望
        emp_features = np.sum(
            x_vec[range(length), y_vec[:-1], y_vec[1:]], axis=0)
        # 计算似然函数
        likelihood += np.sum(log_M_s[range(length),
                                     y_vec[:-1], y_vec[1:]]) - log_Z
        # 计算梯度
        derivative += emp_features - exp_features
    return -likelihood, -derivative

定义一个训练函数,用于训练模型。这里直接调用 [SciPy] 库提供的 scipy.optimize.fmin_l_bfgs_b 接口来进行优化。

from scipy import optimize

def train(x_vecs, y_vecs, w):
    """训练模型,更新w"""
    print("开始训练 ...")

    def l(w):
        return likelihood_and_deriv(x_vecs, y_vecs, w)
    # 调用 scipy 库提供的 l_bfgs 优化算法。
    val = optimize.fmin_l_bfgs_b(l, w)
    w, _, _ = val
    return w

随机初始化一组权重向量,然后训练模型寻找出一组最优的权重向量,并打印出其结果。

# 初始化权重向量 w,w 的长度等于特征函数的个数。
w = np.random.randn(FEAT_FUNC_COUNT)
# 训练模型,得到一组最佳的权重向量 w
result_w = train(x_vecs, y_vecs, w)
result_w

条件随机场的预测
上面我们主要完成了条件随机场模型的训练,接下来使用训练好的模型来进行预测。
条件随机场的预测与隐马尔可夫模型一样,都是寻找一条最优路径的问题。换句话说,就是输入一组观测序列x ,然后求出一组最佳的输出序列y 的问题。因此条件随机场同样可以使用 [维特比] 算法来求解算法来进行求解。
在这里插入图片描述

image.png

通过上面两个公式就可以计算出在给定输出序列x的条件下,前一个状态转移到当前状态的概率。假设我们要预测的句子为【我爱中国】,使用上面两个公式建立出来的篱笆网络如下。
在这里插入图片描述

image.png

看到上图,你是否感觉很熟悉呢。其实条件随机场的预测问题与隐马尔可夫模型一样。都是求最优路径问题,一般都是使用维特比算法来进行求解。维特比算法在上一个实验中进行了详细的讲解。因此其求解过程这里不再赘述。下面定义一个函数来实现维特比算法。

def predict(x_vec, w):
    """给定x,预测y。使用Viterbi算法"""
    # 对输入的句子进行特征提取,得到一个形状为(n+1,Y,Y,K)的矩阵
    all_features = get_all_features(x_vec)
    # 特征乘以权重得到矩阵 M
    log_potential = np.dot(all_features, w)
    T = len(x_vec)
    Y = len(LABELS)
    # Psi保存每个时刻最优情况的下标
    Psi = np.ones((T, Y), dtype=np.int32) * -1
    # 初始化
    delta = log_potential[0, 0]
    # 递推
    for t in range(1, T):
        next_delta = np.zeros(Y)
        for y in range(Y):
            w = delta + log_potential[t, :, y]
            Psi[t, y] = psi = w.argmax()
            next_delta[y] = w[psi]
        delta = next_delta
    # 回溯找到最优路径
    y = delta.argmax()
    trace = []
    for t in reversed(range(T)):
        trace.append(y)
        y = Psi[t, y]
    trace.reverse()
    return [LABELS[i] for i in trace]

下面使用数据集里的第一个句子来进行测试。

sentence = data_X[0]
sentence_label = data_y[0]
pred = predict(sentence, result_w)
dc={'原句子':sentence,'正确标注':sentence_label,'预测标注':pred}
df=pd.DataFrame(dc)
df.T

从上面的结果可以看到,该模型以及成功对输入句子进行词性标注。有些词可能没有标对,这是因为我们考虑到运算时间的问题,所以训练数据集只取 5 个句子。这里也主要是做一个演示,以至于让你对条件随机场有一个更深刻的理解。
sklearn-crfsuite 实现条件随机场
上面主要实现了一个简单的条件随机场模型。一般在工程应用中,很多人都会选择去调用现成的开源库,而不是自己从头开始写一个模型。因此,这里介绍几个常用的条件随机场的实现库。

目前条件随机场实现的库主要有:
[CRF++] :CRF++ 是著名的条件随机场的开源工具,也是目前综合性能最佳的 CRF 工具。但 CRF++ 主要在终端里运行,在 NoteBook 环境中不方便演示。如果你对该工具感兴趣,可以自行去官网下载。
[FlexCRFs] :FlexCRFs 主要使用 C/C++ 实现,主要应用于自然语言处理中的序列标注任务,性能强大。
[CRFsuite] :CRFsuite 也是用 C/C++ 实现的,其也是主要应用于自然语言处理中的序列标注任务。
[python-crfsuite]:python-crfsuite 是 CRFsuite 的封装,使其可以在 Python 环境中使用。
[sklearn-crfsuite] :sklearn-crfsuite 则是 python-crfsuite 的高层封装,使得使用条件随机场就跟使用 sklearn 中的其他算法一样简单。

为了便于理解,接下来使用 sklearn-crfsuite 来进行实验。sklearn-crfsuite 的安装很简单,可以使用如下命令进行安装:

!pip install sklearn-crfsuite

导入实验所必须的库。

import sklearn_crfsuite
from sklearn_crfsuite import metrics

重新进行数据处理,这一次读取全部的数据,并打印出数据的长度。

data_X1 = []  # 存放句子
data_y1 = []  # 存放句子对应的词性标注
for sentence in pre_data:
    sentence = sentence.split()  # 对每句话按空格进行划分成词
    temp_word = []
    temp_label = []
    for sent_word in sentence:
        word = sent_word.split('/')[0]  # 对每个词按斜杠划分出词和词性
        label = sent_word.split('/')[1]
        temp_word.append(word)
        temp_label.append(label)
    data_X1.append(temp_word)
    data_y1.append(temp_label)
len(data_X1), len(data_y1)

从上的输出结果可以看到,该份数据总共含有 6000 个句子,现在取前 5000 个句子进行训练,取后 1000 个句子进行测试。将数据划分为训练集和测试集。

# 划分训练集和测试集
train_X = data_X1[:5000]
train_y = data_y1[:5000]
test_X = data_X1[5000:]
test_y = data_y1[5000:]

len(train_X), len(train_y), len(test_X), len(test_y)

sklearn-crfsuite 要求对输入句子的每个词用一个字典进行保存,字典中可定义多个关于词的特征,例如定义一个键来存放该词是否为数字或者该词由几个字组成等特征。当然定义的特征越丰富,训练结果也就相对越好。
现在定义一个函数将数据转换成为 sklearn-crfsuite 要求的输入格式。

def word2features(sentence):
    features = []
    for sent in sentence:
        feature = {}
        feature['word'] = sent
        feature['len'] = len(list(sent))
        features.append(feature)
    return features

转换训练集所有的句子,并打印出第一个句子的转换结果。

train_X_dc = [word2features(sentence) for sentence in train_X]
train_X_dc[0]

sklearn_crfsuite 提供的条件随机场接口为 sklearn_crfsuite.CRF 。其主要输入参数如下:

- algorithm: 优化算法,默认为 'lbfgs'。
- c1: L1 正则系数,默认为 0。
- c2:L2 正则系数,默认为 0。
- max_iterations: 迭代次数,。

现在定义一个条件随机场模型,训练进行训练。

crf = sklearn_crfsuite.CRF(
    algorithm='lbfgs',
    c1=0.1,
    c2=0.1,
    max_iterations=40,
)
crf.fit(train_X_dc, train_y)

对于 5000 个句子的数据,使用 sklearn_crfsuite 提供的条件随机场训练总共花了差不多 5 分钟。相比于前面自己定义的模型实在快太多。但相比于隐马尔可夫模型等其他方法,要慢很多。这也说明了条件随机场的复杂度太高。需要花费更多的时间来训练。
现在使用【我爱中国】来进行测试训练好的模型。

sentence = []
sentence.append(['我', '爱', '中国'])
sentence_dc = [word2features(sent) for sent in sentence]
y_pred = crf.predict(sentence_dc)
dc={'原句子':sentence[0],'预测标注':y_pred[0]}
df=pd.DataFrame(dc)
df

从输出结果可以看到,该模型能准确标注出每个词的词性。现在使用测试集对模型进行测试。

test_X_dc = [word2features(sentence) for sentence in test_X]
y_pred = crf.predict(test_X_dc)
# 打印测试集的第一个句子预测结果
dc={'原句子':test_X[0],'正确标注':test_y[0],'预测标注':y_pred[0]}
df=pd.DataFrame(dc)
df.T

这里选用 [F1值]来对模型进行评价。只需调用 scikit-learn 中提供的 sklearn_crfsuite.metrics.flat_f1_score 接口来完成即可。

labels = list(crf.classes_)
metrics.flat_f1_score(test_y, y_pred,
                      average='weighted', labels=labels)

从结果可以看到,该训练集只有 5000 个句子,而且我们并未对每个词定义更多的特征。而且我们只定义模型迭代次数为 40。而条件随机场的测试 F1 值达到或接近 90%。这说明条件随机场在序列标注任务中,性能强大。接下来打印出更多测评细节特征。

print(metrics.flat_classification_report(
    test_y, y_pred, labels=labels, digits=3
))

使用条件随机场工具进行分词

挑战介绍

在上一个实验中,我们讲解了词性标注的基本方法,并重点介绍了条件随机场的基本原理,且使用到了 Python 常用的条件随机场工具包 sklearn-crfsuite。
条件随机场是目前最出色的序列标注算法之一。因此本次挑战要求你使用 sklearn-crfsuite 来进行中文分词。由于实验环境没有预装该工具包,因此需要在挑战环境中自行安装。可以在终端输入下面代码进行安装:

sudo pip3 install scikit-learn
sudo pip3 install sklearn-crfsuite

本次挑战的数据仍然使用前面两个关于中文分词实验所使用的数据,即北京大学所标注的人民日报上的数据。你可以在终端执行下面命令进行下载,并保存到 ~/Code 文件夹中。

wget -nc https://labfile.oss.aliyuncs.com/courses/1329/pku_training.txt

该份数据是一个文本数据,因此需要对其进行预处理,得到句子和对应的标注。例如得到 [‘去’,‘成’,‘都’,‘看’,‘大’,‘熊’,‘猫’] ,对应的标注为 [‘S’,‘B’,‘E’,‘S’,‘B’,‘M’,‘E’]。
关于数据预处理的部分可以去参考:实验 3 基于隐马尔可夫模型的中文分词方法。
上面的处理完成之后,还需要将句子转化为 sklearn-crfsuite 需要的格式,例如将 [‘去’,‘成’,‘都’,‘看’,‘大’,‘熊’,‘猫’] 处理为 [{‘word’:‘去’},{‘word’:‘成’},{‘word’:‘都’},{‘word’:‘看’},{‘word’:‘大’},{‘word’:‘熊’},{‘word’:‘猫’}] 。关于转化部分可以去参考上一个实验。然后按 1:1 的比例划分训练集和测试集。
然后使用 sklearn-crfsuite 进行训练。训练完成之后使用 f1 评价指标对测试集进行评价得到一个评价值,最后返回该值。这里可以参考上一个实验。

挑战内容

在本次挑战中,你需要在 ~/Code/sk_crf.py 文件中编写一个函数 crf_model,crf_model 函数不传入参数,但要返回一个 f1 评价值。在实现时,由于数据预处理代码量可能过大,因此可以定义多个函数。但必须定义 crf_model 函数,且返回 f1 评价结果的只能是该函数。

挑战要求

代码必须写入 ~/Code/sk_crf.py 文件中。
主函数名必须是 crf_model 。
测试时请使用 python3 运行 sk_crf.py ,避免出现无相应模块的情况。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辣椒种子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值