声明: 本文由DataScience 发表, 转载请注明本文链接 mlln.cn, 并在文后留言转载
.
摘要 物竞天择是大自然最伟大的发明。生命是如何从无机物一步步进化成智能的实体? 我们可以使用进化算法感受以下这个过程。今天的一个机器学习的小例子不需要任何训练数据和人工指导, 只需要遵循适者生存的生命法则, 就能让生物学会跑步。
本篇文章要带领大家使用tensorflow.js实现一个神经网络, 并使用进化算法更新神经网络的参数, 并使用p5.js实现整个训练过程的可视化。你可以看这个gif图片看到我们要做的东西。另外通过这个DEMO 可以全程观察生物进化的过程, 你会看到跑的更远的生物适应能力更强, 能繁衍更多的后代。
进化算法概念
机器学习模型本质上是函数拟合。
无论是分类,回归还是强化学习,最终目标几乎总是找到将输入数据映射到输出数据的函数。
所以, 问题可以变得很简单, 给你一堆训练数据(包括输入$x$
和输出$y$
), 你要找到一个函数(调整函数的参数)$f$
, 使得$\hat y = f(x)$
, 并且 $\hat y ≈ y$
进化算法的过程
我们假设要学习的函数是一个神经网络结构的函数
生成随机神经网络函数参数, 比如生成100个不同的参数但相同结构的函数
使用高斯分布随机确定每个函数的参数
检验这些函数的性能, 选择最好的函数
性能越好的函数有越大的几率影响他们的下一代函数
繁殖下一代函数
重复上面的过程, 逐步提高函数的性能
进化算法有以下的特点:
因此,我们使用几种模型而不是单一模型。这是与梯度下降的关键区别
NEAT,HyperNEAT和新奇搜索就是一些例子
进化策略,遗传算法等对于如何进行遗传优化的方法都略有不同。
用下图来更具体的说明
首先,确定评估性能的方法。
这意味着检查函数并确定哪些函数表现最佳。
接下来,基于评估结果选择优秀的函数。
对于DNN,常用指标定义为损失或奖励。
生成下一代函数, 让优秀的祖先获得更高的权重来影响下一代的函数参数, 并且增加一些随机值来提高函数的可能性
进化算法用途
Tensorflow.js架构 这里不详细介绍tensorflow.js, 有兴趣的可以关注我以后的教程。总的来说, tensorflow.js分类两个部分:
核心 API (Low Level) - 我们今天的代码用的就是这一部分
计算图是一系列TensorFlow操作,排列成图形。
图形由两种类型的对象组成。
操作(或“操作”):图形的节点。操作描述消耗和产生张量的计算。
张量:图中的边缘。这些代表将流经图表的值。大多数TensorFlow函数返回tf.Tensors。
1 2 3 a = tf.constant (3.0 , dtype=tf.float32 ) b = tf.constant (4.0 ) # also tf.float32 implicitly total = a + b
Layers (high level)
图层将变量和作用于它们的操作打包在一起。
例如,全连接层对每个输出的所有输入执行加权和,并应用可选的激活函数。
连接权重和偏差由图层对象管理。
以下代码创建一个Dense图层,它接受一批输入向量,并为每个向量生成一个输出值。
要将图层应用于输入,请将图层称为函数。例如:
1 2 3 x = tf.placeholder (tf.float32 , shape=[None , 3 ]) linear_model = tf.layers .Dense (units=1 ) y = linear_model (x)
模型介绍 (我会使用”生物”这个词来代表我的函数, 进化算法常常这么叫)
我们要在网页上创建一个20个生物
他们需要自己摸索如何走路, 并且走的越远越表明”基因”越优秀
为了简单, 他们只有两条腿, 如上图
所有生物都有3层前馈神经网络作为他们的大脑
每层的神经元个数是4/10/X,其中输出层中的节点数X取决于生物的肌肉数量。
馈送到网络的输入数据是(可以当作这些生物只有这4个感觉):
性能指标
生物可以根据它从起点移动的距离获得分数。
它越向正确的方向行进,它获得的分数越多, 说明这个函数的性能越好。
在相反方向行驶,将减少分数。
选择优胜的生物
根据生物的性能选择生物, 被选择的生物才有机会进行繁殖。
他们所得的分数被用于计算选择的概率。
表现更好的生物具有更高的分数,因此具有更高的机会进行繁殖。
交配繁殖:
使用上面的选择算法选择两个生物(父母)。
如下图所示,它们的权重按位随机互换,形成一组新的权重。
在我们的例子中,一个位代表一个权重值。这组新的权重用于形成一个新的生物(孩子), 也可以被称为一个函数。
突变
突变率通常约为1-2%,实际上是引入随机性的概率。
代码实现 第一部分实现一个神经网络结构(生物的大脑) 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class NeuralNetwork { constructor (input_nodes, hidden_nodes, output_nodes ) { this .input_nodes = input_nodes; this .hidden_nodes = hidden_nodes; this .output_nodes = output_nodes; this .input_weights = tf.randomNormal ([this .input_nodes , this .hidden_nodes ]); this .output_weights = tf.randomNormal ([this .hidden_nodes , this .output_nodes ]); } predict (user_input ) { let output; tf.tidy (() => { let input_layer = tf.tensor (user_input, [1 , this .input_nodes ]); let hidden_layer = input_layer.matMul (this .input_weights ).sigmoid (); let output_layer = hidden_layer.matMul (this .output_weights ).sigmoid (); output = output_layer.dataSync (); }); return output; } clone ( ) { let clonie = new NeuralNetwork (this .input_nodes , this .hidden_nodes , this .output_nodes ); clonie.dispose (); clonie.input_weights = tf.clone (this .input_weights ); clonie.output_weights = tf.clone (this .output_weights ); return clonie; } dispose ( ) { this .input_weights .dispose (); this .output_weights .dispose (); } }
第二部分实现达尔文选择和交配繁殖 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class Generation { constructor (population ) { this .population = population; this .species = []; this .generation = 1 ; this .high_score = 0 ; this .avg_score = 0 ; this .total_score = 0 ; this .fitness = 0 ; this .progress = 0 ; } initialize (Person ) { for (let i = 0 ; i < this .population ; i++) { let new_Person = new Person ({ upper_length : 30 , upper_width : 8 , lower_length : 30 , lower_width : 6 , x : width * 0.15 , y : height * 0.85 , id : i }); this .species .push (new_Person); } } pickOne ( ) { let index = 0 ; let r = Math .random (); while (r > 0 ) { r -= this .species [index].fitness ; index += 1 ; } index -= 1 ; let selected = this .species [index].clone (); return selected; } evolve ( ) { this .generation += 1 ; let gen_highscore = Math .max .apply (Math , this .species .map (o => o.score )); this .high_score = gen_highscore > this .high_score ? gen_highscore : this .high_score ; let total_score = 0 ; this .species .forEach ((person ) => { total_score += person.score }); this .progress = (total_score / this .population ) - this .avg_score this .avg_score = total_score / this .population ; for (let i = 0 ; i < this .population ; i++) { this .species [i].fitness = this .species [i].score / total_score; }; let new_generation = []; for (let i = 0 ; i < this .population ; i++) { let parentA = this .pickOne (); let parentB = this .pickOne (); let child = parentA.crossover (parentB); child.mutate (); child.id = i; child.params .id = i; child.colors = [parentA.colors [0 ], parentB.colors [1 ]]; child.parents = [{ id : parentA.id , score : this .species [parentA.id ].score }, { id : parentB.id , score : this .species [parentB.id ].score }]; new_generation.push (child); } for (let i = 0 ; i < this .population ; i++) { this .species [i].kill (world); } this .species = new_generation; for (let i = 0 ; i < this .population ; i++) { this .species [i].add_to_world (world); } } }
第三部分可视化相关代码 可视化是用p5.js实现的, 因为不是这篇文章的重点, 所以这里没有贴出来。
总结 通过上面的代码, 你应该能够理解进化算法的基本理念了。进化算法和梯度下降法的区别就是他们更新函数参数的方法不同。
你可以通过这个在线Demo , 观察整个生物进化过程。
上面的代码来自这个GitHub , 感谢作者.
注意 本文由jupyter notebook转换而来, 您可以在这里下载notebook 统计咨询请加QQ 2726725926, 微信 mllncn, SPSS统计咨询是收费的 微博上@mlln-cn可以向我免费题问 请记住我的网址: mlln.cn 或者 jupyter.cn