之前的分类都是只有两个分类,是或者不是。
今天学一下多分类问题,比如下面这个图。识别这个图中的数字,当输出结果的时候 有 0-9 十个分类结果。
比如第一个数字5,经过训练输出可能是 P(Y=5) = 0.9 ,即理解为等于5的概率是90%。
但这样有一个问题,如果这个数字特别模糊,可能出现的情况是P(Y=1) = 0.8 ,P(Y=2) = 0.8 , P(Y=5) = 0.9 ,也就是说,这个数字是1的概率是0.8,是2的概率也是0.8,是5的概率是0.9。这样的结果并不利于训练结果的分类。
所以我们让所有结果符合概率要求,即全部概率相加 = 1。输出一个分布。
用于训练的神经网络中间依旧使用 Sigmoid,最后输出10个概率的分布使用 softmax。
Softmax 函数计算公式:
这里的 Zi 就是最后一层线性层(上图蓝色的层)的输出值,给softmax层的值。线性层输出的值是一般值,还不是概率值。经过sotfmax层之后才能变成概率值。
分子上面的e的指数形式。必然大于0.
分子下面的求和。就是e的每一项都除以全部的e的Zi次方的和。.
等价于
显然这样操作之后,每一项单独的都除以全部总和,保证了结果的值之和为1.
损失函数:
计算过程代码实现:
这里的y就是值真实值,z就是 最后一层的线性层给Softmax的值。
先用 softmax 的公式算出来 y(预测值)。
然后用上面的损失函数算出来y的损失值。
在Pytorch中使用:
这条函数包括了上面的softmax算预测值和算损失值的全部过程。
在使用CrossEntropyLossr的时候,最后一层线性层不要做非线性变换,就是乘以那个α 或 sigmoid激活函数。这条函数(交叉熵)会自动帮你激活。
关于上面的整体流程可以用下面这张图表示:
课上老师问了一个问题 就是 两个损失函数 NLLLoss和CrossEntropyLossr的区别。
其实看到这应该能知道,CrossEntropyLossr交叉熵损失函数是整体计算的。就是从最后一层的线性层开始计算到最后,里面包含了softmax.
而NLLLoss仅仅做了最后一步:
NLLLoss 对数似然损失函数(log-likehood loss function) :
其中,ak表示第k个神经元的输出值,yk表示第k个神经元对应的真实值,取值为0或1。
CrossEntropyLossr = softmax + NLLLoss
回到刚开始的那个数字图像。拿出第一个数字。
该图像由28*28的矩阵像素点构成。颜色深浅由0-255表示,映射到0-1.每个矩阵中的值为0-1,表示该点的颜色的深浅。
代码实现过程还是之前的四步,
1.数据准备 2.设计模型类 3.选择优化器和损失函数 4. 循环训练
用于图像的处理。
不在使用之前的sigmoid了 使用 relu()作为激活函数。
数据准备:
train=True 代表我们读入的数据作为训练集(如果为true则从training.pt创建数据集,否则从test.pt创建数据集)
download=True则是当我们的根目录(root)下没有数据集时,便自动下载。
python3使用pillow处理图片,
将输入进来的图像(0-255像素值 28 * 28)值变为图像张量(映射0-1像素值 1 * 28 * 28)。
1 * 28 * 28 通道 * 宽 * 高
灰白图成为单通道,
读进来的为 W * H * C 变为 C * W * H之后交给Pytorch处理。
下面这个函数就是做变换处理的。
下面这个函数后面的两个参数, 均值(mean) 标准差(std)。
这俩值并不是固定的,而是根据图像算出来的,不过这个图比较经典,所以就用这俩最好。
模型训练:
下图表示输入N个样本,1维 28 *28 。
全连接神经网络要求输入样本二维矩阵。
使用view函数。改变张量形状。
变成二维矩阵。第一个数为 -1 表示自动计算。
所以第一层拿到的数据是N * 784的矩阵
注意,最后一层 self.15(x) 不需要relu()函数激活,直接交给后面的softmax。
本次将训练和测试封装到函数中:
训练:
测试:
其中:
下图中的代码不会计算后面的梯度,因为已经是测试数据了。就是要测试前面训练好的权重W,所以不需要再次计算梯度。
首先拿测试数据去计算预测值,
然后取出来预测值每一行中的最大值,因为最大值才是我们要的那个最大概率的分类嘛。dim表示沿着那个维度去找。
ps:想一下!~组成二维数组中的一维数组是0号维度,一维数组中的值是1号维度。
torch.max:
这里拿到的预测值 每一行都对应10个分类,这10个分类就是该样本对应0-9的概率, 我们要拿到最大的那个概率和其对应的下标。
dim=1 就是从每一个一维数组中找到最大的值和其对应下标。
返回值是两个,最大值是多少,其下标是多少。
下面的 labels就是我们的初始的y数据,应该是一个N *1的矩阵,N就是一共有多少个样本。所以size就是元组(N,1).去0号元素,自然拿到的就是样本数。
下面的 == 号 就是在比较真实值和预测值之间的差距,相等就是1不相等就是0,然后求和 吧数值拿出来。
等所有的数都跑完了,看正确的数除以总数,就能得到正确率了。
完整代码:
import torch
from torch import optim
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
batch_size = 64
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
# 模型类设计
class DiabetesDataset(torch.nn.Module):
def __init__(self):
super(DiabetesDataset, self).__init__()
self.l1 = torch.nn.Linear(784, 512)
self.l2 = torch.nn.Linear(512, 256)
self.l3 = torch.nn.Linear(256, 128)
self.l4 = torch.nn.Linear(128, 64)
self.l5 = torch.nn.Linear(64, 10)
def forward(self, x):
x = x.view(-1, 784) # -1就是自动获取mini_batch
x = F.relu(self.l1(x))
x = F.relu(self.l2(x))
x = F.relu(self.l3(x))
x = F.relu(self.l4(x))
return self.l5(x) # 最后一层不做激活,不进行非线性变换
model = DiabetesDataset()
# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
def train(epoch):
runing_loss = 0.0
for i, data in enumerate(train_loader):
x, y = data
# 清零 正向传播 损失函数 反向传播 更新
optimizer.zero_grad()
y_pre = model(x)
loss = criterion(y_pre, y)
loss.backward()
optimizer.step()
runing_loss += loss.item()
# 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000
print("这是第 %d轮训练,当前损失值 %.5f" % (epoch+1, runing_loss/10000))
def test(epoch):
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
x, y = data
pre_y = model(x)
# 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,
# 我们要拿到最大的那个概率和其对应的下标。
j, pre_y = torch.max(pre_y.data, dim=1) # dim = 1 列是第0个维度,行是第1个维度
total += y.size(0) # 统计方向0上的元素个数 即样本个数
correct += (pre_y == y).sum().item() # 张量之间的比较运算
print("第%d轮测试结束,当前正确率:%d %%" % (epoch+1,correct / total * 100))
if __name__ == '__main__':
for epoch in range(10):
train(epoch)
test(epoch)
看看结果:
这是第 1轮训练,当前损失值 0.10440
第1轮测试结束,当前正确率:89 %
这是第 2轮训练,当前损失值 0.02563
第2轮测试结束,当前正确率:94 %
这是第 3轮训练,当前损失值 0.01636
第3轮测试结束,当前正确率:95 %
这是第 4轮训练,当前损失值 0.01199
第4轮测试结束,当前正确率:96 %
这是第 5轮训练,当前损失值 0.00931
第5轮测试结束,当前正确率:96 %
这是第 6轮训练,当前损失值 0.00745
第6轮测试结束,当前正确率:97 %
这是第 7轮训练,当前损失值 0.00603
第7轮测试结束,当前正确率:97 %
这是第 8轮训练,当前损失值 0.00497
第8轮测试结束,当前正确率:97 %
这是第 9轮训练,当前损失值 0.00400
第9轮测试结束,当前正确率:97 %
这是第 10轮训练,当前损失值 0.00333
第10轮测试结束,当前正确率:97 %