我正在设计一个用于大鼠开颅手术的自动钻孔系统。我希望当神经网络检测到突破时钻头会停止。照片为该系统。
class CNNLSTMClassifier(nn.Module):
def __init__(self, hidden_size, num_layers):
super(CNNLSTMClassifier, self).__init__()
self.cnn = nn.Sequential(
nn.Conv1d(in_channels=1, out_channels=64, kernel_size=4, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2, stride=2),
nn.Conv1d(in_channels=64, out_channels=128, kernel_size=4, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2, stride=2),
nn.Conv1d(in_channels=128, out_channels=256, kernel_size=4, padding=1),
nn.ReLU(),
nn.MaxPool1d(kernel_size=2, stride=2)
)
self.lstm = nn.LSTM(input_size=256, hidden_size=hidden_size, num_layers=num_layers, batch_first=True)
self.fc = nn.Linear(hidden_dim, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = x.unsqueeze(1)
x = self.cnn(x)
x = x.permute(0, 2, 1)
lstm_out, _ = self.lstm(x)
out = self.fc(lstm_out[:, -1, :])
out = self.sigmoid(out)
return out
我明白了
2024-03-18 16:25:50.708972 Epoch 1, Training loss 0.6764530851605625
2024-03-18 16:25:55.804250 Epoch 5, Training loss 0.6663112645497138
2024-03-18 16:26:02.211953 Epoch 10, Training loss 0.660625655507836
..................................................................
2024-03-18 16:28:04.148844 Epoch 100, Training loss 0.6572404681613006
Accuracy train: 0.63
Accuracy test: 0.66
所以效果很差。
我希望无论使用什么方法,神经网络都能检测到突破(可能是另一个神经网络或只是另一种方法)。 补充说明:我认为二元分类的想法是错误的。也许我应该检查的是每0.1s的数据是否有足够的整体下降趋势。
我根据您提供的过滤图合成了一个数据集(10k 个样本),并训练了一个简单的序列到序列 LSTM 来预测序列中每个时间点的标签。我得到的验证准确度约为 84%。虽然不是很好,但比 66% 有了很大的进步。
这个答案只是第一个原型,展示了一个简单的架构,可以产生可行的初步结果。通过更多的数据和调整,我认为有更高的准确度的空间。
我主要使用的是:
这种方法提供了一个良好的起点。
输出:
[epoch 1] train loss 0.668 | val accuracy 0.6378
[epoch 2] train loss 0.558 | val accuracy 0.8347
[epoch 3] train loss 0.462 | val accuracy 0.8295
[epoch 4] train loss 0.439 | val accuracy 0.8363
[epoch 5] train loss 0.425 | val accuracy 0.8429
导入和合成数据:
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
np.random.seed(0)
#
# Make a test dataset.
# You don't need this part because you have the actual filtered data.
#
df = pd.read_csv('../plot-data.csv')
df = df.set_axis(['x', 'y'], axis=1)
#Create a label vector, where 1 means drilling, 0: not drilling
label = np.where(df.y > 0.04, 1, 0)
label[0] = 0
label[[6, 13, 19, 26, 33, 40]] = 1
df['label'] = label
x_fine = np.linspace(df.x.min(), df.x.max(), num=2000)
y_fine = np.interp(x_fine, df.x, df.y)
label_fine = np.interp(x_fine, df.x, df.label)
label_fine = np.round(lab_fine)
#These represent the final data to be used next
time_all = np.linspace(x_fine[0], x_fine[-1] * 5, len(x_fine) * 5)
force_all = np.tile(y_fine, 5) + np.random.randn(x_all.shape[0]) / 500
label_all = np.tile(lab_fine, 5)
#View the data and labels
plt.scatter(time_all, force_all, marker='.', c=label_all, cmap='coolwarm', s=1)
plt.gcf().set_size_inches(11, 3)
plt.xlabel('x')
plt.ylabel('force')
创建一个
DrillDataset
类,并包装在数据加载器中以进行批处理:
import torch
from torch.utils.data import Dataset, DataLoader
from torch import optim
from torch import nn
#
# Prepare dataset
#
#Returns windows from the data and labels
# Windows have size sequence_length
# Window n will return a tuple of (force data from n:n+seq_length, labels n:n+seq_length)
#
class DrillDataset:
def __init__(self, force_data, label_data, sequence_length):
self.sequence_length = sequence_length
#Data to float tensors
self.force_data = torch.tensor(force_data).float()
self.label_data = torch.tensor(label_data).float()
def __len__(self):
return len(self.force_data) - self.sequence_length
def __getitem__(self, index):
if index < 0:
index += self.__len__()
if index >= self.__len__():
raise IndexError()
window = slice(index, index + self.sequence_length)
return self.force_data[window].reshape(-1, 1), self.label_data[window]
sequence_length = 20
batch_size = 32
#Train/validation split of 70%/30%
# For brevity I haven't created a test split
# You'll likely need a test split if you want a final unbiased assessemnt
# of accuracy.
train_samples = int( len(force_all) * 0.7 )
train_dataset = DrillDataset(force_all[:train_samples],
label_all[:train_samples],
sequence_length)
val_dataset = DrillDataset(force_all[train_samples:],
label_all[train_samples:],
sequence_length)
#Wrap in DataLoaders that batchify the dataset
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=len(val_dataset))
定义模型和训练:
#
# Define model
#
#This is a custom layer that let's me
# specify lambda functions.
# Useful for quick and simple manipulations like
# swapping dimenions.
class LambdaLayer(nn.Module):
def __init__(self, func):
super().__init__()
self.func = func
def forward(self, x):
return self.func(x)
model = nn.Sequential(
#1. Permute from [batch, seq_len, 1] to [seq_len, batch, 1]
# This is because in PyTorch sequential models are
# batch_first=False by default.
LambdaLayer(lambda x: x.swapdims(0, 1)),
#2. One-layer LSTM that projects to a hidden size of 32, and then
# projects back down to 1 (same as input dim) at the output
nn.LSTM(input_size=1, hidden_size=32, num_layers=1, proj_size=1),
#3. Get the output at each time step ("output") and ignore
# the other tensors from the LSTM (h_n and c_n).
# Also, permute from [seq_len, batch, 1] back to [batch, seq_len, 1]
LambdaLayer(lambda output_hncn: output_hncn[0].swapdims(0, 1)),
#4. For the output at each time step, provide a value
# between 0 (no drilling) and 1 (drilling). Done using a sigmoid layer.
nn.Sigmoid(),
)
#Try Adam optimiser initially. Tends to work well.
optimiser = optim.Adam(model.parameters())
n_epochs = 5
for epoch in range(n_epochs):
model.train()
cum_loss = 0 #keep track of train loss over each epoch
for X_mb, y_mb in train_loader: #For each minibatch X_mb, y_mb
pred_proba = model(X_mb).squeeze()
loss = nn.BCELoss()(pred_proba, y_mb)
optimiser.zero_grad()
loss.backward()
optimiser.step()
cum_loss += loss.item() * len(X_mb)
#End of epoch
model.eval()
#Get the validation accuracy
with torch.no_grad():
val_x, val_y = next(iter(val_loader))
val_acc = (model(val_x).squeeze().round() == val_y).float().mean()
#Print train loss and val accuracy
print(
f'[epoch {epoch + 1:>2d}] ',
f'train loss {cum_loss / len(train_loader.dataset):>5.3f} ',
f'| val accuracy {val_acc:>.4f}'
)