(4-7-01)文生图大模型实操:基于深度学习的图文匹配系统(1)工具类

4.7  跨模态配对实战:基于深度学习的图文匹配系统

本项目旨在构建一个多模态学习系统,专注于处理图像和文本数据的配对任务,主要基于CUHK-PEDES数据集。本项目实现了多种深度学习模型,包括LSTM、MobileNetV1和ResNet,以分别处理文本和图像特征的提取与融合。通过这些模型的结合,系统能够有效地理解和匹配图像与对应的文本描述,旨在提高图像检索和描述生成的精度。整体架构包括数据处理、模型训练和评估环节,充分利用深度学习技术提升多模态任务的性能。

实例4-30基于深度学习的图文匹配系统(源码路径:codes\4\Image-Text-Matching

在本系统中,通过深度学习模型有效地将图像和其对应的文本描述进行匹配。具体而言,本项目采用了多种神经网络架构,包括LSTM用于文本特征提取,以及MobileNetV1和ResNet用于图像特征提取。这些模型的输出经过处理后进行联合嵌入,最终实现图像与文本之间的高效对应。另外,还结合了损失函数的设计,特别是约束损失,确保具有相同标签的图像和文本在特征空间中更接近,从而提高了匹配的准确性。通过在CUHK-PEDES数据集上的训练与评估,项目展示了多模态学习在图文配对任务中的有效性与潜力。

1. 工具类

在本项目的“utils”目录中提供了一系列实用工具,用于数据处理、统计分析和可视化,帮助用户对数据集进行深入分析,计算图像和标题的数量,并可视化训练过程中的损失和准确率。这些工具为项目的后续数据分析和结果展示奠定了基础,提升了工作效率。

(1)文件directory.py提供了与文件和目录操作相关的功能,主要用于确保在进行数据读写时所需的目录存在,并且能够将数据保存为 JSON 格式。

import os
import json

def makedir(root):
    if not os.path.exists(root):
        os.makedirs(root)


def write_json(data, root):
    with open(dir, 'w') as f:
        json.dump(data, f)

def check_exists(root):
    if os.path.exists(root):
        return True
    return False

对上述代码的具体说明如下所示:

  1. makedir(root):检查指定的目录是否存在,如果不存在,则创建该目录。
  2. write_json(data, root):将数据以 JSON 格式写入指定的文件。注意,此处的 dir 应该更改为 root,以确保函数能正确运行。
  3. check_exists(root):检查指定的路径是否存在,如果存在,则返回 True,否则返回 False。

(2)文件metric.py实现了“图像-文本”匹配任务所需的度量和损失函数,包括计算成对距离、独热编码、约束损失,以及交叉模态投影分类和匹配损失。此外,文件中还定义了用于计算模型性能的 Top-K 准确率的函数和管理平均值的工具类。通过这些功能,文件metric.py 支持模型的训练、评估和性能监控,帮助优化“图像-文本”的匹配效果。

  1. 类EMA实现了指数移动平均操作,用于更新和存储参数的平滑值。
class EMA():
    def __init__(self, decay=0.999):
        self.decay = decay
        self.shadow = {}

    def register(self, name, val):
        self.shadow[name] = val.cpu().detach()

    def get(self, name):
        return self.shadow[name]

    def update(self, name, x):
        assert name in self.shadow
        new_average = (1.0 - self.decay) * x.cpu().detach() + self.decay * self.shadow[name]
        self.shadow[name] = new_average.clone()
  1. 方法pairwise_distance(A, B)的功能是计算两个点集之间的成对距离,返回距离矩阵。
def pairwise_distance(A, B):
    A_square = torch.sum(A * A, dim=1, keepdim=True)
    B_square = torch.sum(B * B, dim=1, keepdim=True)

    distance = A_square + B_square.t() - 2 * torch.matmul(A, B.t())

    return distance
  1. 方法one_hot_coding(index, k)的功能是将索引转换为独热编码格式。
def constraints_old(features, labels):
    distance = pairwise_distance(features, features)
    labels_reshape = torch.reshape(labels, (features.shape[0], 1))
    labels_dist = labels_reshape - labels_reshape.t()
    labels_mask = (labels_dist == 0).float()

    num = torch.sum(labels_mask) - features.shape[0]
    if num == 0:
        con_loss = 0.0
    else:
        con_loss = torch.sum(distance * labels_mask) / num

    return con_loss
  1. 方法constraints_old 的功能是计算约束损失,该损失用于衡量特征之间的距离。通过计算特征的成对距离,并根据标签构建匹配掩码,进而求出匹配对的平均距离,以评估模型的特征学习效果。
def constraints_old(features, labels):
    distance = pairwise_distance(features, features)
    labels_reshape = torch.reshape(labels, (features.shape[0], 1))
    labels_dist = labels_reshape - labels_reshape.t()
    labels_mask = (labels_dist == 0).float()

    num = torch.sum(labels_mask) - features.shape[0]
    if num == 0:
        con_loss = 0.0
    else:
        con_loss = torch.sum(distance * labels_mask) / num

    return con_loss
  1. 方法constraints 的功能是改进的约束损失计算方法,与 constraints_old 类似,但采用了不同的方式计算每个类别的损失。方法constraints通过遍历标签中的唯一值,选择与每个类别相关的特征,并计算这些特征之间的成对距离,从而得到更精确的约束损失。
def constraints(features, labels):
    labels = torch.reshape(labels, (labels.shape[0],1))
    con_loss = AverageMeter()
    index_dict = {k.item() for k in labels}
    for index in index_dict:
        labels_mask = (labels == index)
        feas = torch.masked_select(features, labels_mask)
        feas = feas.view(-1, features.shape[1])
        distance = pairwise_distance(feas, feas)
        num = feas.shape[0] * (feas.shape[0] - 1)
        loss = torch.sum(distance) / num
        con_loss.update(loss, n = num / 2)
    return con_loss.avg
  1. 方法constraints_loss 的功能是计算整个数据集的约束损失,首先收集所有图像和文本的嵌入特征,并根据给定的标签计算图像和文本的约束损失,这为后续的模型训练和评估提供了约束损失值。
def constraints_loss(data_loader, network, args):
    network.eval()
    max_size = args.batch_size * len(data_loader)
    images_bank = torch.zeros((max_size, args.feature_size)).cuda()
    text_bank = torch.zeros((max_size,args.feature_size)).cuda()
    labels_bank = torch.zeros(max_size).cuda()
    index = 0
    con_images = 0.0
    con_text = 0.0
    with torch.no_grad():
        for images, captions, labels, captions_length in data_loader:
            images = images.cuda()
            captions = captions.cuda()
            interval = images.shape[0]
            image_embeddings, text_embeddings = network(images, captions, captions_length)
            images_bank[index: index + interval] = image_embeddings
            text_bank[index: index + interval] = text_embeddings
            labels_bank[index: index + interval] = labels
            index = index + interval
        images_bank = images_bank[:index]
        text_bank = text_bank[:index]
        labels_bank = labels_bank[:index]
    
    if args.constraints_text:
        con_text = constraints(text_bank, labels_bank)
    if args.constraints_images:
        con_images = constraints(images_bank, labels_bank)

    return con_images, con_text
  1. 类Loss的功能是定义模型的损失函数,它根据输入的参数初始化权重,并实现了交叉模态投影分类损失(CMPC)和交叉模态投影匹配损失(CMPM)的计算。这些损失函数用于训练和优化图像和文本嵌入的对齐。   
class Loss(nn.Module):
    def __init__(self, args):
        super(Loss, self).__init__()
        self.CMPM = args.CMPM
        self.CMPC = args.CMPC
        self.epsilon = args.epsilon
        self.num_classes = args.num_classes
        if args.resume:
            checkpoint = torch.load(args.model_path)
            self.W = Parameter(checkpoint['W'])
            print('=========> Loading in parameter W from pretrained models')
        else:
            self.W = Parameter(torch.randn(args.feature_size, args.num_classes))
            self.init_weight()

    def init_weight(self):
        nn.init.xavier_uniform_(self.W.data, gain=1)
  1. 方法compute_cmpc_loss 的功能是计算交叉模态投影分类损失(CMPC),该损失用于评估图像和文本嵌入的分类能力。它通过对图像和文本嵌入进行归一化和投影,计算交叉熵损失,以确保模型在图像和文本之间的相互映射。
    def compute_cmpc_loss(self, image_embeddings, text_embeddings, labels):
        """
        criterion = nn.CrossEntropyLoss(reduction='mean')
        self.W_norm = self.W / self.W.norm(dim=0)
        #labels_onehot = one_hot_coding(labels, self.num_classes).float()
        image_norm = image_embeddings / image_embeddings.norm(dim=1, keepdim=True)
        text_norm = text_embeddings / text_embeddings.norm(dim=1, keepdim=True)

        image_proj_text = torch.sum(image_embeddings * text_norm, dim=1, keepdim=True) * text_norm
        text_proj_image = torch.sum(text_embeddings * image_norm, dim=1, keepdim=True) * image_norm

        image_logits = torch.matmul(image_proj_text, self.W_norm)
        text_logits = torch.matmul(text_proj_image, self.W_norm)
        
        cmpc_loss = criterion(image_logits, labels) + criterion(text_logits, labels)
        image_pred = torch.argmax(image_logits, dim=1)
        text_pred = torch.argmax(text_logits, dim=1)

        image_precision = torch.mean((image_pred == labels).float())
        text_precision = torch.mean((text_pred == labels).float())

        return cmpc_loss, image_precision, text_precision
  1. 方法compute_cmpm_loss 的功能是计算交叉模态投影匹配损失(CMPM),用于评估图像和文本嵌入的匹配能力。它通过计算正负样本对之间的相似性,并利用归一化标签掩码来优化嵌入的匹配性能。
    def compute_cmpm_loss(self, image_embeddings, text_embeddings, labels):

        batch_size = image_embeddings.shape[0]
        labels_reshape = torch.reshape(labels, (batch_size, 1))
        labels_dist = labels_reshape - labels_reshape.t()
        labels_mask = (labels_dist == 0)
        
        image_norm = image_embeddings / image_embeddings.norm(dim=1, keepdim=True)
        text_norm = text_embeddings / text_embeddings.norm(dim=1, keepdim=True)
        image_proj_text = torch.matmul(image_embeddings, text_norm.t())
        text_proj_image = torch.matmul(text_embeddings, image_norm.t())

        # normalize the true matching distribution
        labels_mask_norm = labels_mask.float() / labels_mask.float().norm(dim=1)
         
        i2t_pred = F.softmax(image_proj_text, dim=1)
        #i2t_loss = i2t_pred * torch.log((i2t_pred + self.epsilon)/ (labels_mask_norm + self.epsilon))
        i2t_loss = i2t_pred * (F.log_softmax(image_proj_text, dim=1) - torch.log(labels_mask_norm + self.epsilon))
        
        t2i_pred = F.softmax(text_proj_image, dim=1)
        #t2i_loss = t2i_pred * torch.log((t2i_pred + self.epsilon)/ (labels_mask_norm + self.epsilon))
        t2i_loss = t2i_pred * (F.log_softmax(text_proj_image, dim=1) - torch.log(labels_mask_norm + self.epsilon))

        cmpm_loss = torch.mean(torch.sum(i2t_loss, dim=1)) + torch.mean(torch.sum(t2i_loss, dim=1))

        sim_cos = torch.matmul(image_norm, text_norm.t())

        pos_avg_sim = torch.mean(torch.masked_select(sim_cos, labels_mask))
        neg_avg_sim = torch.mean(torch.masked_select(sim_cos, labels_mask == 0))
        
        return cmpm_loss, pos_avg_sim, neg_avg_sim
  1. 方法forward 的功能是执行前向传播,计算总体损失。根据输入的图像和文本嵌入,分别调用 CMPC 和 CMPM 的损失计算方法,并返回各类损失、精度以及正负样本的平均相似度。
    def forward(self, image_embeddings, text_embeddings, labels):
        cmpm_loss = 0.0
        cmpc_loss = 0.0
        image_precision = 0.0
        text_precision = 0.0
        neg_avg_sim = 0.0
        pos_avg_sim =0.0
        if self.CMPM:
            cmpm_loss, pos_avg_sim, neg_avg_sim = self.compute_cmpm_loss(image_embeddings, text_embeddings, labels)
        if self.CMPC:
            cmpc_loss, image_precision, text_precision = self.compute_cmpc_loss(image_embeddings, text_embeddings, labels)
        
        loss = cmpm_loss + cmpc_loss
        
        return cmpm_loss, cmpc_loss, loss, image_precision, text_precision, pos_avg_sim, neg_avg_sim
  1. 类AverageMeter 的功能是用于计算和存储当前值和平均值的工具。它提供了更新和重置的方法,以便在训练过程中跟踪损失和精度的变化,方便模型性能监控。
class AverageMeter(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += n * val
        self.count += n
        self.avg = self.sum / self.count
  1. 方法compute_topk 的功能是计算给定查询和图库的 Top-K 精度,它通过计算查询和图库之间的余弦相似度,并获取前 K 个相似项,以评估模型的检索性能。
def compute_topk(query, gallery, target_query, target_gallery, k=[1,10], reverse=False):
    result = []
    query = query / query.norm(dim=1,keepdim=True)
    gallery = gallery / gallery.norm(dim=1,keepdim=True)
    sim_cosine = torch.matmul(query, gallery.t())
    result.extend(topk(sim_cosine, target_gallery, target_query, k=[1,10]))
    if reverse:
        result.extend(topk(sim_cosine, target_query, target_gallery, k=[1,10], dim=0))
    return result
  1. 方法topk 的功能是实现 Top-K 精度计算的具体逻辑,它通过对相似度进行排序,找到正确标签在前 K 个预测中的数量,并计算其在总样本中的比例,最终返回各个 K 值对应的精度结果。
def topk(sim, target_gallery, target_query, k=[1,10], dim=1):
    result = []
    maxk = max(k)
    size_total = len(target_gallery)
    _, pred_index = sim.topk(maxk, dim, True, True)
    pred_labels = target_gallery[pred_index]
    if dim == 1:
        pred_labels = pred_labels.t()
    correct = pred_labels.eq(target_query.view(1,-1).expand_as(pred_labels))

    for topk in k:
        #correct_k = torch.sum(correct[:topk]).float()
        correct_k = torch.sum(correct[:topk], dim=0)
        correct_k = torch.sum(correct_k > 0).float()
        result.append(correct_k * 100 / size_total)
    return result

(3)文件statistics.py的功能是对数据集进行统计分析和可视化操作,具体实现代码如下所示。

def count_ids(root, flag=0):
    ids_dict = {}
    captions = 0
    with open(root,'r') as f:
        info = json.load(f)
        for data in info:
            label = data['id'] - flag
            ids_dict[label] = ids_dict.get(label,0) + 1
            captions += len(data['captions'])
    return ids_dict, captions


def count_images(root):
    info = pickle.load(open(root, 'rb'))['label_range']
    images_dict = {}
    for label in info:
        num_images = len(info[label]) - 1
        images_dict[num_images] = images_dict.get(num_images, 0) + 1
    return images_dict

def count_captions(root):
    info = pickle.load(open(root, 'rb'))['label_range']
    captions_dict = {}
    for label in info:
        for index in range(0, len(info[label]) - 1):
            num_captions = info[label][index] - info[label][index - 1]
            captions_dict[num_captions] = captions_dict.get(num_captions, 0) + 1
    return captions_dict

def visualize(data):
    keys = list(data.keys())
    keys.sort()
    values = []
    for key in keys:
        values.append(data[key])
    plt.figure('#captions in each image')
    a = plt.bar(keys, values)
    #plt.yticks([1,5,1,100,200,500,1000,5000])
    plt.xticks(list(range(min(keys), max(keys) + 1, 1)))
    autolabel(a)
    plt.xlim(min(keys) - 1, max(keys) + 1)
    plt.show()

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        plt.text(rect.get_x() + rect.get_width() / 2 - 0.2, height + 2, '%s' % int(height))

if __name__ == "__main__":
    root = 'data/processed_data/train_sort.pkl'
    data = count_images(root)
    print(data)
    visualize(data)

对上述代码的具体说明如下所示:

  1. 函数count_ids:用于统计每个唯一标识符的出现次数以及总字幕数量。
  2. 函数count_images:用于计算每个标签下的图像数量并返回图像数量的分布。
  3. 函数count_captions:用于统计每个标签下的字幕数量并记录其频率。
  4. 函数visualize:负责生成柱状图,展示输入数据的可视化结果。
  5. 函数autolabel:用于在柱状图上自动标记每个条形的高度。
  6. 在主程序中,文件加载了处理过的数据集,计算图像数量,并可视化结果。

(4)文件visualize.py的功能是可视化训练过程中的损失和准确率曲线,该文件的核心是函数 visualize_curve,该函数接受一个日志文件路径作为输入,读取训练日志并提取损失和准确率信息(包括图像到文本和文本到图像的 top-1 和 top-10 准确率)。然后,它生成两个图形:一个用于显示损失曲线,另一个用于显示准确率曲线。在准确率图中,分别绘制了图像到文本和文本到图像的 top-1 和 top-10 准确率。最后,结果图像保存为 train.jpg 并展示。主程序部分设置了日志文件路径并调用可视化函数。

import matplotlib.pyplot as plot
import os
import cv2
from matplotlib import pyplot as plt

def visualize_curve(log_root):
    log_file = open(log_root, 'r')
    result_root = log_root[:log_root.rfind('/') + 1] + 'train.jpg'
    loss = []
    
    top1_i2t = []
    top10_i2t = []
    top1_t2i = []
    top10_t2i = []
    for line in log_file.readlines():
        line = line.strip().split()
        
        if 'top10_t2i' not in line[-2]:
            continue
        
        loss.append(line[1])
        top1_i2t.append(line[3])
        top10_i2t.append(line[5])
        top1_t2i.append(line[7])
        top10_t2i.append(line[9])

    log_file.close()
    plt.figure('loss')
    plt.plot(loss)
    plt.figure('accuracy')
    plt.subplot(211)
    plt.plot(top1_i2t, label = 'top1')
    plt.plot(top10_i2t, label = 'top10')
    plt.legend(['image to text'], loc = 'upper right')
    plt.subplot(212)
    plt.plot(top1_t2i, label = 'top1')
    plt.plot(top10_i2t, label = 'top10')
    plt.legend(['text to image'], loc = 'upper right')
    plt.savefig(result_root)
    plt.show()

if __name__ == '__main__':
    log_root = 'data/logs/train.log'
    visualize_curve(log_root)

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

感谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值