当前位置: 首页 > 科技观察

还有图像风格迁移框架:用Python编写,完美兼容PyTorch,外行也可以使用

时间:2023-03-21 00:35:12 科技观察

简单易用的神经风格迁移框架pystiche。将内容图像与艺术风格图像合并以生成具有特定风格的新图像的想法并不新鲜。早在2015年,Gatys、Ecker和Bethge就率先提出了神经风格迁移(NeuralStyleTransfer,NST)。与深度学习不同,NST目前没有现成的库或框架。因此,新的NST技术要么从头开始实施一切,要么建立在现有方法的基础上。但是这两种方法都有各自的缺点:前者由于可重用部件的实施时间长,限制了技术创新;后者继承了DL硬件和软件快速发展造成的技术债务。最近,一个新项目pystiche很好地解决了这些问题,虽然它的核心受众是研究人员,但其易于使用的用户界面使非专业人士也可以使用NST。pystiche是一个用Python编写的NST框架,建立在PyTorch之上并与PyTorch完全兼容。相关研究通过pyOpenSci同行评审,发表在JOSSJournal(开源软件杂志)上。论文地址:https://joss.theoj.org/papers/10.21105/joss.02761项目地址:https://github.com/pmeier/pystiche在深入实现之前,我们先来回顾一下NST的原理。它有两种优化方法:基于图像的优化和基于模型的优化。虽然pystiche很好地处理了后者,但它更复杂,因此本文仅讨论基于图像的优化方法。在基于图像的方法中,图像的像素被迭代调整以进行训练以适应感知损失函数。感知损失是NST的核心部分,分为内容损失和风格损失。这些损失评估输出图像与目标图像的匹配程度。与传统的风格迁移算法不同,感知损失由称为编码器的多层模型组成,这就是pystiche构建在PyTorch之上的原因。如何使用pystiche下面我们用一个例子来介绍一下如何使用pystiche生成神经风格迁移图像。首先导入需要的模块,选择处理设备。尽管pystiche被设计为与设备无关,但使用GPU可以将NST加速几个数量级。模块导入和设备选择:importtorchimportpystichefrompysticheimportdemo,enc,loss,ops,optimprint(f"pystiche=={pystiche.__version__}")device=torch.device("cuda"iftorch.cuda.is_available()else"cpu")输出:pystiche==0.7.0多层编码器content_loss和style_loss对图像编码而不是图像本身进行操作,这些编码是由不同级别的预训练编码器生成的。pystiche定义了enc.MultiLayerEncoder类,它可以在单个前向传递中有效地处理编码。本示例使用基于VGG19架构的vgg19_multi_layer_encoder。默认情况下,它将加载torchvision提供的权重。多层编码器:multi_layer_encoder=enc.vgg19_multi_layer_encoder()print(multi_layer_encoder)输出:VGGMultiLayerEncoder(arch=vgg19,framework=torch,allow_inplace=True(preprocessing):TorchPreprocessing((0):Normalize(mean=('0.485','0.456','0.406'),std=('0.229','0.224','0.225')))(conv1_1):Conv2d(3,64,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu1_1):ReLU(inplace=True)(conv1_2):Conv2d(64,64,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu1_2):ReLU(inplace=True)(pool1):MaxPool2d(kernel_size=2,stride=2,padding=0,dilation=1,ceil_mode=False)(conv2_1):Conv2d(64,128,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu2_1):ReLU(inplace=True)(conv2_2):Conv2d(128,128,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu2_2):ReLU(inplace=True)(pool2):MaxPool2d(kernel_size=2,stride=2,padding=0,dilation=1,ceil_mode=False)(conv3_1):Conv2d(128,256,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu3_1):ReLU(inplace=True)(conv3_2):Conv2d(256,256,kernel_size=(3,3),stride=(1,1),p添加=(1,1))(relu3_2):ReLU(inplace=True)(conv3_3):Conv2d(256,256,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu3_3):ReLU(inplace=True)(conv3_4):Conv2d(256,256,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu3_4):ReLU(inplace=True)(pool3):MaxPool2d(kernel_size=2,stride=2,padding=0,dilation=1,ceil_mode=False)(conv4_1):Conv2d(256,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu4_1):ReLU(inplace=True)(conv4_2):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu4_2):ReLU(inplace=True)(conv4_3):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu4_3):ReLU(inplace=True)(conv4_4):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu4_4):ReLU(inplace=True)(pool4):MaxPool2d(kernel_size=2,stride=2,padding=0,dilation=1,ceil_mode=False)(conv5_1):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu5_1):ReLU(inplace=True)(conv5_2):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu5_2):ReLU(inplace=True)(conv5_3):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu5_3):ReLU(inplace=True)(conv5_4):Conv2d(512,512,kernel_size=(3,3),stride=(1,1),padding=(1,1))(relu5_4):ReLU(inplace=True)(pool5):MaxPool2d(kernel_size=2,stride=2,padding=0,dilation=1,ceil_mode=False))perceptuallosspystiche将内容损失和样式损失定义为使用ops.FeatureReconstructionOperator作为content_loss的运算符,用于与编码直接比较。如果编码器是为分类任务训练的,例如在这个例子中,这些编码代表内容。对于content_layer,选择multi_layer_encoder的更深层来获得抽象的内容表示,而不是许多不必要的细节。content_layer="relu4_2"encoder=multi_layer_encoder.extract_encoder(content_layer)content_loss=ops.FeatureReconstructionOperator(encoder)pystiche使用ops.GramOperator作为style_loss的基础,通过比较编码通道之间的相关性来丢弃空间信息。这允许在输出图像的任何地方合成风格元素,而不仅仅是它们在风格化图像中的位置。对于ops.GramOperator,如果它适用于浅层和深层style_layers,则性能最佳。style_weight可以控制模型对输出图像的强调——内容或风格。为方便起见,pystiche将所有内容包装在ops.MultiLayerEncodingOperator中,它处理相同类型的运算符在同一multi_layer_encoder的多个层上运行的情况。style_layers=("relu1_1","re??lu2_1","re??lu3_1","re??lu4_1","re??lu5_1")style_weight=1e3defget_encoding_op(编码器,layer_weight):returnops.GramOperator(编码器,score_weight=layer_weight)style_loss=ops.MultiLayer_layer_Encoding(style_layers,get_encoding_op,score_weight=style_weight,)loss.PerceptualLoss结合了content_loss和style_loss,将作为优化的标准。criterion=loss.PerceptualLoss(content_loss,style_loss).to(device)print(criterion)输出:PerceptualLoss((content_loss):FeatureReconstructionOperator(score_weight=1,encoder=VGGMultiLayerEncoder(layer=relu4_2,arch=vgg19,framework=torch,allow_inp=True))(style_loss):MultiLayerEncodingOperator(encoder=VGGMultiLayerEncoder(arch=vgg19,framework=torch,allow_inplace=True),score_weight=1000(relu1_1):GramOperator(score_weight=0.2)(relu2_1):GramOperator(score2)_weight=(relu3_1):GramOperator(score_weight=0.2)(relu4_1):GramOperator(score_weight=0.2)(relu5_1):GramOperator(score_weight=0.2)))图片加载首先加载并显示NST需要的目标图片。因为NST是内存密集型的,所以将图像大小调整为500像素。size=500images=demo.images()content_image=images["bird1"].read(sizesize=size,devicedevice=device)criterion.set_content_image(content_image)内容图像style_image=images["paint"].read(sizesize=size,devicedevice=device)criterion.set_style_image(style_image)风格图像神经风格迁移创建input_image。NST从content_image开始执行,这导致快速收敛。image_optimization函数是为了方便而提供的,也可以不受限制地用手动优化循环代替。如果未指定,则使用torch.optim.LBFGS作为优化器。input_image=content_image.clone()output_image=optim.image_optimization(input_image,criterion,num_steps=500)点此阅读作者更多好文