├── README.md ├── imgs └── macnn.JPG ├── model └── macnn.py └── train.py /README.md: -------------------------------------------------------------------------------- 1 | # MACNN-Pytorch 2 | unofficial implementation of paper [“Multi-scale Attention Convolutional Neural Network for time series classification”](https://www.sciencedirect.com/science/article/abs/pii/S0893608021000010) 3 | 4 | ![macnn](https://github.com/ZachySun/MACNN-Pytorch/blob/main/imgs/macnn.JPG) 5 | 6 | [Tensorflow version](https://github.com/wesley1001/MACNN) 7 | 8 | [Official Final Tensorflow version](https://github.com/holybaozi/MACNN_Final) 9 | -------------------------------------------------------------------------------- /imgs/macnn.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zachysun/MACNN-Pytorch/c7abc9dfca00ad2a2fd09183868ec5af87b9dbed/imgs/macnn.JPG -------------------------------------------------------------------------------- /model/macnn.py: -------------------------------------------------------------------------------- 1 | ## Multi-scale Attention Convolutional Neural Network(MACNN) for 1-d time series 2 | ## Pytorch-version 3 | ## Modified from https://github.com/wesley1001/MACNN/blob/master/model.py 4 | 5 | import torch.nn as nn 6 | import torch 7 | import torch.nn.functional as F 8 | from torch.autograd import Variable 9 | from torch.nn import init 10 | 11 | class SEAttention1d(nn.Module): 12 | ''' 13 | Modified from https://github.com/xmu-xiaoma666/External-Attention-pytorch/blob/master/model/attention/SEAttention.py 14 | ''' 15 | def __init__(self, channel, reduction): 16 | super(SEAttention1d, self).__init__() 17 | self.avg_pool = nn.AdaptiveAvgPool1d(1) 18 | self.fc = nn.Sequential( 19 | nn.Linear(channel, channel//reduction, bias=False), 20 | nn.ReLU(inplace=True), 21 | nn.Linear(channel//reduction, channel, bias=False), 22 | nn.Sigmoid(), 23 | ) 24 | 25 | def init_weights(self): 26 | for m in self.modules(): 27 | if isinstance(m, nn.Conv1d): 28 | init.kaiming_normal_(m.weight, mode='fan_out') 29 | if m.bias is not None: 30 | init.constant_(m.bias, 0) 31 | elif isinstance(m, nn.BatchNorm1d): 32 | init.constant_(m.weight, 1) 33 | init.constant_(m.bias, 0) 34 | elif isinstance(m, nn.Linear): 35 | init.normal_(m.weight, std=0.001) 36 | if m.bias is not None: 37 | init.constant_(m.bias, 0) 38 | 39 | def forward(self, x): 40 | b,c,_ = x.size() 41 | y = self.avg_pool(x).view(b,c) 42 | y = self.fc(y).view(b,c,1) 43 | 44 | return x*y.expand_as(x) 45 | 46 | 47 | class macnn_block(nn.Module): 48 | 49 | def __init__(self, in_channels, out_channels, kernel_size=None, stride=1, reduction=16): 50 | super(macnn_block, self).__init__() 51 | 52 | if kernel_size is None: 53 | kernel_size = [3, 6, 12] 54 | 55 | self.reduction = reduction 56 | 57 | self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size[0], stride=1, padding='same') 58 | self.conv2 = nn.Conv1d(in_channels, out_channels, kernel_size[1], stride=1, padding='same') 59 | self.conv3 = nn.Conv1d(in_channels, out_channels, kernel_size[2], stride=1, padding='same') 60 | 61 | self.bn = nn.BatchNorm1d(out_channels*3) 62 | self.relu = nn.ReLU() 63 | 64 | self.se = SEAttention1d(out_channels*3,reduction=reduction) 65 | 66 | def forward(self,x): 67 | 68 | x1 = self.conv1(x) 69 | x2 = self.conv2(x) 70 | x3 = self.conv3(x) 71 | 72 | x_con = torch.cat([x1,x2,x3], dim=1) 73 | 74 | out = self.bn(x_con) 75 | out = self.relu(out) 76 | 77 | out_se = self.se(out) 78 | 79 | return out_se 80 | 81 | class MACNN(nn.Module): 82 | 83 | def __init__(self, in_channels=3, channels=64, num_classes=7, block_num=None): 84 | super(MACNN, self).__init__() 85 | 86 | if block_num is None: 87 | block_num = [2, 2, 2] 88 | 89 | self.in_channel = in_channels 90 | self.num_classes = num_classes 91 | self.channel = channels 92 | 93 | self.max_pool1 = nn.MaxPool1d(kernel_size=3,stride=2,padding=1) 94 | self.max_pool2 = nn.MaxPool1d(kernel_size=3, stride=2,padding=1) 95 | self.avg_pool = nn.AdaptiveAvgPool1d(1) 96 | self.fc = nn.Linear(self.channel*12, num_classes) 97 | 98 | self.layer1 = self._make_layer(macnn_block, block_num[0], self.channel) 99 | self.layer2 = self._make_layer(macnn_block, block_num[1], self.channel*2) 100 | self.layer3 = self._make_layer(macnn_block, block_num[2], self.channel*4) 101 | 102 | def _make_layer(self, block, block_num, channel, reduction=16): 103 | 104 | layers = [] 105 | for i in range(block_num): 106 | layers.append(block(self.in_channel, channel, kernel_size=None, 107 | stride=1, reduction=reduction)) 108 | self.in_channel = 3*channel 109 | 110 | return nn.Sequential(*layers) 111 | 112 | def forward(self, x): 113 | 114 | out1 = self.layer1(x) 115 | out1 = self.max_pool1(out1) 116 | 117 | out2 = self.layer2(out1) 118 | out2 = self.max_pool2(out2) 119 | 120 | out3 = self.layer3(out2) 121 | out3 = self.avg_pool(out3) 122 | 123 | out = torch.flatten(out3, 1) 124 | out = self.fc(out) 125 | 126 | return out 127 | 128 | if __name__ == '__main__': 129 | 130 | input=Variable(torch.randn(1,3,28)) 131 | net = MACNN(in_channels=3, channels=64, num_classes=7, block_num=None) 132 | out = net(input) 133 | print(out.shape) 134 | 135 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from model.macnn import MACNN 3 | 4 | import torch.nn as nn 5 | import torch 6 | from sklearn.model_selection import train_test_split 7 | import torch.optim as optim 8 | from torch.utils.data import TensorDataset, DataLoader 9 | 10 | from tqdm import tqdm 11 | import sys 12 | 13 | 14 | def main(): 15 | ## load dataset 16 | 17 | x_dir = './data/x_train.npy' 18 | y_dir = './data/y_train.npy' 19 | 20 | x_data = np.load(x_dir) 21 | x_data = torch.tensor(x_data) 22 | x_data = x_data.transpose(1, 2) 23 | 24 | y_data = np.load(y_dir) 25 | y_data = torch.tensor(y_data, dtype=torch.long) 26 | y_data = torch.argmax(y_data, axis=1) 27 | 28 | x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=1/5, random_state=7) 29 | 30 | # X = torch.tensor(x_train) 31 | # y = torch.tensor(y_train, dtype=torch.long) 32 | # 33 | # X_val = torch.tensor(x_test) 34 | # y_val = torch.tensor(y_test, dtype=torch.long) 35 | 36 | X = x_train.clone().detach() 37 | y = y_train.clone().detach() 38 | 39 | X_val = x_test.clone().detach() 40 | y_val = y_test.clone().detach() 41 | 42 | 43 | ## build model,criterion,optimizer,dataloader 44 | 45 | model = MACNN(in_channels=3, channels=64, num_classes=7, block_num=None) 46 | 47 | criterion = nn.CrossEntropyLoss() 48 | optimizer = optim.AdamW(model.parameters()) 49 | 50 | train_dataset = TensorDataset(X, y) 51 | train_loader = DataLoader(dataset=train_dataset, batch_size=128, 52 | shuffle=True) 53 | 54 | val_dataset = TensorDataset(X_val, y_val) 55 | val_loader = DataLoader(dataset=val_dataset, batch_size=128, 56 | shuffle=True) 57 | 58 | val_num = len(val_dataset) 59 | train_steps = len(train_loader) 60 | 61 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 62 | model.to(device) 63 | 64 | save_path = './output/MACNN_1d.pth' 65 | 66 | epochs = 20 67 | best_acc = 0.0 68 | 69 | ## train and validation 70 | for epoch in range(epochs): 71 | model.train() 72 | running_loss = 0.0 73 | train_bar = tqdm(train_loader, file=sys.stdout) 74 | 75 | for batch_x, batch_y in train_bar: 76 | logits = model(batch_x.to(device)) 77 | loss = criterion(logits, batch_y.to(device)) 78 | 79 | optimizer.zero_grad() 80 | loss.backward() 81 | optimizer.step() 82 | 83 | running_loss += loss.item() 84 | train_bar.desc = "train epoch[{}/{}] loss:{:.5f}".format(epoch + 1, 85 | epochs, 86 | loss) 87 | model.eval() 88 | acc = 0.0 89 | 90 | with torch.no_grad(): 91 | val_bar = tqdm(val_loader, file=sys.stdout) 92 | for val_batch_x, val_batch_y in val_bar: 93 | outputs = model(val_batch_x.to(device)) 94 | 95 | predict_y = torch.max(outputs, dim=1)[1] 96 | acc += torch.eq(predict_y, val_batch_y.to(device)).sum().item() 97 | 98 | val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1, 99 | epochs) 100 | 101 | val_accurate = acc / val_num 102 | print('[epoch %d] train_loss: %.5f val_accuracy: %.3f' % 103 | (epoch + 1, running_loss / train_steps, val_accurate)) 104 | 105 | if val_accurate > best_acc: 106 | best_acc = val_accurate 107 | torch.save(model.state_dict(), save_path) 108 | 109 | print('Finished Training') 110 | 111 | if __name__ == '__main__': 112 | main() 113 | --------------------------------------------------------------------------------