Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use nn.Embedding() to handle emg_data #55

Open
shiyi099 opened this issue Oct 18, 2024 · 19 comments
Open

use nn.Embedding() to handle emg_data #55

shiyi099 opened this issue Oct 18, 2024 · 19 comments

Comments

@shiyi099
Copy link

shiyi099 commented Oct 18, 2024

Will it be better when I use nn.Embedding(num_embeddings=256) to handle emg_data? Due to its range from [-128,127].
I use Pytorch to reproduce your project. However, my model's accuracy on val data is under 70%. If we directly use 8-dimensional sequence data, will the features be too singular? Here is my code, could you do me a favor to improve that val_result?

# -*- coding: utf-8 -*-
"""
Created on Fri Oct 18 17:48:07 2024

@author: shrim
"""

import os
import numpy as np
import torch
import torch.nn.functional as F
from torch import nn
from torch.utils.data import DataLoader, Dataset, TensorDataset, random_split
import torch.optim as optim
from torch.optim.lr_scheduler import ExponentialLR


#data = np.loadtxt(f"{root_path}/12345-1/2.txt", delimiter = ",")
def split_dataset(hand = 'R', train_ratio=0.8):
    #config = TUNADataset_config
    #npz_files = [f for f in os.listdir(config['npz_path']) if os.path.splitext(f)[-1]=='.npz']
    if hand.strip().upper() == 'R':
        root_path = 'myo-readings-dataset-main/_readings_right_hand'
    else:
        root_path = 'myo-readings-dataset-main/_readings_left_hand'
    folders = os.listdir(root_path)
    train_data_num =  int(round(len(folders)*train_ratio))
    val_data_num = len(folders) - train_data_num
    full_index = np.arange(len(folders))
    train_data_index = np.random.choice(full_index, size=train_data_num, replace=False)
    val_data_index = np.setdiff1d(full_index, train_data_index)
    return np.sort(train_data_index), np.sort(val_data_index)

class MyoEMGDataset(Dataset):
    def __init__(self, hand, folders_id):
        self.data = None
        self.label = None
        
        if hand.strip().upper() == 'R':
            root_path = 'myo-readings-dataset-main/_readings_right_hand'
        else:
            root_path = 'myo-readings-dataset-main/_readings_left_hand'
        
        folders = os.listdir(root_path)
        
        for f in folders_id:
            for i in os.listdir(root_path+os.sep+folders[f]):
                try:
                    txt = np.loadtxt(f"{root_path}/{folders[f]}/{i}", delimiter = ",")
                except:
                    print(f"Wrong! -> {folders[f]}/{i}")
                    continue
                data = torch.from_numpy(txt[:,:-1]+128).long()
                labels = torch.from_numpy(txt[:,-1]).long()
                if self.data is None:
                    self.data = data
                    self.labels = labels
                else:
                    self.data = torch.cat([self.data,data],dim = 0)
                    self.labels = torch.cat([self.labels,labels],dim = 0)
    def __len__(self):
        return len(self.data)
     
    def __getitem__(self, idx):
        # 获取数据和标签
        x = self.data[idx]
        y = self.labels[idx]
        return x, y     


class MyoEMGNet(nn.Module):
    def __init__(self):
        super(MyoEMGNet, self).__init__()
        
        self.embedding = nn.Embedding(num_embeddings=256, embedding_dim=25)
        
        # First hidden layer
        self.fc1 = nn.Linear(25*8, 256)
        self.bn1 = nn.BatchNorm1d(256)
        self.relu1 = nn.PReLU()
        self.dropout1 = nn.Dropout(p=0.5)
        
        # Second hidden layer
        self.fc2 = nn.Linear(256, 128)
        self.bn2 = nn.BatchNorm1d(128)
        self.relu2 = nn.PReLU()
        self.dropout2 = nn.Dropout(p=0.5)
        
        # Third hidden layer
        self.fc3 = nn.Linear(128, 128)
        self.bn3 = nn.BatchNorm1d(128)
        self.relu3 = nn.PReLU()
        self.dropout3 = nn.Dropout(p=0.5)
        
        # Fourth hidden layer
        self.fc4 = nn.Linear(128, 64)
        self.bn4 = nn.BatchNorm1d(64)
        self.relu4 = nn.PReLU()
        self.dropout4 = nn.Dropout(p=0.5)
        
        # Fifth hidden layer
        self.fc5 = nn.Linear(64, 64)
        self.bn5 = nn.BatchNorm1d(64)
        self.relu5 = nn.PReLU()
        self.dropout5 = nn.Dropout(p=0.5)
        
        # Output layer
        self.fc6 = nn.Linear(64, 8)
    
    def forward(self, x):
        x = self.embedding(x)
        x = x.contiguous().view(x.size(0), -1)
        
        x = self.fc1(x)
        x = self.bn1(x)
        x = self.relu1(x)
        x = self.dropout1(x)
        
        x = self.fc2(x)
        x = self.bn2(x)
        x = self.relu2(x)
        x = self.dropout2(x)
        
        x = self.fc3(x)
        x = self.bn3(x)
        x = self.relu3(x)
        x = self.dropout3(x)
        
        x = self.fc4(x)
        x = self.bn4(x)
        x = self.relu4(x)
        x = self.dropout4(x)
        
        x = self.fc5(x)
        x = self.bn5(x)
        x = self.relu5(x)
        x = self.dropout5(x)
        
        x = self.fc6(x)
        return x


hand = 'L'

train_data_id, val_data_id = split_dataset(hand = hand , train_ratio=0.9)

train_dataset = MyoEMGDataset(hand = hand , folders_id = train_data_id)
val_dataset = MyoEMGDataset(hand = hand , folders_id = val_data_id)
#test_dataset = MyoEMGDataset(hand = 'R', folders_id = val_data_id[-1])

train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=8, shuffle=False)
#test_loader = DataLoader(val_dataset, batch_size=1, shuffle=False)

num_epochs = 1000

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)
model = MyoEMGNet()
model.cuda()

for epoch in range(num_epochs):
    model.train()
    train_loss = 0.0
    for inputs, targets in train_loader:
        inputs, targets = inputs.cuda(), targets.cuda()            
        optimizer.zero_grad()
        outputs = model(inputs)
        
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        
        train_loss += loss.item() * inputs.size(0)  # 累计损失
    
    train_loss /= len(train_loader.dataset)

    model.eval()
    val_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for val_inputs, val_targets in val_loader:
            val_inputs, val_targets = val_inputs.cuda(), val_targets.cuda()
            
            val_outputs = model(val_inputs)
            
            loss = criterion(val_outputs, val_targets)
            val_loss += loss.item() * val_inputs.size(0)  # 累计损失
            
            val_outputs = torch.softmax(val_outputs,dim = -1)
            _, predicted = torch.max(val_outputs, 1)
            
            correct += (predicted == val_targets).sum().item()
            total += val_targets.size(0)
    
    val_loss /= len(val_loader.dataset)
    val_accuracy = correct / total

    print(f'Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, Val Accuracy: {val_accuracy * 100:.2f}%')
@aljazfrancic
Copy link
Owner

aljazfrancic commented Oct 18, 2024

Without looking at the code so far: Are you using the curated sessions only to measure results? I'd recommend using a sliding window to preprocess the raw EMG datasamples using a filter of sorts. About 30-100 samples as filter size and using the root mean square (RMS) equation. Let me know how it goes or if you need additional help.

@shiyi099
Copy link
Author

到目前为止,没有查看代码:您是否仅使用精选会话来衡量结果?我建议使用滑动窗口通过某种过滤器预处理原始 EMG 数据样本。大约 30-100 个样本作为过滤器大小并使用均方根 (RMS) 方程。让我知道进展如何或您是否需要其他帮助。

What do you mean by sliding window and RMS processing? Indeed, I think it's a problem with data preprocessing. When it's not 0.txt, I also loaded all the tags in. Is this incorrect?

@aljazfrancic
Copy link
Owner

aljazfrancic commented Oct 18, 2024

I've just open sourced the solution in Keras if you need it: https://github.com/aljazfrancic/myo-keras/blob/master/myo-keras.ipynb it has RMS implementation in it too.

@shiyi099
Copy link
Author

如果您需要,我刚刚在 Keras 中开源了解决方案:https://github.com/aljazfrancic/myo-keras/blob/master/myo-keras.ipynb其中也有 RMS 实现。

Many thanks,I will try. Will you use other model like LSTM, Transformer to predict the result?

@aljazfrancic
Copy link
Owner

No, a simple multilayer perceptron should suffice for this task I think.

@aljazfrancic
Copy link
Owner

I'm interested in your results and repo, if you are willing to share.

@shiyi099
Copy link
Author

不,我认为一个简单的多层感知器就足以完成这项任务。

I just experimented with your algorithm in PyTorch. Its accuracy is around 80%, which is still a bit low. My scene is music interaction, so I want to achieve a higher accuracy to better control live sound effects, etc. Do you directly use the results of each judgment in actual engineering? For example, if I make a fist, do I need to collect data based on the window length? Is collecting once enough?

@aljazfrancic
Copy link
Owner

For real time control you will require a buffer (que) to store at least the window size of EMG samples. For example if you have window size 40 samples you would keep the last 40 EMG samples in your buffer and do the calculations of RMS on those. Then on next sample remove the oldest of 40 samples and add newest sample. For the first few moments when you don't have samples you can use whatever samples you have or skip this in training and evaluation.

@shiyi099
Copy link
Author

对于实时控制,您将需要一个缓冲区 (que) 来存储至少 EMG 样本的窗口大小。例如,如果您的窗口大小为 40 个样本,您将在缓冲区中保留最后 40 个 EMG 样本,并对这些样本进行 RMS 计算。然后在下一个样本中移除 40 个样本中最旧的样本并添加最新样本。在您没有样本的最初几分钟内,您可以使用您拥有的任何样本,或者在训练和评估中跳过这一步。
I also consider it this way. For example, if my action frequency is 1 second, and the frequency of EMG is 200Hz, if I use 40 units as the window length, it is equivalent to sampling it 5 times. If the results are the same 5 times, it is best. If the results are different 5 times, take the result with the highest number of times. What do you think of this method?

@aljazfrancic
Copy link
Owner

Sounds good if you only need 1 action per second on your output.

@shiyi099
Copy link
Author

shiyi099 commented Oct 19, 2024

为了实时控制,您将需要一个平面图(que)来存储至少 EMG 样本的窗口大小。例如,如果您的窗口大小为 40 个样本,您将在平面图中保留最后 40 个 EMG 样本,记录这些样本进行RMS计算。然后在下一个样本中移除40个样本中最旧的样本并添加最新样本。在您没有样本的四分之一内,您可以使用您拥有的任何样本,或者在训练和评估中跳过这一步。
我也是这么认为的。比如我的动作频率是1秒,肌电的频率是200Hz,如果我用40个单位作为窗口长度,相当于采样了5次。如果5次结果都一样的话最好。如果结果有5次不同,则取次数最多的结果。你觉得这个方法怎么样?

# -*- coding: utf-8 -*-
"""
Created on Fri Oct 18 17:48:07 2024

@author: shrim
"""

import os
import numpy as np
import torch
import torch.nn.functional as F
from torch import nn
from torch.utils.data import DataLoader, Dataset, TensorDataset, random_split
import torch.optim as optim
from torch.optim.lr_scheduler import ExponentialLR


def split_dataset(hand = 'R', train_ratio=0.8):
    if hand.strip().upper() == 'R':
        root_path = 'myo-readings-dataset-main/_readings_right_hand'
    else:
        root_path = 'myo-readings-dataset-main/_readings_left_hand'
    folders = os.listdir(root_path)
    train_data_num =  int(round(len(folders)*train_ratio))
    val_data_num = len(folders) - train_data_num
    full_index = np.arange(len(folders))
    train_data_index = np.random.choice(full_index, size=train_data_num, replace=False)
    val_data_index = np.setdiff1d(full_index, train_data_index)
    return np.sort(train_data_index), np.sort(val_data_index)

def pad_sequence_2d(sequence, timesteps = 30, reshape_3d = True):
    sequence = np.array(sequence)
    assert len(sequence.shape)==2
    padding_length = (sequence.shape[0]//timesteps + bool(sequence.shape[0]%timesteps))*timesteps
    num_rows_to_pad = padding_length - sequence.shape[0]
    # Padding to the size (padding_length, feature_nums)
    padding_sequence =  np.pad(sequence, ((0, num_rows_to_pad), (0, 0)), mode='constant', constant_values=0)
    # Reshape to (padding_length//timesteps, timesteps, feature_nums)
    if reshape_3d:
        feature_nums = sequence.shape[1]
        padding_sequence = padding_sequence.reshape(padding_length//timesteps, timesteps, feature_nums)
    
    return padding_sequence # (feature_nums, timesteps) or (padding_length//timesteps, timesteps, feature_nums)

def pad_sequence_1d(sequence, timesteps = 30, reshape_2d = True, padmode = 'tail'):
    sequence = np.array(sequence)
    assert len(sequence.shape)==1
    padding_length = (sequence.shape[0]//timesteps + bool(sequence.shape[0]%timesteps))*timesteps
    num_rows_to_pad = padding_length - sequence.shape[0]
    # Padding to the size (feature_nums,)
    if padmode == 'tail':
        padding_sequence = np.pad(sequence, (0, num_rows_to_pad), mode='constant', constant_values=0)
    elif  padmode == 'head':
        padding_sequence = np.pad(sequence, (num_rows_to_pad,0), mode='constant', constant_values=0)
    else:
        #sr = 44100
        #num_rows_to_pad_head = num_rows_to_pad//2//(sr//timesteps)
        num_rows_to_pad_head = num_rows_to_pad//2
        
        num_rows_to_pad_tail = num_rows_to_pad-num_rows_to_pad_head
        print('num_rows_to_pad_head',num_rows_to_pad_head)
        print('1:',num_rows_to_pad)
        padding_sequence = np.pad(sequence, (num_rows_to_pad_head, num_rows_to_pad_tail), mode='constant', constant_values=0)
        
    if reshape_2d:
        padding_sequence = padding_sequence.reshape(padding_sequence.shape[0]//timesteps, timesteps)
    return padding_sequence # (feature_nums, )

class MyoEMGDataset(Dataset):
    def __init__(self, hand, folders_id):
        self.data = None
        self.label = None
        
        if hand.strip().upper() == 'R':
            root_path = 'myo-readings-dataset-main/_readings_right_hand'
        else:
            root_path = 'myo-readings-dataset-main/_readings_left_hand'
        
        folders = os.listdir(root_path)
        
        for f in folders_id:
            for i in os.listdir(root_path+os.sep+folders[f]):
                try:
                    txt = np.loadtxt(f"{root_path}/{folders[f]}/{i}", delimiter = ",")
                except:
                    print(f"Wrong! -> {folders[f]}/{i}")
                    continue
                data = pad_sequence_2d(txt[:,:-1], timesteps = 50, reshape_3d = True)
                data = torch.from_numpy(data).float()
                labels =  pad_sequence_1d(txt[:,-1], timesteps = 50, reshape_2d = True)
                labels = torch.from_numpy(labels).long()
                if self.data is None:
                    self.data = data
                    self.labels = labels
                else:
                    self.data = torch.cat([self.data,data],dim = 0)
                    self.labels = torch.cat([self.labels,labels],dim = 0)
    def __len__(self):
        return len(self.data)
     
    def __getitem__(self, idx):
        x = self.data[idx]
        y = self.labels[idx]
        return x, y    


class Encoder(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_layers=1, bidirectional=False):
        super(Encoder, self).__init__()
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        self.bidirectional = bidirectional
        timemodel_input_dim = 8
        
        #self.lstm = nn.LSTM(timemodel_input_dim*1, hidden_dim, num_layers, batch_first=True, bidirectional=bidirectional)
        self.gru = nn.GRU(timemodel_input_dim*1, hidden_dim, num_layers, batch_first=True, bidirectional=bidirectional)
        
    def forward(self, x):
        # Initialize hidden state and cell state for LSTM
        direction = 2 if self.bidirectional else 1 
        h0 = torch.zeros(self.num_layers * direction, x.size(0), self.hidden_dim).to(x.device)
        #c0 = torch.zeros(self.num_layers * direction, x.size(0), self.hidden_dim).to(x.device)
        
        # Forward propagate LSTM/GRU
        #output, (hn, cn) = self.lstm(x, (h0, c0))
        output, hn = self.gru(x, h0)
        
        #output = self.fc(output)
        return x, output, hn#(hn, cn)


class Decoder(nn.Module):
    def __init__(self, output_dim, hidden_dim, num_layers=1, bidirectional=False):
        super(Decoder, self).__init__()
        
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        self.bidirectional = bidirectional

        #self.lstm = nn.LSTM(hidden_dim + hidden_dim *bool(bidirectional), hidden_dim, num_layers, batch_first=True, bidirectional=bidirectional) #+256
        self.gru = nn.GRU(hidden_dim + hidden_dim * bool(bidirectional), hidden_dim, num_layers, batch_first=True, bidirectional=bidirectional)
        self.relu = nn.PReLU()
        self.fc = nn.Linear(hidden_dim * (2 if bidirectional else 1), output_dim)
               
    def forward(self, x, hidden):
        # Forward propagate LSTM/GRU
        output, hidden = self.gru(x, hidden)
        
        output = self.relu(x)
        # Decode the hidden state of the last time step
        output = self.fc(output)
        return output


class MyoEMGNet(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, num_layers=1, bidirectional=False):
        super(MyoEMGNet, self).__init__()
        self.encoder = Encoder(input_dim, hidden_dim, num_layers, bidirectional)
        self.decoder = Decoder(output_dim, hidden_dim, num_layers, bidirectional)
        #self.dropout = nn.Dropout(0.8)
        
    def forward(self, x):
        x, encoder_output, hidden = self.encoder(x)
        output = self.decoder(encoder_output, hidden)
        
        return output

input_dim = 8
hidden_dim = 512  # 可以根据需要调整
output_dim = 8 # 多分类问题的类别数
num_layers = 2
bidirectional = True

hand = 'L'

model = MyoEMGNet(input_dim, hidden_dim, output_dim, num_layers, bidirectional)

train_data_id, val_data_id = split_dataset(hand = hand , train_ratio=0.8)

train_dataset = MyoEMGDataset(hand = hand , folders_id = [0,1,2,3,4,5,6,7])
val_dataset = MyoEMGDataset(hand = hand , folders_id = [8])


train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=16, shuffle=False)

num_epochs = 1000


criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(num_epochs):
    model.train()
    train_loss = 0.0
    for inputs, targets in train_loader:
        inputs, targets = inputs.cuda(), targets.cuda()            
        optimizer.zero_grad()
        outputs = model(inputs)
        
        outputs = outputs.view(-1, output_dim)
        targets = targets.view(-1)
       
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        
        train_loss += loss.item() * inputs.size(0)  # 累计损失
    
    train_loss /= len(train_loader.dataset)

    model.eval()
    val_loss = 0.0
    correct = 0
    total = 0
    with torch.no_grad():
        for val_inputs, val_targets in val_loader:
            val_inputs, val_targets = val_inputs.cuda(), val_targets.cuda()
            
            val_outputs = model(val_inputs)
            
            val_outputs_ = val_outputs.view(-1, output_dim)
            val_targets_ = val_targets.view(-1)
            
            loss = criterion(val_outputs_, val_targets_)
            val_loss += loss.item() * val_inputs.size(0)  # 累计损失
            
            val_outputs = torch.softmax(val_outputs,dim = -1)
            val_outputs = val_outputs.view(-1, output_dim)
            val_targets = val_targets.view(-1)
            
            #print(val_outputs.shape)
            #print(val_targets.shape)
            _, predicted = torch.max(val_outputs, 1)
            
            correct += (predicted == val_targets).sum().item()
            total += val_targets.size(0)
    
    val_loss /= len(val_loader.dataset)
    val_accuracy = correct / total

    print(f'Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, Val Accuracy: {val_accuracy * 100:.2f}%')

Here‘s my newest code. I am trying using sequence to sequence model to solve that problem. And the following is my results of experiment. The highest accuracy is approximately 89% (overfitted).

Epoch 1/1000, Train Loss: 0.7334, Val Loss: 0.5032, Val Accuracy: 85.09%
Epoch 2/1000, Train Loss: 0.4680, Val Loss: 0.4433, Val Accuracy: 85.87%
Epoch 3/1000, Train Loss: 0.3681, Val Loss: 0.4798, Val Accuracy: 84.54%
Epoch 4/1000, Train Loss: 0.2918, Val Loss: 0.4262, Val Accuracy: 87.54%
Epoch 5/1000, Train Loss: 0.2268, Val Loss: 0.5189, Val Accuracy: 86.32%
Epoch 6/1000, Train Loss: 0.1702, Val Loss: 0.4762, Val Accuracy: 87.42%
Epoch 7/1000, Train Loss: 0.1371, Val Loss: 0.4289, Val Accuracy: 89.21%
Epoch 8/1000, Train Loss: 0.1040, Val Loss: 0.4754, Val Accuracy: 88.86%
Epoch 9/1000, Train Loss: 0.0870, Val Loss: 0.4803, Val Accuracy: 90.14%
Epoch 10/1000, Train Loss: 0.0704, Val Loss: 0.5335, Val Accuracy: 88.11%
Epoch 11/1000, Train Loss: 0.0553, Val Loss: 0.5444, Val Accuracy: 88.81%
Epoch 12/1000, Train Loss: 0.0481, Val Loss: 0.5659, Val Accuracy: 88.45%
Epoch 13/1000, Train Loss: 0.0311, Val Loss: 0.5715, Val Accuracy: 88.75%
Epoch 14/1000, Train Loss: 0.0213, Val Loss: 0.5609, Val Accuracy: 88.55%
Epoch 15/1000, Train Loss: 0.0150, Val Loss: 0.6728, Val Accuracy: 87.86%
Epoch 16/1000, Train Loss: 0.0119, Val Loss: 0.6344, Val Accuracy: 88.89%
Epoch 17/1000, Train Loss: 0.0104, Val Loss: 0.6347, Val Accuracy: 88.74%
Epoch 18/1000, Train Loss: 0.0108, Val Loss: 0.6357, Val Accuracy: 88.24%
Epoch 19/1000, Train Loss: 0.0395, Val Loss: 0.7673, Val Accuracy: 86.65%
Epoch 20/1000, Train Loss: 0.0733, Val Loss: 0.5101, Val Accuracy: 89.30%
Epoch 21/1000, Train Loss: 0.0499, Val Loss: 0.5438, Val Accuracy: 87.58%
Epoch 22/1000, Train Loss: 0.0146, Val Loss: 0.5507, Val Accuracy: 89.45%
Epoch 23/1000, Train Loss: 0.0099, Val Loss: 0.6101, Val Accuracy: 88.82%
Epoch 24/1000, Train Loss: 0.0053, Val Loss: 0.6095, Val Accuracy: 88.94%
Epoch 25/1000, Train Loss: 0.0036, Val Loss: 0.6427, Val Accuracy: 89.02%
Epoch 26/1000, Train Loss: 0.0065, Val Loss: 0.6444, Val Accuracy: 89.01%
Epoch 27/1000, Train Loss: 0.0036, Val Loss: 0.6418, Val Accuracy: 89.26%
Epoch 28/1000, Train Loss: 0.0028, Val Loss: 0.6747, Val Accuracy: 89.06%
Epoch 29/1000, Train Loss: 0.0021, Val Loss: 0.6958, Val Accuracy: 89.15%
Epoch 30/1000, Train Loss: 0.0018, Val Loss: 0.6769, Val Accuracy: 89.21%
Epoch 31/1000, Train Loss: 0.0013, Val Loss: 0.7261, Val Accuracy: 89.09%
Epoch 32/1000, Train Loss: 0.0011, Val Loss: 0.7169, Val Accuracy: 89.28%
Epoch 33/1000, Train Loss: 0.0010, Val Loss: 0.7325, Val Accuracy: 89.22%
Epoch 34/1000, Train Loss: 0.0008, Val Loss: 0.7477, Val Accuracy: 89.23%
Epoch 35/1000, Train Loss: 0.0007, Val Loss: 0.7606, Val Accuracy: 89.24%
Epoch 36/1000, Train Loss: 0.0007, Val Loss: 0.7636, Val Accuracy: 89.31%
Epoch 37/1000, Train Loss: 0.0006, Val Loss: 0.7873, Val Accuracy: 89.30%
Epoch 38/1000, Train Loss: 0.0007, Val Loss: 0.7821, Val Accuracy: 89.33%
Epoch 39/1000, Train Loss: 0.0007, Val Loss: 0.7855, Val Accuracy: 89.16%
Epoch 40/1000, Train Loss: 0.0007, Val Loss: 0.7821, Val Accuracy: 89.41%
Epoch 41/1000, Train Loss: 0.0007, Val Loss: 0.7865, Val Accuracy: 89.15%

I want to use this method because the window length for this experiment is 50. Therefore, it is necessary to take 4 results, and in the final generated sequence (batch size=1, timestep=50, features_num=8), select the index (torch.argmax(**,dim = -1))with the most occurrences as the prediction result. Take 4 consecutive times, and if they are consistent, perform the [index] action in that second. What do you think? Thank you for your continuous help and I hope to hear your suggestions!

@aljazfrancic
Copy link
Owner

aljazfrancic commented Oct 19, 2024

Since you have 1 second of EMG, you could also have 1 second of RMS values. You are now only using 5 values out of 200 available, if I understand your method correctly. So maybe you could just check which class/gesture has the most classifications in 200 last samples of classification from RMS. Do you know what I mean? Another thing that I would like to point out that there is a difference between training and validation loss and accuracy. If you have 89 % accuracy on validation set that doesn't mean if was overfitting. That's a good result. Just make sure to always use the model from step that had the lowest validation loss. Nice results, BTW. I don't think its possible to push them much further due to the fact that recordings also include the areas of switching between gestures, and are by no means perfect.

@aljazfrancic
Copy link
Owner

I guess the idea is to low-pass filter both the input into the into the neural net (EMG → RMS) as well as the result you get from it (classification → mode of classification). I'd just use mode to calculate the resulting classification from last 200 samples of classification (the mode is the number that occurs most often in last second of classification).

@shiyi099
Copy link
Author

shiyi099 commented Oct 19, 2024

Since you have 1 second of EMG, you could also have 1 second of RMS values. You are now only using 5 values out of 200 available, if I understand your method correctly. So maybe you could just check which class/gesture has the most classifications in 200 last samples of classification from RMS. Do you know what I mean? Another thing that I would like to point out that there is a difference between training and validation loss and accuracy. If You have 89 % accuracy on validation set that doesn't mean if was overfitting. That's a good result. Just make sure to always use the model from step that had the lowest validation loss. Nice results, BTW. I don't think its possible to push them much further due to the fact that recordings also include the areas of switching between gestures, and are by no means perfect.

It's not entirely like this. I split 200 output values (in 1 second) into every 50 values. When the number of output values is less than 50, I use a sequence (0,0,0,0,0,0,0,0) with a label of 0 to fill in and put it into the sequence model. Therefore, the output of one batch size will have 50 labels. Due to the use of cross entropy for multi classification in my training (unlike Keras), I need to reduce the dimensionality of the input sequence tensor from 3D to 2D and the labels from 2D to 1D. Therefore, the accuracy is based on the results of batch size * 50. In practical application scenarios, batch size=1, so there are both correct and incorrect situations in these labels. Setting aside the incorrect situations, there will also be 0 occurrences. Because the dataset is based on periodic collection, there are also events with a label of 0 occurring in addition to the text in 0.txt. But I think the result should approach the mode of 50 output results, so the mode can be taken as the result of 1/4 second. When the result is the same for four consecutive times, I think this action has occurred. Of course, the results obtained from the experiment are exciting and can serve as a reference, but currently I have not written test code or used the Myo armband provided by my teacher for further verification.

@aljazfrancic
Copy link
Owner

aljazfrancic commented Oct 19, 2024 via email

@shiyi099
Copy link
Author

I guess the idea is to low-pass filter both the input into the into the neural net (EMG → RMS) as well as the result you get from it (classification → mode of classification). I'd just use mode to calculate the resulting classification from last 200 samples of classification (the mode is the number that occurs most often in last second of classification).

I am a rookie in signal processing, lol
Yes, I understand your approach. The RMS algorithm is very clever, but it is difficult to extract very obvious features from signals with only 8 dimensions. Have you tried changing the RMS window length for multi-scale feature concatenation? This may require a longer sequence, for example, if the sequence length is 80, window lengths of 80, 40, and 20 can be taken. Or use other algorithms to extract features for stitching?

@shiyi099
Copy link
Author

A particularly pleasant conversation, thank you for your patient response and assistance with the materials. I hope we can have almost cooperation in the future!

Sounds good, I think I misunderstood a little what you are doing. Best wishes!

On Sat, Oct 19, 2024, 13:28 shiyi099 @.> wrote: Since you have 1 second of EMG, you could also have 1 second of RMS values. You are now only using 5 values out of 200 available, if I understand your method correctly. So maybe you could just check which class/gesture has the most classifications in 200 last samples of classification from RMS. Do you know what I mean? Another thing that I would like to point out that there is a difference between training and validation loss and accuracy. If You have 89 % accuracy on validation set that doesn't mean if was overfitting. That's a good result. Just make sure to always use the model from step that had the lowest validation loss. Nice results, BTW. I don't think its possible to push them much further due to the fact that recordings also include the areas of switching between gestures, and are by no means perfect. It's not entirely like this. I split 200 output values (in 1 second) into every 50 values. When the number of output values is less than 50, I use a sequence (0,0,0,0,0,0,0,0) with a label of 0 to fill in and put it into the sequence model. Therefore, the output of one batch size will have 50 labels. Due to the use of cross entropy for multi classification in my training (unlike Keras), I need to reduce the dimensionality of the input sequence tensor from 3D to 2D and the labels from 2D to 1D. Therefore, the accuracy is based on the results of batch size * 50. In practical operation, batchsize=1, so there are both correct and incorrect situations in these labels. Setting aside the incorrect situations, there will also be 0 occurrences. Because the dataset is based on periodic collection, there are also events with a label of 0 occurring in addition to the text in 0.txt. But I think the result should approach the mode of 50 output results, so the mode can be taken as the result of 1/4 second. When the result is the same for four consecutive times, I think this action has occurred. Of course, the results obtained from the experiment are exciting and can serve as a reference, but currently I have not written test code or used the Myo ArmBand provided by my teacher for further verification. — Reply to this email directly, view it on GitHub <#55 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEIL7DXXXFE36SK5YD5B62LZ4I653AVCNFSM6AAAAABQGGX4ZCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMRTG43TIMJTHA . You are receiving this because you commented.Message ID: @.>

Sounds good, I think I misunderstood a little what you are doing. Best wishes!

On Sat, Oct 19, 2024, 13:28 shiyi099 @.> wrote: Since you have 1 second of EMG, you could also have 1 second of RMS values. You are now only using 5 values out of 200 available, if I understand your method correctly. So maybe you could just check which class/gesture has the most classifications in 200 last samples of classification from RMS. Do you know what I mean? Another thing that I would like to point out that there is a difference between training and validation loss and accuracy. If You have 89 % accuracy on validation set that doesn't mean if was overfitting. That's a good result. Just make sure to always use the model from step that had the lowest validation loss. Nice results, BTW. I don't think its possible to push them much further due to the fact that recordings also include the areas of switching between gestures, and are by no means perfect. It's not entirely like this. I split 200 output values (in 1 second) into every 50 values. When the number of output values is less than 50, I use a sequence (0,0,0,0,0,0,0,0) with a label of 0 to fill in and put it into the sequence model. Therefore, the output of one batch size will have 50 labels. Due to the use of cross entropy for multi classification in my training (unlike Keras), I need to reduce the dimensionality of the input sequence tensor from 3D to 2D and the labels from 2D to 1D. Therefore, the accuracy is based on the results of batch size * 50. In practical operation, batchsize=1, so there are both correct and incorrect situations in these labels. Setting aside the incorrect situations, there will also be 0 occurrences. Because the dataset is based on periodic collection, there are also events with a label of 0 occurring in addition to the text in 0.txt. But I think the result should approach the mode of 50 output results, so the mode can be taken as the result of 1/4 second. When the result is the same for four consecutive times, I think this action has occurred. Of course, the results obtained from the experiment are exciting and can serve as a reference, but currently I have not written test code or used the Myo ArmBand provided by my teacher for further verification. — Reply to this email directly, view it on GitHub <#55 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEIL7DXXXFE36SK5YD5B62LZ4I653AVCNFSM6AAAAABQGGX4ZCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMRTG43TIMJTHA . You are receiving this because you commented.Message ID: @.>

A particularly pleasant conversation, thank you for your patient response and assistance with the materials. I hope we can have almost cooperation in the future!

@aljazfrancic
Copy link
Owner

I guess the idea is to low-pass filter both the input into the into the neural net (EMG → RMS) as well as the result you get from it (classification → mode of classification). I'd just use mode to calculate the resulting classification from last 200 samples of classification (the mode is the number that occurs most often in last second of classification).

I am a rookie in signal processing, lol
Yes, I understand your approach. The RMS algorithm is very clever, but it is difficult to extract very obvious features from signals with only 8 dimensions. Have you tried changing the RMS window length for multi-scale feature concatenation? This may require a longer sequence, for example, if the sequence length is 80, window lengths of 80, 40, and 20 can be taken. Or use other algorithms to extract features for stitching?

We have tried both PCA and ICA for better feature extraction but alas at no benefit. The pipeline was: EMG, RMS, PCA/ICA, neural net inputs and outputs. We of course tried different window sizes for RMS. I think best value is between 30-80, depending on how fast you want feedback from your system. You can substitute RMS with simple addition of absolute values and that works too.

@shiyi099
Copy link
Author

shiyi099 commented Oct 19, 2024

I guess the idea is to low-pass filter both the input into the into the neural net (EMG → RMS) as well as the result you get from it (classification → mode of classification). I'd just use mode to calculate the resulting classification from last 200 samples of classification (the mode is the number that occurs most often in last second of classification).

I am a rookie in signal processing, lol
Yes, I understand your approach. The RMS algorithm is very clever, but it is difficult to extract very obvious features from signals with only 8 dimensions. Have you tried changing the RMS window length for multi-scale feature concatenation? This may require a longer sequence, for example, if the sequence length is 80, window lengths of 80, 40, and 20 can be taken. Or use other algorithms to extract features for stitching?

We have tried both PCA and ICA for better feature extraction but alas at no benefit. The pipeline was: EMG, RMS, PCA/ICA, neural net inputs and outputs. We of course tried different window sizes for RMS. I think best value is between 30-80, depending on how fast you want feedback from your system. You can substitute RMS with simple addition of absolute values and that works too.

Copy that. Many thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants