当大家面临着复杂的数学建模问题时,你是否曾经感到茫然无措?作为2022年美国大学生数学建模比赛的O奖得主,我为大家提供了一套优秀的解题思路,让你轻松应对各种难题。
让我们来看看认证杯的B题!
完整内容可以在文章末尾领取!
问题重述
金属或塑料产品表面的缺陷不仅会影响产品的外观,还可能对产品的性能或耐久性造成严重损害。自动表面异常检测已成为一个引人关注且有前景的研究领域,对视觉检查应用领域有着非常高且直接的影响[1]。Kolektor集团提供了一组有缺陷的生产物品图像数据集[2],我们希望利用这个数据集作为示例,研究通过照片自动检测产品表面缺陷的数学模型。
问题一、确定照片中是否出现表面缺陷,并测量模型执行此操作所需的计算量和存储空间。
使用Vision Transformer(ViT)算法进行表面缺陷检测需要经历以下步骤:
数据准备: 将Kolektor集团提供的有缺陷的生产物品图像数据集用于训练和测试。确保数据集包括正常和有缺陷的样本,并进行适当的标注。
模型选择: ViT是一个基于注意力机制的图像分类模型,可以用于检测表面缺陷。选择适当的预训练的ViT模型,例如Google的ViT或其他经过训练的变体。
模型微调: 使用数据集对所选的ViT模型进行微调,以适应特定的表面缺陷检测任务。调整模型的输出层,使其适应二分类问题(缺陷/正常)。
计算量和存储空间优化: ViT模型可能相对较大,因此需要优化计算量和存储空间。可以使用以下策略:
模型剪枝: 剪枝掉不重要的权重,减小模型参数数量。
量化: 降低模型参数的位数,减小存储空间和计算量。
深度可分离卷积替代注意力层: 在资源受限的情况下,可以考虑使用深度可分离卷积替代注意力层,以减少计算量。
性能评估: 使用测试集评估模型的性能,包括准确性、召回率和精确度。这有助于了解模型在检测表面缺陷方面的效果。
Vision Transformer(ViT)是一种基于注意力机制的深度学习模型,专门设计用于图像分类任务。传统的卷积神经网络(CNN)在图像分类中取得了巨大成功,但ViT采用了一种全新的思路,将图像划分为均匀的图块,并通过自注意力机制来捕捉图块之间的关系。
以下是ViT的主要组成部分和工作原理:
-
图像的划分: ViT将输入的图像划分为一系列非重叠的图块,每个图块称为一个“补丁”。这些补丁被拉平成一维向量,然后作为输入序列传递给模型。
-
嵌入层(Embedding Layer): 对每个补丁进行线性映射,将其转换为一个具有较低维度的嵌入向量。这些嵌入向量将作为输入序列传递给Transformer模型。
-
Transformer编码器: ViT使用Transformer编码器来处理输入序列。每个Transformer编码器由多个自注意力头和全连接前馈网络组成。这使得模型能够捕捉序列中不同位置的关系。
-
多头注意力机制: 自注意力机制允许模型在处理序列时动态关注不同位置的信息。多头注意力则是通过并行计算多个注意力机制,增加了模型对不同特征尺度的感知。
-
全局池化: ViT使用全局平均池化来整合整个序列的信息,得到最终的特征表示。这一步代替了传统CNN中的全连接层,使得ViT更具可扩展性。
ViT的主要优势在于,相对于传统的CNN,它不再需要手工设计的卷积层,而是通过自注意力机制直接学习输入序列中的全局关系。这种思想使得ViT在处理图像分类任务时表现出色,并且在一些领域展现出强大的泛化能力。在图像分类以外的任务中,ViT的变体也被成功地应用,例如目标检测和语义分割。
ViT(Vision Transformer)的核心思想是使用自注意力机制(Self-Attention)捕捉输入序列中元素之间的关系。以下是ViT中的一些关键公式:
-
输入序列表示:
- 假设输入图像被划分为
N
个补丁(patches),每个补丁的维度为d_patch
。 - 将每个补丁拉平成一维向量,得到输入序列: X = [ x 1 , x 2 , … , x N ] \mathbf{X} = [x_1, x_2, \ldots, x_N] X=[x1,x2,…,xN],其中 x i ∈ R d patch x_i \in \mathbb{R}^{d_{\text{patch}}} xi∈Rdpatch。
- 假设输入图像被划分为
-
嵌入层(Embedding Layer):
- 使用一个线性映射来将每个补丁转换为低维嵌入向量: E = Linear ( X ) \mathbf{E} = \text{Linear}(\mathbf{X}) E=Linear(X),其中 E = [ e 1 , e 2 , … , e N ] \mathbf{E} = [e_1, e_2, \ldots, e_N] E=[e1,e2,…,eN], e i ∈ R d embed e_i \in \mathbb{R}^{d_{\text{embed}}} ei∈Rdembed。
-
自注意力机制(Self-Attention):
- 对于输入序列的每个位置 i i i,计算注意力分数(注意力权重):
Attention ( E ) i j = exp ( E i ⋅ E j ) ∑ k exp ( E i ⋅ E k ) \text{Attention}(\mathbf{E})_{ij} = \frac{\exp(\mathbf{E}_i \cdot \mathbf{E}_j)}{\sum_k \exp(\mathbf{E}_i \cdot \mathbf{E}_k)} Attention(E)ij=∑kexp(Ei⋅Ek)exp(Ei⋅Ej) - 这里, E i \mathbf{E}_i Ei 表示位置 i i i 的嵌入向量。
- 对于输入序列的每个位置 i i i,计算注意力分数(注意力权重):
-
多头自注意力机制(Multi-Head Self-Attention):
- 使用多个注意力头( h h h 个)并行计算:
MultiHead ( E ) = Concat ( Head 1 , Head 2 , … , Head h ) ⋅ W O \text{MultiHead}(\mathbf{E}) = \text{Concat}(\text{Head}_1, \text{Head}_2, \ldots, \text{Head}_h) \cdot \mathbf{W}_O MultiHead(E)=Concat(Head1,Head2,…,Headh)⋅WO - 每个注意力头的计算类似,但使用不同的线性映射参数。
- 使用多个注意力头( h h h 个)并行计算:
-
全连接前馈网络(Feedforward Network):
- 对每个位置 i i i 的多头注意力输出应用前馈网络:
FFN ( MultiHead ( E ) ) i = ReLU ( W 1 ⋅ MultiHead ( E ) i + b 1 ) ⋅ W 2 + b 2 \text{FFN}(\text{MultiHead}(\mathbf{E}))_i = \text{ReLU}(\mathbf{W}_1 \cdot \text{MultiHead}(\mathbf{E})_i + \mathbf{b}_1) \cdot \mathbf{W}_2 + \mathbf{b}_2 FFN(MultiHead(E))i=ReLU(W1⋅MultiHead(E)i+b1)⋅W2+b2 - 这里, W 1 , W 2 , b 1 , b 2 \mathbf{W}_1, \mathbf{W}_2, \mathbf{b}_1, \mathbf{b}_2 W1,W2,b1,b2 是网络参数。
- 对每个位置 i i i 的多头注意力输出应用前馈网络:
-
全局平均池化(Global Average Pooling):
- 将前馈网络的输出进行全局平均池化,得到最终的特征表示:
Pool ( FFN ( MultiHead ( E ) ) ) = 1 N ∑ i = 1 N FFN ( MultiHead ( E ) ) i \text{Pool}(\text{FFN}(\text{MultiHead}(\mathbf{E}))) = \frac{1}{N} \sum_{i=1}^{N} \text{FFN}(\text{MultiHead}(\mathbf{E}))_i Pool(FFN(MultiHead(E)))=N1i=1∑NFFN(MultiHead(E))i
- 将前馈网络的输出进行全局平均池化,得到最终的特征表示:
这些公式概括了ViT的核心组成部分,其中自注意力机制和多头机制是其独特之处。这些公式描述了如何从输入图像序列中学习全局关系,以便进行有效的图像分类。
在工业表面缺陷检测的问题场景中应用ViT:
输入数据: 使用Kolektor集团提供的有缺陷的生产物品图像数据集作为训练和测试数据。确保数据集包括正常和有缺陷的样本,并进行适当的标注。
图像划分: 将每张图像划分为均匀的图块,每个图块作为一个输入补丁。这些补丁将被拉平为一维向量,并作为输入序列传递给ViT模型。
模型选择: 选择适当的ViT模型,可以是预训练的ViT模型或者在图像分类任务上微调过的模型。
微调: 对选择的ViT模型进行微调,以适应表面缺陷检测任务。调整模型的输出层,使其能够输出缺陷/正常的分类结果。
优化计算和存储: 使用模型剪枝、量化和深度可分离卷积等技术,以优化模型的计算量和存储空间。这对于在廉价手持设备上部署模型至关重要。
模型评估: 使用测试集评估模型在表面缺陷检测任务上的性能,包括准确性、召回率和精确度。确保模型能够有效地检测出表面缺陷。
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms
from torch.utils.data import DataLoader, Dataset# 定义简单的Vision Transformer模型
class ViTModel(nn.Module):def __init__(self, num_classes, embed_dim=256, num_heads=4, num_layers=6):super(ViTModel, self).__init__()self.embedding = nn.Linear(3 * 16 * 16, embed_dim)self.transformer = nn.TransformerEncoder(nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads),num_layers=num_layers)self.fc = nn.Linear(embed_dim, num_classes)def forward(self, x):x = self.embedding(x.view(x.size(0), -1))x = x.permute(1, 0, 2)x = self.transformer(x)x = x.mean(dim=0)x = self.fc(x)return x# 定义数据集
class SurfaceDefectDataset(Dataset):def __init__(self, data, labels, transform=None):self.data = dataself.labels = labelsself.transform = transformdef __len__(self):return len(self.data)def __getitem__(self, idx):image = self.data[idx]label = self.labels[idx]if self.transform:image = self.transform(image)return image, label# 定义数据转换
transform = transforms.Compose([transforms.ToTensor(),# Add more transformations as needed
])# 准备数据
# Replace 'train_data', 'train_labels', 'test_data', 'test_labels' with your actual data
train_dataset = SurfaceDefectDataset(train_data, train_labels, transform=transform)
test_dataset = SurfaceDefectDataset(test_data, test_labels, transform=transform)train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)# 初始化模型、损失函数和优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = ViTModel(num_classes=2).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)# 训练模型
num_epochs = 10
for epoch in range(num_epochs):model.train()for inputs, labels in train_loader:inputs, labels = inputs.to(device), labels.to(device)optimizer.zero_grad()outputs = model(inputs)loss = criterion(outputs, labels)loss.backward()optimizer.step()# 在测试集上评估模型model.eval()with torch.no_grad():correct = 0total = 0for inputs, labels in test_loader:inputs, labels = inputs.to(device), labels.to(device)outputs = model(inputs)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()accuracy = correct / totalprint(f'Epoch [{epoch+1}/{num_epochs}], Test Accuracy: {accuracy:.4f}')
问题二:自动标记表面缺陷出现的位置或区域,并测量模型所需的计算量、存储空间和标记准确性。
Faster R-CNN(Region-based Convolutional Neural Network)是一种用于目标检测的深度学习模型。其核心理论是通过引入两个阶段的网络,即区域提议网络(Region Proposal Network,RPN)和目标检测网络,来实现快速而准确的目标检测。
-
区域提议网络(RPN):
- Faster R-CNN引入了一个专门的区域提议网络,该网络负责生成潜在的目标框(区域提议)。
- RPN通过在输入图像上滑动一个小窗口,计算每个窗口中是否包含目标,并为每个窗口生成多个候选框(边界框)以及相应的置信度分数。
- RPN使用锚框(anchor boxes)来生成候选框,这些锚框具有多个尺度和长宽比,以适应不同大小和形状的目标。
-
目标检测网络:
- 在Faster R-CNN中,RPN生成的候选框将被用于提取特征。这些候选框中的区域被RoI池化层处理,将它们映射为固定大小的特征图。
- 特征图随后被传递给目标检测网络,通常是一个深度卷积神经网络,如ResNet或VGG。该网络对每个RoI进行分类(目标/非目标)和回归(边界框微调)。
-
损失函数:
- Faster R-CNN使用联合损失函数,结合了RPN和目标检测网络的损失。该损失包括分类损失(如交叉熵损失)和回归损失(如平滑的L1损失)。
- 分类损失用于确保模型正确分类RoI,而回归损失用于确保模型准确地调整RoI的边界框。
-
训练过程:
- Faster R-CNN的训练过程是端到端的,通过在整个模型中反向传播误差信号来更新网络参数。
- 训练过程中,模型同时学习RPN的区域提议和目标检测网络的参数,以便更好地适应具体任务。
-
推理过程:
- 在推理过程中,Faster R-CNN通过RPN生成候选框,然后使用目标检测网络对这些候选框进行分类和回归,最终得到最终的目标检测结果。
总的来说,Faster R-CNN的设计使得它能够在目标检测任务中实现更高的准确性和更快的推理速度。这种两阶段的框架允许模型专注于生成高质量的候选框,然后对这些候选框进行详细的分类和位置调整。
在问题场景下,进行目标检测任务,以自动标记表面缺陷出现的位置或区域,目标检测通常需要使用带有标注框的训练数据集进行训练。
import torch
import torchvision
from torchvision.models.detection import FasterRCNN
from torchvision.models.detection.rpn import AnchorGenerator
from torchvision.transforms import functional as F
from PIL import Image, ImageDraw# 定义Faster R-CNN模型
def get_faster_rcnn_model(num_classes):backbone = torchvision.models.mobilenet_v2(pretrained=True).featuresbackbone.out_channels = 1280anchor_generator = AnchorGenerator(sizes=((32, 64, 128, 256, 512),),aspect_ratios=((0.5, 1.0, 2.0),))roi_pooler = torchvision.ops.MultiScaleRoIAlign(featmap_names=['0'],output_size=7,sampling_ratio=2)model = FasterRCNN(backbone,num_classes=num_classes,rpn_anchor_generator=anchor_generator,box_roi_pool=roi_pooler)return model# 定义数据集和转换
class SurfaceDefectDataset(Dataset):# 请根据你的数据集格式进行定义# 需要包含图像和对应的标注框信息# 参考 torchvision.transforms 的文档以及自定义的标注框格式# https://pytorch.org/vision/stable/transforms.html# 训练模型
def train_model(model, train_loader, num_epochs=5):device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model.to(device)params = [p for p in model.parameters() if p.requires_grad]optimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)for epoch in range(num_epochs):model.train()for images, targets in train_loader:images = [img.to(device) for img in images]targets = [{k: v.to(device) for k, v in t.items()} for t in targets]loss_dict = model(images, targets)losses = sum(loss for loss in loss_dict.values())optimizer.zero_grad()losses.backward()optimizer.step()lr_scheduler.step()print(f"Epoch {epoch+1}/{num_epochs}, Loss: {losses.item()}")# 在测试集上评估模型
def evaluate_model(model, test_loader):model.eval()device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model.to(device)for images, targets in test_loader:images = [img.to(device) for img in images]targets = [{k: v.to(device) for k, v in t.items()} for t in targets]with torch.no_grad():predictions = model(images)# 处理预测结果,计算标记准确性等指标# 可以使用预测框与实际框的IoU(Intersection over Union)等指标# 可视化预测结果
def visualize_predictions(model, test_loader):model.eval()device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model.to(device)for images, targets in test_loader:images = [img.to(device) for img in images]targets = [{k: v.to(device) for k, v in t.items()} for t in targets]with torch.no_grad():predictions = model(images)# 可视化预测结果for i in range(len(images)):image = F.to_pil_image(images[i].cpu())draw = ImageDraw.Draw(image)# 可视化真实框for box in targets[i]["boxes"]:draw.rectangle([(box[0], box[1]), (box[2], box[3])], outline="green", width=2)# 可视化预测框for box, score in zip(predictions[i]["boxes"], predictions[i]["scores"]):draw.rectangle([(box[0], box[1]), (box[2], box[3])], outline="red", width=2)draw.text((box[0], box[1]), f"{score:.2f}", fill="red")image.show()
更多内容具体可以看看我的下方名片!里面包含有认证杯一手资料与分析!
另外在赛中,我们也会陪大家一起解析认证杯的一些方向
关注 CS数模 团队,数模不迷路~