0%

微信跳一跳之线性回归算法优化

最近微信上线了一款小应用—“跳一跳”,这个规则简单,让人上瘾的小游戏和2048一样魔性,朋友圈也是各路小伙伴各显神通:硬件流(树莓派+步进电机)、日天流(篡改http请求)、软件流(adb控制手机模拟点击)。

今天我们也来实践下,当然选择最顺手的Python来搞咯,直接找到开源项目wechat_jump_game进行优化改造。此项目有个pull request[优化]跑分17000+ 新增AI模块,机器人自主学习生成跳跃公式,看到AI我们就来了兴趣,只见过理论,还没有实践过,可以拿这个实践下。

这个pull request介绍如下:

机器人精确采集跳跃结果并自主学习,使用线性回归方法
拟合出最优 [按压时间]->[弹跳距离] 线性公式 Y =kX + b
本优化无需修改config文件,可以适配所有手机,经过十次以上跳跃学习,机器人即可
模拟出相对稳定的线性公式。随着采集结果越多,跳跃也越精确,后期基本连续命中靶心。
理论上只要目标点获取无误,会一直跳下去。

工作两年多,一直在做服务端后台应用相关的测试,没接触过移动端测试呢,正好趁这次机会学习下怎么通过代码自动化控制安卓手机。
下面来动手试一下,找出下岗多年的MX3,充电开机。

第一次调试

  1. 安装好adb,配置好环境变量。
  2. 手机打开开发者模式,连接PC。
  3. 命令行测试是否连接成功: adb devices,手机弹出是否信任窗口,点击确定,已经链接成功。
  4. 测试一些adb命令是否正常: adb shell wm size,返回信息:Physical size: 1080x1800,完美。
  5. 通过virtualenv建立虚拟环境,安装项目所需的第三方库。
  6. 手机微信打开跳一跳,点击开始游戏。
  7. 运行wechat_jump_auto_ai.py,报错T_T…
    查看代码发现是截图部分操作不适配MX3,手动修改代码后成功截图运行
    1
    screenshot = screenshot.replace(b'\r\n', b'\n')
    修改为
    1
    screenshot = screenshot.replace(b'\r\r\n', b'\n')

第二次调试

按照程序逻辑,运行十次之后即可采用线性回归算法学习得到的公式,根据已知距离得出按压时间,但实际结果却和一个弱智一样,2分就挂掉了…
查看代码发现有个魔法数字要自己设置,程序根据这个数字进行截图计算误差:time.sleep(0.2)。

1
# 在跳跃落下的瞬间 摄像机移动前截图 这个参数要自己校调
2
time.sleep(0.2)
3
pull_screenshot_temp()
4
im_temp = Image.open('./autojump_temp.png')
5
temp_piece_x, temp_piece_y = find_piece(im_temp)
6
debug.computing_error(press_time, board_x, board_y, piece_x, piece_y, temp_piece_x, temp_piece_y)

经过debug截图不断调整,得出我的PC和MX3配合的最佳数值是0.04。
删除学习数据jump_range.csv,重新开始训练,程序终于能磕磕绊绊达到400分左右,但是大概需要1小时左右,对学习数据通过pandas和matplotlib进行绘图,看到训练采集到的数据离散程度很高,明显学习效果不佳。

第三次调试(重点)

再次阅读代码,发现这个AI版本的代码有瑕疵:

1
def computing_error(last_press_time, target_board_x, target_board_y, last_piece_x, last_piece_y, temp_piece_x, temp_piece_y):
2
	"""
3
	计算跳跃实际误差
4
	"""
5
	target_distance = math.sqrt(abs(target_board_x - last_piece_x) ** 2 + abs(target_board_y - last_piece_y) ** 2)  # 上一轮目标跳跃距离
6
	actual_distance = math.sqrt(abs(temp_piece_x - last_piece_x) ** 2 + abs(temp_piece_y - last_piece_y) ** 2)  # 上一轮实际跳跃距离
7
	jump_error_value = math.sqrt(abs(target_board_x - temp_piece_x) ** 2 + abs(target_board_y - temp_piece_y) ** 2)  # 跳跃误差
8
9
	print "目标距离: {0}, 实际距离: {1}, 误差距离: {2}, 蓄力时间: {3}ms".format(round(target_distance), round(actual_distance), round(jump_error_value), round(last_press_time))
10
	# 将结果采集进学习字典
11
12
	if last_piece_x > 0 and last_press_time > 0 :
13
		ai.add_data(round(actual_distance, 2), round(last_press_time))

问题1

target_distance与actual_distance有可能会等于0.0或者一个超出实际范围的值,这些值会在游戏失败重新开始时出现,原程序没有进行过滤;

问题2

jump_error_value作为一个很重要的值,没有进行有效利用。

上述两个问题会导致数据质量不高,干扰项太多,目标函数拟合速度太慢。

因此改进数据采集策略:

1
if jump_error_value < 5 and last_piece_x > 0 and last_press_time > 0 and target_distance > 0 and actual_distance > 0:
2
	ai.add_data(round(actual_distance, 2), round(last_press_time))

改进1

要求target_distance与actual_distance必须要大于0才是有效值,剔除干扰项;

改进2

jump_error_value合理利用,开始时将此参数放大到50左右,后续根据学习效果,逐步缩小jump_error_value的阈值,提高精确度,加速训练效果。

效果展示

训练数据拟合函数

经过大概四五轮训练,采样到571条数据,拟合出线性回归函数如图

debug误差效果

  1. 运行debug截图, 基本完美命中目标

  2. 运行debug日志,误差基本控制在40以内

自动运行视频

成绩

因为MX3手机硬件有问题,电量低于75%就会出现屏幕抖动,所以目前成绩如此
AI成绩

项目地址

优化后的代码以及训练数据: https://github.com/toddlerya/WechatJumpAI

后记

不得不承认,机器学习在合适的领域使用合适方式,达到的效果非常棒!只用了简单的线性回归算法,最经典最基本的机器学习算法,达到的效果已经秒杀人类上千倍。
为了不被这个时代淘汰,一定要跟上节奏,加油~


2018年01月07日 于 南京
Email
GitHub