├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── data_function.py ├── hparam.py ├── loss_function.py ├── main.py ├── models ├── three_d │ ├── densenet3d.py │ ├── resnet3d.py │ └── resnext3d.py └── two_d │ ├── alexnet.py │ ├── densenet.py │ ├── googlenet.py │ ├── mobilenet.py │ ├── nasnet.py │ ├── resnet.py │ ├── resnext.py │ └── vggnet.py └── utils ├── convolution.py ├── dilation.py ├── metric.py └── residual.py /.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 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kangneng Zhou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Pytorch Medical Segmentation 2 | 英文版请戳:这里!
3 | 4 | 5 | ## 最近的更新 6 | * 2021.7.7 训练和测试代码已经发布 7 | 8 | 9 | ## 环境要求 10 | * pytorch1.7 11 | * torchio<=0.18.20 12 | * python>=3.6 13 | 14 | ## 通知 15 | * 您可以修改**hparam.py**文件来确定是2D分类还是3D分类以及是否可以进行多分类。 16 | * 我们几乎提供了所有的2D和3D分类的算法。 17 | * 本项目兼容几乎所有的医学数据格式(例如 png, nii.gz, nii, mhd, nrrd, ...),修改**hparam.py**的**fold_arch**即可。 18 | * 如果您想进行**多分类**分割,请自行修改**data_function.py**的对应代码。我不能确定您的具体分类数。 19 | 20 | 21 | 22 | ## 准备您的数据 23 | ### 例 24 | 如果您的source文件夹如下排列 : 25 | ``` 26 | categpry-0 27 | ├── source_1.png 28 | ├── source_2.png 29 | ├── source_3.png 30 | └── ... 31 | ``` 32 | 33 | ``` 34 | categpry-1 35 | ├── source_1.png 36 | ├── source_2.png 37 | ├── source_3.png 38 | └── ... 39 | ``` 40 | 41 | 您应该修改 **fold_arch** 为 **\*/\*.png**, **source_train_0_dir** 为 **categpry-0** 并修改 **source_train_1_dir** 为 **categpry-1** in **hparam.py** 42 | 43 | 44 | 45 | ## 训练 46 | * 不使用预训练模型 47 | ``` 48 | set hparam.train_or_test to 'train' 49 | python main.py 50 | ``` 51 | * 使用预训练模型 52 | ``` 53 | set hparam.train_or_test to 'train' 54 | set hparam.ckpt to True 55 | python main.py 56 | ``` 57 | 58 | ## Inference 59 | * 测试 60 | ``` 61 | set hparam.train_or_test to 'test' 62 | python main.py 63 | ``` 64 | 65 | 66 | 67 | ## Done 68 | ### Network 69 | * 2D 70 | - [x] alexnet 71 | - [x] densenet 72 | - [x] googlenet 73 | - [x] mobilenet 74 | - [x] nasnet 75 | - [x] resnet 76 | - [x] resnext 77 | - [x] vggnet 78 | * 3D 79 | - [x] densenet3d 80 | - [x] resnet3d 81 | - [x] resnext3d 82 | 83 | ## TODO 84 | - [ ] dataset 85 | - [ ] benchmark 86 | 87 | ## By The Way 88 | 这个项目并不完美,还存在很多问题。如果您正在使用这个项目,并想给作者一些反馈,您可以给[Kangneng Zhou](elliszkn@163.com)发邮件,或添加他的**微信**:ellisgege666 89 | 90 | ## 致谢 91 | 这个项目是一个非官方PyTorch实现的3D和2D医学分类,高度依赖于[pytorch-cifar100](https://github.com/weiaicunzai/pytorch-cifar100)和[torchio](https://github.com/fepegar/torchio)。感谢上述项目。感谢[Cheng Chen](b20170310@xs.ustb.edu.cn)和[Weili Jiang](1379252229@qq.com)对我的帮助。 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pytorch Medical Classification 2 | Read Chinese Introduction:Here!
3 | 4 | ## Recent Updates 5 | * 2021.7.7 The train and test codes are released. 6 | 7 | 8 | ## Requirements 9 | * pytorch1.7 10 | * torchio<=0.18.20 11 | * python>=3.6 12 | 13 | ## Notice 14 | * You can modify **hparam.py** to determine whether 2D or 3D classification and whether multicategorization is possible. 15 | * We provide algorithms for almost all 2D and 3D classification. 16 | * This repository is compatible with almost all medical data formats(e.g. png, nii.gz, nii, mhd, nrrd, ...), by modifying **fold_arch** in **hparam.py** of the config. 17 | * If you want to use a **multi-category** program, please modify the corresponding codes in **data_function.py** by yourself. I cannot identify your specific categories. 18 | 19 | ## Prepare Your Dataset 20 | ### Example 21 | if your source dataset is : 22 | ``` 23 | categpry-0 24 | ├── source_1.png 25 | ├── source_2.png 26 | ├── source_3.png 27 | └── ... 28 | ``` 29 | 30 | ``` 31 | categpry-1 32 | ├── source_1.png 33 | ├── source_2.png 34 | ├── source_3.png 35 | └── ... 36 | ``` 37 | 38 | 39 | then your should modify **fold_arch** as **\*.png**, **source_train_0_dir** as **categpry-0** and **source_train_1_dir** as **categpry-1** in **hparam.py** 40 | 41 | 42 | 43 | ## Training 44 | * without pretrained-model 45 | ``` 46 | set hparam.train_or_test to 'train' 47 | python main.py 48 | ``` 49 | * with pretrained-model 50 | ``` 51 | set hparam.train_or_test to 'train' 52 | set hparam.ckpt to True 53 | python main.py 54 | ``` 55 | 56 | ## Inference 57 | * testing 58 | ``` 59 | set hparam.train_or_test to 'test' 60 | python main.py 61 | ``` 62 | 63 | 64 | ## Done 65 | ### Network 66 | * 2D 67 | - [x] alexnet 68 | - [x] densenet 69 | - [x] googlenet 70 | - [x] mobilenet 71 | - [x] nasnet 72 | - [x] resnet 73 | - [x] resnext 74 | - [x] vggnet 75 | * 3D 76 | - [x] densenet3d 77 | - [x] resnet3d 78 | - [x] resnext3d 79 | 80 | 81 | 82 | ## TODO 83 | - [ ] dataset 84 | - [ ] benchmark 85 | 86 | 87 | ## By The Way 88 | This project is not perfect and there are still many problems. If you are using this project and would like to give the author some feedbacks, you can send [Kangneng Zhou](elliszkn@163.com) an email, his **wechat** number is: ellisgege666 89 | 90 | ## Acknowledgements 91 | This repository is an unoffical PyTorch implementation of Medical segmentation in 3D and 2D and highly based on [pytorch-cifar100](https://github.com/weiaicunzai/pytorch-cifar100) and [torchio](https://github.com/fepegar/torchio).Thank you for the above repo. Thank you to [Cheng Chen](b20170310@xs.ustb.edu.cn) and [Weili Jiang](1379252229@qq.com) for all the help I received. 92 | -------------------------------------------------------------------------------- /data_function.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from os.path import dirname, join, basename, isfile 3 | import sys 4 | sys.path.append('./') 5 | import csv 6 | import torch 7 | from medpy.io import load 8 | import numpy as np 9 | from PIL import Image 10 | from torch import nn 11 | import torch.nn.functional as F 12 | import random 13 | import torchio as tio 14 | from torchio import AFFINE, DATA 15 | import torchio 16 | from torchio import ScalarImage, LabelMap, Subject, SubjectsDataset, Queue 17 | from torchio.data import UniformSampler 18 | from torchio.transforms import ( 19 | RandomFlip, 20 | RandomAffine, 21 | RandomElasticDeformation, 22 | RandomNoise, 23 | RandomMotion, 24 | RandomBiasField, 25 | RescaleIntensity, 26 | Resample, 27 | ToCanonical, 28 | ZNormalization, 29 | CropOrPad, 30 | HistogramStandardization, 31 | OneOf, 32 | Compose, 33 | ) 34 | from pathlib import Path 35 | 36 | from hparam import hparams as hp 37 | 38 | 39 | class MedData_train(torch.utils.data.Dataset): 40 | def __init__(self, images_dir_0, images_dir_1): 41 | 42 | self.subjects = [] 43 | 44 | 45 | images_dir_0 = Path(images_dir_0) 46 | self.image_paths_0 = sorted(images_dir_0.glob(hp.fold_arch)) 47 | 48 | images_dir_1 = Path(images_dir_1) 49 | self.image_paths_1 = sorted(images_dir_1.glob(hp.fold_arch)) 50 | 51 | for (image_path) in zip(self.image_paths_0): 52 | subject = tio.Subject( 53 | source=tio.ScalarImage(image_path), 54 | label= 0, 55 | ) 56 | self.subjects.append(subject) 57 | 58 | for (image_path) in zip(self.image_paths_1): 59 | subject = tio.Subject( 60 | source=tio.ScalarImage(image_path), 61 | label= 1, 62 | ) 63 | self.subjects.append(subject) 64 | 65 | self.transforms = self.transform() 66 | 67 | self.training_set = tio.SubjectsDataset(self.subjects, transform=self.transforms) 68 | 69 | 70 | # one_subject = self.training_set[0] 71 | # one_subject.plot() 72 | 73 | def transform(self): 74 | 75 | 76 | if hp.aug: 77 | training_transform = Compose([ 78 | CropOrPad((hp.crop_or_pad_size), padding_mode='reflect'), 79 | # ToCanonical(), 80 | RandomBiasField(), 81 | ZNormalization(), 82 | RandomNoise(), 83 | RandomFlip(axes=(0,)), 84 | OneOf({ 85 | RandomAffine(): 0.8, 86 | RandomElasticDeformation(): 0.2, 87 | }), 88 | ]) 89 | else: 90 | training_transform = Compose([ 91 | CropOrPad((hp.crop_or_pad_size), padding_mode='reflect'), 92 | ZNormalization(), 93 | ]) 94 | 95 | 96 | return training_transform 97 | 98 | 99 | 100 | 101 | class MedData_test(torch.utils.data.Dataset): 102 | def __init__(self, images_dir_0, images_dir_1): 103 | 104 | self.subjects = [] 105 | 106 | 107 | images_dir_0 = Path(images_dir_0) 108 | self.image_paths_0 = sorted(images_dir_0.glob(hp.fold_arch)) 109 | 110 | images_dir_1 = Path(images_dir_1) 111 | self.image_paths_1 = sorted(images_dir_1.glob(hp.fold_arch)) 112 | 113 | for (image_path) in zip(self.image_paths_0): 114 | subject = tio.Subject( 115 | source=tio.ScalarImage(image_path), 116 | label= 0, 117 | ) 118 | self.subjects.append(subject) 119 | 120 | for (image_path) in zip(self.image_paths_1): 121 | subject = tio.Subject( 122 | source=tio.ScalarImage(image_path), 123 | label= 1, 124 | ) 125 | self.subjects.append(subject) 126 | 127 | 128 | 129 | self.transforms = self.transform() 130 | 131 | self.testing_set = tio.SubjectsDataset(self.subjects, transform=self.transforms) 132 | 133 | 134 | # one_subject = self.training_set[0] 135 | # one_subject.plot() 136 | 137 | def transform(self): 138 | 139 | testing_transform = Compose([ 140 | CropOrPad((hp.crop_or_pad_size), padding_mode='reflect'), 141 | ZNormalization(), 142 | ]) 143 | 144 | 145 | return testing_transform -------------------------------------------------------------------------------- /hparam.py: -------------------------------------------------------------------------------- 1 | class hparams: 2 | 3 | train_or_test = 'train' 4 | output_dir = 'logs/your_program_name' 5 | aug = False 6 | latest_checkpoint_file = 'checkpoint_latest.pt' 7 | total_epochs = 100 8 | epochs_per_checkpoint = 10 9 | batch_size = 2 10 | ckpt = None 11 | init_lr = 0.0002 12 | scheduer_step_size = 20 13 | scheduer_gamma = 0.8 14 | debug = False 15 | mode = '2d' # '2d or '3d' 16 | in_class = 1 17 | out_class = 2 18 | 19 | crop_or_pad_size = 224,224,1 # if 3D: 256,256,256 20 | 21 | fold_arch = '*.png' 22 | 23 | source_train_0_dir = 'train/0' 24 | source_train_1_dir = 'train/1' 25 | source_test_0_dir = 'test/0' 26 | source_test_1_dir = 'test/1' 27 | -------------------------------------------------------------------------------- /loss_function.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | import torch.nn.functional as F 4 | class FocalLoss(nn.Module): 5 | def __init__(self, alpha=1, gamma=2, logits=True, reduce=True): 6 | super(FocalLoss, self).__init__() 7 | self.alpha = alpha 8 | self.gamma = gamma 9 | self.logits = logits 10 | self.reduce = reduce 11 | 12 | def forward(self, inputs, targets): 13 | if self.logits: 14 | BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False) 15 | else: 16 | BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=False) 17 | pt = torch.exp(-BCE_loss) 18 | F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss 19 | 20 | if self.reduce: 21 | return torch.mean(F_loss) 22 | else: 23 | return F_loss 24 | 25 | class Classification_Loss(nn.Module): 26 | def __init__(self): 27 | super(Classification_Loss, self).__init__() 28 | self.criterionCE = nn.CrossEntropyLoss() 29 | 30 | 31 | def forward(self, model_output, targets, model): 32 | 33 | # torch.empty(3, dtype=torch.long) 34 | # model_output = model_output.long() 35 | # targets = targets.long() 36 | # print(model_output) 37 | # print(F.sigmoid(model_output)) 38 | # print(targets) 39 | # print('kkk') 40 | regularization_loss = 0 41 | for param in model.module.parameters(): 42 | regularization_loss += torch.sum(torch.abs(param)) #+torch.sum(torch.abs(param))**2 43 | # loss = 0.00001 * regularization_loss 44 | loss = 0 45 | 46 | # model_output = F.sigmoid(model_output) 47 | # loss = self.mse_criterion(model_output,targets) 48 | loss += self.criterionCE(model_output,targets) 49 | return loss 50 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ['CUDA_VISIBLE_DEVICES'] = '0' 3 | devicess = [0] 4 | from collections import defaultdict 5 | import time 6 | import argparse 7 | import numpy as np 8 | from PIL import Image 9 | import torch 10 | from sklearn import metrics 11 | from torch.utils.data import DataLoader 12 | from torch.utils.tensorboard import SummaryWriter 13 | from torch import nn 14 | from torchvision import transforms 15 | import torch.distributed as dist 16 | import math 17 | import torchio 18 | from torchio.transforms import ( 19 | ZNormalization, 20 | ) 21 | from tqdm import tqdm 22 | from torchvision import utils 23 | from hparam import hparams as hp 24 | from utils.metric import metric 25 | from torch.optim.lr_scheduler import ReduceLROnPlateau,StepLR,CosineAnnealingLR 26 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 27 | 28 | 29 | 30 | source_train_0_dir = hp.source_train_0_dir 31 | source_train_1_dir = hp.source_train_1_dir 32 | 33 | 34 | source_test_0_dir = hp.source_test_0_dir 35 | source_test_1_dir = hp.source_test_1_dir 36 | 37 | 38 | 39 | 40 | def parse_training_args(parser): 41 | """ 42 | Parse commandline arguments. 43 | """ 44 | 45 | parser.add_argument('-o', '--output_dir', type=str, default=hp.output_dir, required=False, help='Directory to save checkpoints') 46 | parser.add_argument('--latest-checkpoint-file', type=str, default=hp.latest_checkpoint_file, help='Store the latest checkpoint in each epoch') 47 | 48 | # training 49 | training = parser.add_argument_group('training setup') 50 | training.add_argument('--epochs', type=int, default=hp.total_epochs, help='Number of total epochs to run') 51 | training.add_argument('--epochs-per-checkpoint', type=int, default=hp.epochs_per_checkpoint, help='Number of epochs per checkpoint') 52 | training.add_argument('--batch', type=int, default=hp.batch_size, help='batch-size') 53 | parser.add_argument( 54 | '-k', 55 | "--ckpt", 56 | type=str, 57 | default=hp.ckpt, 58 | help="path to the checkpoints to resume training", 59 | ) 60 | parser.add_argument("--init-lr", type=float, default=hp.init_lr, help="learning rate") 61 | # TODO 62 | parser.add_argument( 63 | "--local_rank", type=int, default=0, help="local rank for distributed training" 64 | ) 65 | 66 | training.add_argument('--amp-run', action='store_true', help='Enable AMP') 67 | training.add_argument('--cudnn-enabled', default=True, help='Enable cudnn') 68 | training.add_argument('--cudnn-benchmark', default=True, help='Run cudnn benchmark') 69 | training.add_argument('--disable-uniform-initialize-bn-weight', action='store_true', help='disable uniform initialization of batchnorm layer weight') 70 | 71 | return parser 72 | 73 | 74 | 75 | def train(): 76 | 77 | parser = argparse.ArgumentParser(description='PyTorch Medical Segmentation Training') 78 | parser = parse_training_args(parser) 79 | args, _ = parser.parse_known_args() 80 | 81 | args = parser.parse_args() 82 | 83 | 84 | torch.backends.cudnn.deterministic = True 85 | torch.backends.cudnn.enabled = args.cudnn_enabled 86 | torch.backends.cudnn.benchmark = args.cudnn_benchmark 87 | 88 | 89 | from data_function import MedData_train 90 | os.makedirs(args.output_dir, exist_ok=True) 91 | 92 | if hp.mode == '2d': 93 | # from models.two_d.alexnet import alexnet 94 | # model = alexnet(num_classes=2) 95 | 96 | # from models.two_d.densenet import densenet121 97 | # model = densenet121(num_class=2) 98 | 99 | from models.two_d.googlenet import googlenet 100 | model = googlenet(num_class=2) 101 | 102 | # from models.two_d.mobilenet import mobilenet 103 | # model = mobilenet(class_num=2) 104 | 105 | # from models.two_d.nasnet import nasnet 106 | # model = nasnet(class_num=2) 107 | 108 | # from models.two_d.resnet import resnet101 109 | # model = resnet101(num_classes=2) 110 | 111 | # from models.two_d.resnext import resnext101 112 | # model = resnext101(class_names=2) 113 | 114 | # from models.two_d.vggnet import vgg16_bn 115 | # model = vgg16_bn(num_class=2) 116 | 117 | elif hp.mode == '3d': 118 | 119 | from models.three_d.resnet3d import generate_model 120 | model = generate_model(18,n_input_channels=1,n_classes=2) 121 | 122 | from models.three_d.resnext3d import generate_model 123 | model = generate_model(50,n_input_channels=1,n_classes=2) 124 | 125 | from models.three_d.densenet3d import generate_model 126 | model = generate_model(121,n_input_channels=1,num_classes=2) 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | model = torch.nn.DataParallel(model, device_ids=devicess) 136 | optimizer = torch.optim.Adam(model.parameters(), lr=args.init_lr) 137 | 138 | 139 | # scheduler = ReduceLROnPlateau(optimizer, 'min',factor=0.5, patience=20, verbose=True) 140 | scheduler = StepLR(optimizer, step_size=hp.scheduer_step_size, gamma=hp.scheduer_gamma) 141 | # scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=5e-6) 142 | 143 | if args.ckpt is not None: 144 | print("load model:", args.ckpt) 145 | print(os.path.join(args.output_dir, args.latest_checkpoint_file)) 146 | ckpt = torch.load(os.path.join(args.output_dir, args.latest_checkpoint_file), map_location=lambda storage, loc: storage) 147 | 148 | model.load_state_dict(ckpt["model"]) 149 | optimizer.load_state_dict(ckpt["optim"]) 150 | 151 | for state in optimizer.state.values(): 152 | for k, v in state.items(): 153 | if torch.is_tensor(v): 154 | state[k] = v.cuda() 155 | 156 | # scheduler.load_state_dict(ckpt["scheduler"]) 157 | elapsed_epochs = ckpt["epoch"] 158 | else: 159 | elapsed_epochs = 0 160 | 161 | model.cuda() 162 | 163 | from loss_function import Classification_Loss 164 | criterion = Classification_Loss().cuda() 165 | 166 | 167 | writer = SummaryWriter(args.output_dir) 168 | 169 | 170 | 171 | train_dataset = MedData_train(source_train_0_dir,source_train_1_dir) 172 | train_loader = DataLoader(train_dataset.training_set, 173 | batch_size=args.batch, 174 | shuffle=True, 175 | pin_memory=True, 176 | drop_last=True) 177 | 178 | model.train() 179 | 180 | epochs = args.epochs - elapsed_epochs 181 | iteration = elapsed_epochs * len(train_loader) 182 | 183 | 184 | 185 | for epoch in range(1, epochs + 1): 186 | print("epoch:"+str(epoch)) 187 | epoch += elapsed_epochs 188 | 189 | num_iters = 0 190 | 191 | gts = [] 192 | predicts = [] 193 | for i, batch in enumerate(train_loader): 194 | 195 | 196 | if hp.debug: 197 | if i >=1: 198 | break 199 | 200 | print(f"Batch: {i}/{len(train_loader)} epoch {epoch}") 201 | 202 | optimizer.zero_grad() 203 | 204 | 205 | x = batch['source']['data'] 206 | y = batch['label'] 207 | 208 | x = x.type(torch.FloatTensor).cuda() 209 | y = y.type(torch.LongTensor).cuda() 210 | 211 | if hp.mode == '2d': 212 | x = x.squeeze(-1) 213 | x = x[:,:1,:,:] 214 | 215 | outputs = model(x) 216 | 217 | outputs_logit = outputs.argmax(dim=1) 218 | 219 | loss = criterion(outputs, y, model) 220 | 221 | num_iters += 1 222 | loss.backward() 223 | 224 | optimizer.step() 225 | iteration += 1 226 | 227 | 228 | print("loss:"+str(loss.item())) 229 | writer.add_scalar('Training/Loss', loss.item(),iteration) 230 | 231 | predicts.append(outputs_logit.cpu().detach().numpy()) 232 | gts.append(y.cpu().detach().numpy()) 233 | 234 | 235 | predicts = np.concatenate(predicts).flatten().astype(np.int16) 236 | gts = np.concatenate(gts).flatten().astype(np.int16) 237 | 238 | print(metrics.confusion_matrix(predicts, gts)) 239 | acc = metrics.accuracy_score(predicts, gts) 240 | recall = metrics.recall_score(predicts, gts) 241 | f1 = metrics.f1_score(predicts, gts) 242 | writer.add_scalar('Training/acc', acc,epoch) 243 | writer.add_scalar('Training/recall', recall,epoch) 244 | writer.add_scalar('Training/f1', f1,epoch) 245 | 246 | 247 | scheduler.step() 248 | 249 | 250 | # Store latest checkpoint in each epoch 251 | torch.save( 252 | { 253 | "model": model.state_dict(), 254 | "optim": optimizer.state_dict(), 255 | "scheduler":scheduler.state_dict(), 256 | "epoch": epoch, 257 | 258 | }, 259 | os.path.join(args.output_dir, args.latest_checkpoint_file), 260 | ) 261 | 262 | 263 | # Save checkpoint 264 | if epoch % args.epochs_per_checkpoint == 0: 265 | 266 | torch.save( 267 | { 268 | "model": model.state_dict(), 269 | "optim": optimizer.state_dict(), 270 | "epoch": epoch, 271 | }, 272 | os.path.join(args.output_dir, f"checkpoint_{epoch:04d}.pt"), 273 | ) 274 | 275 | writer.close() 276 | 277 | 278 | def test(): 279 | 280 | parser = argparse.ArgumentParser(description='PyTorch Medical Segmentation Testing') 281 | parser = parse_training_args(parser) 282 | args, _ = parser.parse_known_args() 283 | 284 | args = parser.parse_args() 285 | 286 | 287 | torch.backends.cudnn.deterministic = True 288 | torch.backends.cudnn.enabled = args.cudnn_enabled 289 | torch.backends.cudnn.benchmark = args.cudnn_benchmark 290 | 291 | from data_function import MedData_test 292 | 293 | 294 | if hp.mode == '2d': 295 | # from models.two_d.alexnet import alexnet 296 | # model = alexnet(num_classes=2) 297 | 298 | # from models.two_d.densenet import densenet121 299 | # model = densenet121(num_class=2) 300 | 301 | from models.two_d.googlenet import googlenet 302 | model = googlenet(num_class=2) 303 | 304 | # from models.two_d.mobilenet import mobilenet 305 | # model = mobilenet(class_num=2) 306 | 307 | # from models.two_d.nasnet import nasnet 308 | # model = nasnet(class_num=2) 309 | 310 | # from models.two_d.resnet import resnet101 311 | # model = resnet101(num_classes=2) 312 | 313 | # from models.two_d.resnext import resnext101 314 | # model = resnext101(class_names=2) 315 | 316 | # from models.two_d.vggnet import vgg16_bn 317 | # model = vgg16_bn(num_class=2) 318 | 319 | elif hp.mode == '3d': 320 | 321 | from models.three_d.resnet3d import generate_model 322 | model = generate_model(18,n_input_channels=1,n_classes=2) 323 | 324 | from models.three_d.resnext3d import generate_model 325 | model = generate_model(50,n_input_channels=1,n_classes=2) 326 | 327 | from models.three_d.densenet3d import generate_model 328 | model = generate_model(121,n_input_channels=1,num_classes=2) 329 | 330 | 331 | 332 | 333 | model = torch.nn.DataParallel(model, device_ids=devicess,output_device=[1]) 334 | 335 | 336 | print("load model:", args.ckpt) 337 | print(os.path.join(args.output_dir, args.latest_checkpoint_file)) 338 | ckpt = torch.load(os.path.join(args.output_dir, args.latest_checkpoint_file), map_location=lambda storage, loc: storage) 339 | 340 | model.load_state_dict(ckpt["model"]) 341 | 342 | 343 | model.cuda() 344 | 345 | test_dataset = MedData_test(source_test_0_dir,source_test_1_dir) 346 | test_loader = DataLoader(test_dataset.testing_set, 347 | batch_size=args.batch, 348 | shuffle=True, 349 | pin_memory=True, 350 | drop_last=True) 351 | 352 | model.eval() 353 | 354 | predicts = [] 355 | gts = [] 356 | for i, batch in enumerate(test_loader): 357 | 358 | x = batch['source']['data'] 359 | y = batch['label'] 360 | 361 | x = x.type(torch.FloatTensor).cuda() 362 | y = y.type(torch.LongTensor).cuda() 363 | 364 | if hp.mode == '2d': 365 | x = x.squeeze(-1) 366 | x = x[:,:1,:,:] 367 | 368 | outputs = model(x) 369 | 370 | outputs_logit = outputs.argmax(dim=1) 371 | 372 | predicts.append(outputs_logit.cpu().detach().numpy()) 373 | gts.append(y.cpu().detach().numpy()) 374 | 375 | predicts = np.concatenate(predicts).flatten().astype(np.int16) 376 | gts = np.concatenate(gts).flatten().astype(np.int16) 377 | acc = metrics.accuracy_score(predicts, gts) 378 | recall = metrics.recall_score(predicts, gts) 379 | f1 = metrics.f1_score(predicts, gts) 380 | ## log 381 | print("acc:"+str(acc)) 382 | print("recall:"+str(recall)) 383 | print("f1:"+str(f1)) 384 | print(metrics.confusion_matrix(predicts, gts)) 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | if __name__ == '__main__': 395 | if hp.train_or_test == 'train': 396 | train() 397 | elif hp.train_or_test == 'test': 398 | test() 399 | -------------------------------------------------------------------------------- /models/three_d/densenet3d.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | from collections import OrderedDict 7 | 8 | 9 | class _DenseLayer(nn.Sequential): 10 | 11 | def __init__(self, num_input_features, growth_rate, bn_size, drop_rate): 12 | super().__init__() 13 | self.add_module('norm1', nn.BatchNorm3d(num_input_features)) 14 | self.add_module('relu1', nn.ReLU(inplace=True)) 15 | self.add_module( 16 | 'conv1', 17 | nn.Conv3d(num_input_features, 18 | bn_size * growth_rate, 19 | kernel_size=1, 20 | stride=1, 21 | bias=False)) 22 | self.add_module('norm2', nn.BatchNorm3d(bn_size * growth_rate)) 23 | self.add_module('relu2', nn.ReLU(inplace=True)) 24 | self.add_module( 25 | 'conv2', 26 | nn.Conv3d(bn_size * growth_rate, 27 | growth_rate, 28 | kernel_size=3, 29 | stride=1, 30 | padding=1, 31 | bias=False)) 32 | self.drop_rate = drop_rate 33 | 34 | def forward(self, x): 35 | new_features = super().forward(x) 36 | if self.drop_rate > 0: 37 | new_features = F.dropout(new_features, 38 | p=self.drop_rate, 39 | training=self.training) 40 | return torch.cat([x, new_features], 1) 41 | 42 | 43 | class _DenseBlock(nn.Sequential): 44 | 45 | def __init__(self, num_layers, num_input_features, bn_size, growth_rate, 46 | drop_rate): 47 | super().__init__() 48 | for i in range(num_layers): 49 | layer = _DenseLayer(num_input_features + i * growth_rate, 50 | growth_rate, bn_size, drop_rate) 51 | self.add_module('denselayer{}'.format(i + 1), layer) 52 | 53 | 54 | class _Transition(nn.Sequential): 55 | 56 | def __init__(self, num_input_features, num_output_features): 57 | super().__init__() 58 | self.add_module('norm', nn.BatchNorm3d(num_input_features)) 59 | self.add_module('relu', nn.ReLU(inplace=True)) 60 | self.add_module( 61 | 'conv', 62 | nn.Conv3d(num_input_features, 63 | num_output_features, 64 | kernel_size=1, 65 | stride=1, 66 | bias=False)) 67 | self.add_module('pool', nn.AvgPool3d(kernel_size=2, stride=2)) 68 | 69 | 70 | class DenseNet(nn.Module): 71 | """Densenet-BC model class 72 | Args: 73 | growth_rate (int) - how many filters to add each layer (k in paper) 74 | block_config (list of 4 ints) - how many layers in each pooling block 75 | num_init_features (int) - the number of filters to learn in the first convolution layer 76 | bn_size (int) - multiplicative factor for number of bottle neck layers 77 | (i.e. bn_size * k features in the bottleneck layer) 78 | drop_rate (float) - dropout rate after each dense layer 79 | num_classes (int) - number of classification classes 80 | """ 81 | 82 | def __init__(self, 83 | n_input_channels=3, 84 | conv1_t_size=7, 85 | conv1_t_stride=1, 86 | no_max_pool=False, 87 | growth_rate=32, 88 | block_config=(6, 12, 24, 16), 89 | num_init_features=64, 90 | bn_size=4, 91 | drop_rate=0, 92 | num_classes=1000): 93 | 94 | super().__init__() 95 | 96 | # First convolution 97 | self.features = [('conv1', 98 | nn.Conv3d(n_input_channels, 99 | num_init_features, 100 | kernel_size=(conv1_t_size, 7, 7), 101 | stride=(conv1_t_stride, 2, 2), 102 | padding=(conv1_t_size // 2, 3, 3), 103 | bias=False)), 104 | ('norm1', nn.BatchNorm3d(num_init_features)), 105 | ('relu1', nn.ReLU(inplace=True))] 106 | if not no_max_pool: 107 | self.features.append( 108 | ('pool1', nn.MaxPool3d(kernel_size=3, stride=2, padding=1))) 109 | self.features = nn.Sequential(OrderedDict(self.features)) 110 | 111 | # Each denseblock 112 | num_features = num_init_features 113 | for i, num_layers in enumerate(block_config): 114 | block = _DenseBlock(num_layers=num_layers, 115 | num_input_features=num_features, 116 | bn_size=bn_size, 117 | growth_rate=growth_rate, 118 | drop_rate=drop_rate) 119 | self.features.add_module('denseblock{}'.format(i + 1), block) 120 | num_features = num_features + num_layers * growth_rate 121 | if i != len(block_config) - 1: 122 | trans = _Transition(num_input_features=num_features, 123 | num_output_features=num_features // 2) 124 | self.features.add_module('transition{}'.format(i + 1), trans) 125 | num_features = num_features // 2 126 | 127 | # Final batch norm 128 | self.features.add_module('norm5', nn.BatchNorm3d(num_features)) 129 | 130 | for m in self.modules(): 131 | if isinstance(m, nn.Conv3d): 132 | m.weight = nn.init.kaiming_normal(m.weight, mode='fan_out') 133 | elif isinstance(m, nn.BatchNorm3d) or isinstance(m, nn.BatchNorm2d): 134 | m.weight.data.fill_(1) 135 | m.bias.data.zero_() 136 | 137 | # Linear layer 138 | self.classifier = nn.Linear(num_features, num_classes) 139 | 140 | for m in self.modules(): 141 | if isinstance(m, nn.Conv3d): 142 | nn.init.kaiming_normal_(m.weight, 143 | mode='fan_out', 144 | nonlinearity='relu') 145 | elif isinstance(m, nn.BatchNorm3d): 146 | nn.init.constant_(m.weight, 1) 147 | nn.init.constant_(m.bias, 0) 148 | elif isinstance(m, nn.Linear): 149 | nn.init.constant_(m.bias, 0) 150 | 151 | def forward(self, x): 152 | features = self.features(x) 153 | out = F.relu(features, inplace=True) 154 | out = F.adaptive_avg_pool3d(out, 155 | output_size=(1, 1, 156 | 1)).view(features.size(0), -1) 157 | out = self.classifier(out) 158 | return out 159 | 160 | 161 | def generate_model(model_depth, **kwargs): 162 | assert model_depth in [121, 169, 201, 264] 163 | 164 | if model_depth == 121: 165 | model = DenseNet(num_init_features=64, 166 | growth_rate=32, 167 | block_config=(6, 12, 24, 16), 168 | **kwargs) 169 | elif model_depth == 169: 170 | model = DenseNet(num_init_features=64, 171 | growth_rate=32, 172 | block_config=(6, 12, 32, 32), 173 | **kwargs) 174 | elif model_depth == 201: 175 | model = DenseNet(num_init_features=64, 176 | growth_rate=32, 177 | block_config=(6, 12, 48, 32), 178 | **kwargs) 179 | elif model_depth == 264: 180 | model = DenseNet(num_init_features=64, 181 | growth_rate=32, 182 | block_config=(6, 12, 64, 48), 183 | **kwargs) 184 | 185 | return model 186 | 187 | 188 | if __name__ == "__main__": 189 | 190 | 191 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 192 | image_size = 64 193 | x = torch.Tensor(1, 1, image_size, image_size, image_size) 194 | x = x.to(device) 195 | print("x size: {}".format(x.size())) 196 | 197 | model = generate_model(121,n_input_channels=1,num_classes=2).to(device) 198 | 199 | 200 | out1 = model(x) 201 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/three_d/resnet3d.py: -------------------------------------------------------------------------------- 1 | import math 2 | from functools import partial 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | 8 | 9 | def get_inplanes(): 10 | return [64, 128, 256, 512] 11 | 12 | 13 | def conv3x3x3(in_planes, out_planes, stride=1): 14 | return nn.Conv3d(in_planes, 15 | out_planes, 16 | kernel_size=3, 17 | stride=stride, 18 | padding=1, 19 | bias=False) 20 | 21 | 22 | def conv1x1x1(in_planes, out_planes, stride=1): 23 | return nn.Conv3d(in_planes, 24 | out_planes, 25 | kernel_size=1, 26 | stride=stride, 27 | bias=False) 28 | 29 | 30 | class BasicBlock(nn.Module): 31 | expansion = 1 32 | 33 | def __init__(self, in_planes, planes, stride=1, downsample=None): 34 | super().__init__() 35 | 36 | self.conv1 = conv3x3x3(in_planes, planes, stride) 37 | self.bn1 = nn.BatchNorm3d(planes) 38 | self.relu = nn.ReLU(inplace=True) 39 | self.conv2 = conv3x3x3(planes, planes) 40 | self.bn2 = nn.BatchNorm3d(planes) 41 | self.downsample = downsample 42 | self.stride = stride 43 | 44 | def forward(self, x): 45 | residual = x 46 | 47 | out = self.conv1(x) 48 | out = self.bn1(out) 49 | out = self.relu(out) 50 | 51 | out = self.conv2(out) 52 | out = self.bn2(out) 53 | 54 | if self.downsample is not None: 55 | residual = self.downsample(x) 56 | 57 | out += residual 58 | out = self.relu(out) 59 | 60 | return out 61 | 62 | 63 | class Bottleneck(nn.Module): 64 | expansion = 4 65 | 66 | def __init__(self, in_planes, planes, stride=1, downsample=None): 67 | super().__init__() 68 | 69 | self.conv1 = conv1x1x1(in_planes, planes) 70 | self.bn1 = nn.BatchNorm3d(planes) 71 | self.conv2 = conv3x3x3(planes, planes, stride) 72 | self.bn2 = nn.BatchNorm3d(planes) 73 | self.conv3 = conv1x1x1(planes, planes * self.expansion) 74 | self.bn3 = nn.BatchNorm3d(planes * self.expansion) 75 | self.relu = nn.ReLU(inplace=True) 76 | self.downsample = downsample 77 | self.stride = stride 78 | 79 | def forward(self, x): 80 | residual = x 81 | 82 | out = self.conv1(x) 83 | out = self.bn1(out) 84 | out = self.relu(out) 85 | 86 | out = self.conv2(out) 87 | out = self.bn2(out) 88 | out = self.relu(out) 89 | 90 | out = self.conv3(out) 91 | out = self.bn3(out) 92 | 93 | if self.downsample is not None: 94 | residual = self.downsample(x) 95 | 96 | out += residual 97 | out = self.relu(out) 98 | 99 | return out 100 | 101 | 102 | class ResNet(nn.Module): 103 | 104 | def __init__(self, 105 | block, 106 | layers, 107 | block_inplanes, 108 | n_input_channels=3, 109 | conv1_t_size=7, 110 | conv1_t_stride=1, 111 | no_max_pool=False, 112 | shortcut_type='B', 113 | widen_factor=1.0, 114 | n_classes=400): 115 | super().__init__() 116 | 117 | block_inplanes = [int(x * widen_factor) for x in block_inplanes] 118 | 119 | self.in_planes = block_inplanes[0] 120 | self.no_max_pool = no_max_pool 121 | 122 | self.conv1 = nn.Conv3d(n_input_channels, 123 | self.in_planes, 124 | kernel_size=(conv1_t_size, 7, 7), 125 | stride=(conv1_t_stride, 2, 2), 126 | padding=(conv1_t_size // 2, 3, 3), 127 | bias=False) 128 | self.bn1 = nn.BatchNorm3d(self.in_planes) 129 | self.relu = nn.ReLU(inplace=True) 130 | self.maxpool = nn.MaxPool3d(kernel_size=3, stride=2, padding=1) 131 | self.layer1 = self._make_layer(block, block_inplanes[0], layers[0], 132 | shortcut_type) 133 | self.layer2 = self._make_layer(block, 134 | block_inplanes[1], 135 | layers[1], 136 | shortcut_type, 137 | stride=2) 138 | self.layer3 = self._make_layer(block, 139 | block_inplanes[2], 140 | layers[2], 141 | shortcut_type, 142 | stride=2) 143 | self.layer4 = self._make_layer(block, 144 | block_inplanes[3], 145 | layers[3], 146 | shortcut_type, 147 | stride=2) 148 | 149 | self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1)) 150 | self.fc = nn.Linear(block_inplanes[3] * block.expansion, n_classes) 151 | 152 | for m in self.modules(): 153 | if isinstance(m, nn.Conv3d): 154 | nn.init.kaiming_normal_(m.weight, 155 | mode='fan_out', 156 | nonlinearity='relu') 157 | elif isinstance(m, nn.BatchNorm3d): 158 | nn.init.constant_(m.weight, 1) 159 | nn.init.constant_(m.bias, 0) 160 | 161 | def _downsample_basic_block(self, x, planes, stride): 162 | out = F.avg_pool3d(x, kernel_size=1, stride=stride) 163 | zero_pads = torch.zeros(out.size(0), planes - out.size(1), out.size(2), 164 | out.size(3), out.size(4)) 165 | if isinstance(out.data, torch.cuda.FloatTensor): 166 | zero_pads = zero_pads.cuda() 167 | 168 | out = torch.cat([out.data, zero_pads], dim=1) 169 | 170 | return out 171 | 172 | def _make_layer(self, block, planes, blocks, shortcut_type, stride=1): 173 | downsample = None 174 | if stride != 1 or self.in_planes != planes * block.expansion: 175 | if shortcut_type == 'A': 176 | downsample = partial(self._downsample_basic_block, 177 | planes=planes * block.expansion, 178 | stride=stride) 179 | else: 180 | downsample = nn.Sequential( 181 | conv1x1x1(self.in_planes, planes * block.expansion, stride), 182 | nn.BatchNorm3d(planes * block.expansion)) 183 | 184 | layers = [] 185 | layers.append( 186 | block(in_planes=self.in_planes, 187 | planes=planes, 188 | stride=stride, 189 | downsample=downsample)) 190 | self.in_planes = planes * block.expansion 191 | for i in range(1, blocks): 192 | layers.append(block(self.in_planes, planes)) 193 | 194 | return nn.Sequential(*layers) 195 | 196 | def forward(self, x): 197 | x = self.conv1(x) 198 | x = self.bn1(x) 199 | x = self.relu(x) 200 | if not self.no_max_pool: 201 | x = self.maxpool(x) 202 | 203 | x = self.layer1(x) 204 | x = self.layer2(x) 205 | x = self.layer3(x) 206 | x = self.layer4(x) 207 | 208 | x = self.avgpool(x) 209 | 210 | x = x.view(x.size(0), -1) 211 | x = self.fc(x) 212 | 213 | return x 214 | 215 | 216 | def generate_model(model_depth, **kwargs): 217 | assert model_depth in [10, 18, 34, 50, 101, 152, 200] 218 | 219 | if model_depth == 10: 220 | model = ResNet(BasicBlock, [1, 1, 1, 1], get_inplanes(), **kwargs) 221 | elif model_depth == 18: 222 | model = ResNet(BasicBlock, [2, 2, 2, 2], get_inplanes(), **kwargs) 223 | elif model_depth == 34: 224 | model = ResNet(BasicBlock, [3, 4, 6, 3], get_inplanes(), **kwargs) 225 | elif model_depth == 50: 226 | model = ResNet(Bottleneck, [3, 4, 6, 3], get_inplanes(), **kwargs) 227 | elif model_depth == 101: 228 | model = ResNet(Bottleneck, [3, 4, 23, 3], get_inplanes(), **kwargs) 229 | elif model_depth == 152: 230 | model = ResNet(Bottleneck, [3, 8, 36, 3], get_inplanes(), **kwargs) 231 | elif model_depth == 200: 232 | model = ResNet(Bottleneck, [3, 24, 36, 3], get_inplanes(), **kwargs) 233 | 234 | return model 235 | 236 | 237 | if __name__ == "__main__": 238 | 239 | 240 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 241 | image_size = 64 242 | x = torch.Tensor(1, 1, image_size, image_size, image_size) 243 | x = x.to(device) 244 | print("x size: {}".format(x.size())) 245 | 246 | model = generate_model(18,n_input_channels=1,n_classes=2).to(device) 247 | 248 | 249 | out1 = model(x) 250 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/three_d/resnext3d.py: -------------------------------------------------------------------------------- 1 | import math 2 | from functools import partial, partialmethod 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | 8 | 9 | def conv3x3x3(in_planes, out_planes, stride=1): 10 | return nn.Conv3d(in_planes, 11 | out_planes, 12 | kernel_size=3, 13 | stride=stride, 14 | padding=1, 15 | bias=False) 16 | 17 | 18 | def conv1x1x1(in_planes, out_planes, stride=1): 19 | return nn.Conv3d(in_planes, 20 | out_planes, 21 | kernel_size=1, 22 | stride=stride, 23 | bias=False) 24 | 25 | 26 | class Bottleneck(nn.Module): 27 | expansion = 4 28 | 29 | def __init__(self, in_planes, planes, stride=1, downsample=None): 30 | super().__init__() 31 | 32 | self.conv1 = conv1x1x1(in_planes, planes) 33 | self.bn1 = nn.BatchNorm3d(planes) 34 | self.conv2 = conv3x3x3(planes, planes, stride) 35 | self.bn2 = nn.BatchNorm3d(planes) 36 | self.conv3 = conv1x1x1(planes, planes * self.expansion) 37 | self.bn3 = nn.BatchNorm3d(planes * self.expansion) 38 | self.relu = nn.ReLU(inplace=True) 39 | self.downsample = downsample 40 | self.stride = stride 41 | 42 | def forward(self, x): 43 | residual = x 44 | 45 | out = self.conv1(x) 46 | out = self.bn1(out) 47 | out = self.relu(out) 48 | 49 | out = self.conv2(out) 50 | out = self.bn2(out) 51 | out = self.relu(out) 52 | 53 | out = self.conv3(out) 54 | out = self.bn3(out) 55 | 56 | if self.downsample is not None: 57 | residual = self.downsample(x) 58 | 59 | out += residual 60 | out = self.relu(out) 61 | 62 | return out 63 | 64 | 65 | class ResNet(nn.Module): 66 | 67 | def __init__(self, 68 | block, 69 | layers, 70 | block_inplanes, 71 | n_input_channels=3, 72 | conv1_t_size=7, 73 | conv1_t_stride=1, 74 | no_max_pool=False, 75 | shortcut_type='B', 76 | widen_factor=1.0, 77 | n_classes=400): 78 | super().__init__() 79 | 80 | block_inplanes = [int(x * widen_factor) for x in block_inplanes] 81 | 82 | self.in_planes = block_inplanes[0] 83 | self.no_max_pool = no_max_pool 84 | 85 | self.conv1 = nn.Conv3d(n_input_channels, 86 | self.in_planes, 87 | kernel_size=(conv1_t_size, 7, 7), 88 | stride=(conv1_t_stride, 2, 2), 89 | padding=(conv1_t_size // 2, 3, 3), 90 | bias=False) 91 | self.bn1 = nn.BatchNorm3d(self.in_planes) 92 | self.relu = nn.ReLU(inplace=True) 93 | self.maxpool = nn.MaxPool3d(kernel_size=3, stride=2, padding=1) 94 | self.layer1 = self._make_layer(block, block_inplanes[0], layers[0], 95 | shortcut_type) 96 | self.layer2 = self._make_layer(block, 97 | block_inplanes[1], 98 | layers[1], 99 | shortcut_type, 100 | stride=2) 101 | self.layer3 = self._make_layer(block, 102 | block_inplanes[2], 103 | layers[2], 104 | shortcut_type, 105 | stride=2) 106 | self.layer4 = self._make_layer(block, 107 | block_inplanes[3], 108 | layers[3], 109 | shortcut_type, 110 | stride=2) 111 | 112 | self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1)) 113 | self.fc = nn.Linear(block_inplanes[3] * block.expansion, n_classes) 114 | 115 | for m in self.modules(): 116 | if isinstance(m, nn.Conv3d): 117 | nn.init.kaiming_normal_(m.weight, 118 | mode='fan_out', 119 | nonlinearity='relu') 120 | elif isinstance(m, nn.BatchNorm3d): 121 | nn.init.constant_(m.weight, 1) 122 | nn.init.constant_(m.bias, 0) 123 | 124 | def _downsample_basic_block(self, x, planes, stride): 125 | out = F.avg_pool3d(x, kernel_size=1, stride=stride) 126 | zero_pads = torch.zeros(out.size(0), planes - out.size(1), out.size(2), 127 | out.size(3), out.size(4)) 128 | if isinstance(out.data, torch.cuda.FloatTensor): 129 | zero_pads = zero_pads.cuda() 130 | 131 | out = torch.cat([out.data, zero_pads], dim=1) 132 | 133 | return out 134 | 135 | def _make_layer(self, block, planes, blocks, shortcut_type, stride=1): 136 | downsample = None 137 | if stride != 1 or self.in_planes != planes * block.expansion: 138 | if shortcut_type == 'A': 139 | downsample = partial(self._downsample_basic_block, 140 | planes=planes * block.expansion, 141 | stride=stride) 142 | else: 143 | downsample = nn.Sequential( 144 | conv1x1x1(self.in_planes, planes * block.expansion, stride), 145 | nn.BatchNorm3d(planes * block.expansion)) 146 | 147 | layers = [] 148 | layers.append( 149 | block(in_planes=self.in_planes, 150 | planes=planes, 151 | stride=stride, 152 | downsample=downsample)) 153 | self.in_planes = planes * block.expansion 154 | for i in range(1, blocks): 155 | layers.append(block(self.in_planes, planes)) 156 | 157 | return nn.Sequential(*layers) 158 | 159 | def forward(self, x): 160 | x = self.conv1(x) 161 | x = self.bn1(x) 162 | x = self.relu(x) 163 | if not self.no_max_pool: 164 | x = self.maxpool(x) 165 | 166 | x = self.layer1(x) 167 | x = self.layer2(x) 168 | x = self.layer3(x) 169 | x = self.layer4(x) 170 | 171 | x = self.avgpool(x) 172 | 173 | x = x.view(x.size(0), -1) 174 | x = self.fc(x) 175 | 176 | return x 177 | 178 | 179 | 180 | def partialclass(cls, *args, **kwargs): 181 | 182 | class PartialClass(cls): 183 | __init__ = partialmethod(cls.__init__, *args, **kwargs) 184 | 185 | return PartialClass 186 | 187 | 188 | def get_inplanes(): 189 | return [128, 256, 512, 1024] 190 | 191 | 192 | class ResNeXtBottleneck(Bottleneck): 193 | expansion = 2 194 | 195 | def __init__(self, in_planes, planes, cardinality, stride=1, 196 | downsample=None): 197 | super().__init__(in_planes, planes, stride, downsample) 198 | 199 | mid_planes = cardinality * planes // 32 200 | self.conv1 = conv1x1x1(in_planes, mid_planes) 201 | self.bn1 = nn.BatchNorm3d(mid_planes) 202 | self.conv2 = nn.Conv3d(mid_planes, 203 | mid_planes, 204 | kernel_size=3, 205 | stride=stride, 206 | padding=1, 207 | groups=cardinality, 208 | bias=False) 209 | self.bn2 = nn.BatchNorm3d(mid_planes) 210 | self.conv3 = conv1x1x1(mid_planes, planes * self.expansion) 211 | 212 | 213 | class ResNeXt(ResNet): 214 | 215 | def __init__(self, 216 | block, 217 | layers, 218 | block_inplanes, 219 | n_input_channels=3, 220 | conv1_t_size=7, 221 | conv1_t_stride=1, 222 | no_max_pool=False, 223 | shortcut_type='B', 224 | cardinality=64, 225 | n_classes=400): 226 | block = partialclass(block, cardinality=cardinality) 227 | super().__init__(block, layers, block_inplanes, n_input_channels, 228 | conv1_t_size, conv1_t_stride, no_max_pool, 229 | shortcut_type, n_classes) 230 | 231 | self.fc = nn.Linear(cardinality * 16 * block.expansion, n_classes) 232 | 233 | 234 | def generate_model(model_depth, **kwargs): 235 | assert model_depth in [50, 101, 152, 200] 236 | 237 | if model_depth == 50: 238 | model = ResNeXt(ResNeXtBottleneck, [3, 4, 6, 3], get_inplanes(), 239 | **kwargs) 240 | elif model_depth == 101: 241 | model = ResNeXt(ResNeXtBottleneck, [3, 4, 23, 3], get_inplanes(), 242 | **kwargs) 243 | elif model_depth == 152: 244 | model = ResNeXt(ResNeXtBottleneck, [3, 8, 36, 3], get_inplanes(), 245 | **kwargs) 246 | elif model_depth == 200: 247 | model = ResNeXt(ResNeXtBottleneck, [3, 24, 36, 3], get_inplanes(), 248 | **kwargs) 249 | 250 | return model 251 | 252 | 253 | if __name__ == "__main__": 254 | 255 | 256 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 257 | image_size = 64 258 | x = torch.Tensor(1, 1, image_size, image_size, image_size) 259 | x = x.to(device) 260 | print("x size: {}".format(x.size())) 261 | 262 | model = generate_model(50,n_input_channels=1,n_classes=1).to(device) 263 | 264 | 265 | out1 = model(x) 266 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/alexnet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | import torch 4 | 5 | class AlexNet(nn.Module): 6 | 7 | def __init__(self, num_classes=1000): 8 | super(AlexNet, self).__init__() 9 | self.features = nn.Sequential( 10 | nn.Conv2d(1, 64, kernel_size=11, stride=4, padding=2), 11 | nn.ReLU(inplace=True), 12 | nn.MaxPool2d(kernel_size=3, stride=2), 13 | nn.Conv2d(64, 192, kernel_size=5, padding=2), 14 | nn.ReLU(inplace=True), 15 | nn.MaxPool2d(kernel_size=3, stride=2), 16 | nn.Conv2d(192, 384, kernel_size=3, padding=1), 17 | nn.ReLU(inplace=True), 18 | nn.Conv2d(384, 256, kernel_size=3, padding=1), 19 | nn.ReLU(inplace=True), 20 | nn.Conv2d(256, 256, kernel_size=3, padding=1), 21 | nn.ReLU(inplace=True), 22 | nn.MaxPool2d(kernel_size=3, stride=2), 23 | ) 24 | self.classifier = nn.Sequential( 25 | nn.Dropout(), 26 | nn.Linear(256 * 6 * 6, 4096), 27 | nn.ReLU(inplace=True), 28 | nn.Dropout(), 29 | nn.Linear(4096, 4096), 30 | nn.ReLU(inplace=True), 31 | nn.Linear(4096, num_classes), 32 | ) 33 | 34 | def forward(self, x): 35 | x = self.features(x) 36 | x = x.view(x.size(0), 256 * 6 * 6) 37 | x = self.classifier(x) 38 | return x 39 | 40 | 41 | def alexnet(num_classes): 42 | return AlexNet(num_classes) 43 | 44 | 45 | if __name__ == "__main__": 46 | 47 | 48 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 49 | image_size = 224 50 | x = torch.Tensor(1, 1, image_size, image_size) 51 | x = x.to(device) 52 | print("x size: {}".format(x.size())) 53 | 54 | model = alexnet(num_classes=2).to(device) 55 | 56 | 57 | out1 = model(x) 58 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/densenet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | 5 | 6 | #"""Bottleneck layers. Although each layer only produces k 7 | #output feature-maps, it typically has many more inputs. It 8 | #has been noted in [37, 11] that a 1×1 convolution can be in- 9 | #troduced as bottleneck layer before each 3×3 convolution 10 | #to reduce the number of input feature-maps, and thus to 11 | #improve computational efficiency.""" 12 | class Bottleneck(nn.Module): 13 | def __init__(self, in_channels, growth_rate): 14 | super().__init__() 15 | #"""In our experiments, we let each 1×1 convolution 16 | #produce 4k feature-maps.""" 17 | inner_channel = 4 * growth_rate 18 | 19 | #"""We find this design especially effective for DenseNet and 20 | #we refer to our network with such a bottleneck layer, i.e., 21 | #to the BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3) version of H ` , 22 | #as DenseNet-B.""" 23 | self.bottle_neck = nn.Sequential( 24 | nn.BatchNorm2d(in_channels), 25 | nn.ReLU(inplace=True), 26 | nn.Conv2d(in_channels, inner_channel, kernel_size=1, bias=False), 27 | nn.BatchNorm2d(inner_channel), 28 | nn.ReLU(inplace=True), 29 | nn.Conv2d(inner_channel, growth_rate, kernel_size=3, padding=1, bias=False) 30 | ) 31 | 32 | def forward(self, x): 33 | return torch.cat([x, self.bottle_neck(x)], 1) 34 | 35 | #"""We refer to layers between blocks as transition 36 | #layers, which do convolution and pooling.""" 37 | class Transition(nn.Module): 38 | def __init__(self, in_channels, out_channels): 39 | super().__init__() 40 | #"""The transition layers used in our experiments 41 | #consist of a batch normalization layer and an 1×1 42 | #convolutional layer followed by a 2×2 average pooling 43 | #layer""". 44 | self.down_sample = nn.Sequential( 45 | nn.BatchNorm2d(in_channels), 46 | nn.Conv2d(in_channels, out_channels, 1, bias=False), 47 | nn.AvgPool2d(2, stride=2) 48 | ) 49 | 50 | def forward(self, x): 51 | return self.down_sample(x) 52 | 53 | #DesneNet-BC 54 | #B stands for bottleneck layer(BN-RELU-CONV(1x1)-BN-RELU-CONV(3x3)) 55 | #C stands for compression factor(0<=theta<=1) 56 | class DenseNet(nn.Module): 57 | def __init__(self, block, nblocks, growth_rate=12, reduction=0.5, num_class=100): 58 | super().__init__() 59 | self.growth_rate = growth_rate 60 | 61 | #"""Before entering the first dense block, a convolution 62 | #with 16 (or twice the growth rate for DenseNet-BC) 63 | #output channels is performed on the input images.""" 64 | inner_channels = 2 * growth_rate 65 | 66 | #For convolutional layers with kernel size 3×3, each 67 | #side of the inputs is zero-padded by one pixel to keep 68 | #the feature-map size fixed. 69 | self.conv1 = nn.Conv2d(1, inner_channels, kernel_size=3, padding=1, bias=False) 70 | 71 | self.features = nn.Sequential() 72 | 73 | for index in range(len(nblocks) - 1): 74 | self.features.add_module("dense_block_layer_{}".format(index), self._make_dense_layers(block, inner_channels, nblocks[index])) 75 | inner_channels += growth_rate * nblocks[index] 76 | 77 | #"""If a dense block contains m feature-maps, we let the 78 | #following transition layer generate θm output feature- 79 | #maps, where 0 < θ ≤ 1 is referred to as the compression 80 | #fac-tor. 81 | out_channels = int(reduction * inner_channels) # int() will automatic floor the value 82 | self.features.add_module("transition_layer_{}".format(index), Transition(inner_channels, out_channels)) 83 | inner_channels = out_channels 84 | 85 | self.features.add_module("dense_block{}".format(len(nblocks) - 1), self._make_dense_layers(block, inner_channels, nblocks[len(nblocks)-1])) 86 | inner_channels += growth_rate * nblocks[len(nblocks) - 1] 87 | self.features.add_module('bn', nn.BatchNorm2d(inner_channels)) 88 | self.features.add_module('relu', nn.ReLU(inplace=True)) 89 | 90 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 91 | 92 | self.linear = nn.Linear(inner_channels, num_class) 93 | 94 | def forward(self, x): 95 | output = self.conv1(x) 96 | output = self.features(output) 97 | output = self.avgpool(output) 98 | output = output.view(output.size()[0], -1) 99 | output = self.linear(output) 100 | return output 101 | 102 | def _make_dense_layers(self, block, in_channels, nblocks): 103 | dense_block = nn.Sequential() 104 | for index in range(nblocks): 105 | dense_block.add_module('bottle_neck_layer_{}'.format(index), block(in_channels, self.growth_rate)) 106 | in_channels += self.growth_rate 107 | return dense_block 108 | 109 | def densenet121(num_class): 110 | return DenseNet(Bottleneck, [6,12,24,16], growth_rate=32, num_class=num_class) 111 | 112 | def densenet169(num_class): 113 | return DenseNet(Bottleneck, [6,12,32,32], growth_rate=32, num_class=num_class) 114 | 115 | def densenet201(num_class): 116 | return DenseNet(Bottleneck, [6,12,48,32], growth_rate=32, num_class=num_class) 117 | 118 | def densenet161(num_class): 119 | return DenseNet(Bottleneck, [6,12,36,24], growth_rate=48, num_class=num_class) 120 | 121 | 122 | if __name__ == "__main__": 123 | 124 | 125 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 126 | image_size = 256 127 | x = torch.Tensor(1, 1, image_size, image_size) 128 | x = x.to(device) 129 | print("x size: {}".format(x.size())) 130 | 131 | model = densenet161(num_class=2).to(device) 132 | 133 | 134 | out1 = model(x) 135 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/googlenet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class Inception(nn.Module): 5 | def __init__(self, input_channels, n1x1, n3x3_reduce, n3x3, n5x5_reduce, n5x5, pool_proj): 6 | super().__init__() 7 | 8 | #1x1conv branch 9 | self.b1 = nn.Sequential( 10 | nn.Conv2d(input_channels, n1x1, kernel_size=1), 11 | nn.BatchNorm2d(n1x1), 12 | nn.ReLU(inplace=True) 13 | ) 14 | 15 | #1x1conv -> 3x3conv branch 16 | self.b2 = nn.Sequential( 17 | nn.Conv2d(input_channels, n3x3_reduce, kernel_size=1), 18 | nn.BatchNorm2d(n3x3_reduce), 19 | nn.ReLU(inplace=True), 20 | nn.Conv2d(n3x3_reduce, n3x3, kernel_size=3, padding=1), 21 | nn.BatchNorm2d(n3x3), 22 | nn.ReLU(inplace=True) 23 | ) 24 | 25 | #1x1conv -> 5x5conv branch 26 | #we use 2 3x3 conv filters stacked instead 27 | #of 1 5x5 filters to obtain the same receptive 28 | #field with fewer parameters 29 | self.b3 = nn.Sequential( 30 | nn.Conv2d(input_channels, n5x5_reduce, kernel_size=1), 31 | nn.BatchNorm2d(n5x5_reduce), 32 | nn.ReLU(inplace=True), 33 | nn.Conv2d(n5x5_reduce, n5x5, kernel_size=3, padding=1), 34 | nn.BatchNorm2d(n5x5, n5x5), 35 | nn.ReLU(inplace=True), 36 | nn.Conv2d(n5x5, n5x5, kernel_size=3, padding=1), 37 | nn.BatchNorm2d(n5x5), 38 | nn.ReLU(inplace=True) 39 | ) 40 | 41 | #3x3pooling -> 1x1conv 42 | #same conv 43 | self.b4 = nn.Sequential( 44 | nn.MaxPool2d(3, stride=1, padding=1), 45 | nn.Conv2d(input_channels, pool_proj, kernel_size=1), 46 | nn.BatchNorm2d(pool_proj), 47 | nn.ReLU(inplace=True) 48 | ) 49 | 50 | def forward(self, x): 51 | return torch.cat([self.b1(x), self.b2(x), self.b3(x), self.b4(x)], dim=1) 52 | 53 | 54 | class GoogleNet(nn.Module): 55 | 56 | def __init__(self, num_class=100): 57 | super().__init__() 58 | self.prelayer = nn.Sequential( 59 | nn.Conv2d(1, 64, kernel_size=3, padding=1, bias=False), 60 | nn.BatchNorm2d(64), 61 | nn.ReLU(inplace=True), 62 | nn.Conv2d(64, 64, kernel_size=3, padding=1, bias=False), 63 | nn.BatchNorm2d(64), 64 | nn.ReLU(inplace=True), 65 | nn.Conv2d(64, 192, kernel_size=3, padding=1, bias=False), 66 | nn.BatchNorm2d(192), 67 | nn.ReLU(inplace=True), 68 | ) 69 | 70 | #although we only use 1 conv layer as prelayer, 71 | #we still use name a3, b3....... 72 | self.a3 = Inception(192, 64, 96, 128, 16, 32, 32) 73 | self.b3 = Inception(256, 128, 128, 192, 32, 96, 64) 74 | 75 | ##"""In general, an Inception network is a network consisting of 76 | ##modules of the above type stacked upon each other, with occasional 77 | ##max-pooling layers with stride 2 to halve the resolution of the 78 | ##grid""" 79 | self.maxpool = nn.MaxPool2d(3, stride=2, padding=1) 80 | 81 | self.a4 = Inception(480, 192, 96, 208, 16, 48, 64) 82 | self.b4 = Inception(512, 160, 112, 224, 24, 64, 64) 83 | self.c4 = Inception(512, 128, 128, 256, 24, 64, 64) 84 | self.d4 = Inception(512, 112, 144, 288, 32, 64, 64) 85 | self.e4 = Inception(528, 256, 160, 320, 32, 128, 128) 86 | 87 | self.a5 = Inception(832, 256, 160, 320, 32, 128, 128) 88 | self.b5 = Inception(832, 384, 192, 384, 48, 128, 128) 89 | 90 | #input feature size: 8*8*1024 91 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 92 | self.dropout = nn.Dropout2d(p=0.4) 93 | self.linear = nn.Linear(1024, num_class) 94 | 95 | def forward(self, x): 96 | x = self.prelayer(x) 97 | x = self.maxpool(x) 98 | x = self.a3(x) 99 | x = self.b3(x) 100 | 101 | x = self.maxpool(x) 102 | 103 | x = self.a4(x) 104 | x = self.b4(x) 105 | x = self.c4(x) 106 | x = self.d4(x) 107 | x = self.e4(x) 108 | 109 | x = self.maxpool(x) 110 | 111 | x = self.a5(x) 112 | x = self.b5(x) 113 | 114 | #"""It was found that a move from fully connected layers to 115 | #average pooling improved the top-1 accuracy by about 0.6%, 116 | #however the use of dropout remained essential even after 117 | #removing the fully connected layers.""" 118 | x = self.avgpool(x) 119 | x = self.dropout(x) 120 | x = x.view(x.size()[0], -1) 121 | x = self.linear(x) 122 | 123 | return x 124 | 125 | def googlenet(num_class): 126 | return GoogleNet(num_class=num_class) 127 | 128 | 129 | 130 | if __name__ == "__main__": 131 | 132 | 133 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 134 | image_size = 256 135 | x = torch.Tensor(1, 1, image_size, image_size) 136 | x = x.to(device) 137 | print("x size: {}".format(x.size())) 138 | 139 | model = googlenet(num_class=2).to(device) 140 | 141 | 142 | out1 = model(x) 143 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/mobilenet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | 5 | class DepthSeperabelConv2d(nn.Module): 6 | 7 | def __init__(self, input_channels, output_channels, kernel_size, **kwargs): 8 | super().__init__() 9 | self.depthwise = nn.Sequential( 10 | nn.Conv2d( 11 | input_channels, 12 | input_channels, 13 | kernel_size, 14 | groups=input_channels, 15 | **kwargs), 16 | nn.BatchNorm2d(input_channels), 17 | nn.ReLU(inplace=True) 18 | ) 19 | 20 | self.pointwise = nn.Sequential( 21 | nn.Conv2d(input_channels, output_channels, 1), 22 | nn.BatchNorm2d(output_channels), 23 | nn.ReLU(inplace=True) 24 | ) 25 | 26 | def forward(self, x): 27 | x = self.depthwise(x) 28 | x = self.pointwise(x) 29 | 30 | return x 31 | 32 | 33 | class BasicConv2d(nn.Module): 34 | 35 | def __init__(self, input_channels, output_channels, kernel_size, **kwargs): 36 | 37 | super().__init__() 38 | self.conv = nn.Conv2d( 39 | input_channels, output_channels, kernel_size, **kwargs) 40 | self.bn = nn.BatchNorm2d(output_channels) 41 | self.relu = nn.ReLU(inplace=True) 42 | 43 | def forward(self, x): 44 | x = self.conv(x) 45 | x = self.bn(x) 46 | x = self.relu(x) 47 | 48 | return x 49 | 50 | 51 | class MobileNet(nn.Module): 52 | 53 | """ 54 | Args: 55 | width multipler: The role of the width multiplier α is to thin 56 | a network uniformly at each layer. For a given 57 | layer and width multiplier α, the number of 58 | input channels M becomes αM and the number of 59 | output channels N becomes αN. 60 | """ 61 | 62 | def __init__(self, width_multiplier=1, class_num=100): 63 | super().__init__() 64 | 65 | alpha = width_multiplier 66 | self.stem = nn.Sequential( 67 | BasicConv2d(1, int(32 * alpha), 3, padding=1, bias=False), 68 | DepthSeperabelConv2d( 69 | int(32 * alpha), 70 | int(64 * alpha), 71 | 3, 72 | padding=1, 73 | bias=False 74 | ) 75 | ) 76 | 77 | #downsample 78 | self.conv1 = nn.Sequential( 79 | DepthSeperabelConv2d( 80 | int(64 * alpha), 81 | int(128 * alpha), 82 | 3, 83 | stride=2, 84 | padding=1, 85 | bias=False 86 | ), 87 | DepthSeperabelConv2d( 88 | int(128 * alpha), 89 | int(128 * alpha), 90 | 3, 91 | padding=1, 92 | bias=False 93 | ) 94 | ) 95 | 96 | #downsample 97 | self.conv2 = nn.Sequential( 98 | DepthSeperabelConv2d( 99 | int(128 * alpha), 100 | int(256 * alpha), 101 | 3, 102 | stride=2, 103 | padding=1, 104 | bias=False 105 | ), 106 | DepthSeperabelConv2d( 107 | int(256 * alpha), 108 | int(256 * alpha), 109 | 3, 110 | padding=1, 111 | bias=False 112 | ) 113 | ) 114 | 115 | #downsample 116 | self.conv3 = nn.Sequential( 117 | DepthSeperabelConv2d( 118 | int(256 * alpha), 119 | int(512 * alpha), 120 | 3, 121 | stride=2, 122 | padding=1, 123 | bias=False 124 | ), 125 | 126 | DepthSeperabelConv2d( 127 | int(512 * alpha), 128 | int(512 * alpha), 129 | 3, 130 | padding=1, 131 | bias=False 132 | ), 133 | DepthSeperabelConv2d( 134 | int(512 * alpha), 135 | int(512 * alpha), 136 | 3, 137 | padding=1, 138 | bias=False 139 | ), 140 | DepthSeperabelConv2d( 141 | int(512 * alpha), 142 | int(512 * alpha), 143 | 3, 144 | padding=1, 145 | bias=False 146 | ), 147 | DepthSeperabelConv2d( 148 | int(512 * alpha), 149 | int(512 * alpha), 150 | 3, 151 | padding=1, 152 | bias=False 153 | ), 154 | DepthSeperabelConv2d( 155 | int(512 * alpha), 156 | int(512 * alpha), 157 | 3, 158 | padding=1, 159 | bias=False 160 | ) 161 | ) 162 | 163 | #downsample 164 | self.conv4 = nn.Sequential( 165 | DepthSeperabelConv2d( 166 | int(512 * alpha), 167 | int(1024 * alpha), 168 | 3, 169 | stride=2, 170 | padding=1, 171 | bias=False 172 | ), 173 | DepthSeperabelConv2d( 174 | int(1024 * alpha), 175 | int(1024 * alpha), 176 | 3, 177 | padding=1, 178 | bias=False 179 | ) 180 | ) 181 | 182 | self.fc = nn.Linear(int(1024 * alpha), class_num) 183 | self.avg = nn.AdaptiveAvgPool2d(1) 184 | 185 | def forward(self, x): 186 | x = self.stem(x) 187 | 188 | x = self.conv1(x) 189 | x = self.conv2(x) 190 | x = self.conv3(x) 191 | x = self.conv4(x) 192 | 193 | x = self.avg(x) 194 | x = x.view(x.size(0), -1) 195 | x = self.fc(x) 196 | return x 197 | 198 | 199 | def mobilenet(class_num=100): 200 | alpha=1 201 | return MobileNet(alpha, class_num) 202 | 203 | 204 | if __name__ == "__main__": 205 | 206 | 207 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 208 | image_size = 256 209 | x = torch.Tensor(1, 1, image_size, image_size) 210 | x = x.to(device) 211 | print("x size: {}".format(x.size())) 212 | 213 | model = mobilenet(class_num=2).to(device) 214 | 215 | 216 | out1 = model(x) 217 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/nasnet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class SeperableConv2d(nn.Module): 5 | 6 | def __init__(self, input_channels, output_channels, kernel_size, **kwargs): 7 | 8 | super().__init__() 9 | self.depthwise = nn.Conv2d( 10 | input_channels, 11 | input_channels, 12 | kernel_size, 13 | groups=input_channels, 14 | **kwargs 15 | ) 16 | 17 | self.pointwise = nn.Conv2d( 18 | input_channels, 19 | output_channels, 20 | 1 21 | ) 22 | def forward(self, x): 23 | x = self.depthwise(x) 24 | x = self.pointwise(x) 25 | 26 | return x 27 | 28 | class SeperableBranch(nn.Module): 29 | 30 | def __init__(self, input_channels, output_channels, kernel_size, **kwargs): 31 | """Adds 2 blocks of [relu-separable conv-batchnorm].""" 32 | super().__init__() 33 | self.block1 = nn.Sequential( 34 | nn.ReLU(), 35 | SeperableConv2d(input_channels, output_channels, kernel_size, **kwargs), 36 | nn.BatchNorm2d(output_channels) 37 | ) 38 | 39 | self.block2 = nn.Sequential( 40 | nn.ReLU(), 41 | SeperableConv2d(output_channels, output_channels, kernel_size, stride=1, padding=int(kernel_size / 2)), 42 | nn.BatchNorm2d(output_channels) 43 | ) 44 | 45 | def forward(self, x): 46 | x = self.block1(x) 47 | x = self.block2(x) 48 | 49 | return x 50 | 51 | class Fit(nn.Module): 52 | """Make the cell outputs compatible 53 | Args: 54 | prev_filters: filter number of tensor prev, needs to be modified 55 | filters: filter number of normal cell branch output filters 56 | """ 57 | 58 | def __init__(self, prev_filters, filters): 59 | super().__init__() 60 | self.relu = nn.ReLU() 61 | 62 | self.p1 = nn.Sequential( 63 | nn.AvgPool2d(1, stride=2), 64 | nn.Conv2d(prev_filters, int(filters / 2), 1) 65 | ) 66 | 67 | #make sure there is no information loss 68 | self.p2 = nn.Sequential( 69 | nn.ConstantPad2d((0, 1, 0, 1), 0), 70 | nn.ConstantPad2d((-1, 0, -1, 0), 0), #cropping 71 | nn.AvgPool2d(1, stride=2), 72 | nn.Conv2d(prev_filters, int(filters / 2), 1) 73 | ) 74 | 75 | self.bn = nn.BatchNorm2d(filters) 76 | 77 | self.dim_reduce = nn.Sequential( 78 | nn.ReLU(), 79 | nn.Conv2d(prev_filters, filters, 1), 80 | nn.BatchNorm2d(filters) 81 | ) 82 | 83 | self.filters = filters 84 | 85 | def forward(self, inputs): 86 | x, prev = inputs 87 | if prev is None: 88 | return x 89 | 90 | #image size does not match 91 | elif x.size(2) != prev.size(2): 92 | prev = self.relu(prev) 93 | p1 = self.p1(prev) 94 | p2 = self.p2(prev) 95 | prev = torch.cat([p1, p2], 1) 96 | prev = self.bn(prev) 97 | 98 | elif prev.size(1) != self.filters: 99 | prev = self.dim_reduce(prev) 100 | 101 | return prev 102 | 103 | 104 | class NormalCell(nn.Module): 105 | 106 | def __init__(self, x_in, prev_in, output_channels): 107 | super().__init__() 108 | 109 | self.dem_reduce = nn.Sequential( 110 | nn.ReLU(), 111 | nn.Conv2d(x_in, output_channels, 1, bias=False), 112 | nn.BatchNorm2d(output_channels) 113 | ) 114 | 115 | self.block1_left = SeperableBranch( 116 | output_channels, 117 | output_channels, 118 | kernel_size=3, 119 | padding=1, 120 | bias=False 121 | ) 122 | self.block1_right = nn.Sequential() 123 | 124 | self.block2_left = SeperableBranch( 125 | output_channels, 126 | output_channels, 127 | kernel_size=3, 128 | padding=1, 129 | bias=False 130 | ) 131 | self.block2_right = SeperableBranch( 132 | output_channels, 133 | output_channels, 134 | kernel_size=5, 135 | padding=2, 136 | bias=False 137 | ) 138 | 139 | self.block3_left = nn.AvgPool2d(3, stride=1, padding=1) 140 | self.block3_right = nn.Sequential() 141 | 142 | self.block4_left = nn.AvgPool2d(3, stride=1, padding=1) 143 | self.block4_right = nn.AvgPool2d(3, stride=1, padding=1) 144 | 145 | self.block5_left = SeperableBranch( 146 | output_channels, 147 | output_channels, 148 | kernel_size=5, 149 | padding=2, 150 | bias=False 151 | ) 152 | self.block5_right = SeperableBranch( 153 | output_channels, 154 | output_channels, 155 | kernel_size=3, 156 | padding=1, 157 | bias=False 158 | ) 159 | 160 | self.fit = Fit(prev_in, output_channels) 161 | 162 | def forward(self, x): 163 | x, prev = x 164 | 165 | #return transformed x as new x, and original x as prev 166 | #only prev tensor needs to be modified 167 | prev = self.fit((x, prev)) 168 | 169 | h = self.dem_reduce(x) 170 | 171 | x1 = self.block1_left(h) + self.block1_right(h) 172 | x2 = self.block2_left(prev) + self.block2_right(h) 173 | x3 = self.block3_left(h) + self.block3_right(h) 174 | x4 = self.block4_left(prev) + self.block4_right(prev) 175 | x5 = self.block5_left(prev) + self.block5_right(prev) 176 | 177 | return torch.cat([prev, x1, x2, x3, x4, x5], 1), x 178 | 179 | class ReductionCell(nn.Module): 180 | 181 | def __init__(self, x_in, prev_in, output_channels): 182 | super().__init__() 183 | 184 | self.dim_reduce = nn.Sequential( 185 | nn.ReLU(), 186 | nn.Conv2d(x_in, output_channels, 1), 187 | nn.BatchNorm2d(output_channels) 188 | ) 189 | 190 | #block1 191 | self.layer1block1_left = SeperableBranch(output_channels, output_channels, 7, stride=2, padding=3) 192 | self.layer1block1_right = SeperableBranch(output_channels, output_channels, 5, stride=2, padding=2) 193 | 194 | #block2 195 | self.layer1block2_left = nn.MaxPool2d(3, stride=2, padding=1) 196 | self.layer1block2_right = SeperableBranch(output_channels, output_channels, 7, stride=2, padding=3) 197 | 198 | #block3 199 | self.layer1block3_left = nn.AvgPool2d(3, 2, 1) 200 | self.layer1block3_right = SeperableBranch(output_channels, output_channels, 5, stride=2, padding=2) 201 | 202 | #block5 203 | self.layer2block1_left = nn.MaxPool2d(3, 2, 1) 204 | self.layer2block1_right = SeperableBranch(output_channels, output_channels, 3, stride=1, padding=1) 205 | 206 | #block4 207 | self.layer2block2_left = nn.AvgPool2d(3, 1, 1) 208 | self.layer2block2_right = nn.Sequential() 209 | 210 | self.fit = Fit(prev_in, output_channels) 211 | 212 | def forward(self, x): 213 | x, prev = x 214 | prev = self.fit((x, prev)) 215 | 216 | h = self.dim_reduce(x) 217 | 218 | layer1block1 = self.layer1block1_left(prev) + self.layer1block1_right(h) 219 | layer1block2 = self.layer1block2_left(h) + self.layer1block2_right(prev) 220 | layer1block3 = self.layer1block3_left(h) + self.layer1block3_right(prev) 221 | layer2block1 = self.layer2block1_left(h) + self.layer2block1_right(layer1block1) 222 | layer2block2 = self.layer2block2_left(layer1block1) + self.layer2block2_right(layer1block2) 223 | 224 | return torch.cat([ 225 | layer1block2, #https://github.com/keras-team/keras-applications/blob/master/keras_applications/nasnet.py line 739 226 | layer1block3, 227 | layer2block1, 228 | layer2block2 229 | ], 1), x 230 | 231 | 232 | class NasNetA(nn.Module): 233 | 234 | def __init__(self, repeat_cell_num, reduction_num, filters, stemfilter, class_num=100): 235 | super().__init__() 236 | 237 | self.stem = nn.Sequential( 238 | nn.Conv2d(1, stemfilter, 3, padding=1, bias=False), 239 | nn.BatchNorm2d(stemfilter) 240 | ) 241 | 242 | self.prev_filters = stemfilter 243 | self.x_filters = stemfilter 244 | self.filters = filters 245 | 246 | self.cell_layers = self._make_layers(repeat_cell_num, reduction_num) 247 | 248 | self.relu = nn.ReLU() 249 | self.avg = nn.AdaptiveAvgPool2d(1) 250 | self.fc = nn.Linear(self.filters * 6, class_num) 251 | 252 | 253 | def _make_normal(self, block, repeat, output): 254 | """make normal cell 255 | Args: 256 | block: cell type 257 | repeat: number of repeated normal cell 258 | output: output filters for each branch in normal cell 259 | Returns: 260 | stacked normal cells 261 | """ 262 | 263 | layers = [] 264 | for r in range(repeat): 265 | layers.append(block(self.x_filters, self.prev_filters, output)) 266 | self.prev_filters = self.x_filters 267 | self.x_filters = output * 6 #concatenate 6 branches 268 | 269 | return layers 270 | 271 | def _make_reduction(self, block, output): 272 | """make normal cell 273 | Args: 274 | block: cell type 275 | output: output filters for each branch in reduction cell 276 | Returns: 277 | reduction cell 278 | """ 279 | 280 | reduction = block(self.x_filters, self.prev_filters, output) 281 | self.prev_filters = self.x_filters 282 | self.x_filters = output * 4 #stack for 4 branches 283 | 284 | return reduction 285 | 286 | def _make_layers(self, repeat_cell_num, reduction_num): 287 | 288 | layers = [] 289 | for i in range(reduction_num): 290 | 291 | layers.extend(self._make_normal(NormalCell, repeat_cell_num, self.filters)) 292 | self.filters *= 2 293 | layers.append(self._make_reduction(ReductionCell, self.filters)) 294 | 295 | layers.extend(self._make_normal(NormalCell, repeat_cell_num, self.filters)) 296 | 297 | return nn.Sequential(*layers) 298 | 299 | 300 | def forward(self, x): 301 | 302 | x = self.stem(x) 303 | prev = None 304 | x, prev = self.cell_layers((x, prev)) 305 | x = self.relu(x) 306 | x = self.avg(x) 307 | x = x.view(x.size(0), -1) 308 | x = self.fc(x) 309 | 310 | return x 311 | 312 | 313 | def nasnet(class_num): 314 | 315 | #stem filters must be 44, it's a pytorch workaround, cant change to other number 316 | return NasNetA(4, 2, 44, 44,class_num=class_num) 317 | 318 | 319 | if __name__ == "__main__": 320 | 321 | 322 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 323 | image_size = 256 324 | x = torch.Tensor(1, 1, image_size, image_size) 325 | x = x.to(device) 326 | print("x size: {}".format(x.size())) 327 | 328 | model = nasnet(class_num=2).to(device) 329 | 330 | 331 | out1 = model(x) 332 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/resnet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | class BasicBlock(nn.Module): 5 | """Basic Block for resnet 18 and resnet 34 6 | """ 7 | 8 | #BasicBlock and BottleNeck block 9 | #have different output size 10 | #we use class attribute expansion 11 | #to distinct 12 | expansion = 1 13 | 14 | def __init__(self, in_channels, out_channels, stride=1): 15 | super().__init__() 16 | 17 | #residual function 18 | self.residual_function = nn.Sequential( 19 | nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False), 20 | nn.BatchNorm2d(out_channels), 21 | nn.ReLU(inplace=True), 22 | nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False), 23 | nn.BatchNorm2d(out_channels * BasicBlock.expansion) 24 | ) 25 | 26 | #shortcut 27 | self.shortcut = nn.Sequential() 28 | 29 | #the shortcut output dimension is not the same with residual function 30 | #use 1*1 convolution to match the dimension 31 | if stride != 1 or in_channels != BasicBlock.expansion * out_channels: 32 | self.shortcut = nn.Sequential( 33 | nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False), 34 | nn.BatchNorm2d(out_channels * BasicBlock.expansion) 35 | ) 36 | 37 | def forward(self, x): 38 | return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x)) 39 | 40 | class BottleNeck(nn.Module): 41 | """Residual block for resnet over 50 layers 42 | """ 43 | expansion = 4 44 | def __init__(self, in_channels, out_channels, stride=1): 45 | super().__init__() 46 | self.residual_function = nn.Sequential( 47 | nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False), 48 | nn.BatchNorm2d(out_channels), 49 | nn.ReLU(inplace=True), 50 | nn.Conv2d(out_channels, out_channels, stride=stride, kernel_size=3, padding=1, bias=False), 51 | nn.BatchNorm2d(out_channels), 52 | nn.ReLU(inplace=True), 53 | nn.Conv2d(out_channels, out_channels * BottleNeck.expansion, kernel_size=1, bias=False), 54 | nn.BatchNorm2d(out_channels * BottleNeck.expansion), 55 | ) 56 | 57 | self.shortcut = nn.Sequential() 58 | 59 | if stride != 1 or in_channels != out_channels * BottleNeck.expansion: 60 | self.shortcut = nn.Sequential( 61 | nn.Conv2d(in_channels, out_channels * BottleNeck.expansion, stride=stride, kernel_size=1, bias=False), 62 | nn.BatchNorm2d(out_channels * BottleNeck.expansion) 63 | ) 64 | 65 | def forward(self, x): 66 | return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x)) 67 | 68 | class ResNet(nn.Module): 69 | 70 | def __init__(self, block, num_block, num_classes=100): 71 | super().__init__() 72 | 73 | self.in_channels = 64 74 | 75 | self.conv1 = nn.Sequential( 76 | nn.Conv2d(1, 64, kernel_size=3, padding=1, bias=False), 77 | nn.BatchNorm2d(64), 78 | nn.ReLU(inplace=True)) 79 | #we use a different inputsize than the original paper 80 | #so conv2_x's stride is 1 81 | self.conv2_x = self._make_layer(block, 64, num_block[0], 1) 82 | self.conv3_x = self._make_layer(block, 128, num_block[1], 2) 83 | self.conv4_x = self._make_layer(block, 256, num_block[2], 2) 84 | self.conv5_x = self._make_layer(block, 512, num_block[3], 2) 85 | self.avg_pool = nn.AdaptiveAvgPool2d((1, 1)) 86 | self.fc = nn.Linear(512 * block.expansion, num_classes) 87 | 88 | def _make_layer(self, block, out_channels, num_blocks, stride): 89 | """make resnet layers(by layer i didnt mean this 'layer' was the 90 | same as a neuron netowork layer, ex. conv layer), one layer may 91 | contain more than one residual block 92 | Args: 93 | block: block type, basic block or bottle neck block 94 | out_channels: output depth channel number of this layer 95 | num_blocks: how many blocks per layer 96 | stride: the stride of the first block of this layer 97 | Return: 98 | return a resnet layer 99 | """ 100 | 101 | # we have num_block blocks per layer, the first block 102 | # could be 1 or 2, other blocks would always be 1 103 | strides = [stride] + [1] * (num_blocks - 1) 104 | layers = [] 105 | for stride in strides: 106 | layers.append(block(self.in_channels, out_channels, stride)) 107 | self.in_channels = out_channels * block.expansion 108 | 109 | return nn.Sequential(*layers) 110 | 111 | def forward(self, x): 112 | output = self.conv1(x) 113 | output = self.conv2_x(output) 114 | output = self.conv3_x(output) 115 | output = self.conv4_x(output) 116 | output = self.conv5_x(output) 117 | output = self.avg_pool(output) 118 | output = output.view(output.size(0), -1) 119 | output = self.fc(output) 120 | 121 | return output 122 | 123 | def resnet18(num_classes): 124 | """ return a ResNet 18 object 125 | """ 126 | return ResNet(BasicBlock, [2, 2, 2, 2],num_classes=num_classes) 127 | 128 | def resnet34(num_classes): 129 | """ return a ResNet 34 object 130 | """ 131 | return ResNet(BasicBlock, [3, 4, 6, 3],num_classes=num_classes) 132 | 133 | def resnet50(num_classes): 134 | """ return a ResNet 50 object 135 | """ 136 | return ResNet(BottleNeck, [3, 4, 6, 3],num_classes=num_classes) 137 | 138 | def resnet101(num_classes): 139 | """ return a ResNet 101 object 140 | """ 141 | return ResNet(BottleNeck, [3, 4, 23, 3],num_classes=num_classes) 142 | 143 | def resnet152(num_classes): 144 | """ return a ResNet 152 object 145 | """ 146 | return ResNet(BottleNeck, [3, 8, 36, 3],num_classes=num_classes) 147 | 148 | 149 | 150 | if __name__ == "__main__": 151 | 152 | 153 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 154 | image_size = 256 155 | x = torch.Tensor(1, 1, image_size, image_size) 156 | x = x.to(device) 157 | print("x size: {}".format(x.size())) 158 | 159 | model = resnet152(num_classes=2).to(device) 160 | 161 | 162 | out1 = model(x) 163 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/resnext.py: -------------------------------------------------------------------------------- 1 | import math 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | #only implements ResNext bottleneck c 7 | 8 | 9 | #"""This strategy exposes a new dimension, which we call “cardinality” 10 | #(the size of the set of transformations), as an essential factor 11 | #in addition to the dimensions of depth and width.""" 12 | CARDINALITY = 32 13 | DEPTH = 4 14 | BASEWIDTH = 64 15 | 16 | #"""The grouped convolutional layer in Fig. 3(c) performs 32 groups 17 | #of convolutions whose input and output channels are 4-dimensional. 18 | #The grouped convolutional layer concatenates them as the outputs 19 | #of the layer.""" 20 | 21 | class ResNextBottleNeckC(nn.Module): 22 | 23 | def __init__(self, in_channels, out_channels, stride): 24 | super().__init__() 25 | 26 | C = CARDINALITY #How many groups a feature map was splitted into 27 | 28 | #"""We note that the input/output width of the template is fixed as 29 | #256-d (Fig. 3), We note that the input/output width of the template 30 | #is fixed as 256-d (Fig. 3), and all widths are dou- bled each time 31 | #when the feature map is subsampled (see Table 1).""" 32 | D = int(DEPTH * out_channels / BASEWIDTH) #number of channels per group 33 | self.split_transforms = nn.Sequential( 34 | nn.Conv2d(in_channels, C * D, kernel_size=1, groups=C, bias=False), 35 | nn.BatchNorm2d(C * D), 36 | nn.ReLU(inplace=True), 37 | nn.Conv2d(C * D, C * D, kernel_size=3, stride=stride, groups=C, padding=1, bias=False), 38 | nn.BatchNorm2d(C * D), 39 | nn.ReLU(inplace=True), 40 | nn.Conv2d(C * D, out_channels * 4, kernel_size=1, bias=False), 41 | nn.BatchNorm2d(out_channels * 4), 42 | ) 43 | 44 | self.shortcut = nn.Sequential() 45 | 46 | if stride != 1 or in_channels != out_channels * 4: 47 | self.shortcut = nn.Sequential( 48 | nn.Conv2d(in_channels, out_channels * 4, stride=stride, kernel_size=1, bias=False), 49 | nn.BatchNorm2d(out_channels * 4) 50 | ) 51 | 52 | def forward(self, x): 53 | return F.relu(self.split_transforms(x) + self.shortcut(x)) 54 | 55 | class ResNext(nn.Module): 56 | 57 | def __init__(self, block, num_blocks, class_names=100): 58 | super().__init__() 59 | self.in_channels = 64 60 | 61 | self.conv1 = nn.Sequential( 62 | nn.Conv2d(1, 64, 3, stride=1, padding=1, bias=False), 63 | nn.BatchNorm2d(64), 64 | nn.ReLU(inplace=True) 65 | ) 66 | 67 | self.conv2 = self._make_layer(block, num_blocks[0], 64, 1) 68 | self.conv3 = self._make_layer(block, num_blocks[1], 128, 2) 69 | self.conv4 = self._make_layer(block, num_blocks[2], 256, 2) 70 | self.conv5 = self._make_layer(block, num_blocks[3], 512, 2) 71 | self.avg = nn.AdaptiveAvgPool2d((1, 1)) 72 | self.fc = nn.Linear(512 * 4, class_names) 73 | 74 | def forward(self, x): 75 | x = self.conv1(x) 76 | x = self.conv2(x) 77 | x = self.conv3(x) 78 | x = self.conv4(x) 79 | x = self.conv5(x) 80 | x = self.avg(x) 81 | x = x.view(x.size(0), -1) 82 | x = self.fc(x) 83 | return x 84 | 85 | def _make_layer(self, block, num_block, out_channels, stride): 86 | """Building resnext block 87 | Args: 88 | block: block type(default resnext bottleneck c) 89 | num_block: number of blocks per layer 90 | out_channels: output channels per block 91 | stride: block stride 92 | Returns: 93 | a resnext layer 94 | """ 95 | strides = [stride] + [1] * (num_block - 1) 96 | layers = [] 97 | for stride in strides: 98 | layers.append(block(self.in_channels, out_channels, stride)) 99 | self.in_channels = out_channels * 4 100 | 101 | return nn.Sequential(*layers) 102 | 103 | def resnext50(class_names): 104 | """ return a resnext50(c32x4d) network 105 | """ 106 | return ResNext(ResNextBottleNeckC, [3, 4, 6, 3], class_names=class_names) 107 | 108 | def resnext101(class_names): 109 | """ return a resnext101(c32x4d) network 110 | """ 111 | return ResNext(ResNextBottleNeckC, [3, 4, 23, 3], class_names=class_names) 112 | 113 | def resnext152(class_names): 114 | """ return a resnext101(c32x4d) network 115 | """ 116 | return ResNext(ResNextBottleNeckC, [3, 4, 36, 3], class_names=class_names) 117 | 118 | 119 | if __name__ == "__main__": 120 | 121 | 122 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 123 | image_size = 256 124 | x = torch.Tensor(1, 1, image_size, image_size) 125 | x = x.to(device) 126 | print("x size: {}".format(x.size())) 127 | 128 | model = resnext152(class_names=2).to(device) 129 | 130 | 131 | out1 = model(x) 132 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /models/two_d/vggnet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | cfg = { 5 | 'A' : [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 6 | 'B' : [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 7 | 'D' : [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 8 | 'E' : [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'] 9 | } 10 | 11 | class VGG(nn.Module): 12 | 13 | def __init__(self, features, num_class=100): 14 | super().__init__() 15 | self.features = features 16 | 17 | self.classifier = nn.Sequential( 18 | nn.Linear(512*7*7, 4096), 19 | nn.ReLU(inplace=True), 20 | nn.Dropout(), 21 | nn.Linear(4096, 4096), 22 | nn.ReLU(inplace=True), 23 | nn.Dropout(), 24 | nn.Linear(4096, num_class) 25 | ) 26 | 27 | def forward(self, x): 28 | output = self.features(x) 29 | output = output.view(output.size()[0], -1) 30 | output = self.classifier(output) 31 | 32 | return output 33 | 34 | def make_layers(cfg, batch_norm=False): 35 | layers = [] 36 | 37 | input_channel = 1 38 | for l in cfg: 39 | if l == 'M': 40 | layers += [nn.MaxPool2d(kernel_size=2, stride=2)] 41 | continue 42 | 43 | layers += [nn.Conv2d(input_channel, l, kernel_size=3, padding=1)] 44 | 45 | if batch_norm: 46 | layers += [nn.BatchNorm2d(l)] 47 | 48 | layers += [nn.ReLU(inplace=True)] 49 | input_channel = l 50 | 51 | return nn.Sequential(*layers) 52 | 53 | def vgg11_bn(num_class): 54 | return VGG(make_layers(cfg['A'], batch_norm=True),num_class=num_class) 55 | 56 | def vgg13_bn(num_class): 57 | return VGG(make_layers(cfg['B'], batch_norm=True),num_class=num_class) 58 | 59 | def vgg16_bn(num_class): 60 | return VGG(make_layers(cfg['D'], batch_norm=True),num_class=num_class) 61 | 62 | def vgg19_bn(num_class): 63 | return VGG(make_layers(cfg['E'], batch_norm=True),num_class=num_class) 64 | 65 | 66 | if __name__ == "__main__": 67 | 68 | 69 | device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 70 | image_size = 224 71 | x = torch.Tensor(1, 1, image_size, image_size) 72 | x = x.to(device) 73 | print("x size: {}".format(x.size())) 74 | 75 | model = vgg16_bn(num_class=2).to(device) 76 | 77 | 78 | out1 = model(x) 79 | print("out size: {}".format(out1.size())) -------------------------------------------------------------------------------- /utils/convolution.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | 4 | 5 | PADDING_MODES = { 6 | 'reflect': 'Reflection', 7 | 'replicate': 'Replication', 8 | 'constant': 'Zero', 9 | } 10 | 11 | 12 | class ConvolutionalBlock(nn.Module): 13 | def __init__( 14 | self, 15 | in_channels, 16 | out_channels, 17 | dilation, 18 | dimensions, 19 | batch_norm=True, 20 | instance_norm=False, 21 | norm_affine=True, 22 | padding_mode='constant', 23 | preactivation=True, 24 | kernel_size=3, 25 | activation=True, 26 | ): 27 | assert padding_mode in PADDING_MODES.keys() 28 | assert not (batch_norm and instance_norm) 29 | super().__init__() 30 | 31 | if dimensions == 2: 32 | # pylint: disable=not-callable 33 | class_name = '{}Pad2d'.format(PADDING_MODES[padding_mode]) 34 | padding_class = getattr(nn, class_name) 35 | padding_instance = padding_class(dilation) 36 | elif dimensions == 3: 37 | padding_instance = Pad3d(dilation, padding_mode) 38 | conv_class = nn.Conv2d if dimensions == 2 else nn.Conv3d 39 | 40 | if batch_norm: 41 | norm_class = nn.BatchNorm2d if dimensions == 2 else nn.BatchNorm3d 42 | if instance_norm: 43 | norm_class = nn.InstanceNorm2d if dimensions == 2 else nn.InstanceNorm3d 44 | 45 | layers = nn.ModuleList() 46 | 47 | if preactivation: 48 | if batch_norm or instance_norm: 49 | layers.append(norm_class(in_channels, affine=norm_affine)) 50 | if activation: 51 | layers.append(nn.ReLU()) 52 | 53 | if kernel_size > 1: 54 | layers.append(padding_instance) 55 | 56 | use_bias = not (instance_norm or batch_norm) 57 | conv_layer = conv_class( 58 | in_channels, 59 | out_channels, 60 | kernel_size=kernel_size, 61 | dilation=dilation, 62 | bias=use_bias, 63 | ) 64 | layers.append(conv_layer) 65 | 66 | if not preactivation: 67 | if batch_norm or instance_norm: 68 | layers.append(norm_class(out_channels, affine=norm_affine)) 69 | if activation: 70 | layers.append(nn.ReLU()) 71 | 72 | self.convolutional_block = nn.Sequential(*layers) 73 | 74 | def forward(self, x): 75 | return self.convolutional_block(x) 76 | 77 | 78 | class Pad3d(nn.Module): 79 | def __init__(self, pad, mode): 80 | assert mode in PADDING_MODES.keys() 81 | super().__init__() 82 | self.pad = 6 * [pad] 83 | self.mode = mode 84 | 85 | def forward(self, x): 86 | return F.pad(x, self.pad, self.mode) -------------------------------------------------------------------------------- /utils/dilation.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | from residual import ResidualBlock 3 | 4 | 5 | class DilationBlock(nn.Module): 6 | def __init__( 7 | self, 8 | in_channels, 9 | out_channels, 10 | dilation, 11 | dimensions, 12 | layers_per_block=2, 13 | num_residual_blocks=3, 14 | batch_norm=True, 15 | instance_norm=False, 16 | residual=True, 17 | padding_mode='constant', 18 | ): 19 | super().__init__() 20 | self.in_channels = in_channels 21 | self.out_channels = out_channels 22 | residual_blocks = nn.ModuleList() 23 | for _ in range(num_residual_blocks): 24 | residual_block = ResidualBlock( 25 | in_channels, 26 | out_channels, 27 | layers_per_block, 28 | dilation, 29 | dimensions, 30 | batch_norm=batch_norm, 31 | instance_norm=instance_norm, 32 | residual=residual, 33 | padding_mode=padding_mode, 34 | ) 35 | residual_blocks.append(residual_block) 36 | in_channels = out_channels 37 | self.dilation_block = nn.Sequential(*residual_blocks) 38 | 39 | def forward(self, x): 40 | return self.dilation_block(x) -------------------------------------------------------------------------------- /utils/metric.py: -------------------------------------------------------------------------------- 1 | import torchio as tio 2 | from pathlib import Path 3 | import torch 4 | import numpy as np 5 | import copy 6 | 7 | def metric(gt,pred): 8 | preds = pred.detach().numpy() 9 | gts = gt.detach().numpy() 10 | 11 | 12 | 13 | pred = preds.astype(int) # float data does not support bit_and and bit_or 14 | gdth = gts.astype(int) # float data does not support bit_and and bit_or 15 | fp_array = copy.deepcopy(pred) # keep pred unchanged 16 | fn_array = copy.deepcopy(gdth) 17 | gdth_sum = np.sum(gdth) 18 | pred_sum = np.sum(pred) 19 | intersection = gdth & pred 20 | union = gdth | pred 21 | intersection_sum = np.count_nonzero(intersection) 22 | union_sum = np.count_nonzero(union) 23 | 24 | tp_array = intersection 25 | 26 | tmp = pred - gdth 27 | fp_array[tmp < 1] = 0 28 | 29 | tmp2 = gdth - pred 30 | fn_array[tmp2 < 1] = 0 31 | 32 | tn_array = np.ones(gdth.shape) - union 33 | 34 | tp, fp, fn, tn = np.sum(tp_array), np.sum(fp_array), np.sum(fn_array), np.sum(tn_array) 35 | 36 | smooth = 0.001 37 | precision = tp / (pred_sum + smooth) 38 | recall = tp / (gdth_sum + smooth) 39 | 40 | false_positive_rate = fp / (fp + tn + smooth) 41 | false_negtive_rate = fn / (fn + tp + smooth) 42 | 43 | jaccard = intersection_sum / (union_sum + smooth) 44 | dice = 2 * intersection_sum / (gdth_sum + pred_sum + smooth) 45 | 46 | return false_positive_rate,false_negtive_rate,dice 47 | -------------------------------------------------------------------------------- /utils/residual.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | from convolution import ConvolutionalBlock 5 | 6 | 7 | BATCH_DIM = 0 8 | CHANNELS_DIM = 1 9 | 10 | 11 | class ResidualBlock(nn.Module): 12 | def __init__( 13 | self, 14 | in_channels, 15 | out_channels, 16 | num_layers, 17 | dilation, 18 | dimensions, 19 | batch_norm=True, 20 | instance_norm=False, 21 | residual=True, 22 | residual_type='pad', 23 | padding_mode='constant', 24 | ): 25 | assert residual_type in ('pad', 'project') 26 | super().__init__() 27 | self.residual = residual 28 | self.change_dimension = in_channels != out_channels 29 | self.residual_type = residual_type 30 | self.dimensions = dimensions 31 | if self.change_dimension: 32 | if residual_type == 'project': 33 | conv_class = nn.Conv2d if dimensions == 2 else nn.Conv3d 34 | self.change_dim_layer = conv_class( 35 | in_channels, 36 | out_channels, 37 | kernel_size=1, 38 | dilation=dilation, 39 | bias=False, # as in NiftyNet and PyTorch's ResNet model 40 | ) 41 | 42 | conv_blocks = nn.ModuleList() 43 | for _ in range(num_layers): 44 | conv_block = ConvolutionalBlock( 45 | in_channels, 46 | out_channels, 47 | dilation, 48 | dimensions, 49 | batch_norm=batch_norm, 50 | instance_norm=instance_norm, 51 | padding_mode=padding_mode, 52 | ) 53 | conv_blocks.append(conv_block) 54 | in_channels = out_channels 55 | self.residual_block = nn.Sequential(*conv_blocks) 56 | 57 | def forward(self, x): 58 | """ 59 | From the original ResNet paper, page 4: 60 | "When the dimensions increase, we consider two options: 61 | (A) The shortcut still performs identity mapping, 62 | with extra zero entries padded for increasing dimensions. 63 | This option introduces no extra parameter 64 | (B) The projection shortcut in Eqn.(2) is used to 65 | match dimensions (done by 1x1 convolutions). 66 | For both options, when the shortcuts go across feature maps of 67 | two sizes, they are performed with a stride of 2." 68 | """ 69 | out = self.residual_block(x) 70 | if self.residual: 71 | if self.change_dimension: 72 | if self.residual_type == 'project': 73 | x = self.change_dim_layer(x) 74 | elif self.residual_type == 'pad': 75 | batch_size = x.shape[BATCH_DIM] 76 | x_channels = x.shape[CHANNELS_DIM] 77 | out_channels = out.shape[CHANNELS_DIM] 78 | spatial_dims = x.shape[2:] 79 | diff_channels = out_channels - x_channels 80 | zeros_half = x.new_zeros( 81 | batch_size, diff_channels // 2, *spatial_dims) 82 | x = torch.cat((zeros_half, x, zeros_half), 83 | dim=CHANNELS_DIM) 84 | out = x + out 85 | return out --------------------------------------------------------------------------------