NumClassifier#
In this example we will train a number classifier.
NumberClassDataset#
we will create a Dataset to generate the data and labels, NumberClassDataset will need to fulfill meta::is_a_dataset_v<NumberClassDataset>
which just asserts that NumberClassDataset
is a Dataset
struct NumberClassDataset{
NumberClassDataset(std::size_t samples, std::size_t batchSize)
: m_index(0)
, m_samples(samples)
, m_batchSize(batchSize)
, m_dataset(make_dataset(samples)){}
std::vector<std::pair<std::vector<double>, std::vector<double>>> make_dataset(std::size_t samples) noexcept{
// full code at the bottom
}
const std::pair<std::vector<double>&, std::vector<double>&> operator()() noexcept{
auto i = m_index;
m_index = (m_index + 1) % m_samples;
return std::make_pair(std::ref(m_dataset[i].first), std::ref(m_dataset[i].second));
}
std::size_t size() const noexcept{
return (m_dataset.size() + m_batchSize - 1) / m_batchSize;
}
std::size_t getBatchSize() const noexcept{
return m_batchSize;
}
void shuffle() noexcept{
auto g = EvoAI::randomGen().getEngine();
std::uniform_int_distribution ud(0, static_cast<int>(m_dataset.size() - 1));
for(auto i=0u;i<m_dataset.size();++i){
auto index1 = ud(g);
auto index2 = ud(g);
std::swap(m_dataset[index1], m_dataset[index2]);
}
}
// data
mutable std::size_t m_index;
std::size_t m_samples;
std::size_t m_batchSize;
std::vector<std::pair<std::vector<double>, std::vector<double>>> m_dataset;
};
testNN function#
This function will be passed to nn->train
to check how well is doing with the test DataSet
std::pair<double, double> testNN(NeuralNetwork& nn, DataLoader<NumberClassDataset>& ds) noexcept{
double error = 0.0;
double totalLoss = 0.0;
for(auto i=0u;i<ds.size();++i){
for(auto j=0u;j<ds.getBatchSize();++j){
auto [inputs, expectedOutputs] = ds();
auto outputs = nn.forward(inputs);
nn.reset();
totalLoss += Loss::MultiClassCrossEntropy{}(expectedOutputs,outputs);
auto pred = Argmax(outputs);
if(pred != Argmax(expectedOutputs)){
error += 1.0;
}
auto isCorrect = pred == Argmax(expectedOutputs) ? "v/":"X";
std::cout << "input -> " << inputs[0] << " :: " << (pred+1) <<" = " << (Argmax(expectedOutputs)+1) <<"\t" << isCorrect << "\n";
}
}
auto avgLoss = totalLoss / (ds.size() * ds.getBatchSize());
error /= (ds.size() * ds.getBatchSize());
error *= 100;
std::cout << " error: " << error << "% " << "accuracy: " << (100.0 - error) << "%" << std::endl;
std::cout << " avgLoss: " << avgLoss << std::endl;
return std::make_pair(avgLoss, 100.0 - error);
}
main#
In the main function we will call createFeedForwardNN to make a NeuralNetwork
then
call UniformInit
that will initialize the weights of the NeuralNetwork
and then set the actiovations for each layer.
We will generate the data (100 random numbers between 1 and 3), show an initial inference, configure the Optimizer
and then call NeuralNetwork::train
int main(){
EvoAI::randomGen().setSeed(42);
auto nn = EvoAI::createFeedForwardNN(1,2,{6, 6},3, 1.0);
EvoAI::UniformInit(*nn);
(*nn)[1].setActivationType(EvoAI::Neuron::ActivationType::RELU);
(*nn)[2].setActivationType(EvoAI::Neuron::ActivationType::IDENTITY);
(*nn)[3].setActivationType(EvoAI::Neuron::ActivationType::SOFTMAX);
std::cout << "before Training:"<< std::endl;
auto batchSize = 10u;
EvoAI::DataLoader<EvoAI::NumberClassDataset> trainingData(EvoAI::NumberClassDataset(100, batchSize));
EvoAI::DataLoader<EvoAI::NumberClassDataset> testingData(EvoAI::NumberClassDataset(10, batchSize));
testNN(*nn, testingData);
EvoAI::Optimizer optim(0.1, batchSize, EvoAI::SGD(nn->getParameters(), 0.0), EvoAI::Scheduler(EvoAI::MultiStepLR({25}, 0.1)));
EvoAI::writeMultiPlot("NumbersClassLossPlot.txt", {"epochAvgLoss", "testAvgLoss", "accuracy"},
nn->train(trainingData, testingData, optim, 100, EvoAI::Loss::MultiClassCrossEntropy{}, &EvoAI::testNN));
nn->writeDotFile("numClass.dot");
std::cout << "after Training:"<< std::endl;
testNN(*nn, testingData);
nn->writeToFile("NumberClass.json");
return 0;
}
the full code is here