├── CMakeLists.txt ├── README.md ├── LICENSE ├── training.cpp └── training_VGG.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0 FATAL_ERROR) 2 | project(Digit-Recognition-MNIST) 3 | find_package(Torch REQUIRED) 4 | add_executable(example training.cpp) 5 | target_link_libraries(example "${TORCH_LIBRARIES}") 6 | set_property(TARGET example PROPERTY CXX_STANDARD 14) 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digit-Recognition-MNIST-SVHN 2 | 3 | ## Blog 4 | 5 | The blog post on using VGG-Net on MNIST Dataset using PyTorch C++ API: https://krshrimali.github.io/posts/2019/06/introduction-to-pytorch-c-api-mnist-digit-recognition-using-vgg-16-network/. 6 | 7 | ## Implementing CNN for Digit Recognition (MNIST and SVHN dataset) using PyTorch C++ API 8 | 9 | **Branch - Version 1** 10 | 11 | 1. Using MNIST dataset currently. 12 | 2. Installing PyTorch C++ API. 13 | 3. Training using PyTorch C++ API. 14 | 15 | **Note**: There may be C10:errors either because of: 16 | 17 | 1. Incorrect data path. Check your data directory of MNIST dataset. 18 | 2. Make sure while using `cmake`: `cmake -DCMAKE_PREFIX_PATH=/absolute_path_to_libtorch/` - it should be absolute path to `libtorch.` 19 | 20 | ## Process 21 | 22 | Note: Prefer using stable version of libtorch. This code doesn't use GPU, as I don't have the resources to test it right now. 23 | 1. `mkdir build` 24 | 2. `cd build` 25 | 3. cmake: `cmake -DCMAKE_PREFIX_PATH=/home/user/path/to/libtorch/ ..` 26 | 4. `make` 27 | 5. `./example` 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kushashwa Ravi Shrimali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /training.cpp: -------------------------------------------------------------------------------- 1 | /* Author: Kushashwa, http://www.github.com/krshrimali 2 | * Reference: https://pytorch.org/cppdocs/frontend.html 3 | */ 4 | 5 | #include 6 | 7 | /* Sample code for training a FCN on MNIST dataset using PyTorch C++ API */ 8 | 9 | struct Net: torch::nn::Module { 10 | Net() { 11 | // Initialize CNN 12 | conv1 = register_module("conv1", torch::nn::Conv2d(torch::nn::Conv2dOptions(1, 10, 5))); 13 | conv2 = register_module("conv2", torch::nn::Conv2d(torch::nn::Conv2dOptions(10, 20, 5))); 14 | conv2_drop = register_module("conv2_drop", torch::nn::Dropout()); 15 | fc1 = register_module("fc1", torch::nn::Linear(320, 50)); 16 | fc2 = register_module("fc2", torch::nn::Linear(50, 10)); 17 | } 18 | 19 | // Implement Algorithm 20 | torch::Tensor forward(torch::Tensor x) { 21 | // std::cout << x.size(0) << ", " << 784 << std::endl; 22 | x = torch::relu(torch::max_pool2d(conv1->forward(x), 2)); 23 | x = torch::relu(torch::max_pool2d(conv2_drop->forward(conv2->forward(x)), 2)); 24 | x = x.view({-1, 320}); 25 | x = torch::relu(fc1->forward(x)); 26 | x = torch::dropout(x, 0.5, is_training()); 27 | x = fc2->forward(x); 28 | return torch::log_softmax(x, 1); 29 | } 30 | torch::nn::Conv2d conv1{nullptr}; 31 | torch::nn::Conv2d conv2{nullptr}; 32 | torch::nn::Dropout conv2_drop{nullptr}; 33 | torch::nn::Linear fc1{nullptr}, fc2{nullptr}; 34 | }; 35 | 36 | int main() { 37 | auto net = std::make_shared(); 38 | 39 | // Create multi-threaded data loader for MNIST data 40 | auto data_loader = torch::data::make_data_loader( 41 | std::move(torch::data::datasets::MNIST("../data").map(torch::data::transforms::Normalize<>(0.13707, 0.3081)).map( 42 | torch::data::transforms::Stack<>())), 64); 43 | torch::optim::SGD optimizer(net->parameters(), 0.01); // Learning Rate 0.01 44 | 45 | // net.train(); 46 | 47 | for(size_t epoch=1; epoch<=10; ++epoch) { 48 | size_t batch_index = 0; 49 | // Iterate data loader to yield batches from the dataset 50 | for (auto& batch: *data_loader) { 51 | // Reset gradients 52 | optimizer.zero_grad(); 53 | // Execute the model 54 | torch::Tensor prediction = net->forward(batch.data); 55 | // Compute loss value 56 | torch::Tensor loss = torch::nll_loss(prediction, batch.target); 57 | // Compute gradients 58 | loss.backward(); 59 | // Update the parameters 60 | optimizer.step(); 61 | 62 | // Output the loss and checkpoint every 100 batches 63 | if (++batch_index % 100 == 0) { 64 | std::cout << "Epoch: " << epoch << " | Batch: " << batch_index 65 | << " | Loss: " << loss.item() << std::endl; 66 | torch::save(net, "net.pt"); 67 | } 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /training_VGG.cpp: -------------------------------------------------------------------------------- 1 | /* Author: Kushashwa, http://www.github.com/krshrimali */ 2 | 3 | #include 4 | 5 | /* Sample code for training a FCN on MNIST dataset using PyTorch C++ API */ 6 | /* This code uses VGG-16 Layer Network */ 7 | 8 | struct Net: torch::nn::Module { 9 | // VGG-16 Layer 10 | // conv1_1 - conv1_2 - pool 1 - conv2_1 - conv2_2 - pool 2 - conv3_1 - conv3_2 - conv3_3 - pool 3 - 11 | // conv4_1 - conv4_2 - conv4_3 - pool 4 - conv5_1 - conv5_2 - conv5_3 - pool 5 - fc6 - fc7 - fc8 12 | Net() { 13 | // Initialize CNN 14 | // On how to pass strides and padding: https://github.com/pytorch/pytorch/issues/12649#issuecomment-430156160 15 | conv1_1 = register_module("conv1_1", torch::nn::Conv2d(torch::nn::Conv2dOptions(1, 10, 3).padding(1))); 16 | conv1_2 = register_module("conv1_2", torch::nn::Conv2d(torch::nn::Conv2dOptions(10, 20, 3).padding(1))); 17 | // Insert pool layer 18 | conv2_1 = register_module("conv2_1", torch::nn::Conv2d(torch::nn::Conv2dOptions(20, 30, 3).padding(1))); 19 | conv2_2 = register_module("conv2_2", torch::nn::Conv2d(torch::nn::Conv2dOptions(30, 40, 3).padding(1))); 20 | // Insert pool layer 21 | conv3_1 = register_module("conv3_1", torch::nn::Conv2d(torch::nn::Conv2dOptions(40, 50, 3).padding(1))); 22 | conv3_2 = register_module("conv3_2", torch::nn::Conv2d(torch::nn::Conv2dOptions(50, 60, 3).padding(1))); 23 | conv3_3 = register_module("conv3_3", torch::nn::Conv2d(torch::nn::Conv2dOptions(60, 70, 3).padding(1))); 24 | // Insert pool layer 25 | conv4_1 = register_module("conv4_1", torch::nn::Conv2d(torch::nn::Conv2dOptions(70, 80, 3).padding(1))); 26 | conv4_2 = register_module("conv4_2", torch::nn::Conv2d(torch::nn::Conv2dOptions(80, 90, 3).padding(1))); 27 | conv4_3 = register_module("conv4_3", torch::nn::Conv2d(torch::nn::Conv2dOptions(90, 100, 3).padding(1))); 28 | // Insert pool layer 29 | conv5_1 = register_module("conv5_1", torch::nn::Conv2d(torch::nn::Conv2dOptions(100, 110, 3).padding(1))); 30 | conv5_2 = register_module("conv5_2", torch::nn::Conv2d(torch::nn::Conv2dOptions(110, 120, 3).padding(1))); 31 | conv5_3 = register_module("conv5_3", torch::nn::Conv2d(torch::nn::Conv2dOptions(120, 130, 3).padding(1))); 32 | // Insert pool layer 33 | fc1 = register_module("fc1", torch::nn::Linear(130, 50)); 34 | fc2 = register_module("fc2", torch::nn::Linear(50, 20)); 35 | fc3 = register_module("fc3", torch::nn::Linear(20, 10)); 36 | } 37 | 38 | // Implement Algorithm 39 | torch::Tensor forward(torch::Tensor x) { 40 | x = torch::relu(conv1_1->forward(x)); 41 | x = torch::relu(conv1_2->forward(x)); 42 | x = torch::max_pool2d(x, 2); 43 | 44 | x = torch::relu(conv2_1->forward(x)); 45 | x = torch::relu(conv2_2->forward(x)); 46 | x = torch::max_pool2d(x, 2); 47 | 48 | x = torch::relu(conv3_1->forward(x)); 49 | x = torch::relu(conv3_2->forward(x)); 50 | x = torch::relu(conv3_3->forward(x)); 51 | x = torch::max_pool2d(x, 2); 52 | 53 | x = torch::relu(conv4_1->forward(x)); 54 | x = torch::relu(conv4_2->forward(x)); 55 | x = torch::relu(conv4_3->forward(x)); 56 | x = torch::max_pool2d(x, 2); 57 | 58 | x = torch::relu(conv5_1->forward(x)); 59 | x = torch::relu(conv5_2->forward(x)); 60 | x = torch::relu(conv5_3->forward(x)); 61 | 62 | x = x.view({-1, 130}); 63 | 64 | x = torch::relu(fc1->forward(x)); 65 | x = torch::relu(fc2->forward(x)); 66 | x = fc3->forward(x); 67 | 68 | return torch::log_softmax(x, 1); 69 | } 70 | 71 | torch::nn::Conv2d conv1_1{nullptr}; 72 | torch::nn::Conv2d conv1_2{nullptr}; 73 | torch::nn::Conv2d conv2_1{nullptr}; 74 | torch::nn::Conv2d conv2_2{nullptr}; 75 | torch::nn::Conv2d conv3_1{nullptr}; 76 | torch::nn::Conv2d conv3_2{nullptr}; 77 | torch::nn::Conv2d conv3_3{nullptr}; 78 | torch::nn::Conv2d conv4_1{nullptr}; 79 | torch::nn::Conv2d conv4_2{nullptr}; 80 | torch::nn::Conv2d conv4_3{nullptr}; 81 | torch::nn::Conv2d conv5_1{nullptr}; 82 | torch::nn::Conv2d conv5_2{nullptr}; 83 | torch::nn::Conv2d conv5_3{nullptr}; 84 | 85 | torch::nn::Linear fc1{nullptr}, fc2{nullptr}, fc3{nullptr}; 86 | }; 87 | 88 | int main() { 89 | auto net = std::make_shared(); 90 | 91 | // Create multi-threaded data loader for MNIST data 92 | // Make sure to enter absolute path to the data directory for no errors later on 93 | auto data_loader = torch::data::make_data_loader( 94 | std::move(torch::data::datasets::MNIST("").map(torch::data::transforms::Normalize<>(0.13707, 0.3081)).map( 95 | torch::data::transforms::Stack<>())), /*batch_size=*/64); 96 | torch::optim::SGD optimizer(net->parameters(), 0.01); // Learning Rate 0.01 97 | 98 | // net.train(); 99 | 100 | for(size_t epoch=1; epoch<=10; ++epoch) { 101 | size_t batch_index = 0; 102 | // Iterate data loader to yield batches from the dataset 103 | for (auto& batch: *data_loader) { 104 | // Reset gradients 105 | optimizer.zero_grad(); 106 | // Execute the model 107 | torch::Tensor prediction = net->forward(batch.data); 108 | // Compute loss value 109 | torch::Tensor loss = torch::nll_loss(prediction, batch.target); 110 | // Compute gradients 111 | loss.backward(); 112 | // Update the parameters 113 | optimizer.step(); 114 | 115 | // Output the loss and checkpoint every 100 batches 116 | if (++batch_index % 100 == 0) { 117 | std::cout << "Epoch: " << epoch << " | Batch: " << batch_index 118 | << " | Loss: " << loss.item() << std::endl; 119 | torch::save(net, "net.pt"); 120 | } 121 | } 122 | } 123 | } 124 | --------------------------------------------------------------------------------