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 npclass 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} " )
方法二:词频/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, TfidfVectorizerimport numpy as npcorpus = [ "我喜欢自然语言处理" , "自然语言处理很有趣" , "我喜欢编程" ] vectorizer = CountVectorizer() bag_of_words = vectorizer.fit_transform(corpus) print ("词表:" , vectorizer.get_feature_names_out())print ("向量形状:" , bag_of_words.shape) print (bag_of_words.toarray())tfidf_vectorizer = TfidfVectorizer() tfidf_matrix = tfidf_vectorizer.fit_transform(corpus) print (tfidf_matrix.toarray())
方法三:词嵌入(Word Embedding)—— 分布式表示 核心思想 分布式假设 :词语的意义由其上下文决定。将词语映射到低维稠密向量空间中,语义相似的词在空间中距离更近。
3.1 Word2Vec 两种架构:
CBOW(Continuous Bag of Words) :用上下文预测中心词
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 Word2Vecimport numpy as npsentences = [ ["我" , "喜欢" , "自然" , "语言" ], ["自然" , "语言" , "处理" , "很" , "有趣" ], ["我" , "爱" , "编程" ], ["编程" , "需要" , "逻辑" , "思维" ], ["自然" , "语言" , "是" , "人类" , "交流" , "的" , "工具" ] ] model = Word2Vec( sentences=sentences, vector_size=50 , window=3 , min_count=1 , sg=1 , epochs=100 ) vector = model.wv["自然" ] print (f"向量维度: {vector.shape} " ) 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 glove2word2vecfrom gensim.models import KeyedVectorsglove_model = KeyedVectors.load_word2vec_format('glove.6B.300d.txt' ) vector = glove_model['自然' ] print (vector.shape)
3.3 FastText 创新点 考虑词的子词信息(n-gram),可以处理未登录词(OOV)。
1 2 3 4 5 6 7 8 9 10 11 from gensim.models import FastTextmodel = FastText( sentences=sentences, vector_size=50 , window=3 , min_n=3 , max_n=6 , epochs=100 )
方法四:上下文词嵌入(Contextual Embeddings) 核心思想 同一个词在不同语境下有不同的向量表示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from transformers import BertTokenizer, BertModelimport torchtokenizer = 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) last_hidden_state = outputs.last_hidden_state print (last_hidden_state.shape) for i, token in enumerate (tokenizer.convert_ids_to_tokens(inputs['input_ids' ][0 ])): print (f"{token} : {last_hidden_state[0 ][i][:5 ]} ..." )
4.2 ELMo(双向LSTM嵌入) 1 2 3 4 5 6 7 8 9 import elmoformanylayerselmo = elmoformanylayers.Elmo(options_file='options.json' , weight_file='weights.hdf5' , batch_size=1 ) embeddings = elmo.get_sentence_embeddings(['自然' , '语言' , '处理' ]) print (embeddings.shape)
原理 通过自注意力机制,每个位置的词向量都融合了整个序列的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from transformers import AutoTokenizer, AutoModelimport torchtokenizer = 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) sentence_vec = outputs.last_hidden_state.mean(dim=1 ) return sentence_vec embedding = get_sentence_embedding("自然语言处理" ) print (embedding.shape)
方法六:字符级嵌入(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 torchimport torch.nn as nnclass 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) embedded = embedded.transpose(1 , 2 ) 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 ) return combined char_model = CharEmbedding(vocab_size=10000 , embed_dim=30 ) char_input = torch.randint(0 , 10000 , (2 , 5 )) output = char_model(char_input) print (output.shape)
四、各种方法的对比
方法
维度
是否含语义
是否考虑词序
是否上下文相关
计算复杂度
独热编码
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 torchfrom transformers import AutoTokenizerclass 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' ) return encodings def get_embeddings (self, texts, model ): """获取文本的语义嵌入""" tensors = self .build_tensor(texts) with torch.no_grad(): outputs = model(**tensors) 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} " )
六、总结 文本张量表示的核心价值在于:
桥梁作用 :连接人类语言与机器计算
语义压缩 :将离散符号映射到连续空间,保留语义信息
可微性 :使反向传播成为可能,支持端到端训练
泛化能力 :预训练张量表示可以在下游任务中迁移学习
从简单的独热编码到复杂的BERT上下文嵌入,文本张量表示的发展推动了NLP从传统方法到深度学习的范式转变。