NLP中文本张量表示及其作用

NLP中文本张量表示及其作用

一、什么是文本张量表示?

文本张量表示(Text Tensor Representation) 是指将自然语言文本转化为计算机可以处理的**多维数值数组(张量/Tensor)**的过程。张量是向量的推广:

  • 标量(Scalar):0维张量
  • 向量(Vector):1维张量
  • 矩阵(Matrix):2维张量
  • 张量(Tensor):3维及以上

在NLP中,文本通常被表示为形状为 (batch_size, sequence_length, embedding_dim) 的3维张量。


二、文本张量表示的作用

1. 机器可读性

计算机只能处理数值,无法直接理解字符或词语的含义。张量化是NLP的第一步。

2. 语义捕捉

高质量的张量表示能够捕捉词语之间的语义关系(如”国王-女王≈男人-女人”)。

3. 模型输入标准化

为深度学习模型提供统一的数值输入格式。

4. 并行计算优化

张量运算可以利用GPU/TPU进行大规模并行加速。


三、文本张量表示的几种主要方法

方法一:独热编码(One-Hot Encoding)

原理

为词汇表中的每个词分配一个唯一索引,然后创建一个与词汇表大小相同的向量,对应位置为1,其余为0。

1
2
3
4
词汇表:["猫", "狗", "人"]
"猫" → [1, 0, 0]
"狗" → [0, 1, 0]
"人" → [0, 0, 1]

优点

  • 简单直观
  • 实现容易

缺点

  • 维度灾难:词汇表越大,向量越稀疏
  • 无语义信息:所有词之间正交,无法表达相似性
  • 计算效率低:高维稀疏向量浪费资源

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import numpy as np

class OneHotEncoder:
def __init__(self, vocab):
self.vocab = vocab
self.word2idx = {word: idx for idx, word in enumerate(vocab)}

def encode(self, sentence):
"""将句子编码为独热向量"""
indices = [self.word2idx[word] for word in sentence if word in self.vocab]
one_hot = np.eye(len(self.vocab))[indices]
return one_hot

def decode(self, one_hot):
"""从独热向量解码回句子"""
indices = np.argmax(one_hot, axis=1)
words = [self.vocab[i] for i in indices]
return words

# 使用示例
vocab = ["我", "爱", "自然", "语言", "处理"]
encoder = OneHotEncoder(vocab)
sentence = ["我", "爱", "自然", "语言"]
encoded = encoder.encode(sentence)
print(f"编码后形状: {encoded.shape}") # (4, 5) - 4个词, 5维词汇表

方法二:词频/TF-IDF向量(Bag-of-Words / TF-IDF)

原理

统计词汇表中每个词在文档中出现的频率,形成固定长度的向量。

1
2
3
4
5
6
文档1: "我喜欢猫"
文档2: "我喜欢狗"

词表: ["我", "喜欢", "猫", "狗"]
文档1: [1, 1, 1, 0]
文档2: [1, 1, 0, 1]

优点

  • 简单高效
  • 对短文本分类有效

缺点

  • 忽略词序:”猫爱我”和”我爱猫”向量相同
  • 稀疏度高
  • 无深层语义

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
import numpy as np

# CountVectorizer (词袋模型)
corpus = [
"我喜欢自然语言处理",
"自然语言处理很有趣",
"我喜欢编程"
]

vectorizer = CountVectorizer()
bag_of_words = vectorizer.fit_transform(corpus)
print("词表:", vectorizer.get_feature_names_out())
print("向量形状:", bag_of_words.shape) # (3, 6)
print(bag_of_words.toarray())

# TfidfVectorizer (加权)
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(corpus)
print(tfidf_matrix.toarray())

方法三:词嵌入(Word Embedding)—— 分布式表示

核心思想

分布式假设:词语的意义由其上下文决定。将词语映射到低维稠密向量空间中,语义相似的词在空间中距离更近。

3.1 Word2Vec

两种架构:
  1. CBOW(Continuous Bag of Words):用上下文预测中心词
  2. Skip-gram:用中心词预测上下文
1
2
3
4
5
句子: "我 爱 自然 语言 处理"

Skip-gram (窗口大小=2):
输入: "自然" → 输出: ["我", "爱", "语言", "处理"]
输入: "语言" → 输出: ["爱", "自然", "处理"]
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from gensim.models import Word2Vec
import numpy as np

# 训练数据(已分词的句子列表)
sentences = [
["我", "喜欢", "自然", "语言"],
["自然", "语言", "处理", "很", "有趣"],
["我", "爱", "编程"],
["编程", "需要", "逻辑", "思维"],
["自然", "语言", "是", "人类", "交流", "的", "工具"]
]

# 训练Word2Vec模型
model = Word2Vec(
sentences=sentences,
vector_size=50, # 嵌入维度
window=3, # 上下文窗口大小
min_count=1, # 最小词频
sg=1, # 1=Skip-gram, 0=CBOW
epochs=100
)

# 获取词的向量
vector = model.wv["自然"]
print(f"向量维度: {vector.shape}") # (50,)

# 语义相似度
similar = model.wv.most_similar("自然")
print(f"'自然'最相似的词: {similar}")

# 类比推理
result = model.wv.most_similar(positive=["国王", "女人"], negative=["男人"])
print(result) # 应该接近"女王"

3.2 GloVe(Global Vectors)

原理

基于全局词共现矩阵的矩阵分解方法,结合了词频统计和局部上下文窗口的优势。

1
2
3
4
5
6
7
from gensim.scripts.glove2word2vec import glove2word2vec
from gensim.models import KeyedVectors

# 加载预训练GloVe模型
glove_model = KeyedVectors.load_word2vec_format('glove.6B.300d.txt')
vector = glove_model['自然']
print(vector.shape) # (300,)

3.3 FastText

创新点

考虑词的子词信息(n-gram),可以处理未登录词(OOV)。

1
2
3
4
5
6
7
8
9
10
11
from gensim.models import FastText

model = FastText(
sentences=sentences,
vector_size=50,
window=3,
min_n=3, # 最小n-gram长度
max_n=6, # 最大n-gram长度
epochs=100
)
# 即使"自然语言"不在训练集中,也能通过子词组合得到向量

方法四:上下文词嵌入(Contextual Embeddings)

核心思想

同一个词在不同语境下有不同的向量表示。

4.1 BERT(Bidirectional Encoder Representations from Transformers)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from transformers import BertTokenizer, BertModel
import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')

text = "自然语言处理很有趣"
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True, max_length=512)

with torch.no_grad():
outputs = model(**inputs)

# 最后一层隐藏状态: (batch, seq_len, hidden_size)
last_hidden_state = outputs.last_hidden_state
print(last_hidden_state.shape) # (1, 5, 768)

# 每个词的上下文向量
for i, token in enumerate(tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])):
print(f"{token}: {last_hidden_state[0][i][:5]}...") # 打印前5个维度

4.2 ELMo(双向LSTM嵌入)

1
2
3
4
5
6
7
8
9
import elmoformanylayers

elmo = elmoformanylayers.Elmo(options_file='options.json',
weight_file='weights.hdf5',
batch_size=1)

# 句子: ["自然", "语言", "处理"]
embeddings = elmo.get_sentence_embeddings(['自然', '语言', '处理'])
print(embeddings.shape) # (1, 3, 1024)

方法五:Transformer自注意力机制下的张量表示

原理

通过自注意力机制,每个位置的词向量都融合了整个序列的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from transformers import AutoTokenizer, AutoModel
import torch

# 使用RoBERTa-wwm-ext(中文优化)
tokenizer = AutoTokenizer.from_pretrained('hfl/chinese-roberta-wwm-ext')
model = AutoModel.from_pretrained('hfl/chinese-roberta-wwm-ext')

def get_sentence_embedding(text):
inputs = tokenizer(text, return_tensors='pt', padding=True, truncation=True)
with torch.no_grad():
outputs = model(**inputs)
# 使用[PAD]或[CLS]作为句子向量
sentence_vec = outputs.last_hidden_state.mean(dim=1)
return sentence_vec

embedding = get_sentence_embedding("自然语言处理")
print(embedding.shape) # (1, 768)

方法六:字符级嵌入(Character-level Embedding)

原理

将每个字映射为向量,再聚合为词向量。适合处理未登录词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import torch
import torch.nn as nn

class CharEmbedding(nn.Module):
def __init__(self, vocab_size, embed_dim, num_filters=100, filter_sizes=[2,3,4]):
super().__init__()
self.char_embed = nn.Embedding(vocab_size, embed_dim)
self.convs = nn.ModuleList([
nn.Conv1d(in_channels=embed_dim,
out_channels=num_filters,
kernel_size=fs)
for fs in filter_sizes
])

def forward(self, char_indices):
"""
char_indices: (batch_size, seq_len)
"""
embedded = self.char_embed(char_indices) # (B, L, D)
embedded = embedded.transpose(1, 2) # (B, D, L)

conv_outputs = [torch.relu(conv(embedding)).squeeze(-1) for conv in self.convs]
max_pool = [torch.max(co, dim=1)[0] for co in conv_outputs]

combined = torch.cat(max_pool, dim=1) # (B, num_filters * len(filter_sizes))
return combined

# 使用示例
char_model = CharEmbedding(vocab_size=10000, embed_dim=30)
char_input = torch.randint(0, 10000, (2, 5)) # 2个样本, 每个5个字
output = char_model(char_input)
print(output.shape) # (2, 300)

四、各种方法的对比

方法 维度 是否含语义 是否考虑词序 是否上下文相关 计算复杂度
独热编码 Vocab大小 O(n)
BoW/TF-IDF Vocab大小 浅层 O(n)
Word2Vec 50-300 O(n²)
GloVe 50-300 O(n²)
FastText 300-300 ✅+子词 O(n²)
BERT 768-1024 ✅✅ O(n²·L)
字符级嵌入 可变 部分 O(n³)

:L为层数,n为序列长度


五、实际应用中的张量构建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import torch
from transformers import AutoTokenizer

class TextTensorBuilder:
"""完整的文本到张量的转换流水线"""

def __init__(self, model_name='hfl/chinese-roberta-wwm-ext'):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)

def build_tensor(self, texts, max_length=128):
"""
将文本列表转换为张量

Args:
texts: 文本列表 ['自然语言处理', '我很感兴趣']
max_length: 最大序列长度

Returns:
{
'input_ids': Tensor(batch, seq_len),
'attention_mask': Tensor(batch, seq_len),
'token_type_ids': Tensor(batch, seq_len) # 可选
}
"""
encodings = self.tokenizer(
texts,
padding=True, # 填充到同一长度
truncation=True, # 截断超长文本
max_length=max_length,
return_tensors='pt' # 返回PyTorch张量
)
return encodings

def get_embeddings(self, texts, model):
"""获取文本的语义嵌入"""
tensors = self.build_tensor(texts)
with torch.no_grad():
outputs = model(**tensors)

# 使用[CLS]标记的向量作为句子表示
cls_embeddings = outputs.last_hidden_state[:, 0, :]
return cls_embeddings

# 使用示例
builder = TextTensorBuilder()
texts = ["自然语言处理很有趣", "我喜欢编程"]
tensors = builder.build_tensor(texts)
print(f"Input IDs shape: {tensors['input_ids'].shape}")
print(f"Attention Mask shape: {tensors['attention_mask'].shape}")

六、总结

文本张量表示的核心价值在于:

  1. 桥梁作用:连接人类语言与机器计算
  2. 语义压缩:将离散符号映射到连续空间,保留语义信息
  3. 可微性:使反向传播成为可能,支持端到端训练
  4. 泛化能力:预训练张量表示可以在下游任务中迁移学习

从简单的独热编码到复杂的BERT上下文嵌入,文本张量表示的发展推动了NLP从传统方法到深度学习的范式转变。