《深度学习入门:基于Python的理论与实现》看了下,这本书对我这种没有任何深度学习基础的小白很有用也很友好,而且是本需要反复阅读加深记忆的读本,故将重要内容整理记录,方便后续快速回顾复习,与大家共勉~
感知机
感知机接收多个输入信号,输出一个信号。和实际的电 流不同的是,感知机的信号只有“流 / 不流”(1/0)两种取值。0 对应“不传递信号”,1 对应“传递信号”。
感知机的定义
图是一个接收两个输入信号的感知机的例子。x1、x2 是输入信号, y 是输出信号,w1、w2 是权重(若 b 为 −0.1,则只要输入信号的加权总和超过 0.1,神经元就会被激活。但是如果 b 为 −20.0,则输入信号的加权总和必须超过 20.0,神经元才会被激活。像这样, 偏置的值决定了神经元被激活的容易程度)。图中的○称为“神 经元”或者“节点”。输入信号被送往神经元时,会被分别乘以固定的权重
(w1x1、w2x2)。神经元会计算传送过来的信号的总和,只有当这个总和超过 了某个界限值时,才会输出1。这也称为“神经元被激活”。这里将这个界 限值称为阈值,用符号 θ 表示。用数学式表达就是
感知机的实现
简单实现
简单的实现“与”门的实现
# 在函数内初始化参数 w1、w2、theta,当输入的加权总和超过阈值时返回 1,
否则返回 0
def AND(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1 + x2*w2
if tmp <= theta:
return 0
elif tmp > theta:
return 1
导入权重和偏置
b 称为偏置,w1 和 w2 称为权重(偏置是调 整神经元被激活的容易程度(输出信号为 1 的程度)的参数)
式子对应的计算如下:
>>> 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):
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]) # 仅权重和偏置与AND不同!
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]) # 仅权重和偏置与AND不同!
b = -0.2
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
感知机的局限性
以异或门为例(仅当 x1 或 x2 中的一方为 1 时 , 才 会 输 出 1 ),前面介绍的感知机是无法实现这个异或门的
一条直线无法完全分开0和1的情况(非线性的):
或门是线性的:
多层感知机
电路组合
感知机 的绝妙之处在于它可以“叠加层”。从而去解决无法实现异或门的问题。
简单门电路的表示
通过简单门实现异或门
异或门的实现
python逻辑实现:
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
使用感知机实现:
小结
• 感知机是具有输入和输出的算法。给定一个输入后,将输出一个既定的值。
• 感知机将权重和偏置设定为参数。
• 使用感知机可以表示与门和或门等逻辑电路。
• 异或门无法通过单层感知机来表示。
• 使用2层感知机可以表示异或门。
• 单层感知机只能表示线性空间,而多层感知机可以表示非线性空间。
• 多层感知机(在理论上)可以表示计算机。
神经网络
神经网络的一 个重要性质是它可以自动地从数据中学习到合适的权重参数,不再需要人工配置
感知机到神经网络
中间层有时也称为隐藏层。
简单激活函数
h(x)函数会将输入信号的总和转换为输出信号,这种函数 一般称为激活函数,也可以表达成以下两个式子
a = b + w1x1 + w2x2
y = h(a)
一 般而言,“朴素感知机”是指单层网络,指的是激活函数使用了阶跃函数(是指一旦输入超过阈值,就切换输出的函数) A 的模型。“多层感知机”是指神经网络,即使用 sigmoid 函数(后述)等平滑的激活函数的多层网络。
激活函数
sigmoid函数
常使用的一个激活函数表达式( exp(−x) 表示 e−x次方 ):
阶跃函数的图形
可以用python实现为:
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype=np.int) # x>0会对数组的各项不等式判定,结果为True和False,dtype=np.int会把结果转换为1、0
x = np.arange(-5.0, 5.0, 0.1) # 在−5.0到5.0的范围内,以0.1为单位,生成 NumPy数组
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定y轴的范围 plt.show()
结果图形:
sigmoid 函数的实现
把阶跃函数的step_functon()改成sigmoid即可
阶跃函数与sigmoid函数的对比
两者均为非线性函数
ReLU 函数
sigmoid 函数很早就开始被使用了,而最近则主要 使用ReLU(Rectified Linear Unit)函数。ReLU 函数在输入大于 0 时,直接输出该值;在输入小于等于0时,输出0
def relu(x):
return np.maximum(0, x)
多维数组
多位数组的生成常用函数在使用时再解释,矩阵的简单运算此处略过
神经网络的内积
>>> X = np.array([1, 2])
>>> X.shape # 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) # dot 数组乘积(矩阵的乘积,看不懂的话学一下矩阵的运算)
>>> print(Y)
[ 5 11 17]
三层神经网络的实现
权重的符号说明:
这样可以使用公式表示(a是后一层的神经元,w是权重,x是输入,b是偏置)
采用矩阵的乘法表示如下:
python使用NumPy多维数组实现如下:
def init_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) # 激活函数
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)# 或者Y = A3(这个函数是恒等函数,输出层的激活函数用 σ() 表示,不同于隐藏层的激活函数 h(),这里σ() 就是这个恒等函数,只是为了和其他层的传递流程保持一致)
return y
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y) # [ 0.31682708 0.69627909]
用图表信号传递过程(第0层至第一层,后两层以此类推)
输出层
神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出 层的激活函数。一般而言,回归问题用恒等函数,分类问题用 softmax 函数。
softmax 函数
分母是所有输入信号的指数函数的和,可以看出, 输出层的各个神经元都受到所有输入信号的影响,输出总和为 1 是 softmax 函数的一个重要性质,正因为有了这个性质,我们才可以把 softmax 函数的输出解释为“概率”。
使用python定义softmax函数:
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‘可以使用任何值,但是为了防止溢出,一般会使用输入信号中的最大值使用python定义softmax函数
使用python定义softmax函数:
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
特征:
>>> a = np.array([0.3, 2.9, 4.0])
>>> y = softmax(a)
>>> print(y)
[ 0.01821127 0.24519181 0.73659691]
>>> np.sum(y)
1.0
上面的例子可以解释成 y[0] 的概率是 0.018(1.8 %),y[1] 的概率 是 0.245(24.5 %),y[2] 的概率是0.737(73.7 %)。从概率的结果来看,可以说“因为第2个元素的概率最高,所以答案是第2个类别”。而且,还可以回答“有74%的概率是第2个类别,有25%的概率是第1个类别,有1%的概率是第0个类别”。也就是说,通过使用 softmax 函数,我们可以用概率的(统计的)方法处理问题。
输出层的神经元数量
对于分类问题,输 出层的神经元数量一般设定为类别的数量。比如,对于某个输入图像,预测 是图中的数字 0 到 9 中的哪一个的问题(10 类别分类问题),可以像图 3-23 这样, 将输出层的神经元设定为 10 个。
手写数字识别
和求解机器学习问题的步骤(分成学习和推理两个阶段进行)一样, 使用神经网络解决问题时,也需要首先使用训练数据(学习数据)进 行权重参数的学习;进行推理时,使用刚才学习到的参数,对输入数据进行分类。
MNIST 数据集
MNIST 数据集是由 0 到 9 的数字图像构成的(图 3-24)。训练图像有 6 万张, 测试图像有 1 万张,这些图像可以用于学习和推理。MNIST 数据集的一般 使用方法是,先用训练图像进行学习,再用学习到的模型度量能在多大程度上对测试图像进行正确的分类。MNIST的图像数据是28像素 ×28像素的灰度图像(1通道),各个像素 的取值在 0 到 255 之间
- 读入minist数据
import sys, os
sys.path.append(os.pardir) # 为了导入父目录中的文件而进行的设定
from dataset.mnist import load_mnist
# 第一次调用会花费几分钟......
# load_mnist 函数以“( 训练图像 , 训练标签 ),( 测试图像,测试标签 )”的 形式返回读入的 MNIST 数据
# 第 1 个参数normalize 设置是否将输入图像正规化为 0.0~1.0 的值。如果将该参数设置 为 False,则输入图像的像素会保持原来的 0~255。
# 第 2 个参数 flatten 设置是否展开输入图像(变成一维数组)。如果将该参数设置为False,则输入图像为 1 × 28 × 28 的三维数组;若设置为 True,则输入图像会保存为由 784 个元素构成的一维数组。
# 第 3 个参数 one_hot_label 设置是否将标签保存为 one-hot 表示(one-hot representation)。one-hot 表示是仅正确解标签为 1,其余皆为0的数组,就像 [0,0,1,0,0,0,0,0,0,0] 这样。当 one_hot_label 为 False 时, 只是像 7、2 这样简单保存正确解标签;当one_hot_label 为 True 时,标签则保存为 one-hot 表示。
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
# 输出各个数据的形状
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)
- 显示 MNIST 图像
import sys, os sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist from PIL import Image
def img_show(img):
pil_img = Image.fromarray(np.uint8(img))
pil_img.show()
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
img = x_train[0]
label = t_train[0]
print(label) # 5
print(img.shape) # (784,)
img = img.reshape(28, 28) # 把图像的形状变成原来的尺寸
print(img.shape) # (28, 28)
img_show(img)
神经网络的推理处理
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
# 读入保存在 pickle 文件 sample_weight.pkl 中的学习到的 权重参数(假设学习已经完成)。
def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network
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()
accuracy_cnt = 0
for i in range(len(x)):
y = predict(network, x[i]) # 获取对各个数字的概率
p = np.argmax(y) # 获取概率最高的元素的索引
if p == t[i]:# 如果和答案相同
accuracy_cnt += 1
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
将图像的各个像 素值除以 255,使得数据的值在 0.0~1.0 的范围内。像这样把数据限定到某 个范围内的处理称为正规化(normalization)
批处理优化:
x, t = get_data() network = init_network()
batch_size = 100 # 批数量
accuracy_cnt = 0
for i in range(0, len(x), batch_size): # 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)))
小结
• 神经网络中的激活函数使用平滑变化的sigmoid函数或ReLU函数。
• 通过巧妙地使用NumPy多维数组,可以高效地实现神经网络。
• 机器学习的问题大体上可以分为回归问题和分类问题。
• 关于输出层的激活函数,回归问题中一般用恒等函数,分类问题中
一般用 softmax 函数。
• 分类问题中,输出层的神经元的数量设置为要分类的类别数。
• 输入数据的集合称为批。通过以批为单位进行推理处理,能够实现
高速的运算。