NLP入門到實戰(二)時間提取

本篇將乾貨~實踐一下基於jieba,spacy, pyltp, lac, nltk, foolltk等開源庫進行實(調)踐(包)!

時間提取屬於NLP中的實體命名識別,例如匹配時間,地點,物體,人物等等…

一、jieba

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Na8gWt6B-1590979894296)(D:\CSDN\pic\NLP\1590974191205.png)]

代碼(有的代碼未刪除,功能不僅爲單純匹配時間。):

import re
from datetime import datetime, timedelta
from dateutil.parser import parse
import jieba.posseg as psg


def time_extract(text):
    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))).strftime('%Y年%m月%d日')
        elif word != '':
            if v in ['m', 't']:
                # print('*', word)

                word = word + k
            else:
                # print('**', word)

                time_res.append(word)
                word = ''
        elif v in ['m', 'k']:
            word = k
    if word != '':
        time_res.append(word)
    print(' '.join(time_res))

    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]


def check_time_valid(word):
    """
    日期串有效性判斷
    :param word:
    :return:
    """
    m = re.match("d+$", word)
    if m:
        if len(word) <= 6:
            return None
    wordl = re.sub('[號|日]d+$', '日', word)
    if wordl != word:
        return check_time_valid(wordl)
    else:
        return wordl


def parse_datetime(msg):
    """
    對日期串進行時間轉換
    :param msg:
    :return:
    """
    if msg is None or len(msg) == 0:
        return None

    try:
        dt = parse(msg, fuzzy=True)
        return dt.strftime('%Y-%m-%d %H:%M:%S')
    except Exception as e:
        m = re.search(
            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 = {}

        # print (res)

        for name in res:
            if res[name] is not None and len(res[name]) != 0:
                tmp = None
                if name == "year":
                    tmp = year2dig(res[name])
                else:
                    tmp = cn2dig(res[name])

                if tmp is not None:
                    params[name] = int(tmp)

        target_data = 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 == u'中午':
                hour = target_data.time().hour
                if hour < 12:
                    target_data = target_data.replace(hour=hour+12)
        return target_data.strftime('%Y-%m-%d %H:%M:%S')
    else:
        return None


UTIL_CN_NUM = {
    '零': 0,
    '一': 1,
    '二': 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 None
    m = re.match("d+", src)
    if m:
        return int(m.group(0))
    rsl = 0
    unit = 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 * unit
        else:
            return None
    if rsl < unit:
        rsl += unit

    return rsl


def 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 + item

    m = re.match("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 None


text1 = '我要住到27日三點'
text2 = '預定28號2點房間'
text3 = '我要從21號下午4點住到11日2號'
# print(text1, time_extract(text1))
# print(text2, time_extract(text2))
# print(text3, time_extract(text3))
time_extract(text1)
time_extract(text2)
time_extract(text3)

在實體命名識別中:jieba分詞先導入所需庫:import jieba.posseg as psg

再通過cut切割出不同標籤的元素,然後進行通過標籤篩選出想要的例如‘t’表示時間:

def time_extract(text):
    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))).strftime('%Y年%m月%d日')
        elif word != '':
            if v in ['m', 't']:
                # print('*', word)

                word = word + k
            else:
                # print('**', word)

                time_res.append(word)
                word = ''
        elif v in ['m', 'k']:
            word = k
    if word != '':
        time_res.append(word)
    print(' '.join(time_res))

    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]

2:哈工大pyltp模型

舉個簡單的例子,我們需要從下面的文本中提取時間:

6月28日,杭州市統計局權威公佈《2019年5月月報》,杭州市醫保參保人數達到1006萬,相比於2月份的989萬,三個月暴漲16萬人參保,傲視新一線城市。

我們可以從文本有提取6月28日2019年5月2月份這三個有效時間。
 通常情況下,較好的解決思路是利用深度學習模型來識別文本中的時間,通過一定數量的標記文本和合適的模型。本文嘗試利用現有的NLP工具來解決如何從文本中提取時間。
使用的工具爲哈工大的pyltp,可以在Python的第三方模塊中找到,通過pip安裝或者通過使用whl安裝,另外python3.7建議使用whl安裝,實現下載好分詞模型cws.model和詞性標註pos.model這兩個模型文件。
 話不多說,我們直接上Python代碼,如下:

# -*- coding: utf-8 -*-

import os
from pyltp import Segmentor
from pyltp import Postagger

class LTP(object):
    def __init__(self):
        cws_model_path = os.path.join(os.path.dirname(__file__), 'cws.model')  # 分詞模型路徑,模型名稱爲`cws.model`
        pos_model_path = os.path.join(os.path.dirname(__file__), 'pos.model')  # 詞性標註模型路徑,模型名稱爲`pos.model`
        self.segmentor = Segmentor()  # 初始化實例
        self.segmentor.load(cws_model_path) # 加載模型
        self.postagger = Postagger()  # 初始化實例
        self.postagger.load(pos_model_path)  # 加載模型

    # 分詞
    def segment(self, text):
        words = list(self.segmentor.segment(text))
        return words

    # 詞性標註
    def postag(self, words):
        postags = list(self.postagger.postag(words))
        return postags

    # 獲取文本中的時間
    def get_time(self, text):

        # 開始分詞及詞性標註
        words = self.segment(text)
        postags = self.postag(words)

        time_lst = []

        i = 0
        for tag, word in zip(postags, words):
            if tag == 'nt':
                j = i
                while postags[j] == 'nt' or words[j] in ['至', '到']:
                    j += 1
                time_lst.append(''.join(words[i:j]))
            i += 1

        # 去重子字符串的情形
        remove_lst = []
        for i in time_lst:
            for j in time_lst:
                if i != j and i in j:
                    remove_lst.append(i)

        text_time_lst = []
        for item in time_lst:
            if item not in remove_lst:
                text_time_lst.append(item)

        # print(text_time_lst)
        return text_time_lst

    # 釋放模型
    def free_ltp(self):
        self.segmentor.release()
        self.postagger.release()

if __name__ == '__main__':
    ltp = LTP()

    # 輸入文本
    sent = '6月28日,杭州市統計局權威公佈《2019年5月月報》,杭州市醫保參保人數達到1006萬,相比於2月份的989萬,三個月暴漲16萬人參保,傲視新一線城市。'
    time_lst = ltp.get_time(sent)
    ltp.free_ltp()

    # 輸出文本中提取的時間
    print('提取時間: %s' % str(time_lst))

接着,我們測試幾個例子。

輸入文本爲:

今天,央行舉行了2019年6月份金融統計數據解讀吹風會,發佈了2019年6月份金融統計數據並就當前的一些熱點問題進行了解讀和迴應。

文本中提取的時間爲:

提取時間: ['今天', '2019年6月份', '2019年6月份', '當前']

輸入文本爲:

2006年,上海的國內生產總值達到10296.97億元,是中國內地第一個GDP突破萬億元的城市。2008年,北京GDP破萬億。兩年後,廣州GDP超過萬億。2011年,深圳、天津、蘇州、重慶4城的GDP也進入了萬億行列。武漢、成都在2014年躋身“萬億俱樂部”,杭州、南京和青島、無錫和長沙的GDP依次在2015年、2016年和2017年過萬億。寧波和鄭州則成爲2018年萬億俱樂部的新成員。

文本中提取的時間爲:

提取時間: ['2006年', '2008年', '2011年', '2014年', '2015年', '2016年', '2018年']

輸入文本爲:

此後,6月28日、7月9日和7月11日下午,武威市政協、市人大、市政府分別召開堅決全面徹底肅清火榮貴流毒和影響專題民主生活會。

文本中提取的時間爲:

提取時間: ['此後', '6月28日', '7月9日', '7月11日下午']

輸入文本爲:

姜保紅出生於1974年4月,她於2016年11月至2018年9月任武威市副市長,履新時,武威市的一把手正是火榮貴。

文本中提取的時間爲:

提取時間: ['1974年4月', '2016年11月至2018年9月']

個人認爲效果不錯!尤其是在時間提取上。

3: Stanford CoreNLP Python

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OCpanpcL-1590979894300)(D:\CSDN\pic\NLP\1590975720584.png)]

這個Stanford CoreNLP的安裝步驟也是挺麻煩的;

安裝依賴

1:下載安裝JDK 1.8及以上版本。
2:下載Stanford CoreNLP文件,解壓。
3:處理中文還需要下載中文的模型jar文件,然後放到stanford-corenlp-full-2018-02-27根目錄下即可(注意一定要下載這個文件,否則它默認是按英文來處理的)。

常用接口

StanfordCoreNLP官網給出了python調用StanfordCoreNLP的接口。

使用

本教程以stanfordcorenlp接口爲例(本文所用版本爲Stanford CoreNLP 3.9.1),講解Python調用StanfordCoreNLP的使用方法。

使用pip安裝stanfordcorenlp:

簡單使用命令:pip install stanfordcorenlp
選擇USTC鏡像安裝(安裝速度很快,畢竟國內鏡像):pip install stanfordcorenlp -i http://pypi.mirrors.ustc.edu.cn/simple/ --trusted-host pypi.mirrors.ustc.edu.cn

如果需要下載其他版本可以:比如:pip install stanfordcorenlp==3.7.0.2;如果不指定就直接下載最新版本;卸載的話:pip uninstall stanfordcorenlp

上代碼:

#coding:utf-8
from stanfordcorenlp import StanfordCoreNLP
nlp = StanfordCoreNLP(r'E:\python_file\NLP\stanford-corenlp-full-2018-10-05/', lang='zh')
s = "撫寧區人民法院關於秦皇島市南戴河洋河口村金海岸度假村5-1-101號及土地使用權" \
    "(變賣)的公告 秦皇島市南戴河藍色海岸公寓A座、B座、洋河口村金海岸度假村涉案房產" \
    "及土地使用權司法變賣公告秦皇島市撫寧區人民法院定於2020年3月23日10時至2020年5月22日10時(延時除外)"
ner = nlp.ner(s)

indexWordNer = {}
wordindex = 0
wordTag = ''
for lineIdx, line in enumerate(ner):
    word = line[0]
    tag = line[1]
    if tag != 'O':
        wordindex += 1
        if wordindex == 1:
            begin = lineIdx
            wordTag = tag
    else:
        if wordindex != 0:
            key = (begin, lineIdx - 1)
            wordner = ''.join([i[0] for i in ner[begin:lineIdx]])
            value = (wordner, wordTag)
            indexWordNer[key] = value
            wordindex = 0
a = []
indexWordNerSorted = sorted(indexWordNer.items(), key=lambda e: e[0][0])
for key, value in indexWordNerSorted:
    print( key, value)
    if 'GPE' in value:
        print(value[0])
        a.append(value[0])
print(a)

方法跟上面的模型幾乎一致,都是調包換參。學習階段,調包俠嘛。

4:paddlehub

![](https://img-blog.csdnimg.cn/20200601105357820.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk4NTc0,size_16,color_FFFFFF

paddlehub的lac。在模型啓動就特別快,除去模型啓動時間,評價每秒識別文本8k字左右,相比斯坦福好很多,準確率也很高,我挺喜歡這個模型的,以後有能力將嘗試進行二次開發,上代碼:

# coding:utf-8
"""
 Named entity recognition based on Baidu lac
__author__: 周小夏
"""
import paddlehub as hub
import pandas as pd
import xlrd
import time


if __name__ == "__main__":
    #  Load the pre training model named lac
    time_start = time.time()
    textlist = list()
    lac = hub.Module(name="lac")
    content = xlrd.open_workbook(filename='樣本外測試日期地址案例資產系統網站20200423.xlsx', encoding_override='gbk')
    data = pd.read_excel(content, engine='xlrd')
    n = 0
    set = set()
    finals = []
    for data_one in data['描述和其他']:
        if data_one.__str__().__len__() <= 20:
            finals.append('數據爲空或地名不常見')
            continue
        # try:
        text = ','.join(data_one.split('二、')[0:-1]).split(',')[0]
        if text == '':
            text = data_one
        test_text = ['土地使用權司法變賣公告秦皇島市撫寧區人民法院定於2020年3月23日10時至2020年5月22日10時(延時除外)', '']
        #  Set the input of participle, whose input is a set of sentences
        inputs = {"text": test_text}
        final = []
        result = ''
        #  Call the model for word segmentation and put the results in results
        results = lac.lexical_analysis(data=inputs)
        for abb in results:
            for a, b in enumerate(abb['tag']):
                lenght = len(abb['word'][a])
                word = abb['word'][a]
                if b == 'TIME':
                    final.append(word)

            result += '\n'
        n += 1

        # Show the results
        l2 = []
        [l2.append(i) for i in final if not i in l2]
        try:
            if len(l2[0]) <= 2:
                pass
        except:
            finals.append('數據爲空或地名不常見')
            continue
        finals.append(''.join(l2))
        if n >= 1:
            break
    print(" Total time spent: {}".format(time.time() - time_start), finals)
    test = pd.DataFrame(data=finals)
    # test.to_csv('nerResult1.csv', encoding='utf-8')

5:nltk

import nltk
from nltk.tokenize import word_tokenize
from nltk.tag import pos_tag
nltk.download()
ex = 'European authorities fined Google a record $5.1 billion on Wednesday for abusing its power in the mobile phone market and ordered the company to alter its practices in 2020 years'
def preprocess(sent):
    sent = nltk.word_tokenize(sent)
    sent = nltk.pos_tag(sent)
    return sent

sent = preprocess(ex)
print(sent)

6:spacy

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-am06ukF3-1590979894311)(D:\CSDN\pic\NLP\1590979773745.png)]

spacy現在支持jieba中文分詞,後續介紹使用,強大的工具。

import re
import string
import nltk
import spacy
import pandas as pd
import numpy as np
import math
from tqdm import tqdm

from spacy.matcher import Matcher
from spacy.tokens import Span
from spacy import displacy

pd.set_option('display.max_colwidth', 200)
nlp = spacy.load("en_core_web_sm")
text = "GDP in developing countries such as Vietnam will continue growing at a high rate.2020 years"
doc = nlp(text)
for tok in doc:
  print(tok.text, "-->",tok.dep_,"-->", tok.pos_)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章