├── data └── __init__.py ├── model ├── __init__.py ├── build_net.py ├── layers_sub.py ├── BaseNet.py └── layers.py ├── tool ├── __init__.py └── utils.py ├── datasets ├── __init__.py └── dataset_load.py ├── .idea ├── vcs.xml ├── .gitignore ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml ├── misc.xml ├── BPD.iml └── deployment.xml ├── README.md ├── LICENSE └── bpd_main.py /data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tool/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /datasets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/BPD.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /model/build_net.py: -------------------------------------------------------------------------------- 1 | from .BaseNet import * 2 | 3 | 4 | def Generator(config=None): 5 | if config.back_net == 'cnn': 6 | return Feature_CNN(config) 7 | elif config.back_net == 'convlstmv2': 8 | return Feature_ConvLSTMv2(config) 9 | 10 | 11 | def Disentangler(config=None): 12 | return Feature_disentangle(config) 13 | 14 | 15 | def Reconstructor(config=None): 16 | return Reconstructor_Net(config) 17 | 18 | 19 | def Mine(config=None): 20 | return Mine_Net(config) 21 | 22 | 23 | def Classifier(config=None): 24 | return Predictor_Net(config) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BPD [[arXiv](https://arxiv.org/abs/2202.07260)] | [[ACM](https://dl.acm.org/doi/abs/10.1145/3517252)] 2 | [![paper](https://img.shields.io/badge/arXiv-Paper-.svg)](https://arxiv.org/abs/2202.07260) 3 | 4 | Official pytorch implementation of "Learning Disentangled Behaviour Patterns for Wearable-based Human Activity 5 | Recognition". (Ubicomp 2022) 6 | 7 | Learning Disentangled Behaviour Patterns for Wearable-based Human Activity Recognition (accepted at Proceedings of the 8 | ACM on Interactive, Mobile, Wearable and Ubiquitous Technologies, 2022) aims to solve the intra-class variability 9 | challenge in Human activity recognition community. The proposed Behaviour Pattern Disentanglement (BPD) framework can 10 | disentangle the behavior patterns from the irrelevant noises such as personal styles or environmental noises, etc. 11 | 12 | This code provides an implementation for BPD. This repository is implemented using PyTorch and it includes code for 13 | running experiments on GOTOV, Pamap2, DSADS, MHEALTH datasets. 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54 | -------------------------------------------------------------------------------- /tool/utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn.functional as F 3 | 4 | 5 | def _ent(out): 6 | return - torch.mean(torch.log(F.softmax(out + 1e-6, dim=-1))) 7 | 8 | 9 | def _discrepancy(out1, out2): 10 | return torch.mean(torch.abs(F.softmax(out1, dim=-1) - F.softmax(out2, dim=-1))) 11 | 12 | 13 | def _l2_rec(src, trg): 14 | return torch.sum((src - trg) ** 2) / (src.shape[0] * src.shape[1]) 15 | 16 | 17 | def str2bool(v): 18 | if v.lower() in ('yes', 'true', 't', 'y', '1'): 19 | return True 20 | elif v.lower() in ('no', 'false', 'f', 'n', '0'): 21 | return False 22 | 23 | 24 | import numpy as np 25 | from numpy.lib.stride_tricks import as_strided as ast 26 | 27 | 28 | def norm_shape(shape): 29 | ''' 30 | Normalize numpy array shapes so they're always expressed as a tuple, 31 | even for one-dimensional shapes. 32 | Parameters 33 | shape - an int, or a tuple of ints 34 | Returns 35 | a shape tuple 36 | ''' 37 | try: 38 | i = int(shape) 39 | return (i,) 40 | except TypeError: 41 | # shape was not a number 42 | pass 43 | 44 | try: 45 | t = tuple(shape) 46 | return t 47 | except TypeError: 48 | # shape was not iterable 49 | pass 50 | 51 | raise TypeError('shape must be an int, or a tuple of ints') 52 | 53 | 54 | def sliding_window(a, ws, ss=None, flatten=True): 55 | ''' 56 | Return a sliding window over a in any number of dimensions 57 | Parameters: 58 | a - an n-dimensional numpy array 59 | ws - an int (a is 1D) or tuple (a is 2D or greater) representing the size 60 | of each dimension of the window 61 | ss - an int (a is 1D) or tuple (a is 2D or greater) representing the 62 | amount to slide the window in each dimension. If not specified, it 63 | defaults to ws. 64 | flatten - if True, all slices are flattened, otherwise, there is an 65 | extra dimension for each dimension of the input. 66 | Returns 67 | an array containing each n-dimensional window from a 68 | ''' 69 | 70 | if None is ss: 71 | # ss was not provided. the windows will not overlap in any direction. 72 | ss = ws 73 | ws = norm_shape(ws) 74 | ss = norm_shape(ss) 75 | 76 | # convert ws, ss, and a.shape to numpy arrays so that we can do math in every 77 | # dimension at once. 78 | ws = np.array(ws) 79 | ss = np.array(ss) 80 | shape = np.array(a.shape) 81 | 82 | # ensure that ws, ss, and a.shape all have the same number of dimensions 83 | ls = [len(shape), len(ws), len(ss)] 84 | if 1 != len(set(ls)): 85 | raise ValueError( \ 86 | 'a.shape, ws and ss must all have the same length. They were %s' % str(ls)) 87 | 88 | # ensure that ws is smaller than a in every dimension 89 | if np.any(ws > shape): 90 | raise ValueError( \ 91 | 'ws cannot be larger than a in any dimension.\ 92 | a.shape was %s and ws was %s' % (str(a.shape), str(ws))) 93 | 94 | # how many slices will there be in each dimension? 95 | newshape = norm_shape(((shape - ws) // ss) + 1) 96 | # the shape of the strided array will be the number of slices in each dimension 97 | # plus the shape of the window (tuple addition) 98 | newshape += norm_shape(ws) 99 | # the strides tuple will be the array's strides multiplied by step size, plus 100 | # the array's strides (tuple addition) 101 | newstrides = norm_shape(np.array(a.strides) * ss) + a.strides 102 | strided = ast(a, shape=newshape, strides=newstrides) 103 | if not flatten: 104 | return strided 105 | 106 | # Collapse strided so that it has one more dimension than the window. I.e., 107 | # the new array is a flat list of slices. 108 | meat = len(ws) if ws.shape else 0 109 | firstdim = (np.product(newshape[:-meat]),) if ws.shape else () 110 | dim = firstdim + (newshape[-meat:]) 111 | # remove any dimensions with size 1 112 | # dim = filter(lambda i : i != 1,dim) 113 | return strided.reshape(dim) 114 | 115 | 116 | def get_sample_weights(y, weights): 117 | ''' 118 | to assign weights to each sample 119 | ''' 120 | label_unique = np.unique(y) 121 | sample_weights = [] 122 | for val in y: 123 | idx = np.where(label_unique == val) 124 | sample_weights.append(weights[idx]) 125 | return sample_weights 126 | 127 | 128 | def opp_sliding_window_w_d(data_x, data_y, d, ws, ss): # window size, step size 129 | data_x = sliding_window(data_x, (ws, data_x.shape[1]), (ss, 1)) 130 | data_y = np.asarray([[i[-1]] for i in sliding_window(data_y, ws, ss)]) 131 | data_d = np.asarray([[i[-1]] for i in sliding_window(d, ws, ss)]) 132 | return data_x.astype(np.float32), data_y.reshape(len(data_y)).astype(np.uint8), data_d.reshape(len(data_d)).astype( 133 | np.uint8) 134 | -------------------------------------------------------------------------------- /model/layers_sub.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import math 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | from torch.autograd import Variable 8 | 9 | 10 | class PositionalEncoding_old(nn.Module): 11 | "Implement the PE function." 12 | 13 | def __init__(self, d_model, dropout=0.2, max_len=168): 14 | super(PositionalEncoding_old, self).__init__() 15 | self.dropout = nn.Dropout(p=dropout) 16 | 17 | # Compute the positional encodings once in log space. 18 | pe = torch.zeros(max_len, d_model) 19 | position = torch.arange(0., max_len).unsqueeze(1) 20 | div_term = torch.exp(torch.arange(0., d_model, 2) * 21 | -(math.log(10000.0) / d_model)) 22 | pe[:, 0::2] = torch.sin(position * div_term) 23 | pe[:, 1::2] = torch.cos(position * div_term) 24 | pe = pe.unsqueeze(0) 25 | self.register_buffer('pe', pe) 26 | 27 | def forward(self, x): 28 | x = x + Variable(self.pe[:, :x.size(1)], 29 | requires_grad=False) 30 | return self.dropout(x) 31 | 32 | 33 | class PositionwiseFeedForward(nn.Module): 34 | "Implements FFN equation." 35 | 36 | def __init__(self, d_model, d_ff, dropout=0.1): 37 | super(PositionwiseFeedForward, self).__init__() 38 | self.w_1 = nn.Linear(d_model, d_ff) 39 | self.w_2 = nn.Linear(d_ff, d_model) 40 | self.dropout = nn.Dropout(dropout) 41 | 42 | def forward(self, x): 43 | return self.w_2(self.dropout(F.relu(self.w_1(x)))) 44 | 45 | 46 | from torch.autograd import Function 47 | 48 | 49 | class ReverseLayerF(Function): 50 | 51 | @staticmethod 52 | def forward(ctx, x, alpha): 53 | ctx.alpha = alpha 54 | 55 | return x.view_as(x) 56 | 57 | @staticmethod 58 | def backward(ctx, grad_output): 59 | output = grad_output.neg() * ctx.alpha 60 | return output, None 61 | 62 | 63 | class SublayerConnection(nn.Module): 64 | """ 65 | A residual connection followed by a layer norm. 66 | Note for code simplicity the norm is first as opposed to last. 67 | """ 68 | 69 | def __init__(self, size, dropout): 70 | super(SublayerConnection, self).__init__() 71 | self.norm = nn.LayerNorm(size) 72 | self.dropout = nn.Dropout(dropout) 73 | 74 | def forward(self, x, sublayer): 75 | "Apply residual connection to any sublayer with the same size." 76 | return self.dropout(x + self.norm(sublayer(x))) 77 | # return x + self.dropout(sublayer(self.norm(x))) 78 | # return self.dropout(sublayer(x)) 79 | # return self.dropout(sublayer(x)) 80 | 81 | 82 | class MultiHeadedAttention(nn.Module): 83 | def __init__(self, d_model, dropout): 84 | super(MultiHeadedAttention, self).__init__() 85 | self.d_model = d_model 86 | 87 | self.d_k = 32 88 | self.h = d_model // 32 89 | self.linears = clones(nn.Linear(d_model, d_model), 4) 90 | self.dropout = nn.Dropout(dropout) 91 | self.att_weights = None 92 | 93 | def forward(self, q, k, v): 94 | 95 | bs = q.size(0) 96 | q, k, v = \ 97 | [l(x).view(bs, -1, self.h, self.d_k).transpose(1, 2) 98 | for l, x in zip(self.linears, (q, k, v))] 99 | output, self.att_weights = self.attention(q, k, v) 100 | output = output.transpose(1, 2).contiguous() \ 101 | .view(bs, -1, self.h * self.d_k) 102 | output_att = self.linears[-1](output) 103 | return output_att 104 | 105 | def attention(self, q, k, v): 106 | scores = torch.matmul(q, k.transpose(-2, -1)) \ 107 | / math.sqrt(self.d_k) 108 | 109 | scores = F.softmax(scores, dim=-1) 110 | 111 | return torch.matmul(scores, v), scores 112 | 113 | def continuous_penalty(self): 114 | win_len = self.att_weights.size(-1) 115 | # bs * heads * win_len * win_len 116 | scores = self.att_weights.view(-1, win_len, win_len) 117 | penalty = 0 118 | for i in range(scores.size(-1) - 1): 119 | t_s = torch.abs(scores[:, :, i] - scores[:, :, i + 1]) 120 | penalty += (torch.sum(t_s) / (scores.size(0) * scores.size(1))) 121 | return penalty 122 | 123 | def continuous_sample_wise_penalty(self): 124 | win_len = self.att_weights.size(-1) 125 | # bs * heads * win_len * win_len 126 | scores = self.att_weights.view(-1, win_len, win_len) 127 | penalty = 0 128 | for i in range(scores.size(-2) - 1): 129 | t_s = torch.abs(scores[:, i, :] - scores[:, i + 1, :]) 130 | penalty += (torch.sum(t_s) / (scores.size(0) * scores.size(1))) 131 | return penalty 132 | 133 | def get_plot_data(self): 134 | return self.att_weights 135 | 136 | 137 | def clones(module, N): 138 | "Produce N identical layers." 139 | return nn.ModuleList([copy.deepcopy(module) for _ in range(N)]) 140 | -------------------------------------------------------------------------------- /model/BaseNet.py: -------------------------------------------------------------------------------- 1 | from model.layers import * 2 | 3 | 4 | def init_weights(layer): 5 | """Init weights for layers w.r.t. the original paper.""" 6 | layer_name = layer.__class__.__name__ 7 | if layer_name.find("Conv") != -1: 8 | layer.weight.data.normal_(0.0, 0.02) 9 | elif layer_name.find("BatchNorm") != -1: 10 | layer.weight.data.normal_(1.0, 0.02) 11 | elif type(layer) == nn.Linear: 12 | layer.weight.data.normal_(0.0, 1e-4) 13 | 14 | 15 | class CNN(nn.Module): 16 | def __init__(self, config): 17 | super(CNN, self).__init__() 18 | 19 | self.cnn = nn.Sequential( 20 | nn.Conv2d(config.input_dim, 64, kernel_size=(5, 1)), 21 | nn.MaxPool2d((2, 1)), 22 | nn.ReLU(), 23 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 24 | nn.MaxPool2d((2, 1)), 25 | nn.ReLU(), 26 | nn.Conv2d(64, 64, kernel_size=(3, 1)), 27 | nn.ReLU(), 28 | ) 29 | 30 | self.classifier = nn.Sequential( 31 | nn.Linear(config.output_dim, 128), 32 | nn.ReLU(), 33 | nn.Dropout(0.5), 34 | nn.Linear(128, config.cls_num), 35 | ) 36 | 37 | def forward(self, x): 38 | x = x.unsqueeze(dim=3) # input size: (batch_size, channel, win, 1) 39 | x = self.cnn(x) 40 | x = x.reshape(x.size(0), -1) 41 | feature = x 42 | x = self.classifier(x) 43 | return feature, x 44 | # return x 45 | 46 | 47 | class ConvLSTMv1(nn.Module): 48 | def __init__(self, config): 49 | super(ConvLSTMv1, self).__init__() 50 | 51 | self.features = nn.Sequential( 52 | nn.Conv2d(config.input_dim, 64, kernel_size=(5, 1)), 53 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 54 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 55 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 56 | ) 57 | 58 | self.lstm = nn.LSTM(64, 128, 2, batch_first=True) 59 | self.dropout = nn.Dropout() 60 | self.classifier = nn.Linear(128, config.cls_num) 61 | 62 | def forward(self, x): 63 | x = x.unsqueeze(dim=3) # input size: (batch_size, channel, win, 1) 64 | x = self.features(x) # [b, 64 , h , w] 65 | x = x.view(x.shape[0], -1, 64) 66 | x, _ = self.lstm(x) 67 | x = self.dropout(x) 68 | x = x[:, -1, :] 69 | x = x.view(x.shape[0], 128) 70 | x = self.classifier(x) 71 | 72 | return x 73 | 74 | 75 | class ConvLSTMv2(nn.Module): 76 | def __init__(self, config): 77 | super(ConvLSTMv2, self).__init__() 78 | 79 | self.features = nn.Sequential( 80 | nn.Conv2d(config.input_dim, 64, kernel_size=(5, 1)), 81 | nn.MaxPool2d((2, 1)), 82 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 83 | nn.MaxPool2d((2, 1)), 84 | nn.Conv2d(64, 32, kernel_size=(3, 1)), 85 | ) 86 | 87 | self.lstm = nn.LSTM(32, 128, 1, batch_first=True) 88 | self.dropout = nn.Dropout(0.5) 89 | self.classifier = nn.Linear(128, config.cls_num) 90 | 91 | def forward(self, x): 92 | x = x.unsqueeze(dim=3) # input size: (batch_size, channel, win, 1) 93 | x = self.features(x) # [b, 64 , h , w] 94 | x = x.view(x.shape[0], -1, 32) 95 | x, _ = self.lstm(x) 96 | x = self.dropout(x) 97 | x = x[:, -1, :] 98 | x = x.view(x.shape[0], 128) 99 | x = self.classifier(x) 100 | 101 | return x 102 | 103 | 104 | class Feature_CNN(nn.Module): 105 | def __init__(self, config): 106 | super(Feature_CNN, self).__init__() 107 | 108 | self.cnn = nn.Sequential( 109 | nn.Conv2d(config.input_dim, 64, kernel_size=(5, 1)), 110 | nn.MaxPool2d((2, 1)), 111 | nn.ReLU(), 112 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 113 | nn.MaxPool2d((2, 1)), 114 | nn.ReLU(), 115 | nn.Conv2d(64, 64, kernel_size=(3, 1)), 116 | nn.ReLU(), 117 | ) 118 | 119 | def forward(self, x): 120 | x = x.unsqueeze(dim=3) # input size: (batch_size, channel, win, 1) 121 | x = self.cnn(x) 122 | x = x.reshape(x.size(0), -1) 123 | return x 124 | 125 | 126 | class Feature_ConvLSTMv2(nn.Module): 127 | def __init__(self, config): 128 | super(Feature_ConvLSTMv2, self).__init__() 129 | 130 | self.features = nn.Sequential( 131 | nn.Conv2d(config.input_dim, 64, kernel_size=(5, 1)), 132 | nn.MaxPool2d((2, 1)), 133 | nn.Conv2d(64, 64, kernel_size=(5, 1)), 134 | nn.MaxPool2d((2, 1)), 135 | nn.Conv2d(64, 32, kernel_size=(3, 1)), 136 | ) 137 | 138 | self.lstm = nn.LSTM(32, 128, 1, batch_first=True) 139 | self.dropout = nn.Dropout(0.5) 140 | 141 | def forward(self, x): 142 | x = x.unsqueeze(dim=3) # input size: (batch_size, channel, win, 1) 143 | x = self.features(x) # [b, 64 , h , w] 144 | x = x.view(x.shape[0], -1, 32) 145 | x, _ = self.lstm(x) 146 | x = self.dropout(x) 147 | x = x[:, -1, :] 148 | x = x.view(x.shape[0], 128) 149 | 150 | return x 151 | 152 | 153 | class Feature_disentangle(nn.Module): 154 | def __init__(self, config): 155 | super(Feature_disentangle, self).__init__() 156 | self.fc1 = nn.Linear(config.output_dim, int(config.output_dim / 4)) 157 | self.bn1_fc = nn.BatchNorm1d(int(config.output_dim / 4)) 158 | 159 | def forward(self, x): 160 | x = F.relu(self.bn1_fc(self.fc1(x))) 161 | return x 162 | 163 | 164 | class Feature_discriminator(nn.Module): 165 | def __init__(self, config): 166 | super(Feature_discriminator, self).__init__() 167 | self.fc1 = nn.Linear(int(config.output_dim / 4), 2) 168 | 169 | def forward(self, x): 170 | x = F.leaky_relu(self.fc1(x), 0.2) 171 | return x 172 | 173 | 174 | class Reconstructor_Net(nn.Module): 175 | def __init__(self, config): 176 | super(Reconstructor_Net, self).__init__() 177 | self.fc = nn.Linear(int(config.output_dim / 2), config.output_dim) 178 | 179 | def forward(self, x): 180 | x = self.fc(x) 181 | return x 182 | 183 | 184 | class Mine_Net(nn.Module): 185 | def __init__(self, config): 186 | super(Mine_Net, self).__init__() 187 | self.fc1_x = nn.Linear(int(config.output_dim / 4), int(config.output_dim / 8)) 188 | self.fc1_y = nn.Linear(int(config.output_dim / 4), int(config.output_dim / 8)) 189 | self.fc2 = nn.Linear(int(config.output_dim / 8), 1) 190 | 191 | def forward(self, x, y): 192 | h1 = F.leaky_relu(self.fc1_x(x) + self.fc1_y(y)) 193 | h2 = self.fc2(h1) 194 | return h2 195 | 196 | 197 | class Predictor_Net(nn.Module): 198 | def __init__(self, config): 199 | super(Predictor_Net, self).__init__() 200 | self.classifier = nn.Sequential( 201 | nn.Linear(int(config.output_dim / 4), config.cls_num) 202 | ) 203 | 204 | def forward(self, x): 205 | x = self.classifier(x) 206 | return x 207 | -------------------------------------------------------------------------------- /model/layers.py: -------------------------------------------------------------------------------- 1 | from model.layers_sub import * 2 | 3 | 4 | class LstmStatefulLayer(nn.Module): 5 | def __init__(self, d_input, config, contain_ln=True, bi_direction=False, hidden_state=None): 6 | super().__init__() 7 | self.config = config 8 | 9 | self.lstm_cell = nn.LSTMCell(d_input, self.config.d_hidden) 10 | self.bi_lstm_cell = nn.LSTMCell(d_input, self.config.d_hidden) 11 | 12 | self.hidden_state = hidden_state 13 | self.bi_hidden_state = None 14 | self.hidden_reuse_overlap = None 15 | self._hidden_layer = None 16 | 17 | self.dropout = nn.Dropout(config.dp_ratio) 18 | self.layer_norm = nn.LayerNorm(config.d_hidden) 19 | 20 | self.dropout_bi = nn.Dropout(config.dp_ratio) 21 | self.layer_norm_bi = nn.LayerNorm(config.d_hidden) 22 | 23 | self.contain_ln = contain_ln 24 | self.bi_direction = bi_direction 25 | 26 | self.dropout_connect = nn.Dropout(0.1) 27 | if bi_direction: 28 | self.bi_decay = nn.Parameter(torch.arange(1, 0.2, -(0.8 / 30))) 29 | self.out = nn.Linear(config.d_hidden * 2, config.d_hidden, bias=False) 30 | 31 | def init_hidden(self, batch_size): 32 | self.hidden_state = (torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float), 33 | torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float)) 34 | self.bi_hidden_state = (torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float), 35 | torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float)) 36 | 37 | def reuse_hidden(self): 38 | # self.init_hidden(self.hidden_state[0].size(0)) 39 | batch_size = self.hidden_state[0].size(0) 40 | self.hidden_state = (self.hidden_state[0].clone().detach(), 41 | self.hidden_state[1].clone().detach()) 42 | self.bi_hidden_state = (torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float), 43 | torch.zeros(batch_size, self.config.d_hidden).type(self.config.type_float)) 44 | 45 | def reuse_hidden_overlap(self): 46 | self.hidden_state = (self.hidden_reuse_overlap[0].clone().detach(), 47 | self.hidden_reuse_overlap[1].clone().detach()) 48 | 49 | def get_hidden_layer(self): 50 | return self._hidden_layer.clone().detach() 51 | 52 | def clear_hidden(self): 53 | self.hidden_state = None 54 | self.bi_hidden_state = None 55 | 56 | def forward(self, inputs): 57 | batch_size = inputs.size(0) 58 | win_len = inputs.size(1) 59 | 60 | if self.hidden_state is None: 61 | self.init_hidden(batch_size) 62 | elif self.config.isOverlap is False: 63 | self.reuse_hidden() 64 | else: 65 | self.reuse_hidden_overlap() 66 | 67 | outputs_hidden = [] 68 | # enumerating through lstm 69 | for t in range(win_len): 70 | if self.config.is_reverse: 71 | t = win_len - 1 - t 72 | input_t = inputs[:, t, :] 73 | # self.hidden_state = self.lstm_cell(input_t, (self.dropout_connect(self.hidden_state[0]), self.hidden_state[1])) 74 | self.hidden_state = self.lstm_cell(input_t, self.hidden_state) 75 | # drop out 76 | # self.hidden_state = (self.dropout(self.hidden_state[0]), self.hidden_state[1]) 77 | # if overlapping, re-use hidden state from middle 78 | if (self.config.isOverlap is True) and t == (win_len / 2) - 1: 79 | self.hidden_reuse_overlap = self.hidden_state 80 | # add layer-norm 81 | if self.contain_ln: 82 | # outputs_hidden.append(self.layer_norm(self.hidden_state[0])) 83 | outputs_hidden.append(self.layer_norm(self.dropout(self.hidden_state[0]))) 84 | else: 85 | outputs_hidden.append(self.dropout(self.hidden_state[0])) 86 | 87 | outputs_hidden = torch.stack(outputs_hidden, 1) 88 | 89 | if self.bi_direction and not self.config.is_reverse: 90 | # self.bi_hidden_state = self.hidden_state 91 | outputs_bi_hidden = [] 92 | for t in range(win_len - 1, -1, -1): 93 | input_t = inputs[:, t, :] 94 | self.bi_hidden_state = self.bi_lstm_cell(input_t, self.bi_hidden_state) 95 | 96 | # self.bi_hidden_state = (self.dropout_bi(self.bi_hidden_state[0]), self.bi_hidden_state[1]) 97 | 98 | outputs_bi_hidden.append(self.layer_norm_bi(self.dropout(self.bi_hidden_state[0]))) 99 | outputs_bi_hidden = torch.stack(outputs_bi_hidden, 1) 100 | # outputs_bi_hidden = outputs_bi_hidden*self.bi_decay[None,:,None] 101 | 102 | outputs_hidden = torch.cat((outputs_hidden, outputs_bi_hidden), 2) 103 | 104 | if self.config.isOverlap: 105 | self._hidden_layer = outputs_hidden[:, :(win_len // 2) - 1, :] 106 | else: 107 | # self._hidden_layer = outputs_hidden[:, (win_len // 2):, :] 108 | self._hidden_layer = outputs_hidden 109 | return outputs_hidden 110 | 111 | 112 | class SelfAttentionLayer(nn.Module): 113 | def __init__(self, d_model, dropout=0.1, pe=True): 114 | super(SelfAttentionLayer, self).__init__() 115 | self.d_model = d_model 116 | self.is_pe = pe 117 | self.pe = PositionalEncoding_old(d_model) 118 | self.self_att = MultiHeadedAttention(d_model, dropout) 119 | self.sublayer = clones(SublayerConnection(d_model, dropout), 2) 120 | self.feedForward = PositionwiseFeedForward(d_model, d_model * 4) 121 | 122 | self.norm = nn.LayerNorm(d_model) 123 | self.dropout = nn.Dropout(dropout) 124 | self.dropout_1 = nn.Dropout(dropout) 125 | 126 | def forward(self, x): 127 | x_or = x 128 | 129 | x = self.pe(x) 130 | if self.is_pe: 131 | x_or = x 132 | att_outputs = self.self_att(x, x, x) 133 | f_outputs = self.feedForward(self.dropout_1(att_outputs)) 134 | return x_or + self.dropout(f_outputs) 135 | 136 | 137 | # class AdversaryLayer(nn.Module): 138 | # def __init__(self, d_model, dropout=0.1, pe=True): 139 | 140 | ## Channel wise attention(CA) layer 141 | class CALayer(nn.Module): 142 | def __init__(self, config): 143 | super(CALayer, self).__init__() 144 | self.config = config 145 | # BxSxA -> Bx1xA 146 | self.avg_pool = nn.AdaptiveAvgPool2d((1, self.config.input_dim)) 147 | # Bx1xA -> Bx1xH Dimension reduction 148 | self.fc_1 = nn.Linear(in_features=self.config.input_dim, out_features=self.config.d_reduction, bias=True) 149 | # Bx1xH -> Bx1xA Dimension increasing 150 | self.fc_2 = nn.Linear(in_features=self.config.d_reduction, out_features=self.config.input_dim, bias=True) 151 | self.act_t = nn.Tanh() 152 | self.act_s = nn.Softmax(dim=1) 153 | 154 | def forward(self, x): 155 | # Transfer to channel wise pooling 156 | # Pooling function BxSxA -> Bx1xA 157 | out = self.avg_pool(x) 158 | # Bx1xA -> Bx1xH 159 | out = self.act_t(self.fc_1(out)) 160 | # Bx1xH -> Bx1xA 161 | out = self.act_s(self.fc_2(out)) 162 | 163 | return x * out 164 | -------------------------------------------------------------------------------- /datasets/dataset_load.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import torch.utils.data as data 4 | from scipy.io import loadmat 5 | 6 | from tool.utils import sliding_window 7 | 8 | base_dir = '../data/' 9 | pamap2_dir = 'PAMAP2_Dataset/Processed/' 10 | dsads_dir = 'DSADS_Dataset/Processed/' 11 | gotov_dir = 'GOTOV_Dataset/Processed/' 12 | mhealth_dir = 'MHEALTH_Dataset/Processed/' 13 | 14 | PAMAP2_DATA_FILES = ['subject_101', 15 | 'subject_102', 16 | 'subject_103', 17 | 'subject_104', 18 | 'subject_105', 19 | 'subject_106', 20 | 'subject_107', 21 | 'subject_108'] 22 | 23 | MHEALTH_DATA_FILES = ['subject_1', 24 | 'subject_2', 25 | 'subject_3', 26 | 'subject_4', 27 | 'subject_5', 28 | 'subject_6', 29 | 'subject_7', 30 | 'subject_8', 31 | 'subject_9', 32 | 'subject_10'] 33 | 34 | DSADS_DATA_FILE = ['subject_1', 35 | 'subject_2', 36 | 'subject_3', 37 | 'subject_4', 38 | 'subject_5', 39 | 'subject_6', 40 | 'subject_7', 41 | 'subject_8'] 42 | 43 | GOTOV_DATA_FILE = ['GOTOV02', 'GOTOV03', 'GOTOV04', 'GOTOV05', 'GOTOV06', 'GOTOV07', 'GOTOV08', 44 | 'GOTOV09', 'GOTOV10', 'GOTOV11', 'GOTOV12', 'GOTOV13', 'GOTOV14', 'GOTOV15', 'GOTOV16', 45 | 'GOTOV17', 'GOTOV18', 'GOTOV19', 'GOTOV20', 'GOTOV21', 'GOTOV22', 'GOTOV23', 'GOTOV24', 46 | 'GOTOV25', 'GOTOV26', 'GOTOV27', 'GOTOV28', 'GOTOV29', 'GOTOV30', 'GOTOV31', 'GOTOV32', 47 | 'GOTOV33', 'GOTOV34', 'GOTOV35', 'GOTOV36'] 48 | 49 | GOTOV_DATA_FILE_Train = ['GOTOV02', 'GOTOV03', 'GOTOV04', 'GOTOV05', 'GOTOV06', 'GOTOV07', 'GOTOV08', 50 | 'GOTOV09', 'GOTOV10', 'GOTOV11', 'GOTOV12', 'GOTOV13', 'GOTOV14', 'GOTOV15', 'GOTOV16', 51 | 'GOTOV17', 'GOTOV18', 'GOTOV19', 'GOTOV20', 'GOTOV21', 'GOTOV22', 'GOTOV23', 'GOTOV24', 52 | 'GOTOV25', 'GOTOV26', 'GOTOV27', 'GOTOV28', 'GOTOV29'] 53 | 54 | GOTOV_DATA_FILE_Test = ['GOTOV30', 'GOTOV31', 'GOTOV32', 'GOTOV33', 'GOTOV34', 'GOTOV35', 'GOTOV36'] 55 | 56 | 57 | def load_pamap2(candidate_number): 58 | global target_train_label, target_train, target_test, target_test_label 59 | target_user = candidate_number 60 | candidate_list = PAMAP2_DATA_FILES 61 | 62 | train_X = np.empty((0, 52)) 63 | train_d = np.empty((0)) 64 | train_y = np.empty((0)) 65 | 66 | for i in range(0, len(candidate_list)): 67 | candidate = loadmat(base_dir + pamap2_dir + candidate_list[i]) 68 | if (i + 1) == target_user: 69 | test_X = candidate["data"] 70 | test_y = candidate["label"].reshape(-1) 71 | test_d = np.ones(test_y.shape) * i 72 | else: 73 | train_X = np.vstack((train_X, candidate["data"])) 74 | train_y = np.concatenate((train_y, candidate["label"].reshape(-1))) 75 | train_d = np.concatenate((train_d, np.ones(train_y.shape) * i)) 76 | 77 | print('pamap2 test user ->', target_user) 78 | print('pamap2 train X shape ->', train_X.shape) 79 | print('pamap2 train y shape ->', train_y.shape) 80 | print('pamap2 test X shape ->', test_X.shape) 81 | print('pamap2 test y shape ->', test_y.shape) 82 | 83 | return train_X, train_y, train_d, test_X, test_y, test_d 84 | 85 | 86 | def load_mhealth(candidate_number): 87 | global target_train_label, target_train, target_test, target_test_label 88 | target_user = candidate_number 89 | candidate_list = MHEALTH_DATA_FILES 90 | 91 | train_X = np.empty((0, 23)) 92 | test_X = np.empty((0, 23)) 93 | train_d = np.empty((0)) 94 | train_y = np.empty((0)) 95 | test_y = np.empty((0)) 96 | 97 | for i in range(0, len(candidate_list)): 98 | candidate = loadmat(base_dir + mhealth_dir + candidate_list[i]) 99 | if (i + 1) == target_user: 100 | test_X = candidate["data"] 101 | test_y = candidate["label"].reshape(-1) 102 | test_d = np.ones(test_y.shape) * i 103 | else: 104 | train_X = np.vstack((train_X, candidate["data"])) 105 | train_y = np.concatenate((train_y, candidate["label"].reshape(-1))) 106 | train_d = np.concatenate((train_d, np.ones(train_y.shape) * i)) 107 | 108 | print('mhealth test user ->', target_user) 109 | print('mhealth train X shape ->', train_X.shape) 110 | print('mhealth train y shape ->', train_y.shape) 111 | print('mhealth test X shape ->', test_X.shape) 112 | print('mhealth test y shape ->', test_y.shape) 113 | 114 | return train_X, train_y, train_d, test_X, test_y, test_d 115 | 116 | 117 | def load_dsads(candidate_number): 118 | global target_train_label, target_train, target_test, target_test_label 119 | target_user = candidate_number 120 | candidate_list = DSADS_DATA_FILE 121 | 122 | train_X = np.empty((0, 45)) 123 | test_X = np.empty((0, 45)) 124 | train_d = np.empty((0)) 125 | train_y = np.empty((0)) 126 | test_y = np.empty((0)) 127 | 128 | for i in range(0, len(candidate_list)): 129 | candidate = loadmat(base_dir + dsads_dir + candidate_list[i]) 130 | if (i + 1) == target_user: 131 | test_X = candidate["data"] 132 | test_y = candidate["label"].reshape(-1) 133 | test_d = np.ones(test_y.shape) * i 134 | else: 135 | train_X = np.vstack((train_X, candidate["data"])) 136 | train_y = np.concatenate((train_y, candidate["label"].reshape(-1))) 137 | train_d = np.concatenate((train_d, np.ones(train_y.shape) * i)) 138 | 139 | print('dsads test user ->', target_user) 140 | print('dsads train X shape ->', train_X.shape) 141 | print('dsads train y shape ->', train_y.shape) 142 | print('dsads test X shape ->', test_X.shape) 143 | print('dsads test y shape ->', test_y.shape) 144 | 145 | return train_X, train_y, train_d, test_X, test_y, test_d 146 | 147 | 148 | def load_gotov(candidate_number, position): 149 | global target_train_label, target_train, target_test, target_test_label 150 | target_user = candidate_number 151 | candidate_list = GOTOV_DATA_FILE 152 | position = position 153 | 154 | train_X = np.empty((0, 3)) 155 | test_X = np.empty((0, 3)) 156 | train_d = np.empty((0)) 157 | train_y = np.empty((0)) 158 | test_y = np.empty((0)) 159 | 160 | for i in range(0, len(candidate_list)): 161 | candidate = loadmat(base_dir + gotov_dir + candidate_list[i]) 162 | if (i + 1) == target_user: 163 | test_X = candidate[position + '_x'] 164 | test_y = candidate[position + '_y'].reshape(-1) 165 | test_d = np.ones(test_y.shape) * i 166 | else: 167 | train_X = np.vstack((train_X, candidate[position + '_x'])) 168 | train_y = np.concatenate((train_y, candidate[position + '_y'].reshape(-1))) 169 | train_d = np.concatenate((train_d, np.ones(train_y.shape) * i)) 170 | 171 | print('gotov test user ->', target_user) 172 | print('gotov train X shape ->', train_X.shape) 173 | print('gotov train y shape ->', train_y.shape) 174 | print('gotov test X shape ->', test_X.shape) 175 | print('gotov test y shape ->', test_y.shape) 176 | 177 | return train_X, train_y, train_d, test_X, test_y, test_d 178 | 179 | 180 | class Dataset(data.Dataset): 181 | def __init__(self, data, label, domain, win_len=168, step_len=32, dim=None): 182 | self.data = data 183 | self.label = label 184 | self.domain = domain 185 | self.window_len = win_len 186 | self.step_len = step_len 187 | self.dim = dim 188 | self.slide_X = sliding_window(self.data, (self.window_len, data.shape[1]), (self.step_len, 1)) 189 | self.slide_y = np.asarray([[i[-1]] for i in sliding_window(self.label, self.window_len, self.step_len)]) 190 | self.slide_d = np.asarray([[i[-1]] for i in sliding_window(self.domain, self.window_len, self.step_len)]) 191 | self.slide_X = self.slide_X.reshape((-1, self.window_len, self.dim)) 192 | self.slide_y = self.slide_y.reshape(len(self.slide_y)) 193 | self.slide_d = self.slide_d.reshape(len(self.slide_d)) 194 | 195 | def __getitem__(self, index): 196 | X = self.slide_X[index] 197 | y = self.slide_y[index] 198 | d = self.slide_d[index] 199 | 200 | return X.astype(np.float32), y.astype(np.uint8), d.astype(np.uint8) 201 | 202 | def __len__(self): 203 | return len(self.slide_X) 204 | 205 | 206 | class DataLoader(): 207 | def initialize(self, data, label, domain, batch_size=64, win_len=168, step_len=32, dim=None): 208 | dataset = Dataset(data, label, domain, win_len, step_len, dim) 209 | 210 | self.data_loader = torch.utils.data.DataLoader( 211 | dataset, 212 | batch_size=batch_size, 213 | shuffle=True, 214 | num_workers=5) 215 | 216 | def load_data(self): 217 | return self.data_loader 218 | 219 | 220 | def dataset_read(dataset, batch_size, dim, candidate, position=None): 221 | if dataset == 'pamap2': 222 | tr_X, tr_y, tr_d, te_X, te_y, te_d = load_pamap2(candidate) 223 | elif dataset == 'mhealth': 224 | tr_X, tr_y, tr_d, te_X, te_y, te_d = load_mhealth(candidate) 225 | elif dataset == 'dsads': 226 | tr_X, tr_y, tr_d, te_X, te_y, te_d = load_dsads(candidate) 227 | elif dataset == 'gotov': 228 | tr_X, tr_y, tr_d, te_X, te_y, te_d = load_gotov(candidate, position) 229 | train_loader = DataLoader() 230 | train_loader.initialize(data=tr_X, label=tr_y, domain=tr_d, batch_size=batch_size, dim=dim) 231 | test_loader = DataLoader() 232 | test_loader.initialize(data=te_X, label=te_y, domain=te_d, batch_size=batch_size, dim=dim) 233 | 234 | dataset_train, dataset_test = train_loader.load_data(), test_loader.load_data() 235 | 236 | return [dataset_train], dataset_test 237 | 238 | 239 | def dataset_selection(args): 240 | source_loaders, target_loader = dataset_read(args.dataset, args.batch_size, args.input_dim, int(args.target_domain), 241 | args.position) 242 | 243 | return source_loaders, target_loader 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /bpd_main.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os 4 | import sys 5 | 6 | print(os.getcwd()) 7 | sys.path.append(os.getcwd()) 8 | sys.path.append('/share/home/litaotao/JS/Disentangle_HAR_Server') 9 | print(sys.path) 10 | 11 | import argparse 12 | from time import gmtime, strftime 13 | 14 | import numpy as np 15 | import torch 16 | import torch.nn as nn 17 | import torch.optim as optim 18 | from sklearn.metrics import f1_score 19 | from torch.utils.tensorboard import SummaryWriter 20 | from tqdm import tqdm 21 | 22 | from datasets.dataset_load import dataset_selection 23 | from model.build_net import Disentangler, Generator, Classifier, Reconstructor, Mine 24 | from tool.utils import _ent, _l2_rec 25 | 26 | # Training settings 27 | parser = argparse.ArgumentParser(description='PyTorch BPD Implementation') 28 | parser.add_argument('--type_float', default=torch.FloatTensor) 29 | parser.add_argument('--type_long', default=torch.LongTensor) 30 | 31 | # Required training parameter 32 | parser.add_argument('--max_epoch', type=int, metavar='N', help='maximum training epochs', required=True) 33 | parser.add_argument('--dataset', type=str, metavar='N', help='selected dataset', required=True) 34 | parser.add_argument('--input_dim', type=int, help='input dimension of backend net~(feature dim of dataset)', 35 | required=True) 36 | parser.add_argument('--output_dim', type=int, help='output dimension of backend net', required=True) 37 | parser.add_argument('--cls_num', type=int, help='total activity class number', required=True) 38 | parser.add_argument('--back_net', type=str, help='backend net', required=True) 39 | parser.add_argument('--gpu', type=int, metavar='S', help='gpu device', required=True) 40 | parser.add_argument('--lr', type=float, metavar='LR', help='learning rate (default: 0.0002)', required=True) 41 | parser.add_argument('--target_domain', type=str, help='the target domain', required=True) 42 | 43 | # Required dataset parameter 44 | parser.add_argument('--win_len', type=int, default=30) 45 | parser.add_argument('--n_domains', type=int, default=4, help='number of total domains actually') 46 | parser.add_argument('--n_target_domains', type=int, default=1, help='number of target domains') 47 | parser.add_argument('--position', type=str, default='wrist', help='position for the gotov dataset only') 48 | 49 | # Default parameter for net 50 | parser.add_argument('--batch_size', type=int, default=64, metavar='N', 51 | help='input batch size for training (default: 64)') 52 | parser.add_argument('--optimizer', type=str, default='adam', metavar='N', help='which optimizer') 53 | parser.add_argument('--seed', type=int, default=10, metavar='S', help='random seed (default: 1)') 54 | parser.add_argument('--checkpoint_dir', type=str, default='checkpoint', metavar='N', help='source only or not') 55 | parser.add_argument('--eval_only', type=int, default=0, help='evaluation only option') 56 | parser.add_argument('--exp_name', type=str, default='cnn', metavar='N') 57 | parser.add_argument('--use_cuda', action='store_true', default=True, help='Use cuda or not') 58 | 59 | args = parser.parse_args() 60 | torch.manual_seed(args.seed) 61 | if torch.cuda.is_available(): 62 | torch.cuda.manual_seed(args.seed) 63 | torch.cuda.set_device(args.gpu) 64 | args.type_float = torch.cuda.FloatTensor 65 | args.type_long = torch.cuda.LongTensor 66 | print(args) 67 | 68 | args.runing_directory = os.path.dirname(os.getcwd()) 69 | print(args.runing_directory) 70 | 71 | 72 | def main(): 73 | if args.back_net == 'cnn': 74 | args.exp_name = 'bpd_cnn' 75 | elif args.back_net == 'convlstmv2': 76 | args.exp_name = 'bpd_convlstmv2' 77 | 78 | if args.eval_only == 0: 79 | args.eval_only = False 80 | elif args.eval_only == 1: 81 | args.eval_only = True 82 | 83 | # loading dataset 84 | source_loaders, target_loader = dataset_selection(args) 85 | compose_dataset = [source_loaders, target_loader] 86 | 87 | # create solver object 88 | solver = Solver(args, batch_size=args.batch_size, candidate=args.target_domain, 89 | dataset=args.dataset, win_len=args.win_len, learning_rate=args.lr, 90 | optimizer=args.optimizer, checkpoint_dir=args.checkpoint_dir, data=compose_dataset) 91 | 92 | # start training 93 | for epoch in range(args.max_epoch): 94 | solver.train_epoch(epoch) 95 | if epoch % 1 == 0: 96 | solver.test(epoch) 97 | if epoch >= args.max_epoch: 98 | break 99 | 100 | 101 | class Solver(): 102 | def __init__(self, args, batch_size, candidate, dataset, win_len, learning_rate, 103 | interval=1, optimizer='adam', checkpoint_dir=None, data=None): 104 | 105 | timestring = strftime("%Y-%m-%d_%H-%M-%S", gmtime()) + "_%s" % args.exp_name + "_%s" % str( 106 | args.target_domain) + "_%s" % str(args.seed) 107 | 108 | self.dim = args.input_dim 109 | self.global_f1 = 0 110 | self.logdir = os.path.join('./logs', dataset, timestring) 111 | self.logger = SummaryWriter(log_dir=self.logdir) 112 | self.device = torch.device("cuda" if args.use_cuda else "cpu") 113 | self.result = [] 114 | self.result_csv = self.logdir + str(args.max_epoch) + '_' + str(args.batch_size) \ 115 | + '_' + args.back_net + '_' + str(args.lr) + '.csv' 116 | 117 | self.class_num = args.cls_num 118 | self.back_net = args.back_net 119 | 120 | self.dataset = dataset 121 | self.candidate = candidate 122 | self.win_len = win_len 123 | self.batch_size = batch_size 124 | self.checkpoint_dir = checkpoint_dir 125 | self.lr = learning_rate 126 | self.mi_coeff = 0.0001 127 | self.interval = interval 128 | self.mi_k = 1 129 | 130 | [self.source_loaders, self.target_loader] = data 131 | 132 | self.G = Generator(config=args) 133 | self.R = Reconstructor(config=args) 134 | self.MI = Mine(config=args) 135 | 136 | self.C = nn.ModuleDict({ 137 | 'ai': Classifier(config=args), 138 | 'ni': Classifier(config=args), 139 | }) 140 | 141 | self.D = nn.ModuleDict({ 142 | 'ai': Disentangler(config=args), 143 | 'ni': Disentangler(config=args) 144 | }) 145 | 146 | self.modules = nn.ModuleDict({ 147 | 'G': self.G, 148 | 'R': self.R, 149 | 'MI': self.MI 150 | }) 151 | 152 | if args.eval_only: 153 | self.G.load_state_dict(torch.load( 154 | os.path.join(self.checkpoint_dir, str(self.dataset) + '_' + self.back_net + '_' + str(self.candidate), 155 | str(self.dataset) + '-' + str(self.candidate) + '-bpd' + "-best-G.pt"))) 156 | self.D.load_state_dict(torch.load( 157 | os.path.join(self.checkpoint_dir, str(self.dataset) + '_' + self.back_net + '_' + str(self.candidate), 158 | str(self.dataset) + '-' + str(self.candidate) + '-bpd' + "-best-D.pt"))) 159 | self.C.load_state_dict(torch.load( 160 | os.path.join(self.checkpoint_dir, str(self.dataset) + '_' + self.back_net + '_' + str(self.candidate), 161 | str(self.dataset) + '-' + str(self.candidate) + '-bpd' + "-best-C.pt"))) 162 | self.xent_loss = nn.CrossEntropyLoss().cuda() 163 | self.adv_loss = nn.BCEWithLogitsLoss().cuda() 164 | self.set_optimizer(which_opt=optimizer, lr=learning_rate) 165 | self.to_device() 166 | 167 | def to_device(self): 168 | for k, v in self.modules.items(): 169 | self.modules[k] = v.cuda() 170 | 171 | for k, v in self.C.items(): 172 | self.C[k] = v.cuda() 173 | 174 | for k, v in self.D.items(): 175 | self.D[k] = v.cuda() 176 | 177 | def set_optimizer(self, which_opt='adam', lr=0.001, momentum=0.9): 178 | self.opt = { 179 | 'C_ai': optim.Adam(self.C['ai'].parameters(), lr=lr, weight_decay=5e-4), 180 | 'C_ni': optim.Adam(self.C['ni'].parameters(), lr=lr, weight_decay=5e-4), 181 | 'D_ai': optim.Adam(self.D['ai'].parameters(), lr=lr, weight_decay=5e-4), 182 | 'D_ni': optim.Adam(self.D['ni'].parameters(), lr=lr, weight_decay=5e-4), 183 | 'G': optim.Adam(self.G.parameters(), lr=lr, weight_decay=5e-4), 184 | 'R': optim.Adam(self.R.parameters(), lr=lr, weight_decay=5e-4), 185 | 'MI': optim.Adam(self.MI.parameters(), lr=lr, weight_decay=5e-4), 186 | } 187 | 188 | def reset_grad(self): 189 | for _, opt in self.opt.items(): 190 | opt.zero_grad() 191 | 192 | def mi_estimator(self, x, y, y_): 193 | joint, marginal = self.MI(x, y), self.MI(x, y_) 194 | return torch.mean(joint) - torch.log(torch.mean(torch.exp(marginal))) 195 | 196 | def group_opt_step(self, opt_keys): 197 | for k in opt_keys: 198 | self.opt[k].step() 199 | self.reset_grad() 200 | 201 | def optimize_classifier(self, data, label, epoch): 202 | feat = self.G(data) 203 | _loss = dict() 204 | for key in ['ai', 'ni']: 205 | _loss['class_' + key] = self.xent_loss( 206 | self.C[key](self.D[key](feat)), label) 207 | 208 | _sum_loss = sum([l for _, l in _loss.items()]) 209 | _sum_loss.backward() 210 | self.group_opt_step(['G', 'C_ai', 'C_ni', 'D_ai', 'D_ni']) 211 | return _loss 212 | 213 | def mutual_information_minimizer(self, feature): 214 | for i in range(0, self.mi_k): 215 | activity, noise = self.D['ai'](self.G(feature)), self.D['ni'](self.G(feature)) 216 | noise_shuffle = torch.index_select( 217 | noise, 0, torch.randperm(noise.shape[0]).to(self.device)) 218 | MI_act_noise = self.mi_estimator(activity, noise, noise_shuffle) 219 | MI = 0.25 * (MI_act_noise) * self.mi_coeff 220 | MI.backward() 221 | self.group_opt_step(['D_ai', 'D_ni', 'MI']) 222 | 223 | def class_confusion(self, data): 224 | # - adversarial training 225 | # f_ci = CI(G(im)) extracts features that are class irrelevant 226 | # by maximizing the entropy, given that the classifier is fixed 227 | loss = _ent(self.C['ni'](self.D['ni'](self.G(data)))) 228 | loss.backward() 229 | self.group_opt_step(['D_ni', 'G']) 230 | return loss 231 | 232 | def optimize_rec(self, data): 233 | feature = self.G(data) 234 | feat_ai, feat_ni = self.D['ai'](feature), self.D['ni'](feature) 235 | rec_feat = self.R(torch.cat([feat_ai, feat_ni], 1)) 236 | recon_loss = _l2_rec(rec_feat, feature) 237 | recon_loss.backward() 238 | self.group_opt_step(['D_ai', 'D_ni', 'R']) 239 | return recon_loss 240 | 241 | def train_epoch(self, epoch): 242 | # set training 243 | for k in self.modules.keys(): 244 | self.modules[k].train() 245 | for k in self.C.keys(): 246 | self.C[k].train() 247 | for k in self.D.keys(): 248 | self.D[k].train() 249 | 250 | # Load training set and testing set with LOSO setting 251 | pbar_descr_prefix = "Epoch %d" % (epoch) 252 | with tqdm(total=10000, ncols=80, dynamic_ncols=False, 253 | desc=pbar_descr_prefix) as pbar: 254 | for source_loader in self.source_loaders: 255 | for batch_idx, (data, label, domain) in enumerate(source_loader): 256 | data = torch.FloatTensor(data.float()).permute(0, 2, 1).to(self.device) 257 | label = label.long().to(self.device) 258 | self.reset_grad() 259 | # ================================== # 260 | class_loss = self.optimize_classifier(data, label, epoch) 261 | self.mutual_information_minimizer(data) 262 | confusion_loss = self.class_confusion(data) 263 | recon_loss = self.optimize_rec(data) 264 | self.logger.add_scalar("confusion_loss", confusion_loss.detach().cpu().numpy(), 265 | global_step=batch_idx) 266 | self.logger.add_scalar("rec_loss", recon_loss.detach().cpu().numpy(), global_step=batch_idx) 267 | # ================================== # 268 | if (batch_idx + 1) % self.interval == 0: 269 | # ================================== # 270 | for key, val in class_loss.items(): 271 | self.logger.add_scalar( 272 | "class_loss/%s" % key, val, 273 | global_step=batch_idx) 274 | 275 | pbar.update() 276 | 277 | def test(self, epoch): 278 | """ 279 | compute the accuracy over the supervised training set or the testing set 280 | """ 281 | # set evaluation modal for Generator, Disentangler, and Classifer 282 | self.G.eval() 283 | self.D['ai'].eval() 284 | self.C['ai'].eval() 285 | 286 | size = 0 287 | correct_aa, correct_an, correct_nn = 0, 0, 0 288 | y_true = [] 289 | y_pre_aa = [] 290 | 291 | with torch.no_grad(): 292 | for batch_idx, (data, label, _) in enumerate(self.target_loader): 293 | # Get loaded dataset 294 | data, label = data.float().permute(0, 2, 1).to(self.device), label.long().to(self.device) 295 | # ouput extracted feature from generator 296 | feat = self.G(data) 297 | # prediction result from C['ai']->D['ai'] 298 | pre_aa = self.C['ai'](self.D['ai'](feat)) 299 | # append result to the corresponding list 300 | y_pre_aa.append(pre_aa.view(-1, self.class_num)) 301 | y_true.append(label.view(-1)) 302 | # calculate correctness of predict label and count number 303 | correct_aa += pre_aa.data.max(1)[1].eq(label.data).cpu().sum() 304 | size += label.data.size()[0] 305 | 306 | # concatenate all prediction 307 | y_pre_aa = torch.cat(y_pre_aa, 0) 308 | y_true = torch.cat(y_true, 0) 309 | # calculate f1 socre for the activity recognition on target domain 310 | f1 = f1_score(y_true.cpu(), y_pre_aa.cpu().max(dim=-1)[1], average='macro') 311 | # calculate acc for aa/an and nn 312 | acc_aa = 100. * correct_aa / size 313 | # If the f1 socre is higher than the global f1 score, then store it 314 | f1_sc = f1 315 | if f1_sc > self.global_f1: 316 | self.global_f1 = f1_sc 317 | torch.save(self.G.state_dict(), 318 | os.path.join(self.logdir, 319 | str(self.dataset) + '-' + str(self.candidate) + "-bpd-best-G.pt")) 320 | torch.save(self.D.state_dict(), 321 | os.path.join(self.logdir, 322 | str(self.dataset) + '-' + str(self.candidate) + "-bpd-best-D.pt")) 323 | torch.save(self.C.state_dict(), 324 | os.path.join(self.logdir, 325 | str(self.dataset) + '-' + str(self.candidate) + "-bpd-best-C.pt")) 326 | 327 | print('\nTest set||aa_acc: {:.2f}%||aa_F1 score: {:.2f}% \n'.format(acc_aa, f1_sc * 100)) 328 | 329 | self.result.append([acc_aa, f1_sc, self.global_f1, epoch]) 330 | result_np = np.array(self.result, dtype=float) 331 | np.savetxt(self.result_csv, result_np, fmt='%.4f', delimiter=',') 332 | 333 | # add scalar to tensorboard 334 | self.logger.add_scalar( 335 | "test_target_acc/acc", acc_aa, 336 | global_step=epoch) 337 | 338 | self.logger.add_scalar( 339 | "test_target_acc/F1_score", f1_sc, 340 | global_step=epoch) 341 | 342 | 343 | if __name__ == '__main__': 344 | main() 345 | --------------------------------------------------------------------------------