深度学习基础:全连接层

最近补了一下深度学习的基础知识,对全连接层中数据的前向传播和梯度的反向传播有了更深的认识,同时自己用Python实现了一个三层的全连接网络,并将结果可视化出来。


准备工作

全连接的神经网络也叫多层感知机(MLP),前后两层的任意两个神经元都相连,同层的神经元之间没有连接关系,通过如下线性组合后送入一个激活函数进行非线性运算。
$$
Zi = \sum_j W{i,~ j} x_j + b_i
$$
常见的激活函数有SigmoidReLU、双曲正切等,本次试验要实现一个二分类的神经网络,因此采用Sigmoid函数。
$$
Sigmoid=\frac {1}{1+e^{-z}}
$$
Sigmoid函数图像如下所示,它会把任意范围的数映射到0和1之间,我们可以把它的输出当作二分类的概率,当最后一层的输出大于0.5时我们认为它类别为1,小于0.5时我们认为类别为0。

Sigmoid

本试验采用的损失函数(Loss Function)为最简单的二次损失函数,即:
$$
Loss(y,t)=\frac{1}{2}(y-t)^2
$$
其中t表示标签数据,y表示神经网络的输出。


实验数据

本次试验构造一个三层全连接神经网络,第一层为输入层,维度为2;最后一层为输出层,维度为1;隐藏层神经元为超参数,可自由设定。

训练样本共有96个,特征数据为浮点数,标签数据为0或者1,分别表示两类。

训练数据如下:

-0.1097 0.5517 1
0.1097 -0.5517 0
-0.2392 0.5774 1
0.2392 -0.5774 0

可视化如图:

Train Data

测试数据和其相似,只是在坐标上有轻微变化。

实验分析

我简单的画了下前向传播和反向传播的流程,为方便起见这里令隐藏层神经元数目为4个,这个值对后续的代码没有影响。

此处x的每一行都表示一个样本,总的行数表示样本的个数。


代码实现

我们首先来定义全连接层:

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
class FC:
"""Define a fully connected layer"""
def __init__(self, input_dim, output_dim, lr):
self._input_dim = input_dim
self._output_dim = output_dim
self.lr = lr
self.w = np.random.randn(input_dim, output_dim)
self.b = np.zeros(output_dim)
def _sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def forward(self, x):
self.y = self._sigmoid(np.dot(x, self.w) + self.b)
self.x = x
return self.y
def backward(self, gradient):
grad_z = gradient * self.y * (1 - self.y)
grad_w = np.dot(self.x.T, grad_z)
grad_b = grad_z.sum(0)
grad_x = np.dot(grad_z, self.w.T)
self.w -= grad_w * self.lr
self.b -= grad_b * self.lr
return grad_x

注意这里反向传播时,对于参数b的偏导grad_b,我们令它等于z的偏导grad_z在第一维度上的求和。在每次前向传播时,我们都把x的值保留下来,用于反向传播。所有的梯度传播完成之后,再进行参数的更新。


然后我们定义损失函数:

1
2
3
4
5
6
7
8
9
10
11
12
class SquareLoss:
"""Define the loss function"""
def forward(self, output, label):
self.loss = output - label
return np.sum(self.loss * self.loss) / self.loss.shape[0] / 2
def backward(self):
return self.loss
def accuracy(self, output, label):
return (np.around(output) == label).sum() / len(label)


接下来我们就可以开始搭建神经网络了:

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
class Net:
"""Train the model"""
def __init__(self, input_dim, hidden_dim, output_dim, lr):
self.fc1 = FC(input_dim, hidden_dim, lr)
self.fc2 = FC(hidden_dim, output_dim, lr)
self.loss = SquareLoss()
def train(self, train_data, train_label, iter):
for i in xrange(iter):
# forward step
out_fc1 = self.fc1.forward(train_data)
out_fc2 = self.fc2.forward(out_fc1)
out_loss = self.loss.forward(out_fc2, train_label)
# backward step
loss_grad = self.loss.backward()
loss_fc2 = self.fc2.backward(loss_grad)
loss_fc1 = self.fc1.backward(loss_fc2)
if i % 10 == 0:
train_accuracy = self.loss.accuracy(out_fc2, train_label)
print("Iter: {0} Train accuracy: {1}".format(
i, train_accuracy))
def test(self, test_data, test_label):
out_fc1 = self.fc1.forward(test_data)
out_fc2 = self.fc2.forward(out_fc1)
out_loss = self.loss.forward(out_fc2, test_label)
accuracy = self.loss.accuracy(out_fc2, test_label)
return accuracy
def predict(self, predict_data):
out_fc1 = self.fc1.forward(predict_data)
out_fc2 = self.fc2.forward(out_fc1)
out_result = np.around(out_fc2)
return out_result

我们让每10次训练打印一次训练准确率,最终训练完成后用测试数据进行测试,并打印出测试准确率。


下面我们把训练数据和测试数据输入进去开始训练:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def main():
train_set=np.loadtxt(TRAIN_DATA)
test_set=np.loadtxt(TEST_DATA)
train_data = train_set[:, :2]
train_label = train_set[:, 2].reshape((-1, 1))
test_data = test_set[:, :2]
test_label = test_set[:, 2].reshape((-1, 1))
net = Net(2, 10, 1, 0.1)
net.train(train_data, train_label, 5000)
accuracy = net.test(test_data, test_label)
print('Test accuracy: {0}'.format(accuracy))
if __name__ == '__main__':
main()


我们首先定义隐藏层神经元为10个,训练迭代次数为5000次,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Iter: 0 Train accuracy: 0.6041666666666666
Iter: 10 Train accuracy: 0.6458333333333334
Iter: 20 Train accuracy: 0.6458333333333334
...
Iter: 650 Train accuracy: 0.6875
Iter: 660 Train accuracy: 0.6979166666666666
Iter: 670 Train accuracy: 0.6979166666666666
...
Iter: 1530 Train accuracy: 0.9270833333333334
Iter: 1540 Train accuracy: 0.9270833333333334
Iter: 1550 Train accuracy: 0.9375
...
Iter: 1790 Train accuracy: 0.9791666666666666
Iter: 1800 Train accuracy: 0.9895833333333334
Iter: 1810 Train accuracy: 1.0
Iter: 1820 Train accuracy: 1.0
...
Iter: 4980 Train accuracy: 1.0
Iter: 4990 Train accuracy: 1.0
Test accuracy: 1.0

可以看出从1800次迭代之后就已经达到了1的训练准确率,最终的测试数据准确率同样为1。


我们添加一个类Draw来画出整个模型的收敛结果:

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
class Draw:
def __init__(self):
self.x = np.linspace(-5,5,100)
self.y = np.linspace(-5,5,100)
self.X, self.Y = np.meshgrid(self.x,self.y)
self.X_f = self.X.flatten()
self.Y_f = self.Y.flatten()
self.p = zip(self.X_f, self.Y_f)
self.data = list()
for i in self.p:
self.data.append(list(i))
self.data = np.array(self.data)
def draw2D(self, Z):
plt.figure()
plt.scatter(self.X_f,self.Y_f,c=Z)
plt.show()
def main():
...
draw = Draw()
out = net.predict(draw.data)
draw.draw2D(out)
if __name__ == '__main__':
main()

输出如下:

我们把隐藏层神经元改为30,得出的结果:

可以看出隐藏层神经元在30的情况下边界相对更加平滑,而且收敛速度也更快。

完整代码和训练数据可在我的GitHub下载。


参考