Learning Transferable Visual Models From Natural Langua
作者:禅与计算机程序设计艺术
1.简介
通常而言,在深度学习领域里
近期(Google Research团队)发布了一项创新性研究——《基于自然语言监督的学习可转移视觉模型》(Learning Transferable Visual Models From Natural Language Supervision, 参考文献4)。该研究表明:将视觉信息转化为自然语言表示(即视觉理解),通过提取自然语言中的语法结构与语境信息对神经网络进行预训练,并实现对新样本的分类预测。研究者在ImageNet数据集上开发并训练了两个深度神经网络模型:Visual-Semantic Embedding Network(VSENet)与Fine-tuned Residual Networks(FRN)。相较于基于AlexNet与ResNet的传统架构,在本研究中提出的VSENet与FRN模型各自增加了自监督学习与微调学习模块。
此外, 文章进一步探讨了一种新型预训练策略——Soft Tuning[6], 即通过在预训练过程中施加额外约束条件, 使得模型能够更加专注于特定的目标, 进而显著地增强模型在不同场景下的适应能力。
本文将详细阐述这项工作的相关技术细节。
2.背景介绍
在计算机视觉领域中,图像识别被视为一个关键任务。不仅在搜索引擎,在安防系统以及机器人导航等领域都有广泛的应用。现有的图像分类技术大多建立在经典的机器学习算法基础之上,并包括支持向量机(SVM)、朴素贝叶斯(NB)以及K-近邻法(KNN)等技术。尽管这些传统算法虽然在某些方面表现尚可,但它们通常需要较长的时间进行模型训练,并且对计算资源的要求较高。但在实际应用环境中,为了达到较好的识别效果,通常需要大量高质量的训练数据。
为了应对这一挑战,在深度学习领域已开发出多种基于神经网络的图像识别系统。其中 notable 代表包括 AlexNet, VGG, GoogLeNet, 和 ResNet 等。这些系统通常在训练速度和效率方面表现出色。然而,在实际应用中仍面临诸多挑战。例如,在处理复杂场景时,传统的系统往往难以达到理想的识别效果。此外,在不同环境或条件下(即不同的具体应用场景),每个特定场景可能需要定制化的数据集来优化模型性能。因此,在某些情况下直接迁移模型到新环境时可能会遇到性能下降等问题
同时
对于图像分类任务来说,将图片理解为自然语言能够带来哪些好处?
- 数据增强技术:通过图像增强技术生成的数据样本不仅能够降低模型过拟合的风险,并且能够提升模型训练的有效性。
- 模型泛化性能:在自然语言处理中可以通过提取更丰富的上下文信息来实现。
- 多角度拍摄的照片:即使在视觉信息丢失的情况下仍能借助自然语言进行有效描述。
- 模型鲁棒性:基于自然语言处理的能力相比仅依靠图片标签而言更加具备鲁棒性、适应性和容错性特征。
从上述分析可以看出,图像分类任务不仅能够通过训练神经网络实现,并且还可以结合神经网络与自然语言处理技术进行联合训练,在一定程度上能够实现与人类表现相似甚至更好的效果。
3.基本概念术语说明
(1)自然语言理解(Natural Language Understanding,NLU)
自然语言理解(NLU)旨在让计算机能够理解和处理人类的语言指令,并非仅限于书面文字的形式。它涵盖了人们在日常生活中使用的语言交流方式以及各种形式的人机互动平台。从最初的阶段来看,这类模型一般会整合分词器、词性标注工具以及其他的辅助分析模块来完成基本的功能需求。随着时间的推移与技术的进步,在这一领域已经涌现出了许多先进的解决方案和技术手段
在自然语言处理领域中,自然语言理解(NLU)被视为一个重要的细分领域。它旨在通过对输入文本进行解析和理解,并将其转换为一种便于计算机执行的形式。其输出通常包括对给定文本进行分类、标记或识别的任务,在实际应用中如日期、时间、地点以及实体名称等信息的提取与归类。
(2)视觉感知(Visual Perception)
Visual Perception是一种由眼睛等感官机制接收外界信号并进行图像解析的过程。该过程主要包括以下几个关键维度:首先是以清晰识别主要关注点为目标的主要感知任务;其次是对环境空间布局进行分析的能力;第三是对物体轮廓与形状进行解析的能力;最后则是通过不同角度下的视网膜成像来理解空间关系的能力。
(3)计算机视觉(Computer Vision)
计算机视觉(Computer Vision)是基于图像分析信息并以此识别或评估某些现实世界的事物的一套系统性方法同时也是人工智能领域中的核心技术之一也是深度学习体系中的重要组成部分。该技术通过复杂的数据处理模型能够自主学习并提取出图像中的深层特征从而实现对现实世界的感知与理解过程
图像的特征涉及颜色、纹理、空间分布和边缘等特性。这些图像特征可用于辅助机器学习算法进行建模和分类,并用于检测。
计算机视觉在多个领域均有显著贡献,在影像分析方面具有重要地位,并延伸至遥感图像分析领域。该技术不仅用于行人跟踪和目标定位技术的实现,在缺陷检测和质量监控方面也发挥着关键作用。此外,在车牌号识别与车辆号牌辨识方面均展现出独特优势,并通过图像恢复工程辅助文字OCR处理功能的完善。该方法还可应用于图像修复与数字图库编辑等领域,并通过目标定位技术提升整体效率的同时实现行为监测系统功能的稳定运行
4.核心算法原理和具体操作步骤以及数学公式讲解
(1)Visual-Semantic Embedding Network (VSENet)
该研究团队开发出了一种新型模型VSENet,在图像分类领域展现出显著性能。该模型以自然语言描述作为输入数据,并通过预训练的图像识别网络生成了视觉编码层与语义编码层的特征表达。在自监督学习过程中,则通过分析视觉编码层与语义编码层之间的映射关系来建立多模态特征关联,并在此基础上实现不同模态间的知识迁移。这种多模态特征融合的学习机制使得VSENet具备了对复杂场景下的目标识别能力
(1.1)自监督学习阶段
首先,在构建一个基于类别嵌入的方法框架中,在线构建一个属于该分类的所有样本点的新空间表示模型中,在特征空间中对各个类别的样本点进行标准化处理后得到新的样本点集合中
然后,在实验中我们采用了基于已训练好的图像模型的方法来提取各个类别样本的特征向量\phi(x_i)。其中对于每一个类别样本x_i来说,在参数化函数f_{\theta}的作用下得到的结果v_i=f_{\theta}(x_i)即代表该类别的图像特征。
最后阶段上,在基于每个类别样本对应的视觉向量表示为v_i和语义向量表征为\phi(\bar x_i)的基础上,并非直接计算该类样例与其他类样例之间的余弦相似度值;而是通过建立一个完整的余弦相似度矩阵来辅助分析
整个流程可以被视为为每个类别样本构建其对应的语义空间。其间的相似程度源于它们在语义上的关联性。
(1.2)微调学习阶段
通过采用softmax损失函数进行训练,能够推导出视觉-语义预测模型y = softmax(Wv+b)。其中W和b代表权重参数,在此模型中其对应的视觉向量表示为v。
VSENet的损失函数如下:
L_{cross\_entropy}(\theta)被定义为\frac{1}{m}\sum_{i=1}^m log\left( \frac{e^{\langle v^{(i)}, W' + \theta b' \rangle}}{\sum_{j=1}^n e^{\langle v^{(j)}, W' + \theta b' \rangle}} } \right) + L_w(\theta) (1)
其中,m是训练集大小,\theta是权重参数,L_w(\theta)是权重正则项。
(2)Fine-tuned Residual Networks (FRN)
FRN是一种由谷歌团队开发的新预训练模型,在传统图像分类任务中面临计算资源消耗以及泛化能力不足的问题时被提出。该模型通过在softmax前增加了一层额外的神经网络层来有效缓解数据冗余带来的负面影响
(2.1)Fine-tuning阶段
在初始化权重参数θ之前,在softmax分类器前馈于FRN层的基础上进行处理后,在基于预训练ResNet架构构建并完成这一系列操作后得到了最终的预训练模型。
第二阶段中, 通过应用Softmax损失函数进行训练, 从而获得视觉与语义对应的预测函数y = \text{softmax}(Wv + b), 其中权重参数由W和b表示, v代表视觉向量的特征表达.
第三部分中指出,FRN架构包含三层全连接(FC)结构,具体包括输入模块、中间处理模块和输出分类模块。其中,输入模块负责接收并传递来自CNN网络的最后一特征图的数据;中间处理模块通过密集连接方式或全局池化操作提取并聚合关键特征信息;最后一步将中间处理模块提取的关键特征与经过预训练的Softmax分类器进行融合。
FRN通过分析ResNet网络输出层的空间特征来提取图像感知信息,在此基础上构建全连接层进行深度学习训练,最终生成完整的语义关联表示矩阵。
整个过程可被视为向ResNet网络添加一个特征融合模块,在其捕获视觉特征后,实现权重更新以提升网络在特征学习方面的性能。
(2.2)Soft Tuning阶段
在预训练过程中(Soft Tuning)指为每个预训练任务增添特定限制条件(constraint)以使模型更加倾向于聚焦于某些关键目标(target),从而提高其在不同场景下的适应能力(generalization capability)。主要采用两种策略:
- 通过引入惩罚机制:在原有的 softmax 损失基础上 Soft Tuning 可以添加相应的惩罚机制,并借鉴主动学习中的方法(类似于主动学习中的策略),通过最大化特定的目标函数(类似于主动学习的目标),迫使网络更加关注那些具有独特特征的样本(类似于主成分分析),从而提升其泛化能力。
- 通过添加额外的约束:除了传统的 softmax 损失之外 Soft Tuning 还可以选择性地添加其他类型的约束条件(例如强制性地引导网络去识别灰度值、空间位置或滤波器值等特定模式),这种做法不仅可以防止网络过于依赖某些特定的数据分布(类似于过拟合现象)以提高泛化能力 而且还能使模型在面对更为复杂的数据分布时表现出更强的表现力。
(3)Soft Tuning方法论
Soft Tuning方法论是建立在以下假设之上的:
- 提升预训练任务效率(Exploration Task)旨在拓展应用范围,在此过程中, 网络需具备处理多样模式的能力。
- 提升动力学性能(Enhance Dynamics)由于所处理网络具有物理背景支撑, 在实现复杂行为特性方面存在潜力.
- 去除多余的信息(Reduce Redundancy)通过精炼信息处理流程以提高准确性.
具体来说,Soft Tuning的三步走:
- 设置模型参数初始化:首先将所有参数固定下来,并通过随机生成的方式确定初始值以形成一个常数模型。
- 开展探索性任务训练:从数据集中随机选取一个样本作为参考对象,并通过优化算法使常数模型输出结果尽可能贴近该样本特征。
- 引入约束条件设置:在现有基础上加入灰度阈值用于图像处理中的对比度调节等具体限制条件,并重新对常数模型进行训练以调整其偏移量参数。最终目标是使网络输出结果偏向于符合这些约束条件所限定的特定区域。
5.具体代码实例和解释说明
(1)训练VISNET模型
import torch
from torchvision import transforms, datasets
from sklearn.metrics import classification_report, confusion_matrix
# Define the dataset path and hyperparameters for training
data_dir = "path/to/your/dataset"
batch_size = 32
num_classes = 100
epochs = 100
# Prepare the data loader
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])
trainset = datasets.CIFAR100(root=data_dir, train=True, download=False, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)
testset = datasets.CIFAR100(root=data_dir, train=False, download=False, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=2)
# Define VISNET model architecture with pre-trained ResNet18 as backbone
import torchvision.models as models
model = models.resnet18()
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, num_classes)
model = nn.Sequential(*list(model.children())[:-1], nn.Flatten(), nn.Linear(512*7*7, num_classes)).cuda()
criterion = nn.CrossEntropyLoss().cuda()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
scheduler = lr_scheduler.MultiStepLR(optimizer, milestones=[int(epoch*0.5) for epoch in range(epochs)], gamma=0.1)
# Train VISNET on CIFAR100
for epoch in range(epochs):
scheduler.step()
running_loss = 0.0
correct = 0
total = 0
# Iterate over all mini-batches of current epoch
for i, data in enumerate(trainloader, 0):
inputs, labels = data[0].cuda(), data[1].cuda()
optimizer.zero_grad()
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
total += labels.size(0)
correct += (predicted == labels).sum().item()
# Print training statistics at the end of each epoch
print('[%d] Loss: %.3f | Acc: %.3f%% (%d/%d)' % (
epoch+1,
running_loss / len(trainloader.dataset),
100.*correct / total,
correct,
total))
# Test the trained VISNET on CIFAR100 test set
def evaluate():
global best_acc
model.eval()
test_loss = 0
correct = 0
total = 0
y_true = []
y_pred = []
with torch.no_grad():
for data in testloader:
images, labels = data[0].cuda(), data[1].cuda()
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()*images.size(0)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
y_true.extend(labels.tolist())
y_pred.extend(predicted.tolist())
print('Test Loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)'.format(
test_loss/(len(testloader)*batch_size),
correct,
total,
100.*correct/total))
return classification_report(y_true, y_pred, target_names=['class_%d' % i for i in range(100)])
print(evaluate())
代码解读
(2)训练FRN模型
import torch
from torchvision import transforms, datasets
from sklearn.metrics import classification_report, confusion_matrix
# Define the dataset path and hyperparameters for training
data_dir = "path/to/your/dataset"
batch_size = 32
num_classes = 100
epochs = 100
# Prepare the data loader
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
])
trainset = datasets.CIFAR100(root=data_dir, train=True, download=False, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)
testset = datasets.CIFAR100(root=data_dir, train=False, download=False, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=2)
# Define FRN model architecture with pre-trained ResNet18 as backbone
import torchvision.models as models
pre_model = models.resnet18()
num_ftrs = pre_model.fc.in_features
pre_model.fc = nn.Identity()
pre_model = nn.Sequential(*list(pre_model.children())[:-1]).cuda()
frn_model = nn.Sequential(nn.Conv2d(512, 100, kernel_size=(1, 1), bias=True),
nn.BatchNorm2d(100),
nn.ReLU()).cuda()
model = nn.Sequential(*(list(pre_model.children()) + [frn_model])).cuda()
criterion = nn.CrossEntropyLoss().cuda()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
scheduler = lr_scheduler.MultiStepLR(optimizer, milestones=[int(epoch*0.5) for epoch in range(epochs)], gamma=0.1)
# Train FRN on CIFAR100
for epoch in range(epochs):
scheduler.step()
running_loss = 0.0
correct = 0
total = 0
# Iterate over all mini-batches of current epoch
for i, data in enumerate(trainloader, 0):
inputs, labels = data[0].cuda(), data[1].cuda()
optimizer.zero_grad()
features = pre_model(inputs)
visual_features = frn_model(features)
flatten_visual_features = visual_features.flatten(start_dim=1)
logits = model(flatten_visual_features)
loss = criterion(logits, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
total += labels.size(0)
_, predicted = torch.max(logits.data, 1)
correct += (predicted == labels).sum().item()
# Print training statistics at the end of each epoch
print('[%d] Loss: %.3f | Acc: %.3f%% (%d/%d)' % (
epoch+1,
running_loss / len(trainloader.dataset),
100.*correct / total,
correct,
total))
# Test the trained FRN on CIFAR100 test set
def evaluate():
global best_acc
model.eval()
test_loss = 0
correct = 0
total = 0
y_true = []
y_pred = []
with torch.no_grad():
for data in testloader:
images, labels = data[0].cuda(), data[1].cuda()
features = pre_model(images)
visual_features = frn_model(features)
flatten_visual_features = visual_features.flatten(start_dim=1)
logits = model(flatten_visual_features)
loss = criterion(logits, labels)
test_loss += loss.item()*images.size(0)
_, predicted = torch.max(logits.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
y_true.extend(labels.tolist())
y_pred.extend(predicted.tolist())
print('Test Loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)'.format(
test_loss/(len(testloader)*batch_size),
correct,
total,
100.*correct/total))
return classification_report(y_true, y_pred, target_names=['class_%d' % i for i in range(100)])
print(evaluate())
代码解读
