Python自学笔记(八)

您所在的位置:网站首页 python中train函数 Python自学笔记(八)

Python自学笔记(八)

#Python自学笔记(八)| 来源: 网络整理| 查看: 265

每次学一点,学一点更新一点

Numpy1.生成numpy数组

一维数组:

import numpy as np x = np.array([1.0,2.0,3.0]) print(x) # [1. 2. 3.] print(type(x)) #

n维数组:

import numpy as np x = np.array([[1,2,3], [2,3,4]]) print(x) print(x.shape) # 查看矩阵的形状 print(x.dtype) # 查看矩阵的数据类型2.numpy数组的算术运算import numpy as np x = np.array([1.0,2.0,3.0]) y = np.array([3,4,5]) c = x + y # 对应元素相加,还可以相减相乘相除 d = c/2 print(d) # numpy数组还可以与标量进行运算

还可以进行矩阵间的运算:

import numpy as np x = np.array([[1,2,3],[2,3,4]]) y = np.array([[1,2,3],[2,3,4]]) print(x.shape) print(y.shape) z = x * y # 点乘 print(z)3.广播

形状不同的矩阵也可以运算,原因是使用了广播,比如上面的数组和标量相乘,原理是将标量广播成与运算数组同型的数组,再进行点乘运算,如果不是进行标量运算,也是同样采用广播:

import numpy as np x = np.array([[1,2,3],[2,3,4]]) y = np.array([1,2,3]) print(x.shape) print(y.shape) z = x * y # 点乘 print(z) # [[ 1 4 9],[ 2 6 12]]4.访问元素

使用索引的方式:

import numpy as np x = np.array([[1,2,3],[2,3,4]]) print(x[0]) print(x[1][2])

使用数组访问各个元素:

import numpy as np x = np.array([[1,2,3],[2,3,4]]) X = x.flatten() # 转化为一维数组,不是列表 print(X) # [1 2 3 2 3 4] print(type(X)) # X1 = X[np.array([0,1,2,3])] print(X1) # [1 2 3 2] '''在此基础上使用索引可以选出满足一定条件的值''' X2 = X[X>2] # 这还能在索引里放表达式。。。 print(X2) # [3 3 4]Matplotlib1.简单绘图import numpy as np import matplotlib.pyplot as plt '''生成数据''' x = np.arange(0,6,0.1) # 以0.1为单位,生成0到6的数据 y = np.sin(x) '''绘制图像''' plt.plot(x,y) plt.show()代码运行结果2.plt的简单功能import numpy as np import matplotlib.pyplot as plt '''生成数据''' x = np.arange(0,6,0.1) # 以0.1为单位,生成0到6的数据 y1 = np.sin(x) y2 = np.cos(x) '''绘制图像''' plt.plot(x,y1,c='red',label='sin') # 线的颜色为红色 plt.plot(x,y2,linestyle='--',label='cos') # 使用虚线绘制 plt.xlabel('x') plt.ylabel('y') plt.title('sin & cos') plt.legend() # 图标 plt.show()代码运行结果3.显示图像imread()和imshow()import numpy as np import matplotlib.pyplot as plt from matplotlib.image import imread img = imread('F:\\pythonProject\\PROJECT10_PDF&WORD\\R-C.jpg') plt.imshow(img) plt.show()代码运行结果感知机

输入多个信号,输出一个信号,多个输入信号进行某种运算,运算结果达到某一阈值时,输出的信号为1,反之为0。通常用以下这个数学表达式表示:

这就涉及到了简单的逻辑电路知识(与、与非、或等)。

1.感知机的实现

第一步:编写实现与、与非、或功能的函数

def And(x1,x2): '''定义实现与功能的函数''' w1,w2,theta = 0.5,0.5,0.7 tmp = x1 * w1 + x2 * w2 if tmp theta: return 1 def NAnd(x1,x2): '''定义实现与非功能的函数''' w1,w2,theta = 0.5,0.5,0.7 tmp = x1 * w1 + x2 * w2 if tmp theta: return 0 def Or(x1,x2): '''定义实现或功能的函数''' w1,w2,theta = 0.5,0.5,0.5 tmp = x1 * w1 + x2 * w2 if tmp < theta: return 0 elif tmp >= theta: return 1

第二步:添加权重和偏置,偏置就是把theta挪到等值左边:

然后使用numpy实现上述函数的重新定义:

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(x*w) + b if tmp 0: return 1 def NAnd(x1,x2): '''定义实现与非功能的函数''' x = np.array([x1,x2]) w = np.array([0.5,0.5]) # 配置权重 b = -0,7 # 配置偏置 tmp = np.sum(x*w) + b if tmp 0: return 0 def Or(x1,x2): '''定义实现或功能的函数''' x = np.array([x1,x2]) w = np.array([0.5,0.5]) # 配置权重 b = -0,7 # 配置偏置 tmp = np.sum(x*w) + b if tmp < 0: return 0 elif tmp >= 0: return 12.感知机的局限性

基于上述原理创建的感知机的代码不能实现异或门,感知机的局限就在于它只能表示用一条直线分割的空间,而异或门不行。

3.多层感知机

由于单个的感知机无法实现异或门功能,但是逻辑门电路可组合叠加,具体实现的原理如下:

异或门电路

下面使用代码按照以上思路实现一下:

def XOR(x1,x2): s1 = NAnd(x1,x2) s2 = Or(x1,x2) y = And(s1,s2) return y

简而言之,异或门就是单层感知机,叠加一层就是多层感知机,这个就是深度学习最基本的概念。

神经网络1.从感知机到神经网络

通过之前的学习,多层感知机的优点是理论上可以表达任意的函数,缺点是设定权重是由人工进行的,神经网络的一个重要性质就是可以自动挡从数据中学习到合适的权重参数。

神经网络的一个简单示意图为:

由于神经网络的输入层没有权重,因此神经网络层数等于总层数减一,不同的定义也会导致不同的神经网络层命名结果。

显然,可以将简单的感知机模型参照神经网络模式体现出来:

将感知机的函数表达式替换成功能相同的简化函数:

转化为:

转化后的简洁函数就是激活函数。作用再与决定如何激活输入信号的总和。

2.利用python实现激活函数

激活函数1——阶跃函数

import numpy as np import matplotlib.pyplot as plt def step_function(x): '''该函数用于实现输入任意的数组x,对每个值进行判断,大于0则输出为1,小于等于0则输出为0''' y = np.array(x > 0, dtype=np.int) # 具体的处理流程是先对输入的数组x进行遍历判断,然后计算出bool值,并把bool值转换为0或1 return y x = np.arange(-5, 5, 0.1) y = step_function(x) plt.plot(x, y) plt.ylim(-0.1, 1.1) # 指定y轴的范围 plt.show()函数运行结果

可以看出跃阶函数只能返回0或者1,输出会发生急剧性的变化,而下面的函数则不同。

激活函数2——sigmoid函数

def sigmoid(x): '''该函数用于实现sigmoid阶跃功能,他能对数组进行运算是因为数组具有广播功能''' return 1 / (1 + np.exp(-x)) '''绘制sigmoid函数图像''' x = np.arange(-5, 5, 0.1) y = sigmoid(x) plt.plot(x, y) plt.ylim(-0.1, 1.1) # 指定y轴的范围 plt.show()函数运行结果

相对于阶跃函数,该函数在神经学习里更加普遍,具有平滑的特性,可以输出0到1中的任何值,对于神经网络具有重要意义(权?)。

激活函数3——ReLU函数

除了sigmoid函数,ReLU函数也使用广泛。其函数表达式为:

def ReLU(x): '''该函数用于实现ReLU阶跃功能''' return np.maximum(0,x) '''绘制ReLU函数图像''' x = np.arange(-5, 5, 0.1) y = ReLU(x) plt.plot(x, y) plt.ylim(-0.1, 8) # 指定y轴的范围 plt.show()函数运行结果3.由多维数组的运算到神经网络的内积

在实现神经网络之前,还需要多维数组的运算,NumPy具有这个功能。

(1)生成矩阵

import numpy as np A = np.array([[1,2,3], [2,3,4], [3,4,5]]) print(A.shape) # (3, 3)

(2)矩阵的乘法

import numpy as np A = np.array([[1,0,0], [0,1,0], [0,0,1]]) B = np.array([[2,0,0], [0,2,0], [0,0,2]]) print(np.dot(A,B)) # 使用np.dot()函数实现矩阵的乘法,这个与*乘法不一样,后者是利用了数组的广播

(3)神经网络的内积

使用代码来表示上述过程:

import numpy as np x = np.array([1,2]) w = np.array([[1,3,5],[2,4,6]]) print(np.dot(x,w))4.三层神经网络的实现

(1)从输入层到第一层

import numpy as np from activation_function import * x = np.array([1, 0.5]) w1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) b1 = np.array([0.1, 0.2, 0.3]) a = np.dot(x, w1) + b1 OUTPUT_a = sigmoid(a) # 使用sigmoid激活函数 print(OUTPUT_a) # [0.57444252 0.66818777 0.75026011] print(OUTPUT_a.shape) # (3,)

(2)从第一层到第二层

'''从第一层到第二层''' w2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) b2 = np.array([0.1, 0.2]) b = np.dot(a, w2) + b2 OUTPUT_b = sigmoid(b) # print(OUTPUT_b) # [0.64565631 0.79084063] # print(OUTPUT_b.shape) # (2,)

(3)从第二层到输出层

'''从第二层到输出层''' w3 = np.array([[0.1, 0.3], [0.2, 0.4]]) b3 = np.array([0.1, 0.2]) c = np.dot(b, w3) + b3 OUTPUT_c = identity_function(c) # 此函数作为输出层的激活函数,为了与之前各层进行规范,是一个恒等函数 # print(OUTPUT_c) # [0.426 0.912] # print(OUTPUT_c.shape) # (2,)

(4)整理代码

import numpy as np from activation_function import * def init_3_layer_network(): '''该函数通过创建一个字典来用于初始化神经网络的权和偏置''' network = {} network['w1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) network['b1'] = np.array([0.1, 0.2, 0.3]) network['w2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) network['b2'] = np.array([0.1, 0.2]) network['w3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) network['b3'] = np.array([0.1, 0.2]) return network def forward(network, x): '''该函数用于进行信号的处理和转换''' w1, w2, w3 = network['w1'], network['w2'], network['w3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = np.dot(x,w1) + b1 z1 = sigmoid(a1) b1 = np.dot(z1, w2) + b2 z2 = sigmoid(b1) a3 = np.dot(z2,w3) + b3 y = identity_function(a3) return y network = init_3_layer_network() x = np.array([1, 0.5]) y = forward(network, x) print(y) # [0.31682708 0.69627909]5.输出层的设计

机器学习的问题大致可以分为回归类型和分类问题,前者是根据输入的数据输出预测的数据,后者是将数据进行分类。神经网络可以用于这两个方面,回归问题用恒等函数,分类问题用softmax函数。恒等函数如之前的代码,保持输入数据的原样输出。

(1)softmax函数

函数表达式为:

n表示输出层的函数个数,yk表示第k个神经元的输出。用代码实现之:

import numpy as np def softmax(a): '''该函数用于实现softmax函数分类功能''' exp_a = np.exp(a) sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y print(softmax(np.array([2,3,4]))) # [0.09003057 0.24472847 0.66524096]

由于softmax函数是使用指数进行运算,当a过大时,计算可能会溢出,这里在函数里添加防止溢出的对策(完全不会影响计算结果):

import numpy as np def softmax(a): '''该函数用于实现softmax函数分类功能''' c = np.max(a) exp_a = np.exp(a-c) # 防止溢出 sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y

(2)softmax函数的特征

特征一:输出的函数值均为0到1之间的值

特征二:输出的值总和为1,这是该函数的一个重要特征,可以解释为概率。比如:

softmax(np.array([2,3,4]))

表示分别计算三个数值的概率。

一般来说,神经网络只会把输出值最大的神经元对应的类别作为识别结果,并且使用这个函数,输出值最大的神经元的位置也不会变。因此在神经网络进行分类时,输出层的softmax函数可以省略(减少运算量)。

神经网络的学习

这里的学习是指从训练数据中获取最优权重的参数的过程,学习的目的是以损失函数为基准,找出使它的值达到最小的权重参数,为了找到尽可能小的损失函数的值,将会使用函数斜率的梯度法。

1.从数据中学习

人类的学习、机器学习和神经网络(深度学习)从概念上的区别:

(1)人类的学习:问题——人想到的算法——答案

(2)机器学习:问题——人想到的特征量——机器学习——答案

(3)神经网络(深度学习):问题——神经网络——答案

2.损失函数

神经网络使用某个指标寻找最优权重参数,这个指标就是损失函数,损失函数可以使用任意函数,但一般为均方误差和交叉熵误差等。

3.均方误差

yk表示神经网络的输出,tk表示监督数据(正确解的标签,索引为1,其他均为0(one-hot表示)),k表示数据的维数。

import numpy as np def mean_squared_error(y, t): '''该函数用于实现计算均方误差的功能,参数 y 和参数 k 均为np数组''' result = 0.5 * np.sum((y - t) ** 2) return result '''假设正确解为2(神经网络的输出最大值),出现的概率最高情况为为0.6''' t = [0, 0, 1, 0, 0] y = [0.1, 0.1, 0.6, 0.1, 0.1] np_t = np.array(t) np_y = np.array(y) print(mean_squared_error(np_y, np_t)) # 0.10000000000000003 '''如果4出现的概率最大(0.6)''' np_y = np.array([0.1, 0.1, 0.1, 0.1, 0.6]) print(mean_squared_error(np_y, np_t)) # 0.6000000000000001

显然第一个情况的值更小,即损失函数的值更小,即说明2为正确解。

4.交叉熵误差

自然对数的函数图像如图:

import matplotlib.pyplot as plt import numpy as np '''绘制对数函数''' x = np.arange(-5, 1.1, 0.1) y = np.log(x) plt.plot(x, y) plt.ylim(-2, 0.1) # 指定y轴的范围 plt.show()

可以看出,当标签为1时,输出为0,标签为0时,输出为负值,再添加个负号,输出的是就比0大了,因此交叉熵误差可以用代码表示为:

import numpy as np def cross_entropy_error(y,t): '''用于实现交叉熵误差的函数,比企鹅设定了保护值''' delta = 1e-7 result = -np.sum(t*np.log(y + delta)) # 这里设定一个保护值,意思就是避免出现标签为0的情况 return result '''假设正确解为2(神经网络的输出最大值),出现的概率最高情况为为0.6''' t = [0, 0, 1, 0, 0] y = [0.1, 0.1, 0.6, 0.1, 0.1] np_t = np.array(t) np_y = np.array(y) print(cross_entropy_error(np_y, np_t)) # 0.510825457099338 '''如果4出现的概率最大(0.6)''' np_y = np.array([0.1, 0.1, 0.1, 0.1, 0.6]) print(cross_entropy_error(np_y, np_t)) # 2.3025840929945465.mini-batch学习

前面的损失函数都是针对单个数据(单组数据)的损失函数,实际过程中会有N组的数据,以交叉熵无吃啥为例,此时的损失函数可以写为:

式中N表示数据(组)有N个,tnk表示第n个数据里的第k个元素的值的监督数据,ynk表示n个数据里的第k个元素的神经网络输出值,随后总和除以N用于规范化。

当遇到庞大的数据时,采用mini-batch学习的方式,基本思路是从很多组数据中按照某种方式(np.random.choice()函数)选择出一部分数据,对这部分数据进行学习。

根据《深度学习入门—基于Python的理论与实现》源代码:

首先是读取mnist数据集的代码:

# 读入数据 (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)#这里的one_hot_label=True参数设定可以得到one_hot表示(正确解的标签为1,其余为0) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

然后随机选择出mini-batch:

train_size = x_train.shape[0] batch_size = 100 batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask]

在此基础上改良对应单个数据的交叉熵误差:

import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np import matplotlib.pyplot as plt from mnist import load_mnist from two_layer_net import TwoLayerNet def cross_entropy_error(y, t): '''该函数用于实现处理单个数据或批量数据(数据作为batch集中输入)的交叉熵误差''' if y.ndim == 1: # 如果y(神经网络的输出)的维度为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

如果监督数据t单标签形式不是one-hot形式,交叉熵误差如下实现:

# coding: utf-8 import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np import matplotlib.pyplot as plt from mnist import load_mnist from two_layer_net import TwoLayerNet def cross_entropy_error(y, t): '''该函数用于实现处理单个数据或批量数据(数据作为batch集中输入)的交叉熵误差''' if y.ndim == 1: # 如果y(神经网络的输出)的维度为1,即为求单个数据的交叉熵误差,需要改变数据的形状 t = t.reshape(1, t.size) y = y.reshape(1, y.size) batch_size = y.shape[0] # 将y第1维度(第一行)数据的长度赋给batch_size return -np.sum(t*np.log(y[np.arange(batch_size), t] - 1e-7)) / batch_size # np.arange(batch_size)会生成一个0到batch_size的数组,t中的标签以非one-hot的形式存储,(y[np.arange(batch_size),t)用于生成各个数据的正确解标签对应的神经网络输出

我的理解:比如采取非one-hot模式有一个监督数据t的标签[1,2,3,4,5,6,7],np.arange(batch_size)生成了一个0到batch_size的数组,batch_size的值是将输入数据y转换成一个一维数组后的第一行的元素数(就是y矩阵的列数),如果y本身就是一维的,那这个batch_size就是y的个数,如果不是一维的,处理的过程就是先确定y的一个维度有多少元素(一行有多少列),然后确定batch_size的值。而y[np.arange(batch_size), t]的含义就是对照非one-hot模式有一个监督数据t的标签[1,2,3,4,5,6,7],取出np.arange(batch_size)生成了的0到batch_size的数组中相应的元素,也就是y[np.arange(batch_size), t]最后生成的还是一个数组。

6.为什么要设定损失函数?

神经网络的学习是指从训练数据中获取最优权重的参数的过程,学习的目的是以损失函数为基准,找出使它的值达到最小的权重参数,为了找到尽可能小的损失函数的值,将会使用函数斜率的梯度法(就是导数)。

我的理解:使用梯度法能比采用识别精度作为神经网络学习的指标更能反映神经网络更精确的变化,比如阶跃函数的导数大部分都为0,而sigmoid函数的导数永远不会为0,也就是说适用后者加导数的方法可以更精确的反映出神经网络里参数和权重的变化(因为刚刚说的两个函数都是输出层的激活函数,输出层又作为下一个输入层)。

7.求导—梯度

(1)求导函数:

import numpy as np import matplotlib.pylab as plt def numerical_diff(f, x): h = 1e-4 # 0.0001 return (f(x+h) - f(x-h)) / (2*h)

之所以采用2h作为分母,是为了更好的展现x处的导数值。

(2)求梯度(是一个向量):

def _numerical_gradient_no_batch(f, x): h = 1e-4 # 0.0001 grad = np.zeros_like(x) # 创建一个和x数组一样的0数组 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

关于fxh1 = f(x) # f(x+h)我的理解是,f是输入的函数,需要在其他地方定义,作为_numerical_gradient_no_batch()的参数,而x这是f需要的参数。这里的x不是一个数,而是一个数组,里面可以有x0,x1等很多参数,这样才有求偏导的意义。

上面的函数画个图看一下:

# 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()代码运行结果8.梯度法

再一次理解这段话:

神经网络的学习是指从训练数据中获取最优权重的参数的过程,学习的目的是以损失函数为基准,找出使它的值达到最小的权重参数,为了找到尽可能小的损失函数的值,将会使用函数斜率的梯度法(就是导数)。

寻找尽可能小的损失函数的值实质上就是寻找损失函数的最小值,使用梯度可以出函数梯度为0的点,也就是极小值,但是高等数学里,极小值不等于最小值,因此需要采用一个新的思路:输入一个初始位置,求解一个梯度较小的值对应的位置,求出来的位置作为下一个初始位置,循环往复,用公式表达为:

u称为学习率,表示以多大程度更新参数。

用代码实现一下:

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

其中lr表示学习率,step_num表示学习的次数,这些参数的设置有讲究,过小过大都不好。

9.神经网络的梯度

话不多说,直接上代码:

# coding: utf-8 import sys, os sys.path.append(os.pardir) # 为了导入父目录中的文件而进行的设定 import numpy as np from functions import softmax, cross_entropy_error from gradient_2d import numerical_gradient class simpleNet: '''这个类主要是实现了2行3列权矩阵求梯度的功能''' def __init__(self): '''初始化权矩阵,由任意数生成''' self.W = np.random.randn(2,3) def predict(self, x): '''该函数是将输入的x与权矩阵相乘,不考虑偏置的情况下输出结果''' return np.dot(x, self.W) def loss(self, x, t): '''该函数用于将输出的结果先分类,然后求出交叉熵误差,要求输入x和监督数据''' z = self.predict(x) y = softmax(z) loss = cross_entropy_error(y, t) return loss x = np.array([0.6, 0.9]) t = np.array([0, 0, 1]) net = simpleNet() f = lambda w: net.loss(x, t) # 定义了一个简单函数,使用生成的net实例的loss方法,得到损失函数 dW = numerical_gradient(f, net.W) # 求梯度 print(dW) # [[ 0.1758998 0.20949144 -0.38539124] # [ 0.2638497 0.31423715 -0.57808685]]

可以看到计算结果dw就是损失函数对响应的权的梯度,0.1758998表示权每变化h,相应的损失函数值就变化0.1758998h。

10.总结神经网络的学习

神经网络存在合适的权和偏置,调整权和偏置以拟合训练数据的过程称为‘学习’。

(2)mini-batch

从训练数据中选择一部分(应该是随机的),这部分称为mini-batch,目标就是减小mini-batch的损失函数的值。

(3)计算梯度

为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度,梯度表示损失函数的值减小最多的方向。

(4)更新参数

将权重参数沿着梯度方向进行微小的更新。

(5)重复

重复2-4步。

由于是使用随机的方法选择出mini-batch,这个方法就又叫做随机梯度下降法,简称SGD(stochastic gradient decent)。

误差反向传播法

误差反向传播法用于高效率计算权重的参数的梯度(求导)。

1.激活函数层的实现

(1)ReLU函数

ReLU函数表达式为:

ReLU函数求导以后的表达式为:

其含义是:如果正向传播的输入x大于0,那么输出值等于输入值,反之,输出为0;反向传播中,如果输入值大于0,那么输出值大于0且等于输入值,如果输出值等于0,输入值小于等于0(此时传给下游的信号就为0)。

class Relu: def __init__(self): self.mask = None # 传入的为np数组 def forward(self, x): self.mask = (x

正向传播的流程为:

反向传播的流程为:

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 dx2.输入以单个数据为对象的Affine层

神经网络的正向传播中进行矩阵的乘积运算在几何学领域被称为仿射变换,因此将仿射变换的处理实现为Affine层,包含一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算。

这里用到一点矩阵的求导和两式求导法则,这位大佬讲的比较细,摘转一下,原文地址是:(矩阵求导及其链式法则 - 阿里郎其的文章 - 知乎 https://zhuanlan.zhihu.com/p/241028378)

输入以单个数据为对象的Affine层的计算图为:

3.输入以多个数据为对象的Affine层

原理一样,只是改变了矩阵的维数。要注意的是,正向传播时,偏置会加到每一个数据上,反向传播时各个数据的反向传播值需要汇总成为偏置的元素。

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 dx4.softmax-with-loss层

公式推导参考这位大佬的文章(深度学习基础:softmax with loss 推导 - 知乎 (zhihu.com)

这种层实现了将神经网络的输出与监督标签的差分,并将这个差分反向传播给神经网络,神经网络根据这个差分调整权重参数。

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] # 有几个监督标签就肯定有几个softmax输出,相应将差分反向传播给正向输入的n各单个数据 if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况 dx = (self.y - self.t) / batch_size # 有几个监督标签就肯定有几个softmax输出,相应将差分反向传播给正向输入的n各单个数据 else: dx = self.y.copy() dx[np.arange(batch_size), self.t] -= 1 dx = dx / batch_size return dx5.反向误差传播法的实现

神经网络的学习步骤为:

(1)前提

神经网络存在合适的权和偏置,调整权和偏置以拟合训练数据的过程称为‘学习’。

(2)mini-batch

从训练数据中选择一部分(应该是随机的),这部分称为mini-batch,目标就是减小mini-batch的损失函数的值。

(3)计算梯度

为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度,梯度表示损失函数的值减小最多的方向。

(4)更新参数

将权重参数沿着梯度方向进行微小的更新。

(5)重复

重复2-4步。

反向误差传播法真是用在第三步,比较数值微分,可以更高效的求出梯度。

与学习有关的技巧1.SGD的缺点

神经网络的学习的目的就是找到使损失函数的值尽可能小的参数,前面学习了随机梯度下降法SGD。用公式表达为:

u称为学习率,一般设置为0.01或0.001等这样是这样事先设定好的值。那么SGD的过程可以表示为:

W表示权重的参数,L表示损失函数。

可以定义一个类表示整个功能:

class SGD: """随机梯度下降法(Stochastic Gradient Descent)""" def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for key in params.keys(): params[key] -= self.lr * grads[key]

在之前的例子中,使用以下代码验证了可以通过SGD找到损失函数的最小值,代码如下:

# 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()

这里使用的函数表达式为:

可以明显的看出图中红点位置(0,0)处为该函数的最小值。如果函数表达式为:

显然,函数的最小是还是在(0,0)处,但是但是用如下代码寻找时会发现很难确定:

# 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([(1 / 1000) * (x ** 2),x ** 2]) else: return np.sum([(1 / 1000) * (x ** 2),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(-10, 10, 0.25) x1 = np.arange(-10, 10, 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([-10, 10]) plt.ylim([-5, 5]) plt.xlabel('x0') plt.ylabel('x1') plt.grid() plt.legend() plt.draw() plt.show()

从图中可以看出,如果不画出坐标轴的刻度并且不知道函数的表达式(或者十分复杂),将难以判断何处是损失函数最小值的地方。SGD低效的根本原因是,梯度的方向并没有指向函数最小值的地方,而是指向函数值减少最快的地方。

2.momentum

Momentum的过程可以表达为:

这里出现了一个新的变量v,对应物理上的速度,第一个是指标示了物体在梯度上的受力,alpha表示在物体不收任何力的情况下,速度逐渐减少,alpha的值一般设定为0.9之类的值,它的代码为:

class Momentum: """Momentum SGD""" def __init__(self, lr=0.01, momentum=0.9): # 设定alpha的值 self.lr = lr self.momentum = momentum self.v = None def update(self, params, grads): '''该函数用于生成一个和参数字典一样形式的初始字典,用于后续保存数据''' if self.v is None: self.v = {} for key, val in params.items(): self.v[key] = np.zeros_like(val) for key in params.keys(): self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] params[key] += self.v[key]

我的理解,使用一个抛物线函数来解释:

假设有一个函数为:

分别采用SGD和momentum来计算w,可以列出以下表格:

这样更直观的看出Momentum(alpha=0.9)更新后的权重参数W相比较L更快趋于0。

3.AdaGrad

该方法的思路是更新学习率,准确来说是学习率衰减,因为过小的学习率会导致学习的时间很长过大的学习率会导致学习发散而不能正确进行,比如上面我列出的表格,当在状态4时,采用u为0.1的学习率时,momentum计算出来的权重参数就已经发散了。AdaGrad则是学习率u让随着学习的进行,逐渐变小(一开始‘多学’,后来‘少学’;一开始‘粗略的学’,后来‘精细的深入的学’)。过程表达式为:

通过上式可以看到,h变量保存了所有梯度值的平方和,在第二个式中更新了学习率。实现的代码如下:

class AdaGrad: """AdaGrad""" def __init__(self, lr=0.01): self.lr = lr self.h = None def update(self, params, grads): if self.h is None: self.h = {} for key, val in params.items(): self.h[key] = np.zeros_like(val) for key in params.keys(): self.h[key] += grads[key] * grads[key] params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7) # 这里设置 1e-7防止出现零除错误4.Adam

Adam方法就是结合了上述两种方法,实现了更快速的参数搜索,参考文献:

Diederik Kingma and Jimmy Ba.(2014):Adam:A method for Stochastic Optimization. arXiv:1412.6908[cs](December 2014)

过程表达式和代码为:

class Adam: """Adam (http://arxiv.org/abs/1412.6980v8)""" def __init__(self, lr=0.001, beta1=0.9, beta2=0.999): self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.iter = 0 self.m = None self.v = None def update(self, params, grads): if self.m is None: self.m, self.v = {}, {} for key, val in params.items(): self.m[key] = np.zeros_like(val) self.v[key] = np.zeros_like(val) self.iter += 1 lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter) # 更新学习率 for key in params.keys(): #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key] #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2) self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key]) self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key]) params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7) #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)5.对比四种方法# coding: utf-8 import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np import matplotlib.pyplot as plt from collections import OrderedDict from optimizer import * def f(x, y): return x**2 / 20.0 + y**2 def df(x, y): return x / 10.0, 2.0*y init_pos = (-7.0, 2.0) params = {} params['x'], params['y'] = init_pos[0], init_pos[1] grads = {} grads['x'], grads['y'] = 0, 0 optimizers = OrderedDict() optimizers["SGD"] = SGD(lr=0.95) optimizers["Momentum"] = Momentum(lr=0.1) optimizers["AdaGrad"] = AdaGrad(lr=1.5) optimizers["Adam"] = Adam(lr=0.3) idx = 1 for key in optimizers: optimizer = optimizers[key] x_history = [] y_history = [] params['x'], params['y'] = init_pos[0], init_pos[1] for i in range(30): x_history.append(params['x']) y_history.append(params['y']) grads['x'], grads['y'] = df(params['x'], params['y']) optimizer.update(params, grads) x = np.arange(-10, 10, 0.01) y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(x, y) Z = f(X, Y) # for simple contour line mask = Z > 7 Z[mask] = 0 # plot plt.subplot(2, 2, idx) idx += 1 plt.plot(x_history, y_history, color="red") plt.contour(X, Y, Z) plt.ylim(-10, 10) plt.xlim(-10, 10) plt.plot(0, 0, '+') #colorbar() #spring() plt.title(key) plt.xlabel("x") plt.ylabel("y") plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=0.5) plt.show()权重的初始值1.可以将权重的初始值设为0吗?

不行,严格地说不能设成相同的数,举下面这个例子:

首先是正向传播:

好像没什么问题,再来看反向传播:

可以看到每层反向输出的权重值都是一样的(4和6),这样的话神经网络的每一层的权重就更新为了相同的权重,每一层存在的意义也就消失了。因此必须使用随机生成的初始值。

2.从神经网络的隐藏层激活函数值来看

首先是使用标准差为0.1的高斯分布作为初始权重值,生成一个五层的神经网络,各层的激活函数使用sigmoid:

# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000个数据 node_num = 100 # 各隐藏层的节点(神经元)数 hidden_layer_size = 5 # 隐藏层有5层 activations = {} # 激活值的结果保存在这里 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 改变初始值进行实验! w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 将激活函数的种类也改变,来进行实验! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 绘制直方图 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()

可以看到各层的激活函数值基本处于0-1分布,那么在反向传播中梯度值会不断减小,造成梯度消失。(这一块我还不是特别理解)

如果使用标准差为0.01的高斯分布:

# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000个数据 node_num = 100 # 各隐藏层的节点(神经元)数 hidden_layer_size = 5 # 隐藏层有5层 activations = {} # 激活值的结果保存在这里 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 改变初始值进行实验! # w = np.random.randn(node_num, node_num) * 1 w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 将激活函数的种类也改变,来进行实验! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 绘制直方图 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()

那么此时各层激活函数的输出几乎都为相同值,反向传播时就会出现之前神经网络的每一层的权重就更新为了相同的权重的现象,每一层存在的意义也就消失了。

3.在大多数深度学习的框架中使用Xavier权重初始值

结论:如果某层的一个神经元的前一层的节点数为n,那么该神经元的权重初始值使用标准差为1/sqrt(n)的分布。也就是说,前一层的节点越多,那么这个神经元的权重就约低。

# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000个数据 node_num = 100 # 各隐藏层的节点(神经元)数 hidden_layer_size = 5 # 隐藏层有5层 activations = {} # 激活值的结果保存在这里 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 改变初始值进行实验! # w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 将激活函数的种类也改变,来进行实验! z = sigmoid(a) # z = ReLU(a) # z = tanh(a) activations[i] = z # 绘制直方图 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) # plt.xlim(0.1, 1) # plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()

但是可以看到,随着层数的增多,也会出现各层的权一样的情况。

4.使用ReLU激活函数的权重

使用标准差为sqrt(2/n),直观上可以解释为ReLU函数只有在大于0时有输出,因此使用2倍的系数让它更有广度:

# coding: utf-8 import numpy as np import matplotlib.pyplot as plt def sigmoid(x): return 1 / (1 + np.exp(-x)) def ReLU(x): return np.maximum(0, x) def tanh(x): return np.tanh(x) input_data = np.random.randn(1000, 100) # 1000个数据 node_num = 100 # 各隐藏层的节点(神经元)数 hidden_layer_size = 5 # 隐藏层有5层 activations = {} # 激活值的结果保存在这里 x = input_data for i in range(hidden_layer_size): if i != 0: x = activations[i-1] # 改变初始值进行实验! # w = np.random.randn(node_num, node_num) * 1 # w = np.random.randn(node_num, node_num) * 0.01 # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) a = np.dot(x, w) # 将激活函数的种类也改变,来进行实验! # z = sigmoid(a) z = ReLU(a) # z = tanh(a) activations[i] = z # 绘制直方图 for i, a in activations.items(): plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") if i != 0: plt.yticks([], []) plt.xlim(0.1, 1) plt.ylim(0, 7000) plt.hist(a.flatten(), 30, range=(0,1)) plt.show()5.Batch Normalization

核心思路是强制性的调整激活值的分布,原理为:

以进行学习时的mini-batch为单位,按照mini-batch进行正规化,具体而言就是使用数据分布均值为0,标准差为1 的正规化:

式中m表示输入mini-batch中所含的数据的个数,上式就是实现了将原有的mini-batch转化为一组新的mini-batch,其均值为0,方差为1。

通常,BN层会对正规化后的mini-batch进行线性变换:

初始时,a=1,b=0,通过学习后调整为其他合适的值。在神经网络中BN层位于如下位置:

BN层具有以下优点:

1.可以是学习更快的进行

2.不那么以来初始值

3.抑制过拟合

正则化1.过拟合

过拟合是指神经网络只能拟合训练数据,不能很好的拟合不包含在训练数据中其他数据的状态。发生过拟合的原因是模型拥有大量参数,且训练数据少。

2.权值衰减

这是一种常用来抑制过拟合的方式,该方法对学习过程中大的权重进行惩罚。例如为损失函数加上权重的平方范数(L2):

式中的lambda是控制正则化强度的超参数。

3.Dropout

如果网络的模型十分复杂,只用权值衰减就会难以应付,此时选用Dropout方法。即在学习过程中随机删除神经元的方法。

4.超参数

超参数是指处理权重和偏置等参数外,各层的神经元数量、batch大小、学习率、权值衰减等参数。超参数的优化可以归结为:先设定超参数的范围,从该范围中采样进行学习,验证该组超参数下的学习识别精度,然后选取其他采样重复同样的步骤,根据精度的大小寻找最优的超参数。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3