??tensorflow2不再需要靜態(tài)建圖啟動(dòng)session()琅摩,拋棄很多繁雜的功能設(shè)計(jì)率触,代碼上更加簡(jiǎn)潔清晰豹储,而在工程上也更加靈活。
但是一些基礎(chǔ)的用法坏匪,單靠api接口去訓(xùn)練模型是遠(yuǎn)遠(yuǎn)無(wú)法滿足實(shí)際的應(yīng)用拟逮,基于這種框架,更多還需要自己在其上自定義開(kāi)發(fā)适滓。
例如:model.fit() 雖然能一句代碼把訓(xùn)練跑起來(lái)敦迄,但你根本無(wú)法知道整個(gè)模型內(nèi)部數(shù)據(jù)的變化,也難以去查看某些變量凭迹。我們不可能永遠(yuǎn)停留在MNIST之類的數(shù)據(jù)集上颅崩。
Resnet
??個(gè)人更傾向在實(shí)戰(zhàn)中學(xué)習(xí)深化基礎(chǔ),而不是把基礎(chǔ)理論學(xué)好了再去實(shí)踐蕊苗。本篇基于tf2.0是搭建Resnet網(wǎng)絡(luò)沿后,Resnet有很多變種,也作為很多模型的骨干網(wǎng)絡(luò)朽砰,這次實(shí)戰(zhàn)項(xiàng)目就從它開(kāi)始
(需要對(duì)Resnet有一定的認(rèn)知了解尖滚,本文只是代碼實(shí)現(xiàn))
網(wǎng)絡(luò)結(jié)構(gòu)
?? 官方給出的Resnet網(wǎng)絡(luò)結(jié)構(gòu),分別為18瞧柔,34漆弄,50,101造锅,152層撼唾,可以看出,不同層數(shù)之間總體的結(jié)構(gòu)是一樣的哥蔚,這樣就很方便用類去實(shí)例化每一個(gè)模塊了
基礎(chǔ)模塊
??從conv2_x到conv5_x倒谷,18和34layer的結(jié)構(gòu)是一樣的蛛蒙,50,101和152是一樣的渤愁,具體分別為:
先定義18or34layer的模塊
# for 18 or 34 layers
class Basic_Block(keras.Model):
def __init__(self, filters, downsample=False, stride=1):
self.expasion = 1
super(Basic_Block, self).__init__()
self.downsample = downsample
self.conv2a = keras.layers.Conv2D(filters=filters,
kernel_size=3,
strides=stride,
kernel_initializer='he_normal',
)
self.bn2a = keras.layers.BatchNormalization(axis=-1)
self.conv2b = keras.layers.Conv2D(filters=filters,
kernel_size=3,
padding='same',
kernel_initializer='he_normal'
)
self.bn2b = keras.layers.BatchNormalization(axis=-1)
self.relu = keras.layers.ReLU()
if self.downsample:
self.conv_shortcut = keras.layers.Conv2D(filters=filters,
kernel_size=1,
strides=stride,
kernel_initializer='he_normal',
)
self.bn_shortcut = keras.layers.BatchNormalization(axis=-1)
def call(self, inputs, **kwargs):
x = self.conv2a(inputs)
x = self.bn2a(x)
x = self.relu(x)
x = self.conv2b(x)
x = self.bn2b(x)
x = self.relu(x)
if self.downsample:
shortcut = self.conv_shortcut(inputs)
shortcut = self.bn_shortcut(shortcut)
else:
shortcut = inputs
x = keras.layers.add([x, shortcut])
x = self.relu(x)
代碼雖然長(zhǎng)了點(diǎn)牵祟,但看一下call() 里面就很清晰了,就是2個(gè) conv+bn+relu抖格,最后與input做點(diǎn)加操作
同理應(yīng)用在50诺苹,101or152layer:
# for 50, 101 or 152 layers
class Block(keras.Model):
def __init__(self, filters, block_name,
downsample=False, stride=1, **kwargs):
self.expasion = 4
super(Block, self).__init__(**kwargs)
conv_name = 'res' + block_name + '_branch'
bn_name = 'bn' + block_name + '_branch'
self.downsample = downsample
self.conv2a = keras.layers.Conv2D(filters=filters,
kernel_size=1,
strides=stride,
kernel_initializer='he_normal',
name=conv_name + '2a')
self.bn2a = keras.layers.BatchNormalization(axis=3, name=bn_name + '2a')
self.conv2b = keras.layers.Conv2D(filters=filters,
kernel_size=3,
padding='same',
kernel_initializer='he_normal',
name=conv_name + '2b')
self.bn2b = keras.layers.BatchNormalization(axis=3, name=bn_name + '2b')
self.conv2c = keras.layers.Conv2D(filters=4 * filters,
kernel_size=1,
kernel_initializer='he_normal',
name=conv_name + '2c')
self.bn2c = keras.layers.BatchNormalization(axis=3, name=bn_name + '2c')
if self.downsample:
self.conv_shortcut = keras.layers.Conv2D(filters=4 * filters,
kernel_size=1,
strides=stride,
kernel_initializer='he_normal',
name=conv_name + '1')
self.bn_shortcut = keras.layers.BatchNormalization(axis=3, name=bn_name + '1')
def call(self, inputs, **kwargs):
x = self.conv2a(inputs)
x = self.bn2a(x)
x = tf.nn.relu(x)
x = self.conv2b(x)
x = self.bn2b(x)
x = tf.nn.relu(x)
x = self.conv2c(x)
x = self.bn2c(x)
if self.downsample:
shortcut = self.conv_shortcut(inputs)
shortcut = self.bn_shortcut(shortcut)
else:
shortcut = inputs
x = keras.layers.add([x, shortcut])
x = tf.nn.relu(x)
return x
對(duì)于downsample的操作,如果input和最后一層輸出的chanels不一樣就需要downsample來(lái)保持chanel一致雹拄,這樣才能相加收奔,一般解析resnet的文章都會(huì)提到。
用類封裝了模塊的功能滓玖,接下來(lái)只需要在主體網(wǎng)路結(jié)構(gòu)里添加這個(gè)模塊就好了
主體結(jié)構(gòu)
用subclassing的方式去搭建model坪哄,就像砌墻一樣,一個(gè)模塊一個(gè)模塊拼上去就好了呢撞,先在init()里面定義好需要用到的方法损姜,再在call()把他們調(diào)用起來(lái)。
對(duì)于resnet的主體結(jié)構(gòu)殊霞,先看一下call()里是該如何寫的:
def call(self, inputs, **kwargs):
x = self.padding(inputs)
x = self.conv1(x)
x = self.bn_conv1(x)
x = tf.nn.relu(x)
x = self.max_pool(x)
# layer2
x = self.res2(x)
# layer3
x = self.res3(x)
# layer4
x = self.res4(x)
# layer5
x = self.res5(x)
x = self.avgpool(x)
x = self.fc(x)
return x
一目了然摧阅,跟文章開(kāi)頭的結(jié)構(gòu)圖一摸一樣,
最重要的是中間conv2-5 的操作绷蹲,這個(gè)需要對(duì)resnet結(jié)構(gòu)熟悉
在Resnet的init()里面棒卷,這樣去定義中間的4個(gè)層
# layer2
self.res2 = self.mid_layer(block, 64, layers[0], stride=1, layer_number=2)
# layer3
self.res3 = self.mid_layer(block, 128, layers[1], stride=2, layer_number=3)
# layer4
self.res4 = self.mid_layer(block, 256, layers[2], stride=2, layer_number=4)
# layer5
self.res5 = self.mid_layer(block, 512, layers[3], stride=2, layer_number=5)
函數(shù)self.mid_layer() 就是把block模塊串起來(lái)
def mid_layer(self, block, filter, block_layers, stride=1, layer_number=1):
layer = keras.Sequential()
if stride != 1 or filter * 4 != 64:
layer.add(block(filters=filter,
downsample=True, stride=stride,
block_name='{}a'.format(layer_number)))
for i in range(1, block_layers):
p = chr(i + ord('a'))
layer.add(block(filters=filter,
block_name='{}'.format(layer_number) + p))
return layer
到此主體的結(jié)構(gòu)就定義好了,官方源碼Resnet祝钢,是直接從上到下直接編寫的比规,就是一邊構(gòu)建網(wǎng)絡(luò)一邊計(jì)算,類似于這樣
x = input()
x = keras.layers.Conv2D()(x)
x = keras.layers.MaxPooling2D()(X)
x = keras.layers.Dense(num_classes)(x)
??相對(duì)來(lái)說(shuō)更喜歡用subclassing的方式去搭建model,雖然代碼量多了點(diǎn)拦英,但是結(jié)構(gòu)清晰蜒什,自己要中間修改的時(shí)候也很簡(jiǎn)單,也方便別的地方直接調(diào)用疤估,但有一點(diǎn)不好就是灾常,當(dāng)想打印模型model.summary() 的時(shí)候,看不到圖像在各個(gè)操作后的shape铃拇,直接顯示multiple钞瀑,目前不知道有沒(méi)其他的方法。慷荔。
代碼
??上述代碼呈現(xiàn)了Resnet的大部分內(nèi)容雕什,可以隨便實(shí)現(xiàn)18-152layer,全部代碼放在了我的github里:https://github.com/angryhen/learning_tensorflow2.0/blob/master/base_model/ResNet.py
??持續(xù)更新中,tensorflow2.0這一系列的代碼也會(huì)放在上面贷岸,包括VGG壹士,Mobilenet的基礎(chǔ)網(wǎng)絡(luò),以后也會(huì)更新引入senet這種變種網(wǎng)絡(luò)凰盔。
Thanks