Слой Soft One-Hot Encoding (SOHE) - это основа 
SoftOrdering-слоя. Почему я его выделяю отдельно? 
Он почему-то улучшает обучение нейросети. Я сам пока не понимаю, это результат экспериментов.
Слой SOHE вставляем между линейными слоями как слой активационной функции. Слой SOHE имеет один гиперпараметр - количество бинов (n_bins).
Выход линейного слоя разбиваем на бины, и если значение попадает в бин, то на выходе единица, иначе ноль. Только реализована мягкая версия такого алгоритма, поэтому слой называется именно Soft One-Hot Encoding. На выход сигнал подаётся через flatten-функцию, итого входной вектор размера n_inputs превращается в вектор размера n_inputs 

 n_bins.
Для последующих слоёв такой сигнал более элементарен и прост для анализа нежели неэнкодированный, такое преобразование способно заменить пару полноценных (Linear-Sigmoid) слоёв, это единственное моё объяснение.
1. Слой SoftOrdering отличается лишь добавлением после слоя SOHE функции mean(). Ниже приведён код, там функция mean() закомментирована, поэтому SoftOrdering превратился в SOHE.
2. Слой RecurrentSoftOrdering зацикливает SOHE, суммируя (накапливая) результат на каждой итерации SOHE. Практика показывает, что чисто рекурсия SOHE (без суммирования) работает хуже чем рекурсия SoftOrdering (который с суммированием). Код RecurrentSoftOrdering не привожу, там всё просто.
3.Разница между SOHE, SoftOrdering, RecurrentSoftOrdering настолько невелика, что склоняюсь к тому, чтобы оставить только один SOHE. (Код ниже, видим SoftOrdering, но имеем в виду SOHE.)
import torch
import torch.nn.functional as F
class SoftOrdering(nn.Module):  #Слой мягкого упорядочивания
    n_bins: int
    n_inputs: int
    gcoef: float
    gain: float
    lspace: np.array
    def __init__(self, n_bins: int, n_inputs: int, gcoef: float = 6.0) -> None:
        super().__init__()
        __constants__ = ["n_bins", "n_inputs", "gcoef"]
        self.n_bins = n_bins
        self.n_inputs = n_inputs
        self.gcoef = gcoef    
        self.lspace = torch.linspace(-0.1 - 1/(n_bins - 1), 1.1 + 1/(n_bins - 1), steps=n_bins + 1)
        self.gain = gcoef * (n_bins - 1)
        self.sigmoid = nn.Sigmoid()
    def extra_repr(self) -> str:
        return f"n_bins={self.n_bins}, n_outputs={self.n_inputs}, coef={self.gcoef}"
    def forward(self, x: torch.Tensor) -> torch.Tensor: # размерность x (batch, channel, input), здесь и далее batch и channel необязательны
        z = x.unsqueeze(-2)
        lenzsize = len(z.size())
        zsize = np.ones(lenzsize)
        zsize[-2] = len(self.lspace)
        z = z.repeat(tuple(zsize.astype('int'))) #размерность z (batch, channel, bin, input)
        lsize = np.array(z.size())
        lsize[-2] = 1
        l = self.lspace.unsqueeze(1).repeat(tuple(lsize.astype('int'))) #размерность l (batch, channel, bin, input)
        z = z - l
        z = self.sigmoid(self.gain * z)
        zslice = [slice(None, None),] * (lenzsize - 2) + [slice(None, -1)]
        z = z[zslice] - z.roll(-1, dims=(-2))[zslice]
        p = z.flatten(-2, -1)
        #p = z.mean(1)
        return p # размерность p (batch, channel, bin * input)
 
  Объявление слоя в __init__()
self.ordering = SoftOrdering(n_bins1, n_inputs1, gcoef=2.0)
 
Использование слоя в forward()
z = self.linear1(z)
z = self.sigmoid1(z) # я здесь нужен, чтобы сигналы были в пределах 0...1
z = self.ordering(z) # ку-ку, это я
z = self.linear2(z)