【小白學PyTorch】21 Keras的API詳解(上)卷積、激活、初始化、正則

【新聞】:機器學習煉丹術的粉絲的人工智能交流羣已經建立,目前有目標檢測、醫學圖像、時間序列等多個目標爲技術學習的分羣和水羣嘮嗑答疑解惑的總羣,歡迎大家加煉丹兄爲好友,加入煉丹協會。微信:cyx645016617.

參考目錄:

我們對Keras應該已經有了一個直觀、宏觀的認識了。現在,我們來系統的學習一下Keras的一些關於網絡層的API,本文的主要內容是圍繞卷積展開的,包含以下的內容:

  • 不同類型的卷積層;
  • 不同的參數初始化方式;
  • 不同的激活函數;
  • 增加L1/L2正則;
  • 不同的池化層;
  • 多個Normalization層;
  • 其他的常用層。

本文內容較多,對於API的學習瞭解即可。

1 Keras卷積層

Keras的卷積層和PyTorch的卷積層,都包括1D、2D和3D的版本,1D就是一維的,2D是圖像,3D是立體圖像。這裏就用最常見的2D圖像來做講解,1D和3D和2D基本相同,不多贅述。

1.1 Conv2D

先看Conv2D的所有參數:

tf.keras.layers.Conv2D(
    filters,
    kernel_size,
    strides=(1, 1),
    padding="valid",
    data_format=None,
    dilation_rate=(1, 1),
    groups=1,
    activation=None,
    use_bias=True,
    kernel_initializer="glorot_uniform",
    bias_initializer="zeros",
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)

先看一個簡單的例子:

import tensorflow as tf
input_shape = (4, 28, 28, 3)
x = tf.random.normal(input_shape)
y = tf.keras.layers.Conv2D(
    filters=2,kernel_size=3,
    activation='relu',padding='same'
)
print(y(x).shape)
>>> (4, 28, 28, 2)

現在來看參數含義

  • filter: 一個int整數,輸出特徵圖的通道數;
  • kernel_size:一個int整數,卷積核大小;
  • strides:一個整數或者是(a,b)這樣的list,表示卷積核是否跳步;
  • padding'valid'表示沒有padding,'same'表示輸出和輸入特徵圖的尺寸相同;只有這兩種選擇
  • data_format'channels_last'或者是'channels_first'。默認是'channels_last',表示特徵圖的最後一個維度是通道,(batch_size, height, width, channels) ;如果選擇了'channels_first'表示每一個樣本的第一個維度是通道,所以特徵圖的格式和PyTorch的格式相同,(batch_size, channels, height, width)。
  • dilation_rate:碰撞卷積的設置,默認是1,1就是一般的卷積。需要注意的是dilation_rate和stride目前不支持同時不爲1,換句話說,如果要膨脹卷積的話,那麼stride必須是1;
  • groups;分組卷積;
  • activation:這個表示,可以直接在卷積層後面設置一個激活層,比方說'relu',這個在後面的章節會詳細講解目前Keras支持的所有激活層,如果什麼都不填入,則不使用激活層
  • use_bias:一個bool參數,True表示使用bias,默認是True;
  • kernel_initializer:卷積核的初始化的方法,這個會在後面的章節詳細講解;
  • bias_initializer:偏置的初始化的方法,這個會在後面的章節詳細講解;
  • kernel_regularizer:卷積核的正則化的方法,在後面的章節會詳細講解;
  • bias_regularizer:偏置的正則化的方法,在後面的章節會詳細講解;

1.2 SeparableConv2D

Keras直接提供了深度可分離卷積層,這個層其實包含兩個卷積層(瞭解深度可分離卷積的應該都知道這個吧),一層是depthwise,一層是pointwise。

這個SeparableConv2D的參數也很多,與Conv2D有很多重複的參數,就不多加贅述了:

tf.keras.layers.SeparableConv2D(
    filters,
    kernel_size,
    strides=(1, 1),
    padding="valid",
    data_format=None,
    dilation_rate=(1, 1),
    depth_multiplier=1,
    activation=None,
    use_bias=True,
    depthwise_initializer="glorot_uniform",
    pointwise_initializer="glorot_uniform",
    bias_initializer="zeros",
    depthwise_regularizer=None,
    pointwise_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    depthwise_constraint=None,
    pointwise_constraint=None,
    bias_constraint=None,
    **kwargs
)

參數詳解:

  • depth_multiplier:depthwise卷積之後,一般會增多通道數。比方說輸入通道是16個,那麼輸出通道數64個,然後再輸入到pointwise卷積層。這個depth_multiplier就是depthwise卷積層的通道數的擴增係數,在上面的例子中這個擴增係數是4;
  • depthwise_initializerpointwise_initializer不用多說,就是兩個卷積層的卷積核的初始化的方法。

但是這個深度可分離卷積完全可以用一般的Conv2D來構建,所以其實在用到深度可分離卷積的時候,自己會重新構建一個這樣的網絡層

1.3 Conv2DTranspose

對於上採樣,這種方法應該並不陌生。Transposed convolution有的時候也被稱爲Deconvolution去卷積

tf.keras.layers.Conv2DTranspose(
    filters,
    kernel_size,
    strides=(1, 1),
    padding="valid",
    output_padding=None,
    data_format=None,
    dilation_rate=(1, 1),
    activation=None,
    use_bias=True,
    kernel_initializer="glorot_uniform",
    bias_initializer="zeros",
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)

參數詳解:

  • output_padding:一個整數或者list,用來給輸出的特徵圖增加一個padding的效果,默認是None,不添加padding;

對於去卷積,可能會比較生疏,這裏多講幾個例子

1.3.1 去卷積的例子1

import tensorflow as tf
from tensorflow import keras

input_shape = (4, 28, 28, 3)
x = tf.random.normal(input_shape)
y = keras.layers.Conv2DTranspose(
    filters=10,kernel_size=3,strides=1,padding='same')
print(y(x).shape)
>>> (4, 28, 28, 10)

但是假如我們去掉了padding=‘same’

input_shape = (4, 28, 28, 3)
x = tf.random.normal(input_shape)
y = keras.layers.Conv2DTranspose(
    filters=10,kernel_size=3,strides=1)
print(y(x).shape)
>>> (4, 30, 30, 10)

這是因爲去卷積的卷積核的中心是從原特徵圖的邊界之外開始計算的。一個3乘3的卷積核,那麼當卷積核的右下角與原特徵圖的左上角重合的時候,去卷積的就已經進行了一次運算,而一般的卷積是隻有當卷積核的全部都與原特徵圖重合的時候,才進行計算的。(這裏的講解不太細緻,因爲之前在其他的文章中已經講過去卷積的詳細過程了)。

1.3.2 去卷積的例子2

現在把stride改成2

input_shape = (4, 28, 28, 3)
x = tf.random.normal(input_shape)
y = keras.layers.Conv2DTranspose(
    filters=10,kernel_size=3,strides=2)
print(y(x).shape)
>>> (4, 57, 57, 10)

假如加上padding='same'

input_shape = (4, 28, 28, 3)
x = tf.random.normal(input_shape)
y = keras.layers.Conv2DTranspose(
    filters=10,kernel_size=3,strides=2,padding='same')
print(y(x).shape)
>>> (4, 56, 56, 10)

所以一般情況下,使用的參數是strides=2,padding='same',這樣特徵圖的尺寸就剛好放大一倍。

2 Keras參數初始化

把之前提到的簡單的例子,增加捲積核和偏置的初始化:

import tensorflow as tf
input_shape = (4, 28, 28, 3)
initializer = tf.keras.initializers.RandomNormal(mean=0., stddev=1.)
x = tf.random.normal(input_shape)
y = tf.keras.layers.Conv2D(
    filters=2,kernel_size=3,
    activation='relu',padding='same',
    kernel_initializer=initializer,
    bias_initializer=initializer
)
print(y(x).shape)
>>> (4, 28, 28, 2)

簡單的說,就是先定義一個初始化器initializer,然後把這個初始化器作爲參數傳給Keras.Layers就行了。

2.1 正態分佈

tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.05, seed=None)

2.2 均勻分佈

tf.keras.initializers.RandomUniform(minval=-0.05, maxval=0.05, seed=None)

2.3 截尾正態分佈

tf.keras.initializers.TruncatedNormal(mean=0.0, stddev=0.05, seed=None)

基本和正態分佈一樣,但是如果隨機的取值是在距離均值兩個標準差的這個範圍之外的,那麼會重新取值。

換句話說,初始化的數值會被限制在均值正負兩個標準差的範圍內

2.4 常數

tf.keras.initializers.Zeros()
tf.keras.initializers.Ones()

2.5 Xavier/Glorot

tf.keras.initializers.GlorotNormal(seed=None)

這個本質是一個截尾正態分佈,但是GlorotNormal(又稱Xavier),是一個以0爲均值,標準差計算公式是:$$std = \sqrt{\frac{2}{in+out}}$$

是in和out表示輸入和輸出神經元數目的數目。如果是之前已經學習過或者看過我寫的關於Xavier初始化的論文筆記的朋友,可能會發現論文中使用的是一個均勻分佈而不是正態分佈。

均勻分佈的初始化如下:
tf.keras.initializers.GlorotUniform(seed=None)

這個均勻分佈是我們講的:

\[[-\sqrt{\frac{6}{in+out}},\sqrt{\frac{6}{in+out}}] \]

這個Xavier方法,也是Keras默認的初始化的方法

2.6 自定義初始化

當然,Keras也是支持自定義初始化的方法的。

import tensorflow as tf

class ExampleRandomNormal(tf.keras.initializers.Initializer):

def __init__(self, mean, stddev):
  self.mean = mean
  self.stddev = stddev

def __call__(self, shape, dtype=None)`:
  return tf.random.normal(
      shape, mean=self.mean, stddev=self.stddev, dtype=dtype)

def get_config(self):  # To support serialization
  return {'mean': self.mean, 'stddev': self.stddev}

關鍵就是在__call__中返回一個和輸入參數shape大小相同的一個tf張量就行了。

3 Keras激活函數

基本支持了所有的常見激活函數。在卷積層的參數activation中,可以輸入relu,sigmoid,softmax等下面的字符串的形式,全部小寫。

3.1 relu

tf.keras.activations.relu(x, alpha=0.0, max_value=None, threshold=0)

  • alpha就是斜率,如果是0.1,則變成leakyReLU;
  • max_value是ReLU的上界,如果是None則沒有上界;
  • threshold是ReLU的下界,小於下界的都會被置0,一般默認是0.

3.2 sigmoid

tf.keras.activations.sigmoid(x)

函數方程:

\[sigmoid(x)=\frac{1}{1+e^{-x}} \]

3.3 softmax

tf.keras.activations.softmax(x, axis=-1)

3.4 softplus

tf.keras.activations.softplus(x)

計算公式:

\[softplus(x)=log(e^x+1) \]

3.5 softsign

tf.keras.activations.softsign(x)

計算公式:

\[softsign(x)=\frac{x}{|x|+1} \]

3.6 tanh

tf.keras.activations.tanh(x)

計算公式:

\[tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}} \]

3.7 selu

tf.keras.activations.selu(x)

  • 如果\(x>0\),返回\(scale \times x\);
  • 如果\(x<0\),返回\(scale \times \alpha \times (e^x-1)\);
  • scale和\(\alpha\)是事先設置的數值,alpha=1.67216214,scale=1.05070098
  • 與elu激活函數類似,但是多了有個scale係數,\(selu=scale\times elu\)
  • 2017年的一篇論文提出selu,elu是2016年提出的

4 Keras的L1/L2正則

正則化就比較簡單,不是L1就是L2,再或者兩者都有。

4.1 L1/L2正則

from tensorflow.keras import layers
from tensorflow.keras import regularizers

layer = layers.Dense(
    units=64,
    kernel_regularizer=regularizers.l1_l2(l1=1e-5, l2=1e-4),
)

這裏的正則化,可以使用:

  • tf.keras.regularizers.l1_l2(l1=1e-5, l2=1e-4)
  • tf.keras.regularizers.l2(1e-4)
  • tf.keras.regularizers.l1(1e-5)

關於L1和L2的計算細節:

  • L1:L1正則就是$$loss=L1\times sum(abs(x))$$
  • L2:L1正則就是$$loss=L1\times sum(x^2)$$

4.2 自定義正則化

class MyRegularizer(tf.keras.regularizers.Regularizer):

    def __init__(self, strength):
        self.strength = strength

    def __call__(self, x):
        return self.strength * tf.reduce_sum(tf.square(x))
        
    def get_config(self):
        return {'strength': self.strength}

這個實現的是L2正則的。其中的get_config是用來保存模型數據的,不要的話也沒事,只是不能序列化的保存模型(不用使用config或者json來存儲模型)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章