第一次结对项目总结
-
结对项目总结要求:https://edu.cnblogs.com/campus/ustc/InnovatingLeadersClass/homework/2231
-
项目Github地址:
https://github.com/Annbless/GoldenNumber
黄金点游戏基本规则
N个玩家参与游戏,每个玩家给出两个$(0 , 100)$之间的有理数,每一轮将所有玩家所给出的数的总和求出平均数,将平均数乘以0.618得到黄金点,所给出的数与黄金点最近的玩家得到N分,所给出的数与黄金点最远的玩家得到-2分,比赛M轮后所得分数最高的玩家获胜。
PSP表格时间记录
PSP各个阶段 | 预估时间 | 实际记录 |
---|---|---|
计划: 明确需求和其他因素,估计以下各个任务需要多长时间 | 30 | 20 |
开发 | ||
需求分析 | 300 | 240 |
生成设计文档 | 20 | 60 |
设计复审 | 10 | 2 |
代码规范 | 5 | 5 |
具体设计 | 20 | 5 |
具体编码 | 120 | 120 |
代码复审 | ||
测试 | 120 | 60 |
总共花费时间 | 625 | 452 |
说明: 以上各项并不是准确记录耗时,而是一个大概的时间。需求分析阶段时间过程是因为我们对强化学习的Q-Learning和DQN并不熟悉,因此我们将学习了解强化学习的时间预估和自己实现原型算法的时间计算在内。生成设计文档时间长于我们预估时间,是因为在经过使用DQN的原型进行测试后,我们发现该算法容易过拟合在某一个数据集,且预估实际比赛数据和测试所用数据会有很大差异,因此我们在这一阶段经过讨论决定采取自己设计手工策略的办法进行算法的设计。因为采取的是手工设计的算法,因此之后的各个阶段的实际时间和预估时间的差异也会偏大。
算法设计与实现
我们对历史数据进行了分析,发现在比赛轮数超过15轮后,整体的黄金点趋势已经趋向于平稳,此时如果有一个非常大的数参与进黄金点的计算,那么可以将增加黄金点数据大概1的大小。而其余依赖上一次或几次的黄金点的算法对于突然变化的黄金点的适应力是较差的,因此我们的算法的重点就是通过输出一个比较大的数和一个自己计算出的接近自己拉高的黄金点的数来争取$N-2$的分数,N是参赛队伍数量。其余的时候就采取保守的加权平均策略来保证自己尽量不丢分。
函数说明:
函数名称 | 功能 |
---|---|
is_smooth |
通过对历史4轮数据的分析判断当前状态是不是平稳 |
small_enough |
通过对历史4轮数据的分析判断当前的黄金点是不是小于预设的值 |
pre_disturb_majority_rule |
采用大数定律预测每个人下一轮扰动增加数值 |
pre_disturb_majority_rule |
采用概率期望预测每个人下一轮扰动增加数值 |
以上四个函数都是功能性辅助函数,代码逻辑主要在主函数中实现:
版本一: 每次以一定概率判断当前自己是不是需要输出较大数值,若是,则采用大数定律或者概率期望预测其他人扰动的数值,从而决定自己的预测值,若否,则采取保守策略,输出黄金点的加权平均值
版本二: 每次判断当前局势是否平稳或者是否黄金点足够小,若是,则输出较大数值,若否,则采取保守策略,输出加权黄金点平均值。
关于UML图,因为并没有采用对象并且函数只是用于实现一些基础功能,所以并没有绘制UML图。
Design by Contract
契约式设计就是各个函数之间约定好输入输出满足的属性,逻辑,格式等,之后在程序中只需要去处理某种约定好的数据类型或者大小的数据,也就是,对于函数对于输入的逻辑,类型等是已知的,外部对于函数的返回值的类型和逻辑等属性也是已知的。可以省却很多设计上的麻烦。具体实现可以使用断言来进行实现。
项目中使用的契约式编程就是约定了各个函数输入输出的类型和格式,保证是同一种输入的情况下调用同一种函数。在数据存储过程中,调用了numpy库中的np.save和np.load对数据进行保存和读取,默认遵守numpy的数据规定。
代码规范及异常处理
编码规范是去遵守编译器的编码规范,比如缩进长度是4个空格等,在进行函数编写的时候,是尽量将一个函数只实现一个功能,减少彼此之间的耦合程度。在异常处理方面,因为并没有调用很多库函数,因此大多数的数据都是约定好的数据格式,所以只是对输出的合法性进行了检查。
关于界面,并没有自己去写界面,只是在提供的C#代码中进行调试
结对编程
结对过程: 因为我们对强化学习并不了解,所以一开始我们确定了几个粗略的时间结点,比如一天的时间来熟悉强化学习的内容,之后交流讨论各自的了解,进一步确定算法步骤等。在确定算法步骤后,我们采取合作编程的方式编写了函数的框架和功能划分,之后采取分工的方式进行了对函数的完善和调试。
结对编程方式: 分工 -> 驾驶员和领航员 -> 分工
结对编程优点和缺点:
-
优点: 可以互相学习算法思路,能够从不同的角度出发考虑到很多情况下各个算法的优缺点,对选择的算法进行补充完善。很多小的错误,比如格式或者缩进等可以在写代码过程中就被改正,对于不熟悉的函数库代码可以通过一个查阅一个编写的方式很快的进行使用,不会打断写代码的流程。而且出现不确定的思路也可以立刻通过讨论的方式确定大致的方向,采取先一步实现算法原型的方法进行书写,另一个人可以思考具体如何补充完善思路。并且两个人都会对代码有更深一步的理解,并不需要从头阅读代码。
-
缺点: 结对编程会压力大,写代码偏向于采取保守策略,写一些简单易懂的代码,算法的提出也会偏向于保守策略。
每个人的优缺点:
-
队友:
-
优点:
- 提出算法思路很快,灵活
- 代码注释清晰
- 基于数据进行问题说明,有理有据
-
缺点: 有时候思路过快,代码中会有跳跃
-
-
自己:
-
优点:
- 对numpy库函数熟悉一些
- 能够在编写代码过程中发现一些明显的错误
- 善于通过查找资料来解决问题
-
缺点:不够细心,对于求加权平均的代码忽略了所加权重已经归一化过的问题。
-
其他收获
算法的实现是一个不断改进的过程,在这次结对编程过程中,除了强化学习的原型,我们采用迭代测试的办法实现了三个版本的代码,分别在公布的测试数据上进行了测试,分析每一版代码的优势和劣势,最后进行综合得到比赛用版本的代码。另外的教训就是编写代码时候的细心程度,要尽量减少通过复制方式的代码复用。在第一轮因为求平均算法出现问题而得到了很差的结果,在第二轮修复了Bug之后算法表现还是可以接受的。通过两人结队讨论的方式来进行编程可以充分发挥个人的长处,并且完善思路中的缺陷,通过测试再进行修改,比个人编程的算法迭代速度要更有效率。