deepecgkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. deepecgkit/__init__.py +42 -0
  2. deepecgkit/__main__.py +6 -0
  3. deepecgkit/cli/__init__.py +61 -0
  4. deepecgkit/cli/commands/__init__.py +16 -0
  5. deepecgkit/cli/commands/evaluate.py +83 -0
  6. deepecgkit/cli/commands/info.py +73 -0
  7. deepecgkit/cli/commands/predict.py +94 -0
  8. deepecgkit/cli/commands/train.py +331 -0
  9. deepecgkit/cli/config.py +23 -0
  10. deepecgkit/cli/evaluation.py +251 -0
  11. deepecgkit/cli/logger.py +26 -0
  12. deepecgkit/cli/main.py +429 -0
  13. deepecgkit/cli/registry.py +32 -0
  14. deepecgkit/datasets/__init__.py +33 -0
  15. deepecgkit/datasets/af_classification.py +301 -0
  16. deepecgkit/datasets/base.py +146 -0
  17. deepecgkit/datasets/ltafdb.py +363 -0
  18. deepecgkit/datasets/mitbih_afdb.py +287 -0
  19. deepecgkit/datasets/modules.py +222 -0
  20. deepecgkit/datasets/preprocessing.py +208 -0
  21. deepecgkit/datasets/ptbxl.py +567 -0
  22. deepecgkit/datasets/splitting.py +225 -0
  23. deepecgkit/datasets/unified_af.py +316 -0
  24. deepecgkit/evaluation/__init__.py +40 -0
  25. deepecgkit/evaluation/evaluator.py +398 -0
  26. deepecgkit/evaluation/metrics.py +164 -0
  27. deepecgkit/evaluation/visualization.py +285 -0
  28. deepecgkit/models/__init__.py +51 -0
  29. deepecgkit/models/af_classifier.py +177 -0
  30. deepecgkit/models/convnext_v2_1d.py +183 -0
  31. deepecgkit/models/crnn.py +117 -0
  32. deepecgkit/models/deep_res_cnn.py +215 -0
  33. deepecgkit/models/ecg_dualnet.py +230 -0
  34. deepecgkit/models/fcn_wang.py +88 -0
  35. deepecgkit/models/gru_model.py +99 -0
  36. deepecgkit/models/inception_time.py +197 -0
  37. deepecgkit/models/kanres_wide_x.py +150 -0
  38. deepecgkit/models/kanres_x.py +126 -0
  39. deepecgkit/models/lstm_model.py +96 -0
  40. deepecgkit/models/mamba1d.py +232 -0
  41. deepecgkit/models/medformer.py +229 -0
  42. deepecgkit/models/resnet1d.py +189 -0
  43. deepecgkit/models/resnet1d_wang.py +182 -0
  44. deepecgkit/models/se_resnet1d.py +232 -0
  45. deepecgkit/models/simple_cnn.py +82 -0
  46. deepecgkit/models/tcn.py +178 -0
  47. deepecgkit/models/transformer_ecg.py +127 -0
  48. deepecgkit/models/xresnet1d.py +265 -0
  49. deepecgkit/models/xresnet1d_benchmark.py +227 -0
  50. deepecgkit/registry.py +97 -0
  51. deepecgkit/training/__init__.py +9 -0
  52. deepecgkit/training/train.py +171 -0
  53. deepecgkit/utils/__init__.py +72 -0
  54. deepecgkit/utils/download.py +123 -0
  55. deepecgkit/utils/weights.py +107 -0
  56. deepecgkit-0.1.0.dist-info/METADATA +99 -0
  57. deepecgkit-0.1.0.dist-info/RECORD +61 -0
  58. deepecgkit-0.1.0.dist-info/WHEEL +5 -0
  59. deepecgkit-0.1.0.dist-info/entry_points.txt +2 -0
  60. deepecgkit-0.1.0.dist-info/licenses/LICENSE +201 -0
  61. deepecgkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,127 @@
1
+ import math
2
+
3
+ import torch
4
+ from torch import nn
5
+
6
+ from deepecgkit.registry import register_model
7
+
8
+
9
+ class PositionalEncoding(nn.Module):
10
+ """
11
+ Positional encoding for transformer model.
12
+
13
+ Args:
14
+ d_model: Dimension of the model
15
+ max_len: Maximum sequence length (default: 5000)
16
+ dropout: Dropout probability (default: 0.1)
17
+ """
18
+
19
+ def __init__(self, d_model: int, max_len: int = 5000, dropout: float = 0.1):
20
+ super().__init__()
21
+ self.dropout = nn.Dropout(p=dropout)
22
+
23
+ position = torch.arange(max_len).unsqueeze(1)
24
+ div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
25
+ pe = torch.zeros(1, max_len, d_model)
26
+ pe[0, :, 0::2] = torch.sin(position * div_term)
27
+ pe[0, :, 1::2] = torch.cos(position * div_term)
28
+ self.register_buffer("pe", pe)
29
+
30
+ def forward(self, x):
31
+ x = x + self.pe[:, : x.size(1)]
32
+ return self.dropout(x)
33
+
34
+
35
+ @register_model(
36
+ name="transformer",
37
+ description="Transformer-based ECG classifier",
38
+ )
39
+ class TransformerECG(nn.Module):
40
+ """
41
+ Transformer-based model for ECG signal classification.
42
+
43
+ A transformer architecture that uses self-attention mechanisms
44
+ to capture long-range dependencies in ECG signals.
45
+
46
+ Args:
47
+ input_channels: Number of input channels (default: 1 for single-lead ECG)
48
+ output_size: Number of output classes
49
+ d_model: Dimension of the model (default: 128)
50
+ nhead: Number of attention heads (default: 8)
51
+ num_encoder_layers: Number of transformer encoder layers (default: 4)
52
+ dim_feedforward: Dimension of feedforward network (default: 512)
53
+ dropout_rate: Dropout probability (default: 0.1)
54
+ max_len: Maximum sequence length (default: 5000)
55
+
56
+ Example:
57
+ >>> model = TransformerECG(input_channels=1, output_size=4)
58
+ >>> x = torch.randn(32, 1, 3000)
59
+ >>> output = model(x)
60
+ >>> print(output.shape)
61
+
62
+ >>> features = model.extract_features(x)
63
+ >>> print(features.shape) # (32, 128) with d_model=128
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ input_channels: int = 1,
69
+ output_size: int = 4,
70
+ d_model: int = 128,
71
+ nhead: int = 8,
72
+ num_encoder_layers: int = 4,
73
+ dim_feedforward: int = 512,
74
+ dropout_rate: float = 0.1,
75
+ max_len: int = 5000,
76
+ ):
77
+ super().__init__()
78
+
79
+ self.d_model = d_model
80
+
81
+ self.input_projection = nn.Linear(input_channels, d_model)
82
+
83
+ self.positional_encoding = PositionalEncoding(d_model, max_len, dropout_rate)
84
+
85
+ encoder_layer = nn.TransformerEncoderLayer(
86
+ d_model=d_model,
87
+ nhead=nhead,
88
+ dim_feedforward=dim_feedforward,
89
+ dropout=dropout_rate,
90
+ batch_first=True,
91
+ )
92
+
93
+ self.transformer_encoder = nn.TransformerEncoder(
94
+ encoder_layer,
95
+ num_layers=num_encoder_layers,
96
+ )
97
+
98
+ self.global_pool = nn.AdaptiveAvgPool1d(1)
99
+ self._feature_dim = d_model
100
+
101
+ self.classifier = nn.Sequential(
102
+ nn.Linear(d_model, dim_feedforward // 2),
103
+ nn.ReLU(),
104
+ nn.Dropout(dropout_rate),
105
+ nn.Linear(dim_feedforward // 2, output_size),
106
+ )
107
+
108
+ @property
109
+ def feature_dim(self) -> int:
110
+ return self._feature_dim
111
+
112
+ def _extract_encoder_features(self, x: torch.Tensor) -> torch.Tensor:
113
+ x = x.transpose(1, 2)
114
+ x = self.input_projection(x)
115
+ x = self.positional_encoding(x)
116
+ x = self.transformer_encoder(x)
117
+ x = x.transpose(1, 2)
118
+ x = self.global_pool(x)
119
+ x = x.squeeze(-1)
120
+ return x
121
+
122
+ def extract_features(self, x: torch.Tensor) -> torch.Tensor:
123
+ return self._extract_encoder_features(x)
124
+
125
+ def forward(self, x):
126
+ x = self.extract_features(x)
127
+ return self.classifier(x)
@@ -0,0 +1,265 @@
1
+ import torch
2
+ import torch.nn.functional as func
3
+ from torch import nn
4
+
5
+ from deepecgkit.registry import register_model
6
+
7
+
8
+ class Mish(nn.Module):
9
+ """Mish activation: x * tanh(softplus(x))."""
10
+
11
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
12
+ return x * torch.tanh(func.softplus(x))
13
+
14
+
15
+ class BlurPool1D(nn.Module):
16
+ """
17
+ Anti-aliased downsampling via blur-then-subsample.
18
+
19
+ Applies a fixed triangular low-pass filter before strided subsampling
20
+ to reduce aliasing artifacts during spatial reduction.
21
+
22
+ Args:
23
+ channels: Number of channels (applied per-channel)
24
+ stride: Downsampling stride (default: 2)
25
+ """
26
+
27
+ def __init__(self, channels: int, stride: int = 2):
28
+ super().__init__()
29
+ self.stride = stride
30
+ kernel = torch.tensor([1.0, 2.0, 1.0]) / 4.0
31
+ kernel = kernel.unsqueeze(0).unsqueeze(0).repeat(channels, 1, 1)
32
+ self.register_buffer("kernel", kernel)
33
+ self.channels = channels
34
+
35
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
36
+ x = func.pad(x, (1, 1), mode="reflect")
37
+ x = func.conv1d(x, self.kernel, stride=self.stride, groups=self.channels)
38
+ return x
39
+
40
+
41
+ class XResBlock1D(nn.Module):
42
+ """
43
+ Improved residual block with Mish activation and optional blur-pool downsampling.
44
+
45
+ Args:
46
+ in_channels: Number of input channels
47
+ out_channels: Number of output channels
48
+ kernel_size: Convolution kernel size (default: 7)
49
+ stride: Convolution stride (default: 1)
50
+ downsample: Downsample layer for skip connection if needed
51
+ use_blur_pool: Whether to use anti-aliased downsampling
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ in_channels: int,
57
+ out_channels: int,
58
+ kernel_size: int = 7,
59
+ stride: int = 1,
60
+ downsample: nn.Module | None = None,
61
+ use_blur_pool: bool = True,
62
+ ):
63
+ super().__init__()
64
+
65
+ self.conv1 = nn.Conv1d(
66
+ in_channels,
67
+ out_channels,
68
+ kernel_size=kernel_size,
69
+ stride=1,
70
+ padding=kernel_size // 2,
71
+ bias=False,
72
+ )
73
+ self.bn1 = nn.BatchNorm1d(out_channels)
74
+
75
+ self.conv2 = nn.Conv1d(
76
+ out_channels,
77
+ out_channels,
78
+ kernel_size=kernel_size,
79
+ stride=1,
80
+ padding=kernel_size // 2,
81
+ bias=False,
82
+ )
83
+ self.bn2 = nn.BatchNorm1d(out_channels)
84
+
85
+ self.act = Mish()
86
+ self.downsample = downsample
87
+
88
+ if stride > 1 and use_blur_pool:
89
+ self.pool = BlurPool1D(out_channels, stride=stride)
90
+ elif stride > 1:
91
+ self.pool = nn.AvgPool1d(kernel_size=stride, stride=stride)
92
+ else:
93
+ self.pool = nn.Identity()
94
+
95
+ nn.init.zeros_(self.bn2.weight)
96
+
97
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
98
+ identity = x
99
+
100
+ out = self.act(self.bn1(self.conv1(x)))
101
+ out = self.bn2(self.conv2(out))
102
+ out = self.pool(out)
103
+
104
+ if self.downsample is not None:
105
+ identity = self.downsample(x)
106
+
107
+ out += identity
108
+ out = self.act(out)
109
+ return out
110
+
111
+
112
+ @register_model(
113
+ name="xresnet",
114
+ description="Improved ResNet with Mish activation and blur-pool downsampling",
115
+ )
116
+ class XResNet1D(nn.Module):
117
+ """
118
+ XResNet model for ECG signal classification.
119
+
120
+ An improved ResNet incorporating three key enhancements from recent
121
+ research: (1) a multi-layer stem instead of a single large convolution,
122
+ (2) Mish activation for smoother gradients, and (3) anti-aliased
123
+ blur-pool downsampling to reduce aliasing.
124
+
125
+ Args:
126
+ input_channels: Number of input channels (default: 1 for single-lead ECG)
127
+ output_size: Number of output classes
128
+ base_channels: Base number of channels (default: 64)
129
+ num_blocks: List of number of blocks in each layer (default: [2, 2, 2, 2])
130
+ dropout_rate: Dropout probability (default: 0.3)
131
+ use_blur_pool: Whether to use anti-aliased downsampling (default: True)
132
+
133
+ Example:
134
+ >>> model = XResNet1D(input_channels=1, output_size=4)
135
+ >>> x = torch.randn(32, 1, 3000)
136
+ >>> output = model(x)
137
+ >>> print(output.shape)
138
+
139
+ >>> features = model.extract_features(x)
140
+ >>> print(features.shape) # (32, 512) with base_channels=64
141
+ """
142
+
143
+ def __init__(
144
+ self,
145
+ input_channels: int = 1,
146
+ output_size: int = 4,
147
+ base_channels: int = 64,
148
+ num_blocks: list | None = None,
149
+ dropout_rate: float = 0.3,
150
+ use_blur_pool: bool = True,
151
+ ):
152
+ super().__init__()
153
+
154
+ if num_blocks is None:
155
+ num_blocks = [2, 2, 2, 2]
156
+
157
+ self.in_channels = base_channels
158
+ self.use_blur_pool = use_blur_pool
159
+ self.act = Mish()
160
+
161
+ self.stem = nn.Sequential(
162
+ nn.Conv1d(
163
+ input_channels,
164
+ base_channels // 2,
165
+ kernel_size=7,
166
+ stride=2,
167
+ padding=3,
168
+ bias=False,
169
+ ),
170
+ nn.BatchNorm1d(base_channels // 2),
171
+ Mish(),
172
+ nn.Conv1d(
173
+ base_channels // 2,
174
+ base_channels // 2,
175
+ kernel_size=3,
176
+ stride=1,
177
+ padding=1,
178
+ bias=False,
179
+ ),
180
+ nn.BatchNorm1d(base_channels // 2),
181
+ Mish(),
182
+ nn.Conv1d(
183
+ base_channels // 2,
184
+ base_channels,
185
+ kernel_size=3,
186
+ stride=1,
187
+ padding=1,
188
+ bias=False,
189
+ ),
190
+ nn.BatchNorm1d(base_channels),
191
+ Mish(),
192
+ )
193
+
194
+ if use_blur_pool:
195
+ self.stem_pool = BlurPool1D(base_channels, stride=2)
196
+ else:
197
+ self.stem_pool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1)
198
+
199
+ self.layer1 = self._make_layer(base_channels, num_blocks[0], stride=1)
200
+ self.layer2 = self._make_layer(base_channels * 2, num_blocks[1], stride=2)
201
+ self.layer3 = self._make_layer(base_channels * 4, num_blocks[2], stride=2)
202
+ self.layer4 = self._make_layer(base_channels * 8, num_blocks[3], stride=2)
203
+
204
+ self.avgpool = nn.AdaptiveAvgPool1d(1)
205
+ self.dropout = nn.Dropout(dropout_rate)
206
+ self._feature_dim = base_channels * 8
207
+ self.fc = nn.Linear(self._feature_dim, output_size)
208
+
209
+ def _make_layer(self, out_channels: int, num_blocks: int, stride: int = 1):
210
+ downsample = None
211
+ if stride != 1 or self.in_channels != out_channels:
212
+ ds_layers: list[nn.Module] = []
213
+ if stride > 1:
214
+ if self.use_blur_pool:
215
+ ds_layers.append(BlurPool1D(self.in_channels, stride=stride))
216
+ else:
217
+ ds_layers.append(nn.AvgPool1d(kernel_size=stride, stride=stride))
218
+ ds_layers.append(nn.Conv1d(self.in_channels, out_channels, kernel_size=1, bias=False))
219
+ ds_layers.append(nn.BatchNorm1d(out_channels))
220
+ downsample = nn.Sequential(*ds_layers)
221
+
222
+ layers = [
223
+ XResBlock1D(
224
+ self.in_channels,
225
+ out_channels,
226
+ stride=stride,
227
+ downsample=downsample,
228
+ use_blur_pool=self.use_blur_pool,
229
+ )
230
+ ]
231
+ self.in_channels = out_channels
232
+
233
+ for _ in range(1, num_blocks):
234
+ layers.append(
235
+ XResBlock1D(
236
+ out_channels,
237
+ out_channels,
238
+ use_blur_pool=self.use_blur_pool,
239
+ )
240
+ )
241
+
242
+ return nn.Sequential(*layers)
243
+
244
+ @property
245
+ def feature_dim(self) -> int:
246
+ return self._feature_dim
247
+
248
+ def extract_features(self, x: torch.Tensor) -> torch.Tensor:
249
+ x = self.stem(x)
250
+ x = self.stem_pool(x)
251
+
252
+ x = self.layer1(x)
253
+ x = self.layer2(x)
254
+ x = self.layer3(x)
255
+ x = self.layer4(x)
256
+
257
+ x = self.avgpool(x)
258
+ x = torch.flatten(x, 1)
259
+ return x
260
+
261
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
262
+ x = self.extract_features(x)
263
+ x = self.dropout(x)
264
+ x = self.fc(x)
265
+ return x
@@ -0,0 +1,227 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+ from deepecgkit.registry import register_model
5
+
6
+
7
+ def _init_cnn(m: nn.Module):
8
+ if getattr(m, "bias", None) is not None:
9
+ nn.init.constant_(m.bias, 0)
10
+ if isinstance(m, (nn.Conv1d, nn.Linear)):
11
+ nn.init.kaiming_normal_(m.weight)
12
+ for child in m.children():
13
+ _init_cnn(child)
14
+
15
+
16
+ class ConvBnAct(nn.Sequential):
17
+ """Conv1d → BatchNorm1d → Activation."""
18
+
19
+ def __init__(
20
+ self,
21
+ ni: int,
22
+ nf: int,
23
+ ks: int = 3,
24
+ stride: int = 1,
25
+ act: bool = True,
26
+ zero_bn: bool = False,
27
+ ):
28
+ layers: list[nn.Module] = [
29
+ nn.Conv1d(ni, nf, kernel_size=ks, stride=stride, padding=(ks - 1) // 2, bias=False),
30
+ ]
31
+ bn = nn.BatchNorm1d(nf)
32
+ if zero_bn:
33
+ nn.init.zeros_(bn.weight)
34
+ layers.append(bn)
35
+ if act:
36
+ layers.append(nn.ReLU(inplace=True))
37
+ super().__init__(*layers)
38
+
39
+
40
+ class XResBlock(nn.Module):
41
+ """
42
+ XResNet residual block with expansion support.
43
+
44
+ Args:
45
+ expansion: Channel expansion factor (1 for BasicBlock, 4 for Bottleneck)
46
+ ni: Input channels (before expansion)
47
+ nf: Output channels (before expansion)
48
+ stride: Stride for downsampling
49
+ kernel_size: Convolution kernel size
50
+ """
51
+
52
+ def __init__(self, expansion: int, ni: int, nf: int, stride: int = 1, kernel_size: int = 5):
53
+ super().__init__()
54
+ nf_out = nf * expansion
55
+ ni_in = ni * expansion
56
+
57
+ if expansion == 1:
58
+ self.convs = nn.Sequential(
59
+ ConvBnAct(ni_in, nf, ks=kernel_size, stride=stride),
60
+ ConvBnAct(nf, nf_out, ks=kernel_size, zero_bn=True, act=False),
61
+ )
62
+ else:
63
+ self.convs = nn.Sequential(
64
+ ConvBnAct(ni_in, nf, ks=1),
65
+ ConvBnAct(nf, nf, ks=kernel_size, stride=stride),
66
+ ConvBnAct(nf, nf_out, ks=1, zero_bn=True, act=False),
67
+ )
68
+
69
+ id_layers: list[nn.Module] = []
70
+ if ni_in != nf_out:
71
+ id_layers.append(ConvBnAct(ni_in, nf_out, ks=1, act=False))
72
+ if stride != 1:
73
+ id_layers.insert(0, nn.AvgPool1d(2, ceil_mode=True))
74
+ self.idpath = nn.Sequential(*id_layers)
75
+ self.act = nn.ReLU(inplace=True)
76
+
77
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
78
+ return self.act(self.convs(x) + self.idpath(x))
79
+
80
+
81
+ @register_model(
82
+ name="xresnet1d-benchmark",
83
+ description="XResNet1d from PTB-XL benchmark (fixed feature dim, concat pooling)",
84
+ )
85
+ class XResNet1dBenchmark(nn.Module):
86
+ """
87
+ XResNet1d adapted from the PTB-XL benchmarking repository.
88
+
89
+ Key differences from the standard XResNet1D:
90
+ - Fixed feature dimension across all layers (all blocks use same width)
91
+ - Concat pooling head (AdaptiveAvgPool + AdaptiveMaxPool concatenated)
92
+ - Multi-layer stem with configurable kernel size
93
+ - Supports both BasicBlock (expansion=1) and Bottleneck (expansion=4)
94
+
95
+ Reference:
96
+ Strodthoff N., Wagner P., Schaeffter T., Samek W.
97
+ "Deep Learning for ECG Analysis: Benchmarks and Insights from PTB-XL" (2021)
98
+ https://github.com/helme/ecg_ptbxl_benchmarking
99
+
100
+ Args:
101
+ input_channels: Number of input channels (default: 12 for 12-lead ECG)
102
+ output_size: Number of output classes
103
+ expansion: Block expansion factor (1=BasicBlock, 4=Bottleneck)
104
+ layers: List of block counts per layer (default: [3, 4, 6, 3] for ResNet-50)
105
+ base_channels: Fixed channel width for all layers (default: 64)
106
+ kernel_size: Convolution kernel size (default: 5)
107
+ kernel_size_stem: Stem convolution kernel size (default: 5)
108
+ stem_channels: Tuple of stem layer channels
109
+ dropout_rate: Dropout probability in head (default: 0.5)
110
+ concat_pooling: Use avg+max concat pooling (default: True)
111
+
112
+ Example:
113
+ >>> model = XResNet1dBenchmark(input_channels=12, output_size=5)
114
+ >>> x = torch.randn(32, 12, 1000)
115
+ >>> output = model(x)
116
+ >>> print(output.shape)
117
+
118
+ >>> features = model.extract_features(x)
119
+ >>> print(features.shape) # (32, 128) with concat_pooling
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ input_channels: int = 12,
125
+ output_size: int = 5,
126
+ expansion: int = 1,
127
+ layers: list[int] | None = None,
128
+ base_channels: int = 64,
129
+ kernel_size: int = 5,
130
+ kernel_size_stem: int = 5,
131
+ stem_channels: tuple[int, ...] = (32, 32, 64),
132
+ dropout_rate: float = 0.5,
133
+ concat_pooling: bool = True,
134
+ ):
135
+ super().__init__()
136
+
137
+ if layers is None:
138
+ layers = [2, 2, 2, 2]
139
+
140
+ self.expansion = expansion
141
+ self.concat_pooling = concat_pooling
142
+
143
+ stem_szs = [input_channels, *stem_channels]
144
+ self.stem = nn.Sequential(
145
+ *[
146
+ ConvBnAct(
147
+ stem_szs[i], stem_szs[i + 1], ks=kernel_size_stem, stride=2 if i == 0 else 1
148
+ )
149
+ for i in range(len(stem_channels))
150
+ ]
151
+ )
152
+ self.stem_pool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1)
153
+
154
+ block_szs = [64 // expansion, *([base_channels] * len(layers))]
155
+
156
+ self.res_layers = nn.Sequential(
157
+ *[
158
+ self._make_layer(
159
+ ni=block_szs[i],
160
+ nf=block_szs[i + 1],
161
+ blocks=n_blocks,
162
+ stride=1 if i == 0 else 2,
163
+ kernel_size=kernel_size,
164
+ )
165
+ for i, n_blocks in enumerate(layers)
166
+ ]
167
+ )
168
+
169
+ final_nf = block_szs[-1] * expansion
170
+ self._feature_dim = final_nf * 2 if concat_pooling else final_nf
171
+
172
+ if concat_pooling:
173
+ self.pool = nn.ModuleList([nn.AdaptiveAvgPool1d(1), nn.AdaptiveMaxPool1d(1)])
174
+ else:
175
+ self.pool = nn.ModuleList([nn.AdaptiveAvgPool1d(1)])
176
+
177
+ self.head = nn.Sequential(
178
+ nn.Flatten(),
179
+ nn.BatchNorm1d(self._feature_dim),
180
+ nn.Dropout(dropout_rate / 2),
181
+ nn.Linear(self._feature_dim, output_size),
182
+ )
183
+
184
+ _init_cnn(self)
185
+
186
+ def _make_layer(
187
+ self, ni: int, nf: int, blocks: int, stride: int, kernel_size: int
188
+ ) -> nn.Sequential:
189
+ return nn.Sequential(
190
+ *[
191
+ XResBlock(
192
+ self.expansion,
193
+ ni if i == 0 else nf,
194
+ nf,
195
+ stride=stride if i == 0 else 1,
196
+ kernel_size=kernel_size,
197
+ )
198
+ for i in range(blocks)
199
+ ]
200
+ )
201
+
202
+ @property
203
+ def feature_dim(self) -> int:
204
+ return self._feature_dim
205
+
206
+ def extract_features(self, x: torch.Tensor) -> torch.Tensor:
207
+ x = self.stem(x)
208
+ x = self.stem_pool(x)
209
+ x = self.res_layers(x)
210
+ pooled = torch.cat([p(x) for p in self.pool], dim=1)
211
+ return torch.flatten(pooled, 1)
212
+
213
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
214
+ x = self.extract_features(x)
215
+ return self.head(x)
216
+
217
+
218
+ def xresnet1d18_benchmark(**kwargs) -> XResNet1dBenchmark:
219
+ return XResNet1dBenchmark(expansion=1, layers=[2, 2, 2, 2], **kwargs)
220
+
221
+
222
+ def xresnet1d50_benchmark(**kwargs) -> XResNet1dBenchmark:
223
+ return XResNet1dBenchmark(expansion=4, layers=[3, 4, 6, 3], **kwargs)
224
+
225
+
226
+ def xresnet1d101_benchmark(**kwargs) -> XResNet1dBenchmark:
227
+ return XResNet1dBenchmark(expansion=4, layers=[3, 4, 23, 3], **kwargs)