深度学习入门导航🚀🚀🚀

  1. 🎉感知机

  2. 🎈神经网络 ⇦ 当前位置🪂

  3. 🧨神经网络的学习

  4. 🎊误差反向传播法

  5. 🧧与学习相关的技巧

  6. 🎑卷积神经网络

  7. 🌈深度学习

感知机设置权重的工作还是人工进行的

神经网络的一个重要性质是它可以自动地从数据中学习到合适的权重参数

image.png

  • 输入层 第0层
  • 中间层(隐藏层) 第1层
  • 输出层 第2层

激活函数

将输入信号的总和转换为输出信号

比如感知机,h(x)就是激活函数

h(x)={0(x0)1(x>0)


image.png


表示神经元的○中明确显示了激活函数的计算过程

  • 节点a 信号的加权总和
  • h() 激活函数

sigmoid函数

  • $exp(−x)$ 表示$e^{−x}$的意思
  • $e$ 纳皮尔常数2.7182
1
2
3
4
5
def sigmoid(x):
return 1 / (1 + np.exp(-x))

print(sigmoid(1.0))
print(sigmoid(2.0))
0.7310585786300049
0.8807970779778823
**图形**
1
2
3
4
5
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

image.png

阶跃函数

1
2
3
def step_function(x):
y = x > 0
return y.astype(np.int)
1
2
def step_function(x):
return np.array(x>0, dtype=bp.int32)
**图形**
1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import matplotlib.pylab as plt

def step_function(x):
return np.array(x > 0, dtype=np.int32)

x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

image.png

sigmoid函数和阶跃函数的比较

  • 平滑性
  • 感知机中神经元之间流动的是0或1的二元信号(阶跃函数)
  • 神经网络中流动的是连续的实数值信号(sigmoid函数)
  • 形状相似
  • 非线性函数 ($h(x)=cx$为线性函数)

神经网络的激活函数必须使用非线性函数
线性函数:不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”

ReLU函数

ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0

h(x)={x(x>0)0(x0)
1
2
def relu(x):
return np.maximum(0, x)

多维数组的运算

神经网络的内积

image.png

1
2
3
4
5
X = np.array([1, 2])
W = np.array([[1, 3, 5], [2, 4, 6]])
Y = np.dot(X, W)
print(Y)
# [ 5 11 17]

3层神经网络的实现

image.png

符号:

image.png

  • 右上角表示权重和神经元的层号(从输入层开始)
  • 右下角前面对应后一层的神经元
  • 右下角后面对应前一层的神经元

各层间信号传递的实现

image.png

  • 灰色的为表示偏置的神经元(右下角的神经元只有一个)

公式表达为

矩阵乘法运算公式为

其中,$A^(1)、X、B^(1)、W^(1)$如下所示

A(1)=(a1(1)a2(1)a3(1)),X=(x1x2),B(1)=(b1(1)b2(1)b3(1))W(1)=(w11(1)w21(1)w31(1)w12(1)w22(1)w32(1))

具体代码和流程

1
2
3
4
5
6
7
8
9
10
import numpy as np
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])

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]

image.png

1
2
3
4
5
6
7
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)

image.png

从第2层到输出层的信号传递的激活函数,一般地,回归问题可以使用恒等函数,二元分类问题可以使用sigmoid函数,多元分类问题可以使用softmax函数

**代码总结**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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)

return y

def identity_function(x):
'''恒等函数'''
return x

def sigmoid(n):
'''sigmoid函数'''
e = 2.718281828459045
return 1 / (1 + np.exp(-n))

network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y) # [ 0.31682708 0.69627909]

输出层的设计

一般而言,回归问题用恒等函数,分类问题用softmax函数

  • 分类问题是数据属于哪一个类别的问题
  • 回归问题是根据某个输入预测一个(连续的)数值的问题

softmax函数

softmax函数数学公式

softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大

softmax函数数学公式改进

1
2
3
4
5
6
7
def softmax(a):
c = np.max()
exp_a = np.exp(a - c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a

return y

softmax函数特征

  • softmax函数的输出是0.0到1.0之间的实数
  • softmax函数的输出值的总和是1
  • 可以把softmax函数的输出解释为“概率”

输出层的神经元数量需要根据待解决的问题来决定

手写数字识别

import sys,os sys.path.append(os.pardir)
sys.path.append(os.pardir)语句实际上是把父目录加入到sys.path(Python的搜索模块的路径集)中,从而可以导入父目录下的任何目录中的任何文件

Python有pickle这个便利的功能。这个功能可以将程序运行中的对象保存为文件。如果加载保存过的pickle文件,可以立刻复原之前程序运行中的对象

MNIST数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# coding: utf-8
# import sys, os
# sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
from 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)

# 0~59999 共 60000 张训练图像
img = x_train[59999]
label = t_train[59999]
print(label) # 8

# flatten=True 使得输入的图像是一维的数组,所以需要把它变回 28 x 28 的图像
print(img.shape) # (784,) 即 784的一维数组
img = img.reshape(28, 28) # 把图像的形状变为原来的尺寸 28 x 28 的二维数组
print(img.shape) # (28, 28)

img_show(img)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# coding: utf-8
try:
import urllib.request
except ImportError:
raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np

url_base = 'https://ossci-datasets.s3.amazonaws.com/mnist/' # mirror site
key_file = {
'train_img':'train-images-idx3-ubyte.gz',
'train_label':'train-labels-idx1-ubyte.gz',
'test_img':'t10k-images-idx3-ubyte.gz',
'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784

def _download(file_name):
file_path = dataset_dir + "/" + file_name

if os.path.exists(file_path):
return

print("Downloading " + file_name + " ... ")
urllib.request.urlretrieve(url_base + file_name, file_path)
print("Done")

def download_mnist():
for v in key_file.values():
_download(v)

def _load_label(file_name):
file_path = dataset_dir + "/" + file_name

print("Converting " + file_name + " to NumPy Array ...")
with gzip.open(file_path, 'rb') as f:
labels = np.frombuffer(f.read(), np.uint8, offset=8)
print("Done")

return labels

def _load_img(file_name):
file_path = dataset_dir + "/" + file_name

print("Converting " + file_name + " to NumPy Array ...")
with gzip.open(file_path, 'rb') as f:
data = np.frombuffer(f.read(), np.uint8, offset=16)
data = data.reshape(-1, img_size)
print("Done")

return data

def _convert_numpy():
dataset = {}
dataset['train_img'] = _load_img(key_file['train_img'])
dataset['train_label'] = _load_label(key_file['train_label'])
dataset['test_img'] = _load_img(key_file['test_img'])
dataset['test_label'] = _load_label(key_file['test_label'])

return dataset

def init_mnist():
download_mnist()
dataset = _convert_numpy()
print("Creating pickle file ...")
with open(save_file, 'wb') as f:
pickle.dump(dataset, f, -1)
print("Done!")

def _change_one_hot_label(X):
T = np.zeros((X.size, 10))
for idx, row in enumerate(T):
row[X[idx]] = 1

return T


def load_mnist(normalize=True, flatten=True, one_hot_label=False):
"""读入MNIST数据集

Parameters
----------
normalize : 将图像的像素值正规化为0.0~1.0
one_hot_label :
one_hot_label为True的情况下,标签作为one-hot数组返回
one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
flatten : 是否将图像展开为一维数组

Returns
-------
(训练图像, 训练标签), (测试图像, 测试标签)
"""
if not os.path.exists(save_file):
init_mnist()

with open(save_file, 'rb') as f:
dataset = pickle.load(f)

if normalize:
for key in ('train_img', 'test_img'):
dataset[key] = dataset[key].astype(np.float32)
dataset[key] /= 255.0

if one_hot_label:
dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
dataset['test_label'] = _change_one_hot_label(dataset['test_label'])

if not flatten:
for key in ('train_img', 'test_img'):
dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])

if __name__ == '__main__':
init_mnist()

神经网络的推理处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# coding: utf-8
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax

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("sample_weight.pkl", 'rb') as f:
# 使用 pickle 模块加载存储在文件中的网络参数(第一到第三层的权重和偏置)
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) # np.argmax(x)函数取出数组中的最大值的索引,获取概率最高的元素的索引
if p == t[i]:
accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
# Accuracy:0.9352
  • np.argmax(x)函数取出数组中的最大值的索引
  • 正规化 把数据限定到某个范围内的处理,最常规是0~1
  • 预处理 对神经网络的输入数据进行某种既定的转换,比如正规化

批处理

  • 打包式的输入数据
  • 批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快
1
2
3
4
5
6
7
8
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])

代码解释

  • range(start, end, step) 生成的列表中的下一个元素会增加step指定的值
  • x[i:i+batch_size] 切片抽取批数据
  • np.argmax(y_batch, axis=1) 沿着第1维方向找到值最大的元素的索引

矩阵的第0维是列方向,第1维是行方向

对于数组或列表的比较,例如 array1 == array2,会返回一个布尔数组