深度学习中单个数字可以做多分类么?

作者:万里鹏程转瞬至


深度学习中单个int数字可以做多分类吗?最直观的答案是不可以。为什么不可以呢?这需要很多深入思考,不能依据直觉给出答案。因为,通过现在深度学习中,单个int可以变成多个int(后文说如何实现,博主也编写了代码进行测试)。需要注意的是,多个int是可以做多分类的。

1、基本概念

再进行深入探讨前,需要明白一些基础概念。

分类的基本概念:所谓分类就是输入一组连续的数据,输出一个离散的结果(类别)。为什么说类别是离散的呢?因为类别之间不存在连续的变化关系。就比如说奇数,偶数这两个类别,又或者说猫和狗这两个类别。

分类的本质:通过神经网络来做二分类,就是是把一个输入数据转化为一个类别概率值(二项分布概率)。多分类就是把一组输入数据,转化为一组概率值(多项分布概率)。

神经网络实现分类的过程可以抽象为两部分,第一部分为(输入数据)特征的映射变换(实现将输入数据转换为可用的特征值),第二部分为特征的概率转换(实现将特征值转换为类别概率值)。

2、多分类探讨

然而在实际中,单个数字做二分类是存在的。这里有个前提,就是先对这个数字做一定的投影变换(回归),得到一个输出值,然后使用sigmoid函数做投影,映射到[0,1]的概率空间。概率趋近于1表示一种类别A,概率趋近于0,表示非类别A。划分两个类别概率值的界限是0.5。做二分类的本质是把一个数字进行一系列变化后得到一个二次项分布概率。

单个int做多分类其实是存在一定困难的,难点一,不存在一个概率映射函数将数字的输出概率[0,1]等分为n段,每一段表示一个类别,目前只存在将数轴分为两段的概率映射函数。难点二,一个数字它只能描述一个特定的属性,数值特别大,表示属性值拉满,属于一种类别;数值特别小,表示这个属性值可能不存在,属于另外一种类别。

那么单个数字做多分类的本质就是把一个数字进行一系列变化后,得到一个多次项分布的概率值。核心问题就出现了,对于难点一:一数字怎么对应多个概率值?,对于难点二:一个数字,如何去描述多种属性? 如果不解决这个问题,那么单个数字是无法做多分类的。 如果难点二解决了,那么难点一其实也间接解决了,因为有了多个属性值后,可以根据属性值生成多类别概率值。

3、单个数字如何描述多个属性值?

有两种实现方式,第一种是使用全连接做维度变化,第二种是用embody layer做特征嵌入。

首先描述第一种方式,做升维变换:

也就是说通过对单个数字进行一系列的排列组合或非线性变换得到一组特征描述符,通过这一组特征描述符去描述多个概率属性。这种操作的本意就是该数字本身内嵌了多种类别属性的描述概率值,经过升维变化,只是把这些概率值进行了解码。解码出这些属性值之后,然后就可以做多次项概率分布映射。
第二种方式,嵌入(embedding):

该方式认为这个数值本身生不具备任何含义,他只是指向了某个特定空间的一个未知向量。也就是说,这个数字只是一个代号。通过这个代号可以找到一组特征向量,然后对这组特征向量进行分类。

4、实操训练

博主对以上两种方式进行了探讨实验,具体任务为区分数字的类型。就是根据一个数字除5后的余数,判断数据的类别。如果余数为零,则表示类别0,余数为四则表示类别4。这里数值本身对类别其实没有太多的含义或作用。

训练数据: 0~80000的数字。
测试数据: 80000~1万的数字。

4.1 全连接的操作方式

代码和运行结果如下所示,可见loss很难下降,可见通过该方式无法挖掘出int内在的多个特征,因而无法对其进行有效训练。

from tqdm import tqdm_notebook as tqdm
from paddle.vision.transforms import ToTensor
import paddle
from paddle.io import Dataset
class MyDataset(Dataset):
    def __init__(self,start=0,end=1000):
        super(MyDataset, self).__init__()
        self.data=[x for x in range(start,end)]
        self.length = len(self.data)
    def __getitem__(self, index):
        x=self.data[index]
        y=x%5
        return paddle.to_tensor([x,],paddle.float32), y
        #return x, y
    def __len__(self):
        return self.length
class Dnet(paddle.nn.Layer):
    def __init__(self):
        super(Dnet, self).__init__()

        self.net = paddle.nn.Sequential(
                paddle.nn.Linear(1,10)
                #paddle.nn.Embedding(100000, 10, sparse=False)
                , paddle.nn.Linear(10,20)
                , paddle.nn.ReLU6()
                , paddle.nn.Linear(20,10)
                , paddle.nn.ReLU6()
                , paddle.nn.Linear(10,5)
                )
    def forward(self, x):
        out=self.net(x)
        return out

from tqdm import tqdm
def train_model(train_loader,model,loss_fn,optim,epoch,Epochs):
    model.train()
    total_loss=0
    total_acc=0
    with tqdm(total=len(train_loader), desc=f'Train epoch {epoch + 1}/{Epochs}', postfix=dict, mininterval=0.3,ncols=100) as pbar:
        for iteration, data in enumerate(train_loader()):
            x_data = data[0]            # 训练数据
            y_data = data[1]            # 训练数据标签
            predicts = model(x_data)    # 预测结果
            # 计算损失 等价于 prepare 中loss的设置
            loss = loss_fn(predicts, y_data)
            # 反向传播
            loss.backward()
            # 更新参数
            optim.step()
            # 梯度清零
            optim.clear_grad()
            #更新进度条
            total_loss+=loss.item()
            pbar.set_postfix(**{'total_loss': "%.4f"%(total_loss / (iteration + 1))})
            pbar.update(1)
def eval_model(test_loader,model,loss_fn,epoch,Epochs):
    model.eval()
    total_loss=0
    total_acc=0
    with tqdm(total=len(test_loader), desc=f'Test epoch {epoch + 1}/{Epochs}', postfix=dict, mininterval=0.3,ncols=100) as pbar:
        for iteration, data in enumerate(test_loader()):
            x_data = data[0]            # 测试数据
            y_data = data[1]            # 测试数据标签
            predicts = model(x_data)    # 预测结果
            # 计算损失与精度
            loss = loss_fn(predicts, y_data)
            total_loss+=loss.item()
            pbar.set_postfix(**{'total_loss': "%.4f"%(total_loss / (iteration + 1))})
            pbar.update(1)
    print("Test result, loss is: {:.4f}".format(total_loss / (iteration + 1)))
    return total_loss / (iteration + 1)
BATCH_SIZE=128

data_train=MyDataset(0,80000)
data_val=MyDataset(80000,100000)
val_loader = paddle.io.DataLoader(data_val, batch_size=BATCH_SIZE, shuffle=True)
train_loader = paddle.io.DataLoader(data_train, batch_size=BATCH_SIZE, shuffle=True)
 
model=Dnet()
#load_weight = paddle.load("lstm_fcn3dp0.1_loss0.036536786247763714.pdparams")
# 设置迭代次数
Epochs =600
# 设置优化器
optim = paddle.optimizer.Adam(parameters=model.parameters(),learning_rate=0.0007)
# 设置损失函数  其中内置了softmax和onehot
loss_fn = paddle.nn.CrossEntropyLoss(reduction='mean', soft_label=False, axis=1)
loss_max=10000
for epoch in range(Epochs):
    train_model(train_loader,model,loss_fn,optim,epoch,Epochs)
    loss=eval_model(val_loader,model,loss_fn,epoch,Epochs)

Train epoch 1/600: 100%|███████████████████████| 625/625 [00:24<00:00, 25.51it/s, total_loss=1.8607]
Test epoch 1/600: 100%|████████████████████████| 157/157 [00:04<00:00, 31.61it/s, total_loss=1.6118]
Train epoch 2/600: 100%|███████████████████████| 625/625 [00:23<00:00, 21.99it/s, total_loss=1.6098]
Test epoch 2/600: 100%|████████████████████████| 157/157 [00:06<00:00, 25.33it/s, total_loss=1.6096]
Train epoch 3/600: 100%|███████████████████████| 625/625 [00:22<00:00, 27.43it/s, total_loss=1.6097]
Test epoch 3/600: 100%|████████████████████████| 157/157 [00:05<00:00, 26.67it/s, total_loss=1.6095]
Train epoch 4/600: 100%|███████████████████████| 625/625 [00:22<00:00, 27.39it/s, total_loss=1.6096]
Test epoch 4/600: 100%|████████████████████████| 157/157 [00:04<00:00, 31.47it/s, total_loss=1.6096]
Train epoch 5/600: 100%|███████████████████████| 625/625 [00:23<00:00, 26.15it/s, total_loss=1.6097]
Test epoch 5/600: 100%|████████████████████████| 157/157 [00:04<00:00, 32.65it/s, total_loss=1.6095]
Train epoch 6/600: 100%|███████████████████████| 625/625 [00:25<00:00, 24.62it/s, total_loss=1.6096]
Test epoch 6/600: 100%|████████████████████████| 157/157 [00:04<00:00, 32.10it/s, total_loss=1.6097]

4.2 嵌入的操作方式

代码和运行结果如下所示,训练集的loss一路下降,而测试级的loss一直不变。因为该种训练方式int的具体数值没有任何含义,在该定义一下,训练集与验证集完全没有交集,所以验证集loss一直不下降。

import paddle
from paddle.io import Dataset
from tqdm import tqdm_notebook as tqdm
from paddle.vision.transforms import ToTensor
class MyDataset(Dataset):
    def __init__(self,start=0,end=1000):
        super(MyDataset, self).__init__()
        self.data=[x for x in range(start,end)]
        self.length = len(self.data)
    def __getitem__(self, index):
        x=self.data[index]
        y=x%5
        #return paddle.to_tensor([x,],paddle.float32), y
        return x, y
    def __len__(self):
        return self.length
class Dnet(paddle.nn.Layer):
    def __init__(self):
        super(Dnet, self).__init__()

        self.net = paddle.nn.Sequential(
                #paddle.nn.Linear(1,10)
                paddle.nn.Embedding(100000, 10, sparse=False)
                , paddle.nn.Linear(10,20)
                , paddle.nn.ReLU6()
                , paddle.nn.Linear(20,10)
                , paddle.nn.ReLU6()
                , paddle.nn.Linear(10,5)
                )
    def forward(self, x):
        out=self.net(x)
        return out

from tqdm import tqdm
def train_model(train_loader,model,loss_fn,optim,epoch,Epochs):
    model.train()
    total_loss=0
    total_acc=0
    with tqdm(total=len(train_loader), desc=f'Train epoch {epoch + 1}/{Epochs}', postfix=dict, mininterval=0.3,ncols=200) as pbar:
        for iteration, data in enumerate(train_loader()):
            x_data = data[0]            # 训练数据
            y_data = data[1]            # 训练数据标签
            predicts = model(x_data)    # 预测结果
            # 计算损失 等价于 prepare 中loss的设置
            loss = loss_fn(predicts, y_data)
            # 反向传播
            loss.backward()
            # 更新参数
            optim.step()
            # 梯度清零
            optim.clear_grad()
            #更新进度条
            total_loss+=loss.item()
            pbar.set_postfix(**{'total_loss': "%.4f"%(total_loss / (iteration + 1))})
            pbar.update(1)
def eval_model(test_loader,model,loss_fn,epoch,Epochs):
    model.eval()
    total_loss=0
    total_acc=0
    with tqdm(total=len(test_loader), desc=f'Test epoch {epoch + 1}/{Epochs}', postfix=dict, mininterval=0.3,ncols=200) as pbar:
        for iteration, data in enumerate(test_loader()):
            x_data = data[0]            # 测试数据
            y_data = data[1]            # 测试数据标签
            predicts = model(x_data)    # 预测结果
            # 计算损失与精度
            loss = loss_fn(predicts, y_data)
            total_loss+=loss.item()
            pbar.set_postfix(**{'total_loss': "%.4f"%(total_loss / (iteration + 1))})
            pbar.update(1)
    print("Test result, loss is: {:.4f}".format(total_loss / (iteration + 1)))
    return total_loss / (iteration + 1)
BATCH_SIZE=128

data_train=MyDataset(0,80000)
data_val=MyDataset(80000,100000)
val_loader = paddle.io.DataLoader(data_val, batch_size=BATCH_SIZE, shuffle=True)
train_loader = paddle.io.DataLoader(data_train, batch_size=BATCH_SIZE, shuffle=True)
 
model=Dnet()
#load_weight = paddle.load("lstm_fcn3dp0.1_loss0.036536786247763714.pdparams")
# 设置迭代次数
Epochs =600
# 设置优化器
optim = paddle.optimizer.Adam(parameters=model.parameters(),learning_rate=0.0007)
# 设置损失函数  其中内置了softmax和onehot
loss_fn = paddle.nn.CrossEntropyLoss(reduction='mean', soft_label=False, axis=1)
loss_max=10000
for epoch in range(Epochs):
    train_model(train_loader,model,loss_fn,optim,epoch,Epochs)
    loss=eval_model(val_loader,model,loss_fn,epoch,Epochs)

Train epoch 1/600: 100%|███████████████████████| 625/625 [00:10<00:00, 58.31it/s, total_loss=1.6095]
Test epoch 1/600: 100%|███████████████████████| 157/157 [00:01<00:00, 103.29it/s, total_loss=1.6094]
Train epoch 2/600: 100%|███████████████████████| 625/625 [00:09<00:00, 65.04it/s, total_loss=1.4993]
Test epoch 2/600: 100%|████████████████████████| 157/157 [00:01<00:00, 87.35it/s, total_loss=1.6433]
Train epoch 3/600: 100%|███████████████████████| 625/625 [00:08<00:00, 76.47it/s, total_loss=0.9569]
Test epoch 3/600: 100%|███████████████████████| 157/157 [00:01<00:00, 110.64it/s, total_loss=1.8536]
Train epoch 4/600: 100%|███████████████████████| 625/625 [00:08<00:00, 76.43it/s, total_loss=0.8630]
Test epoch 4/600: 100%|████████████████████████| 157/157 [00:01<00:00, 92.06it/s, total_loss=3.7162]
Train epoch 5/600: 100%|███████████████████████| 625/625 [00:10<00:00, 68.16it/s, total_loss=0.5198]
Test epoch 5/600: 100%|████████████████████████| 157/157 [00:01<00:00, 86.59it/s, total_loss=5.1813]
Train epoch 6/600: 100%|███████████████████████| 625/625 [00:07<00:00, 79.46it/s, total_loss=0.3148]
Test epoch 6/600: 100%|████████████████████████| 157/157 [00:01<00:00, 85.38it/s, total_loss=5.9802]

5、总结

单个数字做不了多分类,本质原因是:单个数字只能描述一种属性,一种属性最多只能判断两种类别。所以,单个数字只能做二分类。

如果能在一个数字内解析出多种属性,比如说嵌入,那么就可以对单个数字做多分类。但是由于嵌入的方式忽略数值本身,那也就不可能解析数字内有的属性。亦或者,是人工对数字特征进行解析,除特定数的余数是多少,前n位的值是多少,后n位的值是多少,通过对数值进行解码后得到多个特征描述符。

需要注意的是,输入n个数据(n个特征描述符),可以做k(k>=2)个类别的分类。这里k可以大于n,因为通过数据之间的线性变化与排列组合,可以形成新的属性。


版权声明:本文为CSDN博主「万里鹏程转瞬至」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a486259/article/details/125839173

最新文章