神经网络中你需要多少层?
深度学习正在蓬勃发展。现在,随着Keras等高级神经网络API的推出,运行深度神经网络和解决复杂分类问题变得非常简单。
在这篇文章中,我想问一下简单的可分离特征分类所需的神经元和图层的最小数量是多少。虽然这不一定是一个新问题,但我会用Keras探讨这个问题的一些有趣方面。
如果您想知道,一般来说,答案是1层网络可以表示半平面(即AND或OR逻辑),2层网络可以将任意数量的任意线内的点分类,而3层网络可以分类任意尺寸的任意形状。因此,2层模型可以分类任何凸集,而3层模型可以分类任何数量的不连续的凸或凹形状。
但是现在请允许我深入探索一些具体但简单的例子。我们首先加载Keras,numpy和matplotlib。
from keras import models
from keras import layers
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(2018)
分类是关于描述数据的。实际上,数据是一种排序的测量数据,我们无法知道数据是如何生成的。想象一下,我们拥有的数据是以蓝色显示的数据。一个特定的多项式函数产生了这些数据(参见下面的python代码),但是说我们不知道那个。我们的目标是通过一些功能以最好的方式估算数据。想象一下,我们唯一可用的工具是一条直线(见下面的黑色),我们只能自由地优化斜率和截距。在这种情况下,我们无法正确拟合或描述具有最小误差的数据(即零均方误差)。线性函数不能表示生成数据的波动。
x=np.linspace(0,1,100)
y = 1 + x - x**2 + 10*x**3 - 10*x**4
fit = np.polyfit(x,y,1)
fit_fn = np.poly1d(fit)
# fit_fn is now a function which takes in x and returns an estimate for y
plt.figure(figsize=(20,10))
plt.plot(x,y,'ob',linewidth=2)
plt.plot(x, fit_fn(x),'k',linewidth=2)
plt.title('Polynomial and linear fit',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()
这篇文章是关于描述数据或“过拟合”的,但是,这里,过拟合是一件好事,我提出的问题是函数需要代表数据的可变性的最小数量的特征。一次,我们知道我们需要什么样的功能,我们可以在优化过程中添加约束,并确保我们不代表噪音或过多的不良情况。换句话说,如果我们没有正确的工具(即,我们不知道使用什么函数),偏差 - 方差权衡的微妙过程就无法开始,这就是这篇文章要关注的内容。
接下来,我会问一个神经网络需要多少个神经元和多少个层来分类简单的数据集。我将从简单的数据集开始,我们想要对AND和OR数据进行分类,并将继续使用Keras来检查复杂的异或,最后是two-moon分类问题。
以下是我稍后将用于创建数据集的一些常量。
# constants
npts = 100 # points per blob
tot_npts = 4*npts # total number of points
s = 0.005 # ~standard deviation
sigma = np.array([[s, 0], [0, s]]) #cov matrix
AND问题
AND问题很简单。正如您在下面看到的,数据聚集在四个区域:[0,0],[0,1],[1,0]和[1,1]。当我们对每对应用AND逻辑函数时,[0,0] = 0,[0,1] = 0,[1,0] = 0,但[1,1] = 1。当两个点都等于一时,我们将数据对标记为一个(蓝色)。否则,我们将数据标记为零(红色)。
#生成数据
DATA1 = np.random.multivariate_normal([0,0],SIGMA,NPTS)
DATA2 = np.random.multivariate_normal([0,1],SIGMA,NPTS)
DATA3 = np.random.multivariate_normal([1,(data1, data2, data3, data4 ))#data and_labels = np.concatenate((0),sigma,npts)
data4 = np.random.multivariate_normal([1,1],sigma,npts)
and_data = np.concatenate
np.ones((3 * npts)),np.zeros((npts))))#labels
print(and_data.shape)
print(and_labels.shape)
plt.figure(figsize =(20,10))
plt.scatter (and_data [:,0] [and_labels == 0],and_data [:,1] [and_labels == 0],c ='b')
plt.scatter(and_data [:,0] [and_labels == 1], and_data [:,1] [and_labels == 1],c ='r')
plt.plot()
plt.title('AND problem',fontsize = 20)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
plt.grid('on')
plt.show()
(400, 2)
(400,)
分离AND数据很容易。一条直线可以将数据分成蓝色和红色。
Linear line由Wx + b表示(其中W是斜率,b是偏差),并且在神经网络世界中表示为np.dot(W,x)+ b。因此,具有一个神经元(即,一条直线)的一个层就足以分离AND数据。
下面你可以看到我的Keras实现了一层神经网络,一个神经元和一个S形激活。作为损失函数,我选择了带有Adam优化器的binary_crossentropy。在batch_size为16的情况下迭代数据,在整个数据迭代大约100次后,该模型收敛到正确的解决方案,通过精度测量。
model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(and_data,
and_labels,
epochs=200,
batch_size=16,
verbose=0)
history_dict = history.history
history_dict.keys()
plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict['loss']
#val_loss_values = history_dict['val_loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
#plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.subplot(122)
acc_values = history_dict['acc']
#val_acc_values = history_dict['val_acc']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, acc_values, 'bo', label='Training acc')
#plt.plot(epochs, val_acc_values, 'b', label='Validation acc')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
______________________________________________
Layer (type) Output Shape Param #
============================================
dense_1 (Dense) (None, 1) 3
============================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
______________________________________________
OR问题
OR问题也很简单。再次,数据聚集在四个区域:[0,0],[0,1],[1,0]和[1,1]。如前所述,我们将OR逻辑函数应用于每对。它遵循[0,0] = 0,但[0,1] = 1,[1,0] = 1和[1,1] = 1。只有当两个点都等于零时,我们才将数据对标记为零(红色)。否则,我们将数据标记为一个(蓝色)。
#生成数据
DATA1 = np.random.multivariate_normal([0,0],SIGMA,NPTS)
DATA2 = np.random.multivariate_normal([0,1],SIGMA,NPTS)
DATA3 = np.random.multivariate_normal([1, 0],sigma,npts)
data4 = np.random.multivariate_normal([1,1],sigma,npts)
or_data = np.concatenate((data1,data2,data3,data4))
or_labels = np.concatenate((np.(npts)),np.zeros((3 * npts))))
plt.figure(figsize =(20,10))
plt.scatter(or_data [:,0] [or_labels == 0],or_data [ :,1] [or_labels == 0],c ='b')
plt.scatter(or_data [:,0] [or_labels == 1],or_data [:,1] [or_labels == 1],c =' r')
plt.title('OR problem',fontsize = 20)
plt.xticks(fontsize = 20)
plt.yticks(fontsize = 20)
plt.grid('on')
plt.show()
分离这些数据也很简单。对于AND数据,一条直线就足够了,与之前一个神经网络有一层和一个神经元是最小的模型一样,我们需要对数据进行正确的分离或分类。使用与AND问题相同的体系结构,您可以看到该模型经过约300次迭代后收敛到正确的解决方案。作为一个简要说明,让我仅仅提到迭代次数对于这篇文章并不重要,因为我们只是寻找一个可以产生100%准确性的模型。
model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(or_data,
or_labels,
epochs=400,
batch_size=16,
verbose=0)
history_dict = history.history
history_dict.keys()
plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict['loss']
#val_loss_values = history_dict['val_loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
#plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.subplot(122)
acc_values = history_dict['acc']
#val_acc_values = history_dict['val_acc']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, acc_values, 'bo', label='Training acc')
#plt.plot(epochs, val_acc_values, 'b', label='Validation acc')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
______________________________________________
Layer (type) Output Shape Param #
============================================
dense_2 (Dense) (None, 1) 3
=============================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
________________________________________________
XOR问题
XOR问题有点困难。同样,数据点聚集在四个区域,我们将XOR逻辑功能应用于每对。对于XOR逻辑,结果是[0,0] = 0,[1,1] = 0但[0,1] = 1,[1,0] = 1。
# Generate Data
data1 = np.random.multivariate_normal( [0,0], sigma, npts)
data2 = np.random.multivariate_normal( [0,1], sigma, npts)
data3 = np.random.multivariate_normal( [1,0], sigma, npts)
data4 = np.random.multivariate_normal( [1,1], sigma, npts)
xor_data = np.concatenate((data1, data4, data2, data3))
xor_labels = np.concatenate((np.ones((2*npts)),np.zeros((2*npts))))
plt.figure(figsize=(20,10))
plt.scatter(xor_data[:,0][xor_labels==0], xor_data[:,1][xor_labels==0],c='b')
plt.scatter(xor_data[:,0][xor_labels==1], xor_data[:,1][xor_labels==1],c='r')
plt.title('XOR problem',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
问题是没有一条直线可以正确分离数据。然而,如果作为第一步,我们使用两条直线分别分离[0,0]和[1,1],那么作为第二步,我们可以将AND函数应用于两个分离,重叠区域为我们提供了正确的分类。因此,需要两步解决方案:第一个应用两条线性线,第二个解决方案使用AND逻辑联合两个分离线。换言之,最小网络是一个双层神经网络,其中第一个必须有两个神经元(即两条线性线),第二个只有一个(即应用AND逻辑,在我们证明这只需要一个神经元之前)。
model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xor_data,
xor_labels,
epochs=400,
batch_size=32,
verbose=0)
history_dict_10 = history.history
model = models.Sequential()
model.add(layers.Dense(1, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xor_data,
xor_labels,
epochs=400,
batch_size=32,
verbose=0)
history_dict_11 = history.history
model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xor_data,
xor_labels,
epochs=400,
batch_size=32,
verbose=0)
history_dict_21 = history.history
history = model.fit(xor_data,
xor_labels,
epochs=400,
batch_size=32,
verbose=0)
history_dict_41 = history.history
为了测试具有两个和一个神经元(表示为2_1)的两层的最小集合,我还运行了两个Keras实现,其具有较少数量的层和神经元。事实上,你可以看到,2_1模型(two layers with two and one neurons)收敛到正确的解,而1_0(one layer with one neuron)和1_1模型(two layers with one and one neurons)接近数据很好但从未收敛到100%的准确度。所以,虽然它不是一个涵盖分类所需的最小集合的所有方面的形式化证明,但它应该给你足够的实践直觉,以便为什么最小集合只需要两个和一个神经元的两个层。
plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict_10['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_10['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_11['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_21['loss'], 'o', label='Training loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(['1_0','1_1','2_1','4_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.subplot(122)
acc_values = history_dict_10['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_10['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_11['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_21['acc'], 'o', label='Training loss')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(['1_0','1_1','2_1','4_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
two-moon问题
两个月亮问题是众所周知的。具有月亮形状的数据点彼此面对,同时稍微水平移动。花一点时间思考 - 分离月亮所需的神经元和层的最小数量是多少?
import sklearn.datasets as sk
tm_data,tm_labels = sk.make_moons(n_samples = 400,shuffle = True,noise = 0.05,random_state = 0)
print(tm_data.shape)
print(tm_labels.shape)
(400, 2)
(400,)
plt.figure(figsize =(20,10))
plt.scatter(tm_data [:,0] [tm_labels == 0],tm_data [:,1] [tm_labels == 0],c ='b')
plt。 scatter(tm_data [:,0] [tm_labels == 1],tm_data [:,1] [tm_labels == 1],c ='r')
plt.title('Two-moon problem',fontsize = 20)
plt .txt(fontsize = 20)
plt.yticks(fontsize = 20)
plt.grid('on')
plt.show()
在这一点上,我建议你拿一张纸,并尝试仅使用直线将红色月亮与蓝色月亮隔离。需要的最少直线数是多少?
给它几分钟,很快你会发现需要四条线:红色(或蓝色)月亮的一端附近有两条线,红色(或蓝色)月亮的另一端有两条线。所以,第一步是放置四条线。第二步是将AND逻辑应用到每个边缘对(即,取重叠区域) - 这形成两个三角形,每个都覆盖大约一半的月亮。第三步是将OR逻辑应用于两个三角形(即将两个三角形合并),以将红色月亮与蓝色完全隔离。总体而言,最小网络使用三层 - 第一层使用四个神经元,第二层使用两层,第三层使用三层。
e_num=1000
bs_num=32
model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict = history.history
model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict_21 = history.history
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict_41 = history.history
model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict_221 = history.history
model = models.Sequential()
model.add(layers.Dense(3, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict_321 = history.history
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data,
tm_labels,
epochs=e_num,
batch_size=bs_num,
verbose=0)
history_dict_421 = history.history
如前所述,为了测试具有四个,两个和一个神经元(表示为4_2_1)的三层最小集合,我还运行了几个Keras实现,其具有较少数量的层和神经元。很容易看出,只有4_2_1模型达到100%的准确性,而2_1,4_1,2_2_1,3_2_1网络接近数据,但从未收敛到100%的准确度。
plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict_21['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_21['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_41['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_221['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_321['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_421['loss'], 'o', label='Training loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(['2_1','4_1','2_2_1','3_2_1','4_2_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.subplot(122)
acc_values = history_dict_21['loss']
epochs = range(1, len(acc_values) + 1)
plt.plot(epochs, history_dict_21['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_41['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_221['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_321['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_421['acc'], 'o', label='Training loss')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(['2_1','4_1','2_2_1','3_2_1','4_2_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
总而言之,使用神经网络分类允许非常复杂的方式来合成线性函数并分类非凸数据。虽然运行模型和正确分类数据很容易,但我们希望确保我们理解并了解模型在幕后做了些什么。