docs: public static KerasLayer getKerasLayerFromConfig(Map<String, Object> layerConfig,
                                                       boolean enforceTrainingConfig,
                                                       KerasLayerConfiguration conf,
                                                       Map<String, Class<? extends KerasLayer>> customLayers,
                                                       Map<String, SameDiffLambdaLayer> lambdaLayers,
                                                       Map<String, ? extends KerasLayer> previousLayers
      )
              throws InvalidKerasConfigurationException, UnsupportedKerasConfigurationException {
          String layerClassName = getClassNameFromConfig(layerConfig, conf);
          if (layerClassName.equals(conf.getLAYER_CLASS_NAME_TIME_DISTRIBUTED())) {
              layerConfig = getTimeDistributedLayerConfig(layerConfig, conf);
              layerClassName = getClassNameFromConfig(layerConfig, conf);
          }
  
          KerasLayer layer = null;
          if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ACTIVATION())) {
              layer = new KerasActivation(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_LEAKY_RELU())) {
              layer = new KerasLeakyReLU(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MASKING())) {
              layer = new KerasMasking(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_THRESHOLDED_RELU())) {
              layer = new KerasThresholdedReLU(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_PRELU())) {
              layer = new KerasPReLU(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_DROPOUT())) {
              layer = new KerasDropout(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_SPATIAL_DROPOUT_1D())
                  || layerClassName.equals(conf.getLAYER_CLASS_NAME_SPATIAL_DROPOUT_2D())
                  || layerClassName.equals(conf.getLAYER_CLASS_NAME_SPATIAL_DROPOUT_3D())) {
              layer = new KerasSpatialDropout(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ALPHA_DROPOUT())) {
              layer = new KerasAlphaDropout(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_GAUSSIAN_DROPOUT())) {
              layer = new KerasGaussianDropout(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_GAUSSIAN_NOISE())) {
              layer = new KerasGaussianNoise(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_DENSE()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_TIME_DISTRIBUTED_DENSE())) {
              layer = new KerasDense(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_BIDIRECTIONAL())) {
              layer = new KerasBidirectional(layerConfig, enforceTrainingConfig, previousLayers);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_LSTM())) {
              layer = new KerasLSTM(layerConfig, enforceTrainingConfig, previousLayers);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_SIMPLE_RNN())) {
              layer = new KerasSimpleRnn(layerConfig, enforceTrainingConfig, previousLayers);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CONVOLUTION_3D())) {
              layer = new KerasConvolution3D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CONVOLUTION_2D())) {
              layer = new KerasConvolution2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_DECONVOLUTION_2D())) {
              layer = new KerasDeconvolution2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CONVOLUTION_1D())) {
              layer = new KerasConvolution1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ATROUS_CONVOLUTION_2D())) {
              layer = new KerasAtrousConvolution2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ATROUS_CONVOLUTION_1D())) {
              layer = new KerasAtrousConvolution1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_DEPTHWISE_CONVOLUTION_2D())) {
              layer = new KerasDepthwiseConvolution2D(layerConfig, previousLayers, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_SEPARABLE_CONVOLUTION_2D())) {
              layer = new KerasSeparableConvolution2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MAX_POOLING_3D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_AVERAGE_POOLING_3D())) {
              layer = new KerasPooling3D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MAX_POOLING_2D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_AVERAGE_POOLING_2D())) {
              layer = new KerasPooling2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MAX_POOLING_1D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_AVERAGE_POOLING_1D())) {
              layer = new KerasPooling1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_AVERAGE_POOLING_1D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_AVERAGE_POOLING_2D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_AVERAGE_POOLING_3D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_MAX_POOLING_1D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_MAX_POOLING_2D()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_GLOBAL_MAX_POOLING_3D())) {
              layer = new KerasGlobalPooling(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_BATCHNORMALIZATION())) {
              layer = new KerasBatchNormalization(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_EMBEDDING())) {
              layer = new KerasEmbedding(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_INPUT())) {
              layer = new KerasInput(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_REPEAT())) {
              layer = new KerasRepeatVector(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_PERMUTE())) {
              layer = new KerasPermute(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MERGE())) {
              layer = new KerasMerge(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ADD()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_ADD())) {
              layer = new KerasMerge(layerConfig, ElementWiseVertex.Op.Add, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_SUBTRACT()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_FUNCTIONAL_SUBTRACT())) {
              layer = new KerasMerge(layerConfig, ElementWiseVertex.Op.Subtract, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_AVERAGE()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_FUNCTIONAL_AVERAGE())) {
              layer = new KerasMerge(layerConfig, ElementWiseVertex.Op.Average, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_MULTIPLY()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_FUNCTIONAL_MULTIPLY())) {
              layer = new KerasMerge(layerConfig, ElementWiseVertex.Op.Product, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CONCATENATE()) ||
                  layerClassName.equals(conf.getLAYER_CLASS_NAME_FUNCTIONAL_CONCATENATE())) {
              layer = new KerasMerge(layerConfig, null, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_FLATTEN())) {
              layer = new KerasFlatten(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_RESHAPE())) {
              layer = new KerasReshape(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ZERO_PADDING_1D())) {
              layer = new KerasZeroPadding1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ZERO_PADDING_2D())) {
              layer = new KerasZeroPadding2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_ZERO_PADDING_3D())) {
              layer = new KerasZeroPadding3D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_UPSAMPLING_1D())) {
              layer = new KerasUpsampling1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_UPSAMPLING_2D())) {
              layer = new KerasUpsampling2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CROPPING_3D())) {
              layer = new KerasCropping3D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CROPPING_2D())) {
              layer = new KerasCropping2D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_CROPPING_1D())) {
              layer = new KerasCropping1D(layerConfig, enforceTrainingConfig);
          } else if (layerClassName.equals(conf.getLAYER_CLASS_NAME_LAMBDA())) {
              String lambdaLayerName = KerasLayerUtils.getLayerNameFromConfig(layerConfig, conf);
              if (!lambdaLayers.containsKey(lambdaLayerName) && !customLayers.containsKey(layerClassName)){
                  throw new UnsupportedKerasConfigurationException("No SameDiff Lambda layer found for Lambda " +
                          "layer " + lambdaLayerName + ". You can register a SameDiff Lambda layer using KerasLayer." +
                          "registerLambdaLayer(lambdaLayerName, sameDiffLambdaLayer);");
              }
              SameDiffLambdaLayer lambdaLayer = lambdaLayers.get(lambdaLayerName);
              if (lambdaLayer != null){
                  layer = new KerasLambda(layerConfig, enforceTrainingConfig, lambdaLayer);
              }
          }
          if (layer == null){
              Class<? extends KerasLayer> customConfig = customLayers.get(layerClassName);
              if (customConfig == null)
                  throw new UnsupportedKerasConfigurationException("Unsupported keras layer type " + layerClassName);
              try {
                  Constructor constructor = customConfig.getConstructor(Map.class);
                  layer = (KerasLayer) constructor.newInstance(layerConfig);
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
          }
          return layer;
      }