深度学习入门(上)

本文来自《深度学习入门 基于python的理论与实现》的学习

一、一些python注意点

自定义类

注意:初始化函数命名一定要是两个下划线打头和结束:__init__

不然会有TypeError: object() takes no parameters报错

class man:
    def __init__(self,name): # 构造函数
        self.name=name
        print("initialized!")
    def hello(self):
        print("hello"+self.name+"!")
    def goodbye(self):
        print("goodbye"+self.name+"!")

m=man("Alice")
m.hello()
m.goodbye()

构造函数只在生成类的实例时被调用一次self表示自身(python语言特点)

类man的构造函数会接受参数name,然后用这个参数初始化实例变量self.name。实例变量是存储在各个实例中的变量。

Result
initialized!
helloAlice!
goodbyeAlice!

Numpy

np.array()方法接收python列表作为参数,生成numpy数组。numpy数组可以生成N维数组:
一维:向量
二维:矩阵
三维及以上:张量/多维数组

广播

形状不同的数组之间也可以进行运算,例如 2 ∗ 2 2*2 22矩阵和一个标量相乘,实际是将这个标量扩展成 2 ∗ 2 2*2 22的形状,再与矩阵进行乘法运算。

import numpy as np 
A=np.array([[1,2],[3,4]])
B=np.array([10,20])
print(A*B)
Result
[[10 40]
 [30 80]]
访问元素

元素索引从0开始

import numpy as np 
X=np.array([[51,55],[14,19],[0,4]])
print(X)
print(X[0])
print(X[0][1])
# 用for语句访问各个元素
for row in X:
    print(row)
# 使用数组访问各个元素
X=X.flatten() # 将X转换为一维数组
print(X)
X[np.array([0,2,4])] # 获取索引为0,2,4的元素
# 运用这种标记法可以获取满足某种条件的元素
# eg.从X中抽出大于15的元素
print(X>15)
print(X[X>15])

对numpy数组使用不等号运算符(eg. X>15)会得到一个布尔型的数组,可通过这个布尔型数组取出对应元素。

Result
[[51 55]
 [14 19]
 [ 0  4]]
[51 55]
55
[51 55]
[14 19]
[0 4]
[51 55 14 19  0  4]
[ True  True False  True False False]
[51 55 19]

Matplotlib

linestylesupported values are ‘-’, ‘–’, ‘-.’, ‘:’, ‘None’, ’ ', ‘’, ‘solid’, ‘dashed’, ‘dashdot’, ‘dotted’
在这里插入图片描述

import numpy as np 
import matplotlib.pyplot as plt
# 生成数据 以0.1为单位,生成0-6的数据
x=np.arange(0,6,0.1) 
y1=np.sin(x)
y2=np.cos(x)
plt.plot(x,y1,label="sin")
plt.plot(x,y2,linestyle="--",label="cos")
plt.xlabel("x") # x轴标签
plt.ylabel("y") 
plt.title('sin & cos')
plt.legend()
plt.show()
import matplotlib.pyplot as plt
from matplotlib.image import imread
img=imread('openCV/ball.jpg')
plt.imshow(img)
plt.show()

二、感知机 perceptron

感知机接收多个输入信号,输出一个信号。感知机的信号也会形成流,向前方输送信息,但它只有传递信号/不传递信号(1/0)两种取值。
在这里插入图片描述
神经元会计算传送过来的信号的总和,只有当总和超过界限值时,才会输出1(神经元被激活),这个界限值被称为阈值,用θ表示。

另一种表示形式:
y = { 0 , ( b + w 1 x 1 + w 2 x 2 ≤ 0 ) 1 , ( b + w 1 x 1 + w 2 x 2 > 0 ) y= \begin{cases} 0, & (b+w_1x_1+w_2x_2≤0) \\ 1,& (b+w_1x_1+w_2x_2>0) \\ \end{cases} y={0,1,(b+w1x1+w2x20)(b+w1x1+w2x2>0)
w 1 , w 2 w_1,w_2 w1,w2 是权重, b b b 是偏差。权重决定信息传递的难易程度的参数,偏差是调整神经元被激活的容易程度的参数。

import numpy as np
def AND(x1,x2):
    x=np.array([x1,x2])
    w=np.array([0.5,0.5])
    b=-0.7
    tmp=np.sum(w*x)+b
    if tmp<=0:
        return 0
    else:
        return 1

def NAND(x1,x2):
    x=np.array([x1,x2])
    w=np.array([-0.5,-0.5])
    b=0.7
    tmp=np.sum(w*x)+b
    if tmp<=0:
        return 0
    else:
        return 1
        
def OR(x1,x2):
    x=np.array([x1,x2])
    w=np.array([0.5,0.5])
    b=-0.2
    tmp=np.sum(w*x) + b
    if tmp<=0:
        return 0
    else:
        return 1
   
def XOR(x1,x2):
    s1=NAND(x1,x2)
    s2=OR(x1,x2)
    y=AND(s1,s2)
    return y
    
if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = XOR(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))
感知机的局限性

单层感知机可以实现与门、或门、但无法实现异或门(无法分离非线性空间)。
在这里插入图片描述
感知机只能表示由一条直线分割的空间(线性空间)。

曲线可以分割上述两部分,但无法用感知机表示,由曲线分割而成的空间称为非线性空间。

多层感知机

感知机的绝妙之处在于可以“叠加层”,从而可以实现异或门,进行非线性的表示。

理论上来说,两层感知机就能构建计算机。因为两层激活函数使用了非线性的sigmoid函数的感知机可以表示任意函数,但人工进行合适权重的设定比较艰难。

三、神经网络

神经网络的出现,是为了解决人工设定感知机权重难的问题。
我们用一个函数来表示这种分情况的动作(超过0则输出1,否则输出0):
y = h ( b + w 1 x 1 + w 2 x 2 ) y=h(b+w_1x_1+w_2x_2) y=h(b+w1x1+w2x2)
h ( x ) = { 0 , ( x ≤ 0 ) 1 , ( x > 0 ) h(x)= \begin{cases} 0, & (x≤0) \\ 1,& (x>0) \\ \end{cases} h(x)={0,1,(x0)(x>0)
h ( x ) h(x) h(x)会将输入信号的总和转换成输出信号,称为激活函数,激活函数的作用在于决定如何来激活输入信号的总和。
       a = b + w 1 x 1 + w 2 x 2 a=b+w_1x_1+w_2x_2 a=b+w1x1+w2x2

          y = h ( a ) y=h(a) y=h(a)
在这里插入图片描述
上图为激活函数的计算过程。

朴素感知机:单层网络,激活函数使用了阶跃函数(一旦输入超过阈值就切换输出) 的模型。
多层感知机:神经网络,使用sigmoid函数等平滑的激活函数的多层网络。

激活函数

1.阶跃函数

在这里插入图片描述

import numpy as np 
import matplotlib.pylab as plt
def step_function(x):
    return np.array(x>0,dtype=np.int)
x=np.arange(-5.0,5.0,0.1)
y=step_function(x)
plt.plot(x,y)
plt.ylim(-0.1,1.1) # 指定y轴的范围
plt.show()

np.array(x>0,dtype=np.int)首先进行x>0的判断,生成布尔型数组,再将这些布尔型数据转换成int型。

2.sigmoid函数

h ( x ) = 1 1 + e − x h(x)=\frac{1}{1+e^{-x}} h(x)=1+ex1
在这里插入图片描述

def sigmoid(x):
    return 1/(1+np.exp(-x))
阶跃函数和sigmoid函数的比较

在这里插入图片描述
1.平滑性不同
2.阶跃函数只能返回0/1,而sigmoid函数可以返回实数。即感知机中神经元之间的流动是二元信号,神经网络中流动的是连续的实数值信号。
3.共同性质:宏观趋势相同;输入信号是重要信息时,都会输出较大值,输出信号的值都在0~1之间。

非线性函数

线性函数,不论如何加深层数,总是存在可替代的没有隐藏层的神经网络,eg. h(x)=cx,y(x)=h(h(h(x)))=c*c*c*x可以用y=ax,a=c^3替代。
为发挥叠加层带来的优势,激活函数必须使用非线性函数

3.ReLU函数

Rectified Linear Unit:
h ( x ) = { x , ( x > 0 ) 0 , ( x ≤ 0 ) h(x)= \begin{cases} x, & (x>0) \\ 0,& (x≤0) \\ \end{cases} h(x)={x,0,(x>0)(x0)
在这里插入图片描述

def sigmoid(x):
    return np.maximum(0,x)

多维数组的运算

np.ndim(A) 获取数组维度

np.shape(A) 获取每一个维度的元素个数(返回元组形式),例如A数组是3*4的数组,返回的是(3,4)

np.dot(A,B) 矩阵相乘

神经网络的内积:通过矩阵乘法实现神经网络的计算。

输出层的设计

输出层所用的激活函数根据求解问题的性质决定。eg.回归问题用恒等函数,二分类问题用sigmoid函数,多元分类问题用softmax函数

softmax函数

假设输出层共有n个神经元,计算第k个神经元的输出 y k y_k yk
y k = e a k ∑ i = 1 n e a i y_k=\frac{e^{a_k}}{\sum_{i=1}^n e^{a_i}} yk=i=1neaieak
但是要注意,指数函数的值很容易变得非常大而导致溢出,就会出现不确定nan(not a number)的结果。

import numpy as np 
def softmax(a):
    exp_a=np.exp(a)
    sum_exp_a=np.sum(exp_a)
    return exp_a/sum_exp_a

a=np.array([1010,1000,990])
print(softmax(a)) # [nan nan nan]

改进:
y k = e a k ∑ i = 1 n e a i = C e a k C ∑ i = 1 n e a i   = e a k + l o g C ∑ i = 1 n e a i + l o g C = e a k + C ′ ∑ i = 1 n e a i + C ′ y_k=\frac{e^{a_k}}{\sum_{i=1}^n e^{a_i}}=\frac{Ce^{a_k}}{C\sum_{i=1}^n e^{a_i}}\\\ \\=\frac{e^{a_k+logC}}{\sum_{i=1}^n e^{a_i+logC}}=\frac{e^{a_k+C'}}{\sum_{i=1}^n e^{a_i+C'}} yk=i=1neaieak=Ci=1neaiCeak =i=1neai+logCeak+logC=i=1neai+Ceak+C
这里的C‘可以是任意值,不过为了防止溢出,一般会选输入信号中的最大值。

import numpy as np 
def softmax(a):
    c=np.max(a)
    exp_a=np.exp(a-c)
    sum_exp_a=np.sum(exp_a)
    return exp_a/sum_exp_a

a=np.array([1010,1000,990])
y=softmax(a)
print(y) # [9.99954600e-01 4.53978686e-05 2.06106005e-09]
print(np.sum(y)) # 1.0

softmax函数的输出总是0.0~1.0之间的实数,且softmax函数的输出值的总和是1(所以可以把softmax函数的输出解释为"概率")。

由于( y = e x y=e^x y=ex)是单调递增函数,所以a的各元素大小关系和y的各元素大小关系保持不变,一般神经网络只把输出值最大的神经元所对应的类别作为识别结果,所以在分类时,可省略softmax函数(节省算力)。

正规化(normalization):将数据转换成在0.0~1.0的范围中。
数据白化(whitening):将数据整体的分布形状均匀化的方法。

批处理

在这里插入图片描述
np.argmax(y_batch,axis=1)中,该函数可以获取值最大元素的索引,设置参数axis=1,指定了在100*10的数组中,沿着第一维方向去寻找最大值,即会找到100个最大值。

四、神经网络的学习

在这里插入图片描述
泛化能力:处理未被观察过的数据(不包含在训练数据中的数据)的能力。
过拟合(over fitting):只对某个数据集过度拟合的状态。

损失函数

神经网络的学习通过某个指标表示现在的状态,然后以这个状态为基准寻找最优权重参数。

1.均方误差

E = 1 2 ∑ k ( y k − t k ) 2 E=\frac{1}{2}\sum_k(y_k-t_k)^2 E=21k(yktk)2
y k y_k yk表示神经网络的输出, t k t_k tk表示训练数据的结果,k表示数据的维数。

y=[0.1,0.05,0.6,0.0,0.05,0.1,0.0,0.1,0.0,0.0]
t=[0,0,1,0,0,0,0,0,0,0] # one-hot表示
def mean_squard_error(y,t):  #这里的y,t是numpy数组,否则写作np.array(y)作为参数输入
	return 0.5*np.sum((y-t)**2)
2.交叉熵误差

E = − ∑ k t k l o g   y k E=-\sum_k t_klog\ y_k E=ktklog yk
y k y_k yk是神经网络的输出, t k t_k tk是正确解标签,其中只有正确解标签的索引为1,其他均为0(one-hot表示),因此上式只计算对应正确解标签的输出的自然对数。

def cross_entropy_error(y,t):
	delta=1e-7
	return -np.sum(t*np.log(y+delta))

在这里插入图片描述
delta的作用是防止np.log(0)会变成-inf,导致后续计算无法进行。

注意:计算损失函数时必须将所有的训练数据作为对象。eg.如果训练数据有100个的话,就要把这100个损失函数的总和作为学习的指标。以交叉熵误差为例:
E = − 1 N ∑ n ∑ k t n k l o g   y n k E=-\frac{1}{N}\sum_n\sum_kt_{nk}log\ y_{nk} E=N1nktnklog ynk
假设数据有N个, t n k t_{nk} tnk表示第n个数据的第k个元素的值,最后除以N进行正规化,可以求得单个数据的“平均损失函数”。

通常一个数据集的数据有成千上万个,计算全部数据的损失函数不现实,所以我们通常从中选取一部分(mini-batch)来代表全部数据,然后对每个mini-batch进行学习。eg.从60000个训练数据中随机选择100个数据进行学习。

# 从训练数据中随机抽取10笔数据
# 使用numpy中的np.random.choice(train_size,batch_size)
train_size=x_train.shape[0]
batch_size=10
batch_mask=np.random.choice(train_size,batch_size)
x_batch=x_train[batch_mask]
t_batch=t_train[batch_mask]
mini-batch版交叉熵误差的实现

以下代码既可以处理单个数据也可以处理批量数据(数据作为batch集中输入)的情况

1.训练数据是用one-hot表示

def cross_entropy_error(y,t):
	if y.ndim==1:
		t=t.reshape(1,t.size)
		y=y.reshape(1,y.size)
	batch_size=y.shape[0]
	return -np.sum(t*np.log(y+1e-7)/batch_size

2.训练数据是标签形式

return -np.sum(np.log(y[np.arange(batch_size),t]+1e-7))/batch_size
为何用损失函数而非识别精度作为学习指标?

识别精度对微小参数变化基本没有什么反应,即使有,它的值也是不连续的、突然的变化(eg.33% -> 34%),阶跃函数同理。

梯度

梯度:由全部变量的偏导数汇总而成的向量,梯度指示的方向是各点处的函数值减小最多的方向。

f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x_0,x_1)=x_0^2+x_1^2 f(x0,x1)=x02+x12的梯度:

在这里插入图片描述

# coding: utf-8
# cf.http://d.hatena.ne.jp/white_wheels/20100327/p3
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
def _numerical_gradient_no_batch(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)
        
        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        x[idx] = tmp_val # 还原值
        
    return grad
def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad
def function_2(x):
    if x.ndim == 1:
        return np.sum(x**2)
    else:
        return np.sum(x**2, axis=1)
def tangent_line(f, x):
    d = numerical_gradient(f, x)
    print(d)
    y = f(x) - d*x
    return lambda t: d*t + y
     
if __name__ == '__main__':
    x0 = np.arange(-2, 2.5, 0.25)
    x1 = np.arange(-2, 2.5, 0.25)
    X, Y = np.meshgrid(x0, x1)
    
    X = X.flatten()
    Y = Y.flatten()
    
    grad = numerical_gradient(function_2, np.array([X, Y]) )
    
    plt.figure()
    plt.quiver(X, Y, -grad[0], -grad[1],  angles="xy",color="#666666")#,headwidth=10,scale=40,color="#444444")
    plt.xlim([-2, 2])
    plt.ylim([-2, 2])
    plt.xlabel('x0')
    plt.ylabel('x1')
    plt.grid()
    plt.legend()
    plt.draw()
    plt.show()
梯度法

机器学习的主要任务式再学习时寻找最优参数,同样神经网络也许在学习时找到最优参数(weight and bias)。

注意:梯度表示的是各点处的函数值减小最多的方向。所以并不能保证梯度所指的方向就是函数的最小值/真正应该前进的方向。实际上,在复杂函数中梯度指示的方向都不是函数值最小处。

梯度为0的点:函数的极小值、最小值、鞍点

  1. 极小值:局部最小值
  2. 鞍点:从某个方向上看是极大值,从另一个方向看则是极小值的点

所以,梯度法虽然是要找梯度为0的地方,但那个地方不一定就是最小值,也有可能会是极小值或鞍点,但沿着它的方向能最大限度的减小函数的值。

x 0 = x 0 − η ∂ f ∂ x 0 x_0=x_0-\eta\frac{\partial f}{\partial x_0} x0=x0ηx0f
x 1 = x 1 − η ∂ f ∂ x 1 x_1=x_1-\eta\frac{\partial f}{\partial x_1} x1=x1ηx1f
η \eta η 表示更新量,也称学习率,决定在一次学习中应该学多少以及在多大程度上更新参数。

学习率(超参数)的设定很重要,决定了能否到达一个"好位置"。学习率过大,梯度会发散成一个很大的值,过小,基本上没怎么更新就结束了。


梯度法求 f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x_0,x_1)=x_0^2+x_1^2 f(x0,x1)=x02+x12的最小值:

import numpy as np 
import matplotlib.pyplot as plt 
def f(x):
    return x[0]**2+x[1]**2

def numerical_gradient(f,x):
    h=1e4 
    grad=np.zeros_like(x) # 生成与x形状相同的数组
    for idx in range(x.size):
        tmp_val=x[idx]
        x[idx]=tmp_val+h
        fxh1=f(x)

        x[idx]=tmp_val-h
        fxh2=f(x)
        grad[idx]=(fxh1-fxh2)/(2*h)
        x[idx]=tmp_val # 还原值
    return grad

def gradient_descent(f,init_x,lr=0.01,step_num=100):
    x=init_x # 初始值
    for i in range(step_num):
        grad=numerical_gradient(f,x)
        x-=lr*grad
    return x

init_x=np.array([-3.0,4.0])
y=gradient_descent(f,init_x,lr=0.1)
print(y) # [-6.11096789e-10  8.14795650e-10]
神经网络的梯度

损失函数关于权重参数的梯度,用 ∂ L ∂ W \frac{\partial L}{\partial W} WL表示,例如:
W = ( w 11 w 12 w 13 w 21 w 22 w 23 ) W= \begin{pmatrix}w_{11}&w_{12}&w_{13}\\w_{21}&w_{22}&w_{23}\end{pmatrix} W=(w11w21w12w22w13w23)
∂ L ∂ W = ( ∂ L ∂ w 11 ∂ L ∂ w 12 ∂ L ∂ w 13 ∂ L ∂ w 21 ∂ L ∂ w 22 ∂ L ∂ w 23 ) \frac{\partial L}{\partial W}=\begin{pmatrix}\frac{\partial L}{\partial w_{11}}&\frac{\partial L}{\partial w_{12}}&\frac{\partial L}{\partial w_{13}} \\\\ \frac{\partial L}{\partial w_{21}}&\frac{\partial L}{\partial w_{22}}&\frac{\partial L}{\partial w_{23}}\end{pmatrix} WL=w11Lw21Lw12Lw22Lw13Lw23L

∂ L ∂ w 11 \frac{\partial L}{\partial w_{11}} w11L表示当 w 11 w_{11} w11 稍微变化时,损失函数 L L L 会发生多大变化。

# common.gradient 
# coding: utf-8
import numpy as np
def _numerical_gradient_1d(f,x):
    h=1e-4 # 0.0001
    grad=np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val=x[idx]
        x[idx]=float(tmp_val)+h
        fxh1=f(x) # f(x+h)
        
        x[idx]=tmp_val - h 
        fxh2=f(x)# f(x-h)
        grad[idx]=(fxh1 - fxh2)/(2*h)
        
        x[idx]=tmp_val # 还原值        
    return grad
def numerical_gradient_2d(f, X):
    if X.ndim==1:
        return _numerical_gradient_1d(f,X)
    else:
        grad=np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx]= _numerical_gradient_1d(f, x)     
        return grad
def numerical_gradient(f, x):
    h=1e-4 # 0.0001
    grad=np.zeros_like(x)
    
    it=np.nditer(x,flags=['multi_index'],op_flags=['readwrite'])
    while not it.finished:
        idx=it.multi_index
        tmp_val=x[idx]
        x[idx]=float(tmp_val)+h
        fxh1=f(x) # f(x+h)
        
        x[idx]=tmp_val - h 
        fxh2=f(x) # f(x-h)
        grad[idx]=(fxh1 - fxh2)/(2*h)
        
        x[idx]=tmp_val # 还原值
        it.iternext()          
    return grad
import numpy as np 
from common.gradient import numerical_gradient

def softmax(x):
    if x.ndim==2:
        x=x.T
        x=x-np.max(x,axis=0)
        y=np.exp(x)/np.sum(np.exp(x),axis=0)
        return y.T 

    x=x-np.max(x) # 溢出对策
    return np.exp(x)/np.sum(np.exp(x))
def cross_entropy_error(y,t):
    if y.ndim==1:
        t=t.reshape(1,t.size)
        y=y.reshape(1,y.size)
        
    # 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
    if t.size==y.size:
        t=t.argmax(axis=1)
             
    batch_size=y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size),t]+1e-7))/batch_size

class simpleNet:
    def __init__(self):
        self.W=np.random.randn(2,3) # 用高斯分布进行初始化 2*3的矩阵
    def predict(self,x):
        return np.dot(x,self.W)
    def loss(self,x,t):
        z=self.predict(x)
        y=softmax(z)
        loss=cross_entropy_error(y,t)
        return loss

net=simpleNet()
x=np.array([0.6,0.9])
t=np.array([0,0,1])

net=simpleNet()
print(net.W) # 权重参数
print(" ")
# [[ 0.14359452  0.09880721 -0.80202999]
#  [-0.44419552 -1.09349023  0.47002933]]
x=np.array([0.6,0.9])
p=net.predict(x) # [-0.31361926 -0.92485687 -0.0581916 ]
print(p)
print(" ")
print(np.argmax(p)) # 最大值的索引 2
t=np.array([0,0,1]) # 正确标签
print(net.loss(x,t)) # 0.7861527304954589

五、误差反向传播法

反向传播:高效计算权重参数的梯度

对神经网络模型而言,梯度下降法需要计算损失函数对参数的偏导数,如果用链式法则对每个参数逐一求偏导,这是一个非常艰巨的任务。
在这里插入图片描述
首先理解:局部计算、局部导数
在这里插入图片描述

链式法则

eg. z = ( x + y ) 2 z=(x+y)^2 z=(x+y)2

t = x + y t=x+y t=x+y,则 z = t 2 z=t^2 z=t2

∂ z ∂ x = ∂ z ∂ t ∂ t ∂ x \frac{\partial z}{\partial x}=\frac{\partial z}{\partial t}\frac{\partial t}{\partial x} xz=tzxt

∂ z ∂ t = 2 t , ∂ t ∂ x = 1 \frac{\partial z}{\partial t}=2t,\frac{\partial t}{\partial x}=1 tz=2txt=1

∂ z ∂ x = ∂ z ∂ t ∂ t ∂ x = 2 t ∗ 1 = 2 ( x + y ) \frac{\partial z}{\partial x}=\frac{\partial z}{\partial t}\frac{\partial t}{\partial x}=2t*1=2(x+y) xz=tzxt=2t1=2(x+y)

在这里插入图片描述

反向传播

在这里插入图片描述
关于加法 z = x + y z=x+y z=x+y

∂ z ∂ x = 1 , ∂ z ∂ y = 1 \frac{\partial z}{\partial x}=1,\frac{\partial z}{\partial y}=1 xz=1yz=1

关于乘法 z = x y z=xy z=xy

∂ z ∂ x = y , ∂ z ∂ y = x \frac{\partial z}{\partial x}=y,\frac{\partial z}{\partial y}=x xz=yyz=x

加法的反向传播只是将上游的值传给下游(因为乘1),并不需要正向传播的输入信号,但乘法的反向传播会乘以输入信号的翻转值,所以需要保存正向传播的输入信号

简单层的实现

层的实现中,有两个共通的方法forward()backward()

乘法层
class MulLayer:
    def __init__(self):
        self.x=None
        self.y=None

    def forward(self, x, y):
        self.x=x
        self.y=y                
        out=x*y
        return out

    def backward(self, dout):
        dx=dout*self.y
        dy=dout*self.x
        return dx,dy

在这里插入图片描述
注意:backward()调用顺序和forward()调用顺序相反

from layer_naive import *
apple=100
apple_num=2
tax=1.1
mul_apple_layer=MulLayer()
mul_tax_layer=MulLayer()
# forward
apple_price=mul_apple_layer.forward(apple,apple_num)
price=mul_tax_layer.forward(apple_price,tax)
# backward
dprice = 1
dapple_price,dtax=mul_tax_layer.backward(dprice)
dapple, dapple_num=mul_apple_layer.backward(dapple_price)

print("price:",int(price)) # price: 220
print("dApple:",dapple) # dApple: 2.2
print("dApple_num:",int(dapple_num)) # dApple_num: 110
print("dTax:",dtax) # dTax: 200
加法层
class AddLayer:
    def __init__(self):
        pass # 加法层不需要特意初始化,pass表示什么也不运行

    def forward(self,x,y):
        out=x+y
        return out

    def backward(self,dout):
        dx=dout*1
        dy=dout*1
        return dx,dy

在这里插入图片描述

# coding: utf-8
from layer_naive import *
apple = 100
apple_num = 2
orange = 150
orange_num = 3
tax = 1.1
# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()
# forward
apple_price = mul_apple_layer.forward(apple, apple_num)  # (1)
orange_price = mul_orange_layer.forward(orange, orange_num)  # (2)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)  # (3)
price = mul_tax_layer.forward(all_price, tax)  # (4)
# backward
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)  # (4)
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)  # (3)
dorange, dorange_num = mul_orange_layer.backward(dorange_price)  # (2)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)  # (1)
print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dOrange:", dorange)
print("dOrange_num:", int(dorange_num))
print("dTax:", dtax)

激活函数层的实现

ReLU层

y = { x , ( x > 0 ) 0 , ( x ≤ 0 ) y= \begin{cases} x, & (x>0) \\ 0,& (x≤0) \\ \end{cases} y={x,0,(x>0)(x0)
∂ y ∂ x = { 1 , ( x > 0 ) 0 , ( x ≤ 0 ) \frac{\partial y}{\partial x}= \begin{cases} 1, & (x>0) \\ 0,& (x≤0) \\ \end{cases} xy={1,0,(x>0)(x0)

在这里插入图片描述

class Relu:
    def __init__(self):
        self.mask=None

    def forward(self,x):
        self.mask=(x<=0)
        out=x.copy()
        out[self.mask]=0
        return out

    def backward(self,dout):
        dout[self.mask]=0
        dx=dout
        return dx

实例变量mask是由True/False构成的NumPy数组,它会把正向传播时的输入x的元素中≤0的地方保存为True,其他地方保存为False

Sigmoid层

y = 1 1 + e − x y=\frac{1}{1+e^{-x}} y=1+ex1

在这里插入图片描述
∂ L ∂ y y 2 e − x = ∂ L ∂ y 1 ( 1 + e − x ) 2 e − x   = ∂ L ∂ y 1 1 + e − x e − x 1 + e − x   = ∂ L ∂ y y ( 1 − y ) \frac{\partial L}{\partial y}y^2e^{-x}=\frac{\partial L}{\partial y}\frac{1}{(1+e^{-x})^2}e^{-x}\\\ \\=\frac{\partial L}{\partial y}\frac{1}{1+e^{-x}}\frac{e^{-x}}{1+e^{-x}}\\\ \\=\frac{\partial L}{\partial y}y(1-y) yLy2ex=yL(1+ex)21ex =yL1+ex11+exex =yLy(1y)

class Sigmoid:
    def __init__(self):
        self.out=None

    def forward(self, x):
        out=sigmoid(x)
        self.out=out
        return out

    def backward(self,dout):
        dx = dout*(1.0-self.out)*self.out
        return dx

Affine/Softmax层的实现

Affine层

正向传播中进行矩阵乘积运算的层,需要注意的是相乘矩阵维度的对应

几何中,放射变换包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加bias运算。

在这里插入图片描述
在这里插入图片描述
上图介绍的是Affine层的输入X是以单个数据为对象的。下面考虑N个数据一起进行正向传播的情况。

在这里插入图片描述

class Affine:
    def __init__(self,W,b):
        self.W=W
        self.b=b
        
        self.x=None
        self.original_x_shape=None
        # 权重和偏置参数的导数
        self.dW=None
        self.db=None

    def forward(self,x):
        # 对应张量
        self.original_x_shape=x.shape
        x=x.reshape(x.shape[0],-1)
        self.x=x
        out=np.dot(self.x,self.W)+self.b
        return out

    def backward(self,dout):
        dx=np.dot(dout,self.W.T)
        self.dW=np.dot(self.x.T,dout)
        self.db=np.sum(dout,axis=0)
        dx=dx.reshape(*self.original_x_shape)  # 还原输入数据的形状(对应张量)
        return dx

numpy方法中的axis参数理解

Softmax-with-Loss层

Softmax函数: y k = e a k ∑ i = 1 n e a i y_k=\frac{e^{a_k}}{\sum_{i=1}^n e^{a_i}} yk=i=1neaieak
交叉熵误差: E = − ∑ k t k l o g   y k E=-\sum_k t_klog\ y_k E=ktklog yk
神经网络中进行的处理有推理学习两个阶段。推理过程只需要给出一个答案,只对得分最大值感兴趣,所以不需要softmax层;而神经网络的学习阶段则需要softmax层。

在这里插入图片描述
假设要进行3类分类,从前面的层接收3个输入(得分),Softmax层将输入 ( a 1 , a 2 , a 3 ) (a_1,a_2,a_3) (a1,a2,a3)正规化,输出 ( y 1 , y 2 , y 3 ) (y_1,y_2,y_3) (y1,y2,y3)。Cross Entropy Error层接收Softmax层的输出 ( y 1 , y 2 , y 3 ) (y_1,y_2,y_3) (y1,y2,y3)和监督标签 ( t 1 , t 2 , t 3 ) (t_1,t_2,t_3) (t1,t2,t3),计算损失函数值L并输出。

在这里插入图片描述
注意反向传播的结果是Softmax层的输出和监督标签的差分: ( y 1 − t 1 , y 2 − t 2 , y 3 − t 3 ) (y_1-t_1,y_2-t_2,y_3-t_3) (y1t1,y2t2,y3t3),这是一个非常nice的结果。

  • 神经网络的反向传播会把这个差分表示的误差,传递给前面的层,这是神经网络学习中的重要性质。
  • 神经网络学习的目的就是,通过调整权重参数,使神经网络(Softmax)的输出接近监督标签。因此必须将神经网络的输出和监督标签的误差高效的传递给前面的层,而 ( y 1 − t 1 , y 2 − t 2 , y 3 − t 3 ) (y_1-t_1,y_2-t_2,y_3-t_3) (y1t1,y2t2,y3t3) 就非常直截了当。
  • 使用交叉熵误差作为Softmax函数的损失函数后,反向传播能得到 y k − t k y_k-t_k yktk 这样“漂亮”的结果,这并不是偶然的,而是为了得到这样的结果,特意设计交叉熵误差函数。回归问题中输出层使用恒等函数,损失函数使用平方和误差,也是出于同样的理由。

Softmax Loss求导

def softmax(x):
    if x.ndim==2:
        x=x.T
        x=x-np.max(x,axis=0)
        y=np.exp(x)/np.sum(np.exp(x),axis=0)
        return y.T 

    x=x-np.max(x) # 溢出对策
    return np.exp(x)/np.sum(np.exp(x))

def cross_entropy_error(y,t):
    if y.ndim==1:
        t=t.reshape(1,t.size)
        y=y.reshape(1,y.size)
        
    # 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
    if t.size==y.size:
        t=t.argmax(axis=1)
             
    batch_size=y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size),t]+ 1e-7)) / batch_size

class SoftmaxWithLoss:
    def __init__(self):
        self.loss=None
        self.y=None # softmax的输出
        self.t=None # 监督数据

    def forward(self,x,t):
        self.t=t
        self.y=softmax(x)
        self.loss=cross_entropy_error(self.y,self.t) 
        return self.loss

    def backward(self,dout=1):
        batch_size=self.t.shape[0]
        if self.t.size==self.y.size: # 监督数据是one-hot-vector的情况
            dx=(self.y-self.t)/batch_size
        else:
            dx=self.y.copy()
            dx[np.arange(batch_size),self.t]-=1
            dx=dx/batch_size # 传递给前面的层的是 单个数据的误差
        return dx
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值