我是图卷积网络(一般情况下也是pytorch )的新手,所以在继续向网络添加层之前,我正在尝试验证消息传递层是否如预期的那样工作。
但是,当我查看传播的输出时,它与我所期望的不一致,因为我已经手动完成了节点的平均聚合。
我的数据是单个单元格(n)的集合,每个单元格都有记录计数(p)。我为这些单元创建了一个邻接矩阵,基于它们在维约化空间中的相似性(使用UMAP和k近邻)。
接下来我要做的就是在聚合相邻细胞的转录计数的基础上,平滑每个节点(单元)中记录计数的值。
我已经构建了消息传递层,并关闭了许多通常会在节点值中引入一些转换的函数。但是当我取矩阵中的第一个单元格并根据它自己的本地邻居手工平均它的特征时,我得到了非常不同的特征值。
我做错了什么?代码中有错误吗?还是消息传递中仍有一部分正在转换数据?
对于我的大部分代码,我在PyTorch几何学的docs https://pytorch-geometric.readthedocs.io/en/latest/notes/create_gnn.html上大量借用了这个例子
可复制代码如下:
#pytorch imports
import torch
import torch.nn as nn
from torch.nn import functional as F
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_remaining_self_loops, degree
#other imports
import numpy as np
import os
import sys
import pandas as pd
#Create GCN Class
class TranscriptConv(MessagePassing):
def __init__(self, in_channels, out_channels): #Just message passing - so in_channels and out_channels are equal (ie. number of features in = number of features out)
super().__init__(aggr = 'mean')
#self.lin = nn.Linear(in_channels, out_channels, bias=False) #commented out to avoid transformation
#self.bias = nn.Parameter(torch.Tensor(out_channels))
#self.reset_parameters()
#def reset_parameters(self): #Called during __init__ to make sure all parameters are zeroed.
#self.lin.reset_parameters()
#self.bias.data.zero_()
def message(self, x_j, norm):
#flow = "source_to_target" #this doesn't seem to be declared in the example (because 'source to target is the default')
return norm.view(-1,1) * x_j # .view() creates a new tensor, with -1 indicating that this dimension is inferred from the input
def forward(self, data): #input to this is the transcript x cell data itself and the edge_index
x = data.X
edge_index = data.edges
edge_index, _ = add_remaining_self_loops(edge_index, num_nodes= data.len) #this is needed so that it's own value is included in the mean
#x = self.lin(x) #linear summation of input x. commented out to avoid transformation
#degree calculation, normalization
#N = D^(-.5)AD^(-.5) #This isn't code - just the equation written down.
#For directed graph the above has become N = D^(-1)A
row, col = edge_index
#row is list of edges coming in with each value an index to a node. col is list of edges going out with each value an index to a node.
deg = degree(row, x.size(0), dtype=x.dtype) #This is the diagonal matrix D in the above equation. degree only calculates on 1-dimensional index
#deg_inv_sqrt = deg.pow(-0.5) #element wise power. changed to .pow(1) below for directed edges
deg_inv_sqrt = deg.pow(-1)
deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0 #values which were 0 before being inversed put back to 0
#when the edge is bi-direcitonal, it has to be normalised by in-degrees at target and source. for directed edge, just normalised by indegree at target.
norm = deg_inv_sqrt[row] #put in same order as edge index
norm = torch.ones(row.size()) # no normalisation here just to examine aggregation
#And propogate. This is where the magic happens: function calls message(), aggregate(), and update() internally
out = self.propagate(edge_index, x=x, norm=norm) #norm is required argument.
#Leave intercept in, but not sure if necessary
#out += self.bias #commented out here to avoid transformation
return out
#Create toy data
num_cells =100
num_features = 10
feature_mat = np.matrix(np.random.normal(size = (num_cells, num_features)))
adj_mat = np.matrix(np.random.binomial(p = 0.1, n = 1, size = (num_cells, num_cells)))
adj_mat.diagonal = 1 #set self-loops to 1 for the manual aggregating. Convolution adds remaining self-loops when this isn't used.
#Create edge index
edge_index = torch.tensor(data = adj_mat).nonzero().t().contiguous()
np.unique(edge_index[0].numpy(), return_counts=True) #these are the in-degrees for each cell
#DataLoader
class Data(torch.utils.data.DataLoader):
def __init__(self, X, edges):
self.X = torch.from_numpy(X)
self.edges = edges
self.len = self.X.shape[0]
def __getitem__(self, index):
return self.X[index]
def __len__(self):
return self.len
cell_graph = Data(X = feature_mat, edges = edge_index)
#instantiate and propagate
conv = TranscriptConv(num_features, num_features)
smoothed_features = conv(cell_graph)
averaged_firstCell = feature_mat[adj_mat[0,:].nonzero()[1],:].mean(axis = 0) #average features along neighborhood of first cell.
#The output of the below should be equal?
print(averaged_firstCell) #first cell mean-aggregated manually
print(smoothed_features[0,:]) #first cell mean-aggregated through GCN's message passing layer
```发布于 2023-01-09 01:25:10
我明白你想做什么了。看起来,您正在尝试实现一个简单的图卷积网络(GCN),它将图中的局部邻域上的节点值平均化。
对我来说最突出的一点是,您正在使用add_remaining_self_loops向边缘索引中添加自循环。这将导致将节点值与自身聚合在一起,这可能不是您想要的。相反,您可能需要考虑只使用连接不同节点的边缘,而不是添加自循环。
此外,看起来您已经注释掉了TranscriptConv层中的线性转换和偏置项。这意味着在消息传递过程中将不会以任何方式转换节点值。这就是你想要做的吗?
最后,打印消息传递层的输入和输出以查看每一步发生了什么可能是有帮助的。这可以帮助您调试可能发生的任何问题。
https://datascience.stackexchange.com/questions/117692
复制相似问题