Source code for pynas.core.architecture_builder


# architecture_builder.py
import random
import configparser
from .vocabulary import convolution_layer_vocabulary, layer_parameters, parameter_vocabulary
from .vocabulary import activation_functions_vocabulary, upsampling_layer_vocabulary, pooling_layer_vocabulary 



[docs] def generate_random_architecture_code(min_layers: int = 3, max_layers: int = 5): """ Generates a random architecture code string consisting of layers and pooling layers. The function creates a sequence of encoder layers and pooling layers, appending them to form a string representation of an architecture. Each layer and pooling layer is separated by an "E". The architecture ends with an additional "E". Args: min_layers (int): The minimum number of layers to include in the architecture. max_layers (int): The maximum number of layers to include in the architecture. Returns: str: A string representing the randomly generated architecture code. """ architecture_code = "" encoder_layers = [] for _ in range(random.randint(min_layers, max_layers)): layer_code = generate_layer_code() encoder_layers.append(layer_code) architecture_code += layer_code + "E" pooling_layer_code = generate_pooling_layer_code() #pooling_factors.append(pooling_factor) architecture_code += pooling_layer_code + "E" # Insert ender architecture_code += "E" return architecture_code
[docs] def generate_layer_code(): """ Generates a string representation of a neural network layer configuration. This function randomly selects a layer type from a predefined vocabulary and generates a corresponding layer code based on its parameters. The parameters are configured using values from a `config.ini` file. The generated code includes details such as activation type, kernel size, padding, stride, dropout rate, and other layer-specific attributes. Returns: str: A string representing the configuration of the generated layer. """ def get_random_value(param, section, is_float=False, is_odd=False, format_str=None): """Helper function to get a random value for a parameter.""" min_val = config.getfloat(section, f'min_{param}') if is_float else config.getint(section, f'min_{param}') max_val = config.getfloat(section, f'max_{param}') if is_float else config.getint(section, f'max_{param}') value = random.uniform(min_val, max_val) if is_float else random.randint(min_val, max_val) if is_odd and value % 2 == 0: value += 1 if value > max_val: value -= 2 return f"{value:.2f}" if is_float else f"{value:02d}" if format_str == "02d" else str(value) layer_type = random.choice(list(convolution_layer_vocabulary.keys())) parameters = layer_parameters[convolution_layer_vocabulary[layer_type]] layer_code = f"L{layer_type}" config = configparser.ConfigParser() config.read('config.ini') section = convolution_layer_vocabulary[layer_type] for param in parameters: code = parameter_vocabulary.get(param, "") if param == 'activation': activation_code = random.choice(['r', 'g']) # Use only ReLU and GELU for intermediate layers layer_code += f"a{activation_code}" elif param == 'dropout_rate': layer_code += f"{code}{get_random_value(param, section, is_float=True)}" elif param == 'kernel_size': layer_code += f"{code}{get_random_value(param, section, is_odd=True)}" elif param in ['padding', 'stride', 'out_channels_coefficient']: format_str = "02d" if param == 'out_channels_coefficient' else None layer_code += f"{code}{get_random_value(param, section, format_str=format_str)}" elif param != 'num_blocks': # Skip 'num_blocks' as it's not used layer_code += f"{code}{get_random_value(param, section)}" layer_code += "n1" return layer_code
[docs] def generate_pooling_layer_code(): """ Generates a string representing a pooling layer configuration. The function randomly selects a pooling type from the `pooling_layer_vocabulary` and combines it with a predefined pooling factor to create a pooling layer code. Returns: str: A string representing the pooling layer configuration in the format "P<pooling_type><pooling_factor>". """ pooling_type = random.choice(list(pooling_layer_vocabulary.keys())) pooling_factor = 2 # Example factor, you can randomize or configure this as needed pooling_code = f"P{pooling_type}{pooling_factor}" return pooling_code #, pooling_factor
[docs] def generate_upsampling_layer_code(scale_factor=2): """ Generates a string representing the configuration of an upsampling layer. This function reads the available upsampling modes from a configuration file ('config.ini') under the 'Upsample' section and randomly selects one of the modes. It then combines the scale factor and the selected mode into a formatted string. Args: scale_factor (int, optional): The scaling factor for the upsampling layer. Defaults to 2. Returns: str: A formatted string representing the upsampling layer configuration in the format "Uf{scale_factor}m{mode}". """ config = configparser.ConfigParser() config.read('config.ini') modes = config.get('Upsample', 'modes').split(',') mode = random.choice(modes) upsampling_code = f"Uf{scale_factor}m{mode}" return upsampling_code
[docs] def generate_skip_connection_code(layer_index): """ Generates a string representing a skip connection identifier for a given layer index. Args: layer_index (int): The index of the layer for which the skip connection identifier is generated. Returns: str: A string in the format "S{layer_index}" representing the skip connection identifier. """ return f"S{layer_index}"
[docs] def parse_architecture_code(architecture_code): """ Parses a given architecture code string into a list of layer configurations. The function interprets the architecture code by splitting it into segments, identifying the type of each segment (e.g., convolution, pooling, upsampling, etc.), and extracting the associated parameters based on predefined vocabularies and rules. Args: architecture_code (str): A string representing the architecture code. Each segment of the code corresponds to a layer or operation, with specific characters denoting the type and parameters of the layer. Returns: list[dict]: A list of dictionaries, where each dictionary represents a parsed layer or operation. Each dictionary contains: - 'layer_type' (str): The type of the layer (e.g., "Convolution", "Pooling"). - Additional keys for parameters specific to the layer type, such as: - 'scale_factor' (int): The scale factor for upsampling layers. - 'mode' (str): The mode for certain operations. - 'dropout_rate' (float): The dropout rate for layers with dropout. - 'activation' (str): The activation function for layers with activation. - 'out_channels_coefficient' (int): Coefficient for output channels. - Other parameters as defined in the layer's parameter vocabulary. Notes: - The function relies on several predefined vocabularies and mappings: - `convolution_layer_vocabulary`: Maps codes to convolution layer types. - `pooling_layer_vocabulary`: Maps codes to pooling layer types. - `head_vocabulary`: Maps codes to head layer types. - `upsampling_layer_vocabulary`: Maps codes to upsampling layer types. - `layer_parameters`: Defines expected parameters for each layer type. - `parameter_vocabulary`: Maps parameter codes to parameter names. - `activation_functions_vocabulary`: Maps activation codes to function names. - Segments with unknown or unsupported codes are assigned "Unknown" as the layer type. - Skip connections are explicitly identified with the type "SkipConnection". Example: architecture_code = "L1f2mRPE2H3" parsed_layers = parse_architecture_code(architecture_code) # parsed_layers will be a list of dictionaries representing the parsed layers. """ segments = architecture_code.split('E')[:-1] parsed_layers = [] for segment in segments: if not segment: # Skip empty segments continue segment_type_code = segment[0] layer_type_code = segment[1] # Determine the segment's layer type and corresponding parameters if segment_type_code == 'L': layer_type = convolution_layer_vocabulary.get(layer_type_code, "Unknown") elif segment_type_code == 'P': layer_type = pooling_layer_vocabulary.get(layer_type_code, "Unknown") elif segment_type_code == 'U': layer_type = upsampling_layer_vocabulary.get(segment_type_code, "Unknown") elif segment_type_code == 'S': # For skip connections layer_type = "SkipConnection" # Initialize the dictionary for this segment with its type segment_info = {'layer_type': layer_type} # Get parameter definitions for this layer type param_definitions = layer_parameters.get(layer_type, []) # Process remaining characters based on the expected parameters for this type params = segment[2:] # All after layer type code i = 0 while i < len(params): param_code = params[i] i += 1 if param_code == 'f' and 'scale_factor' in param_definitions: scale_factor = '' while i < len(params) and params[i].isdigit(): scale_factor += params[i] i += 1 segment_info['scale_factor'] = int(scale_factor) elif param_code == 'm' and 'mode' in param_definitions: segment_info['mode'] = params[i] i += 1 else: for param_name, code in parameter_vocabulary.items(): if code == param_code and param_name in param_definitions: if param_name == 'dropout_rate': value = params[i:i + 4] i += 4 segment_info[param_name] = float(value) elif param_name == 'activation': segment_info[param_name] = activation_functions_vocabulary.get(params[i], "Unknown") i += 1 elif param_name == 'out_channels_coefficient': value = params[i:i + 2] i += 2 segment_info[param_name] = int(value) else: segment_info[param_name] = params[i] i += 1 break parsed_layers.append(segment_info) return parsed_layers
[docs] def generate_code_from_parsed_architecture(parsed_layers): """ Generates a compact string representation of a neural network architecture based on a list of parsed layer configurations. The function converts each layer's type and parameters into a coded segment using predefined vocabularies and appends them together to form the final architecture code. Each layer segment ends with "E", and the entire architecture code ends with "EE". Args: parsed_layers (list of dict): A list of dictionaries where each dictionary represents a layer configuration. Each dictionary must contain a 'layer_type' key and may include additional parameters specific to the layer type. Returns: str: A string representing the encoded architecture. Notes: - The function uses reverse mappings of predefined vocabularies to encode layer types and parameters. - Special handling is applied for certain parameters like 'activation', 'dropout_rate', and 'out_channels_coefficient'. - Layer types such as "Dropout", "Upsample", and "SkipConnection" have specific encoding rules. """ architecture_code = "" # Utilize the provided configuration directly reverse_convolution_layer_vocabulary = {v: k for k, v in convolution_layer_vocabulary.items()} reverse_pooling_layer_vocabulary = {v: k for k, v in pooling_layer_vocabulary.items()} reverse_activation_functions_vocabulary = {v: k for k, v in activation_functions_vocabulary.items()} reverse_upsampling_functions_vocabulary = {v: k for k, v in upsampling_layer_vocabulary.items()} for layer in parsed_layers: layer_type = layer['layer_type'] segment_code = "" # Prepend the type code with "L", "P", or "H" based on the layer type if layer_type in reverse_convolution_layer_vocabulary: segment_code += "L" + reverse_convolution_layer_vocabulary[layer_type] elif layer_type in reverse_pooling_layer_vocabulary: segment_code += "P" + reverse_pooling_layer_vocabulary[layer_type] elif layer_type == 'Dropout': segment_code += "LD" elif layer_type == "Upsample": segment_code += "U" + reverse_upsampling_functions_vocabulary[layer_type] elif layer_type == "SkipConnection": segment_code += "S" # Append each parameter and its value for param_name, param_value in layer.items(): if param_name == 'layer_type': # Skip 'layer_type' as it's already processed continue if param_name in parameter_vocabulary: param_code = parameter_vocabulary[param_name] # Special handling for activation parameters if param_name == 'activation': param_value = reverse_activation_functions_vocabulary.get(param_value, param_value) segment_code += param_code + str(param_value) elif layer_type == 'Dropout' and param_name == 'dropout_rate': segment_code += f"d{param_value:.2f}" elif param_name == 'out_channels_coefficient': segment_code += f"o{param_value:02d}" # Finalize the segment and add it to the architecture code architecture_code += segment_code + "E" # Ensure the architecture code properly ends with "EE" return architecture_code + "E"