计算语义学深度指南
文档概述
本文档系统性地介绍计算语义学的核心概念、理论框架、算法实现及其与自然语言处理(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 | 描述论元在事件中承担的语义功能 |
| 词向量 | Word Embedding | 用实数向量表示词语语义 |
| 分布式假设 | Distributional Hypothesis | 词语由其上下文定义 |
一、计算语义学入门:让计算机理解”意义”
1.1 什么是计算语义学?
你有没有想过,当你对手机说”hey siri,今天天气怎么样”的时候,它是怎么理解你的意思的?手机当然不懂中文、也不懂英语,它只是一堆0和1的组合。但为什么它能做出正确的反应呢?
这就是计算语义学要解决的问题——让计算机能够理解和处理语言的意义。
简单来说,计算语义学就是想方设法把人类语言这种模糊的、充满歧义的、依赖上下文的东西,变成计算机能处理的形式。在理想情况下,我们希望给计算机一段文字,它就能”理解”这段话在说什么,进而能回答问题、做推理、甚至生成新的内容。
这个目标说起来简单,做起来却难得离谱。因为语言的”意义”本身就是个哲学难题,几千年来哲学家们都没争论清楚”意义到底是什么”。但计算语义学的务实之处在于,它不管那些形而上的争论,而是想办法让方法work——只要计算机能正确处理语义任务就行,至于它是不是真的”理解”了,那是谁也说不清的问题。
1.2 计算语义学的核心挑战
语言的意义为什么这么难处理?让我们看几个例子:
歧义问题:中文里”打”这个字,可以表示”打架”、“打电话”、“打篮球”、“打酱油”、“打工人”……一个”打”字在不同语境下意思完全不同。计算机要理解”打”的意思,必须看它周围的词。
隐含信息:你说”今天忘带钥匙了”,这句话字面意思是”我今天没带钥匙”,但实际上你是在表达”我现在进不了门,能不能帮我开门”或者”我需要找人借钥匙”。这种言外之意需要世界知识和常识推理才能理解。
上下文依赖:同样是”苹果”这个词,在水果店里它指的是水果,在科技新闻里它指的是苹果公司,在数学课上它可能只是个普通的名词。计算机必须根据上下文来判断。
组合爆炸:就算你理解了每个词的意思,把它们组合成句子意思也没那么简单。“咬死了猎人的老虎”到底是”咬死猎人的那只老虎”还是”把老虎咬死了”?这种歧义需要句法和语义的协同分析。
1.3 从形式语义到计算语义的过渡
早期的语义研究走的路线是形式化——用数学工具来严格定义语义。最经典的方法是用一阶逻辑来表示句子的意思。
举个例子,“小明喜欢小红”这句话,用一阶逻辑可以写成:
like(XiaoMing, XiaoHong)
而”所有猫都爱吃鱼”可以写成:
∀x (Cat(x) → Like(x, Fish))
这种表示方法的好处是清晰、无歧义、可以机械地做推理。比如说,如果知道”所有猫都爱吃鱼”和”汤姆是一只猫”,就可以推出”汤姆爱吃鱼”。
但形式语义学遇到了一个瓶颈——它太”干净”了。真实世界的语言充满了不规则、不完整、依赖语境的信息。用一阶逻辑来编码这些语言现象,就像用手术刀来切西瓜——技术上可行,但效率太低。
更重要的是,形式语义学需要人工设计词汇的语义表示和组合规则,规模一大就扛不住。人类有几十万个词汇,每个词可能有几十种用法,穷尽所有组合规则几乎不可能。
计算语义学后来的发展,就是从手工设计走向机器学习,从规则走向统计,从离散表示走向连续向量。这就是我们接下来要讲的词向量革命。
二、从命题逻辑到分布式表示:语义表示的演变
2.1 传统语义表示方法
在说词向量之前,我们先来看看前人是怎么表示词义的。
One-Hot编码
最早的向量化方法叫One-Hot(独热编码)。假设我们有10000个词,那么每个词就是一个10000维的向量,其中第i个词在第i维是1,其他维度都是0。
# One-Hot编码示例
# 假设词汇表只有5个词
vocab = ["苹果", "香蕉", "猫", "狗", "红色"]
# 苹果 = [1, 0, 0, 0, 0]
# 香蕉 = [0, 1, 0, 0, 0]
# 猫 = [0, 0, 1, 0, 0]这种方法简单直观,但有个致命问题——所有词之间的距离都一样。用One-Hot表示的话,“苹果”和”香蕉”的距离等于”苹果”和”猫”的距离。但常识告诉我们,苹果和香蕉都是水果,距离应该更近;而苹果和猫,一个是植物一个是动物,距离应该更远。
词袋模型(Bag of Words)
词袋模型是处理文本的另一个经典方法。它把一个句子表示成词汇表中各词出现次数的向量,完全忽略词序。
from collections import Counter
def bag_of_words(sentence, vocab):
"""词袋模型"""
words = sentence.split()
word_counts = Counter(words)
return [word_counts.get(word, 0) for word in vocab]
# 示例
sentence = "小明 喜欢 小红 小明 喜欢 苹果"
vocab = ["小明", "喜欢", "小红", "苹果", "香蕉"]
print(bag_of_words(sentence, vocab))
# 输出: [2, 2, 1, 1, 0]词袋模型比One-Hot好一些,因为它能捕捉词频信息。但它的问题更明显——它完全忽略了词序。“小明打了小红”和”小红打了小明”用词袋模型表示完全一样,但意思完全相反。
更重要的是,词袋模型无法表达词与词之间的语义关系。“猫”和”狗”在词袋模型里只是两个独立的维度,没有任何关联。
2.2 分布式语义与词向量革命
20世纪80年代,语言学家John Firth提出了一个革命性的观点:
“You shall know a word by the company it keeps.”(观其伴,知其意)
这就是著名的分布式假设(Distributional Hypothesis):词语的意义由它的上下文决定,语义相似的词出现在相似的上下文中。
举个例子,为什么我们知道”猫”和”狗”语义相近?因为它们都出现在这样的句子里:
- “我养了一只猫/狗”
- “猫/狗是人类的朋友”
- “猫/狗会抓老鼠/看家”
而”猫”和”椅子”虽然都是名词,但它们的上下文完全不同,所以我们知道它们的意思也不同。
**词向量(Word Embedding)**就是基于这个假设。词向量把每个词映射到一个固定长度的实数向量(比如300维),相似的词在向量空间里距离更近。
import numpy as np
# 假设我们用某种方法得到了词向量
word_vectors = {
"苹果": np.array([0.2, 0.8, 0.1, ...]), # 300维
"香蕉": np.array([0.3, 0.7, 0.15, ...]),
"猫": np.array([0.8, 0.2, 0.6, ...]),
"狗": np.array([0.75, 0.25, 0.55, ...]),
"汽车": np.array([0.1, 0.1, 0.05, ...]),
}
# 计算余弦相似度
def cosine_similarity(v1, v2):
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
# 苹果和香蕉相似(都是水果)
print(cosine_similarity(word_vectors["苹果"], word_vectors["香蕉"]))
# 输出: 0.92
# 猫和狗相似(都是动物)
print(cosine_similarity(word_vectors["猫"], word_vectors["狗"]))
# 输出: 0.95
# 苹果和汽车不相似
print(cosine_similarity(word_vectors["苹果"], word_vectors["汽车"]))
# 输出: 0.15这个例子说明,词向量能够自动学习到词与词之间的语义关系,而不需要人工标注。
三、词向量入门:从One-Hot到Word2Vec
3.1 词向量的直观理解
词向量到底长什么样?让我们用一个简化版本来说明。
假设我们用两个维度来表示词(实际上通常用100到300维),这两个维度可以有某种语义解释,比如”动物程度”和”水果程度”:
苹果: [0.2, 0.9] # 低动物度,高水果度
香蕉: [0.1, 0.85] # 低动物度,高水果度
猫: [0.9, 0.1] # 高动物度,低水果度
狗: [0.85, 0.15] # 高动物度,低水果度
石头: [0.05, 0.05] # 低动物度,低水果度
把这些点画在坐标系里,你会发现:水果类词语聚在一起,动物类词语聚在一起。这说明词向量确实捕捉到了语义信息。
3.2 Word2Vec:让词向量真正work的方法
2013年,Tomas Mikolov等人发表了Word2Vec论文,彻底改变了NLP领域。他们提出了两种训练词向量的方法:CBOW和Skip-gram。
CBOW(Continuous Bag-of-Words)
CBOW的核心思想是:根据上下文预测中心词。
举例来说,对于句子”小明 今天 心情 很 好”,如果”心情”是中心词,那么”小明”、“今天”、“很”、“好”就是上下文。CBOW模型就是用这些上下文词来预测中心词”心情”。
# CBOW示意图
# 上下文: [小明, 今天, 很, 好]
# ↓
# CBOW模型
# ↓
# 预测: 心情Skip-gram
Skip-gram反过来:用中心词预测上下文。
还是刚才的例子,用”心情”来预测”小明”、“今天”、“很”、“好”。
# Skip-gram示意图
# 中心词: 心情
# ↓
# Skip-gram模型
# ↓
# 预测: [小明, 今天, 很, 好]这两种方法各有优劣:
- CBOW训练速度更快,对高频词效果较好
- Skip-gram对低频词更友好,语料少的时候表现更好
3.3 Word2Vec原理:直观理解
为什么通过这种”预测”任务训练出来的向量会包含语义信息呢?
关键在于神经网络的归纳偏置。当模型学习”根据上下文预测中心词”时,它必须把语义相似的词映射到向量空间的相近位置。因为只有这样,看到相似的上下文,模型才能正确预测出相似的中心词。
举个例子:
句子1:“我 养了一只 猫,很 可爱” 句子2:“我 养了一只 狗,很 可爱”
在这两个句子里,“猫”和”狗”作为中心词时,上下文都是”一只”、“很”等词。因此,模型必须把”猫”和”狗”学成相似的向量,才能在类似上下文中做出正确的预测。
这就是Word2Vec能够自动学习语义关系的本质——相似的词出现在相似的上下文中,而模型必须把相似的词映射到相似的位置。
3.4 词向量的神奇特性
训练好的词向量有很多有趣的性质:
1. 语义类比
最著名例子是:
国王 - 男人 + 女人 ≈ 女王
用向量运算来表示:
vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")
这说明词向量捕捉到了”性别”这个语义维度。
2. 语义聚类
相似的词在向量空间中自然聚类。你可以用k-means或者其他聚类算法,发现词向量自动把水果聚在一起、动物聚在一起、动词聚在一起。
3. 语义距离
可以直接用余弦相似度来衡量词的语义相似度:
def word_similarity(word1, word2, model):
"""计算两个词的余弦相似度"""
vec1 = model[word1]
vec2 = model[word2]
return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
# 测试
print(word_similarity("猫", "狗", word2vec_model)) # 高相似度
print(word_similarity("猫", "汽车", word2vec_model)) # 低相似度四、GloVe:基于全局词共现的词向量
4.1 GloVe的核心思想
GloVe(Global Vectors for Word Representation)是另一种经典的词向量方法,由Pennington等人于2014年提出。
GloVe的核心思想是:结合全局统计信息和局部上下文信息。
回顾一下之前的分布式假设:语义相似的词出现在相似的上下文中。这里”上下文”可以是:
- 局部上下文:目标词周围固定窗口内的词
- 全局共现统计:整个语料库中词与词共同出现的频率
Word2Vec主要利用局部上下文,而GloVe则利用全局共现统计。
4.2 GloVe的工作原理
GloVe首先统计语料库中词与词的共现矩阵。假设词汇表大小为V,那么共现矩阵X是一个V×V的矩阵,X_ij表示词j在词i的上下文中出现的次数。
然后,GloVe定义了一个目标函数:
J = Σ f(X_ij) (w_i · w_j + b_i + b_j - log(X_ij))^2
这个目标函数的意思是:让两个词的向量点积尽可能接近它们共现次数的对数。
为什么用对数?因为共现次数的分布通常遵循幂律分布(Zipf定律),取对数可以把它变成近似均匀的分布。
import numpy as np
class GloVe:
"""GloVe词向量模型(简化版)"""
def __init__(self, vocab_size, embedding_dim):
self.vocab_size = vocab_size
self.embedding_dim = embedding_dim
# 初始化词向量和偏置
self.W = np.random.randn(vocab_size, embedding_dim) * 0.1
self.W_tilde = np.random.randn(vocab_size, embedding_dim) * 0.1
self.b = np.zeros(vocab_size)
self.b_tilde = np.zeros(vocab_size)
def build_cooccurrence_matrix(self, corpus, window_size=5):
"""从语料库构建共现矩阵"""
vocab = self._build_vocab(corpus)
self.vocab = vocab
vocab_size = len(vocab)
# 初始化共现矩阵
cooccurrence = np.zeros((vocab_size, vocab_size))
for sentence in corpus:
words = sentence.split()
for i, center_word in enumerate(words):
if center_word not in vocab:
continue
# 统计上下文词
start = max(0, i - window_size)
end = min(len(words), i + window_size + 1)
for j in range(start, end):
if i != j and words[j] in vocab:
context_word = words[j]
# 距离衰减
distance = abs(i - j)
weight = 1.0 / distance
cooccurrence[vocab[center_word]][vocab[context_word]] += weight
return cooccurrence
def _build_vocab(self, corpus):
"""构建词汇表"""
word_counts = {}
for sentence in corpus:
for word in sentence.split():
word_counts[word] = word_counts.get(word, 0) + 1
return {word: i for i, word in enumerate(word_counts)}
def weighting_function(self, x):
"""GloVe的加权函数"""
# x < x_max时,用 (x/x_max)^0.75
# x >= x_max时,用 1
x_max = 100
alpha = 0.75
if x < x_max:
return (x / x_max) ** alpha
return 1.04.3 Word2Vec vs GloVe
| 特性 | Word2Vec | GloVe |
|---|---|---|
| 训练方式 | 预测任务(神经网络) | 矩阵分解 |
| 利用信息 | 局部上下文 | 全局共现统计 |
| 训练速度 | 快 | 较慢 |
| 语料规模 | 相对较小即可 | 越大越好 |
| 语义类比 | 表现优秀 | 表现优秀 |
| 词向量质量 | 取决于上下文窗口 | 更稳定 |
在实际应用中,两者的词向量质量差别不大,选择哪个更多取决于具体场景和偏好。
五、句子Embedding:从词向量到句子向量
5.1 为什么需要句子向量?
词向量只能表示单个词的意思,但实际应用中我们处理的最小单位往往是句子。用户搜索 query 是句子,文档也是句子,我们需要一种方法来表示整个句子的语义。
最简单的方法是对句子中所有词的词向量做平均:
import numpy as np
def sentence_to_vec(sentence, word_vectors, dim=300):
"""将句子转换为向量(简单平均)"""
words = sentence.split()
vecs = []
for word in words:
if word in word_vectors:
vecs.append(word_vectors[word])
if not vecs:
return np.zeros(dim)
# 平均
return np.mean(vecs, axis=0)
# 测试
sentence = "今天天气很好"
vec = sentence_to_vec(sentence, word_vectors)
print(vec.shape) # (300,)这种方法简单高效,但有个明显问题——忽略了词序和语法结构。
“今天天气很好”和”很好天气今天”用平均词向量方法得到的表示几乎一样,但前者是通顺的句子,后者是病句。
5.2 更复杂的句子表示方法
为了解决词序问题,研究者们提出了各种方法:
1. 加权平均(TF-IDF加权)
给每个词向量加权,常用的是TF-IDF权重:
from collections import Counter
import math
def compute_tf_idf_weighted_vec(sentence, word_vectors, idf_dict, dim=300):
"""TF-IDF加权平均"""
words = sentence.split()
word_counts = Counter(words)
tf_weights = {w: count / len(words) for w, count in word_counts.items()}
vec = np.zeros(dim)
weight_sum = 0
for word, tf in tf_weights.items():
if word in word_vectors and word in idf_dict:
idf = idf_dict[word]
tfidf = tf * idf
vec += word_vectors[word] * tfidf
weight_sum += tfidf
if weight_sum > 0:
vec /= weight_sum
return vec2. DAN(Deep Averaging Network)
先对词向量做平均,然后通过几层神经网络:
class DAN(nn.Module):
"""深度平均网络"""
def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
self.fc1 = nn.Linear(embedding_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, num_classes)
self.dropout = nn.Dropout(0.3)
def forward(self, x):
# x: [batch, seq_len]
embedded = self.embedding(x) # [batch, seq_len, embed_dim]
pooled = torch.mean(embedded, dim=1) # [batch, embed_dim]
hidden = F.relu(self.fc1(pooled))
hidden = self.dropout(hidden)
hidden = F.relu(self.fc2(hidden))
hidden = self.dropout(hidden)
output = self.fc3(hidden)
return output3. Transformer句子编码器
现代方法直接用预训练的Transformer模型来获取句子表示,比如BERT的[CLS]token对应的输出:
from transformers import BertModel, BertTokenizer
class BertSentenceEncoder:
"""BERT句子编码器"""
def __init__(self, model_name='bert-base-chinese'):
self.tokenizer = BertTokenizer.from_pretrained(model_name)
self.model = BertModel.from_pretrained(model_name)
self.model.eval()
def encode(self, sentence):
"""获取句子向量"""
inputs = self.tokenizer(
sentence,
return_tensors='pt',
padding=True,
truncation=True,
max_length=128
)
with torch.no_grad():
outputs = self.model(**inputs)
# 使用[CLS]token的输出作为句子表示
cls_embedding = outputs.last_hidden_state[:, 0, :]
return cls_embedding.numpy()[0]
def encode_batch(self, sentences):
"""批量编码"""
inputs = self.tokenizer(
sentences,
return_tensors='pt',
padding=True,
truncation=True,
max_length=128
)
with torch.no_grad():
outputs = self.model(**inputs)
cls_embeddings = outputs.last_hidden_state[:, 0, :]
return cls_embeddings.numpy()六、上下文词向量ELMo:为什么同一个词在不同语境下意思不同?
6.1 上下文相关的词向量
前面讲的Word2Vec和GloVe有一个根本问题:每个词只有一个固定的向量。
但我们知道,同一个词在不同语境下意思完全不同:
- “银行”(金融机构):我去银行存钱
- “银行”(河岸):黄河的银行很坚固
用Word2Vec训练出来的”银行”向量,是这两种意思的平均——一个模糊的、两边都不像的向量。
**ELMo(Embeddings from Language Models)**解决了这个问题。ELMo的核心思想是:根据上下文动态生成词向量。
6.2 ELMo的原理
ELMo使用双向LSTM来编码上下文。对于一句话”我去银行存钱”,ELMo会:
- 先用字符级别的CNN获取每个词的初始表示
- 然后用双向LSTM编码整句话
- 对于”银行”这个词,取它对应的LSTM隐藏状态
- 这个隐藏状态包含了”银行”在当前上下文中的具体含义
class ELMo(nn.Module):
"""ELMo模型(简化版)"""
def __init__(self, vocab_size, embedding_dim, hidden_dim, num_layers):
super().__init__()
self.num_layers = num_layers
# 字符级别的嵌入
self.char_embedding = nn.Embedding(vocab_size, embedding_dim)
self.char_cnn = nn.Conv1d(embedding_dim, embedding_dim, kernel_size=3)
# 双向LSTM
self.lstms = nn.ModuleList([
nn.LSTM(embedding_dim, hidden_dim, bidirectional=True)
for _ in range(num_layers)
])
# 用于组合不同层的输出
self.combine_weights = nn.Parameter(torch.ones(num_layers + 1))
def forward(self, x):
# x: [batch, seq_len] 字符级别的token IDs
# 字符级别的CNN
char_emb = self.char_embedding(x) # [batch, seq_len, char_dim]
char_emb = char_emb.transpose(1, 2) # [batch, char_dim, seq_len]
conv_out = torch.relu(self.char_cnn(char_emb)) # [batch, char_dim, seq_len]
conv_out = conv_out.transpose(1, 2) # [batch, seq_len, char_dim]
emb = torch.max_pool1d(conv_out.transpose(1, 2), conv_out.size(1)).squeeze(1) # [batch, seq_len, char_dim]
# 逐层通过LSTM
layer_outputs = [emb]
current = emb
for lstm in self.lstms:
lstm_out, _ = lstm(current)
current = lstm_out
layer_outputs.append(lstm_out)
# 加权组合各层输出
weights = F.softmax(self.combine_weights, dim=0)
final_output = sum(w * out for w, out in zip(weights, layer_outputs))
return final_output, layer_outputs6.3 ELMo的意义
ELMo的意义在于:首次实现了真正的上下文相关词向量。
它让模型能够区分”bank”在不同语境下的意思,解决了传统词向量的多义词问题。
更重要的是,ELMo采用的预训练+微调范式成为后来BERT等模型的基础,开启了NLP领域的预训练时代。
七、分布式假设:为什么”你身边的词决定了你的含义”?
7.1 分布式假设的本质
分布式假设(Distributional Hypothesis)是现代计算语义学的理论基础,用一句话概括就是:
语义相同的词,出现在相同的上下文中
这里的关键是”上下文”的理解。上下文可以是:
- 线性上下文:词周围固定窗口内的词
- 句法上下文:词在句法结构中的位置
- 文档上下文:词在文档中的分布
分布式假设的核心洞察是:意义不是孤立的,而是关系性的。一个词的意思取决于它与其他词的关系网络。
7.2 从分布式假设到词向量
分布式假设为什么能work?因为它把”意义”这个抽象概念转化成了可观测的”上下文分布”。
如果两个词的语义相同或相近,按照分布式假设,它们的上下文分布也应该相同或相近。因此,只要统计足够多的上下文,就能可靠地估计词的意义。
词向量就是这种上下文分布的紧凑表示:
- 高维稀疏的上下文向量 → 低维稠密的词向量
- 维度从几万维降到几百维,但保留了关键的语义信息
from collections import defaultdict
import numpy as np
class DistributionalModel:
"""基于分布式假设的简单语义模型"""
def __init__(self, vocab_size, context_dim):
self.vocab_size = vocab_size
self.context_dim = context_dim
# 上下文矩阵
self.context_matrix = np.zeros((vocab_size, context_dim))
def build_context_vectors(self, corpus, window_size=5):
"""从语料库构建上下文向量"""
vocab_index = self._build_vocab(corpus)
self.vocab_index = vocab_index
self.index_vocab = {v: k for k, v in vocab_index.items()}
context_counts = defaultdict(lambda: defaultdict(float))
for sentence in corpus:
words = sentence.split()
for i, word in enumerate(words):
if word not in vocab_index:
continue
word_idx = vocab_index[word]
# 统计上下文
start = max(0, i - window_size)
end = min(len(words), i + window_size + 1)
for j in range(start, end):
if i != j and words[j] in vocab_index:
context_idx = vocab_index[words[j]]
# 距离衰减
distance = abs(i - j)
context_counts[word_idx][context_idx] += 1.0 / distance
# 构建上下文向量
for word_idx, contexts in context_counts.items():
for ctx_idx, count in contexts.items():
self.context_matrix[word_idx][ctx_idx] = count
# 归一化
norms = np.linalg.norm(self.context_matrix, axis=1, keepdims=True)
norms[norms == 0] = 1
self.context_matrix = self.context_matrix / norms
def most_similar(self, word, top_k=5):
"""找出与给定词最相似的词"""
if word not in self.vocab_index:
return []
word_idx = self.vocab_index[word]
word_vec = self.context_matrix[word_idx]
# 计算余弦相似度
similarities = np.dot(self.context_matrix, word_vec)
# 排序
indices = np.argsort(similarities)[::-1][:top_k + 1]
results = []
for idx in indices:
if idx != word_idx:
results.append((self.index_vocab[idx], similarities[idx]))
return results[:top_k]7.3 分布式假设的局限
分布式假设很强大,但也有局限:
-
只关注共现统计,忽略因果关系:两个词可能只是偶然共现,不代表有因果关系
-
难以处理反事实:如果”所有乌鸦都是黑色的”这个命题从未在语料中出现过,分布式方法无法理解这个知识
-
组合性问题:分布式假设擅长处理单个词,但词组、句子的语义如何组合仍是难题
-
静态 vs 动态:传统分布式模型是静态的,无法处理动态变化的语义
这些局限推动了后续研究的发展——从Word2Vec到BERT,从静态表示到上下文相关表示,从纯统计到融合知识图谱。
八、知识图谱与语义网络:结构化知识表示
8.1 为什么需要结构化知识?
词向量能告诉我们”猫”和”狗”很像,但它们为什么像?它们是什么关系?这些信息词向量给不出来。
知识图谱就是来解决这个问题的。知识图谱用结构化的方式表示知识——实体-关系-实体的三元组。
(猫, IS_A, 动物)
(猫, EATS, 鱼)
(猫, HAS_PROPERTY, 有尾巴)
(动物, IS_A, 生物)
这种表示方法有明确的语义结构,可以进行逻辑推理。
8.2 知识图谱的基本概念
实体(Entity):现实世界中的对象,如”小明”、“北京”、“苹果公司”
关系(Relation):实体之间的联系,如”朋友”、“位于”、“生产”
事实(Fact):实体通过关系连接,如”(小明, 朋友, 小红)”
本体(Ontology):定义概念层次和关系约束,如”猫”是”动物”的下位词
class KnowledgeGraph:
"""简单的知识图谱"""
def __init__(self):
# 实体: {实体名: {类型, 属性}}
self.entities = {}
# 三元组: [(头实体, 关系, 尾实体)]
self.triples = []
# 索引
self.head_index = defaultdict(list)
self.tail_index = defaultdict(list)
self.relation_index = defaultdict(list)
def add_entity(self, name, entity_type, properties=None):
"""添加实体"""
self.entities[name] = {
'type': entity_type,
'properties': properties or {}
}
def add_triple(self, head, relation, tail):
"""添加三元组"""
triple = (head, relation, tail)
self.triples.append(triple)
# 更新索引
self.head_index[head].append((relation, tail))
self.tail_index[tail].append((relation, head))
self.relation_index[relation].append((head, tail))
def query(self, head=None, relation=None, tail=None):
"""查询三元组"""
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 find_path(self, start, end, max_length=3):
"""查找两点之间的路径"""
from collections import deque
queue = deque([(start, [start])])
visited = {start}
paths = []
while queue and len(paths) < 100:
current, path = queue.popleft()
if len(path) > max_length:
continue
for neighbor, rel in self.head_index[current]:
if neighbor == end:
paths.append(path + [neighbor])
elif neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return paths
def reasoning(self, head, relation, tail):
"""简单的推理"""
# 检查是否存在这条边
existing = self.query(head, relation, tail)
if existing:
return True, "直接关系"
# 检查传递关系
intermediate = self.query(head, relation)
if intermediate:
for h, r, t in intermediate:
if self.reasoning(t, relation, tail)[0]:
return True, f"传递: {head} --{relation}--> {t} --{relation}--> {tail}"
return False, "无法推理"8.3 知识图谱嵌入
知识图谱嵌入(Knowledge Graph Embedding)是把知识图谱中的实体和关系映射到向量空间的技术。这样就可以用向量运算来做推理了。
TransE模型是最经典的嵌入方法,核心思想是:
头实体 + 关系 ≈ 尾实体
h + r ≈ t
如果”(小明, 朋友, 小红)“是真的,那么vec(“小明”) + vec(“朋友”) 应该接近 vec(“小红”)。
class TransE(nn.Module):
"""TransE知识图谱嵌入模型"""
def __init__(self, num_entities, num_relations, embedding_dim=100):
super().__init__()
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):
"""计算三元组的得分"""
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):
"""前向传播"""
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])
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])
pos_scores = self.score_triple(pos_h, pos_r, pos_t)
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()九、语义角色标注:谁对谁做了什么?
9.1 语义角色标注的任务
语义角色标注(Semantic Role Labeling, SRL)的目标是回答”谁对谁做了什么”这个问题。
对于句子”小明 昨天 在公园 喂了 小鸟”:
- 谓词:喂了
- 施事(Agent):小明
- 时间:昨天
- 地点:在公园
- 主题(Theme):小鸟
语义角色标注就是要识别出这些成分。
9.2 常见的语义角色
| 角色 | 定义 | 示例 |
|---|---|---|
| Agent(施事) | 动作的执行者 | 小明打小明了 |
| Patient(受事) | 动作的承受者 | 小明打小红了 |
| Theme(主题) | 被描述的实体 | 小红受伤了小红 |
| Experiencer(体验者) | 感知或情感的主体 | 小明小明很伤心 |
| Goal(目标) | 动作指向的对象 | 小明给小红一本书 |
| Instrument(工具) | 动作使用的工具 | 小明用刀切西瓜 |
| Location(地点) | 动作发生的地点 | 小明在学校学习 |
| Time(时间) | 动作发生的时间 | 小明昨天去了公园 |
9.3 基于依存分析的SRL
一种简单的SRL方法是基于依存句法分析:
class SimpleSRL:
"""简单的语义角色标注器"""
def __init__(self):
# 依存关系到语义角色的映射
self.dep_to_role = {
'nsubj': 'Agent', # 名词性主语
'nsubjpass': 'Patient', # 被动主语
'dobj': 'Theme', # 直接宾语
'iobj': 'Goal', # 间接宾语
'pobj': 'Location', # 介词宾语
'agent': 'Agent', # by-介词
'nmod': 'Location', # 名词修饰语
'advmod': 'Manner', # 状语
'tmod': 'Time', # 时间修饰语
}
def label(self, sentence, predicate_idx, dependency_parse):
"""
标注语义角色
Args:
sentence: 句子分词列表
predicate_idx: 谓词位置
dependency_parse: 依存分析结果 [(head_idx, dep_type, dep_idx), ...]
Returns:
roles: 语义角色列表
"""
roles = []
for head_idx, dep_type, dep_idx in dependency_parse:
if dep_idx == predicate_idx and head_idx != predicate_idx:
# 这个依存关系指向谓词
role = self._get_role(dep_type)
if role:
roles.append({
'role': role,
'span': (dep_idx, dep_idx + 1),
'text': sentence[dep_idx]
})
return roles
def _get_role(self, dep_type):
"""获取语义角色"""
return self.dep_to_role.get(dep_type, 'Other')现代的SRL系统通常使用神经网络模型,比如基于BERT的Biaffine Attention模型,能达到很高的准确率。
十、语义解析实战:从自然语言到逻辑形式
10.1 什么是语义解析?
语义解析(Semantic Parsing)是把自然语言转换成机器可执行的形式语言的任务。
比如:
- 自然语言:我想要在二星级餐厅预订明晚七点的位置
- 逻辑形式:
Reserve(Tomorrow, 7pm, Rating=2)
语义解析是问答系统、对话系统、数据库查询等应用的核心技术。
10.2 语义解析的流程
典型的语义解析流程包括:
- 词法/句法分析:分词、词性标注、依存分析
- 语义标注:识别实体、关系
- 形式化映射:将句法结构映射到逻辑形式
- 逻辑推理:执行逻辑形式得到答案
class SimpleSemanticParser:
"""简单的语义解析器"""
def __init__(self):
# 词汇-逻辑映射
self.lexicon = {
'预订': 'RESERVE',
'餐厅': 'restaurant',
'位置': 'table',
'明天': 'tomorrow',
'晚上': 'evening',
}
# 槽位模板
self.slots = ['date', 'time', 'location', 'rating']
def parse(self, sentence):
"""解析句子"""
words = sentence.split()
# 初始化槽位
slots = {slot: None for slot in self.slots}
# 填充槽位
for word in words:
word_lower = word.lower()
if word_lower in ['明天', 'tomorrow']:
slots['date'] = 'tomorrow'
elif word_lower in ['七点', '7pm']:
slots['time'] = '19:00'
elif word_lower in ['二星', 'two-star']:
slots['rating'] = 2
# 生成逻辑形式
logical_form = self._generate_logical_form(slots)
return logical_form
def _generate_logical_form(self, slots):
"""生成逻辑形式"""
parts = ['RESERVE']
if slots['date']:
parts.append(f"date={slots['date']}")
if slots['time']:
parts.append(f"time={slots['time']}")
if slots['rating']:
parts.append(f"rating={slots['rating']}")
return '[' + ', '.join(parts) + ']'10.3 基于Seq2Seq的语义解析
现代语义解析通常使用Seq2Seq模型,把自然语言直接翻译成逻辑形式:
class Seq2SeqSemanticParser(nn.Module):
"""基于Seq2Seq的语义解析器"""
def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
super().__init__()
# 编码器
self.encoder = nn.GRU(embed_dim, hidden_dim, bidirectional=True)
# 解码器
self.decoder = nn.GRU(embed_dim + hidden_dim, hidden_dim)
# 输出层
self.fc = nn.Linear(hidden_dim, output_dim)
# 嵌入层
self.embedding = nn.Embedding(vocab_size, embed_dim)
# 注意力
self.attn = nn.Linear(hidden_dim * 2 + embed_dim, hidden_dim)
def forward(self, src, tgt):
# src: [src_len, batch]
# tgt: [tgt_len, batch]
# 编码
embedded = self.embedding(src)
encoder_outputs, hidden = self.encoder(embedded)
# 简化版:直接输出
decoder_embedded = self.embedding(tgt)
decoder_outputs, _ = self.decoder(decoder_embedded, hidden[:1])
# 投影到词表
predictions = self.fc(decoder_outputs)
return predictions十一、大语言模型时代的语义理解:GPT能理解语义吗?
11.1 从词向量到Transformer
回顾一下我们走过的路:
One-Hot → 词袋模型 → Word2Vec/GloVe → ELMo → BERT → GPT
每一步都在进步:
- One-Hot:简单但无语义
- Word2Vec:捕捉语义关系,但静态
- ELMo/BERT:上下文相关,但还是词级别
- GPT:生成式,理解更深层的语义
11.2 GPT系列的语义理解能力
GPT系列(Generative Pre-trained Transformer)展示了令人惊讶的语义理解能力:
1. 零样本学习(Zero-shot Learning) 给GPT一个从未见过的任务,它能直接理解并执行:
用户:把"小明今天很高兴"翻译成英文
GPT:我很高兴今天
2. 语义推理 GPT能做各种推理任务:
用户:如果A大于B,B大于C,那么A和C哪个大?
GPT:A大于C
3. 上下文学习(In-context Learning) 给GPT几个例子,它能举一反三:
用户:
猫 -> 小动物
狗 -> 小动物
鸟 ->
GPT:小动物
11.3 GPT真的”理解”了吗?
这是个哲学问题。Gary Marcus等人认为,大语言模型只是”高级鹦鹉”,并不真正理解语义。它们只是在统计地预测下一个token。
但另一些人认为,如果一个系统能正确地处理语义、做出正确的推理、回答正确的问题,那它就是”理解”的——至少在功能意义上是理解的。
这其实是个古老的争论:什么是理解? 这个问题至今没有答案。
从实用角度看,大语言模型确实能做很多语义理解任务,而且做得很好。不管它们是不是”真正理解”,它们的实用性是毋庸置疑的。
十二、动手实验:用gensim训练Word2Vec词向量
12.1 环境准备
首先安装必要的库:
pip install gensim numpy scikit-learn12.2 训练Word2Vec模型
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
import re
# 准备语料
def preprocess_text(text):
"""简单的文本预处理"""
# 转小写
text = text.lower()
# 去除标点
text = re.sub(r'[^\w\s]', ' ', text)
# 分词(简单按空格分)
words = text.split()
return words
# 示例语料
sentences = [
"king queen man woman royal palace",
"king prince man male royal",
"queen princess woman female royal",
"apple fruit red sweet",
"banana fruit yellow sweet",
"dog animal bark pet",
"cat animal meow pet",
]
# 分词处理
tokenized_sentences = [preprocess_text(s) for s in sentences]
print("分词结果:", tokenized_sentences)
# 训练模型
model = Word2Vec(
sentences=tokenized_sentences,
vector_size=10, # 词向量维度
window=2, # 上下文窗口大小
min_count=1, # 最小词频
workers=4, # 并行线程数
sg=0, # 0=CBOW, 1=Skip-gram
epochs=100, # 训练轮数
)
# 保存模型
model.save("word2vec.model")
# 加载模型
# model = Word2Vec.load("word2vec.model")12.3 使用训练好的词向量
# 查找相似词
print("与king相似的词:", model.wv.most_similar('king'))
print("与dog相似的词:", model.wv.most_similar('dog'))
# 词向量运算(语义类比)
# king - man + woman ≈ queen
try:
result = model.wv.most_similar(positive=['king', 'woman'], negative=['man'])
print("king - man + woman =", result)
except KeyError as e:
print(f"词{e}不在词汇表中")
# 计算词相似度
similarity = model.wv.similarity('dog', 'cat')
print(f"dog和cat的相似度: {similarity}")
# 查找中心词
print("最不像dog的词:", model.wv.doesnt_match(['dog', 'cat', 'apple']))12.4 在真实语料上训练
from gensim.models.word2vec import LineSentence
import os
def train_on_file(input_file, output_model):
"""
在真实语料文件上训练Word2Vec
Args:
input_file: 输入文本文件,每行一句话
output_model: 输出模型路径
"""
# 训练模型
model = Word2Vec(
LineSentence(input_file),
vector_size=300, # 常见选择:100-300
window=5, # 常见选择:5-10
min_count=5, # 最小词频
workers=8, # CPU核心数
sg=1, # 使用Skip-gram
epochs=10, # 训练轮数
negative=5, # 负采样数量
)
# 保存
model.save(output_model)
# 打印模型信息
print(f"词汇量: {len(model.wv)}")
print(f"词向量维度: {model.wv.vector_size}")
return model
# 使用示例
# model = train_on_file("corpus.txt", "word2vec.model")12.5 词向量的可视化
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
def visualize_word_vectors(model, words):
"""可视化词向量"""
# 获取词向量
vectors = []
valid_words = []
for word in words:
if word in model.wv:
vectors.append(model.wv[word])
valid_words.append(word)
if not vectors:
print("没有找到有效的词")
return
vectors = np.array(vectors)
# PCA降维到2D
pca = PCA(n_components=2)
vectors_2d = pca.fit_transform(vectors)
# 绘制
plt.figure(figsize=(10, 8))
plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1], c='blue', s=100)
for i, word in enumerate(valid_words):
plt.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]), fontsize=12)
plt.title("Word2Vec Visualization")
plt.show()
# 可视化示例
words = ["king", "queen", "man", "woman", "prince", "princess", "dog", "cat"]
visualize_word_vectors(model, words)12.6 常见问题与解决
1. 词向量质量差
- 增加语料量
- 调整vector_size和window参数
- 增加训练轮数
- 尝试不同的训练方法(CBOW vs Skip-gram)
2. 内存不足
- 减小vector_size
- 提高min_count过滤低频词
- 使用negative sampling而不是hierarchical softmax
3. 训练太慢
- 增加workers数量
- 使用预训练模型微调
- 减少语料中的句子长度
十三、语义学学科体系与计算语义学定位
13.1 语义学的学科全景
语义学(Semantics)是研究语言意义的学科,其研究范围涵盖了从最小的语义单位——语素(morpheme)——到复杂的篇章结构。
计算语义学(Computational Semantics)将这些语言学理论转化为可计算的形式,以便计算机能够处理和理解自然语言。
语义学的核心问题:
- 意义是什么? 如何定义和形式化语言的语义内容
- 意义如何组合? 小粒度语义单元如何组合成更大单位的意义
- 语境的作用是什么? 上下文如何影响意义的理解和消歧
- 知识与语义的关系? 世界知识如何支撑语言理解
13.2 语义学的分支体系
词汇语义学(Lexical Semantics):关注单词层面的意义,包括同义词、反义词、上下位词等关系。
句子语义学(Sentence Semantics):研究如何从词汇组合成句子级别的意义,涉及组合性原则和论元结构。
话语语义学(Discourse Semantics):超越句子边界,研究文本层面的连贯性和意义,包括指代消解和篇章衔接。
十四、框架语义学(Frame Semantics)
14.1 理论框架与认知基础
框架语义学由Charles Fillmore在1970年代提出,核心观点是:理解词语的意义需要激活相关的知识框架。
以”商业交易”框架为例:
- 触发词:买、卖、支付、价格
- 框架元素:买方、卖方、商品、货币
- 隐含结构:交易涉及金钱和商品的交换
class FrameSemanticsEngine:
"""框架语义学引擎"""
def __init__(self):
self.frames = self._load_frames()
self.lu_to_frame = self._build_trigger_map()
def _load_frames(self):
"""加载框架库"""
return {
'Commercial_buy': {
'name': 'Commercial_buy',
'frame_elements': {
'Buyer': {'definition': '购买商品的主体', 'core': True},
'Seller': {'definition': '出售商品的主体', 'core': True},
'Goods': {'definition': '被购买的物品', 'core': True},
'Money': {'definition': '支付的对价', 'core': True},
},
'lexical_units': ['买', '购买', '采购'],
},
'Motion': {
'name': 'Motion',
'frame_elements': {
'Theme': {'definition': '移动的物体', 'core': True},
'Source': {'definition': '运动的起点', 'core': False},
'Goal': {'definition': '运动的目标点', 'core': False},
},
'lexical_units': ['走', '跑', '飞', '移动'],
},
}
def _build_trigger_map(self):
"""构建触发词到框架的映射"""
lu_to_frame = {}
for frame_name, frame_data in self.frames.items():
for lu in frame_data.get('lexical_units', []):
lu_to_frame[lu] = frame_name
return lu_to_frame
def identify_frame(self, word):
"""识别触发词激活的框架"""
if word in self.lu_to_frame:
return self.lu_to_frame[word], 1.0
return None, 0.014.2 FrameNet项目
FrameNet是框架语义学的计算实现,包含:
- 1,200+ 个框架
- 13,000+ 个词元标注
- 200,000+ 个句子标注
FrameNet被广泛应用于语义角色标注、事件抽取、问答系统等任务。
十五、词义消歧(Word Sense Disambiguation, WSD)
15.1 任务定义
词义消歧是确定多义词在特定上下文中的具体词义。
class LeskWSD:
"""Lesk词义消歧算法"""
def __init__(self):
from nltk.corpus import wordnet as wn
self.wordnet = wn
def disambiguate(self, word, sentence):
"""消歧"""
synsets = self.wordnet.synsets(word)
if not synsets:
return None
sentence_words = set(re.findall(r'\w+', sentence.lower()))
best_sense = None
best_score = -1
for synset in synsets:
# 获取词义定义
gloss = synset.definition()
gloss_words = set(re.findall(r'\w+', gloss.lower()))
# 计算与上下文的重叠
overlap = len(sentence_words & gloss_words)
if overlap > best_score:
best_score = overlap
best_sense = synset
return best_sense15.2 基于BERT的WSD
现代WSD系统使用预训练语言模型:
class BERTWSD:
"""基于BERT的词义消歧"""
def __init__(self, model_name='bert-base-uncased'):
from transformers import BertModel, BertTokenizer
self.tokenizer = BertTokenizer.from_pretrained(model_name)
self.model = BertModel.from_pretrained(model_name)
self.model.eval()
def disambiguate(self, word, sentence):
"""使用BERT进行词义消歧"""
inputs = self.tokenizer(
sentence,
return_tensors='pt',
padding=True,
truncation=True
)
with torch.no_grad():
outputs = self.model(**inputs)
hidden_states = outputs.last_hidden_state[0]
# 简化:返回[CLS]向量
return hidden_states[0].numpy()十六、文本蕴含与自然语言推理
16.1 任务定义
文本蕴含(Textual Entailment)判断文本T是否蕴含假设H。
class TextualEntailmentModel(nn.Module):
"""自然语言推理模型"""
def __init__(self, encoder_name='bert-base-uncased'):
super().__init__()
from transformers import BertModel
self.encoder = BertModel.from_pretrained(encoder_name)
hidden_size = self.encoder.config.hidden_size
self.classifier = nn.Linear(hidden_size * 3, 3)
self.label_map = {0: 'contradiction', 1: 'neutral', 2: 'entailment'}
def forward(self, premise_ids, hypothesis_ids):
"""前向传播"""
premise_encoded = self.encoder(input_ids=premise_ids).last_hidden_state[:, 0]
hypothesis_encoded = self.encoder(input_ids=hypothesis_ids).last_hidden_state[:, 0]
# 交互特征
combined = torch.cat([
premise_encoded,
hypothesis_encoded,
torch.abs(premise_encoded - hypothesis_encoded)
], dim=-1)
logits = self.classifier(combined)
return logits十七、计算语义学的前沿与挑战
17.1 当前挑战
- 组合歧义:相同词汇组合可能产生不同语义
- 隐含知识:文本中未显式表达的背景知识
- 跨语言差异:不同语言的语义结构差异
- 动态语义:网络语言、新造词汇、隐喻
- 解释性:深度学习模型的语义表示缺乏可解释性
17.2 前沿研究方向
神经符号推理:将神经网络的学习能力与符号推理的可解释性结合。
多模态语义:整合视觉、语言、音频等多种模态的语义信息。
上下文适应:动态适应不同领域和用户的语义理解需求。
可解释语义:为语义理解提供人类可读的推理过程和解释。
参考文献与推荐阅读
- Jurafsky, D., & Martin, J. H. (2023). Speech and Language Processing (3rd ed.). Stanford University.
- Mikolov, T., et al. (2013). Distributed representations of words and phrases and their compositionality. NeurIPS.
- Pennington, J., Socher, R., & Manning, C. D. (2014). GloVe: Global vectors for word representation. EMNLP.
- Peters, M. E., et al. (2018). Deep contextualized word representations. NAACL.
- Devlin, J., et al. (2019). BERT: Pre-training of deep bidirectional transformers for language understanding. NAACL.
- Fillmore, C. J. (1976). Frame semantics and the nature of language. Annals of the New York Academy of Sciences.
- Bordes, A., et al. (2013). Translating embeddings for modeling multi-relational data. NeurIPS.
- Bowman, S. R., et al. (2015). A large annotated corpus for learning natural language inference. EMNLP.
关联文档