《机器学习有意思! 02》- 使用机器学习生成超级玛丽关卡

原文:Machine Learning is Fun! Part 2 – Using Machine Learning to generate Super Mario Maker levels
作者:Adam Geitgey


在系列的*Part 1*中我们已经讲过,机器学习是用普适的算法从数据中挖掘出有趣的东西,而无需针对具体问题写代码。

这次我们将见识一种普适算法的炫酷表现——创造乱真的游戏关卡。我们将建立一个神经网络,导入已有的超级马里奥数据,然后创造新的关卡。

正如Part 1一样,本系列教程适合所有对机器学习感兴趣、但却不知从何开始的读者。希望所有人都能读懂——所以难免会有些粗疏。不过但凡能把一个人带进ML的坑里,也就算没白写。


猜得更机智

在Part 1中,我们写了一个简单的算法来根据房产的属性预测价格,数据如下表所示:

预测函数是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
price = 0

# 加少量配方1
price += num_of_bedrooms * 0.123

# 加大把配方2
price += sqft * 0.41

# 适量的配方3
price += neighborhood * 0.57

return price

也就是说,房价的预测值是由每种属性乘以权重再相加得到的。用图形来表示:

箭头代表函数权重

但是这个算法只对很简单的线性问题有效,如果房价背后的真实规律没有那么简单呢?比如周边环境可能对特别大或特别小的房子影响显著,但对于中间房型则没有什么影响。如何才能捕捉到这些复杂细节呢?

为了更加的机智,我们可以用不同的权重多次执行算法,每一组权重对应不同的边界情况。

尝试用4种方法解决问题

现在我们有四个价格预测了,然后再把这4个组合成最终结果,并且也是通过加权(只是权重值变了)。

新的超级答案结合了四次求解尝试,所以相比于初始的简单模型,能够模拟更多的情况。


什么是神经网络?

把我的四次求解尝试整合到一张大图里:

这就是一个神经网络!那个节点都有一组输入,执行加权计算,产生一个输出值。把这些节点串起来就能够对复杂函数进行建模了。

为了节省篇幅,这里我跳过了很多内容(比如特征缩放激励函数),但是最重要的部分还是以下几点基本思想:

  • 我们用简单的函数,对输入进行加权获得输出,这个简单函数就叫神经元
  • 把许多简单的神经元链在一起,就可以对单个神经元描述不了的复杂问题进行建模。

就像乐高一样,单个积木块或许做不出什么,但只要有足够多的积木,就什么都能搭建。

我们的未来会如同塑料玩具一样吗?只有时间知道答案。


让神经网络有记忆

给定相同的输入,此前的神经网络也会给出相同的输出,用编程术语叫无状态算法(stateless algorithm)

在很多实例(比如房价预测)中,这正是我们想要的。但是这类模型无法应对数据中的时变模式。

想象一下,我给了你一个键盘,让写个故事。在开始之前,我要猜你会写哪个字母,那么我该怎么猜呢?

我可以利用我的英语知识来增加命中率,比如你写的第一个字母往往是一个单词的首字母。如果我看过你的过往文章,还可以进一步根据你的文风收缩范围。如果我有全部数据,我就可以搭建一个神经网络,来计算你写下一个字母的概率。

模型可能是这样:

再来看一个更有挑战的问题,随便在故事里定个位,然后我要猜你的下一个字母,这就更有意思了。

用海明威的《太阳照常升起》作为例子:

Robert Cohn was once middleweight boxi

下一个字母是什么?

你大概会猜’n’——这个词应该是boxing。这样的猜测是基于我们已知的常用英语单词积累。同时,’middleweight’ 也给出了额外的线索指向boxing。

下一个字母好猜,是因为我们纳入了其之前的句子,并结合已有的语言知识。

如果要用神经网络来解决这个问题,我们需要在模型中添加一个状态(state),每次我们向神经网络询问答案的时候,还会获得一个中间计算,并在下次当作输入的一部分重复利用。如此一来,我们的模型就会根据之前的输入调整预测结果。

跟着状态走,虽然不能猜第一个字母,但却可以根据前面的字母猜下一个。

这就是*循环神经网络(Recurrent Neural Network)*的基本思路。每次使用,我们都是在更新网络,这就可以基于之前最常见的输入,去更新预测。只要“记忆力”足够,这就可以对数据的时变模式进行建模。


一个字母好干什么?

猜测故事里的下一个字母挺起来没有什么卵用,那还提这茬干嘛?

手机输入法的自动补全就是一个应用:

下一个字母是”t”

往计算情况再想想,如果我们让模型猜完一个再猜一个,如此循环往复——我们就可以让计算机自己来写个故事了!

生产故事

知道了如何猜海明威句子里的下一个字母,让我们试着产生一个完整的海明威风格故事。

我们将使用Andrej Karpathy开发的循环神经网络。Andrej是斯坦福大学的深度学习研究者,并且他写了一个极好的循环神经网络文本生成入门,你可以在GitHub查看完整代码。

模型建立在《太阳照常升起》的文本数据基础上——362,239个字,84个字符(包括标点符号)。相比于实际应用,这一数据量其实小的很。为了真正搭建一个好的海明威文风的模型,至少需要若干倍的样本文字量,不过对于一个入门实例来讲,现在的就足够了。

刚开始训练RNN的时候,预测能力还不太行,这是100次训练迭代后的成果:

hjCTCnhoofeoxelif edElobe negnk e iohehasenoldndAmdaI ayio pe e h’e btentmuhgehi bcgdltt. gey heho grpiahe.

Ddelnss.eelaishaner” cot AAfhB ht ltny

ehbih a”on bhnte ectrsnae abeahngy

amo k ns aeo?cdse nh a taei.rairrhelardr er deffijha

可以看见词与词之间已经有空格了。

1000次迭代之后,明显看起来更上道了:

hing soor ither. And the caraos, and the crowebel for figttier and ale the room of me? Streat was not to him Bill-stook of the momansbed mig out ust on the bull, out here. I been soms inick stalling that aid.

“Hon’t me and acrained on .Hw’s don’t you for the roed,” In’s pair.”

“Alough marith him.”

模型已经识别出了基本句型模式,并且知道在句末加句号,给对话加引号,个别单词已经能读懂了,但整篇仍是一派胡言。

又经过几千次迭代之后,现在已经看起来非常好了:

He went over to the gate of the café. It was like a country bed.

“Do you know it’s been me.”

“Damned us,” Bill said.

“I was dangerous,” I said. “You were she did it and think I would a fine cape you,” I said.

“I can’t look strange in the cab.”

“You know I was this is though,” Brett said.

“It’s a fights no matter?”

“It makes to do it.”

“You make it?”

“Sit down,” I said. “I wish I wasn’t do a little with the man.”

“You found it.”

“I don’t know.”

“You see, I’m sorry of chatches,” Bill said. “You think it’s a friend off back and make you really drunk.”

至此,算法已经捕捉到了海明威对话短小精悍的特征,有些句子甚至已经能够达意了。

跟书中的真实文本对照一下:

There were a few people inside at the bar, and outside, alone, sat Harvey Stone. He had a pile of saucers in front of him, and he needed a shave.

“Sit down,” said Harvey, “I’ve been looking for you.”

“What’s the matter?”

“Nothing. Just looking for you.”

“Been out to the races?”

“No. Not since Sunday.”

“What do you hear from the States?”

“Nothing. Absolutely nothing.”

“What’s the matter?”

即便是循着单个字母的模式,我们的算法已经模仿得有鼻子有眼了,这好的很呐!

无需从零开始生成文本,我们可以提供前几个字母作为“种子”,然后让算法自己寻找后面的。

出于娱乐目的,我们来做一本假书,用”Er”,”He”,”The S”作为种子生成新的作者名字和新标题:

左:真书;右:计算机生成的假书

还不赖!

真正令人振奋的是,这一算法可以从任意数据中识别模式,能够轻易地生成以假乱真的菜谱假的奥巴马讲话。为什么要局限在人类语言当中呢?我们大可以应用在任何内含模式的数据之中。


你可能玩了假的马里奥

2015年,任天堂推出了用于Wii U游戏机的超级马里奥编辑器

这个游戏让你可以自由设计超级马里奥的关卡,并且上传到网上和朋友们分享。你可以加入老游戏中的所有经典机关和敌人,简直是马里奥时代生人的虚拟乐高。

能不能用生成假海明威文字的模型,来生成假马里奥关卡呢?

首先需要准备训练数据,从1985年原版超级马里奥兄弟里提取出所有的室外关卡。游戏中共有32关,其中约70%是在室外,所以就从这下手。

为了获取每个关卡的设计,我写了一个程序从游戏中提取出关卡设计,超级马里奥已有三十多年历史,所以网上关于游戏设计的资源非常多。从老游戏中提取数据也是个很有意思的编程练习,你也可以试一试。

凑近了看,游戏关卡其实是由一组物体网格构成的:

我们可以用一组字符来代表这个网格,每个字符就是一个物体:

1
2
3
4
5
6
7
8
9
10
11
12
--------------------------
--------------------------
--------------------------
#??#----------------------
--------------------------
--------------------------
--------------------------
-##------=--=----------==-
--------==--==--------===-
-------===--===------====-
------====--====----=====-
=========================-

每个物体是一个字符:

  • ‘-‘是空白
  • ‘=’是实心砖
  • ‘#’是可以打破的砖
  • ‘?’是硬币砖

字符替换后的效果就是这样:

观察文本文件可以发现,马里奥的关卡如果逐行来看,并没有什么模式:

但是如果逐列来看,模式就很明显了:

逐列看,模式很明显,比如每列都是以’=’结束的

为了让算法能够识别模式,需要逐列输入数据。从输入数据中找出最有效的表征方式(即特征选择)是用好机器学习算法关键。

为了训练模型,我们把文本文件反转90度,这让特征更加显而易见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-----------=
-------#---=
-------#---=
-------?---=
-------#---=
-----------=
-----------=
[email protected]=
[email protected]=
-----------=
-----------=
-----------=
---------PP=
---------PP=
----------==
---------===
--------====
-------=====
------======
-----=======
---=========
---=========

训练模型

就像模仿海明威文风的例子一样,模型是越训练越好的。只经过很少量训练的结果,看起来真垃圾:

1
2
3
4
5
6
7
8
9
10
11
--------------------------
LL+<&=------P-------------
--------
---------------------T--#--
-----
-=--=-=------------=-&--T--------------
--------------------
--=------$-=#-=-_
--------------=----=<----
-------b
-

大概摸索出了’-‘和’=’出现的比较多,但仅仅如此,没有真正发现什么模式。

经过几千次迭代,开始有点样子了:

1
2
3
4
5
6
7
8
9
10
11
--
-----------=
----------=
--------PP=
--------PP=
-----------=
-----------=
-----------=
-------?---=
-----------=
-----------=

模型已经基本知道了每行的长度应该一致,并且也开始发现了游戏中的一些特点:管子总是两块宽、至少两块长,所以”P”以2X2的小组出现,这很不错!

在更多的训练之后,模型已经生成了很多有效的数据:

1
2
3
4
5
6
7
8
--------PP=
--------PP=
----------=
----------=
----------=
---PPP=---=
---PPP=---=
----------=

来看看模型生成的完整关卡(转回横版):

看起来相当好!有几个亮点值得注意:

  • 开始有一个Lakitu(空中浮游怪)在天上——就像真实的马里奥关卡一样。

  • 空中的管子必须底部有砖块,而不能孤立地飘在半空。

  • 怪物的分布比较合理。

  • 不会出现组织玩家前进的死路。

  • 感觉上确实很像一个真实的关卡。

如果你有超级马里奥编辑器的话,你可以通过bookmarking it online或者查找代码4AC9-0000-0157-F3C3来玩这一关卡。


玩具和真货

我们用来训练模型的循环神经网络算法,跟现实世界里很多公司用来解决难题(如语音识别,机器翻译)的算法是一样的。但我们的模型只建立在很少的数据量之上,所以只能算是一种“玩具”而不能动真格的。原版超级马里奥游戏里的关卡数不足以训练出一个真正好的模型。

如果我们能像任天堂一样,拿到数以十万计的玩家自制关卡数据,我们就可以做个更强的模型了。不过任天堂不会坐视其发生的,大公司都不会免费开放数据。

随着机器学习在很多行业里越来越重要,好程序和坏程序的差别越来越多地体现在,训练模型的数据量。这就是为什么Google和Facebook等公司如此需要你的数据。

比如Google最近开源了用于搭建大规模机器学习应用的软件包TensorFlow,Google免费开放了如此重要而强力的技术,引起了巨大反响,而TensorFlow正是支撑Google Translate的后台。

但如如果没有Google在每种语言上留下的海量数据宝藏,你也不可能做出Google Translate的对手,数据才是Google真正的杀手锏。下次当你打开Google Maps Location HistoryFacebook Location History的时候,留意它们记录了你的一切所到之处。