├── 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 |
4 |
5 |
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 | [](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 |
4 |
5 |
6 |
43 |
44 |
45 |
52 |
53 |
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 |
--------------------------------------------------------------------------------