BERT可以上几年级了?Seq2Seq“硬刚”小学数学应用题
By 苏剑林 | 2020-10-19 | 69291位读者 |“盈亏问题”、“年龄问题”、“植树问题”、“牛吃草问题”、“利润问题”...,小学阶段你是否曾被各种花样的数学应用题折磨过呢?没关系,现在机器学习模型也可以帮助我们去解答应用题了,来看看它可以上几年级了?
本文将给出一个求解小学数学应用题(Math Word Problem)的baseline,基于ape210k数据集训练,直接用Seq2Seq模型生成可执行的数学表达式,最终Large版本的模型能达到75%的准确率,明显高于ape210k论文所报告的结果。所谓“硬刚”,指的是没有对表达式做特别的转换,也没有通过模板处理,就直接生成跟人类做法相近的可读表达式。
数据处理 #
这里我们先观察一下ape210k数据集的情况:
{
"id": "254761",
"segmented_text": "小 王 要 将 150 千 克 含 药 量 20% 的 农 药 稀 释 成 含 药 量 5% 的 药 水 . 需 要 加 水 多 少 千 克 ?",
"original_text": "小王要将150千克含药量20%的农药稀释成含药量5%的药水.需要加水多少千克?",
"ans": "450",
"equation": "x=150*20%/5%-150"
}
{
"id": "325488",
"segmented_text": "一 个 圆 形 花 坛 的 半 径 是 4 米 , 现 在 要 扩 建 花 坛 , 将 半 径 增 加 1 米 , 这 时 花 坛 的 占 地 面 积 增 加 了 多 少 米 * * 2 .",
"original_text": "一个圆形花坛的半径是4米,现在要扩建花坛,将半径增加1米,这时花坛的占地面积增加了多少米**2.",
"ans": "28.26",
"equation": "x=(3.14*(4+1)**2)-(3.14*4**2)"
}
可以看到,我们主要关心的是original_text、equation、ans字段,其中original_text就是题目,equation则是运算过程(一般以x=开头),而ans是最终答案。我们希望训练一个模型,由original_text来生成equation,然后经由python的eval函数直接得到ans。
不过,我们需要做一些前处理,因为ape210k给出的equation并不是都可以直接eval的,像上面的例子150*20%/5%-150对python来说就是一个非法表达式。笔者所做的处理如下:
1、对于a%这样的百分数,统一替换为(a/100);
2、对于a(b/c)这样的带分数,统一替换为(a+b/c);
3、对于(a/b)这样的真分数,在题目中去掉括号变为a/b;
4、对于比例的冒号:,统一替换为/。
经过这样处理后,大部分equation都可以直接eval了,并且可以与ans进行答案对比,只保留结果一致的题目。不过,还有一点改进的地方,就是这样得到的表达式可能会带有一些冗余的括号(也就是去掉括号后与原来的等价),因此还要加上一步去括号,即遍历每一组括号,如果去掉该组括号结果与原来的等价,那么就去掉该组括号,这样可以得到平均长度更短的表达式,而长度越短越容易生成。
最终,我们得到了如下可用的数据集:
\begin{array}{c|ccc}
\hline
& \text{训练集} & \text{验证集} & \text{测试集} \\
\hline
\text{原有数目} & 200488 & 5000 & 5000\\
\hline
\text{保留数目} & 200390 & 4999 & 4998\\
\hline
\end{array}
剩下的基本上是一些错题、乱题了,暂时忽略。
模型简介 #
模型其实是最没什么好讲的,就是以original_text为输入、equation为输出,以“BERT+UniLM”为基础架构,训练一个Seq2Seq模型。如果对模型还有什么疑惑的地方,请阅读《从语言模型到Seq2Seq:Transformer如戏,全靠Mask》。
项目链接:http://github.com/bojone/ape210k_baseline
笔者的训练是用22G的单卡TITAN RTX跑的,优化器是Adam,学习率是2e-5。Base版本的用了batch_size=32,大概需要训练25个epoch,每个epoch约50分钟(算上验证集的评测时间);而large版本则是batch_size=16,大概需要训练15个epoch,每个epoch约2小时(算上验证集的评测时间)。
对了,说到Large,由于UniLM借用了MLM部分权重,所以我们不能用哈工大开源的RoBERTa-wwm-ext-large,因为这个版本的MLM权重是随机初始化的(但它的Base版本是正常的,可以用)。Large版本推荐用腾讯UER开源的权重,原本是PyTorch版的,笔者将它转换为TF版了,在这里(提取码l0k6)可以下载。
效果如下表:
\begin{array}{c|ccc}
\hline
& \text{beam_size} & \text{验证集} & 测试集 \\
\hline
\text{Base} & 1 & 71.67\% & 71.65\%\\
\text{Base} & 2 & 71.81\% & 72.27\%\\
\text{Base} & 3 & \textbf{71.85}\% & \textbf{72.35}\%\\
\hline
\text{Large} & 1 & 74.51\% & 74.43\%\\
\text{Large} & 2 & 74.97\% & 74.99\%\\
\text{Large} & 3 & \textbf{75.04}\% & \textbf{75.01}\%\\
\hline
\end{array}
Large模型的结果已经比ape210k的论文《Ape210K: A Large-Scale and Template-Rich Dataset of
Math Word Problems》所报告的70.20%要明显高了,因此说明我们这里的模型是一个不算太差的baseline。感觉如果用一些Seq2Seq的技巧来缓解一下Exposure Bias问题(参考《Seq2Seq中Exposure Bias现象的浅析与对策》),模型还能有进一步提升;还有或许可以引入copy机制,增强输出与输入数字的一致性;还有可以想办法进一步缩短序列长度(比如四个字符的3.14替换为两个字母pi)。这些就留给大家尝试了~
标准输出 #
如果纯粹从建模的角度来看,其实我们的任务已经完成了,即模型只需要输出式子就行了,评测的时候则只需要判断式子eval后的结果跟参考答案是否一致就好。但是从实际实用的角度,我们还需要对输出做进一步的标准化,即根据不同的题目决定输出的是小数、整数、分数还是百分数等,这就需要我们:1、决定什么时候该输出什么格式;2、根据指定格式对结果进行转换。
第一步比较简单,一般来说根据题目或方程的一些关键字就可以判断了。比如表达式里边如果有小数的,那么输出结果一般也是小数;如果题目是问“多少辆”、“多少个”、“多少人”之类的,那么输出的都是整数;如果直接问“几分之几”或“百分之几”的,那么相应地就是分数或百分数了。比较困难是应该是取整类题目,比如“每盒蛋糕7.90元,50元最多可以买多少盒蛋糕?”要求我们对50/7.90进行下取整,但有时候则是上取整。不过让笔者很意外的是,ape210k里边并没有取整类题目,所以也就不存在这个问题。如果遇到有取整的数据集,如果规则判断起来比较困难,那么最直接的方法就是把取整符号也加入到equation中让模型去预测。
第二步看起来有点复杂,主要是分数的场景,一般读者可能不知道如何让式子保留分数运算结果,如果直接eval('(1+2)/4'),那么得到的是0.75(Python3),但有时我们希望得到的是分数结果3/4。事实上,保持分数的运算属于CAS的范畴(Computer Algebra System,计算机代数系统),说白了就是符号运算而不是数值运算,而Python中刚好也有这样的工具,那就是SymPy,利用SymPy就能达到我们的目的了。具体请看下面的例子:
from sympy import Integer
import re
r = (Integer(1) + Integer(2)) / Integer(4)
print(r) # 输出是 3/4 而不是 0.75
equation = '(1+2)/4'
print(eval(equation)) # 输出是 0.75
new_equation = re.sub('(\d+)', 'Integer(\\1)', equation)
print(new_equation) # 输出是 (Integer(1)+Integer(2))/Integer(4)
print(eval(new_equation)) # 输出是 3/4
文章小结 #
本文介绍了用Seq2Seq模型做数学应用题的一个baseline,主要思路就是通过“BERT+UniLM”直接将问题转换为可eval的表达式,然后分享了一些结果标准化的经验。通过BERT Large模型的UniLM,我们达到了75%的准确率,超过了原论文开源的结果。
所以,你觉得它能上几年级了呢~
转载到请包括本文地址:https://kexue.fm/archives/7809
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Oct. 19, 2020). 《BERT可以上几年级了?Seq2Seq“硬刚”小学数学应用题 》[Blog post]. Retrieved from https://kexue.fm/archives/7809
@online{kexuefm-7809,
title={BERT可以上几年级了?Seq2Seq“硬刚”小学数学应用题},
author={苏剑林},
year={2020},
month={Oct},
url={\url{https://kexue.fm/archives/7809}},
}
October 19th, 2020
您好,想问一下这个qpe210k数据集有下载链接吗,我最近也在做这个有关于数学文本的任务
October 19th, 2020
不仔细阅读,文章第二段 ape210k 不就是一个可点击的链接嘛……
回复的楼上,点错了……
October 20th, 2020
您好,为什么我用的环境是Windows下python3.8+tensorflow2.3+bert4Keras0.88来复现结果但是老是报错JsonDecoderError,提示第一行后需要加“,”分隔符?
我也不知道
October 20th, 2020
太喜欢苏神的这种文章了 , 既长见识又有趣
October 21st, 2020
苏神这是用去年的数据预测今年的题目吧
October 22nd, 2020
苏神你好,最近一直苦于寻找Ape210k这种数学应用题数据集,请问除了Ape210K和Math23K这两个数据集以外,苏神还知道其他的数学应用题数据集嘛?
抱歉,忘了说明,是中文的数学应用题数据集
@成池|comment-14607
苏神在群里回复我了,感谢苏神
ape210k已经是相当大了,目前没发现其他的。
October 22nd, 2020
您好,我有两个疑问,首先,我看代码里对公式的切分,会把数字给切开,比如3.14 会切分成【3,.,14】,这样的结果直观上不是好的。我知道这是由于bert的词典和分词带来的,单纯只是提一个疑问,或者有别的方法。其次,对于提干找那个同一个数字,有没有什么好的办法让模型可以区分。
1、“这样的结果直观上不是好的”,个人认为这个“直观”是不正确的;
2、什么叫做“提干找那个同一个数字”?
October 23rd, 2020
你好,我装载base模型的时候显示权重的结构不一致,这个是什么原因?
ValueError: Cannot feed value of shape (13585, 768) for Tensor 'Placeholder:0', which has shape '(13584, 768)'
那是因为你用了py3~我在github上更新了一下了,你去改一下。
谢谢,可以了
November 10th, 2020
苏神,您好,您文中提到的哈工大开源的RoBERTa-wwm-ext-large,这个版本的MLM权重是随机初始化的,这句话我没理解,随机初始化但是后期训练不是会更新权重吗,为什么不能用呢
后期会更新权重,但是由于随机初始化了,整个模型收敛会比较慢,效果没比非随机初始化的base版本模型好多少,甚至有时会更差。
November 16th, 2020
苏老师,在跑其中一个demo时报错:
tensorflow.python.framework.errors_impl.NotFoundError: Key csl/predictions/transform/dense/kernel not found in checkpoint
想问一下是什么原因?同时,python3上存在一些兼容性问题
1、原因是你所用的模型没有mlm权重(比如electra或徐亮版的roberta)
2、目前我未测出py3有什么不妥之处,如果你测出了,请反馈一下,谢谢~