Search code examples
rkeras

How do I copy a DNN in R Keras without reusing layer names


TLDR:
The question is pretty much in the title. I know how to change layer names in python, as described e.g. here:
https://stackoverflow.com/questions/49550182/keras-rename-model-and-layers.\ However, doing this in r does not work. Something like model$input$name <- "Input_1" leads to the error Error: AttributeError: can't set attribute and in R, the layers do not have _name which I can edit.
Trying to change the attribute using attr(model$input, "name") <- "Input_1 also does not work, leading to the error message:
Error: AttributeError: Can't set the attribute "input", likely because it conflicts with an existing read-only @property of the object. Please choose a different name.
This seems to be a rather obvious and common issue, however I cannot find any documentation on how to do this in R. Any advice is appreciated.

For more context on how I got to this problem:
I am working on a project that requires me to re-use certain sub-models in a larger model, as described in the following minimal working example:

library(tensorflow)
library(keras)

inputs <- layer_input(shape = 1, name = "Input")

output <- inputs %>%
  layer_dense(units = 1, activation = "linear")
linear_model <- keras_model(inputs, output)

output <- inputs %>%
  layer_dense(units = 32, activation = "relu") %>%
  layer_dense(units = 1, activation = "linear")
deep_model <- keras_model(inputs, output)

linear_clone <- clone_model(linear_model)
output <- layer_concatenate(list(linear_clone$output,
                                 deep_model$output)) %>%
  layer_dense(1, use_bias = FALSE)
deep_model_adapted <- keras_model(list(linear_clone$input, inputs), output)

The last line leads to the error Error: ValueError: The name "Input" is used 2 times in the model. All layer names should be unique.. I understand this error message. I do not know how to circumvent this. I am aware that I can simply concatenate linear_model to deep_model_adapted. However, in the final application, linear_model and linear_clone will have different weights, which makes this approach not applicable. If there is an alternative to clone_model that lets me specify new layer names or generates new ones (that have not been used yet, as is done by default when creating a new layer), this would also answer my question.


Solution

  • Although this does not answer the original question, I managed to solve the underlying issue myself by iteratively copying each layer of the DNN and creating new names using the built-in tool that only picks names that aren't used yet.
    My solution only works for the layer types specified in the respective functions, but they can be adapted for other layers by simply including them in the clone_layer_newName function. Anyhow, I believe anyone in their right mind would rather adapt the r-keras code itself instead of doing this, but maybe someone will find this useful. This even works for rather complex architectures involving concatination of multiple layers.

    clone_layer_newName <- function(original_layer)
    {
      layerName <- original_layer$name
      if(str_detect(layerName, "Input"))
      {
        return(layer_input(shape = original_layer$input_shape[[1]][[2]]))
      }
      else
      {
        inboundNodes <- original_layer$inbound_nodes[[1]]$inbound_layers
        if(str_detect(layerName, "dense"))
        {
          return(clone_layer_newName(inboundNodes) %>%
                   layer_dense(units = original_layer$get_config()$units,
                               activation = original_layer$get_config()$activation,
                               use_bias = original_layer$get_config()$use_bias,
                               input_shape = original_layer$input_shape[[2]]))
        }else if(str_detect(layerName, "dropout"))
        {
          return(clone_layer_newName(inboundNodes) %>%
                   layer_dropout(rate = original_layer$get_config()$rate,
                                 input_shape = original_layer$get_config()$batch_input_shape[[2]]))
        }else if(str_detect(layerName, "concatenate"))
        {
          return(layer_concatenate(lapply(inboundNodes,
                                          clone_layer_newName) %>% unlist()))
        }
      }
      # layerName <- original_layer$name
      # new_layer <- layer_dense(clone_layer_newName(inboundLayers))
    }
    findInput <- function(currentInput)
    {
      if(str_detect(currentInput$node$layer$name, "input"))
      {
        return(currentInput)
      }else
      {
        if(is.list(currentInput$node$layer$input))
        {
          return(lapply(currentInput$node$layer$input, findInput))
        }else
        {
          return(findInput(currentInput$node$layer$input))
        }
      }
      lapply(currentInput$layer$node, findInput)
    }
    clone_model_newNames <- function(original_model)
    {
      newOutput <-
        clone_layer_newName(original_model$layers[[length(original_model$layers)]])
      newInput <- findInput(newOutput$node$layer$input)
      newModel <- keras_model(newInput, newOutput)
      newModel %>% set_weights(original_model %>% get_weights())
      return(newModel)
    }