├── BaselineModels ├── CANet │ ├── CANet.py │ ├── predict_CANet.py │ └── train_CANet.py ├── EfficientNet │ ├── EfficientNet.py │ ├── predict.py │ └── train.py └── MobileNetV3 │ ├── model_v3.py │ ├── predict.py │ └── train.py ├── DWParNet ├── par_DWconv.py ├── par_DWconv2.py ├── par_DWconv3.py ├── par_DWconv4.py ├── predict.py └── train.py ├── DataProcessing ├── PreprocessedData.rar ├── data_process.py ├── data_process_normal.py ├── img_generator_seq.py ├── split_testset.py └── split_trainset.py ├── MemoryTest ├── memoryTest_LSTM.py ├── memoryTest_convAggregation.py ├── memoryTest_convBranch1.py ├── memoryTest_convBranch2.py └── memoryTest_convBranch3.py ├── OriginalDataset ├── DoS_Attack_dataset.rar ├── Fuzzy_Attack_dataset.rar ├── Spoofing_the_RPM_gauge_dataset.rar ├── Spoofing_the_drive_gear_dataset.rar └── normal.rar ├── README.md ├── ResouceAdaptationModel └── algorithm.py └── STParNet ├── par_DW_LSTM.py ├── par_DW_LSTM2.py ├── par_DW_LSTM3.py ├── par_DW_LSTM4.py ├── predict_LSTM.py └── train_LSTM.py /BaselineModels/CANet/CANet.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class FC_ELU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel): 7 | # fully connected layer followed with ELU activation 8 | super(FC_ELU, self).__init__( 9 | nn.Linear(in_channel, out_channel), 10 | nn.ELU(inplace=True) 11 | ) 12 | 13 | 14 | 15 | class CANet(nn.Module): 16 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 17 | super(CANet, self).__init__() 18 | 19 | self.hidden_size = hidden_size 20 | self.num_layers = num_layers 21 | self.input_size = input_size 22 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 23 | self.fc1 = FC_ELU(32, 16) 24 | self.fc2 = FC_ELU(16, 4) 25 | self.fc3 = FC_ELU(4, 5) 26 | 27 | if init_weights: 28 | self._initialize_weights() 29 | 30 | def forward(self, x): 31 | x = x.reshape(-1, 27, self.input_size) 32 | # Set initial hidden and cell states 33 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 34 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 35 | 36 | # Forward propagate LSTM 37 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 38 | 39 | # Decode the hidden state of the last time step 40 | out = self.fc1(out[:, -1, :]) 41 | out = self.fc2(out) 42 | out = self.fc3(out) 43 | return out 44 | 45 | def _initialize_weights(self): 46 | for m in self.modules(): 47 | if isinstance(m, nn.Conv2d): 48 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 49 | if m.bias is not None: 50 | nn.init.constant_(m.bias, 0) 51 | elif isinstance(m, nn.Linear): 52 | nn.init.normal_(m.weight, 0, 0.01) 53 | nn.init.constant_(m.bias, 0) 54 | -------------------------------------------------------------------------------- /BaselineModels/CANet/predict_CANet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | from torchvision import transforms, datasets 7 | from tqdm import tqdm 8 | import matplotlib.pyplot as plt 9 | import torchmetrics 10 | from torchsummary import summary 11 | 12 | # from par_LSTM import ParLSTM 13 | from CANet import CANet 14 | 15 | 16 | # Test_Dir = '../data_img/test/' 17 | 18 | 19 | def main(): 20 | batch_size = 32 21 | num_classes = 5 22 | input_size = 9 23 | hidden_size = 32 24 | num_layers = 1 25 | 26 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 27 | 28 | transform = transforms.Compose([transforms.ToTensor(), 29 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 30 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 31 | # transforms.RandomHorizontalFlip(), 32 | # transforms.ToTensor(), 33 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 34 | 35 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 36 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 37 | print(image_path) 38 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 39 | 40 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 41 | print('Using {} dataloader workers every process'.format(nw)) 42 | 43 | predict_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), 44 | transform=transform) 45 | predict_num = len(predict_dataset) 46 | predict_loader = torch.utils.data.DataLoader(predict_dataset, 47 | batch_size=batch_size, shuffle=True, 48 | num_workers=nw) 49 | print(predict_loader) 50 | 51 | net = CANet(input_size=input_size, hidden_size=hidden_size, 52 | num_layers=num_layers, num_classes=num_classes, init_weights=False) 53 | # load model weights 54 | 55 | model_weight_path = "./CANet_15.pth" 56 | net.load_state_dict(torch.load(model_weight_path)) 57 | net.eval() 58 | # acc = 0.0 # accumulate accurate number / epoch 59 | test_acc = torchmetrics.Accuracy() 60 | test_recall = torchmetrics.Recall(average='none', num_classes=num_classes) 61 | test_precision = torchmetrics.Precision(average='none', num_classes=num_classes) 62 | test_f1 = torchmetrics.F1Score(average='none', num_classes=num_classes) 63 | test_auc = torchmetrics.AUROC(average="macro", num_classes=num_classes) 64 | summary(net, (3, 9, 9)) 65 | 66 | with torch.no_grad(): 67 | predict_bar = tqdm(predict_loader, file=sys.stdout) 68 | for predict_data in predict_bar: 69 | predict_images, predict_labels = predict_data 70 | outputs = net(predict_images) 71 | # loss = loss_function(outputs, test_labels) 72 | predict_y = torch.max(outputs, dim=1)[1] 73 | # acc += torch.eq(predict_y, predict_labels).sum().item() 74 | test_acc.update(predict_y, predict_labels) 75 | test_auc.update(outputs, predict_labels) 76 | test_recall(predict_y, predict_labels) 77 | test_precision(predict_y, predict_labels) 78 | test_f1(predict_y, predict_labels) 79 | 80 | # predict_accurate = acc / predict_num 81 | total_acc = test_acc.compute() 82 | total_recall = test_recall.compute() 83 | total_precision = test_precision.compute() 84 | total_auc = test_auc.compute() 85 | total_f1 = test_f1.compute() 86 | 87 | # print('predict_accuracy: %.4f' %(predict_accurate)) 88 | 89 | print("torch metrics acc:", total_acc) 90 | print("recall of every test dataset class: ", total_recall) 91 | print("precision of every test dataset class: ", total_precision) 92 | print("F1-score of every test dataset class: ", total_f1) 93 | print("auc:", total_auc.item()) 94 | 95 | # 清空计算对象 96 | test_precision.reset() 97 | test_acc.reset() 98 | test_recall.reset() 99 | test_auc.reset() 100 | 101 | 102 | 103 | if __name__ == '__main__': 104 | main() 105 | -------------------------------------------------------------------------------- /BaselineModels/CANet/train_CANet.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.optim as optim 8 | from torchvision import transforms, datasets 9 | from tqdm import tqdm 10 | # from par_LSTM import ParLSTM 11 | from CANet import CANet 12 | 13 | 14 | def main(): 15 | batch_size = 32 16 | num_classes = 5 17 | epochs = 15 18 | input_size = 9 19 | hidden_size = 32 20 | num_layers = 1 21 | 22 | transform = transforms.Compose([transforms.ToTensor(), 23 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 24 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 25 | # transforms.RandomHorizontalFlip(), 26 | # transforms.ToTensor(), 27 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 28 | 29 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 30 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 31 | print(image_path) 32 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 33 | train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), 34 | transform=transform) 35 | train_num = len(train_dataset) 36 | print(train_num) 37 | 38 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 39 | print('Using {} dataloader workers every process'.format(nw)) 40 | 41 | train_loader = torch.utils.data.DataLoader(train_dataset, 42 | batch_size=batch_size, shuffle=True, 43 | num_workers=nw) 44 | 45 | validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), 46 | transform=transform) 47 | val_num = len(validate_dataset) 48 | validate_loader = torch.utils.data.DataLoader(validate_dataset, 49 | batch_size=batch_size, shuffle=False, 50 | num_workers=nw) 51 | 52 | print("using {} images for training, {} images for validation.".format(train_num, 53 | val_num)) 54 | 55 | # create model 56 | net = CANet(input_size=input_size, hidden_size=hidden_size, 57 | num_layers=num_layers, num_classes=num_classes, init_weights=True) 58 | 59 | 60 | # define loss function 61 | loss_function = nn.CrossEntropyLoss() 62 | 63 | # construct an optimizer 64 | params = [p for p in net.parameters() if p.requires_grad] 65 | optimizer = optim.Adam(params, lr=0.0001) 66 | 67 | best_acc = 0.0 68 | save_path = './CANet_15.pth' 69 | train_steps = len(train_loader) 70 | for epoch in range(epochs): 71 | # train 72 | net.train() 73 | running_loss = 0.0 74 | train_bar = tqdm(train_loader, file=sys.stdout) 75 | for step, data in enumerate(train_bar): 76 | images, labels = data 77 | optimizer.zero_grad() 78 | logits = net(images) 79 | loss = loss_function(logits, labels) 80 | loss.backward() 81 | optimizer.step() 82 | 83 | # print statistics 84 | running_loss += loss.item() 85 | 86 | train_bar.desc = "train epoch[{}/{}] loss:{:.4f}".format(epoch + 1, 87 | epochs, 88 | loss) 89 | 90 | # validate 91 | net.eval() 92 | acc = 0.0 # accumulate accurate number / epoch 93 | with torch.no_grad(): 94 | val_bar = tqdm(validate_loader, file=sys.stdout) 95 | for val_data in val_bar: 96 | val_images, val_labels = val_data 97 | outputs = net(val_images) 98 | # loss = loss_function(outputs, test_labels) 99 | predict_y = torch.max(outputs, dim=1)[1] 100 | acc += torch.eq(predict_y, val_labels).sum().item() 101 | 102 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 103 | epochs) 104 | val_accurate = acc / val_num 105 | print('[epoch %d] train_loss: %.4f val_accuracy: %.4f' % 106 | (epoch + 1, running_loss / train_steps, val_accurate)) 107 | 108 | if val_accurate > best_acc: 109 | best_acc = val_accurate 110 | torch.save(net.state_dict(), save_path) 111 | 112 | print('Finished Training') 113 | 114 | 115 | 116 | if __name__ == '__main__': 117 | main() 118 | -------------------------------------------------------------------------------- /BaselineModels/EfficientNet/EfficientNet.py: -------------------------------------------------------------------------------- 1 | import math 2 | import copy 3 | from functools import partial 4 | from collections import OrderedDict 5 | from typing import Optional, Callable 6 | 7 | import torch 8 | import torch.nn as nn 9 | from torch import Tensor 10 | from torch.nn import functional as F 11 | 12 | 13 | def _make_divisible(ch, divisor=8, min_ch=None): 14 | """ 15 | This function is taken from the original tf repo. 16 | It ensures that all layers have a channel number that is divisible by 8 17 | It can be seen here: 18 | https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py 19 | """ 20 | if min_ch is None: 21 | min_ch = divisor 22 | new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor) 23 | # Make sure that round down does not go down by more than 10%. 24 | if new_ch < 0.9 * ch: 25 | new_ch += divisor 26 | return new_ch 27 | 28 | 29 | def drop_path(x, drop_prob: float = 0., training: bool = False): 30 | """ 31 | Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). 32 | "Deep Networks with Stochastic Depth", https://arxiv.org/pdf/1603.09382.pdf 33 | 34 | This function is taken from the rwightman. 35 | It can be seen here: 36 | https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/drop.py#L140 37 | """ 38 | if drop_prob == 0. or not training: 39 | return x 40 | keep_prob = 1 - drop_prob 41 | shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets 42 | random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device) 43 | random_tensor.floor_() # binarize 44 | output = x.div(keep_prob) * random_tensor 45 | return output 46 | 47 | 48 | class DropPath(nn.Module): 49 | """ 50 | Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). 51 | "Deep Networks with Stochastic Depth", https://arxiv.org/pdf/1603.09382.pdf 52 | """ 53 | def __init__(self, drop_prob=None): 54 | super(DropPath, self).__init__() 55 | self.drop_prob = drop_prob 56 | 57 | def forward(self, x): 58 | return drop_path(x, self.drop_prob, self.training) 59 | 60 | 61 | class ConvBNActivation(nn.Sequential): 62 | def __init__(self, 63 | in_planes: int, 64 | out_planes: int, 65 | kernel_size: int = 3, 66 | stride: int = 1, 67 | groups: int = 1, 68 | norm_layer: Optional[Callable[..., nn.Module]] = None, 69 | activation_layer: Optional[Callable[..., nn.Module]] = None): 70 | padding = (kernel_size - 1) // 2 71 | if norm_layer is None: 72 | norm_layer = nn.BatchNorm2d 73 | if activation_layer is None: 74 | activation_layer = nn.SiLU # alias Swish (torch>=1.7) 75 | 76 | super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes, 77 | out_channels=out_planes, 78 | kernel_size=kernel_size, 79 | stride=stride, 80 | padding=padding, 81 | groups=groups, 82 | bias=False), 83 | norm_layer(out_planes), 84 | activation_layer()) 85 | 86 | 87 | class SqueezeExcitation(nn.Module): 88 | def __init__(self, 89 | input_c: int, # block input channel 90 | expand_c: int, # block expand channel 91 | squeeze_factor: int = 4): 92 | super(SqueezeExcitation, self).__init__() 93 | squeeze_c = input_c // squeeze_factor 94 | self.fc1 = nn.Conv2d(expand_c, squeeze_c, 1) 95 | self.ac1 = nn.SiLU() # alias Swish 96 | self.fc2 = nn.Conv2d(squeeze_c, expand_c, 1) 97 | self.ac2 = nn.Sigmoid() 98 | 99 | def forward(self, x: Tensor) -> Tensor: 100 | scale = F.adaptive_avg_pool2d(x, output_size=(1, 1)) 101 | scale = self.fc1(scale) 102 | scale = self.ac1(scale) 103 | scale = self.fc2(scale) 104 | scale = self.ac2(scale) 105 | return scale * x 106 | 107 | 108 | class InvertedResidualConfig: 109 | # kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate 110 | def __init__(self, 111 | kernel: int, # 3 or 5 112 | input_c: int, 113 | out_c: int, 114 | expanded_ratio: int, # 1 or 6 115 | stride: int, # 1 or 2 116 | use_se: bool, # True 117 | drop_rate: float, 118 | index: str, # 1a, 2a, 2b, ... 119 | width_coefficient: float): 120 | self.input_c = self.adjust_channels(input_c, width_coefficient) 121 | self.kernel = kernel 122 | self.expanded_c = self.input_c * expanded_ratio 123 | self.out_c = self.adjust_channels(out_c, width_coefficient) 124 | self.use_se = use_se 125 | self.stride = stride 126 | self.drop_rate = drop_rate 127 | self.index = index 128 | 129 | @staticmethod 130 | def adjust_channels(channels: int, width_coefficient: float): 131 | return _make_divisible(channels * width_coefficient, 8) 132 | 133 | 134 | class InvertedResidual(nn.Module): 135 | def __init__(self, 136 | cnf: InvertedResidualConfig, 137 | norm_layer: Callable[..., nn.Module]): 138 | super(InvertedResidual, self).__init__() 139 | 140 | if cnf.stride not in [1, 2]: 141 | raise ValueError("illegal stride value.") 142 | 143 | self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c) 144 | 145 | layers = OrderedDict() 146 | activation_layer = nn.SiLU # alias Swish 147 | 148 | # expand 149 | if cnf.expanded_c != cnf.input_c: 150 | layers.update({"expand_conv": ConvBNActivation(cnf.input_c, 151 | cnf.expanded_c, 152 | kernel_size=1, 153 | norm_layer=norm_layer, 154 | activation_layer=activation_layer)}) 155 | 156 | # depthwise 157 | layers.update({"dwconv": ConvBNActivation(cnf.expanded_c, 158 | cnf.expanded_c, 159 | kernel_size=cnf.kernel, 160 | stride=cnf.stride, 161 | groups=cnf.expanded_c, 162 | norm_layer=norm_layer, 163 | activation_layer=activation_layer)}) 164 | 165 | if cnf.use_se: 166 | layers.update({"se": SqueezeExcitation(cnf.input_c, 167 | cnf.expanded_c)}) 168 | 169 | # project 170 | layers.update({"project_conv": ConvBNActivation(cnf.expanded_c, 171 | cnf.out_c, 172 | kernel_size=1, 173 | norm_layer=norm_layer, 174 | activation_layer=nn.Identity)}) 175 | 176 | self.block = nn.Sequential(layers) 177 | self.out_channels = cnf.out_c 178 | self.is_strided = cnf.stride > 1 179 | 180 | # 只有在使用shortcut连接时才使用dropout层 181 | if self.use_res_connect and cnf.drop_rate > 0: 182 | self.dropout = DropPath(cnf.drop_rate) 183 | else: 184 | self.dropout = nn.Identity() 185 | 186 | def forward(self, x: Tensor) -> Tensor: 187 | result = self.block(x) 188 | result = self.dropout(result) 189 | if self.use_res_connect: 190 | result += x 191 | 192 | return result 193 | 194 | 195 | class EfficientNet(nn.Module): 196 | def __init__(self, 197 | width_coefficient: float, 198 | depth_coefficient: float, 199 | num_classes: int = 1000, 200 | dropout_rate: float = 0.2, 201 | drop_connect_rate: float = 0.2, 202 | block: Optional[Callable[..., nn.Module]] = None, 203 | norm_layer: Optional[Callable[..., nn.Module]] = None 204 | ): 205 | super(EfficientNet, self).__init__() 206 | 207 | # kernel_size, in_channel, out_channel, exp_ratio, strides, use_SE, drop_connect_rate, repeats 208 | default_cnf = [[3, 32, 16, 1, 1, True, drop_connect_rate, 1], 209 | [3, 16, 24, 6, 2, True, drop_connect_rate, 2], 210 | [5, 24, 40, 6, 2, True, drop_connect_rate, 2], 211 | [3, 40, 80, 6, 2, True, drop_connect_rate, 3], 212 | [5, 80, 112, 6, 1, True, drop_connect_rate, 3], 213 | [5, 112, 192, 6, 2, True, drop_connect_rate, 4], 214 | [3, 192, 320, 6, 1, True, drop_connect_rate, 1]] 215 | 216 | def round_repeats(repeats): 217 | """Round number of repeats based on depth multiplier.""" 218 | return int(math.ceil(depth_coefficient * repeats)) 219 | 220 | if block is None: 221 | block = InvertedResidual 222 | 223 | if norm_layer is None: 224 | norm_layer = partial(nn.BatchNorm2d, eps=1e-3, momentum=0.1) 225 | 226 | adjust_channels = partial(InvertedResidualConfig.adjust_channels, 227 | width_coefficient=width_coefficient) 228 | 229 | # build inverted_residual_setting 230 | bneck_conf = partial(InvertedResidualConfig, 231 | width_coefficient=width_coefficient) 232 | 233 | b = 0 234 | num_blocks = float(sum(round_repeats(i[-1]) for i in default_cnf)) 235 | inverted_residual_setting = [] 236 | for stage, args in enumerate(default_cnf): 237 | cnf = copy.copy(args) 238 | for i in range(round_repeats(cnf.pop(-1))): 239 | if i > 0: 240 | # strides equal 1 except first cnf 241 | cnf[-3] = 1 # strides 242 | cnf[1] = cnf[2] # input_channel equal output_channel 243 | 244 | cnf[-1] = args[-2] * b / num_blocks # update dropout ratio 245 | index = str(stage + 1) + chr(i + 97) # 1a, 2a, 2b, ... 246 | inverted_residual_setting.append(bneck_conf(*cnf, index)) 247 | b += 1 248 | 249 | # create layers 250 | layers = OrderedDict() 251 | 252 | # first conv 253 | layers.update({"stem_conv": ConvBNActivation(in_planes=3, 254 | out_planes=adjust_channels(32), 255 | kernel_size=3, 256 | stride=2, 257 | norm_layer=norm_layer)}) 258 | 259 | # building inverted residual blocks 260 | for cnf in inverted_residual_setting: 261 | layers.update({cnf.index: block(cnf, norm_layer)}) 262 | 263 | # build top 264 | last_conv_input_c = inverted_residual_setting[-1].out_c 265 | last_conv_output_c = adjust_channels(1280) 266 | layers.update({"top": ConvBNActivation(in_planes=last_conv_input_c, 267 | out_planes=last_conv_output_c, 268 | kernel_size=1, 269 | norm_layer=norm_layer)}) 270 | 271 | self.features = nn.Sequential(layers) 272 | self.avgpool = nn.AdaptiveAvgPool2d(1) 273 | 274 | classifier = [] 275 | if dropout_rate > 0: 276 | classifier.append(nn.Dropout(p=dropout_rate, inplace=True)) 277 | classifier.append(nn.Linear(last_conv_output_c, num_classes)) 278 | self.classifier = nn.Sequential(*classifier) 279 | 280 | # initial weights 281 | for m in self.modules(): 282 | if isinstance(m, nn.Conv2d): 283 | nn.init.kaiming_normal_(m.weight, mode="fan_out") 284 | if m.bias is not None: 285 | nn.init.zeros_(m.bias) 286 | elif isinstance(m, nn.BatchNorm2d): 287 | nn.init.ones_(m.weight) 288 | nn.init.zeros_(m.bias) 289 | elif isinstance(m, nn.Linear): 290 | nn.init.normal_(m.weight, 0, 0.01) 291 | nn.init.zeros_(m.bias) 292 | 293 | def _forward_impl(self, x: Tensor) -> Tensor: 294 | x = self.features(x) 295 | x = self.avgpool(x) 296 | x = torch.flatten(x, 1) 297 | x = self.classifier(x) 298 | 299 | return x 300 | 301 | def forward(self, x: Tensor) -> Tensor: 302 | return self._forward_impl(x) 303 | 304 | 305 | def efficientnet_b0(num_classes=1000): 306 | # input image size 224x224 307 | return EfficientNet(width_coefficient=1.0, 308 | depth_coefficient=1.0, 309 | dropout_rate=0.2, 310 | num_classes=num_classes) 311 | 312 | 313 | def efficientnet_b1(num_classes=1000): 314 | # input image size 240x240 315 | return EfficientNet(width_coefficient=1.0, 316 | depth_coefficient=1.1, 317 | dropout_rate=0.2, 318 | num_classes=num_classes) 319 | 320 | 321 | def efficientnet_b2(num_classes=1000): 322 | # input image size 260x260 323 | return EfficientNet(width_coefficient=1.1, 324 | depth_coefficient=1.2, 325 | dropout_rate=0.3, 326 | num_classes=num_classes) 327 | 328 | 329 | def efficientnet_b3(num_classes=1000): 330 | # input image size 300x300 331 | return EfficientNet(width_coefficient=1.2, 332 | depth_coefficient=1.4, 333 | dropout_rate=0.3, 334 | num_classes=num_classes) 335 | 336 | 337 | def efficientnet_b4(num_classes=1000): 338 | # input image size 380x380 339 | return EfficientNet(width_coefficient=1.4, 340 | depth_coefficient=1.8, 341 | dropout_rate=0.4, 342 | num_classes=num_classes) 343 | 344 | 345 | def efficientnet_b5(num_classes=1000): 346 | # input image size 456x456 347 | return EfficientNet(width_coefficient=1.6, 348 | depth_coefficient=2.2, 349 | dropout_rate=0.4, 350 | num_classes=num_classes) 351 | 352 | 353 | def efficientnet_b6(num_classes=1000): 354 | # input image size 528x528 355 | return EfficientNet(width_coefficient=1.8, 356 | depth_coefficient=2.6, 357 | dropout_rate=0.5, 358 | num_classes=num_classes) 359 | 360 | 361 | def efficientnet_b7(num_classes=1000): 362 | # input image size 600x600 363 | return EfficientNet(width_coefficient=2.0, 364 | depth_coefficient=3.1, 365 | dropout_rate=0.5, 366 | num_classes=num_classes) -------------------------------------------------------------------------------- /BaselineModels/EfficientNet/predict.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | from tqdm import tqdm 7 | from PIL import Image 8 | from torchvision import transforms, datasets 9 | import matplotlib.pyplot as plt 10 | import torchmetrics 11 | from torchsummary import summary 12 | 13 | # from model_v2 import MobileNetV2 14 | from EfficientNet import efficientnet_b0 15 | 16 | def main(): 17 | batch_size = 32 18 | num_classes = 5 19 | 20 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 21 | 22 | transform = transforms.Compose( 23 | [transforms.Resize(256), 24 | transforms.CenterCrop(224), 25 | transforms.ToTensor(), 26 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) 27 | 28 | 29 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 30 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 31 | print(image_path) 32 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 33 | 34 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 35 | print('Using {} dataloader workers every process'.format(nw)) 36 | 37 | predict_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), 38 | transform=transform) 39 | predict_num = len(predict_dataset) 40 | predict_loader = torch.utils.data.DataLoader(predict_dataset, 41 | batch_size=batch_size, shuffle=True, 42 | num_workers=nw) 43 | print(predict_loader) 44 | 45 | # create model 46 | net = efficientnet_b0(num_classes=5) 47 | 48 | model_weight_path = "./EfficientNet_B0_seq.pth" 49 | net.load_state_dict(torch.load(model_weight_path)) 50 | net.eval() 51 | # acc = 0.0 # accumulate accurate number / epoch 52 | test_acc = torchmetrics.Accuracy() 53 | test_recall = torchmetrics.Recall(average='none', num_classes=num_classes) 54 | test_precision = torchmetrics.Precision(average='none', num_classes=num_classes) 55 | test_f1 = torchmetrics.F1Score(average='none', num_classes=num_classes) 56 | test_auc = torchmetrics.AUROC(average="macro", num_classes=num_classes) 57 | summary(net, (3, 244, 244)) 58 | 59 | with torch.no_grad(): 60 | predict_bar = tqdm(predict_loader, file=sys.stdout) 61 | for predict_data in predict_bar: 62 | predict_images, predict_labels = predict_data 63 | outputs = net(predict_images) 64 | # loss = loss_function(outputs, test_labels) 65 | predict_y = torch.max(outputs, dim=1)[1] 66 | # acc += torch.eq(predict_y, predict_labels).sum().item() 67 | test_acc(predict_y, predict_labels) 68 | test_auc.update(outputs, predict_labels) 69 | test_recall(predict_y, predict_labels) 70 | test_precision(predict_y, predict_labels) 71 | test_f1(predict_y, predict_labels) 72 | 73 | # predict_accurate = acc / predict_num 74 | total_acc = test_acc.compute() 75 | total_recall = test_recall.compute() 76 | total_precision = test_precision.compute() 77 | total_auc = test_auc.compute() 78 | total_f1 = test_f1.compute() 79 | 80 | # print('predict_accuracy: %.4f' %(predict_accurate)) 81 | 82 | print("torch metrics acc:", total_acc) 83 | print("recall of every test dataset class: ", total_recall) 84 | print("precision of every test dataset class: ", total_precision) 85 | print("F1-score of every test dataset class: ", total_f1) 86 | print("auc:", total_auc.item()) 87 | 88 | # 清空计算对象 89 | test_precision.reset() 90 | test_acc.reset() 91 | test_recall.reset() 92 | test_auc.reset() 93 | 94 | 95 | if __name__ == '__main__': 96 | main() 97 | -------------------------------------------------------------------------------- /BaselineModels/EfficientNet/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.optim as optim 8 | from torchvision import transforms, datasets 9 | from tqdm import tqdm 10 | 11 | # from model_v2 import MobileNetV2 12 | from EfficientNet import efficientnet_b0 13 | # import torchvision.models.mobilenet # ctrl+单击,可进入函数/库查看 14 | 15 | def main(): 16 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 17 | # print("using {} device.".format(device)) 18 | 19 | batch_size = 16 20 | epochs = 5 21 | 22 | data_transform = { 23 | "train": transforms.Compose([transforms.RandomResizedCrop(224), 24 | transforms.RandomHorizontalFlip(), 25 | transforms.ToTensor(), 26 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]), 27 | "val": transforms.Compose([transforms.Resize(256), 28 | transforms.CenterCrop(224), 29 | transforms.ToTensor(), 30 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])} 31 | 32 | 33 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 34 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 35 | print(image_path) 36 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 37 | train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), 38 | transform=data_transform["train"]) 39 | train_num = len(train_dataset) 40 | print(train_num) 41 | 42 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 43 | print('Using {} dataloader workers every process'.format(nw)) 44 | 45 | train_loader = torch.utils.data.DataLoader(train_dataset, 46 | batch_size=batch_size, shuffle=True, 47 | num_workers=nw) 48 | # collate_fn=train_dataset.collate_fn) 49 | 50 | validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), 51 | transform=data_transform["val"]) 52 | val_num = len(validate_dataset) 53 | validate_loader = torch.utils.data.DataLoader(validate_dataset, 54 | batch_size=batch_size, shuffle=False, 55 | num_workers=nw) 56 | # collate_fn=validate_dataset.collate_fn) 57 | 58 | print("using {} images for training, {} images for validation.".format(train_num, 59 | val_num)) 60 | 61 | # create model 62 | net = efficientnet_b0(num_classes=5) 63 | 64 | 65 | # define loss function 66 | loss_function = nn.CrossEntropyLoss() 67 | 68 | # construct an optimizer 69 | params = [p for p in net.parameters() if p.requires_grad] 70 | optimizer = optim.Adam(params, lr=0.0001) 71 | 72 | best_acc = 0.0 73 | save_path = './EfficientNet_B0_seq.pth' 74 | train_steps = len(train_loader) 75 | for epoch in range(epochs): 76 | # train 77 | net.train() 78 | running_loss = 0.0 79 | train_bar = tqdm(train_loader, file=sys.stdout) 80 | for step, data in enumerate(train_bar): 81 | images, labels = data 82 | optimizer.zero_grad() 83 | logits = net(images) 84 | loss = loss_function(logits, labels) 85 | loss.backward() 86 | optimizer.step() 87 | 88 | # print statistics 89 | running_loss += loss.item() 90 | 91 | train_bar.desc = "train epoch[{}/{}] loss:{:.4f}".format(epoch + 1, 92 | epochs, 93 | loss) 94 | 95 | # validate 96 | net.eval() 97 | acc = 0.0 # accumulate accurate number / epoch 98 | with torch.no_grad(): 99 | val_bar = tqdm(validate_loader, file=sys.stdout) 100 | for val_data in val_bar: 101 | val_images, val_labels = val_data 102 | outputs = net(val_images) 103 | # loss = loss_function(outputs, test_labels) 104 | predict_y = torch.max(outputs, dim=1)[1] 105 | acc += torch.eq(predict_y, val_labels).sum().item() 106 | 107 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 108 | epochs) 109 | val_accurate = acc / val_num 110 | print('[epoch %d] train_loss: %.4f val_accuracy: %.4f' % 111 | (epoch + 1, running_loss / train_steps, val_accurate)) 112 | 113 | if val_accurate > best_acc: 114 | best_acc = val_accurate 115 | torch.save(net.state_dict(), save_path) 116 | 117 | print('Finished Training') 118 | 119 | 120 | if __name__ == '__main__': 121 | main() 122 | -------------------------------------------------------------------------------- /BaselineModels/MobileNetV3/model_v3.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List, Optional 2 | 3 | import torch 4 | from torch import nn, Tensor 5 | from torch.nn import functional as F 6 | from functools import partial 7 | 8 | 9 | def _make_divisible(ch, divisor=8, min_ch=None): 10 | # 将传入的ch调整到离它最近的8的整数倍的数值,会对硬件更友好,且有一定速度的提升 11 | """ 12 | This function is taken from the original tf repo. 13 | It ensures that all layers have a channel number that is divisible by 8 14 | It can be seen here: 15 | https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py 16 | """ 17 | if min_ch is None: 18 | min_ch = divisor 19 | new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor) 20 | # Make sure that round down does not go down by more than 10%. 21 | if new_ch < 0.9 * ch: 22 | new_ch += divisor 23 | return new_ch 24 | 25 | 26 | class ConvBNActivation(nn.Sequential): 27 | def __init__(self, 28 | in_planes: int, 29 | out_planes: int, 30 | kernel_size: int = 3, 31 | stride: int = 1, 32 | groups: int = 1, 33 | norm_layer: Optional[Callable[..., nn.Module]] = None, # BN层 34 | activation_layer: Optional[Callable[..., nn.Module]] = None): # 激活函数 35 | padding = (kernel_size - 1) // 2 36 | if norm_layer is None: 37 | norm_layer = nn.BatchNorm2d # 默认BN方法 38 | if activation_layer is None: 39 | activation_layer = nn.ReLU6 # 默认激活函数 40 | super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes, 41 | out_channels=out_planes, 42 | kernel_size=kernel_size, 43 | stride=stride, 44 | padding=padding, 45 | groups=groups, 46 | bias=False), # 因为会使用到BN层,偏置默认为false 47 | norm_layer(out_planes), 48 | activation_layer(inplace=True)) 49 | # 使用super方法搭建结构层单元 50 | 51 | 52 | class SqueezeExcitation(nn.Module): # 注意力机制 53 | def __init__(self, input_c: int, squeeze_factor: int = 4): 54 | super(SqueezeExcitation, self).__init__() 55 | squeeze_c = _make_divisible(input_c // squeeze_factor, 8) 56 | self.fc1 = nn.Conv2d(input_c, squeeze_c, 1) 57 | self.fc2 = nn.Conv2d(squeeze_c, input_c, 1) 58 | 59 | def forward(self, x: Tensor) -> Tensor: 60 | scale = F.adaptive_avg_pool2d(x, output_size=(1, 1)) 61 | scale = self.fc1(scale) 62 | scale = F.relu(scale, inplace=True) 63 | scale = self.fc2(scale) 64 | scale = F.hardsigmoid(scale, inplace=True) 65 | return scale * x 66 | 67 | 68 | class InvertedResidualConfig: # 对应每一层到配置参数 69 | def __init__(self, 70 | input_c: int, 71 | kernel: int, 72 | expanded_c: int, 73 | out_c: int, 74 | use_se: bool, 75 | activation: str, 76 | stride: int, 77 | width_multi: float): 78 | self.input_c = self.adjust_channels(input_c, width_multi) 79 | self.kernel = kernel 80 | self.expanded_c = self.adjust_channels(expanded_c, width_multi) 81 | self.out_c = self.adjust_channels(out_c, width_multi) 82 | self.use_se = use_se # 是否使用注意力机制 83 | self.use_hs = activation == "HS" # whether using h-swish activation # 是否使用HS激活函数 84 | self.stride = stride 85 | 86 | @staticmethod 87 | def adjust_channels(channels: int, width_multi: float): # 静态方法 88 | return _make_divisible(channels * width_multi, 8) 89 | 90 | 91 | class InvertedResidual(nn.Module): 92 | def __init__(self, 93 | cnf: InvertedResidualConfig, 94 | norm_layer: Callable[..., nn.Module]): 95 | super(InvertedResidual, self).__init__() 96 | 97 | if cnf.stride not in [1, 2]: # 该模型中stride只能为1或2,否则报错 98 | raise ValueError("illegal stride value.") 99 | 100 | self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c) # 满足条件才有shortcut分支 101 | 102 | layers: List[nn.Module] = [] 103 | activation_layer = nn.Hardswish if cnf.use_hs else nn.ReLU 104 | # 官方在1.7及以上的时候才有hardwish或者hardsigmoid激活函数 105 | 106 | # expand 107 | if cnf.expanded_c != cnf.input_c: # 第一个层(相等),没有这个卷积层 108 | layers.append(ConvBNActivation(cnf.input_c, 109 | cnf.expanded_c, 110 | kernel_size=1, 111 | norm_layer=norm_layer, 112 | activation_layer=activation_layer)) 113 | 114 | # depthwise # DW卷积层 115 | layers.append(ConvBNActivation(cnf.expanded_c, 116 | cnf.expanded_c, 117 | kernel_size=cnf.kernel, 118 | stride=cnf.stride, 119 | groups=cnf.expanded_c, # 每个channel都进行卷积 120 | norm_layer=norm_layer, 121 | activation_layer=activation_layer)) 122 | 123 | if cnf.use_se: # 使用注意力机制 124 | layers.append(SqueezeExcitation(cnf.expanded_c)) 125 | 126 | # project 127 | layers.append(ConvBNActivation(cnf.expanded_c, 128 | cnf.out_c, 129 | kernel_size=1, 130 | norm_layer=norm_layer, 131 | activation_layer=nn.Identity)) 132 | 133 | self.block = nn.Sequential(*layers) 134 | self.out_channels = cnf.out_c 135 | self.is_strided = cnf.stride > 1 136 | 137 | def forward(self, x: Tensor) -> Tensor: 138 | result = self.block(x) 139 | if self.use_res_connect: 140 | result += x 141 | 142 | return result 143 | 144 | 145 | class MobileNetV3(nn.Module): 146 | def __init__(self, 147 | inverted_residual_setting: List[InvertedResidualConfig], # 各层参数列表 148 | last_channel: int, 149 | num_classes: int = 1000, 150 | block: Optional[Callable[..., nn.Module]] = None, 151 | norm_layer: Optional[Callable[..., nn.Module]] = None): 152 | super(MobileNetV3, self).__init__() 153 | 154 | if not inverted_residual_setting: 155 | raise ValueError("The inverted_residual_setting should not be empty.") 156 | elif not (isinstance(inverted_residual_setting, List) and 157 | all([isinstance(s, InvertedResidualConfig) for s in inverted_residual_setting])): 158 | raise TypeError("The inverted_residual_setting should be List[InvertedResidualConfig]") 159 | # 遍历列表进行数据检查 160 | 161 | if block is None: 162 | block = InvertedResidual 163 | 164 | if norm_layer is None: 165 | norm_layer = partial(nn.BatchNorm2d, eps=0.001, momentum=0.01) 166 | # partial方法是一种语法,为batchnorm2d方法传入两个默认参数 167 | 168 | layers: List[nn.Module] = [] 169 | 170 | # building first layer 171 | firstconv_output_c = inverted_residual_setting[0].input_c 172 | layers.append(ConvBNActivation(3, 173 | firstconv_output_c, 174 | kernel_size=3, 175 | stride=2, 176 | norm_layer=norm_layer, 177 | activation_layer=nn.Hardswish)) 178 | # building inverted residual blocks 179 | for cnf in inverted_residual_setting: 180 | layers.append(block(cnf, norm_layer)) # 遍历参数配置列表,搭建出来每层结构 181 | 182 | # building last several layers 183 | lastconv_input_c = inverted_residual_setting[-1].out_c 184 | lastconv_output_c = 6 * lastconv_input_c # 由模型决定 185 | layers.append(ConvBNActivation(lastconv_input_c, 186 | lastconv_output_c, 187 | kernel_size=1, 188 | norm_layer=norm_layer, 189 | activation_layer=nn.Hardswish)) 190 | self.features = nn.Sequential(*layers) 191 | self.avgpool = nn.AdaptiveAvgPool2d(1) 192 | self.classifier = nn.Sequential(nn.Linear(lastconv_output_c, last_channel), 193 | nn.Hardswish(inplace=True), 194 | nn.Dropout(p=0.2, inplace=True), 195 | nn.Linear(last_channel, num_classes)) 196 | 197 | # initial weights 198 | for m in self.modules(): 199 | if isinstance(m, nn.Conv2d): 200 | nn.init.kaiming_normal_(m.weight, mode="fan_out") 201 | if m.bias is not None: 202 | nn.init.zeros_(m.bias) 203 | elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): 204 | nn.init.ones_(m.weight) 205 | nn.init.zeros_(m.bias) 206 | elif isinstance(m, nn.Linear): 207 | nn.init.normal_(m.weight, 0, 0.01) 208 | nn.init.zeros_(m.bias) 209 | 210 | def _forward_impl(self, x: Tensor) -> Tensor: 211 | x = self.features(x) 212 | x = self.avgpool(x) 213 | x = torch.flatten(x, 1) 214 | x = self.classifier(x) 215 | 216 | return x 217 | 218 | def forward(self, x: Tensor) -> Tensor: 219 | return self._forward_impl(x) 220 | 221 | 222 | def mobilenet_v3_large(num_classes: int = 1000, 223 | reduced_tail: bool = False) -> MobileNetV3: 224 | """ 225 | Constructs a large MobileNetV3 architecture from 226 | "Searching for MobileNetV3" . 227 | weights_link: 228 | https://download.pytorch.org/models/mobilenet_v3_large-8738ca79.pth 229 | Args: 230 | num_classes (int): number of classes 231 | reduced_tail (bool): If True, reduces the channel counts of all feature layers 232 | between C4 and C5 by 2. It is used to reduce the channel redundancy in the 233 | backbone for Detection and Segmentation. 234 | """ 235 | width_multi = 1.0 236 | bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi) 237 | adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi) 238 | 239 | reduce_divider = 2 if reduced_tail else 1 # 一种尽量减少参数的调整 240 | 241 | inverted_residual_setting = [ 242 | # input_c, kernel, expanded_c, out_c, use_se, activation, stride 243 | bneck_conf(16, 3, 16, 16, False, "RE", 1), 244 | bneck_conf(16, 3, 64, 24, False, "RE", 2), # C1 245 | bneck_conf(24, 3, 72, 24, False, "RE", 1), 246 | bneck_conf(24, 5, 72, 40, True, "RE", 2), # C2 247 | bneck_conf(40, 5, 120, 40, True, "RE", 1), 248 | bneck_conf(40, 5, 120, 40, True, "RE", 1), 249 | bneck_conf(40, 3, 240, 80, False, "HS", 2), # C3 250 | bneck_conf(80, 3, 200, 80, False, "HS", 1), 251 | bneck_conf(80, 3, 184, 80, False, "HS", 1), 252 | bneck_conf(80, 3, 184, 80, False, "HS", 1), 253 | bneck_conf(80, 3, 480, 112, True, "HS", 1), 254 | bneck_conf(112, 3, 672, 112, True, "HS", 1), 255 | bneck_conf(112, 5, 672, 160 // reduce_divider, True, "HS", 2), # C4 256 | bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1), 257 | bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1), 258 | ] 259 | last_channel = adjust_channels(1280 // reduce_divider) # C5 260 | 261 | return MobileNetV3(inverted_residual_setting=inverted_residual_setting, 262 | last_channel=last_channel, 263 | num_classes=num_classes) 264 | 265 | 266 | def mobilenet_v3_small(num_classes: int = 1000, 267 | reduced_tail: bool = False) -> MobileNetV3: 268 | """ 269 | Constructs a large MobileNetV3 architecture from 270 | "Searching for MobileNetV3" . 271 | weights_link: 272 | https://download.pytorch.org/models/mobilenet_v3_small-047dcff4.pth 273 | Args: 274 | num_classes (int): number of classes 275 | reduced_tail (bool): If True, reduces the channel counts of all feature layers 276 | between C4 and C5 by 2. It is used to reduce the channel redundancy in the 277 | backbone for Detection and Segmentation. 278 | """ 279 | width_multi = 1.0 280 | bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi) 281 | adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi) 282 | 283 | reduce_divider = 2 if reduced_tail else 1 284 | 285 | inverted_residual_setting = [ 286 | # input_c, kernel, expanded_c, out_c, use_se, activation, stride 287 | bneck_conf(16, 3, 16, 16, True, "RE", 2), # C1 288 | bneck_conf(16, 3, 72, 24, False, "RE", 2), # C2 289 | bneck_conf(24, 3, 88, 24, False, "RE", 1), 290 | bneck_conf(24, 5, 96, 40, True, "HS", 2), # C3 291 | bneck_conf(40, 5, 240, 40, True, "HS", 1), 292 | bneck_conf(40, 5, 240, 40, True, "HS", 1), 293 | bneck_conf(40, 5, 120, 48, True, "HS", 1), 294 | bneck_conf(48, 5, 144, 48, True, "HS", 1), 295 | bneck_conf(48, 5, 288, 96 // reduce_divider, True, "HS", 2), # C4 296 | bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1), 297 | bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1) 298 | ] 299 | last_channel = adjust_channels(1024 // reduce_divider) # C5 300 | 301 | return MobileNetV3(inverted_residual_setting=inverted_residual_setting, 302 | last_channel=last_channel, 303 | num_classes=num_classes) -------------------------------------------------------------------------------- /BaselineModels/MobileNetV3/predict.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | from tqdm import tqdm 7 | from PIL import Image 8 | from torchvision import transforms, datasets 9 | import matplotlib.pyplot as plt 10 | import torchmetrics 11 | from torchsummary import summary 12 | 13 | # from model_v2 import MobileNetV2 14 | from model_v3 import mobilenet_v3_small 15 | 16 | def main(): 17 | batch_size = 32 18 | num_classes = 5 19 | 20 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 21 | 22 | transform = transforms.Compose( 23 | [transforms.Resize(256), 24 | transforms.CenterCrop(224), 25 | transforms.ToTensor(), 26 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]) 27 | 28 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 29 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 30 | print(image_path) 31 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 32 | 33 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 34 | print('Using {} dataloader workers every process'.format(nw)) 35 | 36 | predict_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), 37 | transform=transform) 38 | predict_num = len(predict_dataset) 39 | predict_loader = torch.utils.data.DataLoader(predict_dataset, 40 | batch_size=batch_size, shuffle=True, 41 | num_workers=nw) 42 | print(predict_loader) 43 | 44 | # create model 45 | net = mobilenet_v3_small(num_classes=5) 46 | 47 | model_weight_path = "./MobileNetV3_small_seq.pth" 48 | net.load_state_dict(torch.load(model_weight_path)) 49 | net.eval() 50 | # acc = 0.0 # accumulate accurate number / epoch 51 | test_acc = torchmetrics.Accuracy() 52 | test_recall = torchmetrics.Recall(average='none', num_classes=num_classes) 53 | test_precision = torchmetrics.Precision(average='none', num_classes=num_classes) 54 | test_f1 = torchmetrics.F1Score(average='none', num_classes=num_classes) 55 | test_auc = torchmetrics.AUROC(average="macro", num_classes=num_classes) 56 | summary(net, (3, 244, 244)) 57 | 58 | with torch.no_grad(): 59 | predict_bar = tqdm(predict_loader, file=sys.stdout) 60 | for predict_data in predict_bar: 61 | predict_images, predict_labels = predict_data 62 | outputs = net(predict_images) 63 | # loss = loss_function(outputs, test_labels) 64 | predict_y = torch.max(outputs, dim=1)[1] 65 | # acc += torch.eq(predict_y, predict_labels).sum().item() 66 | test_acc(predict_y, predict_labels) 67 | test_auc.update(outputs, predict_labels) 68 | test_recall(predict_y, predict_labels) 69 | test_precision(predict_y, predict_labels) 70 | test_f1(predict_y, predict_labels) 71 | 72 | # predict_accurate = acc / predict_num 73 | total_acc = test_acc.compute() 74 | total_recall = test_recall.compute() 75 | total_precision = test_precision.compute() 76 | total_auc = test_auc.compute() 77 | total_f1 = test_f1.compute() 78 | 79 | # print('predict_accuracy: %.4f' %(predict_accurate)) 80 | 81 | print("torch metrics acc:", total_acc) 82 | print("recall of every test dataset class: ", total_recall) 83 | print("precision of every test dataset class: ", total_precision) 84 | print("F1-score of every test dataset class: ", total_f1) 85 | print("auc:", total_auc.item()) 86 | 87 | # 清空计算对象 88 | test_precision.reset() 89 | test_acc.reset() 90 | test_recall.reset() 91 | test_auc.reset() 92 | 93 | 94 | if __name__ == '__main__': 95 | main() 96 | -------------------------------------------------------------------------------- /BaselineModels/MobileNetV3/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.optim as optim 8 | from torchvision import transforms, datasets 9 | from tqdm import tqdm 10 | 11 | # from model_v2 import MobileNetV2 12 | from model_v3 import mobilenet_v3_small 13 | # import torchvision.models.mobilenet # ctrl+单击,可进入函数/库查看 14 | 15 | def main(): 16 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 17 | # print("using {} device.".format(device)) 18 | 19 | batch_size = 16 20 | epochs = 5 21 | 22 | data_transform = { 23 | "train": transforms.Compose([transforms.RandomResizedCrop(224), 24 | transforms.RandomHorizontalFlip(), 25 | transforms.ToTensor(), 26 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]), 27 | "val": transforms.Compose([transforms.Resize(256), 28 | transforms.CenterCrop(224), 29 | transforms.ToTensor(), 30 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])} 31 | 32 | 33 | 34 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../../")) # get data root path 35 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 36 | print(image_path) 37 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 38 | train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), 39 | transform=data_transform["train"]) 40 | train_num = len(train_dataset) 41 | print(train_num) 42 | 43 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 44 | print('Using {} dataloader workers every process'.format(nw)) 45 | 46 | train_loader = torch.utils.data.DataLoader(train_dataset, 47 | batch_size=batch_size, shuffle=True, 48 | num_workers=nw) 49 | 50 | validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), 51 | transform=data_transform["val"]) 52 | val_num = len(validate_dataset) 53 | validate_loader = torch.utils.data.DataLoader(validate_dataset, 54 | batch_size=batch_size, shuffle=False, 55 | num_workers=nw) 56 | 57 | print("using {} images for training, {} images for validation.".format(train_num, 58 | val_num)) 59 | 60 | # create model 61 | net = mobilenet_v3_small(num_classes=5) 62 | 63 | # define loss function 64 | loss_function = nn.CrossEntropyLoss() 65 | 66 | # construct an optimizer 67 | params = [p for p in net.parameters() if p.requires_grad] 68 | optimizer = optim.Adam(params, lr=0.0001) 69 | 70 | best_acc = 0.0 71 | save_path = './MobileNetV3_small_seq.pth' 72 | train_steps = len(train_loader) 73 | for epoch in range(epochs): 74 | # train 75 | net.train() 76 | running_loss = 0.0 77 | train_bar = tqdm(train_loader, file=sys.stdout) 78 | for step, data in enumerate(train_bar): 79 | images, labels = data 80 | optimizer.zero_grad() 81 | logits = net(images) 82 | loss = loss_function(logits, labels) 83 | loss.backward() 84 | optimizer.step() 85 | 86 | # print statistics 87 | running_loss += loss.item() 88 | 89 | train_bar.desc = "train epoch[{}/{}] loss:{:.4f}".format(epoch + 1, 90 | epochs, 91 | loss) 92 | 93 | # validate 94 | net.eval() 95 | acc = 0.0 # accumulate accurate number / epoch 96 | with torch.no_grad(): 97 | val_bar = tqdm(validate_loader, file=sys.stdout) 98 | for val_data in val_bar: 99 | val_images, val_labels = val_data 100 | outputs = net(val_images) 101 | # loss = loss_function(outputs, test_labels) 102 | predict_y = torch.max(outputs, dim=1)[1] 103 | acc += torch.eq(predict_y, val_labels).sum().item() 104 | 105 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 106 | epochs) 107 | val_accurate = acc / val_num 108 | print('[epoch %d] train_loss: %.4f val_accuracy: %.4f' % 109 | (epoch + 1, running_loss / train_steps, val_accurate)) 110 | 111 | if val_accurate > best_acc: 112 | best_acc = val_accurate 113 | torch.save(net.state_dict(), save_path) 114 | 115 | print('Finished Training') 116 | 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /DWParNet/par_DWconv.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParDW(nn.Module): 16 | def __init__(self, num_classes=1000, init_weights=False): 17 | super(ParDW, self).__init__() 18 | 19 | self.branch1 = nn.Sequential( 20 | ConvBNReLU(3, 64, kernel_size=1), 21 | ConvBNReLU(64, 64, stride=2, groups=64) 22 | ) 23 | 24 | self.branch2 = nn.Sequential( 25 | ConvBNReLU(3, 128, kernel_size=1), 26 | ConvBNReLU(128, 128, groups=128), 27 | ConvBN(128, 256, kernel_size=1), 28 | ConvBNReLU(256, 256, stride=2, groups=256) 29 | ) 30 | 31 | self.branch3 = nn.Sequential( 32 | ConvBNReLU(3, 32, kernel_size=1), 33 | ConvBNReLU(32, 32, groups=32), 34 | ConvBN(32, 96, kernel_size=1), 35 | ConvBNReLU(96, 96, stride=2, groups=96), 36 | ConvBN(96, 192, kernel_size=1), 37 | ConvBNReLU(192, 192, groups=192), 38 | ) 39 | 40 | self.conv = ConvBN(512, 512) 41 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 42 | self.dropout = nn.Dropout(0.5) 43 | self.fc = nn.Linear(512, num_classes) 44 | if init_weights: 45 | self._initialize_weights() 46 | 47 | def forward(self, x): 48 | branch1 = self.branch1(x) 49 | branch2 = self.branch2(x) 50 | branch3 = self.branch3(x) 51 | 52 | outputs = [branch1, branch2, branch3] 53 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 54 | x = self.conv(x) 55 | x = self.avgpool(x) 56 | x = torch.flatten(x, 1) 57 | x = self.dropout(x) 58 | x = self.fc(x) 59 | return x 60 | 61 | def _initialize_weights(self): 62 | for m in self.modules(): 63 | if isinstance(m, nn.Conv2d): 64 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 65 | if m.bias is not None: 66 | nn.init.constant_(m.bias, 0) 67 | elif isinstance(m, nn.Linear): 68 | nn.init.normal_(m.weight, 0, 0.01) 69 | nn.init.constant_(m.bias, 0) 70 | 71 | 72 | class ConvBN(nn.Sequential): 73 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 74 | # group如果等于输入特征模型的深度,则为DW卷积 75 | padding = (kernel_size - 1) // 2 76 | super(ConvBN, self).__init__( 77 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 78 | nn.BatchNorm2d(out_channel) 79 | ) 80 | 81 | -------------------------------------------------------------------------------- /DWParNet/par_DWconv2.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParDW(nn.Module): 16 | def __init__(self, num_classes=1000, init_weights=False): 17 | super(ParDW, self).__init__() 18 | 19 | self.branch1 = nn.Sequential( 20 | ConvBNReLU(3, 64, kernel_size=1), 21 | ConvBNReLU(64, 64, stride=8, groups=64) 22 | ) 23 | 24 | self.branch2 = nn.Sequential( 25 | ConvBNReLU(3, 128, kernel_size=1), 26 | ConvBNReLU(128, 128, stride=4, groups=128), 27 | ConvBN(128, 256, kernel_size=1), 28 | ConvBNReLU(256, 256, stride=2, groups=256) 29 | ) 30 | 31 | self.branch3 = nn.Sequential( 32 | ConvBNReLU(3, 32, kernel_size=1), 33 | ConvBNReLU(32, 32, 34 | stride=2, groups=32), 35 | ConvBN(32, 96, kernel_size=1), 36 | ConvBNReLU(96, 96, stride=2, groups=96), 37 | ConvBN(96, 192, kernel_size=1), 38 | ConvBNReLU(192, 192, stride=2, groups=192) 39 | ) 40 | 41 | self.conv = ConvBN(512, 512) 42 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 43 | self.dropout = nn.Dropout(0.5) 44 | self.fc = nn.Linear(512, num_classes) 45 | if init_weights: 46 | self._initialize_weights() 47 | 48 | def forward(self, x): 49 | branch1 = self.branch1(x) 50 | branch2 = self.branch2(x) 51 | branch3 = self.branch3(x) 52 | 53 | outputs = [branch1, branch2, branch3] 54 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 55 | x = self.conv(x) 56 | x = self.avgpool(x) 57 | x = torch.flatten(x, 1) 58 | x = self.dropout(x) 59 | x = self.fc(x) 60 | return x 61 | 62 | def _initialize_weights(self): 63 | for m in self.modules(): 64 | if isinstance(m, nn.Conv2d): 65 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 66 | if m.bias is not None: 67 | nn.init.constant_(m.bias, 0) 68 | elif isinstance(m, nn.Linear): 69 | nn.init.normal_(m.weight, 0, 0.01) 70 | nn.init.constant_(m.bias, 0) 71 | 72 | 73 | class ConvBN(nn.Sequential): 74 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 75 | # group如果等于输入特征模型的深度,则为DW卷积 76 | padding = (kernel_size - 1) // 2 77 | super(ConvBN, self).__init__( 78 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 79 | nn.BatchNorm2d(out_channel) 80 | ) 81 | 82 | -------------------------------------------------------------------------------- /DWParNet/par_DWconv3.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParDW(nn.Module): 16 | def __init__(self, num_classes=1000, init_weights=False): 17 | super(ParDW, self).__init__() 18 | 19 | # self.branch1 = nn.Sequential( 20 | # ConvBNReLU(3, 64, kernel_size=1), 21 | # ConvBNReLU(64, 64, stride=8, groups=64) 22 | # ) 23 | 24 | self.branch2 = nn.Sequential( 25 | ConvBNReLU(3, 128, kernel_size=1), 26 | ConvBNReLU(128, 128, stride=4, groups=128), 27 | ConvBN(128, 256, kernel_size=1), 28 | ConvBNReLU(256, 256, stride=2, groups=256) 29 | ) 30 | 31 | self.branch3 = nn.Sequential( 32 | ConvBNReLU(3, 32, kernel_size=1), 33 | ConvBNReLU(32, 32, stride=2, groups=32), 34 | ConvBN(32, 96, kernel_size=1), 35 | ConvBNReLU(96, 96, stride=2, groups=96), 36 | ConvBN(96, 192, kernel_size=1), 37 | ConvBNReLU(192, 192, stride=2, groups=192) 38 | ) 39 | 40 | self.conv = ConvBN(448, 448) 41 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 42 | self.dropout = nn.Dropout(0.5) 43 | self.fc = nn.Linear(448, num_classes) 44 | if init_weights: 45 | self._initialize_weights() 46 | 47 | def forward(self, x): 48 | # branch1 = self.branch1(x) 49 | branch2 = self.branch2(x) 50 | branch3 = self.branch3(x) 51 | 52 | outputs = [branch2, branch3] 53 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 54 | x = self.conv(x) 55 | x = self.avgpool(x) 56 | x = torch.flatten(x, 1) 57 | x = self.dropout(x) 58 | x = self.fc(x) 59 | return x 60 | 61 | def _initialize_weights(self): 62 | for m in self.modules(): 63 | if isinstance(m, nn.Conv2d): 64 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 65 | if m.bias is not None: 66 | nn.init.constant_(m.bias, 0) 67 | elif isinstance(m, nn.Linear): 68 | nn.init.normal_(m.weight, 0, 0.01) 69 | nn.init.constant_(m.bias, 0) 70 | 71 | 72 | class ConvBN(nn.Sequential): 73 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 74 | # group如果等于输入特征模型的深度,则为DW卷积 75 | padding = (kernel_size - 1) // 2 76 | super(ConvBN, self).__init__( 77 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 78 | nn.BatchNorm2d(out_channel) 79 | ) 80 | 81 | -------------------------------------------------------------------------------- /DWParNet/par_DWconv4.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParDW(nn.Module): 16 | def __init__(self, num_classes=1000, init_weights=False): 17 | super(ParDW, self).__init__() 18 | 19 | # self.branch1 = nn.Sequential( 20 | # ConvBNReLU(3, 64, kernel_size=1), 21 | # ConvBNReLU(64, 64, stride=8, groups=64) 22 | # ) 23 | 24 | # self.branch2 = nn.Sequential( 25 | # ConvBNReLU(3, 128, kernel_size=1), 26 | # ConvBNReLU(128, 128, stride=4, groups=128), 27 | # ConvBN(128, 256, kernel_size=1), 28 | # ConvBNReLU(256, 256, stride=2, groups=256) 29 | # ) 30 | 31 | self.branch3 = nn.Sequential( 32 | ConvBNReLU(3, 32, kernel_size=1), 33 | ConvBNReLU(32, 32, stride=2, groups=32), 34 | ConvBN(32, 96, kernel_size=1), 35 | ConvBNReLU(96, 96, stride=2, groups=96), 36 | ConvBN(96, 192, kernel_size=1), 37 | ConvBNReLU(192, 192, stride=2, groups=192) 38 | ) 39 | 40 | self.conv = ConvBN(192, 192) 41 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 42 | self.dropout = nn.Dropout(0.5) 43 | self.fc = nn.Linear(192, num_classes) 44 | if init_weights: 45 | self._initialize_weights() 46 | 47 | def forward(self, x): 48 | # branch1 = self.branch1(x) 49 | # branch2 = self.branch2(x) 50 | branch3 = self.branch3(x) 51 | 52 | # outputs = [branch2, branch3] 53 | # x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 54 | x = self.conv(branch3) 55 | x = self.avgpool(x) 56 | x = torch.flatten(x, 1) 57 | x = self.dropout(x) 58 | x = self.fc(x) 59 | return x 60 | 61 | def _initialize_weights(self): 62 | for m in self.modules(): 63 | if isinstance(m, nn.Conv2d): 64 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 65 | if m.bias is not None: 66 | nn.init.constant_(m.bias, 0) 67 | elif isinstance(m, nn.Linear): 68 | nn.init.normal_(m.weight, 0, 0.01) 69 | nn.init.constant_(m.bias, 0) 70 | 71 | 72 | class ConvBN(nn.Sequential): 73 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 74 | # group如果等于输入特征模型的深度,则为DW卷积 75 | padding = (kernel_size - 1) // 2 76 | super(ConvBN, self).__init__( 77 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 78 | nn.BatchNorm2d(out_channel) 79 | ) 80 | 81 | -------------------------------------------------------------------------------- /DWParNet/predict.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | from torchvision import transforms, datasets 7 | from tqdm import tqdm 8 | import matplotlib.pyplot as plt 9 | import torchmetrics 10 | from torchmetrics.classification import MulticlassConfusionMatrix 11 | from torchsummary import summary 12 | 13 | from par_DWconv2 import ParDW 14 | 15 | # Test_Dir = '../data_sequential_img/test/' 16 | 17 | 18 | def main(): 19 | batch_size = 32 20 | num_classes = 5 21 | 22 | transform = transforms.Compose([transforms.ToTensor(), 23 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 24 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 25 | # transforms.RandomHorizontalFlip(), 26 | # transforms.ToTensor(), 27 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 28 | 29 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../")) # get data root path 30 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") 31 | print(image_path) 32 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 33 | 34 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 35 | print('Using {} dataloader workers every process'.format(nw)) 36 | 37 | predict_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), 38 | transform=transform) 39 | predict_num = len(predict_dataset) 40 | predict_loader = torch.utils.data.DataLoader(predict_dataset, 41 | batch_size=batch_size, shuffle=True, 42 | num_workers=nw) 43 | print(predict_loader) 44 | net = ParDW(num_classes=num_classes) 45 | # load model weights 46 | model_weight_path = "weight_res_seq/ParDWconv2.pth" 47 | net.load_state_dict(torch.load(model_weight_path)) 48 | net.eval() 49 | # acc = 0.0 # accumulate accurate number / epoch 50 | test_acc = torchmetrics.Accuracy() 51 | test_recall = torchmetrics.Recall(average='none', num_classes=num_classes) 52 | test_precision = torchmetrics.Precision(average='none', num_classes=num_classes) 53 | test_f1 = torchmetrics.F1Score(average='none', num_classes=num_classes) 54 | test_auc = torchmetrics.AUROC(average="macro", num_classes=num_classes) 55 | test_conf = MulticlassConfusionMatrix(num_classes=5) 56 | summary(net, (3, 9, 9)) 57 | 58 | with torch.no_grad(): 59 | predict_bar = tqdm(predict_loader, file=sys.stdout) 60 | for predict_data in predict_bar: 61 | predict_images, predict_labels = predict_data 62 | outputs = net(predict_images) 63 | # loss = loss_function(outputs, test_labels) 64 | predict_y = torch.max(outputs, dim=1)[1] 65 | # acc += torch.eq(predict_y, predict_labels).sum().item() 66 | test_acc(predict_y, predict_labels) 67 | test_auc.update(outputs, predict_labels) 68 | test_recall(predict_y, predict_labels) 69 | test_precision(predict_y, predict_labels) 70 | test_f1(predict_y, predict_labels) 71 | test_conf.update(predict_y, predict_labels) 72 | 73 | # predict_accurate = acc / predict_num 74 | total_acc = test_acc.compute() 75 | total_recall = test_recall.compute() 76 | total_precision = test_precision.compute() 77 | total_auc = test_auc.compute() 78 | total_f1 = test_f1.compute() 79 | total_conf = test_conf.compute() 80 | 81 | # print('predict_accuracy: %.4f' %(predict_accurate)) 82 | 83 | print("torch metrics acc:", total_acc) 84 | print("recall of every test dataset class: ", total_recall) 85 | print("precision of every test dataset class: ", total_precision) 86 | print("F1-score of every test dataset class: ", total_f1) 87 | print("auc:", total_auc.item()) 88 | print("confusion metrics:", total_conf) 89 | 90 | # 清空计算对象 91 | test_precision.reset() 92 | test_acc.reset() 93 | test_recall.reset() 94 | test_auc.reset() 95 | 96 | 97 | 98 | 99 | 100 | 101 | if __name__ == '__main__': 102 | main() 103 | -------------------------------------------------------------------------------- /DWParNet/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.optim as optim 8 | from torchvision import transforms, datasets 9 | from tqdm import tqdm 10 | from par_DWconv2 import ParDW 11 | 12 | 13 | def main(): 14 | batch_size = 32 15 | epochs = 5 16 | num_classes = 5 17 | 18 | transform = transforms.Compose([transforms.ToTensor(), 19 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 20 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 21 | # transforms.RandomHorizontalFlip(), 22 | # transforms.ToTensor(), 23 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 24 | 25 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../")) # get data root path 26 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 27 | print(image_path) 28 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 29 | train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), 30 | transform=transform) 31 | train_num = len(train_dataset) 32 | print(train_num) 33 | 34 | 35 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 36 | print('Using {} dataloader workers every process'.format(nw)) 37 | 38 | train_loader = torch.utils.data.DataLoader(train_dataset, 39 | batch_size=batch_size, shuffle=True, 40 | num_workers=nw) 41 | 42 | validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), 43 | transform=transform) 44 | val_num = len(validate_dataset) 45 | validate_loader = torch.utils.data.DataLoader(validate_dataset, 46 | batch_size=batch_size, shuffle=False, 47 | num_workers=nw) 48 | 49 | print("using {} images for training, {} images for validation.".format(train_num, 50 | val_num)) 51 | 52 | # create model 53 | net = ParDW(num_classes=num_classes, init_weights=True) 54 | 55 | 56 | # define loss function 57 | loss_function = nn.CrossEntropyLoss() 58 | 59 | # construct an optimizer 60 | params = [p for p in net.parameters() if p.requires_grad] 61 | optimizer = optim.Adam(params, lr=0.0001) 62 | 63 | best_acc = 0.0 64 | save_path = 'weight_res_seq/ParDWconv2.pth' 65 | train_steps = len(train_loader) 66 | for epoch in range(epochs): 67 | # train 68 | net.train() 69 | running_loss = 0.0 70 | train_bar = tqdm(train_loader, file=sys.stdout) 71 | for step, data in enumerate(train_bar): 72 | images, labels = data 73 | optimizer.zero_grad() 74 | logits = net(images) 75 | loss = loss_function(logits, labels) 76 | loss.backward() 77 | optimizer.step() 78 | 79 | # print statistics 80 | running_loss += loss.item() 81 | 82 | train_bar.desc = "train epoch[{}/{}] loss:{:.4f}".format(epoch + 1, 83 | epochs, 84 | loss) 85 | 86 | # validate 87 | net.eval() 88 | acc = 0.0 # accumulate accurate number / epoch 89 | with torch.no_grad(): 90 | val_bar = tqdm(validate_loader, file=sys.stdout) 91 | for val_data in val_bar: 92 | val_images, val_labels = val_data 93 | outputs = net(val_images) 94 | # loss = loss_function(outputs, test_labels) 95 | predict_y = torch.max(outputs, dim=1)[1] 96 | acc += torch.eq(predict_y, val_labels).sum().item() 97 | 98 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 99 | epochs) 100 | val_accurate = acc / val_num 101 | print('[epoch %d] train_loss: %.4f val_accuracy: %.4f' % 102 | (epoch + 1, running_loss / train_steps, val_accurate)) 103 | 104 | if val_accurate > best_acc: 105 | best_acc = val_accurate 106 | torch.save(net.state_dict(), save_path) 107 | 108 | print('Finished Training') 109 | 110 | 111 | 112 | if __name__ == '__main__': 113 | main() 114 | -------------------------------------------------------------------------------- /DataProcessing/PreprocessedData.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/DataProcessing/PreprocessedData.rar -------------------------------------------------------------------------------- /DataProcessing/data_process.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import warnings 3 | warnings.filterwarnings("ignore") 4 | 5 | # change the file name to reprocess different types of attack data 6 | file_path = '../Car-Hacking Dataset/Spoofing_the_RPM_gauge_dataset.csv' 7 | 8 | # Read dataset 9 | df = pd.read_csv(file_path, header=None, usecols=[1, 3, 4, 5, 6, 7, 8, 9, 10, 11]) 10 | 11 | # Add fieldnames 12 | df.columns = ['CAN_ID', 'Data[0]', 'Data[1]', 'Data[2]', 'Data[3]', 13 | 'Data[4]', 'Data[5]', 'Data[6]', 'Data[7]', 'Label'] 14 | # print(df.Label.value_counts()) 15 | 16 | # Data cleaning 17 | rule = {'CAN_ID': '0000', 'Data[0]': '00', 'Data[1]': '00', 'Data[2]': '00', 'Data[3]': '00', 18 | 'Data[4]': '00', 'Data[5]': '00', 'Data[6]': '00', 'Data[7]': '00', 'Label': 'R'} 19 | df.fillna(rule, inplace=True) 20 | df.loc[df["Data[1]"].isin(['R']), "Data[1]"] = '00' 21 | df.loc[df["Data[2]"].isin(['R']), "Data[2]"] = '00' 22 | df.loc[df["Data[3]"].isin(['R']), "Data[3]"] = '00' 23 | df.loc[df["Data[4]"].isin(['R']), "Data[4]"] = '00' 24 | df.loc[df["Data[5]"].isin(['R']), "Data[5]"] = '00' 25 | df.loc[df["Data[6]"].isin(['R']), "Data[6]"] = '00' 26 | df.loc[df["Data[7]"].isin(['R']), "Data[7]"] = '00' 27 | 28 | 29 | # Convert hex to dec 30 | df.CAN_ID = df['CAN_ID'].apply(int, base=16) 31 | df['Data[0]'] = df['Data[0]'].apply(int, base=16) 32 | df['Data[1]'] = df['Data[1]'].apply(int, base=16) 33 | df['Data[2]'] = df['Data[2]'].apply(int, base=16) 34 | df['Data[3]'] = df['Data[3]'].apply(int, base=16) 35 | df['Data[4]'] = df['Data[4]'].apply(int, base=16) 36 | df['Data[5]'] = df['Data[5]'].apply(int, base=16) 37 | df['Data[6]'] = df['Data[6]'].apply(int, base=16) 38 | df['Data[7]'] = df['Data[7]'].apply(int, base=16) 39 | 40 | # Save to new file 41 | df.to_csv('./DataProcess/RPM_dataset.csv', index=False) 42 | 43 | -------------------------------------------------------------------------------- /DataProcessing/data_process_normal.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import warnings 3 | warnings.filterwarnings("ignore") 4 | 5 | file_path = '../../Car-Hacking Dataset/normal.csv' 6 | 7 | # Read dataset 8 | df = pd.read_csv(file_path, header=None, usecols=[2, 4, 5, 6, 7, 8, 9, 10, 11]) 9 | 10 | # Add fieldnames 11 | df.columns = ['CAN_ID', 'Data[0]', 'Data[1]', 'Data[2]', 'Data[3]', 12 | 'Data[4]', 'Data[5]', 'Data[6]', 'Data[7]'] 13 | # print(df.Label.value_counts()) 14 | 15 | # Data cleaning 16 | rule = {'CAN_ID': '0000', 'Data[0]': '00', 'Data[1]': '00', 'Data[2]': '00', 'Data[3]': '00', 17 | 'Data[4]': '00', 'Data[5]': '00', 'Data[6]': '00', 'Data[7]': '00'} 18 | df.fillna(rule, inplace=True) 19 | 20 | 21 | # Add label column 22 | df.insert(loc=len(df.columns), column='Label', value='R') 23 | 24 | # Save to new file 25 | df.to_csv('./normal_dataset.csv', index=False) 26 | 27 | -------------------------------------------------------------------------------- /DataProcessing/img_generator_seq.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import os 4 | from sklearn.preprocessing import QuantileTransformer 5 | from PIL import Image 6 | import warnings 7 | warnings.filterwarnings("ignore") 8 | 9 | # change the path and filename to generate different types of attack (or normal) image data 10 | file_path = 'RPM_dataset.csv' 11 | 12 | # Read dataset 13 | df = pd.read_csv(file_path) 14 | 15 | # Transform all features into the scale of [0,1] 16 | numeric_features = df.dtypes[df.dtypes != 'object'].index 17 | scaler = QuantileTransformer() 18 | df[numeric_features] = scaler.fit_transform(df[numeric_features]) 19 | 20 | # Multiply the feature values by 255 to transform them into the scale of [0,255] 21 | df[numeric_features] = df[numeric_features].apply(lambda x: (x*255)) 22 | 23 | 24 | # Generate 9*9 color images 25 | 26 | count = 0 27 | ims = [] 28 | label = 0 29 | count_attack = 0 30 | count_normal = 0 31 | 32 | # set the directory for generated image data. 1 to 4 represent data from four files with different types of attack, 0 represents normal image data. 33 | image_path_attack = "../data_sequential_img/train/4/" 34 | image_path_normal = "../data_sequential_img/train/0_4/" 35 | os.makedirs(image_path_attack) 36 | os.makedirs(image_path_normal) 37 | 38 | 39 | for i in range(0, len(df)): 40 | count = count+1 41 | if count < 27: 42 | if df.loc[i, 'Label'] == 'T': 43 | label = 1 44 | im = df.iloc[i].drop(['Label']).values 45 | ims = np.append(ims, im) 46 | else: 47 | if df.loc[i, 'Label'] == 'T': 48 | label = 1 49 | im = df.iloc[i].drop(['Label']).values 50 | ims = np.append(ims, im) 51 | ims = np.array(ims).reshape(9, 9, 3) 52 | array = np.array(ims, dtype=np.uint8) 53 | new_image = Image.fromarray(array) 54 | if label == 1: 55 | count_attack += 1 56 | new_image.save(image_path_attack+str(count_attack)+'.png') 57 | else: 58 | count_normal += 1 59 | new_image.save(image_path_normal + str(count_normal) + '.png') 60 | count = 0 61 | ims = [] 62 | label = 0 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /DataProcessing/split_testset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import warnings 4 | warnings.filterwarnings("ignore") 5 | 6 | # Create folders to store images 7 | Train_Dir = '../data_sequential_img/train/' 8 | Val_Dir = '../data_sequential_img/val/' 9 | Test_Dir = '../data_sequential_img/test/' 10 | 11 | def mymovefile(srcfile, dstfile): 12 | if not os.path.isfile(srcfile): 13 | print("%s not exist!" %(srcfile)) 14 | else: 15 | fpath, fname = os.path.split(dstfile) 16 | if not os.path.exists(fpath): 17 | os.makedirs(fpath) 18 | shutil.move(srcfile, dstfile) 19 | #print ("move %s -> %s"%(srcfile,dstfile)) 20 | 21 | # for subdir in os.listdir(Train_Dir): 22 | for subdir in os.listdir(Val_Dir): 23 | # print(subdir) 24 | if subdir == '.DS_Store': 25 | pass 26 | else: 27 | print(subdir) 28 | subimgs = [] 29 | sum = len(os.listdir(os.path.join(Train_Dir, subdir))) 30 | sum_v = len(os.listdir(os.path.join(Val_Dir, subdir))) 31 | # Numbers = 3 * sum // 10 # size of test&val set (30%) 32 | Numbers_v = sum_v // 3 # size of test set (1/3 of val) 33 | # start = sum - Numbers 34 | start_v = sum + sum_v - Numbers_v 35 | 36 | # for i in range(start, sum): 37 | for i in range(start_v, sum + sum_v): 38 | filename = str(i) + '.png' 39 | # filepath = os.path.join(Train_Dir, subdir, filename) 40 | filepath = os.path.join(Val_Dir, subdir, filename) 41 | subimgs.append(filepath) 42 | 43 | for img in subimgs[:]: 44 | # dest_path = img.replace(Train_Dir, Val_Dir) 45 | dest_path = img.replace(Val_Dir, Test_Dir) 46 | mymovefile(img, dest_path) 47 | 48 | 49 | # print('Finish creating val set') 50 | print('Finish creating test set') 51 | 52 | 53 | -------------------------------------------------------------------------------- /DataProcessing/split_trainset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import warnings 4 | warnings.filterwarnings("ignore") 5 | 6 | # Create folders to store images 7 | Train_Dir = '../data_sequential_img/train/' 8 | Val_Dir = '../data_sequential_img/val/' 9 | Test_Dir = '../data_sequential_img/test/' 10 | 11 | def mymovefile(srcfile, dstfile): 12 | if not os.path.isfile(srcfile): 13 | print("%s not exist!" %(srcfile)) 14 | else: 15 | fpath, fname = os.path.split(dstfile) 16 | if not os.path.exists(fpath): 17 | os.makedirs(fpath) 18 | shutil.move(srcfile, dstfile) 19 | #print ("move %s -> %s"%(srcfile,dstfile)) 20 | 21 | for subdir in os.listdir(Train_Dir): 22 | # print(subdir) 23 | if subdir == '.DS_Store': 24 | pass 25 | else: 26 | print(subdir) 27 | subimgs = [] 28 | sum = len(os.listdir(os.path.join(Train_Dir, subdir))) 29 | Numbers = 3 * sum // 10 # size of test&val set (30%) 30 | start = sum - Numbers 31 | 32 | for i in range(start, sum): 33 | filename = str(i) + '.png' 34 | filepath = os.path.join(Train_Dir, subdir, filename) 35 | subimgs.append(filepath) 36 | 37 | for img in subimgs[:]: 38 | dest_path = img.replace(Train_Dir, Val_Dir) 39 | mymovefile(img, dest_path) 40 | 41 | 42 | print('Finish creating val and test set') 43 | 44 | 45 | -------------------------------------------------------------------------------- /MemoryTest/memoryTest_LSTM.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | from torchsummary import summary 4 | 5 | 6 | 7 | class ConvBNReLU(nn.Sequential): 8 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 9 | # group如果等于输入特征模型的深度,则为DW卷积 10 | padding = (kernel_size - 1) // 2 11 | super(ConvBNReLU, self).__init__( 12 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 13 | nn.BatchNorm2d(out_channel), 14 | nn.ReLU6(inplace=True) 15 | ) 16 | 17 | class ParLSTM(nn.Module): 18 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 19 | super(ParLSTM, self).__init__() 20 | 21 | self.hidden_size = hidden_size 22 | self.num_layers = num_layers 23 | self.input_size = input_size 24 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 25 | self.fc = nn.Linear(hidden_size, num_classes) 26 | 27 | 28 | if init_weights: 29 | self._initialize_weights() 30 | 31 | def forward(self, x): 32 | x = x.reshape(-1, 27, self.input_size) 33 | # Set initial hidden and cell states 34 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 35 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 36 | 37 | # Forward propagate LSTM 38 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 39 | 40 | # Decode the hidden state of the last time step 41 | out = self.fc(out[:, -1, :]) 42 | return out 43 | 44 | def _initialize_weights(self): 45 | for m in self.modules(): 46 | if isinstance(m, nn.Conv2d): 47 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 48 | if m.bias is not None: 49 | nn.init.constant_(m.bias, 0) 50 | elif isinstance(m, nn.Linear): 51 | nn.init.normal_(m.weight, 0, 0.01) 52 | nn.init.constant_(m.bias, 0) 53 | 54 | 55 | class ConvBN(nn.Sequential): 56 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 57 | # group如果等于输入特征模型的深度,则为DW卷积 58 | padding = (kernel_size - 1) // 2 59 | super(ConvBN, self).__init__( 60 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 61 | nn.BatchNorm2d(out_channel) 62 | ) 63 | 64 | if __name__ == '__main__': 65 | batch_size = 32 66 | num_classes = 5 67 | input_size = 9 68 | hidden_size = 32 69 | num_layers = 2 70 | net = ParLSTM(input_size=input_size, hidden_size=hidden_size, 71 | num_layers=num_layers, num_classes=num_classes, init_weights=False) 72 | summary(net, (3, 9, 9)) 73 | -------------------------------------------------------------------------------- /MemoryTest/memoryTest_convAggregation.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | from torchsummary import summary 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParDW(nn.Module): 16 | def __init__(self, num_classes=1000, init_weights=False): 17 | super(ParDW, self).__init__() 18 | 19 | # self.branch1 = nn.Sequential( 20 | # ConvBNReLU(3, 64, kernel_size=1), 21 | # ConvBNReLU(64, 64, stride=8, groups=64) 22 | # ) 23 | 24 | # self.branch2 = nn.Sequential( 25 | # ConvBNReLU(3, 128, kernel_size=1), 26 | # ConvBNReLU(128, 128, stride=4, groups=128), 27 | # ConvBN(128, 256, kernel_size=1), 28 | # ConvBNReLU(256, 256, stride=2, groups=256) 29 | # ) 30 | # 31 | # self.branch3 = nn.Sequential( 32 | # ConvBNReLU(3, 32, kernel_size=1), 33 | # ConvBNReLU(32, 32, 34 | # stride=2, groups=32), 35 | # ConvBN(32, 96, kernel_size=1), 36 | # ConvBNReLU(96, 96, stride=2, groups=96), 37 | # ConvBN(96, 192, kernel_size=1), 38 | # ConvBNReLU(192, 192, stride=2, groups=192) 39 | # ) 40 | 41 | self.conv = ConvBN(512, 192) 42 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 43 | self.dropout = nn.Dropout(0.5) 44 | self.fc = nn.Linear(192, num_classes) 45 | 46 | if init_weights: 47 | self._initialize_weights() 48 | 49 | def forward(self, x): 50 | # branch1 = self.branch1(x) 51 | # branch2 = self.branch2(x) 52 | # branch3 = self.branch3(x) 53 | # 54 | # outputs = [branch1, branch2, branch3] 55 | # x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 56 | x = self.conv(x) 57 | x = self.avgpool(x) 58 | x = torch.flatten(x, 1) 59 | x = self.dropout(x) 60 | x = self.fc(x) 61 | return x 62 | 63 | def _initialize_weights(self): 64 | for m in self.modules(): 65 | if isinstance(m, nn.Conv2d): 66 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 67 | if m.bias is not None: 68 | nn.init.constant_(m.bias, 0) 69 | elif isinstance(m, nn.Linear): 70 | nn.init.normal_(m.weight, 0, 0.01) 71 | nn.init.constant_(m.bias, 0) 72 | 73 | 74 | class ConvBN(nn.Sequential): 75 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 76 | # group如果等于输入特征模型的深度,则为DW卷积 77 | padding = (kernel_size - 1) // 2 78 | super(ConvBN, self).__init__( 79 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 80 | nn.BatchNorm2d(out_channel) 81 | ) 82 | 83 | if __name__ == '__main__': 84 | num_classes = 5 85 | net = ParDW(num_classes=num_classes) 86 | summary(net, (512, 2, 2)) 87 | -------------------------------------------------------------------------------- /MemoryTest/memoryTest_convBranch1.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | from torchsummary import summary 4 | 5 | 6 | class ConvBNReLU(nn.Sequential): 7 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 8 | # group如果等于输入特征模型的深度,则为DW卷积 9 | padding = (kernel_size - 1) // 2 10 | super(ConvBNReLU, self).__init__( 11 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 12 | nn.BatchNorm2d(out_channel), 13 | nn.ReLU6(inplace=True) 14 | ) 15 | 16 | class ParDW(nn.Module): 17 | def __init__(self, num_classes=1000, init_weights=False): 18 | super(ParDW, self).__init__() 19 | 20 | self.branch1 = nn.Sequential( 21 | ConvBNReLU(3, 64, kernel_size=1), 22 | ConvBNReLU(64, 64, stride=8, groups=64) 23 | ) 24 | 25 | # self.branch2 = nn.Sequential( 26 | # ConvBNReLU(3, 128, kernel_size=1), 27 | # ConvBNReLU(128, 128, stride=4, groups=128), 28 | # ConvBN(128, 256, kernel_size=1), 29 | # ConvBNReLU(256, 256, stride=2, groups=256) 30 | # ) 31 | # 32 | # self.branch3 = nn.Sequential( 33 | # ConvBNReLU(3, 32, kernel_size=1), 34 | # ConvBNReLU(32, 32, 35 | # stride=2, groups=32), 36 | # ConvBN(32, 96, kernel_size=1), 37 | # ConvBNReLU(96, 96, stride=2, groups=96), 38 | # ConvBN(96, 192, kernel_size=1), 39 | # ConvBNReLU(192, 192, stride=2, groups=192) 40 | # ) 41 | 42 | # self.conv = ConvBN(512, 512) 43 | # self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 44 | # self.dropout = nn.Dropout(0.5) 45 | # self.fc = nn.Linear(512, num_classes) 46 | 47 | if init_weights: 48 | self._initialize_weights() 49 | 50 | def forward(self, x): 51 | branch1 = self.branch1(x) 52 | # branch2 = self.branch2(x) 53 | # branch3 = self.branch3(x) 54 | # 55 | # outputs = [branch1, branch2, branch3] 56 | # x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 57 | # x = self.conv(x) 58 | # x = self.avgpool(x) 59 | # x = torch.flatten(x, 1) 60 | # x = self.dropout(x) 61 | # x = self.fc(x) 62 | return branch1 63 | 64 | def _initialize_weights(self): 65 | for m in self.modules(): 66 | if isinstance(m, nn.Conv2d): 67 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 68 | if m.bias is not None: 69 | nn.init.constant_(m.bias, 0) 70 | elif isinstance(m, nn.Linear): 71 | nn.init.normal_(m.weight, 0, 0.01) 72 | nn.init.constant_(m.bias, 0) 73 | 74 | 75 | # class ConvBN(nn.Sequential): 76 | # def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 77 | # # group如果等于输入特征模型的深度,则为DW卷积 78 | # padding = (kernel_size - 1) // 2 79 | # super(ConvBN, self).__init__( 80 | # nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 81 | # nn.BatchNorm2d(out_channel) 82 | # ) 83 | 84 | if __name__ == '__main__': 85 | num_classes = 5 86 | net = ParDW(num_classes=num_classes) 87 | summary(net, (3, 9, 9)) 88 | -------------------------------------------------------------------------------- /MemoryTest/memoryTest_convBranch2.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | from torchsummary import summary 4 | 5 | 6 | class ConvBNReLU(nn.Sequential): 7 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 8 | # group如果等于输入特征模型的深度,则为DW卷积 9 | padding = (kernel_size - 1) // 2 10 | super(ConvBNReLU, self).__init__( 11 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 12 | nn.BatchNorm2d(out_channel), 13 | nn.ReLU6(inplace=True) 14 | ) 15 | 16 | class ParDW(nn.Module): 17 | def __init__(self, num_classes=1000, init_weights=False): 18 | super(ParDW, self).__init__() 19 | 20 | # self.branch1 = nn.Sequential( 21 | # ConvBNReLU(3, 64, kernel_size=1), 22 | # ConvBNReLU(64, 64, stride=8, groups=64) 23 | # ) 24 | 25 | self.branch2 = nn.Sequential( 26 | ConvBNReLU(3, 128, kernel_size=1), 27 | ConvBNReLU(128, 128, stride=4, groups=128), 28 | ConvBN(128, 256, kernel_size=1), 29 | ConvBNReLU(256, 256, stride=2, groups=256) 30 | ) 31 | # 32 | # self.branch3 = nn.Sequential( 33 | # ConvBNReLU(3, 32, kernel_size=1), 34 | # ConvBNReLU(32, 32, 35 | # stride=2, groups=32), 36 | # ConvBN(32, 96, kernel_size=1), 37 | # ConvBNReLU(96, 96, stride=2, groups=96), 38 | # ConvBN(96, 192, kernel_size=1), 39 | # ConvBNReLU(192, 192, stride=2, groups=192) 40 | # ) 41 | 42 | # self.conv = ConvBN(512, 512) 43 | # self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 44 | # self.dropout = nn.Dropout(0.5) 45 | # self.fc = nn.Linear(512, num_classes) 46 | 47 | if init_weights: 48 | self._initialize_weights() 49 | 50 | def forward(self, x): 51 | # branch1 = self.branch1(x) 52 | branch2 = self.branch2(x) 53 | # branch3 = self.branch3(x) 54 | # 55 | # outputs = [branch1, branch2, branch3] 56 | # x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 57 | # x = self.conv(x) 58 | # x = self.avgpool(x) 59 | # x = torch.flatten(x, 1) 60 | # x = self.dropout(x) 61 | # x = self.fc(x) 62 | return branch2 63 | 64 | def _initialize_weights(self): 65 | for m in self.modules(): 66 | if isinstance(m, nn.Conv2d): 67 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 68 | if m.bias is not None: 69 | nn.init.constant_(m.bias, 0) 70 | elif isinstance(m, nn.Linear): 71 | nn.init.normal_(m.weight, 0, 0.01) 72 | nn.init.constant_(m.bias, 0) 73 | 74 | 75 | class ConvBN(nn.Sequential): 76 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 77 | # group如果等于输入特征模型的深度,则为DW卷积 78 | padding = (kernel_size - 1) // 2 79 | super(ConvBN, self).__init__( 80 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 81 | nn.BatchNorm2d(out_channel) 82 | ) 83 | 84 | if __name__ == '__main__': 85 | num_classes = 5 86 | net = ParDW(num_classes=num_classes) 87 | summary(net, (3, 9, 9)) 88 | -------------------------------------------------------------------------------- /MemoryTest/memoryTest_convBranch3.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | from torchsummary import summary 4 | 5 | 6 | class ConvBNReLU(nn.Sequential): 7 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 8 | # group如果等于输入特征模型的深度,则为DW卷积 9 | padding = (kernel_size - 1) // 2 10 | super(ConvBNReLU, self).__init__( 11 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 12 | nn.BatchNorm2d(out_channel), 13 | nn.ReLU6(inplace=True) 14 | ) 15 | 16 | class ParDW(nn.Module): 17 | def __init__(self, num_classes=1000, init_weights=False): 18 | super(ParDW, self).__init__() 19 | 20 | # self.branch1 = nn.Sequential( 21 | # ConvBNReLU(3, 64, kernel_size=1), 22 | # ConvBNReLU(64, 64, stride=8, groups=64) 23 | # ) 24 | 25 | # self.branch2 = nn.Sequential( 26 | # ConvBNReLU(3, 128, kernel_size=1), 27 | # ConvBNReLU(128, 128, stride=4, groups=128), 28 | # ConvBN(128, 256, kernel_size=1), 29 | # ConvBNReLU(256, 256, stride=2, groups=256) 30 | # ) 31 | # 32 | self.branch3 = nn.Sequential( 33 | ConvBNReLU(3, 32, kernel_size=1), 34 | ConvBNReLU(32, 32, 35 | stride=2, groups=32), 36 | ConvBN(32, 96, kernel_size=1), 37 | ConvBNReLU(96, 96, stride=2, groups=96), 38 | ConvBN(96, 192, kernel_size=1), 39 | ConvBNReLU(192, 192, stride=2, groups=192) 40 | ) 41 | 42 | # self.conv = ConvBN(512, 512) 43 | # self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 44 | # self.dropout = nn.Dropout(0.5) 45 | # self.fc = nn.Linear(512, num_classes) 46 | 47 | if init_weights: 48 | self._initialize_weights() 49 | 50 | def forward(self, x): 51 | # branch1 = self.branch1(x) 52 | # branch2 = self.branch2(x) 53 | branch3 = self.branch3(x) 54 | # 55 | # outputs = [branch1, branch2, branch3] 56 | # x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 57 | # x = self.conv(x) 58 | # x = self.avgpool(x) 59 | # x = torch.flatten(x, 1) 60 | # x = self.dropout(x) 61 | # x = self.fc(x) 62 | return branch3 63 | 64 | def _initialize_weights(self): 65 | for m in self.modules(): 66 | if isinstance(m, nn.Conv2d): 67 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 68 | if m.bias is not None: 69 | nn.init.constant_(m.bias, 0) 70 | elif isinstance(m, nn.Linear): 71 | nn.init.normal_(m.weight, 0, 0.01) 72 | nn.init.constant_(m.bias, 0) 73 | 74 | 75 | class ConvBN(nn.Sequential): 76 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 77 | # group如果等于输入特征模型的深度,则为DW卷积 78 | padding = (kernel_size - 1) // 2 79 | super(ConvBN, self).__init__( 80 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 81 | nn.BatchNorm2d(out_channel) 82 | ) 83 | 84 | if __name__ == '__main__': 85 | num_classes = 5 86 | net = ParDW(num_classes=num_classes) 87 | summary(net, (3, 9, 9)) 88 | -------------------------------------------------------------------------------- /OriginalDataset/DoS_Attack_dataset.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/OriginalDataset/DoS_Attack_dataset.rar -------------------------------------------------------------------------------- /OriginalDataset/Fuzzy_Attack_dataset.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/OriginalDataset/Fuzzy_Attack_dataset.rar -------------------------------------------------------------------------------- /OriginalDataset/Spoofing_the_RPM_gauge_dataset.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/OriginalDataset/Spoofing_the_RPM_gauge_dataset.rar -------------------------------------------------------------------------------- /OriginalDataset/Spoofing_the_drive_gear_dataset.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/OriginalDataset/Spoofing_the_drive_gear_dataset.rar -------------------------------------------------------------------------------- /OriginalDataset/normal.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangkai-tech23/LiPar/fd7f6d906f6cc37ddcc6ec65c9ffaab52a8f0369/OriginalDataset/normal.rar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiPar 2 | 3 | LiPar is a lightweight parallel learning model for practical in-vehicle network intrusion detection. LiPar has great detection performance, running efficiency, and lightweight model size, which can be well adapted to the in-vehicle environment practically and protect the in-vehicle CAN bus security. 4 | 5 | You can see the details in our paper **LiPar: A Lightweight Parallel Learning Model for Practical In-Vehicle Network Intrusion Detection**. ([arXiv:2311.08000v2](https://arxiv.org/abs/2311.08000v2)) 6 | 7 | ## The Dataset 8 | 9 | The dataset we used is [Car-Hacking Dataset](https://ocslab.hksecurity.net/Datasets/car-hacking-dataset) (you can fine the details of this dataset by the link). We also upload the data files in `./OriginalDataset/`. Due to the limitation of upload file size, we compressed each data file into a `.rar` file. You can get the original data by unzipping the files. 10 | 11 | ## The Data Processing 12 | 13 | The codes for data processing are uploaded in `./DataProcessing/`. Here are the steps for our data processing: 14 | 15 | 1. Data preprocessing and data cleaning: `data_process.py` is used to process attack datasets, including `DoS_Attack_dataset`, `Fuzzy_Attack_dataset`, `Spoofing_the_RPM_gauge_dataset` and `Spoofing_the_drie_gear_dataset`. `data_process_normal.py` is only used to process `normal` dataset. You can change the `file_path` and the new file name in `df.to_csv` function in the code to preprocess the datasets one by one. Then, it will generate five preprocessed data files respectively. The generated datasets can be found in the compressed file `./DataProcessing/PreprocessedData.rar`. 16 | 2. Image data generating: `img_generator_seq.py` is used to process one-dimensional data sequentially into RGB image data. For attack datasets, there are both attack messages and normal messages. So, if an image is composed entirely of normal messages, we will label it as normal image. Otherwise, we will label it as attack image. Therefore, each attack dataset can generate two sets of image. You can set different directory addresses in `image_path_attack` and `image_path_normal` to store the generated normal and attack images. For each set of new generated images, the images will be named from 1 to `n` (`n` is the total number of images in the set) by sequence. When you finish processing one data file, you can change the filename and path in `file_path` to process other data files. The files we used are the preprocessed data files obtained in the previous step. For normal dataset, certainly, the program will only generate one set of normal image. At the end, you will obtain 9 sets of images in different directories. 17 | 3. Dataset partitioning: The directory we used to store all images is `./data_sequential_img/train/`. Then, we need to divide the train set, validation set and the test set from all the image data. `split_trainset.py` is used to divide 30% of the total image data into validation and test set, the directory of which named `./data_sequential_img/val/`. Futhermore, `split_testset.py` is used to divide $\frac{1}{3}$ of the images in `val` set into test set named `./data_sequential_img/test/`. Finally, the ratio of images in the training set, validation set, and test set is `7:2:1`. Also, you can change the path and directory name to anything you want by modifying `Train_Dir`, `Val_Dir` and `Test_Dir` in the code. 18 | 19 | ## The Models Training and Testing 20 | 21 | For learning-based models, first of all, the model should be trained, and then we can obtain the optimal weights and parameters value in the model through training. Finally, the weights and parameters obtained are loaded into the model for testing, so as to verify the final detection performance of the model. 22 | 23 | Therefore, we construct two programs for each model, respectively for model training process and testing process. You can find files like `train.py`, `train_LSTM.py` or `train_CANet.py` are for model training process, and files like `predict.py`, `predict_LSTM.py` or `predict_CANet.py` are for model testing process. 24 | 25 | Take STParNet as an example, all of the files for model construction, model training and testing are stored under the same directory `./STParNet/`. Before training, you should import the model you want to train by code like `from par_DW_LSTM4 import ParDWLSTM`. Here, the `par_DW_LSTM4` is the name of python file which contructs the model and `ParDWLSTM` is the main class of the model. You should also set the path of input images. The `image_path` refers to the directory where all the images are located, the `root` in `train_dataset` refers to the directory of training set and the `root` in `validate_dataset` refers to the directory of validation set. Besides, `save_path` refers to the path for saving the file of optimal weights and parameters, which is in .pth format. 26 | 27 | Similarly, when testing models, we may need to change the data path used for testing at `image_path` and the path to load the trained model parameter file at `model_weight_path` in `predict.py`. 28 | 29 | ## The Model Size Statistics 30 | 31 | The codes for model size testing are uploaded in `./MemoryTest/`. Running each program can obtain the memory consumption of each part of the model. 32 | 33 | ## The Resource Adaptation Algorithm 34 | 35 | The code of resource adaption algorithm is uploaded in `./ResouceAdaptationModel/`. The specific parameters in code need to be set according to the actual requirements. 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ResouceAdaptationModel/algorithm.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def resource_idle_rate(P_i, M_i): 4 | S_i = 2 * P_i * M_i / (P_i + M_i) 5 | return S_i 6 | 7 | def availability_index(a, S_i, r_i): 8 | U_i = (1 + math.pow(a, 2)) * S_i / r_i / (math.pow(a, 2) / r_i + S_i) 9 | return U_i 10 | 11 | def calculation_rate(forward_size, total_size): 12 | c_j = forward_size / total_size 13 | return c_j 14 | 15 | def occupation_index(b, c_j, m_ij): 16 | O_ij = (1 + math.pow(b, 2)) * c_j * m_ij / (math.pow(b, 2) * m_ij + c_j) 17 | return O_ij 18 | 19 | def judge_availability(U_i, O_ij): 20 | if U_i >= O_ij: 21 | print("Branch j can be install on ECU i.") 22 | return 1 23 | else: 24 | print("Branch j can not be install on ECU i.") 25 | return 0 26 | 27 | def whole_algorithm(P_i, M_i, a, r_i, forward_size, total_size, b, m_ij): 28 | S_i = resource_idle_rate(P_i, M_i) 29 | # print("Resource idle rate: ", S_i) 30 | 31 | U_i = availability_index(a, S_i, r_i) 32 | # print("Availability index: ", U_i) 33 | 34 | c_j = calculation_rate(forward_size, total_size) 35 | print("Calculation rate: ", c_j) 36 | 37 | O_ij = occupation_index(b, c_j, m_ij) 38 | print("Occupation index: ", O_ij) 39 | return judge_availability(U_i, O_ij) 40 | 41 | if __name__ == '__main__': 42 | processor_idle_rate = 0.6 43 | memory_idle_rate = 0.5 44 | alpha = 1 45 | risk_index = 4 46 | forward_size = 0.08 47 | total_size = 0.09 48 | beta = 2 49 | memory_ecu = 1 50 | memory_occupied_rate = total_size / memory_ecu 51 | 52 | whole_algorithm(processor_idle_rate, memory_idle_rate, alpha, risk_index, forward_size, total_size, beta, memory_occupied_rate) 53 | 54 | 55 | -------------------------------------------------------------------------------- /STParNet/par_DW_LSTM.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParLSTM(nn.Module): 16 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 17 | super(ParLSTM, self).__init__() 18 | 19 | self.hidden_size = hidden_size 20 | self.num_layers = num_layers 21 | self.input_size = input_size 22 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 23 | self.fc = nn.Linear(hidden_size, num_classes) 24 | 25 | 26 | if init_weights: 27 | self._initialize_weights() 28 | 29 | def forward(self, x): 30 | x = x.reshape(-1, 27, self.input_size) 31 | # Set initial hidden and cell states 32 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 33 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 34 | 35 | # Forward propagate LSTM 36 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 37 | 38 | # Decode the hidden state of the last time step 39 | out = self.fc(out[:, -1, :]) 40 | return out 41 | 42 | def _initialize_weights(self): 43 | for m in self.modules(): 44 | if isinstance(m, nn.Conv2d): 45 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 46 | if m.bias is not None: 47 | nn.init.constant_(m.bias, 0) 48 | elif isinstance(m, nn.Linear): 49 | nn.init.normal_(m.weight, 0, 0.01) 50 | nn.init.constant_(m.bias, 0) 51 | 52 | class ParDWLSTM(nn.Module): 53 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 54 | 55 | super(ParDWLSTM, self).__init__() 56 | 57 | self.hidden_size = hidden_size 58 | self.num_layers = num_layers 59 | self.input_size = input_size 60 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 61 | self.fc1 = nn.Linear(hidden_size, num_classes) 62 | 63 | self.branch1 = nn.Sequential( 64 | ConvBNReLU(3, 64, kernel_size=1), 65 | ConvBNReLU(64, 64, stride=8, groups=64) 66 | ) 67 | 68 | self.branch2 = nn.Sequential( 69 | ConvBNReLU(3, 128, kernel_size=1), 70 | ConvBNReLU(128, 128, stride=4, groups=128), 71 | ConvBN(128, 256, kernel_size=1), 72 | ConvBNReLU(256, 256, stride=2, groups=256) 73 | ) 74 | 75 | self.branch3 = nn.Sequential( 76 | ConvBNReLU(3, 32, kernel_size=1), 77 | ConvBNReLU(32, 32, 78 | stride=2, groups=32), 79 | ConvBN(32, 96, kernel_size=1), 80 | ConvBNReLU(96, 96, stride=2, groups=96), 81 | ConvBN(96, 192, kernel_size=1), 82 | ConvBNReLU(192, 192, stride=2, groups=192) 83 | ) 84 | 85 | self.conv = ConvBN(512, 512) 86 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 87 | self.dropout = nn.Dropout(0.5) 88 | self.fc2 = nn.Linear(512, num_classes) 89 | 90 | if init_weights: 91 | self._initialize_weights() 92 | 93 | def forward(self, x): 94 | y = x.reshape(-1, 27, self.input_size) 95 | # Set initial hidden and cell states 96 | h0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 97 | c0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 98 | 99 | # Forward propagate LSTM 100 | y, _ = self.lstm(y, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 101 | 102 | # Decode the hidden state of the last time step 103 | y = self.fc1(y[:, -1, :]) 104 | #print(y.size) 105 | 106 | branch1 = self.branch1(x) 107 | branch2 = self.branch2(x) 108 | branch3 = self.branch3(x) 109 | 110 | outputs = [branch1, branch2, branch3] 111 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 112 | x = self.conv(x) 113 | x = self.avgpool(x) 114 | x = torch.flatten(x, 1) 115 | x = self.dropout(x) 116 | x = self.fc2(x) 117 | #print(x.size) 118 | 119 | out = torch.mul(x+y, 1/2) 120 | #print(out.size) 121 | return out 122 | 123 | def _initialize_weights(self): 124 | for m in self.modules(): 125 | if isinstance(m, nn.Conv2d): 126 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 127 | if m.bias is not None: 128 | nn.init.constant_(m.bias, 0) 129 | elif isinstance(m, nn.Linear): 130 | nn.init.normal_(m.weight, 0, 0.01) 131 | nn.init.constant_(m.bias, 0) 132 | 133 | 134 | class ConvBN(nn.Sequential): 135 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 136 | # group如果等于输入特征模型的深度,则为DW卷积 137 | padding = (kernel_size - 1) // 2 138 | super(ConvBN, self).__init__( 139 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 140 | nn.BatchNorm2d(out_channel) 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /STParNet/par_DW_LSTM2.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParLSTM(nn.Module): 16 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 17 | super(ParLSTM, self).__init__() 18 | 19 | self.hidden_size = hidden_size 20 | self.num_layers = num_layers 21 | self.input_size = input_size 22 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 23 | self.fc = nn.Linear(hidden_size, num_classes) 24 | 25 | 26 | if init_weights: 27 | self._initialize_weights() 28 | 29 | def forward(self, x): 30 | x = x.reshape(-1, 27, self.input_size) 31 | # Set initial hidden and cell states 32 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 33 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 34 | 35 | # Forward propagate LSTM 36 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 37 | 38 | # Decode the hidden state of the last time step 39 | out = self.fc(out[:, -1, :]) 40 | return out 41 | 42 | def _initialize_weights(self): 43 | for m in self.modules(): 44 | if isinstance(m, nn.Conv2d): 45 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 46 | if m.bias is not None: 47 | nn.init.constant_(m.bias, 0) 48 | elif isinstance(m, nn.Linear): 49 | nn.init.normal_(m.weight, 0, 0.01) 50 | nn.init.constant_(m.bias, 0) 51 | 52 | class ParDWLSTM(nn.Module): 53 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 54 | 55 | super(ParDWLSTM, self).__init__() 56 | 57 | self.hidden_size = hidden_size 58 | self.num_layers = num_layers 59 | self.input_size = input_size 60 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 61 | self.fc1 = nn.Linear(hidden_size, num_classes) 62 | 63 | self.branch1 = nn.Sequential( 64 | ConvBNReLU(3, 64, kernel_size=1), 65 | ConvBNReLU(64, 64, stride=8, groups=64) 66 | ) 67 | 68 | self.branch2 = nn.Sequential( 69 | ConvBNReLU(3, 128, kernel_size=1), 70 | ConvBNReLU(128, 128, stride=4, groups=128), 71 | ConvBN(128, 256, kernel_size=1), 72 | ConvBNReLU(256, 256, stride=2, groups=256) 73 | ) 74 | 75 | self.branch3 = nn.Sequential( 76 | ConvBNReLU(3, 32, kernel_size=1), 77 | ConvBNReLU(32, 32, 78 | stride=2, groups=32), 79 | ConvBN(32, 96, kernel_size=1), 80 | ConvBNReLU(96, 96, stride=2, groups=96), 81 | ConvBN(96, 192, kernel_size=1), 82 | ConvBNReLU(192, 192, stride=2, groups=192) 83 | ) 84 | 85 | self.conv = ConvBN(512, 256) 86 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 87 | self.dropout = nn.Dropout(0.5) 88 | self.fc2 = nn.Linear(256, num_classes) 89 | 90 | if init_weights: 91 | self._initialize_weights() 92 | 93 | def forward(self, x): 94 | y = x.reshape(-1, 27, self.input_size) 95 | # Set initial hidden and cell states 96 | h0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 97 | c0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 98 | 99 | # Forward propagate LSTM 100 | y, _ = self.lstm(y, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 101 | 102 | # Decode the hidden state of the last time step 103 | y = self.fc1(y[:, -1, :]) 104 | #print(y.size) 105 | 106 | branch1 = self.branch1(x) 107 | branch2 = self.branch2(x) 108 | branch3 = self.branch3(x) 109 | 110 | outputs = [branch1, branch2, branch3] 111 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 112 | x = self.conv(x) 113 | x = self.avgpool(x) 114 | x = torch.flatten(x, 1) 115 | x = self.dropout(x) 116 | x = self.fc2(x) 117 | #print(x.size) 118 | 119 | out = torch.mul(x+y, 1/2) 120 | #print(out.size) 121 | return out 122 | 123 | def _initialize_weights(self): 124 | for m in self.modules(): 125 | if isinstance(m, nn.Conv2d): 126 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 127 | if m.bias is not None: 128 | nn.init.constant_(m.bias, 0) 129 | elif isinstance(m, nn.Linear): 130 | nn.init.normal_(m.weight, 0, 0.01) 131 | nn.init.constant_(m.bias, 0) 132 | 133 | 134 | class ConvBN(nn.Sequential): 135 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 136 | # group如果等于输入特征模型的深度,则为DW卷积 137 | padding = (kernel_size - 1) // 2 138 | super(ConvBN, self).__init__( 139 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 140 | nn.BatchNorm2d(out_channel) 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /STParNet/par_DW_LSTM3.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParLSTM(nn.Module): 16 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 17 | super(ParLSTM, self).__init__() 18 | 19 | self.hidden_size = hidden_size 20 | self.num_layers = num_layers 21 | self.input_size = input_size 22 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 23 | self.fc = nn.Linear(hidden_size, num_classes) 24 | 25 | 26 | if init_weights: 27 | self._initialize_weights() 28 | 29 | def forward(self, x): 30 | x = x.reshape(-1, 27, self.input_size) 31 | # Set initial hidden and cell states 32 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 33 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 34 | 35 | # Forward propagate LSTM 36 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 37 | 38 | # Decode the hidden state of the last time step 39 | out = self.fc(out[:, -1, :]) 40 | return out 41 | 42 | def _initialize_weights(self): 43 | for m in self.modules(): 44 | if isinstance(m, nn.Conv2d): 45 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 46 | if m.bias is not None: 47 | nn.init.constant_(m.bias, 0) 48 | elif isinstance(m, nn.Linear): 49 | nn.init.normal_(m.weight, 0, 0.01) 50 | nn.init.constant_(m.bias, 0) 51 | 52 | class ParDWLSTM(nn.Module): 53 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 54 | 55 | super(ParDWLSTM, self).__init__() 56 | 57 | self.hidden_size = hidden_size 58 | self.num_layers = num_layers 59 | self.input_size = input_size 60 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 61 | self.fc1 = nn.Linear(hidden_size, num_classes) 62 | 63 | self.branch1 = nn.Sequential( 64 | ConvBNReLU(3, 64, kernel_size=1), 65 | ConvBNReLU(64, 64, stride=8, groups=64) 66 | ) 67 | 68 | self.branch2 = nn.Sequential( 69 | ConvBNReLU(3, 128, kernel_size=1), 70 | ConvBNReLU(128, 128, stride=4, groups=128), 71 | ConvBN(128, 256, kernel_size=1), 72 | ConvBNReLU(256, 256, stride=2, groups=256) 73 | ) 74 | 75 | self.branch3 = nn.Sequential( 76 | ConvBNReLU(3, 32, kernel_size=1), 77 | ConvBNReLU(32, 32, 78 | stride=2, groups=32), 79 | ConvBN(32, 96, kernel_size=1), 80 | ConvBNReLU(96, 96, stride=2, groups=96), 81 | ConvBN(96, 192, kernel_size=1), 82 | ConvBNReLU(192, 192, stride=2, groups=192) 83 | ) 84 | 85 | self.conv = ConvBN(512, 128) 86 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 87 | self.dropout = nn.Dropout(0.5) 88 | self.fc2 = nn.Linear(128, num_classes) 89 | 90 | if init_weights: 91 | self._initialize_weights() 92 | 93 | def forward(self, x): 94 | y = x.reshape(-1, 27, self.input_size) 95 | # Set initial hidden and cell states 96 | h0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 97 | c0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 98 | 99 | # Forward propagate LSTM 100 | y, _ = self.lstm(y, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 101 | 102 | # Decode the hidden state of the last time step 103 | y = self.fc1(y[:, -1, :]) 104 | #print(y.size) 105 | 106 | branch1 = self.branch1(x) 107 | branch2 = self.branch2(x) 108 | branch3 = self.branch3(x) 109 | 110 | outputs = [branch1, branch2, branch3] 111 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 112 | x = self.conv(x) 113 | x = self.avgpool(x) 114 | x = torch.flatten(x, 1) 115 | x = self.dropout(x) 116 | x = self.fc2(x) 117 | #print(x.size) 118 | 119 | out = torch.mul(x+y, 1/2) 120 | #print(out.size) 121 | return out 122 | 123 | def _initialize_weights(self): 124 | for m in self.modules(): 125 | if isinstance(m, nn.Conv2d): 126 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 127 | if m.bias is not None: 128 | nn.init.constant_(m.bias, 0) 129 | elif isinstance(m, nn.Linear): 130 | nn.init.normal_(m.weight, 0, 0.01) 131 | nn.init.constant_(m.bias, 0) 132 | 133 | 134 | class ConvBN(nn.Sequential): 135 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 136 | # group如果等于输入特征模型的深度,则为DW卷积 137 | padding = (kernel_size - 1) // 2 138 | super(ConvBN, self).__init__( 139 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 140 | nn.BatchNorm2d(out_channel) 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /STParNet/par_DW_LSTM4.py: -------------------------------------------------------------------------------- 1 | from torch import nn 2 | import torch 3 | 4 | 5 | class ConvBNReLU(nn.Sequential): 6 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 7 | # group如果等于输入特征模型的深度,则为DW卷积 8 | padding = (kernel_size - 1) // 2 9 | super(ConvBNReLU, self).__init__( 10 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 11 | nn.BatchNorm2d(out_channel), 12 | nn.ReLU6(inplace=True) 13 | ) 14 | 15 | class ParLSTM(nn.Module): 16 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 17 | super(ParLSTM, self).__init__() 18 | 19 | self.hidden_size = hidden_size 20 | self.num_layers = num_layers 21 | self.input_size = input_size 22 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 23 | self.fc = nn.Linear(hidden_size, num_classes) 24 | 25 | 26 | if init_weights: 27 | self._initialize_weights() 28 | 29 | def forward(self, x): 30 | x = x.reshape(-1, 27, self.input_size) 31 | # Set initial hidden and cell states 32 | h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 33 | c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size) 34 | 35 | # Forward propagate LSTM 36 | out, _ = self.lstm(x, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 37 | 38 | # Decode the hidden state of the last time step 39 | out = self.fc(out[:, -1, :]) 40 | return out 41 | 42 | def _initialize_weights(self): 43 | for m in self.modules(): 44 | if isinstance(m, nn.Conv2d): 45 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 46 | if m.bias is not None: 47 | nn.init.constant_(m.bias, 0) 48 | elif isinstance(m, nn.Linear): 49 | nn.init.normal_(m.weight, 0, 0.01) 50 | nn.init.constant_(m.bias, 0) 51 | 52 | class ParDWLSTM(nn.Module): 53 | def __init__(self, input_size, hidden_size, num_layers, num_classes=1000, init_weights=False): 54 | 55 | super(ParDWLSTM, self).__init__() 56 | 57 | self.hidden_size = hidden_size 58 | self.num_layers = num_layers 59 | self.input_size = input_size 60 | self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) 61 | self.fc1 = nn.Linear(hidden_size, num_classes) 62 | 63 | self.branch1 = nn.Sequential( 64 | ConvBNReLU(3, 64, kernel_size=1), 65 | ConvBNReLU(64, 64, stride=8, groups=64) 66 | ) 67 | 68 | self.branch2 = nn.Sequential( 69 | ConvBNReLU(3, 128, kernel_size=1), 70 | ConvBNReLU(128, 128, stride=4, groups=128), 71 | ConvBN(128, 256, kernel_size=1), 72 | ConvBNReLU(256, 256, stride=2, groups=256) 73 | ) 74 | 75 | self.branch3 = nn.Sequential( 76 | ConvBNReLU(3, 32, kernel_size=1), 77 | ConvBNReLU(32, 32, 78 | stride=2, groups=32), 79 | ConvBN(32, 96, kernel_size=1), 80 | ConvBNReLU(96, 96, stride=2, groups=96), 81 | ConvBN(96, 192, kernel_size=1), 82 | ConvBNReLU(192, 192, stride=2, groups=192) 83 | ) 84 | 85 | self.conv = ConvBN(512, 64) 86 | self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 87 | self.dropout = nn.Dropout(0.5) 88 | self.fc2 = nn.Linear(64, num_classes) 89 | 90 | if init_weights: 91 | self._initialize_weights() 92 | 93 | def forward(self, x): 94 | y = x.reshape(-1, 27, self.input_size) 95 | # Set initial hidden and cell states 96 | h0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 97 | c0 = torch.zeros(self.num_layers, y.size(0), self.hidden_size) 98 | 99 | # Forward propagate LSTM 100 | y, _ = self.lstm(y, (h0, c0)) # out: tensor of shape (batch_size, seq_length, hidden_size) 101 | 102 | # Decode the hidden state of the last time step 103 | y = self.fc1(y[:, -1, :]) 104 | #print(y.size) 105 | 106 | branch1 = self.branch1(x) 107 | branch2 = self.branch2(x) 108 | branch3 = self.branch3(x) 109 | 110 | outputs = [branch1, branch2, branch3] 111 | x = torch.cat(outputs, 1) # 在channel纬度上进行合并,1是channel的下标 112 | x = self.conv(x) 113 | x = self.avgpool(x) 114 | x = torch.flatten(x, 1) 115 | x = self.dropout(x) 116 | x = self.fc2(x) 117 | #print(x.size) 118 | 119 | out = torch.mul(x+y, 1/2) 120 | #print(out.size) 121 | return out 122 | 123 | def _initialize_weights(self): 124 | for m in self.modules(): 125 | if isinstance(m, nn.Conv2d): 126 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 127 | if m.bias is not None: 128 | nn.init.constant_(m.bias, 0) 129 | elif isinstance(m, nn.Linear): 130 | nn.init.normal_(m.weight, 0, 0.01) 131 | nn.init.constant_(m.bias, 0) 132 | 133 | 134 | class ConvBN(nn.Sequential): 135 | def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): 136 | # group如果等于输入特征模型的深度,则为DW卷积 137 | padding = (kernel_size - 1) // 2 138 | super(ConvBN, self).__init__( 139 | nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), 140 | nn.BatchNorm2d(out_channel) 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /STParNet/predict_LSTM.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | from torchvision import transforms, datasets 7 | from tqdm import tqdm 8 | import matplotlib.pyplot as plt 9 | import torchmetrics 10 | from torchmetrics.classification import MulticlassConfusionMatrix 11 | from torchsummary import summary 12 | 13 | 14 | from par_DW_LSTM4 import ParDWLSTM 15 | 16 | # Test_Dir = '../data_img/test/' 17 | 18 | 19 | def main(): 20 | batch_size = 32 21 | num_classes = 5 22 | input_size = 9 23 | hidden_size = 32 24 | num_layers = 2 25 | 26 | # device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 27 | 28 | transform = transforms.Compose([transforms.ToTensor(), 29 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 30 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 31 | # transforms.RandomHorizontalFlip(), 32 | # transforms.ToTensor(), 33 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 34 | 35 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../")) # get data root path 36 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") 37 | print(image_path) 38 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 39 | 40 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 41 | print('Using {} dataloader workers every process'.format(nw)) 42 | 43 | predict_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), 44 | transform=transform) 45 | predict_num = len(predict_dataset) 46 | predict_loader = torch.utils.data.DataLoader(predict_dataset, 47 | batch_size=batch_size, shuffle=True, 48 | num_workers=nw) 49 | print(predict_loader) 50 | 51 | net = ParDWLSTM(input_size=input_size, hidden_size=hidden_size, 52 | num_layers=num_layers, num_classes=num_classes, init_weights=False) 53 | # load model weights 54 | model_weight_path = "weight_res_seq/ParDWLSTM7_20.pth" 55 | net.load_state_dict(torch.load(model_weight_path)) 56 | net.eval() 57 | # acc = 0.0 # accumulate accurate number / epoch 58 | test_acc = torchmetrics.Accuracy() 59 | test_recall = torchmetrics.Recall(average='none', num_classes=num_classes) 60 | test_precision = torchmetrics.Precision(average='none', num_classes=num_classes) 61 | test_f1 = torchmetrics.F1Score(average='none', num_classes=num_classes) 62 | test_auc = torchmetrics.AUROC(average="macro", num_classes=num_classes) 63 | test_conf = MulticlassConfusionMatrix(num_classes=5) 64 | summary(net, (3, 9, 9)) 65 | 66 | with torch.no_grad(): 67 | predict_bar = tqdm(predict_loader, file=sys.stdout) 68 | for predict_data in predict_bar: 69 | predict_images, predict_labels = predict_data 70 | outputs = net(predict_images) 71 | # loss = loss_function(outputs, test_labels) 72 | predict_y = torch.max(outputs, dim=1)[1] 73 | # acc += torch.eq(predict_y, predict_labels).sum().item() 74 | test_acc.update(predict_y, predict_labels) 75 | test_auc.update(outputs, predict_labels) 76 | test_recall(predict_y, predict_labels) 77 | test_precision(predict_y, predict_labels) 78 | test_f1(predict_y, predict_labels) 79 | test_conf.update(predict_y, predict_labels) 80 | 81 | # predict_accurate = acc / predict_num 82 | total_acc = test_acc.compute() 83 | total_recall = test_recall.compute() 84 | total_precision = test_precision.compute() 85 | total_auc = test_auc.compute() 86 | total_f1 = test_f1.compute() 87 | total_conf = test_conf.compute() 88 | 89 | # print('predict_accuracy: %.4f' %(predict_accurate)) 90 | 91 | print("torch metrics acc:", total_acc) 92 | print("recall of every test dataset class: ", total_recall) 93 | print("precision of every test dataset class: ", total_precision) 94 | print("F1-score of every test dataset class: ", total_f1) 95 | print("auc:", total_auc.item()) 96 | print("confusion metrics:", total_conf) 97 | 98 | # 清空计算对象 99 | test_precision.reset() 100 | test_acc.reset() 101 | test_recall.reset() 102 | test_auc.reset() 103 | test_conf.reset() 104 | 105 | 106 | 107 | if __name__ == '__main__': 108 | main() 109 | -------------------------------------------------------------------------------- /STParNet/train_LSTM.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | 5 | import torch 6 | import torch.nn as nn 7 | import torch.optim as optim 8 | from torchvision import transforms, datasets 9 | from tqdm import tqdm 10 | 11 | from par_DW_LSTM4 import ParDWLSTM 12 | 13 | 14 | def main(): 15 | batch_size = 32 16 | num_classes = 5 17 | epochs = 30 18 | input_size = 9 19 | hidden_size = 32 20 | num_layers = 2 21 | 22 | transform = transforms.Compose([transforms.ToTensor(), 23 | transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 24 | # transform = transforms.Compose([transforms.RandomResizedCrop(16), 25 | # transforms.RandomHorizontalFlip(), 26 | # transforms.ToTensor(), 27 | # transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]) 28 | 29 | data_root = os.path.abspath(os.path.join(os.getcwd(), "../")) # get data root path 30 | image_path = os.path.join(data_root, "LPN", "data_sequential_img") # flower data set path 31 | print(image_path) 32 | assert os.path.exists(image_path), "{} path does not exist.".format(image_path) 33 | train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), 34 | transform=transform) 35 | train_num = len(train_dataset) 36 | print(train_num) 37 | 38 | nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workers 39 | print('Using {} dataloader workers every process'.format(nw)) 40 | 41 | train_loader = torch.utils.data.DataLoader(train_dataset, 42 | batch_size=batch_size, shuffle=True, 43 | num_workers=nw) 44 | 45 | validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"), 46 | transform=transform) 47 | val_num = len(validate_dataset) 48 | validate_loader = torch.utils.data.DataLoader(validate_dataset, 49 | batch_size=batch_size, shuffle=False, 50 | num_workers=nw) 51 | 52 | print("using {} images for training, {} images for validation.".format(train_num, 53 | val_num)) 54 | 55 | # create model 56 | net = ParDWLSTM(input_size=input_size, hidden_size=hidden_size, 57 | num_layers=num_layers, num_classes=num_classes, init_weights=True) 58 | 59 | # define loss function 60 | loss_function = nn.CrossEntropyLoss() 61 | 62 | # construct an optimizer 63 | params = [p for p in net.parameters() if p.requires_grad] 64 | optimizer = optim.Adam(params, lr=0.0001) 65 | 66 | best_acc = 0.0 67 | 68 | save_path = 'weight_res_seq/ParDWLSTM7_20_.pth' 69 | train_steps = len(train_loader) 70 | val_steps = len(validate_loader) 71 | for epoch in range(epochs): 72 | # train 73 | net.train() 74 | running_loss = 0.0 75 | valing_loss = 0.0 76 | train_acc = 0.0 77 | train_bar = tqdm(train_loader, file=sys.stdout) 78 | for step, data in enumerate(train_bar): 79 | images, labels = data 80 | optimizer.zero_grad() 81 | logits = net(images) 82 | predict_y = torch.max(logits, dim=1)[1] 83 | train_acc += torch.eq(predict_y, labels).sum().item() 84 | loss = loss_function(logits, labels) 85 | loss.backward() 86 | optimizer.step() 87 | # print statistics 88 | running_loss += loss.item() 89 | train_bar.desc = "train epoch[{}/{}] loss:{:.4f}".format(epoch + 1, 90 | epochs, 91 | loss) 92 | 93 | 94 | # validate 95 | net.eval() 96 | acc = 0.0 # accumulate accurate number / epoch 97 | with torch.no_grad(): 98 | val_bar = tqdm(validate_loader, file=sys.stdout) 99 | for val_data in val_bar: 100 | val_images, val_labels = val_data 101 | outputs = net(val_images) 102 | val_loss = loss_function(outputs, val_labels) 103 | # loss = loss_function(logits, labels) 104 | predict_y = torch.max(outputs, dim=1)[1] 105 | acc += torch.eq(predict_y, val_labels).sum().item() 106 | valing_loss += val_loss.item() 107 | 108 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 109 | epochs) 110 | 111 | 112 | train_accurate = train_acc / train_num 113 | val_accurate = acc / val_num 114 | print('[epoch %d] train_loss: %.4f train_acc: %.4f val_loss: %.4f val_accuracy: %.4f' % 115 | (epoch + 1, running_loss / train_steps, train_accurate, valing_loss / val_steps, val_accurate)) 116 | 117 | if val_accurate > best_acc: 118 | best_acc = val_accurate 119 | torch.save(net.state_dict(), save_path) 120 | 121 | print('Finished Training') 122 | 123 | 124 | 125 | if __name__ == '__main__': 126 | main() 127 | --------------------------------------------------------------------------------