词性标注与命名实体识别
詞性標注與命名實體識別
詞性標注
詞性是詞匯基本的語法屬性,通常稱為詞類。詞性標注是在給定句子中判定每個詞的語法范疇,確定其詞性并加以標注的過程。例如,表示人、地點、事物以及其他抽象概念的名稱即為名詞,表示動作或者狀態變化的為動詞,描述或修飾名詞屬性、狀態的詞為形容詞。
在中文中,一個詞的詞性很多時候都是不固定的,一般表現為同音同形的詞在不同的場景下,其表示的語法屬性截然不同,這為詞性標注帶來了很大的困難;但是另一方面,從整體上看,大多數詞語尤其是實詞,一般只有一到兩個詞性,且其中一個詞性的頻率遠大于另一個,即使每次將高頻詞性作為詞性選擇進行標注,準確率也在80%以上。
詞性標注最簡單的方法是從語料庫中統計每個詞所對應的高頻詞性,將其作為默認詞性,但這樣顯然還有提升空間。目前主流的是如同分詞一樣,將句子的詞性標注為一個序列標注問題來解決,那么分詞中常用的手段,如隱含馬爾可夫模型、條件隨機場模型(CRF)等皆可以在標注任務中使用。
詞性標注具有一定的規范,什么表示表示什么,如’n’表示名詞,目前沒有統一規范,主要有北大詞性標注集和賓州詞性標注集兩種,各有千秋。jieba庫是詞性標注不錯的選擇,具體API為jieba.posseg。
命名實體識別
與自動分詞、詞性標注一樣,命名實體識別也是自然語言處理的基礎任務,是信息抽取、信息檢索、機器翻譯、問答系統等多種自然語言處理技術必不可少的組成部分。其目的是識別語料中人名、地名、組織機構名等命名實體。
由于這些命名實體數量不斷增加,不可能在詞典窮盡列出,且其構成方法具有各自規律性,因此,通常把對這些詞的識別在詞匯形態處理(如漢語切分)任務中獨立處理,稱為命名實體識別(NER)。NER研究分為三大類(實體類、時間類、數字類)和七小類(人名、地名、組織機構名、時間、日期、貨幣和百分比)。由于數量、時間、日期、貨幣等實體識別通常可以采用模式匹配的方式取得較好的結果,相比之下人名。地名。組織機構名較為復雜,近年來的研究主要針對這些。
命名實體識別當前并不是一個大熱的研究課題,因為學術界部分認為這是一個已經解決的問題。但是,不少人認為通用識別準確率很低。
同樣的,中文命名實體識別難度更大,主要原因如下。
- 各類命名實體數量眾多。
- 命名實體構成規律復雜。
- 嵌套情況復雜。
- 長度不確定。
其實,除了分詞,NLP每個子任務的劃分方法基本上都是基于規則、基于統計和兩者結合的方法。CRF往往有著更好的效果,CRF++包是個不錯的選擇。
實戰
本案例目標是日期識別,具體為根據用戶電話語音識別出文本,解析文本提到的相關日期。
import re from datetime import datetime, timedelta from dateutil.parser import parse import jieba.posseg as psg# 參考字典,用于文本轉數字 UTIL_CN_NUM = {'零': 0, '一': 1, '二': 2, '兩': 2, '三': 3, '四': 4,'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,'5': 5, '6': 6, '7': 7, '8': 8, '9': 9 } UTIL_CN_UNIT = {'十': 10, '百': 100, '千': 1000, '萬': 10000}def cn2dig(src):"""轉為數字:param src::return:"""if src == "":return Nonem = re.match(r"\d+", src)if m:# 若匹配到數字return int(m.group(0))# 若只有文本,則進行文本轉換rsl = 0unit = 1# 倒序查找for item in src[::-1]:if item in UTIL_CN_UNIT.keys():unit = UTIL_CN_UNIT[item]elif item in UTIL_CN_NUM.keys():num = UTIL_CN_NUM[item]rsl += num * unitelse:return Noneif rsl < unit:rsl += unitreturn rsldef year2dig(year):"""轉為數字:param year::return:"""res = ''for item in year:if item in UTIL_CN_NUM.keys():res = res + str(UTIL_CN_NUM[item])else:res = res + itemm = re.match(r"\d+", res)if m:if len(m.group(0)) == 2:return int(datetime.today().year/100)*100 + int(m.group(0))else:return int(m.group(0))else:return Nonedef parse_datetime(msg):"""解析日期:param msg::return:"""if msg is None or len(msg) == 0:return Nonetry:dt = parse(msg, fuzzy=True)return dt.strftime('%Y-%m-%d %H:%M:%S')except Exception as e:m = re.match(r"([0-9零一二兩三四五六七八九十]+年)?([0-9一二兩三四五六七八九十]+月)?([0-9一二兩三四五六七八九十]+[號日])?([上中下午晚早]+)?([0-9零一二兩三四五六七八九十百]+[點:\.時])?([0-9零一二三四五六七八九十百]+分?)?([0-9零一二三四五六七八九十百]+秒)?",msg)if m.group(0) is not None:res = {"year": m.group(1),"month": m.group(2),"day": m.group(3),"hour": m.group(5) if m.group(5) is not None else '00',"minute": m.group(6) if m.group(6) is not None else '00',"second": m.group(7) if m.group(7) is not None else '00',}params = {}for name in res:if res[name] is not None and len(res[name]) != 0:tmp = Noneif name == 'year':tmp = year2dig(res[name][:-1])else:tmp = cn2dig(res[name][:-1])if tmp is not None:params[name] = int(tmp)target_date = datetime.today().replace(**params)is_pm = m.group(4)if is_pm is not None:if is_pm == u'下午' or is_pm == u'晚上' or is_pm =='中午':hour = target_date.time().hourif hour < 12:target_date = target_date.replace(hour=hour + 12)return target_date.strftime('%Y-%m-%d %H:%M:%S')else:return Nonedef check_time_valid(word):"""確認時間的合法性:param word::return:"""m = re.match(r"\d+$", word)if m:if len(word) <= 6:return Noneword1 = re.sub(r'[號|日]\d+$', '日', word)if word1 != word:return check_time_valid(word1)else:return word1def time_extract(text):"""提取時間:param text::return:"""time_res = []word = ''keyDate = {'今天': 0, '明天': 1, '后天': 2}for k, v in psg.cut(text):if k in keyDate:if word != '':time_res.append(word)word = datetime.today() + timedelta(days=keyDate.get(k, 0))print(word)word = word.strftime('%Y{}%m{}%d{}').format("年", "月", "日")elif word != '':if v in ['m', 't']:word = word + kelse:time_res.append(word)word = ''elif v in ['m', 't']:word = kif word != '':time_res.append(word)result = list(filter(lambda x: x is not None, [check_time_valid(w) for w in time_res]))final_res = [parse_datetime(w) for w in result]return [x for x in final_res if x is not None]if __name__ == '__main__':text1 = '我要住到明天下午三點'print(text1, time_extract(text1), sep=':')text2 = '預定28號的房間'print(text2, time_extract(text2), sep=':')text3 = '我要從26號下午4點住到11月2號'print(text3, time_extract(text3), sep=':')text4 = '我要預訂今天到30的房間'print(text4, time_extract(text4), sep=':')text5 = '今天30號'print(text5, time_extract(text5), sep=':')補充說明
本文參考自《Python自然語言處理實戰》,并對書中的一些錯誤做了糾正(原書使用Jupyter Notebook,我使用Pycharm作為開發環境,推薦Jupyter),并對代碼添加了一定注釋。由于詞性標注和命名實體識別實際上在自然語言處理中還處于一個發展中的領域,所以我這里的介紹其實具有時效性,,請斟酌接受。具體數據集和代碼見我的Github,歡迎star或者fork。到此,中文分詞技術、詞性標注與命名實體識別三個詞法層面的自然語言基礎已經介紹完畢,后面有機會的話我會在本專欄接著介紹其他內容。
總結
以上是生活随笔為你收集整理的词性标注与命名实体识别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器学习-关联之FP-Growth算法原
- 下一篇: 数据挖掘竞赛-轴承故障检测训练赛