How to solve the issue of different dimension of features?

Hi Dgl community,

I need some assistance with the following code. Currently, it works when the number of features for customers, merchants, and transactions
are all three. However, when I include additional features only for customers
(such as df[[‘customer_id’, ‘city_id’, ‘lat’,‘long’]]), an error occurs. Could you advise on how to modify the code to resolve this error?

# Define a Heterograph Conv model
class RGCN(nn.Module):
    def __init__(self, in_feats, hid_feats, hid_feats2, out_feats, rel_names):
        super().__init__()

        self.conv1 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(in_feats, hid_feats)
            for rel in rel_names}, aggregate='sum')
        self.conv2 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(hid_feats, hid_feats2)
            for rel in rel_names}, aggregate='sum')
        self.conv3 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(hid_feats2, out_feats)
            for rel in rel_names}, aggregate='sum')

    def forward(self, graph, inputs):
        # inputs are features of nodes
        h = self.conv1(graph, inputs)
        h = {k: F.relu(v) for k, v in h.items()}
        h = self.conv2(graph, h)
        h = {k: F.relu(v) for k, v in h.items()}
        h = self.conv3(graph, h)
        return h

#Here is my issue
#I would like to use different number of features such as df[[‘customer_id’, ‘city_id’, ‘lat’,‘long’]]
customer_feat = df[['customer_id', 'city_id', 'lat']]
merchant_feat = df[['merchant_id', 'merch_long', 'merch_lat']]
trans_feat = df[['transaction_id', 'is_fraud','amt']]

raph_data = {
   ('customer', 'make', 'transaction'): (df.customer_id.to_numpy(), df.transaction_id.to_numpy()),
   ('merchant', 'create', 'transaction'): (df.merchant_id.to_numpy(), df.transaction_id.to_numpy()),
   ('transaction', 'make-by', 'customer'): (df.transaction_id.to_numpy(), df.customer_id.to_numpy()),
   ('transaction', 'create_by', 'merchant'): (df.transaction_id.to_numpy(), df.merchant_id.to_numpy())
}
hetero_graph = dgl.heterograph(graph_data)
print(hetero_graph)

hetero_graph.nodes['customer'].data['feature'] = torch.tensor(customer_feat.groupby(['customer_id']).max().to_numpy())
hetero_graph.nodes['merchant'].data['feature'] = torch.tensor(merchant_feat.groupby(['merchant_id']).max().to_numpy())
hetero_graph.nodes['transaction'].data['feature'] = torch.tensor(trans_feat.groupby(['transaction_id']).max().to_numpy())
hetero_graph.nodes['transaction'].data['label'] = torch.tensor(trans_feat.sort_values(by=['transaction_id']).is_fraud.to_numpy())

#randomly generate training masks on user nodes and click edges
hetero_graph.nodes['transaction'].data['train_mask'] = torch.cat((torch.ones(encoded_df1_up.shape[0], dtype=torch.bool), torch.zeros(df2.shape[0], dtype=torch.bool)), 0)

model = RGCN(2, 20, 10, len(pd.unique(df.is_fraud)), hetero_graph.etypes)
c_feats = hetero_graph.nodes['customer'].data['feature'].float()
m_feats = hetero_graph.nodes['merchant'].data['feature'].float()
t_feats = hetero_graph.nodes['transaction'].data['feature'].float()
labels = hetero_graph.nodes['transaction'].data['label']
train_mask = hetero_graph.nodes['transaction'].data['train_mask']
test_mask = train_mask.logical_not()

node_features = {'customer': c_feats, 'merchant': m_feats, 'transaction': t_feats}
h_dict = model(hetero_graph, {'customer': c_feats, 'merchant': m_feats, 'transaction': t_feats})
h_cus = h_dict['customer']
h_mer = h_dict['merchant']
h_tran = h_dict['transaction']

opt = torch.optim.Adam(model.parameters(), lr=0.001)

count = 0
for epoch in range(100):
    model.train()
    # forward propagation by using all nodes and extracting the user embeddings
    logits = model(hetero_graph, node_features)['transaction']
    # compute loss
    loss = F.cross_entropy(logits[train_mask], labels[train_mask])
    # Compute validation accuracy.  Omitted in this example.
    # backward propagation
    opt.zero_grad()
    loss.backward()
    opt.step()
    count+=1
#     if count % 10 == 0:
#         print(loss.item())
    model.eval()
    with torch.no_grad():
        pred = model(hetero_graph, node_features)['transaction']
        result = hetero_graph.nodes['transaction'].data['label']


        train_acc = (pred[test_mask].argmax(1) == labels[test_mask]).float().mean()
        test_acc = (pred[test_mask].argmax(1) == labels[test_mask]).float().mean()
        print('*'* 50)
        print('Loss', loss.item())
        print('Test Accuracy: %.2f%%' % (test_acc.item() * 100))
        print('Recall', recall_score(labels[test_mask], pred[test_mask].argmax(1)) * 100)
        print('F1Score', f1_score(labels[test_mask], pred[test_mask].argmax(1))  * 100)
        print('ROC', roc_auc_score(labels[test_mask], pred[test_mask].argmax(1)) * 100)

Current error message

Hi @kyuwanchoi , you may project them into the same dimension with nn.Linear before message passing, or modify the in_feats in self.conv1 for the corresponding relation make as it handles input features of customer nodes.

Hi @dyru ,

Thank you very much for your support! Could you teach me how to modify my code to implement what you told?

The problem is that your first module in your RGCN

        self.conv1 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(in_feats, hid_feats)
            for rel in rel_names}, aggregate='sum')

assumes that all the node types have the same number of features in_feats (which is 2). You could append a dictionary of modules that projects the node features with different sizes into the same size like this:

    # change in_feats to in_feats_dict which is a dictionary of node types and number of features
    def __init__(self, in_feats_dict, hid_feats, hid_feats2, out_feats, rel_names):
        super().__init__()

        # add this
        self.proj = nn.ModuleDict({
            ntype: nn.Linear(in_feats, hid_feats)
            for ntype, in_feats in in_feats_dict.items()
        })
        # then change in_feats in the first GNN layer to hid_feats
        self.conv1 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(hid_feats, hid_feats)
            for rel in rel_names}, aggregate='sum')
        ...

    def forward(self, graph, inputs):
        # add this to project it
        inputs = {ntype: self.proj[ntype](x) for ntype, x in inputs.items()}
        h = self.conv1(graph, inputs)
        ...

@BarclayII Thank you very much for your support! It is really helpful for you. I added your code to mine; however, there is an error. Do you know how to resolve it?

@BarclayII By the way, I updated my code as follows,

class RGCN(nn.Module):

    def __init__(self, in_feats_dict, hid_feats, hid_feats2, hid_feats3, out_feats, rel_names):

        super().__init__()

       

        # add this

        self.proj = nn.ModuleDict({

            ntype: nn.Linear(in_feats, hid_feats)

            for ntype, in_feats in in_feats_dict.items()

        })

        self.conv1 = dglnn.HeteroGraphConv({

            rel: dglnn.GraphConv(hid_feats, hid_feats2)

            for rel in rel_names}, aggregate='sum')

        self.conv2 = dglnn.HeteroGraphConv({

            rel: dglnn.GraphConv(hid_feats2, hid_feats3)

            for rel in rel_names}, aggregate='sum')

        self.conv3 = dglnn.HeteroGraphConv({

            rel: dglnn.GraphConv(hid_feats3, out_feats)

            for rel in rel_names}, aggregate='sum')

    def forward(self, graph, inputs):

        # inputs are features of nodes

        # add this to project it

        inputs = {ntype: self.proj[ntype](x) for ntype, x in inputs.items()}

        h = self.conv1(graph, inputs)

        h = {k: F.relu(v) for k, v in h.items()}

        h = self.conv2(graph, h)

        h = {k: F.relu(v) for k, v in h.items()}

        h = self.conv3(graph, h)

        return h

I updated my code as follows: Could you check my code?

class ProjectionLayer(nn.Module):
    def __init__(self, in_sizes, out_size):
        super().__init__()
        print(len(in_sizes['customer']))
        self.layers = torch.nn.ModuleDict({
            'customer' : torch.nn.Linear(len(in_sizes['customer']), out_size),
            'merchant' : torch.nn.Linear(len(in_sizes['merchant']), out_size),
            'transaction' : torch.nn.Linear(len(in_sizes['transaction']), out_size)})
    def forward(self, feats):
        # user and item features can have different lengths but
        # will become the same after the projection layer
        print(torch.t(feats['customer']))
        result = {'customer' : self.layers['customer'](torch.t(feats['customer'])),
                'merchant' : self.layers['merchant'](torch.t(feats['merchant'])),
                'transaction' : self.layers['transaction'](torch.t(feats['transaction']))}
        print(result)
        return result

class RGCN(nn.Module):
    def __init__(self, in_feats, hid_feats, hid_feats2, out_feats, rel_names):
        super().__init__()

        self.conv1 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(in_feats, hid_feats)
            for rel in rel_names}, aggregate='sum')
        self.conv2 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(hid_feats, hid_feats2)
            for rel in rel_names}, aggregate='sum')
        self.conv3 = dglnn.HeteroGraphConv({
            rel: dglnn.GraphConv(hid_feats2, out_feats)
            for rel in rel_names}, aggregate='sum')

    def forward(self, graph, inputs):
        # inputs are features of nodes
        h = self.conv1(graph, inputs)
        h = {k: F.relu(v) for k, v in h.items()}
        h = self.conv2(graph, h)
        h = {k: F.relu(v) for k, v in h.items()}
        h = self.conv3(graph, h)
        return h

customer_feat = df[['customer_id', 'city_id', 'lat','long']]
merchant_feat = df[['merchant_id', 'merch_long', 'merch_lat']]
trans_feat = df[['transaction_id', 'is_fraud','amt']]

graph_data = {
   ('customer', 'make', 'transaction'): (df.customer_id.to_numpy(), df.transaction_id.to_numpy()),
   ('merchant', 'create', 'transaction'): (df.merchant_id.to_numpy(), df.transaction_id.to_numpy()),
   ('transaction', 'make-by', 'customer'): (df.transaction_id.to_numpy(), df.customer_id.to_numpy()),
   ('transaction', 'create_by', 'merchant'): (df.transaction_id.to_numpy(), df.merchant_id.to_numpy())
}
hetero_graph = dgl.heterograph(graph_data)
print(hetero_graph)

hetero_graph.nodes['customer'].data['feature'] = torch.tensor(customer_feat.groupby(['customer_id']).max().to_numpy())
hetero_graph.nodes['merchant'].data['feature'] = torch.tensor(merchant_feat.groupby(['merchant_id']).max().to_numpy())
hetero_graph.nodes['transaction'].data['feature'] = torch.tensor(trans_feat.groupby(['transaction_id']).max().to_numpy())
hetero_graph.nodes['transaction'].data['label'] = torch.tensor(trans_feat.sort_values(by=['transaction_id']).is_fraud.to_numpy())

#randomly generate training masks on user nodes and click edges
hetero_graph.nodes['transaction'].data['train_mask'] = torch.cat((torch.ones(encoded_df1_up.shape[0], dtype=torch.bool), torch.zeros(df2.shape[0], dtype=torch.bool)), 0) # Train + Test
# hetero_graph.nodes['transaction'].data['train_mask'] = torch.zeros(len(hetero_graph.nodes('transaction')), dtype=torch.bool).bernoulli(0.3)

#model = RGCN(2, 20, 10,10, len(pd.unique(df.is_fraud)), hetero_graph.etypes)
#size 999 x 4
c_feats = hetero_graph.nodes['customer'].data['feature'].float()
#size 693 x 3
m_feats = hetero_graph.nodes['merchant'].data['feature'].float()
#size 3134057 x 3
t_feats = hetero_graph.nodes['transaction'].data['feature'].float()

node_features_pre = {'customer': torch.t(c_feats), 'merchant': torch.t(m_feats), 'transaction': torch.t(t_feats)}
#print(node_features_pre.items())
print(type(node_features_pre))
#model = RGCN(node_features_pre, 2, 10,10, len(pd.unique(df.is_fraud)), hetero_graph.etypes)
projection_model = ProjectionLayer(node_features_pre ,2)
projected_feature = projection_model(node_features_pre)
model = RGCN(2, 20,10, len(pd.unique(df.is_fraud)), hetero_graph.etypes)
labels = hetero_graph.nodes['transaction'].data['label']
train_mask = hetero_graph.nodes['transaction'].data['train_mask']
test_mask = train_mask.logical_not()

upgraded_projected_feature = {'customer':projected_feature['customer'].data,'merchant':projected_feature['merchant'].data,'transaction':projected_feature['transaction'].data}
h_dict = model(hetero_graph, upgraded_projected_feature)
h_cus = h_dict['customer']
h_mer = h_dict['merchant']
h_tran = h_dict['transaction']

opt = torch.optim.Adam(model.parameters(), lr=0.001)

count = 0
for epoch in range(100):
    model.train()
    # forward propagation by using all nodes and extracting the user embeddings
    logits = model(hetero_graph, upgraded_projected_feature)['transaction']
    # compute loss
    loss = F.cross_entropy(logits[train_mask], labels[train_mask])
    # Compute validation accuracy.  Omitted in this example.
    # backward propagation
    opt.zero_grad()
    loss.backward() #it was default

    opt.step()
    count+=1
#     if count % 10 == 0:
#         print(loss.item())
    model.eval()
    with torch.no_grad():
        pred = model(hetero_graph, upgraded_projected_feature)['transaction']
        result = hetero_graph.nodes['transaction'].data['label']


        train_acc = (pred[test_mask].argmax(1) == labels[test_mask]).float().mean()
        test_acc = (pred[test_mask].argmax(1) == labels[test_mask]).float().mean()
        print('*'* 50)
        print('Loss', loss.item())
        print('Test Accuracy: %.2f%%' % (test_acc.item() * 100))
        print('Recall', recall_score(labels[test_mask], pred[test_mask].argmax(1)) * 100)
        print('F1Score', f1_score(labels[test_mask], pred[test_mask].argmax(1))  * 100)
        print('ROC', roc_auc_score(labels[test_mask], pred[test_mask].argmax(1)) * 100)

Hi DGL community members,

could you check my code?

If you’re seeing this message, please support me. You’ll receive good luck!