Série sobre Rede Neural Profunda do Zero em Rust
Ó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, ¶meters);
let cost = self.cost(&al, &y_train_data);
let grads = self.backward(&al, &y_train_data, caches);
parameters = self.update_parameters(¶meters, grads.clone(), learning_rate);
if i % 100 == 0 {
costs.append(&mut vec![cost]);
println!("Época : {}/{} Custo: {:?}", i, iterations, cost);
}
}
parameters
}
Essa função realiza os seguintes passos:
- Inicializa um vetor vazio chamado
costs
para armazenar os valores de custo para cada iteração. - Itera sobre o número especificado de iterações (
iterations
). - Em cada iteração:
- Realiza a propagação para frente usando o método
forward
para obter a ativação finalal
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.
- Realiza a propagação para frente usando o método
- 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, ¶meters);
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
}
A função predict
realiza os seguintes passos:
- Chama o método
forward
com os dados de teste e os parâmetros otimizados para obter a ativação finalal
. - Aplica um limite de
0.5
aos elementos deal
usando o métodomap
, convertendo valores maiores que0.5
para1.0
e valores menores ou iguais a0.5
para0.0
. - 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:
- Calcula a diferença absoluta elemento a elemento entre os rótulos previstos
y_hat
e os rótulos de teste reaisy_test_data
. - Soma as diferenças absolutas usando o método
sum
. - Divide a soma pelo número de exemplos (
y_test_data.shape()[1]
) e multiplica por100.0
para obter a porcentagem de erro. - 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);
}
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"
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);
}
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(¶meters, "model.json".into());
let training_predictions = model.predict(&training_data_array, ¶meters);
println!(
"Acurácia do Conjunto de Treinamento: {}%",
model.score(&training_predictions, &training_labels_array)
);
let test_predictions = model.predict(&test_data_array, ¶meters);
println!(
"Acurácia do Conjunto de Teste: {}%",
model.score(&test_predictions, &test_labels_array)
);
}
- Nós definimos as camadas da rede neural, a taxa de aprendizado e o número de iterações.
- Carregamos os dados de treinamento e teste a partir de arquivos CSV usando a função
dataframe_from_csv
. - Convertemos os dataframes em arrays e normalizamos os valores dos pixels para a faixa [0, 1].
- Criamos uma instância da estrutura
DeepNeuralNetwork
com as camadas especificadas e a taxa de aprendizado. - Inicializamos os parâmetros usando o método
initialize_parameters
. - 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. - Gravamos os parâmetros treinados em um arquivo JSON usando a função
write_parameters_to_json_file
. - Prevemos os rótulos para os dados de treinamento e teste usando o método
predict
. - 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
Isso instalará as dependências e iniciará o treinamento.
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?
Artigo original publicado por Akshay Ballal. Traduzido por Paulinho Giovannini.
Top comments (0)