NLP入門(九)詞義消岐(WSD)的簡介與實現

詞義消岐簡介

  詞義消岐,英文名稱爲Word Sense Disambiguation,英語縮寫爲WSD,是天然語言處理(NLP)中一個很是有趣的基本任務。
  那麼,什麼是詞義消岐呢?一般,在咱們的天然語言中,不論是英語,仍是中文,都有多義詞存在。這些多義詞的存在,會讓人對句子的意思產生混淆,但人經過學習又是能夠正確地區分出來的。
  以「小米」這個詞爲例,若是僅僅只是說「小米」這個詞語,你並不知道它實際指的究竟是小米科技公司仍是穀物。但當咱們把詞語置於某個特定的語境中,咱們能很好地區分出這個詞語的意思。好比,python

雷軍是小米的創始人。web

在這個句子中,咱們知道這個「小米」指的是小米科技公司。好比算法

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

在這個句子中,「小米」指的是穀物、農做物。
  所謂詞義消岐,指的是在特定的語境中,識別出某個歧義詞的正確含義。
  那麼,詞義消岐有什麼做用呢?詞義消岐能夠很好地服務於語言翻譯和智能問答領域,固然,還有許多應用有待開發~微信

詞義消岐實現

  在目前的詞義消岐算法中,有很多原創算法,有些實現起來比較簡單,有些想法較爲複雜,但實現的效果廣泛都不是很好。比較經典的詞義消岐的算法爲Lesk算法,該算法的想法很簡單,經過對某個歧義詞構建不一樣含義的語料及待判別句子中該詞語與語料的重合程度來實現,具體的算法原理可參考網址:https://en.wikipedia.org/wiki/Lesk_algorithm .
  在下面的部分中,筆者將會介紹本身想的一種實現詞義消岐的算法,僅僅是一個想法,僅供參考。
  咱們以詞語「火箭」爲例,選取其中的兩個義項(同一個詞語的不一樣含義):NBA球隊名燃氣推動裝置 ,以下:app

獲取語料

  首先,咱們利用爬蟲爬取這兩個義項的百度百科網頁,以句子爲單位,只要句子中出現該詞語,則把這句話加入到這個義項的預料中。爬蟲的完整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 '<a' not in str(li):
                        gloss = li.text.replace('▪', '')
                        return gloss

        return None

    # 獲取該義項的語料,以句子爲單位
    def get_content(self):
        # 發送HTTP請求
        result = []
        soup = self.web_parse()
        if soup:
            paras = soup.find('div', class_='main-content').text.split('\n')
            for para in paras:
                if self.word in para:
                    sents = list(SentenceSplitter.split(para))
                    for sent in sents:
                        if self.word in sent:
                            sent = sent.replace('\xa0', '').replace('\u3000', '')
                            result.append(sent)

        result = list(set(result))

        return result

    # 將該義項的語料寫入到txt
    def write_2_file(self):
        gloss = self.get_gloss()
        result = self.get_content()
        print(gloss)
        print(result)
        if result and gloss:
            with open('./%s_%s.txt'% (self.word, gloss), 'w', encoding='utf-8') as f:
                f.writelines([_+'\n' for _ in result])

    def run(self):
        self.write_2_file()

# NBA球隊名
#url = 'https://baike.baidu.com/item/%E4%BC%91%E6%96%AF%E6%95%A6%E7%81%AB%E7%AE%AD%E9%98%9F/370758?fromtitle=%E7%81%AB%E7%AE%AD&fromid=8794081#viewPageContent'
# 燃氣推動裝置
url = 'https://baike.baidu.com/item/%E7%81%AB%E7%AE%AD/6308#viewPageContent'
WebScrape('火箭', url).run()

利用這個爬蟲,咱們爬取了「火箭」這個詞語的兩個義項的語料,生成了火箭_燃氣推動裝置.txt文件和火箭_NBA球隊名.txt文件,這兩個文件分別含有361和171個句子。以火箭_燃氣推動裝置.txt文件爲例,前10個句子以下:測試

火箭技術的飛速發展,不只可提供更加完善的各種導彈和推進相關科學的發展,還將使開發空間資源、創建空間產業、空間基地及星際航行等成爲可能。
火箭技術是一項十分複雜的綜合性技術,主要包括火箭推動技術、整體設計技術、火箭結構技術、控制和制導技術、計劃管理技術、可靠性和質量控制技術、試驗技術,對導彈來講還有彈頭制導和控制、
1903年,俄國的К.E.齊奧爾科夫斯基提出了製造大型液體火箭的設想和設計原理。
火箭有不少種,原始的火箭是用引火物附在弓箭頭上,而後射到敵人身上引發焚燒的一種箭矢。
「長征三號丙」火箭是在 「長征三號乙」火箭的基礎上, 減小了兩個助推器並取消了助推器上的尾翼。
火箭與導彈有什麼區別
爲了可以在將來大規模的將人類送入太空,不可能依賴傳統的火箭和飛船。
火箭V2火箭
探測高層大氣的物理特徵(如氣壓、溫度、溼度等)和現象的探空火箭。
可一次發射一發至數十發火箭彈。url

實現算法

  咱們以句子爲單位進行詞義消岐,即輸入一句話,識別出該句子中某個歧義詞的含義。筆者使用的算法比較簡單,是以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))

輸出結果以下:

['賽季', '初', '時候', '衆望所歸', '西部', '決賽', '球隊']
火箭_燃氣推動裝置:
初, 頻數爲: 2, TF-IDF值爲: 12.49585502688717
火箭_NBA球隊名:
賽季, 頻數爲: 63, TF-IDF值爲: 204.6194333469459
初, 頻數爲: 1, TF-IDF值爲: 6.247927513443585
時候, 頻數爲: 1, TF-IDF值爲: 8.055282435501189
西部, 頻數爲: 16, TF-IDF值爲: 80.88451896801904
決賽, 頻數爲: 7, TF-IDF值爲: 33.13348038429679
球隊, 頻數爲: 40, TF-IDF值爲: 158.712783770034

通過詞義消岐,火箭在該句子中的意思爲 NBA球隊名 .

測試

  接着,咱們對上面的算法和程序進行更多的測試。

輸入句子爲:

三十多年前,戰士們在戈壁灘白手起家,建起了我國的火箭發射基地。

輸出結果爲:

['三十多年', '前', '戰士', '們', '戈壁灘', '白手起家', '建起', '我國', '發射', '基地']
火箭_燃氣推動裝置:
前, 頻數爲: 2, TF-IDF值爲: 9.063440958888354
們, 頻數爲: 1, TF-IDF值爲: 6.05528243550119
我國, 頻數爲: 3, TF-IDF值爲: 22.410959804340102
發射, 頻數爲: 89, TF-IDF值爲: 253.27878721862933
基地, 頻數爲: 7, TF-IDF值爲: 42.38697704850833
火箭_NBA球隊名:
前, 頻數爲: 3, TF-IDF值爲: 13.59516143833253
們, 頻數爲: 1, TF-IDF值爲: 6.05528243550119

通過詞義消岐,火箭在該句子中的意思爲 燃氣推動裝置 .

輸入句子爲:

對於馬刺這樣級別的球隊,常規賽只有屈指可數的幾回交鋒具備真正的意義,今天對火箭一役是其中之一。

輸出結果爲:

['對於', '馬刺', '這樣', '級別', '球隊', '常規賽', '只有', '屈指可數', '幾回', '交鋒', '具備', '真正', '意義', '今天', '對', '一役', '其中', '之一']
火箭_燃氣推動裝置:
只有, 頻數爲: 1, TF-IDF值爲: 7.470319934780034
具備, 頻數爲: 5, TF-IDF值爲: 32.35159967390017
真正, 頻數爲: 2, TF-IDF值爲: 14.940639869560068
意義, 頻數爲: 1, TF-IDF值爲: 8.055282435501189
對, 頻數爲: 5, TF-IDF值爲: 24.03677461028802
其中, 頻數爲: 3, TF-IDF值爲: 21.16584730650357
之一, 頻數爲: 2, TF-IDF值爲: 14.11056487100238
火箭_NBA球隊名:
馬刺, 頻數爲: 1, TF-IDF值爲: 7.470319934780034
球隊, 頻數爲: 40, TF-IDF值爲: 158.712783770034
常規賽, 頻數爲: 14, TF-IDF值爲: 73.4709851882102
只有, 頻數爲: 1, TF-IDF值爲: 7.470319934780034
對, 頻數爲: 10, TF-IDF值爲: 48.07354922057604
之一, 頻數爲: 1, TF-IDF值爲: 7.05528243550119

通過詞義消岐,火箭在該句子中的意思爲 NBA球隊名 .

輸入句子爲:

姚明是火箭隊的主要得分手之一。

輸出結果爲:

['姚明', '火箭隊', '主要', '得分手', '之一']
火箭_燃氣推動裝置:
主要, 頻數爲: 9, TF-IDF值爲: 51.60018906552445
之一, 頻數爲: 2, TF-IDF值爲: 14.11056487100238
火箭_NBA球隊名:
姚明, 頻數爲: 18, TF-IDF值爲: 90.99508383902142
火箭隊, 頻數爲: 133, TF-IDF值爲: 284.1437533641371
之一, 頻數爲: 1, TF-IDF值爲: 7.05528243550119

通過詞義消岐,火箭在該句子中的意思爲 NBA球隊名 .

輸入的句子爲:

從1992年開始研製的長征二號F型火箭,是中國航天史上技術最複雜、可靠性和安全性指標最高的運載火箭。

輸出結果爲:

['從', '1992', '年', '開始', '研製', '長征二號', 'F', '型', '中國', '航天史', '技術', '最', '複雜', '、', '可靠性', '和', '安全性', '指標', '最高', '運載火箭']
火箭_燃氣推動裝置:
從, 頻數爲: 6, TF-IDF值爲: 29.312144604353264
1992, 頻數爲: 1, TF-IDF值爲: 6.733354340613827
年, 頻數爲: 43, TF-IDF值爲: 107.52982410441274
開始, 頻數爲: 5, TF-IDF值爲: 30.27641217750595
研製, 頻數爲: 25, TF-IDF值爲: 110.28565614316162
長征二號, 頻數爲: 37, TF-IDF值爲: 159.11461253349566
F, 頻數爲: 7, TF-IDF值爲: 40.13348038429679
中國, 頻數爲: 45, TF-IDF值爲: 153.51418105769093
技術, 頻數爲: 27, TF-IDF值爲: 119.10850863461454
最, 頻數爲: 2, TF-IDF值爲: 7.614709844115208
、, 頻數爲: 117, TF-IDF值爲: 335.25857156467714
可靠性, 頻數爲: 5, TF-IDF值爲: 30.27641217750595
和, 頻數爲: 76, TF-IDF值爲: 191.22539545388003
安全性, 頻數爲: 2, TF-IDF值爲: 14.940639869560068
運載火箭, 頻數爲: 95, TF-IDF值爲: 256.28439093389505
火箭_NBA球隊名:
從, 頻數爲: 5, TF-IDF值爲: 24.42678717029439
1992, 頻數爲: 2, TF-IDF值爲: 13.466708681227654
年, 頻數爲: 52, TF-IDF值爲: 130.0360663588247
開始, 頻數爲: 2, TF-IDF值爲: 12.11056487100238
中國, 頻數爲: 4, TF-IDF值爲: 13.64570498290586
最, 頻數爲: 3, TF-IDF值爲: 11.422064766172813
、, 頻數爲: 16, TF-IDF值爲: 45.847326025938756
和, 頻數爲: 31, TF-IDF值爲: 77.99983235618791
最高, 頻數爲: 8, TF-IDF值爲: 59.76255947824027

通過詞義消岐,火箭在該句子中的意思爲 燃氣推動裝置 .

輸入句子爲:

到目前爲止火箭已經在休斯頓進行了電視宣傳,並在大街小巷豎起廣告欄。

輸出結果爲:

['到', '目前爲止', '已經', '休斯頓', '進行', '電視', '宣傳', '並', '大街小巷', '豎起', '廣告欄']
火箭_燃氣推動裝置:
到, 頻數爲: 11, TF-IDF值爲: 39.19772273088667
已經, 頻數爲: 2, TF-IDF值爲: 13.466708681227654
進行, 頻數爲: 14, TF-IDF值爲: 68.39500407682429
並, 頻數爲: 11, TF-IDF值爲: 49.17351928258037
火箭_NBA球隊名:
到, 頻數爲: 6, TF-IDF值爲: 21.38057603502909
已經, 頻數爲: 2, TF-IDF值爲: 13.466708681227654
休斯頓, 頻數爲: 2, TF-IDF值爲: 14.940639869560068
進行, 頻數爲: 2, TF-IDF值爲: 9.770714868117755
並, 頻數爲: 5, TF-IDF值爲: 22.351599673900168

通過詞義消岐,火箭在該句子中的意思爲 燃氣推動裝置 .

總結

  對於筆者的這個算法,雖然有必定的效果,可是也不老是識別正確。好比,對於最後一個測試的句子,識別的結果就是錯誤的,其實「休斯頓」纔是識別該詞語義項的關鍵詞,但很遺憾,在筆者的算法中,「休斯頓」的權重並不高。
  對於詞義消岐算法,若是仍是筆者的這個思路,那麼有如下幾方面須要改進:

  • 語料大小及豐富程度;
  • 停用詞的擴充;
  • 更好的算法。

  筆者的這篇文章僅做爲詞義消岐的簡介以及簡單實現,但願能對讀者有所啓發~

注意:本人現已開通微信公衆號: Python爬蟲與算法(微信號爲:easy_web_scrape), 歡迎你們關注哦~~

相關文章
相關標籤/搜索