|
|
""" |
|
|
PAD预测器模型 |
|
|
PAD Predictor Model for emotion and physiological state change prediction |
|
|
|
|
|
该模型实现了一个多层感知机(MLP)来预测用户情绪和生理状态的变化。 |
|
|
输入:7维 (User PAD 3维 + Vitality 1维 + Current PAD 3维) |
|
|
输出:5维 (ΔPAD 3维 + ΔPressure 1维 + Confidence 1维) |
|
|
""" |
|
|
|
|
|
import torch |
|
|
import torch.nn as nn |
|
|
import torch.nn.functional as F |
|
|
from typing import Dict, Any, Optional, Tuple |
|
|
import logging |
|
|
|
|
|
|
|
|
class PADPredictor(nn.Module): |
|
|
""" |
|
|
PAD情绪和生理状态变化预测器 |
|
|
|
|
|
网络架构: |
|
|
- 输入层:7维特征 |
|
|
- 隐藏层1:128神经元 + ReLU + Dropout(0.3) |
|
|
- 隐藏层2:64神经元 + ReLU + Dropout(0.3) |
|
|
- 隐藏层3:32神经元 + ReLU |
|
|
- 输出层:5神经元 + Linear激活 |
|
|
""" |
|
|
|
|
|
def __init__(self, |
|
|
input_dim: int = 10, |
|
|
output_dim: int = 4, |
|
|
hidden_dims: list = [128, 64, 32], |
|
|
dropout_rate: float = 0.3, |
|
|
weight_init: str = "xavier_uniform", |
|
|
bias_init: str = "zeros"): |
|
|
""" |
|
|
初始化PAD预测器 |
|
|
|
|
|
Args: |
|
|
input_dim: 输入维度,默认10维(7原始特征+3差异特征) |
|
|
output_dim: 输出维度,默认4维(ΔPAD 3维 + ΔPressure 1维) |
|
|
hidden_dims: 隐藏层维度列表 |
|
|
dropout_rate: Dropout概率 |
|
|
weight_init: 权重初始化方法 |
|
|
bias_init: 偏置初始化方法 |
|
|
""" |
|
|
super(PADPredictor, self).__init__() |
|
|
|
|
|
self.input_dim = input_dim |
|
|
self.output_dim = output_dim |
|
|
self.hidden_dims = hidden_dims |
|
|
self.dropout_rate = dropout_rate |
|
|
self.weight_init = weight_init |
|
|
self.bias_init = bias_init |
|
|
|
|
|
|
|
|
self._build_network() |
|
|
|
|
|
|
|
|
self._initialize_weights() |
|
|
|
|
|
|
|
|
self.logger = logging.getLogger(__name__) |
|
|
|
|
|
def _build_network(self): |
|
|
"""构建网络架构""" |
|
|
layers = [] |
|
|
|
|
|
|
|
|
layers.append(nn.Linear(self.input_dim, self.hidden_dims[0])) |
|
|
layers.append(nn.ReLU()) |
|
|
layers.append(nn.Dropout(self.dropout_rate)) |
|
|
|
|
|
|
|
|
for i in range(len(self.hidden_dims) - 1): |
|
|
layers.append(nn.Linear(self.hidden_dims[i], self.hidden_dims[i + 1])) |
|
|
layers.append(nn.ReLU()) |
|
|
layers.append(nn.Dropout(self.dropout_rate)) |
|
|
|
|
|
|
|
|
layers.append(nn.Linear(self.hidden_dims[-1], self.output_dim)) |
|
|
|
|
|
|
|
|
|
|
|
self.network = nn.Sequential(*layers) |
|
|
|
|
|
def _initialize_weights(self): |
|
|
"""初始化网络权重""" |
|
|
for m in self.modules(): |
|
|
if isinstance(m, nn.Linear): |
|
|
|
|
|
if self.weight_init == "xavier_uniform": |
|
|
nn.init.xavier_uniform_(m.weight) |
|
|
elif self.weight_init == "xavier_normal": |
|
|
nn.init.xavier_normal_(m.weight) |
|
|
elif self.weight_init == "kaiming_uniform": |
|
|
nn.init.kaiming_uniform_(m.weight, nonlinearity='relu') |
|
|
elif self.weight_init == "kaiming_normal": |
|
|
nn.init.kaiming_normal_(m.weight, nonlinearity='relu') |
|
|
else: |
|
|
|
|
|
nn.init.xavier_uniform_(m.weight) |
|
|
|
|
|
|
|
|
if self.bias_init == "zeros": |
|
|
nn.init.zeros_(m.bias) |
|
|
elif self.bias_init == "ones": |
|
|
nn.init.ones_(m.bias) |
|
|
else: |
|
|
|
|
|
nn.init.zeros_(m.bias) |
|
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor: |
|
|
""" |
|
|
前向传播 |
|
|
|
|
|
Args: |
|
|
x: 输入张量,形状为 (batch_size, input_dim) |
|
|
|
|
|
Returns: |
|
|
输出张量,形状为 (batch_size, output_dim) |
|
|
""" |
|
|
|
|
|
if x.dim() != 2: |
|
|
raise ValueError(f"输入张量应该是2维的 (batch_size, input_dim),但得到的是 {x.dim()}维") |
|
|
|
|
|
if x.size(1) != self.input_dim: |
|
|
raise ValueError(f"输入维度应该是 {self.input_dim},但得到的是 {x.size(1)}") |
|
|
|
|
|
|
|
|
output = self.network(x) |
|
|
|
|
|
return output |
|
|
|
|
|
def predict_components(self, x: torch.Tensor) -> Dict[str, torch.Tensor]: |
|
|
""" |
|
|
预测并分解输出组件 |
|
|
|
|
|
Args: |
|
|
x: 输入张量 |
|
|
|
|
|
Returns: |
|
|
包含各组件的字典: |
|
|
- 'delta_pad': ΔPAD (3维) |
|
|
- 'delta_pressure': ΔPressure (1维) |
|
|
- 'confidence': Confidence (1维) |
|
|
""" |
|
|
output = self.forward(x) |
|
|
|
|
|
|
|
|
delta_pad = output[:, :3] |
|
|
delta_pressure = output[:, 3:4] |
|
|
confidence = output[:, 4:5] |
|
|
|
|
|
return { |
|
|
'delta_pad': delta_pad, |
|
|
'delta_pressure': delta_pressure, |
|
|
'confidence': confidence |
|
|
} |
|
|
|
|
|
def get_model_info(self) -> Dict[str, Any]: |
|
|
""" |
|
|
获取模型信息 |
|
|
|
|
|
Returns: |
|
|
包含模型信息的字典 |
|
|
""" |
|
|
total_params = sum(p.numel() for p in self.parameters()) |
|
|
trainable_params = sum(p.numel() for p in self.parameters() if p.requires_grad) |
|
|
|
|
|
return { |
|
|
'model_type': 'PADPredictor', |
|
|
'input_dim': self.input_dim, |
|
|
'output_dim': self.output_dim, |
|
|
'hidden_dims': self.hidden_dims, |
|
|
'dropout_rate': self.dropout_rate, |
|
|
'weight_init': self.weight_init, |
|
|
'bias_init': self.bias_init, |
|
|
'total_parameters': total_params, |
|
|
'trainable_parameters': trainable_params, |
|
|
'architecture': str(self.network) |
|
|
} |
|
|
|
|
|
def save_model(self, filepath: str, include_optimizer: bool = False, optimizer: Optional[torch.optim.Optimizer] = None): |
|
|
""" |
|
|
保存模型 |
|
|
|
|
|
Args: |
|
|
filepath: 保存路径 |
|
|
include_optimizer: 是否包含优化器状态 |
|
|
optimizer: 优化器对象 |
|
|
""" |
|
|
save_dict = { |
|
|
'model_state_dict': self.state_dict(), |
|
|
'model_config': { |
|
|
'input_dim': self.input_dim, |
|
|
'output_dim': self.output_dim, |
|
|
'hidden_dims': self.hidden_dims, |
|
|
'dropout_rate': self.dropout_rate, |
|
|
'weight_init': self.weight_init, |
|
|
'bias_init': self.bias_init |
|
|
}, |
|
|
'model_info': self.get_model_info() |
|
|
} |
|
|
|
|
|
if include_optimizer and optimizer is not None: |
|
|
save_dict['optimizer_state_dict'] = optimizer.state_dict() |
|
|
|
|
|
torch.save(save_dict, filepath) |
|
|
self.logger.info(f"模型已保存到: {filepath}") |
|
|
|
|
|
@classmethod |
|
|
def load_model(cls, filepath: str, device: str = 'cpu'): |
|
|
""" |
|
|
加载模型 |
|
|
|
|
|
Args: |
|
|
filepath: 模型文件路径 |
|
|
device: 设备类型 |
|
|
|
|
|
Returns: |
|
|
PADPredictor实例 |
|
|
""" |
|
|
|
|
|
try: |
|
|
checkpoint = torch.load(filepath, map_location=device, weights_only=False) |
|
|
except TypeError: |
|
|
|
|
|
checkpoint = torch.load(filepath, map_location=device) |
|
|
|
|
|
|
|
|
model_config = checkpoint.get('model_config') or checkpoint.get('config') |
|
|
if model_config is None: |
|
|
raise KeyError("Checkpoint 中未找到 'model_config' 或 'config' 键") |
|
|
|
|
|
model = cls(**model_config) |
|
|
|
|
|
|
|
|
model.load_state_dict(checkpoint['model_state_dict']) |
|
|
|
|
|
logging.info(f"模型已从 {filepath} 加载") |
|
|
return model |
|
|
|
|
|
def freeze_layers(self, layer_names: list = None): |
|
|
""" |
|
|
冻结指定层 |
|
|
|
|
|
Args: |
|
|
layer_names: 要冻结的层名称列表,如果为None则冻结所有层 |
|
|
""" |
|
|
if layer_names is None: |
|
|
|
|
|
for param in self.parameters(): |
|
|
param.requires_grad = False |
|
|
self.logger.info("所有层已被冻结") |
|
|
else: |
|
|
|
|
|
for name, param in self.named_parameters(): |
|
|
for layer_name in layer_names: |
|
|
if layer_name in name: |
|
|
param.requires_grad = False |
|
|
break |
|
|
self.logger.info(f"指定层 {layer_names} 已被冻结") |
|
|
|
|
|
def unfreeze_layers(self, layer_names: list = None): |
|
|
""" |
|
|
解冻指定层 |
|
|
|
|
|
Args: |
|
|
layer_names: 要解冻的层名称列表,如果为None则解冻所有层 |
|
|
""" |
|
|
if layer_names is None: |
|
|
|
|
|
for param in self.parameters(): |
|
|
param.requires_grad = True |
|
|
self.logger.info("所有层已被解冻") |
|
|
else: |
|
|
|
|
|
for name, param in self.named_parameters(): |
|
|
for layer_name in layer_names: |
|
|
if layer_name in name: |
|
|
param.requires_grad = True |
|
|
break |
|
|
self.logger.info(f"指定层 {layer_names} 已被解冻") |
|
|
|
|
|
|
|
|
def create_pad_predictor(config: Optional[Dict[str, Any]] = None) -> PADPredictor: |
|
|
""" |
|
|
创建PAD预测器的工厂函数 |
|
|
|
|
|
Args: |
|
|
config: 配置字典,包含模型参数 |
|
|
|
|
|
Returns: |
|
|
PADPredictor实例 |
|
|
""" |
|
|
if config is None: |
|
|
|
|
|
return PADPredictor() |
|
|
|
|
|
|
|
|
model_config = config.get('architecture', {}) |
|
|
|
|
|
return PADPredictor( |
|
|
input_dim=config.get('dimensions', {}).get('input_dim', 10), |
|
|
output_dim=config.get('dimensions', {}).get('output_dim', 4), |
|
|
hidden_dims=[layer['size'] for layer in model_config.get('hidden_layers', [])], |
|
|
dropout_rate=model_config.get('dropout_config', {}).get('rate', 0.3), |
|
|
weight_init=config.get('initialization', {}).get('weight_init', 'xavier_uniform'), |
|
|
bias_init=config.get('initialization', {}).get('bias_init', 'zeros') |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
|
|
|
|
|
|
|
|
model = PADPredictor().to(device) |
|
|
|
|
|
|
|
|
print("模型信息:") |
|
|
info = model.get_model_info() |
|
|
for key, value in info.items(): |
|
|
print(f" {key}: {value}") |
|
|
|
|
|
|
|
|
batch_size = 4 |
|
|
x = torch.randn(batch_size, 7).to(device) |
|
|
|
|
|
with torch.no_grad(): |
|
|
output = model(x) |
|
|
components = model.predict_components(x) |
|
|
|
|
|
print(f"\n输入形状: {x.shape}") |
|
|
print(f"输出形状: {output.shape}") |
|
|
print(f"ΔPAD形状: {components['delta_pad'].shape}") |
|
|
print(f"ΔPressure形状: {components['delta_pressure'].shape}") |
|
|
print(f"Confidence形状: {components['confidence'].shape}") |
|
|
|
|
|
print("\n模型测试完成!") |