188 lines
4.9 KiB
Python
188 lines
4.9 KiB
Python
"""
|
|
Temporal Convolutional Network (TCN) untuk sequence classification.
|
|
|
|
Berisi implementasi model Temporal Convolutional Network sesuai README.
|
|
|
|
"""
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
from torch.nn.utils import weight_norm
|
|
|
|
|
|
class Chomp1d(nn.Module):
|
|
"""
|
|
Remove padding dari akhir sequence.
|
|
"""
|
|
def __init__(self, chomp_size):
|
|
super(Chomp1d, self).__init__()
|
|
self.chomp_size = chomp_size
|
|
|
|
def forward(self, x):
|
|
return x[:, :, :-self.chomp_size].contiguous()
|
|
|
|
|
|
class TemporalBlock(nn.Module):
|
|
"""
|
|
Temporal block dari TCN.
|
|
Terdiri dari dua dilated conv layers dengan residual connection.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
n_inputs,
|
|
n_outputs,
|
|
kernel_size,
|
|
stride,
|
|
dilation,
|
|
padding,
|
|
dropout=0.2
|
|
):
|
|
super(TemporalBlock, self).__init__()
|
|
|
|
# First dilated convolution
|
|
self.conv1 = weight_norm(nn.Conv1d(
|
|
n_inputs, n_outputs, kernel_size,
|
|
stride=stride, padding=padding, dilation=dilation
|
|
))
|
|
self.chomp1 = Chomp1d(padding)
|
|
self.relu1 = nn.ReLU()
|
|
self.dropout1 = nn.Dropout(dropout)
|
|
|
|
# Second dilated convolution
|
|
self.conv2 = weight_norm(nn.Conv1d(
|
|
n_outputs, n_outputs, kernel_size,
|
|
stride=stride, padding=padding, dilation=dilation
|
|
))
|
|
self.chomp2 = Chomp1d(padding)
|
|
self.relu2 = nn.ReLU()
|
|
self.dropout2 = nn.Dropout(dropout)
|
|
|
|
# Residual connection
|
|
self.net = nn.Sequential(
|
|
self.conv1, self.chomp1, self.relu1, self.dropout1,
|
|
self.conv2, self.chomp2, self.relu2, self.dropout2
|
|
)
|
|
|
|
# Downsample jika perlu
|
|
self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
|
|
self.relu = nn.ReLU()
|
|
|
|
# Initialize weights
|
|
self.conv1.weight.data.normal_(0, 0.01)
|
|
self.conv2.weight.data.normal_(0, 0.01)
|
|
|
|
def forward(self, x):
|
|
out = self.net(x)
|
|
res = x if self.downsample is None else self.downsample(x)
|
|
return self.relu(out + res)
|
|
|
|
|
|
class TemporalConvNet(nn.Module):
|
|
"""
|
|
Temporal Convolutional Network.
|
|
Stack dari temporal blocks dengan increasing dilation.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
num_inputs,
|
|
num_channels,
|
|
kernel_size=3,
|
|
dropout=0.2
|
|
):
|
|
"""
|
|
Args:
|
|
num_inputs: Jumlah input channels
|
|
num_channels: List of hidden layer sizes
|
|
kernel_size: Kernel size untuk convolutions
|
|
dropout: Dropout rate
|
|
"""
|
|
super(TemporalConvNet, self).__init__()
|
|
|
|
layers = []
|
|
num_levels = len(num_channels)
|
|
|
|
for i in range(num_levels):
|
|
dilation_size = 2 ** i
|
|
in_channels = num_inputs if i == 0 else num_channels[i - 1]
|
|
out_channels = num_channels[i]
|
|
|
|
layers.append(TemporalBlock(
|
|
in_channels,
|
|
out_channels,
|
|
kernel_size,
|
|
stride=1,
|
|
dilation=dilation_size,
|
|
padding=(kernel_size - 1) * dilation_size,
|
|
dropout=dropout
|
|
))
|
|
|
|
self.network = nn.Sequential(*layers)
|
|
|
|
def forward(self, x):
|
|
return self.network(x)
|
|
|
|
|
|
class TCNClassifier(nn.Module):
|
|
"""
|
|
TCN-based classifier untuk gesture recognition.
|
|
|
|
Sesuai README:
|
|
- Input: Sequence dari landmark (30, 306)
|
|
- Output: Probability untuk setiap kelas
|
|
"""
|
|
def __init__(
|
|
self,
|
|
input_size: int,
|
|
num_classes: int,
|
|
num_channels: list = None,
|
|
kernel_size: int = 3,
|
|
dropout: float = 0.2,
|
|
fc_hidden_size: int = 512
|
|
):
|
|
"""
|
|
Args:
|
|
input_size: Ukuran input features (306)
|
|
num_classes: Jumlah kelas output
|
|
num_channels: Hidden layer sizes
|
|
kernel_size: Kernel size
|
|
dropout: Dropout rate
|
|
fc_hidden_size: Hidden size untuk fully connected layer
|
|
"""
|
|
super(TCNClassifier, self).__init__()
|
|
|
|
if num_channels is None:
|
|
num_channels = [64, 128, 256]
|
|
|
|
self.tcn = TemporalConvNet(
|
|
num_inputs=input_size,
|
|
num_channels=num_channels,
|
|
kernel_size=kernel_size,
|
|
dropout=dropout
|
|
)
|
|
|
|
tcn_output_size = num_channels[-1]
|
|
|
|
# Fully connected layers
|
|
self.fc = nn.Sequential(
|
|
nn.Linear(tcn_output_size, fc_hidden_size),
|
|
nn.ReLU(),
|
|
nn.Dropout(dropout),
|
|
nn.Linear(fc_hidden_size, num_classes)
|
|
)
|
|
|
|
def forward(self, x):
|
|
# x shape: (batch, seq_len, features)
|
|
# Transpose ke (batch, features, seq_len) untuk Conv1d
|
|
x = x.transpose(1, 2)
|
|
|
|
# TCN
|
|
tcn_out = self.tcn(x)
|
|
|
|
# Global average pooling
|
|
pooled = tcn_out.mean(dim=2)
|
|
|
|
# Classify
|
|
out = self.fc(pooled)
|
|
|
|
return out
|