softmax反向求导+Pytorch实现+项目讲解

前提

  • 三层神经网络;
  • 前两层网络输出的激活函数为relu函数;
  • 最后一层的输出激活函数为softmax(本例是个二分类问题);
  • 损失函数为交叉熵函数;
  • 209个样本;
  • 样本大小为64X64X3;
  • 每层神经元个数为:第一层:16,第二层:8,第三层:2

1.如何求softmax函数导数?

1.1神经网络如下:

(三层,两个输出)
在这里插入图片描述

1.2前向传播:

1.2.1数学公式:

在这里插入图片描述

1.2.2代码:

#三层网络、前向传播
class MLPNet(nn.Module):

    def __init__(self):
        #定义网络层
        self.layer = nn.Linear(64*64*3, 16)
        self.layer2 = nn.Linear(16, 8)
        self.layer3 = nn.Linear(8, 2)

    #定义前向传播(包含激活函数)
    def forward(self,x):
        x = x.reshape(-1, 64*64*3)
        x = self.layer(x)
        x = F.relu(x)
        x = self.layer2(x)
        x = F.relu(x)
        x = self.layer3(x)
        #sofemax函数的轴不写死
        return F.softmax(x, 1)
#softmax函数
def softmax(z):
    # Z的形状:209 2 。求每个样本在两个输出得到概率的和。209个样本即209个概率;
    #求行方向的和:axis=1;
    #求完和后形状变为(209,)。要进行除法运算,需要将(209,)形状通过广播变为(209,1):keepdims=True
    return np.divide(np.exp(z), np.sum(np.exp(z), axis=1, keepdims=True))
#softmax函数的轴不写死,如下:
def softmax(z ,dim):
    return np.divide(np.exp(z), np.sum(np.exp(z), axis=dim, keepdims=True))

1.3反向传播:

1.3.1数学公式:

在这里插入图片描述

1.3.2求导重点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3.3最终代码:

#CrossEntropyLoss损失函数
class CrossEntropyLoss(Module):

    def __call__(self, output, target ):
        self.A = output
        self.Y = target
        self.m = target.shape[0]
        return self.forward(self.A ,self.Y)

    def forward(self, A ,Y):
        self.loss = -np.mean(np.sum(Y * np.log(A), axis=1))
        # axis=1:在行方向相加,做平均值。209行,将每行的结果在行方向进行相加。
        #-np.mean():为求一批样本的平均损失。
        return self

    def float(self):
        return self.loss

    def backward(self):
        #第三层
        """
        dA3::
        #Yj:j是输出的最大值的索引。
        #Aj:j所在的最大值索引对应的最大的输出值。
        """
        #每一行最大值的索引
        j = np.argmax(self.Y, axis=1)
        """
        Yj:
        取Y中最大值
        取值方法:
        如下7个样本:
        10
        01
        10
        01
        01
        10
        10
        找出每个样本中1所在的位置,然后拿这个位置索引A中的输出。
        将Y中的1取出:现将每一行切片y[:],然后
        
        例如:
        data = np.arange(24).reshape(4, 6)
        y = np.array([3, 4, 2, 5]) #每个样本对应的索引不一样:3 10 14 23
        print(data)
        可看成4个样本,每个样本有5个输出,这5个输出对应不同的值。
        通过花式索引找,先全部取出,然后找每个维度中的每个索引:
        print(data[[0, 1, 2, 3], y])
        打印结果为:
        [[ 0  1  2  3  4  5]
         [ 6  7  8  9 10 11]
         [12 13 14 15 16 17]
         [18 19 20 21 22 23]]
        [[ 0  1  2  3  4  5]
         [ 6  7  8  9 10 11]
         [12 13 14 15 16 17]
         [18 19 20 21 22 23]]
        [ 3 10 14 23
        """
        #J为最大值索引,根据索引将每行最大值索引出来:self.Y[np.arange(209), j],
        #其中,np.arange(209)为生成209行。
        #Aj与Yj取法相同。
        dA3 = -self.Y[np.arange(209), j] / self.A[np.arange(209), j]#loss对A的导数
        dA3 = dA3.reshape(-1,1)
        """
        dZ3分两种情况:
        (1):i!=j:
        
        (2):i=j:
        """
        #声明一个(209,2)的零矩阵
        grads = np.zeros_like(self.Y)
        #首先,将零矩阵填充为Aj*Ai的,然后,把需要改动的变为Aj*(1-Aj)的。
        #每个输出*Aj,且Aj为每个样本中最大值索引对应的输出。
        #Aj是一维向量
        Aj = self.A[np.arange(self.Y.shape[0]), j]
        # i != j时:导数为:(-Aj*Ai)
        #reshape(-1, 1):将一维向量变为(广播)二维的向量。
        grads[:] = -self.A * Aj.reshape(-1, 1)
        # i == j时:导数为(Aj(1-AJ))
        #grads[np.arange(self.Y.shape[0]), j] :找到对应位置填充为Aj * (1 - Aj)的值
        grads[np.arange(self.Y.shape[0]), j] = Aj * (1 - Aj)
        #dA3对dZ3的导数
        dA3_dZ3 = grads
        #dZ3的导数
        dZ3 = dA3 * dA3_dZ3
        x_input = self.get_cache_input()
        A2 = next(x_input)
        dW3 = np.dot(dZ3.T, A2) / self.m
        dB3 = np.sum(dZ3.T, axis=1,keepdims=True)
        
		#第二层
        params = self.parameters()
        w3 = next(params).weight # 获取第三层的权重
        # relu 的导数
        relu_grad = np.copy(A2)#不污染原数据
        relu_grad[relu_grad > 0] = 1#找到>0的导数,其余继续为0
        dZ2 = np.dot(dZ3, w3) * relu_grad

        A1 = next(x_input)
        dW2 = np.dot(dZ2.T, A1) / self.m  # 一批样本的平均导数
        dB2 = np.sum(dZ2.T, axis=1, keepdims=True)  #

        # 第一层
        W2 = next(params).weight
        dA1 = np.dot(dZ2, W2)
        relu_grad = np.copy(A1)
        relu_grad[relu_grad > 0] = 1
        dZ1 = dA1 * relu_grad

        X1 = next(x_input)
        dW1 = np.dot(dZ1.T, X1) / self.m
        dB1 = np.sum(dZ1.T, axis=1, keepdims=True)

        return {"dW3":dW3, "dB3":dB3, "dW2": dW2, "dB2": dB2, "dW1": dW1, "dB1": dB1}

2.如何小批次加载数据?

  • 209个样本;
  • 一次输入50个样本;
  • 总输入5次;
#数据
class Dataset:
	mean = [0.4413, 0.4244, 0.3560]
    std = [0.26870, 0.2512, 0.2685]
    def __init__(self,train_patch,test_patch):
        self.train_x=self.trainData["train_set_x"]
        self.train_y=self.trainData["train_set_y"]
        
    #小批量加载
    def loader_data(self,batch_size):
        #计算加载次数
            """
            加载顺序:
            0:50
            50:100
            100:150
            ...
            共加载5次
            """
        iter_num=(self.train_set.shape[0]-1+batch_size)//batch_size
        for i in range(iter_num):
            #每次怎么取
            data=self.train_x[i*[batch_size]:(i+1)*batch_size]/255.#取数据
            data=(data-self.mean)/self.std
            data=self.train_y[i*[batch_size]:(i+1)*batch_size]#取标签
            yield data,target#关键的一步。迭代。
#训练
    def train(self):
            for j ,(input,target) in enumerate(self.dataset.loader_data(batch_size=50)):
                pass

注:小批量效果不一定好,因为一次学习大批量数据,确保一次能学习到很多特征。下面实验将数据打乱,检查效果好不。

3如何打乱数据?

数据没打乱,效果不好。例如:一组数据中前大半部分是猫的图片,后小半部分是狗的图片,学习过程如下所示(到狗时,学到的东西太少,正确率变为0%):

在这里插入图片描述

#实验
    #生成随机数
    data=np.arange(10)#生成一组数
    print(data)
    np.random.shuffle(data)#对原数组进行打乱
    print(data)
#打印结果
[0 1 2 3 4 5 6 7 8 9]
[8 5 7 6 2 1 9 3 0 4]
#实现
"""此处略去前半部分代码"""
    #打乱数据
    def shuffle_data(self,data,target):
        index=np.arange(209)
        np.random.shuffle(index)
        new_x=data[index]
        new_y=target[index]
        return new_x,new_y

    def train(self):
        #打乱数据进行训练
        data,target=self.dataset.get_train_set()
        losses=[]
        for i in range(5000):
            print("epochs:{}".format(i))
            new_x,new_y=self.shuffle_data(data,target)
            new_y=new_y[:,np.newaxis]#对标签进行变换
            out=self.net(new_x)
            loss=self.loss_func(out,new_y)
            gards=loss.backword()
            self.opt.step(gards)
            if i % 4 == 0:
                losses.append(loss.float())
                print("{}/{},loss:{}".format(i, 5000, loss.float()))
                plt.clf()
                plt.plot(losses)
                plt.pause(0.1)
                nn.save(self.net,"models/net.pth")

标题4.one_hot制作

(1)第一种方式

import numpy as np

def to_one_hot(self, x, C):
    target = np.zeros((x.shape[0], C))
    #合适的位置填充为1
    target[np.arange(x.shape[0]), x] = 1
    return target

(2)第二种方式

import numpy as np

    def to_one_hot2(self, x ,C):
        # eye后的索引指定1该在什么位置
        target = np.eye(x.shape[0],C)[x]
        return target
发布了29 篇原创文章 · 获赞 45 · 访问量 5051

猜你喜欢

转载自blog.csdn.net/sinat_39783664/article/details/103237326