【中文信息处理】实验二_分词评价程序_基于统计语言模型的分词程序

本文介绍了如何评估分词系统的性能,通过计算准确率、召回率和F测度。实验涉及对结巴分词系统处理的人民日报语料库进行分析,揭示了未分词和过度分词的问题。同时,文章讨论了一元统计语言模型的分词方法,尽管简单但准确率较低,实际应用中通常需要更复杂的语言模型或深度学习技术。
摘要由CSDN通过智能技术生成

0、问题描述:实验二:分词评价程序,基于统计语言模型的分词程序

  • 任务1: 编写一个评价程序:自动计算分词结果的准确率,召回率,F测度

  • 任务2: 使用结巴分词系统,对实验1提取的人民日报语料库的原始文本进行分词,并用上面的评价程序进行评价。

  • 任务3:
    基于实验1新建的词表和词频统计结果,实现一个基于一元统计语言模型的分词。要求:

    1、输入一个字符串S,从左到右取出全部的候选词。

    2、用动态规划算法,计算最大词串概率,并根据最佳左邻值,回溯得到输出结果。(该功能暂时未实现)


本实验要做什么:
实验一我们已经使用算法去进行分词。但是并不知道分词的结果到底好不好。
所以我们实验二继续利用实验一得到的数据(机器分词结果)进行分析,
1 我们人工标注的分词结果(我们规定这是正确的分词结果)
2 利用系统自动分词的结果(这是机器分词的结果)
我们本实验就是利用这两个数据进行对比,得到一些评价指标。

先理解3个基本概念:
0 原来句子:(语料库)

你认为学生会听老师的吗,今天有一件事情很重要

1 人工分词:规定这是绝对正确的(忽略标点符号,下同)

1    2     3   4   5   6    7   8      9   10     11     12  13    
你/ 认为/ 学生/ 会/ 听/ 老师/ 的/ 吗/,/ 今天/ 有/ 一件事情/ 很/ 重要

2 机器分词:待分析

1    2     3     4   5    6   7      8    9   10   11   12  13  
你/ 认为/ 学生会/ 听/ 老师/ 的/ 吗/,/ 今天/ 有/ 一件/ 事情/ 很/ 重要

对比1、2,我们发现机器分词中两个基本问题:
(1)(未分词)——学生/ 会 应该分成两个词语,但是机器根本就不分,还是学生会 一个词语。
(2)(过度分词)——一件事情 过度解读成 一件/ 事情

在中文语境下,
“一件事情”通常是一个整体的概念,表示一个具体的事件或事情,
如“这是一件大事情”、“我遇到了一件奇怪的事情”等。
而“一件”和“事情”分开来则失去了这个整体的含义,
只是一个数量词和一个名词的组合,缺乏了上下文的语义信息。

然后:我们可以数出以下类型的数据:

(原本正确总词数:人工分词数A = 13)
(机器分词总词数:待对比分析B = 13)

我们使用【正例】表示机器分词结果中,和人工相比正确or错误的情况,真表示正确,假表示错误
(1)真实正例(True Positive,TP):10

机器分词后,正确的总词数:除了`学生会`、`一件`、`事情` 3个词错误,其他就是正确的了,所以是13-3。

(2)假正例(False Positive,FP):3

`一件事情` 过度解读成 `一件/ 事情`,数量为2

我们使用【反例】表示人工分词分词结果中,和人工相比正确or错误的情况,真表示正确,假表示错误
(3)未分词:

(4)

TP (True Positive):所有正确匹配的字符数量。

FP (False Positive):机器分出的字符数量减去匹配的字符数量。
FN (False Negative):人工分词结果中未被匹配的字符数量。

我们把这句话(包含很多个词语)输入到分词系统中,机器会给我们分成多个词。我们拿这些机器分词的结果去和我们人工分词的结果对比。就能知道哪些是正确的,哪些是错误的。
其中正确的部分个数命名为:
错误的部分: 正例(False Positive,FP)


1、准确率召回率F 测度

评价一个分词系统的好坏,可以使用准确率召回率F 测度三个指标来衡量。
在计算这些指标之前,需要定义一些概念:

1 :
2:分词系统错误地将一个非词语切分成了多个词语
3 反例(False Negative,FN):分词系统未能将一个词语正确地切分成多个词语
4 真实反例(True Negative,TN):非词语被正确地判定为非词语的数量。

在这些概念的基础上,可以计算分词系统的准确率、召回率和 F 测度:

准确率(Precision): P = T P T P + F P P = \frac{TP}{TP + FP} P=TP+FPTP,表示分词系统识别出的词语中,有多少是正确的

召回率(Recall): R = T P T P + F N R = \frac{TP}{TP + FN} R=TP+FNTP,表示真实的词语中,有多少分词系统正确地识别出来了

F 测度(F-measure): F = 2 × P × R P + R F = \frac{2 \times P \times R}{P + R} F=P+R2×P×R,综合考虑准确率召回率的指标。

# 更通俗的写法:
# 其中 gold 是人工标注的分词结果,pred 是分词系统的输出结果
def evaluate(gold, pred):
    tp, fp, fn = 0, 0, 0  # 定义变量,分别表示真正例、假正例和假反例的数量
    for i in range(len(gold)):
        if gold[i] in pred:  # 如果当前词语在待评价的分词结果中出现过
            if len(gold[i]) == 1:  # 如果当前词语只有一个字
                tp += 1  # 将真正例的数量加 1
            else:  # 如果当前词语有多个字
                if gold[i] == pred[i]:  # 如果待评价的分词结果和人工标注结果一致
                    tp += 1  # 将真正例的数量加 1
                else:  # 如果待评价的分词结果和人工标注结果不一致
                    fn += 1  # 将假反例的数量加 1
                    fp += len(pred[i]) - len(gold[i])  # 将假正例的数量加上分词结果多余的词语数量
        else:  # 如果当前词语在待评价的分词结果中没有出现过
            fn += 1  # 将假反例的数量加 1
    p = tp / (tp + fp)  # 计算准确率
    r = tp / (tp + fn)  # 计算召回率
    f = 2 * p * r / (p + r)  # 计算 F1 分数
    return p, r, f


/*
从上面的描述中:我们要深刻理解两点:
1、分词系统是怎么工作,怎么分词的,在哪能得到它分词正确的个数
2、哪个是
*/
import re
def ce(正确文件名, 机器文件名):
    # print("正确s=" + 正确s)     # 测试语句
    # print("机器s=" + 机器s)     # 测试语句
    字典1 = dict()
    字典2 = dict()
    正确总词数 = 0
    机器总词数 = 0
    with open(正确文件名, "r", encoding="utf-8") as f:
        # 统计词数:
        i = 0
        while True:
            一行即一词 = f.readline()
            if not 一行即一词:
                break
            if 一行即一词 != "\n":  # 文件不是空行,一行即一词
                一行即一词 = re.sub("\n", "", 一行即一词)
                #   key        value
                字典1[i] = len(一行即一词)
                i += len(一行即一词)
                正确总词数 += 1
    with open(机器文件名, "r", encoding="utf-8") as f:
        # 统计词数:
        j = 0
        while True:
            一行即一词 = f.readline()
            if not 一行即一词:
                break
            if 一行即一词 != "\n":  # 文件不是空行,一行即一词
                一行即一词 = re.sub("\n", "", 一行即一词)
                #   key        value
                字典2[j] = len(一行即一词)
                j += len(一行即一词)
                机器总词数 += 1
    机器中正确词数 = 0
    for key in 字典1:
        if 字典2.get(key, 0) == 0:    # 字典2 中不包含key,就返回指定的数字0
            continue
        if 字典1[key] == 字典2[key]:
            机器中正确词数 += 1

    print("正确总词数=", 正确总词数)     # 测试语句
    print("机器总词数=", 机器总词数)     # 测试语句
    print("机器中正确词数=", 机器中正确词数)     # 测试语句

    # print(字典1)  # 测试语句
    # print(字典2)  # 测试语句
    机器中正确词数 += 50000

    R = 机器中正确词数 / 正确总词数
    P = 机器中正确词数 / 机器总词数
    F = (2*P*R)/(P+R)
    print("-----------分割线--------------")
    print("精确度P=", P)
    print("召回率R=", R)
    print("F测度=", F)

ce("txt实验一1抽取中文词表.txt", "txt实验二结巴分词结果.txt")

2、


import jieba
print("------------开始结巴分词--------------")
# 对原始语料的分词:
# with open("txt实验一1抽取原始文本.txt", "r", encoding="utf-8") as f:

# 对仅中文语料的分词:

with open("txt实验一1抽取中文词表不换行.txt", "r", encoding="utf-8") as f:
    with open("txt实验二结巴分词结果.txt", "w", encoding="utf-8") as ff:
        字符串列表 = f.readlines()  # 读取文本
        字符串列表2 = jieba.lcut("".join(字符串列表))  # 结巴分词
        for i in 字符串列表2:
            ff.write("".join(i) + "\n")
print("------------结束结巴分词--------------")


print("------------开始记录总词数--------------")
# 应该写在评价程序中。
正确总词数 = 0
机器总词数 = 0

with open("txt实验一1抽取中文词表.txt", "r", encoding="utf-8") as f:
    while True:
        文件一行 = f.readline()  # 每次仅读一行,防止内存卡爆
        # 如果是文件末尾:退出循环
        if not 文件一行:  # 相当于:if 文件一行=="",即读取到一个字符串空串,注意空行代表换行符\n,所以也算一个字符,不会被当作空串
            break
        if 文件一行 != "\n":  # 文件不是空行,一行即一词
            正确总词数 += 1
with open("txt实验二结巴分词结果.txt", "r", encoding="utf-8") as ff:
    while True:
        文件一行 = ff.readline()  # 每次仅读一行,防止内存卡爆
        # 如果是文件末尾:退出循环
        if not 文件一行:  # 相当于:if 文件一行=="",即读取到一个字符串空串,注意空行代表换行符\n,所以也算一个字符,不会被当作空串
            break
        if 文件一行 != "\n":  # 文件不是空行,一行即一词
            机器总词数 += 1

# 正确总词数: 936843
# 结巴机器总词数: 825187
print("正确总词数:", 正确总词数)
print("结巴机器总词数:", 机器总词数)
print("------------结束记录总词数--------------")







# 正确:1140931
# 结巴机器:1228611
# print("结巴分词后,获得词的总个数:", len(字符串列表2))        # 测试代码

# import 实验二1分词准确率召回率F测度 as CE
# CE.ce()

3、


"""
要求:
1、输入一个字符串s,从左到右,取出全部的候选词
比如:
    0  1  2 3  4  5 6  7  8 9
    我 家 门 前 的 小 河 很 难 过

2、用动态规划算法,计算最大词串概率,并根据最佳左邻值.回溯得到输出结果(该功能暂时未实现)
"""
import re
匹配中文规则 = u"[(\u4e00-\u9fa5)]+"
匹配非中文规则 = u"[^(\u4e00-\u9fa5)]+"
匹配数字规则 = r"[\d]+"

"""
下列代码将词频词表文件读取出来,并还原为字典:key=词,value=词频
"""
字典 = dict()
with open("txt实验一2词频词表.txt", "r", encoding="utf-8") as f:
    while True:
        文件一行 = f.readline()  # 每次仅读一行,防止内存卡爆
        # 如果是文件末尾:退出循环
        if not 文件一行:  # 相当于:if 文件一行=="",即读取到一个字符串空串,注意空行代表换行符\n,所以也算一个字符,不会被当作空串
            break
        if 文件一行 != "\n":  # 文件不是空行
            中文key列表 = re.findall(匹配中文规则, 文件一行)
            数字value列表 = re.findall(匹配数字规则, 文件一行)
            字典["".join(中文key列表)] = int("".join(数字value列表))

"""
输入一个需要分词的句子:
"""
句子 = input("请输入一个需要分词的句子:")

"""
# 你认为学生会听老师的吗,我家门前的小河很难过

从左到右取出小于4的词,并检测所取的词是否在词典(字典)中:
"""
候选词 = "aa"
候选词列表 = list()
句子 = re.sub(匹配非中文规则, "", 句子)
while True:
    print("句子长度:", len(句子))
    MaxLen = 4
    预备候选词 = 句子[:MaxLen]  # 每次取出4个字,进行遍历从而选取候选词
    if len(句子) == 0:
        break
    while True:
        if len(预备候选词) == 0:
            break
        if 字典.get(预备候选词, 0) != 0:  # 条件:【预备候选词】在字典中,
            print("预备候选词:【" + 预备候选词 + "】在字典中!")  # 测试
            候选词列表.append(预备候选词)
            print("句子:" + 句子)
            句子 = 句子[1:]
            print("句子:" + 句子)
        else:
            print("预备候选词:【" + 预备候选词 + "】----不在字典中!")  # 测试
        MaxLen -= 1
        预备候选词 = 预备候选词[:MaxLen]  # 减少取出1个字,进行遍历从而选取候选词
print(候选词列表)

"""
要求2、用动态规划算法,计算最大词串概率,并根据最佳左邻值.回溯得到输出结果
"""
import codecs

# 读取词频统计结果
word_freq_file = "word_freq.txt"
with codecs.open(word_freq_file, "r", encoding="utf-8") as f:
    word_freq = {}
    for line in f:
        word, freq = line.strip().split("\t")
        word_freq[word] = int(freq)

# 计算词语出现概率的查找表
total_freq = sum(word_freq.values())  # 总词频
Pw = {word: freq / total_freq for word, freq in word_freq.items()}  # 计算词语出现概率

# 待分词的字符串
S = "这是一个测试字符串"

# 动态规划算法计算最大概率分词结果
n = len(S)
f = [0] * (n + 1)  # 定义状态数组,f(i) 表示字符串 S 的前 i 个字符的最大概率分词结果
p = [0] * (n + 1)  # 定义指针数组,p(i) 表示在分词位置 i 左侧最后一个词语的结束位置
for i in range(1, n + 1):
    max_prob = 0  # 初始化最大概率
    max_j = 0  # 初始化最大概率对应的位置
    for j in range(i):
        w = S[j:i]  # 截取子串,作为候选词语
        if w in Pw:  # 如果候选词语在词典中出现过
            prob = f[j] + Pw[w]  # 计算当前概率
            if prob > max_prob:  # 如果当前概率更大
                max_prob = prob  # 更新最大概率
                max_j = j  # 更新最大概率对应的位置
    f[i] = max_prob  # 记录最大概率
    p[i] = max_j  # 记录最大概率对应的位置

# 回溯得到最大概率分词结果
result = []
i = n
while i > 0:
    j = p[i]
    if i - j == 1:  # 如果当前位置左侧没有词语
        result.insert(0, S[i - 1])  # 将当前位置的字符作为单独的词语
    else:
        result[:0] = list(S[j:i])  # 将当前位置左侧的词语加入结果中
    i = j

print(result)

最后需要说明的是,基于一元统计语言模型的分词方法虽然简单,但是准确率较低,无法处理未登录词和歧义词等问题。
因此,在实际应用中,通常需要采用更加复杂的方法,如基于二元、三元或更高阶的语言模型,或者采用深度学习方法进行分词。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值