本次做的项目是糖尿病的预测,也是来自kaggle上面的一个小项目。根据一个人的怀孕次数、血糖、血压、皮肤厚度、胰岛素指标、身体质量指数、糖尿病谱系指数、年龄等身体参数来预测一个人是否患有糖尿病。
老规矩,还是先讲数据集,数据集来自kaggle,这里附上链接(糖尿病数据集)。数据集是一个文本文件,总共有769行、9列数据。除第一行外,所有数据都是单精度浮点数,其中第一行是对数据集中各项身体参数的说明,所以在训练的时候要把第一行数据删除。数据集中前8列是上述的各项身体指标,最后一列是输出结果(标签),0表示未患有糖尿病,1表示患有糖尿病。
接下来是训练过程。第一步,导入需要用到的python包,这里我们要用numpy读取文本文件,DataLoader用来加载数据集,Dataset用来准备数据集,其中Dataset是一个抽象类,只能被继承,所以导入了之后,后面我们还要自己重写Dataset。
import torch
import numpy as np # 要用numpy读取文本数据
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
第二步,重写Dataset,准备数据集。这里我们把用于处理训练集的数据集处理器命名为TrainDataset,创建一个类,继承自Dataset。所有继承自Dataset的类,我们都必须重写三个内置方法:__init__、__getitem__、__len__,分别是初始化函数、根据索引取值、获取数据长度。
在初始化中,我们需要用numpy中的loadtxt读取文本文件,文本文件中数据的分隔符为“,”,所以这里分隔符设置为delimiter=‘,’,数据类型设置为32位浮点数。通过这个操作,把文本文件加载成了一个768行*9列的numpy矩阵。其中前面8列是训练数据,最后一列是标签,所以我们用python的矩阵切片操作把数据集切成x和y,x表示训练数据,y表示标签。因为数据集没有给我们准备测试集,所以我们要自己准备测试集。按照训练集:测试集=9:1的比例,我们去掉数据集中的最后96行作为训练集,把最后96行作为测试集,进行切片。测试集的处理器配置采用相同的操作。
我们同样需要把下载好的数据集放在pycharm创建的project的同一个文件夹中,将它传入TrainDataset,由于采用随机小批量梯度下降算法,所以在DataLoader中,我们将批量设置为32,随机打乱(shuffle=True),线程设置为1(num_workers=1)。
class TrainDataset(Dataset): # 继承自Dataset要重写以下三个方法
def __init__(self, filepath):
xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32)
self.x = xy[:-96, :-1] # 去掉最后96行,去掉最后一列,作为训练集
self.len = self.x.shape[0] # shape获取x的维度,0表示行数
self.y = xy[:-96, [-1]] # 去掉最后96行,只取最后一列,作为训练集的标签
def __getitem__(self, item):
return self.x[item], self.y[item]
def __len__(self):
return self.len
TrainData = TrainDataset('diabetes.csv') # 把数据集传给Dataset
TrainLoader = DataLoader(TrainData, batch_size=32, shuffle=True, num_workers=1)
第三步,搭建训练模型,配置损失函数和优化器。由于是处理文本文件,所以我们采用多隐藏层前馈神经网络,因为数据量较少, 所以我们在模型中设置两个隐藏层,一个输出层。处理二分类问题,我们采用Sigmoid作为激活函数。损失函数设置为BCELoss,优化器设置为SGD,当然读者也可以用别的损失函数或者优化器对比效果,例如交叉熵损失函数,Adam优化器。
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = torch.nn.Linear(8, 6)
self.linear2 = torch.nn.Linear(6, 4)
self.linear3 = torch.nn.Linear(4, 1)
self.activate = torch.nn.Sigmoid()
def forward(self, x):
x = self.activate(self.linear1(x))
x = self.activate(self.linear2(x))
x = self.activate(self.linear3(x))
return x
model = Model()
criterion = torch.nn.BCELoss()
optimizer = torch.optim.SGD(params=model.parameters(), lr=0.01)
最后,定义训练函数和测试函数,添加程序入口,设置训练轮数即可。在测试函数中,由于输出经过sigmoid,变成了(0,1)之间的浮点数,我们把大于0.5的判定为1,表示患有糖尿病,小于0.5的判定为0,表示未患有糖尿病。所以我们需要用到pytorch中的where函数,torch.where(condition, a, b),表示条件condition满足则取a,不满足则取b,这样就可以计算预测的正确率。
def Train():
for x, y in TrainLoader:
pred = model(x) # 预测
loss = criterion(pred, y) # 先放预测值,后放标签
optimizer.zero_grad() # 梯度清零
loss.backward() # 反向传播
optimizer.step() # 更新梯度
def Test():
total = 0
correct = 0
with torch.no_grad(): # 测试不需要梯度
for x, y in TestLoader:
pred = model(x) # 预测
y_pred_label = torch.where(pred >= 0.5, torch.tensor([1.0]), torch.tensor([0.0]))
total = pred.shape[0] # 总标签数
correct = (y_pred_label == y).sum().item() # 预测正确数量
print("accuracy on test is %d %%" % (100*correct/total))
if __name__ == '__main__':
for i in range(20):
Train()
Test()
完整代码如下 。训练结论:在训练结束时,模型最高准确率可以达到75%,由于训练数据较少,导致模型过拟合非常严重,准确率提高困难,读者可以想办法增大训练集数量提高模型准确率。
import torch
import numpy as np # 要用numpy加载数据
from torch.utils.data import DataLoader # 导入加载数据集的包
from torch.utils.data import Dataset # Dataset是一个抽象类,只能被继承,不能够实例化
class TrainDataset(Dataset): # 继承自Dataset要重写以下三个方法
def __init__(self, filepath):
xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32) # 数据集中的数据都是用逗号分开,所以这里分隔符用‘,’
self.x = xy[:-96, :-1] # 去掉最后96行,去掉最后一列,作为训练集
self.len = self.x.shape[0]
self.y = xy[:-96, [-1]] # 去掉最后96行,只取最后一列,作为训练集的标签
def __getitem__(self, item):
return self.x[item], self.y[item]
def __len__(self):
return self.len
class TestDataset(Dataset): # 继承自Dataset要重写以下三个方法
def __init__(self, filepath):
xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32) # 数据集中的数据都是用逗号分开,所以这里分隔符用‘,’
self.x = xy[-96:, :-1] # 取最后96行,去掉最后一列,作为测试集
self.len = self.x.shape[0]
self.y = xy[-96:, [-1]] # 取最后96行,只取最后一列,作为测试集的标签
def __getitem__(self, item):
return self.x[item], self.y[item]
def __len__(self):
return self.len
TrainData = TrainDataset('diabetes.csv') # 把数据集传给Dataset
TrainLoader = DataLoader(TrainData, batch_size=32, shuffle=True, num_workers=1)
TestData = TestDataset('diabetes.csv')
TestLoader = DataLoader(TestData, batch_size=32, shuffle=True, num_workers=1)
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = torch.nn.Linear(8, 6)
self.linear2 = torch.nn.Linear(6, 4)
self.linear3 = torch.nn.Linear(4, 1)
self.activate = torch.nn.Sigmoid()
def forward(self, x):
x = self.activate(self.linear1(x))
x = self.activate(self.linear2(x))
x = self.activate(self.linear3(x))
return x
model = Model()
criterion = torch.nn.BCELoss()
optimizer = torch.optim.SGD(params=model.parameters(), lr=0.01)
def Train():
for x, y in TrainLoader:
pred = model(x)
loss = criterion(pred, y) # 先放预测,后放标签
optimizer.zero_grad()
loss.backward()
optimizer.step()
def Test():
total = 0
correct = 0
with torch.no_grad():
for x, y in TestLoader:
pred = model(x)
y_pred_label = torch.where(pred >= 0.5, torch.tensor([1.0]), torch.tensor([0.0]))
# torch.where(condition,a,b)其中
# 输入参数condition:条件限制,如果满足条件,则选择a,否则选择b作为输出
total = pred.shape[0]
correct = (y_pred_label == y).sum().item()
print("accuracy on test is %d %%" % (100*correct/total))
if __name__ == '__main__':
for i in range(20):
Train()
Test()