使用 PyTorch 套件實現 IMU 原始時域數據經滑動窗口分割後,輸入到卷積神經網絡(ConvNet)進行特徵提取的實作流程。
我會從數據預處理到模型構建逐步說明,並提供具體的程式碼範例。
流程概述
1. 數據準備:假設 IMU 數據為多通道時序數據(例如 3 軸加速度計 + 3 軸陀螺儀,共 6 通道)。
2. 滑動窗口分割:使用滑動窗口將數據分段。
3. 數據預處理:將分段數據轉為 PyTorch 張量,並準備數據加載器(DataLoader)。
4. 構建 ConvNet 模型:設計一個簡單的卷積神經網絡提取特徵。
5. 訓練與特徵提取:運行模型,提取特徵向量。
1. 數據準備與滑動窗口分割
假設 IMU 數據為一個形狀為 (T, C) 的 NumPy 陣列,其中 T 是時間步長,C 是通道數(例如 6 個通道:加速度計 X、Y、Z 和陀螺儀 X、Y、Z)。我們使用滑動窗口分割數據。
滑動窗口分割:
滑動窗口的參數包括窗口大小(window_size)和步幅(stride)。窗口大小決定每個片段的長度,步幅決定窗口移動的距離。
========================================
[python]
import numpy as np
# 假設 IMU 數據
imu_data = np.random.randn(1000, 6) # 模擬數據:1000 個時間點,6 個通道
window_size = 128 # 窗口大小
stride = 64 # 步幅
# 滑動窗口分割函數
def sliding_window(data, window_size, stride):
num_samples = (len(data) - window_size) // stride + 1
segments = []
for i in range(num_samples):
start = i * stride
segment = data[start:start + window_size]
segments.append(segment)
return np.array(segments)
# 分割數據
segments = sliding_window(imu_data, window_size, stride)
print(segments.shape) # 形狀為 (num_segments, window_size, num_channels),例如 (15, 128, 6)
========================================
2. 數據預處理與數據加載
將分割後的數據轉為 PyTorch 張量,並使用 DataLoader 準備批量數據。
假設我們還有標籤(例如動作類別),形狀為 (num_segments,)。轉為 PyTorch 張量並創建數據集
========================================
[python]
import torch
from torch.utils.data import Dataset, DataLoader
# 假設標籤(模擬)
labels = np.random.randint(0, 5, size=(segments.shape[0],)) # 5 個動作類別
# 定義數據集
class IMUDataset(Dataset):
def __init__(self, segments, labels):
self.segments = torch.FloatTensor(segments) # 轉為張量
self.labels = torch.LongTensor(labels) # 標籤轉為張量
def __len__(self):
return len(self.segments)
def __getitem__(self, idx):
return self.segments[idx], self.labels[idx]
# 創建數據集和數據加載器
dataset = IMUDataset(segments, labels)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
========================================
數據形狀說明
• segments 的形狀為 (num_segments, window_size, num_channels)。
• 在 PyTorch 中,ConvNet 通常需要輸入形狀為 (batch_size, num_channels, window_size),因此需要調整維度(在模型中處理)。
3. 構建 ConvNet 模型
我們設計一個簡單的 ConvNet 來提取特徵。假設輸入數據為 (batch_size, num_channels, window_size),輸出為特徵向量。
模型架構
• 使用 1D 卷積(nn.Conv1d),因為 IMU 數據是時間序列。
• 包含卷積層、激活函數(ReLU)、池化層(MaxPooling),最後展平為特徵向量。
========================================
[python]
import torch.nn as nn
class ConvNet(nn.Module):
def __init__(self, num_channels=6, window_size=128, feature_dim=128):
super(ConvNet, self).__init__()
self.conv_layers = nn.Sequential(
nn.Conv1d(num_channels, 16, kernel_size=5, padding=2), # 卷積層1
nn.ReLU(),
nn.MaxPool1d(2), # 池化層
nn.Conv1d(16, 32, kernel_size=5, padding=2), # 卷積層2
nn.ReLU(),
nn.MaxPool1d(2),
)
# 計算展平後的維度
conv_output_size = window_size // 4 # 經過兩次池化(每次除以2)
self.fc = nn.Linear(32 * conv_output_size, feature_dim) # 全連接層輸出特徵向量
def forward(self, x):
# x 的形狀:(batch_size, window_size, num_channels)
x = x.permute(0, 2, 1) # 轉為 (batch_size, num_channels, window_size)
x = self.conv_layers(x) # 卷積層
x = x.view(x.size(0), -1) # 展平
features = self.fc(x) # 特徵向量
return features
# 初始化模型
model = ConvNet()
print(model)
========================================
4. 訓練與特徵提取
假設我們進行簡單的分類任務來訓練模型,同時提取特徵向量。為了完整性,我們添加一個分類頭。
>>添加分類頭並定義損失函數
========================================
[python]
class ConvNetClassifier(nn.Module):
def __init__(self, num_channels=6, window_size=128, feature_dim=128, num_classes=5):
super(ConvNetClassifier, self).__init__()
self.convnet = ConvNet(num_channels, window_size, feature_dim)
self.classifier = nn.Linear(feature_dim, num_classes) # 分類層
def forward(self, x):
features = self.convnet(x) # 提取特徵
logits = self.classifier(features) # 分類
return logits, features
# 初始化模型、損失函數和優化器
model = ConvNetClassifier()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
========================================
>>訓練過程
========================================
[python]
# 訓練循環
num_epochs = 10
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for inputs, targets in dataloader:
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
logits, features = model(inputs)
loss = criterion(logits, targets)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {running_loss / len(dataloader)}")
========================================
>>特徵提取
訓練完成後,可以使用模型提取特徵向量。
========================================
[python]
# 提取特徵
model.eval()
all_features = []
with torch.no_grad():
for inputs, _ in dataloader:
inputs = inputs.to(device)
_, features = model(inputs)
all_features.append(features.cpu().numpy())
# 合併特徵
all_features = np.concatenate(all_features, axis=0)
print(all_features.shape) # 形狀為 (num_segments, feature_dim)
========================================
5. 注意事項與優化
1. 數據標準化:
○ IMU 數據應進行標準化(例如零均值、單位方差),以提高模型性能。
========================================
[python]
imu_data = (imu_data - np.mean(imu_data, axis=0)) / np.std(imu_data, axis=0)
========================================
2. 窗口大小與步幅:
○ window_size 和 stride 需要根據動作頻率調整。例如,步行數據可能需要更大的窗口(例如 2 秒,128 個樣本點,若採樣率為 64Hz)。
○ 重疊窗口(stride < window_size)可以捕捉更多時序信息。
3. 模型設計:
○ ConvNet 層數和參數可以根據數據複雜度調整。
○ 可以添加 dropout 層(nn.Dropout)防止過擬合。
4. 後續處理:
○ 提取的特徵向量可以直接用於分類,或作為後續 RNN-LSTM 的輸入(如問題中的流程)。
完整程式碼總結
以下是完整的實作程式碼:
========================================
[python]
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
# 滑動窗口分割
def sliding_window(data, window_size, stride):
num_samples = (len(data) - window_size) // stride + 1
segments = []
for i in range(num_samples):
start = i * stride
segment = data[start:start + window_size]
segments.append(segment)
return np.array(segments)
# 數據集
class IMUDataset(Dataset):
def __init__(self, segments, labels):
self.segments = torch.FloatTensor(segments)
self.labels = torch.LongTensor(labels)
def __len__(self):
return len(self.segments)
def __getitem__(self, idx):
return self.segments[idx], self.labels[idx]
# ConvNet 模型
class ConvNet(nn.Module):
def __init__(self, num_channels=6, window_size=128, feature_dim=128):
super(ConvNet, self).__init__()
self.conv_layers = nn.Sequential(
nn.Conv1d(num_channels, 16, kernel_size=5, padding=2),
nn.ReLU(),
nn.MaxPool1d(2),
nn.Conv1d(16, 32, kernel_size=5, padding=2),
nn.ReLU(),
nn.MaxPool1d(2),
)
conv_output_size = window_size // 4
self.fc = nn.Linear(32 * conv_output_size, feature_dim)
def forward(self, x):
x = x.permute(0, 2, 1)
x = self.conv_layers(x)
x = x.view(x.size(0), -1)
features = self.fc(x)
return features
# 分類模型
class ConvNetClassifier(nn.Module):
def __init__(self, num_channels=6, window_size=128, feature_dim=128, num_classes=5):
super(ConvNetClassifier, self).__init__()
self.convnet = ConvNet(num_channels, window_size, feature_dim)
self.classifier = nn.Linear(feature_dim, num_classes)
def forward(self, x):
features = self.convnet(x)
logits = self.classifier(features)
return logits, features
# 數據準備
imu_data = np.random.randn(1000, 6)
window_size, stride = 128, 64
segments = sliding_window(imu_data, window_size, stride)
labels = np.random.randint(0, 5, size=(segments.shape[0],))
# 數據加載
dataset = IMUDataset(segments, labels)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
# 訓練
model = ConvNetClassifier()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
num_epochs = 10
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for inputs, targets in dataloader:
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
logits, features = model(inputs)
loss = criterion(logits, targets)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {running_loss / len(dataloader)}")
# 特徵提取
model.eval()
all_features = []
with torch.no_grad():
for inputs, _ in dataloader:
inputs = inputs.to(device)
_, features = model(inputs)
all_features.append(features.cpu().numpy())
all_features = np.concatenate(all_features, axis=0)
print(all_features.shape)
========================================
結論
這段程式碼實現了從 IMU 數據分割到特徵提取的完整流程。提取的特徵向量可以直接用於分類,或者作為後續模型(如 RNN-LSTM)的輸入。
如果你有具體的數據集或需要進一步調整模型結構,請提供更多信息,我可以幫你優化!
沒有留言:
張貼留言