tensorflow实现resnet

先学习下如何自己完成一个resnet网络用于cifar100训练,后面在使用迁移学习训练。
如果不知道resnet具体的网络结构可以自己学习下,这里只进行实现。

1. BasicBlock实现

BasicBlock
BasicBlock首先是继承layer.Layer

class BasicBlock(layers.Layer):

然后重写里面的init和call方法。
1)先在init定义Basic中的基础操作,由上图我们知道,就是两个Conv2D,如果第1个conv下采样了,第二个就不进行下采样。
这里的下采样指的是stride不等于1的情况。这里每个conv后,有个batchnormal操作,bn操作是会训练一个α和β参数的,
如果不是要共享权值,最好定义两个self.bn{$(num)}。relu层是没有训练的参数的,可以使用相同的,但是为了可以
更清晰的了解网络结构,建议分别定义。

    def __init__(self,filter_num,stride):
        super(BasicBlock,self).__init__()

        self.conv1=layers.Conv2D(filter_num,kernel_size=(3,3),strides=stride,padding='same')
        self.bn1=layers.BatchNormalization()
        self.relu1=layers.Activation('relu')

        self.cov2=layers.Conv2D(filter_num,kernel_size=(3,3),strides=1,padding='same')
        self.bn2=layers.BatchNormalization()
        self.relu2=layers.Activation('relu')

        if stride % 2 != 0:
            self.downsample=lambda x:x
        else:
            self.downsample=Sequential()
            self.downsample.add(layers.Conv2D(filter_num,(1,1),strides=stride,padding='same'))

在BasicBlock中call定义调用逻辑,这里逻辑就是先进行第一次conv、bn、relu组合,再进行conv、bn;
另外一个分支就是如果没有进行下采样,返回值就是inputs,否则就进行一次1*1的卷积;
在将两个分支的结果进行相加,再进行一次relu.

    def call(self, inputs, training=None):
        #input[N.H,W,C]
        out=self.conv1(inputs)
        out=self.bn1(out)
        out=self.relu1(out)

        out=self.cov2(out)
        out=self.bn2(out)

        identity=self.downsample(inputs)

        output=layers.add([out,identity])
        output=tf.nn.relu(output)
        return output

2 实现一个ResBlock

这里要明白一个ResBlock中包含一个或多个BasicBlock

比如这里的由两个BasicBlock组成了一个ResBlock

1)定义一个类RestNet
ResNet继承kears.Model

class Resnet(tf.keras.Model):

2)定义ResBlock方法

    def build_resblock(self,filter_num,blocks,stride=1):
        res_block=Sequential()
        res_block.add(
            BasicBlock(filter_num,stride)
        )
        for _ in range(1,blocks):
            res_block.add(BasicBlock(filter_num,stride=1))
        return res_block

这里输入为filter_num,就是N,N就是有多少个卷积核,对应输出的c。
第二个参数blocks对应这里包含几个BasicBlock.
第一步,先创建一个容器
第二步,向容器里添加第一个BasicBlock.这里要区别对待,因为一般一个ResBlock只进行一次下采样。
所以剩余的BasicBlock的stride为1。
2)在init方法中定义我们这个网络中会使用的结构

对于每个resnet网络的定义一般会有一个conv,bn加上一个maxpool。这个不算在ResBlock中

        self.stm=Sequential([layers.Conv2D(64,kernel_size=(3,3),strides=1,padding='same'),
                             layers.BatchNormalization(),
                             layers.Activation('relu'),
                             layers.MaxPool2D(pool_size=(2,2),strides=1,padding='same')])

然后我们根据我们的网络结构创建resblock比如我们先看左边的resnet18,包含有8个resblock,有降采样的也有没有降采样的。
这里我简单写了个全是降采样的``[2,2,2,2]```的结构

        self.layer1=self.build_resblock(64,layer_dim[0])
        self.layer2 = self.build_resblock(128, layer_dim[1],stride=2)
        self.layer3 = self.build_resblock(256, layer_dim[2],stride=2)
        self.layer4 = self.build_resblock(512, layer_dim[3],stride=2)
        self.fc=layers.Dense(num_classes)

3)现在是添加全连接层进行分类
这里可以添加多个全连接层也可以只添加一个。
这里还增加了一个

self.avgpool=layers.GlobalAveragePooling2D()

这是因为为了支持自定义网络输入,比如输入图片较大,卷积完成后没有成为[b,N,1,1]的情况,做个全局的AveragePooling,然后再进行分类。
4)在call方法中设置调用逻辑,这段很简单,就是一个拼积木。

    def call(self,inputs,training=None):
        x=self.stm(inputs)
        x=self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x=self.avgpool(x)
        x=self.fc(x)
        return x

3实现自己的resnet

def MyRest():
    return Resnet([2,2,2,2],100)

这里随便怎么定义了。
下面我们使用cifar100进行测试了
1)加载cifar100

def load_data():
    (x,y),(x_test,y_test)=datasets.cifar100.load_data();
    y=tf.squeeze(y)
    y_test=tf.squeeze(y_test)
    db=tf.data.Dataset.from_tensor_slices((x,y))
    db=db.map(preprocess).shuffle(50000).batch(64)
    db_test=tf.data.Dataset.from_tensor_slices((x_test,y_test))
    db_test=db_test.map(preprocess).batch(64)
    return db,db_test

2)将cifar100进行预处理

def preprocess(x,y):
    x=tf.cast(x, dtype=tf.float32)/255.
    y=tf.cast(y,dtype=tf.int32)
    return x,y

(db,db_test)=load_data()

3)通过我们的刚才定义的网络结构定义网络

    network=Resnet.MyRest()
    network.build(input_shape=(None, 32,32,3))

4)设置优化器

    optimizer=optimizers.Adam(learning_rate=1e-4)

5)开始训练
这里使用交叉熵作为损失函数

    for epoch in range(20):
        for step,(x,y) in enumerate(db):
            with tf.GradientTape() as tape:
                out=network(x)
                #print("y_one_hot shape",y_onehot.shape)
                loss=tf.losses.categorical_crossentropy(y_onehot,out,from_logits=True)
                loss=tf.reduce_mean(loss)
            greds=tape.gradient(loss, network.trainable_variables)
            optimizer.apply_gradients(zip(greds,network.trainable_variables))
            if step % 100 == 0:
              print('loss:',float(loss))

6)每一个epoch计算一下准确率

        total_number=0
        correct_number=0
        for step,(x,y) in enumerate(db_test):
            out=network(x)
            y=tf.cast(y,dtype=tf.int32)
            prod=tf.nn.softmax(out,axis=1)
            pred=tf.argmax(prod,axis=1)
            pred=tf.cast(pred,dtype=tf.int32)
            correct=tf.reduce_sum(tf.cast(tf.equal(pred,y),dtype=tf.int32))

            correct_number += correct
            total_number+=x.shape[0]
        print("acc:",float(correct_number/total_number))

看看效果

完结

后面 将学习如何使用迁移学习和resnet进行分类。为什么要使用迁移学习?你懂得。

posted @ 2021-04-18 12:01  cyssmile  阅读(1242)  评论(0编辑  收藏  举报