Pytorch:十一、CNN(高级)


实际的CNN结构可能要更为复杂,比如:分支,输出拿来再用等;

  • 分支网络
GoogleNet

other是指

如果还是跟之前一样,一个个的去实例化的话,那么这个Net模型的代码就会有一大堆的代码冗余,而这是我们所不期望的。从这里不难看出,每个块都有类似的结构:

如此便可直接将这些块进行封装,归为一类(称为Inception块),再将块给串联起来,从而减少了重复工作。

卷积核有一些超参数是比较难选的:kernel大小等。所以GoogleNet就把他们几个都试一试,再选最好用的那个来进行(通过设置权重大小来进行):

Concatenate表示把若干个张量拼接到一起;Average Pooling,均值池化,可以人为指定w,d,从而保证输入输出图像大小一致(也可以用padding和strid来进行替代);1* 1卷积的个数取决于输入张量的通道数

1*1卷积

可以看到,通过这种方式可以把featuremap中输入通道的所有信息融合到一起——信息融合(可以理解为考试之后求总分,然后按总分进行比较);

通过1* 1卷积可以改变通道数量,从而减少开销:

这里的卷积计算,其实不只是针对1* 1的卷积,对所有的卷积都是一样的。卷积核的个数,决定了输出的通道数。输入的通道数决定了卷积核的层数

用代码实现Inception Module

#分支1,池化分支

#这段写在__init__
self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1)

#这段写在forward
branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
#将上一步的结果放到上面写好的1*1的卷积后得到输出结果
branch_pool = self.branch_pool(branch_pool)

#分支2,就一个1*1的卷积
self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)

#直接给1*1的卷积就行
branch1x1 = self.branch1x1(x)

#分支3,一个1*1卷积接一个5*5的
self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
#为了保证宽高不变,设置padding为2
self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)

brach5x5 = self.branch5x5_1(x)
brach5x5 = self.branch5x5_2(brach5x5)
#此时,batch、w和h都一样,只有通道数c不一样

#分支4,一个1*1接两个3*3的
self.branch3x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
self.branch3x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
self.branch3x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)

branch3x3 = self.branch3x3_1(x)
branch3x3 = self.branch3x3_2(branch3x3)
branch3x3 = self.branch3x3_3(branch3x3)

#最后要把上面四个分支拼接起来(Concatenate)
#将刚刚的四个分支的结果放到一个数组中,再通过torch的cat函数来进行拼接
outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
#维度为1是因为要对channels这个维度进行拼接
return torch. cat(outputs, dim=1)

把上面的分支一起放进一个类中以及对应的Net模型就是下面这样:

class InceptionA(nn.Module):
    def __init__(self, in_channels):
        super(InceptionA, self).__init__()
        #初始输入通道未定,这样方便实例化的时候使用
        self.branch1x1 = nn.Conv2d(in_channels, 16, kernel_size=1)
        
        self.branch5x5_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch5x5_2 = nn.Conv2d(16, 24, kernel_size=5, padding=2)
        
        self.branch3x3_1 = nn.Conv2d(in_channels, 16, kernel_size=1)
        self.branch3x3_2 = nn.Conv2d(16, 24, kernel_size=3, padding=1)
        self.branch3x3_3 = nn.Conv2d(24, 24, kernel_size=3, padding=1)
        
        self.branch_pool = nn.Conv2d(in_channels, 24, kernel_size=1)
        
    def forward(self, x):
        branch1x1 = self.branch1x1(x)
        
        brach5x5 = self.branch5x5_1(x)
        brach5x5 = self.branch5x5_2(brach5x5)
        
        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
        branch3x3 = self.branch3x3_3(branch3x3)
        
        branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1)
        branch_pool = self.branch_pool(branch_pool)
        
        outputs = [branch1x1, branch5x5, branch3x3, branch_pool]
        return torch. cat(outputs, dim=1)
    
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1,10, kernel_size = 5)
        #24*3+16=88,这里共有88个通道
        self.conv2 = nn.Conv2d(88,20, kernel_size = 5)
        
        self.incep1 = InceptionA(in_channels=10)
        self.incep2 = InceptionA(in_channels=20)
        
        self.mp = nn.MaxPool2d(2)
        #1408是根据mnist数据集经过网络计算后所得到的元素个数
        self.fc = nn.Linear(1408, 10)
        
    def forward(self, x):
        in_size = x.size(0)
        x = F.relu(self.mp(self.conv1(x)))
        x = self.incep1(x)
        x = F.relu(self.mp(self.conv2(x)))
        x = self.incep2(x)
        x = x.view(in_size, -1)
        x = self.fc(x)
        return x

将上述代码放到MNIST数据集中所得到的结果就是下面这样:

可以看到,训练太多次的时候也会出现过拟合的状态;我们可以把正确率最高的那个模型进行存盘,方便以后的使用;

  • ResNet:

可以看到,如果把3×3的卷积核一直这样堆下去,那么20层的效果比56层的效果要好;

梯度消失:由一大堆<1的梯度相乘,那么最终的结果会趋近于0。这样一来,权重也就无法更新,也就是无法得到有效的训练;

普通Net与ResNet

ResNet就是把卷积后的结果再加上原来的x之后再进行激活,通过导数不难看出:\frac{∂z}{∂x} = \frac{∂F}{∂x} + 1,使其最终不会趋向于0;

具体实现就是再最后和x做一个加法就行了;

ResNet有一堆跳连接。放大那里画虚线是因为输入的x与输出张量的维度不同,要做单独的处理,如池化层等;

加了新的块之后的流程图

代码实现:

class ResidualBlock(nn.Module):
    def __init__(self, channels):
        super(ResidualBlock, self).__init__()
        self.channels = channels
        #通过padding保证输出图像大小不变,而且输入/出的通道数要一样
        self.conv1 = nn.Conv2d(channels,channels, kernel_size = 3, padding=1)
        self.conv2 = nn.Conv2d(channels,channels, kernel_size = 3, padding=1)
        
    def forward(self, x):
        y = F.relu(self.conv1(x))
        y = self.conv2(y)
        #最后这里记得要加个x
        return F.relu(x + y)

这样就可以写出模型的完整类:

class Net(nn.Module):
    def __init__(self, channels):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(channels,channels, kernel_size = 3, padding=1)
        self.conv2 = nn.Conv2d(channels,channels, kernel_size = 3, padding=1)
        self.mp = nn.MaxPool2d(2)
        
        self,rblock1 = ResidualBlock(16)
        self,rblock2 = ResidualBlock(32)
        
        self.fc = nn.Linear(512,10)
        
    def forward(self, x):
        in_size = x.size(0)
        x = self.mp(F.relu(self.conv1(x)))
        x = self.rblock1(x)
        x = self.mp(F.relu(self.conv2(x)))
        x = self.rblock2(x)
        x = x.view(in_size, -1)
        x = self.fc(x)
        return x
    
    

记住,如果网络结果非常复杂,那么就可以用新的类去封装他,就像这里的ResidualBlock一样;

简单的测试方法:在forward中保留前n行,然后注释掉其他行,看看输出结果和预期的结果是否一致,没问题就多保留一行,继续看结果;

ResNet的结果

以后的路

  • 从理论层面探讨深度学习原理(多读书);
  • 阅读Pytorch文档,不是很多,至少通读一遍;
  • 复现模型的经典工作——读别人的代码,学习一下,再来自己写代码这样一来就可以选择自己感兴趣的特定领域去发展了(如果无法复现就去看下原来的代码,学学别人的技术,从而提升自己);
  • 扩充视野:不断解决知识上的盲点,从而提升自己的能力,将来就可以组装自己的模型了;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容