2025年4月21日 星期一

PyTorch 套件實現 IMU 原始時域數據經滑動窗口分割

 使用 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)的輸入。

如果你有具體的數據集或需要進一步調整模型結構,請提供更多信息,我可以幫你優化!


沒有留言:

張貼留言