自然语言处理(NLP)之词义消歧(WSD)的简介与实现

词义消岐简介

词义消岐,英文名称为Word Sense Disambiguation,英语缩写为WSD,是自然语言处理(NLP)中一个非常有趣的基本任务。

那么,什么是词义消岐呢?通常,在我们的自然语言中,不管是英语,还是中文,都有多义词存在。这些多义词的存在,会让人对句子的意思产生混淆,但人通过学习又是可以正确地区分出来的。

以**“小米”**这个词为例,如果仅仅只是说“小米”这个词语,你并不知道它实际指的到底是小米科技公司还是谷物。但当我们把词语置于某个特定的语境中,我们能很好地区分出这个词语的意思。比如,

雷军是小米的创始人。

在这个句子中,我们知道这个“小米”指的是小米科技公司。比如

我今天早上喝了一碗小米粥。

在这个句子中,“小米”指的是谷物、农作物。

所谓词义消岐,指的是在特定的语境中,识别出某个歧义词的正确含义。

那么,词义消岐有什么作用呢?词义消岐可以很好地服务于语言翻译和智能问答领域,当然,还有许多应用有待开发~

词义消岐实现

比较经典的词义消岐的算法为Lesk算法,该算法的想法很简单,通过对某个歧义词构建不同含义的语料及待判别句子中该词语与语料的重合程度来实现

我们以词语“火箭”为例,选取其中的两个义项(同一个词语的不同含义):NBA球队名 和 燃气推进装置 ,如下:

获取语料

  首先,我们利用爬虫爬取这两个义项的百度百科网页,以句子为单位,只要句子中出现该词语,则把这句话加入到这个义项的预料中。爬虫的完整Python代码如下:

import requests
from bs4 import BeautifulSoup
from pyltp import SentenceSplitter


class WebScrape(object):
    def __init__(self, word, url):
        self.url = url
        self.word = word

    # 爬取百度百科页面
    def web_parse(self):
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 \
                                             (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
        req = requests.get(url=self.url, headers=headers)

        # 解析网页,定位到main-content部分
        if req.status_code == 200:
            soup = BeautifulSoup(req.text.encode(req.encoding), 'lxml')
            return soup
        return None

    # 获取该词语的义项
    def get_gloss(self):
        soup = self.web_parse()
        if soup:
            lis = soup.find('ul', class_="polysemantList-wrapper cmn-clearfix")
            if lis:
                for li in lis('li'):
                    if '

运行结果:

利用这个爬虫,我们爬取了“火箭”这个词语的两个义项的语料,生成了火箭_燃气推进装置.txt文件和火箭_NBA球队名.txt文件,这两个文件分别含有275和190个句子。以火箭_燃气推进装置.txt文件为例,前10个句子如下:

火箭主要分类
火箭美国大力神系列运载火箭
大力神4火箭于1985年开始研制,在1986年挑战者号航天飞机失事之后,成为发射大型军用卫星的主要运载火箭,承担美国空军地球同步轨道卫星(预警、通信、中继、气象)和近地轨道侦察卫星的发射任务。
其目标是在海南建成探空火箭探测和地面联合监测大气和电离层参数的综合探测系统。
揭秘长三甲系列:“金牌火箭”是这样炼成的
固体火箭的动力装置系统较为简单,它的主要部分就是固体火箭发动机,推进剂直接装在发动机的燃烧室壳体内。
火箭按动力能源
1965年起开始研制固体探空火箭“和平”2号和6号。
作为新能源火箭的代表,核能火箭的优点是其发动机比冲比化学能火箭的高,而推进剂只有一种,简化了火箭结构,适合执行长时间任务或星际任务。
中国在1958年以前曾发射过试验性火箭,1958年正式研制探空火箭,先后研制成T-7液体探空火箭和改进型 T-7A探空火箭。

实现算法

  我们以句子为单位进行词义消岐,即输入一句话,识别出该句子中某个歧义词的含义。这儿使用的算法比较简单,是以TF-IDF为权重的频数判别。以句子

赛季初的时候,火箭是众望所归的西部决赛球队。

为例,对该句子分词后,去掉停用词(stopwords),然后分别统计除了“火箭”这个词以外的TF-IDF值,累加起来,比较在两个义项下这个值的大小即可。
  实现这个算法的完整Python代码如下:

import os
import jieba
from math import log2


# 读取每个义项的语料
def read_file(path):
    with open(path, 'r', encoding='utf-8') as f:
        lines = [_.strip() for _ in f.readlines()]
        return lines


# 对示例句子分词
sent = '赛季初的时候,火箭是众望所归的西部决赛球队。'
wsd_word = '火箭'

jieba.add_word(wsd_word)
sent_words = list(jieba.cut(sent, cut_all=False))

# 去掉停用词
stopwords = [wsd_word, '我', '你', '它', '他', '她', '了', '是', '的', '啊', '谁', '什么', '都', \
             '很', '个', '之', '人', '在', '上', '下', '左', '右', '。', ',', '!', '?']

sent_cut = []
for word in sent_words:
    if word not in stopwords:
        sent_cut.append(word)

print(sent_cut)

# 计算其他词的TF-IDF以及频数
wsd_dict = {}
for file in os.listdir('.'):
    if wsd_word in file:
        wsd_dict[file.replace('.txt', '')] = read_file(file)

# 统计每个词语在语料中出现的次数
tf_dict = {}
for meaning, sents in wsd_dict.items():
    tf_dict[meaning] = []
    for word in sent_cut:
        word_count = 0
        for sent in sents:
            example = list(jieba.cut(sent, cut_all=False))
            word_count += example.count(word)

        if word_count:
            tf_dict[meaning].append((word, word_count))

idf_dict = {}
for word in sent_cut:
    document_count = 0
    for meaning, sents in wsd_dict.items():
        for sent in sents:
            if word in sent:
                document_count += 1

    idf_dict[word] = document_count

# 输出值
total_document = 0
for meaning, sents in wsd_dict.items():
    total_document += len(sents)

# 计算tf_idf值
mean_tf_idf = []
for k, v in tf_dict.items():
    print(k + ':')
    tf_idf_sum = 0
    for item in v:
        word = item[0]
        tf = item[1]
        tf_idf = item[1] * log2(total_document / (1 + idf_dict[word]))
        tf_idf_sum += tf_idf
        print('%s, 频数为: %s, TF-IDF值为: %s' % (word, tf, tf_idf))

    mean_tf_idf.append((k, tf_idf_sum))

sort_array = sorted(mean_tf_idf, key=lambda x: x[1], reverse=True)
true_meaning = sort_array[0][0].split('_')[1]
print('\n经过词义消岐,%s在该句子中的意思为 %s .' % (wsd_word, true_meaning))

运行结果:

['赛季', '初', '时候', '众望所归', '西部', '决赛', '球队']
火箭_NBA球队名:
赛季, 频数为: 61, TF-IDF值为: 187.86335874071477
初, 频数为: 1, TF-IDF值为: 5.861086905995394
时候, 频数为: 1, TF-IDF值为: 7.861086905995394
西部, 频数为: 19, TF-IDF值为: 89.13207618650854
决赛, 频数为: 8, TF-IDF值为: 35.21324229886477
球队, 频数为: 43, TF-IDF值为: 155.36585387972775
火箭_燃气推进装置:
初, 频数为: 3, TF-IDF值为: 17.58326071798618

经过词义消岐,火箭在该句子中的意思为 NBA球队名 .

测试

  接着,我们对上面的算法和程序进行更多的测试。

输入句子为:

三十多年前,战士们在戈壁滩白手起家,建起了我国的火箭发射基地。

输出结果为:

['三十多年', '前', '战士', '们', '戈壁滩', '白手起家', '建起', '我国', '发射', '基地']
火箭_NBA球队名:
前, 频数为: 3, TF-IDF值为: 10.617476433324095
们, 频数为: 1, TF-IDF值为: 6.539158811108031
火箭_燃气推进装置:
前, 频数为: 9, TF-IDF值为: 31.852429299972282
我国, 频数为: 5, TF-IDF值为: 31.38062202637119
发射, 频数为: 110, TF-IDF值为: 257.1277444932219

经过词义消岐,火箭在该句子中的意思为 燃气推进装置 .

输入句子为:

对于马刺这样级别的球队,常规赛只有屈指可数的几次交锋具有真正的意义,今天对火箭一役是其中之一。

输出结果为:

['对于', '马刺', '这样', '级别', '球队', '常规赛', '只有', '屈指可数', '几次', '交锋', '具有', '真正', '意义', '今天', '对', '一役', '其中', '之一']
火箭_NBA球队名:
对于, 频数为: 1, TF-IDF值为: 7.2761244052742375
球队, 频数为: 43, TF-IDF值为: 155.36585387972775
常规赛, 频数为: 14, TF-IDF值为: 70.75224777512906
只有, 频数为: 1, TF-IDF值为: 6.861086905995394
对, 频数为: 12, TF-IDF值为: 47.450355724642506
之一, 频数为: 1, TF-IDF值为: 6.539158811108031
火箭_燃气推进装置:
对于, 频数为: 1, TF-IDF值为: 7.2761244052742375
这样, 频数为: 1, TF-IDF值为: 7.861086905995394
只有, 频数为: 2, TF-IDF值为: 13.722173811990787
具有, 频数为: 3, TF-IDF值为: 20.58326071798618
真正, 频数为: 1, TF-IDF值为: 7.861086905995394
意义, 频数为: 1, TF-IDF值为: 7.861086905995394
对, 频数为: 13, TF-IDF值为: 51.40455203502938
其中, 频数为: 5, TF-IDF值为: 31.38062202637119
之一, 频数为: 3, TF-IDF值为: 19.617476433324093

经过词义消岐,火箭在该句子中的意思为 NBA球队名 .

输入的句子为:

从1992年开始研制的长征二号F型火箭,是中国航天史上技术最复杂、可靠性和安全性指标最高的运载火箭。

输出结果为:

['从', '1992', '年', '开始', '研制', '长征二号', 'F', '型', '中国', '航天史', '技术', '最', '复杂', '、', '可靠性', '和', '安全性', '指标', '最高', '运载火箭']
火箭_NBA球队名:
从, 频数为: 6, TF-IDF值为: 23.44134357365111
1992, 频数为: 2, TF-IDF值为: 13.078317622216062
年, 频数为: 64, TF-IDF值为: 150.61070501098862
开始, 频数为: 2, TF-IDF值为: 9.078317622216062
中国, 频数为: 11, TF-IDF值为: 39.74475331806989
最, 频数为: 2, TF-IDF值为: 6.937538966433267
、, 频数为: 25, TF-IDF值为: 62.15118686621173
和, 频数为: 31, TF-IDF值为: 64.47154296659276
最高, 频数为: 8, TF-IDF值为: 52.31327048886425
火箭_燃气推进装置:
从, 频数为: 24, TF-IDF值为: 93.76537429460444
1992, 频数为: 1, TF-IDF值为: 6.539158811108031
年, 频数为: 44, TF-IDF值为: 103.54485969505468
开始, 频数为: 17, TF-IDF值为: 77.16569978883652
研制, 频数为: 24, TF-IDF值为: 102.6269857265817
型, 频数为: 13, TF-IDF值为: 48.51345055765555
中国, 频数为: 23, TF-IDF值为: 83.10266602869159
技术, 频数为: 20, TF-IDF值为: 88.03310574716193
最, 频数为: 3, TF-IDF值为: 10.4063084496499
复杂, 频数为: 3, TF-IDF值为: 20.58326071798618
、, 频数为: 149, TF-IDF值为: 370.4210737226219
可靠性, 频数为: 2, TF-IDF值为: 14.552248810548475
和, 频数为: 97, TF-IDF值为: 201.73353766966122
最高, 频数为: 1, TF-IDF值为: 6.539158811108031
运载火箭, 频数为: 111, TF-IDF值为: 257.7340185324972

经过词义消岐,火箭在该句子中的意思为 燃气推进装置 .

输入句子为:

到目前为止火箭已经在休斯顿进行了电视宣传,并在大街小巷竖起广告栏。

输出结果为:

['到', '目前为止', '已经', '休斯顿', '进行', '电视', '宣传', '并', '大街小巷', '竖起', '广告栏']
火箭_NBA球队名:
到, 频数为: 3, TF-IDF值为: 10.012574849815142
已经, 频数为: 1, TF-IDF值为: 7.2761244052742375
休斯顿, 频数为: 9, TF-IDF值为: 51.220457140977736
进行, 频数为: 2, TF-IDF值为: 8.321294375708604
并, 频数为: 6, TF-IDF值为: 24.01863546520693
火箭_燃气推进装置:
到, 频数为: 17, TF-IDF值为: 56.73792414895247
已经, 频数为: 1, TF-IDF值为: 7.2761244052742375
进行, 频数为: 28, TF-IDF值为: 116.49812125992045
并, 频数为: 18, TF-IDF值为: 72.0559063956208

经过词义消岐,火箭在该句子中的意思为 燃气推进装置 .

总结:

这个算法,虽然有一定的效果,但是也不总是识别正确。比如,对于最后一个测试的句子,识别的结果就是错误的,其实“休斯顿”才是识别该词语义项的关键词,但很遗憾,在笔者的算法中,“休斯顿”的权重并不高。
  对于词义消岐算法,如果还是用这个思路,那么有以下几方面需要改进:

  • 语料大小及丰富程度;
  • 停用词的扩充;
  • 更好的算法。

你可能感兴趣的:(自然语言处理,自然语言处理,nlp,python,机器学习,深度学习)