text: import numpy
  import torch.nn as nn
  
  from neural_network.abstract_neural_network import AbstractNeuralNetwork
  
  
  class ConvolutionalMelanomaNeuralNetwork(AbstractNeuralNetwork):
  
      def __init__(self, device):
          super(ConvolutionalMelanomaNeuralNetwork, self).__init__(device)
          self.num_channels = 3
          self.num_classes = 2
          self.in_channels = 64
          self.layer_list = [3, 4, 23, 3]
          res_block = Bottleneck
          # building nn
          self.conv1 = nn.Conv2d(self.num_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)
          self.batch_norm1 = nn.BatchNorm2d(64)
          self.relu = nn.ReLU()
          self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
          self.layer1 = self._make_layer(res_block, self.layer_list[0], planes=64)
          self.layer2 = self._make_layer(res_block, self.layer_list[1], planes=128, stride=2)
          self.layer3 = self._make_layer(res_block, self.layer_list[2], planes=256, stride=2)
          self.layer4 = self._make_layer(res_block, self.layer_list[3], planes=512, stride=2)
          self.average_pool = nn.AdaptiveAvgPool2d((1, 1))
          self.fc = nn.Linear(512 * res_block.expansion, self.num_classes)
          self.device = device
          self.to(device)
  
      def _make_layer(self, res_block, blocks, planes, stride=1):
          ii_downsample = None
          layers = []
          if stride != 1 or self.in_channels != planes * res_block.expansion:
              ii_downsample = nn.Sequential(
                  nn.Conv2d(self.in_channels, planes * res_block.expansion, kernel_size=1, stride=stride),
                  nn.BatchNorm2d(planes * res_block.expansion)
              )
  
          layers.append(res_block(self.in_channels, planes, i_downsample=ii_downsample, stride=stride))
          self.in_channels = planes * res_block.expansion
          for i in range(blocks - 1):
              layers.append(res_block(self.in_channels, planes))
  
          return nn.Sequential(*layers)
  
      def forward(self, x):
          x = x.to(self.device)
          x = self.relu(self.batch_norm1(self.conv1(x)))
          x = self.max_pool(x)
          x = self.layer1(x)
          x = self.layer2(x)
          x = self.layer3(x)
          x = self.layer4(x)
          x = self.average_pool(x)
          x = x.reshape(x.shape[0], -1)
          x = self.fc(x)
          return x
  
      def _get_gradient(self):
          self.to("cpu")
          to_concatenate = []
          # layer 1
          for i in range(3):
              for j in [0, 2, 4]:
                  to_concatenate.append(self.layer1[i].conv1.weight.grad[j].numpy().flatten())
  
          # layer 2
          for i in range(4):
              for j in [0, 2, 4]:
                  to_concatenate.append(self.layer2[i].conv1.weight.grad[j].numpy().flatten())
  
          # layer 3
          for i in range(23):
              for j in [0, 2, 4]:
                  to_concatenate.append(self.layer3[i].conv1.weight.grad[j].numpy().flatten())
  
          # layer 4
          for i in range(3):
              for j in [0, 2, 4]:
                  to_concatenate.append(self.layer4[i].conv1.weight.grad[j].numpy().flatten())
  
          gradient = numpy.concatenate(to_concatenate)
          self.to(self.device)
          return gradient
  
  
  class Bottleneck(nn.Module):
      expansion = 4
  
      def __init__(self, in_channels, out_channels, i_downsample=None, stride=1):
          super(Bottleneck, self).__init__()
          self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
          self.batch_norm1 = nn.BatchNorm2d(out_channels)
          self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1)
          self.batch_norm2 = nn.BatchNorm2d(out_channels)
          self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, stride=1, padding=0)
          self.batch_norm3 = nn.BatchNorm2d(out_channels * self.expansion)
          self.i_downsample = i_downsample
          self.stride = stride
          self.relu = nn.ReLU()
  
      def forward(self, x):
          identity = x.clone()
          x = self.relu(self.batch_norm1(self.conv1(x)))
          x = self.relu(self.batch_norm2(self.conv2(x)))
          x = self.conv3(x)
          x = self.batch_norm3(x)
          # downsample if needed
          if self.i_downsample is not None:
              identity = self.i_downsample(identity)
  
          # add identity
          x += identity
          x = self.relu(x)
          return x
  