身体活动(physical activity)2---深度学习
·
Human Activity Recognition Using Smartphones Dataset
数据集地址http://archive.ics.uci.edu/ml/datasets/Human+Activity+Recognition+Using+Smartphones
对于智能家居、健康监护等场景来说,如何保护用户隐私一直是个痛点。基于摄像头的方案虽然直观,但隐私问题挥之不去。相比之下,雷达技术因其非侵入式、保护隐私的特性而备受关注。
原始数据有9个输入通道。当前只采用1个通道数据
只用 body_acc_x(一个通道),把每个 128 点的时间窗做 STFT(短时傅里叶变换)→ 时频谱(log-幅值谱),然后用一个小型 2D CNN 做 6 类分类(UCI HAR 的 6 个动作)。你只需要把 DATA_DIR 改成你本地 UCI HAR Dataset 的路径即可。
# -*- coding: utf-8 -*-
"""
UCI HAR - single channel (body_acc_x) -> STFT spectrogram -> 2D CNN classification
Author: you
"""
import os
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt # ← 新增:用于保存时频谱图片
# ---------------------------
# 0. 可复现实验
# ---------------------------
SEED = 2025
torch.manual_seed(SEED)
np.random.seed(SEED)
# ---------------------------
# 1. 仅加载 body_acc_x + 标签
# ---------------------------
def load_body_acc_x(root, split="train"):
"""
root: 指向 'UCI HAR Dataset' 目录
split: 'train' 或 'test'
返回:
X: (N, 128) float32
y: (N,) int64 in [0..5]
"""
inertial_dir = os.path.join(root, split, "Inertial Signals")
assert os.path.isdir(inertial_dir), f"Not found: {inertial_dir}"
# 标签:1..6 -> 0..5
y_path = os.path.join(root, split, f"y_{split}.txt")
y = np.loadtxt(y_path, dtype=int) - 1
# 仅 body_acc_x
fname = "body_acc_x_train.txt" if split == "train" else "body_acc_x_test.txt"
X = np.loadtxt(os.path.join(inertial_dir, fname)).astype(np.float32) # (N, 128)
assert X.shape[0] == y.shape[0], f"X and y size mismatch: {X.shape[0]} vs {y.shape[0]}"
return X, y.astype(np.int64)
# ---------------------------
# 2. 批量 STFT -> 时频谱
# ---------------------------
def stft_spectrogram_batch(x_np, n_fft=64, win_len=32, hop_len=8, log_eps=1e-6):
"""
x_np: (N, T) numpy float32
返回: torch.Tensor (N, 1, F, T_frames),log(1+|STFT|)
说明:
- UCI HAR 每窗 T=128, 采样率约 50Hz(这里仅相对频率,不影响 CNN)
- n_fft >= win_len;win_len=32, hop=8 => 时间帧数较充分(约 13 帧)
"""
x = torch.from_numpy(x_np) # (N, T)
# 去均值(去漂移/重力趋势对频谱的影响)
x = x - x.mean(dim=1, keepdim=True)
window = torch.hann_window(win_len)
# stft: (N, F, T_frames), complex
Xc = torch.stft(
x, n_fft=n_fft, hop_length=hop_len, win_length=win_len,
window=window, center=False, return_complex=True
)
mag = torch.abs(Xc) # (N, F, T_frames)
spec = torch.log1p(mag + log_eps) # 稳定的对数幅度
spec = spec.unsqueeze(1) # (N, 1, F, T_frames)
return spec
# ---------------------------
# 3. 数据集(用预计算的谱,做标准化)
# ---------------------------
class SpecTensorDataset(Dataset):
def __init__(self, spec_tensor, labels, mean=None, std=None):
"""
spec_tensor: (N, 1, F, T_frames) torch.float32
labels: (N,) numpy 或 torch
mean, std: 用训练集计算的全局均值和方差(标量)
"""
self.X = spec_tensor
self.y = torch.as_tensor(labels, dtype=torch.long)
if mean is None or std is None:
mean = self.X.mean()
std = self.X.std() + 1e-8
self.mean = mean
self.std = std
def __len__(self):
return self.X.shape[0]
def __getitem__(self, idx):
x = (self.X[idx] - self.mean) / self.std
return x, self.y[idx]
# ---------------------------
# 4. 一个轻量 2D CNN
# ---------------------------
class SpecCNN(nn.Module):
def __init__(self, num_classes=6):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # 下采样 1/2
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2), # 再下采样 1/2
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d((4, 4)) # 自适应汇聚到固定大小
)
self.head = nn.Sequential(
nn.Flatten(),
nn.Dropout(0.4),
nn.Linear(128 * 4 * 4, 256),
nn.ReLU(inplace=True),
nn.Dropout(0.4),
nn.Linear(256, num_classes)
)
def forward(self, x):
x = self.net(x)
x = self.head(x)
return x
# ---------------------------
# 可视化:保存前两条样本的时频谱图(转换后的样本图片)
# ---------------------------
def save_spec_image(spec_tensor, idx, out_path, title_prefix="Train Sample"):
"""
spec_tensor: (N, 1, F, T_frames) torch.float32
idx: 样本索引
out_path: 保存路径
"""
spec = spec_tensor[idx, 0].cpu().numpy() # 取出 (F, T_frames)
plt.figure(figsize=(6, 4))
plt.imshow(spec, aspect='auto', origin='lower') # 频率从下到上
plt.colorbar(label='log(1+|STFT|)')
plt.xlabel('Time frames')
plt.ylabel('Frequency bins')
plt.title(f'{title_prefix} #{idx}')
plt.tight_layout()
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
plt.savefig(out_path, dpi=200)
plt.close()
# ---------------------------
# 5. 训练与评估
# ---------------------------
def train_one_epoch(model, loader, criterion, optimizer, device):
model.train()
total_loss, total_correct, total = 0.0, 0, 0
for xb, yb in loader:
xb, yb = xb.to(device), yb.to(device)
optimizer.zero_grad(set_to_none=True)
logits = model(xb)
loss = criterion(logits, yb)
loss.backward()
optimizer.step()
total_loss += loss.item() * yb.size(0)
total_correct += (logits.argmax(1) == yb).sum().item()
total += yb.size(0)
return total_loss / total, total_correct / total
@torch.no_grad()
def evaluate(model, loader, criterion, device):
model.eval()
total_loss, total_correct, total = 0.0, 0, 0
for xb, yb in loader:
xb, yb = xb.to(device), yb.to(device)
logits = model(xb)
loss = criterion(logits, yb)
total_loss += loss.item() * yb.size(0)
total_correct += (logits.argmax(1) == yb).sum().item()
total += yb.size(0)
return total_loss / total, total_correct / total
# ---------------------------
# 6. 主流程
# ---------------------------
def main():
# 1) 改成你的本地路径
DATA_DIR = r"D:\project\deep-learning-for-image-processing-master\data_set\UCI HAR Dataset\UCI HAR Dataset"
# 2) 仅 body_acc_x
X_tr, y_tr = load_body_acc_x(DATA_DIR, "train")
X_te, y_te = load_body_acc_x(DATA_DIR, "test")
# 3) 批量生成时频谱(log|STFT|)
# 参数可按需调整:n_fft=64, win_len=32, hop_len=8
spec_tr = stft_spectrogram_batch(X_tr, n_fft=64, win_len=32, hop_len=8) # (N,1,F,Tf)
spec_te = stft_spectrogram_batch(X_te, n_fft=64, win_len=32, hop_len=8)
# —— 输出两张“转换后的样本图片” —— #
save_spec_image(spec_tr, 0, "figs/spec_train_sample_1.png", title_prefix="Train Spectrogram")
save_spec_image(spec_tr, 1, "figs/spec_train_sample_2.png", title_prefix="Train Spectrogram")
print("Saved spectrogram images to:",
"figs/spec_train_sample_1.png, figs/spec_train_sample_2.png")
# 4) 用训练集统计做标准化
mean = spec_tr.mean()
std = spec_tr.std() + 1e-8
train_ds = SpecTensorDataset(spec_tr, y_tr, mean, std)
test_ds = SpecTensorDataset(spec_te, y_te, mean, std)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True, num_workers=0, drop_last=False)
test_loader = DataLoader(test_ds, batch_size=256, shuffle=False, drop_last=False)
# 5) 模型/优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = SpecCNN(num_classes=6).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=3)
# 6) 训练
epochs = 80
best_acc, best_state = 0.0, None
for ep in range(1, epochs + 1):
tr_loss, tr_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)
te_loss, te_acc = evaluate(model, test_loader, criterion, device)
scheduler.step(te_acc)
if te_acc > best_acc:
best_acc, best_state = te_acc, {k: v.cpu().clone() for k, v in model.state_dict().items()}
print(f"Epoch {ep:02d} | train loss {tr_loss:.4f} acc {tr_acc:.4f} | "
f"test loss {te_loss:.4f} acc {te_acc:.4f} | best {best_acc:.4f}")
# 7) 载入最佳模型并最终评估
if best_state is not None:
model.load_state_dict(best_state)
te_loss, te_acc = evaluate(model, test_loader, criterion, device)
print(f"\nFinal Test | loss {te_loss:.4f} acc {te_acc:.4f}")
if __name__ == "__main__":
main()
更多推荐
所有评论(0)