深度学习初探一——奇偶数学习

奇偶数判断,这个是不是太简单了,不就一个数去对2取取余数,如果余数为0,那么这个数为偶数,否则,这个数为奇数,用Python写的话代码就像这样:

def fun(number):
    if number % 2 == 0:
        print "这是偶数"
    else:
        print "这是奇数"

但是,这个判断法则是我们知道的,我们人为的在编程中将判断规则告诉计算机,通过计算来判断一个数是奇数还是偶数。如果我们要只告诉计算机一些数中那些是奇数,那些是偶数,然后让它自己判断新的数据属于偶数还是奇数的话,你要怎么编程呢?这不,最近闲来无视,看了看深度学习的书,突然想试一下,用深度神经网络让计算机去学习奇偶数的特点,然后去判断新的数据是奇数还是偶数。

现在我们来讲重点!!!如何用深度神经网络来让计算机学习判断一个数的奇偶性。

First Step

我们要训练一个神经网络,总要有数据吧?现在,我们就来创造一些训练数据集,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import sklearn.preprocessing as pp

def load_data(number=10001):
temp = [i for i in range(1, number * 2)]
data, labels = [], []
for i in temp:
if i % 2 == 0:
data.append([i, i % 10, 1])
else:
data.append([i, i % 10, 0])
seclar = pp.MinMaxScaler(feature_range=(0, 1))
data = seclar.fit_transform(data)
np.random.shuffle(data)
print data
labels = data[:, -1]
return data, labels

if __name__ == '__main__':
load_data()

我们在这个py文件中导入了numpy包和sklearn包,这两个包是在深度学习中常用的到,numpy用于数据的处理方面,sklearn是一个机器学习模块。我们先通过python自带的列表生成式生成数据范围在[1,number]的list数据类型,然后通过np.array()将我们list对象转为numpy的核心对象ndarry,同时,我们也为每一个数生成标签,如果是偶数,那么这个数的labels是1,否则为0,这样我们就完成了数据集的构建,接下来我们看到这几段代码:

1
2
3
seclar = pp.MinMaxScaler(feature_range=(0, 1))
data = seclar.fit_transform(data)
np.random.shuffle(data)

这几段代码什么意思呢?我们简要分析下:

1
seclar = pp.MinMaxScaler(feature_range=(0, 1))

这行代码是设置了归一化,我们对numpy.ndarry的同一列数据进行归一化操作,所谓归一化就是将所有数据缩小到[0,1]的范围内,那么归一化的数学公式呢?看下图:

1
new_num = (old_num - min_value) / (max_value - min_value)

这就是归一化前和归一化后的区别:

Screenshot from 2018-01-01 01-24-51

为什么要进行归一化呢?这里引用这篇博客(数据标准化和归一化)的原文:

归一化是一种简化计算的方式,即将有量纲的表达式,经过变换,化为无量纲的表达式,成为纯量。归一化是为了加快训练网络的收敛性,可以不进行归一化处理

归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概率分布,归一化在-1–+1之间是统计的坐标分布。归一化有同一、统一和合一的意思。无论是为了建模还是为了计算,首先基本度量单位要同一,神经网络是以样本在事件中的统计分别几率来进行训练(概率计算)和预测的,归一化是同一在0-1之间的统计概率分布;SVM是以降维后线性划分距离来分类和仿真的,因此时空降维归一化是统一在-1–+1之间的统计坐标分布。

现在,我们还需要将每一个数的标签单独提取出来,最为结果用于训练模型与模型预测结果比对:

1
labels = data[:, 1]

这样,我们的前期工作就准备完毕了

Second Step

这一步,我们就开始来构建我们的神经网络模型了,读者可以先去keras中文了解下tensorflow高级封装的深度学习框架,这个框架适合快速上手深度学习,但是还是要回归tensorflow。

1
2
3
4
from keras.layers import Dense, Dropout
from keras.models import Sequential
import matplotlib.pyplot as plt
import load_data

我们先导入相关的包,这是等等构建模型需要用到的,像全连接层:Dense、按一定概率随机断开神经元:Dropout、序贯模型:Sequential、绘图类:pyplot以及上一步我们的创建数据的包:load_data

先放上构建模型的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def built_model():
model = Sequential()
# first layers
model.add(Dense(units=256,
input_dim=2,
activation='relu'))
model.add(Dropout(0.5))

# second layers
model.add(Dense(units=128,
activation='relu'))
model.add(Dropout(0.5))

# third layers
model.add(Dense(units=1,
activation='sigmoid'))

model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
return model

我们这个模型有三层神经网络——三个全连接层(Dense),每个全连接层加了Dropout层,Dense层的作用我在上一篇文章《记一次竞赛题目有感——图像识别》 中解释了下Dense层的作用,但是对于Dropout层的作用我只是说了下防止模型过拟合情况,那么怎么防止这个情况呢?看来还要从全连接层中说起,首先我先放上这个模型的图示以及之前没说过的全连接层的函数表达形式:

学习奇偶数的模型图示

Screenshot from 2017-12-31 19-21-00

y = ∑i (wi * xi) + b 这个模型的第一、二层全连接层函数表达式

由于图的问题,全连接层的神经元个数我并没有全部画出来来,因为全部画出来有点小多……这里就是一个简要的模型图,我们可以分清楚的看出我们的模型输入数据维度是二维的,表示我们对一个数字提取了两个特征,然后就是熟悉的全连接层的操作:每个神经元与下一层的所用神经元都有连接;接下来,我们看到在第一层与第二层的全连接层中,标注了一个记号——Dropout层作用的地方,没错,Dropout层作用的地方就是在每两层Dense层之间;为什么作用在这里?因为上一层的每个神经元与下一层的所有神经元都会连接,每个连接之间会有一个权值wi,连接数量越多,权值wi数量也会越多,而权值wi的取值其实就是这个模型的函数的待定系数取值,这个函数的含义其实就是去尽可能的描述一个数与奇偶性的关系,如果说权值wi越多,那么抓住的信息也就越多,就越能学习之间的关系,但是这不就是我们想要的吗?我们在把视线回到模型的开始之处——输入特征向量矩阵,整个模型的学习,是通过给的训练数据和对应答案来学习这二者之间的关系的,如果说我们的权值wi越多,其实学习到的关系是仅仅训练数据与答案之间的关系,相当于我们限定了自变量x的范围,我们在一个闭区间中让我们的模型去学习,那么最终,我们的模型学到的东西,也仅仅局限于这个闭区间中的内容,如果数据不在这个区间呢?那么模型就没办法处理了,这就是我们所说的模型过拟合问题,其原因就是全连接层之间神经元连接数太多导致权值wi数量也相应的增加。那么我们就想到能不能减少神经元之间的连接数量?这个工作的实现就是Dropout层来实现的,Dropout层的工作是一定几率的将某些神经元之间的连接断开(也就是让输入为0),这样我们就减少了权值wi的个数,也就一定程度上防止了我们的模型过拟合。

最终完整代码

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
from keras.layers import Dense, Dropout
from keras.models import Sequential
import matplotlib.pyplot as plt
import load_data

def built_model():
model = Sequential()
model.add(Dense(units=256,
input_dim=2,
activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=128,
activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units=1,
activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
return model

def train_model(batch_size=32, verbose=2, validation_split=0.2, model=None):
data, labels = load_data.load_data()
train_x, train_y = data[: int(len(data) * 0.7), 0: 2], labels[: int(len(labels) * 0.7)]
test_x, test_y = data[int(len(data) * 0.7): len(data), 0: 2], labels[int(len(labels) * 0.7): len(labels)]
if model is None:
model = built_model()
history = model.fit(train_x, train_y,
batch_size=batch_size,
epochs=10,
verbose=verbose,
validation_split=validation_split)
print "刻画损失函数的变化趋势"
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='valid')
plt.legend()
plt.show()
print "模型构建成功,开始预测数据"
score = model.evaluate(test_x, test_y,
batch_size=batch_size)
print score
print "画图"
predicted = model.predict(test_x,
batch_size=batch_size,
verbose=0)
rounded = [round(w) for w in predicted]
plt.scatter(test_y, list(range(len(test_y))), marker='+')
plt.scatter(rounded, list(range(len(predicted))), marker='*')
plt.show()

if __name__ == '__main__':
train_model()

损失函数值的变化:

loss

YouTube站点查看模型构建视频:

[youtube https://www.youtube.com/watch?v=ZcS0lMsmKMY&w=560&h=315]

哔哩哔哩站点查看模型构建视频:

https://www.bilibili.com/video/av17838797/

源代码地址:

https://github.com/chuntaojun/Deeplearning/tree/master/Parity%20learning