// // main.cpp // transfer-learning // // Created by Kushashwa Ravi Shrimali on 12/08/19. // #include "main.h" torch::Tensor read_data(std::string location) { /* Function to return image read at location given as type torch::Tensor Resizes image to (224, 224, 3) Parameters =========== 1. location (std::string type) - required to load image from the location Returns =========== torch::Tensor type - image read as tensor */ cv::Mat img = cv::imread(location, 1); cv::resize(img, img, cv::Size(224, 224), cv::INTER_CUBIC); torch::Tensor img_tensor = torch::from_blob(img.data, {img.rows, img.cols, 3}, torch::kByte); img_tensor = img_tensor.permute({2, 0, 1}); return img_tensor.clone(); } torch::Tensor read_label(int label) { /* Function to return label from int (0, 1 for binary and 0, 1, ..., n-1 for n-class classification) as type torch::Tensor Parameters =========== 1. label (int type) - required to convert int to tensor Returns =========== torch::Tensor type - label read as tensor */ torch::Tensor label_tensor = torch::full({1}, label); return label_tensor.clone(); } std::vector process_images(std::vector list_images) { /* Function returns vector of tensors (images) read from the list of images in a folder Parameters =========== 1. list_images (std::vector type) - list of image paths in a folder to be read Returns =========== std::vector type - Images read as tensors */ std::vector states; for(std::vector::iterator it = list_images.begin(); it != list_images.end(); ++it) { torch::Tensor img = read_data(*it); states.push_back(img); } return states; } std::vector process_labels(std::vector list_labels) { /* Function returns vector of tensors (labels) read from the list of labels Parameters =========== 1. list_labels (std::vector list_labels) - Returns =========== std::vector type - returns vector of tensors (labels) */ std::vector labels; for(std::vector::iterator it = list_labels.begin(); it != list_labels.end(); ++it) { torch::Tensor label = read_label(*it); labels.push_back(label); } return labels; } std::pair,std::vector> load_data_from_folder(std::vector folders_name) { /* Function to load data from given folder(s) name(s) (folders_name) Returns pair of vectors of string (image locations) and int (respective labels) Parameters =========== 1. folders_name (std::vector type) - name of folders as a vector to load data from Returns =========== std::pair, std::vector> type - returns pair of vector of strings (image paths) and respective labels' vector (int label) */ std::vector list_images; std::vector list_labels; int label = 0; for(auto const& value: folders_name) { std::string base_name = value + "/"; // cout << "Reading from: " << base_name << endl; DIR* dir; struct dirent *ent; if((dir = opendir(base_name.c_str())) != NULL) { while((ent = readdir(dir)) != NULL) { std::string filename = ent->d_name; if(filename.length() > 4 && filename.substr(filename.length() - 3) == "jpg") { // cout << base_name + ent->d_name << endl; // cv::Mat temp = cv::imread(base_name + "/" + ent->d_name, 1); list_images.push_back(base_name + ent->d_name); list_labels.push_back(label); } } closedir(dir); } else { std::cout << "Could not open directory" << std::endl; // return EXIT_FAILURE; } label += 1; } return std::make_pair(list_images, list_labels); } template void train(torch::jit::script::Module net, torch::nn::Linear lin, Dataloader& data_loader, torch::optim::Optimizer& optimizer, size_t dataset_size) { /* This function trains the network on our data loader using optimizer. Also saves the model as model.pt after every epoch. Parameters =========== 1. net (torch::jit::script::Module type) - Pre-trained model without last FC layer 2. lin (torch::nn::Linear type) - last FC layer with revised out_features depending on the number of classes 3. data_loader (DataLoader& type) - Training data loader 4. optimizer (torch::optim::Optimizer& type) - Optimizer like Adam, SGD etc. 5. size_t (dataset_size type) - Size of training dataset Returns =========== Nothing (void) */ float best_accuracy = 0.0; int batch_index = 0; for(int i=0; i<25; i++) { float mse = 0; float Acc = 0.0; for(auto& batch: *data_loader) { auto data = batch.data; auto target = batch.target.squeeze(); // Should be of length: batch_size data = data.to(torch::kF32); target = target.to(torch::kInt64); std::vector input; input.push_back(data); optimizer.zero_grad(); auto output = net.forward(input).toTensor(); // For transfer learning output = output.view({output.size(0), -1}); output = lin(output); auto loss = torch::nll_loss(torch::log_softmax(output, 1), target); loss.backward(); optimizer.step(); auto acc = output.argmax(1).eq(target).sum(); Acc += acc.template item(); mse += loss.template item(); batch_index += 1; } mse = mse/float(batch_index); // Take mean of loss std::cout << "Epoch: " << i << ", " << "Accuracy: " << Acc/dataset_size << ", " << "MSE: " << mse << std::endl; test(net, lin, data_loader, dataset_size); if(Acc/dataset_size > best_accuracy) { best_accuracy = Acc/dataset_size; std::cout << "Saving model" << std::endl; net.save("model.pt"); torch::save(lin, "model_linear.pt"); } } } template void test(torch::jit::script::Module network, torch::nn::Linear lin, Dataloader& loader, size_t data_size) { /* Function to test the network on test data Parameters =========== 1. network (torch::jit::script::Module type) - Pre-trained model without last FC layer 2. lin (torch::nn::Linear type) - last FC layer with revised out_features depending on the number of classes 3. loader (Dataloader& type) - test data loader 4. data_size (size_t type) - test data size Returns =========== Nothing (void) */ network.eval(); float Loss = 0, Acc = 0; for (const auto& batch : *loader) { auto data = batch.data; auto targets = batch.target.squeeze(); data = data.to(torch::kF32); targets = targets.to(torch::kInt64); std::vector input; input.push_back(data); auto output = network.forward(input).toTensor(); output = output.view({output.size(0), -1}); output = lin(output); auto loss = torch::nll_loss(torch::log_softmax(output, 1), targets); auto acc = output.argmax(1).eq(targets).sum(); Loss += loss.template item(); Acc += acc.template item(); } std::cout << "Test Loss: " << Loss/data_size << ", Acc:" << Acc/data_size << std::endl; } int main(int argc, const char * argv[]) { // Set folder names for cat and dog images std::string cats_name = "/Users/krshrimali/Documents/krshrimali-blogs/dataset/train/cat_test"; std::string dogs_name = "/Users/krshrimali/Documents/krshrimali-blogs/dataset/train/dog_test"; std::vector folders_name; folders_name.push_back(cats_name); folders_name.push_back(dogs_name); // Get paths of images and labels as int from the folder paths std::pair, std::vector> pair_images_labels = load_data_from_folder(folders_name); std::vector list_images = pair_images_labels.first; std::vector list_labels = pair_images_labels.second; // Initialize CustomDataset class and read data auto custom_dataset = CustomDataset(list_images, list_labels).map(torch::data::transforms::Stack<>()); // Load pre-trained model // You can also use: auto module = torch::jit::load(argv[1]); torch::jit::script::Module module = torch::jit::load(argv[1]); // Resource: https://discuss.pytorch.org/t/how-to-load-the-prebuilt-resnet-models-or-any-other-prebuilt-models/40269/8 // For VGG: 512 * 14 * 14, 2 torch::nn::Linear lin(512, 2); // the last layer of resnet, which we want to replace, has dimensions 512x1000 torch::optim::Adam opt(lin->parameters(), torch::optim::AdamOptions(1e-3 /*learning rate*/)); auto data_loader = torch::data::make_data_loader(std::move(custom_dataset), 4); train(module, lin, data_loader, opt, custom_dataset.size().value()); return 0; }