实现了类似EUSolver的基于多标签决策树的枚举算法,并且对于当前任务做了一些优化。算法可以在很多数据集(如 array_search 系列)上迅速求出简洁的程序,但在一些程序上效率较低。由于算法二已经完成通过 open_test 中的所有测试用例的目标,目前未尝试继续优化。
在 array_search_15 数据上,只需要 8s 即可得到下面的程序:
(define-fun findIdx ((y1 Int) (y2 Int) (y3 Int) (y4 Int) (y5 Int) (y6 Int) (y7 Int) (y8 Int) (y9 Int) (y10 Int) (y11 Int) (y12 Int) (y13 Int) (y14 Int) (y15 Int) (k1 Int)) Int
(ite (< k1 y8)
(ite (< k1 y4)
(ite (< k1 y2)
(ite (< k1 y1) 0 1)
(ite (< k1 y3) 2 3))
(ite (< k1 y6)
(ite (< k1 y5) 4 5)
(ite (< k1 y7) 6 7)))
(ite (< y12 k1)
(ite (< k1 y14)
(ite (< k1 y13) 12 13)
(ite (< k1 y15) 14 15))
(ite (< k1 y10)
(ite (< k1 y9) 8 9)
(ite (< k1 y11) 10 11)))))
可以注意到它找到的解采用了优雅的二分查找的算法,并且在后面的讨论中我们可以看到,算法的搜索过程会迅速收敛到合适的解上。
算法主要分为预处理,枚举,和决策树学习三个步骤:
- 预处理阶段对约束进行预处理,简化条件,并转化为易于处理的形式,同时求出算法的所有可能输出作为标签,以便决策树算法进行分类。
- 算法通过标准的枚举算法生成决策树判断条件
- 计算每个样例点对应的标签,通过ML-DT决策树算法学习一棵决策树。该决策树即是生成的程序,若该程序存在不满足约束的反例,则加入新的样例点,重复进行3直到学习到一棵不存在反例的决策树。
目前此算法只能处理对于每个反例点,约束中的所有函数调用共享输入的情形,如果不能变换到该情形则无法处理。
注意到输入的约束的形式实际上是多个子句的合取,但由于子句可以任意复杂,可能存在冗余,且可能含有大量不同运算符影响算法进行,故算法第一步是将约束化为标准形式。算法会将输入的约束首先化为合取范式,这就可以保证每个子句中的逻辑运算符只含有或和非,然后算法会通过 SMT Solver 求解约束间的等价性,去除相同的或平凡的约束,从而达到约束的简化。
在简化后的约束中依然会存在一些如下的约束影响决策树学习中的标签计算:
(declare-var x Int)
(constraint (= (f 1) 1))
(constraint (= (f 2) 2))
....
对于样例点 x = 1
,f(x) = 1
应当是其标签,但是当我们将该函数和样例点直接代入时会发现该式为加,因此应将输入变换为 "Guarded" 形式如下:
(constraint (or (= (f x) 1) (not (= x 1))))
(constraint (or (= (f x) 2) (not (= x 2))))
这时就可以直接代入求解了。同时代换过程中可以判断是否存在不能处理的输入,若存在则放弃用此算法计算。
下面是 s1.sl
经过预处理后得到的约束
(constraint (or (= (f x) 0) (not (= x 0))))
(constraint (or (= (f x) 10) (not (= x 1))))
(constraint (or (= (f x) 20) (not (= x 2))))
(constraint (or (= (f x) 30) (not (= x 3))))
(constraint (or (= (f x) 40) (not (= x 4))))
(constraint (or (= (f x) 50) (not (= x 5))))
(constraint (or (= (f x) x) (<= x 5)))
可以看到所有的函数调用共享参数,并且多余的
在约束经过预处理后,算法会计算出所有可能作为返回值的表达式集合,该过程与算法二中的第一部分相同。若不进行此计算,算法也可以通过标准的枚举算法来枚举返回值,但这会带来很大的效率下降。
采用标准的枚举算法对条件进行枚举,注意到用 ite
语句的嵌套组合可以表达所有的 and
和 or
条件,因此只需要生成由比较运算符连接的简单条件即可,但在实际计算过程中,由于含有 and
和 or
的条件的表达式比较大,因此会在算法后期才会被枚举出来,因此不需要特殊处理。
给定当前样例集合和条件语句集合,算法会利用通用的多标签决策树算法进行学习。
在该学习任务中,标签集合是所有可能返回语句,被分类集合为样例集合。对于每一个样例,我们求出满足约束的所有返回语句集合,这个过程可以通过对 LIA 文法的解释执行完成。
以下定义
决策树的内部节点由条件语句组成,条件语句
$$ \mathbb{P}{C_0}(\mathrm{label(c) = t}) = \frac{1}{|C|}\cdot\sum{c\in C_0}\mathbb{P}_{C_0}(\mathrm{label}(c) = t\mid c) $$
决策树算法通过最大化信息增量来选取每一步的分支策略,从而实现学习,并且可以达到良好的泛化性能,从而不需要进行太多步的穷举就能得到满足约束的解。
决策树算法作为数据驱动的机器学习算法,其泛化性能与数据集质量关系非常大,如果不对数据集质量进行控制,算法会在
如果发现在额外约束条件下,无法生成反例,那么就将额外约束逐个放松,然后判断是否有反例。如果所有的额外约束均已放松,仍然不能找到反例,那么算法结束,输出解。
设计了一个较为高效的 Example Based Synthesis 算法,具有如下的特点:
-
效率高,可以在时间限制能正确生成本次大作业 open_tests 中的所有测试用例。
-
结果简洁,下面是本算法生成的两个例子:
(define-fun max6 ((x1 Int) (x2 Int) (x3 Int) (x4 Int) (x5 Int) (x6 Int)) Int (ite (and (and (and (and (<= x2 x1) (<= x3 x1)) (<= x4 x1)) (<= x5 x1)) (<= x6 x1)) x1 (ite (and (and (and (<= x3 x2) (<= x4 x2)) (<= x5 x2)) (<= x6 x2)) x2 (ite (and (and (<= x4 x3) (<= x5 x3)) (<= x6 x3)) x3 (ite (and (<= x5 x4) (<= x6 x4)) x4 (ite (<= x6 x5) x5 x6))))))
(define-fun findIdx ((y1 Int) (y2 Int) (y3 Int) (k1 Int)) Int (ite (or (or (or (or (<= y2 y1) (<= y3 y2)) (<= k1 y1)) (= y3 k1)) (= y2 k1)) 0 (ite (<= k1 y2) 1 (ite (<= k1 y3) 2 3))))
其中第一个程序是
max6
的答案,第二个程序是array_search_3
。值得注意的是,在array_serach_3
中,并没有对序列无序或者$k1$ 和数列中某一个元素值相同的情况进行约束,可以发现在我们的算法把这个约束准确地求出了。
算法主要分成三部分:
- 求出可能作为返回值的表达式集合(不含
ite
)$R$ 。 - 求出可能出现在 if 条件里的比较表达式(形如
$x \leq, <, = y$ )集合$C$ 。 - 对
$R$ 中的每一个元素生成一个它作为返回值的条件(以析取范式的形式)。
目前本算法只能处理在输入中,所有输入均以
这儿定义
可以判断一个表达式集合 ite
)的所有表达式集合,直接找到最小的
因为这样找到的 Int
范围内,很难出现两个不同的表达式集合
当返回值形式比较复杂的时候,也可以用一个 example based synthesis 来生成表达式(每次找到一个反例再生成一个表达式覆盖它)。
因为测试集中,条件判断的形式都非常简单,因此这儿令
我们以析取范式的形式来生成答案,即对每一个返回值,生成一系列的集合
设
为了通过这个 example,需要扩大
我们可以用如下的约数来判断一个集合
可以先检查
直接加入
上述的算法在 max
中仍然会产生严重的过拟合,max10
里会产生
考虑在析取范式中不存在 or
的情况,即可以用一个
在已知真实答案中不存在 or
的前提下,可以采用更高效的做法:每一次用之前产生过的所有测试数据来筛选集合
采用这样的策略,算法可以在非常快的速度内生成 max15
。直观上来说,这个算法寻找
但是在实际生成的时候,并不知道答案中有几个 or
。因为我们采用了如下的扩展方法:
- 维护若干个 example 集合
$S_i$ ,每产生一个新的 example,就对每一个已有的$S_i$ 尝试把$e$ 加入这个集合中。如果$e$ 的加入使得集合$S_i$ 不能被任何一个正确的条件集合$P$ 表出,那么就制止这次加入。 - 如果
$e$ 无法被加入任何已有的集合中,那么就加入一个新的集合,集合的元素只有$e$ 本身。如果这个集合也不能被任何一个正确的条件集合$P$ 表出,那么说明$C$ 集合不够大,回到第二步。
数据点 | 算法一(s) | 算法二(s) | 数据点 | 算法一(s) | 算法二(s) |
---|---|---|---|---|---|
max2 | 0.31 | 0.34 | array2 | 0.38 | 0.50 |
max3 | 0.39 | 0.44 | array3 | 0.52 | 0.80 |
max4 | 0.84 | 0.65 | array4 | 0.65 | 1.31 |
max5 | 4.88 | 0.93 | array5 | 0.85 | 2.14 |
max6 | 230.47 | 1.37 | array6 | 1.09 | 3.11 |
max7 | TLE | 2.01 | array7 | 1.57 | 6.12 |
max8 | TLE | 3.18 | array8 | 1.85 | 10.19 |
max9 | TLE | 4.43 | array9 | 2.50 | 15.29 |
max10 | TLE | 7.72 | array10 | 2.85 | 15.83 |
max11 | TLE | 9.29 | array11 | 4.64 | 22.58 |
max12 | TLE | 12.19 | array12 | 4.63 | 37.58 |
max13 | TLE | 18.34 | array13 | 5.63 | 38.13 |
max14 | TLE | 26.80 | array14 | 6.56 | 50.03 |
max15 | TLE | 29.08 | array15 | 7.97 | 112.21 |
s1 | TLE | 1.12 | tutorial | 69.43 | 64.13 |
s2 | 0.59 | 0.68 | three | WA | 41.27 |
s3 | 2.33 | 2.46 | mytest | TLE | 32.45 |