利用隐马尔可夫模型 (HMM) 完成命名实体识别 (NER) 任务

利用隐马尔可夫模型 (HMM) 完成命名实体识别 (NER) 任务

一、源码地址

https://github.com/busyforest/NER_Project/

二、关于 NER

命名实体识别(Named Entity Recognition,NER)是自然语言处理(NLP)中的一项关键任务,旨在识别并分类文本中具有特定意义的实体。

  • 定义

    • NER任务的目标是识别文本中提到的命名实体,并将这些实体分类到预定义的类别中。这些类别通常包括人名、地名、组织机构、日期、时间、数量、货币等。
  • 示例

    • 例如,给定一句话:“Apple was founded by Steve Jobs in Cupertino.”
    • NER系统应该识别出:"Apple"是一个组织,"Steve Jobs"是一个人名,"Cupertino"是一个地名。
  • 应用

    • 可以从大量文本数据中快速提取有用的信息,比如一大段新闻中的关键人物和事件。

    • 可以用于提高机器翻译的准确性,确保命名实体在翻译过程中保持一致性。

      • 例如,给定一句话:“I am going to study HMM.”,翻译系统试图将其翻译成中文。

      • 对 study 这个词,常见的意思中包含(verb,“学习”)和(noun,“研究”)

      • 在翻译成中文的过程中,如果 NER 系统识别出并标记 study 这个词为 verb,那么翻译系统将其翻译

        成"学习"的几率就会大大增加,翻译成"研究"的几率相应下降。

  • 实现方法

    • 基于规则的方法:使用预定义的模式和词典来匹配和识别命名实体。
    • 机器学习方法:利用有标注的训练数据,训练分类器来识别命名实体。
    • 深度学习方法:使用神经网络模型,如 LSTM,CNN,Transformer 来识别命名实体。

使用HMM 正是属于第一种方法。

三、关于 HMM

隐马尔可夫模型(Hidden Markov Model, HMM)是一种统计模型,用于处理时间序列数据或其他序列数据,特别适合于那些观察值受潜在的隐藏状态影响的情景。以下仅对 HMM 做简单介绍,更详细的数学推导可以自行搜索,有很多讲的很清楚的博客,也可以参考李航《统计学习方法》中的相关内容。

  • 基本概念

    • 状态:HMM由一组隐藏状态(hidden states)组成,这些状态在模型中不可直接观测,但影响着观测到的数据。
    • 观测:观测(observations)是可见的数据序列,由隐藏状态生成。每个隐藏状态都有一个与之相关的观测概率分布。
    • 转移概率:隐藏状态之间的转移是根据转移概率(transition probabilities)进行的,表示从一个状态转移到另一个状态的概率。
    • 观测概率:每个隐藏状态生成一个观测值的概率由观测概率(emission probabilities)确定。
    • 初始概率:初始概率(initial probabilities)定义了模型在初始时刻处于每个隐藏状态的概率。
  • 定义

    一个HMM由以下参数定义:

    • $S $:隐藏状态的集合。
    • O O O :可能的观测值的集合。
    • π \pi π :初始状态分布,表示初始时刻处于每个隐藏状态的概率。
    • A A A:状态转移概率矩阵,表示从一个状态转移到另一个状态的概率。
    • B B B :观测概率矩阵,表示在某个状态下生成某个观测值的概率。
  • 基本问题

    • 概率计算。给定模型参数 A , B , π A,B,\pi A,B,π 和观测序列 ( o 1 , o 2 , o 3 , . . . , o t ) (o_1,o_2,o_3,...,o_t) (o1,o2,o3,...,ot),求这个观测序列出现的概率。
    • 状态预测。给定模型参数 A , B , π A,B,\pi A,B,π 和观测序列 ( o 1 , o 2 , o 3 , . . . , o t ) (o_1,o_2,o_3,...,o_t) (o1,o2,o3,...,ot),求这个观测序列对应最大概率的状态序列 ( i 1 , i 2 , i 3 , . . . , i t ) (i_1,i_2,i_3,...,i_t) (i1,i2,i3,...,it)
    • 模型学习。给定观测序列 ( o 1 , o 2 , o 3 , . . . , o t ) (o_1,o_2,o_3,...,o_t) (o1,o2,o3,...,ot) 和确定的状态序列 ( s 1 , s 2 , s 3 , . . . , s t ) (s_1,s_2,s_3,...,s_t) (s1,s2,s3,...,st) ,估计模型参数 A , B , π A,B,\pi A,B,π ,使得用这套参数预测出来的最大概率状态序列 ( i 1 , i 2 , i 3 , . . . , i t ) (i_1,i_2,i_3,...,i_t) (i1,i2,i3,...,it) ( s 1 , s 2 , s 3 , . . . , s t ) (s_1,s_2,s_3,...,s_t) (s1,s2,s3,...,st) 最接近。
  • 算法

    • 概率计算方法有前向算法、后向算法等。
    • 状态预测算法有维特比解码。
    • 模型学习有极大似然法、鲍勃-韦尔奇算法等。
四、文件结构
  • HMM 目录
    • NER 目录:主要是训练集和测试集,还有一个测试脚本 check.py ,可以检验模型的正确率。运行 check.py 需要安装 sklearn 库。
    • HMM.py :主文件,实现了中英文分词的 NER 任务。
五、代码简析
from collections import defaultdict

initial_matrix = defaultdict(float)
initial_total = 0
transition_matrix = defaultdict(lambda: defaultdict(float))
transition_total = defaultdict(float)
emission_matrix = defaultdict(lambda: defaultdict(float))
emission_total = defaultdict(float)
zero_num = 0
total = 0


def hmm_learn(file_path):
    words = []
    labels = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            tokens = line.strip().split()
            if len(tokens) == 2:
                word, label = tokens
                words.append(word)
                labels.append(label)
            else:
                get_initial_data(labels)
                get_transition_data(labels)
                get_emission_data(words, labels)
                words.clear()
                labels.clear()
        compute_matrix()


def get_initial_data(labels):
    global initial_total
    initial_matrix[labels[0]] += 1
    initial_total += 1


def get_transition_data(labels):
    for i in range(len(labels) - 1):
        transition_matrix[labels[i]][labels[i + 1]] += 1
        transition_total[labels[i]] += 1


def get_emission_data(words, labels):
    for i in range(len(words)):
        emission_matrix[labels[i]][words[i]] += 1
        emission_total[labels[i]] += 1


def compute_matrix():
    for i in transition_matrix:
        initial_matrix[i] = (initial_matrix[i] + 1) / initial_total
    for i in transition_matrix:
        for j in transition_matrix:
            transition_matrix[i][j] = (transition_matrix[i][j] + 1) / transition_total[i]
    for i in emission_matrix:
        for j in emission_matrix[i]:
            emission_matrix[i][j] = (emission_matrix[i][j] + 1) / emission_total[i]


def hmm_test(file_path):
    global zero_num
    global total
    words = []
    labels = []
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            tokens = line.strip().split()
            if len(tokens) == 2:
                word, label = tokens
                words.append(word)
                labels.append(label)
            else:
                viterbi(words)
                total += 1
                words.clear()
                labels.clear()


def viterbi(words):
    global zero_num
    # 初始化
    prob = defaultdict(lambda: defaultdict(float))
    path = defaultdict(lambda: defaultdict(float))
    T = len(words)
    for i in initial_matrix:
        if emission_matrix[i][words[0]] == 0:
            temp_count = 0
            for j in emission_matrix[i]:
                if emission_matrix[i][j] == 0:
                    temp_count += 1
            avg_emission_prob = 1 / temp_count
            prob[0][i] = initial_matrix[i] * avg_emission_prob * 1e-10
        else:
            prob[0][i] = initial_matrix[i] * emission_matrix[i][words[0]]
        path[0][i] = 0
        if prob[0][i] == 0:
            prob[0][i] = 1e-10
    # 递推
    for t in range(1, T):
        for i in transition_matrix:
            temp = 0
            index = list(initial_matrix.keys())[0]
            for j in transition_matrix:
                if prob[t - 1][j] * transition_matrix[j][i] > temp:
                    temp = prob[t - 1][j] * transition_matrix[j][i]
                    index = j
            if emission_matrix[i][words[t]] == 0:
                temp_count = 0
                for j in emission_matrix[i]:
                    if emission_matrix[i][j] == 0:
                        temp_count += 1
                avg_emission_prob = 1 / temp_count
                prob[t][i] = temp * avg_emission_prob * 1e-10
                # print(words[t], i, temp * avg_emission_prob * 1e-20)
            else:
                prob[t][i] = temp * emission_matrix[i][words[t]]
            path[t][i] = index
            if prob[t][i] == 0:
                print(words[t], i)
                prob[t][i] = 1e-300

    # 终止
    final_prob = 0
    final_path = defaultdict(float)
    for i in transition_matrix:
        if prob[T - 1][i] > final_prob:
            final_prob = prob[T - 1][i]
            final_path[T - 1] = i
    # print(final_prob)
    # if final_prob == 1e-300:
    #     zero_num += 1
    #     for t in range(T - 2, -1, -1):
    #         final_path[t] = "O"
    # else:
    for t in range(T - 2, -1, -1):
        final_path[t] = path[t + 1][final_path[t + 1]]
    # for t in words:
    #     temp = 0
    #     for i in emission_matrix:
    #         if emission_matrix[i][t] != 0:
    #             temp += 1
    #     if temp == 0:
    #         print(t)
    for t in range(T):
        string = words[t] + " " + final_path[t]
        print(string)
    print(final_prob)
    with open("NER/Chinese/result.txt", "a", encoding="UTF-8") as file:
        for t in range(T):
            print(words[t], final_path[t])
            string = words[t] + " " + final_path[t] + "\n"
            file.write(string)
        file.write("\n")


train_path = "NER/Chinese/train.txt"
test_path = "NER/Chinese/validation.txt"
hmm_learn(train_path)
hmm_test(test_path)
# print(zero_num/total)
  • hmm_learn():从训练集对应的文件路径中构建 HMM 模型,利用极大似然法学习模型参数。
  • get_initial_data()get_transition_data()get_emission_data():分别统计数据,计算初始矩阵,转移矩阵,发射矩阵的值。
  • compute_matrix():对三个矩阵进行归一化处理和拉普拉斯平滑处理,避免出现数值下溢的情况。
  • hmm_test():从测试集对应的文件路径中获取观测序列,进行 HMM 模型的预测。
  • viterbi():对测试集上的观测序列应用学习来的模型参数进行维特比解码。

由于在 NER 任务中,一个 HMM 的序列往往很长,在中文中,按照一句话来划分可能超过150个字甚至更多,所以在进行维特比解码的时候概率连续相乘可能导致数值下溢出现 0 概率值,造成后续的汉字解码不出来的情况。因此我在进行拉普拉斯平滑处理的同时,设定了一个概率最低值10的-10次方,当下溢为 0 时就重新设定概率值,这样得到的模型有较高的预测成功率和准确率。

六、测试结果

运行 check.py,可以分别得到以下测试结果:

  • 中文

    ​ 正确率为 83.12%。
  • 英文
    在这里插入图片描述

​ 正确率为 87.97%。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

busyforest

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

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

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

打赏作者

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

抵扣说明:

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

余额充值