text: import math
  import torch
  from torch import nn
  import torch.nn.functional as F
  from neural_networks.base_models import BaseModel
  
  CUT_OFF = {'dish washer': 2500,
             'fridge': 300,
             'kettle': 2000,
             'microwave': 3000,
             'washing machine': 2500}
  
  MIN_OFF_DUR = {'dish washer': 1800,
                 'fridge': 12,
                 'kettle': 0,
                 'microwave': 30,
                 'washing machine': 160}
  
  MIN_ON_DUR = {'dish washer': 1800,
                'fridge': 60,
                'kettle': 12,
                'microwave': 12,
                'washing machine': 1800}
  
  POWER_ON_THRESHOLD = {'dish washer': 10,
                        'fridge': 50,
                        'kettle': 2000,
                        'microwave': 200,
                        'washing machine': 20}
  
  LAMBDA = {'dish washer': 1,
            'fridge': 10**(-6),
            'kettle': 1,
            'microwave': 1,
            'washing machine': 10**(-2)}
  
  
  class GELU(nn.Module):
      def forward(self, x):
          return 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3))))
  
  
  class PositionalEmbedding(nn.Module):
      def __init__(self, max_len, d_model):
          super().__init__()
          self.pe = nn.Embedding(max_len, d_model)
  
      def forward(self, x):
          batch_size = x.size(0)
          return self.pe.weight.unsqueeze(0).repeat(batch_size, 1, 1)
  
  
  class LayerNorm(nn.Module):
      def __init__(self, features, eps=1e-6):
          super(LayerNorm, self).__init__()
          self.weight = nn.Parameter(torch.ones(features))
          self.bias = nn.Parameter(torch.zeros(features))
          self.eps = eps
  
      def forward(self, x):
          mean = x.mean(-1, keepdim=True)
          std = x.std(-1, keepdim=True)
          return self.weight * (x - mean) / (std + self.eps) + self.bias
  
  
  class Attention(nn.Module):
      def forward(self, query, key, value, mask=None, dropout=None):
          scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(query.size(-1))
          if mask is not None:
              scores = scores.masked_fill(mask == 0, -1e9)
  
          p_attn = F.softmax(scores, dim=-1)
          if dropout is not None:
              p_attn = dropout(p_attn)
  
          return torch.matmul(p_attn, value), p_attn
  
  
  class MultiHeadedAttention(nn.Module):
      def __init__(self, h, d_model, dropout=0.1):
          super().__init__()
          assert d_model % h == 0
  
          self.d_k = d_model // h
          self.h = h
  
          self.linear_layers = nn.ModuleList([nn.Linear(d_model, d_model) for _ in range(3)])
          self.output_linear = nn.Linear(d_model, d_model)
          self.attention = Attention()
  
          self.dropout = nn.Dropout(p=dropout)
  
      def forward(self, query, key, value, mask=None):
          batch_size = query.size(0)
  
          query, key, value = [l(x).view(batch_size, -1, self.h, self.d_k).transpose(1, 2)
                               for l, x in zip(self.linear_layers, (query, key, value))]
  
          x, attn = self.attention(
              query, key, value, mask=mask, dropout=self.dropout)
  
          x = x.transpose(1, 2).contiguous().view(
              batch_size, -1, self.h * self.d_k)
  
          return self.output_linear(x)
  
  
  class PositionwiseFeedForward(nn.Module):
      def __init__(self, d_model, d_ff):
          super(PositionwiseFeedForward, self).__init__()
          self.w_1 = nn.Linear(d_model, d_ff)
          self.w_2 = nn.Linear(d_ff, d_model)
          self.activation = GELU()
  
      def forward(self, x):
          return self.w_2(self.activation(self.w_1(x)))
  
  
  class SublayerConnection(nn.Module):
      def __init__(self, size, dropout):
          super(SublayerConnection, self).__init__()
          self.layer_norm = LayerNorm(size)
          self.dropout = nn.Dropout(dropout)
  
      def forward(self, x, sublayer):
          return self.layer_norm(x + self.dropout(sublayer(x)))
  
  
  class TransformerBlock(nn.Module):
      def __init__(self, hidden, attn_heads, feed_forward_hidden, dropout):
          super().__init__()
          self.attention = MultiHeadedAttention(
              h=attn_heads, d_model=hidden, dropout=dropout)
          self.feed_forward = PositionwiseFeedForward(
              d_model=hidden, d_ff=feed_forward_hidden)
          self.input_sublayer = SublayerConnection(size=hidden, dropout=dropout)
          self.output_sublayer = SublayerConnection(size=hidden, dropout=dropout)
          self.dropout = nn.Dropout(p=dropout)
  
      def forward(self, x, mask):
          x = self.input_sublayer(
              x, lambda _x: self.attention.forward(_x, _x, _x, mask=mask))
          x = self.output_sublayer(x, self.feed_forward)
          return self.dropout(x)
  
  
  class BERTNet(BaseModel):
      def supports_bert(self) -> bool:
          return True
  
  
  class BERT4NILM(BERTNet):
      def __init__(self, window_size, hidden=256, heads=2, n_layers=2, drop_out=0, output_dim=1):
          super().__init__()
          self.original_len = window_size
          self.latent_len = int(self.original_len / 2)
          self.dropout_rate = drop_out
  
          self.hidden = hidden
          self.heads = heads
          self.n_layers = n_layers
          self.output_dim = output_dim
  
          self.conv = nn.Conv1d(in_channels=1, out_channels=self.hidden,
                                kernel_size=5, stride=1, padding=2, padding_mode='replicate')
          self.pool = nn.LPPool1d(norm_type=2, kernel_size=2, stride=2)
  
          self.position = PositionalEmbedding(
              max_len=self.latent_len, d_model=self.hidden)
          self.layer_norm = LayerNorm(self.hidden)
          self.dropout = nn.Dropout(p=self.dropout_rate)
  
          self.transformer_blocks = nn.ModuleList([TransformerBlock(
              self.hidden, self.heads, self.hidden * 4, self.dropout_rate) for _ in range(self.n_layers)])
  
          self.deconv = nn.ConvTranspose1d(in_channels=self.hidden, out_channels=self.hidden,
                                           kernel_size=4, stride=2, padding=1)
          self.linear1 = nn.Linear(self.hidden, 128)
          self.flat = nn.Flatten()
          self.linear2 = nn.Linear(128*window_size, 128)
          self.out = nn.Linear(128, self.output_dim)
  
          self.truncated_normal_init()
  
      def truncated_normal_init(self, mean=0, std=0.02, lower=-0.04, upper=0.04):
          params = list(self.named_parameters())
          for n, p in params:
              if 'layer_norm' in n:
                  continue
              else:
                  with torch.no_grad():
                      l = (1. + math.erf(((lower - mean) / std) / math.sqrt(2.))) / 2.
                      u = (1. + math.erf(((upper - mean) / std) / math.sqrt(2.))) / 2.
                      p.uniform_(2 * l - 1, 2 * u - 1)
                      p.erfinv_()
                      p.mul_(std * math.sqrt(2.))
                      p.add_(mean)
  
      def forward(self, sequence):
          x_token = self.pool(self.conv(sequence.unsqueeze(1))).permute(0, 2, 1)
          embedding = x_token + self.position(sequence)
          x = self.dropout(self.layer_norm(embedding))
  
          mask = None
          for transformer in self.transformer_blocks:
              x = transformer.forward(x, mask)
  
          x = self.deconv(x.permute(0, 2, 1)).permute(0, 2, 1)
          x = torch.tanh(self.linear1(x))
          x = self.flat(x)
          x = self.linear2(x)
          out = self.out(x)
          return out
  