Press "Enter" to skip to content

神经网络之softmax回归建模

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

 

一 简介

 

softmax主要针对多分类处理,面对分类问题,更为通用的处理办法将其转化为哑变量的形式,然后使用softmax回归进行处理,这种处理方式同样适用于二分类和多分类问题,oftmax是用于挑选最大值的一种方法,通过公式: = \∑ ,这种转化可以将结果放缩到0-1之间,并且使用softmax进行最大值的比较,相比max能有效避免损失函数求解时在0点不可导的问题(需进行反向传播,涉及到求导),损失函数的函数特性。经过softmax的数据处理后,可以在0点实现可导,

 

二 手动实现softmax回归

 

2.1 建模流程

 

2.1.1 模型选择

 

构建一个只包含一层神经网络进行建模。输出层的每个神经元输出结果都代表某条样本在三个类别中softmax后的取值,此时神经网络拥有两层,且是全连接。此时从特征到输出结果,就不再是简单的线性方程变换,而是矩阵相乘之后进行softmax转化。

 

 

#构建向前传播函数
def softmax(X,w):
    m=torch.exp(torch.mm(X,w))
    sp=torch.sum(m,1).reshape(-1,1)
    return m/sp

 

X是特征张量,w是由两层之间的连接权重所组成的矩阵,且w的行数就是输入数据特征的数量,w的列数就是输出层的神经元个数,或者说就是分类问题的类别总数。

 

2.1.2 确定目标函数

 

#定义交叉熵损失函数
def m_cross_entropy(soft_z, y):
    y = y.long()
    prob_real = torch.gather(soft_z, 1, y)
    return (-(1/y.numel()) * torch.log(torch.prod(prob_real)))

 

交叉熵损失函数本质上还是关于w参数的函数方程。我们在进行反向传播时也是将w视为叶节点,通过梯度计算逐步更新w的取值。

 

2.1.3 定义优化算法

 

#定义在softmax回归下的准确率计算函数
def m_accuracy(soft_z, y):
    acc_bool = torch.argmax(soft_z, 1).flatten() == y.flatten()
    acc = torch.mean(acc_bool.float())
    return(acc)
   #梯度调整的函数继续沿用sgd函数
 def sgd(params, lr):
    params.data -= lr * params.grad 
    params.grad.zero_()

 

2.1.4 训练模型

 

torch.manual_seed(300)
features,labels=tensorGenCla(bias=True,deg_dispersion=[6,2])
plt.scatter(features[:,0],features[:,1],c=labels,cmap='rainbow')
torch.manual_seed(300)  #设置随机种子
batch_size=10  #每一个小批数量
lr=0.03     #学习率
num_epochs=3    #遍历次数
w=torch.randn(3,3,requires_grad=True)    #随机设置初始权重
net=softmax    #使用回归方程
loss=m_cross_entropy  #交叉熵损失函数
train_acc=[]
#模型训练
for epoch in range(num_epochs):
    for X,y in data_iter(batch_size,features,labels):
        l=loss(net(X,w),y)
        l.backward()
        sgd(w,lr)
    train_acc=m_accuracy(net(features,w),labels)
    print('epoch %d,  acc %f'%(epoch+1,train_acc))

 

查看模型结果

 

w

 

 

2.1.5 模型调试

 

多迭代几轮,观察模型收敛速度

 

torch.manual_seed(300)
#迭代次数
num_epochs=20
#设置初始权重
w=torch.randn(3,3,requires_grad=True)
train_acc=[]
for i in range(num_epochs):
    for epochs in range(i):
        for X,y in data_iter(batch_size,features,labels):
            l=loss(net(X,w),y)
            l.backward()
            sgd(w,lr)
    train_acc.append(m_accuracy(net(features,w),labels))
    
plt.plot(list(range(num_epochs)),train_acc)

 

和此前的逻辑回归实验结果类似,在数据内部离散程度较低的情况下,模型收敛速度较快,猜想每一轮epoch时w都进行不同的随机取值,会不会影响模型的收敛速度。

 

#10组不同的w,迭代10轮观察收敛速度
for i in range(10):
    w=torch.randn(3,3,requires_grad=True)
    for epoch in range(10):
        for X,y in data_iter(batch_size,features,labels):
            l=loss(net(X,w),y)
            l.backward()
            sgd(w,lr)
        train_acc.append(m_accuracy(net(features,w),labels))
    plt.plot(list(range(10)),train_acc)

 

尽管初始w的随机取值会影响前期模型的准确率,但在整体收敛速度较快的情况下,损失函数的初始值点各不相同,但通过一轮轮梯度下降算法的迭代,都能够找到(逼近)最小值点。此处即验证了梯度下降算法本身的有效性,同时也说明对于该数据集来说,找到(逼近)损失函数的最小值点并不困难。

 

三 调库实现softmax回归

 

3.1 调库建模流程

 

batch_size=10   #每一个小批的数量
lr=0.03       #学习率
num_epochs=3    #训练过程遍历数据次数
torch.manual_seed(300)
#创建数据集
features,labels=tensorGenCla(deg_dispersion=[6,2])
labels=labels.float()    #损失函数的标签必须为浮点型
data=TensorDataset(features,labels)
batchData=DataLoader(data,batch_size=batch_size,shuffle=True)
features

 

 

3.1.1 定义核心参数

 

通过调库快速sofrmax回归

 

batch_size=10   #每一个小批的数量
lr=0.03       #学习率
num_epochs=3    #训练过程遍历数据次数
torch.manual_seed(300)
#创建数据集
features,labels=tensorGenCla(deg_dispersion=[6,2])
labels=labels.float()    #损失函数的标签必须为浮点型
data=TensorDataset(features,labels)
batchData=DataLoader(data,batch_size=batch_size,shuffle=True)
features

 

3.1.2 定义模型

 

class softmaxR(nn.Module):
    def __init__(self,in_features=2,out_features=3,bias=False):
        super(softmaxR,self).__init__()
        self.linear=nn.Linear(in_features,out_features)
    def forward(self,x):
        out=self.linear(x)
        return out
softmax_model=softmaxR()

 

3.1.3 定义损失函数

 

#定义损失函数
criterion=nn.CrossEntropyLoss()

 

3.1.4 定义优化方法

 

#定义优化方法
optimizer=optim.SGD(softmax_model.parameters(),lr=lr)

 

3.1.5 模型训练

 

#训练模型
def fit(net ,criterion,optimizer,batchdata,epochs):
    for epoch in range(epochs):
        for X,y in batchdata:
            zhat=net.forward(X)
            y=y.flatten().long()
            loss=criterion(zhat,y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
fit(net=softmax_model
   ,criterion=criterion
    ,optimizer=optimizer
    ,batchdata=batchData
    ,epochs=num_epochs
   )

 

3.1.6 查看模型及参数

 

#查看模型
softmax_model
#查看模型参数
list(softmax_model.parameters())

 

 

3.1.7 计算交叉熵及准确率

 

#计算交叉熵损失
criterion(softmax_model(features),labels.flatten().long())
#使用F函数,计算准确率
m_accuracy(F.softmax(softmax_model(features),1),labels)

 

 

3.2 模型参数

 

将上述模型多迭代几次查看速度和准确率是否提升。

 

#设置随机种子
torch.manual_seed(300)
#创建数据集
features,labels=tensorGenCla(deg_dispersion=[6,2])
labels=labels.float()
data=TensorDataset(features,labels)
batchData=DataLoader(data,batch_size=batch_size,shuffle=True)
#设置随机数种子
torch.manual_seed(300)
#初始化核心参数
num_epochs=20  #迭代20次
SF1=softmaxR()
cr1=nn.CrossEntropyLoss()
op1=optim.SGD(SF1.parameters(),lr=lr)
#创建列表容器
train_acc=[]
for epochs in range(num_epochs):
    fit(net=SF1
       ,criterion=cr1
        ,optimizer=op1
        ,batchdata=batchData
        ,epochs=epochs
       )
    epoch_acc=m_accuracy(F.softmax(SF1(features),1),labels)
    train_acc.append(epoch_acc)
    
#绘图查看准确率变化情况
plt.plot(list(range(num_epochs)),train_acc)

 

和手动实现相同,此处模型也展示了非常快的收敛速度。当num_epochs=20时,SF1参数已经训练了(19+18+…+1)次了。

 

3.3 增加模型复杂分类难度

 

torch.manual_seed(300)
features,labels=tensorGenCla(deg_dispersion=[6,6])  #增加模型复杂度
labels=labels.float()
data=TensorDataset(features,labels)
batchData=DataLoader(data,batch_size=batch_size,shuffle=True)
plt.scatter(features[:,0],features[:,1],c=labels,cmap='rainbow')

 

 

#设置随机数种子
torch.manual_seed(300)
#初始化核心参数
num_epochs=30
SF1=softmaxR()
cr1=nn.CrossEntropyLoss()
op1=optim.SGD(SF1.parameters(),lr=lr)
#创建列表容器
train_acc=[]
for epochs in range(num_epochs):
    fit(net=SF1
        ,criterion=cr1
        ,batchdata=batchData
        ,optimizer=op1
        ,epochs=epochs    
    )
    epoch_acc=m_accuracy(F.softmax(SF1(features),1),labels)
    train_acc.append(epoch_acc)
plt.grid(alpha=0.3)    
plt.plot(list(range(num_epochs)),train_acc)

 

收敛速度仍然很快,模型很快就到达了比较稳定的状态。但和此前的逻辑回归实验相同,模型结果虽然比较稳定,但受到数据集分类难度提升影响,模型准确率却不高,基本维持在65%左右。一般来说,此时就代表模型抵达了判别效力上界,此时模型已经无法有效捕捉数据集中规律。从根本上来说就是模型已经到达(逼近)损失函数的最小值点,但模型的评估指标却无法继续提升。首先,我们可以初始选择多个w来观察损失函数是否已经逼近最小值点而不是落在了局部最小值点附近。

 

3.4 初始化w值

 

#初始化核心参数
cr1=nn.CrossEntropyLoss()
train_acc=[]
for i in range(num_epochs):
    SF1=softmaxR()
    op1=optim.SGD(SF1.parameters(),lr=lr)
    fit(net=SF1
       ,criterion=cr1
        ,optimizer=op1
        ,batchdata=batchData
        ,epochs=epochs
       )
    epoch_acc=m_accuracy(F.softmax(SF1(features),1),labels)
    train_acc.append(epoch_acc)
plt.grid(alpha=0.3)
plt.plot(list(range(num_epochs)), train_acc)

 

初始化不同的w发现最终模型准确率仍然是65%左右,也从侧面印证迭代过程没有问题,模型已经到达(逼近)最小值点。也就是说问题并不是出在损失函数的求解上,而是出在损失函数的构造上。此时的损失函数哪怕取得最小值点,也无法进一步提升模型效果。而损失函数的构造和模型的构造直接相关,此时若要进一步提升模型效果,就需要调整模型结构了

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注