DeepLearning-風(fēng)格遷移

背景介紹

不知道大家是否用過prisma宾符,就算沒有用過桌粉,也一定看見別人用過這個軟件宣脉,下面是一張這個軟件得到的一個效果圖


example
example

官方宣傳的賣點(diǎn)是一秒鐘讓你的作品擁有名家風(fēng)格,什么畢加索跌宛,梵高,都不在話下积仗。通過這個效果再將你的照片發(fā)到朋友圈疆拘,是不是效果爆棚,簡直是各種裝逼界的一股清流寂曹,秒殺各種修圖ps好嗎哎迄。而且可以完美的掩飾掉一些瑕疵,又比ps更自然隆圆,更有逼格漱挚,是不是很棒。

這個軟件將使用的方法發(fā)了一篇論文渺氧,并且這個軟件在發(fā)布的時候就取得了上千萬的融資旨涝,是不是瞬間感覺現(xiàn)在學(xué)習(xí)了知識也能成為千萬富豪了。如今在這個高速發(fā)展的時代侣背,知識付費(fèi)的時代確實(shí)已經(jīng)到來了白华,所以我們現(xiàn)在努力學(xué)習(xí)各種知識就是在賺錢啊哩治,有木有。這樣大家的學(xué)習(xí)的時候就能夠有著更大的動力了衬鱼。

這篇論文感興趣的同學(xué)可以去查看一下业筏,里面主要涉及的是卷積神經(jīng)網(wǎng)絡(luò)。今天這篇文章要做的是什么呢鸟赫?我們希望自己能夠簡單的實(shí)現(xiàn)這個風(fēng)格遷移算法蒜胖,并且用自己的算法來得到新的風(fēng)格圖片。一想到我們放到朋友圈的照片是自己寫的算法來實(shí)現(xiàn)的就感覺成就感爆棚抛蚤,有沒有台谢。

環(huán)境配置

廢話不多說,我們先來看看需要的基本配置岁经。首先需要python環(huán)境朋沮,建議使用anaconda;然后我們使用的深度學(xué)習(xí)框架是pytorch缀壤,當(dāng)然你也可以用tensorflow樊拓,具體框架的介紹可以去看看之前寫的文章,需要安裝pytorch和torchvision塘慕,這里查看安裝幫助筋夏;同時需要一些其他的包,如果缺什么就pip安裝就好图呢。

這篇文章主要參考于pytorch的官方tutorial条篷,感興趣的同學(xué)可以直接移步至官方教程的地方,這篇文章我會說一些自己的理解蛤织,代碼部分基本都是參考這個教程赴叹,但是我會做一些說明,力求更加清楚指蚜。

原理分析

其實(shí)要實(shí)現(xiàn)的東西很清晰乞巧,就是需要將兩張圖片融合在一起,這個時候就需要定義怎么才算融合在一起姚炕。首先需要的就是內(nèi)容上是相近的摊欠,然后風(fēng)格上是相似的。這樣來我們就知道我們需要做的事情是什么了柱宦,我們需要計算融合圖片和內(nèi)容圖片的相似度些椒,或者說差異性,然后盡可能降低這個差異性掸刊;同時我們也需要計算融合圖片和風(fēng)格圖片在風(fēng)格上的差異性免糕,然后也降低這個差異性就可以了。這樣我們就能夠量化我們的目標(biāo)了。

對于內(nèi)容的差異性我們該如何定義呢石窑?其實(shí)我們能夠很簡答的想到就是兩張圖片每個像素點(diǎn)進(jìn)行比較牌芋,也就是求一下差,因?yàn)楹唵蔚挠嬎闼麄冎g的差會有正負(fù)松逊,所以我們可以加一個平方躺屁,使得差全部是正的,也可以加絕對值经宏,但是數(shù)學(xué)上絕對值會破壞函數(shù)的可微性犀暑,所以大家都用平方,這個地方不理解也沒關(guān)系烁兰,記住普遍都是使用平方就行了耐亏。

對于風(fēng)格的差異性我們該如何定義呢?這才是一個難點(diǎn)沪斟。這也是這篇文章提出的創(chuàng)新點(diǎn)广辰,引入了Gram矩陣計算風(fēng)格的差異。我盡量不使用數(shù)學(xué)的語言來解釋主之,而使用通俗的語言择吊。
首先需要的預(yù)先知識是卷積網(wǎng)絡(luò)的知識,這里不細(xì)講了杀餐,不了解的同學(xué)可以看之前的卷積網(wǎng)絡(luò)的文章干发。我們知道一張圖片通過卷積網(wǎng)絡(luò)之后可以的到一個特征圖朱巨,Gram矩陣就是在這個特征圖上面定義出來的史翘。每個特征圖的大小一般是 MxNxC 或者是 CxMxN 這種大小,這里C表示的時候厚度冀续,放在前面和后面都可以琼讽,MxN 表示的是一個矩陣的大小,其實(shí)就是有 C 個 MxN 這樣的矩陣疊在一起洪唐。

Gram矩陣是如何定義的呢钻蹬?首先Gram矩陣的大小是有特征圖的厚度決定的,等于 CxC凭需,那么每一個Gram矩陣的元素问欠,也就是 Gram(i, j) 等于多少呢?先把特征圖中第 i 層和第 j 層取出來粒蜈,這樣就得到了兩個 MxN的矩陣顺献,然后將這兩個矩陣對應(yīng)元素相乘然后求和就得到了 Gram(i, j),同理 Gram 的所有元素都可以通過這個方式得到枯怖。這樣 Gram 中每個元素都可以表示兩層特征圖的一種組合注整,就可以定義為它的風(fēng)格。

然后風(fēng)格的差異就是兩幅圖的 Gram 矩陣的差異,就像內(nèi)容的差異的計算方法一樣肿轨,計算一下這兩個矩陣的差就可以量化風(fēng)格的差異寿冕。

實(shí)現(xiàn)

以下的內(nèi)容都是用pytorch實(shí)現(xiàn)的,如果對pytorch不熟悉的同學(xué)可以看一下我之前的pytorch介紹文章椒袍,看看官方教程驼唱,如果不想了解pytorch的同學(xué)可以用自己熟悉的框架實(shí)現(xiàn)這個算法,理論部分前面已經(jīng)講完了驹暑。

內(nèi)容差異的loss定義

class Content_Loss(nn.Module):
    def __init__(self, target, weight):
        super(Content_Loss, self).__init__()
        self.weight = weight
        self.target = target.detach() * self.weight
        # 必須要用detach來分離出target曙蒸,這時候target不再是一個Variable,這是為了動態(tài)計算梯度岗钩,否則forward會出錯纽窟,不能向前傳播
        self.criterion = nn.MSELoss()

    def forward(self, input):
        self.loss = self.criterion(input * self.weight, self.target)
        out = input.clone()
        return out

    def backward(self, retain_variabels=True):
        self.loss.backward(retain_variables=retain_variabels)
        return self.loss

其中有個變量weight,這個是表示權(quán)重兼吓,內(nèi)容和風(fēng)格你可以選擇一個權(quán)重臂港,比如你想風(fēng)格上更像,內(nèi)容上多一點(diǎn)差別沒關(guān)系视搏,那么內(nèi)容的權(quán)重你可以定義小一點(diǎn)审孽,風(fēng)格的權(quán)重可以定義大一點(diǎn);反之你可以把風(fēng)格的權(quán)重定義小一點(diǎn)浑娜,內(nèi)容的權(quán)重定義大一點(diǎn)佑力。

風(fēng)格差異的loss定義

Gram 矩陣的定義

class Gram(nn.Module):
    def __init__(self):
        super(Gram, self).__init__()

    def forward(self, input):
        a, b, c, d = input.size()
        feature = input.view(a * b, c * d)
        gram = torch.mm(feature, feature.t())
        gram /= (a * b * c * d)
        return gram

style loss定義

class Style_Loss(nn.Module):
    def __init__(self, target, weight):
        super(Style_Loss, self).__init__()
        self.weight = weight
        self.target = target.detach() * self.weight
        self.gram = Gram()
        self.criterion = nn.MSELoss()

    def forward(self, input):
        G = self.gram(input) * self.weight
        self.loss = self.criterion(G, self.target)
        out = input.clone()
        return out

    def backward(self, retain_variabels=True):
        self.loss.backward(retain_variables=retain_variabels)
        return self.loss

建立模型

使用19層的 vgg 作為提取特征的卷積網(wǎng)絡(luò),并且定義哪幾層為需要的特征筋遭。


vgg = models.vgg19(pretrained=True).features
vgg = vgg.cuda()

content_layers_default = ['conv_4']
style_layers_default = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']


def get_style_model_and_loss(style_img, content_img, cnn=vgg,
                             style_weight=1000,
                             content_weight=1,
                             content_layers=content_layers_default,
                             style_layers=style_layers_default):

    content_loss_list = []
    style_loss_list = []

    model = nn.Sequential()
    model = model.cuda()
    gram = loss.Gram()
    gram = gram.cuda()

    i = 1
    for layer in cnn:
        if isinstance(layer, nn.Conv2d):
            name = 'conv_' + str(i)
            model.add_module(name, layer)

            if name in content_layers_default:
                target = model(content_img)
                content_loss = loss.Content_Loss(target, content_weight)
                model.add_module('content_loss_' + str(i), content_loss)
                content_loss_list.append(content_loss)

            if name in style_layers_default:
                target = model(style_img)
                target = gram(target)
                style_loss = loss.Style_Loss(target, style_weight)
                model.add_module('style_loss_' + str(i), style_loss)
                style_loss_list.append(style_loss)

            i += 1
        if isinstance(layer, nn.MaxPool2d):
            name = 'pool_' + str(i)
            model.add_module(name, layer)

        if isinstance(layer, nn.ReLU):
            name = 'relu' + str(i)
            model.add_module(name, layer)

    return model, style_loss_list, content_loss_list

訓(xùn)練模型

def get_input_param_optimier(input_img):
    """
    input_img is a Variable
    """
    input_param = nn.Parameter(input_img.data)
    optimizer = optim.LBFGS([input_param])
    return input_param, optimizer


def run_style_transfer(content_img, style_img, input_img,
                       num_epoches=300):
    print('Building the style transfer model..')
    model, style_loss_list, content_loss_list = get_style_model_and_loss(
        style_img, content_img
    )
    input_param, optimizer = get_input_param_optimier(input_img)

    print('Opimizing...')
    epoch = [0]
    while epoch[0] < num_epoches:

        def closure():
            input_param.data.clamp_(0, 1)

            model(input_param)
            style_score = 0
            content_score = 0

            optimizer.zero_grad()
            for sl in style_loss_list:
                style_score += sl.backward()
            for cl in content_loss_list:
                content_score += cl.backward()

            epoch[0] += 1
            if epoch[0] % 50 == 0:
                print('run {}'.format(epoch))
                print('Style Loss: {:.4f} Content Loss: {:.4f}'.format(
                    style_score.data[0], content_score.data[0]
                ))
                print()

            return style_score + content_score

        optimizer.step(closure)

        input_param.data.clamp_(0, 1)

    return input_param.data

需要特別注意的是這個模型里面參數(shù)不再是網(wǎng)絡(luò)里面的參數(shù)打颤,因?yàn)榫W(wǎng)絡(luò)使用的是已經(jīng)預(yù)訓(xùn)練好的 vgg 網(wǎng)絡(luò),這個算法里面的參數(shù)是合成圖片里面的每個像素點(diǎn)漓滔,我們可以將內(nèi)容圖片直接 copy 成合成圖片编饺,然后訓(xùn)練使得他的風(fēng)格和我們的風(fēng)格圖片相似,同時也可以隨機(jī)化一張圖片作為合成圖片响驴,然后訓(xùn)練他使得他與內(nèi)容圖片以及風(fēng)格圖片具有相似性透且。

實(shí)驗(yàn)結(jié)果

我們使用的風(fēng)格圖片為

style.png

內(nèi)容圖片為

content.png

得到的合成效果為

demo.png

結(jié)語

通過這篇文章,我們利用pytorch實(shí)現(xiàn)了基本的風(fēng)格轉(zhuǎn)移算法豁鲤,得到的效果也是滿意的秽誊,所以我們可以把自己的圖片通過這個算法做一個風(fēng)格轉(zhuǎn)移,實(shí)現(xiàn)你想要的作品的風(fēng)格琳骡,逼格滿滿锅论,大家學(xué)習(xí)之后肯定會有特別大的成就感,在完成項目的同時也學(xué)習(xí)到了新的知識日熬,同時也會對這個產(chǎn)生更濃厚的感興趣棍厌,興趣才是各種的動力肾胯,比任何雞湯都有用,希望大家都能夠找到自己的興趣耘纱,熱愛自己所做的事敬肚。


本文代碼已經(jīng)上傳到了github

歡迎查看我的知乎專欄,深度煉丹

歡迎訪問我的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末束析,一起剝皮案震驚了整個濱河市艳馒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌员寇,老刑警劉巖弄慰,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蝶锋,居然都是意外死亡陆爽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門扳缕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慌闭,“玉大人,你說我怎么就攤上這事躯舔÷刻蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵粥庄,是天一觀的道長丧失。 經(jīng)常有香客問我,道長惜互,這世上最難降的妖魔是什么布讹? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮载佳,結(jié)果婚禮上炒事,老公的妹妹穿的比我還像新娘。我一直安慰自己蔫慧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布权薯。 她就那樣靜靜地躺著姑躲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盟蚣。 梳的紋絲不亂的頭發(fā)上黍析,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音屎开,去河邊找鬼阐枣。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔼两。 我是一名探鬼主播甩鳄,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼额划!你這毒婦竟也來了妙啃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤俊戳,失蹤者是張志新(化名)和其女友劉穎揖赴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抑胎,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡燥滑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阿逃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突倍。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盆昙,靈堂內(nèi)的尸體忽然破棺而出羽历,到底是詐尸還是另有隱情,我是刑警寧澤淡喜,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布秕磷,位于F島的核電站,受9級特大地震影響炼团,放射性物質(zhì)發(fā)生泄漏澎嚣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一瘟芝、第九天 我趴在偏房一處隱蔽的房頂上張望易桃。 院中可真熱鬧,春花似錦锌俱、人聲如沸晤郑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽造寝。三九已至,卻和暖如春吭练,著一層夾襖步出監(jiān)牢的瞬間诫龙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工鲫咽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留签赃,地道東北人谷异。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像锦聊,于是被迫代替她去往敵國和親歹嘹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容