本文目录

  • 多分类问题:实际上求解的是随机事件的分布
  • 问题引入
  • 网络设计
  • Loss:
  • 课后练习1:交叉熵损失vsNLL损失
  • 解答:
  • MNIST问题:
  • 模型设计:
  • 实现代码:
  • 1. 准备数据
  • 转为格式为C*W*H值为0-1的Tensor transform = transforms.Compose([
  • 2. 模型设计 class Net(torch.nn.Module):
  • 3. 损失、优化器
  • 4. 训练和测试 def train(epoch):
  • 结果:
  • 课后练习2:kaggle商品分类
  • 实现代码:
  • 结果:



多分类问题:实际上求解的是随机事件的分布

问题引入

前篇中,对糖尿病数据集的问题是一个二分类问题,但实际问题中,二分类问题较少,更多的是以MINIST、CIFAR为例的多分类问题。

多类别分割 unet pytorch_pytorch

网络设计

转换为二分类问题进行判断(eg:当输出为1时,对其他的非1输出都规定为0,以此来进行判断。)

多类别分割 unet pytorch_多类别分割 unet pytorch_02

但这种情况下,类别之间所存在的互相抑制的关系没有办法体现,当一个类别出现的概率较高时,其他类别出现的概率仍然有可能很高。
换言之,当计算输出为1的概率之后,再计算输出为2的概率时,并不是在输出为非1的条件下进行的,也就是说,所有输出的概率之和实际上是大于1的。
即对于一个多分类问题,其解决方案应该基于如下要求:

(1)每个分类的出现概率大于等于0

(2)各个分类出现概率之和为1

多类别分割 unet pytorch_分类_03

综上,多分类输出之间是需要有竞争性的。

改进网络:改最后的sigmod层为softmax层,来实现多分类问题的基本要求。

多类别分割 unet pytorch_pytorch_04

SoftMax层:假定Zl为最后一层线性层的输出,Zi为第i类的输出,则最终softmax层函数应为:

多类别分割 unet pytorch_pytorch_05

多类别分割 unet pytorch_多分类_06

事实上,对于多分类问题输出,Softmax会先对所有输出进行指数运算,以满足(1)式要求,再对结果进行归一化处理,以满足(2)式要求。

Loss:

交叉熵的计算公式如下:

多类别分割 unet pytorch_多类别分割 unet pytorch_07

在多分类问题中,该公式可扩展为:

多类别分割 unet pytorch_深度学习_08

其中各个符号含义如下:

多类别分割 unet pytorch_多类别分割 unet pytorch_09

由于上述计算过程中P(Xij)非0即1,且有且只能有一个1,因此一个样本所有分类的loss计算过程可以简化为:

多类别分割 unet pytorch_分类_10

其中,X表示事件预测值与实际值相同,Y表示非0即1的指示变量,Y^表示SoftMax的输出。

此时Y其实是作为独热编码(One-hot)输入的,以对离散的变量进行分类,即只在实际值处为1,其他均为0。

多类别分割 unet pytorch_分类_11

代码:

import numpy as np
 
y = np.array([1, 0, 0])
z = np.array([0.2, 0.1, -0.1])
 
y_pred = np.exp(z) / np.exp(z).sum()
 
loss = (-y*np.log(y_pred)).sum()
 
print(loss)

上述代码封装在CrossEntropyLoss()函数中

多类别分割 unet pytorch_pytorch_12

原代码可以重写成:

import torch
#需要时LongTensor
y = torch.LongTensor([0])
z = torch.Tensor([[0.2,0.1,-0.1]])
criterion = torch.nn.CrossEntropyLoss()
loss = criterion(z,y)
print(loss)

课后练习1:交叉熵损失vsNLL损失

多类别分割 unet pytorch_分类_13

解答:


  • NLLLoss全称是Negative Log Likelihood Loss,即最大似然函数。
  • CrossEntropyLoss就是交叉熵代价函数,就是把上面的我们执行的softmax+log+NLLLoss合并起来了,一步执行完。

请看下图:

多类别分割 unet pytorch_多类别分割 unet pytorch_14

MNIST问题:

MINIST数据集中每个数字都是一个28∗28=784大小的灰度图,将灰度图中的每个像素值映射到(0,1)区间内,可以进行映射。

多类别分割 unet pytorch_分类_15

模型设计:

多类别分割 unet pytorch_多类别分割 unet pytorch_16

实现代码:

import torch
 #组建DataLoader from torchvision import transforms #图像 from torchvision import datasets from torch.utils.data import DataLoader
 #激活函数和优化器 import torch.nn.functional as F import torch.optim as optim1. 准备数据
#Dataset&Dataloader必备 batch_size = 64
 #pillow(PIL)读的原图像格式为WHC,原值较大转为格式为CWH值为0-1的Tensor transform = transforms.Compose([
#变为格式为C*W*H的Tensor
transforms.ToTensor(),
#第一个是均值,第二个是标准差,变值为0-1
transforms.Normalize((0.1307, ), (0.3081, )) ])   train_dataset = datasets.MNIST(root='./mnist_data/',
                           train=True,
                           download=False,
                           transform = transform)   train_loader = DataLoader(train_dataset,shuffle=True,batch_size=batch_size)
test_dataset = datasets.MNIST(root=‘./mnist_data/’,
 train=False,
 download=False,
 transform = transform) test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)2. 模型设计 class Net(torch.nn.Module):
def __init__(self):
    super(Net, self).__init__()
    # 线性层1,input784维 output512维
    self.l1 = torch.nn.Linear(784, 512)
    # 线性层2,input512维 output256维
    self.l2 = torch.nn.Linear(512, 256)
    # 线性层3,input256维 output128维
    self.l3 = torch.nn.Linear(256, 128)
    # 线性层4,input128维 output64维
    self.l4 = torch.nn.Linear(128, 64)
    # 线性层5,input64维 output10维
    self.l5 = torch.nn.Linear(64, 10)

def forward(self, x):
    # 改变张量形状view\reshape
    # view 只能用于内存中连续存储的Tensor,transpose\permute之后的不能用
    # 变为二阶张量(矩阵),-1用于计算填充batch_size
    x = x.view(-1, 784)
    # relu 激活函数
    x = F.relu(self.l1(x))
    x = F.relu(self.l2(x))
    x = F.relu(self.l3(x))
    x = F.relu(self.l4(x))
    # 第五层不再进行relu激活
    return self.l5(x)   model = Net()
3. 损失、优化器
#交叉熵损失 criterion = torch.nn.CrossEntropyLoss()
 #随机梯度下降,momentum表冲量,在更新时一定程度上保留原方向 optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)4. 训练和测试 def train(epoch):
running_loss = 0.0
#提取数据
for batch_idx, data in enumerate(train_loader, 0):
    inputs, target = data
    #优化器清零
    optimizer.zero_grad()
    #前馈+反馈+更新
    outputs = model(inputs)
    loss = criterion(outputs, target)
    loss.backward()
    optimizer.step()
    #累计loss
    running_loss += loss.item()

    if batch_idx % 300 == 299:
        print('[%d, %5d] loss: %.3f' % (epoch+1, batch_idx+1, running_loss/300))
        running_loss = 0.0   epoch_list=[] acc_list=[] def test():
correct = 0
total = 0
#避免计算梯度
with torch.no_grad():
    for data in test_loader:
        images, labels = data
        outputs = model(images)
        #取每一行(dim=1表第一个维度)最大值(max)的下标(predicted)及最大值(_)
        _, predicted = torch.max(outputs.data, dim=1)
        #加上这一个批量的总数(batch_size),label的形式为[N,1]
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    acc_list.append(100 * correct/total)
    print('Accuracy on test set: %d %%' % (100 * correct/total))
     if __name__=='__main__':
for epoch in range(10):
    train(epoch)
    epoch_list.append(epoch+1)
    test()

多类别分割 unet pytorch_多分类_17

多类别分割 unet pytorch_pytorch_18

多类别分割 unet pytorch_分类_19

多类别分割 unet pytorch_多类别分割 unet pytorch_20

结果:

多类别分割 unet pytorch_pytorch_21

多类别分割 unet pytorch_多分类_22

课后练习2:kaggle商品分类

数据集: https://www.kaggle.com/c/otto-group-product-classification-challenge/data

多类别分割 unet pytorch_多分类_23

实现代码:

  • 主要参考博客,可以看看下面这篇学一下另外的写法!包含保存模型和可视化

import torch
from torchvision import datasets
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
import torch.nn.functional as F
import torch.optim as optim
import pandas as pd
import numpy as np


# 1. 准备数据
    # 定义函数将类别标签转为id表示,方便后面计算交叉熵
def lables2id(lables):
        target_id = []
        target_lables = ['Class_1', 'Class_2', 'Class_3', 'Class_4', 'Class_5', 'Class_6', 'Class_7', 'Class_8', 'Class_9']
        for lable in lables:
            target_id.append(target_lables.index(lable))
        return target_id
    #定义数据集
class OttoDataset(Dataset):
    def __init__(self,filepath):
        data = pd.read_csv(filepath)
        self.len = data.shape[0] #获取data行列数
        labels = data['target']
        self.x_data = torch.tensor(np.array(data)[:,1:-1].astype(float)) # 读取所以有行,从第一列开始,最后一列不要,转成float矩阵再存储成tensor 
        self.y_data = lables2id(labels)

    def __getitem__(self, index):
        return self.x_data[index], self.y_data[index]
 
    def __len__(self):
        return self.len

train_data=OttoDataset('./OttoGroupProduct/train.csv')
#建立数据集加载器
batch_size = 64
train_loader = DataLoader(dataset=train_data,shuffle=True,batch_size=batch_size)

## 2. 模型设计
class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 输入93种特征
        self.l1 = torch.nn.Linear(93, 64)
        self.l2 = torch.nn.Linear(64, 32)
        self.l3 = torch.nn.Linear(32, 16)
        # 输出9类
        self.l4 = torch.nn.Linear(16, 9)
        self.relu=torch.nn.ReLU()
    
    def forward(self, x):
        x = F.relu(self.l1(x))
        x = F.relu(self.l2(x))
        x = F.relu(self.l3(x))
        # 最后一层不再进行relu激活
        return self.l4(x)

    def predict(self, x):
        with torch.no_grad():
            x = self.relu(self.l1(x))
            x = self.relu(self.l2(x))
            x = self.relu(self.l3(x))
            x = self.l4(x)
            # 这里先取出最大概率的索引,即是所预测的类别。
            _, predicted = torch.max(x, dim=1)
            # 将预测的类别转为one-hot表示,方便保存为预测文件。
            y = pd.get_dummies(predicted)
            return y
 
model = Net()

# 3. 损失、优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)

# 4. 训练和测试
def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        inputs, target = data
        inputs=inputs.float()

        optimizer.zero_grad()
        outputs = model(inputs)

        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
 
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss: %.3f' % (epoch+1, batch_idx+1, running_loss/300))
            running_loss = 0.0
        
if __name__ == '__main__':
    for epoch in range(100):
        train(epoch)
# 定义预测保存函数,用于保存预测结果。
def predict_save():
    test_data = pd.read_csv('./OttoGroupProduct/test.csv')
    test_inputs = torch.tensor(np.array(test_data)[:,1:].astype(float))
    out = model.predict(test_inputs.float())

    lables=['Class_1', 'Class_2', 'Class_3', 'Class_4', 'Class_5', 'Class_6', 'Class_7', 'Class_8', 'Class_9']
    # 添加列标签
    out.columns = lables
    # 插入id行
    out.insert(0,'id',test_data['id'])
    output = pd.DataFrame(out)
    output.to_csv('./OttoGroupProduct/my_predict.csv', index=False)

predict_save()

结果:

多类别分割 unet pytorch_pytorch_24

多类别分割 unet pytorch_pytorch_25