├── results ├── .gitkeep └── pretrained_models │ └── .gitkeep ├── samples └── .gitkeep ├── figure └── n01440764_36.JPEG ├── requirements.txt ├── data ├── README.md └── ImageNet_1K_labels_map.txt ├── scripts ├── preprocess_imagenet.sh └── preprocess_mini_imagenet.py ├── .gitignore ├── config.py ├── test.py ├── inference.py ├── README.md ├── utils.py ├── model.py ├── dataset.py ├── imgproc.py ├── LICENSE └── train.py /results/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /results/pretrained_models/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /figure/n01440764_36.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lornatang/ResNet-PyTorch/HEAD/figure/n01440764_36.JPEG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow==10.3.0 2 | torch==2.7.1 3 | torchvision==0.13.1+cu116 4 | numpy==1.23.1 5 | opencv-python==4.8.1.78 -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Step1: Download datasets 4 | 5 | Contains MNIST, CIFAR10&CIFAR100, TinyImageNet_200, MiniImageNet_1K, ImageNet_1K, Caltech101&Caltech256 and more etc. 6 | 7 | - [Google Driver](https://drive.google.com/drive/folders/1f-NSpZc07Qlzhgi6EbBEI1wTkN1MxPbQ?usp=sharing) 8 | - [Baidu Driver](https://pan.baidu.com/s/1arNM38vhDT7p4jKeD4sqwA?pwd=llot) 9 | 10 | ## Step2: Prepare the dataset in the following format 11 | 12 | ```text 13 | # Dataset struct 14 | - ImageNet_1K 15 | - ILSVRC2012_img_train 16 | - ILSVRC2012_img_train.tar 17 | - ILSVRC2012_img_val 18 | - ILSVRC2012_img_val.tar 19 | - valprep.sh 20 | ``` 21 | 22 | ## Step3: Preprocess the dataset 23 | 24 | ```bash 25 | cd /scripts 26 | bash preprocess_imagenet.sh 27 | ``` 28 | 29 | ## Step4: Check that the final dataset directory schema is completely correct 30 | 31 | ```text 32 | # Train dataset 33 | - ImageNet_1K 34 | - ILSVRC2012_img_train 35 | - n01440764 36 | - n01440764_18.JPEG 37 | - ... 38 | - ILSVRC2012_img_val 39 | - n01440764 40 | - ILSVRC2012_val_00000293.JPEG 41 | - ... 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /scripts/preprocess_imagenet.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | 15 | # Process ImageNet_1K train dataset 16 | # shellcheck disable=SC2164 17 | cd ../data/ImageNet_1K/ILSVRC2012_img_train 18 | tar -xvf ILSVRC2012_img_train.tar 19 | rm ILSVRC2012_img_train.tar 20 | # shellcheck disable=SC2162 21 | find . -name "*.tar" | while read NAME ; do mkdir -p "${NAME%.tar}"; tar -xvf "${NAME}" -C "${NAME%.tar}"; rm -f "${NAME}"; done 22 | # shellcheck disable=SC2035 23 | cd ../../scripts 24 | 25 | # Process ImageNet_1K valid dataset 26 | # shellcheck disable=SC2164 27 | cd ../data/ImageNet_1K/ILSVRC2012_img_val 28 | tar -xvf ILSVRC2012_img_val.tar 29 | bash valprep.sh 30 | # shellcheck disable=SC2035 31 | rm *.JPEG 32 | # shellcheck disable=SC2035 33 | rm *.sh 34 | cd ../../scripts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # custom 126 | .idea 127 | .vscode 128 | 129 | # Mac configure file. 130 | .DS_Store 131 | 132 | # Program run create directory. 133 | data 134 | results 135 | samples -------------------------------------------------------------------------------- /scripts/preprocess_mini_imagenet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import csv 15 | import os 16 | 17 | from PIL import Image 18 | 19 | train_csv_path = "../data/MiniImageNet_1K/original/train.csv" 20 | valid_csv_path = "../data/MiniImageNet_1K/original/valid.csv" 21 | test_csv_path = "../data/MiniImageNet_1K/original/test.csv" 22 | 23 | inputs_images_dir = "../data/MiniImageNet_1K/original/mini_imagenet/images" 24 | output_images_dir = "../data/MiniImageNet_1K/" 25 | 26 | train_label = {} 27 | val_label = {} 28 | test_label = {} 29 | with open(train_csv_path) as csvfile: 30 | csv_reader = csv.reader(csvfile) 31 | birth_header = next(csv_reader) 32 | for row in csv_reader: 33 | train_label[row[0]] = row[1] 34 | 35 | with open(valid_csv_path) as csvfile: 36 | csv_reader = csv.reader(csvfile) 37 | birth_header = next(csv_reader) 38 | for row in csv_reader: 39 | val_label[row[0]] = row[1] 40 | 41 | with open(test_csv_path) as csvfile: 42 | csv_reader = csv.reader(csvfile) 43 | birth_header = next(csv_reader) 44 | for row in csv_reader: 45 | test_label[row[0]] = row[1] 46 | 47 | for png in os.listdir(inputs_images_dir): 48 | path = inputs_images_dir + "/" + png 49 | im = Image.open(path) 50 | if png in train_label.keys(): 51 | tmp = train_label[png] 52 | temp_path = output_images_dir + "/train" + "/" + tmp 53 | if not os.path.exists(temp_path): 54 | os.makedirs(temp_path) 55 | t = temp_path + "/" + png 56 | im.save(t) 57 | 58 | elif png in val_label.keys(): 59 | tmp = val_label[png] 60 | temp_path = output_images_dir + "/valid" + "/" + tmp 61 | if not os.path.exists(temp_path): 62 | os.makedirs(temp_path) 63 | t = temp_path + "/" + png 64 | im.save(t) 65 | 66 | elif png in test_label.keys(): 67 | tmp = test_label[png] 68 | temp_path = output_images_dir + "/test" + "/" + tmp 69 | if not os.path.exists(temp_path): 70 | os.makedirs(temp_path) 71 | t = temp_path + "/" + png 72 | im.save(t) 73 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import random 15 | 16 | import numpy as np 17 | import torch 18 | from torch.backends import cudnn 19 | 20 | # Random seed to maintain reproducible results 21 | random.seed(0) 22 | torch.manual_seed(0) 23 | np.random.seed(0) 24 | # Use GPU for training by default 25 | device = torch.device("cuda", 0) 26 | # Turning on when the image size does not change during training can speed up training 27 | cudnn.benchmark = True 28 | # Model arch name 29 | model_arch_name = "resnet18" 30 | # Model normalization parameters 31 | model_mean_parameters = [0.485, 0.456, 0.406] 32 | model_std_parameters = [0.229, 0.224, 0.225] 33 | # Model number class 34 | model_num_classes = 1000 35 | # Current configuration parameter method 36 | mode = "train" 37 | # Experiment name, easy to save weights and log files 38 | exp_name = f"{model_arch_name}-ImageNet_1K" 39 | 40 | if mode == "train": 41 | # Dataset address 42 | train_image_dir = "./data/ImageNet_1K/ILSVRC2012_img_train" 43 | valid_image_dir = "./data/ImageNet_1K/ILSVRC2012_img_val" 44 | 45 | image_size = 224 46 | batch_size = 128 47 | num_workers = 4 48 | 49 | # The address to load the pretrained model 50 | pretrained_model_weights_path = "./results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar" 51 | 52 | # Incremental training and migration training 53 | resume = "" 54 | 55 | # Total num epochs 56 | epochs = 600 57 | 58 | # Loss parameters 59 | loss_label_smoothing = 0.1 60 | loss_weights = 1.0 61 | 62 | # Optimizer parameter 63 | model_lr = 0.1 64 | model_momentum = 0.9 65 | model_weight_decay = 2e-05 66 | model_ema_decay = 0.99998 67 | 68 | # Learning rate scheduler parameter 69 | lr_scheduler_T_0 = epochs // 4 70 | lr_scheduler_T_mult = 1 71 | lr_scheduler_eta_min = 5e-5 72 | 73 | # How many iterations to print the training/validate result 74 | train_print_frequency = 200 75 | valid_print_frequency = 20 76 | 77 | if mode == "test": 78 | # Test data address 79 | test_image_dir = "./data/ImageNet_1K/ILSVRC2012_img_val" 80 | 81 | # Test dataloader parameters 82 | image_size = 224 83 | batch_size = 256 84 | num_workers = 4 85 | 86 | # How many iterations to print the testing result 87 | test_print_frequency = 20 88 | 89 | model_weights_path = "./results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar" 90 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import os 15 | import time 16 | 17 | import torch 18 | from torch import nn 19 | from torch.utils.data import DataLoader 20 | 21 | import config 22 | import model 23 | from dataset import CUDAPrefetcher, ImageDataset 24 | from utils import load_state_dict, accuracy, Summary, AverageMeter, ProgressMeter 25 | 26 | model_names = sorted( 27 | name for name in model.__dict__ if name.islower() and not name.startswith("__") and callable(model.__dict__[name])) 28 | 29 | 30 | def build_model() -> nn.Module: 31 | resnet_model = model.__dict__[config.model_arch_name](num_classes=config.model_num_classes) 32 | resnet_model = resnet_model.to(device=config.device, memory_format=torch.channels_last) 33 | 34 | return resnet_model 35 | 36 | 37 | def load_dataset() -> CUDAPrefetcher: 38 | test_dataset = ImageDataset(config.test_image_dir, 39 | config.image_size, 40 | config.model_mean_parameters, 41 | config.model_std_parameters, 42 | "Test") 43 | test_dataloader = DataLoader(test_dataset, 44 | batch_size=config.batch_size, 45 | shuffle=False, 46 | num_workers=config.num_workers, 47 | pin_memory=True, 48 | drop_last=False, 49 | persistent_workers=True) 50 | 51 | # Place all data on the preprocessing data loader 52 | test_prefetcher = CUDAPrefetcher(test_dataloader, config.device) 53 | 54 | return test_prefetcher 55 | 56 | 57 | def main() -> None: 58 | # Initialize the model 59 | resnet_model = build_model() 60 | print(f"Build `{config.model_arch_name}` model successfully.") 61 | 62 | # Load model weights 63 | resnet_model, _, _, _, _, _ = load_state_dict(resnet_model, config.model_weights_path) 64 | print(f"Load `{config.model_arch_name}` " 65 | f"model weights `{os.path.abspath(config.model_weights_path)}` successfully.") 66 | 67 | # Start the verification mode of the model. 68 | resnet_model.eval() 69 | 70 | # Load test dataloader 71 | test_prefetcher = load_dataset() 72 | 73 | # Calculate how many batches of data are in each Epoch 74 | batches = len(test_prefetcher) 75 | batch_time = AverageMeter("Time", ":6.3f", Summary.NONE) 76 | acc1 = AverageMeter("Acc@1", ":6.2f", Summary.AVERAGE) 77 | acc5 = AverageMeter("Acc@5", ":6.2f", Summary.AVERAGE) 78 | progress = ProgressMeter(batches, [batch_time, acc1, acc5], prefix=f"Test: ") 79 | 80 | # Initialize the number of data batches to print logs on the terminal 81 | batch_index = 0 82 | 83 | # Initialize the data loader and load the first batch of data 84 | test_prefetcher.reset() 85 | batch_data = test_prefetcher.next() 86 | 87 | # Get the initialization test time 88 | end = time.time() 89 | 90 | with torch.no_grad(): 91 | while batch_data is not None: 92 | # Transfer in-memory data to CUDA devices to speed up training 93 | images = batch_data["image"].to(device=config.device, non_blocking=True) 94 | target = batch_data["target"].to(device=config.device, non_blocking=True) 95 | 96 | # Get batch size 97 | batch_size = images.size(0) 98 | 99 | # Inference 100 | output = resnet_model(images) 101 | 102 | # measure accuracy and record loss 103 | top1, top5 = accuracy(output, target, topk=(1, 5)) 104 | acc1.update(top1[0].item(), batch_size) 105 | acc5.update(top5[0].item(), batch_size) 106 | 107 | # Calculate the time it takes to fully train a batch of data 108 | batch_time.update(time.time() - end) 109 | end = time.time() 110 | 111 | # Write the data during training to the training log file 112 | if batch_index % config.test_print_frequency == 0: 113 | progress.display(batch_index + 1) 114 | 115 | # Preload the next batch of data 116 | batch_data = test_prefetcher.next() 117 | 118 | # Add 1 to the number of data batches to ensure that the terminal prints data normally 119 | batch_index += 1 120 | 121 | # print metrics 122 | print(f"Acc@1 error: {100 - acc1.avg:.2f}%") 123 | print(f"Acc@5 error: {100 - acc5.avg:.2f}%") 124 | 125 | 126 | if __name__ == "__main__": 127 | main() 128 | -------------------------------------------------------------------------------- /inference.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import argparse 15 | import json 16 | import os 17 | 18 | import cv2 19 | import torch 20 | from PIL import Image 21 | from torch import nn 22 | from torchvision.transforms import Resize, ConvertImageDtype, Normalize 23 | 24 | import imgproc 25 | import model 26 | from utils import load_state_dict 27 | 28 | model_names = sorted( 29 | name for name in model.__dict__ if name.islower() and not name.startswith("__") and callable(model.__dict__[name])) 30 | 31 | 32 | def load_class_label(class_label_file: str, num_classes: int) -> list: 33 | class_label = json.load(open(class_label_file)) 34 | class_label_list = [class_label[str(i)] for i in range(num_classes)] 35 | 36 | return class_label_list 37 | 38 | 39 | def choice_device(device_type: str) -> torch.device: 40 | # Select model processing equipment type 41 | if device_type == "cuda": 42 | device = torch.device("cuda", 0) 43 | else: 44 | device = torch.device("cpu") 45 | return device 46 | 47 | 48 | def build_model(model_arch_name: str, model_num_classes: int, device: torch.device) -> [nn.Module, nn.Module]: 49 | resnet_model = model.__dict__[model_arch_name](num_classes=model_num_classes) 50 | resnet_model = resnet_model.to(device=device, memory_format=torch.channels_last) 51 | 52 | return resnet_model 53 | 54 | 55 | def preprocess_image(image_path: str, image_size: int, device: torch.device) -> torch.Tensor: 56 | image = cv2.imread(image_path) 57 | 58 | # BGR to RGB 59 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 60 | 61 | # OpenCV convert PIL 62 | image = Image.fromarray(image) 63 | 64 | # Resize to 224 65 | image = Resize([image_size, image_size])(image) 66 | # Convert image data to pytorch format data 67 | tensor = imgproc.image_to_tensor(image, False, False).unsqueeze_(0) 68 | # Convert a tensor image to the given ``dtype`` and scale the values accordingly 69 | tensor = ConvertImageDtype(torch.float)(tensor) 70 | # Normalize a tensor image with mean and standard deviation. 71 | tensor = Normalize(args.model_mean_parameters, args.model_std_parameters)(tensor) 72 | 73 | # Transfer tensor channel image format data to CUDA device 74 | tensor = tensor.to(device=device, memory_format=torch.channels_last, non_blocking=True) 75 | 76 | return tensor 77 | 78 | 79 | def main(): 80 | # Get the label name corresponding to the drawing 81 | class_label_map = load_class_label(args.class_label_file, args.model_num_classes) 82 | 83 | device = choice_device(args.device_type) 84 | 85 | # Initialize the model 86 | resnet_model = build_model(args.model_arch_name, args.model_num_classes, device) 87 | print(f"Build `{args.model_arch_name}` model successfully.") 88 | 89 | # Load model weights 90 | resnet_model, _, _, _, _, _ = load_state_dict(resnet_model, args.model_weights_path) 91 | print(f"Load `{args.model_arch_name}` model weights `{os.path.abspath(args.model_weights_path)}` successfully.") 92 | 93 | # Start the verification mode of the model. 94 | resnet_model.eval() 95 | 96 | tensor = preprocess_image(args.image_path, args.image_size, device) 97 | 98 | # Inference 99 | with torch.no_grad(): 100 | output = resnet_model(tensor) 101 | 102 | # Calculate the five categories with the highest classification probability 103 | prediction_class_index = torch.topk(output, k=5).indices.squeeze(0).tolist() 104 | 105 | # Print classification results 106 | for class_index in prediction_class_index: 107 | prediction_class_label = class_label_map[class_index] 108 | prediction_class_prob = torch.softmax(output, dim=1)[0, class_index].item() 109 | print(f"{prediction_class_label:<75} ({prediction_class_prob * 100:.2f}%)") 110 | 111 | 112 | if __name__ == "__main__": 113 | parser = argparse.ArgumentParser() 114 | parser.add_argument("--model_arch_name", type=str, default="resnet18") 115 | parser.add_argument("--model_mean_parameters", type=list, default=[0.485, 0.456, 0.406]) 116 | parser.add_argument("--model_std_parameters", type=list, default=[0.229, 0.224, 0.225]) 117 | parser.add_argument("--class_label_file", type=str, default="./data/ImageNet_1K_labels_map.txt") 118 | parser.add_argument("--model_num_classes", type=int, default=1000) 119 | parser.add_argument("--model_weights_path", type=str, default="./results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar") 120 | parser.add_argument("--image_path", type=str, default="./figure/n01440764_36.JPEG") 121 | parser.add_argument("--image_size", type=int, default=224) 122 | parser.add_argument("--device_type", type=str, default="cpu", choices=["cpu", "cuda"]) 123 | args = parser.parse_args() 124 | 125 | main() 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ResNet-PyTorch 2 | 3 | ## Overview 4 | 5 | This repository contains an op-for-op PyTorch reimplementation 6 | of [Searching for ResNet](https://arxiv.org/pdf/1512.03385v1.pdf). 7 | 8 | ## Table of contents 9 | 10 | - [ResNet-PyTorch](#resnet-pytorch) 11 | - [Overview](#overview) 12 | - [Table of contents](#table-of-contents) 13 | - [Download weights](#download-weights) 14 | - [Download datasets](#download-datasets) 15 | - [How Test and Train](#how-test-and-train) 16 | - [Test](#test) 17 | - [Train model](#train-model) 18 | - [Resume train model](#resume-train-model) 19 | - [Result](#result) 20 | - [Contributing](#contributing) 21 | - [Credit](#credit) 22 | - [Deep Residual Learning for Image Recognition](#deep-residual-learning-for-image-recognition) 23 | 24 | ## Download weights 25 | 26 | - [Google Driver](https://drive.google.com/drive/folders/17ju2HN7Y6pyPK2CC_AqnAfTOe9_3hCQ8?usp=sharing) 27 | - [Baidu Driver](https://pan.baidu.com/s/1yNs4rqIb004-NKEdKBJtYg?pwd=llot) 28 | 29 | ## Download datasets 30 | 31 | Contains MNIST, CIFAR10&CIFAR100, TinyImageNet_200, MiniImageNet_1K, ImageNet_1K, Caltech101&Caltech256 and more etc. 32 | 33 | - [Google Driver](https://drive.google.com/drive/folders/1f-NSpZc07Qlzhgi6EbBEI1wTkN1MxPbQ?usp=sharing) 34 | - [Baidu Driver](https://pan.baidu.com/s/1arNM38vhDT7p4jKeD4sqwA?pwd=llot) 35 | 36 | Please refer to `README.md` in the `data` directory for the method of making a dataset. 37 | 38 | ## How Test and Train 39 | 40 | Both training and testing only need to modify the `config.py` file. 41 | 42 | ### Test 43 | 44 | - line 29: `model_arch_name` change to `resnet18`. 45 | - line 31: `model_mean_parameters` change to `[0.485, 0.456, 0.406]`. 46 | - line 32: `model_std_parameters` change to `[0.229, 0.224, 0.225]`. 47 | - line 34: `model_num_classes` change to `1000`. 48 | - line 36: `mode` change to `test`. 49 | - line 89: `model_weights_path` change to `./results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar`. 50 | 51 | ```bash 52 | python3 test.py 53 | ``` 54 | 55 | ### Train model 56 | 57 | - line 29: `model_arch_name` change to `resnet18`. 58 | - line 31: `model_mean_parameters` change to `[0.485, 0.456, 0.406]`. 59 | - line 32: `model_std_parameters` change to `[0.229, 0.224, 0.225]`. 60 | - line 34: `model_num_classes` change to `1000`. 61 | - line 36: `mode` change to `train`. 62 | - line 50: `pretrained_model_weights_path` change to `./results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar`. 63 | 64 | ```bash 65 | python3 train.py 66 | ``` 67 | 68 | ### Resume train model 69 | 70 | - line 29: `model_arch_name` change to `resnet18`. 71 | - line 31: `model_mean_parameters` change to `[0.485, 0.456, 0.406]`. 72 | - line 32: `model_std_parameters` change to `[0.229, 0.224, 0.225]`. 73 | - line 34: `model_num_classes` change to `1000`. 74 | - line 36: `mode` change to `train`. 75 | - line 53: `resume` change to `./samples/resnet18-ImageNet_1K/epoch_xxx.pth.tar`. 76 | 77 | ```bash 78 | python3 train.py 79 | ``` 80 | 81 | ## Result 82 | 83 | Source of original paper results: [https://arxiv.org/pdf/1512.03385v1.pdf](https://arxiv.org/pdf/1512.03385v1.pdf)) 84 | 85 | In the following table, the top-x error value in `()` indicates the result of the project, and `-` indicates no test. 86 | 87 | | Model | Dataset | Top-1 error (val) | Top-5 error (val) | 88 | |:---------:|:-----------:|:------------------:|:-----------------:| 89 | | resnet18 | ImageNet_1K | 27.88%(**30.25%**) | -(**10.93%**) | 90 | | resnet34 | ImageNet_1K | 25.03%(**26.71%**) | 7.76%(**8.58%**) | 91 | | resnet50 | ImageNet_1K | 22.85%(**19.65%**) | 6.71%(**4.87%**) | 92 | | resnet101 | ImageNet_1K | 21.75%(**18.33%**) | 6.05%(**4.34%**) | 93 | | resnet152 | ImageNet_1K | 21.43%(**17.66%**) | 5.71%(**4.08%**) | 94 | 95 | ```bash 96 | # Download `ResNet18-ImageNet_1K-57bb63e.pth.tar` weights to `./results/pretrained_models` 97 | # More detail see `README.md` 98 | python3 ./inference.py 99 | ``` 100 | 101 | Input: 102 | 103 | 104 | 105 | Output: 106 | 107 | ```text 108 | Build `resnet18` model successfully. 109 | Load `resnet18` model weights `/ResNet-PyTorch/results/pretrained_models/ResNet18-ImageNet_1K-57bb63e.pth.tar` successfully. 110 | tench, Tinca tinca (91.46%) 111 | barracouta, snoek (7.15%) 112 | gar, garfish, garpike, billfish, Lepisosteus osseus (0.43%) 113 | coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch (0.27%) 114 | platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus (0.21%) 115 | ``` 116 | 117 | ## Contributing 118 | 119 | If you find a bug, create a GitHub issue, or even better, submit a pull request. Similarly, if you have questions, 120 | simply post them as GitHub issues. 121 | 122 | I look forward to seeing what the community does with these models! 123 | 124 | ### Credit 125 | 126 | #### Deep Residual Learning for Image Recognition 127 | 128 | *Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun* 129 | 130 | ##### Abstract 131 | 132 | Deeper neural networks are more difficult to train. We present a residual learning framework to ease the training of 133 | networks that are substantially deeper than those used previously. We explicitly reformulate the layers as learning 134 | residual functions with reference to the layer inputs, instead of learning unreferenced functions. We provide 135 | comprehensive empirical evidence showing that these residual networks are easier to optimize, and can gain accuracy from 136 | considerably increased depth. On the ImageNet dataset we evaluate residual nets with a depth of up to 152 layers---8x 137 | deeper than VGG nets but still having lower complexity. An ensemble of these residual nets achieves 3.57% error on the 138 | ImageNet test set. This result won the 1st place on the ILSVRC 2015 classification task. We also present analysis on 139 | CIFAR-10 with 100 and 1000 layers. 140 | The depth of representations is of central importance for many visual recognition tasks. Solely due to our extremely 141 | deep representations, we obtain a 28% relative improvement on the COCO object detection dataset. Deep residual nets are 142 | foundations of our submissions to ILSVRC & COCO 2015 competitions, where we also won the 1st places on the tasks of 143 | ImageNet detection, ImageNet localization, COCO detection, and COCO segmentation. 144 | 145 | [[Paper]](https://arxiv.org/pdf/1512.03385v1.pdf) 146 | 147 | ```bibtex 148 | @inproceedings{he2016deep, 149 | title={Deep residual learning for image recognition}, 150 | author={He, Kaiming and Zhang, Xiangyu and Ren, Shaoqing and Sun, Jian}, 151 | booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition}, 152 | pages={770--778}, 153 | year={2016} 154 | } 155 | ``` -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import os 15 | import shutil 16 | from enum import Enum 17 | from typing import Any, Dict, TypeVar, Optional 18 | 19 | import torch 20 | from torch import nn 21 | 22 | __all__ = [ 23 | "accuracy", "load_state_dict", "make_directory", "ovewrite_named_param", "make_divisible", "save_checkpoint", 24 | "Summary", "AverageMeter", "ProgressMeter" 25 | ] 26 | 27 | V = TypeVar("V") 28 | 29 | 30 | def accuracy(output, target, topk=(1,)): 31 | """Computes the accuracy over the k top predictions for the specified values of k""" 32 | with torch.no_grad(): 33 | maxk = max(topk) 34 | batch_size = target.size(0) 35 | 36 | _, pred = output.topk(maxk, 1, True, True) 37 | pred = pred.t() 38 | correct = pred.eq(target.view(1, -1).expand_as(pred)) 39 | 40 | results = [] 41 | for k in topk: 42 | correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True) 43 | results.append(correct_k.mul_(100.0 / batch_size)) 44 | return results 45 | 46 | 47 | def load_state_dict( 48 | model: nn.Module, 49 | model_weights_path: str, 50 | ema_model: nn.Module = None, 51 | start_epoch: int = None, 52 | best_acc1: float = None, 53 | optimizer: torch.optim.Optimizer = None, 54 | scheduler: torch.optim.lr_scheduler = None, 55 | load_mode: str = None, 56 | ) -> [nn.Module, nn.Module, str, int, float, torch.optim.Optimizer, torch.optim.lr_scheduler]: 57 | # Load model weights 58 | checkpoint = torch.load(model_weights_path, map_location=lambda storage, loc: storage) 59 | 60 | if load_mode == "resume": 61 | # Restore the parameters in the training node to this point 62 | start_epoch = checkpoint["epoch"] 63 | best_acc1 = checkpoint["best_acc1"] 64 | # Load model state dict. Extract the fitted model weights 65 | model_state_dict = model.state_dict() 66 | state_dict = {k: v for k, v in checkpoint["state_dict"].items() if k in model_state_dict.keys()} 67 | # Overwrite the model weights to the current model (base model) 68 | model_state_dict.update(state_dict) 69 | model.load_state_dict(model_state_dict) 70 | # Load ema model state dict. Extract the fitted model weights 71 | ema_model_state_dict = ema_model.state_dict() 72 | ema_state_dict = {k: v for k, v in checkpoint["ema_state_dict"].items() if k in ema_model_state_dict.keys()} 73 | # Overwrite the model weights to the current model (ema model) 74 | ema_model_state_dict.update(ema_state_dict) 75 | ema_model.load_state_dict(ema_model_state_dict) 76 | # Load the optimizer model 77 | optimizer.load_state_dict(checkpoint["optimizer"]) 78 | # Load the scheduler model 79 | scheduler.load_state_dict(checkpoint["scheduler"]) 80 | else: 81 | # Load model state dict. Extract the fitted model weights 82 | model_state_dict = model.state_dict() 83 | state_dict = {k: v for k, v in checkpoint["state_dict"].items() if 84 | k in model_state_dict.keys() and v.size() == model_state_dict[k].size()} 85 | # Overwrite the model weights to the current model 86 | model_state_dict.update(state_dict) 87 | model.load_state_dict(model_state_dict) 88 | 89 | return model, ema_model, start_epoch, best_acc1, optimizer, scheduler 90 | 91 | 92 | def make_directory(dir_path: str) -> None: 93 | if not os.path.exists(dir_path): 94 | os.makedirs(dir_path) 95 | 96 | 97 | def ovewrite_named_param(kwargs: Dict[str, Any], param: str, new_value: V) -> None: 98 | if param in kwargs: 99 | if kwargs[param] != new_value: 100 | raise ValueError(f"The parameter '{param}' expected value {new_value} but got {kwargs[param]} instead.") 101 | else: 102 | kwargs[param] = new_value 103 | 104 | 105 | def make_divisible(v: float, divisor: int, min_value: Optional[int] = None) -> int: 106 | """Copy from: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py 107 | """ 108 | if min_value is None: 109 | min_value = divisor 110 | new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) 111 | # Make sure that round down does not go down by more than 10%. 112 | if new_v < 0.9 * v: 113 | new_v += divisor 114 | return new_v 115 | 116 | 117 | def save_checkpoint( 118 | state_dict: dict, 119 | file_name: str, 120 | samples_dir: str, 121 | results_dir: str, 122 | is_best: bool = False, 123 | is_last: bool = False, 124 | ) -> None: 125 | checkpoint_path = os.path.join(samples_dir, file_name) 126 | torch.save(state_dict, checkpoint_path) 127 | 128 | if is_best: 129 | shutil.copyfile(checkpoint_path, os.path.join(results_dir, "best.pth.tar")) 130 | if is_last: 131 | shutil.copyfile(checkpoint_path, os.path.join(results_dir, "last.pth.tar")) 132 | 133 | 134 | class Summary(Enum): 135 | NONE = 0 136 | AVERAGE = 1 137 | SUM = 2 138 | COUNT = 3 139 | 140 | 141 | class AverageMeter(object): 142 | def __init__(self, name, fmt=":f", summary_type=Summary.AVERAGE): 143 | self.name = name 144 | self.fmt = fmt 145 | self.summary_type = summary_type 146 | self.reset() 147 | 148 | def reset(self): 149 | self.val = 0 150 | self.avg = 0 151 | self.sum = 0 152 | self.count = 0 153 | 154 | def update(self, val, n=1): 155 | self.val = val 156 | self.sum += val * n 157 | self.count += n 158 | self.avg = self.sum / self.count 159 | 160 | def __str__(self): 161 | fmtstr = "{name} {val" + self.fmt + "} ({avg" + self.fmt + "})" 162 | return fmtstr.format(**self.__dict__) 163 | 164 | def summary(self): 165 | if self.summary_type is Summary.NONE: 166 | fmtstr = "" 167 | elif self.summary_type is Summary.AVERAGE: 168 | fmtstr = "{name} {avg:.2f}" 169 | elif self.summary_type is Summary.SUM: 170 | fmtstr = "{name} {sum:.2f}" 171 | elif self.summary_type is Summary.COUNT: 172 | fmtstr = "{name} {count:.2f}" 173 | else: 174 | raise ValueError(f"Invalid summary type {self.summary_type}") 175 | 176 | return fmtstr.format(**self.__dict__) 177 | 178 | 179 | class ProgressMeter(object): 180 | def __init__(self, num_batches, meters, prefix=""): 181 | self.batch_fmtstr = self._get_batch_fmtstr(num_batches) 182 | self.meters = meters 183 | self.prefix = prefix 184 | 185 | def display(self, batch): 186 | entries = [self.prefix + self.batch_fmtstr.format(batch)] 187 | entries += [str(meter) for meter in self.meters] 188 | print("\t".join(entries)) 189 | 190 | def display_summary(self): 191 | entries = [" *"] 192 | entries += [meter.summary() for meter in self.meters] 193 | print(" ".join(entries)) 194 | 195 | def _get_batch_fmtstr(self, num_batches): 196 | num_digits = len(str(num_batches // 1)) 197 | fmt = "{:" + str(num_digits) + "d}" 198 | return "[" + fmt + "/" + fmt.format(num_batches) + "]" 199 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | from typing import Any, List, Type, Union, Optional 15 | 16 | import torch 17 | from torch import Tensor 18 | from torch import nn 19 | 20 | __all__ = [ 21 | "ResNet", 22 | "resnet18", 23 | ] 24 | 25 | 26 | class _BasicBlock(nn.Module): 27 | expansion: int = 1 28 | 29 | def __init__( 30 | self, 31 | in_channels: int, 32 | out_channels: int, 33 | stride: int, 34 | downsample: Optional[nn.Module] = None, 35 | groups: int = 1, 36 | base_channels: int = 64, 37 | ) -> None: 38 | super(_BasicBlock, self).__init__() 39 | self.stride = stride 40 | self.downsample = downsample 41 | self.groups = groups 42 | self.base_channels = base_channels 43 | 44 | self.conv1 = nn.Conv2d(in_channels, out_channels, (3, 3), (stride, stride), (1, 1), bias=False) 45 | self.bn1 = nn.BatchNorm2d(out_channels) 46 | self.relu = nn.ReLU(True) 47 | self.conv2 = nn.Conv2d(out_channels, out_channels, (3, 3), (1, 1), (1, 1), bias=False) 48 | self.bn2 = nn.BatchNorm2d(out_channels) 49 | 50 | def forward(self, x: Tensor) -> Tensor: 51 | identity = x 52 | 53 | out = self.conv1(x) 54 | out = self.bn1(out) 55 | out = self.relu(out) 56 | 57 | out = self.conv2(out) 58 | out = self.bn2(out) 59 | 60 | if self.downsample is not None: 61 | identity = self.downsample(x) 62 | 63 | out = torch.add(out, identity) 64 | out = self.relu(out) 65 | 66 | return out 67 | 68 | 69 | class _Bottleneck(nn.Module): 70 | expansion: int = 4 71 | 72 | def __init__( 73 | self, 74 | in_channels: int, 75 | out_channels: int, 76 | stride: int, 77 | downsample: Optional[nn.Module] = None, 78 | groups: int = 1, 79 | base_channels: int = 64, 80 | ) -> None: 81 | super(_Bottleneck, self).__init__() 82 | self.stride = stride 83 | self.downsample = downsample 84 | self.groups = groups 85 | self.base_channels = base_channels 86 | 87 | channels = int(out_channels * (base_channels / 64.0)) * groups 88 | 89 | self.conv1 = nn.Conv2d(in_channels, channels, (1, 1), (1, 1), (0, 0), bias=False) 90 | self.bn1 = nn.BatchNorm2d(channels) 91 | self.conv2 = nn.Conv2d(channels, channels, (3, 3), (stride, stride), (1, 1), groups=groups, bias=False) 92 | self.bn2 = nn.BatchNorm2d(channels) 93 | self.conv3 = nn.Conv2d(channels, int(out_channels * self.expansion), (1, 1), (1, 1), (0, 0), bias=False) 94 | self.bn3 = nn.BatchNorm2d(int(out_channels * self.expansion)) 95 | self.relu = nn.ReLU(True) 96 | 97 | def forward(self, x: Tensor) -> Tensor: 98 | identity = x 99 | 100 | out = self.conv1(x) 101 | out = self.bn1(out) 102 | out = self.relu(out) 103 | 104 | out = self.conv2(out) 105 | out = self.bn2(out) 106 | out = self.relu(out) 107 | 108 | out = self.conv3(out) 109 | out = self.bn3(out) 110 | 111 | if self.downsample is not None: 112 | identity = self.downsample(x) 113 | 114 | out = torch.add(out, identity) 115 | out = self.relu(out) 116 | 117 | return out 118 | 119 | 120 | class ResNet(nn.Module): 121 | 122 | def __init__( 123 | self, 124 | arch_cfg: List[int], 125 | block: Type[Union[_BasicBlock, _Bottleneck]], 126 | groups: int = 1, 127 | channels_per_group: int = 64, 128 | num_classes: int = 1000, 129 | ) -> None: 130 | super(ResNet, self).__init__() 131 | self.in_channels = 64 132 | self.dilation = 1 133 | self.groups = groups 134 | self.base_channels = channels_per_group 135 | 136 | self.conv1 = nn.Conv2d(3, self.in_channels, (7, 7), (2, 2), (3, 3), bias=False) 137 | self.bn1 = nn.BatchNorm2d(self.in_channels) 138 | self.relu = nn.ReLU(True) 139 | self.maxpool = nn.MaxPool2d((3, 3), (2, 2), (1, 1)) 140 | 141 | self.layer1 = self._make_layer(arch_cfg[0], block, 64, 1) 142 | self.layer2 = self._make_layer(arch_cfg[1], block, 128, 2) 143 | self.layer3 = self._make_layer(arch_cfg[2], block, 256, 2) 144 | self.layer4 = self._make_layer(arch_cfg[3], block, 512, 2) 145 | 146 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 147 | 148 | self.fc = nn.Linear(512 * block.expansion, num_classes) 149 | 150 | # Initialize neural network weights 151 | self._initialize_weights() 152 | 153 | def _make_layer( 154 | self, 155 | repeat_times: int, 156 | block: Type[Union[_BasicBlock, _Bottleneck]], 157 | channels: int, 158 | stride: int = 1, 159 | ) -> nn.Sequential: 160 | downsample = None 161 | 162 | if stride != 1 or self.in_channels != channels * block.expansion: 163 | downsample = nn.Sequential( 164 | nn.Conv2d(self.in_channels, channels * block.expansion, (1, 1), (stride, stride), (0, 0), bias=False), 165 | nn.BatchNorm2d(channels * block.expansion), 166 | ) 167 | 168 | layers = [ 169 | block( 170 | self.in_channels, 171 | channels, 172 | stride, 173 | downsample, 174 | self.groups, 175 | self.base_channels 176 | ) 177 | ] 178 | self.in_channels = channels * block.expansion 179 | for _ in range(1, repeat_times): 180 | layers.append( 181 | block( 182 | self.in_channels, 183 | channels, 184 | 1, 185 | None, 186 | self.groups, 187 | self.base_channels, 188 | ) 189 | ) 190 | 191 | return nn.Sequential(*layers) 192 | 193 | def forward(self, x: Tensor) -> Tensor: 194 | out = self._forward_impl(x) 195 | 196 | return out 197 | 198 | # Support torch.script function 199 | def _forward_impl(self, x: Tensor) -> Tensor: 200 | out = self.conv1(x) 201 | out = self.bn1(out) 202 | out = self.relu(out) 203 | out = self.maxpool(out) 204 | 205 | out = self.layer1(out) 206 | out = self.layer2(out) 207 | out = self.layer3(out) 208 | out = self.layer4(out) 209 | 210 | out = self.avgpool(out) 211 | out = torch.flatten(out, 1) 212 | out = self.fc(out) 213 | 214 | return out 215 | 216 | def _initialize_weights(self) -> None: 217 | for module in self.modules(): 218 | if isinstance(module, nn.Conv2d): 219 | nn.init.kaiming_normal_(module.weight, mode="fan_out", nonlinearity="relu") 220 | elif isinstance(module, (nn.BatchNorm2d, nn.GroupNorm)): 221 | nn.init.constant_(module.weight, 1) 222 | nn.init.constant_(module.bias, 0) 223 | 224 | 225 | def resnet18(**kwargs: Any) -> ResNet: 226 | model = ResNet([2, 2, 2, 2], _BasicBlock, **kwargs) 227 | 228 | return model 229 | 230 | 231 | def resnet34(**kwargs: Any) -> ResNet: 232 | model = ResNet([3, 4, 6, 3], _BasicBlock, **kwargs) 233 | 234 | return model 235 | 236 | 237 | def resnet50(**kwargs: Any) -> ResNet: 238 | model = ResNet([3, 4, 6, 3], _Bottleneck, **kwargs) 239 | 240 | return model 241 | 242 | 243 | def resnet101(**kwargs: Any) -> ResNet: 244 | model = ResNet([3, 4, 23, 3], _Bottleneck, **kwargs) 245 | 246 | return model 247 | 248 | 249 | def resnet152(**kwargs: Any) -> ResNet: 250 | model = ResNet([3, 8, 36, 3], _Bottleneck, **kwargs) 251 | 252 | return model 253 | -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import queue 15 | import sys 16 | import threading 17 | from glob import glob 18 | 19 | import cv2 20 | import torch 21 | from PIL import Image 22 | from torch.utils.data import Dataset, DataLoader 23 | from torchvision import transforms 24 | from torchvision.datasets.folder import find_classes 25 | from torchvision.transforms import TrivialAugmentWide 26 | 27 | import imgproc 28 | 29 | __all__ = [ 30 | "ImageDataset", 31 | "PrefetchGenerator", "PrefetchDataLoader", "CPUPrefetcher", "CUDAPrefetcher", 32 | ] 33 | 34 | # Image formats supported by the image processing library 35 | IMG_EXTENSIONS = ("jpg", "jpeg", "png", "ppm", "bmp", "pgm", "tif", "tiff", "webp") 36 | 37 | # The delimiter is not the same between different platforms 38 | if sys.platform == "win32": 39 | delimiter = "\\" 40 | else: 41 | delimiter = "/" 42 | 43 | 44 | class ImageDataset(Dataset): 45 | """Define training/valid dataset loading methods. 46 | 47 | Args: 48 | image_dir (str): Train/Valid dataset address. 49 | image_size (int): Image size. 50 | mode (str): Data set loading method, the training data set is for data enhancement, 51 | and the verification data set is not for data enhancement. 52 | """ 53 | 54 | def __init__(self, image_dir: str, image_size: int, mean: list, std: list, mode: str) -> None: 55 | super(ImageDataset, self).__init__() 56 | # Iterate over all image paths 57 | self.image_file_paths = glob(f"{image_dir}/*/*") 58 | # Form image class label pairs by the folder where the image is located 59 | _, self.class_to_idx = find_classes(image_dir) 60 | self.image_size = image_size 61 | self.mode = mode 62 | self.delimiter = delimiter 63 | 64 | if self.mode == "Train": 65 | # Use PyTorch's own data enhancement to enlarge and enhance data 66 | self.pre_transform = transforms.Compose([ 67 | transforms.RandomResizedCrop(self.image_size), 68 | TrivialAugmentWide(), 69 | transforms.RandomRotation([0, 270]), 70 | transforms.RandomHorizontalFlip(0.5), 71 | transforms.RandomVerticalFlip(0.5), 72 | ]) 73 | elif self.mode == "Valid" or self.mode == "Test": 74 | # Use PyTorch's own data enhancement to enlarge and enhance data 75 | self.pre_transform = transforms.Compose([ 76 | transforms.Resize(256), 77 | transforms.CenterCrop([self.image_size, self.image_size]), 78 | ]) 79 | else: 80 | raise "Unsupported data read type. Please use `Train` or `Valid` or `Test`" 81 | 82 | self.post_transform = transforms.Compose([ 83 | transforms.ConvertImageDtype(torch.float), 84 | transforms.Normalize(mean, std) 85 | ]) 86 | 87 | def __getitem__(self, batch_index: int) -> [torch.Tensor, int]: 88 | image_dir, image_name = self.image_file_paths[batch_index].split(self.delimiter)[-2:] 89 | # Read a batch of image data 90 | if image_name.split(".")[-1].lower() in IMG_EXTENSIONS: 91 | image = cv2.imread(self.image_file_paths[batch_index]) 92 | target = self.class_to_idx[image_dir] 93 | else: 94 | raise ValueError(f"Unsupported image extensions, Only support `{IMG_EXTENSIONS}`, " 95 | "please check the image file extensions.") 96 | 97 | # BGR to RGB 98 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 99 | 100 | # OpenCV convert PIL 101 | image = Image.fromarray(image) 102 | 103 | # Data preprocess 104 | image = self.pre_transform(image) 105 | 106 | # Convert image data into Tensor stream format (PyTorch). 107 | # Note: The range of input and output is between [0, 1] 108 | tensor = imgproc.image_to_tensor(image, False, False) 109 | 110 | # Data postprocess 111 | tensor = self.post_transform(tensor) 112 | 113 | return {"image": tensor, "target": target} 114 | 115 | def __len__(self) -> int: 116 | return len(self.image_file_paths) 117 | 118 | 119 | class PrefetchGenerator(threading.Thread): 120 | """A fast data prefetch generator. 121 | 122 | Args: 123 | generator: Data generator. 124 | num_data_prefetch_queue (int): How many early data load queues. 125 | """ 126 | 127 | def __init__(self, generator, num_data_prefetch_queue: int) -> None: 128 | threading.Thread.__init__(self) 129 | self.queue = queue.Queue(num_data_prefetch_queue) 130 | self.generator = generator 131 | self.daemon = True 132 | self.start() 133 | 134 | def run(self) -> None: 135 | for item in self.generator: 136 | self.queue.put(item) 137 | self.queue.put(None) 138 | 139 | def __next__(self): 140 | next_item = self.queue.get() 141 | if next_item is None: 142 | raise StopIteration 143 | return next_item 144 | 145 | def __iter__(self): 146 | return self 147 | 148 | 149 | class PrefetchDataLoader(DataLoader): 150 | """A fast data prefetch dataloader. 151 | 152 | Args: 153 | num_data_prefetch_queue (int): How many early data load queues. 154 | kwargs (dict): Other extended parameters. 155 | """ 156 | 157 | def __init__(self, num_data_prefetch_queue: int, **kwargs) -> None: 158 | self.num_data_prefetch_queue = num_data_prefetch_queue 159 | super(PrefetchDataLoader, self).__init__(**kwargs) 160 | 161 | def __iter__(self): 162 | return PrefetchGenerator(super().__iter__(), self.num_data_prefetch_queue) 163 | 164 | 165 | class CPUPrefetcher: 166 | """Use the CPU side to accelerate data reading. 167 | 168 | Args: 169 | dataloader (DataLoader): Data loader. Combines a dataset and a sampler, 170 | and provides an iterable over the given dataset. 171 | """ 172 | 173 | def __init__(self, dataloader) -> None: 174 | self.original_dataloader = dataloader 175 | self.data = iter(dataloader) 176 | 177 | def next(self): 178 | try: 179 | return next(self.data) 180 | except StopIteration: 181 | return None 182 | 183 | def reset(self): 184 | self.data = iter(self.original_dataloader) 185 | 186 | def __len__(self) -> int: 187 | return len(self.original_dataloader) 188 | 189 | 190 | class CUDAPrefetcher: 191 | """Use the CUDA side to accelerate data reading. 192 | 193 | Args: 194 | dataloader (DataLoader): Data loader. Combines a dataset and a sampler, and provides an iterable over the given dataset. 195 | device (torch.device): Specify running device. 196 | """ 197 | 198 | def __init__(self, dataloader, device: torch.device): 199 | self.batch_data = None 200 | self.original_dataloader = dataloader 201 | self.device = device 202 | 203 | self.data = iter(dataloader) 204 | self.stream = torch.cuda.Stream() 205 | self.preload() 206 | 207 | def preload(self): 208 | try: 209 | self.batch_data = next(self.data) 210 | except StopIteration: 211 | self.batch_data = None 212 | return None 213 | 214 | with torch.cuda.stream(self.stream): 215 | for k, v in self.batch_data.items(): 216 | if torch.is_tensor(v): 217 | self.batch_data[k] = self.batch_data[k].to(self.device, non_blocking=True) 218 | 219 | def next(self): 220 | torch.cuda.current_stream().wait_stream(self.stream) 221 | batch_data = self.batch_data 222 | self.preload() 223 | return batch_data 224 | 225 | def reset(self): 226 | self.data = iter(self.original_dataloader) 227 | self.preload() 228 | 229 | def __len__(self) -> int: 230 | return len(self.original_dataloader) 231 | -------------------------------------------------------------------------------- /imgproc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import random 15 | from typing import Any 16 | from torch import Tensor 17 | from numpy import ndarray 18 | import cv2 19 | import numpy as np 20 | import torch 21 | from torchvision.transforms import functional as F_vision 22 | 23 | __all__ = [ 24 | "image_to_tensor", "tensor_to_image", 25 | "center_crop", "random_crop", "random_rotate", "random_vertically_flip", "random_horizontally_flip", 26 | ] 27 | 28 | 29 | def image_to_tensor(image: np.ndarray, range_norm: bool, half: bool) -> torch.Tensor: 30 | """Convert the image data type to the Tensor (NCWH) data type supported by PyTorch 31 | 32 | Args: 33 | image (np.ndarray): The image data read by ``OpenCV.imread``, the data range is [0,255] or [0, 1] 34 | range_norm (bool): Scale [0, 1] data to between [-1, 1] 35 | half (bool): Whether to convert torch.float32 similarly to torch.half type 36 | 37 | Returns: 38 | tensor (torch.Tensor): Data types supported by PyTorch 39 | 40 | Examples: 41 | >>> example_image = cv2.imread("example_image.bmp") 42 | >>> example_tensor = image_to_tensor(example_image, False, False) 43 | 44 | """ 45 | # Convert image data type to Tensor data type 46 | tensor = F_vision.to_tensor(image) 47 | 48 | # Scale the image data from [0, 1] to [-1, 1] 49 | if range_norm: 50 | tensor = tensor.mul(2.0).sub(1.0) 51 | 52 | # Convert torch.float32 image data type to torch.half image data type 53 | if half: 54 | tensor = tensor.half() 55 | 56 | return tensor 57 | 58 | 59 | def tensor_to_image(tensor: torch.Tensor, range_norm: bool, half: bool) -> Any: 60 | """Convert the Tensor(NCWH) data type supported by PyTorch to the np.ndarray(WHC) image data type 61 | 62 | Args: 63 | tensor (torch.Tensor): Data types supported by PyTorch (NCHW), the data range is [0, 1] 64 | range_norm (bool): Scale [-1, 1] data to between [0, 1] 65 | half (bool): Whether to convert torch.float32 similarly to torch.half type. 66 | 67 | Returns: 68 | image (np.ndarray): Data types supported by PIL or OpenCV 69 | 70 | Examples: 71 | >>> example_tensor = torch.randn([1,3, 256, 256], dtype=torch.float) 72 | >>> example_image = tensor_to_image(example_tensor, False, False) 73 | 74 | """ 75 | # Scale the image data from [-1, 1] to [0, 1] 76 | if range_norm: 77 | tensor = tensor.add(1.0).div(2.0) 78 | 79 | # Convert torch.float32 image data type to torch.half image data type 80 | if half: 81 | tensor = tensor.half() 82 | 83 | image = tensor.squeeze(0).permute(1, 2, 0).mul(255).clamp(0, 255).cpu().numpy().astype("uint8") 84 | 85 | return image 86 | 87 | 88 | def center_crop( 89 | images: ndarray | Tensor | list[ndarray] | list[Tensor], 90 | patch_size: int, 91 | ) -> [ndarray] or [Tensor] or [list[ndarray]] or [list[Tensor]]: 92 | if not isinstance(images, list): 93 | images = [images] 94 | 95 | # Detect input image data type 96 | input_type = "Tensor" if torch.is_tensor(images[0]) else "Numpy" 97 | 98 | if input_type == "Tensor": 99 | image_height, image_width = images[0].size()[-2:] 100 | else: 101 | image_height, image_width = images[0].shape[0:2] 102 | 103 | # Calculate the start indices of the crop 104 | top = (image_height - patch_size) // 2 105 | left = (image_width - patch_size) // 2 106 | 107 | # Crop lr image patch 108 | if input_type == "Tensor": 109 | images = [image[ 110 | :, 111 | :, 112 | top:top + patch_size, 113 | left:left + patch_size] for image in images] 114 | else: 115 | images = [image[ 116 | top:top + patch_size, 117 | left:left + patch_size, 118 | ...] for image in images] 119 | 120 | # When image number is 1 121 | if len(images) == 1: 122 | images = images[0] 123 | 124 | return images 125 | 126 | 127 | def random_crop( 128 | images: ndarray | Tensor | list[ndarray] | list[Tensor], 129 | patch_size: int, 130 | ) -> [ndarray] or [Tensor] or [list[ndarray]] or [list[Tensor]]: 131 | if not isinstance(images, list): 132 | images = [images] 133 | 134 | # Detect input image data type 135 | input_type = "Tensor" if torch.is_tensor(images[0]) else "Numpy" 136 | 137 | if input_type == "Tensor": 138 | image_height, image_width = images[0].size()[-2:] 139 | else: 140 | image_height, image_width = images[0].shape[0:2] 141 | 142 | # Just need to find the top and left coordinates of the image 143 | top = random.randint(0, image_height - patch_size) 144 | left = random.randint(0, image_width - patch_size) 145 | 146 | # Crop lr image patch 147 | if input_type == "Tensor": 148 | images = [image[ 149 | :, 150 | :, 151 | top:top + patch_size, 152 | left:left + patch_size] for image in images] 153 | else: 154 | images = [image[ 155 | top:top + patch_size, 156 | left:left + patch_size, 157 | ...] for image in images] 158 | 159 | # When image number is 1 160 | if len(images) == 1: 161 | images = images[0] 162 | 163 | return images 164 | 165 | 166 | def random_rotate( 167 | images: ndarray | Tensor | list[ndarray] | list[Tensor], 168 | angles: list, 169 | center: tuple = None, 170 | rotate_scale_factor: float = 1.0 171 | ) -> [ndarray] or [Tensor] or [list[ndarray]] or [list[Tensor]]: 172 | # Random select specific angle 173 | angle = random.choice(angles) 174 | 175 | if not isinstance(images, list): 176 | images = [images] 177 | 178 | # Detect input image data type 179 | input_type = "Tensor" if torch.is_tensor(images[0]) else "Numpy" 180 | 181 | if input_type == "Tensor": 182 | image_height, image_width = images[0].size()[-2:] 183 | else: 184 | image_height, image_width = images[0].shape[0:2] 185 | 186 | # Rotate LR image 187 | if center is None: 188 | center = (image_width // 2, image_height // 2) 189 | 190 | matrix = cv2.getRotationMatrix2D(center, angle, rotate_scale_factor) 191 | 192 | if input_type == "Tensor": 193 | images = [F_vision.rotate(image, angle, center=center) for image in images] 194 | else: 195 | images = [cv2.warpAffine(image, matrix, (image_width, image_height)) for image in images] 196 | 197 | # When image number is 1 198 | if len(images) == 1: 199 | images = images[0] 200 | 201 | return images 202 | 203 | 204 | def random_horizontally_flip( 205 | images: ndarray | Tensor | list[ndarray] | list[Tensor], 206 | p: float = 0.5 207 | ) -> [ndarray] or [Tensor] or [list[ndarray]] or [list[Tensor]]: 208 | # Get horizontal flip probability 209 | flip_prob = random.random() 210 | 211 | if not isinstance(images, list): 212 | images = [images] 213 | 214 | # Detect input image data type 215 | input_type = "Tensor" if torch.is_tensor(images[0]) else "Numpy" 216 | 217 | if flip_prob > p: 218 | if input_type == "Tensor": 219 | images = [F_vision.hflip(image) for image in images] 220 | else: 221 | images = [cv2.flip(image, 1) for image in images] 222 | 223 | # When image number is 1 224 | if len(images) == 1: 225 | images = images[0] 226 | 227 | return images 228 | 229 | 230 | def random_vertically_flip( 231 | images: ndarray | Tensor | list[ndarray] | list[Tensor], 232 | p: float = 0.5 233 | ) -> [ndarray] or [Tensor] or [list[ndarray]] or [list[Tensor]]: 234 | # Get vertical flip probability 235 | flip_prob = random.random() 236 | 237 | if not isinstance(images, list): 238 | images = [images] 239 | 240 | # Detect input image data type 241 | input_type = "Tensor" if torch.is_tensor(images[0]) else "Numpy" 242 | 243 | if flip_prob > p: 244 | if input_type == "Tensor": 245 | images = [F_vision.vflip(image) for image in images] 246 | else: 247 | images = [cv2.flip(image, 0) for image in images] 248 | 249 | # When image number is 1 250 | if len(images) == 1: 251 | images = images[0] 252 | 253 | return images 254 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Dakewe Biotech Corporation. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # ============================================================================== 14 | import os 15 | import time 16 | 17 | import torch 18 | from torch import nn 19 | from torch import optim 20 | from torch.cuda import amp 21 | from torch.optim import lr_scheduler 22 | from torch.optim.swa_utils import AveragedModel 23 | from torch.utils.data import DataLoader 24 | from torch.utils.tensorboard import SummaryWriter 25 | 26 | import config 27 | import model 28 | from dataset import CUDAPrefetcher, ImageDataset 29 | from utils import accuracy, load_state_dict, make_directory, save_checkpoint, Summary, AverageMeter, ProgressMeter 30 | 31 | model_names = sorted( 32 | name for name in model.__dict__ if name.islower() and not name.startswith("__") and callable(model.__dict__[name])) 33 | 34 | 35 | def main(): 36 | # Initialize the number of training epochs 37 | start_epoch = 0 38 | 39 | # Initialize training network evaluation indicators 40 | best_acc1 = 0.0 41 | 42 | train_prefetcher, valid_prefetcher = load_dataset() 43 | print(f"Load `{config.model_arch_name}` datasets successfully.") 44 | 45 | resnet_model, ema_resnet_model = build_model() 46 | print(f"Build `{config.model_arch_name}` model successfully.") 47 | 48 | pixel_criterion = define_loss() 49 | print("Define all loss functions successfully.") 50 | 51 | optimizer = define_optimizer(resnet_model) 52 | print("Define all optimizer functions successfully.") 53 | 54 | scheduler = define_scheduler(optimizer) 55 | print("Define all optimizer scheduler functions successfully.") 56 | 57 | print("Check whether to load pretrained model weights...") 58 | if config.pretrained_model_weights_path: 59 | resnet_model, ema_resnet_model, start_epoch, best_acc1, optimizer, scheduler = load_state_dict( 60 | resnet_model, 61 | config.pretrained_model_weights_path, 62 | ema_resnet_model, 63 | start_epoch, 64 | best_acc1, 65 | optimizer, 66 | scheduler) 67 | print(f"Loaded `{config.pretrained_model_weights_path}` pretrained model weights successfully.") 68 | else: 69 | print("Pretrained model weights not found.") 70 | 71 | print("Check whether the pretrained model is restored...") 72 | if config.resume: 73 | resnet_model, ema_resnet_model, start_epoch, best_acc1, optimizer, scheduler = load_state_dict( 74 | resnet_model, 75 | config.pretrained_model_weights_path, 76 | ema_resnet_model, 77 | start_epoch, 78 | best_acc1, 79 | optimizer, 80 | scheduler, 81 | "resume") 82 | print("Loaded pretrained generator model weights.") 83 | else: 84 | print("Resume training model not found. Start training from scratch.") 85 | 86 | # Create a experiment results 87 | samples_dir = os.path.join("samples", config.exp_name) 88 | results_dir = os.path.join("results", config.exp_name) 89 | make_directory(samples_dir) 90 | make_directory(results_dir) 91 | 92 | # Create training process log file 93 | writer = SummaryWriter(os.path.join("samples", "logs", config.exp_name)) 94 | 95 | # Initialize the gradient scaler 96 | scaler = amp.GradScaler() 97 | 98 | for epoch in range(start_epoch, config.epochs): 99 | train(resnet_model, ema_resnet_model, train_prefetcher, pixel_criterion, optimizer, epoch, scaler, writer) 100 | acc1 = validate(ema_resnet_model, valid_prefetcher, epoch, writer, "Valid") 101 | print("\n") 102 | 103 | # Update LR 104 | scheduler.step() 105 | 106 | # Automatically save the model with the highest index 107 | is_best = acc1 > best_acc1 108 | is_last = (epoch + 1) == config.epochs 109 | best_acc1 = max(acc1, best_acc1) 110 | save_checkpoint({"epoch": epoch + 1, 111 | "best_acc1": best_acc1, 112 | "state_dict": resnet_model.state_dict(), 113 | "ema_state_dict": ema_resnet_model.state_dict(), 114 | "optimizer": optimizer.state_dict(), 115 | "scheduler": scheduler.state_dict()}, 116 | f"epoch_{epoch + 1}.pth.tar", 117 | samples_dir, 118 | results_dir, 119 | is_best, 120 | is_last) 121 | 122 | 123 | def load_dataset() -> [CUDAPrefetcher, CUDAPrefetcher]: 124 | # Load train, test and valid datasets 125 | train_dataset = ImageDataset(config.train_image_dir, 126 | config.image_size, 127 | config.model_mean_parameters, 128 | config.model_std_parameters, 129 | "Train") 130 | valid_dataset = ImageDataset(config.valid_image_dir, 131 | config.image_size, 132 | config.model_mean_parameters, 133 | config.model_std_parameters, 134 | "Valid") 135 | 136 | # Generator all dataloader 137 | train_dataloader = DataLoader(train_dataset, 138 | batch_size=config.batch_size, 139 | shuffle=True, 140 | num_workers=config.num_workers, 141 | pin_memory=True, 142 | drop_last=True, 143 | persistent_workers=True) 144 | valid_dataloader = DataLoader(valid_dataset, 145 | batch_size=config.batch_size, 146 | shuffle=False, 147 | num_workers=config.num_workers, 148 | pin_memory=True, 149 | drop_last=False, 150 | persistent_workers=True) 151 | 152 | # Place all data on the preprocessing data loader 153 | train_prefetcher = CUDAPrefetcher(train_dataloader, config.device) 154 | valid_prefetcher = CUDAPrefetcher(valid_dataloader, config.device) 155 | 156 | return train_prefetcher, valid_prefetcher 157 | 158 | 159 | def build_model() -> [nn.Module, nn.Module]: 160 | resnet_model = model.__dict__[config.model_arch_name](num_classes=config.model_num_classes) 161 | resnet_model = resnet_model.to(device=config.device, memory_format=torch.channels_last) 162 | 163 | ema_avg = lambda averaged_model_parameter, model_parameter, num_averaged: (1 - config.model_ema_decay) * averaged_model_parameter + config.model_ema_decay * model_parameter 164 | ema_resnet_model = AveragedModel(resnet_model, avg_fn=ema_avg) 165 | 166 | return resnet_model, ema_resnet_model 167 | 168 | 169 | def define_loss() -> nn.CrossEntropyLoss: 170 | criterion = nn.CrossEntropyLoss(label_smoothing=config.loss_label_smoothing) 171 | criterion = criterion.to(device=config.device, memory_format=torch.channels_last) 172 | 173 | return criterion 174 | 175 | 176 | def define_optimizer(model) -> optim.SGD: 177 | optimizer = optim.SGD(model.parameters(), 178 | lr=config.model_lr, 179 | momentum=config.model_momentum, 180 | weight_decay=config.model_weight_decay) 181 | 182 | return optimizer 183 | 184 | 185 | def define_scheduler(optimizer: optim.SGD) -> lr_scheduler.CosineAnnealingWarmRestarts: 186 | scheduler = lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 187 | config.lr_scheduler_T_0, 188 | config.lr_scheduler_T_mult, 189 | config.lr_scheduler_eta_min) 190 | 191 | return scheduler 192 | 193 | 194 | def train( 195 | model: nn.Module, 196 | ema_model: nn.Module, 197 | train_prefetcher: CUDAPrefetcher, 198 | criterion: nn.CrossEntropyLoss, 199 | optimizer: optim.Adam, 200 | epoch: int, 201 | scaler: amp.GradScaler, 202 | writer: SummaryWriter 203 | ) -> None: 204 | # Calculate how many batches of data are in each Epoch 205 | batches = len(train_prefetcher) 206 | # Print information of progress bar during training 207 | batch_time = AverageMeter("Time", ":6.3f") 208 | data_time = AverageMeter("Data", ":6.3f") 209 | losses = AverageMeter("Loss", ":6.6f") 210 | acc1 = AverageMeter("Acc@1", ":6.2f") 211 | acc5 = AverageMeter("Acc@5", ":6.2f") 212 | progress = ProgressMeter(batches, 213 | [batch_time, data_time, losses, acc1, acc5], 214 | prefix=f"Epoch: [{epoch + 1}]") 215 | 216 | # Put the generative network model in training mode 217 | model.train() 218 | 219 | # Initialize the number of data batches to print logs on the terminal 220 | batch_index = 0 221 | 222 | # Initialize the data loader and load the first batch of data 223 | train_prefetcher.reset() 224 | batch_data = train_prefetcher.next() 225 | 226 | # Get the initialization training time 227 | end = time.time() 228 | 229 | while batch_data is not None: 230 | # Calculate the time it takes to load a batch of data 231 | data_time.update(time.time() - end) 232 | 233 | # Transfer in-memory data to CUDA devices to speed up training 234 | images = batch_data["image"].to(device=config.device, memory_format=torch.channels_last, non_blocking=True) 235 | target = batch_data["target"].to(device=config.device, non_blocking=True) 236 | 237 | # Get batch size 238 | batch_size = images.size(0) 239 | 240 | # Initialize generator gradients 241 | model.zero_grad(set_to_none=True) 242 | 243 | # Mixed precision training 244 | with amp.autocast(): 245 | output = model(images) 246 | loss = config.loss_weights * criterion(output, target) 247 | 248 | # Backpropagation 249 | scaler.scale(loss).backward() 250 | # update generator weights 251 | scaler.step(optimizer) 252 | scaler.update() 253 | 254 | # Update EMA 255 | ema_model.update_parameters(model) 256 | 257 | # measure accuracy and record loss 258 | top1, top5 = accuracy(output, target, topk=(1, 5)) 259 | losses.update(loss.item(), batch_size) 260 | acc1.update(top1[0].item(), batch_size) 261 | acc5.update(top5[0].item(), batch_size) 262 | 263 | # Calculate the time it takes to fully train a batch of data 264 | batch_time.update(time.time() - end) 265 | end = time.time() 266 | 267 | # Write the data during training to the training log file 268 | if batch_index % config.train_print_frequency == 0: 269 | # Record loss during training and output to file 270 | writer.add_scalar("Train/Loss", loss.item(), batch_index + epoch * batches + 1) 271 | progress.display(batch_index + 1) 272 | 273 | # Preload the next batch of data 274 | batch_data = train_prefetcher.next() 275 | 276 | # Add 1 to the number of data batches to ensure that the terminal prints data normally 277 | batch_index += 1 278 | 279 | 280 | def validate( 281 | ema_model: nn.Module, 282 | data_prefetcher: CUDAPrefetcher, 283 | epoch: int, 284 | writer: SummaryWriter, 285 | mode: str 286 | ) -> float: 287 | # Calculate how many batches of data are in each Epoch 288 | batches = len(data_prefetcher) 289 | batch_time = AverageMeter("Time", ":6.3f", Summary.NONE) 290 | acc1 = AverageMeter("Acc@1", ":6.2f", Summary.AVERAGE) 291 | acc5 = AverageMeter("Acc@5", ":6.2f", Summary.AVERAGE) 292 | progress = ProgressMeter(batches, [batch_time, acc1, acc5], prefix=f"{mode}: ") 293 | 294 | # Put the exponential moving average model in the verification mode 295 | ema_model.eval() 296 | 297 | # Initialize the number of data batches to print logs on the terminal 298 | batch_index = 0 299 | 300 | # Initialize the data loader and load the first batch of data 301 | data_prefetcher.reset() 302 | batch_data = data_prefetcher.next() 303 | 304 | # Get the initialization test time 305 | end = time.time() 306 | 307 | with torch.no_grad(): 308 | while batch_data is not None: 309 | # Transfer in-memory data to CUDA devices to speed up training 310 | images = batch_data["image"].to(device=config.device, memory_format=torch.channels_last, non_blocking=True) 311 | target = batch_data["target"].to(device=config.device, non_blocking=True) 312 | 313 | # Get batch size 314 | batch_size = images.size(0) 315 | 316 | # Inference 317 | output = ema_model(images) 318 | 319 | # measure accuracy and record loss 320 | top1, top5 = accuracy(output, target, topk=(1, 5)) 321 | acc1.update(top1[0].item(), batch_size) 322 | acc5.update(top5[0].item(), batch_size) 323 | 324 | # Calculate the time it takes to fully train a batch of data 325 | batch_time.update(time.time() - end) 326 | end = time.time() 327 | 328 | # Write the data during training to the training log file 329 | if batch_index % config.valid_print_frequency == 0: 330 | progress.display(batch_index + 1) 331 | 332 | # Preload the next batch of data 333 | batch_data = data_prefetcher.next() 334 | 335 | # Add 1 to the number of data batches to ensure that the terminal prints data normally 336 | batch_index += 1 337 | 338 | # print metrics 339 | progress.display_summary() 340 | 341 | if mode == "Valid" or mode == "Test": 342 | writer.add_scalar(f"{mode}/Acc@1", acc1.avg, epoch + 1) 343 | else: 344 | raise ValueError("Unsupported mode, please use `Valid` or `Test`.") 345 | 346 | return acc1.avg 347 | 348 | 349 | if __name__ == "__main__": 350 | main() 351 | -------------------------------------------------------------------------------- /data/ImageNet_1K_labels_map.txt: -------------------------------------------------------------------------------- 1 | {"0": "tench, Tinca tinca", "1": "goldfish, Carassius auratus", "2": "great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias", "3": "tiger shark, Galeocerdo cuvieri", "4": "hammerhead, hammerhead shark", "5": "electric ray, crampfish, numbfish, torpedo", "6": "stingray", "7": "cock", "8": "hen", "9": "ostrich, Struthio camelus", "10": "brambling, Fringilla montifringilla", "11": "goldfinch, Carduelis carduelis", "12": "house finch, linnet, Carpodacus mexicanus", "13": "junco, snowbird", "14": "indigo bunting, indigo finch, indigo bird, Passerina cyanea", "15": "robin, American robin, Turdus migratorius", "16": "bulbul", "17": "jay", "18": "magpie", "19": "chickadee", "20": "water ouzel, dipper", "21": "kite", "22": "bald eagle, American eagle, Haliaeetus leucocephalus", "23": "vulture", "24": "great grey owl, great gray owl, Strix nebulosa", "25": "European fire salamander, Salamandra salamandra", "26": "common newt, Triturus vulgaris", "27": "eft", "28": "spotted salamander, Ambystoma maculatum", "29": "axolotl, mud puppy, Ambystoma mexicanum", "30": "bullfrog, Rana catesbeiana", "31": "tree frog, tree-frog", "32": "tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui", "33": "loggerhead, loggerhead turtle, Caretta caretta", "34": "leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea", "35": "mud turtle", "36": "terrapin", "37": "box turtle, box tortoise", "38": "banded gecko", "39": "common iguana, iguana, Iguana iguana", "40": "American chameleon, anole, Anolis carolinensis", "41": "whiptail, whiptail lizard", "42": "agama", "43": "frilled lizard, Chlamydosaurus kingi", "44": "alligator lizard", "45": "Gila monster, Heloderma suspectum", "46": "green lizard, Lacerta viridis", "47": "African chameleon, Chamaeleo chamaeleon", "48": "Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis", "49": "African crocodile, Nile crocodile, Crocodylus niloticus", "50": "American alligator, Alligator mississipiensis", "51": "triceratops", "52": "thunder snake, worm snake, Carphophis amoenus", "53": "ringneck snake, ring-necked snake, ring snake", "54": "hognose snake, puff adder, sand viper", "55": "green snake, grass snake", "56": "king snake, kingsnake", "57": "garter snake, grass snake", "58": "water snake", "59": "vine snake", "60": "night snake, Hypsiglena torquata", "61": "boa constrictor, Constrictor constrictor", "62": "rock python, rock snake, Python sebae", "63": "Indian cobra, Naja naja", "64": "green mamba", "65": "sea snake", "66": "horned viper, cerastes, sand viper, horned asp, Cerastes cornutus", "67": "diamondback, diamondback rattlesnake, Crotalus adamanteus", "68": "sidewinder, horned rattlesnake, Crotalus cerastes", "69": "trilobite", "70": "harvestman, daddy longlegs, Phalangium opilio", "71": "scorpion", "72": "black and gold garden spider, Argiope aurantia", "73": "barn spider, Araneus cavaticus", "74": "garden spider, Aranea diademata", "75": "black widow, Latrodectus mactans", "76": "tarantula", "77": "wolf spider, hunting spider", "78": "tick", "79": "centipede", "80": "black grouse", "81": "ptarmigan", "82": "ruffed grouse, partridge, Bonasa umbellus", "83": "prairie chicken, prairie grouse, prairie fowl", "84": "peacock", "85": "quail", "86": "partridge", "87": "African grey, African gray, Psittacus erithacus", "88": "macaw", "89": "sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita", "90": "lorikeet", "91": "coucal", "92": "bee eater", "93": "hornbill", "94": "hummingbird", "95": "jacamar", "96": "toucan", "97": "drake", "98": "red-breasted merganser, Mergus serrator", "99": "goose", "100": "black swan, Cygnus atratus", "101": "tusker", "102": "echidna, spiny anteater, anteater", "103": "platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus", "104": "wallaby, brush kangaroo", "105": "koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus", "106": "wombat", "107": "jellyfish", "108": "sea anemone, anemone", "109": "brain coral", "110": "flatworm, platyhelminth", "111": "nematode, nematode worm, roundworm", "112": "conch", "113": "snail", "114": "slug", "115": "sea slug, nudibranch", "116": "chiton, coat-of-mail shell, sea cradle, polyplacophore", "117": "chambered nautilus, pearly nautilus, nautilus", "118": "Dungeness crab, Cancer magister", "119": "rock crab, Cancer irroratus", "120": "fiddler crab", "121": "king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica", "122": "American lobster, Northern lobster, Maine lobster, Homarus americanus", "123": "spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish", "124": "crayfish, crawfish, crawdad, crawdaddy", "125": "hermit crab", "126": "isopod", "127": "white stork, Ciconia ciconia", "128": "black stork, Ciconia nigra", "129": "spoonbill", "130": "flamingo", "131": "little blue heron, Egretta caerulea", "132": "American egret, great white heron, Egretta albus", "133": "bittern", "134": "crane", "135": "limpkin, Aramus pictus", "136": "European gallinule, Porphyrio porphyrio", "137": "American coot, marsh hen, mud hen, water hen, Fulica americana", "138": "bustard", "139": "ruddy turnstone, Arenaria interpres", "140": "red-backed sandpiper, dunlin, Erolia alpina", "141": "redshank, Tringa totanus", "142": "dowitcher", "143": "oystercatcher, oyster catcher", "144": "pelican", "145": "king penguin, Aptenodytes patagonica", "146": "albatross, mollymawk", "147": "grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus", "148": "killer whale, killer, orca, grampus, sea wolf, Orcinus orca", "149": "dugong, Dugong dugon", "150": "sea lion", "151": "Chihuahua", "152": "Japanese spaniel", "153": "Maltese dog, Maltese terrier, Maltese", "154": "Pekinese, Pekingese, Peke", "155": "Shih-Tzu", "156": "Blenheim spaniel", "157": "papillon", "158": "toy terrier", "159": "Rhodesian ridgeback", "160": "Afghan hound, Afghan", "161": "basset, basset hound", "162": "beagle", "163": "bloodhound, sleuthhound", "164": "bluetick", "165": "black-and-tan coonhound", "166": "Walker hound, Walker foxhound", "167": "English foxhound", "168": "redbone", "169": "borzoi, Russian wolfhound", "170": "Irish wolfhound", "171": "Italian greyhound", "172": "whippet", "173": "Ibizan hound, Ibizan Podenco", "174": "Norwegian elkhound, elkhound", "175": "otterhound, otter hound", "176": "Saluki, gazelle hound", "177": "Scottish deerhound, deerhound", "178": "Weimaraner", "179": "Staffordshire bullterrier, Staffordshire bull terrier", "180": "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier", "181": "Bedlington terrier", "182": "Border terrier", "183": "Kerry blue terrier", "184": "Irish terrier", "185": "Norfolk terrier", "186": "Norwich terrier", "187": "Yorkshire terrier", "188": "wire-haired fox terrier", "189": "Lakeland terrier", "190": "Sealyham terrier, Sealyham", "191": "Airedale, Airedale terrier", "192": "cairn, cairn terrier", "193": "Australian terrier", "194": "Dandie Dinmont, Dandie Dinmont terrier", "195": "Boston bull, Boston terrier", "196": "miniature schnauzer", "197": "giant schnauzer", "198": "standard schnauzer", "199": "Scotch terrier, Scottish terrier, Scottie", "200": "Tibetan terrier, chrysanthemum dog", "201": "silky terrier, Sydney silky", "202": "soft-coated wheaten terrier", "203": "West Highland white terrier", "204": "Lhasa, Lhasa apso", "205": "flat-coated retriever", "206": "curly-coated retriever", "207": "golden retriever", "208": "Labrador retriever", "209": "Chesapeake Bay retriever", "210": "German short-haired pointer", "211": "vizsla, Hungarian pointer", "212": "English setter", "213": "Irish setter, red setter", "214": "Gordon setter", "215": "Brittany spaniel", "216": "clumber, clumber spaniel", "217": "English springer, English springer spaniel", "218": "Welsh springer spaniel", "219": "cocker spaniel, English cocker spaniel, cocker", "220": "Sussex spaniel", "221": "Irish water spaniel", "222": "kuvasz", "223": "schipperke", "224": "groenendael", "225": "malinois", "226": "briard", "227": "kelpie", "228": "komondor", "229": "Old English sheepdog, bobtail", "230": "Shetland sheepdog, Shetland sheep dog, Shetland", "231": "collie", "232": "Border collie", "233": "Bouvier des Flandres, Bouviers des Flandres", "234": "Rottweiler", "235": "German shepherd, German shepherd dog, German police dog, alsatian", "236": "Doberman, Doberman pinscher", "237": "miniature pinscher", "238": "Greater Swiss Mountain dog", "239": "Bernese mountain dog", "240": "Appenzeller", "241": "EntleBucher", "242": "boxer", "243": "bull mastiff", "244": "Tibetan mastiff", "245": "French bulldog", "246": "Great Dane", "247": "Saint Bernard, St Bernard", "248": "Eskimo dog, husky", "249": "malamute, malemute, Alaskan malamute", "250": "Siberian husky", "251": "dalmatian, coach dog, carriage dog", "252": "affenpinscher, monkey pinscher, monkey dog", "253": "basenji", "254": "pug, pug-dog", "255": "Leonberg", "256": "Newfoundland, Newfoundland dog", "257": "Great Pyrenees", "258": "Samoyed, Samoyede", "259": "Pomeranian", "260": "chow, chow chow", "261": "keeshond", "262": "Brabancon griffon", "263": "Pembroke, Pembroke Welsh corgi", "264": "Cardigan, Cardigan Welsh corgi", "265": "toy poodle", "266": "miniature poodle", "267": "standard poodle", "268": "Mexican hairless", "269": "timber wolf, grey wolf, gray wolf, Canis lupus", "270": "white wolf, Arctic wolf, Canis lupus tundrarum", "271": "red wolf, maned wolf, Canis rufus, Canis niger", "272": "coyote, prairie wolf, brush wolf, Canis latrans", "273": "dingo, warrigal, warragal, Canis dingo", "274": "dhole, Cuon alpinus", "275": "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus", "276": "hyena, hyaena", "277": "red fox, Vulpes vulpes", "278": "kit fox, Vulpes macrotis", "279": "Arctic fox, white fox, Alopex lagopus", "280": "grey fox, gray fox, Urocyon cinereoargenteus", "281": "tabby, tabby cat", "282": "tiger cat", "283": "Persian cat", "284": "Siamese cat, Siamese", "285": "Egyptian cat", "286": "cougar, puma, catamount, mountain lion, painter, panther, Felis concolor", "287": "lynx, catamount", "288": "leopard, Panthera pardus", "289": "snow leopard, ounce, Panthera uncia", "290": "jaguar, panther, Panthera onca, Felis onca", "291": "lion, king of beasts, Panthera leo", "292": "tiger, Panthera tigris", "293": "cheetah, chetah, Acinonyx jubatus", "294": "brown bear, bruin, Ursus arctos", "295": "American black bear, black bear, Ursus americanus, Euarctos americanus", "296": "ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus", "297": "sloth bear, Melursus ursinus, Ursus ursinus", "298": "mongoose", "299": "meerkat, mierkat", "300": "tiger beetle", "301": "ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle", "302": "ground beetle, carabid beetle", "303": "long-horned beetle, longicorn, longicorn beetle", "304": "leaf beetle, chrysomelid", "305": "dung beetle", "306": "rhinoceros beetle", "307": "weevil", "308": "fly", "309": "bee", "310": "ant, emmet, pismire", "311": "grasshopper, hopper", "312": "cricket", "313": "walking stick, walkingstick, stick insect", "314": "cockroach, roach", "315": "mantis, mantid", "316": "cicada, cicala", "317": "leafhopper", "318": "lacewing, lacewing fly", "319": "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk", "320": "damselfly", "321": "admiral", "322": "ringlet, ringlet butterfly", "323": "monarch, monarch butterfly, milkweed butterfly, Danaus plexippus", "324": "cabbage butterfly", "325": "sulphur butterfly, sulfur butterfly", "326": "lycaenid, lycaenid butterfly", "327": "starfish, sea star", "328": "sea urchin", "329": "sea cucumber, holothurian", "330": "wood rabbit, cottontail, cottontail rabbit", "331": "hare", "332": "Angora, Angora rabbit", "333": "hamster", "334": "porcupine, hedgehog", "335": "fox squirrel, eastern fox squirrel, Sciurus niger", "336": "marmot", "337": "beaver", "338": "guinea pig, Cavia cobaya", "339": "sorrel", "340": "zebra", "341": "hog, pig, grunter, squealer, Sus scrofa", "342": "wild boar, boar, Sus scrofa", "343": "warthog", "344": "hippopotamus, hippo, river horse, Hippopotamus amphibius", "345": "ox", "346": "water buffalo, water ox, Asiatic buffalo, Bubalus bubalis", "347": "bison", "348": "ram, tup", "349": "bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis", "350": "ibex, Capra ibex", "351": "hartebeest", "352": "impala, Aepyceros melampus", "353": "gazelle", "354": "Arabian camel, dromedary, Camelus dromedarius", "355": "llama", "356": "weasel", "357": "mink", "358": "polecat, fitch, foulmart, foumart, Mustela putorius", "359": "black-footed ferret, ferret, Mustela nigripes", "360": "otter", "361": "skunk, polecat, wood pussy", "362": "badger", "363": "armadillo", "364": "three-toed sloth, ai, Bradypus tridactylus", "365": "orangutan, orang, orangutang, Pongo pygmaeus", "366": "gorilla, Gorilla gorilla", "367": "chimpanzee, chimp, Pan troglodytes", "368": "gibbon, Hylobates lar", "369": "siamang, Hylobates syndactylus, Symphalangus syndactylus", "370": "guenon, guenon monkey", "371": "patas, hussar monkey, Erythrocebus patas", "372": "baboon", "373": "macaque", "374": "langur", "375": "colobus, colobus monkey", "376": "proboscis monkey, Nasalis larvatus", "377": "marmoset", "378": "capuchin, ringtail, Cebus capucinus", "379": "howler monkey, howler", "380": "titi, titi monkey", "381": "spider monkey, Ateles geoffroyi", "382": "squirrel monkey, Saimiri sciureus", "383": "Madagascar cat, ring-tailed lemur, Lemur catta", "384": "indri, indris, Indri indri, Indri brevicaudatus", "385": "Indian elephant, Elephas maximus", "386": "African elephant, Loxodonta africana", "387": "lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens", "388": "giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca", "389": "barracouta, snoek", "390": "eel", "391": "coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch", "392": "rock beauty, Holocanthus tricolor", "393": "anemone fish", "394": "sturgeon", "395": "gar, garfish, garpike, billfish, Lepisosteus osseus", "396": "lionfish", "397": "puffer, pufferfish, blowfish, globefish", "398": "abacus", "399": "abaya", "400": "academic gown, academic robe, judge's robe", "401": "accordion, piano accordion, squeeze box", "402": "acoustic guitar", "403": "aircraft carrier, carrier, flattop, attack aircraft carrier", "404": "airliner", "405": "airship, dirigible", "406": "altar", "407": "ambulance", "408": "amphibian, amphibious vehicle", "409": "analog clock", "410": "apiary, bee house", "411": "apron", "412": "ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin", "413": "assault rifle, assault gun", "414": "backpack, back pack, knapsack, packsack, rucksack, haversack", "415": "bakery, bakeshop, bakehouse", "416": "balance beam, beam", "417": "balloon", "418": "ballpoint, ballpoint pen, ballpen, Biro", "419": "Band Aid", "420": "banjo", "421": "bannister, banister, balustrade, balusters, handrail", "422": "barbell", "423": "barber chair", "424": "barbershop", "425": "barn", "426": "barometer", "427": "barrel, cask", "428": "barrow, garden cart, lawn cart, wheelbarrow", "429": "baseball", "430": "basketball", "431": "bassinet", "432": "bassoon", "433": "bathing cap, swimming cap", "434": "bath towel", "435": "bathtub, bathing tub, bath, tub", "436": "beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon", "437": "beacon, lighthouse, beacon light, pharos", "438": "beaker", "439": "bearskin, busby, shako", "440": "beer bottle", "441": "beer glass", "442": "bell cote, bell cot", "443": "bib", "444": "bicycle-built-for-two, tandem bicycle, tandem", "445": "bikini, two-piece", "446": "binder, ring-binder", "447": "binoculars, field glasses, opera glasses", "448": "birdhouse", "449": "boathouse", "450": "bobsled, bobsleigh, bob", "451": "bolo tie, bolo, bola tie, bola", "452": "bonnet, poke bonnet", "453": "bookcase", "454": "bookshop, bookstore, bookstall", "455": "bottlecap", "456": "bow", "457": "bow tie, bow-tie, bowtie", "458": "brass, memorial tablet, plaque", "459": "brassiere, bra, bandeau", "460": "breakwater, groin, groyne, mole, bulwark, seawall, jetty", "461": "breastplate, aegis, egis", "462": "broom", "463": "bucket, pail", "464": "buckle", "465": "bulletproof vest", "466": "bullet train, bullet", "467": "butcher shop, meat market", "468": "cab, hack, taxi, taxicab", "469": "caldron, cauldron", "470": "candle, taper, wax light", "471": "cannon", "472": "canoe", "473": "can opener, tin opener", "474": "cardigan", "475": "car mirror", "476": "carousel, carrousel, merry-go-round, roundabout, whirligig", "477": "carpenter's kit, tool kit", "478": "carton", "479": "car wheel", "480": "cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM", "481": "cassette", "482": "cassette player", "483": "castle", "484": "catamaran", "485": "CD player", "486": "cello, violoncello", "487": "cellular telephone, cellular phone, cellphone, cell, mobile phone", "488": "chain", "489": "chainlink fence", "490": "chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour", "491": "chain saw, chainsaw", "492": "chest", "493": "chiffonier, commode", "494": "chime, bell, gong", "495": "china cabinet, china closet", "496": "Christmas stocking", "497": "church, church building", "498": "cinema, movie theater, movie theatre, movie house, picture palace", "499": "cleaver, meat cleaver, chopper", "500": "cliff dwelling", "501": "cloak", "502": "clog, geta, patten, sabot", "503": "cocktail shaker", "504": "coffee mug", "505": "coffeepot", "506": "coil, spiral, volute, whorl, helix", "507": "combination lock", "508": "computer keyboard, keypad", "509": "confectionery, confectionary, candy store", "510": "container ship, containership, container vessel", "511": "convertible", "512": "corkscrew, bottle screw", "513": "cornet, horn, trumpet, trump", "514": "cowboy boot", "515": "cowboy hat, ten-gallon hat", "516": "cradle", "517": "crane", "518": "crash helmet", "519": "crate", "520": "crib, cot", "521": "Crock Pot", "522": "croquet ball", "523": "crutch", "524": "cuirass", "525": "dam, dike, dyke", "526": "desk", "527": "desktop computer", "528": "dial telephone, dial phone", "529": "diaper, nappy, napkin", "530": "digital clock", "531": "digital watch", "532": "dining table, board", "533": "dishrag, dishcloth", "534": "dishwasher, dish washer, dishwashing machine", "535": "disk brake, disc brake", "536": "dock, dockage, docking facility", "537": "dogsled, dog sled, dog sleigh", "538": "dome", "539": "doormat, welcome mat", "540": "drilling platform, offshore rig", "541": "drum, membranophone, tympan", "542": "drumstick", "543": "dumbbell", "544": "Dutch oven", "545": "electric fan, blower", "546": "electric guitar", "547": "electric locomotive", "548": "entertainment center", "549": "envelope", "550": "espresso maker", "551": "face powder", "552": "feather boa, boa", "553": "file, file cabinet, filing cabinet", "554": "fireboat", "555": "fire engine, fire truck", "556": "fire screen, fireguard", "557": "flagpole, flagstaff", "558": "flute, transverse flute", "559": "folding chair", "560": "football helmet", "561": "forklift", "562": "fountain", "563": "fountain pen", "564": "four-poster", "565": "freight car", "566": "French horn, horn", "567": "frying pan, frypan, skillet", "568": "fur coat", "569": "garbage truck, dustcart", "570": "gasmask, respirator, gas helmet", "571": "gas pump, gasoline pump, petrol pump, island dispenser", "572": "goblet", "573": "go-kart", "574": "golf ball", "575": "golfcart, golf cart", "576": "gondola", "577": "gong, tam-tam", "578": "gown", "579": "grand piano, grand", "580": "greenhouse, nursery, glasshouse", "581": "grille, radiator grille", "582": "grocery store, grocery, food market, market", "583": "guillotine", "584": "hair slide", "585": "hair spray", "586": "half track", "587": "hammer", "588": "hamper", "589": "hand blower, blow dryer, blow drier, hair dryer, hair drier", "590": "hand-held computer, hand-held microcomputer", "591": "handkerchief, hankie, hanky, hankey", "592": "hard disc, hard disk, fixed disk", "593": "harmonica, mouth organ, harp, mouth harp", "594": "harp", "595": "harvester, reaper", "596": "hatchet", "597": "holster", "598": "home theater, home theatre", "599": "honeycomb", "600": "hook, claw", "601": "hoopskirt, crinoline", "602": "horizontal bar, high bar", "603": "horse cart, horse-cart", "604": "hourglass", "605": "iPod", "606": "iron, smoothing iron", "607": "jack-o'-lantern", "608": "jean, blue jean, denim", "609": "jeep, landrover", "610": "jersey, T-shirt, tee shirt", "611": "jigsaw puzzle", "612": "jinrikisha, ricksha, rickshaw", "613": "joystick", "614": "kimono", "615": "knee pad", "616": "knot", "617": "lab coat, laboratory coat", "618": "ladle", "619": "lampshade, lamp shade", "620": "laptop, laptop computer", "621": "lawn mower, mower", "622": "lens cap, lens cover", "623": "letter opener, paper knife, paperknife", "624": "library", "625": "lifeboat", "626": "lighter, light, igniter, ignitor", "627": "limousine, limo", "628": "liner, ocean liner", "629": "lipstick, lip rouge", "630": "Loafer", "631": "lotion", "632": "loudspeaker, speaker, speaker unit, loudspeaker system, speaker system", "633": "loupe, jeweler's loupe", "634": "lumbermill, sawmill", "635": "magnetic compass", "636": "mailbag, postbag", "637": "mailbox, letter box", "638": "maillot", "639": "maillot, tank suit", "640": "manhole cover", "641": "maraca", "642": "marimba, xylophone", "643": "mask", "644": "matchstick", "645": "maypole", "646": "maze, labyrinth", "647": "measuring cup", "648": "medicine chest, medicine cabinet", "649": "megalith, megalithic structure", "650": "microphone, mike", "651": "microwave, microwave oven", "652": "military uniform", "653": "milk can", "654": "minibus", "655": "miniskirt, mini", "656": "minivan", "657": "missile", "658": "mitten", "659": "mixing bowl", "660": "mobile home, manufactured home", "661": "Model T", "662": "modem", "663": "monastery", "664": "monitor", "665": "moped", "666": "mortar", "667": "mortarboard", "668": "mosque", "669": "mosquito net", "670": "motor scooter, scooter", "671": "mountain bike, all-terrain bike, off-roader", "672": "mountain tent", "673": "mouse, computer mouse", "674": "mousetrap", "675": "moving van", "676": "muzzle", "677": "nail", "678": "neck brace", "679": "necklace", "680": "nipple", "681": "notebook, notebook computer", "682": "obelisk", "683": "oboe, hautboy, hautbois", "684": "ocarina, sweet potato", "685": "odometer, hodometer, mileometer, milometer", "686": "oil filter", "687": "organ, pipe organ", "688": "oscilloscope, scope, cathode-ray oscilloscope, CRO", "689": "overskirt", "690": "oxcart", "691": "oxygen mask", "692": "packet", "693": "paddle, boat paddle", "694": "paddlewheel, paddle wheel", "695": "padlock", "696": "paintbrush", "697": "pajama, pyjama, pj's, jammies", "698": "palace", "699": "panpipe, pandean pipe, syrinx", "700": "paper towel", "701": "parachute, chute", "702": "parallel bars, bars", "703": "park bench", "704": "parking meter", "705": "passenger car, coach, carriage", "706": "patio, terrace", "707": "pay-phone, pay-station", "708": "pedestal, plinth, footstall", "709": "pencil box, pencil case", "710": "pencil sharpener", "711": "perfume, essence", "712": "Petri dish", "713": "photocopier", "714": "pick, plectrum, plectron", "715": "pickelhaube", "716": "picket fence, paling", "717": "pickup, pickup truck", "718": "pier", "719": "piggy bank, penny bank", "720": "pill bottle", "721": "pillow", "722": "ping-pong ball", "723": "pinwheel", "724": "pirate, pirate ship", "725": "pitcher, ewer", "726": "plane, carpenter's plane, woodworking plane", "727": "planetarium", "728": "plastic bag", "729": "plate rack", "730": "plow, plough", "731": "plunger, plumber's helper", "732": "Polaroid camera, Polaroid Land camera", "733": "pole", "734": "police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria", "735": "poncho", "736": "pool table, billiard table, snooker table", "737": "pop bottle, soda bottle", "738": "pot, flowerpot", "739": "potter's wheel", "740": "power drill", "741": "prayer rug, prayer mat", "742": "printer", "743": "prison, prison house", "744": "projectile, missile", "745": "projector", "746": "puck, hockey puck", "747": "punching bag, punch bag, punching ball, punchball", "748": "purse", "749": "quill, quill pen", "750": "quilt, comforter, comfort, puff", "751": "racer, race car, racing car", "752": "racket, racquet", "753": "radiator", "754": "radio, wireless", "755": "radio telescope, radio reflector", "756": "rain barrel", "757": "recreational vehicle, RV, R.V.", "758": "reel", "759": "reflex camera", "760": "refrigerator, icebox", "761": "remote control, remote", "762": "restaurant, eating house, eating place, eatery", "763": "revolver, six-gun, six-shooter", "764": "rifle", "765": "rocking chair, rocker", "766": "rotisserie", "767": "rubber eraser, rubber, pencil eraser", "768": "rugby ball", "769": "rule, ruler", "770": "running shoe", "771": "safe", "772": "safety pin", "773": "saltshaker, salt shaker", "774": "sandal", "775": "sarong", "776": "sax, saxophone", "777": "scabbard", "778": "scale, weighing machine", "779": "school bus", "780": "schooner", "781": "scoreboard", "782": "screen, CRT screen", "783": "screw", "784": "screwdriver", "785": "seat belt, seatbelt", "786": "sewing machine", "787": "shield, buckler", "788": "shoe shop, shoe-shop, shoe store", "789": "shoji", "790": "shopping basket", "791": "shopping cart", "792": "shovel", "793": "shower cap", "794": "shower curtain", "795": "ski", "796": "ski mask", "797": "sleeping bag", "798": "slide rule, slipstick", "799": "sliding door", "800": "slot, one-armed bandit", "801": "snorkel", "802": "snowmobile", "803": "snowplow, snowplough", "804": "soap dispenser", "805": "soccer ball", "806": "sock", "807": "solar dish, solar collector, solar furnace", "808": "sombrero", "809": "soup bowl", "810": "space bar", "811": "space heater", "812": "space shuttle", "813": "spatula", "814": "speedboat", "815": "spider web, spider's web", "816": "spindle", "817": "sports car, sport car", "818": "spotlight, spot", "819": "stage", "820": "steam locomotive", "821": "steel arch bridge", "822": "steel drum", "823": "stethoscope", "824": "stole", "825": "stone wall", "826": "stopwatch, stop watch", "827": "stove", "828": "strainer", "829": "streetcar, tram, tramcar, trolley, trolley car", "830": "stretcher", "831": "studio couch, day bed", "832": "stupa, tope", "833": "submarine, pigboat, sub, U-boat", "834": "suit, suit of clothes", "835": "sundial", "836": "sunglass", "837": "sunglasses, dark glasses, shades", "838": "sunscreen, sunblock, sun blocker", "839": "suspension bridge", "840": "swab, swob, mop", "841": "sweatshirt", "842": "swimming trunks, bathing trunks", "843": "swing", "844": "switch, electric switch, electrical switch", "845": "syringe", "846": "table lamp", "847": "tank, army tank, armored combat vehicle, armoured combat vehicle", "848": "tape player", "849": "teapot", "850": "teddy, teddy bear", "851": "television, television system", "852": "tennis ball", "853": "thatch, thatched roof", "854": "theater curtain, theatre curtain", "855": "thimble", "856": "thresher, thrasher, threshing machine", "857": "throne", "858": "tile roof", "859": "toaster", "860": "tobacco shop, tobacconist shop, tobacconist", "861": "toilet seat", "862": "torch", "863": "totem pole", "864": "tow truck, tow car, wrecker", "865": "toyshop", "866": "tractor", "867": "trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi", "868": "tray", "869": "trench coat", "870": "tricycle, trike, velocipede", "871": "trimaran", "872": "tripod", "873": "triumphal arch", "874": "trolleybus, trolley coach, trackless trolley", "875": "trombone", "876": "tub, vat", "877": "turnstile", "878": "typewriter keyboard", "879": "umbrella", "880": "unicycle, monocycle", "881": "upright, upright piano", "882": "vacuum, vacuum cleaner", "883": "vase", "884": "vault", "885": "velvet", "886": "vending machine", "887": "vestment", "888": "viaduct", "889": "violin, fiddle", "890": "volleyball", "891": "waffle iron", "892": "wall clock", "893": "wallet, billfold, notecase, pocketbook", "894": "wardrobe, closet, press", "895": "warplane, military plane", "896": "washbasin, handbasin, washbowl, lavabo, wash-hand basin", "897": "washer, automatic washer, washing machine", "898": "water bottle", "899": "water jug", "900": "water tower", "901": "whiskey jug", "902": "whistle", "903": "wig", "904": "window screen", "905": "window shade", "906": "Windsor tie", "907": "wine bottle", "908": "wing", "909": "wok", "910": "wooden spoon", "911": "wool, woolen, woollen", "912": "worm fence, snake fence, snake-rail fence, Virginia fence", "913": "wreck", "914": "yawl", "915": "yurt", "916": "web site, website, internet site, site", "917": "comic book", "918": "crossword puzzle, crossword", "919": "street sign", "920": "traffic light, traffic signal, stoplight", "921": "book jacket, dust cover, dust jacket, dust wrapper", "922": "menu", "923": "plate", "924": "guacamole", "925": "consomme", "926": "hot pot, hotpot", "927": "trifle", "928": "ice cream, icecream", "929": "ice lolly, lolly, lollipop, popsicle", "930": "French loaf", "931": "bagel, beigel", "932": "pretzel", "933": "cheeseburger", "934": "hotdog, hot dog, red hot", "935": "mashed potato", "936": "head cabbage", "937": "broccoli", "938": "cauliflower", "939": "zucchini, courgette", "940": "spaghetti squash", "941": "acorn squash", "942": "butternut squash", "943": "cucumber, cuke", "944": "artichoke, globe artichoke", "945": "bell pepper", "946": "cardoon", "947": "mushroom", "948": "Granny Smith", "949": "strawberry", "950": "orange", "951": "lemon", "952": "fig", "953": "pineapple, ananas", "954": "banana", "955": "jackfruit, jak, jack", "956": "custard apple", "957": "pomegranate", "958": "hay", "959": "carbonara", "960": "chocolate sauce, chocolate syrup", "961": "dough", "962": "meat loaf, meatloaf", "963": "pizza, pizza pie", "964": "potpie", "965": "burrito", "966": "red wine", "967": "espresso", "968": "cup", "969": "eggnog", "970": "alp", "971": "bubble", "972": "cliff, drop, drop-off", "973": "coral reef", "974": "geyser", "975": "lakeside, lakeshore", "976": "promontory, headland, head, foreland", "977": "sandbar, sand bar", "978": "seashore, coast, seacoast, sea-coast", "979": "valley, vale", "980": "volcano", "981": "ballplayer, baseball player", "982": "groom, bridegroom", "983": "scuba diver", "984": "rapeseed", "985": "daisy", "986": "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum", "987": "corn", "988": "acorn", "989": "hip, rose hip, rosehip", "990": "buckeye, horse chestnut, conker", "991": "coral fungus", "992": "agaric", "993": "gyromitra", "994": "stinkhorn, carrion fungus", "995": "earthstar", "996": "hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa", "997": "bolete", "998": "ear, spike, capitulum", "999": "toilet tissue, toilet paper, bathroom tissue"} 2 | --------------------------------------------------------------------------------