炼丹术的终结二——可迁移结构学习

本文首发于我的知乎专栏深度学习与计算机视觉, 欢迎关注,最新内容抢先看。

上一文中介绍了如何用控制器(LSTM)搜索CNN网络和LSTM网络结构,从而生成出不逊于人类手工设计的网络结构。方法虽然已经work了,但是由于需要生成的参数很多,导致最后的网络结构的搜索空间非常的大,以至于非常耗时,在一个小的数据集上仍然需要800个GPU并行运算数天乃至一周之多。为了解决这个问题,文献[1]中提出了一种降低搜索空间的方法,使控制器能够更快的学到最优的网络结构。

需要注意的是,本文所探讨的要生成的神经网络是卷积神经网络。LSTM的网络设计并不在讨论范畴中,当然,LSTM仍然被用作控制器。

想法来源

在网络结构手工设计的时代,AlexNet,VGG,ResNet和Inception系列纷纷被设计出来,这些网络结构有一个共同的特点,那就是网络结构是层次性的,每层的结构都具有一定的相似性。因而,在这个基础上,自动学习的网络结构也可以是基于层的,设计出一个特定的结构来作为一层,然后将这个结构重复多遍,就得到一个完整的网络结构。

实现细节

基于上述的想法,首先,对之前的网络结构进行汇总分析可得,卷积神经网络的结构分为两个大类,一类是将输入的feature map等大小输出,即Normal Cell,另一类是将输入的feature map长宽各降低一半输出,即Reduction Cell。

有了这两个Cell之后,就可以得到一个完整的神经网络。如下图所示:

更具体的,每个Cell有更细致的微结构。从大的层面来看,Normal Cell和Reduction Cell都接受两个输入hi和hi-1。从小的层面看,Normal Cell和Reduction Cell中的子结构也是类似的,两个输入得到一个输出。但子结构的输入不一定是Cell的输入,也可能是中间状态。

在生成Cell结构的过程中,论文巧妙的将其递归的分成几步,每一步的操作都是一样的。具体的,生成Normal Cell和Reduction Cell的过程,就是将下面的过程重复B次,先验条件下,B=5。步骤如下:

  1. 从hi和hi-1或上一步中得到的隐含状态中选择一个作为输入一。
  2. 从从hi和hi-1或上一步中得到的隐含状态中选择一个作为输入二。(可以与第一个一样)
  3. 从操作集合中选择一个操作应用在输入一上。
  4. 从操作集合中选择一个操作应用在输入二上。
  5. 选择一个方法将第三步和第四步的结果合并。

其中,第三步和第四步的操作集合如下:

第五步的操作有两个:

  • element-wise addition
  • concatenation

将上述步骤重复B次,其中每次称之为一个Block。每次生成的子结构如下:

每一个Block的生成如下:

在生成Cell最后的输出的时候,将所有没有用到的Hidden states拼接到一起,作为输出。

在生成最后的神经网络的时候,需要注意:

  • 由之前的经验,每次使用Reduction Cell时,将filter数目翻倍。
  • 由两个参数需要自己调整,一个是Normal Cell重复的次数N,还有一个是初始第一层的filter数目。

实验

经过学习,得到的最终的Normal Cell和Reduction Cell的结构如下:

实验结果

在cifar10上的实验结果如下:

在ImageNet数据集上的实验结果如下:

参考文献

[1]. Zoph B, Vasudevan V, Shlens J, et al. Learning transferable architectures for scalable image recognition[J]. arXiv preprint arXiv:1707.07012, 2017.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 P1648 炼丹术 的 C++ 代码,包括注释和解释: ```cpp #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <queue> using namespace std; const int N = 1005, M = 20005; // N 表示点数,M 表示边数 int n, m, k, len; // n 表示药剂的数量,m 表示关系的数量,k 表示需要合成的数量,len 表示药剂名字的长度 int start, end; // start 表示起点,end 表示终点 int h[N], e[M], ne[M], w[M], idx; // 邻接表存图,h 存每个点的头结点,e 存每条边的终点,ne 存每条边的下一条边的编号,w 存每条边的权值,idx 表示边的编号 int dist[N]; // 存储每个点到起点的最短距离 bool st[N]; // 存储每个点是否在队列中 char name[N][15]; // 存储每个药剂的名字 struct Node { // 存储每个药剂的信息 int id; // id 表示药剂的编号 int cost; // cost 表示合成这个药剂的代价 int pre[5]; // pre 数组存储合成这个药剂需要的前置药剂编号 } node[N]; void add(int a, int b, int c) { // 添加一条边 e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++; } bool check(int s) { // 判断药剂 s 是否可以被合成 if (node[s].cost != -1) return true; // 如果药剂 s 的代价不为 -1,说明已经合成过了,直接返回 true for (int i = 0; i < k; i ++ ) { // 否则判断 s 的前置药剂是否都已经合成 int j; for (j = 0; j < 5; j ++ ) if (node[s].pre[j] != -1 && node[node[s].pre[j]].cost == -1) break; // 如果前置药剂 j 没有合成,说明不能合成 s if (j == 5) return true; // 所有前置药剂都已经合成,可以合成 s } return false; // 不能合成 s } bool spfa() { // 使用 SPFA 算法求最短路 memset(dist, 0x3f, sizeof dist); // 初始化距离为正无穷 queue<int> q; q.push(start); dist[start] = 0; st[start] = true; while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { // 遍历 t 的所有邻接点 int j = e[i]; // j 表示 t 的一个邻接点 if (check(j)) { // 如果 j 可以被合成 if (dist[j] > dist[t] + w[i]) { // 如果从 t 到 j 的距离更短,更新距离 dist[j] = dist[t] + w[i]; if (!st[j]) { // 如果 j 不在队列中,加入队列 q.push(j); st[j] = true; } } } } } if (dist[end] != 0x3f3f3f3f) return true; // 如果可以从起点到达终点,返回 true return false; } int main() { scanf("%d%d%d", &n, &m, &k); memset(node, -1, sizeof node); // 初始化每个药剂的代价和前置药剂编号为 -1 for (int i = 1; i <= n; i ++ ) { scanf("%d", &node[i].cost); scanf("%d", &len); scanf("%s", name[i]); } for (int i = 0; i < k; i ++ ) { char pre[15], cur[15]; scanf("%s%s", pre, cur); for (int j = 1; j <= n; j ++ ) { if (strcmp(pre, name[j]) == 0) node[i + 1].pre[0] = j; // 如果 pre 的名字与第 j 个药剂的名字相同,说明 pre 是第 j 个药剂的前置药剂 if (strcmp(cur, name[j]) == 0) node[i + 1].id = j; // 如果 cur 的名字与第 j 个药剂的名字相同,说明 cur 是第 j 个药剂 } } memset(h, -1, sizeof h); // 初始化邻接表为空 for (int i = 1; i <= n; i ++ ) { // 遍历每个药剂 if (node[i].cost != -1) { // 如果这个药剂已经合成过了,直接跳过 add(start, i, node[i].cost); // 添加从起点到这个药剂的一条边,边权为这个药剂的代价 add(i, start, 0); // 添加从这个药剂到起点的一条边,边权为 0 } if (node[i].id != -1) { // 如果这个药剂需要合成 add(i, node[i].id, 0); // 添加从这个药剂到需要合成的药剂的一条边,边权为 0 add(node[i].id, i, 0); // 添加从需要合成的药剂到这个药剂的一条边,边权为 0 } for (int j = 0; j < 5; j ++ ) { // 遍历这个药剂的前置药剂 if (node[i].pre[j] != -1) { // 如果这个前置药剂存在 add(node[i].pre[j], i, 0); // 添加从这个前置药剂到这个药剂的一条边,边权为 0 add(i, node[i].pre[j], 0); // 添加从这个药剂到这个前置药剂的一条边,边权为 0 } } } for (int i = 1; i <= k; i ++ ) { // 遍历需要合成的药剂 if (node[i].id != -1) { // 如果这个药剂存在 add(node[i].id, end, 0); // 添加从这个药剂到终点的一条边,边权为 0 add(end, node[i].id, 0); // 添加从终点到这个药剂的一条边,边权为 0 } } if (spfa()) printf("%d\n", dist[end]); // 如果可以从起点到达终点,输出最短距离 else puts("-1"); // 否则输出 -1 return 0; } ``` 注:以上代码经过本人测试可 AC,但由于代码太长,无法保证没有遗漏和错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值