PyTorch实战(8)——图像描述生成

PyTorch实战(8)------图像描述生成

    • [0. 前言](#0. 前言)
    • [1. 使用 CNN 和 LSTM 构建神经网络](#1. 使用 CNN 和 LSTM 构建神经网络)
      • [1.1 文本编码示例](#1.1 文本编码示例)
    • [2. 数据集处理](#2. 数据集处理)
      • [2.1 下载图像描述数据集](#2.1 下载图像描述数据集)
      • [2.2 文本标注数据预处理](#2.2 文本标注数据预处理)
      • [2.3 图像数据预处理](#2.3 图像数据预处理)
      • [2.4 定义数据加载器](#2.4 定义数据加载器)
    • [3. 构建图像描述生成模型](#3. 构建图像描述生成模型)
      • [3.1 定义 CNN-LSTM 模型](#3.1 定义 CNN-LSTM 模型)
      • [3.2 训练 CNN-LSTM 模型](#3.2 训练 CNN-LSTM 模型)
      • [3.3 使用训练好的模型生成图像描述](#3.3 使用训练好的模型生成图像描述)
    • 小结
    • 系列链接

0. 前言

卷积神经网络 (Convolutional Neural Network, CNN)作为一种重要的深度学习模型,在图像分类、目标检测、语音识别等涉及视觉和听觉的机器学习任务中表现卓越。其核心优势在于采用了具有参数共享特性的卷积层,参数共享基于图像特征(如边缘轮廓)与像素位置无关的假设。与之相对应,长短期记忆网络 (Long Short-Term Memory, LSTM)作为循环神经网络 (Recurrent Neural Network, RNN) 的变体,在处理文本等序列数据时展现出独特优势,能够有效建模词语间的时序依赖关系。

将这两种网络结构有机结合,可构建出能够实现图像到文本转换的多模态模型。这种混合模型的一个典型应用是图像描述生成,模型接收图像输入后,自动生成符合图像内容的文字描述。虽然机器学习应用于执行图像描述生成任务可追溯至 2010 年,但神经网络直到 2015 年左右才首次成功应用于这一任务。随着技术的不断改进,这一深度学习应用已切实在实际应用中发挥作用。本节将讨论这种多模态模型的架构及 PyTorch 实现细节,使用 PyTorch 从零开始构建一个图像描述生成模型。

1. 使用 CNN 和 LSTM 构建神经网络

CNN-LSTM 网络架构由卷积层(用于从输入图像数据中提取特征)和 LSTM 层(用于执行序列预测)组成,兼具空间深度 (CNN) 与时间深度 (LSTM) 双重特性。模型的卷积部分通常作为编码器,接收输入图像并输出高维特征或嵌入。

在实际应用中,CNN 部分通常采用预训练模型(如图像分类网络),然后,预训练 CNN 模型的最后一个隐藏层作为输入传递给 LSTM 部分,LSTM 作为解码器用于生成文本。

处理文本数据时,需要将单词和标点符号、标识符等符号(统称为词元,token )转化为数字,通过为文本中的每个词元分配一个唯一的数字来实现。接下来,我们将通过一个文本编码示例说明编码过程。

1.1 文本编码示例

假设我们正在构建一个机器学习模型来处理文本数据,原始文本为:

shell 复制代码
<start> PyTorch is a deep learning library. <end>

然后,将每个词元映射为数字:

shell 复制代码
<start> : 0
PyTorch : 1
is : 2
a : 3
deep : 4
learning : 5
library : 6
. : 7
<end> : 8

利用这个映射,可以将这个句子表示为数字序列:

shell 复制代码
<start> PyTorch is a deep learning library. <end> -> [0, 1, 2, 3, 4, 5, 6, 7, 8]

例如,<start> PyTorch is deep. <end> 被编码为 [0, 1, 2, 4, 7, 8]。这个映射通常称为词汇表,构建词汇表是大多数与文本相关的机器学习问题中的一个关键步骤。

在图像描述任务中,LSTM 模型作为解码器,在时间步 t=0 时接收 CNN 嵌入作为输入。然后,每个 LSTM 单元在每个时间步预测一个 token,并将其作为输入传递给下一个 LSTM 单元。最终整体架构如下所示。

如果输入不是一张图像,而是一系列图像(比如视频),则需在每个时间步都注入 CNN 编码特征,而不仅仅是在 t=0 时输入。

接下来,我们将使用 PyTorch 实现一个图像描述生成系统,涵盖混合模型构建、数据加载预处理、训练评估全流程。

2. 数据集处理

在本节中,我们将使用 COCO (Common Objects in Context) 数据集,这是一个大规模的物体检测、分割和描述数据集。COCO 数据集包含超过 200000 张带标签的图像,每张图像有五个描述,极大推动了计算机视觉领域的发展,现已成为物体检测、实例分割及图像描述等基准测试中最常用的数据集之一。在本节中,我们将使用 PyTorchCOCO 数据集上训练一个 CNN-LSTM 模型,并用于生成新图像的描述文本。

2.1 下载图像描述数据集

在构建图像描述系统之前,需要下载所需的数据集。训练数据集和验证数据集的大小分别为 13 GB6 GB。下载并解压数据集文件:

shell 复制代码
# linux
!apt-get install wget
# mac
# !brew install wget

!mkdir data_dir
!wget http://images.cocodataset.org/annotations/annotations_trainval2014.zip -P ./data_dir/
!wget http://images.cocodataset.org/zips/train2014.zip -P ./data_dir/
!wget http://images.cocodataset.org/zips/val2014.zip -P ./data_dir/
    
!unzip ./data_dir/annotations_trainval2014.zip -d ./data_dir/
!rm ./data_dir/annotations_trainval2014.zip
!unzip ./data_dir/train2014.zip -d ./data_dir/
!rm ./data_dir/train2014.zip 
!unzip ./data_dir/val2014.zip -d ./data_dir/ 
!rm ./data_dir/val2014.zip

输出结果如下所示:

该步骤会创建 ./data_dir 目录,自动下载压缩的图像文件与标注文件并解压至该目录。

2.2 文本标注数据预处理

数据集包含图像与文本描述两部分,在本节中,我们将对文本数据进行预处理,以便可以用于 CNN-LSTM 模型。

(1) 首先导入所需模块:

python 复制代码
import os
import nltk
import pickle
import numpy as np
from PIL import Image
from collections import Counter
from pycocotools.coco import COCO
import matplotlib.pyplot as plt
 
import torch
import torch.nn as nn
import torch.utils.data as data
from torchvision import transforms
import torchvision.models as models
import torchvision.transforms as transforms
from torch.nn.utils.rnn import pack_padded_sequence

nltk 是自然语言工具包,在构建词汇表时非常有用,而 pycocotools 是一个用于处理 COCO 数据集的辅助工具。pack_padded_sequence 函数可将不同长度(单词数量)的句子通过填充转换为固定长度格式。

(2) 下载 nltk 库的 punkt 分词器模型,用于将给定的文本分解为其组成单词:

python 复制代码
nltk.download('punkt')

(3) 接下来,构建词汇表 ------ 即一个可以将实际词元(如单词)转换为数字索引的字典,这一步骤对于大多数与文本相关的任务都是必不可少的。

首先,在词汇表构建函数中加载 JSON 格式的文本描述,并对描述中的单个单词进行分词转换成数字,并将其存储在计数器 counter 中:

python 复制代码
class Vocab(object):
    def __init__(self):
        self.w2i = {}
        self.i2w = {}
        self.index = 0
 
    def __call__(self, token):
        if not token in self.w2i:
            return self.w2i['<unk>']
        return self.w2i[token]
 
    def __len__(self):
        return len(self.w2i)
    def add_token(self, token):
        if not token in self.w2i:
            self.w2i[token] = self.index
            self.i2w[self.index] = token
            self.index += 1

def build_vocabulary(json, threshold):
    coco = COCO(json)
    counter = Counter()
    ids = coco.anns.keys()
    for i, id in enumerate(ids):
        caption = str(coco.anns[id]['caption'])
        tokens = nltk.tokenize.word_tokenize(caption.lower())
        counter.update(tokens)
 
        if (i+1) % 1000 == 0:
            print("[{}/{}] Tokenized the captions.".format(i+1, len(ids)))

然后,删除出现次数少于某个阈值的词元,其余的词元添加到词汇表对象中,同时加入一些特殊词元 ------ 句子开始 (start)、结束 (end)、未知单词 (unknown_word) 和填充 (padding) 词元:

python 复制代码
    tokens = [token for token, cnt in counter.items() if cnt >= threshold]
 
    vocab = Vocab()
    vocab.add_token('<pad>')
    vocab.add_token('<start>')
    vocab.add_token('<end>')
    vocab.add_token('<unk>')
 
    for i, token in enumerate(tokens):
        vocab.add_token(token)
    return vocab

最后,使用词汇表构建函数,创建一个词汇表对象 vocab 并将其保存在本地,以便后续使用:

python 复制代码
vocab = build_vocabulary(json='data_dir/annotations/captions_train2014.json', threshold=4)
vocab_path = './data_dir/vocabulary.pkl'
with open(vocab_path, 'wb') as f:
    pickle.dump(vocab, f)
print("Total vocabulary size: {}".format(len(vocab)))
print("Saved the vocabulary wrapper to '{}'".format(vocab_path))

输出结果如下所示:

完成词汇表构建后,即可在运行时将文本数据动态转换为数字序列。

2.3 图像数据预处理

在下载数据并为文本描述构建词汇表之后,我们还需要对图像数据进行一些预处理。由于数据集中的图像尺寸不一,需要将所有图像调整为固定形状以适应 CNN 模型输入要求:

python 复制代码
def reshape_image(image, shape):
    return image.resize(shape, Image.LANCZOS)
 
def reshape_images(image_path, output_path, shape):
    if not os.path.exists(output_path):
        os.makedirs(output_path)
 
    images = os.listdir(image_path)
    num_im = len(images)
    for i, im in enumerate(images):
        with open(os.path.join(image_path, im), 'r+b') as f:
            with Image.open(f) as image:
                image = reshape_image(image, shape)
                image.save(os.path.join(output_path, im), image.format)
        if (i+1) % 100 == 0:
            print ("[{}/{}] Resized the images and saved into '{}'."
                   .format(i+1, num_im, output_path))

image_path = './data_dir/train2014/'
output_path = './data_dir/resized_images/'
image_shape = [256, 256]
reshape_images(image_path, output_path, image_shape)

输出结果如下:

经过图像尺寸统一标准化处理(调整为 256×256 像素),所有输入图像均已适配 CNN 模型架构要求。

2.4 定义数据加载器

在完成数据下载与预处理后,需将数据封装为 PyTorch 数据集对象,进而构建可用于训练循环的数据加载器。

(1) 实现自定义数据加载器:

python 复制代码
class CustomCocoDataset(data.Dataset):
    def __init__(self, data_path, coco_json_path, vocabulary, transform=None):
        self.root = data_path
        self.coco_data = COCO(coco_json_path)
        self.indices = list(self.coco_data.anns.keys())
        self.vocabulary = vocabulary
        self.transform = transform
 
    def __getitem__(self, idx):
        coco_data = self.coco_data
        vocabulary = self.vocabulary
        annotation_id = self.indices[idx]
        caption = coco_data.anns[annotation_id]['caption']
        image_id = coco_data.anns[annotation_id]['image_id']
        image_path = coco_data.loadImgs(image_id)[0]['file_name']
 
        image = Image.open(os.path.join(self.root, image_path)).convert('RGB')
        if self.transform is not None:
            image = self.transform(image)
 
        word_tokens = nltk.tokenize.word_tokenize(str(caption).lower())
        caption = []
        caption.append(vocabulary('<start>'))
        caption.extend([vocabulary(token) for token in word_tokens])
        caption.append(vocabulary('<end>'))
        ground_truth = torch.Tensor(caption)
        return image, ground_truth
 
    def __len__(self):
        return len(self.indices)

自定义 PyTorch Dataset 对象,为实例化、获取数据样本和返回数据集大小分别定义了 __init____getitem____len__ 方法。

(2) 定义 collate_fn,该函数以 Xy 的形式返回数据批次:

python 复制代码
def collate_function(data_batch):
    data_batch.sort(key=lambda d: len(d[1]), reverse=True)
    imgs, caps = zip(*data_batch)

    imgs = torch.stack(imgs, 0)
    cap_lens = [len(cap) for cap in caps]
    tgts = torch.zeros(len(caps), max(cap_lens)).long()
    for i, cap in enumerate(caps):
        end = cap_lens[i]
        tgts[i, :end] = cap[:end]        
    return imgs, tgts, cap_lens

通常,我们不需要编写 collate_fn 函数,这里定义 collate_fn 为了处理可变长度的句子。这样,当句子的长度 k 小于固定长度 n 时,需要使用 pack_padded_sequence 函数用 <pad> 词元进行填充。

(3) 最后,实现 get_loader 函数,该函数返回一个用于 COCO 数据集的自定义数据加载器:

python 复制代码
def get_loader(data_path, coco_json_path, vocabulary, transform, batch_size, shuffle):
    coco_dataser = CustomCocoDataset(data_path=data_path,
                       coco_json_path=coco_json_path,
                       vocabulary=vocabulary,
                       transform=transform)
    
    custom_data_loader = torch.utils.data.DataLoader(dataset=coco_dataser, 
                                              batch_size=batch_size,
                                              shuffle=shuffle,
                                              collate_fn=collate_function)
    return custom_data_loader

完成数据管道构建后,开始着手定义模型。

3. 构建图像描述生成模型

3.1 定义 CNN-LSTM 模型

(1) 构建模型,定义两个子模型,包括 CNN 模型和 LSTM 模型:

python 复制代码
class CNNModel(nn.Module):
    def __init__(self, embedding_size):
        super(CNNModel, self).__init__()
        resnet = models.resnet152(weights=models.ResNet152_Weights.DEFAULT)
        module_list = list(resnet.children())[:-1]      # delete the last fc layer.
        self.resnet_module = nn.Sequential(*module_list)
        self.linear_layer = nn.Linear(resnet.fc.in_features, embedding_size)
        self.batch_norm = nn.BatchNorm1d(embedding_size, momentum=0.01)
        
    def forward(self, input_images):
        with torch.no_grad():
            resnet_features = self.resnet_module(input_images)
        resnet_features = resnet_features.reshape(resnet_features.size(0), -1)
        final_features = self.batch_norm(self.linear_layer(resnet_features))
        return final_features

对于 CNN 部分,使用了一个预训练的 CNN 模型ResNet 152。移除预训练的 ResNet 模型的最后一层,替换为全连接层+批归一化层组合。

神经网络可以被看作是一系列的权重矩阵,一个预训练的模型则可以被看作是一系列经过精心调整的权重矩阵。通过替换最后一层,实际上是在将最后一个权重矩阵(假设倒数第二层有 K 个神经元,那么是一个 K x 1000 维的矩阵)替换为一个新的随机初始化的权重矩阵( K x 256 维的矩阵,其中 256 是新的输出大小)。

批归一化层 (Batch Normalization Layer) 会对全连接层的输出进行标准化处理,使得整个批次的输出数据均值为 0、标准差为 1。这类似于使用 torch.transforms 进行输入数据归一化的方式,批归一化有助于限制隐藏层输出值的波动范围,通常也能加速模型学习过程。由于优化超平面变得更加均匀(均值为 0,标准差为 1),我们可以采用更高的学习率。

批归一化能够有效隔离 CNN 可能引发的数据偏移,保护 LSTM 子模型不受影响。如果不使用批归一化,那么在最坏情况下,CNN 最后一层可能会在训练时输出均值大于 0.5 且标准差为 1 的值(假设训练数据的分布有限)。但在推理阶段,如果某张图像的 CNN 输出均值小于 0.5 且标准差为 1,那么 LSTM 子模型就会很难在这种新的数据分布上正常工作。因此,我们需要通过批归一化来标准化 CNN 输出(即 LSTM 的输入),从而确保 LSTM 不会产生异常输出。

使用自定义全连接层,因为我们不需要 ResNet 模型输出的 1000 个类别概率,而是希望生成每个图像的嵌入向量。这个嵌入向量可以视为输入图像的一维数值编码表示,随后将被输入到 LSTM 模型中。

(2) LSTM 层接收这些嵌入向量作为输入,并输出一个单词序列------这个序列理想情况下应该能准确描述生成该嵌入向量的图像:

python 复制代码
class LSTMModel(nn.Module):
    def __init__(self, embedding_size, hidden_layer_size, vocabulary_size, num_layers, max_seq_len=20):
        super(LSTMModel, self).__init__()
        self.embedding_layer = nn.Embedding(vocabulary_size, embedding_size)
        self.lstm_layer = nn.LSTM(embedding_size, hidden_layer_size, num_layers, batch_first=True)
        self.linear_layer = nn.Linear(hidden_layer_size, vocabulary_size)
        self.max_seq_len = max_seq_len
        
    def forward(self, input_features, capts, lens):
        embeddings = self.embedding_layer(capts)
        embeddings = torch.cat((input_features.unsqueeze(1), embeddings), 1)
        lstm_input = pack_padded_sequence(embeddings, lens, batch_first=True) 
        hidden_variables, _ = self.lstm_layer(lstm_input)
        model_outputs = self.linear_layer(hidden_variables[0])
        return model_outputs

LSTM 模型由 LSTM 层和全连接层组成。在本节中,这些 LSTM 单元将在每个时间步输出单词预测概率,最终将概率最高的单词逐步拼接成完整句子。在每个时间步的 LSTM 单元除了输出预测结果外,还会生成内部单元状态 (cell state),这个状态会作为下一个时间步 LSTM 单元的输入。该过程持续循环,直到某个 LSTM 单元输出 <end> 结束词元。<end> 词元附加到输出句子后,就得到了最终的图像描述文本。

需要注意的是,我们通过 max_seq_len 参数将最大序列长度设定为 20 个单词。这意味着不足 20 个单词的句子,都会在结尾处填充空词元,超过 20 词的则截断保留前 20 词。

理论上,如果我们希望 LSTM 处理任意长度的句子,可以将该值设为极大值(如 9999)。但基于两点考量:首先,现实中极少有图像描述需要这么多词汇;更重要的是,过长的异常序列会导致 LSTM 难以有效学习跨时间步的时序模式。虽然 LSTM 比传统 RNN 在处理较长序列时更有效,但在超长序列上保持记忆仍然困难。考虑到常见图像描述的长度分布,我们选择 20 作为平衡值。

(3) 以上 LSTM 层和线性层对象都继承自 nn.Module,我们通过 __init__ 方法构建模型结构,forward 方法实现前向传播。对于 LSTM 模型,额外实现了一个 sample 方法,该方法采用贪心搜索策略生成句子,即始终选择整体概率最高的单词序列,这对后续图像描述生成非常有用:

python 复制代码
    def sample(self, input_features, lstm_states=None):
        sampled_indices = []
        lstm_inputs = input_features.unsqueeze(1)
        for i in range(self.max_seq_len):
            hidden_variables, lstm_states = self.lstm_layer(lstm_inputs, lstm_states)          # hiddens: (batch_size, 1, hidden_size)
            model_outputs = self.linear_layer(hidden_variables.squeeze(1))            # outputs:  (batch_size, vocab_size)
            _, predicted_outputs = model_outputs.max(1)                        # predicted: (batch_size)
            sampled_indices.append(predicted_outputs)
            lstm_inputs = self.embedding_layer(predicted_outputs)                       # inputs: (batch_size, embed_size)
            lstm_inputs = lstm_inputs.unsqueeze(1)                         # inputs: (batch_size, 1, embed_size)
        sampled_indices = torch.stack(sampled_indices, 1)                # sampled_ids: (batch_size, max_seq_length)
        return sampled_indices

完成了图像描述模型的架构定义后,接下来进入模型训练阶段。

3.2 训练 CNN-LSTM 模型

在完成模型架构定义后,开始训练 CNN-LSTM 模型。

(1) 首先,定义设备:

python 复制代码
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

(2) 虽然所有图像尺寸都已统一调整为 256×256,但我们仍然需要对数据进行归一化。归一化至关重要,因为不同的数据维度可能存在分布差异,这会导致优化空间扭曲,导致梯度下降效率低下。使用 PyTorchtransform 模块归一化输入图像的像素值:

python 复制代码
if not os.path.exists('models_dir/'):
    os.makedirs('models_dir/')

    
transform = transforms.Compose([ 
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(), 
    transforms.ToTensor(), 
    transforms.Normalize((0.485, 0.456, 0.406), 
                         (0.229, 0.224, 0.225))])

此外,还需要对数据集进行数据增强。数据增强不仅能扩充训练数据规模,更能提升模型对输入数据变化的鲁棒性。使用 PyTorchtransform 模块,本节中我们实现了两种数据增强技术:

  • 随机裁剪,将图像大小从 (256, 256) 缩小到 (224, 224)。通过截取图像不同区域,实现了单张图像生成多张样本的效果。这也使得模型能够学习到更具鲁棒性的图像表示,而不仅仅专注于训练数据集中原始的 (256, 256) 大小的图像
  • 图像的水平翻转。这种方法可使数据量翻倍,更多的数据通常意味着更好的效果。对于本节所用的数据集,水平翻转不会改变图像本质语义(翻转后的狗图像依然是狗),因此增强效果显著。但需注意应用场景的适用性,如果我们在做数字识别任务,那么将数字 5 的图像水平翻转并继续训练模型去识别它为 5 会导致模型混淆,因为翻转后的图像看起来更像数字 2,而不是 5

(3) 加载构建的词汇表,并使用 get_loader() 函数来初始化数据加载器:

python 复制代码
with open('data_dir/vocabulary.pkl', 'rb') as f:
    vocabulary = pickle.load(f)
    
custom_data_loader = get_loader('data_dir/resized_images', 'data_dir/annotations/captions_train2014.json', vocabulary, 
                         transform, 128,
                         shuffle=True) 

(4) 实例化 CNNLSTM 模型,分别作为编码器 (encoder) 和解码器 (decoder),同时定义交叉熵损失函数和 Adam 优化器:

python 复制代码
encoder_model = CNNModel(256).to(device)
decoder_model = LSTMModel(256, 512, len(vocabulary), 1).to(device)

loss_criterion = nn.CrossEntropyLoss()
parameters = list(decoder_model.parameters()) + list(encoder_model.linear_layer.parameters()) + list(encoder_model.batch_norm.parameters())
optimizer = torch.optim.Adam(parameters, lr=0.001)

Adam 优化器在处理稀疏数据时是最佳选择。当前任务同时涉及图像和文本,这两类数据的稀疏性特征明显:图像中并非所有像素都携带有效信息,而数值化/向量化的文本本身就是稀疏矩阵。

(5) 最后执行训练循环,在循环中,使用数据加载器获取 COCO 数据集的小批量数据,先后通过编码器和解码器网络进行前向传播,并最终通过反向传播调整 CNN-LSTM 模型参数:

python 复制代码
total_num_steps = len(custom_data_loader)
for epoch in range(5):
    for i, (imgs, caps, lens) in enumerate(custom_data_loader):
 
        imgs = imgs.to(device)
        caps = caps.to(device)
        tgts = pack_padded_sequence(caps, lens, batch_first=True)[0]
 
        feats = encoder_model(imgs)
        outputs = decoder_model(feats, caps, lens)
        loss = loss_criterion(outputs, tgts)
        decoder_model.zero_grad()
        encoder_model.zero_grad()
        loss.backward()
        optimizer.step()

每完成 1000 次训练迭代,保存一次模型检查点:

python 复制代码
        if i % 10 == 0:
            print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Perplexity: {:5.4f}'
                  .format(epoch, 5, i, total_num_steps, loss.item(), np.exp(loss.item()))) 
 
        if (i+1) % 1000 == 0:
            torch.save(decoder_model.state_dict(), os.path.join(
                'models_dir/', 'decoder-{}-{}.ckpt'.format(epoch+1, i+1)))
            torch.save(encoder_model.state_dict(), os.path.join(
                'models_dir/', 'encoder-{}-{}.ckpt'.format(epoch+1, i+1)))

输出结果如下所示:

3.3 使用训练好的模型生成图像描述

在完成图像描述模型的训练后。在本节中,我们将使用训练好的模型为之前未见过的图像生成描述文字。

(1) 准备测试样本 sample.jpg 并定义预处理函数。该函数会加载图像并将其调整为 224×224 像素,随后通过转换模块对像素值进行归一化处理:

python 复制代码
image_file_path = 'sample.jpg'

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

def load_image(image_file_path, transform=None):
    img = Image.open(image_file_path).convert('RGB')
    img = img.resize([224, 224], Image.LANCZOS)
    
    if transform is not None:
        img = transform(img).unsqueeze(0)
    
    return img

transform = transforms.Compose([
    transforms.ToTensor(), 
    transforms.Normalize((0.485, 0.456, 0.406), 
                         (0.229, 0.224, 0.225))])

(2) 加载词汇表并实例化编码器-解码器模型:

python 复制代码
with open('data_dir/vocabulary.pkl', 'rb') as f:
    vocabulary = pickle.load(f)

encoder_model = CNNModel(256).eval()  # eval mode (batchnorm uses moving mean/variance)
decoder_model = LSTMModel(256, 512, len(vocabulary), 1)
encoder_model = encoder_model.to(device)
decoder_model = decoder_model.to(device)

(3) 准备好模型框架后,使用训练过程中保存的检查点设置模型参数:

python 复制代码
encoder_model.load_state_dict(torch.load('models_dir/encoder-2-3000.ckpt'))
decoder_model.load_state_dict(torch.load('models_dir/decoder-2-3000.ckpt'))

(4) 接下来,加载图像并执行模型推理------也就是说,首先使用编码器生成图像嵌入向量,随后将嵌入向量输入解码器生成序列:

python 复制代码
img = load_image(image_file_path, transform)
img_tensor = img.to(device)


feat = encoder_model(img_tensor)
sampled_indices = decoder_model.sample(feat)
sampled_indices = sampled_indices[0].cpu().numpy()          # (1, max_seq_length) -> (max_seq_length)

(5) 此时生成的描述仍为数值词元 (token) 形式,需利用词汇表进行反向转换,将数值词元转换为实际文本:

python 复制代码
predicted_caption = []
for token_index in sampled_indices:
    word = vocabulary.i2w[token_index]
    predicted_caption.append(word)
    if word == '<end>':
        break
predicted_sentence = ' '.join(predicted_caption)

(6) 完成文本转换后,可视化图像及其生成描述:

python 复制代码
print (predicted_sentence)
img = Image.open(image_file_path)
plt.imshow(np.asarray(img))

输出结果如下所示,模型已能生成合理的图像描述:

小结

本节重点探讨了卷积神经网络 (Convolutional Neural Network, CNN) 与长短期记忆网络 (Long Short-Term Memory, LSTM) 在编码器-解码器框架中的协同应用,包括:两种模型的联合训练方法,以及组合模型在图像描述生成中的应用。

系列链接

PyTorch实战(1)------深度学习概述
PyTorch实战(2)------使用PyTorch构建神经网络
PyTorch实战(3)------PyTorch vs. TensorFlow详解
PyTorch实战(4)------卷积神经网络(Convolutional Neural Network,CNN)
PyTorch实战(5)------深度卷积神经网络
PyTorch实战(6)------模型微调详解
PyTorch实战(7)------循环神经网络

相关推荐
AI绘画哇哒哒4 小时前
实战:SQL统一访问200+数据源,构建企业级智能检索与RAG系统(下)
人工智能·sql·深度学习·学习·ai·程序员·大模型
HyperAI超神经5 小时前
香港科技大学提出融合神经网络框架,高效预测蛋白质序列的多金属结合位点
人工智能·深度学习·ai
辞--忧5 小时前
PyTorch 实战:CIFAR-10 图像分类与网络优化
pytorch·分类
王彦臻5 小时前
PyTorch 中模型测试与全局平均池化的应用总结
人工智能·pytorch·python
却道天凉_好个秋7 小时前
深度学习(十三):向量化与矩阵化
人工智能·深度学习·矩阵·向量化
大模型铲屎官7 小时前
【数据结构与算法-Day 35】拓扑排序:从依赖关系到关键路径的完整解析
人工智能·python·深度学习·操作系统·数据结构与算法·关键路径·扩扑排序
糖葫芦君7 小时前
17-Language Modeling with Gated Convolutional Networks
深度学习·机器学习·语言模型
蒜鸟~蒜鸟~7 小时前
PyTorch 神经网络工具箱全解析:从核心组件到模型实战
pytorch·神经网络
火山引擎边缘云9 小时前
字节跳动多媒体实验室联合ISCAS举办第五届神经网络视频编码竞赛
人工智能·深度学习·神经网络