Loading

【文本挖掘】(四)信息抽取

问题引入

有哪些方法能从非结构化文本中提取结构数据?或识别文本中描述的实体和关系?
image

特征选择

  • 卡方统计法
  • 递归式特征消除法
  • 岭回归

信息抽取

概念

从文本中抽出实体、关系、事件等事实信息,并将自然语言数据转换为结构化数据。

文本抽取到的四类基本元素:

  • 实体:人、公司、地址等
  • 属性:人的头衔、人的年龄等
  • 关系:公司和员工之间的雇佣关系等
  • 事件:两个公司间的联谊

实现顺序:

  • 分词
  • 词性标注
  • 命名实体识别Named Entity Recognition NER
  • 关系识别搜索文本中不同实体间可能的关系

实体识别

命名实体识别

graph LR A(命名实体识别)-->B(命名实体) A-->C(实现方法) A-->J(实现流程) B(命名实体)-->D(三大类) B(命名实体)-->E(七小类) D-->F(实体类) D-->G(时间类) D-->H(数字类) E-->I(人名、机构名、地名、时间、日期、货币、百分比) C-->P(基于规则和词典) C-->Q(基于统计) C-->R(混合) P-->S(指示词、方向词、中心词、标点符号等) Q-->T(隐马尔可夫 HMM) Q-->U(最大熵 ME) Q-->V(支持向量机 SVM) Q-->W(条件随机场 CRF) J-->X(<p align="left">1.分词<br>2.对分词进行标签标注<br>3.抽取标签标注的分词<br>4.组成命名实体)

开放域实体识别

基于已知实体的语义特征去搜索日志中识别出命名的实体,然后进行聚类。
不限实体类别,给定种子,根据种子提取更多同类实体,如:

给定种子<中国,美国,俄罗斯>
找出其他国家<德国,英国,法国..>

graph LR A(开放域实体抽取方法)-->B(基于查询日志的抽取方法) A-->C(基于网页的抽取方法) A-->E(融合多个数据源的抽取方法)

这部分内容信息较少,待补充

关系抽取

将实体和实体间的关系构成一个三元组,如:

输入文本: 张三毕业于清华大学,工作在福州。
抽取结果:(张三,毕业于,清华大学)(张三,工作在,福州)

graph LR A(关系抽取方法)-->B(基于规则) B-->C(<p align="left">优点:比较准确;无需训练数据<br>缺点:人力成本高) A-->E(有监督学习) E-->F(原理:训练一个二分类判断两个实体间是否存在关系) A-->G(半监督/无监督学习) G-->H(Bootstrapping) G-->I(Distantsupervision)

案例

一:正则表达式抽取结构化信息

import re
import pandas as pd

data='''
张华考上了北京大学
丽萍进了中等技术学校
韩梅梅进了百货公司
他们都有光明的前途
'''

regex=r'(.*)[考|进].*了(.*)'
mylist=[]
for line in data.split('\n'):
    mysearch=re.search(regex,line)
    if mysearch:
        name=mysearch.group(1)
        dest=mysearch.group(2)
        mylist.append((name,dest))
df=pd.DataFrame(mylist)
df.columns=['姓名','去向']
print(df)

二:dateuitl+正则表达式抽取日期实体

# -*- coding:utf-8 -*-
# @project: transcoding_stopword.py
# @filename: 时间实体识别.py
# @author: GaGim-H
# @github: https://github.com/GaGim-H
# @time:  2024/05/22 19:15


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

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):
    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):
    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


def parse_datetime(msg):
    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.match(
            r"([0-9零一二三四五六七八九十]+年)?([0-9一二三四五六七八九十]+月)?"
            r"([0-9一二三四五六七八九十]+[号日])?([上中下五晚早]+)?"
            r"([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 = None
                    # 将日期文字转换成数字
                    if 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().hour
                    print("hour:",hour)
                    if hour<12:
                        target_date=target_date.replace(hour=hour+12)
            return target_date.strftime("%Y-%m-%d %H:%M:%S")
        else:
            return None


# 判断日期是否有效
def check_time_valid(word):
    m=re.match("%d+$",word)
    if m:
        if len(word)<=6:
            return None
    world=re.sub('[号|日]\d+$','日',word)
    if world!=word:
        return check_time_valid(world)
    else:
        return world


# 提取时间:解析句子,提取可能表示时间的词,并进行上下文拼接
def time_extract(txt):
    time_res=[]
    word=''
    keyDate={'今天':0,'明天':1,'后天':2,'今日':0,'明日':1}
    # print([ [k,v] for k,v in psg.cut(txt)])
    for k,v in psg.cut(txt):
        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']:
                word=word+k
            else:
                time_res.append(word)
                word=''
        elif v in ['m','t'] :
            word=k
    if 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]


text1="中新网3月3日,据WTT世界乒联消息,WTT新加坡大满贯2023资格赛将于明日正式打响,3月7日至9日为资格赛,11日至19日为正赛。"
print(text1,time_extract(text1),sep=":")

三:使用pyhanlp和jieba提取命名实体

from pyhanlp import *
sentence=u'''今天是xxx等老一辈革命家为雷锋同志题词60周年,xxx***近日作出重要指示指出,雷锋的名字家喻户晓,雷锋的事迹深入人心,雷锋精神滋养着一代代中华儿女的心灵。党的十八大以来,xxx***对弘扬雷锋精神作出一系列重要论述,指导推动新时代学雷锋活动不断拓展内容、创新形式、丰富载体、涌现出一批又一批雷锋式先进集体和模范人物,为新时代伟大变革注入不竭精神动力。今天,党建网梳理了xxx***部分相关重要论述,邀您一起学习领会。'''
words=HanLP.segment(sentence)
for item in words:
	print(item.word,item.nature)
# 关键词提取
keyword_list=HanLP.extractKeyword(sentence,5) #提取五个关键词
# 文本摘要
summary_list=HanLP.extractSummary(sentence,2) #提取文本中两个关键句作为摘要
# 依存句法分析
Denpency_lst=HanLP.parseDepency(sentence)
# 短句提取 
phrase_list=HanLP.extractPhrase(sentence,5)
# 词性切分
analyzer=PerceptronLexicalAnalyzer()
segs=analyzer.analyze(sentence)
arr=str(segs).split(" ")

def get_result(arr):
	re_list=[]
	ner=['n','ns']
	for x in arr:
		temp=x.split("/")
		if (temp[1] in ner):
			re_list.append(temp[0])
	return re_list
	

# 使用jieba
import jieba.analyse
# 词性切分
kw=jieba.analyse.extract_tags(sentence,allowPOS=('n','ns'))
kw01=jieba.analyse.textrank(sentence,allowPOS=('n','ns'))
for item in kw01:
	print(item)
from pyhanlo import *
content="3月19日晚,WTT新加坡大满贯女双决赛上演中日对决——孙颖莎/王曼对阵日本组合伊藤美成/早田希娜,最终3-0完胜对手,取得WTT新加坡大满贯女双冠军。"
# 1. 分词标注
print(HanLP.segment(content))
# 2.添加自定义词典,用部分人名不能被正确分词
CustomDictionary.add("孙颖莎","nr 300")
CustomDictionary.insert("伊藤美诚",'nr 100')
CustomDictionary.add("中日对决","nz 1024 n 1")
print(HanLP.segment(content))
# 人名识别
segment=HanLP.newsSegment().enableNmaeRecognize(True)
name_list=[]
for i in segment.seg(conetent):
	split_words=str(i).split('/')
	word,tag=split_words[0],split_words[-1]
	if tag=='nr':
		name_list.append(word)
print(name_list)
# 地名识别ns   HanLP.newSegment().enablePlaceRecognize(True)
# 机构名识别nt   HanLP.newSegment().enableOrganizationRecognize(True)
posted @ 2024-05-23 00:13  踩坑大王  阅读(1221)  评论(0)    收藏  举报