ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【Bug已解决】How to convert a list of strings into a tensor in pytorch? 解决方案

【Bug已解决】How to convert a list of strings into a tensor in pytorch? 解决方案 【Bug已解决】How to convert a list of strings into a tensor in pytorch? 解决方案问题描述在自然语言处理NLP任务中我们经常需要将文本数据字符串列表转换为 PyTorch 张量用于模型训练。然而PyTorch 张量本质上是数值型数据的容器不能直接存储字符串。很多开发者在尝试将字符串列表转为张量时会遇到各种错误。常见的错误表现import torch # 尝试直接转换 strings [hello, world, pytorch] try: tensor torch.tensor(strings) except Exception as e: print(f错误: {e}) # ValueError: failed to convert to tensor # 或 TypeError: not a valid type这个问题的核心在于PyTorch 张量只能存储数值类型int, float, bool 等不支持字符串类型。要将文本数据转为张量需要先进行编码encoding将字符串映射为数值表示。文本到张量的转换涉及多个层次词汇表映射将每个单词/子词映射为整数索引字符编码将每个字符映射为 ASCII/Unicode 码嵌入表示将索引进一步映射为稠密向量序列填充处理不同长度的文本序列错误复现以下代码演示了各种尝试将字符串列表转为张量时遇到的错误import torch # 错误1直接转换字符串列表 strings [hello, world, pytorch] try: tensor torch.tensor(strings) except Exception as e: print(f错误1: {type(e).__name__}: {e}) # ValueError: failed to convert # 错误2转换混合类型 mixed [hello, 42, world] try: tensor torch.tensor(mixed) except Exception as e: print(f错误2: {type(e).__name__}: {e}) # 错误3使用 LongTensor try: tensor torch.LongTensor(strings) except Exception as e: print(f错误3: {type(e).__name__}: {e}) # 错误4使用 from_numpy import numpy as np try: np_array np.array(strings) tensor torch.from_numpy(np_array) print(f错误4: 成功创建但类型为: {tensor.dtype}) # 这会创建一个字符串类型的张量但无法用于模型训练 except Exception as e: print(f错误4: {type(e).__name__}: {e}) # 错误5不同长度的字符串 var_strings [hi, hello, greetings] try: # 尝试将每个字符转为数字 tensor torch.tensor([[ord(c) for c in s] for s in var_strings]) print(f错误5: 成功但形状不统一: {tensor.shape}) # 这会失败因为不同长度的列表无法构成规则张量 except Exception as e: print(f错误5: {type(e).__name__}: {e}) # ValueError: expected sequence of length 2 at dim 1 (got 5)根因分析1. PyTorch 张量的类型限制PyTorch 张量torch.Tensor是基于数值计算的支持的数据类型包括浮点型float16,float32,float64整型int8,int16,int32,int64布尔型bool复数型complex64,complex128PyTorch不支持字符串类型的张量。这是因为 GPU 计算需要固定大小的数值数据而字符串是变长的、非数值的。2. 文本数据的变长特性文本数据天然是变长的——不同的句子有不同的单词数量不同的单词有不同的字符数量。PyTorch 张量要求每个维度的大小是固定的因此需要通过填充padding或截断truncation来统一长度。3. 编码方案的多样性将文本转为数值有多种方案每种方案适用于不同的场景词级别编码每个单词映射为一个整数索引字符级别编码每个字符映射为其 ASCII/Unicode 码子词编码使用 BPEByte Pair Encoding等算法预训练编码使用 BERT、GPT 等模型的 tokenizer4. 批处理的需求在模型训练中通常需要将多个文本组成一个批次batch。不同长度的文本需要通过填充padding对齐并使用注意力掩码attention mask标记有效位置。解决方案方案一使用词汇表进行词级别编码import torch from collections import Counter class Vocabulary: 词汇表管理器 def __init__(self, min_freq1, max_sizeNone): self.min_freq min_freq self.max_size max_size self.word2idx {} self.idx2word {} # 特殊标记 self.pad_token PAD self.unk_token UNK self.sos_token SOS # Start of sequence self.eos_token EOS # End of sequence def build_vocab(self, texts): 从文本列表构建词汇表 # 统计词频 counter Counter() for text in texts: words text.lower().split() counter.update(words) # 按频率排序 sorted_words sorted( counter.items(), keylambda x: x[1], reverseTrue ) # 过滤低频词 if self.min_freq 1: sorted_words [(w, f) for w, f in sorted_words if f self.min_freq] # 限制词汇表大小 if self.max_size: sorted_words sorted_words[:self.max_size] # 构建映射 self.word2idx { self.pad_token: 0, self.unk_token: 1, self.sos_token: 2, self.eos_token: 3, } self.idx2word {v: k for k, v in self.word2idx.items()} for word, freq in sorted_words: idx len(self.word2idx) self.word2idx[word] idx self.idx2word[idx] word print(f词汇表大小: {len(self.word2idx)}) return self def encode(self, text, max_lenNone): 将文本编码为索引列表 words text.lower().split() indices [self.word2idx.get(w, self.word2idx[self.unk_token]) for w in words] if max_len: if len(indices) max_len: indices indices[:max_len] else: indices indices [self.word2idx[self.pad_token]] * (max_len - len(indices)) return indices def decode(self, indices): 将索引列表解码为文本 words [] for idx in indices: if idx in [self.word2idx[self.pad_token], self.word2idx[self.eos_token]]: break words.append(self.idx2word.get(idx, self.unk_token)) return .join(words) # 使用示例 texts [ hello world this is pytorch, pytorch is a deep learning framework, deep learning is fun and powerful, hello from the world of ai, ] vocab Vocabulary(min_freq1) vocab.build_vocab(texts) # 编码单个文本 encoded vocab.encode(hello world, max_len10) tensor torch.tensor(encoded, dtypetorch.long) print(f编码: {encoded}) print(f张量: {tensor}) print(f解码: {vocab.decode(encoded)})方案二使用字符级别编码import torch import string class CharEncoder: 字符级别编码器 def __init__(self): # 包含所有可打印字符 self.chars string.printable # 0-9, a-z, A-Z, 标点, 空格等 self.char2idx {c: i 1 for i, c in enumerate(self.chars)} self.idx2char {i 1: c for i, c in enumerate(self.chars)} self.pad_idx 0 self.unk_idx len(self.chars) 1 self.char2idx[PAD] self.pad_idx self.char2idx[UNK] self.unk_idx self.idx2char[self.pad_idx] PAD self.idx2char[self.unk_idx] UNK def encode(self, text, max_lenNone): 将文本编码为字符索引 indices [self.char2idx.get(c, self.unk_idx) for c in text] if max_len: if len(indices) max_len: indices indices[:max_len] else: indices indices [self.pad_idx] * (max_len - len(indices)) return indices def decode(self, indices): 将索引解码为文本 chars [] for idx in indices: if idx self.pad_idx: break chars.append(self.idx2char.get(idx, UNK)) return .join(chars) def encode_batch(self, texts, max_lenNone): 批量编码文本 if max_len is None: max_len max(len(t) for t in texts) batch [self.encode(t, max_len) for t in texts] return torch.tensor(batch, dtypetorch.long) # 使用示例 encoder CharEncoder() texts [hello, world, pytorch] batch_tensor encoder.encode_batch(texts, max_len10) print(f批量编码形状: {batch_tensor.shape}) # torch.Size([3, 10]) print(f批量编码:\n{batch_tensor}) # 解码 for i in range(len(texts)): decoded encoder.decode(batch_tensor[i].tolist()) print(f解码 {i}: {decoded})方案三使用 PyTorch 内置的 collate_fn 进行批处理import torch from torch.utils.data import Dataset, DataLoader class TextDataset(Dataset): 文本数据集 def __init__(self, texts, labels, vocab): self.texts texts self.labels labels self.vocab vocab def __len__(self): return len(self.texts) def __getitem__(self, idx): encoded self.vocab.encode(self.texts[idx]) return torch.tensor(encoded, dtypetorch.long), self.labels[idx] def collate_fn(batch, pad_idx0): 自定义批处理函数。 将不同长度的序列填充到批次内最大长度。 # 分离数据和标签 sequences, labels zip(*batch) # 获取批次内最大长度 max_len max(len(seq) for seq in sequences) # 填充序列 padded_sequences [] attention_masks [] for seq in sequences: padding_length max_len - len(seq) padded torch.cat([ seq, torch.full((padding_length,), pad_idx, dtypeseq.dtype) ]) mask torch.cat([ torch.ones(len(seq), dtypetorch.long), torch.zeros(padding_length, dtypetorch.long) ]) padded_sequences.append(padded) attention_masks.append(mask) # 堆叠为张量 sequences_tensor torch.stack(padded_sequences) masks_tensor torch.stack(attention_masks) labels_tensor torch.tensor(labels, dtypetorch.long) return sequences_tensor, masks_tensor, labels_tensor ![配图](https://i-blog.csdnimg.cn/img_convert/2242c3b84ea993382eb7ffd3ebda54e9.png) # 使用示例 texts [ hello world, this is a longer sentence with more words, short text, medium length text here, ] labels [0, 1, 0, 1] # 构建词汇表 vocab Vocabulary(min_freq1) vocab.build_vocab(texts) # 创建数据集和数据加载器 dataset TextDataset(texts, labels, vocab) dataloader DataLoader( dataset, batch_size2, shuffleTrue, collate_fnlambda batch: collate_fn(batch, pad_idxvocab.word2idx[PAD]), ) # 迭代数据 for batch_seqs, batch_masks, batch_labels in dataloader: print(f序列形状: {batch_seqs.shape}) print(f掩码形状: {batch_masks.shape}) print(f标签: {batch_labels}) print(f序列:\n{batch_seqs}) print(f掩码:\n{batch_masks}) print(- * 40)方案四使用 HuggingFace Tokenizer# pip install transformers from transformers import AutoTokenizer import torch # 使用预训练模型的 tokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) texts [ Hello, how are you?, I am learning PyTorch for NLP., This is a great framework!, ] # 批量编码 encoded tokenizer( texts, paddingTrue, # 填充到批次内最大长度 truncationTrue, # 截断超长文本 max_length20, # 最大长度 return_tensorspt, # 返回 PyTorch 张量 ) print(finput_ids 形状: {encoded[input_ids].shape}) print(fattention_mask 形状: {encoded[attention_mask].shape}) print(finput_ids:\n{encoded[input_ids]}) print(fattention_mask:\n{encoded[attention_mask]}) # 解码 for i in range(len(texts)): decoded tokenizer.decode(encoded[input_ids][i], skip_special_tokensTrue) print(f解码 {i}: {decoded})完整修复代码以下是一个完整的文本到张量转换工具支持多种编码方案import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader from collections import Counter from typing import List, Dict, Tuple, Optional import string class TextToTensorConverter: 完整的文本到张量转换工具。 支持词级别、字符级别和子词级别的编码。 # 特殊标记 PAD_TOKEN PAD UNK_TOKEN UNK SOS_TOKEN SOS EOS_TOKEN EOS def __init__(self, levelword, min_freq1, max_vocab_sizeNone): Args: level: 编码级别 (word, char) min_freq: 最小词频 max_vocab_size: 最大词汇表大小 self.level level self.min_freq min_freq self.max_vocab_size max_vocab_size self.token2idx: Dict[str, int] {} self.idx2token: Dict[int, str] {} # 初始化特殊标记 self.pad_idx 0 self.unk_idx 1 self.sos_idx 2 self.eos_idx 3 self._built False def _tokenize(self, text: str) - List[str]: 分词 if self.level word: return text.lower().split() elif self.level char: return list(text) else: raise ValueError(f不支持的级别: {self.level}) def build_vocab(self, texts: List[str]): 构建词汇表 counter Counter() for text in texts: tokens self._tokenize(text) counter.update(tokens) # 过滤低频 if self.min_freq 1: items [(t, f) for t, f in counter.items() if f self.min_freq] else: items list(counter.items()) # 按频率排序 items.sort(keylambda x: x[1], reverseTrue) # 限制大小 if self.max_vocab_size: items items[:self.max_vocab_size - 4] # 留出特殊标记 # 构建映射 self.token2idx { self.PAD_TOKEN: self.pad_idx, self.UNK_TOKEN: self.unk_idx, self.SOS_TOKEN: self.sos_idx, self.EOS_TOKEN: self.eos_idx, } for token, freq in items: self.token2idx[token] len(self.token2idx) self.idx2token {v: k for k, v in self.token2idx.items()} self._built True print(f词汇表构建完成: {len(self.token2idx)} 个 token (level{self.level})) return self def encode(self, text: str, max_len: Optional[int] None, add_special: bool False) - torch.Tensor: 将单个文本编码为张量。 Args: text: 输入文本 max_len: 最大长度None 表示不限制 add_special: 是否添加 SOS/EOS 标记 Returns: 编码后的张量 (LongTensor) tokens self._tokenize(text) if add_special: tokens [self.SOS_TOKEN] tokens [self.EOS_TOKEN] indices [self.token2idx.get(t, self.unk_idx) for t in tokens] if max_len: if len(indices) max_len: indices indices[:max_len] else: indices indices [self.pad_idx] * (max_len - len(indices)) return torch.tensor(indices, dtypetorch.long) def encode_batch(self, texts: List[str], max_len: Optional[int] None, add_special: bool False) - Tuple[torch.Tensor, torch.Tensor]: 批量编码文本。 Returns: (input_ids, attention_mask) input_ids: 编码后的张量 (batch_size, max_len) attention_mask: 注意力掩码 (batch_size, max_len) # 编码所有文本 encoded_list [self.encode(t, add_specialadd_special) for t in texts] # 计算最大长度 if max_len is None: max_len max(len(e) for e in encoded_list) # 填充 padded [] masks [] for encoded in encoded_list: length len(encoded) if length max_len: padded.append(encoded[:max_len]) masks.append(torch.ones(max_len, dtypetorch.long)) else: pad_length max_len - length padded.append(torch.cat([ encoded, torch.full((pad_length,), self.pad_idx, dtypetorch.long) ])) masks.append(torch.cat([ torch.ones(length, dtypetorch.long), torch.zeros(pad_length, dtypetorch.long) ])) input_ids torch.stack(padded) attention_mask torch.stack(masks) return input_ids, attention_mask def decode(self, indices: torch.Tensor, skip_special: bool True) - str: 将索引张量解码为文本 if isinstance(indices, torch.Tensor): indices indices.tolist() tokens [] for idx in indices: if skip_special and idx in [self.pad_idx, self.sos_idx, self.eos_idx]: if idx self.eos_idx: break continue tokens.append(self.idx2token.get(idx, self.UNK_TOKEN)) if self.level word: return .join(tokens) else: return .join(tokens) def get_embedding_layer(self, embedding_dim: int 128) - nn.Embedding: 获取与词汇表匹配的嵌入层 return nn.Embedding( num_embeddingslen(self.token2idx), embedding_dimembedding_dim, padding_idxself.pad_idx, ) class NLPDataset(Dataset): 用于 NLP 任务的 PyTorch 数据集 def __init__(self, texts, labels, converter, max_lenNone): self.texts texts self.labels labels self.converter converter self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): encoded self.converter.encode(self.texts[idx], max_lenself.max_len) return encoded, self.labels[idx] def collate_batch(batch, pad_idx0): 用于 DataLoader 的 collate_fn sequences, labels zip(*batch) max_len max(len(seq) for seq in sequences) padded [] for seq in sequences: if len(seq) max_len: padding torch.full((max_len - len(seq),), pad_idx, dtypeseq.dtype) padded.append(torch.cat([seq, padding])) else: padded.append(seq[:max_len]) return torch.stack(padded), torch.tensor(labels) # 完整使用示例 def demo_word_level(): 词级别编码示例 print( * 60) print(词级别编码示例) print( * 60) texts [ the quick brown fox jumps over the lazy dog, machine learning is a subset of artificial intelligence, pytorch is an open source deep learning framework, natural language processing is fascinating, transformers have revolutionized nlp tasks, ] labels [0, 1, 1, 0, 1] # 构建转换器 converter TextToTensorConverter(levelword, min_freq1) converter.build_vocab(texts) # 单个编码 text the lazy fox tensor converter.encode(text, max_len10) print(f\n文本: {text}) print(f编码: {tensor}) print(f解码: {converter.decode(tensor)}) # 批量编码 batch_texts texts[:3] input_ids, attention_mask converter.encode_batch(batch_texts, max_len10) print(f\n批量编码:) print(f input_ids 形状: {input_ids.shape}) print(f attention_mask 形状: {attention_mask.shape}) print(f input_ids:\n{input_ids}) print(f attention_mask:\n{attention_mask}) # 创建 DataLoader dataset NLPDataset(texts, labels, converter, max_len10) dataloader DataLoader( dataset, batch_size2, shuffleTrue, collate_fnlambda b: collate_batch(b, converter.pad_idx) ) print(f\nDataLoader 迭代:) for batch_x, batch_y in dataloader: print(f Batch shape: {batch_x.shape}, Labels: {batch_y}) # 嵌入层 embedding converter.get_embedding_layer(embedding_dim64) embedded embedding(input_ids) print(f\n嵌入后形状: {embedded.shape}) # (batch, seq_len, embed_dim) def demo_char_level(): 字符级别编码示例 print(\n * 60) print(字符级别编码示例) print( * 60) texts [hello, world, pytorch] converter TextToTensorConverter(levelchar) converter.build_vocab(texts) # 编码 input_ids, masks converter.encode_batch(texts, max_len10) print(f字符级编码形状: {input_ids.shape}) print(f编码:\n{input_ids}) # 解码 for i, text in enumerate(texts): decoded converter.decode(input_ids[i]) print(f {text} - {input_ids[i].tolist()} - {decoded}) def demo_with_model(): 与模型集成的示例 print(\n * 60) print(与模型集成示例) print( * 60) texts [ good movie I loved it, terrible film waste of time, amazing story great acting, boring and predictable plot, ] labels [1, 0, 1, 0] # 1正面, 0负面 # 构建转换器 converter TextToTensorConverter(levelword) converter.build_vocab(texts) # 创建数据集 dataset NLPDataset(texts, labels, converter, max_len10) dataloader DataLoader( dataset, batch_size2, shuffleTrue, collate_fnlambda b: collate_batch(b, converter.pad_idx) ) # 创建简单的文本分类模型 class TextClassifier(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes, pad_idx0): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idxpad_idx) self.fc nn.Sequential( nn.Linear(embed_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, num_classes), ) def forward(self, x): # x: (batch, seq_len) embedded self.embedding(x) # (batch, seq_len, embed_dim) # 平均池化 pooled embedded.mean(dim1) # (batch, embed_dim) return self.fc(pooled) # 初始化模型 model TextClassifier( vocab_sizelen(converter.token2idx), embed_dim64, hidden_dim32, num_classes2, pad_idxconverter.pad_idx, ) # 训练一个 epoch optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() model.train() for batch_x, batch_y in dataloader: optimizer.zero_grad() output model(batch_x) loss criterion(output, batch_y) loss.backward() optimizer.step() print(f Loss: {loss.item():.4f}) # 推理 model.eval() test_text great movie loved it test_encoded converter.encode(test_text, max_len10).unsqueeze(0) with torch.no_grad(): pred model(test_encoded) pred_label pred.argmax(dim1).item() print(f\n推理: {test_text} - 预测类别: {pred_label} ({正面 if pred_label 1 else 负面})) if __name__ __main__: demo_word_level() demo_char_level() demo_with_model() print(\n * 60) print(所有示例运行完成) print( * 60)常见陷阱与注意事项1. 填充值的选择填充值PAD index通常设为 0。在nn.Embedding中设置padding_idx0可以确保填充位置的嵌入向量始终为零向量不参与梯度更新。2. 注意力掩码的使用填充位置不应参与注意力计算。使用attention_mask标记有效位置在注意力计算时将填充位置的注意力权重设为负无穷softmax 后为 0。3. 词汇表覆盖问题测试集中可能出现训练集中没有的词OOV 词。使用UNK标记处理未知词但过多的 OOV 词会影响模型性能。考虑使用子词编码如 BPE减少 OOV。4. 编码一致性确保训练和推理使用相同的词汇表和编码方式。保存模型时应同时保存词汇表token2idx和idx2token。5. 中文文本处理中文没有天然的空格分词需要使用专门的分词工具如 jieba或字符级别编码import jieba tokens list(jieba.cut(自然语言处理很有趣))6. 大规模词汇表的内存问题词汇表过大时nn.Embedding层会占用大量内存。可以使用哈希技巧hashing trick或自适应嵌入adaptive softmax来减少内存。总结将字符串列表转换为 PyTorch 张量需要经过编码步骤因为 PyTorch 张量只支持数值类型。核心要点如下选择编码级别词级别适合大多数 NLP 任务字符级别适合处理拼写变化或未知词较多的场景。构建词汇表统计词频建立 token 到索引的映射处理特殊标记PAD, UNK, SOS, EOS。处理变长序列使用填充padding和注意力掩码attention mask统一批次内序列长度。使用嵌入层nn.Embedding将离散的索引映射为稠密向量是文本到张量转换后的下一步。使用预训练 Tokenizer对于生产级应用推荐使用 HuggingFace 的 tokenizer它处理了 BPE、特殊标记等复杂逻辑。保存词汇表与模型一起保存词汇表确保推理时编码一致。通过系统性地处理文本到张量的转换可以构建高效、可扩展的 NLP 数据处理流水线。
返回列表