Efficient Time Distributed Dense


Let’s see if anyone can help me with this particular case. I have an input of dimension 1600x240 (1600 time steps and 240 features for each time step) and I want to apply a linear layer independently for each time step. I know that the pytorch nn.Linear module works with 2 dimensional inputs, but it doesn’t do exactly what I want. If I apply nn.Linear(240,100) on the input, we are only considering a 240x100 weight matrix. I have tried these four alternatives:

import torch.nn as nn

class Linear_only(nn.Module):
    def __init__(self,):
        super(Linear_only, self).__init__()
        self.linear = nn.Linear(240,100)
    def forward(self,x):
        return self.linear(x)

class TimeDistributedLinear(nn.Module):
    def __init__(self,):
        super(TimeDistributedLinear, self).__init__()
        self.linear = nn.Linear(240,100)
    def forward(self,x):
        x = x.view(-1,240)
        return self.linear(x)

class UsingModulelist(nn.Module):
    def __init__(self,):
        super(UsingModulelist, self).__init__()
        self.modulelistLinear = nn.ModuleList([nn.Linear(240,100) for i in range(1600)])
    def forward(self,x):
        output_list = []
        for i in range(1600):
            linear = self.modulelistLinear[i]
            x_i = linear(x[:,i,:].view(16,240))
            output_list.append(x_i)
        x = torch.stack(output_list)
        x = x.permute(1,0,2)
        return x    

class UsingParameter(nn.Module):
    def __init__(self,):
        super(UsingParameter, self).__init__()
        self.weight_last_linear = nn.Parameter(torch.empty((1600, 240,100), requires_grad = True))
        self.bias_last_linear = nn.Parameter(torch.empty(1600,100),requires_grad = True)

        stdv = 1. / math.sqrt(self.weight_last_linear.size(1))
        self.weight_last_linear.data.uniform_(-stdv, stdv)
        if self.bias_last_linear is not None:
            self.bias_last_linear.data.uniform_(-stdv, stdv)
            
    def forward(self,x):
        x = torch.einsum('ijl, jlm -> ijm',x,self.weight_last_linear)+self.bias_last_linear
        x = x.view(16,1600,100)
        return x


batch_size = 16
x = torch.rand(batch_size,1600, 240)
net_linear = Linear_only()
print(net_linear(x).size()) #torch.Size([16, 1600, 100]) Total Trainable Params: 24100
net_td_linear = TimeDistributedLinear()
print(net_td_linear(x).size()) #torch.Size([16, 1600, 100]) Total Trainable Params: 24100
net_modulelist = UsingModulelist()
print(net_modulelist(x).size()) #torch.Size([16, 1600, 100]) Total Trainable Params: 38560000
net_parameter = UsingParameter()
print(net_parameter(x).size()) #torch.Size([16, 1600, 100]) Total Trainable Params: 38560000

Of these alternatives, the first two do not do what I want. The third one does exactly what I want, but the 1600 loop makes it take too long, so I’m not interested. And the last one, I think it should do what I really want but it doesn’t work properly, so I prefer not to use nn.Parameter(). Can you think of any other way?

Can you elaborate more on why doesn’t it work properly?

Hi @ID56,
I can’t give much information about the use case I am working on but it is a time-series anomaly detection case. The code I have attached is just a small part of a model that returns the reconstructions of the input time-series. When I use nn.Parameter(), I get strange spikes in the reconstructions, like these (in the lilac-colored series):
image
Something that with nn.Linear() does not happen…