我又回来了。这几天经过和ai的协作,我最后发现没法训练一个ai,但是我希望在这里分享一下探索的结果,可以作为别人的参考。
核心的问题的确就在于录像文件的解析:ygopro的代码一直在更新,曾经有一次大的更新导致yrp前后不兼容。即使解决了这个问题,还有另外的问题: yrp通常包含这些东西:
- 录像头信息:版本、标识、时间、标志位、随机种子
- 玩家信息:玩家名字、单局/组队信息等
- 对局参数:初始 LP、初始手牌数、每回合抽几张、duel flag
- 卡组列表:双方主卡组、额外卡组,tag 模式下会更多
- 回放正文:一串按顺序记录的引擎消息/response packet,基本是长度前缀的原始字节流
- 压缩信息:很多 yrp 的正文会被 LZMA 压缩存放
关键点是:yrp 不直接保存“这个回合场上有哪些卡、为什么能发动、谁看得见什么”这种高层语义;这些都要靠引擎按顺序把消息重放出来再重建。换句话说,yrp 更像“可逆的对局输入流”,不是“人类可读的战报”。
因而,我们无法轻易还原整个游戏某个时点的信息。虽然可以通过ocgcore重建游戏,但是无法保证由于代码更新例如bug修复导致的状态还原不准确的问题。我无法准确评估这种长期的代码更新积累下来的变动对于数据质量的影响。
这是我试图用next token prediction训练一个decoder only model遇到的问题。如果想用完全从0开始自我博弈训练,需要的计算量又非常大(如果想训练出一个能够对大多数卡组都适用的ai)
最后稍微再扯一下具体设计:
- 模型架构模仿LLM,使用decoder only,每次预测下一个token。这里的token可以是一张卡,也可以是一个action比如丢弃,召唤等可以被ocgcore理解的操作,毕竟我们最后的目的是让ai操作游戏。为了实现这一点必须完整列出所有ocgcore允许的操作并格式化成为固定的表达形式。例如,发动魔法卡可以被tokenize成为:【发动】【手牌】【旋风】【放在】【魔法陷阱2号位置】【对象为】【对方魔法陷阱位置3的卡片】。这部分我其实并没有花特别多的心思,主要就是直接解读yrp的描述形式。至于LP这种整数,可以用区间表示,比如【4000到4999】作为一个token。对卡片的embedding可以把有限状态例如属性,等级用可学习的embedding表示,而效果可以用LLM encoder直接提取特征。最后做个concat组装成向量。这样做的好处是有新的卡也可以直接embedding。
- 训练的时候用有监督学习直接让模型预测下一个token,推理时候用ocgcore对输出的token进行约束,这个不难。通过调整温度可以改变模型出牌的不确定性(打法死板还是灵活)
- 训练数据具体格式为3个角色:system prompt提供状态快照,user是对方玩家,assistant是我方玩家。一个yrp可以产生两个视角的dialog。但是不同视角应当设置不同的可见信息。
- 如果未来有机会,可以让ygopro的yrp记录更完整的信息,但是这样不可避免的导致yrp文件大小指数级增加,对网络带宽可能也是个压力。
最后,感谢管理员的支持。未来有更好的条件我会回顾这个问题。