代码资料:
https://github.com/Whiffe/SCB-dataset/tree/main/EvaluationMethod/LSTM
上面的代码资料分两部分,代码与数据。
LSTM的三个文件只是参数和加载的训练测试文件不一样。
train test 是视频的行为序列与专注度分类。
图 6 学生科学实验评估流程
从下面的图可以看出train.csv,train2.csv的区别在于:train.csv中的ID 0,在train2.csv中变成了ID 5,这样做的目的是,在后续标准化的过程中,会进行补0操作,与train.csv中的发生冲突,所以在train2.csv中将0替换成了5.
下面是我在论文中描述的整个流程:
如图 6所示,是学生科学实验评估过程,输入的是学生录制的视频,然后抽帧,将抽帧的视频送入学生科学实验模型中检测,输出数据包括学生ID、时间ID和行为ID,其中0代表称重、1代表测高度、2代表丢球、3代表测大小、4代表记录。然后,我们重新组织这些行为数据,如图 6的步骤1所示,我们将整个视频中的每一位学生的行为集中在一个数组中,形成学生的原始行为序列,需要注意的是,由于行为的连续性,这些原始行为序列通常包含大量的连续重复行为ID,为了后续标准化与模型训练的方便,我们对原始行为序列进行重复删除的操作,然后对每个行为ID+1,这样做的目的是让行为ID不从0开始,如图 6的步骤2。
接下来,我们按照训练是设置的batch size大小来对行为序列进行标准化,找到每组中最大序列长度,然后按照这个最大的序列长度,对该组内的行为序列进行末尾空白补0,使得每一组内部的序列长度一致如图 6的步骤3。
我们选取了503个学生科学实验视频,然后将这些视频分成3类,高专注(286个,id=0)、中专注(100个,id=1)、低专注(117个,id=2),通过RE-DETR-ASF行为检测模型检测出原始行为序列,然后行为序列去重,行为ID+1,在进行对序列进行末尾补0标准化,这样就形成了一个学生行为序列专注度的数据集,如图 6的步骤4。
我们选择LSTM模型学生行为序列专注度数据集进行训练与测试,原因是LSTM模型能够有效地处理序列数据中的长期依赖关系,具有较强的记忆能力和灵活性,在很多序列建模任务中表现出色,并且有丰富的研究基础支持其应用。训练结束后的模型,我们就用于对学生视频专注度的预测,图 6的步骤5。
学生科学实验评估
我们选取了503个视频,将视频分成3类,286个高专注、100个中专注、117个低专注,然后通过RE-DETR-ASF行为检测模型检测出视频的原始行为序列,然后行为序列去重,行为ID+1,在进行对序列进行末尾补0标准化,然后按照4:1将行为序列划分为训练集与测试集,我们再将数据集送入LSTM模型中进行训练,最后在测试集上的准确度为:86.0%。
代码如下:
! pip install pandas
import torch
import torch.nn as nn
import pandas as pd
from torch.utils.data import Dataset, DataLoader
import torch.nn.functional as F
# 定义LSTM模型
class LSTMModel(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_classes):
super(LSTMModel, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, num_classes)
def forward(self, x):
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
out, _ = self.lstm(x, (h0, c0))
out = self.fc(out[:, -1, :])
return out
# 自定义数据集类
class CustomDataset(Dataset):
def __init__(self, csv_file):
self.data = pd.read_csv(csv_file)
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
sequence = list(map(int, str(self.data.iloc[idx, 0]).split(', '))) # 将字符串序列转换为整数列表
label = int(self.data.iloc[idx, 1]) # 获取标签
return torch.tensor(sequence), torch.tensor(label)
# 超参数设置
input_size = 1 # 输入特征维度(每个行为序列中的元素个数)
hidden_size = 128 # LSTM隐藏单元数
num_layers = 2 # LSTM层数
num_classes = 3 # 分类类别数
batch_size = 4
num_epochs = 20
learning_rate = 0.0001
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def collate_fn(batch):
sequences, labels = zip(*batch)
# 找到最长的序列长度
max_length = max([len(seq) for seq in sequences])
# 填充序列
sequences_padded = [torch.cat([seq, torch.zeros(max_length - len(seq)).long()]) for seq in sequences]
return torch.stack(sequences_padded, dim=0), torch.tensor(labels)
# 加载数据集
dataset = CustomDataset('/root/LSTM2/train.csv') # 修改为Excel文件的路径
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
# loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# 初始化模型、损失函数和优化器
model = LSTMModel(input_size, hidden_size, num_layers, num_classes).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 训练模型
total_step = len(loader)
for epoch in range(num_epochs):
for i, (sequences, labels) in enumerate(loader):
# print(i, (sequences, labels))
# input()
sequences = sequences.view(-1, len(sequences[0]), input_size).float().to(device) # 将输入形状调整为(batch_size, sequence_length, input_size)
labels = labels.to(device)
# 正向传播
outputs = model(sequences)
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 20 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, i+1, total_step, loss.item()))
print('训练完成')
# 在测试集上评估模型
# ...
# 加载测试数据集
test_dataset = CustomDataset('/root/LSTM2/test.csv') # 使用测试集的CSV文件路径
# test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)
# 将模型设置为评估模式
model.eval()
total_correct = 0
total_samples = 0
with torch.no_grad():
for sequences, labels in test_loader:
sequences = sequences.view(-1, len(sequences[0]), input_size).float().to(device) # 将输入形状调整为(batch_size, sequence_length, input_size)
# sequences = sequences.unsqueeze(2).to(device) # 添加一个维度,将序列变成三维张量
labels = labels.to(device)
# 正向传播
outputs = model(sequences)
# probabilities = F.softmax(outputs, dim=1) # 应用softmax变换
_, predicted = torch.max(outputs, 1)
# 统计正确预测的样本数量
total_correct += (predicted == labels).sum().item()
total_samples += labels.size(0)
# 计算准确率
accuracy = total_correct / total_samples
print('在测试集上的准确率: {:.2f}%'.format(accuracy * 100))