diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/.keep" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/.keep" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/class_indices.json" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/class_indices.json" new file mode 100644 index 0000000000000000000000000000000000000000..be3f9e3731602b547bd918ba89bb7bf5d2588eef --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/class_indices.json" @@ -0,0 +1,18 @@ +{ + "0": "American", + "1": "Baroque", + "2": "Empire", + "3": "Gothic", + "4": "Japanese", + "5": "Mediterranean", + "6": "Ming", + "7": "ModernChinese", + "8": "ModernFrench", + "9": "Modernist", + "10": "Neo-Classicism", + "11": "Qing", + "12": "Renaissance", + "13": "Rococo", + "14": "Rural", + "15": "SoutheastAsia" +} \ No newline at end of file diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/model.py" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/model.py" new file mode 100644 index 0000000000000000000000000000000000000000..d17beb8b67cc02973469cd28e784ad20ef402860 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/model.py" @@ -0,0 +1,366 @@ +import math +import copy +from functools import partial +from collections import OrderedDict +from typing import Optional, Callable + +import torch +import torch.nn as nn +from torch import Tensor +from torch.nn import functional as F + + +def _make_divisible(ch, divisor=8, min_ch=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + """ + if min_ch is None: + min_ch = divisor + new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_ch < 0.9 * ch: + new_ch += divisor + return new_ch + + +def drop_path(x, drop_prob: float = 0., training: bool = False): + """ + Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + "Deep Networks with Stochastic Depth", https://arxiv.org/pdf/1603.09382.pdf + + This function is taken from the rwightman. + It can be seen here: + https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/drop.py#L140 + """ + if drop_prob == 0. or not training: + return x + keep_prob = 1 - drop_prob + shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets + random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device) + random_tensor.floor_() # binarize + output = x.div(keep_prob) * random_tensor + return output + + +class DropPath(nn.Module): + """ + Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + "Deep Networks with Stochastic Depth", https://arxiv.org/pdf/1603.09382.pdf + """ + def __init__(self, drop_prob=None): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +class ConvBNActivation(nn.Sequential): + def __init__(self, + in_planes: int, + out_planes: int, + kernel_size: int = 3, + stride: int = 1, + groups: int = 1, + norm_layer: Optional[Callable[..., nn.Module]] = None, + activation_layer: Optional[Callable[..., nn.Module]] = None): + padding = (kernel_size - 1) // 2 + if norm_layer is None: + norm_layer = nn.BatchNorm2d + if activation_layer is None: + activation_layer = nn.SiLU # alias Swish (torch>=1.7) + + super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes, + out_channels=out_planes, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + bias=False), + norm_layer(out_planes), + activation_layer()) + + +class SqueezeExcitation(nn.Module): + def __init__(self, + input_c: int, # block input channel + expand_c: int, # block expand channel + squeeze_factor: int = 4): + super(SqueezeExcitation, self).__init__() + squeeze_c = input_c // squeeze_factor + self.fc1 = nn.Conv2d(expand_c, squeeze_c, 1) + self.ac1 = nn.SiLU() # alias Swish + self.fc2 = nn.Conv2d(squeeze_c, expand_c, 1) + self.ac2 = nn.Sigmoid() + + def forward(self, x: Tensor) -> Tensor: + scale = F.adaptive_avg_pool2d(x, output_size=(1, 1)) + scale = self.fc1(scale) + scale = self.ac1(scale) + scale = self.fc2(scale) + scale = self.ac2(scale) + return scale * x + + +class InvertedResidualConfig: + # kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate + def __init__(self, + kernel: int, # 3 or 5 + input_c: int, + out_c: int, + expanded_ratio: int, # 1 or 6 + stride: int, # 1 or 2 + use_se: bool, # True + drop_rate: float, + index: str, # 1a, 2a, 2b, ... + width_coefficient: float): + self.input_c = self.adjust_channels(input_c, width_coefficient) + self.kernel = kernel + self.expanded_c = self.input_c * expanded_ratio + self.out_c = self.adjust_channels(out_c, width_coefficient) + self.use_se = use_se + self.stride = stride + self.drop_rate = drop_rate + self.index = index + + @staticmethod + def adjust_channels(channels: int, width_coefficient: float): + return _make_divisible(channels * width_coefficient, 8) + + +class InvertedResidual(nn.Module): + def __init__(self, + cnf: InvertedResidualConfig, + norm_layer: Callable[..., nn.Module]): + super(InvertedResidual, self).__init__() + + if cnf.stride not in [1, 2]: + raise ValueError("illegal stride value.") + + self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c) + + layers = OrderedDict() + activation_layer = nn.SiLU # alias Swish + + # expand + if cnf.expanded_c != cnf.input_c: + layers.update({"expand_conv": ConvBNActivation(cnf.input_c, + cnf.expanded_c, + kernel_size=1, + norm_layer=norm_layer, + activation_layer=activation_layer)}) + + # depthwise + layers.update({"dwconv": ConvBNActivation(cnf.expanded_c, + cnf.expanded_c, + kernel_size=cnf.kernel, + stride=cnf.stride, + groups=cnf.expanded_c, + norm_layer=norm_layer, + activation_layer=activation_layer)}) + + if cnf.use_se: + layers.update({"se": SqueezeExcitation(cnf.input_c, + cnf.expanded_c)}) + + # project + layers.update({"project_conv": ConvBNActivation(cnf.expanded_c, + cnf.out_c, + kernel_size=1, + norm_layer=norm_layer, + activation_layer=nn.Identity)}) + + self.block = nn.Sequential(layers) + self.out_channels = cnf.out_c + self.is_strided = cnf.stride > 1 + + # 只有在使用shortcut连接时才使用dropout层 + if self.use_res_connect and cnf.drop_rate > 0: + self.dropout = DropPath(cnf.drop_rate) + else: + self.dropout = nn.Identity() + + def forward(self, x: Tensor) -> Tensor: + result = self.block(x) + result = self.dropout(result) + if self.use_res_connect: + result += x + + return result + + +class EfficientNet(nn.Module): + def __init__(self, + width_coefficient: float, + depth_coefficient: float, + num_classes: int = 1000, + dropout_rate: float = 0.2, + drop_connect_rate: float = 0.2, + block: Optional[Callable[..., nn.Module]] = None, + norm_layer: Optional[Callable[..., nn.Module]] = None + ): + super(EfficientNet, self).__init__() + + # kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate, repeats + default_cnf = [[3, 32, 16, 1, 1, True, drop_connect_rate, 1], + [3, 16, 24, 6, 2, True, drop_connect_rate, 2], + [5, 24, 40, 6, 2, True, drop_connect_rate, 2], + [3, 40, 80, 6, 2, True, drop_connect_rate, 3], + [5, 80, 112, 6, 1, True, drop_connect_rate, 3], + [5, 112, 192, 6, 2, True, drop_connect_rate, 4], + [3, 192, 320, 6, 1, True, drop_connect_rate, 1]] + + def round_repeats(repeats): + """Round number of repeats based on depth multiplier.""" + return int(math.ceil(depth_coefficient * repeats)) + + if block is None: + block = InvertedResidual + + if norm_layer is None: + norm_layer = partial(nn.BatchNorm2d, eps=1e-3, momentum=0.1) + + adjust_channels = partial(InvertedResidualConfig.adjust_channels, + width_coefficient=width_coefficient) + + # build inverted_residual_setting + bneck_conf = partial(InvertedResidualConfig, + width_coefficient=width_coefficient) + + b = 0 + num_blocks = float(sum(round_repeats(i[-1]) for i in default_cnf)) + inverted_residual_setting = [] + for stage, args in enumerate(default_cnf): + cnf = copy.copy(args) + for i in range(round_repeats(cnf.pop(-1))): + if i > 0: + # strides equal 1 except first cnf + cnf[-3] = 1 # strides + cnf[1] = cnf[2] # input_channel equal output_channel + + cnf[-1] = args[-2] * b / num_blocks # update dropout ratio + index = str(stage + 1) + chr(i + 97) # 1a, 2a, 2b, ... + inverted_residual_setting.append(bneck_conf(*cnf, index)) + b += 1 + + # create layers + layers = OrderedDict() + + # first conv + layers.update({"stem_conv": ConvBNActivation(in_planes=3, + out_planes=adjust_channels(32), + kernel_size=3, + stride=2, + norm_layer=norm_layer)}) + + # building inverted residual blocks + for cnf in inverted_residual_setting: + layers.update({cnf.index: block(cnf, norm_layer)}) + + # build top + last_conv_input_c = inverted_residual_setting[-1].out_c + last_conv_output_c = adjust_channels(1280) + layers.update({"top": ConvBNActivation(in_planes=last_conv_input_c, + out_planes=last_conv_output_c, + kernel_size=1, + norm_layer=norm_layer)}) + + self.features = nn.Sequential(layers) + self.avgpool = nn.AdaptiveAvgPool2d(1) + + classifier = [] + if dropout_rate > 0: + classifier.append(nn.Dropout(p=dropout_rate, inplace=True)) + classifier.append(nn.Linear(last_conv_output_c, num_classes)) + self.classifier = nn.Sequential(*classifier) + + # initial weights + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode="fan_out") + if m.bias is not None: + nn.init.zeros_(m.bias) + elif isinstance(m, nn.BatchNorm2d): + nn.init.ones_(m.weight) + nn.init.zeros_(m.bias) + elif isinstance(m, nn.Linear): + nn.init.normal_(m.weight, 0, 0.01) + nn.init.zeros_(m.bias) + + def _forward_impl(self, x: Tensor) -> Tensor: + x = self.features(x) + x = self.avgpool(x) + x = torch.flatten(x, 1) + x = self.classifier(x) + + return x + + def forward(self, x: Tensor) -> Tensor: + return self._forward_impl(x) + + +def efficientnet_b0(num_classes=1000): + # input image size 224x224 + return EfficientNet(width_coefficient=1.0, + depth_coefficient=1.0, + dropout_rate=0.2, + num_classes=num_classes) + + +def efficientnet_b1(num_classes=1000): + # input image size 240x240 + return EfficientNet(width_coefficient=1.0, + depth_coefficient=1.1, + dropout_rate=0.2, + num_classes=num_classes) + + +def efficientnet_b2(num_classes=1000): + # input image size 260x260 + return EfficientNet(width_coefficient=1.1, + depth_coefficient=1.2, + dropout_rate=0.3, + num_classes=num_classes) + + +def efficientnet_b3(num_classes=1000): + # input image size 300x300 + return EfficientNet(width_coefficient=1.2, + depth_coefficient=1.4, + dropout_rate=0.3, + num_classes=num_classes) + + +def efficientnet_b4(num_classes=1000): + # input image size 380x380 + return EfficientNet(width_coefficient=1.4, + depth_coefficient=1.8, + dropout_rate=0.4, + num_classes=num_classes) + + +def efficientnet_b5(num_classes=1000): + # input image size 456x456 + return EfficientNet(width_coefficient=1.6, + depth_coefficient=2.2, + dropout_rate=0.4, + num_classes=num_classes) + + +def efficientnet_b6(num_classes=1000): + # input image size 528x528 + return EfficientNet(width_coefficient=1.8, + depth_coefficient=2.6, + dropout_rate=0.5, + num_classes=num_classes) + + +def efficientnet_b7(num_classes=1000): + # input image size 600x600 + return EfficientNet(width_coefficient=2.0, + depth_coefficient=3.1, + dropout_rate=0.5, + num_classes=num_classes) diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/my_dataset.py" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/my_dataset.py" new file mode 100644 index 0000000000000000000000000000000000000000..167bc9a306e501d434298f5a64ac022a7aab6e42 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/my_dataset.py" @@ -0,0 +1,37 @@ +from PIL import Image +import torch +from torch.utils.data import Dataset + + +class MyDataSet(Dataset): + """自定义数据集""" + + def __init__(self, images_path: list, images_class: list, transform=None): + self.images_path = images_path + self.images_class = images_class + self.transform = transform + + def __len__(self): + return len(self.images_path) + + def __getitem__(self, item): + img = Image.open(self.images_path[item]) + # RGB为彩色图片,L为灰度图片 + if img.mode != 'RGB': + raise ValueError("image: {} isn't RGB mode.".format(self.images_path[item])) + label = self.images_class[item] + + if self.transform is not None: + img = self.transform(img) + + return img, label + + @staticmethod + def collate_fn(batch): + # 官方实现的default_collate可以参考 + # https://github.com/pytorch/pytorch/blob/67b7e751e6b5931a9f45274653f4f653a4e6cdf6/torch/utils/data/_utils/collate.py + images, labels = tuple(zip(*batch)) + + images = torch.stack(images, dim=0) + labels = torch.as_tensor(labels) + return images, labels diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/predict.py" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/predict.py" new file mode 100644 index 0000000000000000000000000000000000000000..f9a260417f09fdd4e49585a91dc036ed7dce2969 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/predict.py" @@ -0,0 +1,68 @@ +import os +import json + +import torch +from PIL import Image +from torchvision import transforms +import matplotlib.pyplot as plt + +from model import efficientnet_b0 as create_model + + +def main(): + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + img_size = {"B0": 224, + "B1": 240, + "B2": 260, + "B3": 300, + "B4": 380, + "B5": 456, + "B6": 528, + "B7": 600} + num_model = "B0" + + data_transform = transforms.Compose( + [transforms.Resize(img_size[num_model]), + transforms.CenterCrop(img_size[num_model]), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) + + # load image + img_path = "../tulip.jpg" + assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path) + img = Image.open(img_path) + plt.imshow(img) + # [N, C, H, W] + img = data_transform(img) + # expand batch dimension + img = torch.unsqueeze(img, dim=0) + + # read class_indict + json_path = './class_indices.json' + assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path) + + json_file = open(json_path, "r") + class_indict = json.load(json_file) + + # create model + model = create_model(num_classes=5).to(device) + # load model weights + model_weight_path = "./weights/model-29.pth" + model.load_state_dict(torch.load(model_weight_path, map_location=device)) + model.eval() + with torch.no_grad(): + # predict class + output = torch.squeeze(model(img.to(device))).cpu() + predict = torch.softmax(output, dim=0) + predict_cla = torch.argmax(predict).numpy() + + print_res = "class: {} prob: {:.3}".format(class_indict[str(predict_cla)], + predict[predict_cla].numpy()) + plt.title(print_res) + print(print_res) + plt.show() + + +if __name__ == '__main__': + main() diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/requirements.txt" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/requirements.txt" new file mode 100644 index 0000000000000000000000000000000000000000..531b86558aecf6ffe06b5b80be61199e2b3fa5d9 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/requirements.txt" @@ -0,0 +1,5 @@ +numpy==1.19.5 +matplotlib==3.2.1 +tqdm==4.56.0 +torch>=1.7.1 +torchvision>=0.8.2 diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/train.py" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/train.py" new file mode 100644 index 0000000000000000000000000000000000000000..b228d5489cd9c540c5f92b800187ac270ad19510 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/train.py" @@ -0,0 +1,145 @@ +import os +import math +import argparse + +import torch +import torch.optim as optim +from torch.utils.tensorboard import SummaryWriter +from torchvision import transforms +import torch.optim.lr_scheduler as lr_scheduler + +from model import efficientnet_b0 as create_model +from my_dataset import MyDataSet +from utils import read_split_data, train_one_epoch, evaluate + + +def main(args): + device = torch.device(args.device if torch.cuda.is_available() else "cpu") + + print(args) + print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/') + tb_writer = SummaryWriter() + if os.path.exists("./weights") is False: + os.makedirs("./weights") + + train_images_path, train_images_label, val_images_path, val_images_label = read_split_data(args.data_path) + + img_size = {"B0": 224, + "B1": 240, + "B2": 260, + "B3": 300, + "B4": 380, + "B5": 456, + "B6": 528, + "B7": 600} + num_model = "B0" + + data_transform = { + "train": transforms.Compose([transforms.RandomResizedCrop(img_size[num_model]), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]), + "val": transforms.Compose([transforms.Resize(img_size[num_model]), + transforms.CenterCrop(img_size[num_model]), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])} + + # 实例化训练数据集 + train_dataset = MyDataSet(images_path=train_images_path, + images_class=train_images_label, + transform=data_transform["train"]) + + # 实例化验证数据集 + val_dataset = MyDataSet(images_path=val_images_path, + images_class=val_images_label, + transform=data_transform["val"]) + + batch_size = args.batch_size + nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers + print('Using {} dataloader workers every process'.format(nw)) + train_loader = torch.utils.data.DataLoader(train_dataset, + batch_size=batch_size, + shuffle=True, + pin_memory=True, + num_workers=nw, + collate_fn=train_dataset.collate_fn) + + val_loader = torch.utils.data.DataLoader(val_dataset, + batch_size=batch_size, + shuffle=False, + pin_memory=True, + num_workers=nw, + collate_fn=val_dataset.collate_fn) + + # 如果存在预训练权重则载入 + model = create_model(num_classes=args.num_classes).to(device) + if args.weights != "": + if os.path.exists(args.weights): + weights_dict = torch.load(args.weights, map_location=device) + load_weights_dict = {k: v for k, v in weights_dict.items() + if model.state_dict()[k].numel() == v.numel()} + print(model.load_state_dict(load_weights_dict, strict=False)) + else: + raise FileNotFoundError("not found weights file: {}".format(args.weights)) + + # 是否冻结权重 + if args.freeze_layers: + for name, para in model.named_parameters(): + # 除最后一个卷积层和全连接层外,其他权重全部冻结 + if ("features.top" not in name) and ("classifier" not in name): + para.requires_grad_(False) + else: + print("training {}".format(name)) + + pg = [p for p in model.parameters() if p.requires_grad] + optimizer = optim.SGD(pg, lr=args.lr, momentum=0.9, weight_decay=1E-4) + # Scheduler https://arxiv.org/pdf/1812.01187.pdf + lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf # cosine + scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) + + for epoch in range(args.epochs): + # train + mean_loss = train_one_epoch(model=model, + optimizer=optimizer, + data_loader=train_loader, + device=device, + epoch=epoch) + + scheduler.step() + + # validate + acc = evaluate(model=model, + data_loader=val_loader, + device=device) + print("[epoch {}] accuracy: {}".format(epoch, round(acc, 3))) + tags = ["loss", "accuracy", "learning_rate"] + tb_writer.add_scalar(tags[0], mean_loss, epoch) + tb_writer.add_scalar(tags[1], acc, epoch) + tb_writer.add_scalar(tags[2], optimizer.param_groups[0]["lr"], epoch) + + torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--num_classes', type=int, default=16) + parser.add_argument('--epochs', type=int, default=30) + parser.add_argument('--batch-size', type=int, default=16) + parser.add_argument('--lr', type=float, default=0.01) + parser.add_argument('--lrf', type=float, default=0.01) + + # 数据集所在根目录 + # 链接:https://pan.baidu.com/s/1eclz3ZjaLcHlAl22DnQJlQ 提取码:nfw7 + parser.add_argument('--data-path', type=str, + default="./Singapore_style") + + # download model weights + # 链接: https://pan.baidu.com/s/1ouX0UmjCsmSx3ZrqXbowjw 密码: 090i + parser.add_argument('--weights', type=str, default='./efficientnetb0.pth', + help='initial weights path') + parser.add_argument('--freeze-layers', type=bool, default=False) + parser.add_argument('--device', default='cuda:0', help='device id (i.e. 0 or 0,1 or cpu)') + + opt = parser.parse_args() + + main(opt) diff --git "a/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/utils.py" "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/utils.py" new file mode 100644 index 0000000000000000000000000000000000000000..c413747b6af4f4e8d7b900a32c33f83f0798c407 --- /dev/null +++ "b/code/2021_autumn/\347\216\213\351\233\250\347\224\260-\345\237\272\344\272\216EfficientNet\347\232\204\345\256\266\345\205\267\351\243\216\346\240\274\345\210\206\347\261\273\347\240\224\347\251\266/utils.py" @@ -0,0 +1,161 @@ +import os +import sys +import json +import pickle +import random + +import torch +from tqdm import tqdm + +import matplotlib.pyplot as plt + + +def read_split_data(root: str, val_rate: float = 0.2): + random.seed(0) # 保证随机结果可复现 + assert os.path.exists(root), "dataset root: {} does not exist.".format(root) + + # 遍历文件夹,一个文件夹对应一个类别 + flower_class = [cla for cla in os.listdir(root) if os.path.isdir(os.path.join(root, cla))] + # 排序,保证顺序一致 + flower_class.sort() + # 生成类别名称以及对应的数字索引 + class_indices = dict((k, v) for v, k in enumerate(flower_class)) + json_str = json.dumps(dict((val, key) for key, val in class_indices.items()), indent=4) + with open('class_indices.json', 'w') as json_file: + json_file.write(json_str) + + train_images_path = [] # 存储训练集的所有图片路径 + train_images_label = [] # 存储训练集图片对应索引信息 + val_images_path = [] # 存储验证集的所有图片路径 + val_images_label = [] # 存储验证集图片对应索引信息 + every_class_num = [] # 存储每个类别的样本总数 + supported = [".jpg", ".JPG", ".png", ".PNG"] # 支持的文件后缀类型 + # 遍历每个文件夹下的文件 + for cla in flower_class: + cla_path = os.path.join(root, cla) + # 遍历获取supported支持的所有文件路径 + images = [os.path.join(root, cla, i) for i in os.listdir(cla_path) + if os.path.splitext(i)[-1] in supported] + # 获取该类别对应的索引 + image_class = class_indices[cla] + # 记录该类别的样本数量 + every_class_num.append(len(images)) + # 按比例随机采样验证样本 + val_path = random.sample(images, k=int(len(images) * val_rate)) + + for img_path in images: + if img_path in val_path: # 如果该路径在采样的验证集样本中则存入验证集 + val_images_path.append(img_path) + val_images_label.append(image_class) + else: # 否则存入训练集 + train_images_path.append(img_path) + train_images_label.append(image_class) + + print("{} images were found in the dataset.".format(sum(every_class_num))) + print("{} images for training.".format(len(train_images_path))) + print("{} images for validation.".format(len(val_images_path))) + + plot_image = False + if plot_image: + # 绘制每种类别个数柱状图 + plt.bar(range(len(flower_class)), every_class_num, align='center') + # 将横坐标0,1,2,3,4替换为相应的类别名称 + plt.xticks(range(len(flower_class)), flower_class) + # 在柱状图上添加数值标签 + for i, v in enumerate(every_class_num): + plt.text(x=i, y=v + 5, s=str(v), ha='center') + # 设置x坐标 + plt.xlabel('image class') + # 设置y坐标 + plt.ylabel('number of images') + # 设置柱状图的标题 + plt.title('flower class distribution') + plt.show() + + return train_images_path, train_images_label, val_images_path, val_images_label + + +def plot_data_loader_image(data_loader): + batch_size = data_loader.batch_size + plot_num = min(batch_size, 4) + + json_path = './class_indices.json' + assert os.path.exists(json_path), json_path + " does not exist." + json_file = open(json_path, 'r') + class_indices = json.load(json_file) + + for data in data_loader: + images, labels = data + for i in range(plot_num): + # [C, H, W] -> [H, W, C] + img = images[i].numpy().transpose(1, 2, 0) + # 反Normalize操作 + img = (img * [0.229, 0.224, 0.225] + [0.485, 0.456, 0.406]) * 255 + label = labels[i].item() + plt.subplot(1, plot_num, i+1) + plt.xlabel(class_indices[str(label)]) + plt.xticks([]) # 去掉x轴的刻度 + plt.yticks([]) # 去掉y轴的刻度 + plt.imshow(img.astype('uint8')) + plt.show() + + +def write_pickle(list_info: list, file_name: str): + with open(file_name, 'wb') as f: + pickle.dump(list_info, f) + + +def read_pickle(file_name: str) -> list: + with open(file_name, 'rb') as f: + info_list = pickle.load(f) + return info_list + + +def train_one_epoch(model, optimizer, data_loader, device, epoch): + model.train() + loss_function = torch.nn.CrossEntropyLoss() + mean_loss = torch.zeros(1).to(device) + optimizer.zero_grad() + + data_loader = tqdm(data_loader) + + for step, data in enumerate(data_loader): + images, labels = data + + pred = model(images.to(device)) + + loss = loss_function(pred, labels.to(device)) + loss.backward() + mean_loss = (mean_loss * step + loss.detach()) / (step + 1) # update mean losses + + data_loader.desc = "[epoch {}] mean loss {}".format(epoch, round(mean_loss.item(), 3)) + + if not torch.isfinite(loss): + print('WARNING: non-finite loss, ending training ', loss) + sys.exit(1) + + optimizer.step() + optimizer.zero_grad() + + return mean_loss.item() + + +@torch.no_grad() +def evaluate(model, data_loader, device): + model.eval() + + # 验证样本总个数 + total_num = len(data_loader.dataset) + + # 用于存储预测正确的样本个数 + sum_num = torch.zeros(1).to(device) + + data_loader = tqdm(data_loader) + + for step, data in enumerate(data_loader): + images, labels = data + pred = model(images.to(device)) + pred = torch.max(pred, dim=1)[1] + sum_num += torch.eq(pred, labels.to(device)).sum() + + return sum_num.item() / total_num