一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

“讓Keras更酷一些!”:層與模型的重用技巧

 LibraryPKU 2019-10-10

作者丨蘇劍林

單位丨追一科技

研究方向丨NLP,神經(jīng)網(wǎng)絡(luò)

個(gè)人主頁(yè)丨kexue.fm

今天我們繼續(xù)來(lái)深挖 Keras,再次體驗(yàn) Keras 那無(wú)與倫比的優(yōu)雅設(shè)計(jì)。這一次我們的焦點(diǎn)是“重用”,主要是層與模型的重復(fù)使用。

所謂重用,一般就是奔著兩個(gè)目標(biāo)去:一是為了共享權(quán)重,也就是說(shuō)要兩個(gè)層不僅作用一樣,還要共享權(quán)重,同步更新;二是避免重寫代碼,比如我們已經(jīng)搭建好了一個(gè)模型,然后我們想拆解這個(gè)模型,構(gòu)建一些子模型等。


基礎(chǔ)


事實(shí)上,Keras 已經(jīng)為我們考慮好了很多,所以很多情況下,掌握好基本用法,就已經(jīng)能滿足我們很多需求了。 

層的重用

層的重用是最簡(jiǎn)單的,將層初始化好,存起來(lái),然后反復(fù)調(diào)用即可:

x_in = Input(shape=(784,))
x = x_in

layer = Dense(784, activation='relu') # 初始化一個(gè)層,并存起來(lái)

x = layer(x) # 第一次調(diào)用
x = layer(x) # 再次調(diào)用
x = layer(x) # 再次調(diào)用

要注意的是,必須先初始化好一個(gè)層,存為一個(gè)變量好再調(diào)用,才能保證重復(fù)調(diào)用的層是共享權(quán)重的。反之,如果是下述形式的代碼,則是非共享權(quán)重的:

x = Dense(784, activation='relu')(x) 
x = Dense(784, activation='relu')(x) # 跟前面的不共享權(quán)重
x = Dense(784, activation='relu')(x) # 跟前面的不共享權(quán)重

模型重用

Keras 的模型有著類似層的表現(xiàn),在調(diào)用時(shí)可以用跟層一樣的方式,比如:

x_in = Input(shape=(784,))
x = x_in

x = Dense(10, activation='softmax')(x)

model = Model(x_in, x) # 建立模型


x_in = Input(shape=(100,))
x = x_in

x = Dense(784, activation='relu')(x)
x = model(x) # 將模型當(dāng)層一樣用

model2 = Model(x_in, x)

讀過(guò) Keras 源碼的朋友就會(huì)明白,之所以可以將模型當(dāng)層那樣用,是因?yàn)?Model 本身就是繼承 Layer 類來(lái)寫的,所以模型自然也包含了層的一些相同特性。 

模型克隆

模型克隆跟模型重用類似,只不過(guò)得到的新模型跟原模型不共享權(quán)重了,也就是說(shuō),僅僅保留完全一樣的模型結(jié)構(gòu),兩個(gè)模型之間的更新是獨(dú)立的。Keras 提供了模型可用專用的函數(shù),直接調(diào)用即可:

from keras.models import clone_model

model2 = clone_model(model1)

注意,clone_model 完全復(fù)制了原模型模型的結(jié)構(gòu),并重新構(gòu)建了一個(gè)模型,但沒(méi)有復(fù)制原模型的權(quán)重的值。也就是說(shuō),對(duì)于同樣的輸入,model1.predict 和 model2.predict 的結(jié)果是不一樣的。

如果要把權(quán)重也搬過(guò)來(lái),需要手動(dòng) set_weights 一下:

model2.set_weights(K.batch_get_value(model1.weights))

進(jìn)階

上述談到的是原封不等的調(diào)用原來(lái)的層或模型,所以比較簡(jiǎn)單,Keras 都準(zhǔn)備好了。下面介紹一些復(fù)雜一些的例子。 

交叉引用

這里的交叉引用是指在定義一個(gè)新層的時(shí)候,沿用已有的某個(gè)層的權(quán)重,注意這個(gè)自定義層可能跟舊層的功能完全不一樣,它們之間純粹是共享了某個(gè)權(quán)重而已。比如,Bert 在訓(xùn)練 MLM 的時(shí)候,最后預(yù)測(cè)字詞概率的全連接層,權(quán)重就是跟 Embedding 層共享的。 

參考寫法如下:

class EmbeddingDense(Layer):
    '''運(yùn)算跟Dense一致,只不過(guò)kernel用Embedding層的embedding矩陣
    '''
    def __init__(self, embedding_layer, activation='softmax', **kwargs):
        super(EmbeddingDense, self).__init__(**kwargs)
        self.kernel = K.transpose(embedding_layer.embeddings)
        self.activation = activation
        self.units = K.int_shape(self.kernel)[1]

    def build(self, input_shape):
        super(EmbeddingDense, self).build(input_shape)
        self.bias = self.add_weight(name='bias',
                                    shape=(self.units,),
                                    initializer='zeros')

    def call(self, inputs):
        outputs = K.dot(inputs, self.kernel)
        outputs = K.bias_add(outputs, self.bias)
        outputs = Activation(self.activation).call(outputs)
        return outputs

    def compute_output_shape(self, input_shape):
        return input_shape[:-1] + (self.units,)


# 用法
embedding_layer = Embedding(10000, 128)
x = embedding_layer(x) # 調(diào)用Embedding層
x = EmbeddingDense(embedding_layer)(x) # 調(diào)用EmbeddingDense層

提取中間層

有時(shí)候我們需要從搭建好的模型中提取中間層的特征,并且構(gòu)建一個(gè)新模型,在 Keras 中這同樣是很簡(jiǎn)單的操作:

from keras.applications.resnet50 import ResNet50
model = ResNet50(weights='imagenet')

Model(
    inputs=model.input,
    outputs=[
        model.get_layer('res5a_branch1').output,
        model.get_layer('activation_47').output,
    ]
)

從中間拆開(kāi)

最后,來(lái)到本文最有難度的地方了,我們要將模型從中間拆開(kāi),搞懂之后也可以實(shí)現(xiàn)往已有模型插入或替換新層的操作。這個(gè)需求看上去比較奇葩,但是還別說(shuō),stackoverflow 上面還有人提問(wèn)過(guò),說(shuō)明這確實(shí)是有價(jià)值的。 

https:///questions/49492255/how-to-replace-or-insert-intermediate-layer-in-keras-model

假設(shè)我們有一個(gè)現(xiàn)成的模型,它可以分解為:

那可能我們需要將 h2 替換成一個(gè)新的輸入,然后接上后面的層,來(lái)構(gòu)建一個(gè)新模型,即新模型的功能是:

如果是 Sequential 類模型,那比較簡(jiǎn)單,直接把 model.layers 都遍歷一邊,就可以構(gòu)建新模型了:

x_in = Input(shape=(100,))
x = x_in

for layer in model.layers[2:]:
    x = layer(x)

model2 = Model(x_in, x)

但是,如果模型是比較復(fù)雜的結(jié)構(gòu),比如殘差結(jié)構(gòu)這種不是一條路走到底的,就沒(méi)有這么簡(jiǎn)單了。事實(shí)上,這個(gè)需求本來(lái)沒(méi)什么難度,該寫的 Keras 本身已經(jīng)寫好了,只不過(guò)沒(méi)有提供現(xiàn)成的接口罷了。為什么這么說(shuō),因?yàn)槲覀兺ㄟ^(guò) model(x) 這樣的代碼調(diào)用已有模型的時(shí)候,

實(shí)際上 Keras 就相當(dāng)于把這個(gè)已有的這個(gè) model 從頭到尾重新搭建了一遍,既然可以重建整個(gè)模型,那搭建“半個(gè)”模型原則上也是沒(méi)有任技術(shù)難度的,只不過(guò)沒(méi)有現(xiàn)成的接口。具體可以參考 Keras 源碼的 keras/engine/network.py 的 run_internal_graph 函數(shù):


https://github.com/keras-team/keras/blob/master/keras/engine/network.py

完整重建一個(gè)模型的邏輯在 run_internal_graph 函數(shù)里邊,并且可以看到它還不算簡(jiǎn)單,所以如無(wú)必要我們最好不要重寫這個(gè)代碼。但如果不重寫這個(gè)代碼,又想調(diào)用這個(gè)代碼,實(shí)現(xiàn)從中間層拆解模型的功能,唯一的辦法是“移花接木”了:通過(guò)修改已有模型的一些屬性,欺騙一下 run_internal_graph 函數(shù),使得它以為模型的輸入層是中間層,而不是原始的輸入層。有了這個(gè)思想,再認(rèn)真讀讀 run_internal_graph 函數(shù)的代碼,就不難得到下述參考代碼:

def get_outputs_of(model, start_tensors, input_layers=None):
    '''start_tensors為開(kāi)始拆開(kāi)的位置
    '''
    # 為此操作建立新模型
    model = Model(inputs=model.input,
                  outputs=model.output,
                  name='outputs_of_' + model.name)
    # 適配工作,方便使用
    if not isinstance(start_tensors, list):
        start_tensors = [start_tensors]
    if input_layers is None:
        input_layers = [
            Input(shape=K.int_shape(x)[1:], dtype=K.dtype(x))
            for x in start_tensors
        ]
    elif not isinstance(input_layers, list):
        input_layers = [input_layers]
    # 核心:覆蓋模型的輸入
    model.inputs = start_tensors
    model._input_layers = [x._keras_history[0] for x in input_layers]
    # 適配工作,方便使用
    if len(input_layers) == 1:
        input_layers = input_layers[0]
    # 整理層,參考自 Model 的 run_internal_graph 函數(shù)
    layers, tensor_map = [], set()
    for x in model.inputs:
        tensor_map.add(str(id(x)))
    depth_keys = list(model._nodes_by_depth.keys())
    depth_keys.sort(reverse=True)
    for depth in depth_keys:
        nodes = model._nodes_by_depth[depth]
        for node in nodes:
            n = 0
            for x in node.input_tensors:
                if str(id(x)) in tensor_map:
                    n += 1
            if n == len(node.input_tensors):
                if node.outbound_layer not in layers:
                    layers.append(node.outbound_layer)
                for x in node.output_tensors:
                    tensor_map.add(str(id(x)))
    model._layers = layers # 只保留用到的層
    # 計(jì)算輸出
    outputs = model(input_layers)
    return input_layers, outputs

用法:

from keras.applications.resnet50 import ResNet50
model = ResNet50(weights='imagenet')

x, y = get_outputs_of(
    model,
    model.get_layer('add_15').output
)

model2 = Model(x, y)

代碼有點(diǎn)長(zhǎng),但其實(shí)邏輯很簡(jiǎn)單,真正核心的代碼只有三行:

model.inputs = start_tensors
model._input_layers = [x._keras_history[0] for x in input_layers]
outputs = model(input_layers)

也就是覆蓋模型的 model.inputs 和 model._input_layers 就可以實(shí)現(xiàn)欺騙模型從中間層開(kāi)始構(gòu)建的效果了,其余的多數(shù)是適配工作,不是技術(shù)上的,而 model._layers = layers 這一句是只保留了從中間層開(kāi)始所用到的層,只是為了統(tǒng)計(jì)模型參數(shù)量的準(zhǔn)確性,如果去掉這一部分,模型的參數(shù)量依然是原來(lái)整個(gè) model 那么多。

小結(jié)

Keras 是最讓人賞心悅目的深度學(xué)習(xí)框架,至少到目前為止,就模型代碼的可讀性而言,沒(méi)有之一。可能讀者會(huì)提到 PyTorch,誠(chéng)然 PyTorch 也有不少可取之處,但就可讀性而言,我認(rèn)為是比不上 Keras 的。

在深究 Keras 的過(guò)程中,我不僅驚嘆于 Keras 作者們的深厚而優(yōu)雅的編程功底,甚至感覺(jué)自己的編程技能也提高了不少。不錯(cuò),我的很多 Python 編程技巧,都是從讀 Keras 源碼中學(xué)習(xí)到的。

點(diǎn)擊以下標(biāo)題查看作者其他文章: 

#投 稿 通 道#

 讓你的論文被更多人看到 


如何才能讓更多的優(yōu)質(zhì)內(nèi)容以更短路徑到達(dá)讀者群體,縮短讀者尋找優(yōu)質(zhì)內(nèi)容的成本呢?答案就是:你不認(rèn)識(shí)的人。

總有一些你不認(rèn)識(shí)的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學(xué)者和學(xué)術(shù)靈感相互碰撞,迸發(fā)出更多的可能性。

PaperWeekly 鼓勵(lì)高校實(shí)驗(yàn)室或個(gè)人,在我們的平臺(tái)上分享各類優(yōu)質(zhì)內(nèi)容,可以是最新論文解讀,也可以是學(xué)習(xí)心得技術(shù)干貨。我們的目的只有一個(gè),讓知識(shí)真正流動(dòng)起來(lái)。

?? 來(lái)稿標(biāo)準(zhǔn):

· 稿件確系個(gè)人原創(chuàng)作品,來(lái)稿需注明作者個(gè)人信息(姓名+學(xué)校/工作單位+學(xué)歷/職位+研究方向) 

· 如果文章并非首發(fā),請(qǐng)?jiān)谕陡鍟r(shí)提醒并附上所有已發(fā)布鏈接 

· PaperWeekly 默認(rèn)每篇文章都是首發(fā),均會(huì)添加“原創(chuàng)”標(biāo)志

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    一区二区福利在线视频| 免费啪视频免费欧美亚洲| 国产欧美精品对白性色| 欧美日韩精品久久亚洲区熟妇人| 日韩三级黄色大片免费观看| 亚洲性生活一区二区三区| 国产成人精品午夜福利av免费| 一区二区三区日本高清| 亚洲一区二区三区在线中文字幕 | 欧美日韩在线视频一区| 日本最新不卡免费一区二区| 在线日本不卡一区二区| 开心五月激情综合婷婷色| 九九热视频免费在线视频| 日本在线不卡高清欧美| 亚洲中文字幕剧情在线播放| 国产99久久精品果冻传媒| 亚洲视频偷拍福利来袭| 91亚洲人人在字幕国产| 九九热这里只有精品视频| 激情五月天免费在线观看| 韩国日本欧美国产三级| 亚洲精品福利视频在线观看| 亚洲国产成人一区二区在线观看| 女厕偷窥一区二区三区在线| 九九热精彩视频在线免费| 婷婷激情五月天丁香社区| 欧美国产日韩变态另类在线看| 日本黄色美女日本黄色| 五月情婷婷综合激情综合狠狠| 老鸭窝老鸭窝一区二区| 亚洲天堂一区在线播放| 国产亚洲精品俞拍视频福利区| 日韩国产亚洲欧美激情| 99久久精品午夜一区二| 国产内射一级一片内射高清视频| 国语对白刺激高潮在线视频| 色婷婷成人精品综合一区| 国产亚洲欧美另类久久久 | 一区二区三区在线不卡免费| 国产又粗又深又猛又爽又黄|