作者️‍♂️:让机器理解语言か

专栏:PyTorch

描述:PyTorch 是一个基于 Torch 的 Python 开源机器学习库。

寄语:没有白走的路,每一步都算数!

介绍

上一个实验我们讲解了线性问题的求解步骤,本实验我们以乳腺癌的预测为实例,详细的阐述如何利用 PyTorch 求解一个非线性问题

知识点

  • 数据集的标准化
  • 数据集的划分
  • Sigmoid 函数
  • 乳腺癌的预测

数据集的预处理

数据集的加载

首先,让我们来加载数据集合。这里我们使用pandas对数据集合进行加载

import pandas as pddf = pd.read_csv('https://labfile.oss.aliyuncs.com/courses/2534/breast_cancer.csv', index_col=False) # index_col 指定某个列为索引。 df 
mean radiusmean texturemean perimetermean areamean smoothnessmean compactnessmean concavitymean concave pointsmean symmetrymean fractal dimensionworst textureworst perimeterworst areaworst smoothnessworst compactnessworst concavityworst concave pointsworst symmetryworst fractal dimensiontarget
017.9910.38122.801001.00.118400.277600.300100.147100.24190.0787117.33184.602019.00.162200.665600.71190.26540.46010.118900
120.5717.77132.901326.00.084740.078640.086900.070170.18120.0566723.41158.801956.00.123800.186600.24160.18600.27500.089020
219.6921.25130.001203.00.109600.159900.197400.127900.20690.0599925.53152.501709.00.144400.424500.45040.24300.36130.087580
311.4220.3877.58386.10.142500.283900.241400.105200.25970.0974426.5098.87567.70.209800.866300.68690.25750.66380.173000
420.2914.34135.101297.00.100300.132800.198000.104300.18090.0588316.67152.201575.00.137400.205000.40000.16250.23640.076780
56421.5622.39142.001479.00.111000.115900.243900.138900.17260.0562326.40166.102027.00.141000.211300.41070.22160.20600.071150
56520.1328.25131.201261.00.097800.103400.144000.097910.17520.0553338.25155.001731.00.116600.192200.32150.16280.25720.066370
56616.6028.08108.30858.10.084550.102300.092510.053020.15900.0564834.12126.701124.00.113900.309400.34030.14180.22180.078200
56720.6029.33140.101265.00.117800.277000.351400.152000.23970.0701639.42184.601821.00.165000.868100.93870.26500.40870.124000
5687.7624.5447.92181.00.052630.043620.000000.000000.15870.0588430.3759.16268.60.089960.064440.00000.00000.28710.070391

569 rows × 31 columns

可以看到该数据集合一共有 569 条数据,每条数据有 30 个和乳腺癌相关的病变特征,最后一列是该患者是否患有乳腺癌的诊断结果。其中 0 表示没有患有乳腺癌,1 表示患有乳腺癌。

我们可以利用 pandas 中的切片,先将上表中的特征标签分开

X = df[df.columns[0:-1]].values # 取出30个特征的值# df.columns[0:-1]取出第0列到倒数第二列的指标名# df[df.columns[0:-1]].values取出这些指标名所在列的值y = df[df.columns[-1]].values # 取出1个标签的值X.shape, y.shape# ((569, 30), (569,))

可以看到共有 569 条数据,每条数据有 30 个特征和 1 个标签。

数据集的划分和标准化

为了能够评价模型的好坏,这里我们利用sklearn.model_selection函数,将原数据按比例随机分为训练数据集和测试数据集,如下:

from sklearn.model_selection import train_test_split# 按照 0.8 和 0.2 的比例随机划分数据集合X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1234) # test_size=0.2:测试集为20%;random_state=1234:设置了固定值,每次分割结果一样,不会数据洗牌X_train.shape, y_train.shape, X_test.shape, y_test.shape# ((455, 30), (455,), (114, 30), (114,))

为了加快模型的收敛速度,一般我们都需要对原始数据进行标准化处理,将所有的数据按照比例缩放到一定范围内。这里我们可以使用sklearn.preprocessing来对数据集合进行标准化。

from sklearn.preprocessing import StandardScalersc = StandardScaler()# 对特征进行标准化,标签不要标准化,因为标签只有 0 和 1X_train = sc.fit_transform(X_train)# fit_transform对训练集进行标准化X_test = sc.transform(X_test)# transform对测试集进行标准化X_train

最后,为了将数据放入 PyTorch 定义的模型之中,我们必须将所有的数据转为 张量类型:

import torchimport numpy as np# 将 NumPy 类型的变量转为 TensorX_train = torch.from_numpy(X_train.astype(np.float32))X_test = torch.from_numpy(X_test.astype(np.float32))y_train = torch.from_numpy(y_train.astype(np.float32))y_test = torch.from_numpy(y_test.astype(np.float32))# 将标签也转为 2 维,否则放入模型之中训练时,可能出错(因为标签原本只有一列,每一行就一个数,现在要变为二维(标签的第二维为1),和x匹配)y_train = y_train.view(y_train.shape[0], 1)y_test = y_test.view(y_test.shape[0], 1)X_train.size(), y_train.size()# (torch.Size([455, 30]), torch.Size([455, 1]))

乳腺癌的预测

模型的定义

在处理完数据后,接下来,我们就需要建立相应的模型,用于乳腺癌的预测了。

线性函数是一条没有上界和下界的直线,即线性函数预测出来的值可以很大如 112321442,也可以很小如 -1231242412。而本实验的数据标签只有 0(患病) 或 1(不患病),因此用线性函数来拟合乳腺癌的数据点是不合理的。

我们需要找到输出始终为 0-1 之间的函数模型。如果拥有这样的函数模型,那么将任意 x 放入该模型中,都会输出一个 0-1 之间的值。这个值我们可以看做是患有乳腺癌的概率。如果这个概率值小于 0.5 则表示没有患乳腺癌。如果这个概率值大于 0.5 则表示患有乳腺癌。

逻辑回归函数 Sigmoid 就是这样一种函数,该函数又叫做激活函数,公式如下:

该函数的几何形式如下所示:

从图中我们可以看出,该函数就是一个上下界分别为 1 和 0 的有界非线性函数。我们可以让通过了线性函数的输出再通过一次上面的激活函数,进而得到 0-1 之间的结果。

综上,乳腺癌的预测模型如下:

import torch.nn as nn# 我们的模型是一个线性函数+激活函数的非线性模型# modle(x) = sigmoid(w*x+b)class Model(nn.Module):def __init__(self, n_input_features):super(Model, self).__init__()self.linear = nn.Linear(n_input_features, 1) # 参数:输入神经元的个数和输出神经元的个数def forward(self, x):# torch 中已经定义了 sigmoid 函数模型y_pred = torch.sigmoid(self.linear(x))return y_pred# 获得样本量和特征数n_samples, n_features = X.shape# 模型的初始化model = Model(n_features)model

至此,我们就得到了一个乳腺癌的初始模型。由于最后通过了一层逻辑回归函数,无论输入的值为多少,模型的输出都必定属于 0-1 之间。

损失函数和优化器

接下来的步骤和上个实验中的步骤类似。

首先,让我们来定义一下损失函数,由于我们的标签只有 0 和 1,因此这里使用二元交叉熵损失nn.BCELoss()计算真实值和预测值之间的距离了。该损失函数的公式如下:

当然,我们不必手写上面的损失函数, 直接使用nn.BCELoss()即可:

# 损失和优化器的定义# 迭代次数num_epochs = 100# 学习率learning_rate = 0.01# 二元交叉熵损失criterion = nn.BCELoss()# SGD 优化器optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)criterion, optimizer

模型的训练

定义完损失函数和优化器后,接下来的模型训练步骤就是固定的了,如下:

  • 通过模型的正向传播,输出预测结果
  • 通过预测结果和真实标签计算损失
  • 通过后向传播,获得梯度
  • 通过梯度更新模型的权重
  • 进行梯度的清空
  • 循环上面操作,直到损失较小为止。

让我们用代码完成上面的步骤:

for epoch in range(num_epochs):y_pred = model(X_train)loss = criterion(y_pred, y_train)# 后向传播、梯度更新、梯度清空loss.backward()optimizer.step()optimizer.zero_grad()if (epoch+1) % 10 == 0:print(f'epoch: {epoch+1}, loss = {loss.item():.4f}')print("模型训练完毕!!")

综上,我们训练好了一个乳腺癌的预测模型。我们可以尝试对任意一条数据进行预测:

index = np.random.randint(0, len(X_test)) # 生成随机整数y_predicted = model(X_test[index]) # 向模型中传入x,模型返回y# 小于 0.5 则输出 0 ,大于0.5 则输出 1y_predicted_cls = y_predicted.round() # round()四舍五入取整# 将结果转为 numpy类型real = y_test[index].detach().numpy()[0]# 真实值# real = y_test[index].detach():通过.detach() “分离”得到的的变量会和原来的变量共用同样的数据,y_test[index]发生了变化,原来的张量也会发生变化,而且新分离得到的张量real是不可求导的。predict = y_predicted_cls.detach().numpy()[0] # 预测值print("第 {} 条测试数据的真实结果为 {} ,预测结果为 {} ".format(index, real, predict))# 第 82 条测试数据的真实结果为 1.0 ,预测结果为 1.0 

由于模型准确率不是 100%,因此,上面的预测结果和真实结果也可能会不相同。但是,你多运行几次上面代码,必定会出现预测结果和真实结果相同的情况。

那么,我们训练出来的模型准确率到底是多少呢?

with torch.no_grad():y_predicted = model(X_test)y_predicted_cls = y_predicted.round()acc = y_predicted_cls.eq(y_test).sum().numpy() / float(y_test.shape[0]) #y_predicted_cls.eq(y_test).sum() # 测试值与预测值相同的样本总数print(f'accuracy: {acc.item():.4f}')# accuracy: 0.9123

我们利用测试数据,计算出了整个模型的预测准确率大概在 90% 左右,证明我们的模型可以很好地进行乳腺癌的诊断预测。

实验总结

本实验以乳腺癌的预测为例,引入了激活函数 sigmoid 的概念。建立了一个简单的非线性模型用于诊断患者是否患有乳腺癌。其实,本实验建立的一个线性函数+激活函数的模型就是一个简单的神经网络模型。全连接神经网络的实质其实就是无数个线性函数和非线性网络组成的集合。