WEB3DEV

Cover image for Rede Neural Profunda do Zero em Rust 🦀 - Parte 5 - Treinamento e Inferência
Paulo Gio
Paulo Gio

Posted on

Rede Neural Profunda do Zero em Rust 🦀 - Parte 5 - Treinamento e Inferência

Série sobre Rede Neural Profunda do Zero em Rust

https://miro.medium.com/v2/resize:fit:1100/format:webp/0*BeB-LSDjLQVPnvST.png

Ótimo!! Você chegou à parte final da série. Nesta parte, vamos treinar nosso modelo e testá-lo construindo uma função de previsão. Felizmente, não há matemática envolvida nesta parte 😃. Então, vamos ao código.

Treinamento

Primeiro, vamos construir nosso loop de treinamento, que recebe os dados de treinamento: x_train_data, os rótulos de treinamento: y_train_data, o dicionário de parâmetros: parameters, o número de iterações do loop de treinamento: iterations e a taxa de aprendizado: learning_rate. A função retornará os novos parâmetros após uma iteração de treinamento. Em impl DeepNeuralNetwork, adicione a seguinte função.

pub fn train_model(
        &self,
        x_train_data: &Array2<f32>,
        y_train_data: &Array2<f32>,
        mut parameters: HashMap<String, Array2<f32>>,
        iterations: usize,
        learning_rate: f32,
    ) -> HashMap<String, Array2<f32>> {
        let mut costs: Vec<f32> = vec![];

        for i in 0..iterations {
            let (al, caches) = self.forward(&x_train_data, &parameters);
            let cost = self.cost(&al, &y_train_data);
            let grads = self.backward(&al, &y_train_data, caches);
            parameters = self.update_parameters(&parameters, grads.clone(), learning_rate);

            if i % 100 == 0 {
                costs.append(&mut vec![cost]);
                println!("Época : {}/{}    Custo: {:?}", i, iterations, cost);
            }
        }
        parameters
    }
Enter fullscreen mode Exit fullscreen mode

Essa função realiza os seguintes passos:

  1. Inicializa um vetor vazio chamado costs para armazenar os valores de custo para cada iteração.
  2. Itera sobre o número especificado de iterações (iterations).
  3. Em cada iteração:
    • Realiza a propagação para frente usando o método forward para obter a ativação final al e os caches.
    • Calcula o custo usando o método cost.
    • Realiza a propagação para trás usando o método backward para calcular os gradientes.
    • Atualiza os parâmetros usando o método update_parameters com os gradientes calculados e a taxa de aprendizado.
    • Se a iteração atual é um múltiplo de 100, anexa o valor do custo ao vetor costs e imprime o número atual da época e o valor do custo.
  4. Após o loop, retorna os parâmetros atualizados.

Previsão

Após o loop de treinamento ser concluído, podemos fazer uma função de previsão que recebe os dados de teste: x_test_data e os parâmetros otimizados: parameters.

pub fn predict(
        &self,
        x_test_data: &Array2<f32>,
        parameters: HashMap<String, Array2<f32>>,
    ) -> Array2<f32> {
        let (al, _) = self.forward(&x_test_data, &parameters);

        let y_hat = al.map(|x| (x > &0.5) as i32 as f32);
        y_hat
    }

    pub fn score(&self, y_hat: &Array2<f32>, y_test_data: &Array2<f32>) -> f32 {
        let error =
            (y_hat - y_test_data).map(|x| x.abs()).sum() / y_test_data.shape()[1] as f32 * 100.0;
        100.0 - error
    }
Enter fullscreen mode Exit fullscreen mode

A função predict realiza os seguintes passos:

  1. Chama o método forward com os dados de teste e os parâmetros otimizados para obter a ativação final al.
  2. Aplica um limite de 0.5 aos elementos de al usando o método map, convertendo valores maiores que 0.5 para 1.0 e valores menores ou iguais a 0.5 para 0.0.
  3. Retorna os rótulos previstos como y_hat.

A função score calcula a pontuação de acurácia dos rótulos previstos em comparação com os rótulos de teste reais. Ela realiza os seguintes passos:

  1. Calcula a diferença absoluta elemento a elemento entre os rótulos previstos y_hat e os rótulos de teste reais y_test_data.
  2. Soma as diferenças absolutas usando o método sum.
  3. Divide a soma pelo número de exemplos (y_test_data.shape()[1]) e multiplica por 100.0 para obter a porcentagem de erro.
  4. Subtrai a porcentagem de erro de 100.0 para obter a pontuação de acurácia e retorna-a.

Gravação de Parâmetros em um Arquivo JSON

//lib.rs

use std::fs::OpenOptions;

pub fn write_parameters_to_json_file(
   parameters: &HashMap<String, Array2<f32>>,
   file_path: PathBuf,
) {
   let file = OpenOptions::new()
       .create(true)
       .write(true)
       .truncate(true)
       .open(file_path)
       .unwrap();

   _ = serde_json::to_writer(file, parameters);
}
Enter fullscreen mode Exit fullscreen mode

Podemos definir uma função auxiliar para gravar os parâmetros do modelo treinado em um arquivo JSON. Isso nos permite salvar os parâmetros para uso posterior sem a necessidade de retrabalho.

Primeiro, no Cargo.toml, adicione esta linha.

serde_json = "1.0.91"
Enter fullscreen mode Exit fullscreen mode

Depois, no lib.rs, importe OpenOptions e crie a função.

//lib.rs

use std::fs::OpenOptions;

pub fn write_parameters_to_json_file(
    parameters: &HashMap<String, Array2<f32>>,
    file_path: PathBuf,
) {
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(file_path)
        .unwrap();

    _ = serde_json::to_writer(file, parameters);
}
Enter fullscreen mode Exit fullscreen mode

Esta função recebe os parâmetros e um caminho de arquivo onde o arquivo JSON será criado. Ela abre o arquivo no modo de gravação, truncando qualquer conteúdo existente. Em seguida, usa o crate serde_json para serializar os parâmetros e gravá-los no arquivo.

Exemplo de Aplicação

Para demonstrar o uso da biblioteca, podemos criar uma aplicação que carrega os dados, treina o modelo e o testa. Podemos criar um arquivo chamado rust_dnn.rs dentro da pasta src/bin. Aqui está um exemplo de implementação:

use dnn_rust_blog::*;
use std::env;
fn main() {
    env::set_var("RUST_BACKTRACE", "1");
    let neural_network_layers: Vec<usize> = vec![12288, 20, 7, 5, 1];
    let learning_rate = 0.0075;
    let iterations = 1000;

    let (training_data, training_labels) =
        dataframe_from_csv("datasets/training_set.csv".into()).unwrap();
    let (test_data, test_labels) = dataframe_from_csv("datasets/test_set.csv".into()).unwrap();

    let training_data_array = array_from_dataframe(&training_data)/255.0;
    let training_labels_array = array_from_dataframe(&training_labels);
    let test_data_array = array_from_dataframe(&test_data)/255.0;
    let test_labels_array = array_from_dataframe(&test_labels);

    let model = DeepNeuralNetwork {
        layers: neural_network_layers,
        learning_rate,
    };

    let parameters = model.initialize_parameters();

    let parameters = model.train_model(
        &training_data_array,
        &training_labels_array,
        parameters,
        iterations,
        model.learning_rate,
    );
    write_parameters_to_json_file(&parameters, "model.json".into());

    let training_predictions = model.predict(&training_data_array, &parameters);
    println!(
        "Acurácia do Conjunto de Treinamento: {}%",
        model.score(&training_predictions, &training_labels_array)
    );

    let test_predictions = model.predict(&test_data_array, &parameters);
    println!(
        "Acurácia do Conjunto de Teste: {}%",
        model.score(&test_predictions, &test_labels_array)
    );
}
Enter fullscreen mode Exit fullscreen mode
  1. Nós definimos as camadas da rede neural, a taxa de aprendizado e o número de iterações.
  2. Carregamos os dados de treinamento e teste a partir de arquivos CSV usando a função dataframe_from_csv.
  3. Convertemos os dataframes em arrays e normalizamos os valores dos pixels para a faixa [0, 1].
  4. Criamos uma instância da estrutura DeepNeuralNetwork com as camadas especificadas e a taxa de aprendizado.
  5. Inicializamos os parâmetros usando o método initialize_parameters.
  6. Treinamos o modelo usando o método train_model, passando os dados de treinamento, os rótulos de treinamento, os parâmetros iniciais, as iterações e a taxa de aprendizado.
  7. Gravamos os parâmetros treinados em um arquivo JSON usando a função write_parameters_to_json_file.
  8. Prevemos os rótulos para os dados de treinamento e teste usando o método predict.
  9. Calculamos e imprimimos as pontuações de acurácia para as previsões de treinamento e teste usando o método score.

Agora, no terminal, execute o seguinte comando para instalar o binário e executá-lo:

cargo install --path && rust_dnn
Enter fullscreen mode Exit fullscreen mode

Isso instalará as dependências e iniciará o treinamento.

https://miro.medium.com/v2/resize:fit:626/format:webp/0*c0MeotLscpANVxmk.png

Conclusão

Embora o conjunto de dados usado em nosso exemplo seja pequeno e a arquitetura da rede não seja complexa, o objetivo desta série é fornecer um fluxo de trabalho geral e introduzir o funcionamento interno de uma rede neural. Com esta base, você pode agora expandir e aprimorar a biblioteca para lidar com conjuntos de dados maiores, arquiteturas de rede mais complexas e recursos adicionais.

Ao construir esta biblioteca de rede neural em Rust, nos beneficiamos das características de segurança, desempenho e simultaneidade da linguagem. O forte sistema de tipos e as garantias de segurança de memória do Rust ajudam a prevenir bugs comuns e garantir a exatidão do código. Além disso, o foco do Rust em eficiência e paralelismo nos permite aproveitar recursos de multiencadeamento e tirar vantagem das capacidades de hardware modernas.

Com esta biblioteca, você agora tem uma ferramenta poderosa para desenvolver modelos de redes neurais em Rust. Você pode explorar e experimentar ainda mais com diferentes arquiteturas de rede, funções de ativação, técnicas de otimização e métodos de regularização para melhorar o desempenho de seus modelos.

À medida que você continua sua jornada em aprendizado de máquina e aprendizado profundo, lembre-se de manter a curiosidade, continuar explorando novos conceitos e técnicas, e aproveitar o rico ecossistema Rust para aprimorar ainda mais sua biblioteca de redes neurais.

Repositório GitHub: https://github.com/akshayballal95/dnn_rust_blog.git

Quer se conectar?

Meu website
LinkedIn
Twitter

Artigo original publicado por Akshay Ballal. Traduzido por Paulinho Giovannini.

Top comments (0)