AI 生成音游 Malody 谱面

请注意,这篇文章编写于2020年,其内容可能已经过时,请考虑使用性能更优的 Mug Diffusion 来生成音游谱面

前几天想玩玩 Malody ,但苦于找不到自己喜欢的曲子,于是脑子一抽,想到了万能的 AI。然后经过在网上的一阵搜索,我找到了一个大佬提出的解决方案。不过他只用了 24 首曲子训练了 2w 和 10w 步。为了尝试取得更好的效果,我扩大数据集到 104 首曲子,在 Kaggle 上训练了 20w 和 100w 步。效果嘛,经过我的测试,还是挺一般的(或者说根本不太好)。如果有想尝试的小伙伴照常前往橘子的实践时间瞧瞧。

效果展示

测试了 零和Zero-Sum、潮鳴り 和 R.I.P 三首曲子。在此感谢Ma娘献上的演奏~毕竟我真的是太菜了233

关于训练

训练过程中还是遇到了不少坑的,在此顺便记录吧。

准备和清洗数据

经过网上的一阵搜索,我找到了 Malody Mappack Project 作为原始数据。经过查看我发现里面有些 .osu 格式的转谱,显然这不是我们想要的东西。另外许多谱面有不同的难度,为了避免干扰,我们只选用 15-25 级的谱面,且越接近 20 级的谱面越优先,其余删除。另外其中一部分谱面带有 Key 音,我直接将其删除处理。为了清洗这些数据,我写了一段代码自动化处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import os
import sys
import json
import shutil
import random

os.mkdir("PreparedData")
for i in os.listdir(os.path.join(sys.path[0],"Data")):
path1 = os.path.join(sys.path[0], "Data", i)
if(not os.path.isdir(path1)):
continue
path2 = os.path.join(path1,os.listdir(path1)[0])
levelnumber = 0
levelfilepath = ""
for filename in os.listdir(path2):
filepath = os.path.join(path2,filename)
if(os.path.isfile(filepath)):
if(filename.endswith(".osu")):
print("删除:",path1)
shutil.rmtree(path1)
break
elif(filename.endswith(".mc")):
with open(filepath,"r",encoding="utf-8") as f:
mcfile = json.load(f)
version = mcfile["meta"]["version"]
level = version.split(" ")[-1]
if(level.strip().startswith("Lv.")):
levelint = int(level.strip().replace("Lv.",""))
if(levelint >= 15 and levelint <= 30 and (abs(20 - levelnumber) > abs(20 - levelint))):
levelnumber = levelint
levelfilepath = filepath
if(levelfilepath != ""):
dirpath = "PreparedData\\" + "%09d"%random.randint(0,999999999)
os.mkdir(dirpath)
shutil.move(levelfilepath,dirpath)
for file2 in os.listdir(path2):
filepath2 = os.path.join(path2,file2)
if(file2.endswith(".ogg") or file2.endswith(".mp3")):
shutil.move(filepath2,dirpath)
else:
os.remove(filepath2)

另外写了一小段程序自动打包成 .mcz 文件,mcz 其实就是一个zip文件。过于简单代码就不展示了。其实这个步骤可以简化,因为数据集生成的时候还是要解包的。但我实在懒得修改别人的代码。

生成数据集

数据集生成代码我就直接用了原作者的代码,丢到 Colab 上生成的。有几个 Bug,稍微修改了了一下。另外似乎有一个谱面咋整都报错,我懒得处理就直接将其删除了。其实这一步耗时令我意外的长,因为那几个 Bug 只有在某些谱面上出现,每次跳出来我都得重新生成一遍数据集,跑到会出错的文件,再看 Bug 的位置。如果要训练的话建议直接用我踩完坑修完 Bug 的版本。

训练模型

训练代码我进行了一定的修改,方便在运行的时候指定输出位置、备份位置、数据集位置和学习率、步数。另外还加入了写 TensorBoard 日志的支持。

刚开始我打算照常丢到 Colab 上训练,但 Colab 限制一个半小时无操作自动断开连接。由于训练步数多,耗时长,我寻思着总不能一直看着它吧。于是我决定把它放到 Kaggle 上训练。Kaggle 的笔记本最长运行9小时,且没有无操作超时的限制。

于是我就照 Colab 的样子在上面写了训练用的笔记本,就开始跑了。然后我遇到了本次项目最大的坑。

当我好不容易训练完之后,我在笔记本中到处寻找下载训练的模型的地方。我好不容易在Data那一栏找到了下载按钮,按一按,莫得反应。我大惊,尝试挂载 Google 云端硬盘,发现 Kaggle 并不能像 Colab 一样轻松挂载 GoogleDrive。我尝试退出重新进入笔记本,然后我训练出来的文件就都无了。

后来我查了半天,才知道 Kaggle 训练是写好代码之后点右上 SaveVersion,然后 Save&Run All,训练完之后再去取结果,而不是像 Colab 一样交互式使用。等过一阵子我打算再写一篇 Kaggle 的使用教程。

以下是 Kaggle 笔记本代码。需要加载我的数据集。

1
2
3
4
!git clone https://github.com/mirrorange/AI_beatmap_generator.git
%cd AI_beatmap_generator
!cp /kaggle/input/malody4kbeatmaps/malody.txt glove
!cp /kaggle/input/malody4kbeatmaps/malody2.txt glove
1
2
3
4
5
%cd glove
!make
!./create_glove_embedding.sh
!./create_ln_embedding.sh
%cd ..
1
2
3
4
5
6
7
8
!rm -rf checkpoints
!mkdir checkpoints
!mkdir log
!mkdir /kaggle/working/checkpoints
!python train_beat_cls.py /kaggle/working/AI_beatmap_generator/checkpoints /kaggle/working/checkpoints /kaggle/input/malody4kbeatmaps/dataset.json 0.0002 200
!python train_beat_decoder.py /kaggle/working/AI_beatmap_generator/checkpoints /kaggle/working/checkpoints /kaggle/input/malody4kbeatmaps/dataset.json 0.0002 1000
!python train_ln_cls.py /kaggle/working/AI_beatmap_generator/checkpoints /kaggle/working/checkpoints /kaggle/input/malody4kbeatmaps/dataset.json 0.0002 200
!python train_ln_decoder.py /kaggle/working/AI_beatmap_generator/checkpoints /kaggle/working/checkpoints /kaggle/input/malody4kbeatmaps/dataset.json 0.0002 1000

最后是训练日志

附各种数据的获取地址

AI Beatmap Generator(源仓库)+ 原模型

AI Beatmap Generator(橘子修改版)+ 橘子训练的模型

Malody Mappack Project 项目页面

橘子清洗过的数据(104张谱面)及预处理过的数据集

橘子为训练编写的Kaggle笔记本

生成的示例谱面:潮鳴り零和Zero-SumR.I.P.