图片与矩阵

我们的图片(位图)可通过三维矩阵叠加而成也就是三个只有R、G、B的矩阵叠加,而每个矩阵每个值为[0, 255]之间来进行储存。

image-20210427155458683

也就是说我们可以通过将图片数字化,使得计算机也具有对其进行处理的能力,目前机器视觉使用最广泛也最成熟的技术为“神经网络”。

神经元模型

a0fe8f4886f2ab0c7abf18b0190e085531a55991

神经元接收来自n个其它神经元传递过来的输入信号,这些信号的表达,通常通过神经元之间连接的权重(weight)大小来表示,神经元将接收到的输入值按照某种权重叠加起来,并将当前神经元的阈值进行比较,然后通过“激活函数(activation function)”向外表达输出。

激活函数

神经网络要引入激活函数来给神经网络增加一些非线性的特性。

比如最常见的Sigmoid函数和ReLU函数

Sigmoid:f(x)=11+exReLU:f(x)={xx00x<0Sigmoid: f(x)=\frac{1}{1+e^{-x}} \\ ReLU:f(x)=\begin{cases} x & x\ge0 \\ 0 & x<0 \end{cases}

其图像为

image-20210429163247753

神经元的简单应用实例

  • 单个神经元可实现与或非
  • 再加一个神经元即可实现异或

所以通过网络的叠加即可实现更加复杂的分类预测问题

image-20210427171033177

神经网络模型的建立

神经网络中成千上万的参数靠我们手工调试很明显是不可能的,所以引入一种能通过数据集自动获取适合参数的算法——误差逆传播

但在这之前我们先介绍其他的一些算法

前向传播

既是f(xw)f(x \cdot w)获得隐含层的值,同样然后再乘以下一层的权重,经过激活函数,获得输出层的最终结果。

反向传播

反向传播是神经网络最巧妙的地方,通过反向算法,神经网络实现了无需人们手动调参,自动“学习”到恰当的参数。

损失函数

用来表示预测值和实际值的“差距”,常用的表示误差的方式有很多,比如最常见的均方误差

Ek=12(y^y)2E_k=\frac{1}{2}\sum(\hat{y}-y)^2

也就是预测值减去实际值的平方,前面的12\frac{1}{2}是是为了求导更加方便。

更新参数

我们通过误差来更新我们每层神经网络的权重

vv+Δvv\leftarrow v + \Delta v

梯度下降算法

Δwhj=ηEkwwhj\Delta w_{hj}=-\eta \frac{\partial E_k}{\partial w_{w_{hj}}}

其中η\eta是学习率。

假设我们的损失函数图像如下图所示,我们可以通过梯度下降算法逐步逼近最小值

image-20210429161023196

一个简单的实例——手写数字识别

这里使用的手写数字既是刚开始我们所讲的图片储存方法,只是他没有颜色通道(RGB)只用一个通道28*28个像素点的灰度值[0:255]

例如绘制出来如图所示

image-20210430105300887

这是所有的训练集,共100个数据。

image-20210430104052074

其中一个样本如图所示,第一个数字为实际值,后面的数字就都是图片信息了。

image-20210430105444962

算法难点

这里使用最简单的神经网络模型,也就是只有一层隐含层。且不考虑阈值上一层到下一层只需要乘以权重w

img

前向传播很简单便可以实现,难点便是反向传播中参数的更迭

在这里我们使用上面提到的均方误差作为损失函数

也就是

E(w)=12k=outputs(tkok)2E(\vec{w})=\frac{1}{2}\sum_{k=outputs}(t_k-o_k)^2

权重影响

通过权重来确定改变量

偏导数

为了确定某个权重wjiw_{ji}对误差的影响,我们可以使用偏导数

Ewji\frac{\partial{E}}{\partial{w_{ji}}}

在运用上我们学过的链式求导法则

Ewji=Enetjnetjwji\frac{\partial{E}}{\partial{w_{ji}}}=\frac{\partial{E}}{\partial{net_j}} \cdot \frac{\partial{net_j}}{\partial{w_{ji}}}

我们知道net和w的关系是net=wijxjinet=w_{ij} \cdot x_{ji}所以

netjwji=xji\frac{\partial{net_j}}{\partial{w_{ji}}}=x_{ji}

所以

Ewji=Enetjxji\frac{\partial{E}}{\partial{w_{ji}}}=\frac{\partial{E}}{\partial{net_j}}\cdot x_{ji}

对于输出层单元

Enetj=Eojojnetj\frac{\partial{E}}{\partial{net_j}}=\frac{\partial{E}}{\partial{o_j}}\frac{\partial{o_j}}{\partial{net_j}}

这里我们使用的激活函数是上面提到的Sigmoid函数,他的一个特点是求导

f(x)=f(x)(1f(x))f'(x)=f(x)(1-f(x))

所以

Enetj=(tjoj)0j(1oj)\frac{\partial{E}}{\partial{net_j}}=-(t_j-o_j)0_j(1-o_j)

对于隐层层单元

我们只需

Enetj=Enetknetknetj\frac{\partial{E}}{\partial{net_j}}=\frac{\partial{E}}{\partial{net_k}}\frac{\partial{net_k}}{\partial{net_j}}

其中

netknetj=netkσjσjnetj=wwj0j(1oj)\frac{\partial{net_k}}{\partial{net_j}}=\frac{\partial{net_k}}{\partial{\sigma_j}}\frac{\partial{\sigma_j}}{\partial{net_j}}=w_{wj}\cdot 0_j(1-o_j)

所以

Enetj=oj(1oj)koutputsδkwkj\frac{\partial{E}}{\partial{net_j}}=-o_j(1-o_j)\sum_{k\in outputs}\delta_k w_{kj}

其中

δ=(tjoj)0j(1oj)\delta=(t_j-o_j)0_j(1-o_j)

最后得到权值变化量

Δwji=ηEwji=ηEnetjxji\Delta w_{ji}=-\eta\frac{\partial{E}}{\partial{w_{ji}}}=-\eta\frac{\partial{E}}{\partial{net_j}}\cdot x_{ji}

代码

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
import matplotlib.pyplot as plt
import numpy as np
import scipy.special


class Network:

# 初始化神经网络
def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
# 设置输入层、隐藏层、输出层的神经元数量
self.input_nodes = input_nodes
self.hidden_nodes = hidden_nodes
self.output_nodes = output_nodes

# 设置学习率
self.learning_rate = learning_rate

# 设置初始各层权重
self.w_i_h = np.random.normal(0.0, pow(self.hidden_nodes, -0.5), (self.hidden_nodes, self.input_nodes))
self.w_h_o = np.random.normal(0.0, pow(self.output_nodes, -0.5), (self.output_nodes, self.hidden_nodes))

# 设置激活函数
self.activation_function = lambda x: scipy.special.expit(x)
pass

# 训练模型
def train(self, inputs_list, targets_list):
inputs = np.array(inputs_list, ndmin=2).T
targets = np.array(targets_list, ndmin=2).T

# 获取隐藏层输入和输出
hidden_inputs = np.dot(self.w_i_h, inputs)
hidden_outputs = self.activation_function(hidden_inputs)

# 获取最终输入和输出
final_inputs = np.dot(self.w_h_o, hidden_outputs)
final_outputs = self.activation_function(final_inputs)

# 计算误差
output_errors = targets - final_outputs
hidden_errors = np.dot(self.w_h_o.T, output_errors)

# 更新权重
self.w_h_o += self.learning_rate * np.dot((output_errors * final_outputs * (1 - final_outputs)), np.transpose(hidden_outputs))
self.w_i_h += self.learning_rate * np.dot((hidden_errors * hidden_outputs * (1 - hidden_outputs)), np.transpose(inputs))
pass

def query(self, input_list):
# 输入并预测答案
inputs = np.array(input_list, ndmin=2).T
hidden_inputs = np.dot(self.w_i_h, inputs)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = np.dot(self.w_h_o, hidden_outputs)
final_outputs = self.activation_function(final_inputs)

return final_outputs


# 神经网络参数
input_nodes = 784
hidden_nodes = 150
output_nodes = 10
learning_rate = 0.1

# 建立实例
n = Network(input_nodes, hidden_nodes, output_nodes, learning_rate)

# 读取数据
training_data_file = open('./data/mnist_train_100.csv', 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()

# 设置训练次数
epochs = 5

for e in range(epochs):
for record in training_data_list:
all_values = record.split(',')
inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
targets = np.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
pass
pass

预测

1
2
3
4
5
6
7
8
9
10
def prediction_data(all_data):
all_data = all_data.split(',')
pic = np.asfarray(all_data[1:]).reshape(28, 28)
plt.imshow(pic)
plt.show()
inputs = (np.asfarray(all_data[1:]) / 255.0 * 0.99) + 0.01
outputs = n.query(inputs)
num_index = np.argmax(outputs)
print("这个数字有" + str(outputs[num_index]) + "的概率为: " + str(num_index) + " 正确答案为: " + str(all_data[0]))
pass
1
2
3
4
5
text_data_file = open('./data/mnist_test_10.csv', 'r')
text_data = text_data_file.readlines()
text_data_file.close()

prediction_data(text_data[3])

image-20210429171038649

image-20210429171055553

不过也有预测失误的时候(训练集太小了,100个训练集)。而且这个长得也很像是9

image-20210429171140190

框架

伴随着模型的网络层数的增加,使用手动推导梯度也会变得非常艰难,所以我们可以使用一些框架自动获取梯度,只需定义前向自动后向传播,模型的建立也就变得像搭积木一样。

主流框架有TensorflowPytorch

比如使用pytorch框架创建一个卷积模型

convnet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5) # 卷积: 这里的三个参数为(in_channels, out_channels, kernel_size) 6个3*5*5卷积核, 得到6*(32-5+1=)28*28的下一层
self.pool = nn.MaxPool2d(2, 2) # 池化 2*2 28/2=14
self.conv2 = nn.Conv2d(6, 16, 5) # 卷积: 16个6*5*5的卷积核, 得到16*(14-5+1=)10*10的下一层 池化后变为16*5*5
self.fc1 = nn.Linear(16*5*5, 120) # 全连接层 神经元数量 16*5*5 -> 120
self.fc2 = nn.Linear(120, 84) # 同上 120 -> 84
self.fc3 = nn.Linear(84, 10) # 同上,84 -> 10 十个类别所以最后一层为10 这一次不使用relu激活函数

def forward(self, x):
x = self.pool(F.relu(self.conv1(x))) # 卷积池化
x = self.pool(F.relu(self.conv2(x))) # 卷积池化
x = x.view(-1, 16*5*5) # 将图形变为一行
x = F.relu(self.fc1(x)) # 全连接层 除最后一层都使用relu为激活函数
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x

# 实例化
net = Net()

然后加上损失函数和优化器 训练即可

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
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 遍历数据两次
for epoch in range(2):
running_loss = 0.0 初始化loss

for i, data in enumerate(trainloader, 0):
inputs, lables = data

optimizer.zero_grad() # 初始化梯度

outputs = net(inputs)
loss = criterion(outputs, lables)
loss.backward() # 反向传播
optimizer.step() # 更新梯度

running_loss += loss.item()
# 打印训练状况
if i % 2000 == 1999:
print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss/2000))
running_loss = 0.0

print('finish training')