使用PyTorch完成多指标多分类问题

Kaggle otto-group-product-classification-challenge

Posted by MetaNetworks on September 10, 2020
本页面总访问量

比赛信息

Otto Group Product Classification

https://www.kaggle.com/c/otto-group-product-classification-challenge

工具:PyTorch 1.6.0

信息:

  • 93特征值
  • 分类数为9类

这里假设特征之间没有关联,是个简单的仅分类问题,直接使用全连接层完成神经网络的搭建

面向PyTorch编程法则

  • 准备数据集
    • 数据集清洗
    • 训练集顺序打乱
  • 搭建神经网络
    • 线性Linear层
      • 一般用于分类、预测
    • CNN卷积神经网络
      • 适用于大于等于二维数据
    • 单/双向RNN循环神经网络
      • 一般适用于一维数据+特征项之间有联系
      • 多用于语义分析,预测
      • 还有GRU、LSTM
  • 选取损失函数
    • 二分交叉熵
    • 交叉熵、二分交叉熵…
  • 选取优化器
    • Adam、SGD…
  • 搭建模型
  • 训练模型
  • 评估模型

正片开始

此处使用Google Colab的GPU进行运算

导包、定义GPU是否开启

1
2
3
4
5
6
7
8
9
import torch
import torchvision
import pandas as pd
import numpy as np
import os
from torch.utils.data import Dataset,DataLoader

# GPU配置
USE_GPU = True

导入数据、数据处理

  • 此处要把Class_1-Class-9转换为0-8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root_dir = "/content/drive/My Drive/Colab Notebooks/otto-group-product-classification-challenge"
test_path = "test.csv"
train_path = "train.csv"

test_csv = os.path.join(root_dir,test_path)
train_csv = os.path.join(root_dir,train_path)

# 转换为数值型数据
train_data = pd.read_csv(train_csv)
def convertClass(s):
    if type(s) == int:
        return s
    s = s.replace("Class_","")
    return int(s)-1
train_data["target"] = train_data["target"].map(convertClass)

test_data = pd.read_csv(test_csv)
#test_data["target"] = train_data["target"].map(convertClass)
# 训练的时候不需要id,此处drop掉
train_data.drop("id",axis=1,inplace=True)

实现Dataset类

  • 实现__init____getitem(self,index)__len__(self)(可选)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyDataset(Dataset):
    def __init__(self,data):
        data 
        self.x_data = torch.from_numpy(np.array(data,dtype='float32')[:,:-1])
        self.y_data = torch.from_numpy(np.array(data,dtype='float32')[:,-1])
        self.length = data.shape[0]
        
    def __getitem__(self,index):
        return (self.x_data[index],self.y_data[index])
    
    def __len__(self):
        return self.length

class TestDataset(Dataset):
    def __init__(self,data):
        self.x_data = torch.from_numpy(np.array(data,dtype='float32'))
        self.length = data.shape[0]
        
    def __getitem__(self,index):
        return (self.x_data[index])
    
    def __len__(self):
        return self.length

train_dataset = MyDataset(train_data)
test_dataset = TestDataset(test_data)

模型定义

  • 使用Linear进行全连接

  • 使用ReLU6进行激活(ReLU6比较新,先试试)

  • 若启用了GPU,则model = model.cuda()

  • 若使用的是交叉熵损失,则最后一层不需要激活

    • CrossEntropyLoss中会使用Softmax对最后一层的输出进行激活

    • PS:激活是为了更好的拟合非线性输出,线性层+激活->更好拟合非线性输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 全连接模型
class TrainModel(torch.nn.Module):
    def __init__(self):
        super(TrainModel,self).__init__()
        # layers
        self.linear1 = torch.nn.Linear(93,48)
        self.linear2 = torch.nn.Linear(48,24)
        self.linear3 = torch.nn.Linear(24,12)
        self.linear4 = torch.nn.Linear(12,9)
        self.relu = torch.nn.ReLU6()
        
    def forward(self,x):
        x = self.relu(self.linear1(x))
        x = self.relu(self.linear2(x))
        x = self.relu(self.linear3(x))
        # 由于是交叉熵损失,最后一步不激活
        return self.linear4(x)
    
model = TrainModel()
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())

# 若开启了GPU,则导入cuda
if USE_GPU:
  model = model.cuda()

训练模型

  • 训练之前,先清空优化器的梯度
  • loss不要每次都输出,可以在一个epoch输出一次,或者每隔300次输出一次
  • 编写逻辑顺序
    • 清空梯度
    • 前向传播xs
    • 反向传播loss
    • 优化器调整模型参数
    • 循环…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for epoch in range(10000):
    print("training (epoch %d)"%epoch)
    total_loss = 0
    cnt = 0
    for index,(xs,ys) in enumerate(train_loader,0):
        if USE_GPU:
            xs = torch.tensor(xs)
            ys = torch.tensor(ys)
            xs = xs.to(device)
            ys = ys.to(device)
        optimizer.zero_grad()
        y_pred = model(xs)
        loss = criterion(y_pred,ys.long())
        total_loss += loss.item()
        loss.backward()
        optimizer.step()
        cnt += 1
    print("epoch %d, loss: %f"%(epoch,total_loss/cnt))
        

写完之后

开始炼丹!!!

  • 可以使用Tensorboard或者Matplotlib对loss进行可视化,可以看看loss下降趋势
  • 有测试集的话可以对其进行测试,查看准确率
    • 记得使用torch.no_grad()取消梯度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def test():
    correct = 0
    total = 0
    with torch.no_grad():
        print("testing...")
        for index,(x) in enumerate(test_loader,0):
            if USE_GPU:
                x = x.cuda()
            output = model(x[:,1:])
            output = torch.max(output,1)[1]
            print("x",x[:,0])
            print("pred",output + 1)
						# 官方没有测试集的结果
            此处省略
    #print("accu: %d %%"%(100*correct/total))