代码实现

准备数据

异或数据比较简单,只有4个记录,所以就hardcode在此,不用再建立数据集了。这也给读者一个机会了解如何从DataReader类派生出一个全新的子类XOR_DataReader。

比如在下面的代码中,我们覆盖了父类中的三个方法:

  • init() 初始化方法:因为父类的初始化方法要求有两个参数,代表train/test数据文件
  • ReadData()方法:父类方法是直接读取数据文件,此处直接在内存中生成样本数据,并且直接令训练集等于原始数据集(不需要归一化),令测试集等于训练集
  • GenerateValidationSet()方法,由于只有4个样本,所以直接令验证集等于训练集

因为NeuralNet2中的代码要求数据集比较全,有训练集、验证集、测试集,为了已有代码能顺利跑通,我们把验证集、测试集都设置成与训练集一致,对于解决这个异或问题没有什么影响。

  1. class XOR_DataReader(DataReader):
  2. def ReadData(self):
  3. self.XTrainRaw = np.array([0,0,0,1,1,0,1,1]).reshape(4,2)
  4. self.YTrainRaw = np.array([0,1,1,0]).reshape(4,1)
  5. self.XTrain = self.XTrainRaw
  6. self.YTrain = self.YTrainRaw
  7. self.num_category = 1
  8. self.num_train = self.XTrainRaw.shape[0]
  9. self.num_feature = self.XTrainRaw.shape[1]
  10. self.XTestRaw = self.XTrainRaw
  11. self.YTestRaw = self.YTrainRaw
  12. self.XTest = self.XTestRaw
  13. self.YTest = self.YTestRaw
  14. self.num_test = self.num_train
  15. def GenerateValidationSet(self, k = 10):
  16. self.XVld = self.XTrain
  17. self.YVld = self.YTrain

测试函数

与逻辑与门和或门一样,我们需要神经网络的运算结果达到一定的精度,也就是非常的接近0,1两端,而不是说勉强大于0.5就近似为1了,所以精度要求是误差绝对值小于1e-2。

  1. def Test(dataReader, net):
  2. print("testing...")
  3. X,Y = dataReader.GetTestSet()
  4. A = net.inference(X)
  5. diff = np.abs(A-Y)
  6. result = np.where(diff < 1e-2, True, False)
  7. if result.sum() == dataReader.num_test:
  8. return True
  9. else:
  10. return False

主过程代码

  1. if __name__ == '__main__':
  2. ......
  3. n_input = dataReader.num_feature
  4. n_hidden = 2
  5. n_output = 1
  6. eta, batch_size, max_epoch = 0.1, 1, 10000
  7. eps = 0.005
  8. hp = HyperParameters2(n_input, n_hidden, n_output, eta, max_epoch, batch_size, eps, NetType.BinaryClassifier, InitialMethod.Xavier)
  9. net = NeuralNet2(hp, "Xor_221")
  10. net.train(dataReader, 100, True)
  11. ......

此处的代码有几个需要强调的细节:

  • n_input = dataReader.num_feature,值为2,而且必须为2,因为只有两个特征值
  • n_hidden=2,这是人为设置的隐层神经元数量,可以是大于2的任何整数
  • eps精度=0.005是后验知识,笔者通过测试得到的停止条件,用于方便案例讲解
  • 网络类型是NetType.BinaryClassifier,指明是二分类网络
  • 最后要调用Test函数验证精度

运行结果

经过快速的迭代后,会显示训练过程如图10-10所示。

实现逻辑异或门 - 图1

可以看到二者的走势很理想。

同时在控制台会打印一些信息,最后几行如下:

  1. ......
  2. epoch=5799, total_iteration=23199
  3. loss_train=0.005553, accuracy_train=1.000000
  4. loss_valid=0.005058, accuracy_valid=1.000000
  5. epoch=5899, total_iteration=23599
  6. loss_train=0.005438, accuracy_train=1.000000
  7. loss_valid=0.004952, accuracy_valid=1.000000
  8. W= [[-7.10166559 5.48008579]
  9. [-7.10286572 5.48050039]]
  10. B= [[ 2.91305831 -8.48569781]]
  11. W= [[-12.06031599]
  12. [-12.26898815]]
  13. B= [[5.97067802]]
  14. testing...
  15. 1.0
  16. None
  17. testing...
  18. A2= [[0.00418973]
  19. [0.99457721]
  20. [0.99457729]
  21. [0.00474491]]
  22. True

一共用了5900个epoch,达到了指定的loss精度(0.005),loss_valid是0.004991,刚好小于0.005时停止迭代。

我们特意打印出了A2值,即网络推理结果,如表10-7所示。

表10-7 异或计算值与神经网络推理值的比较

x1 x2 XOR Inference diff
0 0 0 0.0041 0.0041
0 1 1 0.9945 0.0055
1 0 1 0.9945 0.0055
1 1 0 0.0047 0.0047

表中第四列的推理值与第三列的XOR结果非常的接近,继续训练的话还可以得到更高的精度,但是一般没这个必要了。由此我们再一次认识到,神经网络只可以得到无限接近真实值的近似解。

代码位置

原代码位置:ch10, Level1

个人代码:XorGateClassifier**

keras实现

  1. from XorGateClassifier import *
  2. from keras.models import Sequential
  3. from keras.layers import Dense
  4. import os
  5. os.environ['KMP_DUPLICATE_LIB_OK']='True'
  6. def load_data():
  7. dataReader = XOR_DataReader()
  8. dataReader.ReadData()
  9. x_train, y_train = dataReader.XTrain, dataReader.YTrain
  10. return x_train, y_train
  11. def build_model():
  12. model = Sequential()
  13. model.add(Dense(2, activation='sigmoid', input_shape=(2, )))
  14. model.add(Dense(1, activation='sigmoid'))
  15. model.compile(optimizer='Adam',
  16. loss='binary_crossentropy',
  17. metrics=['accuracy'])
  18. return model
  19. #画出训练过程中训练和验证的精度与损失
  20. def draw_train_history(history):
  21. plt.figure(1)
  22. # summarize history for accuracy
  23. plt.subplot(211)
  24. plt.plot(history.history['accuracy'])
  25. plt.plot(history.history['val_accuracy'])
  26. plt.title('model accuracy')
  27. plt.ylabel('accuracy')
  28. plt.xlabel('epoch')
  29. plt.legend(['train', 'validation'], loc='upper left')
  30. # summarize history for loss
  31. plt.subplot(212)
  32. plt.plot(history.history['loss'])
  33. plt.plot(history.history['val_loss'])
  34. plt.title('model loss')
  35. plt.ylabel('loss')
  36. plt.xlabel('epoch')
  37. plt.legend(['train', 'validation'], loc='upper left')
  38. plt.show()
  39. if __name__ == '__main__':
  40. x_train, y_train = load_data()
  41. model = build_model()
  42. history = model.fit(x_train, y_train, epochs=5000, batch_size=1, validation_data=(x_train, y_train))
  43. draw_train_history(history)
  44. loss, accuracy = model.evaluate(x_train, y_train)
  45. print("test loss: {}, test accuracy: {}".format(loss, accuracy))
  46. weights = model.get_weights()
  47. print("weights: ", weights)

程序输出

  1. test loss: 0.48245617747306824, test accuracy: 0.75
  2. weights: [array([[ 2.7659676, -8.135253 ],
  3. [ 2.790494 , -8.361388 ]], dtype=float32), array([2.6930966, 1.8859061], dtype=float32), array([[ 0.21971573],
  4. [-5.7732844 ]], dtype=float32), array([0.45315525], dtype=float32)]

损失函数以及精确率曲线

实现逻辑异或门 - 图2