传知代码-上下位关系自动检测方法(论文复现)

news2024/12/23 20:37:33

代码以及视频讲解

本文所涉及所有资源均在传知代码平台可获取

概述

本文复现论文 Hearst patterns revisited: Automatic hypernym detection from large text corpora[1] 提出的文本中上位词检测方法。

在自然语言处理中,上下位关系(Is-a Relationship)表示的是概念(又称术语)之间的语义包含关系。其中,上位词(Hypernym)表示的是下位词(Hyponym)的抽象化和一般化,而下位词则是对上位词的具象化和特殊化。举例来说:“水果”是“苹果”、“香蕉”、“橙子”等的上位词,“汽车”、“电动车”、“自行车”等则是“交通工具”的下位词。在自然语言处理任务中,理解概念之间的上下位关系对于诸如词义消歧、信息检索、自动问答、语义推理等任务都具有重要意义。

在这里插入图片描述

文本中上位词检测方法,即从文本中提取出互为上下位关系的概念。现有的无监督上位词检测方法大致可以分为两类——基于模式的方法和基于分布模型的方法:

(1)基于模式的方法:其主要思想是利用特定的词汇-句法模式来检测文本中的上下位关系。例如,我们可以通过检测文本中是否存在句式“【词汇1】是一种【词汇2】”或“【词汇1】,例如【词汇2】”来判断【词汇1】和【词汇2】间是否存在上下位关系。这些模式可以是预定义的,也可以是通过机器学习得到的。然而,基于模式的方法存在一个众所周知的问题——极端稀疏性,即词汇必须在有限的模式中共同出现,其上下位关系才能被检测到。

(2)基于分布模型的方法:基于大型文本语料库,词汇可以被学习并表示成向量的形式。利用特定的相似度度量,我们可以区分词汇间的不同关系。

在该论文中,作者研究了基于模式的方法和基于分布模型的方法在几个上下位关系检测任务中的表现,并发现简单的基于模式的方法在常见的数据集上始终优于基于分布模型的方法。作者认为这种差异产生的原因是:基于模式的方法提供了尚不能被分布模型准确捕捉到的重要上下文约束。

算法原理

Hearst 模式

作者使用如下模式来捕捉文本中的上下位关系:

模板例子
X X X which is a (example|class|kind…) of Y Y YCoffee, which is a beverage, is enjoyed worldwide.
X X X (and|or) (any|some) other Y Y YCoffee and some other hot beverages are popular in the morning.
X X X which is called Y Y YCoffee, which is called “java”.
X X X is JJS (most)? Y Y YCoffee is the most consumed beverage worldwide.
X X X is a special case of Y Y YEspresso is a special case of coffee.
X X X is an Y Y Y thatA latte is a coffee that includes steamed milk.
X X X is a !(member|part|given) Y Y YA robot is a machine.
!(features|properties) Y Y Y such as X 1 X_1 X1, X 2 X_2 X2, …Beverages such as coffee, tea, and soda have various properties such as caffeine content and flavor.
(Unlike|like) (most|all|any|other) Y Y Y, X X XUnlike most beverages, coffee is often consumed hot.
Y Y Y including X 1 X_1 X1, X 2 X_2 X2, …Beverages including coffee, tea, and hot chocolate are served at the café.

通过对大型语料库使用模式捕捉候选上下位词对并统计频次,可以计算任意两个词汇之间存在上下位关系的概率

上下位关系得分

p ( x , y ) p(x,y) p(x,y)是词汇 x x x y y y 分别作为下位词和上位词出现在预定义模式集合 P P P 中的频率, p − ( x ) p^-(x) p(x) x x x 作为任意词汇的下位词出现在预定义模式中的频率, p + ( y ) p^+(y) p+(y) y y y 作为任意词汇的上位词出现在预定义模式中的频率。作者定义正逐点互信息(Positive Point-wise Mutual Information)作为词汇间上下位关系得分的依据:
ppmi ( x , y ) = max ⁡ ( 0 , log ⁡ p ( x , y ) p − ( x ) , p + ( y ) ) \text{ppmi}(x,y)=\max(0,\log\frac{p(x,y)}{p^-(x),p^+(y)}) ppmi(x,y)=max(0,logp(x),p+(y)p(x,y))
由于模式的稀疏性,部分存在上下位关系的词对并不会出现在特定的模式中。为了解决这一问题,作者利用PPMI得分矩阵的稀疏表示来预测任意未知词对的上下位关系得分。PPMI得分矩阵定义如下:
M ∈ R m × m , M i j = ppmi ( x , y ) ( 1 ≤ x , y ≤ m ) M\in R^{m\times m},M_{ij}=\text{ppmi}(x,y)(1\le x,y\le m) MRm×m,Mij=ppmi(x,y)(1x,ym)
,其中 KaTeX parse error: Undefined control sequence: \or at position 18: …|\{x|(x,y)\in P\̲o̲r̲(y,x)\in P\}|

对矩阵 M M M 做奇异值分解可得 M = U Σ V T M=U\Sigma V^T M=UΣVT,然后我们可以通过下式计算出上下位关系 spmi 得分:
spmi ( x , y ) = u x T Σ r v y \text{spmi}(x,y)=u_x^T\Sigma_r v_y spmi(x,y)=uxTΣrvy
其中 u x u_x ux v y v_y vy 分别是矩阵 U U U V V V 的第 x x x 行和第 y y y 行, Σ r \Sigma_r Σr是对 Σ \Sigma Σ r r r 截断(即除了最大的 r r r 个元素其余全部置零)。

核心逻辑

具体的核心逻辑如下所示:

import spacy
import json
from tqdm import tqdm
import re
from collections import Counter
import numpy as np
import math

nlp = spacy.load("en_core_web_sm")

def clear_text(text):
    """对文本进行清理"""
    # 这里可以添加自己的清理步骤
    # 删去交叉引用标识,例如"[1]"
    pattern = r'\[\d+\]'
    result = re.sub(pattern, '', text)
    return result

def split_sentences(text):
    """将文本划分为句子"""
    doc = nlp(text)
    sentences = [sent.text.strip() for sent in doc.sents]
    return sentences

def extract_noun_phrases(text):
    """从文本中抽取出术语"""
    doc = nlp(text)
    terms = []
    # 遍历句子中的名词性短语(例如a type of robot)
    for chunk in doc.noun_chunks:
        term_parts = []
        for token in list(chunk)[-1::]:
            # 以非名词且非形容词,或是代词的词语为界,保留右半部分(例如robot)
            if token.pos_ in ['NOUN', 'ADJ'] and token.dep_ != 'PRON':
                term_parts.append(token.text)
            else:
                break
        if term_parts != []:
            term = ' '.join(term_parts)
            terms.append(term)
    return terms

def term_lemma(term):
    """将术语中的名词还原为单数"""
    lemma = []
    doc = nlp(term)
    for token in doc:
        if token.pos_ == 'NOUN':
            lemma.append(token.lemma_)
        else:
            lemma.append(token.text)
    return ' '.join(lemma)

def find_co_occurrence(sentence, terms, patterns):
    """找出共现于模板的术语对"""
    pairs = []
    # 两两之间匹配
    for hyponym in terms:
        for hypernym in terms:
            if hyponym == hypernym:
                continue
            for pattern in patterns:
                # 将模板中的占位符替换成候选上下位词
                pattern = pattern.replace('__HYPONYM__', re.escape(hyponym))
                pattern = pattern.replace('__HYPERNYM__', re.escape(hypernym))
                # 在句子中匹配
                if re.search(pattern, sentence) != None:
                    # 将名词复数还原为单数
                    pairs.append((term_lemma(hyponym), term_lemma(hypernym)))
    return pairs

def count_unique_tuple(tuple_list):
    """统计列表中独特元组出现次数"""
    counter = Counter(tuple_list)
    result = [{"tuple": unique, "count": count} for unique, count in counter.items()]
    return result

def find_rth_largest(arr, r):
    """找到第r大的元素"""
    rth_largest_index = np.argpartition(arr, -r)[-r]
    return arr[rth_largest_index]

def find_pairs(corpus_file, patterns, disable_tqdm=False):
    """读取文件并找出共现于模板的上下位关系术语对"""
    pairs = []
    # 按行读取语料库
    lines = corpus_file.readlines()
    for line in tqdm(lines, desc="Finding pairs", ascii=" 123456789#", disable=disable_tqdm):
        # 删去首尾部分的空白字符
        line = line.strip()
        # 忽略空白行
        if line == '':
            continue
        # 清理文本
        line = clear_text(line)
        # 按句处理
        sentences = split_sentences(line)
        for sentence in sentences:
            # 抽取出句子中的名词性短语并分割成术语
            candidates_terms = extract_noun_phrases(sentence)
            # 找出共现于模板的术语对
            pairs = pairs + find_co_occurrence(sentence, candidates_terms, patterns)
    return pairs

def spmi_calculate(configs, unique_pairs):
    """基于对共现频率的统计,计算任意两个术语间的spmi得分"""
    # 计算每个术语分别作为上下位词的出现频次
    terms = list(set([pair["tuple"][0] for pair in unique_pairs] + [pair["tuple"][1] for pair in unique_pairs]))
    term_count = {term: {'hyponym_count': 0, 'hypernym_count': 0} for term in terms}
    all_count = 0
    for pair in unique_pairs:
        term_count[pair["tuple"][0]]['hyponym_count'] += pair["count"]
        term_count[pair["tuple"][1]]['hypernym_count'] += pair["count"]
        all_count += pair["count"]
    # 计算PPMI矩阵 
    ppmi_matrix = np.zeros((len(terms), len(terms)), dtype=np.float32)
    for pair in unique_pairs:
        hyponym = pair["tuple"][0]
        hyponym_id = terms.index(hyponym)
        hypernym = pair["tuple"][1]
        hypernym_id = terms.index(hypernym)
        ppmi = (pair["count"] * all_count) / (term_count[hyponym]['hyponym_count'] * term_count[hypernym]['hypernym_count'])
        ppmi = max(0, math.log(ppmi))
        ppmi_matrix[hyponym_id, hypernym_id] = ppmi
    # 对PPMI进行奇异值分解并截断
    r = configs['clip']
    U, S, Vt = np.linalg.svd(ppmi_matrix)
    S[S < find_rth_largest(S, r)] = 0
    S_r = np.diag(S)
    # 计算任意两个术语间的spmi
    paris2spmi = []
    for hyponym_id in range(len(terms)):
        for hypernym_id in range(len(terms)):
            # 同一个术语间不计算得分
            if hyponym_id == hypernym_id:
                continue
            spmi = np.dot(np.dot(U[hyponym_id , :], S_r), Vt[:, hypernym_id]).item()
            # 保留得分大于阈值的术语对
            if spmi > configs["threshold"]:
                hyponym = terms[hyponym_id]
                hypernym = terms[hypernym_id]
                paris2spmi.append({"hyponym": hyponym, "hypernym": hypernym, "spmi": spmi})
    # 按spmi从大到小排序
    paris2spmi = sorted(paris2spmi, key=lambda x: x["spmi"], reverse=True)
    return paris2spmi

if __name__ == "__main__":
    # 读取配置文件
    with open('config.json', 'r') as config_file:
        configs = json.load(config_file)
    # 读取模板
    with open(configs['patterns_path'], 'r') as patterns_file:
        patterns = json.load(patterns_file)
    # 语料库中共现于模板的术语对
    with open(configs['corpus_path'], 'r', encoding='utf-8') as corpus_file:
        pairs = find_pairs(corpus_file, patterns)
    # 统计上下位关系的出现频次
    unique_pairs = count_unique_tuple(pairs)
    with open(configs["pairs_path"], 'w') as pairs_file:
        json.dump(unique_pairs, pairs_file, indent=6, ensure_ascii=True)
    # 计算任意两个术语间的spmi得分
    paris2spmi = spmi_calculate(configs, unique_pairs)
    with open(configs['spmi_path'], 'w') as spmi_file:
        json.dump(paris2spmi, spmi_file, indent=6, ensure_ascii=True)

以上代码仅作展示,更详细的代码文件请参见附件。

效果演示

运行脚本main.py,程序会自动检测语料库中存在的上下位关系。运行结果如下所示:

在这里插入图片描述

使用方式

  • 解压附件压缩包并进入工作目录。如果是Linux系统,请使用如下命令:
unzip Revisit-Hearst-Pattern.zip
cd Revisit-Hearst-Pattern
  • 代码的运行环境可通过如下命令进行配置:
pip install -r requirements.txt
python -m spacy download en_core_web_sm
  • 如果希望在本地运行程序,请运行如下命令:
python main.py
  • 如果希望在线部署,请运行如下命令:
python main-flask.py
  • 如果希望添加新的模板,请修改文件data/patterns.json
    • "_HYPONYM_"表示下位词占位符;
    • "_HYPERNYM_"表示上位词占位符;
    • 其余格式请遵照 python.re 模块的正则表达式要求。
  • 如果希望使用自己的文件路径或改动其他实验设置,请在文件config.json中修改对应参数。以下是参数含义对照表:
参数名含义
corpus_path文本语料库文件路径,默认为“data/corpus.txt”。
patterns_path预定义模式库的路径。默认为“data/patterns.json”。
pairs_path利用模式筛选出的上下位关系词对路径,默认为“data/pairs.json”。
spmi_path上下位关系词对及其spmi得分路径,默认为“data/spmi.json”。
clip用于对 Σ \Sigma Σ 进行截断的参数 r r r ,默认为10。
thresholdspmi得分小于该值的词对将被舍去。默认为1。
max_bytes输入文件大小上限(用于在线演示),默认为200kB。

(以上内容皆为原创,请勿转载)

参考文献

[1] Roller S, Kiela D, Nickel M. Hearst patterns revisited: Automatic hypernym detection from large text corpora[J]. arXiv preprint arXiv:1806.03191, 2018.

源码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1965384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单链表的应用(附代码)

链表 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。其实链表可以想象为小火车&#xff0c;链表比顺序表具有更好的灵活性&#xff0c;只需要通过指针的改变就可以实现增删查改。 这是逻辑思维下链表的样…

使用TensorRT对YOLOv8模型进行加速推理

这里使用GitHub上shouxieai的 infer框架 对YOLOv8模型进行加速推理&#xff0c;操作过程如下所示&#xff1a; 1.配置环境&#xff0c;依赖项&#xff0c;包括&#xff1a; (1).CUDA: 11.8 (2).cuDNN: 8.7.0 (3).TensorRT: 8.5.3.1 (4).ONNX: 1.16.0 (5).OpenCV: 4.10.0 2.clon…

redis:Linux安装redis,redis常用的数据类型及相关命令

1. 什么是NoSQL nosql[not only sql]不仅仅是sql。所有非关系型数据库的统称。除去关系型数据库之外的都是非关系数据库。 1.1为什么使用NoSQL ​ NoSQL数据库相较于传统关系型数据库具有灵活性、可扩展性和高性能等优势&#xff0c;适合处理非结构化和半结构化数据&#xff0c…

服务运营|摘要:INFORMS 近期收益管理(Revenue Management )相关文章

编者按&#xff1a; 本期涵盖了INFORMS与收益管理相关的文章及其基本信息。 Title: Online Learning for Constrained Assortment Optimization Under Markov Chain Choice Model 基于马尔可夫链选择模型的约束下选品优化的在线学习 Link: https://pubsonline.informs.org/do…

召唤生命,阻止轻生——《生命门外》

本书的目的&#xff0c;就是阻止自杀&#xff01;拉回那些深陷在这样的思维当中正在挣扎犹豫的人&#xff0c;提醒他们珍爱生命&#xff0c;让更多的人&#xff0c;尤其是年轻人从执迷不悟的犹豫徘徊中幡然醒悟&#xff0c;回归正常的生活。 网络上抱孩子跳桥轻生的母亲&#…

Linux中gdb调试器的使用

Linux调试器&#xff1a;gdb gdb简介基本使用和常见的指令断点相关运行相关命令 gdb简介 我们都知道一个程序一般有两个版本分别是debug&#xff0c;和release版本&#xff0c;后者就是发布给用户的版本&#xff0c;而前者就是我们程序员用来调试用的版本。 他们有什么区别呢&…

Docker搭建Mysql主从复制,最新,最详细

Docker搭建Mysql主从复制&#xff0c;最新&#xff0c;最详细 这次搭建Mysql主从复制的时候&#xff0c;遇到不少问题&#xff0c;所以本次重新记录一下&#xff0c;使用Docker搭建一主三从的Mysql 一、Docker-Compose创建4个Mysql容器 1.1 创建对应的映射文件夹和对应的配置…

GitLab的安装步骤与代码拉取上传操作

一、GitLab的安装 详情见如下博客链接&#xff1a;gitlab安装 二、GitLab配置ssh key &#xff08;1&#xff09;打开Git Bash终端生成SSH和添加步骤 1、全局配置git用户名 git config --global user.name "xxx"注意&#xff1a;xxx为你自己gitlab的名字 2、全局…

JavaScript递归菜单栏

HTML就一个div大框架 <div class"treemenu"></div> 重中之重的JavaScript部分他来啦&#xff01; 注释也很清楚哟家人们&#xff01; let data; let arr []; let cons;let xhr new XMLHttpRequest(); // 设置请求方式和请求地址 xhr.open(get, ./js…

Linux上如何分析进程内存分配,优化进程内存占用大小

云计算场景下,服务器上内存宝贵,只有尽可能让服务器上服务进程占用更少的内存,方才可以提供更多的内存给虚拟机,卖给云客户。 虚拟化三大件:libvirt、qemu、kvm内存开销不小,可以优化占用更少的内存。如何找到进程内存开销的地方直观重要,以qemu为例说明。 一、查看进…

别让不专业的HR逼走你的人才!人力资源管理应该遵循哪些原则?

优秀的HR能够带领整个人力资源部门为企业招揽人才、培养人才和留住人才&#xff0c;促使人才为企业的业务增长提供支持。而不专业的HR&#xff0c;不仅无法做到这些&#xff0c;还会把企业原有的人才逼走&#xff0c;因为不合适的人力管理也是导致人才离职的原因。所以&#xf…

【C++】前缀和算法专题

目录 介绍 【模版】一维前缀和 算法思路&#xff1a; 代码实现 【模版】二维前缀和 算法思路 代码实现 寻找数组中心的下标 算法思路 代码实现 总结 除自身以外数组的乘积 算法思路 代码实现 和为K的子数组 算法思路 代码实现 和可被整除的K的子数组 算法思…

C++ 操作Git仓库

代码 #include "common.h" #include "args.c" #include "common.c"enum index_mode {INDEX_NONE,INDEX_ADD };struct index_options {int dry_run;int verbose;git_repository* repo;enum index_mode mode;int add_update; };/* Forward declar…

Python零基础详细入门教程

Python零基础详细入门教程可以从以下几个方面展开&#xff0c;帮助初学者系统地学习Python编程&#xff1a; 一、Python基础入门 1. Python简介 Python的由来与发展&#xff1a;Python是一种广泛使用的高级编程语言&#xff0c;以其简洁的语法和强大的功能而受到开发者的喜爱…

2024第二十届中国国际粮油产品及设备技术展示交易会

2024第二十届中国国际粮油产品及设备技术展示交易会 时间&#xff1a;2024年11月15-17日 地点&#xff1a; 南昌绿地国际博览中心 展会介绍&#xff1a; 随着国家逐年加大对农业的投入&#xff0c;调整农业产业结构&#xff0c;提高农产品附加值&#xff0c;促进农民增收。…

CRMEB-众邦科技 使用笔记

1.启动项目报错 Unable to load authentication plugin ‘caching_sha2_password’. 参考&#xff1a;http://t.csdnimg.cn/5EqaE 解决办法&#xff1a;升级mysql驱动 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</ar…

超级弱口令检查工具

一、背景 弱口令问题主要源于用户和管理员的安全意识不足&#xff0c;以及为了方便记忆而采用简单易记的密码。这些密码往往仅包含简单的数字和字母&#xff0c;缺乏复杂性和多样性&#xff0c;因此极易被破解。弱口令的存在严重威胁到系统和用户的数据安全&#xff0c;使得攻击…

在局域网中的另一台主机如何访问windows10WSL中的服务

文章目录 1&#xff0c;开启win10 路由功能2&#xff0c;配置转发规则 1&#xff0c;开启win10 路由功能 2&#xff0c;配置转发规则 netsh advfirewall firewall add rule name"Allowing LAN connections" dirin actionallow protocolTCP localport80 netsh interf…

计算机体系结构:缓存一致性ESI

集中式缓存处理器结构&#xff08;SMP&#xff09; 不同核访问存储器时间相同。 分布式缓存处理器结构&#xff08;NUMA&#xff09; 共享存储器按模块分散在各处理器附近&#xff0c;处理器访问本地存储器和远程存储器的延迟不同&#xff0c;共享数据可进入处理器私有高速缓存…

程序员自曝接单:三年时间接了25个单子,收入12万

程序员接单在程序员的副业中并不少见。程序员接单作为一个起步快、门槛低、类型多样的副业选择&#xff0c;一直深受程序员的青睐。就算你没有接触过接单&#xff0c;也一定对接单有过了解。 程序员接单是指程序员通过接取开发者发布的项目或任务来获取收入的一种工作方式。程序…