text: """
  modified first layer of AlexNet from using ReLU to square non-linearity,
  also didn't include bias in first layer
  Adapted from alexnet.py, code written by Frederik Kratzert at
  https://kratzert.github.io/2017/02/24/finetuning-alexnet-with-tensorflow.html
  """
  
  import tensorflow as tf
  import numpy as np
  
  
  class SqnlAlexNet(object):
      """Implementation of the AlexNet."""
  
      def __init__(self, x, keep_prob, num_classes, skip_layer,
                   weights_path='DEFAULT'):
          """Create the graph of the AlexNet model.
  
          Args:
              x: Placeholder for the input tensor.
              keep_prob: Dropout probability.
              num_classes: Number of classes in the dataset.
              skip_layer: List of names of the layer, that get trained from
                  scratch
              weights_path: Complete path to the pretrained weight file, if it
                  isn't in the same folder as this code
          """
          # Parse input arguments into class variables
          self.X = x
          self.NUM_CLASSES = num_classes
          self.KEEP_PROB = keep_prob
          self.SKIP_LAYER = skip_layer
  
          if weights_path == 'DEFAULT':
              self.WEIGHTS_PATH = 'kernel_alexnet.npy'
          else:
              self.WEIGHTS_PATH = weights_path
  
          # Call the create function to build the computational graph of AlexNet
          self.create()
  
      def create(self):
          """Create the network graph."""
          # 1st Layer: Conv (w ReLu) -> Lrn -> Pool
          conv1 = sqnlconv(self.X, 11, 11, 96, 1, 1, padding='SAME', name='conv1')
          norm1 = lrn(conv1, 2, 1e-04, 0.75, name='norm1')
          pool1 = max_pool(norm1, 3, 3, 2, 2, padding='VALID', name='pool1')
  
          # 2nd Layer: Conv (w ReLu)  -> Lrn -> Pool with 2 groups
          conv2 = conv(pool1, 5, 5, 256, 1, 1, groups=2, name='conv2')
          norm2 = lrn(conv2, 2, 1e-04, 0.75, name='norm2')
          pool2 = max_pool(norm2, 3, 3, 2, 2, padding='VALID', name='pool2')
  
          # 3rd Layer: Conv (w ReLu)
          conv3 = conv(pool2, 3, 3, 384, 1, 1, name='conv3')
  
          # 4th Layer: Conv (w ReLu) splitted into two groups
          conv4 = conv(conv3, 3, 3, 384, 1, 1, groups=2, name='conv4')
  
          # 5th Layer: Conv (w ReLu) -> Pool splitted into two groups
          conv5 = conv(conv4, 3, 3, 256, 1, 1, groups=2, name='conv5')
          pool5 = max_pool(conv5, 3, 3, 2, 2, padding='VALID', name='pool5')
  
          # 6th Layer: Flatten -> FC (w ReLu) -> Dropout
          flattened = tf.reshape(pool5, [-1, 27 * 27 * 256])
          fc6 = fc(flattened, 27 * 27 * 256, 4096, name='fc6')
          dropout6 = dropout(fc6, self.KEEP_PROB)
  
          # 7th Layer: FC (w ReLu) -> Dropout
          fc7 = fc(dropout6, 4096, 4096, name='fc7')
          dropout7 = dropout(fc7, self.KEEP_PROB)
  
          # 8th Layer: FC and return unscaled activations
          self.fc8 = fc(dropout7, 4096, self.NUM_CLASSES, relu=False, name='fc8')
  
      def load_initial_weights(self, session):
          """
          load pre-trained kernel from first layer of AlexNet
          """
          # Load the weights into memory
          kernel_pretrained = np.load(self.WEIGHTS_PATH, encoding='bytes')
          with tf.variable_scope('conv1', reuse=True):
              # Assign kernel to its corresponding tf variable
              var = tf.get_variable('weights', trainable=False)
              session.run(var.assign(kernel_pretrained))
  
  
  def sqnlconv(x, filter_height, filter_width, num_filters, stride_y, stride_x, name,
               padding='SAME'):
      """
      using square non-linearity instead of ReLU, didn't include bias
      """
      with tf.variable_scope(name) as scope:
          weights = tf.get_variable('weights', trainable=True,
                                    shape=[filter_height,
                                           filter_width,
                                           3,
                                           num_filters])
  
      # Create lambda function for the convolution
      convolve = lambda i, k: tf.nn.conv2d(i, k,
                                           strides=[1, stride_y, stride_x, 1],
                                           padding=padding)
      output_groups0 = convolve(x[:, :, :, 0:1], weights[:, :, 0:1, :]) ** 2
      output_groups1 = convolve(x[:, :, :, 1:2], weights[:, :, 1:2, :]) ** 2
      output_groups2 = convolve(x[:, :, :, 2:3], weights[:, :, 2:3, :]) ** 2
  
      output = tf.add(tf.add(output_groups0, output_groups1), output_groups2)
      return output
  
  
  def conv(x, filter_height, filter_width, num_filters, stride_y, stride_x, name,
           padding='SAME', groups=1):
      """Create a convolution layer.
  
      Adapted from: https://github.com/ethereon/caffe-tensorflow
      """
      # Get number of input channels
      input_channels = int(x.get_shape()[-1])
  
      # Create lambda function for the convolution
      convolve = lambda i, k: tf.nn.conv2d(i, k,
                                           strides=[1, stride_y, stride_x, 1],
                                           padding=padding)
  
      with tf.variable_scope(name) as scope:
          # Create tf variables for the weights and biases of the conv layer
          weights = tf.get_variable('weights', shape=[filter_height,
                                                      filter_width,
                                                      input_channels / groups,
                                                      num_filters])
          biases = tf.get_variable('biases', shape=[num_filters])
  
      if groups == 1:
          conv = convolve(x, weights)
  
      # In the cases of multiple groups, split inputs & weights and
      else:
          # Split input and weights and convolve them separately
          input_groups = tf.split(axis=3, num_or_size_splits=groups, value=x)
          weight_groups = tf.split(axis=3, num_or_size_splits=groups,
                                   value=weights)
          output_groups = [convolve(i, k) for i, k in zip(input_groups, weight_groups)]
  
          # Concat the convolved output together again
          conv = tf.concat(axis=3, values=output_groups)
  
      # Add biases
      bias = tf.reshape(tf.nn.bias_add(conv, biases), tf.shape(conv))
  
      # Apply relu function
      relu = tf.nn.relu(bias, name=scope.name)
  
      return relu
  
  
  def fc(x, num_in, num_out, name, relu=True):
      """Create a fully connected layer."""
      with tf.variable_scope(name) as scope:
  
          # Create tf variables for the weights and biases
          weights = tf.get_variable('weights', shape=[num_in, num_out],
                                    trainable=True)
          biases = tf.get_variable('biases', [num_out], trainable=True)
  
          # Matrix multiply weights and inputs and add bias
          act = tf.nn.xw_plus_b(x, weights, biases, name=scope.name)
  
      if relu:
          # Apply ReLu non linearity
          relu = tf.nn.relu(act)
          return relu
      else:
          return act
  
  
  def max_pool(x, filter_height, filter_width, stride_y, stride_x, name,
               padding='SAME'):
      """Create a max pooling layer."""
      return tf.nn.max_pool(x, ksize=[1, filter_height, filter_width, 1],
                            strides=[1, stride_y, stride_x, 1],
                            padding=padding, name=name)
  
  
  def lrn(x, radius, alpha, beta, name, bias=1.0):
      """Create a local response normalization layer."""
      return tf.nn.local_response_normalization(x, depth_radius=radius,
                                                alpha=alpha, beta=beta,
                                                bias=bias, name=name)
  
  
  def dropout(x, keep_prob):
      """Create a dropout layer."""
      return tf.nn.dropout(x, keep_prob)
  