import os import numpy as np import torch # type: ignore import torch.nn as nn # type: ignore import torch.nn.functional as F # type: ignore os.environ.setdefault('KMP_DUPLICATE_LIB_OK', 'TRUE') VOLTAGE_SPACE = np.linspace(-1.0, 0.0, 99) VOLTAGE = VOLTAGE_SPACE[VOLTAGE_SPACE <= -0.40] N_SIG = len(VOLTAGE) N_INT_TS = 3 N_INT_FEAT = 20 VOLTAGE_INTERVALS = [ ('int1', -1.00, -0.82), ('int2', -0.82, -0.62), ('int3', -0.62, -0.40), ] FEATURE_SUFFIXES = [ 'valley', 'kurtosis', 'skewness', 'area', 'valley_position', 'peak_width', 'd1_max', 'd1_min', 'n_zero_crossings', 'd2_max', 'd2_min', 'mean', 'std', 'range', 'energy', 'valley_to_mean', 'asymmetry', 'slope_start', 'slope_end', 'overall_slope', ] INTERVAL_LABELS = [ f"{v0:.2f}–{v1:.2f} V" for _, v0, v1 in VOLTAGE_INTERVALS ] FEAT_NAMES = [ f"int_({v0:.2f},{v1:.2f})_{feature}" for _, v0, v1 in VOLTAGE_INTERVALS for feature in FEATURE_SUFFIXES ] INTERVAL_COLORS = ['#1f77b4', '#ff7f0e', '#2ca02c'] class MLPNet(nn.Module): def __init__(self, n_features, num_classes): super().__init__() self.fc1 = nn.Linear(n_features, 64) self.bn1 = nn.BatchNorm1d(64) self.drop1 = nn.Dropout(0.3) self.fc2 = nn.Linear(64, 32) self.bn2 = nn.BatchNorm1d(32) self.drop2 = nn.Dropout(0.3) self.fc3 = nn.Linear(32, 16) self.fc_out = nn.Linear(16, num_classes) def forward(self, x): x = self.drop1(F.relu(self.bn1(self.fc1(x)))) x = self.drop2(F.relu(self.bn2(self.fc2(x)))) return self.fc_out(F.relu(self.fc3(x))) class LSTMWithAttn(nn.Module): def __init__(self, n_features, num_classes, hidden=64): super().__init__() self.lstm = nn.LSTM(n_features, hidden, batch_first=True, bidirectional=True) self.norm = nn.LayerNorm(hidden * 2) self.drop = nn.Dropout(0.3) self.attn = nn.Linear(hidden * 2, 1) self.fc1 = nn.Linear(hidden * 2, 32) self.drop_fc = nn.Dropout(0.2) self.fc_out = nn.Linear(32, num_classes) def forward(self, x): x, _= self.lstm(x) x= self.drop(self.norm(x)) weights = torch.softmax(self.attn(x), dim=1) pooled = (weights * x).sum(dim=1) return self.fc_out(self.drop_fc(F.relu(self.fc1(pooled)))) class DualBranchLSTM(nn.Module): def __init__(self, n_sig_features, n_int_features, num_classes, hidden_a=64, hidden_b=32): super().__init__() self.lstm_a = nn.LSTM(n_sig_features, hidden_a, batch_first=True, bidirectional=True) self.norm_a = nn.LayerNorm(hidden_a * 2) self.drop_a = nn.Dropout(0.3) self.attn_a = nn.Linear(hidden_a * 2, 1) self.lstm_b = nn.LSTM(n_int_features, hidden_b, batch_first=True, bidirectional=True) self.norm_b = nn.LayerNorm(hidden_b * 2) self.drop_b = nn.Dropout(0.2) self.attn_b = nn.Linear(hidden_b * 2, 1) fused_dim = hidden_a * 2 + hidden_b * 2 self.norm_fuse = nn.LayerNorm(fused_dim) self.drop_fuse = nn.Dropout(0.3) self.fc1 = nn.Linear(fused_dim, 32) self.fc_out = nn.Linear(32, num_classes) def forward(self, x_signal, x_intervals): a, _ = self.lstm_a(x_signal) a = self.drop_a(self.norm_a(a)) weights = torch.softmax(self.attn_a(a), dim=1) a = (weights * a).sum(dim=1) b, _ = self.lstm_b(x_intervals) b = self.drop_b(self.norm_b(b)) weights_b = torch.softmax(self.attn_b(b), dim=1) b = (weights_b * b).sum(dim=1) x = torch.cat([a, b], dim=1) x = self.drop_fuse(self.norm_fuse(x)) return self.fc_out(F.relu(self.fc1(x))) class MetaLearner(nn.Module): def __init__(self, n_base_models, num_classes): super().__init__() self.fc1 = nn.Linear(n_base_models * num_classes, 32) self.drop = nn.Dropout(0.3) self.fc2 = nn.Linear(32, num_classes) def forward(self, x): return self.fc2(self.drop(F.relu(self.fc1(x)))) class FlatWrapper(nn.Module): def __init__(self, model, T, F): super().__init__() self.model = model self.T = T self.F = F def forward(self, x): return self.model(x.reshape(-1, self.T, self.F)) class DualInputWrapper(nn.Module): def __init__(self, model, n_sig, n_int_ts, n_int_feat): super().__init__() self.model = model self.n_sig = n_sig self.n_int_ts = n_int_ts self.n_int_feat = n_int_feat def forward(self, x): sig = x[:, :self.n_sig].unsqueeze(-1) intervals = x[:, self.n_sig:].reshape(-1, self.n_int_ts, self.n_int_feat) return self.model(sig, intervals)