Repository: Xiaohan-Chen/transfer-learning-fault-diagnosis-pytorch Branch: main Commit: 44afdc60956f Files: 18 Total size: 72.8 KB Directory structure: gitextract_75kh58ds/ ├── Backbone/ │ ├── CNN1D.py │ ├── MLPNet.py │ └── ResNet1D.py ├── DANN.py ├── DDC.py ├── OSDABP.py ├── PreparData/ │ ├── CWRU.py │ ├── __init__.py │ └── preprocess.py ├── README.md ├── Utils/ │ ├── __init__.py │ ├── logger.py │ └── utils.py ├── classification.py └── loss/ ├── CORAL.py ├── MKMMD.py ├── MMDLinear.py └── __init__.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: Backbone/CNN1D.py ================================================ import torch.nn as nn class CNN1D(nn.Module): def __init__(self, num_out = 10): super(CNN1D, self).__init__() self.layer1 = nn.Sequential( nn.Conv1d(1,32,kernel_size=3,padding=1), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(kernel_size=2, padding=0) ) self.layer2 = nn.Sequential( nn.Conv1d(32,64,kernel_size=3,padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(kernel_size=2, padding=0) ) self.layer3 = nn.Sequential( nn.Conv1d(64,64,kernel_size=3,padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(kernel_size=2, padding=0) ) self.avgpool = nn.AdaptiveAvgPool1d(1) # output (64,1) self.fc = nn.Sequential(nn.Linear(64,num_out, nn.Dropout(0.5))) def forward(self, x): x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.avgpool(x) x = x.view(-1,64) return x ================================================ FILE: Backbone/MLPNet.py ================================================ import torch.nn as nn class MLPNet(nn.Module): def __init__(self, num_in = 1024, num_out = 10): super(MLPNet, self).__init__() self.fc1 = nn.Sequential( nn.Linear(num_in,512), nn.BatchNorm1d(512), nn.ReLU() ) self.fc2 = nn.Sequential( nn.Linear(512, 256), nn.BatchNorm1d(256), nn.ReLU() ) self.fc3 = nn.Sequential( nn.Linear(256, 128), nn.BatchNorm1d(128), nn.ReLU() ) self.fc4 = nn.Sequential( nn.Linear(128, 64), nn.BatchNorm1d(64), nn.ReLU() ) self.fc5 = nn.Linear(64,num_out) def forward(self, x): x = self.fc1(x) x = self.fc2(x) x = self.fc3(x) x = self.fc4(x) return x ================================================ FILE: Backbone/ResNet1D.py ================================================ # one-dimentional ResNet source code reference: https://github.com/ZhaoZhibin/UDTL/blob/master/models/resnet18_1d.py import torch.nn as nn import torch.utils.model_zoo as model_zoo model_urls = { 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', } def conv3x1(in_planes, out_planes, stride=1): """3x3 convolution with padding""" return nn.Conv1d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False) def conv1x1(in_planes, out_planes, stride=1): """1x1 convolution""" return nn.Conv1d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) class BasicBlock(nn.Module): expansion = 1 def __init__(self, inplanes, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = conv3x1(inplanes, planes, stride) self.bn1 = nn.BatchNorm1d(planes) self.relu = nn.ReLU(inplace=True) self.conv2 = conv3x1(planes, planes) self.bn2 = nn.BatchNorm1d(planes) self.downsample = downsample self.stride = stride def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out class Bottleneck(nn.Module): # For ResNet50, ResNet101, ResNet152 expansion = 4 def __init__(self, inplanes, planes, stride=1, downsample=None): super(Bottleneck, self).__init__() self.conv1 = conv1x1(inplanes, planes) self.bn1 = nn.BatchNorm1d(planes) self.conv2 = conv3x1(planes, planes, stride) self.bn2 = nn.BatchNorm1d(planes) self.conv3 = conv1x1(planes, planes * self.expansion) self.bn3 = nn.BatchNorm1d(planes * self.expansion) self.relu = nn.ReLU(inplace=True) self.downsample = downsample self.stride = stride def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) if self.downsample is not None: identity = self.downsample(x) out += identity out = self.relu(out) return out class ResNet(nn.Module): def __init__(self, block, layers, in_channel=1, out_channel=10, zero_init_residual=False): super(ResNet, self).__init__() self.inplanes = 64 self.conv1 = nn.Conv1d(in_channel, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm1d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, layers[0]) self.layer2 = self._make_layer(block, 128, layers[1], stride=2) self.layer3 = self._make_layer(block, 256, layers[2], stride=2) self.layer4 = self._make_layer(block, 512, layers[3], stride=2) self.avgpool = nn.AdaptiveAvgPool1d(1) # output (512, 1) for m in self.modules(): if isinstance(m, nn.Conv1d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') elif isinstance(m, nn.BatchNorm1d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) # Zero-initialize the last BN in each residual branch, # so that the residual branch starts with zeros, and each residual block behaves like an identity. # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 if zero_init_residual: for m in self.modules(): if isinstance(m, Bottleneck): nn.init.constant_(m.bn3.weight, 0) elif isinstance(m, BasicBlock): nn.init.constant_(m.bn2.weight, 0) def _make_layer(self, block, planes, blocks, stride=1): downsample = None if stride != 1 or self.inplanes != planes * block.expansion: downsample = nn.Sequential( conv1x1(self.inplanes, planes * block.expansion, stride), nn.BatchNorm1d(planes * block.expansion), ) layers = [] layers.append(block(self.inplanes, planes, stride, downsample)) self.inplanes = planes * block.expansion for _ in range(1, blocks): layers.append(block(self.inplanes, planes)) return nn.Sequential(*layers) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = x.view(x.size(0), -1) return x def resnet18(pretrained=False, **kwargs): """Constructs a ResNet-18 model. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) if pretrained: model.load_state_dict(model_zoo.load_url(model_urls['resnet18'])) return model # convnet without the last layer class resnet18_features(nn.Module): def __init__(self, pretrained=False): super(resnet18_features, self).__init__() self.model_resnet18 = resnet18(pretrained) self.__in_features = 512 def forward(self, x): x = self.model_resnet18(x) return x def output_num(self): return self.__in_features ================================================ FILE: DANN.py ================================================ import argparse import os import numpy as np from Utils.logger import setlogger from turtle import forward import torch import torch.nn as nn import torch.optim as optim import torchvision.models as models from Backbone import ResNet1D, MLPNet, CNN1D from loss import MKMMD, MMDLinear, CORAL from PreparData.CWRU import CWRUloader import Utils.utils as utils from tqdm import * import warnings import logging # ===== Define argments ===== def parse_args(): parser = argparse.ArgumentParser(description='Implementation of Domain Adversarial Neural Networks') # task setting parser.add_argument("--log_file", type=str, default="./logs/DANN.log", help="log file path") # dataset information parser.add_argument("--datadir", type=str, default="./datasets", help="data directory") parser.add_argument("--source_dataname", type=str, default="CWRU", choices=["CWRU", "PU"], help="choice a dataset") parser.add_argument("--target_dataname", type=str, default="CWRU", choices=["CWRU", "PU"], help="choice a dataset") parser.add_argument("--s_load", type=int, default=3, help="source domain working condition") parser.add_argument("--t_load", type=int, default=2, help="target domain working condition") parser.add_argument("--s_label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="source domain label set") parser.add_argument("--t_label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="target domain label set") parser.add_argument("--val_rat", type=float, default=0.3, help="training-validation rate") parser.add_argument("--test_rat", type=float, default=0.5, help="validation-test rate") parser.add_argument("--seed", type=int, default="29") # pre-processing parser.add_argument("--fft", type=bool, default=False, help="FFT preprocessing") parser.add_argument("--window", type=int, default=128, help="time window, if not augment data, window=1024") parser.add_argument("--normalization", type=str, default="0-1", choices=["None", "0-1", "mean-std"], help="normalization option") parser.add_argument("--savemodel", type=bool, default=False, help="whether save pre-trained model in the classification task") parser.add_argument("--pretrained", type=bool, default=False, help="whether use pre-trained model in transfer learning tasks") # backbone parser.add_argument("--backbone", type=str, default="ResNet1D", choices=["ResNet1D", "ResNet2D", "MLPNet", "CNN1D"]) # if backbone in ("ResNet1D", "CNN1D"), data shape: (batch size, 1, 1024) # elif backbone == "ResNet2D", data shape: (batch size, 3, 32, 32) # elif backbone == "MLPNet", data shape: (batch size, 1024) # optimization & training parser.add_argument("--num_workers", type=int, default=0, help="the number of dataloader workers") parser.add_argument("--batch_size", type=int, default=256) parser.add_argument("--max_epoch", type=int, default=100) parser.add_argument("--lr", type=float, default=1e-3, help="learning rate") parser.add_argument('--lr_scheduler', type=str, default='stepLR', choices=['step', 'exp', 'stepLR', 'fix'], help='the learning rate schedule') parser.add_argument('--gamma', type=float, default=0.8, help='learning rate scheduler parameter for step and exp') parser.add_argument('--steps', type=str, default='30, 120', help='the learning rate decay for step and stepLR') parser.add_argument("--optimizer", type=str, default="adam", choices=["adam", "sgd"]) args = parser.parse_args() return args # ===== Build Model ===== class FeatureNet(nn.Module): def __init__(self, args): super(FeatureNet, self).__init__() if args.backbone == "ResNet1D": self.feature_net = ResNet1D.resnet18() elif args.backbone == "ResNet2D": self.model_ft = models.resnet18(pretrained=True) self.bottleneck = nn.Sequential(nn.Linear(self.model_ft.fc.out_features, 512), nn.ReLU(), nn.Dropout(0.5)) self.feature_net = nn.Sequential(self.model_ft, self.bottleneck) elif args.backbone == "MLPNet": if args.fft: self.feature_net = MLPNet.MLPNet(num_in=512) else: self.feature_net = MLPNet.MLPNet() elif args.backbone == "CNN1D": self.feature_net = CNN1D.CNN1D() else: raise Exception("model not implement") def forward(self, x): logits = self.feature_net(x) return logits class Classifier(nn.Module): def __init__(self, args, num_out=10): super(Classifier, self).__init__() if args.backbone in ("ResNet1D", "ResNet2D"): self.classifier = nn.Sequential(nn.Linear(512,num_out, nn.Dropout(0.5))) if args.backbone in ("MLPNet", "CNN1D"): self.classifier = nn.Sequential(nn.Linear(64,num_out, nn.Dropout(0.5))) def forward(self, logits): outputs = self.classifier(logits) return outputs # Define the discriminator def calc_coeff(iter_num, high=1.0, low=0.0, alpha=10.0, max_iter=10000.0): return np.float(2.0 * (high - low) / (1.0 + np.exp(-alpha * iter_num / max_iter)) - (high - low) + low) ## The hook will be called every time a gradient with respect to the Tensor is computed. ## https://pytorch.org/docs/stable/generated/torch.Tensor.register_hook.html?highlight=register_hook#torch.Tensor.register_hook def grl_hook(coeff): def fun1(grad): return -coeff * grad.clone() return fun1 class Discriminator(nn.Module): def __init__(self, args, num_out = 1, max_iter=10000.0, trade_off_adversarial='Cons', lam_adversarial=1.0): super(Discriminator, self).__init__() if args.backbone in ("ResNet1D", "ResNet2D"): self.domain_classifier = nn.Sequential( nn.Linear(512,128, nn.Dropout(0.5)), nn.BatchNorm1d(128), nn.ReLU(), nn.Linear(128, num_out) ) elif args.backbone in ("MLPNet", "CNN1D"): self.domain_classifier = nn.Sequential( nn.Linear(64,32, nn.Dropout(0.5)), nn.BatchNorm1d(32), nn.ReLU(), nn.Linear(32, num_out) ) self.sigmoid = nn.Sigmoid() # parameters self.iter_num = 0 self.alpha = 10 self.low = 0.0 self.high = 1.0 self.max_iter = max_iter self.trade_off_adversarial = trade_off_adversarial self.lam_adversarial = lam_adversarial def forward(self, x): if self.training: self.iter_num += 1 if self.trade_off_adversarial == "Cons": coeff = self.lam_adversarial elif self.trade_off_adversarial == "Step": coeff = calc_coeff(self.iter_num, self.high, self.low,\ self.alpha, self.max_iter) else: raise Exception("loss not implement") x = x * 1.0 x.register_hook(grl_hook(coeff)) x = self.domain_classifier(x) x = self.sigmoid(x) return x # ===== Load Data ===== def loaddata(args): if args.source_dataname == "CWRU": source_data, source_label = CWRUloader(args, args.s_load, args.s_label_set) source_data, source_label = np.concatenate(source_data, axis=0), np.concatenate(source_label, axis=0) if args.target_dataname == "CWRU": target_data, target_label = CWRUloader(args, args.t_load, args.t_label_set) target_data, target_label = np.concatenate(target_data, axis=0), np.concatenate(target_label, axis=0) source_loader, _, _ = utils.DataSplite(args, source_data, source_label) target_trainloader, target_valloader, target_testloader = utils.DataSplite(args, target_data, target_label) return source_loader, target_trainloader, target_valloader, target_testloader # ===== Test the Model ===== def tester(featurenet, classifier, dataloader): featurenet.eval() classifier.eval() device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") correct_num, total_num = 0, 0 for i, (x_batch, y_batch) in enumerate(dataloader): x_batch, y_batch = x_batch.to(device), y_batch.to(device) # compute model cotput and loss logtis_batch = featurenet(x_batch) output_batch = classifier(logtis_batch) pre = torch.max(output_batch.cpu(), 1)[1].numpy() y = y_batch.cpu().numpy() correct_num += (pre == y).sum() total_num += len(y) accuracy = (correct_num / total_num) * 100.0 return accuracy # ===== Train the Model ===== def trainer(args): # Consider the gpu or cpu condition if torch.cuda.is_available(): device = torch.device("cuda") device_count = torch.cuda.device_count() logging.info('using {} gpus'.format(device_count)) assert args.batch_size % device_count == 0, "batch size should be divided by device count" else: warnings.warn("gpu is not available") device = torch.device("cpu") device_count = 1 logging.info('using {} cpu'.format(device_count)) # load the dataset source_trainloader, target_trainloader, target_valloader, target_testloader = loaddata(args) # load the model featurenet = FeatureNet(args) classifier = Classifier(args, num_out=len(args.t_label_set)) discriminator = Discriminator(args) # load the checkpoint if args.pretrained: if args.backbone != "ResNet2D": # pretrained ResNet2D model is downloaded from torchvision module if not args.fft: path = "./checkpoints/{}_checkpoint.tar".format(args.backbone) else: path = "./checkpoints/{}FFT_checkpoint.tar".format(args.backbone) featurenet.load_state_dict(torch.load(path)) parameter_list = [{"params": featurenet.parameters(), "lr": 0.5*args.lr}, {"params": classifier.parameters(), "lr": args.lr}, {"params": discriminator.parameters(), "lr": args.lr}] # Define optimizer and learning rate decay optimizer, lr_scheduler = utils.optimizer(args, parameter_list) ## define loss function loss_cls = nn.CrossEntropyLoss() loss_adver = nn.BCELoss() featurenet.to(device) classifier.to(device) discriminator.to(device) # train best_acc = 0.0 meters = {"acc_source_train":[], "acc_target_train": [], "acc_target_val": []} for epoch in range(args.max_epoch): featurenet.train() classifier.train() with tqdm(total=len(target_trainloader), leave=False) as pbar: for i, ((x_s_batch, y_s_batch), (x_t_batch, y_t_batch)) in enumerate(zip(source_trainloader,target_trainloader)): if len(y_s_batch) != len(y_t_batch): break batch_num = x_s_batch.size(0) domain_label_source = torch.ones(batch_num).float() domain_label_target = torch.zeros(batch_num).float() inputs = torch.cat((x_s_batch, x_t_batch), dim=0) domain_label = torch.cat((domain_label_source, domain_label_target), dim=0) # move to GPU if available inputs = inputs.to(device) s_labels = y_s_batch.to(device) t_labels = y_t_batch.to(device) domain_label = domain_label.to(device) # compute model cotput and loss logits = featurenet(inputs) outputs = classifier(logits) domain_outputs = discriminator(logits) classification_loss = loss_cls(outputs.narrow(0, 0, batch_num), s_labels.long()) adversarial_loss = loss_adver(domain_outputs.squeeze(), domain_label) loss = classification_loss + adversarial_loss # clear previous gradients, compute gradients optimizer.zero_grad() loss.backward() # performs updates using calculated gradients optimizer.step() # evaluate # training accuracy acc_source_train = utils.accuracy(outputs.narrow(0, 0, batch_num), s_labels) acc_target_train = utils.accuracy(outputs.narrow(0, batch_num, batch_num), t_labels) pbar.update() # update lr if lr_scheduler is not None: lr_scheduler.step() val_acc = tester(featurenet, classifier, target_valloader) if val_acc > best_acc: best_acc = val_acc if args.savemodel: utils.save_model(featurenet, args) logging.info("Epoch: {:>3}/{}, loss_cls: {:.4f}, loss: {:.4f}, source_train_acc: {:>6.2f}%, target_train_acc: {:>6.2f}%, target_val_acc: {:>6.2f}%".format(\ epoch+1, args.max_epoch, classification_loss, loss, acc_source_train, acc_target_train, val_acc)) meters["acc_source_train"].append(acc_source_train) meters["acc_target_train"].append(acc_target_train) meters["acc_target_val"].append(val_acc) logging.info("Best accuracy: {:.4f}".format(best_acc)) utils.save_log(meters, "./logs/DDC_{}_{}_meters.pkl".format(args.backbone, args.max_epoch)) logging.info("="*15+"Done!"+"="*15) if __name__ == "__main__": args = parse_args() # set the logger if not os.path.exists("./logs"): os.makedirs("./logs") setlogger(args.log_file) # save the args for k, v in args.__dict__.items(): logging.info("{}: {}".format(k, v)) trainer(args) ================================================ FILE: DDC.py ================================================ import argparse import os import numpy as np from Utils.logger import setlogger from turtle import forward import torch import torch.nn as nn import torch.optim as optim import torchvision.models as models from Backbone import ResNet1D, MLPNet, CNN1D from loss import MKMMD, MMDLinear, CORAL from PreparData.CWRU import CWRUloader import Utils.utils as utils from tqdm import * import warnings import logging # ===== Define argments ===== def parse_args(): parser = argparse.ArgumentParser(description='Implementation of Deep Domain Confusion networks') # task setting parser.add_argument("--log_file", type=str, default="./logs/DDC.log", help="log file path") # dataset information parser.add_argument("--datadir", type=str, default="./datasets", help="data directory") parser.add_argument("--source_dataname", type=str, default="CWRU", choices=["CWRU"], help="choice a dataset") parser.add_argument("--target_dataname", type=str, default="CWRU", choices=["CWRU"], help="choice a dataset") parser.add_argument("--s_load", type=int, default=3, help="source domain working condition") parser.add_argument("--t_load", type=int, default=2, help="target domain working condition") parser.add_argument("--s_label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="source domain label set") parser.add_argument("--t_label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="target domain label set") parser.add_argument("--val_rat", type=float, default=0.3, help="training-validation rate") parser.add_argument("--test_rat", type=float, default=0.5, help="validation-test rate") parser.add_argument("--seed", type=int, default="29") # pre-processing parser.add_argument("--fft", type=bool, default=False, help="FFT preprocessing") parser.add_argument("--window", type=int, default=128, help="time window, if not augment data, window=1024") parser.add_argument("--normalization", type=str, default="0-1", choices=["None", "0-1", "mean-std"], help="normalization option") parser.add_argument("--savemodel", type=bool, default=False, help="whether save pre-trained model in the classification task") parser.add_argument("--pretrained", type=bool, default=False, help="whether use pre-trained model in transfer learning tasks") # backbone parser.add_argument("--backbone", type=str, default="ResNet1D", choices=["ResNet1D", "ResNet2D", "MLPNet", "CNN1D"]) # if backbone in ("ResNet1D", "CNN1D"), data shape: (batch size, 1, 1024) # elif backbone == "ResNet2D", data shape: (batch size, 3, 32, 32) # elif backbone == "MLPNet", data shape: (batch size, 1024) # optimization & training parser.add_argument("--num_workers", type=int, default=0, help="the number of dataloader workers") parser.add_argument("--batch_size", type=int, default=256) parser.add_argument("--max_epoch", type=int, default=100) parser.add_argument("--lr", type=float, default=1e-3, help="learning rate") parser.add_argument('--lr_scheduler', type=str, default='stepLR', choices=['step', 'exp', 'stepLR', 'fix'], help='the learning rate schedule') parser.add_argument('--gamma', type=float, default=0.8, help='learning rate scheduler parameter for step and exp') parser.add_argument('--steps', type=str, default='30, 120', help='the learning rate decay for step and stepLR') parser.add_argument("--optimizer", type=str, default="adam", choices=["adam", "sgd"]) parser.add_argument("--kernel", type=str, default='Linear', choices=["Linear", "CORAL"]) args = parser.parse_args() return args # ===== Build Model ===== class FeatureNet(nn.Module): def __init__(self, args): super(FeatureNet, self).__init__() if args.backbone == "ResNet1D": self.feature_net = ResNet1D.resnet18() elif args.backbone == "ResNet2D": self.model_ft = models.resnet18(pretrained=True) self.bottleneck = nn.Sequential(nn.Linear(self.model_ft.fc.out_features, 512), nn.ReLU(), nn.Dropout(0.5)) self.feature_net = nn.Sequential(self.model_ft, self.bottleneck) elif args.backbone == "MLPNet": if args.fft: self.feature_net = MLPNet.MLPNet(num_in=512) else: self.feature_net = MLPNet.MLPNet() elif args.backbone == "CNN1D": self.feature_net = CNN1D.CNN1D() else: raise Exception("model not implement") def forward(self, x): logits = self.feature_net(x) return logits class Classifier(nn.Module): def __init__(self, args, num_out=10): super(Classifier, self).__init__() if args.backbone in ("ResNet1D", "ResNet2D"): self.classifier = nn.Sequential(nn.Linear(512,num_out, nn.Dropout(0.5))) if args.backbone in ("MLPNet", "CNN1D"): self.classifier = nn.Sequential(nn.Linear(64,num_out, nn.Dropout(0.5))) def forward(self, logits): outputs = self.classifier(logits) return outputs # ===== Load Data ===== def loaddata(args): if args.source_dataname == "CWRU": source_data, source_label = CWRUloader(args, args.s_load, args.s_label_set) else: raise NotImplementedError("Source dataset {} not implemented.".format(args.source_dataname)) source_data, source_label = np.concatenate(source_data, axis=0), np.concatenate(source_label, axis=0) if args.target_dataname == "CWRU": target_data, target_label = CWRUloader(args, args.t_load, args.t_label_set) else: raise NotImplementedError("Target dataset {} not implemented.".format(args.target_dataname)) target_data, target_label = np.concatenate(target_data, axis=0), np.concatenate(target_label, axis=0) source_loader, _, _ = utils.DataSplite(args, source_data, source_label) target_trainloader, target_valloader, target_testloader = utils.DataSplite(args, target_data, target_label) return source_loader, target_trainloader, target_valloader, target_testloader # ===== Test the Model ===== def tester(featurenet, classifier, dataloader): featurenet.eval() classifier.eval() device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") correct_num, total_num = 0, 0 for i, (x_batch, y_batch) in enumerate(dataloader): x_batch, y_batch = x_batch.to(device), y_batch.to(device) # compute model cotput and loss logtis_batch = featurenet(x_batch) output_batch = classifier(logtis_batch) pre = torch.max(output_batch.cpu(), 1)[1].numpy() y = y_batch.cpu().numpy() correct_num += (pre == y).sum() total_num += len(y) accuracy = (correct_num / total_num) * 100.0 return accuracy # ===== Train the Model ===== def trainer(args): # Consider the gpu or cpu condition if torch.cuda.is_available(): device = torch.device("cuda") device_count = torch.cuda.device_count() logging.info('using {} gpus'.format(device_count)) assert args.batch_size % device_count == 0, "batch size should be divided by device count" else: warnings.warn("gpu is not available") device = torch.device("cpu") device_count = 1 logging.info('using {} cpu'.format(device_count)) # load the dataset source_trainloader, target_trainloader, target_valloader, target_testloader = loaddata(args) # load the model featurenet = FeatureNet(args) classifier = Classifier(args, num_out=len(args.t_label_set)) # load the checkpoint if args.pretrained: if args.backbone != "ResNet2D": # pretrained ResNet2D model is downloaded from torchvision module if not args.fft: path = "./checkpoints/{}_checkpoint.tar".format(args.backbone) else: path = "./checkpoints/{}FFT_checkpoint.tar".format(args.backbone) featurenet.load_state_dict(torch.load(path)) parameter_list = [{"params": featurenet.parameters(), "lr": args.lr}, {"params": classifier.parameters(), "lr": args.lr}] # Define optimizer and learning rate decay optimizer, lr_scheduler = utils.optimizer(args, parameter_list) # define loss function loss_cls = nn.CrossEntropyLoss() if args.kernel == "Linear": loss_dis = MMDLinear.MMDLinear elif args.kernel == "CORAL": loss_dis = CORAL.CORAL_loss else: raise NotImplemented("Kernel {} not implemented.".format(args.kernel)) featurenet.to(device) classifier.to(device) # train best_acc = 0.0 meters = {"acc_source_train":[], "acc_target_train": [], "acc_target_val": []} for epoch in range(args.max_epoch): featurenet.train() classifier.train() with tqdm(total=len(target_trainloader), leave=False) as pbar: for i, ((x_s_batch, y_s_batch), (x_t_batch, y_t_batch)) in enumerate(zip(source_trainloader,target_trainloader)): if len(y_s_batch) != len(y_t_batch): break batch_num = x_s_batch.size(0) inputs = torch.cat((x_s_batch, x_t_batch), dim=0) # move to GPU if available inputs = inputs.to(device) s_labels = y_s_batch.to(device) t_labels = y_t_batch.to(device) # compute model cotput and loss logits = featurenet(inputs) outputs = classifier(logits) classification_loss = loss_cls(outputs.narrow(0, 0, s_labels.size(0)), s_labels.long()) distance_loss = loss_dis(outputs.view(outputs.size(0),-1).narrow(0, 0, s_labels.size(0)),\ outputs.view(outputs.size(0),-1).narrow(0, s_labels.size(0), s_labels.size(0))) loss = classification_loss + distance_loss # clear previous gradients, compute gradients optimizer.zero_grad() loss.backward() # performs updates using calculated gradients optimizer.step() # evaluate # training accuracy acc_source_train = utils.accuracy(outputs.narrow(0, 0, batch_num), s_labels) acc_target_train = utils.accuracy(outputs.narrow(0, batch_num, batch_num), t_labels) pbar.update() # update lr if lr_scheduler is not None: lr_scheduler.step() val_acc = tester(featurenet, classifier, target_valloader) if val_acc > best_acc: best_acc = val_acc if args.savemodel: utils.save_model(featurenet, args) logging.info("Epoch: {:>3}/{}, loss_cls: {:.4f}, loss: {:.4f}, source_train_acc: {:>6.2f}%, target_train_acc: {:>6.2f}%, target_val_acc: {:>6.2f}%".format(\ epoch+1, args.max_epoch, classification_loss, loss, acc_source_train, acc_target_train, val_acc)) meters["acc_source_train"].append(acc_source_train) meters["acc_target_train"].append(acc_target_train) meters["acc_target_val"].append(val_acc) logging.info("Best accuracy: {:.4f}".format(best_acc)) utils.save_log(meters, "./logs/DDC_{}_{}_meters.pkl".format(args.backbone, args.max_epoch)) logging.info("="*15+"Done!"+"="*15) if __name__ == "__main__": args = parse_args() # set the logger if not os.path.exists("./logs"): os.makedirs("./logs") setlogger(args.log_file) # save the args for k, v in args.__dict__.items(): logging.info("{}: {}".format(k, v)) trainer(args) ================================================ FILE: OSDABP.py ================================================ import argparse import os import numpy as np from Utils.logger import setlogger import torch import torch.nn as nn import torch.nn.functional as F import torchvision.models as models from torch.autograd import Function from Backbone import ResNet1D, MLPNet, CNN1D from PreparData.CWRU import CWRUloader import Utils.utils as utils from tqdm import * import warnings import logging # ===== Define argments ===== def parse_args(): parser = argparse.ArgumentParser(description='Implementation of Deep Domain Confusion networks') # task setting parser.add_argument("--log_file", type=str, default="./logs/OSDABP.log", help="log file path") # dataset information parser.add_argument("--datadir", type=str, default="./datasets", help="data directory") parser.add_argument("--source_dataname", type=str, default="CWRU", choices=["CWRU", "PU"], help="choice a dataset") parser.add_argument("--target_dataname", type=str, default="CWRU", choices=["CWRU", "PU"], help="choice a dataset") parser.add_argument("--s_load", type=int, default=3, help="source domain working condition") parser.add_argument("--t_load", type=int, default=2, help="target domain working condition") parser.add_argument("--s_label_set", type=list, default=[0,1,2,3,4,5], help="source domain label set") parser.add_argument("--t_label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="target domain label set") parser.add_argument("--val_rat", type=float, default=0.3, help="training-validation rate") parser.add_argument("--test_rat", type=float, default=0.5, help="validation-test rate") parser.add_argument("--seed", type=int, default="29") # pre-processing parser.add_argument("--fft", type=bool, default=False, help="FFT preprocessing") parser.add_argument("--window", type=int, default=128, help="time window, if not augment data, window=1024") parser.add_argument("--normalization", type=str, default="0-1", choices=["None", "0-1", "mean-std"], help="normalization option") parser.add_argument("--savemodel", type=bool, default=False, help="whether save pre-trained model in the classification task") parser.add_argument("--pretrained", type=bool, default=False, help="whether use pre-trained model in transfer learning tasks") # backbone parser.add_argument("--backbone", type=str, default="ResNet1D", choices=["ResNet1D", "ResNet2D", "MLPNet", "CNN1D"]) # if backbone in ("ResNet1D", "CNN1D"), data shape: (batch size, 1, 1024) # elif backbone == "ResNet2D", data shape: (batch size, 3, 32, 32) # elif backbone == "MLPNet", data shape: (batch size, 1024) # optimization & training parser.add_argument("--num_workers", type=int, default=0, help="the number of dataloader workers") parser.add_argument("--batch_size", type=int, default=256) parser.add_argument("--max_epoch", type=int, default=100) parser.add_argument("--lr", type=float, default=1e-3, help="learning rate") parser.add_argument('--lr_scheduler', type=str, default='stepLR', choices=['step', 'exp', 'stepLR', 'fix'], help='the learning rate schedule') parser.add_argument('--gamma', type=float, default=0.8, help='learning rate scheduler parameter for step and exp') parser.add_argument('--steps', type=str, default='30, 120', help='the learning rate decay for step and stepLR') parser.add_argument("--optimizer", type=str, default="adam", choices=["adam", "sgd"]) args = parser.parse_args() return args # ===== Build Model ===== class GradReverse(Function): @staticmethod def forward(ctx, x): return x.view_as(x) @staticmethod def backward(ctx, grad_output): output = grad_output.neg() return output, None def grad_reverse(x): return GradReverse.apply(x) # define the model class FeatureNet(nn.Module): def __init__(self, args): super(FeatureNet, self).__init__() if args.backbone == "ResNet1D": self.feature_net = ResNet1D.resnet18() elif args.backbone == "ResNet2D": self.model_ft = models.resnet18(pretrained=True) self.bottleneck = nn.Sequential(nn.Linear(self.model_ft.fc.out_features, 512), nn.ReLU(), nn.Dropout(0.2)) self.feature_net = nn.Sequential(self.model_ft, self.bottleneck) elif args.backbone == "MLPNet": self.feature_net = MLPNet.MLPNet() elif args.backbone == "CNN1D": self.feature_net = CNN1D.CNN1D() else: raise Exception("model not implement") def forward(self, x): logits = self.feature_net(x) return logits class Classifier(nn.Module): def __init__(self, args, num_out=10): super(Classifier, self).__init__() if args.backbone in ("ResNet1D", "ResNet2D"): self.classifier = nn.Sequential(nn.Linear(512,num_out, nn.Dropout(0.5))) if args.backbone in ("MLPNet", "CNN1D"): self.classifier = nn.Sequential(nn.Linear(64,num_out, nn.Dropout(0.5))) def forward(self, logits, reverse = False): if reverse: logits = grad_reverse(logits) outputs = self.classifier(logits) return outputs # ===== Load Data ===== def loaddata(args): if args.source_dataname == "CWRU": source_data, source_label = CWRUloader(args, args.s_load, args.s_label_set) source_data, source_label = np.concatenate(source_data, axis=0), np.concatenate(source_label, axis=0) if args.target_dataname == "CWRU": target_data, target_label = CWRUloader(args, args.t_load, args.t_label_set) target_data, target_label = np.concatenate(target_data, axis=0), np.concatenate(target_label, axis=0) source_loader, _, _ = utils.DataSplite(args, source_data, source_label) target_trainloader, target_valloader, target_testloader = utils.DataSplite(args, target_data, target_label) return source_loader, target_trainloader, target_valloader, target_testloader # ===== Define Loss Function ===== def bce_loss(output, target): output_neg = 1 - output target_neg = 1 - target result = torch.mean(target * torch.log(output + 1e-6)) result += torch.mean(target_neg * torch.log(output_neg + 1e-6)) return -torch.mean(result) # ===== Test the Model ===== def tester(featurenet, classifier, dataloader): featurenet.eval() classifier.eval() device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") correct_num, total_num = 0, 0 for i, (x_batch, y_batch) in enumerate(dataloader): x_batch, y_batch = x_batch.to(device), y_batch.to(device) # compute model cotput and loss logtis_batch = featurenet(x_batch) output_batch = classifier(logtis_batch) pre = torch.max(output_batch.cpu(), 1)[1].numpy() y = y_batch.cpu().numpy() correct_num += (pre == y).sum() total_num += len(y) accuracy = (correct_num / total_num) * 100.0 return accuracy # ===== Train the Model ===== def trainer(args): # Consider the gpu or cpu condition if torch.cuda.is_available(): device = torch.device("cuda") device_count = torch.cuda.device_count() logging.info('using {} gpus'.format(device_count)) assert args.batch_size % device_count == 0, "batch size should be divided by device count" else: warnings.warn("gpu is not available") device = torch.device("cpu") device_count = 1 logging.info('using {} cpu'.format(device_count)) # load the dataset source_trainloader, target_trainloader, target_valloader, target_testloader = loaddata(args) num_out = len(args.s_label_set)+1 # load the model featurenet = FeatureNet(args) classifier = Classifier(args, num_out=num_out) # load the checkpoint if args.pretrained: if args.backbone != "ResNet2D": # pretrained ResNet2D model is downloaded from torchvision module if not args.fft: path = "./checkpoints/{}_checkpoint.tar".format(args.backbone) else: path = "./checkpoints/{}FFT_checkpoint.tar".format(args.backbone) featurenet.load_state_dict(torch.load(path)) parameter_list = [{"params": featurenet.parameters(), "lr": args.lr}, {"params": classifier.parameters(), "lr": args.lr}] # Define optimizer and learning rate decay optimizer, lr_scheduler = utils.optimizer(args, parameter_list) ## define loss function criterion = nn.CrossEntropyLoss() featurenet.to(device) classifier.to(device) # train best_acc = 0.0 meters = {"acc_source_train":[], "acc_target_train": [], "acc_target_val": []} for epoch in range(args.max_epoch): featurenet.train() classifier.train() with tqdm(total=len(target_trainloader), leave=False) as pbar: for i, ((x_s_batch, y_s_batch), (x_t_batch, y_t_batch)) in enumerate(zip(source_trainloader,target_trainloader)): if len(y_s_batch) != len(y_t_batch): break batch_num = x_s_batch.size(0) target_funk = torch.FloatTensor(batch_num, 2).fill_(0.5).cuda() # clear previous gradients, compute gradients optimizer.zero_grad() # move to GPU if available x_s = x_s_batch.to(device) x_t = x_t_batch.to(device) y_s = y_s_batch.to(device) y_t = y_t_batch.to(device) # compute model output and loss # source data logits_s = featurenet(x_s) outputs_s = classifier(logits_s) loss_s = criterion(outputs_s, y_s.long()) loss_s.backward() # target data logits_t = featurenet(x_t) outputs_t = classifier(logits_t, reverse=True) outputs_t = F.softmax(outputs_t) prob1 = torch.sum(outputs_t[:, :num_out-1], 1).view(-1, 1) prob2 = outputs_t[:, num_out-1].contiguous().view(-1, 1) prob = torch.cat([prob1, prob2], 1) loss_t = bce_loss(prob, target_funk) loss_t.backward() # performs updates using calculated gradients optimizer.step() # clear previous gradients optimizer.zero_grad() pbar.update() # update lr if lr_scheduler is not None: lr_scheduler.step() # validation featurenet.eval() classifier.eval() correct_num = 0 val_num = 0 per_class_num = np.zeros((num_out)) per_class_correct = np.zeros((num_out)).astype(np.float32) for step, (x_val_batch, y_val_batch) in enumerate(target_valloader): # move to GPU if available x_val= x_val_batch.to(device) y_val = y_val_batch.to(device) batch_size_val = y_val.data.size()[0] logits_val = featurenet(x_val) outputs_val = classifier(logits_val) pre = torch.max(outputs_val.cpu(), 1)[1].numpy() y_val = y_val.cpu().numpy() correct_num += (pre == y_val).sum() # the number of correct preditions per batch val_num += batch_size_val # the number of predictions per batch for i in range(num_out): if i < num_out -1: index = np.where(y_val == i) # known classes else: index = np.where(y_val >= i) # unknown classes # Thanks to @Wang-Dongdong for reporting the bug correct_ind = np.where(pre[index[0]]==i) per_class_correct[i] += float(len(correct_ind[0])) per_class_num[i] += float(len(index[0])) per_class_acc = (per_class_correct / per_class_num) * 100.0 known_acc = (per_class_correct[:-1].sum() / per_class_num[:-1].sum()) * 100.0 all_acc = (correct_num / val_num) * 100.0 if all_acc > best_acc: best_acc = all_acc logging.info("Epoch: {:>3}/{}, loss_s: {:.4f}, loss_t: {:.4f}, all_acc: {:>6.2f}, known_acc: {:>6.2f}%".format(\ epoch+1, args.max_epoch, loss_s, loss_t, all_acc, known_acc)) logging.info("Best all accuracy: {:.4f}".format(best_acc)) logging.info("="*10+"Done!"+"="*10) if __name__ == "__main__": args = parse_args() # set the logger if not os.path.exists("./logs"): os.makedirs("./logs") setlogger(args.log_file) # save the args for k, v in args.__dict__.items(): logging.info("{}: {}".format(k, v)) trainer(args) ================================================ FILE: PreparData/CWRU.py ================================================ """ @Author: Xiaohan Chen @Email: cxh_bb@outlook.com """ import numpy as np from scipy.io import loadmat from PreparData.preprocess import transformation # datanames in every working conditions dataname_dict= {0:[97, 109, 122, 135, 174, 189, 201, 213, 226, 238], # 1797rpm 1:[98, 110, 123, 136, 175, 190, 202, 214, 227, 239], # 1772rpm 2:[99, 111, 124, 137, 176, 191, 203, 215, 228, 240], # 1750rpm 3:[100,112, 125, 138, 177, 192, 204, 217, 229, 241]} # 1730rpm axis = "_DE_time" data_length = 1024 def CWRU(datadir, load, labels, window, normalization, backbone, fft): """ loading the hole dataset """ path = datadir + "/CWRU/" + "Drive_end_" + str(load) + "/" dataset = {label: [] for label in labels} for label in labels: fault_type = dataname_dict[load][label] if fault_type < 100: realaxis = "X0" + str(fault_type) + axis else: realaxis = "X" + str(fault_type) + axis mat_data = loadmat(path+str(fault_type)+".mat")[realaxis] start, end = 0, data_length # set the endpoint of data sequence endpoint = mat_data.shape[0] # split the data and transformation while end < endpoint: sub_data = mat_data[start : end].reshape(-1,) sub_data = transformation(sub_data, fft, normalization, backbone) dataset[label].append(sub_data) start += window end += window dataset[label] = np.array(dataset[label], dtype="float32") return dataset def CWRUloader(args, load, label_set, number="all"): """ args: arguments number: the numbers of training samples, "all" or specific numbers (string type) """ dataset = CWRU(args.datadir, load, label_set, args.window, args.normalization, args.backbone, args.fft) DATA, LABEL = [], [] if number == "all": counter = [] for key in dataset.keys(): counter.append(dataset[key].shape[0]) datan = min(counter) # choosing the min value as the sample size per class for key in dataset.keys(): LABEL.append(np.tile(key, datan)) DATA.append(dataset[key][:datan]) else: datan = int(number) for key in dataset.keys(): LABEL.append(np.tile(key, datan)) DATA.append(dataset[key][:datan]) DATA, LABEL = np.array(DATA, dtype="float32"), np.array(LABEL, dtype="int32") return DATA, LABEL ================================================ FILE: PreparData/__init__.py ================================================ ================================================ FILE: PreparData/preprocess.py ================================================ import numpy as np def transformation(sub_data, fft, normalization, backbone): if fft: sub_data = np.fft.fft(sub_data) sub_data = np.abs(sub_data) / len(sub_data) sub_data = sub_data[:int(sub_data.shape[0] / 2)].reshape(-1,) if normalization == "0-1": sub_data = (sub_data - sub_data.min()) / (sub_data.max() - sub_data.min()) elif normalization == "mean-std": sub_data = (sub_data - sub_data.mean()) / sub_data.std() if backbone in ("ResNet1D", "CNN1D"): sub_data = sub_data[np.newaxis, :] elif backbone == "ResNet2D": n = int(np.sqrt(sub_data.shape[0])) if fft: sub_data = sub_data[:n*n] sub_data = np.reshape(sub_data, (n, n)) sub_data = sub_data[np.newaxis, :] sub_data = np.concatenate((sub_data, sub_data, sub_data), axis=0) return sub_data ================================================ FILE: README.md ================================================ # Deep transfer learing for fault diagnosis ## :book: 1. Introduction This repository contains popular deep transfer learning algorithms implemented via PyTorch for cross-load fault diagnosis transfer tasks, including: - [x] General supervised learning classification task: traing and test apply the same machines, working conditions and faults. - [x] *domain adaptation*: the distribution of the source domain data may be different from the target domain data, but the label set of the target domain is the same as the source domain, i.e., $\mathcal{D} _{s}=(X_s,Y_s)$, $\mathcal{D} _{t}=(X_t,Y_t)$, $X_s \ne X_t$, $Y_s = Y_t$. - [x] **DDC**: Deep Domain Confusion [[arXiv 2014]](https://arxiv.org/pdf/1412.3474.pdf) - [x] **Deep CORAL**: Correlation Alignment for Deep Domain Adaptation [[ECCV 2016]](https://arxiv.org/abs/1607.01719) - [x] **DANN**: Unsupervised Domain Adaptation by Backpropagation [[ICML 2015]](http://proceedings.mlr.press/v37/ganin15.pdf) - [ ] TODO - [x] *Open-set domain adaptation*: the distribution of the source domain data may be different from the target domain data. What's more, the target label set contains unknown categories, i.e., $\mathcal{D} _{s}=(X_s,Y_s)$, $\mathcal{D} _{t}=(X_t,Y_t)$, $X_s \ne X_t$, $Y_s \in Y_t$. We refer to their common categories $\mathcal{Y}_s\cap \mathcal{Y}_t$ as the *known classes*, and $\mathcal{Y}_s\setminus \mathcal{Y}_t$ (or $\mathcal{Y}_t\setminus \mathcal{Y}_s$) in the target domain as the *unknown class*. - [x] **OSDABP**: Open Set Domain Adaptation by Backpropagation [[ECCV 2018]](http://openaccess.thecvf.com/content_ECCV_2018/papers/Kuniaki_Saito_Adversarial_Open_Set_ECCV_2018_paper.pdf) - [ ] TODO > **Few-shot** learning-based bearing fault diagnosis methods please see: https://github.com/Xiaohan-Chen/few-shot-fault-diagnosis ## :balloon: 2. Citation For further introductions to transfer learning in bearing fault diagnosis, please read our [paper](https://ieeexplore.ieee.org/document/10042467). And if you find this repository useful and use it in your works, please cite our paper, thank you~: ``` @ARTICLE{10042467, author={Chen, Xiaohan and Yang, Rui and Xue, Yihao and Huang, Mengjie and Ferrero, Roberto and Wang, Zidong}, journal={IEEE Transactions on Instrumentation and Measurement}, title={Deep Transfer Learning for Bearing Fault Diagnosis: A Systematic Review Since 2016}, year={2023}, volume={72}, number={}, pages={1-21}, doi={10.1109/TIM.2023.3244237}} ``` --- ## :wrench: 3. Requirements - python 3.9.12 - Numpy 1.23.1 - pytorch 1.12.0 - scikit-learn 1.1.1 - torchvision 0.13.0 --- ## :handbag: 4. Dataset Download the bearing dataset from [CWRU Bearing Dataset Center](https://engineering.case.edu/bearingdatacenter/48k-drive-end-bearing-fault-data) and place the `.mat` files in the `./datasets` folder according to the following structure: ``` datasets/ └── CWRU/ ├── Drive_end_0/ │ └── 97.mat 109.mat 122.mat 135.mat 174.mat 189.mat 201.mat 213.mat 226.mat 238.mat ├── Drive_end_1/ │ └── 98.mat 110.mat 123.mat 136.mat 175.mat 190.mat 202.mat 214.mat 227.mat 239.mat ├── Drive_end_2/ │ └── 99.mat 111.mat 124.mat 137.mat 176.mat 191.mat 203.mat 215.mat 228.mat 240.mat └── Drive_end_3/ └── 100.mat 112.mat 125.mat 138.mat 177.mat 192.mat 204.mat 217.mat 229.mat 241.mat ``` --- ## :pencil: 5. Usage > **NOTE**: When using pre-trained models to initialise the backbone and classifier in transfer learning tasks, run classification tasks first to generate corresponding checkpoints. Four typical neural networks are implemented in this repository, including MLP, 1D CNN, 1D ResNet18, and 2D ResNet18(torchvision package). More details can be found in the `./Backbone` folder. **General Supervised Learning Classification:** - Train and test the model on the same machines, working conditions and faults. Use the following commands: ```python python3 classification.py --datadir './datasets' --max_epoch 100 ``` **Transfer Learning:** - If using the DDC transfer learning method, use the following commands: ```python python3 DDC.py --datadir './datasets' --backbone "CNN1D" --pretrained False --kernel 'Linear' ``` - If using the DeepCORAL transfer learning method, use the following commands: ```python python3 DDC.py --datadir './datasets' --backbone "CNN1D" --pretrained False --kernel 'CORAL' ``` - If using the DANN transfer learning method, use following commands: ```python python3 DANN.py --backbone "CNN1D" ``` **Open Set Domain Adaptation:** - The target domain contains unknow classes, use the following commands: ```python python3 OSDABP.py ``` --- ## :flashlight: 6. Results > The following results do not represent the best results. **General Classification task:** Dataset: CWRU Load: 3 Label set: [0,1,2,3,4,5,6,7,8,9] | | MLPNet | CNN1D | ResNet1D | ResNet2D | | :---------------: | :----: | :---: | :------: | :------: | | acc (time domain) | 93.95 | 97.70 | 99.58 | 98.02 | | acc (freq domain) | 99.95 | 99.44 | 100.0 | 99.96 | **Transfer Learning:** Dataset: CWRU Source load: 3 Target Load: 2 Label set: [0,1,2,3,4,5,6,7,8,9] Pre-trained model: True Time domain: | | MLPNet | CNN1D | ResNet1D | ResNet2D | | :-----------------: | :----: | :---: | :------: | :------: | | DDC (linear kernel) | 75.47 | 85.53 | 91.79 | 91.32 | | DeepCORAL | 82.33 | 88.23 | 93.88 | 90.84 | | DANN | 87.68 | 94.77 | 98.88 | 93.95 | Frequency domain | | MLPNet | CNN1D | ResNet1D | ResNet2D | | :-------: | :----: | :---: | :------: | :------: | | DeepCORAL | 98.65 | 98.22 | 99.75 | 99.31 | | DANN | 99.38 | 98.74 | 99.89 | 99.47 | **Open Set Domain Adaptation** - *OSDABP* Dataset: CWRU Source load: 3 Target Load: 2 Source label set: [0,1,2,3,4,5] Target label set: [0,1,2,3,4,5,6,7,8,9] Pre-trained model: True | Label | 0 | 1 | 2 | 3 | 4 | 5 | unk | All | Only known | | :------: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | ----- | ---------- | | MLPNet | 99.83 | 95.96 | 59.76 | 76.10 | 19.85 | 96.58 | 59.21 | 70.21 | 75.99 | | CNN1D | 100.0 | 94.95 | 94.47 | 99.08 | 47.31 | 74.32 | 26.36 | 61.75 | 85.35 | | ResNet1D | 100.0 | 100.0 | 80.14 | 100.0 | 43.32 | 93.49 | 45.22 | 70.04 | 86.58 | | ResNet2D | 100.0 | 100.0 | 94.82 | 100.0 | 18.55 | 98.12 | 53.42 | 72.95 | 85.96 | --- ## :camping: 7. See also - Multi-scale CNN and LSTM bearing fault diagnosis [[paper](https://link.springer.com/article/10.1007/s10845-020-01600-2)][[GitHub](https://github.com/Xiaohan-Chen/baer_fault_diagnosis)] - TFPred self-supervised learning for few labeled fault diagnosis [[Paper](https://www.sciencedirect.com/science/article/pii/S0967066124000601)][[GitHub](https://github.com/Xiaohan-Chen/TFPred)] --- ## :globe_with_meridians: 8. Acknowledgement ``` @article{zhao2021applications, title={Applications of Unsupervised Deep Transfer Learning to Intelligent Fault Diagnosis: A Survey and Comparative Study}, author={Zhibin Zhao and Qiyang Zhang and Xiaolei Yu and Chuang Sun and Shibin Wang and Ruqiang Yan and Xuefeng Chen}, journal={IEEE Transactions on Instrumentation and Measurement}, year={2021} } ``` I would like to thank the following person for contributing to this repository: [@Wang-Dongdong](https://github.com/Wang-Dongdong),[@zhuting233](https://github.com/zhuting233) ================================================ FILE: Utils/__init__.py ================================================ ================================================ FILE: Utils/logger.py ================================================ import logging def setlogger(path): logger = logging.getLogger() logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S") consoleHandler = logging.StreamHandler() fileHandler = logging.FileHandler(filename=path) consoleHandler.setFormatter(formatter) fileHandler.setFormatter(formatter) logger.addHandler(consoleHandler) logger.addHandler(fileHandler) ================================================ FILE: Utils/utils.py ================================================ import logging import torch import pickle import torch.optim as optim from sklearn.model_selection import train_test_split def accuracy(outputs, labels): """ Compute the accuracy outputs, labels: (tensor) return: (float) accuracy in [0, 100] """ pre = torch.max(outputs.cpu(), 1)[1].numpy() y = labels.data.cpu().numpy() acc = ((pre == y).sum() / len(y)) * 100 return acc def save_log(obj, path): with open(path, "wb") as f: pickle.dump(obj, f) def read_pkl(path): with open(path, 'rb') as f: data = pickle.load(f) return data def save_model(model, args): if not args.fft: torch.save(model.state_dict(), "./checkpoints/{}_checkpoint.tar".format(args.backbone)) else: torch.save(model.state_dict(), "./checkpoints/{}FFT_checkpoint.tar".format(args.backbone)) def DataSplite(args, data, label): """ split the data and lebel and transform the narray type to tensor type """ data_train, data_val, label_train, label_val = train_test_split(data, label, test_size = args.val_rat, random_state=args.seed) data_val, data_test, label_val, label_test = train_test_split(data_val, label_val, test_size= args.test_rat) # numpy to tensor data_train = torch.from_numpy(data_train).float() data_val = torch.from_numpy(data_val).float() data_test = torch.from_numpy(data_test).float() label_train = torch.from_numpy(label_train).float() label_val = torch.from_numpy(label_val).float() label_test = torch.from_numpy(label_test).float() # logging the data shape logging.info("training data/label shape: {},{}".format(data_train.size(), label_train.size())) logging.info("validation data/label shape: {},{}".format(data_val.size(), label_val.size())) logging.info("test data/label shape: {},{}".format(data_test.size(), label_test.size())) # build the dataloader train = torch.utils.data.TensorDataset(data_train, label_train) val = torch.utils.data.TensorDataset(data_val, label_val) test = torch.utils.data.TensorDataset(data_test, label_test) train_loader = torch.utils.data.DataLoader(train, batch_size=args.batch_size, \ shuffle=True, num_workers=args.num_workers) val_loader = torch.utils.data.DataLoader(val, batch_size=args.batch_size, \ shuffle=False, num_workers=args.num_workers) test_loader = torch.utils.data.DataLoader(test, batch_size=args.batch_size, \ shuffle=False, num_workers=args.num_workers) return train_loader, val_loader, test_loader def optimizer(args, parameter_list): # define optimizer if args.optimizer == "sgd": optimizer = optim.SGD(parameter_list, lr=args.lr, momentum=0.9, weight_decay=5e-4) elif args.optimizer == "adam": optimizer = optim.Adam(parameter_list, lr=args.lr) else: raise Exception("optimizer not implement") # Define the learning rate decay if args.lr_scheduler == 'step': steps = [int(step) for step in args.steps.split(',')] lr_scheduler = optim.lr_scheduler.MultiStepLR(optimizer, steps, gamma=args.gamma) elif args.lr_scheduler == 'exp': lr_scheduler = optim.lr_scheduler.ExponentialLR(optimizer, args.gamma) elif args.lr_scheduler == 'stepLR': steps = int(args.steps.split(",")[0]) lr_scheduler = optim.lr_scheduler.StepLR(optimizer, steps, args.gamma) elif args.lr_scheduler == 'cos': lr_scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, 20, 0) elif args.lr_scheduler == 'fix': lr_scheduler = None else: raise Exception("lr schedule not implement") return optimizer, lr_scheduler ================================================ FILE: classification.py ================================================ import argparse import os import numpy as np from Utils.logger import setlogger from turtle import forward import torch import torch.nn as nn import torch.optim as optim import torchvision.models as models from Backbone import ResNet1D, MLPNet, CNN1D from PreparData.CWRU import CWRUloader import Utils.utils as utils from tqdm import * import warnings import logging # ===== Define argments ===== def parse_args(): parser = argparse.ArgumentParser(description='classification task') # task setting parser.add_argument("--log_file", type=str, default="./logs/classification.log", help="log file path") # dataset information parser.add_argument("--datadir", type=str, default="./datasets", help="data directory") parser.add_argument("--load", type=int, default=3, help="working condition") parser.add_argument("--label_set", type=list, default=[0,1,2,3,4,5,6,7,8,9], help="label set") parser.add_argument("--val_rat", type=float, default=0.3, help="training-validation rate") parser.add_argument("--test_rat", type=float, default=0.5, help="validation-test rate") parser.add_argument("--seed", type=int, default="29") # pre-processing parser.add_argument("--fft", type=bool, default=False, help="FFT preprocessing") parser.add_argument("--window", type=int, default=128, help="time window, if not augment data, window=1024") parser.add_argument("--normalization", type=str, default="0-1", choices=["None", "0-1", "mean-std"], help="normalization option") parser.add_argument("--savemodel", type=bool, default=False, help="whether save pre-trained model in the classification task") parser.add_argument("--pretrained", type=bool, default=False, help="whether use pre-trained model in transfer learning tasks") # backbone parser.add_argument("--backbone", type=str, default="ResNet1D", choices=["ResNet1D", "ResNet2D", "MLPNet", "CNN1D"]) # if backbone in ("ResNet1D", "CNN1D"), data shape: (batch size, 1, 1024) # elif backbone == "ResNet2D", data shape: (batch size, 3, 32, 32) # elif backbone == "MLPNet", data shape: (batch size, 1024) # optimization & training parser.add_argument("--num_workers", type=int, default=0, help="the number of dataloader workers") parser.add_argument("--batch_size", type=int, default=256) parser.add_argument("--max_epoch", type=int, default=100) parser.add_argument("--lr", type=float, default=1e-3, help="learning rate") parser.add_argument('--lr_scheduler', type=str, default='stepLR', choices=['step', 'exp', 'stepLR', 'fix'], help='the learning rate schedule') parser.add_argument('--gamma', type=float, default=0.8, help='learning rate scheduler parameter for step and exp') parser.add_argument('--steps', type=str, default='30, 120', help='the learning rate decay for step and stepLR') parser.add_argument("--optimizer", type=str, default="adam", choices=["adam", "sgd"]) args = parser.parse_args() return args # ===== Build Model ===== class FeatureNet(nn.Module): def __init__(self, args): super(FeatureNet, self).__init__() if args.backbone == "ResNet1D": self.feature_net = ResNet1D.resnet18() elif args.backbone == "ResNet2D": self.model_ft = models.resnet18(pretrained=True) self.bottleneck = nn.Sequential(nn.Linear(self.model_ft.fc.out_features, 512), nn.ReLU(), nn.Dropout(0.5)) self.feature_net = nn.Sequential(self.model_ft, self.bottleneck) elif args.backbone == "MLPNet": if args.fft: self.feature_net = MLPNet.MLPNet(num_in=512) else: self.feature_net = MLPNet.MLPNet() elif args.backbone == "CNN1D": self.feature_net = CNN1D.CNN1D() else: raise Exception("model not implement") def forward(self, x): logits = self.feature_net(x) return logits class Classifier(nn.Module): def __init__(self, args, num_out=10): super(Classifier, self).__init__() if args.backbone in ("ResNet1D", "ResNet2D"): self.classifier = nn.Sequential(nn.Linear(512,num_out, nn.Dropout(0.5))) if args.backbone in ("MLPNet", "CNN1D"): self.classifier = nn.Sequential(nn.Linear(64,num_out, nn.Dropout(0.5))) def forward(self, logits): outputs = self.classifier(logits) return outputs # ===== Load Data ===== def loaddata(args): data, label = CWRUloader(args, args.load, args.label_set) data, label = np.concatenate(data, axis=0), np.concatenate(label, axis=0) train_loader, val_loader, test_laoder = utils.DataSplite(args, data, label) return train_loader, val_loader, test_laoder # ===== Test the Model ===== def tester(featurenet, classifier, dataloader): featurenet.eval() classifier.eval() device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") correct_num, total_num = 0, 0 for i, (x_batch, y_batch) in enumerate(dataloader): x_batch, y_batch = x_batch.to(device), y_batch.to(device) # compute model cotput and loss logtis_batch = featurenet(x_batch) output_batch = classifier(logtis_batch) pre = torch.max(output_batch.cpu(), 1)[1].numpy() y = y_batch.cpu().numpy() correct_num += (pre == y).sum() total_num += len(y) accuracy = (correct_num / total_num) * 100.0 return accuracy # ===== Train the Model ===== def trainer(args): # Consider the gpu or cpu condition if torch.cuda.is_available(): device = torch.device("cuda") device_count = torch.cuda.device_count() logging.info('using {} gpus'.format(device_count)) assert args.batch_size % device_count == 0, "batch size should be divided by device count" else: warnings.warn("gpu is not available") device = torch.device("cpu") device_count = 1 logging.info('using {} cpu'.format(device_count)) # load the dataset trainloader, valloader, testloader = loaddata(args) # load the model featurenet = FeatureNet(args) classifier = Classifier(args, num_out=len(args.label_set)) parameter_list = [{"params": featurenet.parameters(), "lr": args.lr}, {"params": classifier.parameters(), "lr": args.lr}] # Define optimizer and learning rate decay optimizer, lr_scheduler = utils.optimizer(args, parameter_list) # define loss function loss_fn = nn.CrossEntropyLoss() featurenet.to(device) classifier.to(device) # train best_acc = 0.0 meters = {"acc_train": [], "acc_val": []} for epoch in range(args.max_epoch): featurenet.train() classifier.train() with tqdm(total=len(trainloader), leave=False) as pbar: for i, (x_batch, y_batch) in enumerate(trainloader): # move to GPU if available x_batch, y_batch = x_batch.to(device), y_batch.to(device) # compute model cotput and loss logtis_batch = featurenet(x_batch) output_batch = classifier(logtis_batch) loss = loss_fn(output_batch, y_batch.long()) # clear previous gradients, compute gradients optimizer.zero_grad() loss.backward() # performs updates using calculated gradients optimizer.step() # evaluate # training accuracy train_acc = utils.accuracy(output_batch, y_batch) pbar.update() # update lr if lr_scheduler is not None: lr_scheduler.step() val_acc = tester(featurenet, classifier, valloader) if val_acc > best_acc: best_acc = val_acc if args.savemodel: utils.save_model(featurenet, args) logging.info("Epoch: {:>3}/{}, loss: {:.4f}, train_acc: {:>6.2f}%, val_acc: {:>6.2f}%".format(\ epoch+1, args.max_epoch, loss, train_acc, val_acc)) meters["acc_train"].append(train_acc) meters["acc_val"].append(val_acc) logging.info("Best accuracy: {:.4f}%".format(best_acc)) utils.save_log(meters, "./logs/cls_{}_{}_meters.pkl".format(args.backbone, args.max_epoch)) logging.info("="*15+"Done!"+"="*15) if __name__ == "__main__": args = parse_args() # set the logger if not os.path.exists("./logs"): os.makedirs("./logs") setlogger(args.log_file) # save the pre-trained model if not os.path.exists("./checkpoints"): os.makedirs("./checkpoints") # save the args for k, v in args.__dict__.items(): logging.info("{}: {}".format(k, v)) trainer(args) ================================================ FILE: loss/CORAL.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import torch """ Created on Saturday Feb 25 2020 @authors: Alan Preciado, Santosh Muthireddy """ def CORAL_loss(source, target): """ From the paper, the vectors that compose Ds and Dt are D-dimensional vectors :param source: torch tensor: source data (Ds) with dimensions DxNs :param target: torch tensor: target data (Dt) with dimensons DxNt """ d = source.size(1) # d-dimensional vectors (same for source, target) source_covariance = compute_covariance(source) target_covariance = compute_covariance(target) # take Frobenius norm (https://pytorch.org/docs/stable/torch.html) loss = torch.norm(torch.mul((source_covariance-target_covariance), (source_covariance-target_covariance)), p="fro") # loss = torch.norm(torch.mm((source_covariance-target_covariance), # (source_covariance-target_covariance)), p="fro") loss = loss/(4*d*d) return loss def compute_covariance(data): """ Compute covariance matrix for given dataset as shown in paper (eqs 2 and 3). :param data: torch tensor: input source/target data """ # data dimensions: nxd (this for Ns or Nt) n = data.size(0) # get batch size #print("compute covariance bath size n:", n) # check gpu or cpu support if data.is_cuda: device = torch.device("cuda") else: device = torch.device("cpu") # proper matrix multiplication for right side of equation (2) ones_vector = torch.ones(n).resize(1, n).to(device=device) # 1xN dimensional vector (transposed) one_onto_D = torch.mm(ones_vector, data) mult_right_terms = torch.mm(one_onto_D.t(), one_onto_D) mult_right_terms = torch.div(mult_right_terms, n) # element-wise divison # matrix multiplication for left side of equation (2) mult_left_terms = torch.mm(data.t(), data) covariance_matrix= 1/(n-1) * torch.add(mult_left_terms,-1*(mult_right_terms)) return covariance_matrix ================================================ FILE: loss/MKMMD.py ================================================ # source code: https://github.com/ZhaoZhibin/UDTL (it seems the link is not the original source of the MK-MMD code) # Params: # source: source data # target: target data # kernel_mul: # kernel_num: the number of kernel # fix_sigma: import torch def guassian_kernel(source, target, kernel_mul=2.0, kernel_num=5, fix_sigma=None): n_samples = int(source.size()[0])+int(target.size()[0]) # the number of source+target total = torch.cat([source, target], dim=0) total0 = total.unsqueeze(0).expand(int(total.size(0)), int(total.size(0)), int(total.size(1))) total1 = total.unsqueeze(1).expand(int(total.size(0)), int(total.size(0)), int(total.size(1))) L2_distance = ((total0-total1)**2).sum(2) if fix_sigma: bandwidth = fix_sigma else: bandwidth = torch.sum(L2_distance.data) / (n_samples**2-n_samples) bandwidth /= kernel_mul ** (kernel_num // 2) bandwidth_list = [bandwidth * (kernel_mul**i) for i in range(kernel_num)] kernel_val = [torch.exp(-L2_distance / bandwidth_temp) for bandwidth_temp in bandwidth_list] return sum(kernel_val)#/len(kernel_val) def MKMMD(source, target, kernel_mul=2.0, kernel_num=5, fix_sigma=None): batch_size = int(source.size()[0]) kernels = guassian_kernel(source, target, kernel_mul=kernel_mul, kernel_num=kernel_num, fix_sigma=fix_sigma) XX = kernels[:batch_size, :batch_size] YY = kernels[batch_size:, batch_size:] XY = kernels[:batch_size, batch_size:] YX = kernels[batch_size:, :batch_size] loss = torch.mean(XX + YY - XY - YX) return loss ================================================ FILE: loss/MMDLinear.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import torch """ Created on Sunday 22 Mar 2020 @authors: Alan Preciado, Santosh Muthireddy """ def MMDLinear(source_activation, target_activation): """ From the paper, the loss used is the maximum mean discrepancy (MMD) :param source: torch tensor: source data (Ds) with dimensions DxNs :param target: torch tensor: target data (Dt) with dimensons DxNt """ diff_domains = source_activation - target_activation loss = torch.mean(torch.mm(diff_domains, torch.transpose(diff_domains, 0, 1))) return loss ================================================ FILE: loss/__init__.py ================================================