【深度学习实践】 预训练与迁移学习

以下内容将系统地阐述预训练模型(Pretrained Model)迁移学习(Transfer Learning)的核心概念及常用方法,展示如何在不同领域利用已有模型进行微调,加速模型开发并提升性能。文末给出三个实战案例,分别覆盖图像分类、文本分类和目标检测,并附有详细的示例代码和解释,帮助读者在实际项目中快速上手。


一、预训练模型与迁移学习

1. 预训练模型的概念

  • 定义:预训练模型是指在一个大规模数据集(如ImageNet、COCO,或海量文本数据)上提前训练好的深度网络。它在原始数据上学到了一般性的特征表示(如图像的边缘、纹理或语言的词向量与上下文),从而在某些下游任务上能够带来更好的初始性能。
  • 好处
    1. 减少计算成本:无需从头训练一个大模型,可极大缩短训练时间。
    2. 降低数据需求:在下游任务数据较少时,也可得益于模型在大型数据集上学到的通用特征。
    3. 提升模型性能:通常预训练模型相比随机初始化能在准确率、收敛速度等方面都有显著优势。

2. 迁移学习的核心方法

迁移学习的主要目标是:将已有模型(或知识)迁移到新的数据集或新任务上。常见策略如下:

  1. 特征提取(Feature Extraction)

    • 将预训练模型的主体部分(卷积层或Transformer编码器等)作为固定的特征提取器,不更新这些部分的权重,仅在其上加一个新的“分类层/回归层”,只训练这个新层的参数。
    • 适合新任务数据量较小、且预训练网络学到的特征能较好泛化的场景。
  2. 微调(Fine-tuning)

    • 在预训练权重的基础上,对新任务的整个网络或部分层进行继续训练。
    • 在数据量相对充足且任务与预训练任务相关性较高时,微调通常能获得更好的结果。
    • 常见做法是**“先冻结大部分网络层,只训练最后几层”**,或者采用分段学习率配置,再逐步解冻更多层进行精调。
  3. 混合策略

    • 结合特征提取和微调的思想,先冻结大部分层只更新最后几层,之后再解冻更多的网络层进行小学习率微调。
    • 也可以针对不同层设置不同的学习率,对越接近输出层的权重赋予更大的学习率。
  4. 其他相关策略

    • Domain Adaptation:着重减少源域数据与目标域数据的分布差异(如图像风格、文本领域不同等)。
    • Few-Shot / Zero-Shot:当目标任务数据极少或几乎没有标注时,依靠大模型的通用特征或Prompt等方法进行迁移。

二、三个不同领域的落地项目示例

下面给出三个具体案例,分别涵盖:

  1. 图像分类:使用PyTorch中预训练的ResNet18进行微调。
  2. 文本分类:使用Hugging Face的BERT模型做情感分析。
  3. 目标检测:使用YOLOv5在自定义数据集上进行微调。

每个项目都会提供示例代码详细注释,帮助读者快速掌握迁移学习在实际场景中的应用。


项目一:图像分类(PyTorch + ResNet18)

1. 项目背景
  • 任务场景:假设我们需要对一个包含猫和狗图片的小型数据集(或其他自定义二分类数据集)进行分类。
  • 挑战:数据集规模不大,直接从头训练一个深层模型可能导致过拟合、收敛慢。
  • 解决方案:使用在ImageNet上预训练的ResNet18,并对其进行微调。
2. 项目代码(示例)

下面示例使用PyTorch进行训练;你可在本地或Colab笔记本中运行。

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, models, transforms
import os
from torch.utils.data import DataLoader

# -------------------
# 1. 数据准备
# -------------------
# 假设我们的训练数据集结构如下:
# data/
#   train/
#       cats/
#           cat001.jpg
#           cat002.jpg
#           ...
#       dogs/
#           dog001.jpg
#           dog002.jpg
#           ...
#   val/
#       cats/
#       dogs/
# 你可以根据实际需求修改路径或类别。

data_dir = 'data'
train_dir = os.path.join(data_dir, 'train')
val_dir = os.path.join(data_dir, 'val')

# 图像预处理 & 数据增强
train_transforms = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                         std=[0.229, 0.224, 0.225])
])

val_transforms = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                         std=[0.229, 0.224, 0.225])
])

train_dataset = datasets.ImageFolder(root=train_dir, transform=train_transforms)
val_dataset = datasets.ImageFolder(root=val_dir, transform=val_transforms)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)

# -------------------
# 2. 加载预训练模型 & 修改最后分类层
# -------------------
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 使用 torchvision.models 提供的预训练ResNet18
model = models.resnet18(pretrained=True)  
# 冻结前面层的参数(可选)
for param in model.parameters():
    param.requires_grad = False

# 替换最后的全连接层,根据自己的类别数来设置out_features
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 2)  # 2表示猫和狗二分类
model = model.to(device)

# -------------------
# 3. 定义损失函数与优化器
# -------------------
criterion = nn.CrossEntropyLoss()
# 只优化最后那一层 (model.fc) 的参数,因为我们冻结了其它部分
optimizer = optim.Adam(model.fc.parameters(), lr=0.001)

# -------------------
# 4. 训练循环
# -------------------
num_epochs = 5

for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    running_corrects = 0

    for inputs, labels in train_loader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        _, preds = torch.max(outputs, 1)
        running_loss += loss.item() * inputs.size(0)
        running_corrects += torch.sum(preds == labels.data).item()

    epoch_loss = running_loss / len(train_dataset)
    epoch_acc = running_corrects / len(train_dataset)
    print(f"Epoch [{epoch+1}/{num_epochs}]  Train Loss: {epoch_loss:.4f}, Train Acc: {epoch_acc:.4f}")

    # 验证过程
    model.eval()
    val_loss = 0.0
    val_corrects = 0

    with torch.no_grad():
        for inputs, labels in val_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            loss = criterion(outputs, labels)

            _, preds = torch.max(outputs, 1)
            val_loss += loss.item() * inputs.size(0)
            val_corrects += torch.sum(preds == labels.data).item()

    val_loss = val_loss / len(val_dataset)
    val_acc = val_corrects / len(val_dataset)
    print(f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}")

print("Training Complete!")
3. 关键说明
  • 冻结层:代码中先冻结了ResNet18的所有卷积层,只更新最后的model.fc层。若数据量足够多并与ImageNet有较强相关性,可解冻更多层进行微调。
  • 数据增强:采用随机裁剪、水平翻转等简单变换,可提升模型的泛化能力。
  • 训练策略:先以较大学习率训练最后的全连接层,再根据验证集效果可考虑解冻一部分卷积层,缩小学习率(如1e-4或1e-5),进行更细致的全网络微调。

项目二:文本分类(Hugging Face + BERT)

1. 项目背景
  • 任务场景:常见的文本分类场景,如对社交媒体上的评论进行情感分析(正向/负向/中性)。
  • 挑战:训练一个从头开始的语言模型需要大规模语料,耗时且性能不稳定。
  • 解决方案:基于Hugging Face提供的预训练BERT模型进行微调,快速获得高精度分类器。
2. 项目代码(示例)

此处示例使用transformers库(需先安装:pip install transformers),并使用IMDB或自定义情感数据集做演示。

!pip install transformers datasets -q  # 安装所需库,若环境已配置可忽略

import torch
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
from datasets import load_dataset

# -------------------
# 1. 加载数据集
# -------------------
# 这里示例使用datasets库中的IMDB情感分析数据
imdb_dataset = load_dataset("imdb")
# 将原始dataset分为训练集和测试集,这里默认80%训练, 20%验证
train_dataset = imdb_dataset['train'].shuffle(seed=42).select(range(2000))  # 选取部分样本示例
val_dataset = imdb_dataset['test'].shuffle(seed=42).select(range(1000))

# -------------------
# 2. 定义分词器与数据处理函数
# -------------------
model_name = "bert-base-uncased"
tokenizer = BertTokenizer.from_pretrained(model_name)

def tokenize_function(example):
    return tokenizer(example["text"], padding="max_length", truncation=True, max_length=128)

train_dataset = train_dataset.map(tokenize_function, batched=True)
val_dataset = val_dataset.map(tokenize_function, batched=True)

# datasets库将文本存储在 'input_ids', 'attention_mask' 中
train_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])
val_dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])

# -------------------
# 3. 加载预训练模型并设置分类任务
# -------------------
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)

# -------------------
# 4. 定义训练参数
# -------------------
training_args = TrainingArguments(
    output_dir="./bert-finetuned-imdb",
    evaluation_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    num_train_epochs=3,
    weight_decay=0.01,
    logging_steps=50,
    save_steps=1000,
    logging_dir='./logs'
)

# -------------------
# 5. 使用Trainer进行训练
# -------------------
def compute_metrics(eval_pred):
    # 这里可根据需求定义更复杂的评估指标
    logits, labels = eval_pred
    preds = torch.argmax(torch.tensor(logits), dim=1)
    accuracy = (preds == labels).float().mean()
    return {"accuracy": accuracy.item()}

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=val_dataset,
    compute_metrics=compute_metrics
)

trainer.train()

# 训练完后可测试预测
test_texts = ["I love this movie so much!", 
              "It was a terrible film, won't recommend anyone to watch."]
inputs = tokenizer(test_texts, return_tensors="pt", padding=True, truncation=True)
outputs = model(**inputs)
predictions = torch.argmax(outputs.logits, dim=1)
print("Predictions:", predictions)
3. 关键说明
  • BertTokenizer:将文本转化为模型可接受的Tokens(ID序列),同时生成attention_mask用以区分实际文本与补齐部分。
  • BertForSequenceClassification:在BERT模型基础上加了一个线性分类层,用于情感二分类。
  • Trainertransformers库提供了便利的TrainerAPI,可轻松管理训练循环、评估、保存模型等。

项目三:目标检测(YOLOv5 + 自定义数据集)

1. 项目背景
  • 任务场景:对自定义数据集(例如仓库中的货物检测、小型植物检测等)进行目标检测。
  • 挑战:训练一个目标检测模型从头开始难度高、需求大量标注数据。
  • 解决方案:利用YOLOv5的预训练权重,加载COCO预训练模型,并对新数据集进行微调。
2. 项目代码(示例)

YOLOv5提供了简单易用的仓库,可直接在Github克隆并使用命令行或Python脚本进行训练。
以下示例采用命令行方式为主,并简单展示Python调用。

# 克隆 YOLOv5 仓库
git clone https://github.com/ultralytics/yolov5.git
cd yolov5
pip install -r requirements.txt
  • 准备数据集

    1. 按照YOLO格式将数据集划分为images/labels/目录。
    2. data.yaml文件中指定训练集与验证集路径,以及类别名称。例如:
      train: ./data/images/train
      val: ./data/images/val
      nc: 2  # 类别数
      names: ['cat', 'dog']
      
  • 开始训练:假设我们要训练YOLOv5s(small版本)

    # 在yolov5目录下执行
    python train.py --img 640 --batch 16 --epochs 50 --data ./data.yaml --cfg ./models/yolov5s.yaml --weights yolov5s.pt
    
    • --weights yolov5s.pt:表示从官方预训练权重开始训练。
    • 训练完成后会在runs/train/expX/weights/best.pt中生成最佳模型。
3. Python脚本调用并推理示例

以下是简单调用推理的示例:

import torch

# 加载训练好的权重
model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/exp/best.pt', force_reload=True)

# 推理示例:detect single image
results = model('test.jpg')
results.print()   # 打印检测结果
results.show()    # 显示带检测框的图片
results.save()    # 保存检测可视化到 runs/detect/exp
4. 关键说明
  • 预训练权重yolov5s.pt是在COCO数据集上训练好的模型,具备较好的通用检测能力;微调后可适应你的小众检测任务。
  • 数据标注格式:YOLO需要每张图片对应的.txt标签文件,文件内包含class_id x_center y_center width height等信息,坐标通常是归一化后的值。
  • 可视化:训练过程中提供mAPprecisionrecall等指标,每个epoch都会输出在终端或者TensorBoard上。

三、总结与扩展

  1. 预训练模型和迁移学习

    • 帮助我们利用大型数据集上学到的通用特征,极大提高了训练效率和结果稳定性。
    • 适用于多种场景:图像、文本、语音、时间序列等,尤其在数据有限的情况下效果尤为明显。
  2. 微调策略多样

    • 可根据数据规模和任务相似度选择仅训练最后几层或全网络微调;
    • 选择合适的学习率调度(如分层学习率等)能够充分发挥预训练模型的潜力。
  3. 实战项目建议

    • 尽量保持与预训练数据分布的相似度,若相差较大需加大数据增强或考虑更多的Domain Adaptation技巧。
    • 在多类别或更加复杂场景下可拓展现有思路,如多标签分类、序列标注、实例分割等。
  4. 前沿趋势

    • 大模型(如GPT、CLIP、SAM等)在多模态、跨领域任务中表现出更强的通用能力;
    • 提升Prompt设计和微调方式(LoRA、Prefix Tuning等)也成为新的研究方向。

通过以上三个跨领域的示例项目,大家应当对预训练模型与迁移学习有更直观的认识,并能在实际研发过程中快速上手,提高模型的开发效率和最终性能。祝在深度学习的工程化落地中一切顺利!

哈佛博后带小白玩转机器学习【限时5折-含直播】哈佛博后带小白玩转机器学习_哔哩哔哩_bilibili

总课时超400+,时长75+小时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值