关于DiffPool和MAML的结合

1. 整体思路

  • 熟悉原代码结构;
  • 了解maml代码结构;
  • 了解diffpool代码结构;
  • 根据接口修改(把接口是什么整理出来)。别忘了输出接口!

2. 开始

1. 整个代码是在做什么?
  • 从聊天记录了解;
  • 之前文档了解;
  • 整理资料。

3. 文章思路

​ 我们可以把很多结构转化成图,比如生化领域分子结构、金融领域的item。转化成图之后,对这些图分类就达成了对结构、item分类的目的。

​ 但是有两个挑战:

  1. 在小样本学习领域,怎么在保持图结构的情况下比较query graphs和support graphs?
  2. 怎么剔除任务相关的信息?
解决方式:
  1. $Hierarchical\quad Pooling-based \quad Graph \quad Matching$: 构造超图解决。
  2. $Hierarchical Graphset2vec:$ 我们用图池化结构。

4. MAML熟悉

1. 术语总结
  • Meta Learning: learning to learn, 实际的影响是对数据集的划分,进而让研究者制订模型结构;(根据资料找一个 找一个函数f 的函数F的能力)(具体到MAML是为了找到一个更好的初始化参数的方式,是的模型学习到先验知识,可以在新的任务上表现得更好)
  • Few-shot Learning: Meta Learning在监督学习领域的应用;
  • Meta training: Few-shot Learning的两个阶段,Meta training和meta testing阶段;
  • Meta testing: 测试阶段;
  • Meta task: 等同于支撑集(support set)
2. 关键理解

​ 通过预训练得到meta网络的参数,该参数可以在测试任务中,使用测试任务的Support Set对meta网络的参数进行finetuing。

3. 问题记录
  1. 为什么没有validation,难道超参数不需要优化吗?(meta learing本身就是为多种任务设计的,不需要在一个任务上做到最好)
  2. 怎么评价F的好坏呢?(其实就是利用其它的task,利用数据 )
  3. MAML的执行过程和model pretarining的区别?(MAML是用query更新参数,pretraining是用support更新)

5. Diffpool熟悉

1. 理解

​ 类比CNN,我们是把一个图卷积后缩小了一个尺寸,这是一层。但是GNN始终在一个图上进行,只是在原本的图结构上做一次信息提取,相当于一层的CNN。所以我们要做的就是 构建一个都层次的GNN网络:不仅完成信息的提取,还要把当前的图聚合成一个更粗粒度的图。

2. 关键公式

​ 这是关键的信息传递过程,其中有两个中间变量$Z$和$S$,其计算过程:

​ DiffPool的具体过程:

3. 实验设置

​ todo: 明确代码中哪里是用的diffpool。

Hi the soft-assign is the pooling method, base is the baseline, and set2set is the baseline with the set aggregation pooling for all node embeddings. The set2set method refers to “Order Matters: Sequence to sequence for sets”

​ 实际上代码中的SoftPoolingGcnEncoder就是GCN为基础的diffpool,我现在要做的就是把SoftPoolingGcnEncoder看懂,然后搬进去。

​ 我们的DIFFPOOL模型再GraphSAGE架构的基础上,比标准的GCN好很多。我们用平均的变种,并且在每两个GraphSAGE层后弄一个DiffPool层,总共用了两个Diffpool层。再每个Diffpool层之后,3个图卷积,然后是下一个diffpol层,或者readout层。embedding矩阵assignment矩阵通过两个分离的GraphSAGE模型计算出来。在两个diffpool层的架构中,clusters数目被设定为25%,在1个diffpool层的架构中,clusters数目是10%.Batch normalization被应用在每个GraphSAGE层之后。

  • input node embedding matrix: Z
  • cluster assignment matrix: S
4. 理解SoftPoolingGcnEncoder代码

According to the paper, it said you used the GraphSAGE as the GNN module and stack two layers before diffpool layer. However, in this code implementation, are you using GCN instead and stack 3 layers before diffpool?

The difference from GCN is the DeepSet aggregation, and concatenation in GraphSAGE. But you are right, that this is doing whole graph convolution, rather than the minibatch updates in GraphSAGE.

1
ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input)
  • h0就是很多图节点feature的列表。
  • adj就是临界矩阵的列表。
  • batch_num_nodes就是节点数的列表。
  • assign_x就是很多图节点assign_feature的列表。

​ 主要看forward函数,先是利用$X$,$A$,计算出$Z$。注意这里embedding用的矩阵是conv_first,conv_block和conv_last。这个的生成是继承GCN的,用到的参数是input_dim, hidden_dim, embedding_dim, num_layers等。

​ 然后就到了循环里面,循环数目是num_pooling。

​ 循环体,首先x_a接收的是assign_input的信息,然后用$X_a$,$A$计算出$S$,这里用的是self.assign_conv_first_modules[i], self.assign_conv_block_modules[i], self.assign_conv_last_modules[i],然后用assign_pred_modules[i]进行预测,并用Sotfmax给出$S$。用到的参数是assign_input_dim, assign_hidden_dim, assign_dim, assign_num_layers。

​ 循环体,然后更新$X$和$A$,并把$X$赋值给$X_a$。

​ 循环体,用$X$和$A$计算$Z$,这里用到的是self.conv_first_after_pool[i], self.conv_block_after_pool[i], self.conv_last_after_pool[i]。用到的参数是self.pred_input_dim, hidden_dim, embedding_dim, num_layers。

​ 循环体,然后找到$Z$里最大的作为输出out,添加到out_all里面。

​ 走出循环,判断是否concat,如果是就输出所有output,否就输出out。(soft-assignment)

​ 然后利用self.pred_model对output进行计算得到ypred。

5. 理解graph_sampler.py的代码
  • max_num_nodes: 对于networkx中的图的数据,找到G_list中节点最多的G的节点数。

  • feat_dim: 图中任意一个节点的feature向量维度。

  • adj_all: 邻接矩阵的array的列表。

  • len_all: 节点数的列表。

  • label_all: label的列表。

  • feature_all: “default”是max_num_nodes x feat_dim的列表; “id”是identity(max_num_nodes)的列表;”deg-num”是边数操作后的列表; “deg”是边。。。的列表;”struct”。。。

  • assign_feat: ”id“是identity(max_num_nodes)和feature_all[-1]的组合;否则是feature_all[-1]。

  • feat_dim: 是feature_all[0].shape[1]。

  • assign_feat_dim: 是assign_feat_all[0].shape[1]。

    ​ 这里的feature_all和assign_feat_all其实是一样的。其实可以看出来,就是收集每个图中每个节点的feature,弄成了feature_all和assign_feat_all,默认情况下都是”default”。

6. 两者结合

6. 原代码熟悉

​ 整体框架是maml,base learner是gcn,然后要把gcn换成diffpool。

1. 现有情况:

​ fit_function就是maml的框架,meta_model就是base learner。

2. 记录们
记录1:
1
2
3
model = encoders.GcnEncoderGraph(
input_dim, args.hidden_dim, args.output_dim, args.num_classes,
args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda()

​ 这里model的作用是利用gcn的diffpool,了解其过程即可能用这个。

  • input_dim: 3
  • args.hidden_dim: 20
  • args.output_dim: 20
  • args.num_classes: 6
  • args.num_gc_layers: 3
  • bn = args.bn: True
  • dropout=args.dropout: 0.0
  • args = args
记录2:
1
2
3
self.conv_first, self.conv_block, self.conv_last = self.build_conv_layers(
input_dim, hidden_dim, embedding_dim, num_layers,
add_self, normalize=True, dropout=dropout)

​ 这里应该是要构建卷积层,用到的参数是:

  • input_dim: 3
  • hidden_dim: 20
  • embedding_dim: 20
  • num_layers: 3
  • add_self: false
  • normalize: True
  • dropout: dropout
记录3:
1
2
3
4
5
6
model = encoders.SoftPoolingGcnEncoder(
max_num_nodes,
input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers,
args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool,
bn=args.bn, dropout=args.dropout, linkpred=args.linkpred, args=args,
assign_input_dim=assign_input_dim).cuda()
  • max_num_nodes: 100 (number of nodes for each graph in batch)
  • input_dim: 3
  • args.hidden_dim: 20
  • args.output_dim: 20
  • args.num_classes: 6
  • args.num_gc_layers: 3 (number of gc layers before each pooling)
  • args.hidden_dim: 3
  • assign_ratio = args.assign_ratio: 0.1
  • num_pooling=args.num_pool: 1
  • bn = args.bn: True
  • dropout=args.dropout: 0.0
  • linkpred=args.linkpred: False ()
  • args = args
  • assign_input_dim=assign_input_dim: 3
3. 要做的任务:

​ 将diffpool代码的encoders.py放入自己代码中,并对接口进行修改。将自己数据传入,利用encoders.py运算,并给出结果。

​ 注意,两方面信息要传入:

  1. 让model实例化的时候;
  2. 输入数据进行训练的时候。
4. 理解之前的工作:
  1. 为什么只改了forward而不是整个类?

    ​ 因为要做的其实只是将数据转换成diffpool需要的,网络结构并没有改动,所以只对过程中输入接口转换即可。

5. model实例化修改记录
1
2
3
4
self, max_num_nodes, input_dim, hidden_dim, embedding_dim, label_dim, num_layers,
assign_hidden_dim, assign_ratio=0.25, assign_num_layers=-1, num_pooling=1,
pred_hidden_dims=[50], concat=True, bn=True, dropout=0.0, linkpred=True,
assign_input_dim=-1, args=None

​ 把diffpool需要的信息都给他!

6. maml里的修改记录
1
def forward(self, x, adj, batch_num_nodes, **kwargs):

​ 把forward需要的信息都给他!不止,还要把自己的模块加进去!

diffpool里面:

1
2
3
4
5
6
7
adj = Variable(data['adj'].float(), requires_grad=False).cuda()
h0 = Variable(data['feats'].float()).cuda()
labels.append(data['label'].long().numpy())
batch_num_nodes = data['num_nodes'].int().numpy()
assign_input = Variable(data['assign_feats'].float(), requires_grad=False).cuda()

ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input)

把自己的fast_weights加到里面:

​ 我猜测functional_forward的作用就是这个。

7. 针对上面提到的问题,我要写一个functional forward把fast_weights传进去然后运算。

我有两个方案:

  1. 对diffpool及里面的gcn改造;
  2. 将diffpool里面的gcn换成models.py里面的gcn然后改造。

我决定采用方案1,原因有2:

  1. gcn的不同实现应该是一样的,不会影响结果;
  2. 难度似乎不是特别大。
1. weights会在什么时候用到?

图卷积里面,把self.weight换成了weights,并且弄了个id。

1
self.weight -> weights['gc{}.weight'.format(id)]

猜测应该跟生成fast_weights时候有关系。

查看一下fast_weights生成时候格式:

image-20200710182038074
image-20200710182038074

查看图卷积层的源代码对比forward和functional_forward可以知道,多穿进去一个id和weights。weights[‘id’]用于替换原有的weights。高层的functional_forward只是走了个形式。

2. 修改过程

​ 明确两点:

  1. 我是要用fast_weights代替所有self.weights;
  2. 我只修改输入数据阶段,不用改实例化阶段。

​ 最底层:修改了GraphConv的代码,加入了functional_forward函数。

​ 最顶层:第一个embedding_tensor就是$Z$应该对应的

3. 对nn.Linear的修改

​ 过程中有一个问题,用了nn.Linear,但是这个函数并没有暴露出weights接口,我要继承这个类并修改一下。

4. pred_model理解并修改

7. Debug记录

1. 为什么fast_weights层数变少了?18层变成了12层。
image-20200711155319777
image-20200711155319777

​ 思路:从实例化和forward过程中查看参数都是怎么来的。

​ pred_model修改,nn.Sequential可能也要修改。把nn.Sequential重写一下。@的用法?

2. 为什么construct_mask调用了3次?代码中只有两次出现(包括循环)

​ 理解错了,是gcn_forward调用了三次。

3. 为什么diffpool原代码的邻接矩阵大小没变?

​ 变化了,这是两个batch。

3. RuntimeError: One of the differentiated Tensors appears to not have been used in the graph. Set allow_unused=True if this is the desired behavior.

​ 如果allow_unused=True,就会出现一个None.

​ 思路是先通过unused找到哪个没有用,然后解决。

1
2
    (name, param - inner_lr * grad)
TypeError: unsupported operand type(s) for *: 'float' and 'NoneType'

​ 可以判断,应该是某个变量梯度为0了。

​ 另一个思路,看gcn的maml运行是怎样的,并照着这个修改。

​ python继承的学习!

正确情况:

image-20200711212811030
image-20200711212811030
image-20200711212831852
image-20200711212831852

错误情况:

​ 果然,我感觉就是fast_weights不全的锅。即上面的问题1.

4. 为什么maml.py这里train_output的维度是0,而label是10

​ 猜测是数据输入batch那里错了。实际没有错。

5. 重写nn.Sequential

​ 需要把id和weights传进去。

6. 为什么ypred = self.pred_model(output, 30, weights)之前的output维度是(10,100)不是(10,2)了?

​ 现在网络是14层,比18层少了4层,找回来。

7. 网络缺失pred_model的4层。

​ 有可能是nn.Sequential没写好。现在已经恢复18层了。

8. output结果正常, 通过pred_model应该变为2,但还是50.

​ 估计pred_model里面多层只有一个正常运行了。

9. train_output的输出有点奇怪
image-20200712140725897
image-20200712140725897
image-20200712140811346
image-20200712140811346

过程中发现,第二个epoch的fast_weights大量出现nan:

image-20200712141720026
image-20200712141720026

说明第一个epoch的参数那里就出现了问题。

找到这里出现了问题:

image-20200712144944127
image-20200712144944127
10. backward后grad为0?

​ 在meta_batch_loss.backward()函数里面grad已经是0了。

​ 找到问题在Variable._execution_engine.run_backward()函数这里。

​ 正常运行时情况:

image-20200712180526426
image-20200712180526426

​ 错误运行时情况:

image-20200712181324998
image-20200712181324998

​ 没什么区别,说明错误应该不在这里,而是网络里面。

​ 现在思路:从diffpool里面找找灵感。

​ 应该是输入的不是变量是常量的原因。

11. y = torch.matmul(adj, x) 大小不匹配

​ 已解决,adj和x顺序写错了。

​ 我猜测可能是loss不匹配,我就把原代码中的loss弄成了分支,将diffpool中的loss搬了进来。

​ 撤销这一步的修改。

12. 回到之前问题
image-20200712191246900
image-20200712191246900

​ 第一个思路是否能通过修改loss,研究loss代码之后发现没区别,失败。

此篇文档结束,此问题下篇文档解决。