基于HuggingFace预训练BERT微调时遇AttributeError问题求助
问题原因分析
错误核心在于:HuggingFace的BERT模型返回的是BaseModelOutputWithPoolingAndCrossAttentions对象(包含last_hidden_state、pooler_output等属性),而fastai的损失函数期望接收PyTorch张量,直接调用transpose方法会因对象类型不匹配报错。此外还有两个潜在问题:
- 自定义Tokenizer实现存在缺陷
- 标准
TextDataBunch无法提供BERT所需的attention_mask等输入参数
解决方案
1. 封装BERT模型,返回张量输出
创建自定义模型类,包裹HuggingFace的BERT模型,提取任务所需的张量(如分类任务的logits)返回给fastai:
import torch.nn as nn from transformers import BertModel class BertForFastAI(nn.Module): def __init__(self, num_classes): super().__init__() self.bert = BertModel.from_pretrained("bert-base-uncased") # 根据任务定义分类头,这里以二分类为例 self.classifier = nn.Linear(self.bert.config.hidden_size, num_classes) def forward(self, input_ids, attention_mask=None): # 获取BERT输出对象 outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) # 使用pooler_output作为句子表征 pooled_output = outputs.pooler_output # 计算分类logits并返回张量 logits = self.classifier(pooled_output) return logits
2. 修正Tokenizer实现
替换自定义Tokenizer为fastai兼容的版本,确保正确添加BERT要求的[CLS]和[SEP]标记:
from fastai.text import Tokenizer, Vocab from transformers import BertTokenizer from fastai.text.transform import BaseTokenizer class CustomBertTokenizer(BaseTokenizer): def __init__(self, pretrained_tokenizer, max_seq_len=128, **kwargs): self.tokenizer = pretrained_tokenizer self.max_seq_len = max_seq_len def tokenize(self, t): # 添加CLS和SEP标记,并限制序列长度 tokens = ["[CLS]"] + self.tokenizer.tokenize(t)[:self.max_seq_len-2] + ["[SEP]"] return tokens # 初始化预训练Tokenizer bert_tok = BertTokenizer.from_pretrained("bert-base-uncased") # 创建fastai兼容的Tokenizer tokenizer = Tokenizer(tok_func=CustomBertTokenizer(bert_tok, max_seq_len=128), pre_rules=[], post_rules=[]) # 复用BERT的词汇表 vocab = Vocab(list(bert_tok.vocab.keys()))
3. 构建包含Attention Mask的DataBunch
标准TextDataBunch无法生成BERT所需的attention_mask,需要自定义数据处理流程:
from fastai.data_block import DataBunch, ItemList import torch def get_attention_mask(input_ids, pad_token_id=0): # 生成attention_mask:1表示有效token,0表示padding return (input_ids != pad_token_id).long() # 自定义ItemList处理文本数据 class BertTextItemList(ItemList): def __init__(self, items, tokenizer, vocab, max_seq_len=128, **kwargs): super().__init__(items, **kwargs) self.tokenizer = tokenizer self.vocab = vocab self.max_seq_len = max_seq_len def get(self, i): text = self.items[i] tokens = self.tokenizer.tokenize(text) # 转换为数字id input_ids = self.vocab.numericalize(tokens) # 补全到max_seq_len input_ids = torch.cat([input_ids, torch.zeros(self.max_seq_len - len(input_ids), dtype=torch.long)]) # 生成attention_mask attention_mask = get_attention_mask(input_ids, pad_token_id=self.vocab.stoi["[PAD]"]) return (input_ids, attention_mask) # 构建DataBunch train_list = BertTextItemList(train_df['text'], tokenizer=tokenizer, vocab=vocab) valid_list = BertTextItemList(val_df['text'], tokenizer=tokenizer, vocab=vocab) test_list = BertTextItemList(test_df['text'], tokenizer=tokenizer, vocab=vocab) # 绑定标签 train_list = train_list.label_from_df(train_df, cols='label') valid_list = valid_list.label_from_df(val_df, cols='label') test_list = test_list.label_from_df(test_df, cols='label') # 创建DataBunch data_bunch = DataBunch.create(train_list, valid_list, test_list, bs=8)
4. 初始化Learner并启动训练
from fastai.basic_train import Learner from fastai.metrics import accuracy # 初始化模型,假设是二分类任务 model = BertForFastAI(num_classes=2) # 创建Learner,指定损失函数 learner = Learner(data_bunch, model, loss_func=nn.CrossEntropyLoss(), metrics=[accuracy]) # 启动训练 learner.fit_one_cycle(1)
关键说明
- 必须将HuggingFace模型的输出对象转换为张量,才能被fastai的损失函数处理
- BERT模型需要
attention_mask区分有效token和padding,这是标准文本DataBunch缺失的 - Tokenizer必须正确添加
[CLS]和[SEP]标记,符合BERT的输入格式要求
内容的提问来源于stack exchange,提问作者Adwaith
相关产品推荐
相关产品推荐

