Python解释器也被称为“对话模式”,用户能够以和Python对话的方式进行编程。
Python是属于“动态类型语言”的编程语言,所谓动态,是指变量的类型是根据情况自动决定的。
在深度学习的实现中,经常出现数组和矩阵的计算。NumPy的数组类(numpy.array)中提供了很多便捷的方法,在实现深度学习时,我们将使用这些方法
NumPy 数组(np.array)可以生成N维数组,即可以生成一维数组、二维数组、三维数组等任意维数的数组。数学上将一维数组称为向量,将二维数组称为矩阵。另外,可以将一般化之后的向量或矩阵等统称为张量(tensor)。
使用步骤:
import numpy as np
Python 中使用import 语句来导入库。这里的import numpy as np,直译的话就是“将numpy作为np 导入”的意思。通过写成这样的形式,之后NumPy相关的方法均可通过np来调用。
要生成NumPy数组,需要使用np.array()方法。np.array()接收Python列表作为参数,生成NumPy数组(numpy.ndarray)。
>>> x = np.array([1.0, 2.0, 3.0])
>>> print(x)
[ 1. 2. 3.]
>>> type(x)
>>> x = np.array([1.0, 2.0, 3.0])
>>> y = np.array([2.0, 4.0, 6.0])
>>> x + y # 对应元素的加法
array([ 3., 6., 9.])
>>> x - y
array([ -1., -2., -3.])
>>> x * y # element-wise product
array([ 2., 8., 18.])
>>> x / y
array([ 0.5, 0.5, 0.5])
这里需要注意的是,数组x和数组y的元素个数是相同的(两者均是元素个数为3 的一维数组)。当x和y的元素个数相同时,可以对各个元素进行算术运算。如果元素个数不同,程序就会报错,所以元素个数保持一致非常重要。
>>> x = np.array([1.0, 2.0, 3.0])
>>> x / 2.0
array([ 0.5, 1. , 1.5])
此时,需要在NumPy数组的各个元素和标量之间进行运算。这个功能被称为广播.
>>> A = np.array([[1, 2], [3, 4]])
>>> print(A)
[[1 2]
[3 4]]
>>> A.shape
(2, 2)
>>> A.dtype
dtype('int64')
这里生成了一个2 × 2 的矩阵A。另外,矩阵A的形状可以通过shape查看,矩阵元素的数据类型可以通过dtype查看
>>> B = np.array([[3, 0],[0, 6]])
>>> A + B
array([[ 4, 2],
[ 3, 10]])
>>> A * B
array([[ 3, 0],
[ 0, 24]])
和数组的算术运算一样,矩阵的算术运算也可以在相同形状的矩阵间以对应元素的方式进行。并且,也可以通过标量(单一数值)对矩阵进行算术运算。这也是基于广播的功能。
>>> print(A)
[[1 2]
[3 4]]
>>> A * 10
array([[ 10, 20],
[ 30, 40]])
NumPy中,形状不同的数组之间也可以进行*运算。
>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([10, 20])
>>> A * B
array([[ 10, 40],
[ 30, 80]])
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjSqtNo3-1654612519093)(深度学习入门.assets/image-20220519225741290.png)]
>>> A = np.array([[1,2],[3,4]])
>>> B = np.array([10,20,30])
>>> A * B
Traceback (most recent call last):
File "", line 1, in
ValueError: operands could not be broadcast together with shapes (2,2) (3,)
>>> B = np.array([[10],[20]])
>>> A * B
array([[10, 20],
[60, 80]])
由此可见广播必须得满足扩展成大数组的特性
可以使用索引的方式访问
>>> X = np.array([[51, 55], [14, 19], [0, 4]])
>>> print(X)
[[51 55]
[14 19]
[ 0 4]]
>>> X[0] # 第0行
array([51, 55])
>>> X[0][1] # (0,1)的元素
55
也可以使用for语句访问各个元素。
>>> for row in X:
... print(row)
...
[51 55]
[14 19]
[0 4]
NumPy还可以使用数组访问各个元素。
>>> X = X.flatten() # 将X转换为一维数组
>>> print(X)
[51 55 14 19 0 4]
>>> X[np.array([0, 2, 4])] # 获取索引为0、2、4的元素
array([51, 14, 0])
>>> X[[0,2]]
array([51, 14])
运用这个标记法,可以获取满足一定条件的元素。例如,要从X中抽出大于15 的元素,可以写成如下形式。只能有一个判断元素?
>>> X > 15
array([ True, True, False, True, False, False], dtype=bool)
>>> X[X>15]
array([51, 55, 19])
>>> X[X>15 and X < 20]
Traceback (most recent call last):
File "", line 1, in
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
在深度学习的实验中,图形的绘制和数据的可视化非常重要。Matplotlib是用于绘制图形的库,使用Matplotlib 可以轻松地绘制图形和实现数据的可视化。这里,我们来介绍一下图形的绘制方法和图像的显示方法。
绘制简单图形
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()
这里使用NumPy的arange方法生成了[0, 0.1, 0.2, … , 5.8, 5.9]的数据,将其设为x。对x的各个元素,应用NumPy的sin 函数np.sin(),将x、y的数据传给plt.plot方法,然后绘制图形。最后,通过plt.show()显示图形。
pyplot的功能
在刚才的sin 函数的图形中,我们尝试追加cos 函数的图形,并尝试使用pyplot的添加标题和x轴标签名等其他功能。
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, label="sin") # label添加标题
plt.plot(x, y2, linestyle = "--", label="cos") # linestyle用虚线绘制
plt.xlabel("x") # x轴标签
plt.ylabel("y") # y轴标签
plt.title('sin & cos') # 标题
plt.legend()
plt.show()
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Ellipse
fig, ax = plt.subplots(1, 2, subplot_kw={"aspect": "equal"})
angles = np.linspace(0, 180, 8)
ellipse = [Ellipse((3, 3), 4, 1, a) for a in angles]
for elle in ellipse:
ax[0].add_patch(elle)
elle.set_alpha(0.4)
elle.set_color("#FF5511")
ax[0].axis([0, 6, 0, 6])
num = np.arange(0, 100, 1)
ellipse = [Ellipse(xy=np.random.rand(2)*10+1, width=np.random.rand(1), height=np.random.rand(1),
angle=np.random.rand(1)*360) for i in num]
for elle in ellipse:
ax[1].add_patch(elle)
elle.set_alpha(np.random.rand(1))
elle.set_color(np.random.rand(3))
ax[1].axis([0, 12, 0, 12])
plt.tight_layout()
plt.show()
显示图像
pyplot 中还提供了用于显示图像的方法imshow()。另外,可以使用matplotlib.image模块的imread()方法读入图像。下面我们来看一个例子。
import matplotlib.pyplot as plt
from matplotlib.image import imread
img = imread('fate.png') # 读入图像(设定合适的路径!)
plt.imshow(img)
plt.show()
感知机就是一个多输入,单输出的一个算法,x是节点的值只能为0或1,每个输入过程都有一个权值(w是weight 的首字母),图中的○称为**“神经元”或者“节点”。输入信号被送往神经元时,会被分别乘以固定的权重(w1x1、w2x2)神经元会计算传送过来的信号的总和,当这个总和超过了某个界限值时会输出1(1 对应“传递信号”)否则输出0(0对应“不传递信号”)这也称为“神经元被激活” 。这里将这个界限值称为阈值**,用符号θ 表示。
感知机的多个输入信号都有各自固有的权重,这些权重发挥着控制各个信号的重要性的作用。也就是说,权重越大,对应该权重的信号的重要性就越高。
(w1, w2, θ)=(0.5, 0.5, 0.7)
w1 <= θ && w2 <= θ && θ > 0
(w1, w2, θ)= (−0.5, −0.5, −0.7)
只要把实现与门的参数值的符号取反,就可以实现与非门。
(w1, w2, θ)=(0.5,0.5,0.3)
w1 > θ && w2 > θ && θ > 0
这里决定感知机参数的并不是计算机,而是我们人。我们看着真值表这种“训练数据”,人工考虑(想到)了参数的值。而机器学习的课题就是将这个决定参数值的工作交由计算机自动进行。学习是确定合适的参数的过程,而人要做的是思考感知机的构造(模型),并把训练数据交给计算机。
也就是说,相同构造的感知机,只需通过适当地调整参数的值,就可以像“变色龙演员”表演不同的角色一样,变身为与门、与非门、或门。
简单实现
def AND(x1, x2):
w1 = 0.5
w2 = 0.5
theta = 0.7
if w1 * x1 + w2 * x2 > theta:
return 1
else:
return 0
print(AND(0, 0))
print(AND(0, 1))
print(AND(1, 0))
print(AND(1, 1))
def NOT(x):
return -x + 1
def NAND(x1, x2):
return NOT(AND(x1, x2))
def OR(x1, x2):
w1 = 0.5
w2 = 0.5
theta = 0.4
if w1 * x1 + w2 * x2 > theta:
return 1
else:
return 0
导入权重和偏置
刚才的与门的实现比较直接、容易理解,但是考虑到以后的事情,我们将其修改为另外一种实现形式。在此之前,首先把式子的θ 换成−b,于是就可以用下式来表示感知机的行为。
两式虽然有一个符号不同,但表达的内容是完全相同的。此处,b 称为偏置,w1 和w2 称为权重。如上式所示,感知机会计算输入信号和权重的乘积,然后加上偏置,如果这个值大于0 则输出1,否则输出0。
>>> import numpy as np
>>> x = np.array([0, 1]) # 输入
>>> w = np.array([0.5, 0.5]) # 权重
>>> b = -0.7 # 偏置
>>> w*x
array([ 0. , 0.5])
>>> np.sum(w*x)
0.5
>>> np.sum(w*x) + b
-0.19999999999999996 # 大约为-0.2(由浮点小数造成的运算误差)
使用权重和偏置的实现
def AND(x1, x2):
w = np.array([0.5, 0.5])
x = np.array([x1, x2])
theta = 0.7
b = -theta
flag = np.sum(w * x) + b
if flag > 0:
return 1
else:
return 0
def AND(x1, x2):
w1 = 0.5
w2 = 0.5
b = -0.7
flag = w1 * x1 + w2 * x2 + b
if flag > 0:
return 1
else:
return 0
print(AND(0, 0))
print(AND(0, 1))
print(AND(1, 0))
print(AND(1, 1))
def NOT(x):
return -x + 1
def NAND(x1, x2):
return NOT(AND(x1, x2))
def OR(x1, x2):
w = np.array([0.5, 0.5])
x = np.array([x1, x2])
theta = 0.3
b = -theta
flag = np.sum(w * x) + b
if flag > 0:
return 1
else:
return 0
与门、与非门、或门是具有相同构造的感知机,区别只在于权重参数的值。因此,在与非门和或门的实现中,仅设置权重和偏置的值这一点和与门的实现不同。
异或门也被称为逻辑异或电路。感知机是无法实现这个异或门。
判断能否通过单层感知机直接实现的方式是判断感知机表达式是否是线性的
例如
这样的一个或门(b, w1, w2) = (−0.5, 1.0, 1.0)
最终的图像会被直线分割成上下两部分,上为1下为0
而异或的图像则是
这显然是一个非线性的图像故不能由单层感知机直接实现,但可以由多层感知机间接实现
叠加了多层的感知机称为多层感知机。
门电路的基本组成是与或非,任何的门电路均可有有这三项组合完成
代码实现
def XOR(x1, x2):
flag = OR(AND(NOT(x1), x2), AND(x1, NOT(x2)))
if flag > 0:
return 1
else:
return 0
设定权重的工作,即确定合适的、能符合预期的输入与输出的权重,现在还是由人工进行的。神经网络的出现就是为了解决这个问题。
我们把最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层。中间层有时也称为隐藏层。“隐藏”一词的意思是,隐藏层的神经元(和输入层、输出层不同)肉眼看不见。另外,可以把输入层到输出层依次称为第0 层、第1 层、第2 层。
复习感知机
感知机表达式
可以用函数h(x)表示为y = h(b + w1x1 + w2x2)
刚才登场的h(x)函数会将输入信号的总和转换为输出信号,这种函数一般称为激活函数(activation function)。
激活函数以阈值为界,一旦输入超过阈值,就切换输出。这样的函数称为“阶跃函数”。因此,可以说感知机中使用了阶跃函数作为激活函数。
实际上,如果将激活函数从阶跃函数换成其他函数,就可以进入神经网络的世界了
sigmoid 函数
神经网络中经常使用的一个激活函数就是式(3.6)表示的sigmoid 函数(sigmoid function)。
式中的exp(−x)表示e^−x 的意思。e是纳皮尔常数2.7182 . . .
阶跃函数的实现
def step_function(x):
if x > 0:
return 1
else:
return 0
为了能够让跃迁函数支持数组输入输出故使用NumPy库
def step_function(x):
y = x > 0
return y.astype(np.int)
阶跃函数的图形
显示图形需要使用matplotlib库。
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()
如图所示,阶跃函数以0 为界,输出从0 切换为1(或者从1 切换为0)。它的值呈阶梯式变化,所以称为阶跃函数。
sigmoid 函数的实现
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴的范围
plt.show()
sigmoid 函数和阶跃函数的比较
非线性函数
神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。为什么不能使用线性函数呢?因为使用线性函数的话,加深神经网络的层数就没有意义了。
线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”
说白了就是加深层数的线性函数可以由另一个单层的线性函数表示
例如:
h(x) = cx
y(x) = h(h(h(x)))=c * c * c * x = a * x (a = c^3)
ReLU函数
ReLU函数在输入大于0 时,直接输出该值;在输入小于等于0 时,输出0
def relu(x):
return np.maximum(0, x)
>>> import numpy as np
>>> A = np.array([1, 2, 3, 4])
>>> print(A)
[1 2 3 4]
>>> np.ndim(A)
1
>>> A.shape
(4,)
>>> A.shape[0]
4
数组的维数可以通过np.dim()函数获得
数组的形状可以通过实例变量shape获得
A.shape的结果是个元组(tuple)
矩阵乘法
>>> A = np.array([[1,2], [3,4]])
>>> A.shape
(2, 2)
>>> B = np.array([[5,6], [7,8]])
>>> B.shape
(2, 2)
>>> np.dot(A, B)
array([[19, 22],
[43, 50]])
矩阵乘法使用dot函数
神经网络的内积
>>> X = np.array([1, 2])
>>> X.shape
(2,)
>>> W = np.array([[1, 3, 5], [2, 4, 6]])
>>> print(W)
[[1 3 5]
[2 4 6]]
>>> W.shape
(2, 3)
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]
X = np.array([1.0, 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])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)
print(A1) # [0.3, 0.7, 1.1]
print(Z1) # [0.57444252, 0.66818777, 0.75026011]
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) # 或者Y = A3
输出层所用的激活函数,要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用sigmoid 函数,多元分类问题可以使用softmax 函数。
代码实现小结
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def identity_function(x):
return x
def init_network():
network = {}
network['x'] = np.array([1, 2])
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 forword():
network = init_network()
network['a1'] = np.dot(network['x'], network['W1']) + network['b1']
network['z1'] = sigmoid(network['a1'])
network['a2'] = np.dot(network['z1'], network['W2']) + network['b2']
network['z2'] = sigmoid(network['a2'])
network['a3'] = np.dot(network['z2'], network['W3']) + network['b3']
network['y'] = identity_function(network['a3'])
return network['y']
print(forword())
神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出层的激活函数。一般而言,回归问题用恒等函数,分类问题用softmax 函数。
恒等函数
def identity_function(x):
return x
y = x
softmax函数
说白了就是这个输出神经元占所有输出神经元的比例,这样就可以反映出这个结果出现的概率了
>>> a = np.array([0.3, 2.9, 4.0])
>>>
>>> exp_a = np.exp(a) # 指数函数
>>> print(exp_a)
[ 1.34985881 18.17414537 54.59815003]
>>>
>>> sum_exp_a = np.sum(exp_a) # 指数函数的和
>>> print(sum_exp_a)
74.1221542102
>>>
>>> y = exp_a / sum_exp_a
>>> print(y)
[ 0.01821127 0.24519181 0.73659691]
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
实现softmax函数时的注意事项
要注意内存溢出的问题,所以采用一下方式进行改进
这里的C’可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值。
def softmax(a):
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
softmax函数的特征
输出层的神经元数量
输出层的神经元数量需要根据待解决的问题来决定。
对于分类问题,输出层的神经元数量一般设定为类别的数量。
MNIST 数据集
MNIST是机器学习领域最有名的数据集之一,被应用于从简单的实验到发表的论文研究等各种场合。
load_mnist(normalize=True,flatten=True, one_hot_label=False) :load_mnist函数以“( 训练图像, 训练标签),( 测试图像,测试标签)”的形式返回读入的MNIST数据。
神经网络的推理处理
from dataset.mnist import load_mnist
import pickle
import numpy as np
# 隐藏层激活函数
def sigmoid_function(x):
return 1 / (1 + np.exp(-x))
# 输出层激活函数
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
# 1.准备一个获取数据集的函数
def get_data():
# 通过这个函数,获得训练集和测试集
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, flatten=True, one_hot_label=False)
# 后面只用到测试集,所以只返回这俩(因为模型是训练好的,所以用不到训练集)
return x_test, t_test
# 2.准备一个获取权重信息的函数
def get_weight():
# 你应该保证sample_weight.pkl文件的路径是正确的
# 这个文件就是实例经过pickle模块的dumps打包之后的文件(这里我们直接拿来用)
with open("dataset/sample_weight.pkl", 'rb') as f:
# 把这个文件重新还原成了实例
network = pickle.load(f)
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
# 返回了各层的权重和偏置
return W1, W2, W3, b1, b2, b3
# 3.准备一个通过数据集及权重信息进行推理的神经网络函数
# 这个过程也叫前向传播(forward propagation)
def forward_propagation(x, network):
W1, W2, W3, b1, b2, b3 = network
a1 = np.dot(x, W1) + b1
z1 = sigmoid_function(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid_function(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3)
return y
# 4.准备一个统计真实结果与预测结果相同数量的函数
def get_accuracy():
network = get_weight()
x, t = get_data()
# 这个变量用于记录正确的预测数量
accuracy_cnt = 0
for i in range(len(x)):
# 逐个预测结果
y = forward_propagation(x[i], network)
p = np.argmax(y) # 获取概率最高的元素的索引
# 对预测结果与真实结果对比
if p == t[i]:
accuracy_cnt += 1
# 返回正确比例
return str(float(accuracy_cnt) / len(x))
# 5.运行主函数,查看效果
def main():
Accuracy = get_accuracy()
print(Accuracy) # 输出0.9352
if __name__ == '__main__':
main()
批处理
打包式的输入数据称为批(batch)。批处理对计算机的运算大有利处,可以大幅缩短每张图像的处理时间。
from dataset.mnist import load_mnist
import pickle
import numpy as np
# 隐藏层激活函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 输出层激活函数
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
def get_data():
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test
def init_network():
with open("dataset/sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network
def softmax(a):
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
def predict(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)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3)
return y
x, t = get_data()
network = init_network()
batch_size = 100 # 批数量
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
x_batch = x[i:i + batch_size]
y_batch = predict(network, x_batch)
p = np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(p == t[i:i + batch_size])
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
“学习”是指从训练数据中自动获取最优权重参数的过程。为了使神经网络能进行学习,将导入损失函数这一指标。而学习的目的就是以该损失函数为基准,找出能使它的值达到最小的权重参数。为了找出尽可能小的损失函数的值。
神经网络的特征就是可以从数据中学习。所谓“从数据中学习”,是指可以由数据自动决定权重参数的值。
对于线性可分问题,第2 章的感知机是可以利用数据自动学习的。根据“感知机收敛定理”,通过有限次数的学习,线性可分问题是可解的。但是,非线性可分问题则无法通过(自动)学习来解决。
数据驱动
机器学习的方法中,由机器从收集到的数据中找出规律性。
神经网络的优点是对所有的问题都可以用同样的流程来解决。
训练数据和测试数据
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和实验等。
为了正确评价模型的泛化能力,就必须划分训练数据和测试数据。另外,训练数据也可以称为监督数据。
泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的能力。
因此,仅仅用一个数据集去学习和评价参数,是无法进行正确评价的。这样会导致可以顺利地处理某个数据集,但无法处理其他数据集的情况。顺便说一下,只对某个数据集过度拟合的状态称为过拟合(over fitting)。避免过拟合也是机器学习的一个重要课题。
神经网络的学习通过某个指标表示现在的状态。然后,以这个指标为基准,寻找最优权重参数。
神经网络的学习中所用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,但一般用均方误差和交叉熵误差等。
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
均方误差
可以用作损失函数的函数有很多,其中最有名的是均方误差(mean squarederror)。
这里,yk 是表示神经网络的输出,tk 表示监督数据(标准答案),k 表示数据的维数。
将正确解标签表示为1,其他标签表示为0 的表示方法称为one-hot 表示。
均方误差会计算神经网络的输出和正确解监督数据的各个元素之差的平方,再求总和。
def mean_squared_error(y, t):
return 0.5 * np.sum((y-t)**2)
交叉熵误差
除了均方误差之外,交叉熵误差(cross entropy error)也经常被用作损失函数。
这里,log表示以e为底数的自然对数(log e)。yk是神经网络的输出,tk是正确解标签。
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
个微小值delta的作用是保护程序防止当出现np.log(0)时出现一个无穷大导致程序崩溃
mini-batch学习
就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。
以交叉熵误差为例求单个数据的“平均损失函数”:
这里, 假设数据有N个,tnk 表示第n个数据的第k 个元素的值(ynk 是神经网络的输出,tnk 是监督数据)。其实只是把求单个数据的损失函数的式扩大到了N份数据
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)
mini-batch版交叉熵误差的实现
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(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
为何要设定损失函数
之所以不能用识别精度作为指标,是因为这样一来绝大多数地方的导数都会变为0,导致参数无法更新。
在进行神经网络的学习时,不能将识别精度作为指标。因为如果以识别精度为指标,则参数的导数在绝大多数地方都会变为0。
导数
为了减小这个误差,我们可以计算函数f 在(x + h) 和(x − h) 之间的差分。因为这种计算方法以x 为中心,计算它左右两边的差分,所以也称为中心差分(而(x + h) 和x之间的差分称为前向差分)。
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
偏导数
对有两个变量的函数求其中某一个变量的导数
梯度
由全部变量的偏导数汇总而成的向量称为梯度(gradient)。
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h)的计算
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h)的计算
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
梯度法
η 表示更新量,在神经网络的学习中,称为学习率(learningrate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。
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
参数f 是要进行最优化的函数,init_x 是初始值,lr 是学习率learning rate,step_num 是梯度法的重复次数。numerical_gradient(f,x) 会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num 指定重复的次数。
学习率过大的话,会发散成一个很大的值;学率过小的话,基本上没怎么更新就结束了。也就是说,设定合适的学习率是一个很重要的问题。
神经网络的梯度
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(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
步骤:
2 层神经网络的类
import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size,weight_init_std=0.01):
# 初始化权重
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
变量 | 说明 |
---|---|
params | 保存神经网络的参数的字典型变量(实例变量)。 params[‘W1’]是第1 层的权重,params[‘b1’]是第1 层的偏置。 params[‘W2’]是第2 层的权重,params[‘b2’]是第2 层的偏置 |
grads | 保存梯度的字典型变量(numerical_gradient()方法的返回值)。 grads[‘W1’]是第1 层权重的梯度,grads[‘b1’]是第1 层偏置的梯度。 grads[‘W2’]是第2 层权重的梯度,grads[‘b2’]是第2 层偏置的梯度 |
方法 | 说明 |
---|---|
init(self, input_size,hidden_size, output_size) | 进行初始化。 参数从头开始依次表示输入层的神经元数、隐藏层的神经元数、输出层的神经元数 |
predict(self, x) | 进行识别(推理)。 参数x是图像数据 |
loss(self, x, t) | 计算损失函数的值。 参数x 是图像数据,t 是正确解标签(后面3 个方法的参数也一样) |
accuracy(self, x, t) | 计算识别精度 |
numerical_gradient(self, x, t) | 计算权重参数的梯度 |
gradient(self, x, t) | 计算权重参数的梯度。 numerical_gradient()的高速版,将在下一章实现 |
mini-batch的实现
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
(x_train, t_train), (x_test, t_test) = \ load_mnist(normalize=True, one_hot_
laobel = True)
train_loss_list = []
# 超参数
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
for i in range(iters_num):
# 获取mini-batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
grad = network.numerical_gradient(x_batch, t_batch)
# grad = network.gradient(x_batch, t_batch) # 高速版!
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
# 记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
基于测试数据的评价
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000 # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 计算梯度
# grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
在上面的例子中,每经过一个epoch,就对所有的训练数据和测试数据计算识别精度,并记录结果。之所以要计算每一个epoch 的识别精度,是因为如果在for语句的循环中一直计算识别精度,会花费太多时间。并且,也没有必要那么频繁地记录识别精度(只要从大方向上大致把握识别精度的推移就可以了)。因此,我们才会每经过一个epoch就记录一次训练数据的识别精度。
epoch是一个单位。一个epoch表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于10000 笔训练数据,用大小为100笔数据的mini-batch 进行学习时,重复随机梯度下降法100 次,所有的训练数据就都被“看过”了A。此时,100次就是一个epoch。
反向传播时一个求导的过程,输出结果是这个变量对于最终结果的影响力
以加和乘为例:
z = x + y
所以加相当于将上游的值原封不动地输出到下游
z = xy
所以乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游
乘法层的实现
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 # 翻转x和y
dy = dout * self.x
return dx, dy
加法层的实现
class AddLayer:
def __init__(self):
pass
def forward(self, x, y):
out = x + y
return out
def backward(self, dout):
dx = dout * 1
dy = dout * 1
return dx, dy
ReLU层
如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游。反过来,如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处。
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
ReLU 层的作用就像电路中的开关一样。正向传播时,有电流通过的话,就将开关设为ON;没有电流通过的话,就将开关设为OFF。反向传播时,开关为ON 的话,电流会直接通过;开关为OFF 的话,则不会有电流通过。
Sigmoid 层
最终化简得:
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine 层
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
def forward(self, x):
self.x = x
out = np.dot(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)
return dx
Softmax-with-Loss 层
softmax 函数会将输入值正规化之后再输出。
class SoftmaxWithLoss:
def __init__(self):
self.loss = None # 损失
self.y = None # softmax的输出
self.t = None # 监督数据(one-hot vector)
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]
dx = (self.y - self.t) / batch_size
return dx
神经网络学习的步骤
误差反向传播法会在步骤2 中出现,误差反向传播法可以快速高效地计算梯度。
对应误差反向传播法的神经网络的实现
实例变量 | 说明 |
---|---|
params | 保存神经网络的参数的字典型变量。 params[‘W1’]是第1 层的权重,params[‘b1’]是第1 层的偏置。 params[‘W2’]是第2 层的权重,params[‘b2’]是第2 层的偏置 |
layers | 保存神经网络的层的有序字典型变量。 以layers[‘Affine1’]、layers[‘ReLu1’]、layers[‘Affine2’]的形式, 通过有序字典保存各个层 |
lastLayer | 神经网络的最后一层。 本例中为SoftmaxWithLoss层 |
方法 | 说明 |
---|---|
init(self, input_size,hidden_size, output_size,weight_init_std) | 进行初始化。 参数从头开始依次是输入层的神经元数、隐藏层的神经元数、输出层的神经元数、初始化权重时的高斯分布的规模 |
predict(self, x) | 进行识别(推理)。 参数x是图像数据 |
loss(self, x, t) | 计算损失函数的值。 参数X是图像数据、t是正确解标签 |
accuracy(self, x, t) | 计算识别精度 |
numerical_gradient(self, x, t) | 通过数值微分计算关于权重参数的梯度 |
gradient(self, x, t) | 通过误差反向传播法计算关于权重参数的梯度 |
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 初始化权重
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 生成层
self.layers = OrderedDict()
self.layers['Affine1'] = \
Affine(self.params['W1'], self.params['b1'])
self.layers['Relu1'] = Relu()
self.layers['Affine2'] = \
Affine(self.params['W2'], self.params['b2'])
self.lastLayer = SoftmaxWithLoss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
if t.ndim != 1 : t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self, x, t):
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 设定
grads = {}
grads['W1'] = self.layers['Affine1'].dW
grads['b1'] = self.layers['Affine1'].db
grads['W2'] = self.layers['Affine2'].dW
grads['b2'] = self.layers['Affine2'].db
return grads
误差反向传播法的梯度确认
数值微分的优点是实现简单,因此,一般情况下不太容易出错。而误差反向传播法的实现很复杂,容易出错。所以,经常会比较数值微分的结果和误差反向传播法的结果,以确认误差反向传播法的实现是否正确。确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致(严格地讲,是非常相近)的操作称为梯度确认(gradient check)
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
for key in grad_numerical.keys():
diff = np.average(np.abs(grad_backprop[key] - grad_numerical[key]))
print(key + ":" + str(diff))
使用误差反向传播法的学习
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 梯度
# grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print(train_acc, test_acc)
神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。
SGD
为了找到最优参数,我们将参数的梯度(导数)作为了线索。使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称SGD
SGD 的缺点
SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。
例如:
这个函数图像是一个碗,SGD会呈“之”字形移动。
Momentum
Momentum是“动量”的意思,和物理有关。
类似小球在一个弧面上运动速度越快摩擦力越大,减数越快,相较于SGD的之字形能更快到达最低点
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
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]
AdaGrad
在关于学习率的有效技巧中,有一种被称为学习率衰减(learning ratedecay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。
class 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)
Adam
Momentum参照小球在碗中滚动的物理规则进行移动,AdaGrad为参数的每个元素适当地调整更新步伐。Adam是将这两个方法融合在一起。
通过减小权重参数的值来抑制过拟合的发生。如果想减小权重的值,一开始就将初始值设为较小的值才是正途。权重初始值都是像0.01 * np.random.randn(10, 100)这样,但又不能设置成0,而且不能相同。因为如果相同那么在误差反向传播法中,所有的权重值都会进行相同的更新。权重被更新为相同的值,并拥有了对称的值(重复的值)。这使得神经网络拥有许多不同的权重的意义丧失了。
过拟合
原因:
权值衰减
权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。
Dropout
权值衰减在模型负责的情况下难以解决过拟合的情况,Dropout是作为抑制过拟合辅助方法。
Dropout理解为,通过在学习过程中随机删除神经元,从而每一次都让不同的模型进行学习。并且,推理时,通过对神经元的输出乘以删除比例(比如,0.5 等),可以取得模型的平均值。也就是说,可以理解成,Dropout将集成学习的效果(模拟地)通过一个网络实现了。
神经网络中,除了权重和偏置等参数,超参数(hyper-parameter)也经常出现。这里所说的超参数是指,比如各层的神经元数量、batch 大小、参数更新时的学习率或权值衰减等。
验证数据
不能使用测试数据评估超参数的性能,因为如果使用测试数据调整超参数,超参数的值会对测试数据发生过拟合。因此,调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)。
根据不同的数据集,有的会事先分成训练数据、验证数据、测试数据三部分,有的只分成训练数据和测试数据两部分,有的则不进行分割。
超参数的最优化
超参数的范围只要“大致地指定”就可以了。
步骤:
可以理解为之前的“Affi ne - ReLU”连接被替换成了“Convolution -ReLU -(Pooling)”连接。
此外,靠近输出的层中使用了之前的“Affi ne - ReLU”组合,最后的输出层中使用了之前的“Affine -Softmax”组合。
全连接层存在的问题
数据的形状被“忽视”了。比如,输入数据是图像时,图像通常是高、长、通道方向上的3 维形状。但是,向全连接层输入时,需要将3 维数据拉平为1 维数据。实际上,前面提到的使用了MNIST数据集的例子中,输入图像就是1 通道、高28 像素、长28 像素的(1, 28, 28)形状,但却被排成1 列,以784 个数据的形式输入到最开始的Affine层。
因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。
CNN中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征图(output feature map)。
卷积运算
卷积层进行的处理就是卷积运算。卷积运算相当于图像处理中的“滤波器运算”。
乘积累加运算
填充
在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0 等),这称为填充(padding)
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4) 的输入数据应用(3, 3) 的滤波器时,输出大小变为(2, 2),相当于输出大小比输入大小缩小了2 个元素。这在反复进行多次卷积运算的深度网络中会成为问题.
步幅
应用滤波器的位置间隔称为步幅(stride)。
3维数据的卷积运算
通道数只能设定为和输入数据的通道数相同的值
池化是缩小高、长方向上的空间的运算。比如,如图7-14 所示,进行将2 × 2 的区域集约成1 个元素的处理,缩小空间大小。
除了Max 池化之外,还有Average 池化等。相对于Max 池化是从目标区域中取出最大值,Average 池化则是计算目标区域的平均值。在图像识别领域,主要使用Max 池化。
池化层的特征
基于im2col 的展开
im2col是一个函数,将输入数据展开以适合滤波器(权重),空间换时间
im2col (input_data, filter_h, filter_w, stride=1, pad=0)
卷积层的实现
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T # 滤波器的展开
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
池化层的实现
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
# 展开(1)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
# 最大值(2)
out = np.max(col, axis=1)
# 转换(3)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
第1层权重的可视化
学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。我们发现,通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。
由此可知,卷积层的滤波器会提取边缘或斑块等原始信息。而刚才实现的CNN会将这些原始信息传递给后面的层。
基于分层结构的信息提取
随着层次加深,提取的信息(正确地讲,是反映强烈的神经元)也越来越抽象
LeNet
LeNet 中使用sigmoid 函数,而现在的CNN中主要使用ReLU函数。
LeNet 中使用子采样(subsampling)缩小中间数据的大小,而现在的CNN中Max池化是主流。
AlexNet
VGG
GoogLeNet
ResNet