原始界面如下图所示,输入患者的各项数据,选择使用的模型,点击预测可以在右侧输出框中输出分类结果。
点击模型页可以配置三个分类器的参数。可调节训练集的划分比例,随机种子可以控制每次划分是否相同。预测内容中包括死亡和几类疾病。
点击训练,即可训练该模型;点击测试即可输出该模型的测试准确率。
model用来存放训练好的模型
allmodel.py 搭建神经网络模型
train.py 训练模型
test_model.py 测试模型
data_processing.py 数据预处理
main.py 测试各函数是否正常运行
gui.py 直接运行此程序即可打开上述界面
data文件整理自MIMIC-III数据集
下面,我将一步一步描述程序的完成过程
SVM是用Keras实现的,实现非常简单不做赘述。
其余两个模型使用pytorch实现
最简单的MLP结构
class MLP(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(MLP, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.dropout = nn.Dropout(p=0.5)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.dropout(out)
out = self.relu(out)
out = self.fc2(out)
return out
该部分仅使用了transformer的encoder部分,从ViT的代码上改的。完整ViT的实现可以参考这篇博客
ViT的结构如下,因为我的输入是各项实验室指标,是19个一维的特征,因此我将图像部分移除了,直接输入 x(batch,feature_n,dim) (一批数据的个数,特征个数,特征维度),本程序中,feature_n即为19,维度dim为1.这个参数都不需要特别输入,只需将输入样本调整为相应格式即可。
class PreNorm(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.norm = nn.LayerNorm(dim)
self.fn = fn
def forward(self, x, **kwargs):
return self.fn(self.norm(x), **kwargs)
class FeedForward(nn.Module):
def __init__(self, dim, hidden_dim, dropout = 0.):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, dim),
nn.Dropout(dropout)
)
def forward(self, x):
return self.net(x)
class Attention(nn.Module):
def __init__(self, dim, heads , dim_head , dropout ):
super().__init__()
inner_dim = dim_head * heads
project_out = not (heads == 1 and dim_head == dim)
self.heads = heads
self.scale = dim_head ** -0.5
self.attend = nn.Softmax(dim = -1)
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
self.to_out = nn.Sequential(
nn.Linear(inner_dim, dim),
nn.Dropout(dropout)
) if project_out else nn.Identity()
def forward(self, x):
qkv = self.to_qkv(x).chunk(3, dim = -1)
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = self.heads), qkv)
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
attn = self.attend(dots)
out = torch.matmul(attn, v)
out = rearrange(out, 'b h n d -> b n (h d)')
return self.to_out(out)
class Transformer(nn.Module):
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout = 0.):
super().__init__()
self.layers = nn.ModuleList([])
for _ in range(depth):
self.layers.append(nn.ModuleList([
PreNorm(dim, Attention(dim, heads = heads, dim_head = dim_head, dropout = dropout)),
PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout))
]))
def forward(self, x):
for attn, ff in self.layers:
x = attn(x) + x
x = ff(x) + x
return x
class ViT(nn.Module):
'''
num_classes=2, #分类数量
dim = 1 , #每个特征的维度
depth= 1, #encoder层数
heads=1, #注意力头数
mlp_dim=32, #encoder 中 MLP 输出
pool = 'cls', #分类方式 cls or mean
dim_head = 32, #注意力输出向量维度
dropout = 0., #transformer里的dropout
emb_dropout = 0. #emb的dropout
'''
def __init__(self, num_classes, dim, depth, heads, mlp_dim, pool, dim_head, dropout = 0., emb_dropout=0. ):
super().__init__()
assert pool in {'cls', 'mean'}, 'pool type must be either cls (cls token) or mean (mean pooling)'
self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
self.dropout = nn.Dropout(emb_dropout)
self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim, dropout) #dim:输入每个特征维度,dim_head,q和k维度
self.pool = pool
self.to_latent = nn.Identity()
self.mlp_head = nn.Sequential(
nn.LayerNorm(dim),
nn.Linear(dim, num_classes)
)
def forward(self, x): #b为批大小,x(batch,feature_n,dim)
b,_ =x.shape
cls_tokens = repeat(self.cls_token, '() n d -> b n d', b = b)
x = torch.unsqueeze(x, 2) #输入特征是1维的,所以要增添这一维度,否则不用
x = torch.cat((cls_tokens, x), dim=1) #(1, 65, 1024) 65:加cls之后的feature数,1024是每个特征的维度
x = self.dropout(x)
x = self.transformer(x)
x = x.mean(dim = 1) if self.pool == 'mean' else x[:, 0]
x = self.to_latent(x)
return self.mlp_head(x)
以MLP为例,编写了函数,为了更方便地与前端gui进行对接,可对模型和训练的超参数进行修改。最后会保存模型,方便预测时进行调用。
def train_MLP(train_data, #训练数据
train_label, #训练标签
hidden_size, #隐藏层大小
num_classes, #分类数量
num_epochs, #训练轮数
batch_size, #批大小
learning_rate #学习率
):
input_size = train_data.shape[1]
torch_dataset = Data.TensorDataset(train_data, train_label)
loader = Data.DataLoader( #批训练数据加载器
dataset=torch_dataset,
batch_size=batch_size,
shuffle=True, # 每次训练打乱数据, 默认为False
num_workers=0, # 使用多进行程读取数据, 默认0,为不使用多进程
drop_last=False
)
model = allmodel.MLP(input_size, hidden_size, num_classes)
print(model)
summary(model,(1,input_size))
model.train()
# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 训练模型
total_step = math.ceil(len(train_data)/batch_size)
for epoch in range(num_epochs):
for i, (input_data, labels) in enumerate(loader):
# 前向传播
outputs = model(input_data)
loss = criterion(outputs, labels.long())
#
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 每一百步打印一次
if (i + 1) % 10 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch + 1, num_epochs, i + 1, total_step, loss.item()))
print("MLP训练完毕")
# 保存训练完的模型
torch.save(model, 'model/MLP.pth')
此部分主要注意输入的size要符合模型输入的条件。
def predict(x,model_name):
x=x.reshape(1,len(x))
if model_name == 'SVM' :
model = joblib.load("model//SVM.pth")
predicted = model.predict(x)
else :
input_data = torch.tensor(x).to(torch.float32) #将array转变为tensor
model = torch.load('model//%s.pth'%model_name)
outputs = model(input_data)
_, predicted = torch.max(outputs.data, 1)
predicted = predicted.item()
if predicted == 1:
out_pre = '是'
elif predicted == 0:
out_pre = '否'
print (out_pre)
return out_pre
最后,使用PyQt5编写界面。