Skip to content

Commit a336cfb

Browse files
authored
Add ImageFolderDataset, train convnet tutorial on imagenette dataset (#95)
* Train convolutional neural network tutorial on Imagenette dataset instead of MNIST. * Remove macos CI build using Xcode 10.3.
1 parent 8027e04 commit a336cfb

File tree

10 files changed

+187
-19
lines changed

10 files changed

+187
-19
lines changed

.github/workflows/build_macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
runs-on: macos-10.15
2323
strategy:
2424
matrix:
25-
xcode: [10.3, 11.5, 12.4]
25+
xcode: [11.5, 12.4]
2626
steps:
2727
- name: Checkout
2828
uses: actions/[email protected]

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ if(DOWNLOAD_DATASETS)
5959
add_custom_target(flickr8k COMMAND ${CMAKE_COMMAND}
6060
-D DATA_DIR=${DATA_DIR}
6161
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/fetch_flickr8k.cmake)
62+
add_custom_target(imagenette COMMAND ${CMAKE_COMMAND}
63+
-D DATA_DIR=${DATA_DIR}
64+
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/fetch_imagenette.cmake)
6265
endif()
6366

6467
# Add tutorial sub-projects:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
| OS (Compiler)\\LibTorch | 1.10.1 |
1414
| :--------------------- | :--------------------------------------------------------------------------------------------------- |
15-
| macOS (clang 10.0, 11.0, 12.0) | [![Status](https://github.com/prabhuomkar/pytorch-cpp/actions/workflows/build_macos.yml/badge.svg?branch=master)](https://github.com/prabhuomkar/pytorch-cpp/actions?query=workflow%3Aci-build-macos) |
15+
| macOS (clang 11.0, 12.0) | [![Status](https://github.com/prabhuomkar/pytorch-cpp/actions/workflows/build_macos.yml/badge.svg?branch=master)](https://github.com/prabhuomkar/pytorch-cpp/actions?query=workflow%3Aci-build-macos) |
1616
| Linux (gcc 8, 9, 10, 11) | [![Status](https://github.com/prabhuomkar/pytorch-cpp/actions/workflows/build_ubuntu.yml/badge.svg?branch=master)](https://github.com/prabhuomkar/pytorch-cpp/actions?query=workflow%3Aci-build-ubuntu) |
1717
| Windows (msvc 2017, 2019) | [![Status](https://github.com/prabhuomkar/pytorch-cpp/actions/workflows/build_windows.yml/badge.svg?branch=master)](https://github.com/prabhuomkar/pytorch-cpp/actions?query=workflow%3Aci-build-windows) |
1818

cmake/fetch_imagenette.cmake

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
2+
3+
function(fetch_imagenette DATA_DIR)
4+
set(IMAGENETTE_DIR "${DATA_DIR}/imagenette2-160")
5+
set(IMAGENETTE_DOWNLOAD_DIR "${DATA_DIR}/imagenette_download")
6+
7+
set(IMAGENETTE_DATA_URL
8+
"https://s3.amazonaws.com/fast-ai-imageclas/imagenette2-160.tgz"
9+
)
10+
11+
if(NOT EXISTS ${IMAGENETTE_DIR})
12+
message(STATUS "Fetching Imagenette dataset...")
13+
14+
file(
15+
DOWNLOAD ${IMAGENETTE_DATA_URL}
16+
"${IMAGENETTE_DOWNLOAD_DIR}/imagenette2-160.tgz"
17+
EXPECTED_MD5 "e793b78cc4c9e9a4ccc0c1155377a412"
18+
SHOW_PROGRESS)
19+
20+
execute_process(
21+
COMMAND ${CMAKE_COMMAND} -E tar xf
22+
"${IMAGENETTE_DOWNLOAD_DIR}/imagenette2-160.tgz"
23+
"imagenette2-160/train"
24+
"imagenette2-160/val"
25+
WORKING_DIRECTORY ${DATA_DIR})
26+
27+
file(REMOVE_RECURSE ${IMAGENETTE_DOWNLOAD_DIR})
28+
29+
message(STATUS "Fetching Imagenette dataset - done")
30+
endif()
31+
endfunction()
32+
33+
fetch_imagenette(${DATA_DIR})

tutorials/intermediate/convolutional_neural_network/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@ set(EXECUTABLE_NAME convolutional-neural-network)
77
add_executable(${EXECUTABLE_NAME})
88
target_sources(${EXECUTABLE_NAME} PRIVATE src/main.cpp
99
src/convnet.cpp
10+
src/imagefolder_dataset.cpp
1011
include/convnet.h
12+
include/imagefolder_dataset.h
1113
)
1214

1315
target_include_directories(${EXECUTABLE_NAME} PRIVATE include)
1416

15-
target_link_libraries(${EXECUTABLE_NAME} ${TORCH_LIBRARIES})
17+
target_link_libraries(${EXECUTABLE_NAME} ${TORCH_LIBRARIES} image-io)
1618

1719
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
1820
CXX_STANDARD 17
1921
CXX_STANDARD_REQUIRED YES
2022
)
2123

2224
if(DOWNLOAD_DATASETS)
23-
add_dependencies(${EXECUTABLE_NAME} mnist)
25+
add_dependencies(${EXECUTABLE_NAME} imagenette)
2426
endif()
2527

2628
if(MSVC)

tutorials/intermediate/convolutional_neural_network/include/convnet.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ class ConvNetImpl : public torch::nn::Module {
1010

1111
private:
1212
torch::nn::Sequential layer1{
13-
torch::nn::Conv2d(torch::nn::Conv2dOptions(1, 16, 5).stride(1).padding(2)),
13+
torch::nn::Conv2d(torch::nn::Conv2dOptions(3, 16, 3).stride(1)),
1414
torch::nn::BatchNorm2d(16),
1515
torch::nn::ReLU(),
1616
torch::nn::MaxPool2d(torch::nn::MaxPool2dOptions(2).stride(2))
1717
};
1818

1919
torch::nn::Sequential layer2{
20-
torch::nn::Conv2d(torch::nn::Conv2dOptions(16, 32, 5).stride(1).padding(2)),
20+
torch::nn::Conv2d(torch::nn::Conv2dOptions(16, 32, 3).stride(1)),
2121
torch::nn::BatchNorm2d(32),
2222
torch::nn::ReLU(),
2323
torch::nn::MaxPool2d(torch::nn::MaxPool2dOptions(2).stride(2))
2424
};
2525

26+
torch::nn::Sequential layer3{
27+
torch::nn::Conv2d(torch::nn::Conv2dOptions(32, 64, 3).stride(1)),
28+
torch::nn::BatchNorm2d(64),
29+
torch::nn::ReLU(),
30+
};
31+
32+
torch::nn::AdaptiveAvgPool2d pool{torch::nn::AdaptiveAvgPool2dOptions({4, 4})};
33+
2634
torch::nn::Linear fc;
2735
};
2836

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2020-present pytorch-cpp Authors
2+
#pragma once
3+
4+
#include <torch/data/datasets/base.h>
5+
#include <torch/data/example.h>
6+
#include <torch/types.h>
7+
#include <string>
8+
#include <vector>
9+
#include <unordered_map>
10+
11+
namespace dataset {
12+
/**
13+
* Dataset class that provides image-label samples.
14+
*/
15+
class ImageFolderDataset : public torch::data::datasets::Dataset<ImageFolderDataset> {
16+
public:
17+
enum class Mode {
18+
TRAIN,
19+
VAL
20+
};
21+
22+
explicit ImageFolderDataset(const std::string &root, Mode mode = Mode::TRAIN,
23+
torch::IntArrayRef image_load_size = {});
24+
25+
torch::data::Example<> get(size_t index) override;
26+
27+
torch::optional<size_t> size() const override;
28+
29+
private:
30+
Mode mode_;
31+
std::vector<int64_t> image_load_size_;
32+
std::string mode_dir_;
33+
std::vector<std::string> classes_;
34+
std::unordered_map<std::string, int> class_to_index_;
35+
std::vector<std::pair<std::string, int>> samples_;
36+
};
37+
} // namespace dataset

tutorials/intermediate/convolutional_neural_network/src/convnet.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
#include <torch/torch.h>
44

55
ConvNetImpl::ConvNetImpl(int64_t num_classes)
6-
: fc(7 * 7 * 32, num_classes) {
6+
: fc(64 * 4 * 4, num_classes) {
77
register_module("layer1", layer1);
88
register_module("layer2", layer2);
9+
register_module("layer3", layer3);
10+
register_module("pool", pool),
911
register_module("fc", fc);
1012
}
1113

1214
torch::Tensor ConvNetImpl::forward(torch::Tensor x) {
1315
x = layer1->forward(x);
1416
x = layer2->forward(x);
15-
x = x.view({-1, 7 * 7 * 32});
17+
x = layer3->forward(x);
18+
x = pool->forward(x);
19+
x = x.view({-1, 64 * 4 * 4});
1620
return fc->forward(x);
1721
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2020-present pytorch-cpp Authors
2+
#include <imagefolder_dataset.h>
3+
#include <torch/torch.h>
4+
#include <vector>
5+
#include <algorithm>
6+
#include <filesystem>
7+
#include <unordered_map>
8+
#include "image_io.h"
9+
10+
namespace fs = std::filesystem;
11+
12+
using image_io::load_image;
13+
14+
namespace dataset {
15+
namespace {
16+
std::vector<std::string> parse_classes(const std::string &directory) {
17+
std::vector<std::string> classes;
18+
19+
for (auto &p : fs::directory_iterator(directory)) {
20+
if (p.is_directory()) {
21+
classes.push_back(p.path().filename().string());
22+
}
23+
}
24+
25+
std::sort(classes.begin(), classes.end());
26+
27+
return classes;
28+
}
29+
30+
std::unordered_map<std::string, int> create_class_to_index_map(const std::vector<std::string> &classes) {
31+
std::unordered_map<std::string, int> class_to_index;
32+
33+
int index = 0;
34+
35+
for (const auto &class_name : classes) {
36+
class_to_index[class_name] = index++;
37+
}
38+
39+
return class_to_index;
40+
}
41+
42+
std::vector<std::pair<std::string, int>> create_samples(
43+
const std::string &directory,
44+
const std::unordered_map<std::string, int> &class_to_index) {
45+
std::vector<std::pair<std::string, int>> samples;
46+
47+
for (const auto &[class_name, class_index] : class_to_index) {
48+
for (const auto &p : fs::directory_iterator(directory + "/" + class_name)) {
49+
if (p.is_regular_file()) {
50+
samples.emplace_back(p.path().string(), class_index);
51+
}
52+
}
53+
}
54+
55+
return samples;
56+
}
57+
} // namespace
58+
59+
ImageFolderDataset::ImageFolderDataset(const std::string &root, Mode mode, torch::IntArrayRef image_load_size)
60+
: mode_(mode),
61+
image_load_size_(image_load_size.begin(), image_load_size.end()),
62+
mode_dir_(root + "/" + (mode == Mode::TRAIN ? "train" : "val")),
63+
classes_(parse_classes(mode_dir_)),
64+
class_to_index_(create_class_to_index_map(classes_)),
65+
samples_(create_samples(mode_dir_, class_to_index_)) {}
66+
67+
torch::optional<size_t> ImageFolderDataset::size() const {
68+
return samples_.size();
69+
}
70+
71+
torch::data::Example<> ImageFolderDataset::get(size_t index) {
72+
const auto &[image_path, class_index] = samples_[index];
73+
74+
return {load_image(image_path, image_load_size_), torch::tensor(class_index)};
75+
}
76+
} // namespace dataset

tutorials/intermediate/convolutional_neural_network/src/main.cpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include <iostream>
44
#include <iomanip>
55
#include "convnet.h"
6+
#include "imagefolder_dataset.h"
7+
8+
using dataset::ImageFolderDataset;
69

710
int main() {
811
std::cout << "Convolutional Neural Network\n\n";
@@ -14,22 +17,23 @@ int main() {
1417

1518
// Hyper parameters
1619
const int64_t num_classes = 10;
17-
const int64_t batch_size = 100;
18-
const size_t num_epochs = 5;
19-
const double learning_rate = 0.001;
20+
const int64_t batch_size = 8;
21+
const size_t num_epochs = 10;
22+
const double learning_rate = 1e-3;
23+
const double weight_decay = 1e-3;
2024

21-
const std::string MNIST_data_path = "../../../../data/mnist/";
25+
const std::string imagenette_data_path = "../../../../data/imagenette2-160";
2226

23-
// MNIST dataset
24-
auto train_dataset = torch::data::datasets::MNIST(MNIST_data_path)
25-
.map(torch::data::transforms::Normalize<>(0.1307, 0.3081))
27+
// Imagenette dataset
28+
auto train_dataset = ImageFolderDataset(imagenette_data_path, ImageFolderDataset::Mode::TRAIN, {160, 160})
29+
.map(torch::data::transforms::Normalize<>({0.485, 0.456, 0.406}, {0.229, 0.224, 0.225}))
2630
.map(torch::data::transforms::Stack<>());
2731

2832
// Number of samples in the training set
2933
auto num_train_samples = train_dataset.size().value();
3034

31-
auto test_dataset = torch::data::datasets::MNIST(MNIST_data_path, torch::data::datasets::MNIST::Mode::kTest)
32-
.map(torch::data::transforms::Normalize<>(0.1307, 0.3081))
35+
auto test_dataset = ImageFolderDataset(imagenette_data_path, ImageFolderDataset::Mode::VAL, {160, 160})
36+
.map(torch::data::transforms::Normalize<>({0.485, 0.456, 0.406}, {0.229, 0.224, 0.225}))
3337
.map(torch::data::transforms::Stack<>());
3438

3539
// Number of samples in the testset
@@ -47,7 +51,8 @@ int main() {
4751
model->to(device);
4852

4953
// Optimizer
50-
torch::optim::Adam optimizer(model->parameters(), torch::optim::AdamOptions(learning_rate));
54+
torch::optim::Adam optimizer(
55+
model->parameters(), torch::optim::AdamOptions(learning_rate).weight_decay(weight_decay));
5156

5257
// Set floating point output precision
5358
std::cout << std::fixed << std::setprecision(4);
@@ -98,7 +103,7 @@ int main() {
98103

99104
// Test the model
100105
model->eval();
101-
torch::NoGradGuard no_grad;
106+
torch::InferenceMode no_grad;
102107

103108
double running_loss = 0.0;
104109
size_t num_correct = 0;

0 commit comments

Comments
 (0)