人人都是毕加索

作者: _frendy | 来源:发表于2017-06-21 16:24 被阅读131次



    基于 Pytorch 和 VGG19 模型实现图片风格迁移。

    相关 Pytorch 官方教程

    相关 Github 源码


    版权声明:本文为 frendy 原创文章,可以随意转载,但请务必在明确位置注明出处。


    welcome.png

    实践出真知,其实该 Demo 基本是参考 Pytorch 官方教程实现,frendy 在这里只是按照自己的学习路径实践记录了一遍,并提取部分重点跟大家做分享,还是墙裂建议大家去看看官方教程的,文章前面已附上链接地址。好吧,稍微啰嗦一下,笔者的传统学习路径是:浏览概念不求甚解;跑 Demo;回头啃概念或论文(捂脸,目前啃得都不好哎)。

    文中涉及到的相关概念如有偏颇或错误,请各位大神不吝赐教,批评指出,这里先谢过。


    为什么选 Pytorch



    Pytorch 来自 Facebook,我们先来看看它的宣传语吧:

    Matlab is so 2012.
    Caffe is so 2013.
    Theano is so 2014.
    Torch is so 2015.
    TensorFlow is so 2016.
    ‏It's 2017 now.
    

    实际上呢?frendy 还不敢妄言,学习和实践也不是很透彻。不过感觉 Pytorch 比 TensorFlow 要容易上手一点,而且是动态图,比起 TensorFlow 的静态图要灵活一点。道听途说,TensorFlow 依然拥有最大的社区,而 Pytorch 则最近增长比较快。


    环境配置



    可以到下面的官网获取相应配置的安装命令(官方目前只支持 Linux 和 OSX):

    http://pytorch.org/
    

    frendy 这里是 Win10,侥幸找到一个别人编译好的版本,更侥幸的是他的电脑配置跟我的基本一样,可以直接使用(百度云下载地址):

    conda install pytorch-0.1.12-py36_0.1.12cu80.tar.bz2
    pip install torchvision
    

    其中 conda 的版本为 Anaconda3 (with Python 3.6)。


    原理分析



    提取图片 A 的内容,提取图片 B 的风格,合成一张新图:

    Image_A (content) + Image_B (style) = Image_C (result)
    
    • 使得 A 和 C 的内容差异尽可能小;
    • 使得 B 和 C 的风格差异尽可能小。

    好了,目标明确,但是该怎么定义或者说是衡量内容差异和风格差异呢?frendy 觉得这就是风格迁移的核心问题所在了。

    内容差异

    其实比较容易可以想到的就是比较两张图片每个像素点。怎么比较呢?也就是求一下差。在 torch 里,我们可以直接调用 nn.MSELoss 来计算输入和目标之间的均方误差。

    class ContentLoss(nn.Module):
        def __init__(self, target, weight):
            super(ContentLoss, self).__init__()
            # we 'detach' the target content from the tree used
            self.target = target.detach() * weight
            # to dynamically compute the gradient: this is a stated value,
            # not a variable. Otherwise the forward method of the criterion
            # will throw an error.
            self.weight = weight
            self.criterion = nn.MSELoss()
    
    
        def forward(self, inputs):
            self.loss = self.criterion(inputs*self.weight, self.target)
            self.outputs = inputs
            return self.outputs
    
        def backward(self, retain_variables=True):
            self.loss.backward(retain_variables=retain_variables)
            return self.loss
    

    其中 forward 是根据图往前计算,backward 是反向传播优化权重。Pytorch 封装了细节,支持自动求导。

    风格差异

    风格是一个挺抽象的概念。什么是风格?有个性。恩,自己的特点越突出,别人的越不突出最好。巨人们的论文告诉我们可以用 Gram 矩阵来衡量风格。

    004.png

    从上图可以看出,内积之后,对角线元素(即不同特征图各自的信息)就放大了,同时其余元素则提供了不同特征图之间的相关信息。于是,在一个Gram矩阵中,既能体现出有哪些特征,又能体现出不同特征间的紧密程度。那么,接下来我们就可以定量分析风格啦:

    class GramMatrix(nn.Module):
        def forward(self, input):
            a, b, c, d = input.size()  
            # a=batch size(=1)
            # b=number of feature maps
            # (c,d)=dimensions of a f. map (N=c*d)
    
            features = input.view(a * b, c * d)  # resise F_XL into \hat F_XL
    
            G = torch.mm(features, features.t())  # compute the gram product
    
            # we 'normalize' the values of the gram matrix
            # by dividing by the number of element in each feature maps.
            return G.div(a * b * c * d)
    
    class StyleLoss(nn.Module):
        def __init__(self, target, weight):
            super(StyleLoss, self).__init__()
            self.target = target.detach() * weight
            self.weight = weight
            self.gram = GramMatrix()
            self.criterion = nn.MSELoss()
    
        def forward(self, inputs):
            self.output = inputs.clone()
            self.G = self.gram(inputs)
            self.G.mul_(self.weight)
            self.loss = self.criterion(self.G, self.target)
            return self.output
    
        def backward(self, retain_variables=True):
            self.loss.backward(retain_variables=retain_variables)
            return self.loss
    
    模型搭建

    这里使用 19 层的 vgg 作为提取特征的卷积网络,并定义了哪几层为需要的特征。

    def get_model_and_losses(style_img, content_img,
                                   style_weight=1000, content_weight=1):
    
        content_layers = ['conv_4']
        style_layers = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']
    
        use_cuda = torch.cuda.is_available()
    
        cnn = models.vgg19(pretrained=True).features
        if use_cuda:
            cnn = cnn.cuda()
        cnn = copy.deepcopy(cnn)
    
        content_losses = []
        style_losses = []
    
        model = nn.Sequential()
        gram = GramMatrix()
    
        if use_cuda:
            model = model.cuda()
            gram = gram.cuda()
    
        i = 1
        for layer in list(cnn):
            if isinstance(layer, nn.Conv2d):
                name = "conv_" + str(i)
                model.add_module(name, layer)
    
                if name in content_layers:
                    # add content loss:
                    target = model(content_img).clone()
                    content_loss = ContentLoss(target, content_weight)
                    model.add_module("content_loss_" + str(i), content_loss)
                    content_losses.append(content_loss)
    
                if name in style_layers:
                    # add style loss:
                    target_feature = model(style_img).clone()
                    target_feature_gram = gram(target_feature)
                    style_loss = StyleLoss(target_feature_gram, style_weight)
                    model.add_module("style_loss_" + str(i), style_loss)
                    style_losses.append(style_loss)
    
            if isinstance(layer, nn.ReLU):
                name = "relu_" + str(i)
                model.add_module(name, layer)
    
                if name in content_layers:
                    # add content loss:
                    target = model(content_img).clone()
                    content_loss = ContentLoss(target, content_weight)
                    model.add_module("content_loss_" + str(i), content_loss)
                    content_losses.append(content_loss)
    
                if name in style_layers:
                    # add style loss:
                    target_feature = model(style_img).clone()
                    target_feature_gram = gram(target_feature)
                    style_loss = StyleLoss(target_feature_gram, style_weight)
                    model.add_module("style_loss_" + str(i), style_loss)
                    style_losses.append(style_loss)
    
                i += 1
    
            if isinstance(layer, nn.MaxPool2d):
                name = "pool_" + str(i)
                model.add_module(name, layer)
    
        return model, style_losses, content_losses
    
    模型训练

    这里使用 L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno) 算法来跑梯度下降,并通过 backward 反向优化权重,不断缩小 A 和 C 的内容差异、缩小 B 和 C 的风格差异。

    def get_input_param_optimizer(input_img):
        # this line to show that input is a parameter that requires a gradient
        input_param = nn.Parameter(input_img.data)
        optimizer = optim.LBFGS([input_param])
        return input_param, optimizer
    
    def train():
        ...
        model, style_losses, content_losses = get_model_and_losses(style_img, content_img, style_weight, content_weight)
        input_param, optimizer = get_input_param_optimizer(input_img)
    
        print('Optimizing..')
        run = [0]
        while run[0] <= num_steps:
    
            def closure():
                # correct the values of updated input image
                input_param.data.clamp_(0, 1)
    
                optimizer.zero_grad()
                model(input_param)
                style_score = 0
                content_score = 0
    
                for sl in style_losses:
                    style_score += sl.backward()
                for cl in content_losses:
                    content_score += cl.backward()
    
                run[0] += 1
                if run[0] % 50 == 0:
                    print("run {}:".format(run))
                    print('Style Loss : {:4f} Content Loss: {:4f}'.format(
                        style_score.data[0], content_score.data[0]))
                    print()
    
                return style_score + style_score
    
            optimizer.step(closure)
        input_param.data.clamp_(0, 1)
        output = input_param.data
    

    效果图

    • 莫奈(900 次迭代的结果)
    001.png
    • 毕加索(900 次迭代的结果)
    002.png
    • 毕加索(300 次迭代的结果)
    003.png

    这 300 次的结果是不是感觉比 900 次的结果要好看?!

    后话:谷歌最近又开源了一个库 Tensor2Tensor,号称 One Model To Learn Them All;苹果的 Core ML 几行代码就可以导入并使用训练好的模型...进击的巨人们啊!


    qrcode_card.png

    相关文章

      网友评论

        本文标题:人人都是毕加索

        本文链接:https://www.haomeiwen.com/subject/sicwqxtx.html