From 2676711dd45a291d0e43ed6308147cb819804f3a Mon Sep 17 00:00:00 2001 From: Oisin Date: Sun, 16 Feb 2025 19:18:20 +0000 Subject: [PATCH 01/12] Adjusted logic to use new torch data load object. Added timer entries for logging execution time. --- model/arch/classify_image_torch.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/model/arch/classify_image_torch.py b/model/arch/classify_image_torch.py index b794307..f730ec0 100644 --- a/model/arch/classify_image_torch.py +++ b/model/arch/classify_image_torch.py @@ -31,6 +31,8 @@ import cons from model.torch.VGG16_pretrained import VGG16_pretrained from model.torch.CustomDataset import CustomDataset +from model.arch.load_image_v2 import TorchLoadImages +from model.utilities.TimeIt import TimeIt # device configuration device = torch.device('cuda' if torch.cuda.is_available() and cons.check_gpu else 'cpu') @@ -45,13 +47,15 @@ ]) @beartype -def classify_image_torch(image_fpath:str, model_fpath:str=cons.torch_model_pt_fpath): +def classify_image_torch(image_fpath:str, timeLogger:TimeIt, model_fpath:str=cons.torch_model_pt_fpath): """Classifies an input image using the torch model Parameters ---------- image_fpath : str The full filepath to the image to classify using the torch model + timeLogger : TimeIt + Timer object for logging execution time of process model_fpath : str The full filepath to the torch model to use for classification, default is cons.torch_model_pt_fpath @@ -66,22 +70,29 @@ def classify_image_torch(image_fpath:str, model_fpath:str=cons.torch_model_pt_fp #model = AlexNet8(num_classes=2).to(device) model = VGG16_pretrained(num_classes=2).to(device) model.load(input_fpath=model_fpath) + timeLogger.logTime(parentKey="Preparation", subKey="ModelLoad") logging.info("Generating dataset...") # prepare test data - dataframe = pd.DataFrame({'filepath': [image_fpath]}) + torchLoadImages = TorchLoadImages(torch_transforms=torch_transforms, n_workers=None) + dataframe = pd.DataFrame.from_records(torchLoadImages.loadImages(filepaths=[image_fpath])) + dataframe["model_fpath"] = model_fpath + timeLogger.logTime(parentKey="Preparation", subKey="DataFrame") logging.info("Creating dataloader...") # set train data loader - dataset = CustomDataset(dataframe, transforms=torch_transforms, mode='test') - loader = DataLoader(dataset, batch_size=cons.batch_size, shuffle=False, num_workers=cons.num_workers, pin_memory=True) + dataset = CustomDataset(dataframe) + loader = DataLoader(dataset, batch_size=None, shuffle=False, num_workers=cons.num_workers, pin_memory=True, collate_fn=CustomDataset.collate_fn) + timeLogger.logTime(parentKey="Preparation", subKey="DataLoader") logging.info("Classifying image...") # make test set predictions predict = model.predict(loader, device) dataframe['category'] = np.argmax(predict, axis=-1) - dataframe["category"] = dataframe["category"].replace(cons.category_mapper) - response = dataframe.to_dict(orient="records") + dataframe["categoryname"] = dataframe["category"].replace(cons.category_mapper) + sub_cols = ["model_fpath", "filepaths", "categoryname"] + response = dataframe[sub_cols].to_dict(orient="records") + timeLogger.logTime(parentKey="Model", subKey="Classification") logging.info(response) return response @@ -90,6 +101,7 @@ def classify_image_torch(image_fpath:str, model_fpath:str=cons.torch_model_pt_fp # set up logging lgr = logging.getLogger() lgr.setLevel(logging.INFO) + timeLogger = TimeIt() # define argument parser object parser = argparse.ArgumentParser(description="Classify Image (Torch Model)") @@ -100,5 +112,6 @@ def classify_image_torch(image_fpath:str, model_fpath:str=cons.torch_model_pt_fp input_params_dict = {} # extract input arguments args = parser.parse_args() + timeLogger.logTime(parentKey="Initialisation", subKey="CommandlineArguments") # classify image using torch model - response = classify_image_torch(image_fpath=args.image_fpath, model_fpath=args.model_fpath) \ No newline at end of file + response = classify_image_torch(image_fpath=args.image_fpath, model_fpath=args.model_fpath, timeLogger=timeLogger) \ No newline at end of file From d2531f070b162da431166ff8262438edac3a2cec Mon Sep 17 00:00:00 2001 From: Oisin Date: Sun, 16 Feb 2025 19:18:55 +0000 Subject: [PATCH 02/12] Calling unsqueeze for __getitem__ to ensure batch dimension is present in torch tensor --- model/torch/CustomDataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/torch/CustomDataset.py b/model/torch/CustomDataset.py index 5084cb4..ebf32ca 100644 --- a/model/torch/CustomDataset.py +++ b/model/torch/CustomDataset.py @@ -13,7 +13,7 @@ def __len__(self): return len(self.image_tensors) def __getitem__(self, idx): - image_tensor = self.image_tensors[idx] + image_tensor = self.image_tensors[idx].unsqueeze(0) category_tensor = self.category_tensors[idx] return image_tensor, category_tensor From 6511181a906b4e2ca8431e058b0b12ce5f30b99c Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 09:21:01 +0000 Subject: [PATCH 03/12] Setting cpu usage to os.cpu_count - 1 --- webscrapers/cons.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webscrapers/cons.py b/webscrapers/cons.py index 006d829..0850267 100644 --- a/webscrapers/cons.py +++ b/webscrapers/cons.py @@ -33,5 +33,6 @@ # webscraping constants n_images = 6000 +ncpu = os.cpu_count()-1 home_url = "https://free-images.com" output_dir = os.path.join(data_fdir, "{search}") \ No newline at end of file From 116de305d313bec2bfcd95c42e016b027c381e29 Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 09:22:19 +0000 Subject: [PATCH 04/12] Calling ncpu from main webscraper script. Adjusted logic to run basic for loop if ncpu is None. --- webscrapers/prg_scrape_imgs.py | 6 ++++-- webscrapers/utilities/webscraper.py | 15 +++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/webscrapers/prg_scrape_imgs.py b/webscrapers/prg_scrape_imgs.py index 1230038..ba9f239 100644 --- a/webscrapers/prg_scrape_imgs.py +++ b/webscrapers/prg_scrape_imgs.py @@ -50,7 +50,8 @@ def scrape_imags( search="cat", n_images=cons.n_images, home_url=cons.home_url, - output_dir=cons.train_fdir + output_dir=cons.train_fdir, + ncpu=cons.ncpu ) logging.info("Running dog image webscraper ...") # run dog webscraper @@ -58,7 +59,8 @@ def scrape_imags( search="dog", n_images=cons.n_images, home_url=cons.home_url, - output_dir=cons.train_fdir + output_dir=cons.train_fdir, + ncpu=cons.ncpu ) # if running as main programme diff --git a/webscrapers/utilities/webscraper.py b/webscrapers/utilities/webscraper.py index c2a43a7..9512633 100644 --- a/webscrapers/utilities/webscraper.py +++ b/webscrapers/utilities/webscraper.py @@ -7,6 +7,7 @@ from bs4 import BeautifulSoup from multiprocessing import Pool from beartype import beartype +from typing import Union import cons @beartype @@ -101,7 +102,7 @@ def download_src(src:str, output_dir:str, search:str): logging.error(e) @beartype -def multiprocess(func, args, ncpu:int=os.cpu_count()) -> list: +def multiprocess(func, args, ncpu:int) -> list: """This utility function applyies another function in parallel given a specified number of cpus Parameters @@ -111,7 +112,7 @@ def multiprocess(func, args, ncpu:int=os.cpu_count()) -> list: args : dict The arguments to pass to the function ncpu : int - The number of cpus to use for parallel processing, default is os.cpu_count() + The number of cpus to use for parallel processing Returns ------- @@ -124,7 +125,7 @@ def multiprocess(func, args, ncpu:int=os.cpu_count()) -> list: return results @beartype -def webscraper(search:str, n_images:int=cons.n_images, home_url:str=cons.home_url, output_dir:str=cons.train_fdir): +def webscraper(search:str, n_images:int=cons.n_images, home_url:str=cons.home_url, output_dir:str=cons.train_fdir, ncpu:Union[int,None]=None): """The main beautiful soup webscrapping programme Parameters @@ -137,6 +138,8 @@ def webscraper(search:str, n_images:int=cons.n_images, home_url:str=cons.home_ur The url for the home page to web scrape from, default is cons.home_url output_dir : str The output file directory to download the scraped images to, default is cons.train_fdir + ncpu : int + The number of cpus to use for parallel processing, default is None Returns ------- @@ -151,4 +154,8 @@ def webscraper(search:str, n_images:int=cons.n_images, home_url:str=cons.home_ur # run function and scrape srcs srcs = scrape_srcs(urls=urls, n_images=n_images, home_url=home_url) # run function to download src - multiprocess(download_src, [(src, output_dir, search) for src in srcs]) \ No newline at end of file + if ncpu == None: + for src in srcs: + download_src(src=src,output_dir=output_dir,search=search) + else: + multiprocess(func=download_src, args=[(src, output_dir, search) for src in srcs], ncpu=ncpu) \ No newline at end of file From 4bc79d0029b6b758b85067614248751739328aaf Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 09:22:38 +0000 Subject: [PATCH 05/12] Logging formatted zip file deletion --- webscrapers/utilities/download_comp_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webscrapers/utilities/download_comp_data.py b/webscrapers/utilities/download_comp_data.py index c46b524..9e0687d 100644 --- a/webscrapers/utilities/download_comp_data.py +++ b/webscrapers/utilities/download_comp_data.py @@ -79,7 +79,7 @@ def download_comp_data( # if deleting zip file if del_zip == True: for zip_fpath in zip_fpaths_list: - logging.info("deleting zip file {zip_fpath} ...") + logging.info(f"deleting zip file {zip_fpath} ...") os.remove(path = zip_fpath) @beartype From e0701da1f06583e190e50c67d41e2904de52fbf2 Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 10:58:24 +0000 Subject: [PATCH 06/12] Added pretrained AlexNet8 model. --- model/torch/AlexNet8_pretrained.py | 148 +++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 model/torch/AlexNet8_pretrained.py diff --git a/model/torch/AlexNet8_pretrained.py b/model/torch/AlexNet8_pretrained.py new file mode 100644 index 0000000..295887b --- /dev/null +++ b/model/torch/AlexNet8_pretrained.py @@ -0,0 +1,148 @@ +import numpy as np +import torch +from torch import nn +from torchvision import models +# load custom modules +from model.torch.predict import predict as predict_module +from model.torch.save import save as save_module +from model.torch.load import load as load_module +from model.torch.fit import fit as fit_module +from model.torch.validate import validate as validate_module +from model.torch.EarlyStopper import EarlyStopper +from typing import Union +from beartype import beartype + +class AlexNet8_pretrained(nn.Module): + def __init__(self, num_classes=1000): + super(AlexNet8_pretrained, self).__init__() + self.model_id = "AlexNet8_pretrained" + self.alexnet = models.alexnet(weights ="DEFAULT") + self.num_ftrs = self.alexnet.classifier[len(self.alexnet.classifier)-1].out_features + self.classifier = nn.Sequential(nn.Linear(in_features=self.num_ftrs, out_features=num_classes)) + + @beartype + def forward(self, x): + """Applies a forward pass across an array x + + Parameters + ---------- + x : array + The array to apply a forward pass to + + Returns + ------- + array + The output array from the forward pass + """ + x = self.alexnet(x) + x = self.classifier(x) + return x + + @beartype + def fit(self, device:torch.device, criterion:torch.nn.CrossEntropyLoss, optimizer:torch.optim.SGD, train_dataloader:torch.utils.data.DataLoader, num_epochs:int=4, scheduler:Union[torch.optim.lr_scheduler.ReduceLROnPlateau,None]=None, valid_dataLoader:Union[torch.utils.data.DataLoader,None]=None, early_stopper:Union[EarlyStopper,None]=None, checkpoints_dir:Union[str,None]=None, load_epoch_checkpoint:Union[int,None]=None): + """Fits model to specified data loader given the criterion and optimizer + + Parameters + ---------- + device : torch.device + The torch device to use when fitting the model + criterion : torch.nn.CrossEntropyLoss + The criterion to use when fitting the model + optimizer : torch.optim.SGD + The torch optimizer to use when fitting the model + train_dataloader : torch.utils.data.DataLoader + The torch data loader to use when fitting the model + num_epochs : int + The number of training epochs, default is 4 + scheduler : torch.optim.lr_scheduler.ReduceLROnPlateau + The torch scheduler to use when fitting the model, default is None + valid_dataLoader : torch.utils.data.DataLoader + The torch data loader to use for validation when fitting the model, default is None + early_stopper : EarlyStopper + The EarlyStopper object for halting fitting when performing validation, default is None + checkpoints_dir : str + The local folder location where model epoch checkpoints are to be read and wrote to, default is None + load_epoch_checkpoint : int + The epoch checkpoint to load and start from, default is None + + Returns + ------- + """ + self, self.model_fit = fit_module(self, device, criterion, optimizer, train_dataloader, num_epochs, scheduler, valid_dataLoader, early_stopper, checkpoints_dir, load_epoch_checkpoint) + + @beartype + def validate(self, device:torch.device, dataloader:torch.utils.data.DataLoader, criterion:torch.nn.CrossEntropyLoss) -> tuple: + """Calculates validation loss and accuracy + + Parameters + ---------- + device : torch.device + The torch device to use when fitting the model + dataloader : torch.utils.data.DataLoader + The torch data loader to use when fitting the model + criterion : torch.nn.CrossEntropyLoss + The criterion to use when fitting the model + + Returns + ------- + tuple + The validation loss and accuracy + """ + valid_loss, valid_acc = validate_module(self, device, dataloader, criterion) + return (valid_loss, valid_acc) + + @beartype + def predict(self, dataloader:torch.utils.data.DataLoader, device:torch.device) -> np.ndarray: + """Predicts probabilities for a given data loader + + Parameters + ---------- + dataloader : torch.utils.data.DataLoader + The torch data loader to use when fitting the model + device : torch.device + The torch device to use when fitting the model + + Returns + ------- + np.ndarry + The dataloader target probabilities + """ + proba = predict_module(self, dataloader, device) + return proba + + + @beartype + def save(self, output_fpath:str) -> str: + """Writes a torch model to disk as a file + + Parameters + ---------- + output_fpath : str + The output file location to write the torch model to disk + + Returns + ------- + str + The model data message status + """ + msg = save_module(self, output_fpath) + return msg + + @beartype + def load(self, input_fpath:str, weights_only:bool=False) -> str: + """Loads a torch model from disk as a file + + Parameters + ---------- + input_fpath : str + The input file location to load the torch model from disk + weights_only : bool + Whether loading just the model weights or the full serialised model object, default is False + + Returns + ------- + str + The load model message status + """ + msg = load_module(self, input_fpath, weights_only=weights_only) + return msg \ No newline at end of file From b0a2a7f6fde0b4849935a722eb62b4e62e9fb174 Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 10:58:59 +0000 Subject: [PATCH 07/12] Fine tuned pretrained AlexNet8 model --- model/prg_torch_model.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/model/prg_torch_model.py b/model/prg_torch_model.py index 8941c43..d70e718 100644 --- a/model/prg_torch_model.py +++ b/model/prg_torch_model.py @@ -18,8 +18,7 @@ # load custom scripts import cons from model.torch.VGG16_pretrained import VGG16_pretrained -from model.torch.AlexNet8 import AlexNet8 -from model.torch.LeNet5 import LeNet5 +from model.torch.AlexNet8_pretrained import AlexNet8_pretrained from model.torch.CustomDataset import CustomDataset from model.torch.EarlyStopper import EarlyStopper from model.utilities.plot_model import plot_model_fit @@ -65,7 +64,7 @@ image_filepaths=np.array([os.path.join(cons.train_fdir, x) for x in os.listdir(cons.train_fdir)]) np.random.shuffle(image_filepaths) # create torch load images object - sample_size = 30000 + sample_size = 20000 torchLoadImages = TorchLoadImages(torch_transforms=torch_transforms, n_workers=None) df = pd.DataFrame.from_records(torchLoadImages.loadImages(image_filepaths[0:sample_size])) # only consider images with 3 dimensions @@ -116,9 +115,8 @@ logging.info("Initiate torch model...") logging.info(f"device: {device}") # initiate cnn architecture - #model = LeNet5(num_classes=2) - #model = AlexNet8(num_classes=2).to(device) - model = VGG16_pretrained(num_classes=2).to(device) + model = AlexNet8_pretrained(num_classes=2).to(device) + #model = VGG16_pretrained(num_classes=2).to(device) if device == "cuda": model = nn.DataParallel(model) model = model.to(device) @@ -152,9 +150,8 @@ logging.info("Load fitted torch model from disk...") # load model - #model = LeNet5(num_classes=2).to(device) - #model = AlexNet8(num_classes=2).to(device) - model = VGG16_pretrained(num_classes=2).to(device) + model = AlexNet8_pretrained(num_classes=2).to(device) + #model = VGG16_pretrained(num_classes=2).to(device) model.load(input_fpath=cons.torch_model_pt_fpath) timeLogger.logTime(parentKey="ModelSerialisation", subKey="Load") From dc54045a19b47f09dc1b6b62614dc53eb0f36e3f Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 11:24:25 +0000 Subject: [PATCH 08/12] Defining torch model once outside of main function --- model/prg_torch_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/model/prg_torch_model.py b/model/prg_torch_model.py index d70e718..d5f29f6 100644 --- a/model/prg_torch_model.py +++ b/model/prg_torch_model.py @@ -32,6 +32,10 @@ # device configuration device = torch.device('cuda' if torch.cuda.is_available() and cons.check_gpu else 'cpu') +# initialise model +model = AlexNet8_pretrained(num_classes=2).to(device) +#model = VGG16_pretrained(num_classes=2).to(device) + random_state = 42 torch_transforms = transforms.Compose([ @@ -115,8 +119,6 @@ logging.info("Initiate torch model...") logging.info(f"device: {device}") # initiate cnn architecture - model = AlexNet8_pretrained(num_classes=2).to(device) - #model = VGG16_pretrained(num_classes=2).to(device) if device == "cuda": model = nn.DataParallel(model) model = model.to(device) @@ -150,8 +152,6 @@ logging.info("Load fitted torch model from disk...") # load model - model = AlexNet8_pretrained(num_classes=2).to(device) - #model = VGG16_pretrained(num_classes=2).to(device) model.load(input_fpath=cons.torch_model_pt_fpath) timeLogger.logTime(parentKey="ModelSerialisation", subKey="Load") From 865b71bc7cf3e71a8b85df7206a5325ba7ef7022 Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 13:44:08 +0000 Subject: [PATCH 09/12] Training with VGG16, added comment reference to ResNet50 --- model/prg_torch_model.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/model/prg_torch_model.py b/model/prg_torch_model.py index d5f29f6..3c762bf 100644 --- a/model/prg_torch_model.py +++ b/model/prg_torch_model.py @@ -17,6 +17,7 @@ # load custom scripts import cons +from model.torch.ResNet50_pretrained import ResNet50_pretrained from model.torch.VGG16_pretrained import VGG16_pretrained from model.torch.AlexNet8_pretrained import AlexNet8_pretrained from model.torch.CustomDataset import CustomDataset @@ -33,8 +34,9 @@ device = torch.device('cuda' if torch.cuda.is_available() and cons.check_gpu else 'cpu') # initialise model -model = AlexNet8_pretrained(num_classes=2).to(device) -#model = VGG16_pretrained(num_classes=2).to(device) +#model = AlexNet8_pretrained(num_classes=2).to(device) +model = VGG16_pretrained(num_classes=2).to(device) +#model = ResNet50_pretrained(num_classes=2).to(device) random_state = 42 From 74f8c7faf34ab38dfbd6d953a7443e3b8232c9aa Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 13:44:31 +0000 Subject: [PATCH 10/12] Replaced pretrained = True with weights = DEFAULT --- model/torch/ResNet50_pretrained.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/torch/ResNet50_pretrained.py b/model/torch/ResNet50_pretrained.py index be33d6b..85a1f2d 100644 --- a/model/torch/ResNet50_pretrained.py +++ b/model/torch/ResNet50_pretrained.py @@ -16,7 +16,7 @@ class ResNet50_pretrained(nn.Module): def __init__(self, num_classes=1000): self.model_id = "ResNet50_pretrained" super(ResNet50_pretrained, self).__init__() - self.resnet = models.resnet50(pretrained=True) + self.resnet = models.resnet50(weights ="DEFAULT") self.num_ftrs = self.resnet.fc.out_features self.classifier = nn.Sequential(nn.Linear(in_features=self.num_ftrs, out_features=num_classes)) From 1b95daa7ddf7656f91200166f8950360cd8ec5fc Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 13:47:25 +0000 Subject: [PATCH 11/12] Removed keras info from README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0d23333..95c094e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This git repository contains code and configurations for implementing a Convolutional Neural Network to classify images containing cats or dogs. The data was sourced from the [dogs-vs-cats](https://www.kaggle.com/competitions/dogs-vs-cats/overview) Kaggle competition, and also from [freeimages.com](https://www.freeimages.com/) using a web scraper. -Two models were trained to classify the images; an AlexNet8 model via Keras and a VGG16 model via Torch. +Three pretrained models were fine tuned to classify the images using PyTorch; AlexNet8, VGG16 and ResNet50. Docker containers were used to deploy the application on an EC2 spot instances in order to scale up hardware and computation power. @@ -16,7 +16,7 @@ The images were further normalised using rotations, scaling, zooming, flipping a ![Generator Plot](report/torch/generator_plot.jpg) -Models were trained across 10 to 25 epochs using stochastic gradient descent and cross entropy loss. Learning rate reduction on plateau and early stopping were implemented as part of training procedure. +The pretrained models were fine tuned across 10 epochs using stochastic gradient descent and cross entropy loss. Learning rate reduction on plateau and early stopping were implemented as part of training procedure. ![Predicted Images](report/torch/pred_images.jpg) @@ -24,7 +24,7 @@ See the analysis results notebook for a further details on the analysis; includi * https://nbviewer.org/github/oislen/CatClassifier/blob/main/report/torch_analysis_results.ipynb -Master serialised copies of the trainined models are available on Kaggle: +Master serialised copies of the fine tuned models are available on Kaggle: * https://www.kaggle.com/models/oislen/cat-classifier-cnn-models From 1c8988a3094af8ffe49626fd23fcb63008330035 Mon Sep 17 00:00:00 2001 From: Oisin Date: Mon, 17 Feb 2025 14:20:19 +0000 Subject: [PATCH 12/12] Pulling models from version 2 of default model repo --- webscrapers/cons.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webscrapers/cons.py b/webscrapers/cons.py index 0850267..a91605c 100644 --- a/webscrapers/cons.py +++ b/webscrapers/cons.py @@ -29,7 +29,7 @@ del_zip = True # set kaggle model detailes -model_instance_url="oislen/cat-classifier-cnn-models/pyTorch/default/1" +model_instance_url="oislen/cat-classifier-cnn-models/pyTorch/default/2" # webscraping constants n_images = 6000