计算语义学深度指南
文档概述
本文档系统性地介绍计算语义学的核心概念、理论框架、算法实现及其与自然语言处理(NLP)的深度关联。计算语义学是连接语言学理论与计算机科学的桥梁学科,旨在形式化地表示和计算自然语言的语义信息。文档涵盖从词汇语义学到话语语义学的完整知识体系,提供详尽的数学推导和代码实现。
关键词速览
| 术语 | 英文 | 核心定义 |
|---|---|---|
| 词汇语义学 | Lexical Semantics | 研究单词意义及其相互关系 |
| 句子语义学 | Sentence Semantics | 研究句子级别的意义组合 |
| 话语语义学 | Discourse Semantics | 研究超越句子的文本意义 |
| 语义角色标注 | Semantic Role Labeling | 识别句中谓词与论元的语义关系 |
| 框架语义学 | Frame Semantics | 通过语义框架理解词义的理论 |
| 语义网络 | Semantic Network | 图结构表示语义知识的形式 |
| 词义消歧 | Word Sense Disambiguation | 消除词语歧义确定正确词义 |
| 语义相似度 | Semantic Similarity | 度量词语或句子的语义距离 |
| 文本蕴含 | Textual Entailment | 判断文本与假设之间的蕴含关系 |
| 知识表示 | Knowledge Representation | 形式化表示和组织知识的技术 |
| 组合性原则 | Compositionality Principle | 复合表达式意义由成分及其组合方式决定 |
| 一阶逻辑 | First-Order Logic | 用于形式化语义的形式系统 |
| 内涵逻辑 | Intensional Logic | 处理命题态度的逻辑系统 |
| 语义角色 | Thematic Role | 描述论元在事件中承担的语义功能 |
一、语义学学科体系与计算语义学定位
1.1 语义学的学科全景
语义学(Semantics)是研究语言意义的学科,其研究范围涵盖了从最小的语义单位——语素(morpheme)——到复杂的篇章结构。作为语言学的核心分支之一,语义学与形式逻辑、认知心理学、计算机科学等学科深度交叉,形成了一个庞大的学科体系。
在计算语义学(Computational Semantics)的框架下,我们需要将这些语言学理论转化为可计算的形式,以便计算机能够处理和理解自然语言。这一转化的核心挑战在于:自然语言的语义是模糊的、语境依赖的、且常常隐含不明的,但同时又遵循着一定的规律和约束。
语义学的核心问题
语义学的核心问题可以归纳为以下几个维度:
- 意义是什么? 如何定义和形式化语言的语义内容,这涉及到意义的指称论、观念论、使用论等多种理论立场
- 意义如何组合? 小粒度语义单元如何组合成更大单位的意义,这涉及到组合性原则的具体实现
- 语境的作用是什么? 上下文如何影响意义的理解和消歧,包括语言内语境和语言外语境
- 知识与语义的关系? 世界知识如何支撑语言理解,词汇知识和推理知识如何协同工作
1.2 语义学的分支体系
语义学的内部结构可以从多个角度进行划分。按照研究对象的不同粒度,可以分为词汇语义学、句子语义学和话语语义学三大分支。
1.2.1 词汇语义学(Lexical Semantics)
词汇语义学关注单词层面的意义,是计算语义学的基础,也是NLP中最具挑战性的问题之一。一个看似简单的词,可能承载着丰富的语义信息,包括其核心意义、附加色彩、搭配限制等多个维度。
词义表征的多维视角
词义可以通过多种方式表征,这些不同的表征方式反映了不同的语义理论立场:
指称视角(Referential View) 词语与其所指代的世界实体之间的关系构成了指称语义学的核心。单词”猫”之所以有意义,是因为它指代了世界上存在的某种实体——猫科动物。这种观点强调语义与世界的对应关系,其形式化表达为:
其中 是可能世界的实体集合。这种表征方式的优点是直观明了,缺点是难以处理抽象概念和虚构实体的指称问题。
观念视角(Conceptual View) 词语在人类认知系统中的心理表征构成了观念语义学的研究对象。单词”猫”激活了一个心理概念,这个概念包含了猫的典型特征(四条腿、有尾巴、会捉老鼠等)以及与猫相关的联想(宠物、动物、可爱等)。这种观点强调语义的主观性和心理现实性:
使用视角(Usage-based View) 词语在语言社群中的典型使用模式和分布构成了分布式语义学的基础。这种观点与Ludwig Wittgenstein的”意义即使用”哲学观点一脉相承,强调语义的社会性和经验性:
词义关系类型学
词汇语义学研究的经典关系类型构成了语义网络和知识图谱的基础:
| 关系类型 | 英文名称 | 定义 | 典型示例 | 计算表示 |
|---|---|---|---|---|
| 同义词 | Synonymy | 意义相同或极其相近 | ”快乐”-“高兴” | 语义相似度 ≈ 1 |
| 反义词 | Antonymy | 意义相反或对立 | ”是”-“否”、“黑”-“白” | 余弦相似度低 |
| 上位词 | Hypernymy | 概念泛化关系 | ”动物”是”狗”的上位词 | IS-A层次 |
| 下位词 | Hyponymy | 概念特化关系 | ”玫瑰”是”花”的下位词 | IS-A层次 |
| 部分-整体 | Meronymy | 组成关系 | ”车轮”是”汽车”的部件 | PART-OF层次 |
| 主题关联 | Associative | 联想关系 | ”医生”-“医院” | 语义关联度高 |
| 蕴含关系 | Entailment | 蕴含关系 | ”喝”蕴含”喝液体” | 推理规则 |
class LexicalRelationAnalyzer:
"""
词汇语义关系分析器
分析和计算词汇之间的各种语义关系
"""
def __init__(self, wordnet_path=None):
"""
Args:
wordnet_path: WordNet数据库路径(可选)
"""
self.wordnet = None
if wordnet_path:
import nltk
try:
nltk.data.find('corpora/wordnet')
except LookupError:
nltk.download('wordnet')
from nltk.corpus import wordnet as wn
self.wordnet = wn
def get_synsets(self, word, pos=None):
"""获取词的所有词义(同义词集合)"""
if self.wordnet:
synsets = self.wordnet.synsets(word, pos=pos)
return [
{
'name': syn.name(),
'definition': syn.definition(),
'examples': syn.examples(),
'lemmas': syn.lemma_names()
}
for syn in synsets
]
return []
def compute_hypernymy_distance(self, word1, word2):
"""
计算上位词距离
距离 = LCS的深度(如果有共同上位词)
Returns:
distance: 距离值,None表示无共同上位词
"""
if not self.wordnet:
return None
synsets1 = self.wordnet.synsets(word1)
synsets2 = self.wordnet.synsets(word2)
if not synsets1 or not synsets2:
return None
min_distance = float('inf')
for syn1 in synsets1:
for syn2 in synsets2:
# 寻找最低公共上位词
common_hypernyms = syn1.lowest_common_hypernyms(syn2)
if common_hypernyms:
# 距离 = 两个词到LCS的路径长度之和
dist = (syn1.shortest_path_distance(common_hypernyms[0]) or 0) + \
(syn2.shortest_path_distance(common_hypernyms[0]) or 0)
min_distance = min(min_distance, dist)
return min_distance if min_distance != float('inf') else None
def compute_word_similarity(self, word1, word2, method='path'):
"""
计算词汇语义相似度
Args:
word1, word2: 待比较的两个词
method: 计算方法 ('path', 'lch', 'wup', 'resnik', 'lin', 'jiang')
Returns:
similarity: 相似度分数
"""
if not self.wordnet:
return 0.0
synsets1 = self.wordnet.synsets(word1)
synsets2 = self.wordnet.synsets(word2)
if not synsets1 or not synsets2:
return 0.0
if method == 'path':
# 路径相似度
max_sim = 0.0
for s1 in synsets1:
for s2 in synsets2:
sim = s1.path_similarity(s2)
if sim and sim > max_sim:
max_sim = sim
return max_sim
elif method == 'wup':
# Wu-Palmer相似度
max_sim = 0.0
for s1 in synsets1:
for s2 in synsets2:
sim = s1.wup_similarity(s2)
if sim and sim > max_sim:
max_sim = sim
return max_sim
elif method == 'lin':
# Lin相似度
max_sim = 0.0
for s1 in synsets1:
for s2 in synsets2:
sim = s1.lin_similarity(s2, self.wordnet.ic('ic-brown.dat'))
if sim and sim > max_sim:
max_sim = sim
return max_sim
return 0.0
def build_semantic_field(self, seed_word, depth=2, max_words=100):
"""
构建语义场
从种子词出发,构建相关的语义词汇网络
"""
if not self.wordnet:
return {}
from collections import deque
semantic_field = {}
visited = set()
queue = deque([(seed_word, 0)])
while queue and len(semantic_field) < max_words:
word, level = queue.popleft()
if word in visited or level > depth:
continue
visited.add(word)
synsets = self.wordnet.synsets(word)
for syn in synsets:
# 添加同义词
for lemma in syn.lemma_names():
if lemma not in visited:
semantic_field[lemma] = {
'synset': syn.name(),
'relation': 'synonym',
'depth': level
}
if level < depth:
queue.append((lemma, level + 1))
# 添加上下位词
for hyper in syn.hypernyms():
for lemma in hyper.lemma_names():
if lemma not in visited:
semantic_field[lemma] = {
'synset': hyper.name(),
'relation': 'hypernym',
'depth': level
}
if level < depth:
queue.append((lemma, level + 1))
# 添加部分-整体关系
for holo in syn.member_holonyms()[:3]:
for lemma in holo.lemma_names():
if lemma not in visited:
semantic_field[lemma] = {
'synset': holo.name(),
'relation': 'holonym',
'depth': level
}
return semantic_field词汇语义歧义的本质与处理
自然语言中的词汇歧义是NLP的核心挑战之一。歧义的存在源于语言的历時演变、一词多义现象的累积、以及语言表达的 经济性原则。英语中的单词”bank”可以指”银行”(金融机构)或”河岸”(地理实体),这种歧义需要通过上下文来消解。
歧义的主要类型包括:
| 歧义类型 | 定义 | 示例 | 消解方法 |
|---|---|---|---|
| 词汇歧义 | 同一词形有多种词义 | ”bank”(银行/河岸) | WSD |
| 句法歧义 | 同一句子有多种句法分析 | ”I saw the man with the telescope” | 句法分析 |
| 语义歧义 | 句子有多种语义解释 | ”Every farmer killed a duck”(数量歧义) | 语义消歧 |
| 指代歧义 | 指代表达有多种解释 | ”它”可能指代多个实体 | 指代消解 |
| 语用歧义 | 话语有多种语用含义 | ”It’s cold in here”(请求/陈述) | 语用推理 |
1.2.2 句子语义学(Sentence Semantics)
句子语义学研究如何从词汇组合成句子级别的意义。这涉及到组合性原则、论元结构和语义类型系统等核心概念。
组合性原则的数学表达
组合性原则(Principle of Compositionality)是形式语义学的核心假设,由Gottlob Frege在19世纪末提出,其经典表述为:
复合表达式的意义由其组成部分的意义及其组合方式决定。
这一原则保证了语言理解的系统性:我们可以通过理解各部分及其组合规则来理解整个句子。形式化地,组合性原则可以表示为:
其中:
- 表示语义解释函数(语义映射)
- 是语义组合函数(合成规则)
- 组合函数 的类型签名为:
组合性原则的实现方式
组合性原则的实现有多种策略,不同的语义学框架采用不同的实现方式:
函项-参数结构(Function-Argument Structure)
在类型逻辑语义学中,组合性通过函项-参数结构实现:
- 谓词是函项(函数),需要参数(论元)
- 函项应用于参数产生命题
合并规则(Merge Operations)
在生成语法与语义接口的研究中,合并规则定义了如何组合两个句法单元:
class CompositionalSemantics:
"""
组合性语义计算器
实现基于类型逻辑的组合性语义计算
"""
def __init__(self):
# 类型系统
self.types = {
'e': '实体类型', # 最小指示类型
't': '命题类型', # 真值类型
'n': '名词类型', # 名词类型
's': '情境类型', # 情境类型
}
# 词汇语义库
self.lexicon = self._build_lexicon()
def _build_lexicon(self):
"""构建词汇语义库"""
return {
# 名词:类型 n
'小明': {'type': 'e', 'var': 'x1'},
'小红': {'type': 'e', 'var': 'x2'},
'猫': {'type': 'n', 'extension': 'cat'},
'狗': {'type': 'n', 'extension': 'dog'},
'苹果': {'type': 'n', 'extension': 'apple'},
# 不及物动词:类型 n\\t
'跑': {'type': 'n\\\\t', 'lambda': 'λx. run(x)'},
'睡觉': {'type': 'n\\\\t', 'lambda': 'λx. sleep(x)'},
# 及物动词:类型 (n\\t)/(n)
'喜欢': {'type': '(n\\\\t)/(n)', 'lambda': 'λy.λx. like(x, y)'},
'喂': {'type': '(n\\\\t)/(n)', 'lambda': 'λy.λx. feed(x, y)'},
# 形容词:类型 n\\n
'大的': {'type': 'n\\\\n', 'lambda': 'λx. big(x)'},
'红的': {'type': 'n\\\\n', 'lambda': 'λx. red(x)'},
# 介词:类型 (n\\t)/(n)
'在': {'type': '(n\\\\t)/(n)', 'lambda': 'λy.λx. at(x, y)'},
}
def type_check(self, expr_type, target_type):
"""类型检查"""
return expr_type == target_type
def combine(self, functor, argument):
"""
语义组合
实现函项-参数应用
"""
if isinstance(functor, str):
functor = self.lexicon.get(functor, {})
if isinstance(argument, str):
argument = self.lexicon.get(argument, {})
# 类型检查
# 例如:动词(n\t) 应用 名词(n) -> 命题(t)
return {
'type': 't', # 组合后类型
'lambda': f"{functor.get('lambda', '?')} ({argument.get('var', argument.get('lambda', '?'))})",
'meaning': self._compute_meaning(functor, argument)
}
def _compute_meaning(self, functor, argument):
"""计算语义表示"""
# 这里应该实现lambda演算的归约
# 简化版本
return f"{functor.get('lambda', '')}({argument.get('var', '')})"
def parse_sentence(self, sentence):
"""
句子语义分析
返回句子的语义表示
"""
tokens = sentence.split()
# 简化的自底向上分析
# 实际实现需要句法分析器
if len(tokens) == 2:
# 主语 + 谓语
subject, predicate = tokens
return self.combine(predicate, subject)
elif len(tokens) == 3:
# 主语 + 谓语 + 宾语
subject, predicate, object = tokens
verb_phrase = self.combine(predicate, object)
return self.combine(verb_phrase, subject)
return None论元结构与动词配价
论元结构(Argument Structure)是连接句法与语义的桥梁。每个动词都会选择特定数量和类型的论元,形成其论元结构。动词的这种选择限制被称为配价(Valency)。
| 动词类型 | 配价 | 论元结构 | 示例 |
|---|---|---|---|
| 一价(不及物) | 1 | Agent | ”跑”、“死” |
| 二价(及物) | 2 | Agent + Theme | ”吃”、“打” |
| 三价(双宾) | 3 | Agent + Theme + Goal | ”给”、“告诉” |
| 心理动词 | 2 | Experiencer + Stimulus | ”喜欢”、“害怕” |
| 变化动词 | 2 | Theme + Result | ”变成”、“成为” |
class VerbArgumentStructure:
"""
动词论元结构分析器
识别动词要求的论元及其语义角色
"""
def __init__(self):
# 动词论元结构库(简化版)
self.verb_frames = {
'吃': {
'arity': 2,
'subject_role': 'Agent', # 主语角色
'object_role': 'Theme', # 宾语角色
'frame': '<Agent> 吃 <Theme>',
},
'给': {
'arity': 3,
'subject_role': 'Agent',
'indirect_object_role': 'Goal',
'object_role': 'Theme',
'frame': '<Agent> 给 <Goal> <Theme>',
},
'变成': {
'arity': 2,
'subject_role': 'Theme',
'object_role': 'Result',
'frame': '<Theme> 变成 <Result>',
},
'喜欢': {
'arity': 2,
'subject_role': 'Experiencer',
'object_role': 'Stimulus',
'frame': '<Experiencer> 喜欢 <Stimulus>',
},
}
def get_frame(self, verb):
"""获取动词的论元框架"""
return self.verb_frames.get(verb, {
'arity': 1,
'subject_role': 'Agent',
'frame': '<Agent> {verb}'
})
def fill_frame(self, verb, arguments):
"""
填充论元框架
Args:
verb: 动词
arguments: 填充的论元列表
Returns:
filled_frame: 填充后的语义表示
"""
frame_info = self.get_frame(verb)
arity = frame_info['arity']
if len(arguments) != arity:
return None
# 构建语义表示
if arity == 1:
return f"{frame_info['subject_role']}({arguments[0]})"
elif arity == 2:
return f"{frame_info['object_role']}({arguments[1]}, {frame_info['subject_role']}({arguments[0]}))"
elif arity == 3:
return f"{frame_info['object_role']}({arguments[2]}, {frame_info['indirect_object_role']}({arguments[1]}, {frame_info['subject_role']}({arguments[0]})))"
return None语义类型系统
在类型论语义学中,我们为表达式分配语义类型,作为组合性计算的基础。Montague语义学建立了经典的类型系统:
| 类型 | 含义 | 示例 | 语义值 |
|---|---|---|---|
| 实体类型 | 个体词 | 特定个体 | |
| 真值类型 | 句子 | True/False | |
| 一元谓词类型 | 名词 | 实体集合 | |
| 二元关系类型 | 及物动词 | 关系集合 | |
| 命题态度类型 | 认知动词 | 可能世界函数 |
类型的组合遵循函数式编程的规则:
- 函项类型 表示从 类型到 类型的函数
- 应用规则:如果 且 ,则
1.2.3 话语语义学(Discourse Semantics)
话语语义学超越句子边界,研究文本层面的连贯性和意义。话语语义学的核心任务是理解句子之间如何连接、如何共享信息、如何构建连贯的叙事或论证结构。
指代消解(Coreference Resolution)
指代消解是话语语义学的核心任务之一,其目标是识别文本中指向同一实体的不同表达式。
指代的类型
指代现象分类:
指代类型
├── 照应指代 (Anaphora)
│ ├── 回指照应 (Cataphora):后照前
│ │ └── 示例:"当我走进房间,发现它很暗。"
│ └── 回指 (Anaphora):前照后
│ └── 示例:"小明走进了房间。他很高兴。"
│
├── 指示指代 (Demonstrative)
│ ├── this/that + 名词
│ │ └── 示例:"那个问题很难解决。"
│ └── 定冠词 the
│ └── 示例:"The president announced..."
│
├── 零形指代 (Ellipsis)
│ ├── 主语省略
│ │ └── 示例:"[我]很高兴见到你。"
│ └── 谓语省略
│ └── 示例:"张三喜欢吃苹果,[李四也喜欢吃苹果]。"
│
└── 提及省略 (VP Ellipsis)
└── 示例:"谁喜欢咖啡?我[喜欢咖啡]。"
class CoreferenceResolver:
"""
指代消解系统
实现基于规则的指代消解(简化版本)
实际系统需要使用机器学习或神经网络模型
"""
def __init__(self):
# 实体跟踪器
self.entities = {} # {entity_id: EntityInfo}
self.entity_counter = 0
# 语义角色标注器(简化)
self.role_tagger = RoleTagger()
def resolve(self, sentences):
"""
对句子序列进行指代消解
Args:
sentences: 句子列表
Returns:
resolved_text: 消解后的文本
mentions: 识别出的指代链
"""
all_mentions = []
all_chains = []
for sent_id, sentence in enumerate(sentences):
# 识别当前句子的提及
mentions = self._identify_mentions(sentence, sent_id)
# 链接到已有的指代链
for mention in mentions:
linked_chain = self._link_mention(mention)
if linked_chain:
linked_chain.mentions.append(mention)
else:
# 创建新的指代链
chain = CoreferenceChain(mention)
self.entity_counter += 1
chain.entity_id = self.entity_counter
all_chains.append(chain)
all_mentions.append(mention)
# 更新实体信息
self._update_entities(sentence, sent_id)
return self._format_output(sentences, all_chains)
def _identify_mentions(self, sentence, sent_id):
"""识别句子中的提及"""
mentions = []
# 识别代词提及
pronouns = ['他', '她', '它', '他们', '她们', '它们', '这', '那', '这个', '那个']
for pronoun in pronouns:
if pronoun in sentence:
idx = sentence.index(pronoun)
mentions.append(Mention(
text=pronoun,
mention_type='pronoun',
sentence_id=sent_id,
start_char=idx,
end_char=idx + len(pronoun)
))
# 识别命名实体提及
named_entities = self._extract_named_entities(sentence)
for ne in named_entities:
mentions.append(Mention(
text=ne['text'],
mention_type='named_entity',
entity_type=ne['type'],
sentence_id=sent_id,
start_char=ne['start'],
end_char=ne['end']
))
return mentions
def _extract_named_entities(self, sentence):
"""提取命名实体(简化版)"""
# 实际应用中应该使用NER模型
# 这里返回空列表作为占位
return []
def _link_mention(self, mention):
"""将提及链接到已有指代链"""
for chain in self.entity_counter:
# 简单的最近距离策略
last_mention = chain.mentions[-1]
if self._is_compatible(mention, last_mention):
return chain
return None
def _is_compatible(self, mention1, mention2):
"""判断两个提及是否可能指向同一实体"""
# 简化规则
if mention1.mention_type == 'pronoun' and mention2.mention_type == 'named_entity':
# 代词与命名实体可能兼容
# 性别和数的一致性检查
return True
if mention1.mention_type == 'named_entity' and mention2.mention_type == 'named_entity':
# 两个命名实体同名则兼容
return mention1.text == mention2.text
return False
def _update_entities(self, sentence, sent_id):
"""更新实体跟踪信息"""
# 更新实体的最新出现位置等信息
pass
def _format_output(self, sentences, chains):
"""格式化输出"""
output = []
for chain in chains:
entity_repr = f"实体{chain.entity_id}: " + ", ".join(
f"[{m.text}](句子{m.sentence_id})" for m in chain.mentions
)
output.append(entity_repr)
return "\n".join(output)
class Mention:
"""指代提及"""
def __init__(self, text, mention_type, sentence_id, start_char, end_char, **kwargs):
self.text = text
self.mention_type = mention_type
self.sentence_id = sentence_id
self.start_char = start_char
self.end_char = end_char
for key, value in kwargs.items():
setattr(self, key, value)
class CoreferenceChain:
"""指代链"""
def __init__(self, first_mention):
self.entity_id = None
self.mentions = [first_mention]篇章衔接理论
篇章连贯性的理论基础包括多种分析框架:
修辞结构理论(Rhetorical Structure Theory, RST)
RST由Mann和Thompson在1980年代提出,将篇章分析为由修辞关系连接的树结构。
| 关系类型 | 定义 | 示例 |
|---|---|---|
| 详述(Elaboration) | 后句详细说明前句 | ”他买了房子。是一座大房子。“ |
| 原因(Cause) | 后句是前句的原因 | ”地面湿了。因为下过雨。“ |
| 目的(Purpose) | 后句是前句的目的 | ”他努力学习。为了通过考试。“ |
| 对比(Contrast) | 两部分形成对比 | ”他喜欢安静。她喜欢热闹。“ |
| 顺序(Sequence) | 事件按顺序排列 | ”他先洗脸,然后吃早饭。“ |
class RSTParser:
"""
修辞结构理论解析器
将句子序列解析为RST树结构
"""
def __init__(self):
# 修辞关系定义
self.relations = {
'elaboration': self._elaboration_relation,
'cause': self._cause_relation,
'purpose': self._purpose_relation,
'contrast': self._contrast_relation,
'sequence': self._sequence_relation,
}
def parse(self, sentences):
"""
解析句子序列的修辞结构
Returns:
rst_tree: RST树结构
"""
# 简化的贪婪解析
# 实际应用需要更复杂的算法
if len(sentences) == 1:
return RSTNode(type='leaf', text=sentences[0])
# 寻找最可能的修辞关系
best_relation = None
best_score = -1
for i in range(len(sentences) - 1):
for rel_name, rel_func in self.relations.items():
score = rel_func(sentences[i], sentences[i+1])
if score > best_score:
best_score = score
best_relation = (rel_name, i)
if best_relation:
rel_name, split_point = best_relation
left_tree = self.parse(sentences[:split_point+1])
right_tree = self.parse(sentences[split_point+1:])
return RSTNode(
type='span',
relation=rel_name,
nucleus=self._determine_nucleus(rel_name, sentences[split_point], sentences[split_point+1]),
left=left_tree,
right=right_tree
)
# 无法确定关系,使用序列关系
return self._sequence_parse(sentences)
def _determine_nucleus(self, relation, nucleus_candidate, satellite_candidate):
"""确定核心和卫星"""
# 某些关系有固定的核心方向
nucleus_rules = {
'elaboration': 'left', # 左为核心
'cause': 'right', # 右为核心
'purpose': 'left', # 左为核心
'contrast': 'both', # 两者都是核心
'sequence': 'left', # 左为核心
}
return nucleus_rules.get(relation, 'left')
def _elaboration_relation(self, s1, s2):
"""评估详述关系"""
# 简化的评分函数
# 实际应用中应该使用更复杂的特征
score = 0
# 共享名词多
nouns1 = set(self._extract_nouns(s1))
nouns2 = set(self._extract_nouns(s2))
score += len(nouns1 & nouns2)
return score
def _cause_relation(self, s1, s2):
"""评估因果关系"""
cause_markers = ['因为', '由于', '所以', '因此', '导致']
result_markers = ['所以', '因此', '于是', '结果']
has_cause = any(m in s2 for m in cause_markers)
has_result = any(m in s1 for m in result_markers)
return int(has_cause) + int(has_result)
def _purpose_relation(self, s1, s2):
"""评估目的关系"""
purpose_markers = ['为了', '以便', '目的是']
return any(m in s2 for m in purpose_markers)
def _contrast_relation(self, s1, s2):
"""评估对比关系"""
contrast_markers = ['但是', '然而', '而', '可是', '不过']
return any(m in s1 or m in s2 for m in contrast_markers)
def _sequence_relation(self, s1, s2):
"""评估顺序关系"""
sequence_markers = ['然后', '接着', '之后', '随后']
return any(m in s2 for m in sequence_markers)
def _extract_nouns(self, sentence):
"""提取名词(简化版)"""
# 实际应用中应该使用词性标注器
return []
class RSTNode:
"""RST树节点"""
def __init__(self, type='span', text=None, relation=None,
nucleus=None, left=None, right=None):
self.type = type # 'span' 或 'leaf'
self.text = text
self.relation = relation
self.nucleus = nucleus # 'left', 'right', 'both'
self.left = left
self.right = right
def to_string(self, indent=0):
"""转换为字符串表示"""
prefix = ' ' * indent
if self.type == 'leaf':
return f"{prefix}[LEAF: {self.text}]"
else:
result = [f"{prefix}[SPAN: {self.relation} ({self.nucleus})"]
result.append(self.left.to_string(indent + 1))
result.append(self.right.to_string(indent + 1))
result.append(f"{prefix}]")
return '\n'.join(result)二、语义角色标注(Semantic Role Labeling, SRL)
2.1 任务定义与理论背景
语义角色标注(Semantic Role Labeling,SRL)是识别句子中谓词与其论元之间语义关系的任务。作为连接句法分析与语义理解的桥梁,SRL在信息抽取、问答系统、机器翻译等NLP任务中发挥着重要作用。
任务形式化
给定一个句子 和一个目标谓词位置 ,SRL任务要求识别:
其中 是语义角色类型, 是对应的论元span(起始位置到结束位置)。
语义角色类型体系
语义角色的研究有多个重要的理论传统,包括:
Proto-Agent/Proto-Patient理论(Dowty, 1991)
Dowty提出,语义角色不是离散的二元划分,而是原型的、程度性的概念。Proto-Agent具有以下典型特征:
- 自主性(autotelicity):动作的自愿性
- 感知性(sentience):对事件的感知
- 因果性(causation):导致变化
- 运动性(movement):相对于其他实体移动
- 独立存在(independent existence):独立于事件
Levin的动词分类(Beth Levin, 1993)
Levin基于动词的句法行为(特别是与句法转换的关系)对英语动词进行了分类,建立了超过3000个动词的分类体系。这一分类对于预测动词的论元结构非常有用。
class SemanticRoleLabeler:
"""
语义角色标注器
基于依存句法分析的语义角色标注
"""
def __init__(self):
# FrameNet角色定义
self.framenet_roles = {
# 核心角色
'Agent': {
'definition': '动作的有意执行者',
'criteria': ['自立性', '感知性', '因果性']
},
'Theme': {
'definition': '被描述的实体,经历变化',
'criteria': ['变化性', '移动性']
},
'Experiencer': {
'definition': '感知或情感体验者',
'criteria': ['感知性', '情感性']
},
'Result': {
'definition': '动作产生的结果或产物',
'criteria': ['产生性']
},
# 非核心角色
'Instrument': {
'definition': '执行动作的手段或工具',
'criteria': ['使用性']
},
'Location': {
'definition': '动作发生的地点',
'criteria': ['空间性']
},
'Time': {
'definition': '动作发生的时间',
'criteria': ['时间性']
},
'Cause': {
'definition': '动作的原因',
'criteria': ['因果性']
},
'Beneficiary': {
'definition': '动作的受益者',
'criteria': ['受益性']
},
}
# 依存关系到语义角色的映射
self.dep_to_role = {
'nsubj': 'Agent', # 名词性主语
'nsubjpass': 'Theme', # 被动主语
'dobj': 'Theme', # 直接宾语
'iobj': 'Recipient', # 间接宾语
'pobj': 'Location', # 介词宾语
'agent': 'Agent', # by-介词短语
'nmod': 'Location', # 名词修饰语
'advmod': 'Manner', # 状语
'tmod': 'Time', # 时间修饰语
}
def label(self, sentence, predicate_position, dependency_parse):
"""
对句子进行语义角色标注
Args:
sentence: 句子分词列表
predicate_position: 谓词位置索引
dependency_parse: 依存分析结果 [(head, dep, dep_index), ...]
Returns:
roles: 语义角色列表 [(role, span_start, span_end), ...]
"""
predicate = sentence[predicate_position]
roles = []
for dep_type, dependent, dep_idx in dependency_parse:
if dep_idx == predicate_position:
# 这个依存关系指向谓词
role = self._map_dependency_to_role(dep_type, predicate, dependent)
if role:
roles.append({
'role': role,
'span_start': dep_idx,
'span_end': dep_idx,
'text': dependent,
'dep_relation': dep_type
})
elif dependent == predicate_position:
# 谓词是这个依存的头
# 检查是否是状语修饰语
pass # 其他情况
# 处理介词短语
roles.extend(self._label_prepositional_phrases(
sentence, predicate_position, dependency_parse
))
return roles
def _map_dependency_to_role(self, dep_type, predicate, dependent):
"""将依存关系映射到语义角色"""
# 首先检查通用映射
if dep_type in self.dep_to_role:
return self.dep_to_role[dep_type]
# 基于动词类别的特定映射
# 简化版本
return None
def _label_prepositional_phrases(self, sentence, predicate_position, deps):
"""标注介词短语"""
roles = []
for dep_type, word, idx in deps:
if 'prep' in dep_type:
# 介词短语
role = self._infer_pp_role(word)
if role:
roles.append({
'role': role,
'span_start': idx,
'span_end': idx,
'text': word,
'dep_relation': dep_type
})
return roles
def _infer_pp_role(self, preposition):
"""基于介词推断语义角色"""
role_mapping = {
'在': 'Location',
'于': 'Location',
'向': 'Direction',
'从': 'Source',
'到': 'Goal',
'为了': 'Purpose',
'因为': 'Cause',
'在...时': 'Time',
'在...前': 'Time',
'在...后': 'Time',
}
return role_mapping.get(preposition, 'Location')
def _role_to_string(self, role_info):
"""将角色信息转换为字符串"""
return f"[{role_info['role']}]({role_info['text']})"2.2 基于神经网络的SRL方法
现代SRL系统广泛使用深度神经网络,特别是基于注意力机制的模型。
class NeuralSRLModel(nn.Module):
"""
神经网络语义角色标注模型
使用BERT编码器和 Biaffine 分类器
"""
def __init__(self, encoder_name='bert-base-chinese', num_roles=50):
super().__init__()
# BERT编码器
from transformers import BertModel, BertTokenizer
self.encoder = BertModel.from_pretrained(encoder_name)
self.tokenizer = BertTokenizer.from_pretrained(encoder_name)
hidden_size = self.encoder.config.hidden_size
# 谓词表示层
self.predicate_proj = nn.Linear(hidden_size, hidden_size)
# Biaffine注意力层
# 用于谓词-论元的依赖建模
self.biaffine_s = nn.Bilinear(hidden_size, hidden_size, 1)
# 角色分类层
self.role_classifier = nn.Linear(hidden_size, num_roles)
# dropout
self.dropout = nn.Dropout(0.1)
def forward(self, tokens, predicate_mask, attention_mask=None):
"""
前向传播
Args:
tokens: token ID张量 [batch, seq_len]
predicate_mask: 谓词位置掩码 [batch, seq_len]
attention_mask: 注意力掩码 [batch, seq_len]
Returns:
role_logits: 角色分类logits [batch, seq_len-1, num_roles]
"""
# BERT编码
encoded = self.encoder(
tokens,
attention_mask=attention_mask
)
hidden_states = encoded.last_hidden_state # [batch, seq_len, hidden]
# 获取谓词表示
# 扩展谓词掩码以处理WordPiece对齐
pred_hidden = hidden_states * predicate_mask.unsqueeze(-1)
pred_repr = pred_hidden.sum(dim=1) / (predicate_mask.sum(dim=1, keepdim=True) + 1e-10)
pred_repr = self.dropout(pred_repr)
pred_repr = torch.tanh(self.predicate_proj(pred_repr))
# 候选论元表示
arg_hidden = self.dropout(hidden_states)
# Biaffine注意力
# 计算每个位置的谓词-论元得分
biaffine_scores = []
for i in range(hidden_states.size(1)):
arg_i = arg_hidden[:, i, :] # [batch, hidden]
score = self.biaffine_s(pred_repr, arg_i) # [batch, 1]
biaffine_scores.append(score)
biaffine_scores = torch.cat(biaffine_scores, dim=1) # [batch, seq_len]
# 角色分类
role_logits = self.role_classifier(hidden_states) # [batch, seq_len, num_roles]
# 结合biaffine得分和角色分类
combined_logits = role_logits + biaffine_scores.unsqueeze(-1)
return combined_logits
def decode(self, tokens, predicate_positions, role_logits):
"""
解码SRL结果
Args:
tokens: token列表
predicate_positions: 谓词位置列表
role_logits: 角色分类logits
Returns:
srl_results: SRL结果列表
"""
predictions = torch.argmax(role_logits, dim=-1)
results = []
for i, pred_pos in enumerate(predicate_positions):
pred_roles = predictions[i].tolist()
pred_tokens = tokens[i]
# 构建角色-论元对
role_arguments = []
for j, role_id in enumerate(pred_roles):
if j != pred_pos and role_id != 0: # 排除自身和空角色
role_arguments.append({
'role': self.role_vocabulary[role_id],
'token_start': j,
'token_end': j,
'text': pred_tokens[j]
})
results.append(role_arguments)
return results三、框架语义学(Frame Semantics)
3.1 理论框架与认知基础
框架语义学(Frame Semantics)由Charles Fillmore在1970年代提出,其核心观点是:理解词语的意义需要激活相关的知识框架。这一理论植根于认知语言学的核心假设:语言理解依赖于激活储存在记忆中的概念结构。
框架的定义
框架(Frame)是一个与特定语言表达相关的概念结构系统。当我们理解一个词时,特别是某些”词汇化”了特定场景的词,我们会自动激活该词所预设的概念框架。
以”商业交易”(Commercial_Transaction)框架为例:
框架: Commercial_Transaction (商业交易)
├── 框架元素:
│ ├── Buyer(买方):购买商品的主体
│ ├── Seller(卖方):出售商品的主体
│ ├── Goods(商品):被交易的物品
│ ├── Money(货币):支付的对价
│ └── Transaction(交易):发生的商业行为
│
├── 触发词(Lexical Units):
│ ├── buy, purchase, acquire, shop for...
│ ├── sell, offer, trade...
│ └── cost, price, pay...
│
└── 框架关系:
├── 子框架: 商榷 -> 成交 -> 支付 -> 交付
└── 相关框架: 商议, 所有权转移, 货币流通
框架语义学的认知基础
框架语义学的提出有深刻的认知科学背景:
- 图式理论(Schema Theory):人类使用预先存在的心理结构来组织新信息
- 脚本理论(Script Theory):日常场景有标准化的行为序列
- 概念依赖理论(Conceptual Dependency Theory):意义可以用primitive actions表示
class FrameSemanticsEngine:
"""
框架语义学引擎
实现基于框架的语义分析
"""
def __init__(self):
# 框架库
self.frames = self._load_frames()
# 触发词到框架的映射
self.lexical_unit_to_frame = {}
for frame_name, frame_data in self.frames.items():
for lu in frame_data.get('lexical_units', []):
self.lexical_unit_to_frame[lu] = frame_name
def _load_frames(self):
"""加载框架库(简化版本)"""
return {
'Commercial_buy': {
'name': 'Commercial_buy',
'definition': '通过支付货币获得商品的商业行为',
'frame_elements': {
'Buyer': {'definition': '购买商品的主体', 'core': True},
'Seller': {'definition': '出售商品的主体', 'core': True},
'Goods': {'definition': '被购买的物品', 'core': True},
'Money': {'definition': '支付的对价', 'core': True},
'Means': {'definition': '购买方式', 'core': False},
},
'lexical_units': ['买', '购买', '采购', '购入'],
'semantic_constraints': {
'Buyer ≠ Seller': True,
}
},
'Motion': {
'name': 'Motion',
'definition': '物体在空间中的移动',
'frame_elements': {
'Theme': {'definition': '移动的物体', 'core': True},
'Source': {'definition': '运动的起点', 'core': False},
'Goal': {'definition': '运动的目标点', 'core': False},
'Path': {'definition': '运动的路径', 'core': False},
'Direction': {'definition': '运动的方向', 'core': False},
},
'lexical_units': ['走', '跑', '飞', '移动', '行驶', '移动'],
},
'Communication': {
'name': 'Communication',
'definition': '发送者向接收者传递信息的活动',
'frame_elements': {
'Speaker': {'definition': '信息的发送者', 'core': True},
'Addressee': {'definition': '信息的接收者', 'core': True},
'Message': {'definition': '传递的信息', 'core': True},
'Channel': {'definition': '传递的媒介', 'core': False},
'Topic': {'definition': '信息的主题', 'core': False},
},
'lexical_units': ['说', '告诉', '问', '回答', '解释'],
},
'Perception': {
'name': 'Perception',
'definition': '感知者通过感官获取信息的过程',
'frame_elements': {
'Perceiver': {'definition': '进行感知的主体', 'core': True},
'Stimulus': {'definition': '被感知的对象', 'core': True},
'Sense': {'definition': '使用的感官', 'core': True},
'Medium': {'definition': '感知的媒介', 'core': False},
},
'lexical_units': ['看', '看见', '观察', '听', '听到'],
},
'Emotion': {
'name': 'Emotion',
'definition': '体验者的情感状态',
'frame_elements': {
'Experiencer': {'definition': '体验情感的主体', 'core': True},
'Emotion': {'definition': '体验的情感类型', 'core': True},
'Cause': {'definition': '情感的触发原因', 'core': False},
'Topic': {'definition': '情感指向的主题', 'core': False},
},
'lexical_units': ['喜欢', '讨厌', '害怕', '高兴', '悲伤', '愤怒'],
},
}
def identify_frame(self, word):
"""
识别触发词激活的框架
Args:
word: 输入词
Returns:
frame_name: 框架名称,如果未找到返回None
confidence: 置信度
"""
# 直接匹配
if word in self.lexical_unit_to_frame:
return self.lexical_unit_to_frame[word], 1.0
# 模糊匹配(简化版)
for lu, frame_name in self.lexical_unit_to_frame.items():
if word in lu or lu in word:
return frame_name, 0.8
return None, 0.0
def frame_parsing(self, sentence, frame_elements):
"""
框架解析
根据框架填充框架元素
Args:
sentence: 输入句子
frame_elements: 提取的框架元素
Returns:
frame_instance: 框架实例
"""
frame_instance = {
'frame_name': frame_elements.get('frame'),
'filled_elements': {},
'missing_elements': [],
'inferred_elements': {},
}
# 填充已识别的元素
for element_name, element_value in frame_elements.items():
if element_name != 'frame':
frame_instance['filled_elements'][element_name] = element_value
# 识别缺失的核心元素
frame_def = self.frames.get(frame_instance['frame_name'], {})
for element_name, element_def in frame_def.get('frame_elements', {}).items():
if element_def.get('core', False) and element_name not in frame_instance['filled_elements']:
frame_instance['missing_elements'].append({
'name': element_name,
'definition': element_def.get('definition'),
})
return frame_instance
def infer_missing_elements(self, frame_instance, sentence_context):
"""
推理缺失的框架元素
Args:
frame_instance: 框架实例
sentence_context: 句子上下文
Returns:
inferred_elements: 推理出的元素
"""
inferred = {}
# 基于句子上下文的简单推理规则
missing = frame_instance['missing_elements']
# 尝试从上下文推断
if 'Seller' in [m['name'] for m in missing]:
# 尝试找到出售方
seller_patterns = ['从', '向', '由']
for pattern in seller_patterns:
if pattern in sentence_context:
# 简化:假设pattern后的名词是卖方
inferred['Seller'] = {'inferred': True, 'source': pattern}
return inferred3.2 FrameNet项目与资源
FrameNet是框架语义学的计算实现,由加州大学伯克利分校开发,是自然语言处理领域最重要的语义资源之一。
FrameNet的规模
| 资源类型 | 数量 |
|---|---|
| 框架数量 | 1,200+ |
| 词元标注 | 13,000+ |
| 句子标注 | 200,000+ |
| 框架关系 | 5,000+ |
FrameNet的应用
FrameNet资源被广泛应用于:
- 语义角色标注(SRL)的训练数据
- 事件抽取(Event Extraction)
- 问答系统中的框架匹配
- 语义解析(Semantic Parsing)
class FrameNetAPI:
"""
FrameNet API接口
提供对FrameNet数据的访问
"""
def __init__(self, framenet_path=None):
"""
Args:
framenet_path: FrameNet数据路径(可选)
"""
self.framenet_path = framenet_path
self.frames = {}
self.lu_to_frame = {}
if framenet_path:
self._load_framenet()
def _load_framenet(self):
"""加载FrameNet数据"""
# 实际应用中应解析FrameNet的XML文件
# 这里加载简化版本
pass
def get_frame(self, frame_name):
"""获取框架定义"""
return self.frames.get(frame_name)
def get_frame_elements(self, frame_name):
"""获取框架的元素定义"""
frame = self.get_frame(frame_name)
if frame:
return frame.get('frame_elements', {})
return {}
def get_lexical_units(self, frame_name):
"""获取框架的词元"""
frame = self.get_frame(frame_name)
if frame:
return frame.get('lexical_units', [])
return []
def find_frames(self, target_word):
"""查找词触发的所有框架"""
# 模糊匹配
results = []
for lu, frame_name in self.lu_to_frame.items():
if target_word in lu or lu in target_word:
results.append({
'frame': frame_name,
'lexical_unit': lu,
'match_type': 'exact' if lu == target_word else 'partial'
})
return results
def search_frames(self, query):
"""搜索框架"""
results = []
for frame_name, frame_data in self.frames.items():
# 检查名称匹配
if query.lower() in frame_name.lower():
results.append({
'frame_name': frame_name,
'match_type': 'name',
'definition': frame_data.get('definition', '')
})
# 检查定义匹配
elif query.lower() in frame_data.get('definition', '').lower():
results.append({
'frame_name': frame_name,
'match_type': 'definition',
'definition': frame_data.get('definition', '')
})
return results四、语义网络与知识表示
4.1 语义网络的理论基础
语义网络(Semantic Network)是一种用图结构表示知识的形式化方法,起源于认知科学中对人类知识组织的研究。在语义网络中,知识被表示为由节点和边构成的网络结构。
历史渊源
语义网络的概念可以追溯到1960年代:
- 1960年:Quillian提出语义记忆网络
- 1970年代:Schank和Abelson发展出脚本理论
- 1980年代:Brachman和Schmolze发展出KL-ONE知识表示系统
- 1990年代以后:万维网催生了语义Web(Semantic Web)
语义网络的数学表示
从数学角度看,语义网络可以形式化为有向图结构:
其中:
- 是实体集合(节点)
- 是关系类型集合(边标签)
- 是三元组集合
每个三元组 表示从头实体 到尾实体 的关系 。
class SemanticNetwork:
"""
语义网络管理器
构建和查询语义网络
"""
def __init__(self):
# 实体存储: {entity_id: Entity}
self.entities = {}
# 三元组存储: [(head, relation, tail)]
self.triples = []
# 实体ID计数器
self.entity_counter = 0
# 索引结构
self.head_index = defaultdict(list) # head -> [(relation, tail)]
self.tail_index = defaultdict(list) # tail -> [(relation, head)]
self.relation_index = defaultdict(list) # relation -> [(head, tail)]
def add_entity(self, entity_name, entity_type=None, properties=None):
"""
添加实体
Args:
entity_name: 实体名称
entity_type: 实体类型(可选)
properties: 实体属性字典(可选)
Returns:
entity_id: 实体ID
"""
self.entity_counter += 1
entity_id = f"e{self.entity_counter}"
self.entities[entity_id] = {
'id': entity_id,
'name': entity_name,
'type': entity_type,
'properties': properties or {}
}
return entity_id
def add_triple(self, head_id, relation, tail_id):
"""
添加三元组
Args:
head_id: 头实体ID
relation: 关系名称
tail_id: 尾实体ID
"""
triple = (head_id, relation, tail_id)
self.triples.append(triple)
# 更新索引
self.head_index[head_id].append((relation, tail_id))
self.tail_index[tail_id].append((relation, head_id))
self.relation_index[relation].append((head_id, tail_id))
def query(self, head=None, relation=None, tail=None):
"""
查询三元组
至少需要指定一个参数
Args:
head: 头实体(可选)
relation: 关系(可选)
tail: 尾实体(可选)
Returns:
results: 匹配的三元组列表
"""
results = []
if head is not None:
for rel, t in self.head_index[head]:
if tail is None or t == tail:
if relation is None or rel == relation:
results.append((head, rel, t))
elif tail is not None:
for rel, h in self.tail_index[tail]:
if relation is None or rel == relation:
results.append((h, rel, tail))
elif relation is not None:
results = [(h, r, t) for h, t in self.relation_index[relation]]
return results
def get_neighbors(self, entity_id, relation=None):
"""
获取实体的邻居
Args:
entity_id: 实体ID
relation: 关系类型(可选,为None时返回所有邻居)
Returns:
neighbors: 邻居列表 [(neighbor_id, relation), ...]
"""
neighbors = []
# 出边邻居
for rel, t in self.head_index[entity_id]:
if relation is None or rel == relation:
neighbors.append((t, rel, 'out'))
# 入边邻居
for rel, h in self.tail_index[entity_id]:
if relation is None or rel == relation:
neighbors.append((h, rel, 'in'))
return neighbors
def find_path(self, start_id, end_id, max_length=3):
"""
寻找两个实体之间的路径
使用BFS算法
Args:
start_id: 起始实体ID
end_id: 目标实体ID
max_length: 最大路径长度
Returns:
paths: 路径列表,每条路径是实体ID列表
"""
from collections import deque
if start_id == end_id:
return [[start_id]]
queue = deque([(start_id, [start_id])])
visited = {start_id}
paths = []
while queue and len(paths) < 100: # 限制返回数量
current, path = queue.popleft()
if len(path) > max_length:
continue
for neighbor, _, direction in self.get_neighbors(current):
new_path = path + [neighbor]
if neighbor == end_id:
paths.append(new_path)
elif neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, new_path))
return paths
def compute_similarity(self, entity1_id, entity2_id):
"""
计算两个实体的语义相似度
基于它们在网络中的结构相似性
Returns:
similarity: 相似度分数 [0, 1]
"""
# 简化的基于邻居的相似度
neighbors1 = set(n[0] for n in self.get_neighbors(entity1_id))
neighbors2 = set(n[0] for n in self.get_neighbors(entity2_id))
if not neighbors1 or not neighbors2:
return 0.0
# Jaccard相似度
intersection = len(neighbors1 & neighbors2)
union = len(neighbors1 | neighbors2)
return intersection / union if union > 0 else 0.0
def to_networkx(self):
"""转换为NetworkX图"""
import networkx as nx
G = nx.DiGraph()
# 添加节点
for entity_id, entity_data in self.entities.items():
G.add_node(entity_id, **entity_data)
# 添加边
for head, relation, tail in self.triples:
G.add_edge(head, tail, relation=relation)
return G4.2 知识图谱的语义基础
现代知识图谱(如Wikidata、DBpedia、Google Knowledge Graph)建立在语义网络理论的基础上,但引入了更强的形式化约束和推理能力。
知识图谱的构成
| 组件 | 语义网络对应 | 说明 |
|---|---|---|
| 实体(Entity) | 节点 | 现实世界的对象 |
| 关系(Relation) | 边类型 | 实体间的语义联系 |
| 事实(Fact) | 三元组 | 头-关系-尾的元组 |
| 本体(Ontology) | 关系约束 | 类层次和属性约束 |
RDF与OWL
资源描述框架(RDF)是W3C推荐的语义Web数据模型:
<subject> <predicate> <object> .Web本体语言(OWL)提供了更强的推理能力:
- 等价性公理:
EquivalentClasses,EquivalentProperties - 不相交公理:
DisjointWith - 基数约束:
minCardinality,maxCardinality - 属性特征:
TransitiveProperty,SymmetricProperty
class KnowledgeGraphEmbedding:
"""
知识图谱嵌入模型
将实体和关系映射到连续向量空间
"""
def __init__(self, num_entities, num_relations, embedding_dim=100):
self.num_entities = num_entities
self.num_relations = num_relations
self.embedding_dim = embedding_dim
# 实体嵌入
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
# 关系嵌入
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# 初始化
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def score_triple(self, head_id, relation_id, tail_id):
"""
计算三元组的得分
Args:
head_id: 头实体ID
relation_id: 关系ID
tail_id: 尾实体ID
Returns:
score: 得分(越高越可能是真的)
"""
h = self.entity_embeddings(head_id)
r = self.relation_embeddings(relation_id)
t = self.entity_embeddings(tail_id)
# TransE模型:h + r ≈ t
score = -torch.norm(h + r - t, dim=-1)
return score
def forward(self, positive_triples, negative_triples=None):
"""
前向传播
Args:
positive_triples: 正三元组 [(h, r, t), ...]
negative_triples: 负三元组(可选)
Returns:
loss: 损失值
"""
# 正样本得分
pos_h = torch.tensor([t[0] for t in positive_triples])
pos_r = torch.tensor([t[1] for t in positive_triples])
pos_t = torch.tensor([t[2] for t in positive_triples])
pos_scores = self.score_triple(pos_h, pos_r, pos_t)
if negative_triples is None:
# 使用伯努利负采样
return -pos_scores.mean()
# 负样本得分
neg_h = torch.tensor([t[0] for t in negative_triples])
neg_r = torch.tensor([t[1] for t in negative_triples])
neg_t = torch.tensor([t[2] for t in negative_triples])
neg_scores = self.score_triple(neg_h, neg_r, neg_t)
# 边际排名损失
margin = 1.0
loss = torch.relu(margin + neg_scores - pos_scores)
return loss.mean()
def predict(self, head_id, relation_id, tail_ids):
"""
预测尾实体
给定头实体和关系,预测可能的尾实体
Args:
head_id: 头实体ID
relation_id: 关系ID
tail_ids: 候选尾实体ID列表
Returns:
scores: 每个候选实体的得分
"""
h = self.entity_embeddings(head_id)
r = self.relation_embeddings(relation_id)
h = h.unsqueeze(0).expand(len(tail_ids), -1)
r = r.unsqueeze(0).expand(len(tail_ids), -1)
t = self.entity_embeddings(torch.tensor(tail_ids))
scores = -torch.norm(h + r - t, dim=-1)
return scores
class TransE(KnowledgeGraphEmbedding):
"""
TransE知识图谱嵌入模型
核心思想:h + r ≈ t
适用于一对多关系
"""
def __init__(self, num_entities, num_relations, embedding_dim=100):
super().__init__(num_entities, num_relations, embedding_dim)
def score_triple(self, head_id, relation_id, tail_id):
h = self.entity_embeddings(head_id)
r = self.relation_embeddings(relation_id)
t = self.entity_embeddings(tail_id)
# L1或L2距离
return -torch.norm(h + r - t, p=1, dim=-1)
class TransR(KnowledgeGraphEmbedding):
"""
TransR知识图谱嵌入模型
核心思想:先投影到关系空间,再进行转换
h_proj + r ≈ t_proj
"""
def __init__(self, num_entities, num_relations, embedding_dim=100, relation_dim=100):
super().__init__(num_entities, num_relations, embedding_dim)
self.relation_dim = relation_dim
# 关系特定的投影矩阵
self.projection_matrices = nn.Embedding(num_relations, embedding_dim * relation_dim)
def score_triple(self, head_id, relation_id, tail_id):
h = self.entity_embeddings(head_id)
r = self.relation_embeddings(relation_id)
t = self.entity_embeddings(tail_id)
# 获取投影矩阵
proj_weights = self.projection_matrices(relation_id)
proj_matrix = proj_weights.view(self.embedding_dim, self.relation_dim)
# 投影到关系空间
h_proj = torch.matmul(h, proj_matrix)
t_proj = torch.matmul(t, proj_matrix)
return -torch.norm(h_proj + r - t_proj, dim=-1)五、词义消歧(Word Sense Disambiguation, WSD)
5.1 任务定义与挑战
词义消歧(Word Sense Disambiguation,WSD)是确定多义词在特定上下文中具体词义的任务。作为语义计算的基础问题之一,WSD被认为是NLP领域的”AI-complete”问题之一,其解决被认为是通向通用人工智能的重要里程碑。
任务的数学形式化
给定:
- 目标词 具有 个词义:
- 上下文
- 外部知识资源 (词典、语料库等)
WSD任务要求选择最合适的词义:
WSD的方法论分类
| 方法类型 | 核心思想 | 代表方法 | 优缺点 |
|---|---|---|---|
| 基于知识 | 利用词典/知识库消歧 | Lesk算法、基于图的方法 | 需要资源,效果依赖知识库 |
| 基于监督 | 训练分类器预测词义 | SVM、神经网络 | 效果好,但需要标注数据 |
| 半监督 | 利用未标注数据辅助 | Co-Training、EM算法 | 减少标注需求 |
| 无监督 | 无需标注自动发现词义 | 聚类、主题模型 | 可扩展,但解释性差 |
| 深度学习 | 端到端学习上下文表示 | BERT-WSD、BILSTM | 效果好,端到端 |
5.2 基于知识的WSD方法
Lesk算法
Lesk算法是最经典的基于词典的WSD方法,由Michael Lesk在1986年提出。其核心思想是:词及其上下文的定义应该与该词某个词义的定义有词汇重叠。
class LeskWSD:
"""
Lesk词义消歧算法
基于词定义的词汇重叠进行消歧
"""
def __init__(self, wordnet_path=None):
self.wordnet = None
if wordnet_path:
import nltk
from nltk.corpus import wordnet as wn
self.wordnet = wn
def disambiguate(self, word, sentence, pos=None):
"""
消歧
Args:
word: 目标词
sentence: 上下文句子
pos: 词性标签(可选)
Returns:
best_sense: 最佳词义
score: 得分
"""
if not self.wordnet:
return None, 0
# 获取词的synsets
synsets = self.wordnet.synsets(word, pos=pos)
if not synsets:
return None, 0
# 句子中的词集合
sentence_words = set(self._tokenize(sentence.lower()))
# 评分每个词义
best_sense = None
best_score = -1
for synset in synsets:
# 获取词义的定义和例句
sense_gloss = synset.definition()
sense_examples = synset.examples()
# 获取词义中所有词的synsets(用于扩展)
sense_words = self._get_sense_words(synset)
# 计算与上下文的词汇重叠
sense_set = set(self._tokenize(sense_gloss))
sense_set.update(sense_words)
for example in sense_examples:
sense_set.update(self._tokenize(example))
# 计算Jaccard相似度
overlap = len(sentence_words & sense_set)
if overlap > best_score:
best_score = overlap
best_sense = synset
return best_sense, best_score
def _tokenize(self, text):
"""简单的分词"""
import re
tokens = re.findall(r'\b\w+\b', text.lower())
return tokens
def _get_sense_words(self, synset):
"""获取词义相关的词汇"""
words = set()
# 同义词
for lemma in synset.lemma_names():
words.update(self._tokenize(lemma.replace('_', ' ')))
# 上位词的定义(传递闭包)
for hypernym in synset.hypernyms():
words.update(self._tokenize(hypernym.definition()))
# 领域标签
if synset.usage_domains():
for domain in synset.usage_domains():
words.add(domain.name())
return words
class ExtendedLesk(LeskWSD):
"""
扩展Lesk算法
考虑更多的上下文信息和知识库信息
"""
def __init__(self, wordnet_path=None):
super().__init__(wordnet_path)
def disambiguate_extended(self, word, sentence, pos=None, window_size=5):
"""
扩展Lesk消歧
考虑:
1. 句子上下文
2. 局部上下文窗口
3. 词义的完整定义链
4. 相关词义的信息
"""
if not self.wordnet:
return None, 0
synsets = self.wordnet.synsets(word, pos=pos)
if not synsets:
return None, 0
# 分词
tokens = self._tokenize(sentence.lower())
# 找到目标词的位置
word_indices = [i for i, t in enumerate(tokens) if word.lower() in t]
if not word_indices:
return None, 0
# 局部上下文
start = max(0, word_indices[0] - window_size)
end = min(len(tokens), word_indices[-1] + window_size + 1)
local_context = set(tokens[start:end])
# 全局上下文
global_context = set(tokens)
best_sense = None
best_score = -1
for synset in synsets:
score = self._compute_sense_score(
synset, local_context, global_context
)
if score > best_score:
best_score = score
best_sense = synset
return best_sense, best_score
def _compute_sense_score(self, synset, local_context, global_context):
"""计算词义得分"""
score = 0
# 1. 定义匹配(权重最高)
definition = self._tokenize(synset.definition())
score += 2 * len(set(definition) & local_context)
# 2. 例句匹配
for example in synset.examples():
example_tokens = self._tokenize(example)
score += 1.5 * len(set(example_tokens) & local_context)
# 3. 同义词匹配
for lemma in synset.lemma_names():
lemma_tokens = self._tokenize(lemma.replace('_', ' '))
score += 1.0 * len(set(lemma_tokens) & local_context)
# 4. 上位词定义(扩展语义场)
for hypernym in synset.hypernyms():
hyper_def = self._tokenize(hypernym.definition())
score += 0.5 * len(set(hyper_def) & global_context)
# 5. 下位词定义(具体化语义)
for hyponym in synset.hyponyms()[:3]: # 只取前3个
hypo_def = self._tokenize(hyponym.definition())
score += 0.3 * len(set(hypo_def) & global_context)
# 6. 主题领域匹配
for domain in synset.usage_domains():
domain_name = self._tokenize(domain.name())
score += 0.5 * len(set(domain_name) & global_context)
# 7. 整体例句中的词
for lemma in synset.lemmas():
for sent in lemma.synset().examples():
sent_tokens = self._tokenize(sent)
score += 0.1 * len(set(sent_tokens) & global_context)
return score5.3 基于深度学习的WSD方法
BERT用于词义消歧
预训练语言模型,特别是BERT,已经成为WSD的主流方法。
class BERTWSD:
"""
基于BERT的词义消歧
使用上下文感知的Transformer编码器
"""
def __init__(self, model_name='bert-base-uncased'):
from transformers import BertModel, BertTokenizer
from nltk.corpus import wordnet as wn
self.tokenizer = BertTokenizer.from_pretrained(model_name)
self.model = BertModel.from_pretrained(model_name)
self.model.eval()
self.wordnet = wn
def disambiguate(self, word, sentence):
"""
使用BERT进行词义消歧
Args:
word: 目标词
sentence: 上下文句子
Returns:
best_synset: 最佳词义synset
scores: 所有词义的得分分布
"""
# 获取目标词的所有词义
synsets = self.wordnet.synsets(word)
if not synsets:
return None, {}
# Tokenize
inputs = self.tokenizer(
sentence,
return_tensors='pt',
padding=True,
truncation=True
)
# 找到目标词对应的token位置
word_pieces = self.tokenizer.tokenize(word)
# 在token序列中搜索目标词片
tokens = self.tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
target_positions = []
for i in range(len(tokens) - len(word_pieces) + 1):
if tokens[i:i+len(word_pieces)] == word_pieces:
target_positions.extend(range(i, i+len(word_pieces)))
break
if not target_positions:
return None, {}
# BERT编码
with torch.no_grad():
outputs = self.model(**inputs)
hidden_states = outputs.last_hidden_state[0] # [seq_len, hidden]
# 获取目标词的上下文表示
target_hidden = hidden_states[target_positions].mean(dim=0)
# 计算每个词义的得分
scores = {}
for synset in synsets:
# 使用词义定义的表示作为查询
sense_repr = self._get_sense_representation(synset)
score = torch.cosine_similarity(
target_hidden.unsqueeze(0),
sense_repr.unsqueeze(0)
).item()
scores[synset.name()] = score
# 归一化
total = sum(scores.values())
if total > 0:
scores = {k: v/total for k, v in scores.items()}
# 选择最佳词义
best_synset_name = max(scores, key=scores.get)
best_synset = self._name_to_synset(best_synset_name)
return best_synset, scores
def _get_sense_representation(self, synset):
"""获取词义的表示"""
# 使用定义和示例的表示
text = synset.definition()
for example in synset.examples()[:2]:
text += ' ' + example
inputs = self.tokenizer(
text,
return_tensors='pt',
truncation=True,
max_length=32
)
with torch.no_grad():
outputs = self.model(**inputs)
repr = outputs.last_hidden_state[0, 0] # [CLS] token
return repr
def _name_to_synset(self, synset_name):
"""将synset名称转换为synset对象"""
return self.wordnet.synset(synset_name)
def contextualized_embedding(self, word, sentence):
"""
获取词在上下文中的嵌入表示
Returns:
embedding: 上下文相关的词嵌入
"""
inputs = self.tokenizer(
sentence,
return_tensors='pt',
padding=True,
truncation=True
)
tokens = self.tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
word_pieces = self.tokenizer.tokenize(word)
# 找到目标词位置
target_positions = []
for i in range(len(tokens) - len(word_pieces) + 1):
if tokens[i:i+len(word_pieces)] == word_pieces:
target_positions = range(i, i+len(word_pieces))
break
if not target_positions:
return None
with torch.no_grad():
outputs = self.model(**inputs)
hidden_states = outputs.last_hidden_state[0]
# 平均目标词位置的表示
embedding = hidden_states[target_positions].mean(dim=0)
return embedding.numpy()六、文本蕴含与自然语言推理
6.1 任务定义
文本蕴含(Textual Entailment,TE)任务判断文本T是否蕴含假设H。这是自然语言理解的核心问题之一。
形式化定义
设 为文本(Text), 为假设(Hypothesis),则:
这一定义基于人类的语言理解能力,强调了文本蕴含的认知基础。
蕴含类型
| 类型 | 描述 | 示例 |
|---|---|---|
| 正向蕴含 | T为真则H必为真 | T: 鸟会飞 → H: 动物会动 |
| 矛盾 | T为真则H必为假 | T: 猫是动物 → H: 猫不是动物 |
| 中立 | T为真但H可能为假 | T: 猫是动物 → H: 猫是宠物 |
| 蕴含 | T蕴含H | T: 小明在公园 → H: 小明在外面 |
6.2 自然语言推理模型
class NaturalLanguageInferenceModel(nn.Module):
"""
自然语言推理模型
基于BERT的文本蕴含分类器
"""
def __init__(self, encoder_name='bert-base-uncased'):
super().__init__()
from transformers import BertModel, BertPreTrainedModel
self.encoder = BertModel.from_pretrained(encoder_name)
hidden_size = self.encoder.config.hidden_size
# 三分类:entailment, neutral, contradiction
self.classifier = nn.Sequential(
nn.Linear(hidden_size * 3, hidden_size),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(hidden_size, 3)
)
self.label_map = {0: 'contradiction', 1: 'neutral', 2: 'entailment'}
def forward(self, premise_ids, hypothesis_ids,
premise_mask=None, hypothesis_mask=None):
"""
前向传播
Args:
premise_ids: 前提token IDs [batch, seq_len]
hypothesis_ids: 假设token IDs [batch, seq_len]
Returns:
logits: 分类logits [batch, 3]
"""
# 分别编码前提和假设
premise_encoded = self.encoder(
input_ids=premise_ids,
attention_mask=premise_mask
).last_hidden_state
hypothesis_encoded = self.encoder(
input_ids=hypothesis_ids,
attention_mask=hypothesis_mask
).last_hidden_state
# 使用[CLS]token表示
p_cls = premise_encoded[:, 0, :] # [batch, hidden]
h_cls = hypothesis_encoded[:, 0, :]
# 交互特征
# 差值特征:表示前提与假设的差异
diff = torch.abs(p_cls - h_cls)
# 元素积:表示前提与假设的相似性
product = p_cls * h_cls
# 组合特征
combined = torch.cat([p_cls, h_cls, diff, product], dim=-1)
# 简化为三特征版本
combined = torch.cat([p_cls, h_cls, torch.abs(p_cls - h_cls)], dim=-1)
# 分类
logits = self.classifier(combined)
return logits
def predict(self, premise_text, hypothesis_text):
"""
预测文本对的关系
Args:
premise_text: 前提文本
hypothesis_text: 假设文本
Returns:
prediction: 预测标签
confidence: 置信度
"""
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
premise_encoded = tokenizer(
premise_text,
return_tensors='pt',
padding=True,
truncation=True
)
hypothesis_encoded = tokenizer(
hypothesis_text,
return_tensors='pt',
padding=True,
truncation=True
)
with torch.no_grad():
logits = self.forward(
premise_encoded['input_ids'],
hypothesis_encoded['input_ids']
)
probs = torch.softmax(logits, dim=-1)
pred_id = torch.argmax(probs, dim=-1).item()
confidence = probs[0, pred_id].item()
return self.label_map[pred_id], confidence七、计算语义学的前沿与挑战
7.1 当前挑战
计算语义学面临的核心挑战包括:
- 组合歧义:相同的词汇组合可能产生不同语义,需要世界知识和语境推理
- 隐含知识:文本中未显式表达的背景知识需要从外部知识库获取
- 跨语言差异:不同语言的语义结构差异给跨语言语义计算带来困难
- 动态语义:网络语言、新造词汇、隐喻等动态语义现象难以处理
- 解释性:深度学习模型的语义表示缺乏可解释性
7.2 前沿研究方向
神经符号推理 将神经网络的学习能力与符号推理的可解释性结合,构建混合AI系统。
多模态语义 整合视觉、语言、音频等多种模态的语义信息,实现更全面的语义理解。
上下文适应 动态适应不同领域和用户的语义理解需求,实现个性化的语义计算。
可解释语义 为语义理解提供人类可读的推理过程和解释。
八、语义资源的计算接口
8.1 WordNet计算接口
WordNet是计算语义学最重要的资源之一。
class WordNetComputer:
"""
WordNet计算接口
提供WordNet的各种语义计算功能
"""
def __init__(self):
import nltk
try:
nltk.data.find('corpora/wordnet')
except LookupError:
nltk.download('wordnet')
from nltk.corpus import wordnet as wn
self.wn = wn
def get_all_senses(self, word, pos=None):
"""获取词的所有词义"""
synsets = self.wn.synsets(word, pos=pos)
return [
{
'name': syn.name(),
'definition': syn.definition(),
'examples': syn.examples(),
'pos': syn.pos(),
'lemmas': syn.lemma_names()
}
for syn in synsets
]
def compute_similarity(self, word1, word2, method='wup'):
"""
计算两个词的语义相似度
Args:
method: 'path', 'lch', 'wup', 'resnik', 'lin', 'jan'
"""
synsets1 = self.wn.synsets(word1)
synsets2 = self.wn.synsets(word2)
if not synsets1 or not synsets2:
return 0.0
max_sim = 0.0
for s1 in synsets1:
for s2 in synsets2:
if method == 'path':
sim = s1.path_similarity(s2)
elif method == 'wup':
sim = s1.wup_similarity(s2)
elif method == 'lch':
sim = s1.lch_similarity(s2)
else:
sim = s1.path_similarity(s2)
if sim and sim > max_sim:
max_sim = sim
return max_sim
def get_hypernym_chain(self, word):
"""获取上位词链"""
synsets = self.wn.synsets(word)
if not synsets:
return []
# 选择第一个synset
synset = synsets[0]
hypernyms = synset.hypernyms()
chain = [synset.name()]
while hypernyms:
parent = hypernyms[0]
chain.append(parent.name())
hypernyms = parent.hypernyms()
return chain
def get_closest_common_hypernym(self, word1, word2):
"""找到两个词的最近公共上位词"""
synsets1 = self.wn.synsets(word1)
synsets2 = self.wn.synsets(word2)
if not synsets1 or not synsets2:
return None
for s1 in synsets1:
for s2 in synsets2:
common = s1.lowest_common_hypernyms(s2)
if common:
return common[0].name()
return None
def find_metonymy_pairs(self, word):
"""
寻找可能的转喻关系
基于上位词层次结构
"""
synsets = self.wn.synsets(word)
metonymy_pairs = []
for synset in synsets:
# 获取直接上位词
for hyper in synset.hypernyms():
# 上位词可能与当前词形成转喻关系
# 例如"政府" -> "Washington" (代表关系)
for lemma in synset.lemma_names():
for hyper_lemma in hyper.lemma_names():
if lemma != hyper_lemma:
metonymy_pairs.append({
'specific': lemma,
'general': hyper_lemma,
'synset': synset.name()
})
return metonymy_pairs参考文献与推荐阅读
- Jurafsky, D., & Martin, J. H. (2023). Speech and Language Processing (3rd ed.). Stanford University.
- Fillmore, C. J. (1976). Frame semantics and the nature of language. Annals of the New York Academy of Sciences, 280(1), 20-32.
- Baker, C. F., Fillmore, C. J., & Lowe, J. B. (1998). The Berkeley FrameNet project. COLING, 86-90.
- Gildea, D., & Jurafsky, D. (2002). Automatic labeling of semantic roles. Computational Linguistics, 28(3), 245-288.
- Bowman, S. R., et al. (2015). A large annotated corpus for learning natural language inference. EMNLP.
- Levinson, S. C., & Preslosky, S. (2023). Semantic Analysis: A Practical Introduction. Oxford University Press.
- Dowty, D. R. (1991). Thematic proto-roles and argument selection. Language, 67(3), 547-619.
- Levin, B. (1993). English Verb Classes and Alternations: A Preliminary Investigation. University of Chicago Press.
- Montague, R. (1970). Universal grammar. Synthese, 22(1), 371-398.
- Resnik, P. (1999). Semantic similarity in a taxonomy: An information-based measure and its application to problems of ambiguity in natural language. JAIR, 11, 95-130.
关联文档