NLP中的Transformer应用

深度学习专题 · 预训练模型在NLP中的实践

专题:深度学习系统学习

关键词:NLP, 文本分类, NER, 问答系统, 文本摘要, 机器翻译, Longformer, pipeline

一、概述:Transformer在NLP中的革命

2017年Google在论文"Attention Is All You Need"中提出的Transformer架构,彻底改变了自然语言处理(NLP)领域的面貌。在此之前,NLP的主流方法是RNN(循环神经网络)和LSTM(长短期记忆网络),它们通过逐步读取序列来理解文本。然而,RNN/LSTM存在两个根本性缺陷:一是无法并行计算,训练效率低下;二是面对长距离依赖时容易"遗忘"早期信息。

Transformer通过自注意力机制(Self-Attention)解决了上述问题。在自注意力机制中,每个词都可以直接与序列中的任何其他词建立关联,无论它们之间的距离有多远。这类似于人类阅读时,并不是逐字记忆,而是建立起一个"词汇关联网络"——每个词的意义取决于它与其他词的关系。同时,由于所有位置的计算可以同时进行,Transformer训练的效率远高于RNN。

2018年BERT(Bidirectional Encoder Representations from Transformers)的发布标志着NLP进入了"预训练-微调"时代。BERT通过在海量无标注文本上进行预训练(掩码语言模型MLM + 下一句预测NSP),学习到了丰富的语言知识。此后,GPT系列的快速发展推动NLP迈向了大语言模型时代。2026年的今天,由Transformer衍生出的各类预训练模型已经渗透到NLP的每一个子领域,成为该领域的基础设施。

核心思想:Transformer的成功本质上是规模化注意力机制的成功。通过堆叠多层自注意力和前馈网络,模型可以学习到文本中复杂的层级化语义特征。预训练则让这些特征具备了通用的语言理解能力,使得下游任务只需要少量的标注数据就能达到优秀的性能。

二、文本分类

文本分类是NLP中最基础也最广泛的应用场景之一。从垃圾邮件检测、新闻分类到情感分析,文本分类贯穿了信息处理的全流程。基于Transformer的预训练模型(以BERT为代表)大幅提升了文本分类的准确率,同时极大地降低了特征工程的工作量。

2.1 情感分类与BERT微调

情感分类(Sentiment Analysis)是最常见的文本分类任务,目标是判断文本的情感倾向:正面、负面或中性。使用BERT进行情感分类微调非常直观:在BERT的[CLS]标记对应的输出上接一个线性分类层,然后用标注数据端到端训练即可。

import torch import torch.nn as nn from transformers import BertTokenizer, BertModel, AdamW from torch.utils.data import DataLoader, Dataset class BertSentimentClassifier(nn.Module): def __init__(self, pretrained_model='bert-base-chinese', num_classes=3): super().__init__() self.bert = BertModel.from_pretrained(pretrained_model) self.dropout = nn.Dropout(0.1) self.classifier = nn.Linear(self.bert.config.hidden_size, num_classes) def forward(self, input_ids, attention_mask): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) cls_embedding = outputs.last_hidden_state[:, 0, :] # [CLS] token cls_embedding = self.dropout(cls_embedding) logits = self.classifier(cls_embedding) return logits # 初始化 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = BertSentimentClassifier() device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) # 简单推理示例 text = "这家餐厅的服务非常好,食物也很美味!" inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=128) with torch.no_grad(): logits = model(inputs['input_ids'], inputs['attention_mask']) pred = torch.argmax(logits, dim=1).item() print(f"预测类别: {pred} (0=负面, 1=中性, 2=正面)")

2.2 多标签文本分类

在现实场景中,一段文本常常属于多个类别。例如,一篇新闻可能同时属于"科技"和"经济"两个类别。多标签分类(Multi-label Classification)与单标签分类的关键区别在于输出层使用Sigmoid而非Softmax,并且损失函数采用二值交叉熵损失(BCEWithLogitsLoss)。

class BertMultiLabelClassifier(nn.Module): def __init__(self, pretrained_model, num_labels): super().__init__() self.bert = BertModel.from_pretrained(pretrained_model) self.classifier = nn.Linear(self.bert.config.hidden_size, num_labels) def forward(self, input_ids, attention_mask): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) pooled = outputs.pooler_output # [CLS] 经过tanh logits = self.classifier(pooled) return logits # 每个类别独立输出,后续用sigmoid # 推理:阈值法决定类别 def predict_multilabel(model, text, id2label, threshold=0.5): inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=128) model.eval() with torch.no_grad(): logits = model(inputs['input_ids'], inputs['attention_mask']) probs = torch.sigmoid(logits).squeeze().numpy() predicted_labels = [id2label[i] for i, p in enumerate(probs) if p > threshold] return predicted_labels, probs # 训练时使用 BCEWithLogitsLoss # criterion = nn.BCEWithLogitsLoss()

2.3 层次分类(Hierarchical Classification)

层次分类适用于类别之间存在层级关系的场景,例如:动物 → 哺乳动物 → 鲸类。处理层次分类主要有三种策略:平面方法(忽略层级,直接预测所有叶子节点)、层级方法(为层级中的每个节点训练一个分类器)、联合方法(设计能同时建模层级关系的网络结构)。实践中,基于Transformer的方法通常采用层级微调或层级感知的损失函数来利用类别之间的父-子关系。

# 层级感知损失函数示例 (Hierarchical Cross-Entropy) def hierarchical_loss(logits, targets, hierarchy_penalty=0.1): """ logits: [batch_size, num_classes] targets: [batch_size, num_classes] (one-hot) hierarchy: dict mapping child_idx -> parent_idx """ base_loss = nn.BCEWithLogitsLoss()(logits, targets) # 惩罚违反层级约束的情况:父类概率应 >= 子类概率 hierarchy_loss = 0.0 for child, parent in hierarchy_map.items(): child_prob = torch.sigmoid(logits[:, child]) parent_prob = torch.sigmoid(logits[:, parent]) # 如果 child 概率 > parent 概率,施加惩罚 penalty = torch.relu(child_prob - parent_prob).mean() hierarchy_loss += penalty return base_loss + hierarchy_penalty * hierarchy_loss

2.4 长文本处理与Sliding Window

BERT等标准Transformer模型的最大输入长度通常是512个token(约为350-400个中文字符或300-350个英文单词)。对于超过此长度的文本,简单的截断会丢失重要信息。Sliding Window(滑动窗口)是一种常用的长文本处理策略:将长文本切分成多个重叠的窗口,分别输入模型后通过池化或投票的方式聚合结果。

def sliding_window_classify(model, tokenizer, text, window_size=512, stride=256): """使用滑动窗口对长文本进行分类,投票聚合""" tokens = tokenizer.tokenize(text) windows = [] for i in range(0, len(tokens), stride): window_tokens = tokens[i:i + window_size] if len(window_tokens) < 32: break # 太短的窗口跳过 windows.append(tokenizer.convert_tokens_to_ids(window_tokens)) if not windows: return 0, 0.0 votes = [] confidences = [] for win_ids in windows: inputs = { 'input_ids': torch.tensor([win_ids]), 'attention_mask': torch.tensor([[1] * len(win_ids)]) } with torch.no_grad(): logits = model(**inputs) probs = torch.softmax(logits, dim=-1) pred = torch.argmax(probs, dim=-1).item() conf = probs[0, pred].item() votes.append(pred) confidences.append(conf) # 投票聚合(也可加权投票) from collections import Counter final_pred = Counter(votes).most_common(1)[0][0] avg_conf = sum(c for v, c in zip(votes, confidences) if v == final_pred) / \ max(1, sum(1 for v in votes if v == final_pred)) return final_pred, avg_conf # 更进一步:使用Longformer或BigBird等支持更长上下文的模型 # from transformers import LongformerForSequenceClassification # model = LongformerForSequenceClassification.from_pretrained( # 'allenai/longformer-base-4096')

优化建议:当处理超长文档(超过4096个token)时,滑动窗口+投票方法简单但有效。如果希望模型能捕捉跨窗口的信息,可以考虑使用Longformer(支持4096 token)、BigBird(支持4096+ token)或通过层次注意力机制将窗口级别的表示聚合为文档级别的表示。

三、命名实体识别(NER)

命名实体识别(Named Entity Recognition, NER)是从非结构化文本中识别出具有特定意义的实体,如人名、地名、组织名、时间、日期、金额等。NER是信息抽取的基础任务,广泛应用于知识图谱构建、搜索优化和内容分析。

3.1 BIO标注体系

在深度学习中,NER通常被建模为序列标注任务。BIO(Begin-Inside-Outside)是最常用的标注体系:B-PER表示人名开始,I-PER表示人名内部,O表示非实体。例如句子"张三在北京上班"对应的BIO标注为:“张[B-PER] 三[I-PER] 在[O] 北[B-LOC] 京[I-LOC] 上[O] 班[O]”。

# BIO标注示例 BIO_TAGS = ['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] tag2id = {t: i for i, t in enumerate(BIO_TAGS)} id2tag = {i: t for t, i in tag2id.items()} def encode_ner_example(tokens, labels, tokenizer, max_length=128): """将token和BIO标签编码为模型输入""" encoding = tokenizer( tokens, is_split_into_words=True, truncation=True, padding='max_length', max_length=max_length, return_tensors='pt' ) word_ids = encoding.word_ids() # 每个subtoken对应的原始token位置 label_ids = [] previous_word_idx = None for word_idx in word_ids: if word_idx is None: label_ids.append(-100) # 特殊token忽略 elif word_idx != previous_word_idx: label_ids.append(tag2id[labels[word_idx]]) else: # 同一个token的后续subtoken沿用BIO标签 label = labels[word_idx] label_ids.append(tag2id[label] if label != 'O' else tag2id[label]) previous_word_idx = word_idx encoding['labels'] = torch.tensor(label_ids) return encoding

3.2 BERT-CRF:序列标注的最佳实践

尽管BERT等预训练模型本身已经具有很强的序列标注能力,但直接使用BERT+Softmax进行NER存在一个明显缺陷:它无法捕捉标签之间的转移约束。例如,I-PER前面必须是B-PER或I-PER,而不应该出现I-PER紧跟在B-LOC后面的情况。CRF(条件随机场)层正是为了解决这个问题而引入的——它通过学习标签之间的转移矩阵来确保预测的标签序列符合语法约束。

import torch.nn as nn from torchcrf import CRF class BertCRFNER(nn.Module): def __init__(self, pretrained_model='bert-base-chinese', num_tags=len(BIO_TAGS)): super().__init__() self.bert = BertModel.from_pretrained(pretrained_model) self.dropout = nn.Dropout(0.1) self.classifier = nn.Linear(self.bert.config.hidden_size, num_tags) self.crf = CRF(num_tags, batch_first=True) self.num_tags = num_tags def forward(self, input_ids, attention_mask, labels=None): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) sequence_output = self.dropout(outputs.last_hidden_state) emissions = self.classifier(sequence_output) if labels is not None: # 训练模式:计算CRF负对数似然损失 mask = attention_mask.bool() loss = -self.crf(emissions, labels, mask=mask, reduction='mean') return loss else: # 推理模式:Viterbi解码得到最佳标注序列 mask = attention_mask.bool() predictions = self.crf.decode(emissions, mask=mask) return predictions # 使用示例 model = BertCRFNER() inputs = tokenizer(["张三在北京上班"], return_tensors='pt', padding=True) with torch.no_grad(): predictions = model(inputs['input_ids'], inputs['attention_mask']) # predictions: list of tag sequences print([id2tag[t] for t in predictions[0]])

3.3 嵌套NER与序列标注的局限

标准序列标注方法难以处理嵌套实体(Nested NER),即一个实体完全包含在另一个实体内部的情况。例如"上海交通大学医学院附属瑞金医院"中包含着"上海交通大学"(ORG)和"瑞金医院"(ORG)等多个层级。处理嵌套NER的常见方法包括:分层标注(为每一层实体分别标注)、基于跨度的方法(Span-based,直接预测所有可能的实体跨度)、基于超图或指针网络的方法

# 基于跨度(Span-based)的嵌套NER方法示例 class SpanNER(nn.Module): """使用边界预测和跨度分类进行嵌套NER""" def __init__(self, pretrained_model, num_entity_types): super().__init__() self.bert = BertModel.from_pretrained(pretrained_model) hidden = self.bert.config.hidden_size # 边界预测:每个token是实体的开始或结束 self.start_classifier = nn.Linear(hidden, 2) # B-start / O self.end_classifier = nn.Linear(hidden, 2) # B-end / O # 跨度分类:给定开始和结束位置,预测实体类型 self.span_classifier = nn.Linear(hidden * 2, num_entity_types) def forward(self, input_ids, attention_mask): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) hidden_states = outputs.last_hidden_state start_logits = self.start_classifier(hidden_states) end_logits = self.end_classifier(hidden_states) # 枚举所有可能的跨度(可过滤过长的候选) batch_size, seq_len, _ = hidden_states.shape spans = [] for b in range(batch_size): for i in range(seq_len): for j in range(i, min(i + 30, seq_len)): # max span length = 30 span_repr = torch.cat([ hidden_states[b, i], hidden_states[b, j] ]) spans.append(span_repr) spans_tensor = torch.stack(spans) # [N, hidden*2] span_type_logits = self.span_classifier(spans_tensor) # [N, num_types] return start_logits, end_logits, span_type_logits

实践建议:在实际项目中,如果嵌套实体不多,使用BERT-CRF已经可以满足大部分需求。对于嵌套实体较多的领域(如生物医学文本),推荐采用Span-based方法或分层序列标注方法。此外,利用外部知识库(如Wikidata)作为辅助信息可以显著提升实体识别的效果。

四、问答系统(QA)

基于Transformer的问答系统主要分为三类:抽取式问答(Extractive QA)、生成式问答(Generative QA)和多选式问答(Multiple-choice QA)。其中,抽取式问答是最成熟的应用形式,在SQuAD等基准数据集上已经达到了接近人类的水平。

4.1 SQuAD与BERT阅读理解

SQuAD(Stanford Question Answering Dataset)是抽取式问答的标准基准数据集。给定一段上下文和一个问题,模型需要在上下文中找出一段连续的文本作为答案。BERT通过在上方添加两个额外的输出层来实现阅读理解:一个预测答案的起始位置,另一个预测答案的结束位置。

from transformers import BertForQuestionAnswering, BertTokenizer import torch # 加载预训练的阅读理解模型 model_name = 'bert-large-uncased-whole-word-masking-finetuned-squad' model = BertForQuestionAnswering.from_pretrained(model_name) tokenizer = BertTokenizer.from_pretrained(model_name) # 推理函数 def answer_question(question, context): inputs = tokenizer( question, context, return_tensors='pt', max_length=384, truncation='only_second', stride=128, return_overflowing_tokens=True ) with torch.no_grad(): outputs = model(**inputs) # 获取最佳答案跨度 start_logits = outputs.start_logits end_logits = outputs.end_logits # 约束:start <= end,且span长度合理 start_idx = torch.argmax(start_logits) end_idx = torch.argmax(end_logits) # 如果end < start,选择次优组合 if end_idx < start_idx: start_probs = torch.softmax(start_logits, dim=-1) end_probs = torch.softmax(end_logits, dim=-1) best_score = -1 best_span = (0, 0) for s in range(len(start_probs[0])): for e in range(s, min(s + 30, len(end_probs[0]))): score = start_probs[0, s].item() * end_probs[0, e].item() if score > best_score: best_score = score best_span = (s, e) start_idx, end_idx = best_span # 将token ID解码为文本 answer_tokens = inputs['input_ids'][0][start_idx:end_idx + 1] answer = tokenizer.decode(answer_tokens, skip_special_tokens=True) return answer # 使用示例 context = "Transformer模型由Vaswani等人在2017年提出,彻底改变了自然语言处理领域。" question = "Transformer是什么时候提出的?" answer = answer_question(question, context) print(f"答案: {answer}")

4.2 答案边界预测与置信度

在抽取式问答中,精确预测答案的边界至关重要。常见的策略包括:可学习边界偏移(在BERT输出上额外添加一个边界调整层)、跨度评分(对每个可能的开始-结束对打分)、指针网络(使用注意力机制动态预测边界)。置信度评估通常基于Softmax概率的乘积或模型输出的logits差值,用于过滤低质量的预测。

def answer_with_confidence(question, context, model, tokenizer, max_answer_len=30): inputs = tokenizer(question, context, return_tensors='pt', max_length=384, truncation=True) with torch.no_grad(): outputs = model(**inputs) start_logits = outputs.start_logits[0] end_logits = output.end_logits[0] # 使用Softmax获取概率 start_probs = torch.softmax(start_logits, dim=-1) end_probs = torch.softmax(end_logits, dim=-1) # 枚举所有有效跨度,计算联合概率 best_score = -float('inf') best_start = best_end = 0 for start in range(len(start_probs)): for end in range(start, min(start + max_answer_len, len(end_probs))): score = start_probs[start].item() * end_probs[end].item() if score > best_score: best_score = score best_start = start best_end = end # 解码答案 answer_tokens = inputs['input_ids'][0][best_start:best_end + 1] answer = tokenizer.decode(answer_tokens, skip_special_tokens=True) # 置信度阈值过滤 confidence = best_score return answer, confidence # 置信度过滤示例 if confidence > 0.5: print(f"高置信度答案: {answer}") elif confidence > 0.1: print(f"低置信度答案(需要人工复核): {answer}") else: print("模型无法确定答案")

关键挑战:在真实场景中,问答系统面临的挑战包括:答案不在给定的上下文中(不可回答问题)、需要跨句推理、需要多步推理(Multi-hop QA,如HotpotQA数据集)。对于不可回答问题,可以通过添加一个"无答案阈值"来判断——如果最佳跨度的置信度低于某个阈值,则判定为不可回答。

五、文本摘要

文本摘要旨在自动将长文本压缩为简洁的摘要,保留关键信息。基于Transformer的方法分为两大类:抽取式摘要(Extractive Summarization)从原文中选取重要句子组成摘要,生成式摘要(Abstractive Summarization)则通过语言模型生成全新的摘要文本。

5.1 抽取式 vs 生成式对比

抽取式摘要

优点:事实保真度高,不会产生原文中没有的信息;实现相对简单

缺点:灵活性差,无法重新组织语言;摘要连贯性可能不佳

代表模型:BERTSUM、MatBiLSTM

生成式摘要

优点:灵活度高,可以生成流畅、简洁的摘要;能进行信息融合和重述

缺点:可能产生事实错误(Hallucination);需要更大的模型

代表模型:BART、PEGASUS、Longformer-Encoder-Decoder

5.2 BART与PEGASUS

BART和PEGASUS是生成式摘要的两个代表性模型。BART采用了去噪自编码器的预训练方式,结合了BERT的双向编码器和GPT的自回归解码器,特别适合文本生成任务。PEGASUS则提出了Gap Sentence Generation(GSG)预训练目标——在预训练阶段直接遮蔽重要句子让模型生成,这使得它天生就适合摘要生成任务。

from transformers import BartForConditionalGeneration, BartTokenizer from transformers import PegasusForConditionalGeneration, PegasusTokenizer # BART生成摘要 def summarize_with_bart(text, max_input_length=1024, max_output_length=150): model_name = 'facebook/bart-large-cnn' tokenizer = BartTokenizer.from_pretrained(model_name) model = BartForConditionalGeneration.from_pretrained(model_name) inputs = tokenizer( text, return_tensors='pt', max_length=max_input_length, truncation=True ) summary_ids = model.generate( inputs['input_ids'], num_beams=4, max_length=max_output_length, min_length=30, length_penalty=2.0, early_stopping=True, no_repeat_ngram_size=3 ) summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True) return summary # PEGASUS生成摘要 def summarize_with_pegasus(text): model_name = 'google/pegasus-xsum' tokenizer = PegasusTokenizer.from_pretrained(model_name) model = PegasusForConditionalGeneration.from_pretrained(model_name) inputs = tokenizer(text, return_tensors='pt', max_length=512, truncation=True) summary_ids = model.generate(inputs['input_ids']) summary = tokenizer.decode(summary_ids[0], skip_special_tokens=True) return summary text = "Transformer模型由Vaswani等人在2017年提出,...(长文本)" print("BART摘要:", summarize_with_bart(text)) print("PEGASUS摘要:", summarize_with_pegasus(text))

5.3 Longformer与长文档摘要

标准Transformer的self-attention计算复杂度是O(n²),使其难以处理长文档。Longformer通过引入稀疏注意力机制(Dilated Sliding Window Attention + Global Attention),将复杂度降低到O(n),从而支持长达4096个token的输入。对于需要处理书籍、论文等超长文档的场景,Longformer-Encoder-Decoder(LED)是一个理想的选择。

from transformers import LongformerModel, AutoTokenizer class LongformerSummarizer: def __init__(self, model_name='allenai/longformer-base-4096'): self.tokenizer = AutoTokenizer.from_pretrained(model_name) self.model = LongformerModel.from_pretrained(model_name) # 注意:Longformer需要设置global attention的token位置 # 通常对[CLS] token和问题中的关键token设置global attention def process_long_document(self, document, max_length=4096): # 用sliding window编码 encoded = self.tokenizer( document, return_tensors='pt', max_length=max_length, truncation=True, padding='max_length' ) # 配置global attention:前2个token([CLS]和[SEP])设置为全局注意力 attention_mask = encoded['attention_mask'] global_attention_mask = torch.zeros_like(attention_mask) global_attention_mask[:, :2] = 1 outputs = self.model( input_ids=encoded['input_ids'], attention_mask=attention_mask, global_attention_mask=global_attention_mask ) return outputs.last_hidden_state # 对于超长文档,还可以使用分层摘要策略: # 1. 将文档分成多个chunk,每chunk独立生成摘要 # 2. 将各chunk的摘要拼接成新的"摘要文档" # 3. 对摘要文档再次进行摘要,直到满足长度要求 def hierarchical_summarize(document, chunk_size=2000, max_summary_len=500): """分层摘要:先分块摘要,再合批摘要""" chunks = [document[i:i+chunk_size] for i in range(0, len(document), chunk_size)] chunk_summaries = [] for chunk in chunks: summary = summarize_with_bart(chunk) # 使用BART生成chunk摘要 chunk_summaries.append(summary) # 合并所有chunk摘要进行最终摘要 combined = " ".join(chunk_summaries) final_summary = summarize_with_bart( combined, max_input_length=1024, max_output_length=max_summary_len ) return final_summary

模型选择建议:对于新闻类短文本(500-2000字),BART-large和PEGASUS效果最佳;对于论文和报告(2000-10000字),推荐使用Longformer-Encoder-Decoder(LED);对于书籍等超长文本(10万字以上),需要结合分层摘要策略或使用专门的长文档摘要模型。

六、机器翻译

机器翻译(Machine Translation, MT)是NLP最早的应用之一。从早期的统计机器翻译(SMT)到神经机器翻译(NMT),再到如今基于Transformer的大规模多语种翻译模型,机器翻译的质量已经取得了质的飞跃。

6.1 M2M-100:真正的多语种翻译

传统翻译模型通常需要为每对语言单独训练(如英中、英日、中法),这导致资源匮乏的语言对翻译质量较差。Facebook AI在2020年发布的M2M-100(Many-to-Many)是一个支持100种语言的翻译模型,它采用统一的编码器-解码器架构,在不同语言之间共享参数,并引入语言标签(Language Tokens)来指示源语言和目标语言。这使得模型可以充分利用高资源语言的训练信号来提升低资源语言的翻译质量。

from transformers import M2M100ForConditionalGeneration, M2M100Tokenizer class MultilingualTranslator: def __init__(self, model_name='facebook/m2m100_418M'): self.tokenizer = M2M100Tokenizer.from_pretrained(model_name) self.model = M2M100ForConditionalGeneration.from_pretrained(model_name) def translate(self, text, src_lang='zh', tgt_lang='en'): """多语种翻译函数 Args: text: 输入文本 src_lang: 源语言代码 (zh, en, ja, fr, de, ...) tgt_lang: 目标语言代码 Returns: 翻译结果文本 """ # 设置源语言 self.tokenizer.src_lang = src_lang encoded = self.tokenizer(text, return_tensors='pt', max_length=256, truncation=True) # 设置目标语言ID用于强制解码 forced_bos_token_id = self.tokenizer.get_lang_id(tgt_lang) generated_tokens = self.model.generate( **encoded, forced_bos_token_id=forced_bos_token_id, num_beams=5, max_length=256, length_penalty=1.0 ) result = self.tokenizer.decode(generated_tokens[0], skip_special_tokens=True) return result # 使用示例 translator = MultilingualTranslator() # 中译英 zh_text = "Transformer架构彻底改变了自然语言处理领域。" en_result = translator.translate(zh_text, 'zh', 'en') print(f"中译英: {en_result}") # 英译日 en_text = "Deep learning has revolutionized artificial intelligence." ja_result = translator.translate(en_text, 'en', 'ja') print(f"英译日: {ja_result}") # 中译法 fr_result = translator.translate(zh_text, 'zh', 'fr') print(f"中译法: {fr_result}")

6.2 Translation Quality Estimation

机器翻译质量估计(Translation Quality Estimation, QE)是一个不同于翻译质量评估的任务。QE的目标是在没有参考答案的情况下,预测机器翻译输出的质量分数。这对于实际部署翻译系统至关重要——当QE分数较低时,可以触发人工校对或切换反向翻译流程。

# 使用OpenKiwi进行质量估计(伪代码) from transformers import AutoModelForSequenceClassification, AutoTokenizer class TranslationQualityEstimator: """基于Pretrained模型的翻译质量估计""" def __init__(self): # 使用XLM-R等跨语言模型进行QE self.model = AutoModelForSequenceClassification.from_pretrained( 'microsoft/infoxlm-large', num_labels=1 ) self.tokenizer = AutoTokenizer.from_pretrained('microsoft/infoxlm-large') def estimate_quality(self, source, translation): """估计翻译质量(返回HTER预测分数,越低越好)""" inputs = self.tokenizer( source, translation, return_tensors='pt', truncation=True, max_length=256, padding=True ) with torch.no_grad(): score = self.model(**inputs).logits.squeeze().item() # 将分数映射到[0, 1]区间,接近1表示高质量 quality_score = 1.0 / (1.0 + np.exp(-score)) return quality_score def quality_to_label(self, score): if score >= 0.8: return "高质量,可直接使用" elif score >= 0.5: return "中等质量,建议人工校对" else: return "低质量,需要重新翻译或人工重写"

最佳实践:在实际的翻译系统中,建议采用回译验证(将翻译结果译回源语言,比较与原句的语义相似度)和质量估计模型相结合的方式进行质量控制。对于专业领域(如法律、医学),建议对翻译模型进行领域微调(Domain Adaptation),可以使用少量高质量的领域平行语料。

七、文本生成与对话系统

Transformer的诞生极大地推动了文本生成和对话系统的发展。从GPT系列到LLaMA、Vicuna,大规模语言模型已经能够生成令人惊叹的连贯、有创造力的文本。对话系统也从早期的检索式、任务型演变为基于大语言模型的开放域对话。

7.1 BlenderBot与开放域对话

Meta AI的BlenderBot系列是将多种对话技能(知识、共情、个性)融入单一模型的代表性工作。BlenderBot通过在大规模对话数据和知识库上的多任务训练,实现了更有深度、更富人情味的对话体验。

from transformers import BlenderbotTokenizer, BlenderbotForConditionalGeneration class OpenDomainChatbot: def __init__(self, model_name='facebook/blenderbot-400M-distill'): self.tokenizer = BlenderbotTokenizer.from_pretrained(model_name) self.model = BlenderbotForConditionalGeneration.from_pretrained(model_name) def chat(self, utterance, conversation_history=None): """ 与机器人对话 Args: utterance: 用户输入 conversation_history: 对话历史列表 ["user: ...", "bot: ...", ...] Returns: 机器人回复 """ if conversation_history: # 将对话历史拼接到当前输入 history_str = " ".join(conversation_history[-5:]) # 保留最近5轮 inputs = self.tokenizer( [history_str, utterance], return_tensors='pt', truncation=True, max_length=256 ) else: inputs = self.tokenizer([utterance], return_tensors='pt', truncation=True, max_length=256) reply_ids = self.model.generate( **inputs, do_sample=True, temperature=0.7, top_p=0.9, max_length=128, repetition_penalty=1.03 ) reply = self.tokenizer.decode(reply_ids[0], skip_special_tokens=True) return reply # 多轮对话示例 bot = OpenDomainChatbot() history = [] user_inputs = [ "你好!今天天气真好啊。", "我喜欢读科幻小说,你有推荐吗?", "最近在看《三体》,非常精彩!" ] for user_input in user_inputs: reply = bot.chat(user_input, history) history.append(f"user: {user_input}") history.append(f"bot: {reply}") print(f"用户: {user_input}") print(f"机器人: {reply}") print()

7.2 LLaMA与Vicuna:开源大模型的崛起

Meta在2023年发布的LLaMA(Large Language Model Meta AI)系列模型在开源社区引起了巨大反响。LLaMA虽然参数量不如GPT-3,但通过在更多数据上训练,实现了卓越的性能。更值得注意的是,LLaMA通过指令微调(Instruction Tuning)和RLHF(基于人类反馈的强化学习)可以演变为对话模型。Vicuna是基于LLaMA微调的对话模型,通过对ShareGPT数据集进行微调,以较少的计算资源达到了接近ChatGPT的水平。

# 使用HuggingFace加载LLaMA系列模型 from transformers import LlamaForCausalLM, LlamaTokenizer import torch class LLaMAInference: def __init__(self, model_name='meta-llama/Llama-2-7b-chat-hf'): self.tokenizer = LlamaTokenizer.from_pretrained(model_name) self.model = LlamaForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16, # 半精度推理节省显存 device_map='auto' ) def generate_response(self, prompt, system_prompt=None): """LLaMA格式的对话生成""" if system_prompt: full_prompt = ( f"<s>[INST] <<SYS>>\n{system_prompt}\n<</SYS>>\n\n" f"{prompt} [/INST]" ) else: full_prompt = f"<s>[INST] {prompt} [/INST]" inputs = self.tokenizer(full_prompt, return_tensors='pt') outputs = self.model.generate( inputs['input_ids'].to('cuda'), max_new_tokens=512, do_sample=True, temperature=0.7, top_p=0.9, repetition_penalty=1.1 ) response = self.tokenizer.decode( outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True ) return response.strip() # 对话示例 llama = LLaMAInference() response = llama.generate_response( "请用通俗的语言解释一下Transformer的自注意力机制。", system_prompt="你是一个AI知识助手,用简洁易懂的语言回答问题。" ) print(response)

7.3 对话上下文处理策略

在实际部署对话系统时,上下文管理是一个关键挑战。LLM的输入长度是有限的(如4096或8192 token),但对话可能持续很长时间。常见的上下文处理策略包括:滑动窗口截断(保留最近的K轮对话)、摘要压缩(定期对历史对话进行摘要压缩)、检索增强(从完整历史中检索与当前问题最相关的部分)。

class ConversationManager: """对话上下文管理器 - 处理长对话的上下文策略""" def __init__(self, max_turns=10, max_tokens=2048, summary_model=None): self.history = [] # [(role, content), ...] self.max_turns = max_turns self.max_tokens = max_tokens self.summary_model = summary_model def add_turn(self, role, content): self.history.append((role, content)) def get_context(self, current_query=None): """获取当前对话上下文""" total_tokens = 0 context_parts = [] # 策略1:滑动窗口(保留最近N轮) recent_history = self.history[-self.max_turns:] # 策略2:Token计数截断 for role, content in reversed(recent_history): estimated_tokens = len(content) * 1.5 # 粗略token估算 if total_tokens + estimated_tokens > self.max_tokens: break context_parts.insert(0, f"{role}: {content}") total_tokens += estimated_tokens return "\n".join(context_parts) def summarize_if_needed(self): """策略3:历史过长时进行摘要压缩""" if len(self.history) > self.max_turns * 2 and self.summary_model: # 对早期历史进行摘要 early_history = self.history[:-self.max_turns] history_text = "\n".join( [f"{r}: {c}" for r, c in early_history] ) summary = self.summary_model.generate(history_text) # 用摘要替换早期历史 self.history = [("system", f"对话历史摘要: {summary}")] + \ self.history[-self.max_turns:] def get_retrieval_context(self, query, retriever, top_k=3): """策略4:检索增强 - 从完整历史检索相关部分""" all_queries = [c for r, c in self.history if r == 'user'] relevant_idx = retriever.search(query, all_queries, top_k=top_k) context = [] for idx in relevant_idx: turn_idx = [i for i, (r, c) in enumerate(self.history) if r == 'user'][idx] if turn_idx + 1 < len(self.history): context.append(self.history[turn_idx]) context.append(self.history[turn_idx + 1]) return context

上下文管理建议:在大模型对话系统中,推荐采用混合策略:对于短对话使用全量上下文,对于长对话采用滑动窗口+检索增强的组合。检索增强方法特别适合知识密集型的对话场景(如客服、教育),可以精确找到历史中相关的对话内容。

八、HuggingFace Pipeline与模型部署

HuggingFace Transformers库提供了Pipeline API,极大地简化了NLP模型的使用流程。通过Pipeline,开发者可以在极少的代码行数内完成文本分类、NER、问答、翻译、摘要等任务。更关键的是,Pipeline自动处理了tokenization、batch processing、device placement等繁琐的细节。

8.1 Pipeline高级用法

from transformers import pipeline import torch # ========== 基础Pipeline使用 ========== # 情感分类 classifier = pipeline( 'sentiment-analysis', model='distilbert-base-uncased-finetuned-sst-2-english', device=0 if torch.cuda.is_available() else -1, batch_size=32 # 批量推理加速 ) results = classifier([ "This product is amazing!", "I am very disappointed with the service.", "It is okay, nothing special." ]) for text, result in zip(texts, results): print(f"{text} -> {result['label']}: {result['score']:.4f}") # NER Pipeline ner_pipeline = pipeline( 'ner', model='dbmdz/bert-large-cased-finetuned-conll03-english', aggregation_strategy='simple' # 合并同实体的多个subtoken ) entities = ner_pipeline("Apple Inc. was founded by Steve Jobs in Cupertino.") for ent in entities: print(f"{ent['word']}: {ent['entity_group']} (score: {ent['score']:.3f})") # 问答Pipeline qa_pipeline = pipeline( 'question-answering', model='deepset/roberta-base-squad2' ) result = qa_pipeline({ 'question': "What is the capital of France?", 'context': "France is a country in Europe. Its capital is Paris." }) print(f"Answer: {result['answer']} (confidence: {result['score']:.3f})") # 文本摘要Pipeline summarizer = pipeline( 'summarization', model='facebook/bart-large-cnn', device=0, min_length=30, max_length=150 ) summary = summarizer(long_text) print(summary[0]['summary_text'])

8.2 自定义Pipeline与模型集成

除了使用预定义的Pipeline,开发者可以创建自定义Pipeline来实现复杂的推理逻辑。例如,组合多个模型完成一个复合任务:先使用NER模型抽取实体,再使用QA模型对每个实体进行属性提取,最终输出结构化的信息。

from transformers import pipeline from dataclasses import dataclass, field from typing import List, Dict @dataclass class EntityInfo: name: str type: str attributes: Dict[str, str] = field(default_factory=dict) class CustomNERQAPipeline: """自定义Pipeline:NER + 属性问答""" def __init__(self): self.ner = pipeline('ner', aggregation_strategy='simple') self.qa = pipeline('question-answering') def extract_entities_with_attributes(self, text, attribute_questions): """ 提取实体及其属性 Args: text: 输入文本 attribute_questions: dict mapping entity type to list of (attr_name, question) """ # 第一步:识别实体 raw_entities = self.ner(text) entities = [] seen = set() for ent in raw_entities: if ent['word'] not in seen: entities.append(EntityInfo( name=ent['word'], type=ent['entity_group'] )) seen.add(ent['word']) # 第二步:为每个实体提取属性 for entity in entities: if entity.type in attribute_questions: for attr_name, question in attribute_questions[entity.type]: q = question.format(entity=entity.name) result = self.qa({ 'question': q, 'context': text }) if result['score'] > 0.3: # 置信度过滤 entity.attributes[attr_name] = result['answer'] return entities # 使用示例 text = ("Tesla CEO Elon Musk announced the new Cybertruck pricing. " "The company's headquarters are in Austin, Texas.") attr_questions = { 'PER': [ ("position", "What is the position of {entity}?") ], 'ORG': [ ("founded_by", "Who founded {entity}?"), ("headquarters", "Where is {entity}'s headquarters?") ] } pipeline_custom = CustomNERQAPipeline() entities = pipeline_custom.extract_entities_with_attributes(text, attr_questions) for e in entities: print(f"{e.name} ({e.type}): {e.attributes}")

8.3 模型部署与推理优化

将Transformer模型部署到生产环境需要考虑推理速度、显存占用和吞吐量。常用的优化技术包括:模型量化(FP16/INT8/INT4)、KV Cache(减少自回归解码中的重复计算)、批处理(动态batching)、模型蒸馏(使用小模型模拟大模型行为)、ONNX Runtime(跨平台优化推理引擎)以及vLLM / TensorRT-LLM等专门的推理加速框架。

# ========== 模型量化 ========== from transformers import AutoModelForSequenceClassification, AutoTokenizer import torch # FP16半精度量化 model = AutoModelForSequenceClassification.from_pretrained('bert-base-chinese') model = model.half() # 转换为FP16,显存减半 model.to('cuda') # INT8动态量化(CPU推理优化) quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) # ========== ONNX导出与推理 ========== import torch.onnx import onnxruntime as ort def export_to_onnx(model, tokenizer, output_path='model.onnx'): """将PyTorch模型导出为ONNX格式""" dummy_input = tokenizer( "测试输入文本", return_tensors='pt', max_length=128, padding='max_length', truncation=True ) torch.onnx.export( model, (dummy_input['input_ids'], dummy_input['attention_mask']), output_path, input_names=['input_ids', 'attention_mask'], output_names=['logits'], dynamic_axes={ 'input_ids': {0: 'batch_size', 1: 'sequence_length'}, 'attention_mask': {0: 'batch_size', 1: 'sequence_length'} }, opset_version=14 ) print(f"模型已导出到 {output_path}") # ONNX Runtime推理 class ONNXInference: def __init__(self, onnx_path): self.session = ort.InferenceSession(onnx_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) def predict(self, input_ids, attention_mask): outputs = self.session.run( ['logits'], { 'input_ids': input_ids.numpy(), 'attention_mask': attention_mask.numpy() } ) return torch.from_numpy(outputs[0]) # ========== Flask部署示例 ========== from flask import Flask, request, jsonify app = Flask(__name__) class ModelServer: def __init__(self): self.classifier = pipeline( 'sentiment-analysis', model='distilbert-base-uncased-finetuned-sst-2-english' ) def predict_batch(self, texts): """批量预测接口""" results = self.classifier(texts, batch_size=32) return [{ 'text': t, 'label': r['label'], 'score': r['score'] } for t, r in zip(texts, results)] server = ModelServer() @app.route('/predict', methods=['POST']) def predict(): data = request.json texts = data.get('texts', []) if not texts: return jsonify({'error': 'No texts provided'}), 400 results = server.predict_batch(texts) return jsonify({'results': results}) @app.route('/health', methods=['GET']) def health(): return jsonify({'status': 'ok'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

部署策略总结:
- 低延迟场景(实时对话):使用量化模型(INT8/INT4)+ 预填充KV Cache + 流式输出
- 高吞吐场景(批量离线处理):使用FP16模型 + 大batch size + 动态batching
- 边缘部署(手机/IoT):使用蒸馏小模型(DistilBERT/TinyBERT/T5-Small)+ ONNX Runtime + 量化
- 大模型部署(LLaMA-70B等):使用vLLM + Tensor Parallel + PagedAttention

九、总结与实践建议

Transformer在NLP中的核心地位:从我们讨论的文本分类、NER、问答系统、文本摘要、机器翻译到对话系统,Transformer架构已经渗透到NLP的每一个角落。自注意力机制的多功能性和可扩展性使其成为NLP乃至整个深度学习领域的基础设施。

模型选型指南

任务推荐模型关键考量
文本分类BERT / RoBERTa / DistilBERT精度需求、推理速度
NERBERT-CRF / RoBERTa-CRF标签约束、嵌套实体
问答系统BERT-SQuAD / RoBERTa-SQuAD文档长度、置信度阈值
文本摘要BART / PEGASUS / LED生成长度、事实一致性
机器翻译M2M-100 / NLLB / mBART语言对、领域适配
文本生成LLaMA / Mistral / GPT推理成本、上下文窗口

核心技术演进方向

学习路线建议

入门阶段:掌握HuggingFace Transformers基础Pipeline用法,理解Transformer自注意力机制的核心原理,能够使用预训练模型完成文本分类和NER等基础任务。

进阶阶段:深入理解BERT/GPT的预训练和微调原理,掌握模型量化、蒸馏、ONNX导出等部署技术,学习多任务学习、Prompt Engineering等高级技巧。

高阶阶段:研究Flash Attention、PagedAttention等底层优化技术,掌握大模型微调技术(LoRA/QLoRA/AdaLoRA),探索多模态Transformer和Agent系统设计。

持续学习资源推荐:HuggingFace官方课程(免费)、The Annotated Transformer(代码级解读)、CS224N(Stanford NLP课程)、LLM University by Cohere、以及各大顶会论文(ACL/EMNLP/NAACL/NeurIPS)。保持阅读最新论文的习惯,关注HuggingFace Blog和社区动态。