412 lines
15 KiB
Python
412 lines
15 KiB
Python
# -*- encoding:utf-8 -*-
|
||
|
||
'''
|
||
@Author : dingjiawen
|
||
@Date : 2023/11/15 14:44
|
||
@Usage :
|
||
@Desc :
|
||
'''
|
||
import torch
|
||
import torch.nn as nn
|
||
from RUL.otherIdea.adaRNN.loss_transfer import TransferLoss
|
||
import torch.nn.functional as F
|
||
from RUL.baseModel.dctChannelAttention import dct_channel_block
|
||
|
||
import torch.nn as nn
|
||
import torch
|
||
from RUL.baseModel.dctAttention import dct_channel_block
|
||
|
||
|
||
class dctLSTMCell(nn.Module):
|
||
|
||
def __init__(self, input_dim, hidden_dim, bias):
|
||
"""
|
||
Initialize ConvLSTM cell.
|
||
|
||
Parameters
|
||
----------
|
||
input_dim: int
|
||
Number of channels of input tensor.
|
||
hidden_dim: int
|
||
Number of channels of hidden state.
|
||
kernel_size: int
|
||
Size of the convolutional kernel.
|
||
bias: bool
|
||
Whether or not to add the bias.
|
||
|
||
Input:
|
||
A tensor of size B, T, C
|
||
B: bacth_size
|
||
T: timestamp
|
||
C: channel
|
||
"""
|
||
|
||
super(dctLSTMCell, self).__init__()
|
||
|
||
self.input_dim = input_dim
|
||
self.hidden_dim = hidden_dim
|
||
self.bias = bias
|
||
|
||
self.hidden = nn.Linear(in_features=self.input_dim + self.hidden_dim,
|
||
out_features=4 * self.hidden_dim,
|
||
bias=self.bias)
|
||
self.attention = dct_channel_block(channel=self.input_dim + self.hidden_dim)
|
||
|
||
def forward(self, input_tensor, cur_state):
|
||
# shape :b,c
|
||
h_cur, c_cur = cur_state
|
||
|
||
combined = torch.cat([input_tensor, h_cur], dim=-1) # concatenate along channel axis
|
||
# 增加一个channelAttention
|
||
combined = self.attention(combined)
|
||
combined_linear = self.hidden(combined)
|
||
cc_i, cc_f, cc_o, cc_g = torch.split(combined_linear, self.hidden_dim, dim=-1)
|
||
i = torch.sigmoid(cc_i)
|
||
f = torch.sigmoid(cc_f)
|
||
o = torch.sigmoid(cc_o)
|
||
g = torch.tanh(cc_g)
|
||
|
||
c_next = f * c_cur + i * g
|
||
h_next = o * torch.tanh(c_next)
|
||
|
||
return h_next, c_next
|
||
|
||
def init_hidden(self, batch_size):
|
||
return (torch.zeros(batch_size, self.hidden_dim, device=self.hidden.weight.device),
|
||
torch.zeros(batch_size, self.hidden_dim, device=self.hidden.weight.device))
|
||
|
||
|
||
class LSTM(nn.Module):
|
||
"""
|
||
|
||
Parameters:
|
||
input_dim: Number of channels in input
|
||
hidden_dim: Number of hidden channels
|
||
kernel_size: Size of kernel in convolutions
|
||
num_layers: Number of LSTM layers stacked on each other
|
||
batch_first: Whether or not dimension 0 is the batch or not
|
||
bias: Bias or no bias in Convolution
|
||
return_all_layers: Return the list of computations for all layers
|
||
Note: Will do same padding.
|
||
|
||
Input:
|
||
A tensor of size B, T, C or T, B, C
|
||
Output:
|
||
A tuple of two lists of length num_layers (or length 1 if return_all_layers is False).
|
||
0 - layer_output_list is the list of lists of length T of each output
|
||
1 - last_state_list is the list of last states
|
||
each element of the list is a tuple (h, c) for hidden state and memory
|
||
Example:
|
||
>> x = torch.rand((32, 10, 64))
|
||
>> convlstm = ConvLSTM(64, 16, 3, 1, True, True, False)
|
||
>> _, last_states = convlstm(x)
|
||
>> h = last_states[0][0] # 0 for layer index, 0 for h index
|
||
"""
|
||
|
||
def __init__(self, input_dim, hidden_dim, num_layers,
|
||
batch_first=False, bias=True, return_all_layers=False):
|
||
super(LSTM, self).__init__()
|
||
|
||
# Make sure that both `kernel_size` and `hidden_dim` are lists having len == num_layers
|
||
hidden_dim = self._extend_for_multilayer(hidden_dim, num_layers)
|
||
if not len(hidden_dim) == num_layers:
|
||
raise ValueError('Inconsistent list length.')
|
||
|
||
self.input_dim = input_dim
|
||
self.hidden_dim = hidden_dim
|
||
self.num_layers = num_layers
|
||
self.batch_first = batch_first
|
||
self.bias = bias
|
||
self.return_all_layers = return_all_layers
|
||
|
||
cell_list = []
|
||
|
||
for i in range(0, self.num_layers):
|
||
cur_input_dim = self.input_dim if i == 0 else self.hidden_dim[i - 1]
|
||
|
||
cell_list.append(
|
||
dctLSTMCell(input_dim=cur_input_dim,
|
||
hidden_dim=self.hidden_dim[i],
|
||
bias=self.bias),
|
||
)
|
||
|
||
self.cell_list = nn.ModuleList(cell_list)
|
||
|
||
def forward(self, input_tensor, hidden_state=None):
|
||
"""
|
||
|
||
Parameters
|
||
----------
|
||
input_tensor: todo
|
||
5-D Tensor either of shape (t, b, c) or (b, t, c)
|
||
hidden_state: todo
|
||
None. todo implement stateful
|
||
|
||
Returns
|
||
-------
|
||
last_state_list, layer_output
|
||
"""
|
||
|
||
if not self.batch_first:
|
||
# 等同于transpose
|
||
# (t, b, c, h, w) -> (b, t, c, h, w)
|
||
input_tensor = input_tensor.permute(1, 0, 2)
|
||
|
||
b, _, _ = input_tensor.size()
|
||
|
||
# Implement stateful ConvLSTM
|
||
if hidden_state is not None:
|
||
raise NotImplementedError()
|
||
else:
|
||
# Since the init is done in forward. Can send image size here
|
||
hidden_state = self._init_hidden(batch_size=b)
|
||
|
||
layer_output_list = []
|
||
last_state_list = []
|
||
|
||
timestamp = input_tensor.size(1)
|
||
cur_layer_input = input_tensor
|
||
|
||
for layer_idx in range(self.num_layers):
|
||
|
||
h, c = hidden_state[layer_idx]
|
||
output_inner = []
|
||
for t in range(timestamp):
|
||
h, c = self.cell_list[layer_idx](input_tensor=cur_layer_input[:, t, :],
|
||
cur_state=[h, c])
|
||
output_inner.append(h)
|
||
|
||
layer_output = torch.stack(output_inner, dim=1)
|
||
|
||
# TODO 每层之间增加一个dct_attention
|
||
# layer_output = self.attention_list[layer_idx](layer_output)
|
||
|
||
cur_layer_input = layer_output
|
||
|
||
layer_output_list.append(layer_output)
|
||
last_state_list.append([h, c])
|
||
|
||
if not self.return_all_layers:
|
||
layer_output_list = layer_output_list[-1:]
|
||
last_state_list = last_state_list[-1:]
|
||
|
||
return layer_output_list, last_state_list
|
||
|
||
def _init_hidden(self, batch_size):
|
||
init_states = []
|
||
for i in range(self.num_layers):
|
||
init_states.append(self.cell_list[i].init_hidden(batch_size))
|
||
return init_states
|
||
|
||
@staticmethod
|
||
def _extend_for_multilayer(param, num_layers):
|
||
if not isinstance(param, list):
|
||
param = [param] * num_layers
|
||
return param
|
||
|
||
|
||
class AdaRNN(nn.Module):
|
||
"""
|
||
model_type: 'Boosting', 'AdaRNN'
|
||
bottleneck_list: (dim,is_BatchNorm,is_ReLu,drop_out)
|
||
"""
|
||
|
||
def __init__(self, use_bottleneck=False, bottleneck_list=[(64, False, False, 0), (64, True, True, 0.5)],
|
||
n_input=128, n_hiddens=[64, 64], n_output=6,
|
||
dropout=0.0, len_seq=9, model_type='AdaRNN',
|
||
trans_loss='mmd'):
|
||
super(AdaRNN, self).__init__()
|
||
self.use_bottleneck = use_bottleneck
|
||
self.n_input = n_input
|
||
self.num_layers = len(n_hiddens)
|
||
self.hiddens = n_hiddens
|
||
self.n_output = n_output
|
||
self.model_type = model_type
|
||
self.trans_loss = trans_loss
|
||
self.len_seq = len_seq
|
||
in_size = self.n_input
|
||
|
||
features = nn.ModuleList()
|
||
# dctAttention = nn.ModuleList()
|
||
for hidden in n_hiddens:
|
||
# rnn = nn.GRU(
|
||
# input_size=in_size,
|
||
# num_layers=1,
|
||
# hidden_size=hidden,
|
||
# batch_first=True,
|
||
# dropout=dropout
|
||
# )
|
||
rnn = LSTM(input_dim=in_size, hidden_dim=[hidden], num_layers=1, batch_first=True, return_all_layers=True)
|
||
# attention = dct_channel_block(channel=hidden)
|
||
features.append(rnn)
|
||
# dctAttention.append(attention)
|
||
in_size = hidden
|
||
self.features = nn.Sequential(*features)
|
||
# self.dctAttention = nn.Sequential(*dctAttention)
|
||
|
||
if use_bottleneck == True: # finance
|
||
bottleneck = []
|
||
for i in range(len(bottleneck_list)):
|
||
cur_input_dim = self.hiddens[-1] if i == 0 else bottleneck_list[i - 1][0]
|
||
bottleneck.append(
|
||
nn.Linear(cur_input_dim, bottleneck_list[i][0])
|
||
)
|
||
### 不加初始权重会让Hard predict更不稳定,振幅更大
|
||
# 初始权重越大,振幅越大
|
||
bottleneck[-1].weight.data.normal_(0, 0.03)
|
||
bottleneck[-1].bias.data.fill_(0.01)
|
||
if bottleneck_list[i][1]:
|
||
bottleneck.append(nn.BatchNorm1d(bottleneck_list[i][0]))
|
||
if bottleneck_list[i][2]:
|
||
bottleneck.append(nn.ReLU())
|
||
if bottleneck_list[i][3] != 0:
|
||
bottleneck.append(nn.Dropout(bottleneck_list[i][3]))
|
||
self.bottleneck = nn.Sequential(*bottleneck)
|
||
self.fc = nn.Linear(bottleneck_list[-1][0], n_output)
|
||
|
||
torch.nn.init.xavier_normal_(self.fc.weight)
|
||
else:
|
||
self.fc_out = nn.Linear(n_hiddens[-1], self.n_output)
|
||
|
||
if self.model_type == 'AdaRNN':
|
||
gate = nn.ModuleList()
|
||
for i in range(len(n_hiddens)):
|
||
gate_weight = nn.Linear(
|
||
len_seq * self.hiddens[i] * 2, len_seq)
|
||
gate.append(gate_weight)
|
||
self.gate = gate
|
||
|
||
bnlst = nn.ModuleList()
|
||
for i in range(len(n_hiddens)):
|
||
bnlst.append(nn.BatchNorm1d(len_seq))
|
||
self.bn_lst = bnlst
|
||
self.softmax = torch.nn.Softmax(dim=0)
|
||
self.init_layers()
|
||
|
||
def init_layers(self):
|
||
for i in range(len(self.hiddens)):
|
||
self.gate[i].weight.data.normal_(0, 0.05)
|
||
self.gate[i].bias.data.fill_(0.0)
|
||
|
||
def forward_pre_train(self, x, len_win=0):
|
||
out = self.gru_features(x)
|
||
# 两层GRU之后的结果
|
||
fea = out[0]
|
||
|
||
if self.use_bottleneck == True:
|
||
fea_bottleneck = self.bottleneck(fea[:, -1, :])
|
||
fc_out = self.fc(fea_bottleneck).squeeze()
|
||
else:
|
||
fc_out = self.fc_out(fea[:, -1, :]).squeeze()
|
||
# 每层GRU之后的结果,每层GRU前后权重归一化之后的结果
|
||
out_list_all, out_weight_list = out[1], out[2]
|
||
# 可以理解为前半段 和 后半段
|
||
out_list_s, out_list_t = self.get_features(out_list_all)
|
||
loss_transfer = torch.zeros((1,))
|
||
for i in range(len(out_list_s)):
|
||
criterion_transder = TransferLoss(
|
||
loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2])
|
||
h_start = 0
|
||
for j in range(h_start, self.len_seq, 1):
|
||
i_start = max(j - len_win, 0)
|
||
i_end = j + len_win if j + len_win < self.len_seq else self.len_seq - 1
|
||
for k in range(i_start, i_end + 1):
|
||
weight = out_weight_list[i][j] if self.model_type == 'AdaRNN' else 1 / (
|
||
self.len_seq - h_start) * (2 * len_win + 1)
|
||
loss_transfer = loss_transfer + weight * criterion_transder.compute(
|
||
out_list_s[i][:, j, :], out_list_t[i][:, k, :])
|
||
return fc_out, loss_transfer, out_weight_list
|
||
|
||
def gru_features(self, x, predict=False):
|
||
x_input = x
|
||
out = None
|
||
out_lis = []
|
||
out_weight_list = [] if (
|
||
self.model_type == 'AdaRNN') else None
|
||
for i in range(self.num_layers):
|
||
# GRU的输出
|
||
out, _ = self.features[i](x_input.float())
|
||
out = out[0]
|
||
# out = self.dctAttention[i](out.float())
|
||
x_input = out
|
||
out_lis.append(out)
|
||
if self.model_type == 'AdaRNN' and predict == False:
|
||
out_gate = self.process_gate_weight(x_input, i)
|
||
out_weight_list.append(out_gate)
|
||
# 两层GRU之后的结果,每层GRU之后的结果,每层GRU前后权重归一化之后的结果
|
||
return out, out_lis, out_weight_list
|
||
|
||
def process_gate_weight(self, out, index):
|
||
x_s = out[0: int(out.shape[0] // 2)] # 可以理解为前一半个batch_size的分布 域Di
|
||
x_t = out[out.shape[0] // 2: out.shape[0]] # 可以理解为后一半个batch_size的分布 域Dj
|
||
# 对应着不同的域
|
||
x_all = torch.cat((x_s, x_t), 2)
|
||
x_all = x_all.view(x_all.shape[0], -1)
|
||
weight = torch.sigmoid(self.bn_lst[index](
|
||
self.gate[index](x_all.float())))
|
||
weight = torch.mean(weight, dim=0)
|
||
res = self.softmax(weight).squeeze()
|
||
return res
|
||
|
||
def get_features(self, output_list):
|
||
fea_list_src, fea_list_tar = [], []
|
||
for fea in output_list:
|
||
fea_list_src.append(fea[0: fea.size(0) // 2])
|
||
fea_list_tar.append(fea[fea.size(0) // 2:])
|
||
return fea_list_src, fea_list_tar
|
||
|
||
# For Boosting-based
|
||
def forward_Boosting(self, x, weight_mat=None):
|
||
out = self.gru_features(x)
|
||
fea = out[0]
|
||
|
||
if self.use_bottleneck:
|
||
fea_bottleneck = self.bottleneck(fea[:, -1, :])
|
||
fc_out = self.fc(fea_bottleneck).squeeze()
|
||
else:
|
||
fc_out = self.fc_out(fea[:, -1, :]).squeeze()
|
||
|
||
out_list_all = out[1]
|
||
# 可以理解为前半段和后半段
|
||
out_list_s, out_list_t = self.get_features(out_list_all)
|
||
loss_transfer = torch.zeros((1,))
|
||
if weight_mat is None:
|
||
weight = (1.0 / self.len_seq *
|
||
torch.ones(self.num_layers, self.len_seq))
|
||
else:
|
||
weight = weight_mat
|
||
dist_mat = torch.zeros(self.num_layers, self.len_seq)
|
||
for i in range(len(out_list_s)):
|
||
criterion_transder = TransferLoss(
|
||
loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2])
|
||
for j in range(self.len_seq):
|
||
loss_trans = criterion_transder.compute(
|
||
out_list_s[i][:, j, :], out_list_t[i][:, j, :])
|
||
loss_transfer = loss_transfer + weight[i, j] * loss_trans
|
||
dist_mat[i, j] = loss_trans
|
||
return fc_out, loss_transfer, dist_mat, weight
|
||
|
||
# For Boosting-based
|
||
def update_weight_Boosting(self, weight_mat, dist_old, dist_new):
|
||
epsilon = 1e-12
|
||
dist_old = dist_old.detach()
|
||
dist_new = dist_new.detach()
|
||
ind = dist_new > dist_old + epsilon
|
||
weight_mat[ind] = weight_mat[ind] * \
|
||
(1 + torch.sigmoid(dist_new[ind] - dist_old[ind]))
|
||
weight_norm = torch.norm(weight_mat, dim=1, p=1)
|
||
weight_mat = weight_mat / weight_norm.t().unsqueeze(1).repeat(1, self.len_seq)
|
||
return weight_mat
|
||
|
||
def predict(self, x):
|
||
out = self.gru_features(x, predict=True)
|
||
fea = out[0]
|
||
|
||
if self.use_bottleneck:
|
||
fea_bottleneck = self.bottleneck(fea[:, -1, :])
|
||
fc_out = self.fc(fea_bottleneck).squeeze()
|
||
else:
|
||
fc_out = self.fc_out(fea[:, -1, :]).squeeze()
|
||
|
||
return fc_out
|