可视化的重要性

我们虽然得到了结果,但都是一些神秘的数字,我们如何知道它们是正确还是错误的呢?

后面我们会讲到,在实际的工程实践中,一般我们会把样本分成训练集、验证集、测试集,用测试集来测试训练结果的正确性。在本例中我们没有这样做,原因有二:

  1. 样本数据量比较少,一共只有200个样本,如果再分成两部分,会造成数据集覆盖不全面,存在很大的差异,对训练、验证、测试都没有帮助
  2. 由于本例的数据特征比较少,所以我们有更好的手段:可视化。在神经网络学习初期,可视化的训练过程与结果会对读者有巨大的帮助。

神经网络的可视化,说简单也很简单,说难也很难,关键是对框架系统的理解,对运行机制和工作原理的理解,掌握了这些,可视化就会使一件轻而易举且令人愉快的事情。

权重值的含义

上一节中的训练结果如下,这几个关于W,B数字如何解读呢?

  1. W= [[-7.66469954]
  2. [ 3.15772116]]
  3. B= [[2.19442993]]
  4. A= [[0.65791301]
  5. [0.30556477]
  6. [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如下:

  1. def draw_split_line(net):
  2. b12 = -net.B[0,0]/net.W[1,0]
  3. w12 = -net.W[0,0]/net.W[1,0]
  4. print(w12,b12)
  5. x = np.linspace(0,1,10)
  6. y = w12 * x + b12
  7. plt.plot(x,y)
  8. plt.axis([-0.1,1.1,-0.1,1.1])
  9. 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]

同时需要展示样本数据,以便判断分割线和样本数据的吻合程度:

  1. def draw_source_data(net, dataReader):
  2. fig = plt.figure(figsize=(6.5,6.5))
  3. X,Y = dataReader.GetWholeTrainSamples()
  4. for i in range(200):
  5. if Y[i,0] == 1:
  6. plt.scatter(X[i,0], X[i,1], marker='x', c='g')
  7. else:
  8. plt.scatter(X[i,0], X[i,1], marker='o', c='r')
  9. #end if
  10. #end for

最后还可以显示一下三个预测点的位置,看看是否正确:

  1. def draw_predicate_data(net):
  2. x = np.array([0.58,0.92,0.62,0.55,0.39,0.29]).reshape(3,2)
  3. a = net.inference(x)
  4. print("A=", a)
  5. for i in range(3):
  6. if a[i,0] > 0.5:
  7. plt.scatter(x[i,0], x[i,1], marker='^', c='g', s=100)
  8. else:
  9. plt.scatter(x[i,0], x[i,1], marker='^', c='r', s=100)
  10. #end if
  11. #end for

主程序:

  1. # 主程序
  2. if __name__ == '__main__':
  3. ......
  4. # show result
  5. draw_source_data(net, reader)
  6. draw_predicate_data(net)
  7. draw_split_line(net)
  8. plt.show()

运行结果

图6-8为二分类结果。

二分类结果可视化 - 图1

虽然蓝色的分割线大体分开了楚汉两国,但是细心的读者会发现在上下两端,还是绿点在分割线右侧,而红点在分割线左侧的情况。这说明我们的神经网络的训练精度不够。所以,稍微改一下超参,再训练一遍:

  1. params = HyperParameters(eta=0.1, max_epoch=10000, batch_size=10, eps=1e-3, net_type=NetType.BinaryClassifier)

把max_epoch从100改成了10000,再跑一次。

二分类结果可视化 - 图2

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

二分类结果可视化 - 图3

三个三角点是求解问题的三个坐标,其中第三个三角点处于分割线附近,用肉眼不是很容易分得出来,看打印输出:

  1. W= [[-42.62417571]
  2. [ 21.36558218]]
  3. B= [[10.5773054]]
  4. A= [[0.99597669]
  5. [0.01632475]
  6. [0.53740392]]
  7. w12= 1.994992477013739
  8. b12= -0.49506282174794675

前两个点的概率分别是0.995和0.016,可以明确地区分正例负例,第三个点是0.537,大于0.5,可以算作正例。

在matplot的绘图控件中,我们也可以放大局部观察,可以图6-11的细节。

二分类结果可视化 - 图4

第三个点位于左侧正例区域。

好了,我们已经自信满满地找到了解释神经网络工作的原理,有数值计算验证,有公式推导,有图形显示,至少可以自圆其说了。但实际情况是不是这样呢?有没有更高深的原理还没有接触到呢?暂且留下这个问题,留在以后的章节去继续学习。

代码实现

原代码位置:ch06,Level3

个人代码:ShowBinaryResult

keras实现

  1. from keras.layers import Dense
  2. from keras.models import Sequential
  3. from keras.callbacks import EarlyStopping
  4. from sklearn.preprocessing import StandardScaler
  5. from HelperClass.DataReader_1_1 import *
  6. import matplotlib.pyplot as plt
  7. def get_data():
  8. sdr = DataReader_1_1("../data/ch06.npz")
  9. sdr.ReadData()
  10. X, Y = sdr.GetWholeTrainSamples()
  11. ss = StandardScaler()
  12. X = ss.fit_transform(X)
  13. # Y = ss.fit_transform(Y)
  14. return X, Y
  15. def build_model():
  16. model = Sequential()
  17. model.add(Dense(1, activation='sigmoid', input_shape=(2, )))
  18. model.compile(optimizer='SGD', loss='binary_crossentropy')
  19. return model
  20. def plt_loss(history):
  21. loss = history.history['loss']
  22. epochs = range(1, len(loss) + 1)
  23. plt.plot(epochs, loss, 'b', label='Training loss')
  24. plt.title('Training loss')
  25. plt.xlabel('Epochs')
  26. plt.ylabel('Loss')
  27. plt.legend()
  28. plt.show()
  29. def plt_data(X, Y):
  30. DrawTwoCategoryPoints(X[:, 0], X[:, 1], Y[:, 0], show=False)
  31. def plt_split_line(w, b):
  32. b12 = -b[0]/w[1,0]
  33. w12 = -w[0,0]/w[1,0]
  34. print("w12=", w12)
  35. print("b12=", b12)
  36. x = np.linspace(-2,2,10)
  37. y = w12 * x + b12
  38. plt.plot(x,y)
  39. plt.axis([-2,2,-2,2])
  40. plt.show()
  41. def plt_predicate_data(net, threshold=0.5):
  42. x = np.array([0.58,0.92,0.62,0.55,0.39,0.29]).reshape(3,2)
  43. a = net.predict(x)
  44. print("A=", a)
  45. DrawTwoCategoryPoints(x[:,0], x[:,1], a[:,0], show=False, isPredicate=True)
  46. def DrawTwoCategoryPoints(X1, X2, Y, xlabel="x1", ylabel="x2", title=None, show=False, isPredicate=False):
  47. colors = ['b', 'r']
  48. shapes = ['s', 'x']
  49. assert(X1.shape[0] == X2.shape[0] == Y.shape[0])
  50. count = X1.shape[0]
  51. for i in range(count):
  52. j = (int)(round(Y[i]))
  53. if j < 0:
  54. j = 0
  55. if isPredicate:
  56. plt.scatter(X1[i], X2[i], color=colors[j], marker='^', s=200, zorder=10)
  57. else:
  58. plt.scatter(X1[i], X2[i], color=colors[j], marker=shapes[j], zorder=10)
  59. # end for
  60. plt.xlabel(xlabel)
  61. plt.ylabel(ylabel)
  62. if title is not None:
  63. plt.title(title)
  64. if show:
  65. plt.show()
  66. if __name__ == '__main__':
  67. X, Y = get_data()
  68. x = np.array(X)
  69. y = np.array(Y)
  70. print(x.shape)
  71. print(y.shape)
  72. print(x)
  73. model = build_model()
  74. early_stopping = EarlyStopping(monitor='loss', patience=100)
  75. history = model.fit(x, y, epochs=1000, batch_size=10, callbacks=[early_stopping])
  76. w, b = model.layers[0].get_weights()
  77. print(w)
  78. print(b)
  79. # # inference
  80. # x_predicate = np.array([0.58, 0.92, 0.62, 0.55, 0.39, 0.29]).reshape(3, 2)
  81. # a = model.predict(x_predicate)
  82. # print("A=", a)
  83. # 在这里直接进行预测
  84. plt_data(X, Y)
  85. # plt.show()
  86. plt_predicate_data(model)
  87. plt_split_line(w, b)
  88. plt.show()
  89. plt_loss(history)

运行结果

  1. W=[[-5.9363837]
  2. [ 2.6259723]]
  3. B=[0.31348762]
  4. A= [[0.32877734]
  5. [0.12755126]
  6. [0.22440639]]

分类结果

二分类结果可视化 - 图5

训练损失

二分类结果可视化 - 图6