Transferência de Estilo Neural ============================== Se você usa aplicativos de compartilhamento social ou é um fotógrafo amador, está familiarizado com os filtros. Os filtros podem alterar os estilos de cor das fotos para tornar o fundo mais nítido ou o rosto das pessoas mais branco. No entanto, um filtro geralmente só pode alterar um aspecto de uma foto. Para criar a foto ideal, muitas vezes você precisa tentar muitas combinações de filtros diferentes. Esse processo é tão complexo quanto ajustar os hiperparâmetros de um modelo. Nesta seção, discutiremos como podemos usar redes neurais de convolução (CNNs) para aplicar automaticamente o estilo de uma imagem a outra imagem, uma operação conhecida como transferência de estilo :cite:`Gatys.Ecker.Bethge.2016`. Aqui, precisamos de duas imagens de entrada, uma imagem de conteúdo e uma imagem de estilo. Usamos uma rede neural para alterar a imagem do conteúdo de modo que seu estilo espelhe o da imagem do estilo. Em :numref:`fig_style_transfer`, a imagem do conteúdo é uma foto de paisagem que o autor tirou na Mount Rainier National Part perto de Seattle. A imagem do estilo é uma pintura a óleo de carvalhos no outono. A imagem composta de saída retém as formas gerais dos objetos na imagem de conteúdo, mas aplica a pincelada de pintura a óleo da imagem de estilo e torna a cor geral mais vívida. .. _fig_style_transfer: .. figure:: ../img/style-transfer.svg Imagens de entrada de conteúdo e estilo e imagem composta produzida por transferência de estilo. Técnica ------- O modelo de transferência de estilo baseado em CNN é mostrado em :numref:`fig_style_transfer_model`. Primeiro, inicializamos a imagem composta. Por exemplo, podemos inicializá-la como a imagem do conteúdo. Esta imagem composta é a única variável que precisa ser atualizada no processo de transferência de estilo, ou seja, o parâmetro do modelo a ser atualizado na transferência de estilo. Em seguida, selecionamos uma CNN pré-treinada para extrair recursos de imagem. Esses parâmetros do modelo não precisam ser atualizados durante o treinamento. O CNN profundo usa várias camadas neurais que extraem sucessivamente recursos de imagem. Podemos selecionar a saída de certas camadas para usar como recursos de conteúdo ou recursos de estilo. Se usarmos a estrutura em :numref:`fig_style_transfer_model`, a rede neural pré-treinada contém três camadas convolucionais. A segunda camada produz os recursos de conteúdo da imagem, enquanto as saídas da primeira e terceira camadas são usadas como recursos de estilo. Em seguida, usamos a propagação para a frente (na direção das linhas sólidas) para calcular a função de perda de transferência de estilo e a propagação para trás (na direção das linhas pontilhadas) para atualizar o parâmetro do modelo, atualizando constantemente a imagem composta. As funções de perda usadas na transferência de estilo geralmente têm três partes: 1. A perda de conteúdo é usada para fazer a imagem composta se aproximar da imagem de conteúdo no que diz respeito aos recursos de conteúdo. 2. A perda de estilo é usada para fazer com que a imagem composta se aproxime da imagem de estilo em termos de recursos de estilo. 3. A perda total de variação ajuda a reduzir o ruído na imagem composta. Finalmente, depois de terminar de treinar o modelo, produzimos os parâmetros do modelo de transferência de estilo para obter a imagem composta final. .. _fig_style_transfer_model: .. figure:: ../img/neural-style.svg Processo de transferência de estilo baseado em CNN. As linhas sólidas mostram a direção da propagação para a frente e as linhas pontilhadas mostram a propagação para trás. A seguir, faremos um experimento para nos ajudar a entender melhor os detalhes técnicos da transferência de estilo. Lendo o Conteúdo e as Imagens de Estilo --------------------------------------- Primeiro, lemos o conteúdo e as imagens de estilo. Ao imprimir os eixos de coordenadas da imagem, podemos ver que eles têm dimensões diferentes. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python %matplotlib inline from mxnet import autograd, gluon, image, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() d2l.set_figsize() content_img = image.imread('../img/rainier.jpg') d2l.plt.imshow(content_img.asnumpy()); .. figure:: output_neural-style_5de8ca_3_0.svg .. code:: python style_img = image.imread('../img/autumn-oak.jpg') d2l.plt.imshow(style_img.asnumpy()); .. figure:: output_neural-style_5de8ca_4_0.svg .. raw:: html
.. raw:: html
.. code:: python %matplotlib inline import torch import torchvision from torch import nn from d2l import torch as d2l d2l.set_figsize() content_img = d2l.Image.open('../img/rainier.jpg') d2l.plt.imshow(content_img); .. figure:: output_neural-style_5de8ca_7_0.svg .. code:: python style_img = d2l.Image.open('../img/autumn-oak.jpg') d2l.plt.imshow(style_img); .. figure:: output_neural-style_5de8ca_8_0.svg .. raw:: html
.. raw:: html
Pré-processamento e Pós-processamento ------------------------------------- A seguir, definimos as funções de pré-processamento e pós-processamento de imagens. A função ``pré-processamento`` normaliza cada um dos três canais RGB das imagens de entrada e transforma os resultados em um formato que pode ser inserido na CNN. A função ``postprocess`` restaura os valores de pixel na imagem de saída para seus valores originais antes da normalização. Como a função de impressão de imagem requer que cada pixel tenha um valor de ponto flutuante de 0 a 1, usamos a função ``clip`` para substituir valores menores que 0 ou maiores que 1 por 0 ou 1, respectivamente. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python rgb_mean = np.array([0.485, 0.456, 0.406]) rgb_std = np.array([0.229, 0.224, 0.225]) def preprocess(img, image_shape): img = image.imresize(img, *image_shape) img = (img.astype('float32') / 255 - rgb_mean) / rgb_std return np.expand_dims(img.transpose(2, 0, 1), axis=0) def postprocess(img): img = img[0].as_in_ctx(rgb_std.ctx) return (img.transpose(1, 2, 0) * rgb_std + rgb_mean).clip(0, 1) .. raw:: html
.. raw:: html
.. code:: python rgb_mean = torch.tensor([0.485, 0.456, 0.406]) rgb_std = torch.tensor([0.229, 0.224, 0.225]) def preprocess(img, image_shape): transforms = torchvision.transforms.Compose([ torchvision.transforms.Resize(image_shape), torchvision.transforms.ToTensor(), torchvision.transforms.Normalize(mean=rgb_mean, std=rgb_std)]) return transforms(img).unsqueeze(0) def postprocess(img): img = img[0].to(rgb_std.device) img = torch.clamp(img.permute(1, 2, 0) * rgb_std + rgb_mean, 0, 1) return torchvision.transforms.ToPILImage()(img.permute(2, 0, 1)) .. raw:: html
.. raw:: html
Extraindo *Features* -------------------- Usamos o modelo VGG-19 pré-treinado no conjunto de dados ImageNet para extrair características da imagem [1]. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python pretrained_net = gluon.model_zoo.vision.vgg19(pretrained=True) .. raw:: html
.. raw:: html
.. code:: python pretrained_net = torchvision.models.vgg19(pretrained=True) .. raw:: html
.. raw:: html
Para extrair o conteúdo da imagem e os recursos de estilo, podemos selecionar as saídas de certas camadas na rede VGG. Em geral, quanto mais próxima uma saída estiver da camada de entrada, mais fácil será extrair informações detalhadas da imagem. Quanto mais longe uma saída estiver, mais fácil será extrair informações globais. Para evitar que a imagem composta retenha muitos detalhes da imagem de conteúdo, selecionamos uma camada de rede VGG próxima à camada de saída para produzir os recursos de conteúdo da imagem. Essa camada é chamada de camada de conteúdo. Também selecionamos as saídas de diferentes camadas da rede VGG para combinar os estilos local e global. Elas são chamadas de camadas de estilo. Como mencionamos em :numref:`sec_vgg`, as redes VGG têm cinco blocos convolucionais. Neste experimento, selecionamos a última camada convolucional do quarto bloco convolucional como a camada de conteúdo e a primeira camada de cada bloco como camadas de estilo. Podemos obter os índices para essas camadas imprimindo a instância ``pretrained_net``. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python style_layers, content_layers = [0, 5, 10, 19, 28], [25] .. raw:: html
.. raw:: html
.. code:: python style_layers, content_layers = [0, 5, 10, 19, 28], [25] .. raw:: html
.. raw:: html
Durante a extração de *features*, só precisamos usar todas as camadas VGG da camada de entrada até a camada de conteúdo ou estilo mais próxima da camada de saída. Abaixo, construímos uma nova rede, ``net``, que retém apenas as camadas da rede VGG que precisamos usar. Em seguida, usamos ``net`` para extrair *features*. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python net = nn.Sequential() for i in range(max(content_layers + style_layers) + 1): net.add(pretrained_net.features[i]) .. raw:: html
.. raw:: html
.. code:: python net = nn.Sequential(*[pretrained_net.features[i] for i in range(max(content_layers + style_layers) + 1)]) .. raw:: html
.. raw:: html
Dada a entrada ``X``, se simplesmente chamarmos a computação direta de ``net (X)``, só podemos obter a saída da última camada. Como também precisamos das saídas das camadas intermediárias, precisamos realizar computação camada por camada e reter o conteúdo e as saídas da camada de estilo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def extract_features(X, content_layers, style_layers): contents = [] styles = [] for i in range(len(net)): X = net[i](X) if i in style_layers: styles.append(X) if i in content_layers: contents.append(X) return contents, styles .. raw:: html
.. raw:: html
.. code:: python def extract_features(X, content_layers, style_layers): contents = [] styles = [] for i in range(len(net)): X = net[i](X) if i in style_layers: styles.append(X) if i in content_layers: contents.append(X) return contents, styles .. raw:: html
.. raw:: html
A seguir, definimos duas funções: a função ``get_contents`` obtém as *features* de conteúdo extraídos da imagem do conteúdo, enquanto a função ``get_styles`` obtém os recursos de estilo extraídos da imagem de estilo. Como não precisamos alterar os parâmetros do modelo VGG pré-treinado durante o treinamento, podemos extrair as *features* de conteúdo da imagem de conteúdo e recursos de estilo da imagem de estilo antes do início do treinamento. Como a imagem composta é o parâmetro do modelo que deve ser atualizado durante a transferência do estilo, só podemos chamar a função ``extract_features`` durante o treinamento para extrair o conteúdo e os recursos de estilo da imagem composta. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def get_contents(image_shape, device): content_X = preprocess(content_img, image_shape).copyto(device) contents_Y, _ = extract_features(content_X, content_layers, style_layers) return content_X, contents_Y def get_styles(image_shape, device): style_X = preprocess(style_img, image_shape).copyto(device) _, styles_Y = extract_features(style_X, content_layers, style_layers) return style_X, styles_Y .. raw:: html
.. raw:: html
.. code:: python def get_contents(image_shape, device): content_X = preprocess(content_img, image_shape).to(device) contents_Y, _ = extract_features(content_X, content_layers, style_layers) return content_X, contents_Y def get_styles(image_shape, device): style_X = preprocess(style_img, image_shape).to(device) _, styles_Y = extract_features(style_X, content_layers, style_layers) return style_X, styles_Y .. raw:: html
.. raw:: html
Definindo a Função de Perda --------------------------- A seguir, veremos a função de perda usada para transferência de estilo. A função de perda inclui a perda de conteúdo, perda de estilo e perda total de variação. Perda de Conteúdo ~~~~~~~~~~~~~~~~~ Semelhante à função de perda usada na regressão linear, a perda de conteúdo usa uma função de erro quadrado para medir a diferença nos recursos de conteúdo entre a imagem composta e a imagem de conteúdo. As duas entradas da função de erro quadrada são ambas saídas da camada de conteúdo obtidas da função ``extract_features``. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def content_loss(Y_hat, Y): return np.square(Y_hat - Y).mean() .. raw:: html
.. raw:: html
.. code:: python def content_loss(Y_hat, Y): # we 'detach' the target content from the tree used # to dynamically compute the gradient: this is a stated value, # not a variable. Otherwise the loss will throw an error. return torch.square(Y_hat - Y.detach()).mean() .. raw:: html
.. raw:: html
Perda de Estilo ~~~~~~~~~~~~~~~ A perda de estilo, semelhante à perda de conteúdo, usa uma função de erro quadrático para medir a diferença de estilo entre a imagem composta e a imagem de estilo. Para expressar a saída de estilos pelas camadas de estilo, primeiro usamos a função ``extract_features`` para calcular a saída da camada de estilo. Supondo que a saída tenha 1 exemplo, :math:`c` canais, e uma altura e largura de :math:`h` e :math:`w`, podemos transformar a saída na matriz :math:`\mathbf{X}`, que tem :math:`c` linhas e :math:`h \cdot w` colunas. Você pode pensar na matriz :math:`\mathbf{X}` como a combinação dos vetores :math:`c` e :math:`\mathbf{x}_1, \ldots, \mathbf{x}_c`, que têm um comprimento de :math:`hw`. Aqui, o vetor :math:`\mathbf{x}_i` representa a característica de estilo do canal :math:`i`. Na matriz Gram desses vetores :math:`\mathbf{X}\mathbf{X}^\top \in \mathbb{R}^{c \times c}`, elemento :math:`x_{ij}` na linha :math:`i` coluna :math:`j` é o produto interno dos vetores :math:`\mathbf{x}_i` e :math:`\mathbf{x}_j`. Ele representa a correlação dos recursos de estilo dos canais :math:`i` e :math:`j`. Usamos esse tipo de matriz de Gram para representar a saída do estilo pelas camadas de estilo. Você deve notar que, quando o valor :math:`h \cdot w` é grande, isso geralmente leva a valores grandes na matriz de Gram. Além disso, a altura e a largura da matriz de Gram são o número de canais :math:`c`. Para garantir que a perda de estilo não seja afetada pelo tamanho desses valores, definimos a função ``gram`` abaixo para dividir a matriz de Gram pelo número de seus elementos, ou seja, :math:`c \cdot h \cdot w`. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def gram(X): num_channels, n = X.shape[1], d2l.size(X) // X.shape[1] X = X.reshape((num_channels, n)) return np.dot(X, X.T) / (num_channels * n) .. raw:: html
.. raw:: html
.. code:: python def gram(X): num_channels, n = X.shape[1], X.numel() // X.shape[1] X = X.reshape((num_channels, n)) return torch.matmul(X, X.T) / (num_channels * n) .. raw:: html
.. raw:: html
Naturalmente, as duas entradas de matriz de Gram da função de erro quadrado para perda de estilo são obtidas da imagem composta e das saídas da camada de estilo de imagem de estilo. Aqui, assumimos que a matriz de Gram da imagem de estilo, ``gram_Y``, foi calculada antecipadamente. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def style_loss(Y_hat, gram_Y): return np.square(gram(Y_hat) - gram_Y).mean() .. raw:: html
.. raw:: html
.. code:: python def style_loss(Y_hat, gram_Y): return torch.square(gram(Y_hat) - gram_Y.detach()).mean() .. raw:: html
.. raw:: html
Perda de Variância Total ~~~~~~~~~~~~~~~~~~~~~~~~ Às vezes, as imagens compostas que aprendemos têm muito ruído de alta frequência, principalmente pixels claros ou escuros. Um método comum de redução de ruído é a redução total de ruído na variação. Assumimos que :math:`x_ {i, j}` representa o valor do pixel na coordenada :math:`(i, j)`, então a perda total de variância é: .. math:: \sum_{i, j} \left|x_{i, j} - x_{i+1, j}\right| + \left|x_{i, j} - x_{i, j+1}\right|. Tentamos tornar os valores dos pixels vizinhos tão semelhantes quanto possível. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def tv_loss(Y_hat): return 0.5 * (np.abs(Y_hat[:, :, 1:, :] - Y_hat[:, :, :-1, :]).mean() + np.abs(Y_hat[:, :, :, 1:] - Y_hat[:, :, :, :-1]).mean()) .. raw:: html
.. raw:: html
.. code:: python def tv_loss(Y_hat): return 0.5 * (torch.abs(Y_hat[:, :, 1:, :] - Y_hat[:, :, :-1, :]).mean() + torch.abs(Y_hat[:, :, :, 1:] - Y_hat[:, :, :, :-1]).mean()) .. raw:: html
.. raw:: html
Função de Perda ~~~~~~~~~~~~~~~ A função de perda para transferência de estilo é a soma ponderada da perda de conteúdo, perda de estilo e perda total de variância. Ajustando esses hiperparâmetros de peso, podemos equilibrar o conteúdo retido, o estilo transferido e a redução de ruído na imagem composta de acordo com sua importância relativa. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python content_weight, style_weight, tv_weight = 1, 1e3, 10 def compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram): # Calculate the content, style, and total variance losses respectively contents_l = [content_loss(Y_hat, Y) * content_weight for Y_hat, Y in zip( contents_Y_hat, contents_Y)] styles_l = [style_loss(Y_hat, Y) * style_weight for Y_hat, Y in zip( styles_Y_hat, styles_Y_gram)] tv_l = tv_loss(X) * tv_weight # Add up all the losses l = sum(styles_l + contents_l + [tv_l]) return contents_l, styles_l, tv_l, l .. raw:: html
.. raw:: html
.. code:: python content_weight, style_weight, tv_weight = 1, 1e3, 10 def compute_loss(X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram): # Calculate the content, style, and total variance losses respectively contents_l = [content_loss(Y_hat, Y) * content_weight for Y_hat, Y in zip( contents_Y_hat, contents_Y)] styles_l = [style_loss(Y_hat, Y) * style_weight for Y_hat, Y in zip( styles_Y_hat, styles_Y_gram)] tv_l = tv_loss(X) * tv_weight # Add up all the losses l = sum(styles_l + contents_l + [tv_l]) return contents_l, styles_l, tv_l, l .. raw:: html
.. raw:: html
Criação e inicialização da imagem composta ------------------------------------------ Na transferência de estilo, a imagem composta é a única variável que precisa ser atualizada. Portanto, podemos definir um modelo simples, ``GeneratedImage``, e tratar a imagem composta como um parâmetro do modelo. No modelo, a computação direta retorna apenas o parâmetro do modelo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python class GeneratedImage(nn.Block): def __init__(self, img_shape, **kwargs): super(GeneratedImage, self).__init__(**kwargs) self.weight = self.params.get('weight', shape=img_shape) def forward(self): return self.weight.data() .. raw:: html
.. raw:: html
.. code:: python class GeneratedImage(nn.Module): def __init__(self, img_shape, **kwargs): super(GeneratedImage, self).__init__(**kwargs) self.weight = nn.Parameter(torch.rand(*img_shape)) def forward(self): return self.weight .. raw:: html
.. raw:: html
A seguir, definimos a função ``get_inits``. Esta função cria uma instância de modelo de imagem composta e a inicializa na imagem ``X``. A matriz de Gram para as várias camadas de estilo da imagem de estilo, ``styles_Y_gram``, é calculada antes do treinamento. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def get_inits(X, device, lr, styles_Y): gen_img = GeneratedImage(X.shape) gen_img.initialize(init.Constant(X), ctx=device, force_reinit=True) trainer = gluon.Trainer(gen_img.collect_params(), 'adam', {'learning_rate': lr}) styles_Y_gram = [gram(Y) for Y in styles_Y] return gen_img(), styles_Y_gram, trainer .. raw:: html
.. raw:: html
.. code:: python def get_inits(X, device, lr, styles_Y): gen_img = GeneratedImage(X.shape).to(device) gen_img.weight.data.copy_(X.data) trainer = torch.optim.Adam(gen_img.parameters(), lr=lr) styles_Y_gram = [gram(Y) for Y in styles_Y] return gen_img(), styles_Y_gram, trainer .. raw:: html
.. raw:: html
Treinamento ----------- Durante o treinamento do modelo, extraímos constantemente o conteúdo e as *features* de estilo da imagem composta e calculamos a função de perda. Lembre-se de nossa discussão sobre como as funções de sincronização forçam o *front-end* a esperar pelos resultados de computação em :numref:`sec_async`. Como apenas chamamos a função de sincronização ``asnumpy`` a cada 10 épocas, o processo pode ocupar uma grande quantidade de memória. Portanto, chamamos a função de sincronização ``waitall`` durante cada época. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def train(X, contents_Y, styles_Y, device, lr, num_epochs, lr_decay_epoch): X, styles_Y_gram, trainer = get_inits(X, device, lr, styles_Y) animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[10, num_epochs], legend=['content', 'style', 'TV'], ncols=2, figsize=(7, 2.5)) for epoch in range(num_epochs): with autograd.record(): contents_Y_hat, styles_Y_hat = extract_features( X, content_layers, style_layers) contents_l, styles_l, tv_l, l = compute_loss( X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram) l.backward() trainer.step(1) npx.waitall() if (epoch + 1) % lr_decay_epoch == 0: trainer.set_learning_rate(trainer.learning_rate * 0.1) if (epoch + 1) % 10 == 0: animator.axes[1].imshow(postprocess(X).asnumpy()) animator.add(epoch + 1, [float(sum(contents_l)), float(sum(styles_l)), float(tv_l)]) return X .. raw:: html
.. raw:: html
.. code:: python def train(X, contents_Y, styles_Y, device, lr, num_epochs, lr_decay_epoch): X, styles_Y_gram, trainer = get_inits(X, device, lr, styles_Y) scheduler = torch.optim.lr_scheduler.StepLR(trainer, lr_decay_epoch) animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[10, num_epochs], legend=['content', 'style', 'TV'], ncols=2, figsize=(7, 2.5)) for epoch in range(num_epochs): trainer.zero_grad() contents_Y_hat, styles_Y_hat = extract_features( X, content_layers, style_layers) contents_l, styles_l, tv_l, l = compute_loss( X, contents_Y_hat, styles_Y_hat, contents_Y, styles_Y_gram) l.backward() trainer.step() scheduler.step() if (epoch + 1) % 10 == 0: animator.axes[1].imshow(postprocess(X)) animator.add(epoch + 1, [float(sum(contents_l)), float(sum(styles_l)), float(tv_l)]) return X .. raw:: html
.. raw:: html
Em seguida, começamos a treinar o modelo. Primeiro, definimos a altura e a largura das imagens de conteúdo e estilo para 150 por 225 pixels. Usamos a imagem de conteúdo para inicializar a imagem composta. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python device, image_shape = d2l.try_gpu(), (225, 150) net.collect_params().reset_ctx(device) content_X, contents_Y = get_contents(image_shape, device) _, styles_Y = get_styles(image_shape, device) output = train(content_X, contents_Y, styles_Y, device, 0.01, 500, 200) .. figure:: output_neural-style_5de8ca_140_0.svg .. raw:: html
.. raw:: html
.. code:: python device, image_shape = d2l.try_gpu(), (150, 225) # PIL Image (h, w) net = net.to(device) content_X, contents_Y = get_contents(image_shape, device) _, styles_Y = get_styles(image_shape, device) output = train(content_X, contents_Y, styles_Y, device, 0.01, 500, 200) .. figure:: output_neural-style_5de8ca_143_0.svg .. raw:: html
.. raw:: html
Como você pode ver, a imagem composta retém o cenário e os objetos da imagem de conteúdo, enquanto introduz a cor da imagem de estilo. Como a imagem é relativamente pequena, os detalhes são um pouco confusos. Para obter uma imagem composta mais clara, treinamos o modelo usando um tamanho de imagem maior: :math:`900 \times 600`. Aumentamos a altura e a largura da imagem usada antes por um fator de quatro e inicializamos uma imagem composta maior. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python image_shape = (900, 600) _, content_Y = get_contents(image_shape, device) _, style_Y = get_styles(image_shape, device) X = preprocess(postprocess(output) * 255, image_shape) output = train(X, content_Y, style_Y, device, 0.01, 300, 100) d2l.plt.imsave('../img/neural-style.jpg', postprocess(output).asnumpy()) .. figure:: output_neural-style_5de8ca_149_0.svg .. raw:: html
.. raw:: html
.. code:: python image_shape = (600, 900) # PIL Image (h, w) _, content_Y = get_contents(image_shape, device) _, style_Y = get_styles(image_shape, device) X = preprocess(postprocess(output), image_shape).to(device) output = train(X, content_Y, style_Y, device, 0.01, 300, 100) d2l.plt.imsave('../img/neural-style.jpg', postprocess(output)) .. figure:: output_neural-style_5de8ca_152_0.svg .. raw:: html
.. raw:: html
Como você pode ver, cada época leva mais tempo devido ao tamanho maior da imagem. Conforme mostrado em :numref:`fig_style_transfer_large`, a imagem composta produzida retém mais detalhes devido ao seu tamanho maior. A imagem composta não só tem grandes blocos de cores como a imagem de estilo, mas esses blocos têm até a textura sutil de pinceladas. .. _fig_style_transfer_large: .. figure:: ../img/neural-style.jpg :width: 500px Imagem :math:`900 \times 600` composta. Resumo ------ - As funções de perda usadas na transferência de estilo geralmente têm três partes: 1. A perda de conteúdo é usada para fazer a imagem composta se aproximar da imagem de conteúdo no que diz respeito aos recursos de conteúdo. 2. A perda de estilo é usada para fazer com que a imagem composta se aproxime da imagem de estilo em termos de recursos de estilo. 3. A perda total de variação ajuda a reduzir o ruído na imagem composta. - Podemos usar um CNN pré-treinado para extrair recursos de imagem e minimizar a função de perda para atualizar continuamente a imagem composta. - Usamos uma matriz de Gram para representar a saída do estilo pelas camadas de estilo. Exercícios ---------- 1. Como a saída muda quando você seleciona diferentes camadas de conteúdo e estilo? 2. Ajuste os hiperparâmetros de peso na função de perda. A saída retém mais conteúdo ou tem menos ruído? 3. Use imagens de conteúdo e estilo diferentes. Você pode criar imagens compostas mais interessantes? 4. Podemos aplicar transferência de estilo para texto? Dica: você pode consultar o documento de pesquisa de Hu et al. :cite:`Hu.Lee.Aggarwal.2020`. .. raw:: html
mxnetpytorch
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
.. raw:: html