You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

HED.rst 11 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. Handwritten Equation Decipherment (HED)
  2. =======================================
  3. Below shows an implementation of `Handwritten Equation
  4. Decipherment <https://proceedings.neurips.cc/paper_files/paper/2019/file/9c19a2aa1d84e04b0bd4bc888792bd1e-Paper.pdf>`__.
  5. In this task, the handwritten equations are given, which consist of
  6. sequential pictures of characters. The equations are generated with
  7. unknown operation rules from images of symbols (‘0’, ‘1’, ‘+’ and ‘=’),
  8. and each equation is associated with a label indicating whether the
  9. equation is correct (i.e., positive) or not (i.e., negative). Also, we
  10. are given a knowledge base which involves the structure of the equations
  11. and a recursive definition of bit-wise operations. The task is to learn
  12. from a training set of above mentioned equations and then to predict
  13. labels of unseen equations.
  14. Intuitively, we first use a machine learning model (learning part) to
  15. obtain the pseudo-labels (‘0’, ‘1’, ‘+’ and ‘=’) for the observed
  16. pictures. We then use the knowledge base (reasoning part) to perform
  17. abductive reasoning so as to yield ground hypotheses as possible
  18. explanations to the observed facts, suggesting some pseudo-labels to be
  19. revised. This process enables us to further update the machine learning
  20. model.
  21. .. code:: ipython3
  22. # Import necessary libraries and modules
  23. import os.path as osp
  24. import torch
  25. import torch.nn as nn
  26. import matplotlib.pyplot as plt
  27. from datasets import get_dataset, split_equation
  28. from models.nn import SymbolNet
  29. from abl.learning import ABLModel, BasicNN
  30. from reasoning import HedKB, HedReasoner
  31. from abl.data.evaluation import ReasoningMetric, SymbolAccuracy
  32. from abl.utils import ABLLogger, print_log
  33. from bridge import HedBridge
  34. Working with Data
  35. -----------------
  36. First, we get the datasets of handwritten equations:
  37. .. code:: ipython3
  38. total_train_data = get_dataset(train=True)
  39. train_data, val_data = split_equation(total_train_data, 3, 1)
  40. test_data = get_dataset(train=False)
  41. The dataset are shown below:
  42. .. code:: ipython3
  43. true_train_equation = train_data[1]
  44. false_train_equation = train_data[0]
  45. print(f"Equations in the dataset is organized by equation length, " +
  46. f"from {min(train_data[0].keys())} to {max(train_data[0].keys())}")
  47. print()
  48. true_train_equation_with_length_5 = true_train_equation[5]
  49. false_train_equation_with_length_5 = false_train_equation[5]
  50. print(f"For each euqation length, there are {len(true_train_equation_with_length_5)} " +
  51. f"true equation and {len(false_train_equation_with_length_5)} false equation " +
  52. f"in the training set")
  53. true_val_equation = val_data[1]
  54. false_val_equation = val_data[0]
  55. true_val_equation_with_length_5 = true_val_equation[5]
  56. false_val_equation_with_length_5 = false_val_equation[5]
  57. print(f"For each euqation length, there are {len(true_val_equation_with_length_5)} " +
  58. f"true equation and {len(false_val_equation_with_length_5)} false equation " +
  59. f"in the validation set")
  60. true_test_equation = test_data[1]
  61. false_test_equation = test_data[0]
  62. true_test_equation_with_length_5 = true_test_equation[5]
  63. false_test_equation_with_length_5 = false_test_equation[5]
  64. print(f"For each euqation length, there are {len(true_test_equation_with_length_5)} " +
  65. f"true equation and {len(false_test_equation_with_length_5)} false equation " +
  66. f"in the test set")
  67. Out:
  68. .. code:: none
  69. :class: code-out
  70. Equations in the dataset is organized by equation length, from 5 to 26
  71. For each euqation length, there are 225 true equation and 225 false equation in the training set
  72. For each euqation length, there are 75 true equation and 75 false equation in the validation set
  73. For each euqation length, there are 300 true equation and 300 false equation in the test set
  74. As illustrations, we show four equations in the training dataset:
  75. .. code:: ipython3
  76. true_train_equation_with_length_5 = true_train_equation[5]
  77. true_train_equation_with_length_8 = true_train_equation[8]
  78. print(f"First true equation with length 5 in the training dataset:")
  79. for i, x in enumerate(true_train_equation_with_length_5[0]):
  80. plt.subplot(1, 5, i+1)
  81. plt.axis('off')
  82. plt.imshow(x.squeeze(), cmap='gray')
  83. plt.show()
  84. print(f"First true equation with length 8 in the training dataset:")
  85. for i, x in enumerate(true_train_equation_with_length_8[0]):
  86. plt.subplot(1, 8, i+1)
  87. plt.axis('off')
  88. plt.imshow(x.squeeze(), cmap='gray')
  89. plt.show()
  90. false_train_equation_with_length_5 = false_train_equation[5]
  91. false_train_equation_with_length_8 = false_train_equation[8]
  92. print(f"First false equation with length 5 in the training dataset:")
  93. for i, x in enumerate(false_train_equation_with_length_5[0]):
  94. plt.subplot(1, 5, i+1)
  95. plt.axis('off')
  96. plt.imshow(x.squeeze(), cmap='gray')
  97. plt.show()
  98. print(f"First false equation with length 8 in the training dataset:")
  99. for i, x in enumerate(false_train_equation_with_length_8[0]):
  100. plt.subplot(1, 8, i+1)
  101. plt.axis('off')
  102. plt.imshow(x.squeeze(), cmap='gray')
  103. plt.show()
  104. Out:
  105. .. code:: none
  106. :class: code-out
  107. First true equation with length 5 in the training dataset:
  108. .. image:: ../img/hed_dataset1.png
  109. :width: 300px
  110. .. code:: none
  111. :class: code-out
  112. First true equation with length 8 in the training dataset:
  113. .. image:: ../img/hed_dataset2.png
  114. :width: 480px
  115. .. code:: none
  116. :class: code-out
  117. First false equation with length 5 in the training dataset:
  118. .. image:: ../img/hed_dataset3.png
  119. :width: 300px
  120. .. code:: none
  121. :class: code-out
  122. First false equation with length 8 in the training dataset:
  123. .. image:: ../img/hed_dataset4.png
  124. :width: 480px
  125. Building the Learning Part
  126. --------------------------
  127. To build the learning part, we need to first build a machine learning
  128. base model. We use SymbolNet, and encapsulate it within a ``BasicNN``
  129. object to create the base model. ``BasicNN`` is a class that
  130. encapsulates a PyTorch model, transforming it into a base model with an
  131. sklearn-style interface.
  132. .. code:: ipython3
  133. # class of symbol may be one of ['0', '1', '+', '='], total of 4 classes
  134. cls = SymbolNet(num_classes=4)
  135. loss_fn = nn.CrossEntropyLoss()
  136. optimizer = torch.optim.RMSprop(cls.parameters(), lr=0.001, weight_decay=1e-4)
  137. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  138. base_model = BasicNN(
  139. cls,
  140. loss_fn,
  141. optimizer,
  142. device,
  143. batch_size=32,
  144. num_epochs=1,
  145. stop_loss=None,
  146. )
  147. However, the base model built above deals with instance-level data
  148. (i.e., individual images), and can not directly deal with example-level
  149. data (i.e., a list of images comprising the equation). Therefore, we
  150. wrap the base model into ``ABLModel``, which enables the learning part
  151. to train, test, and predict on example-level data.
  152. .. code:: ipython3
  153. model = ABLModel(base_model)
  154. Building the Reasoning Part
  155. ---------------------------
  156. In the reasoning part, we first build a knowledge base. As mentioned
  157. before, the knowledge base in this task involves the structure of the
  158. equations and a recursive definition of bit-wise operations, which are
  159. defined in Prolog file ``examples/hed/reasoning/BK.pl``
  160. and ``examples/hed/reasoning/learn_add.pl``, respectively.
  161. Specifically, the knowledge about the structure of equations is a set of DCG
  162. rules recursively define that a digit is a sequence of ‘0’ and ‘1’, and
  163. equations share the structure of X+Y=Z, though the length of X, Y and Z
  164. can be varied. The knowledge about bit-wise operations is a recursive
  165. logic program, which reversely calculates X+Y, i.e., it operates on
  166. X and Y digit-by-digit and from the last digit to the first.
  167. The knowledge base is already built in ``HedKB``.
  168. ``HedKB`` is derived from class ``PrologKB``, and is built upon the aformentioned Prolog
  169. files.
  170. .. code:: ipython3
  171. kb = HedKB()
  172. .. note::
  173. Please notice that, the specific rules for calculating the
  174. operations are undefined in the knowledge base, i.e., results of ‘0+0’,
  175. ‘0+1’ and ‘1+1’ could be ‘0’, ‘1’, ‘00’, ‘01’ or even ‘10’. The missing
  176. calculation rules are required to be learned from the data. Therefore,
  177. ``HedKB`` incorporates methods for abducing rules from data. Users
  178. interested can refer to the specific implementation of ``HedKB`` in
  179. ``examples/hed/reasoning/reasoning.py``
  180. Then, we create a reasoner. Due to the indeterminism of abductive
  181. reasoning, there could be multiple candidates compatible to the
  182. knowledge base. When this happens, reasoner can minimize inconsistencies
  183. between the knowledge base and pseudo-labels predicted by the learning
  184. part, and then return only one candidate that has the highest
  185. consistency.
  186. In this task, we create the reasoner by instantiating the class
  187. ``HedReasoner``, which is a reasoner derived from ``Reasoner`` and
  188. tailored specifically for this task. ``HedReasoner`` leverages `ZOOpt
  189. library <https://github.com/polixir/ZOOpt>`__ for acceleration, and has
  190. designed a specific strategy to better harness ZOOpt’s capabilities.
  191. Additionally, methods for abducing rules from data have been
  192. incorporated. Users interested can refer to the specific implementation
  193. of ``HedReasoner`` in ``reasoning/reasoning.py``.
  194. .. code:: ipython3
  195. reasoner = HedReasoner(kb, dist_func="hamming", use_zoopt=True, max_revision=10)
  196. Building Evaluation Metrics
  197. ---------------------------
  198. Next, we set up evaluation metrics. These metrics will be used to
  199. evaluate the model performance during training and testing.
  200. Specifically, we use ``SymbolAccuracy`` and ``ReasoningMetric``, which are
  201. used to evaluate the accuracy of the machine learning model’s
  202. predictions and the accuracy of the final reasoning results,
  203. respectively.
  204. .. code:: ipython3
  205. # Set up metrics
  206. metric_list = [SymbolAccuracy(prefix="hed"), ReasoningMetric(kb=kb, prefix="hed")]
  207. Bridge Learning and Reasoning
  208. -----------------------------
  209. Now, the last step is to bridge the learning and reasoning part. We
  210. proceed this step by creating an instance of ``HedBridge``, which is
  211. derived from ``SimpleBridge`` and tailored specific for this task.
  212. .. code:: ipython3
  213. bridge = HedBridge(model, reasoner, metric_list)
  214. Perform pretraining, training and testing by invoking the ``pretrain``, ``train`` and ``test`` methods of ``HedBridge``.
  215. .. code:: ipython3
  216. # Build logger
  217. print_log("Abductive Learning on the HED example.", logger="current")
  218. # Retrieve the directory of the Log file and define the directory for saving the model weights.
  219. log_dir = ABLLogger.get_current_instance().log_dir
  220. weights_dir = osp.join(log_dir, "weights")
  221. bridge.pretrain("./weights")
  222. bridge.train(train_data, val_data)
  223. bridge.test(test_data)

An efficient Python toolkit for Abductive Learning (ABL), a novel paradigm that integrates machine learning and logical reasoning in a unified framework.