可视化的重要性
我们虽然得到了结果,但都是一些神秘的数字,我们如何知道它们是正确还是错误的呢?
后面我们会讲到,在实际的工程实践中,一般我们会把样本分成训练集、验证集、测试集,用测试集来测试训练结果的正确性。在本例中我们没有这样做,原因有二:
- 样本数据量比较少,一共只有200个样本,如果再分成两部分,会造成数据集覆盖不全面,存在很大的差异,对训练、验证、测试都没有帮助
- 由于本例的数据特征比较少,所以我们有更好的手段:可视化。在神经网络学习初期,可视化的训练过程与结果会对读者有巨大的帮助。
神经网络的可视化,说简单也很简单,说难也很难,关键是对框架系统的理解,对运行机制和工作原理的理解,掌握了这些,可视化就会使一件轻而易举且令人愉快的事情。
权重值的含义
在上一节中的训练结果如下,这几个关于W,B数字如何解读呢?
W= [[-7.66469954][ 3.15772116]]B= [[2.19442993]]A= [[0.65791301][0.30556477][0.53019727]]
在上一节中我们一起学习了线性二分类的原理,其中提到了如果我们能够根据训练结果,在图上画出一条直线来分割正例和负例两个区域,是不是就很直观了呢?
z = x{1} \cdot w_1 + x{2} \cdot w_2 + b \tag{1}
a=Logistic(z) \tag{2}
对公式2来说,当a大于0.5时,属于正例(属于汉),当a小于0.5时,属于负例(属于楚)。那么a=0.5时,就是楚汉边界啦!
a = 0.5, 相当于z=0
z = x{1} \cdot w_1 + x{2} \cdot w_2 + b = 0
把x2留在等式左侧,其它的挪到右侧去,就可以得到一条直线的方程了:
x{2} \cdot w_2 = -x{1} \cdot w_1 - b
x_2 = -{w_1 \over w_2}x_1 - {b \over w_2} \tag{3}
好了,这就是标准的直线方程y=ax+b的形式了。这个公式等同于二分类原理中的公式7,8。
代码实现
用Python代码实现公式3如下:
def draw_split_line(net):b12 = -net.B[0,0]/net.W[1,0]w12 = -net.W[0,0]/net.W[1,0]print(w12,b12)x = np.linspace(0,1,10)y = w12 * x + b12plt.plot(x,y)plt.axis([-0.1,1.1,-0.1,1.1])plt.show()
上面代码中的计算w12,b12就是根据公式3来的,只不过我们的W的定义是(w1, w2),而python是zero-based,所以: w_1 = W[0,0],w_2 = W[0,1],b = B[0,0]。
同时需要展示样本数据,以便判断分割线和样本数据的吻合程度:
def draw_source_data(net, dataReader):fig = plt.figure(figsize=(6.5,6.5))X,Y = dataReader.GetWholeTrainSamples()for i in range(200):if Y[i,0] == 1:plt.scatter(X[i,0], X[i,1], marker='x', c='g')else:plt.scatter(X[i,0], X[i,1], marker='o', c='r')#end if#end for
最后还可以显示一下三个预测点的位置,看看是否正确:
def draw_predicate_data(net):x = np.array([0.58,0.92,0.62,0.55,0.39,0.29]).reshape(3,2)a = net.inference(x)print("A=", a)for i in range(3):if a[i,0] > 0.5:plt.scatter(x[i,0], x[i,1], marker='^', c='g', s=100)else:plt.scatter(x[i,0], x[i,1], marker='^', c='r', s=100)#end if#end for
主程序:
# 主程序if __name__ == '__main__':......# show resultdraw_source_data(net, reader)draw_predicate_data(net)draw_split_line(net)plt.show()
运行结果
图6-8为二分类结果。

虽然蓝色的分割线大体分开了楚汉两国,但是细心的读者会发现在上下两端,还是绿点在分割线右侧,而红点在分割线左侧的情况。这说明我们的神经网络的训练精度不够。所以,稍微改一下超参,再训练一遍:
params = HyperParameters(eta=0.1, max_epoch=10000, batch_size=10, eps=1e-3, net_type=NetType.BinaryClassifier)
把max_epoch从100改成了10000,再跑一次。

从图6-9的曲线看,损失函数值一直在下降,说明网络还在继续收敛。再看图6-10的直线位置,已经比较完美地分开了红色和绿色区域。

三个三角点是求解问题的三个坐标,其中第三个三角点处于分割线附近,用肉眼不是很容易分得出来,看打印输出:
W= [[-42.62417571][ 21.36558218]]B= [[10.5773054]]A= [[0.99597669][0.01632475][0.53740392]]w12= 1.994992477013739b12= -0.49506282174794675
前两个点的概率分别是0.995和0.016,可以明确地区分正例负例,第三个点是0.537,大于0.5,可以算作正例。
在matplot的绘图控件中,我们也可以放大局部观察,可以图6-11的细节。

第三个点位于左侧正例区域。
好了,我们已经自信满满地找到了解释神经网络工作的原理,有数值计算验证,有公式推导,有图形显示,至少可以自圆其说了。但实际情况是不是这样呢?有没有更高深的原理还没有接触到呢?暂且留下这个问题,留在以后的章节去继续学习。
代码实现
原代码位置:ch06,Level3
个人代码:ShowBinaryResult
keras实现
from keras.layers import Densefrom keras.models import Sequentialfrom keras.callbacks import EarlyStoppingfrom sklearn.preprocessing import StandardScalerfrom HelperClass.DataReader_1_1 import *import matplotlib.pyplot as pltdef get_data():sdr = DataReader_1_1("../data/ch06.npz")sdr.ReadData()X, Y = sdr.GetWholeTrainSamples()ss = StandardScaler()X = ss.fit_transform(X)# Y = ss.fit_transform(Y)return X, Ydef build_model():model = Sequential()model.add(Dense(1, activation='sigmoid', input_shape=(2, )))model.compile(optimizer='SGD', loss='binary_crossentropy')return modeldef plt_loss(history):loss = history.history['loss']epochs = range(1, len(loss) + 1)plt.plot(epochs, loss, 'b', label='Training loss')plt.title('Training loss')plt.xlabel('Epochs')plt.ylabel('Loss')plt.legend()plt.show()def plt_data(X, Y):DrawTwoCategoryPoints(X[:, 0], X[:, 1], Y[:, 0], show=False)def plt_split_line(w, b):b12 = -b[0]/w[1,0]w12 = -w[0,0]/w[1,0]print("w12=", w12)print("b12=", b12)x = np.linspace(-2,2,10)y = w12 * x + b12plt.plot(x,y)plt.axis([-2,2,-2,2])plt.show()def plt_predicate_data(net, threshold=0.5):x = np.array([0.58,0.92,0.62,0.55,0.39,0.29]).reshape(3,2)a = net.predict(x)print("A=", a)DrawTwoCategoryPoints(x[:,0], x[:,1], a[:,0], show=False, isPredicate=True)def DrawTwoCategoryPoints(X1, X2, Y, xlabel="x1", ylabel="x2", title=None, show=False, isPredicate=False):colors = ['b', 'r']shapes = ['s', 'x']assert(X1.shape[0] == X2.shape[0] == Y.shape[0])count = X1.shape[0]for i in range(count):j = (int)(round(Y[i]))if j < 0:j = 0if isPredicate:plt.scatter(X1[i], X2[i], color=colors[j], marker='^', s=200, zorder=10)else:plt.scatter(X1[i], X2[i], color=colors[j], marker=shapes[j], zorder=10)# end forplt.xlabel(xlabel)plt.ylabel(ylabel)if title is not None:plt.title(title)if show:plt.show()if __name__ == '__main__':X, Y = get_data()x = np.array(X)y = np.array(Y)print(x.shape)print(y.shape)print(x)model = build_model()early_stopping = EarlyStopping(monitor='loss', patience=100)history = model.fit(x, y, epochs=1000, batch_size=10, callbacks=[early_stopping])w, b = model.layers[0].get_weights()print(w)print(b)# # inference# x_predicate = np.array([0.58, 0.92, 0.62, 0.55, 0.39, 0.29]).reshape(3, 2)# a = model.predict(x_predicate)# print("A=", a)# 在这里直接进行预测plt_data(X, Y)# plt.show()plt_predicate_data(model)plt_split_line(w, b)plt.show()plt_loss(history)
运行结果
W=[[-5.9363837][ 2.6259723]]B=[0.31348762]A= [[0.32877734][0.12755126][0.22440639]]
分类结果

训练损失

