diff --git a/README.md b/README.md index febd225..c98db50 100644 --- a/README.md +++ b/README.md @@ -1,228 +1,37 @@

## Frog | 人工生命 -这是一个人工生命试验项目,最终目标是创建“有自我意识表现”的模拟生命体,技术架构基于02年提出的 [一个人工脑模型](other/一个人工脑模型.md)。这个项目永远没有结束的时候,开始于模拟一个简单的生命体,然后是青蛙、狗......, 结束于有“自我意识表现”的人工脑,或者说,结束于被机器人代替人类的那一天。 +这是一个人工生命试验项目,最终目标是创建“有自我意识表现”的模拟生命体。 -## 缘起 | Origin -目前人工智能的进展已经比较完美地解决了模式识别这块的难题,人脸识别、语音识别已经不弱于人类的水平,而这是我在二十年前感到最困惑的一块。模式识别解决了,剩下的问题就简单多了,目前距离人工意识的诞生只差临门一脚了,就是如何在识别的基础上“理解”这些识别的内容并与人类形成交互的反馈。所以这个项目的重点是建立在模式识别基础上,训练神经网络形成条件反射,表现出高等动物才具有的条形反射行为,最终表现为"拥有自我意识"的行为。根据“意识不是一种存在,而是一种现象”原理,如果最终一个系统表现出具有自我意识的行为,即可认为它也是人,应该获得人权。目前有些人工智能的研究目的是想让人工智能解决一些复杂的人类社会方面的问题如机器翻译等,则是完全错误的目标,不可能成功,因为如果一个系统不能表现出自我意识,它就不能与人类交流,也就不可能具有解决这些问题的能力,表现出来的现象就是通常说的"机器不犯错,一旦犯错就是大错"。另一方面,如果一个系统表现出具有自我意识的行为,它就完全有能力解决世界上所有难题,包括改进它的自身和淘汰人类(因为他是先进生产力的代表)。所以人工智能的研究重点应该放在人工生命(或通用人工智能,人工生命和通用人工智能是等价的)的构建和伦理研究,而不是期待短期收益,指望人类可以一直享受人工智能的大餐是很危险的。模式识别和深度学习的成果只是通用人工智能的一个路标,人工智能的“有用”的应用期,很可能只是奇点之前白马过隙般短暂的一个过渡期而已,不用高兴得太早,也许都是白忙,给机器人作嫁衣。奇点之后,很可能所有生物智能都将淘汰。实际上,人工智能不光是技术问题,还是个人生观问题,专家的认识有时候并不总对。在此摘一段芦秋迪群友建议放上来的对话: -![talk](talk.png) +### 注:因README篇幅太长,2023年8月之前的内容已归档到[README1.md](README1.md)中了,如果第一次看到这个项目的网友,请先从[README1.md](README1.md)开始看。如果已经了解这个项目的,请接着往下看即可。 -简单来说,这个项目是一个民科项目,试图以实验为导向,模拟生命进化的过程,按照优胜夯汰、随机变异、用进废退这三大原则,一步一步地搭建出从低等到复杂的人工生命体,除了模式识别的成果可以借签,原则上不需要学习很多数学知识,因为它强调由实验来驱动,而不是由复杂的算法来搭建神经网络。目前神经网络研究重点在于模式识别,但对系统赋予条件反射功能关注不够,没有把无生命的神经网络和有意识的人类看作同一个等级的自然现象。 -从单细胞进化到多细胞、从青蛙进化到人类,这是一个漫长的、随机的进化过程,但在超级电脑上跑可能只要几天时间,就可能得到一个相当不错的脑模型。当然电脑速度越快、容量越大、环境模拟的越真实,则优胜夯汰后形成的脑结构就越复杂,错的脑模型都被自然淘汰掉了。从算法着手搭建,还是从模拟环境着手自动进化,这是创建人工生命的两个大方向,第一个方向有可能走到死胡同里,因为它不具备算法自改进、变异、遗传(算法的压缩)功能,当脑模型复杂到一定地步,可能会超出人脑能理解的范畴。模拟环境方式的难点则在于环境本身必须足够复杂、正确。而且必须循序渐进,与脑的进化同步,如果把一群青蛙扔到猴子的模拟环境中,则所有青蛙都会被自然淘汰掉,项目就无法进行下去了,另一个困难是电脑必须非常快,因为目前是用串行方式模拟并行算法,不断试错前进的过程。项目语言为Java,利用Swing作图环境,构建一个虚拟环境、并模拟一群草履虫的优胜夯汰,来获取一个具备自进化功能的人工生命体,具体脑(即电脑生成的神经网络)的生成、进化算法还需要以后逐渐加入。欢迎有对神经网络感兴趣的朋友一起来琢磨,这个项目不需要多少高等数学知识,重在实践。 - -## 短期目标 | Sort-term Goals -第一个初步目标是:造出一个真正意义上的人工生命:草履虫(备注:基本已完成)。它必须具备以下前四个特点: -* 脑结构由电脑生成:神经网络由电脑算法生成,但是电脑算法仅限于模拟环境,而不是直接参与搭建神经网络,就好象大自然只负责拍死不合格的生命,它从不主动参与设计大脑。 -* 脑结构可遗传:类似于生物的DNA,电脑生成的脑结构(神经网络),可通过简单的算法规则描述,并且此算法规则可以压缩成较短的片段存储,并参与到下一代草履虫的构建。 -* 脑结构可变异:算法规则可以变异,下一代生成的草履虫在脑结构上与上一代总体相似,但存在部分变异。 -* 适应环境:草履虫能够在模拟的虚拟环境下存活下来,环境有微小的变化,能够自适应环境,并一代代生存将适应这种环境的能力遗传下来。 -* 用进废退:这是一个假想,对于生物来说,存在这样一种现象,就是用的多的器官,容易发生变异(例如经常嚼槟榔,容易发生口腔癌变),有理由相信这不是偶然现象,而是生物在进化过程中的一个有用的功能,以便于更快地变异,以适应环境,并很可能这种变异会通过遗传细胞影响到下一代。 - -## 理论 | Theory -为什么明明是个电脑程序,只要满足上述前四个特点就可以称之为"真正"的人工生命? 这一点我不想多说,大家可以搜索一下"zhangrex的造人论坛"就知道我的观点了:意识从来就不存在,意识只是一种现象。风吹、树动和风吹、添衣,都只是现象而已,意识本质上是一种现象,同理,只要表现出类似生命现象的事物,就可以称其为生命了,不管它是高等还是低等,不管它的物质存在基础是怎样的。二十年前我就开始思考这个问题,提出了“我思我不在”的口号,请仔细考虑一下这个观点,哲学上、理论上对智能、意识的研究是很无聊的,相当于在研究“不存在”到底是什么,不必多纠缠在理论和算法上。是的,用模拟环境得到的人工神经网络模型,我们确实无法掌控它的算法是怎样生成的,但是我们知道,这符合大自然创造生命的规律。 - -## 项目架构 | Architecture -这是一个Java项目,分为Application、Env、Frog三个模块: -Application模块: 用于项目的启动、关闭等基础服务,Windows环境下可以用run.bat来启动它查看演示。 -Env模块: 模拟一个生物生存区,用不同形状的图形点阵来表达和模拟食物、天敌、障碍等物体,这个虚拟空间由程序员全权控制,将随着Frog的脑进化而不断变得越来越复杂。 -Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什么都一样。它主要具备以下器官: -* 运动器官: 与运动神经元相连,只有4个动作:上下左右。 -* 进食器官:当Frog与食物的坐标重合时,食物会被从Env中删除,并相应增加Frog的能量值,并激活Frog的进食感觉神经元,随时间流逝能量将减少,能量耗尽则Frog死亡。 -* 视觉器官: 这是脑模型的一部分,在实验中先固定随意取脑内一片神经元区作为视觉区。 -* 脑器官: 这即是程序员要解决的问题,也是我们要获取的最终目标。脑模型的生成由电脑优胜夯汰、循环迭代进化生成,但这个进化的算法还是必须由程序员来掌控,一步步探索出来,即要防止虚拟环境太复杂,也要避免脑模型不适应环境,生命体全部被淘汰,导致实验中断,无法进行下去。 - -## 技术细节和构思 -* 通过数组来模拟神经网络,用串行的循环来模拟并行芯片运作方式。用Frog的能量多少来衡量是否将它淘汰还是允许它产生后代(下蛋)参与下一轮的测试,因为这个项目的目的是获取智能体,与一般的生命游戏还是有区别的,并不是适者生存就结束了,而是必须完成一系列程序员设定好的目标,一步步进化,直到表现出自我意识现象为止。脑模型的生成算法通过简单的神经元连接完成,原则上不允行出现任何形式的硬编码(除模式识别外),因为硬编码可能会破坏“随机变异”这一生命特性。为简单起见,暂不考虑引入GPU图形芯片进行加速。 - -## 项目要实现的短期和长远目标 -* 脑模型和虚拟环境的初步搭建 [脑模型刚开始搭建。虚拟环境已完成,点击run.bat可以查看演示] -* 使脑模型具有视觉功能,如果有食物在它附近,将激发天生条件反射,向食物移动,并获得进食奖励 [未全部完成] -* 引入现成的图像识别算法,使脑模型具有图像识别功能,根据形状区分食物、毒物、天敌 [未完成] -* 如果误食有毒食物,将激发天生条件反射,获得惩罚并扣除能量,天生痛觉区兴奋。[未完成] -* 如果被天敌攻击,将激发天生条件反射,获得惩罚并扣除能量,天生痛觉区强烈兴奋。[未完成] -* 训练它将打击行为与痛觉兴奋区发生关联。[未完成] -* 训练它将看到“打”这个文字,与打击行为和痛觉兴奋区建立即系。[未完成] -* 训练它背下这100个字中所有汉字可能组成的常用词组,给它一个字,所有与这个字相关的词组细胞区兴奋。[未完成] -* 训练它一看到“食物来了”文字,就从窝里出来找吃的。[未完成] -* 训练它理解“你”,“我”、“他”文字,只针对“我”相关的指令作出反应。[未完成] -* 训练它认识数字,会做四则运算[未完成] -* 训练它认识圆、矩形、会计算面积,学会估算和判断"大"和"小"[未完成] -* 训练它认识坐标和时间,并按指令行动,如看到"你在9点走到右上角去,等三分钟后再回来",将遵从指令行动。[未完成] - -## 最终目标 -* 扩大它的输入网格和输出网格规模,扩大神经元数量, -* 移殖到超级电脑上,由人来同它交流,输入新的图形和汉字,纠正它说的错误的话 -* 移殖到并行芯片硬件上 - -## 目前进展和成绩 -2019.03.11 虚拟环境已建好,可以模拟低等生命的遗传、繁殖、变异、进化现象,但只能往一个方向运动,相当于一个最简单的单细胞生物,还不具备视觉能力,不具备主动找食能力。 -运行run.bat可以查看演示(需要安装Java8和Maven)。 -![result1](result1.gif) -另外每步演示的结果(egg)会存盘在根目根目录下,名为egg.ser,可以删除这个文件以从头开始新的测试。因为还没涉及脑模型的搭建,可以看到有些青蛙跑得飞快,这是自然选择的结果,因为跑在最前面的吃得多。 -一些重要的测试参数如显示区大小、是否每次测试要删除保存的蛋等,请参见Env.java中开头的常量设定,可以手工修改进行不同参数的测试。 -2019.03.21 添加了脑图,改进随机运动模式为Hungry区驱动。从脑图上可以直观地观察脑结构,方便调试。 -2019.04.01 改进脑图的显示bug, 每一次生成Frog时添加随机神经元,并简单实现"卵+精子->受精卵"算法,以促进种群多样性。 -2019-04-12 添加一个简单的眼睛(只有四个感光细胞),自然选择的结果是眼睛被选中,但是和运动区短路了,谈不上智能。但有眼睛后找食效率明显提高了,见下图: -![resut2](result2.gif) -2019-06-13 做了一些重构清理,加上了Happy和Pain两个器官,分别对应进食奖励和痛苦感,后者在靠近边界时激发。观查它的表现,痛苦感生效了,一些Frog跑到边界后就不再前进,而是顺着边界溜下去了,但是Happy器官没有生效,这也很显然,因为Happy属于复杂的进食条件反射链的一部分,在没有记忆器官(算法)引入之前,再怎么优胜劣汰也是没办法用上进食奖励信号的。见下图: -![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) -2019-06-26 找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,人为设计的算法居然比不过随机连接。Pain器官的加入没有提高找食效率,必须与感光细胞合用才能知道是哪个边界,急需引入记忆功能。 -2019-06-28 为了让青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。 -2019-07-28 有以下改动: 1.在Env区中间加了一个陷阱区Trap,以增加趣味性,自然选择的结果是青蛙会自动绕开陷阱区。2.增加一个Active器官,它的作用是一直保持激活,发现比Hungry器官驱动更能提高找食效率。3.增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。目前进食奖励信号没用到,白白浪费了。 -另外Chance和Eye类里也再次运用了随机试错原理去确定关键参数,效果还不错,有兴趣的可以看一看源码。 -![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) -另外发现青蛙其实是有记忆能力的,因为连接本身就是一种记忆,只不过它没有复杂的模式识别能力,例如给个蛇的图片它就认不出来。以后的工作将以模式识别为重点(当然随机连接看起来很有用,以后还可能保留),基本原理是见note中提到的仿照波传播及全息存储原理,在思维区逆向成像。而且脑可能改成三维结构,并根据逆向成像原理,要将所有输入输出器官全移到三维结构的同一侧(即思维区)。这将会是一个非常大的改动,下面我简单画了一个3D示意图来说明我想象中的这个模式识别和记忆的原理,至于对不对还需要实验来验证: -![3d-model](3d-model.gif) -这个模型的最顶层表示眼睛的感光细胞(或任意输入输出细胞),同时也是思维区,红色表示一个长条的图形,兰色表示一个三角图形,如果这两个图形经常有规律地同时出现,就会把它们共有的节点撑大,见紫色的节点,当红色图形单独出现,就会强烈激活紫色节点,然后紫色节点的信号反向传播,就会激活三角图形,反之亦然。这就同时解释了模式识别和记忆(或者说回忆)功能的的原理。一个节点可以被多个感光细胞共享,所以它的存储能力是很强的。而且可能这个原理比较符合生物脑结构。当然,实际编程时,虚拟神经元不一定要排成正立方三角,而可能通过胡乱排放,大致上过得去就行了,也许能乱拳打死老师傅,最终要靠电脑自动随机的排放,然后用优胜劣汰来筛选。目前有个难点是这个记忆功能在思维区成像是如何有条不紊地大体上按串行进行工作的,这个问题先放一放。 -2019-08-04 更新了一个分组测试功能,如果测试青蛙数量太多,可以分批来测试,每轮测试最少的青蛙数量可以少到只有一个,这是用时间来换空间。 -2019-08-05 有了分组测试功能后,顺手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: -![result5](result5_seesaw.gif) -这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,青蛙只会找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,我将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 -青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,器官的用进废退、奖励信号的利用都没反映,但我认为这些还不关键,目前最急迫的任务应该是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式完全不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 -2019-09-09 开始3D脑的构建,任务又回到原点:找食,从静止的青蛙要能进化到吃光所有食物。目前只是搭建空的3D框架,还未涉及3D脑模型编程。新的工作存放在core3d目录,原有的旧core目录保留,相应地批处理文件也分为普通版run.bat和3d版run3d.bat,蛋文件也分为普通版eggs.ser和3d版eggs3d.ser。 -脑的3D版要引入模式识别功能,第一个编程任务是要用这个3D脑模拟出体全息存贮现象,也就等价于模式识别功能,用字母的点阵图像激活它的视觉区,并同时激活一个随意指定的脑区作为字母识别区,然后只激活视觉区的图像,再检查这个随意指定的字母区是否会被激活,而其它字母区基本不激活。体全息存贮在物理上很难实现,因为光和材料受物理特性制约,但在虚拟脑中不存在这个物理限制,让虚拟的光子反射、拐弯、增强、拆分都可轻易模拟出。 -反之,如果对应A字母的脑区兴奋,也要能激活一个模糊的A的像素点阵图像脑内某个区(与视网膜重合或下层),这就是信息的检索和联想,这个联想功能以后会用到,由痛苦、愉快等奖惩机制来调节这个联想过程一直永无止歇地进行下去。 -2019-11-11 字母的模式识别功能 -这是切换到3D脑的第一个正式版本更新,它实现了ABCD四个字母的识别。测试时分别用ABCD四个字母,并同时加上一个声音信号,模拟体全息存贮。另外这个模式识别是双向的,如果只单单激活听力区,也会在视网膜区成像。(如果要演示这点,需要将LetterTester.java中的seeImage和hearSound两行注释互换一下,并去除Cell.java中的59和60两行,这两行代码的作用是阻止光子逆向传播到视网膜上)。以下是这个模式识别的截图,黑色的小点表示视网膜发出的视觉波信号,蓝色的表示耳朵发出的听力波信号:在它们交汇的地方,细胞象果冻一样,被波的载体(光子)砸出洞来,每当接收到新的光子,就有可能在旧的洞里砸出光子来并逆向传播,用红色小点来表示,最终在波源处逆向成像。这个工作原理在细胞级别将相关的信号关联起来,也是体全息存贮的模拟实现,可以在三维空间实现信息的高密度存贮: -这个模式识别的原理比较简单,不需要任何高等数学知识,每个人都能看懂,而且它可能更符合人脑的工作模式,它可以进行图像到声音的关联,也可以实现声音到图像成像的逆关联,另外还有两个重要优点:1.它可以同时处理多维的信号,也就是说可以同时处理多个图片、声音等信号。 2.它的训练速度非常快,没有采用什么海量的大数据来进行训练,只要任意有关联的信号,哪怕信号只出现一两次,它都会自动将它们关联起来,这个关联是动物的条件反射能够建立的基础。 -有了模式识别,以后的工作就好办了。今后将在这个模式识别基础上进行扩展,进行多参数优化自动生成器官、声音的编码、把小蛇引入到虚拟环境等等一系列更复杂有趣的任务。 -2019-11-16 模式识别功能更新 -上次提交有个大bug,有多个字母出现时将不能区分,这次提交更正过来。到此为止,基本完成了模式识别的原理验证过程,即如果字母的象素点与训练图片的重点合越多,则听力区收到的反向红色光子数就越多,这是一个简单、直观的模式识别方法,以后可以通过将声音分成多个小区编码,并统计每个区收到多少反向的光子总数来判断是哪个字母图像输入。原理验证比较枯燥,但这个模式识别功能是无法绕过去的,一旦原理被证实,以后就可以有意地引导或者说设计青蛙向这个方向进化,而不是由手工来搭建包含模式识别功能的脑模型,因为一来要减少手工的干预,硬编码越少越好,尽量利用随机变异、生存竟争这个电脑工具,二来这个原理不光是模式识别要用到,其它信号处理(如快感、痛觉信号与行为信号之间的关联)都要用到类似的细胞级逻辑,因为我一直在强调“任意两个时间上相关的信号,大脑总会将它们关联起来,这是条件反射行为建立的基础”。 -另外,这次更新加强了暂停功能,可以在脑图任一时刻暂停,并加上了脑图的剖面显示。所有脑图快捷键有: T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标操作:缩放旋转平移 -![result6](result6_letter.gif) -2019-11-26 优化了一下程序,用"对酒当歌人生几何"8个汉字来进行模式识别原理演示,但容错性依然没有,变形、变位后的文字识别率很差。以后要考虑借签算法中的侧抑制、卷积、分层等原理来提高它的容错性,用图形化模拟的方式来实现。总体上算法和图形化模拟是两条路,算法通常可以用模拟的方式来表达,但不是所有模拟都可以归纳成算法,因为模拟(或者说软件本身)有时会非常复杂,不容易总结出规律。也就是说模拟的表现力比算法更强,但模拟的缺点是资源消耗大。 -2019-12-27 开始设立history目录,给主要的版本直接在history目录下创建副本,以方便运行。在history\003a_legs目录下(依然是2维脑)尝试给青蛙加两条腿,看它能不能自动学会走路。一条腿位于下方,负责左右移动,一条腿位于右侧,负责上下移动,每条腿有抬腿、落腿、转动和相应的感觉细胞。只有当腿落下且转动,而且另一条脚抬起来时青蛙才会位移,具体什么时候抬腿、什么时候转动腿完全由随机数决定。经过一段时间的生存汰淘之后,青蛙会进化出会利用两条腿走路了,但需要的时间非常长,约几个小时之后才达到最高吃食率50%左右,走路风格也比较诡异,是小碎步而不是大踏步。但至少这是青蛙第一次利用两条腿来走路,还是有点意义的,这证明生命进化中就算神经元随机排布,进化出眼睛和腿也是非常简单自然的事。这个实验只给青蛙加了两条腿,但同理如果有四条或更多的腿它应该也是可以随机进化出来的。 -![result7](result7_legs.gif) -2020-05-04 在进行3维脑改造过程中,发现找食率很低,自己也看不懂以前的程序怎么编的了,所以在history目录下又添加一个003b_simple目录,把2维脑简化一下,去掉不重要的器官,好分析它的逻辑。 -2020-05-07 经过一番折腾和走弯路之后,最后还是原汁原味地将2维脑003b目录的逻辑搬到了3维脑core目录里了,实现了同样的找食率(~50%左右)。从现在开始,可以专注于改进3D脑本身了。 -2020-06-26 小蛇吃青蛙 -位于history\006目录下,设定小蛇只能看到青蛙,青蛙只能看到蛇(严格说是蛇的舌头)。可以看到小蛇会追着青蛙,而青蛙会躲开小蛇,当然也有躲不开被吃掉的。除了引入负值连线用蓝色线条来表示外,技术细节上倒没有什么突破,但这个实验有趣的地方在于它证实了就算是完全随机的排列脑细胞,在长期的优胜劣汰后,生命也会进化出捕食和逃避行为。即然可以进化出捕食和逃避行为,而生命进化又会向越来越复杂的方向进化,所以这个原理可以解释为意识的萌芽了。高等生命的意识,本质上也无非就是大自然随机运动产生的一种复杂现象而已。 -![result8](result8_snake.gif) -下一步的工作将移回到体全息存贮的模式识别,因为青蛙需要这个模式识别功能来更好地分辨出蛇和食物的区别,而体全息存贮个人感觉有很多潜力可挖,它有两个最大的优点:一是可以小样本学习,二是可以同时处理多维的信息输入输出。 - -2020-09-13 这次不是更新了,因为我最近太忙,所以先临时发布一个小开发任务,见下图,如果有兴趣的同学可以尝试一下看能不能做出来: -![task1](task1.png) -说明:详见图片,通过模拟一个虚拟的环境来淘汰青蛙,只有当青蛙进化出能够在震心的位置发出叫声,其它的青蛙能听到这个叫声跳在空中躲避震波时,青蛙的生存机率才会变高,但是跳起来也会消耗能量,只有听到报警后跳起来才正好。如果这个实验能做成功,就可以证明生物即使脑神经元随机排列,也会进化出发音和听力器官,这是挺有意义的,因为这是一个群体进化,对于单个青蛙来说,进化出能叫,或是能听到声音对它自己是意义不大的,但是对于群体的生存有利,这个进化相当于是语言和听力的萌芽。整个虚拟环境的设定,就是要逼迫这种群体进化现象产生出来,个人认为是有大概率能做出来的。这里说进化出器官,不是变出来,而是模拟生物体偶然变异出了听力细胞和发音器官,这个是没问题的,问题的重点在于听力细胞和发音器官是如何在进化过程演变成脑神经网络的一个组成部分的。 - -2021-01-23 语言的诞生。 -好不容易,告别漫长的2020,去年出的题目我自己解出来了,下面是答案,运行history\007_earthquake目录下的run.bat,可能看到类似下面的运行画面: -![result9](result9_earthquake.gif) -详细解说:这个题目的模拟环境被简化成左右两个区,设定地震发生时(用红框表示)会扣除所有青蛙的能量,但是只有位于左侧的青蛙可以看到地震发生和停止,右区的青蛙不能看到地震发生和停止,但是青蛙有发音器官和听音器官,如果左侧的青蛙发出叫声是可以被右侧的青蛙听到的。看到地震发生、看到地震停止、发出叫声、听到叫声、跳起、落地这6个功能分别对应6种器官并都偶然进化出来(这个无需证明),这个实验的目的是要验证青蛙会不会在环境逼迫下,在这6种器官对应的脑细胞间形成神经关联。结果可以看到,左侧的青蛙看到地震后,跳在空中(用黄色表示),并发出叫声,然后右侧的青蛙听到叫声后也跳在空中。左侧的青蛙通过叫声信号传递了信息给右侧的青蛙,从而让右侧的青蛙避开了它看不见的地震伤害。这是一个成功的群体进化的演示,它证明了即使是随机生成神经细胞连线,也可以进化出生物的发音-听力功能,也就是说进化出初步的语言功能。 -另外说一下,这个运行结果可能要运行多次才有可能遇到,这个可以用群友"背叛的细胞膜"提到的自然界的“顶端优势”来解释,生物进化有可能陷入局部最优解而绕不出来,对自然界来说就是没找到最优解的生物种群可能会被汰淘,存在即合理。 - -2021-05-15 细胞分裂的演示 -这是我在微信群里发布的一个小任务, 还没来及更新到这里,群里的pama_1234就做出来了,编程速度不是一般的快,顺便说一下,他现在还只是高一,现在的后浪都这么历害了。 -任务很简单,就是画出类似下图小蛇形状的就赢了。要求: -1.小蛇至少30x30象素,有眼睛和舌头,反正一眼看上去要象一条蛇的样子。 -2.要求使用遗传算法和分裂算法,遗传算法大家都了解了。分裂算法我现在只有一个大致的思路,就是模仿细胞分裂,来演化出不同的形状。分裂可以是把别的细胞推开,也可以是只在边沿分裂以减少运算量,这个不限。 -3.画出“蛇”、“青蛙”的汉字也可以 -这个任务看起来和神经网络关系不大,但我觉得有可能利用这个算法来进行器官自动排布,所以还是有一定的意义的,任意复杂的形状生成,今后三维脑的细胞结构,都有可能从这个任务演化出来。 -输出结果((pama_1234编写): -![result10](result10_drawsnake.gif) -他的项目位于这里:[细胞画蛇](https://gitee.com/pama1234/cell-painting-snake), 有兴趣研究源码的可以看一看,是基于processing编写的。 -顺便我把我的构思也写在这里,我没有仔细研究他的代码,但估计思路应该大体一致: -1.与形状表达相关的基因采用多叉树结构数据结构,树结构是单例,只保存1份在内存中,每个细胞分裂后,端粒酶减一,相当于从树结构往下走一级。 -2.每个细胞分裂后,为了获得当前细胞的位置,但是又不能复制整个子树,所以要保存一个指针,指向树的当前节点,即子树的顶点。不再分裂的节点可以不保存节点指针。 -3.基因树总体稳定,但有大概率小变化,小概率大变化,基因树变化包括数量重复、分叉方向、分叉数量。 -4.因为是多叉树,可以从任意一点开始作为受精卵开始分裂,最后都可以形成指定的图形。 - -画小蛇本身任务很简单,就是个填充色块的任务,用window里的画图笔刷不到半秒就可以搞定,之所以要把它当成一个任务来做,并限定用细胞分裂的方式,是因为大自然的解决方案不是用什么人为设计的算法,而是只有一招就是细胞随机的分裂、遗传和变异,现在看起来这个分裂模拟还是比较容易实现的,大致上解释了自然界生物各种形态生成的原因。 - -2021-05-15 顺便发布下一个开发任务:青蛙吃蘑菇 -1. 青蛙要根据不同的画案区分有毒无毒的蘑菇。蘑菇用随机生成十种不同蘑菇图案来表示,这些蘑菇中有一半无毒,另一半有毒。 -2. 要利用体全息存储或面全息存储方案来进行蘑姑图案识别。体全息存储参见005_letter_test示例;面全息存储方案可以参考激光全息存储方案,和体全息的区别是信息是保存在一个平面上,而不是一个立体间里,相同点是利用入射光和出射光位置信息进行存储和读取,入射光和出射光互为参考信号。 -3. 要在现有的Java项目界面下进行,也就是说,要显示虚拟环境和脑图,源文件名必须为.java后缀。这个和细胞分裂的演示不一样,这个任务是项目的主线任务了。 -4. 蘑茹有毒还是无毒是与图案相关的,但是有毒还是无毒必须由青蛙在进化过程中自动判断出来,不能由人为来设一个有毒的信号告诉青蛙(青蛙之间互传信号不在这个限制)。任务完成的标志是青蛙们要能进化成吃掉虚拟环境里的所有无毒蘑茹,避开所有有毒蘑菇。 - -2021-07-04 依然是模式识别演示 -位于history\005a和005b两个目录下,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来: -![result11](result11_letter_test.gif) -这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但考虑到实现这些功能的复杂性,我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有简单模式识别功能的模拟生命体,也就是说实现上面发布的任务。 - -2021-08-13 演示同时处理多个方向的信号 -位于history\005a1目录下,演示用5个声母和5个韵母的组合来关联到25个图像的识别,这样可以减少声音输入区的数量。它的另一个目的是演示体全息存贮的工作模式可以同时处理多个方向的信号。这个演示分辨率极差,只有约一半的识别率,但我不打算继续改进了。 -![result12](result12_letter_test2.png) - -2021-10-13 失败的细胞分裂尝试 -位于history\008目录下,这次本来想模仿生物细胞的分裂,从一个细胞开始分裂出任意指定的三维形状,并设计了split、goto等基因命令,但是做来做去做不出结果,细胞们就象跳蚤一样乱跑不听使唤,最终还是决定放弃,细胞分裂这个算法太难了。细胞分裂的优点是更“象”生物,而且估计可以利用分形原理缩小基因的长度,基因相当于一种自带循环和条件判断的计算机语言。 -最终生成三维形状这个目标还是借助简单遗传算法完成,通过细胞在相邻位置随机生成,并在基因里记录每个细胞的坐标的方式来实现,基因命令被删得只剩一个了,就是随机生成细胞。 -用遗传算法来生成任意形状,就好象一个画家在画画,但是画什么根本不知道,只知道听从旁边人打分,画的好就打高分,画的不好就打低分,这样一直循环下去,最终画的内容只由打分的人决定。目前速度上还有改进余地,比如让新细胞有更多变异率。 -![result13](result13_frog3d.gif) -生成三维形状目的是为生成三维脑结构做准备,神经网络空间上应该是三维的,这样实现模式识别会很方便,而早期随机连线结构损失了空间位置信息,三维全息演示问题则是它是手工设计的,优化困难。 - -2021-11-08 成功的细胞分裂尝试 -位于history\009目录下,这次的细胞分裂算法采用自顶向下的策略,也就是从单个细胞开始,一个细胞分裂成8个(因为1个正方体切三刀正好是8个小正方体)这种方式来进行。这种方案不是从目标形状的局部开始填充,而是从毛胚、从目标的粗轮廓开始利用遗传算法细化,直到细化出每个细节。这种方案的优点是更接近生物实际,符合“从总体到局部”的正常逻辑,而且有高效的基因压缩存储率,为了说明存储率这点,大家可以看看下图左面这个树结构,猜一猜要存储它最少需要多少个字节? -![depth_tree](depth_tree.png) -答案是最少只需要一个整数7就可以表达这个树结构了。说一下原理:首先所有树结构都可以用行号+深度的方式来表达,详见我的博客[基于前序遍历的无递归的树形结构](https://my.oschina.net/drinkjava2/blog/1818631),获取子树时可以避免递归访问,其次因为采用基因敲除的方式,只需要记录被敲除的树节点的行号和深度就就可以了,最后因为固定采用3叉树分形结构,所以深度值也可以省略,只用一个行号7就可以表达这整棵树了。 -下图是这个算法的动画,可以看出它与上次的演示是不同的分裂模式,是先有总体后有细节。项目中实际采用的是8叉树,深度用细胞边长表示: -![result14](result14_wa3d.gif) -细胞分裂算法一方面可以利用来生成和优化物理形状(比方虚拟风叶、翅膀、受力结构等形状),另一方面它和神经网络的形成算法是有共通点的,因为众所周知心脏形状、血管网络、大脑神经网络都是由基因控制细胞分裂出来的。所以以后有可能利用这个算法来自动生成和优化神经网络触突三维空间分布结构。 -顺便说一下,自顶向下的问题是它一旦主分支被误敲除,就不容易补回去,实际的生物例子就是人眼结构还不如章鱼。自然界是用生物的多样化和环境的连续化来保证各种主分支都有尝试。我们电脑模拟要尽量保持环境(任务)的连续化,从低到高一步步走,个别时候考虑结合其它算法给错误的分支打补丁。 - -2021-11-26 多参数的细胞分裂 -位于history\009a目录下,只是在上次细胞分裂基础上改进了速度,将三维cell对象数组改为long型数组,节省了对象创建和销毁开销,速度有明显改进。long类型有64位,所以一个细胞可以有64维独立参数,应该够用了。下图是一个6倍速显示的三维鱼分裂生成动图。它一共用到4维参数,分别是细胞的位置和三个不同颜色的细胞色彩参数,每一维分别一个细胞分裂算法单独控制,参数之间没有做关联关系: -![result15](result15_fish3d.gif) -这个动画的每一帧是细胞分裂到最小不可再分的最终结果,而且是从400个青蛙中生存下来的最佳个体,这就是遗传算法,遗传算法就是穷举法。这个16x16x16的大立方体要理解成第一个细胞,只是画的大了而已。以后等有时间可以做1个细胞分成8个,8个变64个的动画,能更好地演示分裂的中间过程。 -细胞分裂研究到此结束,下面要开始生成神经网络空间结构了。我的思路是,脑结构也无非就是三维细胞的空间排布而已,细胞有各种参数,比如触突长度、方向、密度、信号收发阀值、信号强度、信号遗忘曲线等,只要每个细胞不超过64个构造参数,就可以用分裂算法来随机试错把神经网络的空间结构给试出来。分裂算法的优点是遵循从主干到细节的生成次序,如果要完成的任务(即外界信号输入输出)也是从简单到复杂,就可能正好符合这个脑的空间结构生成顺序。 - -2022-01-03 多参数的细胞分裂的小改进 -位于history\009b目录下,与009a分支相比,算法是相同的,只是作了以下一些小改动: -1.可增加颜色 2.添加存盘选择框,可以在运行期选择存盘 3.添加每维参数分别显示的选择框 4.将颜色参数与细胞位置参数产生关联 5.新增了上次提到的显示分裂过程动画图,见下图: -![result16](result16_cell_split.gif) -因为参数之间有关联,更容易发生缺色现象,这是因为进化过程中,主要参数(细胞位置)的进化会导致次要参数的分支被误删,然后就很难再补回来了。为了解决这个问题,下一个版本打算改进算法,采用黑白节点的方式,黑节点删除节点以下的细胞,白节点保留节点以下的细胞。 -细胞分裂研究算法目前还不能结束(打脸自己),还要先解决这个缺色问题。多参数的进化,如果一旦某个参数被误删除就不能再进化回来,这种算法是不能接受的。 - -2022-01-15 多参数的细胞分裂继续改进:阴阳无极八卦阵算法 -位于history\009c目录下,采用了阴阳(黑白)节点算法,阴节点基因会删除节点下所有节点,是自顶向下的减材加工,阳节点基因会保留节点下所有节点,是自底向上的增材加工。利用遗传算法的大样本筛选,把自顶向下和自底向上两个进化方向结合起来,这样基本能解决误删分支后缺色不能补回这个问题。而且对于奖罚不再象以前一样要设成差距很大的值,animal.java中awardAAAA()原来要设成8000, 现在设成20也不会产生缺色现象。考虑到这个算法的特点,我给它起名“阴阳无极八卦阵算法“,阴阳是指它有阴阳两种节点,无极是指它的分裂阶数没有限制,八卦阵是指它采用了多个8叉树结构,每一维细胞参数都对应一个8叉树。 - -2022-07-22 树的生长演示 -位于history\010目录,演示一个树的生长。这个演示是基于规则而不是模板来生成一棵树,这棵树的形状仅由以下规则决定: -1.树根位于底部中心 -2.树的每个细胞水平方向四周不能有其它细胞 -3.树的每个细胞正下方不能有细胞,但必须在斜下方至少有一个细胞 -基于这三条规则,利用阴阳无极八卦阵分裂算法,树能够自动进化出来。这个演示的意义是表示形状可以由规则来决定,不同的规则能进化出不同的形状,生物会通过改变自己的形状来适应环境规则。 -![result17](result17_tree_grow.gif) - -2022-09-25 用阴阳无极八卦阵完成找食任务 -位于history\011目录,这是利用阴阳无极八卦阵算法来完成最开始的“找食”这个任务,即采用模拟细胞分裂再配合遗传算法,生成一个简单功能的神经网络,输入只有上下左右四种视觉细胞,输出只有上下左右四种运动细胞。 -为什么要使用阴阳无极八卦阵来生成神经网络,是因为它有几个特点: -1.传统神经网络因为手工调优参数太多,被类比为”炼丹法“,而采用阴阳无极八卦阵之后,算法将只有固定的这一个,调参完全交给计算机,手工只需要设计基因和细胞行为即可,以不变应万变。只要有利于生存的脑结构,就会被筛选出来,结构即逻辑。 -2.有高效的信息存储方式(见先前8叉树的介绍),基因虽然是随机生成的,但是它采用树结构这种方式有非常高的信息压缩比,可以用少量的基因控制巨量细胞的生成。 -3.更贴近实际生物的神经网络生成原理。大自然就是采用分裂+遗传算法来生成脑神经网络的,理论上我们应该可以照抄这个模式来创造出高智能的神经网络。 -4.它的神经网络是三维的,结构组合方式是无穷的。同一个细胞可以由多个基因控制,一个基因可能控制零个或所有的细胞。 -下面是实际运行结果,最上面一层用4种颜色来表示眼睛细胞,其余的细胞参数如运动细胞、轴突方向、轴突长度等用不同大小、颜色的圆来表示: -![result18](result18_yinyan_eatfood.gif) -这个版本的目的是验证阴阳无极八卦阵算法的实用性和搭建初步框架。后面的任务将是进一步复杂化,生成有模式识别功能的神经网络三维结构。 - -2022-10-18 继续纸上谈兵,谈谈脑结构 -本次更新是一篇软文,不涉及具体编程。细胞分裂算法可以解释神经网络生成的原因,但是完全依靠它来生成复杂神经网络,也是有问题的,就是资源不够,电脑模拟不像大自然一样有无限的时间和样本,随机试错不知要试到猴年马月去。为了加快模拟脑的生成,可以走一点捷径,就是如果已知生物脑的大致结构,可以先把细胞空间位置先固定下来,然后再利用细胞分裂算法来微调细胞参数。对的,分裂算法不光可以用来生成结构,也可以在结构固定的前提下用来调整参数,因为参数的空间分布也是一种结构。 -下图是我最近构想的脑结构图,类似于机械中的总装图,从总体上勾勒出脑的结构和运行机制,这个没什么依据,仅供参考,但还是有点意义的,因为如果总体上猜对了脑结构,会对今后的编程开发带来很大便利。 -这个结构图中的要点是脑核只处理少量精简后的信息,图中各个部分都有注释,大家应该能看懂我的大概思路。这个脑结构并不完全符合生物脑结构,而是从脑的功能出发倒推猜测出来的: -![brainstructure](brainstructure.png) - -## 2023-02-26 阴阳4叉树分裂算法 -本次是个小更新,位于history/012_tree4目录下,演示4叉树分裂算法。 -如果已知参数是分布在一个平面上时,就没必要用8叉树了,因为效率太低,这时可以改成阴阳4叉树分裂算法,其优点有两个,一是可以手工分层式设计神经网络参数,二是速度快。 -在16x16x16的空间中,演示在三个层中分别显示“SAM”这几个字母,如果采用阴阳8叉树算法(将Cells.java中调用register方法第三个参数改为-1),则需要3000循环以上,而采用阴阳4叉树分裂算法只需要300次循环就够了。 -![result19](result19_tree4.png) -在同一个三维空间中,8叉树和4叉树算法可以同时结合起来使用,这里就不演示了,以后在三维神经网络搭建时可能会碰到。 - -## 2023-07-02 阴阳2叉树分裂算法 -位于history/013_tree2目录下,演示阴阳2叉树分裂算法。如果已知参数是分布在一条线上时,就没必要用8叉或4叉阴阳树算法了,这时可以用阴阳2叉树分裂算法。演示中在三个层中分别显示“SAM”这几个字母,"S"用4叉树平面分裂算法,“A”和"M"用2叉树算法,其中"M"只允许基因分布在Z坐标为偶数的Z轴上。本次更新因为太简单就不上图了。 -虽然2叉这个更简单,我本来也没觉得有必要添加,但最近构思用分裂算法实现模式识别时,发现有些思路验证可能没必要直接从2维或3维图形开始,直接从线状信号的识别开始会更简单,有利于思考和做原型验证。在线状上符合的逻辑,外推到2维或3维必然成立。 - -题外话: -目前我思考的是 “视觉”、“苦味”、“甜味”、“咬”、“不咬”这几种生物细胞,可以说是构成一个具备模式识别能力的昆虫脑最简化的信号输入输出单位了,其中视觉信号还可以再简化为单维的视觉信号,也就是如果已知某些神经元参数是线状分布,就可以用2叉分裂算法来快速建模了。 -模式识别的本质是预测,也就是说,本来正常逻辑应该是 "图案"->“咬”(随机发生)->“甜味感觉”, 具备了模式识别的昆虫会把这个随机事件中的随机性去除,变成"图案"->“甜味感觉”和"图案"->“咬”两个神经关联。也就是在图案和动作之间形成短路(条件反射),直接根据图案来预测它是可以咬的,从而在生存竟争中胜出。 -以上讨论的是结果,具体昆虫脑是如何实现这个逻辑的,还必须有记忆细胞的参与,否则以前的信号不可能影响到后面的预测。记忆细胞的作用是将短期内的所有信号如“视觉”和“咬”动作信号形成关联。“甜味感觉”这种信号会加强这种关联,“苦味感觉”这种信号会抑制这种关联。 -因为最近我业余时间少,项目进度非常慢,如果有兴趣的朋友也可以自己尝试在以上描述基础上实现这个昆虫脑模式识别功能,这就是一个最简单的脑模型了,目前虚拟环境和分裂算法都已经有了,接下来就是如何定义脑细胞基因和它对应的信号处理行为来实现这个逻辑了。智能本质上是模式信号、奖惩信号和行为输出之间的关联,少量几次奖惩即可形成条件反射,虚拟环境如果很简单,对应地训练也应该很简单,不需要用到大模型信号来训练。 +## 目前进展 +2023-08-25 三个细胞一台戏 +本次更新在目录history\014_3cells下。在上次更新里已经说过了,为了实现模式识别,可以先从最简单的几个细胞的场景开始做起。于是就一路简化,最终简化到只剩下三个细胞,分别为视细胞、咬细胞、忆细胞。 实验目的是要达到这样一个效果,当食物出现时,视细胞激活,然后视细胞在忆细胞上打个洞,咬细胞则随机激活,然后也在忆细胞上也打个洞,最终实现的效果将会是视细胞激活忆细胞,然后忆细胞在洞上反向发送能量给咬细胞,这样就实现了视细胞到咬细胞的短路,形成一个最简单的条件反射。忆细胞的作用是隔离视细胞和咬细胞,防止形成视细胞直接驱动咬细胞这种简单连接。任务看起来很简单,但做起来就不太美好了,快两个月了才有点进展,先更新上来再说。 +![pic1](result20_3cells1.gif) ![pic2](result20_3cells2.gif) ![pic3](result20_3cells3.gif) +上面从左到右三个图,分别是对应三种场景下青蛙的行为:1奖惩值都很大;2奖励值远比惩罚值大;3只有奖励,没有惩罚。 +奖励是当咬下时正好有食物;惩罚是当咬下时食物不存在,咬了个空。测试时请修改Genes.java源码第138行,进行不同惩罚值的调整。可以看到,根据奖罚值的不同,三个细胞进化出的神经网络参数是不同的,奖惩值都很大时,细胞就会躺平,多咬多错,还不如不咬,以避免惩罚;奖励值远比惩罚值大时,细胞就会比较活跃,没有食物时也经常空咬;当完全没有惩罚时,细胞就会放飞自我,直接在咬细胞和忆细胞之间进化出信号循环回路锁定,全程都在咬,而且最过分的是干脆忽略掉视觉信号,把视细胞和忆细胞之间的连线(洞)直接用一个负值(蓝色)参数来掐断。 + +这次更新的主角不是分裂算法(因为就三个细胞,谈不上结构了),而是全局常量。本次程序中控制细胞特性的全局参数有7个,分别是: +视细胞激活后产生多大强度的能量? +咬细胞激活后产生多大强度的能量? +每个细胞激活后能量随时间流逝,每一步会遗失多少能量? +咬细胞激活后向记忆细胞传送多少能量? +视细胞激活后向记忆细胞传送多少能量 +忆细胞激活后反向向视细胞传送多少能量? +忆细胞激活后反向向咬细胞传送多少能量? +这些全局参数是跟随青蛙终身的,一旦青蛙孵化出来就不再动了,在程序里把所有常量放在一个数组里,用遗传算法来控制,基本规则是小变动有大概率发生,大变动有小概率发生。青蛙的参数分为两类,一类是与空间位置相关的,如脑细胞是否会出现在某个空间位置,一类是与位置无关的,如每个细胞激活后向其它细胞发送多少能量。前者要放到分裂算法里,用一串8/4/2叉基因树来控制空间分布,后者就没必要这么浪费了,直接用一组全局数字表示即可,并用遗传算法来随机变异和筛选它们。在给神经网络编程时,如果碰到可以用全局常量来控制的参数,尽量不要手工赋值,而要用遗传算法来控制,因为多变量的优化组合筛选靠人力是不可能做好的。就拿这个例子来说,我压根不知道这些参数将会是多大,是正还是负,但是我知道应该有这些参数,这就够了。人工生命项目编程不讲究精准,思维模式要从传统的精细化编程转变为以概率、笼统、可能为导向的思维,大方向人为确定,细节交给电脑去算,这和大自然用遗传算法来筛选出脑细胞的参数是一个道理。 +从本次更新可以看到,青蛙是工作在一个连续的信息流下面,信息是以脉冲方式在细胞之间互相传递大小不等的能量,可以说是一个最简单的脉冲神经网络大脑了。这个实验不很实美,没有实现一个不漏地吃掉食物但是又不空咬这个目标,但是人生苦短,我不想继续和这三个细胞缠斗下去了,后面将转到多个视觉细胞和引入苦甜奖惩信号细胞来影响洞(权重)的大小,这个会更有趣、更智能,也更接近模式识别任务。我认为通用智能就是模式识别与行为输出结合起来的系统,如果奖罚细胞和行为输出细胞在一开始就做为这个模式识别系统的一个组成部分,并且由遗传算法来筛选参数,那通用人工智能就不远了。 + + + ## 运行方式 | Run 运行core或history各个子目录下的run.bat批处理文件即可启动运行,history下有多个子目录,按版本号顺序排列,存放着这个项目演化过程中的主要历史版本供演示。 另外如果想要研究这个项目的早期版本,可以结合gitk命令和参考"版本提交记录.md"的介绍,用git reset命令回复到以前任一个版本,例如用: git reset --hard ae34b07e 可以转回到以前一个分组测试的找食版本。 码云上通常是大版本提交,跑出结果才会更新,GitHub上则是日常提交。 -更多关于项目源码的介绍可以参见"初学者入门介绍.md"以及history目录下的项目文档如"Frog_doc_012.doc"。 +更多关于项目源码的介绍可以参见other目录下的"初学者入门介绍.md"以及history目录下的项目文档。 ## 重要参数 | Parameters 在Env.java类中以下有以下可调整参数,请手工修改这些参数进行不同的测试,前4个参数很重要: @@ -244,7 +53,7 @@ FOOD_QTY:食物的数量,食物越多,则Frog的生存率就越高,能 [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) ## 期望 | Futures -欢迎发issue、评论等方式提出建议或加入开发组。另外在other目录下也可以上传你的文章、项目链接等资源。 +欢迎发issue、评论等方式提出建议或加入开发组。另外在other目录下可以提交你的文章、项目链接等资源。另外也欢迎给项目捐助,这可以加快项目的开发速度。 ## 关注我 | About Me [Gitee](https://gitee.com/drinkjava2) diff --git a/README1.md b/README1.md new file mode 100644 index 0000000..febd225 --- /dev/null +++ b/README1.md @@ -0,0 +1,252 @@ +

+ +## Frog | 人工生命 +这是一个人工生命试验项目,最终目标是创建“有自我意识表现”的模拟生命体,技术架构基于02年提出的 [一个人工脑模型](other/一个人工脑模型.md)。这个项目永远没有结束的时候,开始于模拟一个简单的生命体,然后是青蛙、狗......, 结束于有“自我意识表现”的人工脑,或者说,结束于被机器人代替人类的那一天。 + +## 缘起 | Origin +目前人工智能的进展已经比较完美地解决了模式识别这块的难题,人脸识别、语音识别已经不弱于人类的水平,而这是我在二十年前感到最困惑的一块。模式识别解决了,剩下的问题就简单多了,目前距离人工意识的诞生只差临门一脚了,就是如何在识别的基础上“理解”这些识别的内容并与人类形成交互的反馈。所以这个项目的重点是建立在模式识别基础上,训练神经网络形成条件反射,表现出高等动物才具有的条形反射行为,最终表现为"拥有自我意识"的行为。根据“意识不是一种存在,而是一种现象”原理,如果最终一个系统表现出具有自我意识的行为,即可认为它也是人,应该获得人权。目前有些人工智能的研究目的是想让人工智能解决一些复杂的人类社会方面的问题如机器翻译等,则是完全错误的目标,不可能成功,因为如果一个系统不能表现出自我意识,它就不能与人类交流,也就不可能具有解决这些问题的能力,表现出来的现象就是通常说的"机器不犯错,一旦犯错就是大错"。另一方面,如果一个系统表现出具有自我意识的行为,它就完全有能力解决世界上所有难题,包括改进它的自身和淘汰人类(因为他是先进生产力的代表)。所以人工智能的研究重点应该放在人工生命(或通用人工智能,人工生命和通用人工智能是等价的)的构建和伦理研究,而不是期待短期收益,指望人类可以一直享受人工智能的大餐是很危险的。模式识别和深度学习的成果只是通用人工智能的一个路标,人工智能的“有用”的应用期,很可能只是奇点之前白马过隙般短暂的一个过渡期而已,不用高兴得太早,也许都是白忙,给机器人作嫁衣。奇点之后,很可能所有生物智能都将淘汰。实际上,人工智能不光是技术问题,还是个人生观问题,专家的认识有时候并不总对。在此摘一段芦秋迪群友建议放上来的对话: +![talk](talk.png) + +简单来说,这个项目是一个民科项目,试图以实验为导向,模拟生命进化的过程,按照优胜夯汰、随机变异、用进废退这三大原则,一步一步地搭建出从低等到复杂的人工生命体,除了模式识别的成果可以借签,原则上不需要学习很多数学知识,因为它强调由实验来驱动,而不是由复杂的算法来搭建神经网络。目前神经网络研究重点在于模式识别,但对系统赋予条件反射功能关注不够,没有把无生命的神经网络和有意识的人类看作同一个等级的自然现象。 + +从单细胞进化到多细胞、从青蛙进化到人类,这是一个漫长的、随机的进化过程,但在超级电脑上跑可能只要几天时间,就可能得到一个相当不错的脑模型。当然电脑速度越快、容量越大、环境模拟的越真实,则优胜夯汰后形成的脑结构就越复杂,错的脑模型都被自然淘汰掉了。从算法着手搭建,还是从模拟环境着手自动进化,这是创建人工生命的两个大方向,第一个方向有可能走到死胡同里,因为它不具备算法自改进、变异、遗传(算法的压缩)功能,当脑模型复杂到一定地步,可能会超出人脑能理解的范畴。模拟环境方式的难点则在于环境本身必须足够复杂、正确。而且必须循序渐进,与脑的进化同步,如果把一群青蛙扔到猴子的模拟环境中,则所有青蛙都会被自然淘汰掉,项目就无法进行下去了,另一个困难是电脑必须非常快,因为目前是用串行方式模拟并行算法,不断试错前进的过程。项目语言为Java,利用Swing作图环境,构建一个虚拟环境、并模拟一群草履虫的优胜夯汰,来获取一个具备自进化功能的人工生命体,具体脑(即电脑生成的神经网络)的生成、进化算法还需要以后逐渐加入。欢迎有对神经网络感兴趣的朋友一起来琢磨,这个项目不需要多少高等数学知识,重在实践。 + +## 短期目标 | Sort-term Goals +第一个初步目标是:造出一个真正意义上的人工生命:草履虫(备注:基本已完成)。它必须具备以下前四个特点: +* 脑结构由电脑生成:神经网络由电脑算法生成,但是电脑算法仅限于模拟环境,而不是直接参与搭建神经网络,就好象大自然只负责拍死不合格的生命,它从不主动参与设计大脑。 +* 脑结构可遗传:类似于生物的DNA,电脑生成的脑结构(神经网络),可通过简单的算法规则描述,并且此算法规则可以压缩成较短的片段存储,并参与到下一代草履虫的构建。 +* 脑结构可变异:算法规则可以变异,下一代生成的草履虫在脑结构上与上一代总体相似,但存在部分变异。 +* 适应环境:草履虫能够在模拟的虚拟环境下存活下来,环境有微小的变化,能够自适应环境,并一代代生存将适应这种环境的能力遗传下来。 +* 用进废退:这是一个假想,对于生物来说,存在这样一种现象,就是用的多的器官,容易发生变异(例如经常嚼槟榔,容易发生口腔癌变),有理由相信这不是偶然现象,而是生物在进化过程中的一个有用的功能,以便于更快地变异,以适应环境,并很可能这种变异会通过遗传细胞影响到下一代。 + +## 理论 | Theory +为什么明明是个电脑程序,只要满足上述前四个特点就可以称之为"真正"的人工生命? 这一点我不想多说,大家可以搜索一下"zhangrex的造人论坛"就知道我的观点了:意识从来就不存在,意识只是一种现象。风吹、树动和风吹、添衣,都只是现象而已,意识本质上是一种现象,同理,只要表现出类似生命现象的事物,就可以称其为生命了,不管它是高等还是低等,不管它的物质存在基础是怎样的。二十年前我就开始思考这个问题,提出了“我思我不在”的口号,请仔细考虑一下这个观点,哲学上、理论上对智能、意识的研究是很无聊的,相当于在研究“不存在”到底是什么,不必多纠缠在理论和算法上。是的,用模拟环境得到的人工神经网络模型,我们确实无法掌控它的算法是怎样生成的,但是我们知道,这符合大自然创造生命的规律。 + +## 项目架构 | Architecture +这是一个Java项目,分为Application、Env、Frog三个模块: +Application模块: 用于项目的启动、关闭等基础服务,Windows环境下可以用run.bat来启动它查看演示。 +Env模块: 模拟一个生物生存区,用不同形状的图形点阵来表达和模拟食物、天敌、障碍等物体,这个虚拟空间由程序员全权控制,将随着Frog的脑进化而不断变得越来越复杂。 +Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什么都一样。它主要具备以下器官: +* 运动器官: 与运动神经元相连,只有4个动作:上下左右。 +* 进食器官:当Frog与食物的坐标重合时,食物会被从Env中删除,并相应增加Frog的能量值,并激活Frog的进食感觉神经元,随时间流逝能量将减少,能量耗尽则Frog死亡。 +* 视觉器官: 这是脑模型的一部分,在实验中先固定随意取脑内一片神经元区作为视觉区。 +* 脑器官: 这即是程序员要解决的问题,也是我们要获取的最终目标。脑模型的生成由电脑优胜夯汰、循环迭代进化生成,但这个进化的算法还是必须由程序员来掌控,一步步探索出来,即要防止虚拟环境太复杂,也要避免脑模型不适应环境,生命体全部被淘汰,导致实验中断,无法进行下去。 + +## 技术细节和构思 +* 通过数组来模拟神经网络,用串行的循环来模拟并行芯片运作方式。用Frog的能量多少来衡量是否将它淘汰还是允许它产生后代(下蛋)参与下一轮的测试,因为这个项目的目的是获取智能体,与一般的生命游戏还是有区别的,并不是适者生存就结束了,而是必须完成一系列程序员设定好的目标,一步步进化,直到表现出自我意识现象为止。脑模型的生成算法通过简单的神经元连接完成,原则上不允行出现任何形式的硬编码(除模式识别外),因为硬编码可能会破坏“随机变异”这一生命特性。为简单起见,暂不考虑引入GPU图形芯片进行加速。 + +## 项目要实现的短期和长远目标 +* 脑模型和虚拟环境的初步搭建 [脑模型刚开始搭建。虚拟环境已完成,点击run.bat可以查看演示] +* 使脑模型具有视觉功能,如果有食物在它附近,将激发天生条件反射,向食物移动,并获得进食奖励 [未全部完成] +* 引入现成的图像识别算法,使脑模型具有图像识别功能,根据形状区分食物、毒物、天敌 [未完成] +* 如果误食有毒食物,将激发天生条件反射,获得惩罚并扣除能量,天生痛觉区兴奋。[未完成] +* 如果被天敌攻击,将激发天生条件反射,获得惩罚并扣除能量,天生痛觉区强烈兴奋。[未完成] +* 训练它将打击行为与痛觉兴奋区发生关联。[未完成] +* 训练它将看到“打”这个文字,与打击行为和痛觉兴奋区建立即系。[未完成] +* 训练它背下这100个字中所有汉字可能组成的常用词组,给它一个字,所有与这个字相关的词组细胞区兴奋。[未完成] +* 训练它一看到“食物来了”文字,就从窝里出来找吃的。[未完成] +* 训练它理解“你”,“我”、“他”文字,只针对“我”相关的指令作出反应。[未完成] +* 训练它认识数字,会做四则运算[未完成] +* 训练它认识圆、矩形、会计算面积,学会估算和判断"大"和"小"[未完成] +* 训练它认识坐标和时间,并按指令行动,如看到"你在9点走到右上角去,等三分钟后再回来",将遵从指令行动。[未完成] + +## 最终目标 +* 扩大它的输入网格和输出网格规模,扩大神经元数量, +* 移殖到超级电脑上,由人来同它交流,输入新的图形和汉字,纠正它说的错误的话 +* 移殖到并行芯片硬件上 + +## 目前进展和成绩 +2019.03.11 虚拟环境已建好,可以模拟低等生命的遗传、繁殖、变异、进化现象,但只能往一个方向运动,相当于一个最简单的单细胞生物,还不具备视觉能力,不具备主动找食能力。 +运行run.bat可以查看演示(需要安装Java8和Maven)。 +![result1](result1.gif) +另外每步演示的结果(egg)会存盘在根目根目录下,名为egg.ser,可以删除这个文件以从头开始新的测试。因为还没涉及脑模型的搭建,可以看到有些青蛙跑得飞快,这是自然选择的结果,因为跑在最前面的吃得多。 +一些重要的测试参数如显示区大小、是否每次测试要删除保存的蛋等,请参见Env.java中开头的常量设定,可以手工修改进行不同参数的测试。 +2019.03.21 添加了脑图,改进随机运动模式为Hungry区驱动。从脑图上可以直观地观察脑结构,方便调试。 +2019.04.01 改进脑图的显示bug, 每一次生成Frog时添加随机神经元,并简单实现"卵+精子->受精卵"算法,以促进种群多样性。 +2019-04-12 添加一个简单的眼睛(只有四个感光细胞),自然选择的结果是眼睛被选中,但是和运动区短路了,谈不上智能。但有眼睛后找食效率明显提高了,见下图: +![resut2](result2.gif) +2019-06-13 做了一些重构清理,加上了Happy和Pain两个器官,分别对应进食奖励和痛苦感,后者在靠近边界时激发。观查它的表现,痛苦感生效了,一些Frog跑到边界后就不再前进,而是顺着边界溜下去了,但是Happy器官没有生效,这也很显然,因为Happy属于复杂的进食条件反射链的一部分,在没有记忆器官(算法)引入之前,再怎么优胜劣汰也是没办法用上进食奖励信号的。见下图: +![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) +2019-06-26 找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,人为设计的算法居然比不过随机连接。Pain器官的加入没有提高找食效率,必须与感光细胞合用才能知道是哪个边界,急需引入记忆功能。 +2019-06-28 为了让青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。 +2019-07-28 有以下改动: 1.在Env区中间加了一个陷阱区Trap,以增加趣味性,自然选择的结果是青蛙会自动绕开陷阱区。2.增加一个Active器官,它的作用是一直保持激活,发现比Hungry器官驱动更能提高找食效率。3.增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。目前进食奖励信号没用到,白白浪费了。 +另外Chance和Eye类里也再次运用了随机试错原理去确定关键参数,效果还不错,有兴趣的可以看一看源码。 +![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) +另外发现青蛙其实是有记忆能力的,因为连接本身就是一种记忆,只不过它没有复杂的模式识别能力,例如给个蛇的图片它就认不出来。以后的工作将以模式识别为重点(当然随机连接看起来很有用,以后还可能保留),基本原理是见note中提到的仿照波传播及全息存储原理,在思维区逆向成像。而且脑可能改成三维结构,并根据逆向成像原理,要将所有输入输出器官全移到三维结构的同一侧(即思维区)。这将会是一个非常大的改动,下面我简单画了一个3D示意图来说明我想象中的这个模式识别和记忆的原理,至于对不对还需要实验来验证: +![3d-model](3d-model.gif) +这个模型的最顶层表示眼睛的感光细胞(或任意输入输出细胞),同时也是思维区,红色表示一个长条的图形,兰色表示一个三角图形,如果这两个图形经常有规律地同时出现,就会把它们共有的节点撑大,见紫色的节点,当红色图形单独出现,就会强烈激活紫色节点,然后紫色节点的信号反向传播,就会激活三角图形,反之亦然。这就同时解释了模式识别和记忆(或者说回忆)功能的的原理。一个节点可以被多个感光细胞共享,所以它的存储能力是很强的。而且可能这个原理比较符合生物脑结构。当然,实际编程时,虚拟神经元不一定要排成正立方三角,而可能通过胡乱排放,大致上过得去就行了,也许能乱拳打死老师傅,最终要靠电脑自动随机的排放,然后用优胜劣汰来筛选。目前有个难点是这个记忆功能在思维区成像是如何有条不紊地大体上按串行进行工作的,这个问题先放一放。 +2019-08-04 更新了一个分组测试功能,如果测试青蛙数量太多,可以分批来测试,每轮测试最少的青蛙数量可以少到只有一个,这是用时间来换空间。 +2019-08-05 有了分组测试功能后,顺手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: +![result5](result5_seesaw.gif) +这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,青蛙只会找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,我将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 +青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,器官的用进废退、奖励信号的利用都没反映,但我认为这些还不关键,目前最急迫的任务应该是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式完全不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 +2019-09-09 开始3D脑的构建,任务又回到原点:找食,从静止的青蛙要能进化到吃光所有食物。目前只是搭建空的3D框架,还未涉及3D脑模型编程。新的工作存放在core3d目录,原有的旧core目录保留,相应地批处理文件也分为普通版run.bat和3d版run3d.bat,蛋文件也分为普通版eggs.ser和3d版eggs3d.ser。 +脑的3D版要引入模式识别功能,第一个编程任务是要用这个3D脑模拟出体全息存贮现象,也就等价于模式识别功能,用字母的点阵图像激活它的视觉区,并同时激活一个随意指定的脑区作为字母识别区,然后只激活视觉区的图像,再检查这个随意指定的字母区是否会被激活,而其它字母区基本不激活。体全息存贮在物理上很难实现,因为光和材料受物理特性制约,但在虚拟脑中不存在这个物理限制,让虚拟的光子反射、拐弯、增强、拆分都可轻易模拟出。 +反之,如果对应A字母的脑区兴奋,也要能激活一个模糊的A的像素点阵图像脑内某个区(与视网膜重合或下层),这就是信息的检索和联想,这个联想功能以后会用到,由痛苦、愉快等奖惩机制来调节这个联想过程一直永无止歇地进行下去。 +2019-11-11 字母的模式识别功能 +这是切换到3D脑的第一个正式版本更新,它实现了ABCD四个字母的识别。测试时分别用ABCD四个字母,并同时加上一个声音信号,模拟体全息存贮。另外这个模式识别是双向的,如果只单单激活听力区,也会在视网膜区成像。(如果要演示这点,需要将LetterTester.java中的seeImage和hearSound两行注释互换一下,并去除Cell.java中的59和60两行,这两行代码的作用是阻止光子逆向传播到视网膜上)。以下是这个模式识别的截图,黑色的小点表示视网膜发出的视觉波信号,蓝色的表示耳朵发出的听力波信号:在它们交汇的地方,细胞象果冻一样,被波的载体(光子)砸出洞来,每当接收到新的光子,就有可能在旧的洞里砸出光子来并逆向传播,用红色小点来表示,最终在波源处逆向成像。这个工作原理在细胞级别将相关的信号关联起来,也是体全息存贮的模拟实现,可以在三维空间实现信息的高密度存贮: +这个模式识别的原理比较简单,不需要任何高等数学知识,每个人都能看懂,而且它可能更符合人脑的工作模式,它可以进行图像到声音的关联,也可以实现声音到图像成像的逆关联,另外还有两个重要优点:1.它可以同时处理多维的信号,也就是说可以同时处理多个图片、声音等信号。 2.它的训练速度非常快,没有采用什么海量的大数据来进行训练,只要任意有关联的信号,哪怕信号只出现一两次,它都会自动将它们关联起来,这个关联是动物的条件反射能够建立的基础。 +有了模式识别,以后的工作就好办了。今后将在这个模式识别基础上进行扩展,进行多参数优化自动生成器官、声音的编码、把小蛇引入到虚拟环境等等一系列更复杂有趣的任务。 +2019-11-16 模式识别功能更新 +上次提交有个大bug,有多个字母出现时将不能区分,这次提交更正过来。到此为止,基本完成了模式识别的原理验证过程,即如果字母的象素点与训练图片的重点合越多,则听力区收到的反向红色光子数就越多,这是一个简单、直观的模式识别方法,以后可以通过将声音分成多个小区编码,并统计每个区收到多少反向的光子总数来判断是哪个字母图像输入。原理验证比较枯燥,但这个模式识别功能是无法绕过去的,一旦原理被证实,以后就可以有意地引导或者说设计青蛙向这个方向进化,而不是由手工来搭建包含模式识别功能的脑模型,因为一来要减少手工的干预,硬编码越少越好,尽量利用随机变异、生存竟争这个电脑工具,二来这个原理不光是模式识别要用到,其它信号处理(如快感、痛觉信号与行为信号之间的关联)都要用到类似的细胞级逻辑,因为我一直在强调“任意两个时间上相关的信号,大脑总会将它们关联起来,这是条件反射行为建立的基础”。 +另外,这次更新加强了暂停功能,可以在脑图任一时刻暂停,并加上了脑图的剖面显示。所有脑图快捷键有: T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标操作:缩放旋转平移 +![result6](result6_letter.gif) +2019-11-26 优化了一下程序,用"对酒当歌人生几何"8个汉字来进行模式识别原理演示,但容错性依然没有,变形、变位后的文字识别率很差。以后要考虑借签算法中的侧抑制、卷积、分层等原理来提高它的容错性,用图形化模拟的方式来实现。总体上算法和图形化模拟是两条路,算法通常可以用模拟的方式来表达,但不是所有模拟都可以归纳成算法,因为模拟(或者说软件本身)有时会非常复杂,不容易总结出规律。也就是说模拟的表现力比算法更强,但模拟的缺点是资源消耗大。 +2019-12-27 开始设立history目录,给主要的版本直接在history目录下创建副本,以方便运行。在history\003a_legs目录下(依然是2维脑)尝试给青蛙加两条腿,看它能不能自动学会走路。一条腿位于下方,负责左右移动,一条腿位于右侧,负责上下移动,每条腿有抬腿、落腿、转动和相应的感觉细胞。只有当腿落下且转动,而且另一条脚抬起来时青蛙才会位移,具体什么时候抬腿、什么时候转动腿完全由随机数决定。经过一段时间的生存汰淘之后,青蛙会进化出会利用两条腿走路了,但需要的时间非常长,约几个小时之后才达到最高吃食率50%左右,走路风格也比较诡异,是小碎步而不是大踏步。但至少这是青蛙第一次利用两条腿来走路,还是有点意义的,这证明生命进化中就算神经元随机排布,进化出眼睛和腿也是非常简单自然的事。这个实验只给青蛙加了两条腿,但同理如果有四条或更多的腿它应该也是可以随机进化出来的。 +![result7](result7_legs.gif) +2020-05-04 在进行3维脑改造过程中,发现找食率很低,自己也看不懂以前的程序怎么编的了,所以在history目录下又添加一个003b_simple目录,把2维脑简化一下,去掉不重要的器官,好分析它的逻辑。 +2020-05-07 经过一番折腾和走弯路之后,最后还是原汁原味地将2维脑003b目录的逻辑搬到了3维脑core目录里了,实现了同样的找食率(~50%左右)。从现在开始,可以专注于改进3D脑本身了。 +2020-06-26 小蛇吃青蛙 +位于history\006目录下,设定小蛇只能看到青蛙,青蛙只能看到蛇(严格说是蛇的舌头)。可以看到小蛇会追着青蛙,而青蛙会躲开小蛇,当然也有躲不开被吃掉的。除了引入负值连线用蓝色线条来表示外,技术细节上倒没有什么突破,但这个实验有趣的地方在于它证实了就算是完全随机的排列脑细胞,在长期的优胜劣汰后,生命也会进化出捕食和逃避行为。即然可以进化出捕食和逃避行为,而生命进化又会向越来越复杂的方向进化,所以这个原理可以解释为意识的萌芽了。高等生命的意识,本质上也无非就是大自然随机运动产生的一种复杂现象而已。 +![result8](result8_snake.gif) +下一步的工作将移回到体全息存贮的模式识别,因为青蛙需要这个模式识别功能来更好地分辨出蛇和食物的区别,而体全息存贮个人感觉有很多潜力可挖,它有两个最大的优点:一是可以小样本学习,二是可以同时处理多维的信息输入输出。 + +2020-09-13 这次不是更新了,因为我最近太忙,所以先临时发布一个小开发任务,见下图,如果有兴趣的同学可以尝试一下看能不能做出来: +![task1](task1.png) +说明:详见图片,通过模拟一个虚拟的环境来淘汰青蛙,只有当青蛙进化出能够在震心的位置发出叫声,其它的青蛙能听到这个叫声跳在空中躲避震波时,青蛙的生存机率才会变高,但是跳起来也会消耗能量,只有听到报警后跳起来才正好。如果这个实验能做成功,就可以证明生物即使脑神经元随机排列,也会进化出发音和听力器官,这是挺有意义的,因为这是一个群体进化,对于单个青蛙来说,进化出能叫,或是能听到声音对它自己是意义不大的,但是对于群体的生存有利,这个进化相当于是语言和听力的萌芽。整个虚拟环境的设定,就是要逼迫这种群体进化现象产生出来,个人认为是有大概率能做出来的。这里说进化出器官,不是变出来,而是模拟生物体偶然变异出了听力细胞和发音器官,这个是没问题的,问题的重点在于听力细胞和发音器官是如何在进化过程演变成脑神经网络的一个组成部分的。 + +2021-01-23 语言的诞生。 +好不容易,告别漫长的2020,去年出的题目我自己解出来了,下面是答案,运行history\007_earthquake目录下的run.bat,可能看到类似下面的运行画面: +![result9](result9_earthquake.gif) +详细解说:这个题目的模拟环境被简化成左右两个区,设定地震发生时(用红框表示)会扣除所有青蛙的能量,但是只有位于左侧的青蛙可以看到地震发生和停止,右区的青蛙不能看到地震发生和停止,但是青蛙有发音器官和听音器官,如果左侧的青蛙发出叫声是可以被右侧的青蛙听到的。看到地震发生、看到地震停止、发出叫声、听到叫声、跳起、落地这6个功能分别对应6种器官并都偶然进化出来(这个无需证明),这个实验的目的是要验证青蛙会不会在环境逼迫下,在这6种器官对应的脑细胞间形成神经关联。结果可以看到,左侧的青蛙看到地震后,跳在空中(用黄色表示),并发出叫声,然后右侧的青蛙听到叫声后也跳在空中。左侧的青蛙通过叫声信号传递了信息给右侧的青蛙,从而让右侧的青蛙避开了它看不见的地震伤害。这是一个成功的群体进化的演示,它证明了即使是随机生成神经细胞连线,也可以进化出生物的发音-听力功能,也就是说进化出初步的语言功能。 +另外说一下,这个运行结果可能要运行多次才有可能遇到,这个可以用群友"背叛的细胞膜"提到的自然界的“顶端优势”来解释,生物进化有可能陷入局部最优解而绕不出来,对自然界来说就是没找到最优解的生物种群可能会被汰淘,存在即合理。 + +2021-05-15 细胞分裂的演示 +这是我在微信群里发布的一个小任务, 还没来及更新到这里,群里的pama_1234就做出来了,编程速度不是一般的快,顺便说一下,他现在还只是高一,现在的后浪都这么历害了。 +任务很简单,就是画出类似下图小蛇形状的就赢了。要求: +1.小蛇至少30x30象素,有眼睛和舌头,反正一眼看上去要象一条蛇的样子。 +2.要求使用遗传算法和分裂算法,遗传算法大家都了解了。分裂算法我现在只有一个大致的思路,就是模仿细胞分裂,来演化出不同的形状。分裂可以是把别的细胞推开,也可以是只在边沿分裂以减少运算量,这个不限。 +3.画出“蛇”、“青蛙”的汉字也可以 +这个任务看起来和神经网络关系不大,但我觉得有可能利用这个算法来进行器官自动排布,所以还是有一定的意义的,任意复杂的形状生成,今后三维脑的细胞结构,都有可能从这个任务演化出来。 +输出结果((pama_1234编写): +![result10](result10_drawsnake.gif) +他的项目位于这里:[细胞画蛇](https://gitee.com/pama1234/cell-painting-snake), 有兴趣研究源码的可以看一看,是基于processing编写的。 +顺便我把我的构思也写在这里,我没有仔细研究他的代码,但估计思路应该大体一致: +1.与形状表达相关的基因采用多叉树结构数据结构,树结构是单例,只保存1份在内存中,每个细胞分裂后,端粒酶减一,相当于从树结构往下走一级。 +2.每个细胞分裂后,为了获得当前细胞的位置,但是又不能复制整个子树,所以要保存一个指针,指向树的当前节点,即子树的顶点。不再分裂的节点可以不保存节点指针。 +3.基因树总体稳定,但有大概率小变化,小概率大变化,基因树变化包括数量重复、分叉方向、分叉数量。 +4.因为是多叉树,可以从任意一点开始作为受精卵开始分裂,最后都可以形成指定的图形。 + +画小蛇本身任务很简单,就是个填充色块的任务,用window里的画图笔刷不到半秒就可以搞定,之所以要把它当成一个任务来做,并限定用细胞分裂的方式,是因为大自然的解决方案不是用什么人为设计的算法,而是只有一招就是细胞随机的分裂、遗传和变异,现在看起来这个分裂模拟还是比较容易实现的,大致上解释了自然界生物各种形态生成的原因。 + +2021-05-15 顺便发布下一个开发任务:青蛙吃蘑菇 +1. 青蛙要根据不同的画案区分有毒无毒的蘑菇。蘑菇用随机生成十种不同蘑菇图案来表示,这些蘑菇中有一半无毒,另一半有毒。 +2. 要利用体全息存储或面全息存储方案来进行蘑姑图案识别。体全息存储参见005_letter_test示例;面全息存储方案可以参考激光全息存储方案,和体全息的区别是信息是保存在一个平面上,而不是一个立体间里,相同点是利用入射光和出射光位置信息进行存储和读取,入射光和出射光互为参考信号。 +3. 要在现有的Java项目界面下进行,也就是说,要显示虚拟环境和脑图,源文件名必须为.java后缀。这个和细胞分裂的演示不一样,这个任务是项目的主线任务了。 +4. 蘑茹有毒还是无毒是与图案相关的,但是有毒还是无毒必须由青蛙在进化过程中自动判断出来,不能由人为来设一个有毒的信号告诉青蛙(青蛙之间互传信号不在这个限制)。任务完成的标志是青蛙们要能进化成吃掉虚拟环境里的所有无毒蘑茹,避开所有有毒蘑菇。 + +2021-07-04 依然是模式识别演示 +位于history\005a和005b两个目录下,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来: +![result11](result11_letter_test.gif) +这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但考虑到实现这些功能的复杂性,我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有简单模式识别功能的模拟生命体,也就是说实现上面发布的任务。 + +2021-08-13 演示同时处理多个方向的信号 +位于history\005a1目录下,演示用5个声母和5个韵母的组合来关联到25个图像的识别,这样可以减少声音输入区的数量。它的另一个目的是演示体全息存贮的工作模式可以同时处理多个方向的信号。这个演示分辨率极差,只有约一半的识别率,但我不打算继续改进了。 +![result12](result12_letter_test2.png) + +2021-10-13 失败的细胞分裂尝试 +位于history\008目录下,这次本来想模仿生物细胞的分裂,从一个细胞开始分裂出任意指定的三维形状,并设计了split、goto等基因命令,但是做来做去做不出结果,细胞们就象跳蚤一样乱跑不听使唤,最终还是决定放弃,细胞分裂这个算法太难了。细胞分裂的优点是更“象”生物,而且估计可以利用分形原理缩小基因的长度,基因相当于一种自带循环和条件判断的计算机语言。 +最终生成三维形状这个目标还是借助简单遗传算法完成,通过细胞在相邻位置随机生成,并在基因里记录每个细胞的坐标的方式来实现,基因命令被删得只剩一个了,就是随机生成细胞。 +用遗传算法来生成任意形状,就好象一个画家在画画,但是画什么根本不知道,只知道听从旁边人打分,画的好就打高分,画的不好就打低分,这样一直循环下去,最终画的内容只由打分的人决定。目前速度上还有改进余地,比如让新细胞有更多变异率。 +![result13](result13_frog3d.gif) +生成三维形状目的是为生成三维脑结构做准备,神经网络空间上应该是三维的,这样实现模式识别会很方便,而早期随机连线结构损失了空间位置信息,三维全息演示问题则是它是手工设计的,优化困难。 + +2021-11-08 成功的细胞分裂尝试 +位于history\009目录下,这次的细胞分裂算法采用自顶向下的策略,也就是从单个细胞开始,一个细胞分裂成8个(因为1个正方体切三刀正好是8个小正方体)这种方式来进行。这种方案不是从目标形状的局部开始填充,而是从毛胚、从目标的粗轮廓开始利用遗传算法细化,直到细化出每个细节。这种方案的优点是更接近生物实际,符合“从总体到局部”的正常逻辑,而且有高效的基因压缩存储率,为了说明存储率这点,大家可以看看下图左面这个树结构,猜一猜要存储它最少需要多少个字节? +![depth_tree](depth_tree.png) +答案是最少只需要一个整数7就可以表达这个树结构了。说一下原理:首先所有树结构都可以用行号+深度的方式来表达,详见我的博客[基于前序遍历的无递归的树形结构](https://my.oschina.net/drinkjava2/blog/1818631),获取子树时可以避免递归访问,其次因为采用基因敲除的方式,只需要记录被敲除的树节点的行号和深度就就可以了,最后因为固定采用3叉树分形结构,所以深度值也可以省略,只用一个行号7就可以表达这整棵树了。 +下图是这个算法的动画,可以看出它与上次的演示是不同的分裂模式,是先有总体后有细节。项目中实际采用的是8叉树,深度用细胞边长表示: +![result14](result14_wa3d.gif) +细胞分裂算法一方面可以利用来生成和优化物理形状(比方虚拟风叶、翅膀、受力结构等形状),另一方面它和神经网络的形成算法是有共通点的,因为众所周知心脏形状、血管网络、大脑神经网络都是由基因控制细胞分裂出来的。所以以后有可能利用这个算法来自动生成和优化神经网络触突三维空间分布结构。 +顺便说一下,自顶向下的问题是它一旦主分支被误敲除,就不容易补回去,实际的生物例子就是人眼结构还不如章鱼。自然界是用生物的多样化和环境的连续化来保证各种主分支都有尝试。我们电脑模拟要尽量保持环境(任务)的连续化,从低到高一步步走,个别时候考虑结合其它算法给错误的分支打补丁。 + +2021-11-26 多参数的细胞分裂 +位于history\009a目录下,只是在上次细胞分裂基础上改进了速度,将三维cell对象数组改为long型数组,节省了对象创建和销毁开销,速度有明显改进。long类型有64位,所以一个细胞可以有64维独立参数,应该够用了。下图是一个6倍速显示的三维鱼分裂生成动图。它一共用到4维参数,分别是细胞的位置和三个不同颜色的细胞色彩参数,每一维分别一个细胞分裂算法单独控制,参数之间没有做关联关系: +![result15](result15_fish3d.gif) +这个动画的每一帧是细胞分裂到最小不可再分的最终结果,而且是从400个青蛙中生存下来的最佳个体,这就是遗传算法,遗传算法就是穷举法。这个16x16x16的大立方体要理解成第一个细胞,只是画的大了而已。以后等有时间可以做1个细胞分成8个,8个变64个的动画,能更好地演示分裂的中间过程。 +细胞分裂研究到此结束,下面要开始生成神经网络空间结构了。我的思路是,脑结构也无非就是三维细胞的空间排布而已,细胞有各种参数,比如触突长度、方向、密度、信号收发阀值、信号强度、信号遗忘曲线等,只要每个细胞不超过64个构造参数,就可以用分裂算法来随机试错把神经网络的空间结构给试出来。分裂算法的优点是遵循从主干到细节的生成次序,如果要完成的任务(即外界信号输入输出)也是从简单到复杂,就可能正好符合这个脑的空间结构生成顺序。 + +2022-01-03 多参数的细胞分裂的小改进 +位于history\009b目录下,与009a分支相比,算法是相同的,只是作了以下一些小改动: +1.可增加颜色 2.添加存盘选择框,可以在运行期选择存盘 3.添加每维参数分别显示的选择框 4.将颜色参数与细胞位置参数产生关联 5.新增了上次提到的显示分裂过程动画图,见下图: +![result16](result16_cell_split.gif) +因为参数之间有关联,更容易发生缺色现象,这是因为进化过程中,主要参数(细胞位置)的进化会导致次要参数的分支被误删,然后就很难再补回来了。为了解决这个问题,下一个版本打算改进算法,采用黑白节点的方式,黑节点删除节点以下的细胞,白节点保留节点以下的细胞。 +细胞分裂研究算法目前还不能结束(打脸自己),还要先解决这个缺色问题。多参数的进化,如果一旦某个参数被误删除就不能再进化回来,这种算法是不能接受的。 + +2022-01-15 多参数的细胞分裂继续改进:阴阳无极八卦阵算法 +位于history\009c目录下,采用了阴阳(黑白)节点算法,阴节点基因会删除节点下所有节点,是自顶向下的减材加工,阳节点基因会保留节点下所有节点,是自底向上的增材加工。利用遗传算法的大样本筛选,把自顶向下和自底向上两个进化方向结合起来,这样基本能解决误删分支后缺色不能补回这个问题。而且对于奖罚不再象以前一样要设成差距很大的值,animal.java中awardAAAA()原来要设成8000, 现在设成20也不会产生缺色现象。考虑到这个算法的特点,我给它起名“阴阳无极八卦阵算法“,阴阳是指它有阴阳两种节点,无极是指它的分裂阶数没有限制,八卦阵是指它采用了多个8叉树结构,每一维细胞参数都对应一个8叉树。 + +2022-07-22 树的生长演示 +位于history\010目录,演示一个树的生长。这个演示是基于规则而不是模板来生成一棵树,这棵树的形状仅由以下规则决定: +1.树根位于底部中心 +2.树的每个细胞水平方向四周不能有其它细胞 +3.树的每个细胞正下方不能有细胞,但必须在斜下方至少有一个细胞 +基于这三条规则,利用阴阳无极八卦阵分裂算法,树能够自动进化出来。这个演示的意义是表示形状可以由规则来决定,不同的规则能进化出不同的形状,生物会通过改变自己的形状来适应环境规则。 +![result17](result17_tree_grow.gif) + +2022-09-25 用阴阳无极八卦阵完成找食任务 +位于history\011目录,这是利用阴阳无极八卦阵算法来完成最开始的“找食”这个任务,即采用模拟细胞分裂再配合遗传算法,生成一个简单功能的神经网络,输入只有上下左右四种视觉细胞,输出只有上下左右四种运动细胞。 +为什么要使用阴阳无极八卦阵来生成神经网络,是因为它有几个特点: +1.传统神经网络因为手工调优参数太多,被类比为”炼丹法“,而采用阴阳无极八卦阵之后,算法将只有固定的这一个,调参完全交给计算机,手工只需要设计基因和细胞行为即可,以不变应万变。只要有利于生存的脑结构,就会被筛选出来,结构即逻辑。 +2.有高效的信息存储方式(见先前8叉树的介绍),基因虽然是随机生成的,但是它采用树结构这种方式有非常高的信息压缩比,可以用少量的基因控制巨量细胞的生成。 +3.更贴近实际生物的神经网络生成原理。大自然就是采用分裂+遗传算法来生成脑神经网络的,理论上我们应该可以照抄这个模式来创造出高智能的神经网络。 +4.它的神经网络是三维的,结构组合方式是无穷的。同一个细胞可以由多个基因控制,一个基因可能控制零个或所有的细胞。 +下面是实际运行结果,最上面一层用4种颜色来表示眼睛细胞,其余的细胞参数如运动细胞、轴突方向、轴突长度等用不同大小、颜色的圆来表示: +![result18](result18_yinyan_eatfood.gif) +这个版本的目的是验证阴阳无极八卦阵算法的实用性和搭建初步框架。后面的任务将是进一步复杂化,生成有模式识别功能的神经网络三维结构。 + +2022-10-18 继续纸上谈兵,谈谈脑结构 +本次更新是一篇软文,不涉及具体编程。细胞分裂算法可以解释神经网络生成的原因,但是完全依靠它来生成复杂神经网络,也是有问题的,就是资源不够,电脑模拟不像大自然一样有无限的时间和样本,随机试错不知要试到猴年马月去。为了加快模拟脑的生成,可以走一点捷径,就是如果已知生物脑的大致结构,可以先把细胞空间位置先固定下来,然后再利用细胞分裂算法来微调细胞参数。对的,分裂算法不光可以用来生成结构,也可以在结构固定的前提下用来调整参数,因为参数的空间分布也是一种结构。 +下图是我最近构想的脑结构图,类似于机械中的总装图,从总体上勾勒出脑的结构和运行机制,这个没什么依据,仅供参考,但还是有点意义的,因为如果总体上猜对了脑结构,会对今后的编程开发带来很大便利。 +这个结构图中的要点是脑核只处理少量精简后的信息,图中各个部分都有注释,大家应该能看懂我的大概思路。这个脑结构并不完全符合生物脑结构,而是从脑的功能出发倒推猜测出来的: +![brainstructure](brainstructure.png) + +## 2023-02-26 阴阳4叉树分裂算法 +本次是个小更新,位于history/012_tree4目录下,演示4叉树分裂算法。 +如果已知参数是分布在一个平面上时,就没必要用8叉树了,因为效率太低,这时可以改成阴阳4叉树分裂算法,其优点有两个,一是可以手工分层式设计神经网络参数,二是速度快。 +在16x16x16的空间中,演示在三个层中分别显示“SAM”这几个字母,如果采用阴阳8叉树算法(将Cells.java中调用register方法第三个参数改为-1),则需要3000循环以上,而采用阴阳4叉树分裂算法只需要300次循环就够了。 +![result19](result19_tree4.png) +在同一个三维空间中,8叉树和4叉树算法可以同时结合起来使用,这里就不演示了,以后在三维神经网络搭建时可能会碰到。 + +## 2023-07-02 阴阳2叉树分裂算法 +位于history/013_tree2目录下,演示阴阳2叉树分裂算法。如果已知参数是分布在一条线上时,就没必要用8叉或4叉阴阳树算法了,这时可以用阴阳2叉树分裂算法。演示中在三个层中分别显示“SAM”这几个字母,"S"用4叉树平面分裂算法,“A”和"M"用2叉树算法,其中"M"只允许基因分布在Z坐标为偶数的Z轴上。本次更新因为太简单就不上图了。 +虽然2叉这个更简单,我本来也没觉得有必要添加,但最近构思用分裂算法实现模式识别时,发现有些思路验证可能没必要直接从2维或3维图形开始,直接从线状信号的识别开始会更简单,有利于思考和做原型验证。在线状上符合的逻辑,外推到2维或3维必然成立。 + +题外话: +目前我思考的是 “视觉”、“苦味”、“甜味”、“咬”、“不咬”这几种生物细胞,可以说是构成一个具备模式识别能力的昆虫脑最简化的信号输入输出单位了,其中视觉信号还可以再简化为单维的视觉信号,也就是如果已知某些神经元参数是线状分布,就可以用2叉分裂算法来快速建模了。 +模式识别的本质是预测,也就是说,本来正常逻辑应该是 "图案"->“咬”(随机发生)->“甜味感觉”, 具备了模式识别的昆虫会把这个随机事件中的随机性去除,变成"图案"->“甜味感觉”和"图案"->“咬”两个神经关联。也就是在图案和动作之间形成短路(条件反射),直接根据图案来预测它是可以咬的,从而在生存竟争中胜出。 +以上讨论的是结果,具体昆虫脑是如何实现这个逻辑的,还必须有记忆细胞的参与,否则以前的信号不可能影响到后面的预测。记忆细胞的作用是将短期内的所有信号如“视觉”和“咬”动作信号形成关联。“甜味感觉”这种信号会加强这种关联,“苦味感觉”这种信号会抑制这种关联。 +因为最近我业余时间少,项目进度非常慢,如果有兴趣的朋友也可以自己尝试在以上描述基础上实现这个昆虫脑模式识别功能,这就是一个最简单的脑模型了,目前虚拟环境和分裂算法都已经有了,接下来就是如何定义脑细胞基因和它对应的信号处理行为来实现这个逻辑了。智能本质上是模式信号、奖惩信号和行为输出之间的关联,少量几次奖惩即可形成条件反射,虚拟环境如果很简单,对应地训练也应该很简单,不需要用到大模型信号来训练。 + +## 运行方式 | Run +运行core或history各个子目录下的run.bat批处理文件即可启动运行,history下有多个子目录,按版本号顺序排列,存放着这个项目演化过程中的主要历史版本供演示。 +另外如果想要研究这个项目的早期版本,可以结合gitk命令和参考"版本提交记录.md"的介绍,用git reset命令回复到以前任一个版本,例如用: +git reset --hard ae34b07e 可以转回到以前一个分组测试的找食版本。 码云上通常是大版本提交,跑出结果才会更新,GitHub上则是日常提交。 +更多关于项目源码的介绍可以参见"初学者入门介绍.md"以及history目录下的项目文档如"Frog_doc_012.doc"。 + +## 重要参数 | Parameters +在Env.java类中以下有以下可调整参数,请手工修改这些参数进行不同的测试,前4个参数很重要: +``` +SHOW_SPEED: 调整实验的速度(1~1000),值越小则越慢。 +EGG_QTY: 每次允许Frog下多少个蛋,通常下蛋取值在10~1000之间。蛋保存着我们测试的结果。实验的最终目标就是获得一批蛋。 +FROG_PER_EGG: 每个蛋可以孵出多少个青蛙。 +SCREEN: 分屏测试,一轮测试可以分为多个批次进行,这里是屏数。每轮总的青蛙数量=EGG_QTY * FROG_PER_EGG, 每屏青蛙数=总数/SCREEN +DELETE_EGGS: 每次运行是否先删除保存的蛋,如果设为false,将不删除保存的蛋,会接着上次的测试结果续继运行。 +SAVE_EGGS_FILE: 是否允许输出蛋文件到磁盘上 +ENV_WIDTH: 虚拟环境的宽度大小,通常取值100~1000左右 +ENV_HEIGHT: 虚拟环境高度大小,通常取值100~1000左右 +FROG_BRAIN_DISP_WIDTH: Frog的脑图在屏幕上的显示大小,通常取值100~1000左右 +STEPS_PER_ROUND: 每轮测试步数, 每一步相当于脑思考的一桢,所有青蛙的脑神经元被遍历一次。 +FOOD_QTY:食物的数量,食物越多,则Frog的生存率就越高,能量排名靠前的一批Frog可以下蛋,其余的被淘汰。 +``` + +## 版权 | License +[Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +## 期望 | Futures +欢迎发issue、评论等方式提出建议或加入开发组。另外在other目录下也可以上传你的文章、项目链接等资源。 + +## 关注我 | About Me +[Gitee](https://gitee.com/drinkjava2) +[GitHub](https://github.com/drinkjava2) +微信:yong99819981(如想长期关注本项目、或参与开发,请加我并留言"人工生命群") \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 9beec6c..9198112 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -2,9 +2,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.gitee.drinkjava2 - frog013 + frog014 jar - 13.0 + 14.0 frog 当前目标是用分裂算法来实现第一个具备模式识别功能的神经网络 diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/Animal.java b/core/src/main/java/com/gitee/drinkjava2/frog/Animal.java index 80624a1..8fe8b75 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/Animal.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/Animal.java @@ -10,8 +10,7 @@ */ package com.gitee.drinkjava2.frog; -import static com.gitee.drinkjava2.frog.brain.Cells.GENE_NUMBERS; -import static com.gitee.drinkjava2.frog.util.RandomUtils.percent; +import static com.gitee.drinkjava2.frog.brain.Genes.GENE_NUMBERS; import java.awt.Graphics; import java.awt.Image; @@ -21,14 +20,11 @@ import java.util.ArrayList; import javax.imageio.ImageIO; -import com.gitee.drinkjava2.frog.brain.Cells; +import com.gitee.drinkjava2.frog.brain.Genes; import com.gitee.drinkjava2.frog.egg.Egg; -import com.gitee.drinkjava2.frog.judge.D2Judge; import com.gitee.drinkjava2.frog.objects.Material; +import com.gitee.drinkjava2.frog.util.GeneUtils; import com.gitee.drinkjava2.frog.util.RandomUtils; -import com.gitee.drinkjava2.frog.util.Tree2Util; -import com.gitee.drinkjava2.frog.util.Tree4Util; -import com.gitee.drinkjava2.frog.util.Tree8Util; /** * Animal is all artificial lives' father class @@ -51,26 +47,33 @@ public abstract class Animal {// 这个程序大量用到public变量而不是ge } } - public ArrayList> genes = new ArrayList<>(); // 基因是多个数列,有点象多条染色体。每个数列都代表一个基因的分裂次序(8叉或4叉)。 + public ArrayList> genes = new ArrayList<>(); // 基因是多个数列,有点象多条染色体。每个数列都代表一个基因的分裂次序(8叉/4叉/2叉)。 + + public static final int CONSTS_LENGTH = 8; + public int[] consts = new int[CONSTS_LENGTH]; //常量基因,用来存放不参与分裂算法的全局常量,这些常量也参与遗传算法筛选,规则是有大概率小变异,小概率大变异,见constGenesMutation方法 /** brain cells,每个细胞对应一个神经元。long是64位,所以目前一个细胞只能允许最多64个基因,64个基因有些是8叉分裂,有些是4叉分裂 * 如果今后要扩充到超过64个基因限制,可以定义多个三维数组,同一个细胞由多个三维数组相同坐标位置的基因共同表达 */ - public long[][][] cells = new long[Env.BRAIN_CUBE_SIZE][Env.BRAIN_CUBE_SIZE][Env.BRAIN_CUBE_SIZE]; + public long[][][] cells = new long[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE]; //所有脑细胞 - public float[][][] energys = new float[Env.BRAIN_CUBE_SIZE][Env.BRAIN_CUBE_SIZE][Env.BRAIN_CUBE_SIZE]; //每个细胞的能量值,这些不参与打分。打分是由Animan的energy字段承担 + public float[][][] energys = new float[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE]; //每个细胞的能量值,细胞能量不参与打分。打分是由fat变量承担 - public int x; // animal在Env中的x坐标 - public int y; // animal在Env中的y坐标 - public long energy = 1000000000; // 青蛙的能量为0则死掉 + public int[][][][] holes = new int[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE][]; //每个细胞的洞(相当于触突) + + public int xPos; // animal在Env中的x坐标 + public int yPos; // animal在Env中的y坐标 + public long fat = 1000000000; // 青蛙的肥胖度, 只有胖的青蛙才允许下蛋, 以前版本这个变量名为energy,为了不和脑细胞的能量重名,从这个版本起改名为fat public boolean alive = true; // 设为false表示青蛙死掉了,将不参与计算和显示,以节省时间 - public int ateFood = 0; // 青蛙曾吃过的食物总数,下蛋时如果两个青蛙能量相等,可以比数量 + public int ateFood = 0; // 青蛙曾吃过的食物总数 + public int ateWrong = 0; // 青蛙咬了个空气的次数 public int no; // 青蛙在Env.animals中的序号,从1开始, 会在运行期写到当前brick的最低位,可利用Env.animals.get(no-1)快速定位青蛙 public int animalMaterial; public Image animalImage; public Animal(Egg egg) {// x, y 是虑拟环境的坐标 + System.arraycopy(egg.constGenes, 0, this.consts, 0, consts.length);//从蛋中拷一份全局参数 for (int i = 0; i < GENE_NUMBERS; i++) { genes.add(new ArrayList<>()); } @@ -79,161 +82,88 @@ public abstract class Animal {// 这个程序大量用到public变量而不是ge genes.get(i++).addAll(gene); i = 0; if (Env.BORN_AT_RANDOM_PLACE) { //是否随机出生在地图上? - x = RandomUtils.nextInt(Env.ENV_WIDTH); - y = RandomUtils.nextInt(Env.ENV_HEIGHT); + xPos = RandomUtils.nextInt(Env.ENV_WIDTH); + yPos = RandomUtils.nextInt(Env.ENV_HEIGHT); } else {//否则出生成指定区域 - this.x = egg.x + RandomUtils.nextInt(80) - 40; - this.y = egg.y + RandomUtils.nextInt(80) - 40; - if (this.x < 0) - this.x = 0; - if (this.y < 0) - this.y = 0; - if (this.x >= (Env.ENV_WIDTH - 1)) - this.x = Env.ENV_WIDTH - 1; - if (this.y >= (Env.ENV_HEIGHT - 1)) - this.y = Env.ENV_HEIGHT - 1; + this.xPos = egg.x + RandomUtils.nextInt(80) - 40; + this.yPos = egg.y + RandomUtils.nextInt(80) - 40; + if (this.xPos < 0) + this.xPos = 0; + if (this.yPos < 0) + this.yPos = 0; + if (this.xPos >= (Env.ENV_WIDTH - 1)) + this.xPos = Env.ENV_WIDTH - 1; + if (this.yPos >= (Env.ENV_HEIGHT - 1)) + this.yPos = Env.ENV_HEIGHT - 1; } } public void initAnimal() { // 初始化animal,生成脑细胞是在这一步,这个方法是在当前屏animal生成之后调用,比方说有一千个青蛙分为500屏测试,每屏只生成2个青蛙的脑细胞,可以节约内存 - //TODO: for 2D genes need use 4-tree instead of 8-tree 平面的要改成4叉树以加快速度 - - geneMutation(); //有小概率基因突变 - if (RandomUtils.percent(50)) + GeneUtils.geneMutation(this); //有小概率基因突变 + GeneUtils.constGenesMutation(this); //常量基因突变 + if (RandomUtils.percent(40)) for (ArrayList gene : genes) //基因多也要适当小扣点分,防止基因无限增长 - energy -= gene.size(); - createCellsFromGene(); //根据基因,分裂生成脑细胞 - - D2Judge.pic1.judge(this); //对平面上分布的参数打分 - D2Judge.pic2.judge(this); - D2Judge.pic3.judge(this); - + fat -= gene.size(); + GeneUtils.createCellsFromGene(this); //根据基因,分裂生成脑细胞 } - private static final int MIN_ENERGY_LIMIT = Integer.MIN_VALUE + 5000; - private static final int MAX_ENERGY_LIMIT = Integer.MAX_VALUE - 5000; + private static final int MIN_FAT_LIMIT = Integer.MIN_VALUE + 5000; + private static final int MAX_FAT_LIMIT = Integer.MAX_VALUE - 5000; //@formatter:off 下面几行是重要的奖罚方法,会经常调整或注释掉,集中放在一起,不要格式化为多行 - public void changeEnergy(int energy_) {//正数为奖励,负数为惩罚, energy大小是环境对animal唯一的奖罚,也是animal唯一的下蛋竞争标准 - energy += energy_; - if (energy > MAX_ENERGY_LIMIT) - energy = MAX_ENERGY_LIMIT; - if (energy < MIN_ENERGY_LIMIT) - energy = MIN_ENERGY_LIMIT; + public void changeFat(int fat_) {//正数为奖励,负数为惩罚, fat值是环境对animal唯一的奖罚,也是animal唯一的下蛋竞争标准 + fat += fat_; + if (fat > MAX_FAT_LIMIT) + fat = MAX_FAT_LIMIT; + if (fat < MIN_FAT_LIMIT) + fat = MIN_FAT_LIMIT; } - //如果改奖罚值,就可能出现缺色,这个要在基因变异算法(从上到下,从下到上)和环境本身奖罚合理性上下功夫 - public void awardAAAA() { changeEnergy(2000);} - public void awardAAA() { changeEnergy(10);} - public void awardAA() { changeEnergy(5);} - public void awardA() { changeEnergy(2);} + //没定各个等级的奖罚值,目前是手工设定的常数 + public void awardAAAA() { changeFat(2000);} + public void awardAAA() { changeFat(1000);} + public void awardAA() { changeFat(60);} + public void awardA() { changeFat(10);} - public void penaltyAAAA() { changeEnergy(-2000);} - public void penaltyAAA() { changeEnergy(-10);} - public void penaltyAA() { changeEnergy(-5);} - public void penaltyA() { changeEnergy(-2);} - public void kill() { this.alive = false; changeEnergy(-5000000); Env.clearMaterial(x, y, animalMaterial); } //kill是最大的惩罚 + public void penaltyAAAA() { changeFat(-2000);} + public void penaltyAAA() { changeFat(-1000);} + public void penaltyAA() { changeFat(-60);} + public void penaltyA() { changeFat(-10);} + public void kill() { this.alive = false; changeFat(-5000000); Env.clearMaterial(xPos, yPos, animalMaterial); } //kill是最大的惩罚 //@formatter:on - public boolean active() {// 这个active方法在每一步循环都会被调用,是脑思考的最小帧 - // 如果能量小于0、出界、与非食物的点重合则判死 + public boolean active(int step) {// 这个active方法在每一步循环都会被调用,是脑思考的最小帧,step是当前屏的帧数 + // 如果fat小于0、出界、与非食物的点重合则判死 if (!alive) { return false; } - if (energy <= 0 || Env.outsideEnv(x, y) || Env.bricks[x][y] >= Material.KILL_ANIMAL) { + if (fat <= 0 || Env.outsideEnv(xPos, yPos) || Env.bricks[xPos][yPos] >= Material.KILL_ANIMAL) { kill(); return false; } - this.energys[0][0][0] = 10; //设某个细胞固定激活 - //Eye.active(this); //如看到食物,给顶层细胞赋能量 - Cells.active(this); //细胞之间互相传递能量 - // - // if (Food.foundAndAteFood(this.x, this.y)) { //如当前位置有食物就吃掉,并获得奖励 - // this.awardAAAA(); - // this.ateFood++; - // } + //holesReduce(); //TODO: 所有细胞上的洞都随时间消逝,即信息的遗忘,旧的不去新的不来 + Genes.active(this, step); //调用每个细胞的活动,重要! return alive; } public void show(Graphics g) {// 显示当前动物 if (!alive) return; - g.drawImage(animalImage, x - 8, y - 8, 16, 16, null);// 减去坐标,保证嘴巴显示在当前x,y处 + //g.drawImage(animalImage, xPos - 8, yPos - 8, 16, 16, null);// 减去坐标,保证嘴巴显示在当前x,y处 } /** Check if x,y,z out of animal's brain range */ public static boolean outBrainRange(int x, int y, int z) {// 检查指定坐标是否超出animal脑空间界限 - return x < 0 || x >= Env.BRAIN_XSIZE || y < 0 || y >= Env.BRAIN_YSIZE || z < 0 || z >= Env.BRAIN_ZSIZE; + return x < 0 || x >= Env.BRAIN_SIZE || y < 0 || y >= Env.BRAIN_SIZE || z < 0 || z >= Env.BRAIN_SIZE; } - private void createCellsFromGene() {//根据基因生成细胞参数 - for (int g = 0; g < GENE_NUMBERS; g++) {//动物有多条基因,一条基因控制一维细胞参数,目前最多有64维,也就是最多有64条基因 - long geneMask = 1l << g; - ArrayList gene = genes.get(g); - int xLayer = Cells.xLayer[g]; - int yLayer = Cells.yLayer[g]; - if (xLayer < 0) { //如xLayer没定义,使用阴阳8叉树分裂算法在三维空间分裂,这个最慢但分布范围大 - Tree8Util.knockNodesByGene(gene);//根据基因,把要敲除的8叉树节点作个标记 - for (int i = 0; i < Tree8Util.NODE_QTY; i++) {//再根据敲剩下的8叉树keep标记生成细胞参数 - if (Tree8Util.keep[i] > 0) { - int[] node = Tree8Util.TREE8[i]; - if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在三维空间对间数组的位置把当前基因geneMask置1 - cells[node[1]][node[2]][node[3]] = cells[node[1]][node[2]][node[3]] | geneMask; //在相应的细胞处把细胞参数位置1 - } - } - } - } else if (yLayer < 0) { // 如果xLayer>=0, yLalyer没定义, 表示此基因分布在坐标x的yz平面上,此时使用阴阳4叉树分裂算法在此平面上分裂加速!!!! - Tree4Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 - for (int i = 0; i < Tree4Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 - if (Tree4Util.keep[i] > 0) { - int[] node = Tree4Util.TREE4[i]; - if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 - cells[xLayer][node[1]][node[2]] = cells[xLayer][node[1]][node[2]] | geneMask; //在相应的细胞处把细胞参数位置1 - } - } - } - } else { // 如果xLayer>=0, yLalyer>=0,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 - Tree2Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 - for (int i = 0; i < Tree2Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 - if (Tree2Util.keep[i] > 0) { - int[] node = Tree2Util.TREE2[i]; - if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 - cells[xLayer][yLayer][node[1]] = cells[xLayer][yLayer][node[1]] | geneMask; //在相应的细胞处把细胞参数位置1 - } - } - } - } - } + public boolean hasGene(int x, int y, int z, long geneMask) { //判断cell是否含某个基因 + return (cells[x][y][z] & geneMask) > 0; } - private void geneMutation() { //基因变异,注意这一个方法同时变异青蛙的所有条基因 - if (percent(90)) - for (int g = 0; g < GENE_NUMBERS; g++) {//随机新增阴节点基因,注意只是简单地随机新增,所以可能有重复基因 - ArrayList gene = genes.get(g); - - int geneMaxLength; //8叉、4叉树、2叉树的节点最大序号不同,基因随机生成时要限制它不能大于最大序号 - if (Cells.xLayer[g] < 0) { //如xLayer没定义,使用阴阳8叉树分裂算法 - geneMaxLength= Tree8Util.NODE_QTY; - } else if (Cells.yLayer[g] < 0) { // 如果xLayer>=0, yLalyer没定义, 表示此基因分布在坐标x的yz平面上,此时使用阴阳4叉树分裂算法 - geneMaxLength= Tree4Util.NODE_QTY; - } else { // 如果xLayer>=0, yLalyer>=0,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 - geneMaxLength= Tree2Util.NODE_QTY; - } - - - int n=3; //这是个魔数,今后可以考虑放在基因里去变异,8\4\2叉树的变异率可以不一样 - if (percent(n)) //生成随机负节点号,对应阴节点, - gene.add(-RandomUtils.nextInt(geneMaxLength)); - - if (percent(n)) //生成随机负正节点号,对应阳节点 - gene.add(RandomUtils.nextInt(geneMaxLength)); - - if (percent(n+n)) //随机删除一个节点,用这种方式来清除节点,防止节点无限增长,如果删对了,就不会再回来,如果删错了,系统就会把这个青蛙整个淘汰,这就是遗传算法的好处 - if (!gene.isEmpty()) - gene.remove(RandomUtils.nextInt(gene.size())); - - } + public boolean hasGene(int x, int y, int z) { //判断cell是否含任一基因 + return cells[x][y][z] > 0; } public void open(int x, int y, int z) { //打开指定的xyz坐标对应的cell能量值为极大 @@ -252,7 +182,105 @@ public abstract class Animal {// 这个程序大量用到public变量而不是ge energys[a[0]][a[1]][a[2]] = 0; } - public float get(int[] a) {//返回指定的a坐标对应的cell能量值 - return energys[a[0]][a[1]][a[2]]; + public void addEng(int[] a, float e) {//指定的a坐标对应的cell能量值加e + if (cells[a[0]][a[1]][a[2]] == 0) + return; + energys[a[0]][a[1]][a[2]] += e; + if (energys[a[0]][a[1]][a[2]] < 0) + energys[a[0]][a[1]][a[2]] = 0f; + if (energys[a[0]][a[1]][a[2]] > 10) + energys[a[0]][a[1]][a[2]] = 10f; } + + public void addEng(int x, int y, int z, float e) {//指定的a坐标对应的cell能量值加e + if (cells[x][y][z] == 0) + return; + energys[x][y][z] += e; + } + + public float get(int x, int y, int z) {//返回指定的a坐标对应的cell能量值 + return energys[x][y][z]; + } + + static final int HOLE_MAX_SIZE = 1000 * 1000; + + public void digHole(int sX, int sY, int sZ, int tX, int tY, int tZ, int holeSize) {//在t细胞上挖洞,将洞的方向链接到源s,如果洞已存在,扩大洞, 新洞大小为1,洞最大不超过100 + if (!hasGene(tX, tY, tZ)) + return; + if (!Env.insideBrain(sX, sY, sZ)) + return; + if (!Env.insideBrain(tX, tY, tZ)) + return; + if (get(tX, tY, tZ) < 1) //要调整 + addEng(tX, tY, tZ, 1); //要调整 + + int[] cellHoles = holes[tX][tY][tZ]; + if (cellHoles == null) { //洞不存在,新建一个, 洞参数是一个一维数组,分别为源坐标X,Y,Z, 洞的大小,洞的新鲜度(TODO:待加) + holes[tX][tY][tZ] = new int[]{sX, sY, sZ, holeSize}; + return; + } else { + int emptyPos = -1; //找指定源坐标已存在的洞,如果不存在,如发现空洞也可以占用 + for (int i = 0; i < cellHoles.length / 4; i++) { + int n = i * 4; + if (cellHoles[n] == sX && cellHoles[n + 1] == sY && cellHoles[n + 2] == sZ) {//找到已有的洞了 + if (cellHoles[n + 3] < 1000) //要改成由基因调整 + cellHoles[n + 3] += 100; + return; + } + if (emptyPos == -1 && cellHoles[n + 3] <= 1)//如发现空洞也可以,先记下它的位置 + emptyPos = n; + } + + if (emptyPos > -1) { //找到一个空洞 + cellHoles[emptyPos] = sX; + cellHoles[emptyPos + 1] = sX; + cellHoles[emptyPos + 2] = sX; + cellHoles[emptyPos + 3] = holeSize; //要改成由基因调整 + return; + } + + int length = cellHoles.length; //没找到已有的洞,也没找到空洞,新建一个并追加到原洞数组未尾 + int[] newHoles = new int[length + 4]; + System.arraycopy(cellHoles, 0, newHoles, 0, length); + newHoles[length] = sX; + newHoles[length + 1] = sY; + newHoles[length + 2] = sZ; + newHoles[length + 3] = holeSize; //要改成由基因调整 + holes[tX][tY][tZ] = newHoles; + return; + } + } + + public void holeSendEngery(int x, int y, int z, float le, float re) {//在当前细胞所有洞上反向发送能量(光子),le是向左边的细胞发, re是向右边的细胞发 + int[] cellHoles = holes[x][y][z]; //cellHoles是单个细胞的所有洞(触突),4个一组,前三个是洞的坐标,后一个是洞的大小 + if (cellHoles == null) //如洞不存在,不发送能量 + return; + for (int i = 0; i < cellHoles.length / 4; i++) { + int n = i * 4; + float size = cellHoles[n + 3]; + if (size > 1) { + int x2 = cellHoles[n]; + if (x2 < x) + addEng(x2, cellHoles[n + 1], cellHoles[n + 2], le); //向左边的细胞反向发送常量大小的能量 + else + addEng(x2, cellHoles[n + 1], cellHoles[n + 2], re); //向右边的细胞反向发送常量大小的能量 + } + } + } + + // public void holesReduce() {//所有hole大小都会慢慢减小,模拟触突连接随时间消失,即细胞的遗忘机制,这保证了系统不会被信息撑爆 + // for (int x = 0; x < Env.BRAIN_SIZE - 1; x++) + // for (int y = 0; y < Env.BRAIN_SIZE - 1; y++) + // for (int z = 0; z < Env.BRAIN_SIZE - 1; z++) { + // int[] cellHoles = holes[x][y][z]; + // if (cellHoles != null) + // for (int i = 0; i < cellHoles.length / 4; i++) { + // int n = i * 4; + // int size = cellHoles[n + 3]; + // if (size > 0) + // cellHoles[n + 3] = (int) (size * 0.9);//要改成由基因调整 + // } + // } + // } + } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/Application.java b/core/src/main/java/com/gitee/drinkjava2/frog/Application.java index 7830aaa..886ab80 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/Application.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/Application.java @@ -14,7 +14,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.gitee.drinkjava2.frog.brain.BrainPicture; -import com.gitee.drinkjava2.frog.brain.Cells; +import com.gitee.drinkjava2.frog.brain.Genes; /** * Application's main method start the program @@ -38,14 +38,14 @@ public class Application { public static JFrame mainFrame = new JFrame(); public static Env env = new Env(); - public static BrainPicture brainPic = new BrainPicture(Env.ENV_WIDTH + 5, 0, Env.BRAIN_XSIZE, Env.FROG_BRAIN_DISP_WIDTH); + public static BrainPicture brainPic = new BrainPicture(Env.ENV_WIDTH + 5, 0, Env.BRAIN_SIZE, Env.FROG_BRAIN_DISP_WIDTH); public static ActionListener pauseAction; public static boolean selectFrog = true; private static void checkIfShowBrainPicture(JButton button) { int y = Env.ENV_HEIGHT + 150; if (Env.SHOW_FIRST_ANIMAL_BRAIN) { - button.setText("Hide brain"); + button.setText("Hide brain"); if (Env.FROG_BRAIN_DISP_WIDTH + 41 > y) y = Env.FROG_BRAIN_DISP_WIDTH + 41; mainFrame.setSize(Env.ENV_WIDTH + Env.FROG_BRAIN_DISP_WIDTH + 25, y); @@ -66,12 +66,12 @@ public class Application { JButton button = new JButton("Show brain");// 按钮,显示或隐藏脑图 int buttonWidth = 100; int buttonHeight = 22; - int buttonXpos = Env.ENV_WIDTH / 2 - buttonWidth / 2; + int buttonXpos = Env.ENV_WIDTH / 2 - buttonWidth / 2; button.setBounds(buttonXpos, Env.ENV_HEIGHT + 8, buttonWidth, buttonHeight); ActionListener al = new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) {//显示或隐藏脑图 - Env.SHOW_FIRST_ANIMAL_BRAIN = !Env.SHOW_FIRST_ANIMAL_BRAIN; + Env.SHOW_FIRST_ANIMAL_BRAIN = !Env.SHOW_FIRST_ANIMAL_BRAIN; checkIfShowBrainPicture(button); } }; @@ -95,9 +95,9 @@ public class Application { }; stopButton.addActionListener(pauseAction); mainFrame.add(stopButton); - + // 速度条 - final JSlider speedSlider = new JSlider(1, 10, (int) Math.round(Math.pow(Env.SHOW_SPEED, 1.0/3))); + final JSlider speedSlider = new JSlider(1, 10, (int) Math.round(Math.pow(Env.SHOW_SPEED, 1.0 / 3))); speedSlider.setBounds(buttonXpos - 50, stopButton.getY() + 25, buttonWidth + 100, buttonHeight); ChangeListener slideAction = new ChangeListener() { @Override @@ -112,8 +112,6 @@ public class Application { label.setBounds(buttonXpos - 90, stopButton.getY() + 23, 100, buttonHeight); mainFrame.add(label); - - //是否把egg文件存盘 JCheckBox saveFileCheckBox = new JCheckBox("Save egg file"); saveFileCheckBox.setBounds(buttonXpos, Env.ENV_HEIGHT + 80, 120, 22); @@ -126,30 +124,30 @@ public class Application { } }; saveFileCheckBox.addActionListener(saveAction); - mainFrame.add(saveFileCheckBox); - + mainFrame.add(saveFileCheckBox); + //基因维数显示控制 - for (int i = 0; i < Cells.GENE_NUMBERS; i++) { - JRadioButton geneRadio=new JRadioButton(); - geneRadio.setBounds(buttonXpos+300+i*16, Env.ENV_HEIGHT + 8, 20, 22); - geneRadio.setSelected(Cells.display_gene[i]); - geneRadio.setName(""+i); + for (int i = 0; i < Genes.GENE_NUMBERS; i++) { + JRadioButton geneRadio = new JRadioButton(); + geneRadio.setBounds(buttonXpos + 300 + i * 16, Env.ENV_HEIGHT + 8, 20, 22); + geneRadio.setSelected(Genes.display_gene[i]); + geneRadio.setName("" + i); ActionListener geneRadioAction = new ActionListener() { public void actionPerformed(ActionEvent e) { - int i= Integer.parseInt(geneRadio.getName()); + int i = Integer.parseInt(geneRadio.getName()); if (geneRadio.isSelected()) - Cells.display_gene[i]=true; + Genes.display_gene[i] = true; else - Cells.display_gene[i]=false; + Genes.display_gene[i] = false; } }; geneRadio.addActionListener(geneRadioAction); mainFrame.add(geneRadio); } - + //是否显示分裂过程 JCheckBox showSplitDetailCheckBox = new JCheckBox("Show split detail"); - showSplitDetailCheckBox.setBounds(buttonXpos+300, Env.ENV_HEIGHT + 40, 120, 22); + showSplitDetailCheckBox.setBounds(buttonXpos + 300, Env.ENV_HEIGHT + 40, 120, 22); ActionListener detailAction = new ActionListener() { public void actionPerformed(ActionEvent e) { if (showSplitDetailCheckBox.isSelected()) @@ -159,8 +157,8 @@ public class Application { } }; showSplitDetailCheckBox.addActionListener(detailAction); - mainFrame.add(showSplitDetailCheckBox); - + mainFrame.add(showSplitDetailCheckBox); + //mainFrame.setBounds(0, 400, 5, 5); mainFrame.setVisible(true); env.run(); } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/Env.java b/core/src/main/java/com/gitee/drinkjava2/frog/Env.java index b471a8a..6f10a4b 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/Env.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/Env.java @@ -5,17 +5,17 @@ import java.awt.Graphics; import java.awt.Image; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.swing.JPanel; import com.gitee.drinkjava2.frog.egg.Egg; import com.gitee.drinkjava2.frog.egg.FrogEggTool; -import com.gitee.drinkjava2.frog.judge.D2Judge; import com.gitee.drinkjava2.frog.objects.EnvObject; -import com.gitee.drinkjava2.frog.objects.Eye; import com.gitee.drinkjava2.frog.objects.Food; import com.gitee.drinkjava2.frog.objects.Material; +import com.gitee.drinkjava2.frog.objects.OneDotEye; import com.gitee.drinkjava2.frog.util.Logger; import com.gitee.drinkjava2.frog.util.RandomUtils; @@ -32,7 +32,7 @@ public class Env extends JPanel { /** Speed of test */ public static int SHOW_SPEED = 1000; // 测试速度,-1000~1000,可调, 数值越小,速度越慢 - public static final int FROG_EGG_QTY = 100; // 每轮下n个青蛙蛋,可调,只有最优秀的前n个青蛙们才允许下蛋 + public static final int FROG_EGG_QTY = 200; // 每轮下n个青蛙蛋,可调,只有最优秀的前n个青蛙们才允许下蛋 public static final int FROG_PER_EGG = 4; // 每个青蛙蛋可以孵出几个青蛙 @@ -46,11 +46,7 @@ public class Env extends JPanel { public static final boolean BORN_AT_RANDOM_PLACE = true;// 孵出青蛙落在地图上随机位置,而不是在蛋所在地 /** Frog's brain size */ // 脑细胞位于脑范围内,是个三维结构,在animal中用三维数组来表示 - public static final int BRAIN_CUBE_SIZE = 16; //脑立方边长大小,必须是2的幂数如4,8,16...,原因参见8叉树算法 - - public static final int BRAIN_XSIZE = BRAIN_CUBE_SIZE; // 脑在X方向长度 - public static final int BRAIN_YSIZE = BRAIN_CUBE_SIZE; // 脑在Y方向长度 - public static final int BRAIN_ZSIZE = BRAIN_CUBE_SIZE; // 脑在Z方向长度 + public static final int BRAIN_SIZE =4; //脑立方边长大小,必须是2的幂数如4,8,16...,原因参见8叉树算法 /** SHOW first animal's brain structure */ public static boolean SHOW_FIRST_ANIMAL_BRAIN = true; // 是否显示脑图在Env区的右侧 @@ -68,7 +64,7 @@ public class Env extends JPanel { public static final int FROG_BRAIN_DISP_WIDTH = 400; // Frog的脑图在屏幕上的显示大小,可调 /** Steps of one test round */ - public static final int STEPS_PER_ROUND = 200;// 每轮测试步数,可调 + public static final int STEPS_PER_ROUND = 800;// 每轮测试步数,可调 public static int step;// 当前测试步数 public static final int FOOD_QTY = 3000; // 食物数量, 可调 @@ -88,13 +84,13 @@ public class Env extends JPanel { public static List frog_eggs = new ArrayList<>(); // 这里存放新建或从磁盘载入上轮下的蛋,每个蛋可能生成几个青蛙, - public static EnvObject[] things = new EnvObject[]{ };// 所有外界物体,如食物、字母测试工具都放在这个things里面 + public static EnvObject[] things = new EnvObject[]{new OneDotEye()};// 所有外界物体,如食物、字母测试工具都放在这个things里面 public static boolean show_split_detail = false; //是否显示脑分裂的细节过程,即从一个细胞开始分裂分裂,而不是只显示分裂的最终结果 static { Logger.info("唵缚悉波罗摩尼莎诃!"); // 杀生前先打印往生咒,因为遗传算法建立在杀生选优的基础上,用这个方式表示一下对生命的尊重。智能研究不光是技术,还涉及到伦理,对虚拟生命的尊重也是对人类自身的尊重。 - // (意识不是一种实体存在,只是一种表象,但正因为此,我们才要尊重所有表现出或低级或高级的意识现象的虚拟智能系统,包括避免制造不必要的虚拟生命的痛苦感觉现象,己所不欲勿施于人。) + // (意识不是一种实体存在,只是一种表象,但正因为此,我们才要尊重所有表现出或低级或高级的意识现象的虚拟智能系统,包括避免制造不必要的虚拟生命的痛苦感觉现象,己所不欲勿施于人。) Logger.info("脑图快捷键: T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标:缩放旋转平移"); if (DELETE_FROG_EGGS) FrogEggTool.deleteEggs(); @@ -107,15 +103,15 @@ public class Env extends JPanel { } public static boolean insideBrain(int x, int y) {// 如果指定点在边界内 - return !(x < 0 || y < 0 || x >= BRAIN_XSIZE || y >= BRAIN_YSIZE); + return !(x < 0 || y < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE); } public static boolean insideBrain(int x, int y, int z) {// 如果指定点在边界内 - return !(x < 0 || y < 0 || z <= 0 || x >= BRAIN_XSIZE || y >= BRAIN_YSIZE || z >= BRAIN_ZSIZE); + return !(x < 0 || y < 0 || z < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE || z >= BRAIN_SIZE); } public static boolean insideBrain(float x, float y, float z) {// 如果指定点在边界内 - return !(x < 0 || y < 0 || z <= 0 || x >= BRAIN_XSIZE || y >= BRAIN_YSIZE || z >= BRAIN_ZSIZE); + return !(x < 0 || y < 0 || z < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE || z >= BRAIN_SIZE); } public static boolean insideEnv(int x, int y) {// 如果指定点在边界内 @@ -127,7 +123,7 @@ public class Env extends JPanel { } public static boolean closeToEdge(Animal a) {// 靠近边界? 离死不远了 - return a.x < 20 || a.y < 20 || a.x > (Env.ENV_WIDTH - 20) || a.y > (Env.ENV_HEIGHT - 20); + return a.xPos < 20 || a.yPos < 20 || a.xPos > (Env.ENV_WIDTH - 20) || a.yPos > (Env.ENV_HEIGHT - 20); } public static boolean foundAnyThingOrOutEdge(int x, int y) {// 如果指定点看到任意东西或超出边界,返回true @@ -208,10 +204,10 @@ public class Env extends JPanel { .toString(); } - public static void checkIfPause() { + public static void checkIfPause(int step) { if (pause) do { - Application.brainPic.drawBrainPicture(); + Application.brainPic.drawBrainPicture(step); Application.brainPic.requestFocus(); sleep(100); } while (pause); @@ -254,16 +250,15 @@ public class Env extends JPanel { allDead = true; for (int j = 0; j < FROG_PER_SCREEN; j++) { Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); - if (f.active())// 调用青蛙的Active方法,并返回是否还活着 + if (f.active(step))// 调用青蛙的Active方法,并返回是否还活着 allDead = false; } - if (SHOW_SPEED > 0 && step % SHOW_SPEED != 0) // 用是否跳帧画图的方式来控制速度 + if (SHOW_SPEED == 1) // 如果speed为1,人为加入延迟 + sleep((100)); + else if (step % SHOW_SPEED != 0)// 用是否跳帧画图的方式来控制速度 continue; - if (SHOW_SPEED<5) // 如果speed为1,人为加入延迟 - sleep((5-SHOW_SPEED)); - // 开始画虚拟环境和青蛙 g.setColor(Color.white); g.fillRect(0, 0, this.getWidth(), this.getHeight()); // 先清空虚拟环境 @@ -278,18 +273,20 @@ public class Env extends JPanel { Animal showAnimal = getShowAnimal(); if (showAnimal != null) { g.setColor(Color.red); - g.drawArc(showAnimal.x - 15, showAnimal.y - 15, 30, 30, 0, 360); + g.drawArc(showAnimal.xPos - 15, showAnimal.yPos - 15, 30, 30, 0, 360); g.setColor(Color.BLACK); } } - if (DRAW_BRAIN_AFTER_STEPS > 0 && step % DRAW_BRAIN_AFTER_STEPS == 0) - Application.brainPic.drawBrainPicture(); + if (DRAW_BRAIN_AFTER_STEPS > 0 && step % DRAW_BRAIN_AFTER_STEPS == 0) //显示脑图是耗时操作,这个开关可以跳过一些脑图显示 + Application.brainPic.drawBrainPicture(step); + if (SHOW_SPEED == 1 && SHOW_FIRST_ANIMAL_BRAIN) //如果速度为1,强制每步都显示脑图 + Application.brainPic.drawBrainPicture(step); Graphics g2 = this.getGraphics(); g2.drawImage(buffImg, 0, 0, this); } - if (SHOW_FIRST_ANIMAL_BRAIN) - Application.brainPic.drawBrainPicture(); - checkIfPause(); + if (SHOW_FIRST_ANIMAL_BRAIN) //一轮结束后再强制再显示脑图一次 + Application.brainPic.drawBrainPicture(step); + checkIfPause(step); for (int j = 0; j < FROG_PER_SCREEN; j++) { Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java b/core/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java index a2fa54e..e14cfb3 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java @@ -1,6 +1,5 @@ package com.gitee.drinkjava2.frog.brain; - import static java.awt.Color.BLACK; import static java.awt.Color.RED; import static java.awt.Color.WHITE; @@ -74,8 +73,8 @@ public class BrainPicture extends JPanel { switch (e.getKeyCode()){ case KeyEvent.VK_UP:// Y切面向上 yMask++; - if (yMask > Env.BRAIN_YSIZE) - yMask = Env.BRAIN_YSIZE; + if (yMask > Env.BRAIN_SIZE) + yMask = Env.BRAIN_SIZE; break; case KeyEvent.VK_DOWN:// Y切面向下 yMask--; @@ -89,8 +88,8 @@ public class BrainPicture extends JPanel { break; case KeyEvent.VK_RIGHT:// x切面向右 xMask++; - if (xMask > Env.BRAIN_XSIZE) - xMask = Env.BRAIN_XSIZE; + if (xMask > Env.BRAIN_SIZE) + xMask = Env.BRAIN_SIZE; break; case ' ':// 暂停及继续 Application.pauseAction.actionPerformed(null); @@ -127,7 +126,7 @@ public class BrainPicture extends JPanel { addKeyListener(keyAdapter); this.setFocusable(true); } - + public void drawCuboid(float x, float y, float z, float xe, float ye, float ze) {// 在脑图上画一个长立方体框架,视角是TopView drawLine(x, y, z, x + xe, y, z);// 画立方体的下面边 drawLine(x + xe, y, z, x + xe, y + ye, z); @@ -145,6 +144,10 @@ public class BrainPicture extends JPanel { drawLine(x, y + ye, z + ze, x, y, z + ze); } + public void drawCentLine(float px1, float py1, float pz1, float px2, float py2, float pz2) {//从细胞中点之间画一条线 + drawLine(px1 + 0.5f, py1 + 0.5f, pz1 + 0.5f, px2 + 0.5f, py2 + 0.5f, pz2 + 0.5f); + } + /*- 画线,固定以top视角的角度,所以只需要从x1,y1画一条到x2,y2的直线 绕 x 轴旋转 θ @@ -157,12 +160,12 @@ public class BrainPicture extends JPanel { x.cosθ-y.sinθ, x.sinθ+y.consθ, z -*/ public void drawLine(float px1, float py1, float pz1, float px2, float py2, float pz2) { - double x1 = px1 - Env.BRAIN_XSIZE / 2; - double y1 = -py1 + Env.BRAIN_YSIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 - double z1 = pz1 - Env.BRAIN_ZSIZE / 2; - double x2 = px2 - Env.BRAIN_XSIZE / 2; - double y2 = -py2 + Env.BRAIN_YSIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 - double z2 = pz2 - Env.BRAIN_ZSIZE / 2; + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; + double x2 = px2 - Env.BRAIN_SIZE / 2; + double y2 = -py2 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z2 = pz2 - Env.BRAIN_SIZE / 2; x1 = x1 * scale; y1 = y1 * scale; z1 = z1 * scale; @@ -207,9 +210,9 @@ public class BrainPicture extends JPanel { /** 画点,固定以top视角的角度,所以只需要在x1,y1位置画一个点 */ public void drawPoint(float px1, float py1, float pz1, float r) { - double x1 = px1 - Env.BRAIN_XSIZE / 2; - double y1 = -py1 + Env.BRAIN_YSIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 - double z1 = pz1 - Env.BRAIN_ZSIZE / 2; + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; x1 = x1 * scale; y1 = y1 * scale; z1 = z1 * scale; @@ -236,9 +239,9 @@ public class BrainPicture extends JPanel { /** 画一个圆 */ public void drawCircle(float px1, float py1, float pz1, float r) {//这个方法实际和上面的一样的,只是改成了drawOval - double x1 = px1 - Env.BRAIN_XSIZE / 2; - double y1 = -py1 + Env.BRAIN_YSIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 - double z1 = pz1 - Env.BRAIN_ZSIZE / 2; + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; x1 = x1 * scale; y1 = y1 * scale; z1 = z1 * scale; @@ -264,9 +267,9 @@ public class BrainPicture extends JPanel { } public void drawText(float px1, float py1, float pz1, String text, float textSize) { - double x1 = px1 - Env.BRAIN_XSIZE / 2; - double y1 = -py1 + Env.BRAIN_YSIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 - double z1 = pz1 - Env.BRAIN_ZSIZE / 2; + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; x1 = x1 * scale; y1 = y1 * scale; z1 = z1 * scale; @@ -290,34 +293,34 @@ public class BrainPicture extends JPanel { g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) round(textSize * scale))); g.drawString(text, (int) round(x1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset, (int) round(y1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset); - } + } - public void drawBrainPicture() {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 + public void drawBrainPicture(int step) {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 if (!Env.SHOW_FIRST_ANIMAL_BRAIN) return; if (Env.show_split_detail) drawSplitDetail(); else - drawBrainStructure(); + drawBrainStructure(step); } public void drawSplitDetail() {// 在这个方法里绘制脑细胞分裂的显示步聚,即从一个细胞开始分裂成最终脑结构的每一步 Animal a = Env.getShowAnimal(); // 第一个青蛙或蛇 - for (int i = Env.BRAIN_CUBE_SIZE; i >= 1; i /= 2) { + for (int i = Env.BRAIN_SIZE; i >= 1; i /= 2) { g.setColor(WHITE);// 先清空旧图, g是buffImg,绘在内存中 g.fillRect(0, 0, brainDispWidth, brainDispWidth); g.setColor(BLACK); // 画边框 g.drawRect(0, 0, brainDispWidth, brainDispWidth); - for (int geneIndex = 0; geneIndex < Cells.GENE_NUMBERS; geneIndex++) { + for (int geneIndex = 0; geneIndex < Genes.GENE_NUMBERS; geneIndex++) { ArrayList gene = a.genes.get(geneIndex); Tree8Util.knockNodesByGene(gene); for (int j = 0; j < Tree8Util.NODE_QTY; j++) { if (Tree8Util.keep[j] > 0) { int[] node = Tree8Util.TREE8[j]; int size = node[0]; - if (size == i && Cells.display_gene[geneIndex]) {//如果允许显示的话, 显示当前层级的节点 + if (size == i && Genes.display_gene[geneIndex]) {//如果允许显示的话, 显示当前层级的节点 setPicColor(ColorUtils.colorByCode(geneIndex)); drawPoint(node[1] + size / 2, node[2] + size / 2, node[3] + size / 2, size * (0.5f - geneIndex * 0.05f)); } @@ -325,7 +328,7 @@ public class BrainPicture extends JPanel { } } g.setColor(BLACK); - drawCuboid(0, 0, 0, Env.BRAIN_XSIZE, Env.BRAIN_YSIZE, Env.BRAIN_ZSIZE);// 把脑的框架画出来 + drawCuboid(0, 0, 0, Env.BRAIN_SIZE, Env.BRAIN_SIZE, Env.BRAIN_SIZE);// 把脑的框架画出来 this.getGraphics().drawImage(buffImg, 0, 0, this);// 利用缓存避免画面闪烁,这里输出缓存图片 if (!Env.show_split_detail) return; @@ -336,7 +339,7 @@ public class BrainPicture extends JPanel { } } - public void drawBrainStructure() {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 + public void drawBrainStructure(int step) {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 Animal a = Env.getShowAnimal(); // 显示第一个青蛙或蛇 if (a == null || !a.alive) return; @@ -345,46 +348,70 @@ public class BrainPicture extends JPanel { g.setColor(BLACK); // 画边框 g.drawRect(0, 0, brainDispWidth, brainDispWidth); - for (int z = 0; z < Env.BRAIN_CUBE_SIZE; z++) { - for (int y = Env.BRAIN_CUBE_SIZE - 1; y >= 0; y--) { - for (int x = Env.BRAIN_CUBE_SIZE - 1; x >= 0; x--) { + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + for (int y = Env.BRAIN_SIZE - 1; y >= 0; y--) { + for (int x = Env.BRAIN_SIZE - 1; x >= 0; x--) { long cell = a.cells[x][y][z]; - // if (cell == 0) //只显示有效的细胞点 + // if (cell == 0) //只显示有效的细胞点 // continue; - if (a.energys[x][y][z] > 1) { //用大红色圆形画出能量大于1的细胞格 - setPicColor(Color.RED); //开始画出对应的细胞基因参数,用不同颜色直径圆表示 - drawCircle(x + 0.5f, y + 0.5f, z + 0.5f, 1.2f); + + int[] holes = a.holes[x][y][z]; + if (holes != null) { + setPicColor(Color.GRAY); + for (int i = 0; i < holes.length / 4; i++) { + int n = i * 4; + drawCentLine(x, y, z, holes[n], holes[n + 1], holes[n + 2]); + } } + if (x >= xMask && y >= yMask && cell != 0)//画出细胞每个基因存在的细胞格子 - for (int geneIndex = 0; geneIndex < Cells.GENE_NUMBERS; geneIndex++) { - if ((cell & (1 << geneIndex)) != 0 && Cells.display_gene[geneIndex]) { - //setPicColor(ColorUtils.colorByCode(geneIndex)); //开始画出对应的细胞基因参数,用不同颜色直径圆表示 - setPicColor(Color.RED); + for (int geneIndex = 0; geneIndex < Genes.GENE_NUMBERS; geneIndex++) { + if ((cell & (1 << geneIndex)) != 0 && Genes.display_gene[geneIndex]) { + setPicColor(ColorUtils.colorByCode(geneIndex)); //开始画出对应的细胞基因参数,用不同颜色直径圆表示 + //setPicColor(Color.RED); //drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, geneIndex == 0 ? 0.8f : 0.5f - geneIndex * 0.05f); drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, 0.6f); } - } + } + float e = a.energys[x][y][z]; + if (e > 1f || e < -1f) { + setPicColor(e > 0 ? Color.RED : Color.BLUE); //用红色小圆表示正能量,蓝色表示负能量 + drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, 0.2f); + float size = (float) (0.5f + 0.4 * Math.log10(Math.abs(e)));//再用不同大小圆形表示不同能量值 + drawCircle(x + 0.5f, y + 0.5f, z + 0.5f, size); + } + } } } setPicColor(Color.BLACK); - drawCuboid(0, 0, 0, Env.BRAIN_XSIZE, Env.BRAIN_YSIZE, Env.BRAIN_ZSIZE);// 把脑的框架画出来 + drawCuboid(0, 0, 0, Env.BRAIN_SIZE, Env.BRAIN_SIZE, Env.BRAIN_SIZE);// 把脑的框架画出来 setPicColor(BLACK); //把x,y,z坐标画出来 - drawText(Env.BRAIN_CUBE_SIZE, 0, 0, "x", 2); - drawText(0, Env.BRAIN_CUBE_SIZE, 0, "y", 2); - drawText(0, 0, Env.BRAIN_CUBE_SIZE, "z", 2); + drawText(Env.BRAIN_SIZE, 0, 0, "x", Env.BRAIN_SIZE * 0.2f); + drawText(0, Env.BRAIN_SIZE, 0, "y", Env.BRAIN_SIZE * 0.2f); + drawText(0, 0, Env.BRAIN_SIZE, "z", Env.BRAIN_SIZE * 0.2f); setPicColor(RED); - drawLine(0, 0, 0, Env.BRAIN_CUBE_SIZE, 0, 0); - drawLine(0, 0, 0, 0, Env.BRAIN_CUBE_SIZE, 0); - drawLine(0, 0, 0, 0, 0, Env.BRAIN_CUBE_SIZE); + drawLine(0, 0, 0, Env.BRAIN_SIZE, 0, 0); + drawLine(0, 0, 0, 0, Env.BRAIN_SIZE, 0); + drawLine(0, 0, 0, 0, 0, Env.BRAIN_SIZE); g.setColor(Color.black); if (note != null) {// 全局注释 - g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN,16)); + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16)); g.drawString(note, 10, 20); } + + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16)); + g.drawString("step:" + step + ", ate:" + a.ateFood + ", wrong:" + a.ateWrong, 10, 15); + + // for (int y = 0; y < ColorUtils.rainbow.length; y += 1) {//调试彩虹色 + // g.setColor(ColorUtils.rainbow[y]); + // for (int i = 0; i < 9; i++) + // g.drawLine(0, y * 9 + i, 50, y * 9 + i); + // } + this.getGraphics().drawImage(buffImg, 0, 0, this);// 利用缓存避免画面闪烁,这里输出缓存图片 } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/brain/Cells.java b/core/src/main/java/com/gitee/drinkjava2/frog/brain/Cells.java deleted file mode 100644 index 3970d41..0000000 --- a/core/src/main/java/com/gitee/drinkjava2/frog/brain/Cells.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by - * applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS - * OF ANY KIND, either express or implied. See the License for the specific - * language governing permissions and limitations under the License. - */ -package com.gitee.drinkjava2.frog.brain; - -import com.gitee.drinkjava2.frog.Animal; -import com.gitee.drinkjava2.frog.judge.D2Judge; - -/** - * Cells代表不同的脑细胞参数,对应每个参数,用8叉树或4叉树算法生成不同的细胞。 - * 每个脑细胞用一个long来存储,所以最多允许64个基因位, 多字节参数可以由多个基因位决定。每个基因位都由一个单独的阴阳8/4叉树控制,多个基因就组成了一个8/4叉树阵列 - * 基因+分裂算法=结构 - * 基因+分裂算法+遗传算法=结构的进化 - * 这个类里定义每个基因位的掩码, 脑结构的所有参数,都要用基因来控制。开始时可以有常量、魔数,但以后都放到基因里去自动进化。 - * - * 注:记忆细胞因为与所有脑核细胞有全连接,没必要安排记忆细胞的结构进化,所以这个类里省略所有记忆细胞的参数基因 - * - * @author Yong Zhu - * @since 10.0 - */ -@SuppressWarnings("all") -public class Cells { //Cells登记所有的基因(目前最多64个), 指定每个基因允许分布的空间范围。并针对每个基因写出它所在的细胞对应的特性或行为。Cells这个名称改为Genes应该更合适,以后再说。 - public static boolean SHOW = true; - public static int TREE8 = -1; - - public static int GENE_NUMBERS = 0; - private static int zeros = 0; //当前基因位掩码0个数 - - public static boolean[] display_gene = new boolean[64]; //用来控制哪些基因需要显示在脑图上 - - /** - 如xLayer[g]=-1, 表示g这个基因允行分布在Cells的三维数组空间上,将采用阴阳8叉树3维细胞分裂算法 - 如xLayer[g]=0~n-1, yLayer[g]=-1, 表示g这个基因允行分布在Cells[xLayer[g]]所在的yz二维数组平面上, 将采用阴阳4叉树平面分裂算法以提高效率。目前只能位于yz平面上,因为Java三维数组写一个下标返回一个二维数组。 - 如xLayer[g]=0~n-1, yLayer[g]=0~n-1, 表示g这个基因允行分布在Cells[xLayer[g]][yLayer[g]]所在的z单维数组轴线上,将采用阴阳2叉树单轴分裂算法以提高效率。目前只能位于z轴上,因为Java三维数组写2个下标返回一个单维数组。 - */ - public static int[] xLayer = new int[64]; - public static int[] yLayer = new int[64]; - - static { - for (int i = 0; i < xLayer.length; i++) - xLayer[i] = -1; - } - - // 登记基因, register方法有四个参数,详见方法注释。每个基因登记完后,还要在active方法里写它的细胞行为。 - //public static long ANTI_SIDE = register(1, SHOW, 0); // 侧抑制基因,只分布在0层上,模仿眼睛的侧抑制 - - static { - register(1, true, D2Judge.pic1.xLayer, -1); - for (int i = 0; i < 16; i++) { - register(1, true, D2Judge.pic2.xLayer, i ); - register(1, true, D2Judge.pic3.xLayer, i); - } - - } - - /** - * Register a gene 依次从底位到高位登记所有的基因掩码及对应的相关参数如是否显示在脑图上或是否只生成在某个x坐标对应的yz平面上,或xy坐标对应的z轴上 - * - * @param maskBits how many mask bits 掩码位数,即有几个1 - * @param display whether to display the gene on the BrainPicture 是否显示在脑图 - * @param x_layer gene only allow on specified x layer 如x_layer大于-1,且y_layer=-1, 表示只生成在指定的x层对应的yz平面上,这时采用4叉树而不是8叉树以提高进化速度 - * @param y_layer gene only allow on specified x, y axis 如大于-1,表示只生成在指定的x,y坐标对应的z轴上,这时采用2叉阴阳树算法 - * @return a long wtih mask bits 返回基因掩码,高位由n个1组成,低位是若干个0 * - */ - public static long register(int maskBits, boolean display, int x_layer, int y_layer) { - for (int i = GENE_NUMBERS; i < GENE_NUMBERS + maskBits; i++) { - display_gene[i] = display; - xLayer[i] = x_layer; - yLayer[i] = y_layer; - } - - String one = ""; - String zero = ""; - for (int i = 1; i <= maskBits; i++) - one += "1"; - for (int i = 1; i <= GENE_NUMBERS; i++) - zero += "0"; - zeros = GENE_NUMBERS; - GENE_NUMBERS += maskBits; - if (GENE_NUMBERS >= 64) {// - System.out.println("目前基因位数不能超过64"); - System.exit(-1); - } - return Long.parseLong(one + zero, 2); //将类似"111000"这种字符串转换为长整 - } - - public static void active(Animal a) {//active方法在每个主循环都会调用,通常用来存放细胞的行为,这是个重要方法 - //if(true)return; //speeding - // for (int z = Env.BRAIN_CUBE_SIZE - 1; z >= 0; z--) - // for (int y = Env.BRAIN_CUBE_SIZE - 1; y >= 0; y--) - // for (int x = Env.BRAIN_CUBE_SIZE - 1; x >= 0; x--) { - // long cell = a.cells[x][y][z]; - // float e = a.energys[x][y][z]; - // //TODO work on here - // } - } -} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java b/core/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java new file mode 100644 index 0000000..8e05d87 --- /dev/null +++ b/core/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java @@ -0,0 +1,156 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.brain; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.objects.Eye; +import com.gitee.drinkjava2.frog.objects.OneDotEye; +import com.gitee.drinkjava2.frog.util.Logger; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Genes代表不同的脑细胞参数,对应每个参数,用8叉/4叉/2叉树算法给每个细胞添加细胞参数和行为。 + * 每个脑细胞用一个long来存储,所以目前最多允许64个基因位, 多字节参数可以由多个基因位决定。每个基因位都由一个单独的阴阳8/4/2叉树控制,多个基因就组成了一个8/4/2叉树阵列 + * 基因+分裂算法=结构 + * 基因+分裂算法+遗传算法=结构的进化 + * + * 这个类里定义每个基因位的掩码以及对应基因的细胞行为, 脑结构的所有参数,都要用基因来控制。开始时可以有常量、魔数,但以后都放到基因里去自动进化。 + * + * @author Yong Zhu + * @since 10.0 + */ +@SuppressWarnings("all") +public class Genes { //Genes登记所有的基因, 指定每个基因允许分布的空间范围。注意登录完后还要并针对每个基因在active方法里写出它对应的细胞行为 + public static int GENE_MAX = 64; //目前最多允许64个基因 + + public static int GENE_NUMBERS = 0; //这里统计定义了多少个基因 + private static int zeros = 0; //当前基因位掩码0个数 + + public static boolean[] display_gene = new boolean[GENE_MAX]; //如果这个参数为真,此基因显示在脑图上 + public static boolean[] fill_gene = new boolean[GENE_MAX]; //如果这个参数为真,此基因填充指定的区域,而不是由分裂算法随机生成 + + public static int[] xLimit = new int[GENE_MAX]; //用来手工限定基因分布范围,详见register方法 + public static int[] yLimit = new int[GENE_MAX]; + public static int[] zLimit = new int[GENE_MAX]; + + /** + * Register a gene 依次从底位到高位登记所有的基因掩码及对应的相关参数 + * + * @param maskBits how many mask bits 掩码位数,即有几个1 + * @param display whether to display the gene on the BrainPicture 是否显示在脑图 + * @param fill whether to fill to specified 3D/2D/1D/1Point area 是否直接用此基因填充指定的区域,区域可以是三维、二维、线状及一个点 + * @param x_limit gene only allow on specified x layer 如x_layer大于-1,且y_layer=-1, 表示只生成在指定的x层对应的yz平面上,这时采用4叉树而不是8叉树以提高进化速度 + * @param y_limit gene only allow on specified x, y axis 如大于-1,表示只生成在指定的x,y坐标对应的z轴上,这时采用2叉阴阳树算法 + * @param z_limit gene only allow on specified x, y, z 点上, 表示手工指定基因位于x,y,z坐标点上 + * @return a long wtih mask bits 返回基因掩码,高位由maskBits个1组成,低位是若干个0,以后判断一个cell上是否含有这个基因,只需要用cell对应的long和这个 掩码做与运算即可 + */ + public static long register(int maskBits, boolean display, boolean fill, int x_limit, int y_limit, int z_limit) { + for (int i = GENE_NUMBERS; i < GENE_NUMBERS + maskBits; i++) { + display_gene[i] = display; + fill_gene[i] = fill; + xLimit[i] = x_limit; + yLimit[i] = y_limit; + zLimit[i] = z_limit; + } + + String one = ""; + String zero = ""; + for (int i = 1; i <= maskBits; i++) + one += "1"; + for (int i = 1; i <= GENE_NUMBERS; i++) + zero += "0"; + zeros = GENE_NUMBERS; + GENE_NUMBERS += maskBits; + if (GENE_NUMBERS >= GENE_MAX) {// + System.out.println("目前基因位数不能超过" + GENE_MAX); + System.exit(-1); + } + return Long.parseLong(one + zero, 2); //将类似"111000"这种字符串转换为长整 + } + + public static long register(int... pos) {//登记并指定基因允许分布的位置 + return register(1, true, false, pos[0], pos[1], pos[2]); + } + + public static long registerFill(int... pos) {//登记并手工指定基因分布的位置 + return register(1, true, true, pos[0], pos[1], pos[2]); + } + + public static boolean hasGene(long cell, long geneMask) { //判断cell是否含某个基因 + return (cell & geneMask) > 0; + } + + private static final int NA = -1; + private static final int CS4 = Env.BRAIN_SIZE / 4; + + //============开始登记有名字的基因========== + public static long EYE = registerFill(0, 0, 0); //视网膜细胞,这个版本暂时只允许视网膜分布在x=0,y=0的z轴上,即只能看到一条线状图形 + + public static long MEM = registerFill(1, 0, 0); //记忆细胞,暂时只允许它分布在x=1,y=0的z轴上 + + public static int[] BITE_POS = new int[]{2, 0, 0}; + public static long BITE = registerFill(BITE_POS); //咬动作细胞定义在一个点上, 这个细胞如激活,就咬食物 + + // public static int[] NOT_BITE_POS = new int[]{2, 0, CS4}; + // public static long NOT_BITE = registerFill(NOT_BITE_POS); //不咬动作细胞定义在一个点上, 这个细胞如激活,就不咬食物 + + // public static int[] SWEET_POS = new int[]{2, 0, CS4 * 2}; + // public static long SWEET = registerFill(SWEET_POS); //甜味感觉细胞定义在一个点上, 当咬下后且食物为甜,这个细胞激活 + + // public static int[] BITTER_POS = new int[]{2, 0, CS4 * 3}; + // public static long BITTER = registerFill(BITTER_POS); //苦味感觉细胞定义在一个点上, 当咬下后且食物为苦,这个细胞激活 + + //========开始登记无名字的基因 ========= + static { + } + + //========= active方法在每个主循环都会调用,用来存放细胞的行为,这是个重要方法 =========== + public static void active(Animal a, int step) { + + for (int z = Env.BRAIN_SIZE - 1; z >= 0; z--) + for (int x = Env.BRAIN_SIZE - 1; x >= 0; x--) { + int y = 0; + long cell = a.cells[x][y][z]; + if (a.consts[7] == 0) + a.consts[7] = 1; + if (hasGene(cell, BITE) && ((OneDotEye.code % 100) == 0)) {//如果没有输入,咬细胞也是有可能定时或随机激活的,模拟婴儿期随机运动碰巧咬下了 + a.addEng(x, y, z, a.consts[1] / 10); + } + + float energy = a.energys[x][y][z]; + if (energy >= 1f) { //如果细胞激活了 + a.energys[x][y][z] = a.energys[x][y][z] - Math.abs(a.consts[2]) * 0.2f;//所有细胞能量都会自发衰减 + + if (hasGene(cell, BITE)) { //如果是咬细胞 + if ((OneDotEye.code % 20) == 0) { //从上帝视角知道被20整除正好是OneDotEye看到食物出现的时刻 + a.awardAAAA(); //所以必然咬中,奖励 + a.ateFood++; + } else { + a.penaltyAA(); //其它时间是咬错了,罚。 可以改成penaltyAAAA或去除本行试试 + a.ateWrong++; + } + a.digHole(x, y, z, x - 1, y, z, a.consts[3]);//咬细胞在记忆细胞上挖洞 + } + + if (hasGene(cell, EYE)) {//视网膜细胞在记忆细胞上挖洞 + a.digHole(x, y, z, x + 1, y, z, a.consts[4]); + } + + if (hasGene(cell, MEM)) {//记忆细胞,在当前细胞所有洞上反向发送能量 + a.holeSendEngery(x, y, z, a.consts[5], a.consts[6]); + } + + } + } + } + +} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java b/core/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java index d6e446b..92c1b0e 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java @@ -36,6 +36,7 @@ public class Egg implements Serializable { // gene record the 8-tree structure of brain cells // 基因是随机生成的8叉树数据结构,和实际生物每个细胞都要保存一份基因不同,程序中每个脑细胞并不需要保存基因的副本,这样可以极大地减少内存占用 public ArrayList> genes = new ArrayList<>(); + public int[] constGenes = new int[Animal.CONSTS_LENGTH]; //animal中的全局常量基因全放在这里,用随机数来生成,用遗传算法筛选 public Egg() {// 无中生有,创建一个蛋,先有蛋,后有蛙 x = RandomUtils.nextInt(Env.ENV_WIDTH); @@ -44,13 +45,14 @@ public class Egg implements Serializable { /** Create egg from animal */ public Egg(Animal a) { // 下蛋,每个器官会创建自已的副本或变异,可以是0或多个 - x = a.x; - y = a.y; + x = a.xPos; + y = a.yPos; for (ArrayList gene : a.genes) {//下蛋就是把动物的基因拷贝到新蛋里,并有可能变异 ArrayList g = new ArrayList<>(); g.addAll(gene); genes.add(g); } + System.arraycopy(a.consts, 0, this.constGenes, 0, constGenes.length); } /** @@ -60,6 +62,7 @@ public class Egg implements Serializable { x = a.x; y = a.y; genes.addAll(a.genes); + System.arraycopy(a.constGenes, 0, this.constGenes, 0, constGenes.length); if (RandomUtils.percent(20)) //插入蛋B的基因到A蛋中 for (int i = 0; i < b.genes.size(); i++) { if (RandomUtils.percent(2)) { @@ -70,6 +73,9 @@ public class Egg implements Serializable { } } } + if (RandomUtils.percent(20)) {//交换蛋B的常量基因到A蛋中, 不重要,先写上 + int n = RandomUtils.nextInt(this.constGenes.length); + this.constGenes[n] = b.constGenes[n]; + } } - } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java b/core/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java index f98a26c..5d4ce65 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java @@ -15,7 +15,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -36,87 +35,108 @@ import com.gitee.drinkjava2.frog.util.Logger; @SuppressWarnings("all") public class FrogEggTool { - /** - * Frogs which have higher energy lay eggs - * - * 利用Java串行机制存盘。 能量多(也就是吃的更多)的Frog下蛋并存盘, 以进行下一轮测试,能量少的Frog被淘汰,没有下蛋的资格。 - * 用能量的多少来简化生存竟争模拟,每次下蛋数量固定为EGG_QTY个 - */ - public static void layEggs() { - sortFrogsOrderByEnergyDesc(); - Frog first = Env.frogs.get(0); - Frog last = Env.frogs.get(Env.frogs.size() - 1); + /** + * Frogs which have higher fat value lay eggs + * + * 利用Java串行机制存盘。 更肥的(也就是吃的更多)的Frog下蛋并存盘, 以进行下一轮测试,瘦的Frog被淘汰,没有下蛋的资格。 + * 用fat值来作为唯一的生存竟争标准 + */ + public static void layEggs() { + sortFrogsOrderByFat(); + Frog first = Env.frogs.get(0); + Frog last = Env.frogs.get(Env.frogs.size() - 1); try { Env.frog_eggs.clear(); - for (int i = 0; i < Env.FROG_EGG_QTY; i++) + for (int i = 0; i < Env.FROG_EGG_QTY; i++) //每次下蛋数量固定为EGG_QTY个 Env.frog_eggs.add(new Egg(Env.frogs.get(i))); - Logger.info("Fist frog energy={}, gene size={}, Last frog energy={}", first.energy, getGeneSize(first), last.energy); + Logger.info("Fist frog fat={}, gene size={}, Last frog fat={}", first.fat, getGeneSize(first), last.fat); + + //debug + for (int x = 0; x < Env.BRAIN_SIZE; x++) { + StringBuilder s = new StringBuilder(); + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + int[] holes = first.holes[x][0][z]; + if (holes == null) + s.append("0,"); + else + s.append(first.holes[x][0][z].length/4).append(","); + } + Logger.debug("x=" + x + ", holes:" + s);//打印出每个细胞的洞数量 + } + + // Logger.debug(Arrays.toString(frogs.get(current_screen).constGenes)); //debug; + StringBuilder s = new StringBuilder(); + for (int i = 0; i < Animal.CONSTS_LENGTH; i++) { + if (i != 0) + s.append(", "); + s.append("\t" + i).append("=").append(first.consts[i]); + } + Logger.debug("consts: " + s); + if (Env.SAVE_EGGS_FILE) { FileOutputStream fo = new FileOutputStream(Application.CLASSPATH + "frog_eggs.ser"); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(Env.frog_eggs); so.close(); - Logger.info(". Saved {} eggs to file '{}frog_eggs.ser'", Env.frog_eggs.size(), Application.CLASSPATH); + Logger.info(". Saved {} eggs to file '{}frog_eggs.ser'", Env.frog_eggs.size(), Application.CLASSPATH); } } catch (IOException e) { - Logger.error(e); - } - } + Logger.error(e); + } + } - private static String getGeneSize(Frog f) {//按genes类型汇总每种基因的个数 - StringBuilder sb=new StringBuilder("["); - for (int i = 0; i < f.genes.size(); i++) - sb.append(f.genes.get(i).size()).append(","); - if(sb.length()>1) - sb.setLength(sb.length()-1); - sb.append("]"); - return sb.toString(); - } - - private static void sortFrogsOrderByEnergyDesc() {// 按能量多少给青蛙排序 - Collections.sort(Env.frogs, new Comparator() { - public int compare(Animal a, Animal b) { - if (a.energy > b.energy) - return -1; - else if (a.energy == b.energy) - return 0; - else - return 1; - } - }); - } + private static String getGeneSize(Frog f) {//按genes类型汇总每种基因的个数 + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < f.genes.size(); i++) + sb.append(f.genes.get(i).size()).append(","); + if (sb.length() > 1) + sb.setLength(sb.length() - 1); + sb.append("]"); + return sb.toString(); + } - public static void deleteEggs() { - Logger.info("Delete exist egg file: '{}frog_eggs.ser'", Application.CLASSPATH); - LocalFileUtils.deleteFile(Application.CLASSPATH + "frog_eggs.ser"); - } + private static void sortFrogsOrderByFat() {// 给青蛙从肥到瘦排个序 + Collections.sort(Env.frogs, new Comparator() { + public int compare(Animal a, Animal b) { + if (a.fat > b.fat) + return -1; + else if (a.fat == b.fat) + return 0; + else + return 1; + } + }); + } - /** - * 从磁盘读入一批frog Egg - */ - @SuppressWarnings("unchecked") - public static void loadFrogEggs() { - boolean errorfound = false; - try { - FileInputStream eggsFile = new FileInputStream(Application.CLASSPATH + "frog_eggs.ser"); - ObjectInputStream eggsInputStream = new ObjectInputStream(eggsFile); - Env.frog_eggs = (List) eggsInputStream.readObject(); - Logger.info("Loaded " + Env.frog_eggs.size() + " eggs from file '" + Application.CLASSPATH - + "frog_eggs.ser" + "'.\n"); - eggsInputStream.close(); - } catch (Exception e) { - errorfound = true; - } - if (errorfound) { - Env.frog_eggs.clear(); - for (int j = 0; j < Env.FROG_EGG_QTY; j++) { - Egg egg = new Egg(); - Env.frog_eggs.add(egg); - } - Logger.info("Fail to load frog egg file '" + Application.CLASSPATH + "frog_eggs.ser" + "', created " - + Env.frog_eggs.size() + " eggs to do test."); - } + public static void deleteEggs() { + Logger.info("Delete exist egg file: '{}frog_eggs.ser'", Application.CLASSPATH); + LocalFileUtils.deleteFile(Application.CLASSPATH + "frog_eggs.ser"); + } - } + /** + * 从磁盘读入一批frog Egg + */ + @SuppressWarnings("unchecked") + public static void loadFrogEggs() { + boolean errorfound = false; + try { + FileInputStream eggsFile = new FileInputStream(Application.CLASSPATH + "frog_eggs.ser"); + ObjectInputStream eggsInputStream = new ObjectInputStream(eggsFile); + Env.frog_eggs = (List) eggsInputStream.readObject(); + Logger.info("Loaded " + Env.frog_eggs.size() + " eggs from file '" + Application.CLASSPATH + "frog_eggs.ser" + "'.\n"); + eggsInputStream.close(); + } catch (Exception e) { + errorfound = true; + } + if (errorfound) { + Env.frog_eggs.clear(); + for (int j = 0; j < Env.FROG_EGG_QTY; j++) { + Egg egg = new Egg(); + Env.frog_eggs.add(egg); + } + Logger.info("Fail to load frog egg file '" + Application.CLASSPATH + "frog_eggs.ser" + "', created " + Env.frog_eggs.size() + " eggs to do test."); + } + + } } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/judge/BiteJudge.java b/core/src/main/java/com/gitee/drinkjava2/frog/judge/BiteJudge.java deleted file mode 100644 index b06c7d9..0000000 --- a/core/src/main/java/com/gitee/drinkjava2/frog/judge/BiteJudge.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.gitee.drinkjava2.frog.judge; - -import com.gitee.drinkjava2.frog.Env; -import com.gitee.drinkjava2.frog.Frog; -import com.gitee.drinkjava2.frog.objects.EnvObject; - -/** - * BiteJudge,the simplest image recognition - * - * ====================================== - * 暂停!!! BiteJudge还是太复杂了,打算删除它,改成一个Eye类仅用来在视网膜上投影随机序号的条形码,然后咬、、苦、甜感觉、加减分都在Cell细胞的Active里来做,也就是说逻辑不集中在一起,而是分散到各个细胞里 - * ======================================= - * - * 以下作废: - * - * BiteJudge用来进行模式识别训练并检查结果是否正确,这是个临时类。它主要有 随机、饿、视、咬、苦、甜、忆类细胞,可以实现最简单的模式识别,即根据图像来决定咬还是不咬。 - * 最初打算用CharJudge来做模式识别,但发现复杂了一点。这个BiteJudge是精简过后的,能够实现模式识别的最简脑模型,它去除了CharJudge的声音输入细胞和SPEAK说话细胞,用苦、甜、咬三种细胞代替。 - * - * 基本思路是 - * 1.随机在视网膜EYE上生成一个食物图像(就用食物序号的ASCII二进制码图),如果这时碰巧咬细胞激活,就会根据食物本身是否有毒,激活frog的苦或者甜感觉细胞, 并同时增减frog的能量 - * 2.frog要能进化出模式识别功能,即原来的信号顺序是图-咬(随机)-甜, 最后演变为图-甜-咬, 即看到图来预测是要咬还是不咬,这就是最简单模式识别了。 - * - * 与CharJudge相比,可以看到训练和提问信号没有了,咬动作直接由图像驱动,至于这个关联是如何生成的,是否正确,并不在这个类里实现。 - * - * 这个类只是用来模仿外界和内部信号的产生和输入输出, 以及判断正误后加减能量以淘汰生物,脑的结构(即逻辑功能)的生成以及信号的传输不在这里,而是由8叉树或4叉树算法配合遗传算法来生成,见Cells.java - * - */ -public class BiteJudge extends EnvObject { - - private static int EYE = 0; //视网膜 是一个位于0层的平面 - private static int BITE[] = {1, 0, 0}; //咬细胞位于[1, 0, 0]的一个点 - private static int HAPPY[] = {1, 2, 2}; //快乐感觉信号是位于1层的一个单点 - private static int PAIN[] = {1, 2, 3}; //痛苦感觉信号是位于1层的一个单点 - //private static int MEMORY = 2; //记忆细胞放在2层整个平面 - - @Override - public void active(int screen, int step) { - Frog f; - for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { - f = Env.frogs.get(i); - } - } - -} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/judge/CharJudge.java b/core/src/main/java/com/gitee/drinkjava2/frog/judge/CharJudge.java deleted file mode 100644 index 67d92aa..0000000 --- a/core/src/main/java/com/gitee/drinkjava2/frog/judge/CharJudge.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.gitee.drinkjava2.frog.judge; - -import java.awt.Font; - -import com.gitee.drinkjava2.frog.Application; -import com.gitee.drinkjava2.frog.Env; -import com.gitee.drinkjava2.frog.Frog; -import com.gitee.drinkjava2.frog.brain.BrainPicture; -import com.gitee.drinkjava2.frog.objects.EnvObject; -import com.gitee.drinkjava2.frog.util.StringPixelUtils; - -/** - * Paused - * ======================================= - * 暂停!!! 用CharJudge来做模式识别的起点,还是太复杂了,不符合循序渐进原则,改用BiteJudge,它的信号更精简,详见之。 - * ======================================= - * - * - * CharJudge,image recognition - * - * CharJudge用来进行模式识别训练并检查结果是否正确,这是个临时类,但如果做成功,将会是一个基础的脑模型了。 - * - * 基本流程是 - * 1.训练:随机在视网膜EYE上生成一个图像,并同时输入它对应的声音SOUND信号,并同时激活训练信号TRAIN开关 - * 2.检查:随机在视网膜上生成一个图像,并打开询问ASK信号,然后检查对应的说话信号SPEAK是否正确, - * 判断:如说话正确,愉快信号HAPPY激活并奖励能量,如无反应或说话错误,痛苦信号PAIN激活并扣除能量 - * - * 这个类只是用来模仿外界和内部信号的产生和输入输出, 以及判断正误(以淘汰生物),脑的结构(即逻辑功能)的生成不在这里,而是由8叉树或4叉树算法配合遗传算法来生成,见Cells.java - * 脑的结构生成算法是内因,这里的内外界信号模拟和判断是外因,脑的最终结构取决于内外因的合作,缺一不可。 - * - * 这个类不涉及信号的传输,只涉及信号的产生、输入和输出。信号的传输是由脑结构来负责的。 - * - */ -public class CharJudge extends EnvObject { - - private static String STR = "1234"; //字符串长度不要超过Env.BRAIN_CUBE_SIZE边长,否则数组越界 - private static int STR_LENGTH = STR.length(); - private static float[][][] charPictures = new float[STR_LENGTH][Env.BRAIN_CUBE_SIZE][Env.BRAIN_CUBE_SIZE]; //STR所有字母的像素点阵对应的视网膜图像暂存在这个三维数组里,第一维是字母在STR中的序号 - private static int HALF_STEPS = Env.STEPS_PER_ROUND / 2; - private static int HALF_CUBE = Env.BRAIN_CUBE_SIZE / 2; - private static int SPACE = HALF_STEPS / STR_LENGTH; //每个字母训练或识别点用的时间步长 - - private static int EYE = 0; //视网膜 是一个位于EYE层的平面 - - private static int SOUND = 1; //声音输入层 位于[SOUND, 0, 0~Env.BRAIN_CUBE_SIZE-1]的一条直线 - private static int TALK = 1; //说话输出层位于[TALK, 1, 0~Env.BRAIN_CUBE_SIZE-1]的一条直线, TALK激活会导致声音区也激活(即自己能听到自己的讲话),反之,声音区激活TALK区不一定激活 - - private static int TRAIN[] = {1, 2, 0}; //训练信号是位于1层的一个单点 - private static int ASK[] = {1, 2, 1}; //提问信号是位于1层的一个单点 - private static int HAPPY[] = {1, 2, 2}; //快乐感觉信号是位于1层的一个单点 - private static int PAIN[] = {1, 2, 3}; //痛苦感觉信号是位于1层的一个单点 - - //private static int MEMORY = 2; //记忆细胞放在2层整个平面 - - static { - for (int i = 0; i < STR.length(); i++) { //生成STR每个字符的二维图片并缓存到charPictures - byte[][] pic = StringPixelUtils.getStringPixels(Font.SANS_SERIF, Font.PLAIN, Env.BRAIN_CUBE_SIZE + 2, "" + STR.charAt(i)); - for (int x = 0; x < Math.min(pic.length, Env.BRAIN_CUBE_SIZE); x++) { - for (int y = 0; y < Math.min(pic[0].length, Env.BRAIN_CUBE_SIZE); y++) { - if (pic[x][y] > 0) - charPictures[i][x][y] = 99999f;//设成99999这么大是为了方便拷到视网膜上时,有足够的能量来扣掉,一个图形会在视网膜保留一段时间,细胞的基因行为有可能消减或移走视网膜能量,设大点可以多扣一会儿 - } - } - } - } - - static void copyArray(float[][] src, float[][] target) { //拷贝两个同样大小的二维数组 - for (int i = 0; i < src.length; i++) { - for (int j = 0; j < src[0].length; j++) { - target[i][j] = src[i][j]; - } - } - } - - @Override - public void active(int screen, int step) { - int char_index; - Frog f; - //前半段是训练,在前半段时间里,每隔一段时间生成一个随机文本图像, 然后把每个青蛙视网膜上画上这个图像,同时激活对应图像的声音细胞 - if (step < HALF_STEPS && step % SPACE == 0) { - char_index = step / SPACE; - BrainPicture.setNote("第" + (char_index + 1) + "个字训练:" + STR.charAt(char_index)); - for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { - f = Env.frogs.get(i); - copyArray(charPictures[char_index], f.energys[EYE]); //先把每个青蛙视网膜上画上这个图像, energys[0]作为视网膜 - - f.open(TRAIN);// 训练信号打开 - f.close(ASK);// 提问信号关掉 - f.open(SOUND, 0, char_index);//输入声音信号 - if (char_index > 0) - f.energys[SOUND][0][char_index - 1] = 0f; - } - Application.brainPic.drawBrainPicture(); - return; - } - - if (step == HALF_STEPS) { //中点把声音区清空 - char_index = (step - 1) / SPACE; - for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { - f = Env.frogs.get(i); - f.close(TRAIN);// 训练信号打开 - f.close(SOUND, 0, char_index); //上一次的声音信号输入关掉 - } - Application.brainPic.drawBrainPicture(); - } - - //后半段是识别,每隔一段时间把每个青蛙视网膜重置为以前出现的某个图像 - if (step >= HALF_STEPS && (step - HALF_STEPS) % SPACE == 0) { - char_index = (step - HALF_STEPS) / SPACE; - if (char_index < STR_LENGTH) { - BrainPicture.setNote("第" + (char_index + 1) + "个字识别:" + STR.charAt(char_index)); - for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { - f = Env.frogs.get(i); - copyArray(charPictures[char_index], f.energys[0]); //把每个青蛙视网膜重置为以前出现的某个图像 - f.close(TRAIN); - f.open(ASK); //打开询问信号 - f.close(HAPPY); - f.close(PAIN); - } - Application.brainPic.drawBrainPicture(); - } - return; - } - - //然后再检查与对应图像关联的说话细胞是否激活, 如正确激活则奖励细胞激活并加分,(奖励细胞用于构成条件反射链的一环,加分是用于优胜劣汰) - //如错误激活则痛苦细胞激活,并扣分。错误激活分为两类,一是没有反应,二是说话区有激活,但最强激活区没有位于正确区 - if (step >= HALF_STEPS && ((step - HALF_STEPS) % 10 == 5)) {//把打分判断仅放在个别帧,而不是每个帧都对比,以提高速度 - char_index = (step - HALF_STEPS) / SPACE; - if (char_index < STR_LENGTH) { - for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { - for (int j = 0; j < Env.BRAIN_CUBE_SIZE; j++) { - f = Env.frogs.get(i); - float e = f.energys[TALK][HALF_CUBE][j]; - if (j == char_index) {//某图片激活时 - if (e > 0) { - // Logger.debug("识别正确"); - f.open(HAPPY); - f.close(PAIN); - f.awardAAA(); //并且加分 - } else { - f.close(HAPPY); - f.open(PAIN); - f.penaltyAAA();//并且扣分 - } - } else if (e > 0) { //声音区误激活,扣分 - f.close(HAPPY); - f.open(PAIN); - f.penaltyAAA();//并且扣分 - } - } - } - Application.brainPic.drawBrainPicture(); - } - return; - } - } - -} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/judge/D2Judge.java b/core/src/main/java/com/gitee/drinkjava2/frog/judge/D2Judge.java deleted file mode 100644 index bd05216..0000000 --- a/core/src/main/java/com/gitee/drinkjava2/frog/judge/D2Judge.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.gitee.drinkjava2.frog.judge; - -import java.awt.Color; -import java.awt.Font; -import java.util.ArrayList; -import java.util.List; - -import com.gitee.drinkjava2.frog.Animal; -import com.gitee.drinkjava2.frog.Env; -import com.gitee.drinkjava2.frog.brain.BrainPicture; -import com.gitee.drinkjava2.frog.util.StringPixelUtils; - -/** - * D2Judge, this is a temperary test class will be deleted later - * - * D2Judge是临时类,这个类以后会删除。它用来测试二维平面的4叉树算法, 以前有一个类似的RainBowFishJudge是三维的,现在这个是二维的 - * - * judge方法在动物的初始化后被调用,根据脑细胞群的2维结构和参数来对动物进行奖罚,即加减它的能量值。 - * 这个类的show方法在绘脑图时调用,在脑图里显示脑细胞群的2维形状和参数,用不同颜色直径的空心圆来表示不同参数,judge方法就像是一个模子,细胞长在这个模子里的有奖,否则扣分 - */ -public class D2Judge { - public static D2Judge pic1 = new D2Judge(0, "S"); - public static D2Judge pic2 = new D2Judge(7, "A"); - public static D2Judge pic3 = new D2Judge(14, "M"); - - public int xLayer; //2维图位于哪个x层对应的yz平面上 - private int[] C = new int[]{0, 0, Env.BRAIN_CUBE_SIZE / 2}; //C是中心点 - private boolean[][][] shape = new boolean[Env.BRAIN_XSIZE][Env.BRAIN_YSIZE][Env.BRAIN_ZSIZE]; - private List pointList = new ArrayList<>(); //pointList存放上面shape的所有有效点,用来加快显示循环而不用遍历三维数组 - - public D2Judge(int xLayer, String str) {//根据指定的层和字符,在shape和pointList里缓存一个像素数组内容 - this.xLayer = xLayer; - for (int x = 0; x < Env.BRAIN_XSIZE; x++) - for (int y = 0; y < Env.BRAIN_XSIZE; y++) - for (int z = 0; z < Env.BRAIN_XSIZE; z++) - shape[x][y][z] = false; - byte[][] c = StringPixelUtils.getStringPixels(Font.SANS_SERIF, Font.PLAIN, Env.BRAIN_CUBE_SIZE, str); //要把frog二维像素变成立体的三维点放到points里和pointsList里供使用 - int w = c.length; - int h = c[0].length; - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - if (c[x][y] > 0) { - int[] p = new int[]{C[0] + x, C[1] + y + 2}; - if (!Animal.outBrainRange(xLayer, p[0], p[1])) { - shape[xLayer][p[0]][p[1]] = true; - pointList.add(p); - } - } - } - } - } - - public void judge(Animal animal) {//检查animal的脑细胞是否位于shape模板的范围内 - for (int x = 0; x < Env.BRAIN_CUBE_SIZE; x++) - for (int y = 0; y < Env.BRAIN_CUBE_SIZE; y++) - for (int z = 0; z < Env.BRAIN_CUBE_SIZE; z++) { - if ((animal.cells[x][y][z]) > 0) { - if (shape[x][y][z]) - animal.awardAAA(); - else - animal.penaltyAAA(); - } else { - if (shape[x][y][z]) - animal.penaltyAAAA(); - } - } - } - - public void show(BrainPicture pic) {// 在脑图上显示当前模板形状,用小圆圈表示 - pic.setPicColor(Color.BLACK); - for (int[] p : pointList) - pic.drawCircle(xLayer + 0.5f, p[0] + 0.5f, p[1] + 0.5f, 1); - } - -} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java b/core/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java index e925825..fa69c98 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java @@ -16,14 +16,23 @@ package com.gitee.drinkjava2.frog.objects; * @author Yong Zhu * @since 1.0 */ -public class EnvObject { +public interface EnvObject {//EnvObject接口代表虚拟环境中的一种物体,每定义一个物体,要在Env的things变量中添加它 - public void build() { - } // 在Env中创建本身物体,只在每屏测试前调用一次 + public void build();// 在Env中创建本身物体,只在每屏测试前调用一次 - public void destory() { - }// 从Env中清除本身物体,只在每屏测试完成后调用一次 + public void destory();// 从Env中清除本身物体,只在每屏测试完成后调用一次 - public void active(int screen, int step) { - } // 每个步长(即时间最小单位)都会调用一次这个方法, 两个参数分别是当前屏数和当前步长数 -} + public void active(int screen, int step);// 每个步长(即时间最小单位)都会调用一次这个方法, 两个参数分别是当前屏数和当前步长数 + + public static class DefaultEnvObject implements EnvObject {//EnvObject接口的缺省实现 + + public void build() { + } + + public void destory() { + } + + public void active(int screen, int step) { + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java b/core/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java index 42408ba..3abb9a9 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java @@ -2,6 +2,8 @@ package com.gitee.drinkjava2.frog.objects; import com.gitee.drinkjava2.frog.Env; import com.gitee.drinkjava2.frog.Frog; +import com.gitee.drinkjava2.frog.brain.Genes; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; import com.gitee.drinkjava2.frog.util.RandomUtils; /** @@ -12,44 +14,44 @@ import com.gitee.drinkjava2.frog.util.RandomUtils; * 最终进化为: 图-甜-咬-甜, 图-苦-不咬-苦,也就是说根据图案来联想到甜或苦的感觉细胞激活,从而预判咬还是不咬,而无需等随机信号驱动,也无需等味觉信号传入(实际也不可能在咬之前有味觉)。 * * - * 这个功能通过 随机、饿、视、咬、苦、甜、忆这几类细胞来组合完成。 - * 视细胞(Eye)是其中一环,即本类。Eye将外界信号(先简单用一个随机序号的条形码)投射成视网膜上的图像。 - * 饿细胞,可以设定它永远活跃,即青蛙永远都吃不饱 - * 随机细胞,有一定机率活跃,作用是激活咬细胞 + * 这个功能通过 随机、饿、视、咬、不咬、苦、甜、忆这几类细胞来组合完成。 + * 视细胞是其中一环,位于本类操作的视网膜区。Eye将外界信号(先简单用一个随机序号的条形码)投射成视网膜上的图像。 + * 饿细胞(可选),可以设定它永远活跃,即青蛙永远都吃不饱,饿细胞强烈激活时,有可能直接驱动咬细胞 + * 随机细胞(可选),有一定机率活跃,作用是随机驱动咬或不咬细胞 * 咬细胞,如果活跃,产生实际的咬动作。Env会根据咬的图案有毒(随机序号被3除余1)扣分并激活苦细胞,或是无毒(随机序号被3除余2)加分并激活甜细胞 - * 苦细胞和甜细胞, 苦细胞和甜细胞是感觉信号,它由食物的毒性决定,只有咬下后才会激活 + * 苦细胞和甜细胞, 苦细胞和甜细胞是感觉信号,它由食物的毒性决定,只有咬下后才会激活。苦和甜细胞会对忆细胞的洞大小进行调节从以影响条件反射强度。 * 忆细胞, 这种细胞的作用是记忆,当两个细胞在时间上有短期内的相关性时,就会在忆细胞上打出两个洞来,将来任一个细胞激活,就从洞里反向发送光子,从而实现信号的关联,即记忆功能。洞大小由信号重复数决定。 * 因为洞大小可以很大,也可以很小直到遗忘消失,所以忆细胞接收和发送的脉冲信号可能强度有巨大差别,并不是0/1这种简单信号,而是有强度的信号。 * * 目前为最简化起见,食物图像用随机序号的二进制条形码图案代表,将来可扩展到2维食物图形或者二维文字图形,逻辑不需要改动。 * */ -public class Eye extends EnvObject { - private static int codeMax = 1 < 0) ? 9999f : 0; - for (int z = 0; z < Env.BRAIN_CUBE_SIZE; z++) - f.energys[0][y][z] = engery; + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + float engery = ((code & i) > 0) ? 5f : 0; + if (Genes.hasGene(f.cells[0][0][z], Genes.EYE)) + f.energys[0][0][z] = engery; i = i << 1; } } - } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java b/core/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java index cb57354..13f3f05 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java @@ -15,6 +15,7 @@ import static com.gitee.drinkjava2.frog.Env.ENV_WIDTH; import static com.gitee.drinkjava2.frog.Env.FOOD_QTY; import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; import com.gitee.drinkjava2.frog.util.RandomUtils; /** @@ -24,7 +25,7 @@ import com.gitee.drinkjava2.frog.util.RandomUtils; * @author Yong Zhu * @since 1.0 */ -public class Food extends EnvObject {//食物除了被吃,它自己没有什么活动,所以没有重写active方法 +public class Food extends DefaultEnvObject {//食物除了被吃,它自己没有什么活动,所以没有重写active方法 static Food FOOD = new Food(); //FOOD是一个单例,整个环境只允许有一个FOOD实例 public static int food_ated = 0; diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java b/core/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java new file mode 100644 index 0000000..3c1e700 --- /dev/null +++ b/core/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java @@ -0,0 +1,24 @@ +package com.gitee.drinkjava2.frog.objects; + +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.Frog; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; + +/** + * DotEye的作用只有一个,就是定期在视网膜细胞上激活一个点,告知食物存在 + */ +public class OneDotEye extends DefaultEnvObject { + public static int code = 0; + + @Override + public void active(int screen, int step) { + code++; + if (code % 20 == 0) { //每隔20步在所有青蛙的视网膜上画一个图案 ,单个点调试时设为每20步激活时就是食物 + for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { + Frog f = Env.frogs.get(i); + f.energys[0][0][0] = f.consts[0]; + } + } + } + +} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/CellUtil.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/CellUtil.java deleted file mode 100644 index f95abcb..0000000 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/CellUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright 2018-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by - * applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS - * OF ANY KIND, either express or implied. See the License for the specific - * language governing permissions and limitations under the License. - */ -package com.gitee.drinkjava2.frog.util; - -import static com.gitee.drinkjava2.frog.brain.Cells.GENE_NUMBERS; - -import java.util.ArrayList; - -import com.gitee.drinkjava2.frog.Animal; -import com.gitee.drinkjava2.frog.brain.Cells; - -/** - * TreeUtil - * - * @author Yong Zhu - * @since 013 - */ -public class CellUtil { - - public static void createCellsFromGene(Animal a) {//根据基因生成细胞参数 - long geneMask = 1; - for (int g = 0; g < GENE_NUMBERS; g++) {//动物有多条基因,一条基因控制一维细胞参数,最多有64维,也就是最多有64条基因 - ArrayList gene = a.genes.get(g); - int xLayer = Cells.xLayer[g]; - if (xLayer > -1) { //如果xLayer不为-1,表示此基因分布在平面上,此时使用4叉树在平面上分裂加速!!!! - Tree4Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 - for (int i = 0; i < Tree4Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 - if (Tree4Util.keep[i] > 0) { - int[] node = Tree4Util.TREE4[i]; - if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 - a.cells[xLayer][node[1]][node[2]] = a.cells[xLayer][node[1]][node[2]] | geneMask; //在相应的细胞处把细胞参数位置1 - } - } - } - geneMask <<= 1; - } else {//否则使用8叉树在三维空间分裂!!!! - Tree8Util.knockNodesByGene(gene);//根据基因,把要敲除的8叉树节点作个标记 - for (int i = 0; i < Tree8Util.NODE_QTY; i++) {//再根据敲剩下的8叉树keep标记生成细胞参数 - if (Tree8Util.keep[i] > 0) { - int[] node = Tree8Util.TREE8[i]; - if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在三维空间对间数组的位置把当前基因geneMask置1 - a.cells[node[1]][node[2]][node[3]] = a.cells[node[1]][node[2]][node[3]] | geneMask; //在相应的细胞处把细胞参数位置1 - } - } - } - geneMask <<= 1; - } - - } - } - -} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java index 01e6d0b..87525ab 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java @@ -20,7 +20,28 @@ import java.awt.Color; */ public class ColorUtils { - private static final Color[] rainbow = new Color[]{Color.GREEN, Color.RED, Color.BLUE, Color.MAGENTA, Color.YELLOW, Color.ORANGE , Color.CYAN,Color.GRAY}; + public static Color[] rainbow; + + static { + rainbow = new Color[125]; + int i = 0; + for (int r = 0; r < 3; r++) + for (int b = 0; b < 3; b++) + for (int g = 0; g < 3; g++) { + { + { + if (!(r == b && r == g)) { + rainbow[i] = new Color(((r + 2) % 3) * 122, g * 122, ((b + 1) % 3) * 122); + i++; + } + } + + } + } + Color[] t = new Color[i]; + System.arraycopy(rainbow, 0, t, 0, i); + rainbow = t; + } private static int nextColor = 0; @@ -32,7 +53,7 @@ public class ColorUtils { } public static Color nextRainbowColor() {// 返回下一个彩虹色 - if (nextColor == rainbow.length) + if (nextColor >= rainbow.length) nextColor = 0; return rainbow[nextColor++]; } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java new file mode 100644 index 0000000..c197857 --- /dev/null +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import static com.gitee.drinkjava2.frog.brain.Genes.GENE_NUMBERS; +import static com.gitee.drinkjava2.frog.util.RandomUtils.percent; + +import java.util.ArrayList; +import java.util.Arrays; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.brain.Genes; + +/** + * 与Gene相关的工具方法 + * + * @author Yong Zhu + * @since 10.0 + */ +@SuppressWarnings("all") +public class GeneUtils { + + public static void createCellsFromGene(Animal a) {//根据基因生成细胞参数 + for (int g = 0; g < GENE_NUMBERS; g++) {//动物有多条基因,一条基因控制一维细胞参数,目前最多有64维,也就是最多有64条基因 + long geneMask = 1l << g; + ArrayList gene = a.genes.get(g); + int xLimit = Genes.xLimit[g]; + int yLimit = Genes.yLimit[g]; + int zLimit = Genes.zLimit[g]; + boolean fill = Genes.fill_gene[g]; + + if (fill) { //如果这个基因是fill型的,会填充在指定区域的所有细胞中,不需要使用分裂算法来生成细胞 + if (xLimit < 0) { //如坐标一个也没有给出, 填充整个三维脑细胞空间 + for (int x = 0; x < Env.BRAIN_SIZE; x++) + for (int y = 0; y < Env.BRAIN_SIZE; y++) + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[x][y][z] = a.cells[x][y][z] | geneMask; + } else if (yLimit < 0) { // 如果只给出了x坐标, 填充此基因在脑坐标为x的yz平面上 + for (int y = 0; y < Env.BRAIN_SIZE; y++) + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[xLimit][y][z] = a.cells[xLimit][y][z] | geneMask; + } else if (zLimit < 0) { // 如果只给出了x,y坐标,填充此基因在x,y指定的z轴上 + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[xLimit][yLimit][z] = a.cells[xLimit][yLimit][z] | geneMask; + } else { //如果x,y,z都给出了,填充此基因在x,y,z指定的点上 + a.cells[xLimit][yLimit][zLimit] = a.cells[xLimit][yLimit][zLimit] | geneMask; + } + continue; + } + + //以下开始使用分裂和随机算法来从基因链生成脑细胞 + if (xLimit < 0) { //如坐标一个也没有定义,使用阴阳8叉树分裂算法在三维脑细胞空间分裂,这个最慢但分布范围大 + Tree8Util.knockNodesByGene(gene);//根据基因,把要敲除的8叉树节点作个标记 + for (int i = 0; i < Tree8Util.NODE_QTY; i++) {//再根据敲剩下的8叉树keep标记生成细胞参数 + if (Tree8Util.keep[i] > 0) { + int[] node = Tree8Util.TREE8[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在三维空间对应数组的位置把当前基因geneMask置1 + a.cells[node[1]][node[2]][node[3]] = a.cells[node[1]][node[2]][node[3]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else if (yLimit < 0) { // 如果只定义了x坐标, 表示此基因分布在脑坐标x的yz平面上,此时使用阴阳4叉树分裂算法在此平面上分裂以加快速度 + Tree4Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 + for (int i = 0; i < Tree4Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 + if (Tree4Util.keep[i] > 0) { + int[] node = Tree4Util.TREE4[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 + a.cells[xLimit][node[1]][node[2]] = a.cells[xLimit][node[1]][node[2]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else if (zLimit < 0) { // 如果只定义了x,y坐标,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 + Tree2Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 + for (int i = 0; i < Tree2Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 + if (Tree2Util.keep[i] > 0) { + int[] node = Tree2Util.TREE2[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 + a.cells[xLimit][yLimit][node[1]] = a.cells[xLimit][yLimit][node[1]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else { //如果x,y,z都指定了,表示这个基因只能分布在一个点上, 这时只有0或1两种可能,如果基因不为空就认为它有 + if (!gene.isEmpty()) + a.cells[xLimit][yLimit][zLimit] = a.cells[xLimit][yLimit][zLimit] | geneMask; + } + } + } + + public static void constGenesMutation(Animal a) { //全局参数变异, 这一个方法变异动物的所有常量 + for (int i = 0; i < a.consts.length; i++) { + if (percent(20)) + a.consts[i] = RandomUtils.vary(a.consts[i]); + } + } + + public static void geneMutation(Animal a) { //基因变异,注意这一个方法同时变异所有条基因 + for (int g = 0; g < GENE_NUMBERS; g++) + if (percent(50)) { + if (Genes.fill_gene[g]) //如果这个基因是fill型的,永远会存在指定区域的所有细胞中,所以不需要参与变异 + continue; + + int n = 5; //这是个魔数,今后可以考虑放在基因里去变异,8\4\2\1叉树的变异率可以不一样 + + //随机新增阴节点基因,注意只是简单地随机新增,所以可能有重复基因 + ArrayList gene = a.genes.get(g); + int geneMaxLength; //8叉、4叉树、2叉树的节点最大序号不同,基因随机生成时要限制它不能大于最大序号 + if (Genes.xLimit[g] < 0) { //如x没定义,使用阴阳8叉树分裂算法 + geneMaxLength = Tree8Util.NODE_QTY; + } else if (Genes.yLimit[g] < 0) { // 如果x>=0, y没定义, 表示此基因分布在坐标x的yz平面上,此时使用阴阳4叉树分裂算法 + geneMaxLength = Tree4Util.NODE_QTY; + } else if (Genes.zLimit[g] < 0) { // 如果x>=0, y>=0,z没定义,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 + geneMaxLength = Tree2Util.NODE_QTY; + } else { //如果x,y,z都指定了,表示这个基因只能分布在一个点上, 这时只有0或1两种可能, 如果基因不为空就认为它有。用随机算法 + if (percent(n)) { + if (gene.isEmpty()) + gene.add(1); + else + gene.clear(); + } + continue; + } + + if (percent(n)) //随机生成负节点号,对应阴节点, + gene.add(-RandomUtils.nextInt(geneMaxLength)); + + if (percent(n)) //生成随机负正节点号,对应阳节点 + gene.add(RandomUtils.nextInt(geneMaxLength)); + + if (percent(n * 2 + 2)) //随机删除一个节点,用这种方式来清除节点,防止节点无限增长,如果删对了,就不会再回来,如果删错了,系统就会把这个青蛙整个淘汰,这就是遗传算法的好处 + if (!gene.isEmpty()) + gene.remove(RandomUtils.nextInt(gene.size())); + + } + } + +} diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java index 480786e..aa3d6c9 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java @@ -33,8 +33,9 @@ import java.util.concurrent.BlockingQueue; */ @SuppressWarnings("all") public class Logger { + private static final int SYSTEM_OUT_PRINT = 0; //如设为1,不使用log,直接System.out.print输出 private static final int LOGGER_STYLE = 0; //风格设定, 0:不输出前缀, 1:输出时间、类、行号等前缀 - private static final String LEV_EL = "debug"; + private static final String LEV_EL = "DEBUG"; private static final int LEVEL_INT; private static final BlockingQueue LOG_LIST = new ArrayBlockingQueue<>(256); private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); @@ -47,8 +48,8 @@ public class Logger { static { LEVEL_MAP.put("DEBUG", 1); LEVEL_MAP.put("INFO", 2); - LEVEL_MAP.put("WARN", 2); - LEVEL_MAP.put("ERROR", 2); + LEVEL_MAP.put("WARN", 3); + LEVEL_MAP.put("ERROR", 4); LEVEL_INT = LEVEL_MAP.get(LEV_EL.toUpperCase()); new Thread(() -> { while (true) { @@ -114,18 +115,21 @@ public class Logger { } private static void printLog(int levelInt, String msg, Object... params) { - try { - if (levelInt >= LEVEL_INT) { + if (levelInt < LEVEL_INT) + return; + if (SYSTEM_OUT_PRINT == 1) + System.out.print(generateMsg(getLevelStr(levelInt), msg, params)); + else + try { LOG_LIST.put(generateMsg(getLevelStr(levelInt), msg, params)); + } catch (InterruptedException e) { + e.printStackTrace(); } - } catch (InterruptedException e) { - e.printStackTrace(); - } } private static String generateMsg(String levelStr, String msg, Object... params) { - if(LOGGER_STYLE==0) - return formatMsg(msg+ LINE_SEPARATOR, null, params); + if (LOGGER_STYLE == 0) + return formatMsg(msg + LINE_SEPARATOR, null, params); StackTraceElement stack = Thread.currentThread().getStackTrace()[4]; String s = "{} [{}][{}#{} {}] - " + msg + LINE_SEPARATOR; Object[] args = new Object[5 + params.length]; @@ -180,17 +184,17 @@ public class Logger { } private static String getLevelStr(int levelInt) { - switch (levelInt) { - case 1: - return "DEBUG"; - case 2: - return "INFO"; - case 3: - return "WARN"; - case 4: - return "ERROR"; - default: - throw new IllegalStateException("Level " + levelInt + " is unknown."); + switch (levelInt){ + case 1: + return "DEBUG"; + case 2: + return "INFO"; + case 3: + return "WARN"; + case 4: + return "ERROR"; + default: + throw new IllegalStateException("Level " + levelInt + " is unknown."); } } } diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java index dff7533..c112ca6 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java @@ -26,11 +26,11 @@ public class RandomUtils { private static final Random rand = new Random(); public static int nextInt(int i) {//返回随机整数,最小为0,最大为n-1 - if(i==0) + if (i == 0) return 0; return rand.nextInt(i); } - + public static int nextNegOrPosInt(int n) {//返回随机整数,最小为-(n-1),最大为n-1 int x = nextInt(n); if (percent(50)) @@ -52,20 +52,35 @@ public class RandomUtils { return v; } - public static int vary(int v) {// 随机有大概率小变异,小概率大变异,极小概率极大变异 - if (percent(40)) - v += v * .04 * (nextFloat() - 0.5); // v=v+-.04 - if (percent(10)) - v += v * .103 * (nextFloat() - 0.5); // v=v+-0.1 - else if (percent(5)) - v += v * 1 * (nextFloat() - 0.5); // v=v+-0.4 - else if (percent(2)) - v += v * 4 * (nextFloat() - 0.5); // v=v+-2 - else if (percent(1f)) - v += v * 8 * (nextFloat() - 0.5); // v=v+-6 + public static int vary(int v) {// 随机有大概率小变异,小概率大变异,极小概率极大变异 + if (percent(50)) + v += (nextInt(3) - 0.5); + else if (percent(50)) + v += 2 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 4 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 8 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 16 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 32 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 64 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 100 * (nextInt(2) - 0.5); return v; } +// public static void main(String[] args) { +// int n=0; +// for (int i = 0; i < 100; i++) { +// n=vary(n); +// if(n<0)n=0-n; +// System.out.println(vary(0)); +// } +// } + public static float vary(float v) {// 随机有大概率小变异,小概率大变异,极小概率极大变异 if (percent(40)) v += v * .04 * (nextFloat() - 0.5); // v=v+-.04 diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java index c3d7f3a..825f6f1 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java @@ -20,13 +20,15 @@ import com.gitee.drinkjava2.frog.Env; * 这里缓存着一个前序排列的2叉树用来加快速度 * 这里的2叉树和4叉树的原理类似,只不过4叉树生成2维平面形状, 2叉树用来生成一维线状,对于线状生成速度更快 * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * * @author Yong Zhu * @since 1.0 */ public class Tree2Util { //EIGHT_TREE store a pre-order Traversal tree array - public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_CUBE_SIZE); + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); public static int[][] TREE2 = new int[NODE_QTY][2]; //2叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的2叉树细胞的size, x值 @@ -38,13 +40,13 @@ public class Tree2Util { private static int index = 0; static { - tree2Split(0, Env.BRAIN_CUBE_SIZE); + tree2Split(0, Env.BRAIN_SIZE); } static int calculateNodeSize(int n) {//计算2叉树全展开的总节点数 if (n == 1) return 1; - return n + calculateNodeSize(n / 2); + return n + calculateNodeSize(n / 2); } //if cube can split, then split it to 8 small cubes @@ -54,7 +56,7 @@ public class Tree2Util { return; int half = size / 2;//每个细胞可以分裂成2个size为原来1/2的小细胞 tree2Split(x, half); - tree2Split(x+half, half); + tree2Split(x + half, half); } public static void knockNodesByGene(List gene) {//根据基因,把要敲除的2叉树节点作敲除或保留标记 diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java index 231b731..9b6c803 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java @@ -20,13 +20,15 @@ import com.gitee.drinkjava2.frog.Env; * 这里缓存着一个前序排列的4叉树用来加快速度 * 这里的4叉树和4叉树的原理类似,只不过4叉树生成3维形状,4叉树用来生成2维平面形状,对于平面形状生成速度更快 * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * * @author Yong Zhu * @since 1.0 */ public class Tree4Util { //EIGHT_TREE store a pre-order Traversal tree array - public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_CUBE_SIZE); + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); public static int[][] TREE4 = new int[NODE_QTY][3]; //4叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的4叉树细胞的size, x, y值 @@ -38,7 +40,7 @@ public class Tree4Util { private static int index = 0; static { - tree4Split(0, 0, Env.BRAIN_CUBE_SIZE); + tree4Split(0, 0, Env.BRAIN_SIZE); } static int calculateNodeSize(int n) {//计算4叉树全展开的总节点数 @@ -53,10 +55,10 @@ public class Tree4Util { if (size == 1) return; int half = size / 2;//每个细胞可以分裂成4个size为原来1/2的小细胞 - tree4Split(x, y, half); + tree4Split(x, y, half); tree4Split(x + half, y, half); - tree4Split(x, y + half, half); - tree4Split(x + half, y + half, half); + tree4Split(x, y + half, half); + tree4Split(x + half, y + half, half); } public static void knockNodesByGene(List gene) {//根据基因,把要敲除的4叉树节点作敲除或保留标记 diff --git a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java index 839bc90..a5fde8b 100644 --- a/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java +++ b/core/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java @@ -19,13 +19,15 @@ import com.gitee.drinkjava2.frog.Env; * * 这里缓存着一个前序排列的八叉树用来在细胞生成时加快速度和简化运算,关于树结构可用深度树数组来表达的知识可以参见这里:https://my.oschina.net/drinkjava2/blog/1818631 * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * * @author Yong Zhu * @since 1.0 */ public class Tree8Util { //EIGHT_TREE store a pre-order Traversal tree array - public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_CUBE_SIZE); + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); public static int[][] TREE8 = new int[NODE_QTY][4]; //八叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的八叉树细胞的size, x, y, z值 @@ -37,7 +39,7 @@ public class Tree8Util { private static int index = 0; static { - tree8Split(0, 0, 0, Env.BRAIN_CUBE_SIZE); + tree8Split(0, 0, 0, Env.BRAIN_SIZE); } static int calculateNodeSize(int n) {//计算8叉树全展开的总节点数 @@ -75,18 +77,15 @@ public class Tree8Util { if (g < 0) { //g是阴节点 if (Tree8Util.keep[line] == 1) //如果是1,表示这个节点将从保留状态转为删除状态 keepNodeQTY--; - Tree8Util.keep[line]=0; + Tree8Util.keep[line] = 0; } else if (g > 0) { //g是阳节点 if (Tree8Util.keep[line] == 0) //如果是0,表示这个节点将从删除状态转为保留状态 keepNodeQTY++; - Tree8Util.keep[line]=1; + Tree8Util.keep[line] = 1; } } } } } - - - } diff --git a/history/014_3cells/LICENSE b/history/014_3cells/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/history/014_3cells/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/history/014_3cells/README.md b/history/014_3cells/README.md new file mode 100644 index 0000000..b40b94e --- /dev/null +++ b/history/014_3cells/README.md @@ -0,0 +1,12 @@ +## core目录简介 +core目录是当前工作目录,如果跑出什么结果就会拷贝一份放到history目录里存档。 + +当前目标是大方向是由遗传算法来自动排列脑细胞和触突参数,以实现模式识别功能,并与上下左右运动细胞、进食奖罚感觉细胞结合起来,实现吃掉无毒蘑菇,避开有毒蘑菇这个任务。(未完成) +当前小目标: +1)是要利用阴阳8叉树分裂算法进化出第一个可以工作(向食物运动)的神经网络。(已完成, 见011_yinyan_eatfood) +2)利用阴阳8叉树或4叉树分裂算法(见012_tree4)来进化出具备模式识别功能的神经网络。即实现图像到声音的关联,比如对应1,2,3,4 数字的图像会反同激活训练时对应的声音细胞(未完成) +3)简单时序信号的模式识别。比如AB后是C, AD后是E, ABC后是F,多次重复后即可形成时序信号的预测关联。功能类似RNN,但用分裂算法来实现参数自动生成。 + 以上2和3识别原理类似,都建立在细胞连接的遗忘曲线基础上,但是一个强调细胞的空间位置关系,另一个强调信号留存的时间 + +当前小小目标: +进化出具备简单模式识别功能的神经网络,即实现图像到声音的关联, \ No newline at end of file diff --git a/history/014_3cells/maven_clean.bat b/history/014_3cells/maven_clean.bat new file mode 100644 index 0000000..2f3d3e5 --- /dev/null +++ b/history/014_3cells/maven_clean.bat @@ -0,0 +1 @@ +mvn clean \ No newline at end of file diff --git a/history/014_3cells/maven_eclipse_clean.bat b/history/014_3cells/maven_eclipse_clean.bat new file mode 100644 index 0000000..a427bd7 --- /dev/null +++ b/history/014_3cells/maven_eclipse_clean.bat @@ -0,0 +1 @@ +mvn eclipse:clean \ No newline at end of file diff --git a/history/014_3cells/maven_eclipse_eclipse.bat b/history/014_3cells/maven_eclipse_eclipse.bat new file mode 100644 index 0000000..99fa0b2 --- /dev/null +++ b/history/014_3cells/maven_eclipse_eclipse.bat @@ -0,0 +1,2 @@ +call mvn eclipse:eclipse +call pause \ No newline at end of file diff --git a/history/014_3cells/pom.xml b/history/014_3cells/pom.xml new file mode 100644 index 0000000..9198112 --- /dev/null +++ b/history/014_3cells/pom.xml @@ -0,0 +1,102 @@ + + 4.0.0 + com.gitee.drinkjava2 + frog014 + jar + 14.0 + + frog + 当前目标是用分裂算法来实现第一个具备模式识别功能的神经网络 + https://gitee.com/drinkjava2/frog + + + gitee Issue + https://gitee.com/drinkjava2/frog/issues + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + Yong Zhu + yong9981@gmail.com + https://gitee.com/drinkjava2/ + + + + + scm:git@gitee.com:drinkjava2/frog.git + scm:git@gitee.com:drinkjava2/frog.git + git@gitee.com:drinkjava2/frog.git + + + + UTF-8 + UTF-8 + UTF-8 + + 1.8 + 6 + 3.3 + 2.6 + 3.0.0 + 2.7 + 2.19 + 2.6 + 2.4 + 2.10.3 + 1.6 + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.compiler-plugin} + + ${version.java} + ${version.java} + UTF-8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.2 + + + + true + false + lib/ + com.gitee.drinkjava2.frog.Application + + + + + + + + + + + \ No newline at end of file diff --git a/history/014_3cells/run.bat b/history/014_3cells/run.bat new file mode 100644 index 0000000..0156beb --- /dev/null +++ b/history/014_3cells/run.bat @@ -0,0 +1,3 @@ +call mvn clean compile +cd target\classes +java -classpath ".;*" com.gitee.drinkjava2.frog.Application \ No newline at end of file diff --git a/history/014_3cells/run.sh b/history/014_3cells/run.sh new file mode 100644 index 0000000..836c941 --- /dev/null +++ b/history/014_3cells/run.sh @@ -0,0 +1,2 @@ +mvn clean package +java -jar target/frog-*.jar diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Animal.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Animal.java new file mode 100644 index 0000000..8fe8b75 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Animal.java @@ -0,0 +1,286 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog; + +import static com.gitee.drinkjava2.frog.brain.Genes.GENE_NUMBERS; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.FileInputStream; +import java.util.ArrayList; + +import javax.imageio.ImageIO; + +import com.gitee.drinkjava2.frog.brain.Genes; +import com.gitee.drinkjava2.frog.egg.Egg; +import com.gitee.drinkjava2.frog.objects.Material; +import com.gitee.drinkjava2.frog.util.GeneUtils; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Animal is all artificial lives' father class + * Animal only keep one copy of genes from egg, not store gene in cell + * Animal是所有动物(青蛙、蛇等)的父类, animal是由蛋孵出来的,蛋里保存着脑细胞结构生成的基因, Animal只保存一份基因而不是每个细胞都保存一份基因,这是人工生命与实际生物的最大不同 + * 基因是一个list结构, 每一条list代表一条由深度树方式存储的基因树,分表控制细胞的一个参数,当cell用长整数表示时最多可以表达支持64个参数 + * + * + * @author Yong Zhu + * @since 1.0 + */ +public abstract class Animal {// 这个程序大量用到public变量而不是getter/setter,主要是为了编程方便和简洁,但缺点是编程者需要小心维护各个变量 + public static BufferedImage FROG_IMAGE; + + static { + try { + FROG_IMAGE = ImageIO.read(new FileInputStream(Application.CLASSPATH + "frog.png")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ArrayList> genes = new ArrayList<>(); // 基因是多个数列,有点象多条染色体。每个数列都代表一个基因的分裂次序(8叉/4叉/2叉)。 + + public static final int CONSTS_LENGTH = 8; + public int[] consts = new int[CONSTS_LENGTH]; //常量基因,用来存放不参与分裂算法的全局常量,这些常量也参与遗传算法筛选,规则是有大概率小变异,小概率大变异,见constGenesMutation方法 + + /** brain cells,每个细胞对应一个神经元。long是64位,所以目前一个细胞只能允许最多64个基因,64个基因有些是8叉分裂,有些是4叉分裂 + * 如果今后要扩充到超过64个基因限制,可以定义多个三维数组,同一个细胞由多个三维数组相同坐标位置的基因共同表达 + */ + public long[][][] cells = new long[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE]; //所有脑细胞 + + public float[][][] energys = new float[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE]; //每个细胞的能量值,细胞能量不参与打分。打分是由fat变量承担 + + public int[][][][] holes = new int[Env.BRAIN_SIZE][Env.BRAIN_SIZE][Env.BRAIN_SIZE][]; //每个细胞的洞(相当于触突) + + public int xPos; // animal在Env中的x坐标 + public int yPos; // animal在Env中的y坐标 + public long fat = 1000000000; // 青蛙的肥胖度, 只有胖的青蛙才允许下蛋, 以前版本这个变量名为energy,为了不和脑细胞的能量重名,从这个版本起改名为fat + public boolean alive = true; // 设为false表示青蛙死掉了,将不参与计算和显示,以节省时间 + public int ateFood = 0; // 青蛙曾吃过的食物总数 + public int ateWrong = 0; // 青蛙咬了个空气的次数 + public int no; // 青蛙在Env.animals中的序号,从1开始, 会在运行期写到当前brick的最低位,可利用Env.animals.get(no-1)快速定位青蛙 + + public int animalMaterial; + public Image animalImage; + + public Animal(Egg egg) {// x, y 是虑拟环境的坐标 + System.arraycopy(egg.constGenes, 0, this.consts, 0, consts.length);//从蛋中拷一份全局参数 + for (int i = 0; i < GENE_NUMBERS; i++) { + genes.add(new ArrayList<>()); + } + int i = 0; + for (ArrayList gene : egg.genes)//动物的基因是蛋的基因的拷贝 + genes.get(i++).addAll(gene); + i = 0; + if (Env.BORN_AT_RANDOM_PLACE) { //是否随机出生在地图上? + xPos = RandomUtils.nextInt(Env.ENV_WIDTH); + yPos = RandomUtils.nextInt(Env.ENV_HEIGHT); + } else {//否则出生成指定区域 + this.xPos = egg.x + RandomUtils.nextInt(80) - 40; + this.yPos = egg.y + RandomUtils.nextInt(80) - 40; + if (this.xPos < 0) + this.xPos = 0; + if (this.yPos < 0) + this.yPos = 0; + if (this.xPos >= (Env.ENV_WIDTH - 1)) + this.xPos = Env.ENV_WIDTH - 1; + if (this.yPos >= (Env.ENV_HEIGHT - 1)) + this.yPos = Env.ENV_HEIGHT - 1; + } + } + + public void initAnimal() { // 初始化animal,生成脑细胞是在这一步,这个方法是在当前屏animal生成之后调用,比方说有一千个青蛙分为500屏测试,每屏只生成2个青蛙的脑细胞,可以节约内存 + GeneUtils.geneMutation(this); //有小概率基因突变 + GeneUtils.constGenesMutation(this); //常量基因突变 + if (RandomUtils.percent(40)) + for (ArrayList gene : genes) //基因多也要适当小扣点分,防止基因无限增长 + fat -= gene.size(); + GeneUtils.createCellsFromGene(this); //根据基因,分裂生成脑细胞 + } + + private static final int MIN_FAT_LIMIT = Integer.MIN_VALUE + 5000; + private static final int MAX_FAT_LIMIT = Integer.MAX_VALUE - 5000; + + //@formatter:off 下面几行是重要的奖罚方法,会经常调整或注释掉,集中放在一起,不要格式化为多行 + public void changeFat(int fat_) {//正数为奖励,负数为惩罚, fat值是环境对animal唯一的奖罚,也是animal唯一的下蛋竞争标准 + fat += fat_; + if (fat > MAX_FAT_LIMIT) + fat = MAX_FAT_LIMIT; + if (fat < MIN_FAT_LIMIT) + fat = MIN_FAT_LIMIT; + } + + //没定各个等级的奖罚值,目前是手工设定的常数 + public void awardAAAA() { changeFat(2000);} + public void awardAAA() { changeFat(1000);} + public void awardAA() { changeFat(60);} + public void awardA() { changeFat(10);} + + public void penaltyAAAA() { changeFat(-2000);} + public void penaltyAAA() { changeFat(-1000);} + public void penaltyAA() { changeFat(-60);} + public void penaltyA() { changeFat(-10);} + public void kill() { this.alive = false; changeFat(-5000000); Env.clearMaterial(xPos, yPos, animalMaterial); } //kill是最大的惩罚 + //@formatter:on + + public boolean active(int step) {// 这个active方法在每一步循环都会被调用,是脑思考的最小帧,step是当前屏的帧数 + // 如果fat小于0、出界、与非食物的点重合则判死 + if (!alive) { + return false; + } + if (fat <= 0 || Env.outsideEnv(xPos, yPos) || Env.bricks[xPos][yPos] >= Material.KILL_ANIMAL) { + kill(); + return false; + } + + //holesReduce(); //TODO: 所有细胞上的洞都随时间消逝,即信息的遗忘,旧的不去新的不来 + Genes.active(this, step); //调用每个细胞的活动,重要! + return alive; + } + + public void show(Graphics g) {// 显示当前动物 + if (!alive) + return; + //g.drawImage(animalImage, xPos - 8, yPos - 8, 16, 16, null);// 减去坐标,保证嘴巴显示在当前x,y处 + } + + /** Check if x,y,z out of animal's brain range */ + public static boolean outBrainRange(int x, int y, int z) {// 检查指定坐标是否超出animal脑空间界限 + return x < 0 || x >= Env.BRAIN_SIZE || y < 0 || y >= Env.BRAIN_SIZE || z < 0 || z >= Env.BRAIN_SIZE; + } + + public boolean hasGene(int x, int y, int z, long geneMask) { //判断cell是否含某个基因 + return (cells[x][y][z] & geneMask) > 0; + } + + public boolean hasGene(int x, int y, int z) { //判断cell是否含任一基因 + return cells[x][y][z] > 0; + } + + public void open(int x, int y, int z) { //打开指定的xyz坐标对应的cell能量值为极大 + energys[x][y][z] = 99999f; + } + + public void open(int[] a) { //打开指定的a坐标对应的cell能量值为极大 + energys[a[0]][a[1]][a[2]] = 99999f; + } + + public void close(int x, int y, int z) { //关闭指定的xyz坐标对应的cell能量值为0 + energys[x][y][z] = 0; + } + + public void close(int[] a) {//关闭指定的a坐标对应的cell能量值为0 + energys[a[0]][a[1]][a[2]] = 0; + } + + public void addEng(int[] a, float e) {//指定的a坐标对应的cell能量值加e + if (cells[a[0]][a[1]][a[2]] == 0) + return; + energys[a[0]][a[1]][a[2]] += e; + if (energys[a[0]][a[1]][a[2]] < 0) + energys[a[0]][a[1]][a[2]] = 0f; + if (energys[a[0]][a[1]][a[2]] > 10) + energys[a[0]][a[1]][a[2]] = 10f; + } + + public void addEng(int x, int y, int z, float e) {//指定的a坐标对应的cell能量值加e + if (cells[x][y][z] == 0) + return; + energys[x][y][z] += e; + } + + public float get(int x, int y, int z) {//返回指定的a坐标对应的cell能量值 + return energys[x][y][z]; + } + + static final int HOLE_MAX_SIZE = 1000 * 1000; + + public void digHole(int sX, int sY, int sZ, int tX, int tY, int tZ, int holeSize) {//在t细胞上挖洞,将洞的方向链接到源s,如果洞已存在,扩大洞, 新洞大小为1,洞最大不超过100 + if (!hasGene(tX, tY, tZ)) + return; + if (!Env.insideBrain(sX, sY, sZ)) + return; + if (!Env.insideBrain(tX, tY, tZ)) + return; + if (get(tX, tY, tZ) < 1) //要调整 + addEng(tX, tY, tZ, 1); //要调整 + + int[] cellHoles = holes[tX][tY][tZ]; + if (cellHoles == null) { //洞不存在,新建一个, 洞参数是一个一维数组,分别为源坐标X,Y,Z, 洞的大小,洞的新鲜度(TODO:待加) + holes[tX][tY][tZ] = new int[]{sX, sY, sZ, holeSize}; + return; + } else { + int emptyPos = -1; //找指定源坐标已存在的洞,如果不存在,如发现空洞也可以占用 + for (int i = 0; i < cellHoles.length / 4; i++) { + int n = i * 4; + if (cellHoles[n] == sX && cellHoles[n + 1] == sY && cellHoles[n + 2] == sZ) {//找到已有的洞了 + if (cellHoles[n + 3] < 1000) //要改成由基因调整 + cellHoles[n + 3] += 100; + return; + } + if (emptyPos == -1 && cellHoles[n + 3] <= 1)//如发现空洞也可以,先记下它的位置 + emptyPos = n; + } + + if (emptyPos > -1) { //找到一个空洞 + cellHoles[emptyPos] = sX; + cellHoles[emptyPos + 1] = sX; + cellHoles[emptyPos + 2] = sX; + cellHoles[emptyPos + 3] = holeSize; //要改成由基因调整 + return; + } + + int length = cellHoles.length; //没找到已有的洞,也没找到空洞,新建一个并追加到原洞数组未尾 + int[] newHoles = new int[length + 4]; + System.arraycopy(cellHoles, 0, newHoles, 0, length); + newHoles[length] = sX; + newHoles[length + 1] = sY; + newHoles[length + 2] = sZ; + newHoles[length + 3] = holeSize; //要改成由基因调整 + holes[tX][tY][tZ] = newHoles; + return; + } + } + + public void holeSendEngery(int x, int y, int z, float le, float re) {//在当前细胞所有洞上反向发送能量(光子),le是向左边的细胞发, re是向右边的细胞发 + int[] cellHoles = holes[x][y][z]; //cellHoles是单个细胞的所有洞(触突),4个一组,前三个是洞的坐标,后一个是洞的大小 + if (cellHoles == null) //如洞不存在,不发送能量 + return; + for (int i = 0; i < cellHoles.length / 4; i++) { + int n = i * 4; + float size = cellHoles[n + 3]; + if (size > 1) { + int x2 = cellHoles[n]; + if (x2 < x) + addEng(x2, cellHoles[n + 1], cellHoles[n + 2], le); //向左边的细胞反向发送常量大小的能量 + else + addEng(x2, cellHoles[n + 1], cellHoles[n + 2], re); //向右边的细胞反向发送常量大小的能量 + } + } + } + + // public void holesReduce() {//所有hole大小都会慢慢减小,模拟触突连接随时间消失,即细胞的遗忘机制,这保证了系统不会被信息撑爆 + // for (int x = 0; x < Env.BRAIN_SIZE - 1; x++) + // for (int y = 0; y < Env.BRAIN_SIZE - 1; y++) + // for (int z = 0; z < Env.BRAIN_SIZE - 1; z++) { + // int[] cellHoles = holes[x][y][z]; + // if (cellHoles != null) + // for (int i = 0; i < cellHoles.length / 4; i++) { + // int n = i * 4; + // int size = cellHoles[n + 3]; + // if (size > 0) + // cellHoles[n + 3] = (int) (size * 0.9);//要改成由基因调整 + // } + // } + // } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Application.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Application.java new file mode 100644 index 0000000..886ab80 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Application.java @@ -0,0 +1,166 @@ +package com.gitee.drinkjava2.frog; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JRadioButton; +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.gitee.drinkjava2.frog.brain.BrainPicture; +import com.gitee.drinkjava2.frog.brain.Genes; + +/** + * Application's main method start the program + * + * @author Yong Zhu + * @since 1.0 + */ +@SuppressWarnings("all") +public class Application { + + public static final String CLASSPATH; + + static { + String classpath = new File("").getAbsolutePath(); + int i = classpath.lastIndexOf("\\frog\\"); + if (i > 0) + CLASSPATH = classpath.substring(0, i) + "\\frog\\";// windows + else + CLASSPATH = classpath + "/"; // UNIX + } + + public static JFrame mainFrame = new JFrame(); + public static Env env = new Env(); + public static BrainPicture brainPic = new BrainPicture(Env.ENV_WIDTH + 5, 0, Env.BRAIN_SIZE, Env.FROG_BRAIN_DISP_WIDTH); + public static ActionListener pauseAction; + public static boolean selectFrog = true; + + private static void checkIfShowBrainPicture(JButton button) { + int y = Env.ENV_HEIGHT + 150; + if (Env.SHOW_FIRST_ANIMAL_BRAIN) { + button.setText("Hide brain"); + if (Env.FROG_BRAIN_DISP_WIDTH + 41 > y) + y = Env.FROG_BRAIN_DISP_WIDTH + 41; + mainFrame.setSize(Env.ENV_WIDTH + Env.FROG_BRAIN_DISP_WIDTH + 25, y); + brainPic.requestFocus(); + } else { + button.setText("Show brain"); + mainFrame.setSize(Env.ENV_WIDTH + 20, y); + } + } + + public static void main(String[] args) { + mainFrame.setLayout(null); + mainFrame.setSize(Env.ENV_WIDTH + 200, Env.ENV_HEIGHT + 150); // 窗口大小 + mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭时退出程序 + mainFrame.add(env); // 添加虚拟环境Panel + mainFrame.add(brainPic); // 添加脑图Panel + + JButton button = new JButton("Show brain");// 按钮,显示或隐藏脑图 + int buttonWidth = 100; + int buttonHeight = 22; + int buttonXpos = Env.ENV_WIDTH / 2 - buttonWidth / 2; + button.setBounds(buttonXpos, Env.ENV_HEIGHT + 8, buttonWidth, buttonHeight); + ActionListener al = new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) {//显示或隐藏脑图 + Env.SHOW_FIRST_ANIMAL_BRAIN = !Env.SHOW_FIRST_ANIMAL_BRAIN; + checkIfShowBrainPicture(button); + } + }; + checkIfShowBrainPicture(button); + button.addActionListener(al); + mainFrame.add(button); + + JButton stopButton = new JButton("Pause");// 暂停或继续按钮 + stopButton.setBounds(buttonXpos, Env.ENV_HEIGHT + 35, buttonWidth, buttonHeight); + pauseAction = new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + Env.pause = !Env.pause; + if (Env.pause) { + stopButton.setText("Resume"); + } else { + stopButton.setText("Pause"); + brainPic.requestFocus(); + } + } + }; + stopButton.addActionListener(pauseAction); + mainFrame.add(stopButton); + + // 速度条 + final JSlider speedSlider = new JSlider(1, 10, (int) Math.round(Math.pow(Env.SHOW_SPEED, 1.0 / 3))); + speedSlider.setBounds(buttonXpos - 50, stopButton.getY() + 25, buttonWidth + 100, buttonHeight); + ChangeListener slideAction = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + Env.SHOW_SPEED = speedSlider.getValue() * speedSlider.getValue() * speedSlider.getValue(); + brainPic.requestFocus(); + } + }; + speedSlider.addChangeListener(slideAction); + mainFrame.add(speedSlider); + final JLabel label = new JLabel("Speed:"); + label.setBounds(buttonXpos - 90, stopButton.getY() + 23, 100, buttonHeight); + mainFrame.add(label); + + //是否把egg文件存盘 + JCheckBox saveFileCheckBox = new JCheckBox("Save egg file"); + saveFileCheckBox.setBounds(buttonXpos, Env.ENV_HEIGHT + 80, 120, 22); + ActionListener saveAction = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (saveFileCheckBox.isSelected()) + Env.SAVE_EGGS_FILE = true; + else + Env.SAVE_EGGS_FILE = false; + } + }; + saveFileCheckBox.addActionListener(saveAction); + mainFrame.add(saveFileCheckBox); + + //基因维数显示控制 + for (int i = 0; i < Genes.GENE_NUMBERS; i++) { + JRadioButton geneRadio = new JRadioButton(); + geneRadio.setBounds(buttonXpos + 300 + i * 16, Env.ENV_HEIGHT + 8, 20, 22); + geneRadio.setSelected(Genes.display_gene[i]); + geneRadio.setName("" + i); + ActionListener geneRadioAction = new ActionListener() { + public void actionPerformed(ActionEvent e) { + int i = Integer.parseInt(geneRadio.getName()); + if (geneRadio.isSelected()) + Genes.display_gene[i] = true; + else + Genes.display_gene[i] = false; + } + }; + geneRadio.addActionListener(geneRadioAction); + mainFrame.add(geneRadio); + } + + //是否显示分裂过程 + JCheckBox showSplitDetailCheckBox = new JCheckBox("Show split detail"); + showSplitDetailCheckBox.setBounds(buttonXpos + 300, Env.ENV_HEIGHT + 40, 120, 22); + ActionListener detailAction = new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (showSplitDetailCheckBox.isSelected()) + Env.show_split_detail = true; + else + Env.show_split_detail = false; + } + }; + showSplitDetailCheckBox.addActionListener(detailAction); + mainFrame.add(showSplitDetailCheckBox); + //mainFrame.setBounds(0, 400, 5, 5); + mainFrame.setVisible(true); + env.run(); + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Env.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Env.java new file mode 100644 index 0000000..6f10a4b --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Env.java @@ -0,0 +1,307 @@ +package com.gitee.drinkjava2.frog; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Image; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.JPanel; + +import com.gitee.drinkjava2.frog.egg.Egg; +import com.gitee.drinkjava2.frog.egg.FrogEggTool; +import com.gitee.drinkjava2.frog.objects.EnvObject; +import com.gitee.drinkjava2.frog.objects.Food; +import com.gitee.drinkjava2.frog.objects.Material; +import com.gitee.drinkjava2.frog.objects.OneDotEye; +import com.gitee.drinkjava2.frog.util.Logger; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Env is the living space of frog. draw it on JPanel + * + * @author Yong Zhu + * @since 1.0 + */ +@SuppressWarnings("all") +public class Env extends JPanel { + private static final long serialVersionUID = 1L; + + /** Speed of test */ + public static int SHOW_SPEED = 1000; // 测试速度,-1000~1000,可调, 数值越小,速度越慢 + + public static final int FROG_EGG_QTY = 200; // 每轮下n个青蛙蛋,可调,只有最优秀的前n个青蛙们才允许下蛋 + + public static final int FROG_PER_EGG = 4; // 每个青蛙蛋可以孵出几个青蛙 + + public static final int SCREEN = 1; // 分几屏测完 + + /** Delete eggs at beginning of each run */ + public static final boolean DELETE_FROG_EGGS = true;// 每次运行是否先删除以前保存的青蛙蛋文件,如果为false将加载旧蛋文件继续运行 + + public static boolean SAVE_EGGS_FILE = false; //从2021-11-23起,添加这个选项,允许不输出蛋文件到磁盘上 + + public static final boolean BORN_AT_RANDOM_PLACE = true;// 孵出青蛙落在地图上随机位置,而不是在蛋所在地 + + /** Frog's brain size */ // 脑细胞位于脑范围内,是个三维结构,在animal中用三维数组来表示 + public static final int BRAIN_SIZE =4; //脑立方边长大小,必须是2的幂数如4,8,16...,原因参见8叉树算法 + + /** SHOW first animal's brain structure */ + public static boolean SHOW_FIRST_ANIMAL_BRAIN = true; // 是否显示脑图在Env区的右侧 + + /** Draw first frog's brain after some steps */ + public static int DRAW_BRAIN_AFTER_STEPS = 0; // 以此值为间隔动态画出脑图,设为0则关闭这个动态脑图功能,只显示一个静态、不闪烁的脑图 + + /** Environment x width, unit: pixels */ + public static final int ENV_WIDTH = 400; // 虚拟环境的宽度, 可调 + + /** Environment y height, unit: pixels */ + public static final int ENV_HEIGHT = ENV_WIDTH; // 虚拟环境高度, 可调,通常取正方形 + + /** Frog's brain display width on screen, not important */ + public static final int FROG_BRAIN_DISP_WIDTH = 400; // Frog的脑图在屏幕上的显示大小,可调 + + /** Steps of one test round */ + public static final int STEPS_PER_ROUND = 800;// 每轮测试步数,可调 + public static int step;// 当前测试步数 + + public static final int FOOD_QTY = 3000; // 食物数量, 可调 + + // 以下是程序内部变量,不要手工修改它们 + public static final int TOTAL_FROG_QTY = FROG_EGG_QTY * FROG_PER_EGG; // 蛇的总数 + + public static final int FROG_PER_SCREEN = TOTAL_FROG_QTY / SCREEN; // 每屏显示几个青蛙,这个数值由其它常量计算得来 + + public static int current_screen = 0; + + public static boolean pause = false; // 暂停按钮按下将暂停测试 + + public static int[][] bricks = new int[ENV_WIDTH][ENV_HEIGHT];// 组成环境的材料,见Material.java + + public static List frogs = new ArrayList<>(); // 这里存放所有待测的青蛙,可能分几次测完,由FROG_PER_SCREEN大小来决定 + + public static List frog_eggs = new ArrayList<>(); // 这里存放新建或从磁盘载入上轮下的蛋,每个蛋可能生成几个青蛙, + + public static EnvObject[] things = new EnvObject[]{new OneDotEye()};// 所有外界物体,如食物、字母测试工具都放在这个things里面 + + public static boolean show_split_detail = false; //是否显示脑分裂的细节过程,即从一个细胞开始分裂分裂,而不是只显示分裂的最终结果 + + static { + Logger.info("唵缚悉波罗摩尼莎诃!"); // 杀生前先打印往生咒,因为遗传算法建立在杀生选优的基础上,用这个方式表示一下对生命的尊重。智能研究不光是技术,还涉及到伦理,对虚拟生命的尊重也是对人类自身的尊重。 + // (意识不是一种实体存在,只是一种表象,但正因为此,我们才要尊重所有表现出或低级或高级的意识现象的虚拟智能系统,包括避免制造不必要的虚拟生命的痛苦感觉现象,己所不欲勿施于人。) + Logger.info("脑图快捷键: T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标:缩放旋转平移"); + if (DELETE_FROG_EGGS) + FrogEggTool.deleteEggs(); + } + + public Env() { + super(); + this.setLayout(null);// 空布局 + this.setBounds(1, 1, ENV_WIDTH, ENV_HEIGHT); + } + + public static boolean insideBrain(int x, int y) {// 如果指定点在边界内 + return !(x < 0 || y < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE); + } + + public static boolean insideBrain(int x, int y, int z) {// 如果指定点在边界内 + return !(x < 0 || y < 0 || z < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE || z >= BRAIN_SIZE); + } + + public static boolean insideBrain(float x, float y, float z) {// 如果指定点在边界内 + return !(x < 0 || y < 0 || z < 0 || x >= BRAIN_SIZE || y >= BRAIN_SIZE || z >= BRAIN_SIZE); + } + + public static boolean insideEnv(int x, int y) {// 如果指定点在边界内 + return !(x < 0 || y < 0 || x >= ENV_WIDTH || y >= ENV_HEIGHT); + } + + public static boolean outsideEnv(int x, int y) {// 如果指定点超出边界 + return x < 0 || y < 0 || x >= ENV_WIDTH || y >= ENV_HEIGHT; + } + + public static boolean closeToEdge(Animal a) {// 靠近边界? 离死不远了 + return a.xPos < 20 || a.yPos < 20 || a.xPos > (Env.ENV_WIDTH - 20) || a.yPos > (Env.ENV_HEIGHT - 20); + } + + public static boolean foundAnyThingOrOutEdge(int x, int y) {// 如果指定点看到任意东西或超出边界,返回true + return x < 0 || y < 0 || x >= ENV_WIDTH || y >= ENV_HEIGHT || Env.bricks[x][y] != 0; + } + + public static boolean foundFrogOrOutEdge(int x, int y) {// 如果指定点看到青蛙或超出边界,返回true + if (x < 0 || y < 0 || x >= ENV_WIDTH || y >= ENV_HEIGHT) + return true;// 如果出界返回true + if ((Env.bricks[x][y] & Material.FROG_TAG) > 0) + return true; + else + return false; + } + + public static void setMaterial(int x, int y, int material) { + if (Env.insideEnv(x, y)) + Env.bricks[x][y] = Env.bricks[x][y] | material; + } + + public static boolean hasMaterial(int x, int y, int material) { + if (!Env.insideEnv(x, y)) + return false; + return (Env.bricks[x][y] & material) > 0; + } + + public static void clearMaterial(int x, int y, int material) { + if (Env.insideEnv(x, y)) + Env.bricks[x][y] = Env.bricks[x][y] & ~material; + } + + private void rebuildFrogs() {// 根据蛙蛋重新孵化出蛙 + frogs.clear(); + for (int i = 0; i < frog_eggs.size(); i++) {// 创建青蛙,每个蛋生成n个蛙,并随机取一个别的蛋作为精子 + int loop = FROG_PER_EGG; + if (frog_eggs.size() > 20) { // 如果数量多,进行一些优化,让排名靠前的Egg多孵出青蛙 + if (i < FROG_PER_EGG)// 0,1,2,3 + loop = FROG_PER_EGG + 1; + if (i >= (frog_eggs.size() - FROG_PER_EGG)) + loop = FROG_PER_EGG - 1; + } + for (int j = 0; j < loop; j++) { + Egg zygote = new Egg(frog_eggs.get(i), frog_eggs.get(RandomUtils.nextInt(frog_eggs.size()))); + Frog f = new Frog(zygote); + frogs.add(f); + f.no = frogs.size(); + } + } + } + + private void drawWorld(Graphics g) { + int brick; + for (int x = 0; x < ENV_WIDTH; x++) + for (int y = 0; y < ENV_HEIGHT; y++) { + brick = bricks[x][y]; + if (brick != 0) { + g.setColor(Material.color(brick)); + if ((brick & Material.FOOD) > 0) { + g.fillRoundRect(x, y, 4, 4, 2, 2); //食物只有一个点太小,画大一点 + } else + g.drawLine(x, y, x, y); // only 1 point + } + } + g.setColor(Color.BLACK); + } + + static final NumberFormat format100 = NumberFormat.getPercentInstance(); + static { + format100.setMaximumFractionDigits(2); + } + + private String foodAtedCount() {// 统计吃食总数等 + int maxFound = 0; + for (Frog f : frogs) + if (f.ateFood > maxFound) + maxFound = f.ateFood; + return new StringBuilder("吃食率:").append(format100.format(Food.food_ated * 1.00 / FOOD_QTY)).append(", 平均: ").append(Food.food_ated * 1.0f / FROG_PER_SCREEN).append(",最多:").append(maxFound) + .toString(); + } + + public static void checkIfPause(int step) { + if (pause) + do { + Application.brainPic.drawBrainPicture(step); + Application.brainPic.requestFocus(); + sleep(100); + } while (pause); + } + + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static Animal getShowAnimal() { + return frogs.get(current_screen * FROG_PER_SCREEN); + } + + public void run() { + FrogEggTool.loadFrogEggs(); // 从磁盘加载蛙egg,或新建一批egg + Image buffImg = createImage(this.getWidth(), this.getHeight()); + Graphics g = buffImg.getGraphics(); + long time0;// 计时用 + int round = 1; + do { + rebuildFrogs(); // 根据蛙蛋重新孵化出蛙,注意基因变异有可能在孵化过程中发生 + for (current_screen = 0; current_screen < SCREEN; current_screen++) {// 分屏测试,每屏FROG_PER_SCREEN个蛙 + time0 = System.currentTimeMillis(); + for (EnvObject thing : things) // 创建食物、陷阱等物体 + thing.build(); + boolean allDead = false; + for (int j = 0; j < FROG_PER_SCREEN; j++) { + Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); + f.initAnimal(); // 初始化器官延迟到这一步,是因为脑细胞太占内存,而且当前屏测完后会清空 + } + for (step = 0; step < STEPS_PER_ROUND; step++) { + for (EnvObject thing : things)// 调用食物、陷阱等物体的动作 + thing.active(current_screen, step); + if (allDead) + break; // 青蛙全死光了就直接跳到下一轮,以节省时间 + allDead = true; + for (int j = 0; j < FROG_PER_SCREEN; j++) { + Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); + if (f.active(step))// 调用青蛙的Active方法,并返回是否还活着 + allDead = false; + } + + if (SHOW_SPEED == 1) // 如果speed为1,人为加入延迟 + sleep((100)); + else if (step % SHOW_SPEED != 0)// 用是否跳帧画图的方式来控制速度 + continue; + + // 开始画虚拟环境和青蛙 + g.setColor(Color.white); + g.fillRect(0, 0, this.getWidth(), this.getHeight()); // 先清空虚拟环境 + g.setColor(Color.BLACK); + drawWorld(g);// 画整个虚拟环境 + for (int j = 0; j < FROG_PER_SCREEN; j++) { // 显示青蛙 + Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); + f.show(g); + } + + if (SHOW_FIRST_ANIMAL_BRAIN) {// 在showAnimal上画一个红圈 + Animal showAnimal = getShowAnimal(); + if (showAnimal != null) { + g.setColor(Color.red); + g.drawArc(showAnimal.xPos - 15, showAnimal.yPos - 15, 30, 30, 0, 360); + g.setColor(Color.BLACK); + } + } + if (DRAW_BRAIN_AFTER_STEPS > 0 && step % DRAW_BRAIN_AFTER_STEPS == 0) //显示脑图是耗时操作,这个开关可以跳过一些脑图显示 + Application.brainPic.drawBrainPicture(step); + if (SHOW_SPEED == 1 && SHOW_FIRST_ANIMAL_BRAIN) //如果速度为1,强制每步都显示脑图 + Application.brainPic.drawBrainPicture(step); + Graphics g2 = this.getGraphics(); + g2.drawImage(buffImg, 0, 0, this); + } + if (SHOW_FIRST_ANIMAL_BRAIN) //一轮结束后再强制再显示脑图一次 + Application.brainPic.drawBrainPicture(step); + checkIfPause(step); + for (int j = 0; j < FROG_PER_SCREEN; j++) { + Frog f = frogs.get(current_screen * FROG_PER_SCREEN + j); + } + StringBuilder sb = new StringBuilder("Round: "); + sb.append(round).append(", screen:").append(current_screen).append(", speed:").append(Env.SHOW_SPEED).append(", ").append(", 用时: ").append(System.currentTimeMillis() - time0) + .append("ms, "); + sb.append(foodAtedCount()); + + Application.mainFrame.setTitle(sb.toString()); + for (EnvObject thing : things)// 去除食物、陷阱等物体 + thing.destory(); + } + round++; + FrogEggTool.layEggs(); //能量高的青蛙才有权下蛋 + } while (true); + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Frog.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Frog.java new file mode 100644 index 0000000..f2dbeb8 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/Frog.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog; + +import com.gitee.drinkjava2.frog.egg.Egg; +import com.gitee.drinkjava2.frog.objects.Material; + +/** + * Frog is child class of Animal, Frog's name is Sam + * Frog是Animal的一个子类 + * + * @since 1.0 + */ +public class Frog extends Animal { + + public Frog(Egg egg) { + super(egg); + animalMaterial = Material.FROG_TAG; + animalImage = Animal.FROG_IMAGE; + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java new file mode 100644 index 0000000..e14cfb3 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/BrainPicture.java @@ -0,0 +1,479 @@ +package com.gitee.drinkjava2.frog.brain; + +import static java.awt.Color.BLACK; +import static java.awt.Color.RED; +import static java.awt.Color.WHITE; +import static java.lang.Math.cos; +import static java.lang.Math.round; +import static java.lang.Math.sin; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import javax.swing.JPanel; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Application; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.util.ColorUtils; +import com.gitee.drinkjava2.frog.util.Tree8Util; + +/** + * BrainPicture show first frog's brain structure, for debug purpose only + * + * 这个类用来画出脑图,这不是一个关键类,对脑的运行逻辑无影响,但有了脑图后可以直观地看出脑的3维结构,进行有针对性的改进 + * 可以用鼠标进行平移、缩放、旋转,以及t、f、l、r,x五个键来选择顶视、前视、左视、右视、斜视这5个方向的视图,以及空格暂停、方向键调整切面 + * 鼠标的动作定义在MouseAction类中。 + * + * @author Yong Zhu + * @since 1.0 + */ +@SuppressWarnings("all") +public class BrainPicture extends JPanel { + private static final long serialVersionUID = 1L; + + private static final float D90 = (float) (Math.PI / 2); + + Color picColor = RED; + int brainDispWidth; // screen display piexls width + float scale; // brain scale + int xOffset = 0; // brain display x offset compare to screen + int yOffset = 0; // brain display y offset compare to screen + float xAngle = D90 * .8f; // brain rotate on x axis + float yAngle = D90 / 4; // brain rotate on y axis + float zAngle = 0;// brain rotate on z axis + int xMask = -1;// x Mask + int yMask = -1;// y Mask + BufferedImage buffImg; + Graphics g; + String note; + public KeyAdapter keyAdapter; + + public BrainPicture(int x, int y, float brainWidth, int brainDispWidth) { + super(); + this.setLayout(null);// 空布局 + this.brainDispWidth = brainDispWidth; + scale = 0.5f * brainDispWidth / brainWidth; + this.setBounds(x, y, brainDispWidth + 1, brainDispWidth + 1); + buffImg = new BufferedImage(Env.FROG_BRAIN_DISP_WIDTH, Env.FROG_BRAIN_DISP_WIDTH, BufferedImage.TYPE_INT_RGB); + g = buffImg.getGraphics(); + MouseAction act = new MouseAction(this); + this.addMouseListener(act); // 添加鼠标动作监听 + this.addMouseWheelListener(act);// 添加鼠标滚轮动作监听 + this.addMouseMotionListener(act);// 添加鼠标移动动作监听 + + keyAdapter = new KeyAdapter() {// 处理t,f,l,r,x键盘命令 + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()){ + case KeyEvent.VK_UP:// Y切面向上 + yMask++; + if (yMask > Env.BRAIN_SIZE) + yMask = Env.BRAIN_SIZE; + break; + case KeyEvent.VK_DOWN:// Y切面向下 + yMask--; + if (yMask < 0) + yMask = 0; + break; + case KeyEvent.VK_LEFT:// x切面向左 + xMask--; + if (xMask < 0) + xMask = 0; + break; + case KeyEvent.VK_RIGHT:// x切面向右 + xMask++; + if (xMask > Env.BRAIN_SIZE) + xMask = Env.BRAIN_SIZE; + break; + case ' ':// 暂停及继续 + Application.pauseAction.actionPerformed(null); + break; + case 'T':// 顶视 + xAngle = 0; + yAngle = 0; + zAngle = 0; + break; + case 'F':// 前视 + xAngle = D90; + yAngle = 0; + zAngle = 0; + break; + case 'L':// 左视 + xAngle = D90; + yAngle = D90; + zAngle = 0; + break; + case 'R':// 右视 + xAngle = D90; + yAngle = -D90; + zAngle = 0; + break; + case 'X':// 斜视 + xAngle = D90 * .8f; + yAngle = D90 / 4; + zAngle = 0; + break; + default: + } + } + }; + addKeyListener(keyAdapter); + this.setFocusable(true); + } + + public void drawCuboid(float x, float y, float z, float xe, float ye, float ze) {// 在脑图上画一个长立方体框架,视角是TopView + drawLine(x, y, z, x + xe, y, z);// 画立方体的下面边 + drawLine(x + xe, y, z, x + xe, y + ye, z); + drawLine(x + xe, y + ye, z, x, y + ye, z); + drawLine(x, y + ye, z, x, y, z); + + drawLine(x, y, z, x, y, z + ze);// 画立方体的中间边 + drawLine(x + xe, y, z, x + xe, y, z + ze); + drawLine(x + xe, y + ye, z, x + xe, y + ye, z + ze); + drawLine(x, y + ye, z, x, y + ye, z + ze); + + drawLine(x, y, z + ze, x + xe, y, z + ze);// 画立方体的上面边 + drawLine(x + xe, y, z + ze, x + xe, y + ye, z + ze); + drawLine(x + xe, y + ye, z + ze, x, y + ye, z + ze); + drawLine(x, y + ye, z + ze, x, y, z + ze); + } + + public void drawCentLine(float px1, float py1, float pz1, float px2, float py2, float pz2) {//从细胞中点之间画一条线 + drawLine(px1 + 0.5f, py1 + 0.5f, pz1 + 0.5f, px2 + 0.5f, py2 + 0.5f, pz2 + 0.5f); + } + + /*- + 画线,固定以top视角的角度,所以只需要从x1,y1画一条到x2,y2的直线 + 绕 x 轴旋转 θ + x, y.cosθ-zsinθ, y.sinθ+z.cosθ + + 绕 y 轴旋转 θ + z.sinθ+x.cosθ, y, z.cosθ-x.sinθ + + 绕 z 轴旋转 θ + x.cosθ-y.sinθ, x.sinθ+y.consθ, z + -*/ + public void drawLine(float px1, float py1, float pz1, float px2, float py2, float pz2) { + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; + double x2 = px2 - Env.BRAIN_SIZE / 2; + double y2 = -py2 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z2 = pz2 - Env.BRAIN_SIZE / 2; + x1 = x1 * scale; + y1 = y1 * scale; + z1 = z1 * scale; + x2 = x2 * scale; + y2 = y2 * scale; + z2 = z2 * scale; + double x, y, z; + y = y1 * cos(xAngle) - z1 * sin(xAngle);// 绕x轴转 + z = y1 * sin(xAngle) + z1 * cos(xAngle); + y1 = y; + z1 = z; + + x = z1 * sin(yAngle) + x1 * cos(yAngle);// 绕y轴转 + // z = z1 * cos(yAngle) - x1 * sin(yAngle); + x1 = x; + // z1 = z; + + x = x1 * cos(zAngle) - y1 * sin(zAngle);// 绕z轴转 + y = x1 * sin(zAngle) + y1 * cos(zAngle); + x1 = x; + y1 = y; + + y = y2 * cos(xAngle) - z2 * sin(xAngle);// 绕x轴转 + z = y2 * sin(xAngle) + z2 * cos(xAngle); + y2 = y; + z2 = z; + + x = z2 * sin(yAngle) + x2 * cos(yAngle);// 绕y轴转 + // z = z2 * cos(yAngle) - x2 * sin(yAngle); + x2 = x; + // z2 = z; + + x = x2 * cos(zAngle) - y2 * sin(zAngle);// 绕z轴转 + y = x2 * sin(zAngle) + y2 * cos(zAngle); + x2 = x; + y2 = y; + + g.setColor(picColor); + g.drawLine((int) round(x1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset, (int) round(y1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset, (int) round(x2) + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset, + (int) round(y2) + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset); + } + + /** 画点,固定以top视角的角度,所以只需要在x1,y1位置画一个点 */ + public void drawPoint(float px1, float py1, float pz1, float r) { + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; + x1 = x1 * scale; + y1 = y1 * scale; + z1 = z1 * scale; + double x, y, z; + y = y1 * cos(xAngle) - z1 * sin(xAngle);// 绕x轴转 + z = y1 * sin(xAngle) + z1 * cos(xAngle); + y1 = y; + z1 = z; + + x = z1 * sin(yAngle) + x1 * cos(yAngle);// 绕y轴转 + // z = z1 * cos(yAngle) - x1 * sin(yAngle); + x1 = x; + // z1 = z; + + x = x1 * cos(zAngle) - y1 * sin(zAngle);// 绕z轴转 + y = x1 * sin(zAngle) + y1 * cos(zAngle); + x1 = x; + y1 = y; + + g.setColor(picColor); + g.fillOval(round((float) x1 + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset - r * scale * .5f), round((float) y1 + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset - r * scale * .5f), round(r * scale), + round(r * scale)); + } + + /** 画一个圆 */ + public void drawCircle(float px1, float py1, float pz1, float r) {//这个方法实际和上面的一样的,只是改成了drawOval + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; + x1 = x1 * scale; + y1 = y1 * scale; + z1 = z1 * scale; + double x, y, z; + y = y1 * cos(xAngle) - z1 * sin(xAngle);// 绕x轴转 + z = y1 * sin(xAngle) + z1 * cos(xAngle); + y1 = y; + z1 = z; + + x = z1 * sin(yAngle) + x1 * cos(yAngle);// 绕y轴转 + // z = z1 * cos(yAngle) - x1 * sin(yAngle); + x1 = x; + // z1 = z; + + x = x1 * cos(zAngle) - y1 * sin(zAngle);// 绕z轴转 + y = x1 * sin(zAngle) + y1 * cos(zAngle); + x1 = x; + y1 = y; + + g.setColor(picColor); + g.drawOval(round((float) x1 + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset - r * scale * .5f), round((float) y1 + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset - r * scale * .5f), round(r * scale), + round(r * scale)); + } + + public void drawText(float px1, float py1, float pz1, String text, float textSize) { + double x1 = px1 - Env.BRAIN_SIZE / 2; + double y1 = -py1 + Env.BRAIN_SIZE / 2;// 屏幕的y坐标是反的,显示时要正过来 + double z1 = pz1 - Env.BRAIN_SIZE / 2; + x1 = x1 * scale; + y1 = y1 * scale; + z1 = z1 * scale; + double x, y, z; + y = y1 * cos(xAngle) - z1 * sin(xAngle);// 绕x轴转 + z = y1 * sin(xAngle) + z1 * cos(xAngle); + y1 = y; + z1 = z; + + x = z1 * sin(yAngle) + x1 * cos(yAngle);// 绕y轴转 + // z = z1 * cos(yAngle) - x1 * sin(yAngle); + x1 = x; + // z1 = z; + + x = x1 * cos(zAngle) - y1 * sin(zAngle);// 绕z轴转 + y = x1 * sin(zAngle) + y1 * cos(zAngle); + x1 = x; + y1 = y; + + g.setColor(picColor); + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, (int) round(textSize * scale))); + g.drawString(text, (int) round(x1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + xOffset, (int) round(y1) + Env.FROG_BRAIN_DISP_WIDTH / 2 + yOffset); + + } + + public void drawBrainPicture(int step) {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 + if (!Env.SHOW_FIRST_ANIMAL_BRAIN) + return; + if (Env.show_split_detail) + drawSplitDetail(); + else + drawBrainStructure(step); + } + + public void drawSplitDetail() {// 在这个方法里绘制脑细胞分裂的显示步聚,即从一个细胞开始分裂成最终脑结构的每一步 + Animal a = Env.getShowAnimal(); // 第一个青蛙或蛇 + + for (int i = Env.BRAIN_SIZE; i >= 1; i /= 2) { + g.setColor(WHITE);// 先清空旧图, g是buffImg,绘在内存中 + g.fillRect(0, 0, brainDispWidth, brainDispWidth); + g.setColor(BLACK); // 画边框 + g.drawRect(0, 0, brainDispWidth, brainDispWidth); + + for (int geneIndex = 0; geneIndex < Genes.GENE_NUMBERS; geneIndex++) { + ArrayList gene = a.genes.get(geneIndex); + Tree8Util.knockNodesByGene(gene); + for (int j = 0; j < Tree8Util.NODE_QTY; j++) { + if (Tree8Util.keep[j] > 0) { + int[] node = Tree8Util.TREE8[j]; + int size = node[0]; + if (size == i && Genes.display_gene[geneIndex]) {//如果允许显示的话, 显示当前层级的节点 + setPicColor(ColorUtils.colorByCode(geneIndex)); + drawPoint(node[1] + size / 2, node[2] + size / 2, node[3] + size / 2, size * (0.5f - geneIndex * 0.05f)); + } + } + } + } + g.setColor(BLACK); + drawCuboid(0, 0, 0, Env.BRAIN_SIZE, Env.BRAIN_SIZE, Env.BRAIN_SIZE);// 把脑的框架画出来 + this.getGraphics().drawImage(buffImg, 0, 0, this);// 利用缓存避免画面闪烁,这里输出缓存图片 + if (!Env.show_split_detail) + return; + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + } + + public void drawBrainStructure(int step) {// 在这个方法里进行动物的三维脑结构的绘制,蛇是青蛙的子类,所以也可以当参数传进来 + Animal a = Env.getShowAnimal(); // 显示第一个青蛙或蛇 + if (a == null || !a.alive) + return; + g.setColor(WHITE);// 先清空旧图, g是buffImg,绘在内存中 + g.fillRect(0, 0, brainDispWidth, brainDispWidth); + g.setColor(BLACK); // 画边框 + g.drawRect(0, 0, brainDispWidth, brainDispWidth); + + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + for (int y = Env.BRAIN_SIZE - 1; y >= 0; y--) { + for (int x = Env.BRAIN_SIZE - 1; x >= 0; x--) { + long cell = a.cells[x][y][z]; + // if (cell == 0) //只显示有效的细胞点 + // continue; + + int[] holes = a.holes[x][y][z]; + if (holes != null) { + setPicColor(Color.GRAY); + for (int i = 0; i < holes.length / 4; i++) { + int n = i * 4; + drawCentLine(x, y, z, holes[n], holes[n + 1], holes[n + 2]); + } + } + + if (x >= xMask && y >= yMask && cell != 0)//画出细胞每个基因存在的细胞格子 + for (int geneIndex = 0; geneIndex < Genes.GENE_NUMBERS; geneIndex++) { + if ((cell & (1 << geneIndex)) != 0 && Genes.display_gene[geneIndex]) { + setPicColor(ColorUtils.colorByCode(geneIndex)); //开始画出对应的细胞基因参数,用不同颜色直径圆表示 + //setPicColor(Color.RED); + //drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, geneIndex == 0 ? 0.8f : 0.5f - geneIndex * 0.05f); + drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, 0.6f); + } + } + float e = a.energys[x][y][z]; + if (e > 1f || e < -1f) { + setPicColor(e > 0 ? Color.RED : Color.BLUE); //用红色小圆表示正能量,蓝色表示负能量 + drawPoint(x + 0.5f, y + 0.5f, z + 0.5f, 0.2f); + float size = (float) (0.5f + 0.4 * Math.log10(Math.abs(e)));//再用不同大小圆形表示不同能量值 + drawCircle(x + 0.5f, y + 0.5f, z + 0.5f, size); + } + + } + } + } + + setPicColor(Color.BLACK); + drawCuboid(0, 0, 0, Env.BRAIN_SIZE, Env.BRAIN_SIZE, Env.BRAIN_SIZE);// 把脑的框架画出来 + + setPicColor(BLACK); //把x,y,z坐标画出来 + drawText(Env.BRAIN_SIZE, 0, 0, "x", Env.BRAIN_SIZE * 0.2f); + drawText(0, Env.BRAIN_SIZE, 0, "y", Env.BRAIN_SIZE * 0.2f); + drawText(0, 0, Env.BRAIN_SIZE, "z", Env.BRAIN_SIZE * 0.2f); + setPicColor(RED); + drawLine(0, 0, 0, Env.BRAIN_SIZE, 0, 0); + drawLine(0, 0, 0, 0, Env.BRAIN_SIZE, 0); + drawLine(0, 0, 0, 0, 0, Env.BRAIN_SIZE); + + g.setColor(Color.black); + if (note != null) {// 全局注释 + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16)); + g.drawString(note, 10, 20); + } + + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16)); + g.drawString("step:" + step + ", ate:" + a.ateFood + ", wrong:" + a.ateWrong, 10, 15); + + // for (int y = 0; y < ColorUtils.rainbow.length; y += 1) {//调试彩虹色 + // g.setColor(ColorUtils.rainbow[y]); + // for (int i = 0; i < 9; i++) + // g.drawLine(0, y * 9 + i, 50, y * 9 + i); + // } + + this.getGraphics().drawImage(buffImg, 0, 0, this);// 利用缓存避免画面闪烁,这里输出缓存图片 + } + + public static void setNote(String note) { + Application.brainPic.note = note; + } + + // getters & setters + public float getScale() { + return scale; + } + + public void setScale(float scale) { + this.scale = scale; + } + + public float getxAngle() { + return xAngle; + } + + public void setxAngle(float xAngle) { + this.xAngle = xAngle; + } + + public float getyAngle() { + return yAngle; + } + + public void setyAngle(float yAngle) { + this.yAngle = yAngle; + } + + public float getzAngle() { + return zAngle; + } + + public void setzAngle(float zAngle) { + this.zAngle = zAngle; + } + + public void setPicColor(Color color) { + this.picColor = color; + } + + public Color getPicColor() { + return picColor; + } + + public int getxOffset() { + return xOffset; + } + + public void setxOffset(int xOffset) { + this.xOffset = xOffset; + } + + public int getyOffset() { + return yOffset; + } + + public void setyOffset(int yOffset) { + this.yOffset = yOffset; + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java new file mode 100644 index 0000000..8e05d87 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/Genes.java @@ -0,0 +1,156 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.brain; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.objects.Eye; +import com.gitee.drinkjava2.frog.objects.OneDotEye; +import com.gitee.drinkjava2.frog.util.Logger; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Genes代表不同的脑细胞参数,对应每个参数,用8叉/4叉/2叉树算法给每个细胞添加细胞参数和行为。 + * 每个脑细胞用一个long来存储,所以目前最多允许64个基因位, 多字节参数可以由多个基因位决定。每个基因位都由一个单独的阴阳8/4/2叉树控制,多个基因就组成了一个8/4/2叉树阵列 + * 基因+分裂算法=结构 + * 基因+分裂算法+遗传算法=结构的进化 + * + * 这个类里定义每个基因位的掩码以及对应基因的细胞行为, 脑结构的所有参数,都要用基因来控制。开始时可以有常量、魔数,但以后都放到基因里去自动进化。 + * + * @author Yong Zhu + * @since 10.0 + */ +@SuppressWarnings("all") +public class Genes { //Genes登记所有的基因, 指定每个基因允许分布的空间范围。注意登录完后还要并针对每个基因在active方法里写出它对应的细胞行为 + public static int GENE_MAX = 64; //目前最多允许64个基因 + + public static int GENE_NUMBERS = 0; //这里统计定义了多少个基因 + private static int zeros = 0; //当前基因位掩码0个数 + + public static boolean[] display_gene = new boolean[GENE_MAX]; //如果这个参数为真,此基因显示在脑图上 + public static boolean[] fill_gene = new boolean[GENE_MAX]; //如果这个参数为真,此基因填充指定的区域,而不是由分裂算法随机生成 + + public static int[] xLimit = new int[GENE_MAX]; //用来手工限定基因分布范围,详见register方法 + public static int[] yLimit = new int[GENE_MAX]; + public static int[] zLimit = new int[GENE_MAX]; + + /** + * Register a gene 依次从底位到高位登记所有的基因掩码及对应的相关参数 + * + * @param maskBits how many mask bits 掩码位数,即有几个1 + * @param display whether to display the gene on the BrainPicture 是否显示在脑图 + * @param fill whether to fill to specified 3D/2D/1D/1Point area 是否直接用此基因填充指定的区域,区域可以是三维、二维、线状及一个点 + * @param x_limit gene only allow on specified x layer 如x_layer大于-1,且y_layer=-1, 表示只生成在指定的x层对应的yz平面上,这时采用4叉树而不是8叉树以提高进化速度 + * @param y_limit gene only allow on specified x, y axis 如大于-1,表示只生成在指定的x,y坐标对应的z轴上,这时采用2叉阴阳树算法 + * @param z_limit gene only allow on specified x, y, z 点上, 表示手工指定基因位于x,y,z坐标点上 + * @return a long wtih mask bits 返回基因掩码,高位由maskBits个1组成,低位是若干个0,以后判断一个cell上是否含有这个基因,只需要用cell对应的long和这个 掩码做与运算即可 + */ + public static long register(int maskBits, boolean display, boolean fill, int x_limit, int y_limit, int z_limit) { + for (int i = GENE_NUMBERS; i < GENE_NUMBERS + maskBits; i++) { + display_gene[i] = display; + fill_gene[i] = fill; + xLimit[i] = x_limit; + yLimit[i] = y_limit; + zLimit[i] = z_limit; + } + + String one = ""; + String zero = ""; + for (int i = 1; i <= maskBits; i++) + one += "1"; + for (int i = 1; i <= GENE_NUMBERS; i++) + zero += "0"; + zeros = GENE_NUMBERS; + GENE_NUMBERS += maskBits; + if (GENE_NUMBERS >= GENE_MAX) {// + System.out.println("目前基因位数不能超过" + GENE_MAX); + System.exit(-1); + } + return Long.parseLong(one + zero, 2); //将类似"111000"这种字符串转换为长整 + } + + public static long register(int... pos) {//登记并指定基因允许分布的位置 + return register(1, true, false, pos[0], pos[1], pos[2]); + } + + public static long registerFill(int... pos) {//登记并手工指定基因分布的位置 + return register(1, true, true, pos[0], pos[1], pos[2]); + } + + public static boolean hasGene(long cell, long geneMask) { //判断cell是否含某个基因 + return (cell & geneMask) > 0; + } + + private static final int NA = -1; + private static final int CS4 = Env.BRAIN_SIZE / 4; + + //============开始登记有名字的基因========== + public static long EYE = registerFill(0, 0, 0); //视网膜细胞,这个版本暂时只允许视网膜分布在x=0,y=0的z轴上,即只能看到一条线状图形 + + public static long MEM = registerFill(1, 0, 0); //记忆细胞,暂时只允许它分布在x=1,y=0的z轴上 + + public static int[] BITE_POS = new int[]{2, 0, 0}; + public static long BITE = registerFill(BITE_POS); //咬动作细胞定义在一个点上, 这个细胞如激活,就咬食物 + + // public static int[] NOT_BITE_POS = new int[]{2, 0, CS4}; + // public static long NOT_BITE = registerFill(NOT_BITE_POS); //不咬动作细胞定义在一个点上, 这个细胞如激活,就不咬食物 + + // public static int[] SWEET_POS = new int[]{2, 0, CS4 * 2}; + // public static long SWEET = registerFill(SWEET_POS); //甜味感觉细胞定义在一个点上, 当咬下后且食物为甜,这个细胞激活 + + // public static int[] BITTER_POS = new int[]{2, 0, CS4 * 3}; + // public static long BITTER = registerFill(BITTER_POS); //苦味感觉细胞定义在一个点上, 当咬下后且食物为苦,这个细胞激活 + + //========开始登记无名字的基因 ========= + static { + } + + //========= active方法在每个主循环都会调用,用来存放细胞的行为,这是个重要方法 =========== + public static void active(Animal a, int step) { + + for (int z = Env.BRAIN_SIZE - 1; z >= 0; z--) + for (int x = Env.BRAIN_SIZE - 1; x >= 0; x--) { + int y = 0; + long cell = a.cells[x][y][z]; + if (a.consts[7] == 0) + a.consts[7] = 1; + if (hasGene(cell, BITE) && ((OneDotEye.code % 100) == 0)) {//如果没有输入,咬细胞也是有可能定时或随机激活的,模拟婴儿期随机运动碰巧咬下了 + a.addEng(x, y, z, a.consts[1] / 10); + } + + float energy = a.energys[x][y][z]; + if (energy >= 1f) { //如果细胞激活了 + a.energys[x][y][z] = a.energys[x][y][z] - Math.abs(a.consts[2]) * 0.2f;//所有细胞能量都会自发衰减 + + if (hasGene(cell, BITE)) { //如果是咬细胞 + if ((OneDotEye.code % 20) == 0) { //从上帝视角知道被20整除正好是OneDotEye看到食物出现的时刻 + a.awardAAAA(); //所以必然咬中,奖励 + a.ateFood++; + } else { + a.penaltyAA(); //其它时间是咬错了,罚。 可以改成penaltyAAAA或去除本行试试 + a.ateWrong++; + } + a.digHole(x, y, z, x - 1, y, z, a.consts[3]);//咬细胞在记忆细胞上挖洞 + } + + if (hasGene(cell, EYE)) {//视网膜细胞在记忆细胞上挖洞 + a.digHole(x, y, z, x + 1, y, z, a.consts[4]); + } + + if (hasGene(cell, MEM)) {//记忆细胞,在当前细胞所有洞上反向发送能量 + a.holeSendEngery(x, y, z, a.consts[5], a.consts[6]); + } + + } + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/MouseAction.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/MouseAction.java new file mode 100644 index 0000000..337e5de --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/brain/MouseAction.java @@ -0,0 +1,107 @@ +package com.gitee.drinkjava2.frog.brain; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + +/** + * MouseAction + * + * 这个类用来处理脑图BrainPicture上的鼠标动作,有平移、旋转、缩放三种 + * + * @author Yong Zhu + * @since 2.0.2 + */ +public class MouseAction implements MouseListener, MouseWheelListener, MouseMotionListener { + private BrainPicture brainPic; + private int buttonPressed = 0; + private int x; + private int y; + + public MouseAction(BrainPicture brainPic) { + this.brainPic = brainPic; + } + + @Override + public void mousePressed(MouseEvent e) {// 记录当前鼠标点 + if (e.getButton() == 1)// 旋转 + buttonPressed = 1; + else if (e.getButton() == 2)// 缩放 + buttonPressed = 2; + else + buttonPressed = 0; + x = e.getPoint().x; + y = e.getPoint().y; + brainPic.requestFocus(); + } + + @Override + public void mouseReleased(MouseEvent e) { + buttonPressed = 0; + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) {// 缩放 + if (e.getWheelRotation() < 0) { + brainPic.scale *= 1.1; + brainPic.xOffset *= 1.1; + brainPic.yOffset *= 1.1; + } else { + brainPic.scale /= 1.1; + brainPic.xOffset /= 1.1; + brainPic.yOffset /= 1.1; + } + } + + @Override + public void mouseDragged(MouseEvent e) {// 旋转 + if (buttonPressed == 1) { + if (e.getX() > x && e.getY() > y) + brainPic.zAngle -= .00f; + else if (e.getX() < x && e.getY() < y) + brainPic.zAngle += .00f; + else { + if (e.getX() > x) + brainPic.yAngle += .02f; + if (e.getX() < x) + brainPic.yAngle -= .02f; + if (e.getY() > y) + brainPic.xAngle -= .02f; + if (e.getY() < y) + brainPic.xAngle += .02f; + } + x = e.getX(); + y = e.getY(); + } + if (buttonPressed == 2) {// 平移 + if (e.getX() > x) + brainPic.xOffset += 6; + if (e.getX() < x) + brainPic.xOffset -= 6; + if (e.getY() > y) + brainPic.yOffset += 6; + if (e.getY() < y) + brainPic.yOffset -= 6; + x = e.getX(); + y = e.getY(); + } + } + + @Override + public void mouseClicked(MouseEvent e) {// do nothing + } + + @Override + public void mouseEntered(MouseEvent e) {// do nothing + } + + @Override + public void mouseExited(MouseEvent e) {// do nothing + } + + @Override + public void mouseMoved(MouseEvent e) { // do nothing + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java new file mode 100644 index 0000000..92c1b0e --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/Egg.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.egg; + +import java.io.Serializable; +import java.util.ArrayList; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Egg is the static structure description of brain cells + * + * 蛋存在的目的是为了以最小的字节数串行化存储脑细胞,它是海量脑细胞的生成算法描述,而不是脑细胞本身 + * 蛋和基因的关系:基因是一种语言,相当于染色体,不存在坐标位置。蛋则是基因的载体,有x,y坐标,表示在虚拟环境中蛋存在的位置。 + * + * 另外青蛙本身也是基因的载体,所以青蛙里有一个gene属性 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Egg implements Serializable { + private static final long serialVersionUID = 1L; + public int x; // 蛋的x位置 + public int y; // 蛋的y位置 + + // gene record the 8-tree structure of brain cells + // 基因是随机生成的8叉树数据结构,和实际生物每个细胞都要保存一份基因不同,程序中每个脑细胞并不需要保存基因的副本,这样可以极大地减少内存占用 + public ArrayList> genes = new ArrayList<>(); + public int[] constGenes = new int[Animal.CONSTS_LENGTH]; //animal中的全局常量基因全放在这里,用随机数来生成,用遗传算法筛选 + + public Egg() {// 无中生有,创建一个蛋,先有蛋,后有蛙 + x = RandomUtils.nextInt(Env.ENV_WIDTH); + y = RandomUtils.nextInt(Env.ENV_HEIGHT); + } + + /** Create egg from animal */ + public Egg(Animal a) { // 下蛋,每个器官会创建自已的副本或变异,可以是0或多个 + x = a.xPos; + y = a.yPos; + for (ArrayList gene : a.genes) {//下蛋就是把动物的基因拷贝到新蛋里,并有可能变异 + ArrayList g = new ArrayList<>(); + g.addAll(gene); + genes.add(g); + } + System.arraycopy(a.consts, 0, this.constGenes, 0, constGenes.length); + } + + /** + * Create a egg by join 2 eggs, x+y=zygote 模拟X、Y 染色体合并,两个蛋生成一个新的蛋 + */ + public Egg(Egg a, Egg b) {//两个蛋的基因混合, 生成一个新蛋 + x = a.x; + y = a.y; + genes.addAll(a.genes); + System.arraycopy(a.constGenes, 0, this.constGenes, 0, constGenes.length); + if (RandomUtils.percent(20)) //插入蛋B的基因到A蛋中 + for (int i = 0; i < b.genes.size(); i++) { + if (RandomUtils.percent(2)) { + ArrayList aGene = a.genes.get(i); + ArrayList bGene = b.genes.get(i); + if (bGene.size() > 1) {//随机插入一个B的基因,不用担心基因越来越多,因为随机删除的速度大于增长的 + aGene.add(bGene.get(RandomUtils.nextInt(bGene.size()))); + } + } + } + if (RandomUtils.percent(20)) {//交换蛋B的常量基因到A蛋中, 不重要,先写上 + int n = RandomUtils.nextInt(this.constGenes.length); + this.constGenes[n] = b.constGenes[n]; + } + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java new file mode 100644 index 0000000..5d4ce65 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/egg/FrogEggTool.java @@ -0,0 +1,142 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.egg; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Application; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.Frog; +import com.gitee.drinkjava2.frog.util.LocalFileUtils; +import com.gitee.drinkjava2.frog.util.Logger; + +/** + * FrogEggTool save/load frog eggs to file + * + * @author Yong Zhu + * @since 1.0 + */ +@SuppressWarnings("all") +public class FrogEggTool { + + /** + * Frogs which have higher fat value lay eggs + * + * 利用Java串行机制存盘。 更肥的(也就是吃的更多)的Frog下蛋并存盘, 以进行下一轮测试,瘦的Frog被淘汰,没有下蛋的资格。 + * 用fat值来作为唯一的生存竟争标准 + */ + public static void layEggs() { + sortFrogsOrderByFat(); + Frog first = Env.frogs.get(0); + Frog last = Env.frogs.get(Env.frogs.size() - 1); + try { + Env.frog_eggs.clear(); + for (int i = 0; i < Env.FROG_EGG_QTY; i++) //每次下蛋数量固定为EGG_QTY个 + Env.frog_eggs.add(new Egg(Env.frogs.get(i))); + Logger.info("Fist frog fat={}, gene size={}, Last frog fat={}", first.fat, getGeneSize(first), last.fat); + + //debug + for (int x = 0; x < Env.BRAIN_SIZE; x++) { + StringBuilder s = new StringBuilder(); + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + int[] holes = first.holes[x][0][z]; + if (holes == null) + s.append("0,"); + else + s.append(first.holes[x][0][z].length/4).append(","); + } + Logger.debug("x=" + x + ", holes:" + s);//打印出每个细胞的洞数量 + } + + // Logger.debug(Arrays.toString(frogs.get(current_screen).constGenes)); //debug; + StringBuilder s = new StringBuilder(); + for (int i = 0; i < Animal.CONSTS_LENGTH; i++) { + if (i != 0) + s.append(", "); + s.append("\t" + i).append("=").append(first.consts[i]); + } + Logger.debug("consts: " + s); + + if (Env.SAVE_EGGS_FILE) { + FileOutputStream fo = new FileOutputStream(Application.CLASSPATH + "frog_eggs.ser"); + ObjectOutputStream so = new ObjectOutputStream(fo); + so.writeObject(Env.frog_eggs); + so.close(); + Logger.info(". Saved {} eggs to file '{}frog_eggs.ser'", Env.frog_eggs.size(), Application.CLASSPATH); + } + } catch (IOException e) { + Logger.error(e); + } + } + + private static String getGeneSize(Frog f) {//按genes类型汇总每种基因的个数 + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < f.genes.size(); i++) + sb.append(f.genes.get(i).size()).append(","); + if (sb.length() > 1) + sb.setLength(sb.length() - 1); + sb.append("]"); + return sb.toString(); + } + + private static void sortFrogsOrderByFat() {// 给青蛙从肥到瘦排个序 + Collections.sort(Env.frogs, new Comparator() { + public int compare(Animal a, Animal b) { + if (a.fat > b.fat) + return -1; + else if (a.fat == b.fat) + return 0; + else + return 1; + } + }); + } + + public static void deleteEggs() { + Logger.info("Delete exist egg file: '{}frog_eggs.ser'", Application.CLASSPATH); + LocalFileUtils.deleteFile(Application.CLASSPATH + "frog_eggs.ser"); + } + + /** + * 从磁盘读入一批frog Egg + */ + @SuppressWarnings("unchecked") + public static void loadFrogEggs() { + boolean errorfound = false; + try { + FileInputStream eggsFile = new FileInputStream(Application.CLASSPATH + "frog_eggs.ser"); + ObjectInputStream eggsInputStream = new ObjectInputStream(eggsFile); + Env.frog_eggs = (List) eggsInputStream.readObject(); + Logger.info("Loaded " + Env.frog_eggs.size() + " eggs from file '" + Application.CLASSPATH + "frog_eggs.ser" + "'.\n"); + eggsInputStream.close(); + } catch (Exception e) { + errorfound = true; + } + if (errorfound) { + Env.frog_eggs.clear(); + for (int j = 0; j < Env.FROG_EGG_QTY; j++) { + Egg egg = new Egg(); + Env.frog_eggs.add(egg); + } + Logger.info("Fail to load frog egg file '" + Application.CLASSPATH + "frog_eggs.ser" + "', created " + Env.frog_eggs.size() + " eggs to do test."); + } + + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java new file mode 100644 index 0000000..fa69c98 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/EnvObject.java @@ -0,0 +1,38 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.objects; + +/** + * EnvObject means some virtual object in Env + * + * @author Yong Zhu + * @since 1.0 + */ +public interface EnvObject {//EnvObject接口代表虚拟环境中的一种物体,每定义一个物体,要在Env的things变量中添加它 + + public void build();// 在Env中创建本身物体,只在每屏测试前调用一次 + + public void destory();// 从Env中清除本身物体,只在每屏测试完成后调用一次 + + public void active(int screen, int step);// 每个步长(即时间最小单位)都会调用一次这个方法, 两个参数分别是当前屏数和当前步长数 + + public static class DefaultEnvObject implements EnvObject {//EnvObject接口的缺省实现 + + public void build() { + } + + public void destory() { + } + + public void active(int screen, int step) { + } + } +} \ No newline at end of file diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java new file mode 100644 index 0000000..3abb9a9 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Eye.java @@ -0,0 +1,57 @@ +package com.gitee.drinkjava2.frog.objects; + +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.Frog; +import com.gitee.drinkjava2.frog.brain.Genes; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Eye的作用只有一个,就是把一个随机序号的二进制条码投射到视网膜细胞上(暂定于x=0的YZ平面上) + * + * 当前目标是实现简单的模式识别,frog要能进化出模式识别功能,即看到图来预测是要咬还是不咬,这就是最简单模式识别了,即原来的信号顺序是: + * 图-(随机驱动)咬-甜, 或 图-(随机驱动)咬-苦, + * 最终进化为: 图-甜-咬-甜, 图-苦-不咬-苦,也就是说根据图案来联想到甜或苦的感觉细胞激活,从而预判咬还是不咬,而无需等随机信号驱动,也无需等味觉信号传入(实际也不可能在咬之前有味觉)。 + * + * + * 这个功能通过 随机、饿、视、咬、不咬、苦、甜、忆这几类细胞来组合完成。 + * 视细胞是其中一环,位于本类操作的视网膜区。Eye将外界信号(先简单用一个随机序号的条形码)投射成视网膜上的图像。 + * 饿细胞(可选),可以设定它永远活跃,即青蛙永远都吃不饱,饿细胞强烈激活时,有可能直接驱动咬细胞 + * 随机细胞(可选),有一定机率活跃,作用是随机驱动咬或不咬细胞 + * 咬细胞,如果活跃,产生实际的咬动作。Env会根据咬的图案有毒(随机序号被3除余1)扣分并激活苦细胞,或是无毒(随机序号被3除余2)加分并激活甜细胞 + * 苦细胞和甜细胞, 苦细胞和甜细胞是感觉信号,它由食物的毒性决定,只有咬下后才会激活。苦和甜细胞会对忆细胞的洞大小进行调节从以影响条件反射强度。 + * 忆细胞, 这种细胞的作用是记忆,当两个细胞在时间上有短期内的相关性时,就会在忆细胞上打出两个洞来,将来任一个细胞激活,就从洞里反向发送光子,从而实现信号的关联,即记忆功能。洞大小由信号重复数决定。 + * 因为洞大小可以很大,也可以很小直到遗忘消失,所以忆细胞接收和发送的脉冲信号可能强度有巨大差别,并不是0/1这种简单信号,而是有强度的信号。 + * + * 目前为最简化起见,食物图像用随机序号的二进制条形码图案代表,将来可扩展到2维食物图形或者二维文字图形,逻辑不需要改动。 + * + */ +public class Eye extends DefaultEnvObject { + public static int code; //这里用code的二进制单维条码代表任务图形,随便假设被3除后余1的有毒且苦,余2的可食且甜,余0的无毒无味 (debug:调试时再简化一下,只要看到就意味可食) + + @Override + public void active(int screen, int step) { + if (step % 20 == 0) { //每隔20步在所有青蛙的视网膜上画code的二进制条形码 ,设code能被3除余2即为食物(在Genes里去判断) + code = RandomUtils.nextInt(8); + Frog f; + for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { + f = Env.frogs.get(i); + drawImageOnEye(f, code); + } + } + } + + /** + * 根据code数字在视网膜上画图,即给某些神经细胞赋能量值,实验阶段先用单维的二进制条形码代替二维图像 + */ + private static void drawImageOnEye(Frog f, int code) { + long i = 1; + for (int z = 0; z < Env.BRAIN_SIZE; z++) { + float engery = ((code & i) > 0) ? 5f : 0; + if (Genes.hasGene(f.cells[0][0][z], Genes.EYE)) + f.energys[0][0][z] = engery; + i = i << 1; + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java new file mode 100644 index 0000000..13f3f05 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Food.java @@ -0,0 +1,62 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.objects; + +import static com.gitee.drinkjava2.frog.Env.ENV_HEIGHT; +import static com.gitee.drinkjava2.frog.Env.ENV_WIDTH; +import static com.gitee.drinkjava2.frog.Env.FOOD_QTY; + +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; +import com.gitee.drinkjava2.frog.util.RandomUtils; + +/** + * Food randomly scatter on Env + * 食物 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Food extends DefaultEnvObject {//食物除了被吃,它自己没有什么活动,所以没有重写active方法 + static Food FOOD = new Food(); //FOOD是一个单例,整个环境只允许有一个FOOD实例 + + public static int food_ated = 0; + + @Override + public void build() { + food_ated = 0; + for (int i = 0; i < FOOD_QTY; i++) { // 随机位置生成食物 + int x = RandomUtils.nextInt(ENV_WIDTH); + int y = RandomUtils.nextInt(ENV_HEIGHT); + if (!Env.hasMaterial(x, y, Material.FOOD)) { + Env.setMaterial(x, y, Material.FOOD); //在环境里标记上FOOD + } + } + } + + @Override + public void destory() { + food_ated = 0; + for (int x = 0; x < ENV_WIDTH; x++) // 清除食物 + for (int y = 0; y < ENV_HEIGHT; y++) { + Env.clearMaterial(x, y, Material.FOOD); + } + } + + public static boolean foundAndAteFood(int x, int y) {// 如果x,y有食物,将其清0,返回true + if (Env.hasMaterial(x, y, Material.FOOD)) { + food_ated++; + Env.clearMaterial(x, y, Material.FOOD);//在环境里清除FOOD + return true; + } + return false; + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Material.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Material.java new file mode 100644 index 0000000..2e1931d --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/Material.java @@ -0,0 +1,50 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.objects; + +import java.awt.Color; + +/** + * Material store material types + * + * 虚拟环境中每个点由一个int代表,多个材料可以同时出现在同一个点,每种材料用int中的一个bit位来表示, + * 小于等于16384的位数用来标记青蛙序号,可利用Env.frogs.get(no-1)获取青蛙对象,其它各种材料用整数中其它位来表示 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Material {// NOSONAR + + public static final int FROG_TAG = 0b11111111111111; // 16383 小于等于16384的位数用来标记青蛙序号,可利用Env.frogs.get(no-1)快速定位青蛙 + + private static int material = FROG_TAG + 1; // 大于16384用来作为各种材料的标记 + + public static final int FOOD = nextMaterial(); + public static final int SNAKE = nextMaterial(); // 蛇的图形 + public static final int KILL_ANIMAL = nextMaterial(); // if>=KILLFROG will kill animal + public static final int BRICK = nextMaterial();// brick will kill frog + public static final int TRAP = nextMaterial(); // trap will kill frog + + private static int nextMaterial() {// 每次将material左移1位 + material = material << 1; + if (material < 0) + throw new IllegalArgumentException("Material out of maximum range"); + return material; + } + + public static Color color(int material) { + if ((material & TRAP) > 0) + return Color.LIGHT_GRAY; + else + return Color.BLACK; + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java new file mode 100644 index 0000000..3c1e700 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/objects/OneDotEye.java @@ -0,0 +1,24 @@ +package com.gitee.drinkjava2.frog.objects; + +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.Frog; +import com.gitee.drinkjava2.frog.objects.EnvObject.DefaultEnvObject; + +/** + * DotEye的作用只有一个,就是定期在视网膜细胞上激活一个点,告知食物存在 + */ +public class OneDotEye extends DefaultEnvObject { + public static int code = 0; + + @Override + public void active(int screen, int step) { + code++; + if (code % 20 == 0) { //每隔20步在所有青蛙的视网膜上画一个图案 ,单个点调试时设为每20步激活时就是食物 + for (int i = screen; i < screen + Env.FROG_PER_SCREEN; i++) { + Frog f = Env.frogs.get(i); + f.energys[0][0][0] = f.consts[0]; + } + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java new file mode 100644 index 0000000..87525ab --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/ColorUtils.java @@ -0,0 +1,83 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.awt.Color; + +/** + * Color Utilities used in this project + * + * @author Yong Zhu + * @since 1.0 + */ +public class ColorUtils { + + public static Color[] rainbow; + + static { + rainbow = new Color[125]; + int i = 0; + for (int r = 0; r < 3; r++) + for (int b = 0; b < 3; b++) + for (int g = 0; g < 3; g++) { + { + { + if (!(r == b && r == g)) { + rainbow[i] = new Color(((r + 2) % 3) * 122, g * 122, ((b + 1) % 3) * 122); + i++; + } + } + + } + } + Color[] t = new Color[i]; + System.arraycopy(rainbow, 0, t, 0, i); + rainbow = t; + } + + private static int nextColor = 0; + + private ColorUtils() {// default private constr + } + + public static int nextColorCode() { + return nextColor++; + } + + public static Color nextRainbowColor() {// 返回下一个彩虹色 + if (nextColor >= rainbow.length) + nextColor = 0; + return rainbow[nextColor++]; + } + + public static Color colorByCode(int i) {// 数值取模后返回一个固定彩虹色 + return rainbow[i % rainbow.length]; + } + + public static Color rainbowColor(float i) { // 根据数值大小范围,在8种彩虹色中取值 + if (i <= 20) + return Color.GRAY; + if (i <= 30) + return Color.BLACK; + if (i <= 50) + return Color.RED; + return Color.MAGENTA; + } + + public static Color grayColor(float f) { // 根据数值大小范围0~1000,返回一个灰度色,越大越黑 + if (f > 1000) + f = 1000; + int i1 = 255 - (int) Math.round(f * .255); + int i2 = 200 - (int) Math.round(f * .200); + int i3 = 150 - (int) Math.round(f * .150); + return new Color(i1, i2, i3); + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java new file mode 100644 index 0000000..c197857 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/GeneUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright 2018 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import static com.gitee.drinkjava2.frog.brain.Genes.GENE_NUMBERS; +import static com.gitee.drinkjava2.frog.util.RandomUtils.percent; + +import java.util.ArrayList; +import java.util.Arrays; + +import com.gitee.drinkjava2.frog.Animal; +import com.gitee.drinkjava2.frog.Env; +import com.gitee.drinkjava2.frog.brain.Genes; + +/** + * 与Gene相关的工具方法 + * + * @author Yong Zhu + * @since 10.0 + */ +@SuppressWarnings("all") +public class GeneUtils { + + public static void createCellsFromGene(Animal a) {//根据基因生成细胞参数 + for (int g = 0; g < GENE_NUMBERS; g++) {//动物有多条基因,一条基因控制一维细胞参数,目前最多有64维,也就是最多有64条基因 + long geneMask = 1l << g; + ArrayList gene = a.genes.get(g); + int xLimit = Genes.xLimit[g]; + int yLimit = Genes.yLimit[g]; + int zLimit = Genes.zLimit[g]; + boolean fill = Genes.fill_gene[g]; + + if (fill) { //如果这个基因是fill型的,会填充在指定区域的所有细胞中,不需要使用分裂算法来生成细胞 + if (xLimit < 0) { //如坐标一个也没有给出, 填充整个三维脑细胞空间 + for (int x = 0; x < Env.BRAIN_SIZE; x++) + for (int y = 0; y < Env.BRAIN_SIZE; y++) + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[x][y][z] = a.cells[x][y][z] | geneMask; + } else if (yLimit < 0) { // 如果只给出了x坐标, 填充此基因在脑坐标为x的yz平面上 + for (int y = 0; y < Env.BRAIN_SIZE; y++) + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[xLimit][y][z] = a.cells[xLimit][y][z] | geneMask; + } else if (zLimit < 0) { // 如果只给出了x,y坐标,填充此基因在x,y指定的z轴上 + for (int z = 0; z < Env.BRAIN_SIZE; z++) + a.cells[xLimit][yLimit][z] = a.cells[xLimit][yLimit][z] | geneMask; + } else { //如果x,y,z都给出了,填充此基因在x,y,z指定的点上 + a.cells[xLimit][yLimit][zLimit] = a.cells[xLimit][yLimit][zLimit] | geneMask; + } + continue; + } + + //以下开始使用分裂和随机算法来从基因链生成脑细胞 + if (xLimit < 0) { //如坐标一个也没有定义,使用阴阳8叉树分裂算法在三维脑细胞空间分裂,这个最慢但分布范围大 + Tree8Util.knockNodesByGene(gene);//根据基因,把要敲除的8叉树节点作个标记 + for (int i = 0; i < Tree8Util.NODE_QTY; i++) {//再根据敲剩下的8叉树keep标记生成细胞参数 + if (Tree8Util.keep[i] > 0) { + int[] node = Tree8Util.TREE8[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在三维空间对应数组的位置把当前基因geneMask置1 + a.cells[node[1]][node[2]][node[3]] = a.cells[node[1]][node[2]][node[3]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else if (yLimit < 0) { // 如果只定义了x坐标, 表示此基因分布在脑坐标x的yz平面上,此时使用阴阳4叉树分裂算法在此平面上分裂以加快速度 + Tree4Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 + for (int i = 0; i < Tree4Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 + if (Tree4Util.keep[i] > 0) { + int[] node = Tree4Util.TREE4[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 + a.cells[xLimit][node[1]][node[2]] = a.cells[xLimit][node[1]][node[2]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else if (zLimit < 0) { // 如果只定义了x,y坐标,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 + Tree2Util.knockNodesByGene(gene);//根据基因,把要敲除的4叉树节点作个标记 + for (int i = 0; i < Tree2Util.NODE_QTY; i++) {//再根据敲剩下的4叉树keep标记生成细胞参数 + if (Tree2Util.keep[i] > 0) { + int[] node = Tree2Util.TREE2[i]; + if (node[0] == 1) {//如果node边长为1,即不可以再分裂了,就在2维空间对间数组的位置把当前基因geneMask置1 + a.cells[xLimit][yLimit][node[1]] = a.cells[xLimit][yLimit][node[1]] | geneMask; //在相应的细胞处把细胞参数位置1 + } + } + } + } else { //如果x,y,z都指定了,表示这个基因只能分布在一个点上, 这时只有0或1两种可能,如果基因不为空就认为它有 + if (!gene.isEmpty()) + a.cells[xLimit][yLimit][zLimit] = a.cells[xLimit][yLimit][zLimit] | geneMask; + } + } + } + + public static void constGenesMutation(Animal a) { //全局参数变异, 这一个方法变异动物的所有常量 + for (int i = 0; i < a.consts.length; i++) { + if (percent(20)) + a.consts[i] = RandomUtils.vary(a.consts[i]); + } + } + + public static void geneMutation(Animal a) { //基因变异,注意这一个方法同时变异所有条基因 + for (int g = 0; g < GENE_NUMBERS; g++) + if (percent(50)) { + if (Genes.fill_gene[g]) //如果这个基因是fill型的,永远会存在指定区域的所有细胞中,所以不需要参与变异 + continue; + + int n = 5; //这是个魔数,今后可以考虑放在基因里去变异,8\4\2\1叉树的变异率可以不一样 + + //随机新增阴节点基因,注意只是简单地随机新增,所以可能有重复基因 + ArrayList gene = a.genes.get(g); + int geneMaxLength; //8叉、4叉树、2叉树的节点最大序号不同,基因随机生成时要限制它不能大于最大序号 + if (Genes.xLimit[g] < 0) { //如x没定义,使用阴阳8叉树分裂算法 + geneMaxLength = Tree8Util.NODE_QTY; + } else if (Genes.yLimit[g] < 0) { // 如果x>=0, y没定义, 表示此基因分布在坐标x的yz平面上,此时使用阴阳4叉树分裂算法 + geneMaxLength = Tree4Util.NODE_QTY; + } else if (Genes.zLimit[g] < 0) { // 如果x>=0, y>=0,z没定义,这时基因只能分布在x,y指定的z轴上,此时使用阴阳2叉树分裂算法 + geneMaxLength = Tree2Util.NODE_QTY; + } else { //如果x,y,z都指定了,表示这个基因只能分布在一个点上, 这时只有0或1两种可能, 如果基因不为空就认为它有。用随机算法 + if (percent(n)) { + if (gene.isEmpty()) + gene.add(1); + else + gene.clear(); + } + continue; + } + + if (percent(n)) //随机生成负节点号,对应阴节点, + gene.add(-RandomUtils.nextInt(geneMaxLength)); + + if (percent(n)) //生成随机负正节点号,对应阳节点 + gene.add(RandomUtils.nextInt(geneMaxLength)); + + if (percent(n * 2 + 2)) //随机删除一个节点,用这种方式来清除节点,防止节点无限增长,如果删对了,就不会再回来,如果删错了,系统就会把这个青蛙整个淘汰,这就是遗传算法的好处 + if (!gene.isEmpty()) + gene.remove(RandomUtils.nextInt(gene.size())); + + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/LocalFileUtils.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/LocalFileUtils.java new file mode 100644 index 0000000..7422435 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/LocalFileUtils.java @@ -0,0 +1,141 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Local File Utilities used in this project + * + * @author Yong Zhu + * @since 1.0 + */ +public class LocalFileUtils { + + private LocalFileUtils() { + // default constructor + } + + public static boolean deleteFile(String fileFullPath) { + File file = new File(fileFullPath); + return file.delete(); // NOSONAR + } + + public static void writeFile(String fileFullPath, byte[] byteArry) { + File file = new File(fileFullPath); + if (!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + fos.write(byteArry); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + try { + fos.flush(); + } catch (Exception e) { + } + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static void writeFile(String fileFullPath, String text, String encoding) { + File file = new File(fileFullPath); + if (!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + byte[] bytes; + bytes = text.getBytes(encoding); + fos.write(bytes); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + try { + fos.flush(); + } catch (Exception e) { + } + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static String readFile(String fileFullPath, String encoding) { + InputStream inputStream; + try { + inputStream = new FileInputStream(new File(fileFullPath)); + } catch (FileNotFoundException e1) { + return null; + } + try { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) + result.write(buffer, 0, length); + String string = result.toString(encoding); + return string; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + inputStream.close(); + } catch (IOException e) { + } + } + } + + public static void appendFile(String fileName, String content) { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(fileName, true); + fos.write(content.getBytes()); + fos.write("\r\n".getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + try { + fos.flush(); + } catch (Exception e) { + } + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java new file mode 100644 index 0000000..aa3d6c9 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Logger.java @@ -0,0 +1,200 @@ +/* + * Copyright 2021 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * Usually a logger tool is used like below: + * Logger log = LoggerFactory.getLogger(Xxxx.class); + * log.info("some msg"); + * + * But to simplify, in this project directly use static method: + * Logger.info("some msg"); + * + * @Description: 简版控制台日志打印,从码云上合并来,见 https://gitee.com/drinkjava2/frog/pulls/4 + * @author 栾成翔 + * @Date: 2021/12/07 + */ +@SuppressWarnings("all") +public class Logger { + private static final int SYSTEM_OUT_PRINT = 0; //如设为1,不使用log,直接System.out.print输出 + private static final int LOGGER_STYLE = 0; //风格设定, 0:不输出前缀, 1:输出时间、类、行号等前缀 + private static final String LEV_EL = "DEBUG"; + private static final int LEVEL_INT; + private static final BlockingQueue LOG_LIST = new ArrayBlockingQueue<>(256); + private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); + private static final OutputStream OUTPUT_STREAM = System.out; + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private static final String DELIM_STR = "{}"; + private static final String TAB = "\tat"; + private static final Map LEVEL_MAP = new HashMap<>(); + + static { + LEVEL_MAP.put("DEBUG", 1); + LEVEL_MAP.put("INFO", 2); + LEVEL_MAP.put("WARN", 3); + LEVEL_MAP.put("ERROR", 4); + LEVEL_INT = LEVEL_MAP.get(LEV_EL.toUpperCase()); + new Thread(() -> { + while (true) { + try { + outPutConsole(LOG_LIST.take()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }).start(); + } + + public static void debug(String msg) { + printLog(LEVEL_MAP.get("DEBUG"), msg); + } + + public static void debug(String msg, Object params) { + printLog(LEVEL_MAP.get("DEBUG"), msg, params); + } + + public static void debug(String msg, Object... params) { + printLog(LEVEL_MAP.get("DEBUG"), msg, params); + } + + public static void info(String msg) { + printLog(LEVEL_MAP.get("INFO"), msg); + } + + public static void info(String msg, Object params) { + printLog(LEVEL_MAP.get("INFO"), msg, params); + } + + public static void info(String msg, Object... params) { + printLog(LEVEL_MAP.get("INFO"), msg, params); + } + + public static void warn(String msg) { + printLog(LEVEL_MAP.get("WARN"), msg); + } + + public static void warn(String msg, Object params) { + printLog(LEVEL_MAP.get("WARN"), msg, params); + } + + public static void warn(String msg, Object... params) { + printLog(LEVEL_MAP.get("WARN"), msg, params); + } + + public static void error(String msg) { + printLog(LEVEL_MAP.get("ERROR"), msg); + } + + public static void error(String msg, Object params) { + printLog(LEVEL_MAP.get("ERROR"), msg, params); + } + + public static void error(String msg, Object... params) { + printLog(LEVEL_MAP.get("ERROR"), msg, params); + } + + public static void error(Object param) { + printLog(LEVEL_MAP.get("ERROR"), "", param); + } + + private static void printLog(int levelInt, String msg, Object... params) { + if (levelInt < LEVEL_INT) + return; + if (SYSTEM_OUT_PRINT == 1) + System.out.print(generateMsg(getLevelStr(levelInt), msg, params)); + else + try { + LOG_LIST.put(generateMsg(getLevelStr(levelInt), msg, params)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static String generateMsg(String levelStr, String msg, Object... params) { + if (LOGGER_STYLE == 0) + return formatMsg(msg + LINE_SEPARATOR, null, params); + StackTraceElement stack = Thread.currentThread().getStackTrace()[4]; + String s = "{} [{}][{}#{} {}] - " + msg + LINE_SEPARATOR; + Object[] args = new Object[5 + params.length]; + args[0] = FORMAT.format(System.currentTimeMillis()); + args[1] = levelStr; + args[2] = stack.getClassName(); + args[3] = stack.getMethodName(); + args[4] = stack.getLineNumber(); + + Throwable throwable = null; + if (params.length > 0) { + final Object lastEntry = params[params.length - 1]; + if (lastEntry instanceof Throwable) { + throwable = (Throwable) lastEntry; + System.arraycopy(params, 0, args, 5, params.length - 1); + } else { + System.arraycopy(params, 0, args, 5, params.length); + } + } + return formatMsg(s, throwable, args); + } + + private static String formatMsg(String msg, Throwable throwable, Object... params) { + StringBuilder sb = new StringBuilder(); + int s; + int i = 0; + for (Object o : params) { + s = msg.indexOf(DELIM_STR, i); + if (s > -1) { + sb.append(msg, i, s).append(o); + i = s + 2; + } + } + sb.append(msg, i, msg.length()); + if (null != throwable) { + sb.append(throwable).append(LINE_SEPARATOR); + StackTraceElement[] stack = throwable.getStackTrace(); + for (StackTraceElement element : stack) { + sb.append(TAB).append(element).append(LINE_SEPARATOR); + } + } + return sb.toString(); + } + + private static void outPutConsole(String msg) { + try { + OUTPUT_STREAM.write(msg.getBytes(StandardCharsets.UTF_8)); + OUTPUT_STREAM.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static String getLevelStr(int levelInt) { + switch (levelInt){ + case 1: + return "DEBUG"; + case 2: + return "INFO"; + case 3: + return "WARN"; + case 4: + return "ERROR"; + default: + throw new IllegalStateException("Level " + levelInt + " is unknown."); + } + } +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java new file mode 100644 index 0000000..c112ca6 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/RandomUtils.java @@ -0,0 +1,116 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.util.Random; + +/** + * Random Utilities used in this project + * + * @author Yong Zhu + * @since 1.0 + */ +public class RandomUtils { + + private RandomUtils() { + } + + private static final Random rand = new Random(); + + public static int nextInt(int i) {//返回随机整数,最小为0,最大为n-1 + if (i == 0) + return 0; + return rand.nextInt(i); + } + + public static int nextNegOrPosInt(int n) {//返回随机整数,最小为-(n-1),最大为n-1 + int x = nextInt(n); + if (percent(50)) + return x; + return -x; + } + + public static float nextFloat() { + return rand.nextFloat(); + } + + public static boolean percent(float percent) {// 有百分这percent的机率为true + return rand.nextFloat() * 100 < percent; + } + + public static int vary(int v, int percet) { + if (percent(percet)) + return vary(v); + return v; + } + + public static int vary(int v) {// 随机有大概率小变异,小概率大变异,极小概率极大变异 + if (percent(50)) + v += (nextInt(3) - 0.5); + else if (percent(50)) + v += 2 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 4 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 8 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 16 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 32 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 64 * (nextInt(2) - 0.5); + else if (percent(50)) + v += 100 * (nextInt(2) - 0.5); + return v; + } + +// public static void main(String[] args) { +// int n=0; +// for (int i = 0; i < 100; i++) { +// n=vary(n); +// if(n<0)n=0-n; +// System.out.println(vary(0)); +// } +// } + + public static float vary(float v) {// 随机有大概率小变异,小概率大变异,极小概率极大变异 + if (percent(40)) + v += v * .04 * (nextFloat() - 0.5); // v=v+-.04 + if (percent(10)) + v += v * .103 * (nextFloat() - 0.5); // v=v+-0.1 + else if (percent(5)) + v += v * 1 * (nextFloat() - 0.5); // v=v+-0.4 + else if (percent(2)) + v += v * 4 * (nextFloat() - 0.5); // v=v+-2 + else if (percent(1f)) + v += v * 8 * (nextFloat() - 0.5); // v=v+-6 + return v; + } + + public static int varyInLimit(int v, int from, int to) {// 让返回值在from和to之间随机变异 + int i = vary(v); + if (i < from) + i = from; + if (i > to) + i = to; + return i; + } + + public static float varyInLimit(float v, float from, float to) {// 让返回值在from和to之间随机变异 + float i = vary(v); + if (i < from) + i = from; + if (i > to) + i = to; + return i; + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/StringPixelUtils.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/StringPixelUtils.java new file mode 100644 index 0000000..9541863 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/StringPixelUtils.java @@ -0,0 +1,110 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +/** + * StringPixelUtils used to get pixel array from a given string + * + * 根据给定的字体和字符串,返回它的像素点阵,lettersMap[0][0]是左下角素 + * + * @author Yong Zhu + * @since 2.0.2 + */ +public class StringPixelUtils { + private static final Map lettersMap = new HashMap<>();//cache + + public static byte[][] getSanserif10Pixels(String s) { + return getStringPixels(Font.SANS_SERIF, Font.PLAIN, 10, s); + } + + public static byte[][] getSanserif12Pixels(String s) { + return getStringPixels(Font.SANS_SERIF, Font.PLAIN, 12, s); + } + + public static byte[][] getSanserifItalic10Pixels(String s) { + return getStringPixels(Font.SANS_SERIF, Font.ITALIC, 10, s); + } + + /* 在内存 BufferedImage里输出文本并获取它的像素点 */ + public static byte[][] getStringPixels(String fontName, int fontStyle, int fontSize, String s) { + String key = new StringBuilder(fontName).append("_").append(fontStyle).append("_").append(fontSize).append("_") + .append(s).toString(); + if (lettersMap.containsKey(key)) + return lettersMap.get(key); + Font font = new Font(fontName, fontStyle, fontSize); + + BufferedImage bi = new BufferedImage(fontSize * 10, fontSize * 50, BufferedImage.TYPE_INT_RGB); + Graphics g = bi.getGraphics(); + Graphics2D g2d = (Graphics2D) g; + g2d.setFont(font); + FontMetrics fm = g2d.getFontMetrics(); + int strHeight = fm.getAscent() + fm.getDescent() - 1; + int strWidth = fm.stringWidth(s); + g2d.drawString(s, 0, fm.getAscent() - fm.getLeading() -1); + + int ystart;//改进:在命令行和eclipse下会有不同的空行,所以要用ystart和yend来限定只获取有效象素行数 + loop1: for (ystart = 0; ystart < strHeight; ystart++) + for (int x = 0; x < strWidth; x++) { + if (bi.getRGB(x, ystart) == -1) + break loop1; + } + + int yend; + loop2: for (yend = strHeight; yend >= 0; yend--) + for (int x = 0; x < strWidth; x++) { + if (bi.getRGB(x, yend) == -1) + break loop2; + } + if(yend 0) + System.out.print("*"); + else + System.out.print(" "); + } + System.out.println(); + } + } + + /*- 这个是测试输出,平时不需要用 + public static void main(String[] args) { + System.out.println("==============="); + byte[][] c = getStringPixels(Font.SANS_SERIF, Font.PLAIN, 12, "FROG"); + printPixelArray(c); + System.out.println("==============="); + } + */ +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java new file mode 100644 index 0000000..825f6f1 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree2Util.java @@ -0,0 +1,86 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.util.List; + +import com.gitee.drinkjava2.frog.Env; + +/** + * Tree2Util used to store a pre-order Traversal 2tree array to speed + * + * 这里缓存着一个前序排列的2叉树用来加快速度 + * 这里的2叉树和4叉树的原理类似,只不过4叉树生成2维平面形状, 2叉树用来生成一维线状,对于线状生成速度更快 + * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Tree2Util { + + //EIGHT_TREE store a pre-order Traversal tree array + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); + + public static int[][] TREE2 = new int[NODE_QTY][2]; //2叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的2叉树细胞的size, x值 + + public static byte[] keep = new byte[NODE_QTY]; //这里临时记录树的敲除记录,大于0的值表示要keep, 小于等于0表示要敲除 + + private static byte[] KEEP0 = new byte[NODE_QTY]; //这里保存初值为0的数组常量,可以用System.arraycopy(KEEP, 0, keep, 0, NODE_QTY)快速清空enable数组 + + public static int keepNodeQTY = NODE_QTY; //这里临时记录需keep的节点总数,好用来继续敲除,初始值是全部节点 + + private static int index = 0; + static { + tree2Split(0, Env.BRAIN_SIZE); + } + + static int calculateNodeSize(int n) {//计算2叉树全展开的总节点数 + if (n == 1) + return 1; + return n + calculateNodeSize(n / 2); + } + + //if cube can split, then split it to 8 small cubes + private static void tree2Split(int x, int size) {//如正方形可分裂,就继续递归分裂成4个 + TREE2[index++] = new int[]{size, x}; //这里size类似于深度树中的level,只不过是size从大到小,level是从小到大,原理一样 + if (size == 1) + return; + int half = size / 2;//每个细胞可以分裂成2个size为原来1/2的小细胞 + tree2Split(x, half); + tree2Split(x + half, half); + } + + public static void knockNodesByGene(List gene) {//根据基因,把要敲除的2叉树节点作敲除或保留标记 + System.arraycopy(KEEP0, 0, keep, 0, NODE_QTY);//清空keep数组 + keepNodeQTY = 0; + for (int g : gene) {//g基因,用带符号的2叉数的行号表示,负数表示阴节点要敲除,正数表示是阳节点要保留 + int gLine = Math.abs(g); //基因对应节点的行号 + int size = Tree2Util.TREE2[gLine][0]; //size是基因对应节点的细胞正方形边长 + for (int line = gLine; line < Tree2Util.NODE_QTY; line++) {//从这个g节点开始,往下找节点 + if (line > gLine && Tree2Util.TREE2[line][0] >= size) //如果除了第一个节点外,边长大于等于size,说明节点不是g的子节点,退出 + break; + else {//否则就是g的子节点 + if (g < 0) { //g是阴节点 + if (Tree2Util.keep[line] == 1) //如果是1,表示这个节点将从保留状态转为删除状态 + keepNodeQTY--; + Tree2Util.keep[line] = 0; + } else if (g > 0) { //g是阳节点 + if (Tree2Util.keep[line] == 0) //如果是0,表示这个节点将从删除状态转为保留状态 + keepNodeQTY++; + Tree2Util.keep[line] = 1; + } + } + } + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java new file mode 100644 index 0000000..9b6c803 --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree4Util.java @@ -0,0 +1,88 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.util.List; + +import com.gitee.drinkjava2.frog.Env; + +/** + * Tree4Util used to store a pre-order Traversal 4tree array to speed + * + * 这里缓存着一个前序排列的4叉树用来加快速度 + * 这里的4叉树和4叉树的原理类似,只不过4叉树生成3维形状,4叉树用来生成2维平面形状,对于平面形状生成速度更快 + * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Tree4Util { + + //EIGHT_TREE store a pre-order Traversal tree array + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); + + public static int[][] TREE4 = new int[NODE_QTY][3]; //4叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的4叉树细胞的size, x, y值 + + public static byte[] keep = new byte[NODE_QTY]; //这里临时记录树的敲除记录,大于0的值表示要keep, 小于等于0表示要敲除 + + private static byte[] KEEP0 = new byte[NODE_QTY]; //这里保存初值为0的数组常量,可以用System.arraycopy(KEEP, 0, keep, 0, NODE_QTY)快速清空enable数组 + + public static int keepNodeQTY = NODE_QTY; //这里临时记录需keep的节点总数,好用来继续敲除,初始值是全部节点 + + private static int index = 0; + static { + tree4Split(0, 0, Env.BRAIN_SIZE); + } + + static int calculateNodeSize(int n) {//计算4叉树全展开的总节点数 + if (n == 1) + return 1; + return n * n + calculateNodeSize(n / 2); + } + + //if cube can split, then split it to 8 small cubes + private static void tree4Split(int x, int y, int size) {//如正方形可分裂,就继续递归分裂成4个 + TREE4[index++] = new int[]{size, x, y}; //这里size类似于深度树中的level,只不过是size从大到小,level是从小到大,原理一样 + if (size == 1) + return; + int half = size / 2;//每个细胞可以分裂成4个size为原来1/2的小细胞 + tree4Split(x, y, half); + tree4Split(x + half, y, half); + tree4Split(x, y + half, half); + tree4Split(x + half, y + half, half); + } + + public static void knockNodesByGene(List gene) {//根据基因,把要敲除的4叉树节点作敲除或保留标记 + System.arraycopy(KEEP0, 0, keep, 0, NODE_QTY);//清空keep数组 + keepNodeQTY = 0; + for (int g : gene) {//g基因,用带符号的4叉数的行号表示,负数表示阴节点要敲除,正数表示是阳节点要保留 + int gLine = Math.abs(g); //基因对应节点的行号 + int size = Tree4Util.TREE4[gLine][0]; //size是基因对应节点的细胞正方形边长 + for (int line = gLine; line < Tree4Util.NODE_QTY; line++) {//从这个g节点开始,往下找节点 + if (line > gLine && Tree4Util.TREE4[line][0] >= size) //如果除了第一个节点外,边长大于等于size,说明节点不是g的子节点,退出 + break; + else {//否则就是g的子节点 + if (g < 0) { //g是阴节点 + if (Tree4Util.keep[line] == 1) //如果是1,表示这个节点将从保留状态转为删除状态 + keepNodeQTY--; + Tree4Util.keep[line] = 0; + } else if (g > 0) { //g是阳节点 + if (Tree4Util.keep[line] == 0) //如果是0,表示这个节点将从删除状态转为保留状态 + keepNodeQTY++; + Tree4Util.keep[line] = 1; + } + } + } + } + } + +} diff --git a/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java new file mode 100644 index 0000000..a5fde8b --- /dev/null +++ b/history/014_3cells/src/main/java/com/gitee/drinkjava2/frog/util/Tree8Util.java @@ -0,0 +1,91 @@ +/* Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package com.gitee.drinkjava2.frog.util; + +import java.util.List; + +import com.gitee.drinkjava2.frog.Env; + +/** + * Tree8Util used to store a pre-order Traversal tree array to speed + * + * 这里缓存着一个前序排列的八叉树用来在细胞生成时加快速度和简化运算,关于树结构可用深度树数组来表达的知识可以参见这里:https://my.oschina.net/drinkjava2/blog/1818631 + * + * 另一种思路是先把基因按绝对值排序,然后顺着多叉树先序排列的次序依次设每个细胞的黑白,可能速度更快且可以很快发现无效和重复基因,但我没时间按这个思路来优化了,以后有时间做。 + * + * @author Yong Zhu + * @since 1.0 + */ +public class Tree8Util { + + //EIGHT_TREE store a pre-order Traversal tree array + public static final int NODE_QTY = calculateNodeSize(Env.BRAIN_SIZE); + + public static int[][] TREE8 = new int[NODE_QTY][4]; //八叉数用数组表示,第一维是深度树的行号,第二维是一个整数数组,内容是深度树表示的八叉树细胞的size, x, y, z值 + + public static byte[] keep = new byte[NODE_QTY]; //这里临时记录树的敲除记录,大于0的值表示要keep, 小于等于0表示要敲除 + + private static byte[] KEEP0 = new byte[NODE_QTY]; //这里保存初值为0的数组常量,可以用System.arraycopy(KEEP, 0, keep, 0, NODE_QTY)快速清空enable数组 + + public static int keepNodeQTY = NODE_QTY; //这里临时记录需keep的节点总数,好用来继续敲除,初始值是全部节点 + + private static int index = 0; + static { + tree8Split(0, 0, 0, Env.BRAIN_SIZE); + } + + static int calculateNodeSize(int n) {//计算8叉树全展开的总节点数 + if (n == 1) + return 1; + return n * n * n + calculateNodeSize(n / 2); + } + + //if cube can split, then split it to 8 small cubes + private static void tree8Split(int x, int y, int z, int size) {//如立方体可分裂,就继续递归分裂成8个 + TREE8[index++] = new int[]{size, x, y, z}; //这里size类似于深度树中的level,只不过是size从大到小,level是从小到大,原理一样 + if (size == 1) + return; + int half = size / 2;//每个细胞可以分裂成8个size为原来1/2的小细胞 + tree8Split(x, y, z, half); + tree8Split(x + half, y, z, half); + tree8Split(x, y + half, z, half); + tree8Split(x + half, y + half, z, half); + tree8Split(x, y, z + half, half); + tree8Split(x + half, y, z + half, half); + tree8Split(x, y + half, z + half, half); + tree8Split(x + half, y + half, z + half, half); + } + + public static void knockNodesByGene(List gene) {//根据基因,把要敲除的8叉树节点作敲除或保留标记 + System.arraycopy(KEEP0, 0, keep, 0, NODE_QTY);//清空keep数组 + keepNodeQTY = 0; + for (int g : gene) {//g基因,用带符号的8叉数的行号表示,负数表示阴节点要敲除,正数表示是阳节点要保留 + int gLine = Math.abs(g); //基因对应节点的行号 + int size = Tree8Util.TREE8[gLine][0]; //size是基因对应节点的细胞立方体边长 + for (int line = gLine; line < Tree8Util.NODE_QTY; line++) {//从这个g节点开始,往下找节点 + if (line > gLine && Tree8Util.TREE8[line][0] >= size) //如果除了第一个节点外,边长大于等于size,说明节点不是g的子节点,退出 + break; + else {//否则就是g的子节点 + if (g < 0) { //g是阴节点 + if (Tree8Util.keep[line] == 1) //如果是1,表示这个节点将从保留状态转为删除状态 + keepNodeQTY--; + Tree8Util.keep[line] = 0; + } else if (g > 0) { //g是阳节点 + if (Tree8Util.keep[line] == 0) //如果是0,表示这个节点将从删除状态转为保留状态 + keepNodeQTY++; + Tree8Util.keep[line] = 1; + } + } + } + } + } + +} diff --git a/README_ENG.md b/other/README_ENG.md similarity index 100% rename from README_ENG.md rename to other/README_ENG.md diff --git a/初学者入门介绍.md b/other/初学者入门介绍.md similarity index 99% rename from 初学者入门介绍.md rename to other/初学者入门介绍.md index d645042..4cc22de 100644 --- a/初学者入门介绍.md +++ b/other/初学者入门介绍.md @@ -1,81 +1,81 @@ -## 初学者入门介绍 -注意这个介绍只是针对码云"人工生命"(以后简称Frog)这个项目本身,并不是神经网络知识入门介绍。 -下面的介绍仅针对早期版本,因时间关系没有继续更新,后来陆续更新了好多版后,按时间顺序放在history目录下。大家可以参数Readme中的介绍一个目录一个目录地看,如果想研究源码的,建议看懂003b_simple目录。但是总的来说,源码并不重要,关键是思路。 -最近做的一个比较详细的项目文档请参见history目录下的"Frog_doc_012.doc"。 - -### 项目的运行环境 -Frog项目需要安装JDK8、Maven、GIT客户端,如果已经在本机上安装过的请跳过这一节: -* 首先电脑要安装JDK8运行环境,请百度"JDK8 安装"。 -* 然后需要安装Maven,请百度"Maven 安装"。Frog项目暂时没用到任何第三方库,但是安装Maven会方便它的运行。 -* 然后需要安装Git客户端,请百度"Git 客户端安装"。 -* 以下三项安装好后,打开windows命令行,在任一个目录下运行"git clone https://gitee.com/drinkjava2/frog", 就会从码云网站下载Frog项目的源码,然后打开frog目录,运行其中的run.bat批处理,就可以启动运行了。 -* 如果不能下载源码或启动运行,需要检查是否路径环境变量设置正确,请在桌面“我的电脑”图标上右击,选“属性”->"系统高级设置"->"环境变量设置",在系统变量Path里,确保三个软件的路径设置已加入,例如,在我的电脑上有,Path中包含了以下三个路径设置: -...(略)...;C:\jdk8\bin;F:\DevTools\git\Git\cmd;F:\DevTools\maven3.2.5\bin;...(略)... - 系统变量JAVA_HOME必须存且设为Java安装目录,如我的电脑里设为c:\jdk8 -对于git初学者来说,可用在命令行下输入gitk查看历史记录,并结合“版本提交记录.md”的介绍了解项目的演化过程,如果要回到以前的某个历史版本,可以用git reset命令, 例如以下: -git reset --hard 2fac4ae4084 -可以将项目源码重置到"人工生命v1.0.0"第一版。 - -git reset --hard ae34b07e -可以转回到分组测试的青蛙找食版。 - -在项目根目录,有一个"版本提交记录.md",简单介绍了一些提交的修改内容。 - -### 项目的设计思路 -这个项目的基本设计思路很简单,就是试图模拟大自然的生命进化过程,一步步地从低到高,利用生物的数量、随机变异、优胜劣汰来逐渐获取一个能够适应复杂环境的模拟生命体。 -它是模拟大自然进化,但是技术细节实现上和大自然有明显不同: -大自然的生命首先必须面临一个生存问题,这需要它进化出一系列生理功能如繁殖、进食、肌肉等,而电脑模拟可以完全忽略这些生理要素,只需要关注模拟脑的形成过程就可以了。 -大自然的进化是一个不存在人为设计的过程,这是它的劣势,随时可能因为环境突变灭绝所有生命;而电脑模拟可以人为参与设计,一门心思让模拟生命适应环境,人的参与可以极大地缩短进化模拟过程。 -大自然生命体脑细胞数量可以极其庞大,这是它的优势,电脑虽然速度快,可以用串行模拟并行的脑细胞运算,但是随着细胞数向海量发展,串行机最终还是会力不从心,需要移植到并行机硬件。 -大自然的进化生命样本数量极其庞大,所有的生命都是它的实验品,而电脑模拟在这一点是弱项,只能用极少的样本数量和粗糙的虚拟环境去模拟。 -大自然的进化没有目的,没有参照物,存在即合理,而电脑模拟可以通过解剖和学习大自然的样本来进行拷贝、模拟。 -考虑到以上差异,电脑模拟必须尽量发挥自已的优点,避开缺点,模拟但不是全盘照搬真实生命的进化过程。因为我们的最终目的是要获得一个智能脑。 - -### 算法 -这个项目的一个特点就是重视实践胜过重视算法。目前神经网络研究主要偏重于模式识别等算法,对于如何构造出一个类似人脑这样复杂的、整体化的脑结构并没有给出一个按步就班可以实现的途径,而且笔者认为利用算法的搭配,有可能在搭建脑结构过程陷入误区,复杂的大脑结构可能不能用人为搭建的方式来构造,而必须通过让电脑自动进化出脑结构这种方式来完成。因为人的参与有两个问题:1.人可能犯错. 2.人的速度太慢,不利于用电脑的高速度来循环迭代,淘汰错误模型。 -如果说大自然有算法的话,那就只有一个算法:随机试错。不合理的生理结构、不合理的脑结构,都被大自然给淘汰掉了,剩下的肯定是最合理的。 -Frog项目的模拟也一样,如果有算法的话,"随机数"就是算法之王,排在第一位。其它的算法或模型,如模式识别等,都受"随机数"这个算法之王管辖。 -"随机数"要和大样本数量、变异、优胜劣汰等原则一起使用,这样就可以保证正确的方案一定会出现,而且正确的方案一定会最后保留下来。例如: -当前版本中,Frog有四个运动脑细胞,可以控制青蛙进行上下左右移动,还有四个感光脑细胞,能够看到四个不同方向是否有食物,我想让这个青蛙在看到食物时向它移动,但是不能使用硬编码,在算法上是怎么实现的呢? 方法就是每次青蛙诞生时会随机生成许多脑细胞,它的输入和输出触突位于脑内的任意随机位置,如果碰巧输入区在感光细胞位置,输出区在运动细胞位置,而且方向正确,那么这个青蛙当然会看到食物就向这个方向运动,最终会吃到更多的食物,会在生存竞争中胜出。 -这是一个最简单的例子,随机数可以解决很多复杂的问题,今后的脑的结构自动分化、模式识别算法中各个参数的调整等,都可以考虑用随机试错、适者生存的方式来解决。一招鲜,吃遍天,大自然也就只有随机试错这一个算法,但是它就是用这唯一一个算法造出了人类。 - -### 程序源码导读 -这是一个Java项目,分为三大模块: Application模块、Env模块、Frog模块 - -#### Application模块 -见Application.java。这个模块用于项目的启动、关闭等基础服务,Windows环境下可以用run.bat来启动它查看演示。读它的源码需要了解一些Java Swing作图基本知识。 - -#### Env模块 -见Env.java,这个模块模拟一个生物生存区,用不同形状的图形点阵来表达和模拟食物、天敌、障碍等物体,这个虚拟空间由程序员全权控制和设计,将随着Frog的脑进化而不断变得越来越复杂,通过完成人为设计的一个又一个任务来引导青蛙的进化。 -Env.java中的可调整参数说明(请手工修改这些参数进行不同的测试,前5个参数很重要): -``` -SHOW_SPEED: 调整实验的速度(1~1000),值越小则越慢。 -DELETE_EGGS: 每次运行是否先删除保存的蛋,如果设为false,将不删除保存的蛋,会接着上次的测试结果续继运行。 -EGG_QTY: 每次允许Frog下多少个蛋,通常下蛋取值在10~1000之间。蛋保存着我们测试的结果。实验的最终目标就是获得一批蛋。 -FROG_PER_EGG: 每个蛋可以孵出多少个青蛙。 -SCREEN: 分屏测试,一轮测试可以分为多个批次进行,这里是屏数。每轮总的青蛙数量=EGG_QTY * FROG_PER_EGG, 每屏青蛙数=总数/SCREEN -ENV_WIDTH: 虚拟环境的宽度大小,通常取值100~1000左右 -ENV_HEIGHT: 虚拟环境高度大小,通常取值100~1000左右 -FROG_BRAIN_DISP_WIDTH: Frog的脑图在屏幕上的显示大小,通常取值100~1000左右 -STEPS_PER_ROUND: 每轮测试步数, 每一步相当于脑思考的一桢,所有青蛙的脑神经元被遍历一次。 -FOOD_QTY:食物的数量,食物越多,则Frog的生存率就越高,能量排名靠前的一批Frog可以下蛋,其余的被淘汰。 -``` -#### Frog模块 -见Frog.java, 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什么都一样。Frog不是生命的全盘模拟,它主要模拟生物的脑结构和功能,它主要具备以下与脑功能相关的器官: -* 运动器官: (见MoveUp.java,MoveDown.java等)与运动神经元相连,目前只有4个动作:上下左右。简单地,它只是随便在脑区域指定了四个区,当这些区激活后,青蛙会向相应方向移动。 -* 进食器官:(见Eat.java)当Frog与食物的坐标重合时,食物会被从Env中删除,并相应增加Frog的能量值,并激活Frog的进食感觉器官(Happy.java)。随时间流逝青蛙能量将减少,能量耗尽则Frog死亡。 -* 视觉感觉器官: (见Eye.java)这是脑模型的一部分,在实验中先固定随意取脑内一片神经元区作为视网膜,这个视网膜上目前只有四个感光细胞,今后眼的感光细胞数量会参与进化,但目前笔者还没有头绪。 -* 进食奖励感觉器官: (见Happy.java)当青蛙吃掉食物后,这个器官所在区域内的所有神经元的输入触突激活。 -* 痛觉感觉器官: (见Pain.java)当青蛙靠近边界时激发,这个器官的作用是模拟痛觉,将来青蛙接近天敌时,这个器官也会激活。至于青蛙本体是否感觉到痛苦,这不在程序员考虑范围之内,反正这个器官激活后,它应该表现出很痛苦的应激反应,表现不出来的已经被自然淘汰了。我们只从青蛙的外在表现判断它的主观感受。 -* 脑内细胞组: (见Group.java抽象类及其子类),Group类是器官的子类,它代表一组功能相似的脑细胞,目前只有一个随机连结实例(RandomConnectGroup.java),它实现的算法是在指定矩形区内随机选取两点作为脑细胞的触突感受区和触突输出区,这个连接会在下一代青蛙生成时遗传下来,如果这个连接从没有用到,则有很大的机率被丢弃掉。另一方面如果用到了但不确,这个连接会连带它的主人青蛙一起被淘汰掉。随机连接模仿生物体天生的、可以遗传的本能条件反射,这不是一个很有用的反射,将来会被更多的后天形成的条件反射覆盖掉。 - -Group在脑活动中不起作用,可以把Group比作播种机,把种子排列好后,就撒手不管了,在遗传过程中有一个fat参数,如果细胞活跃多,则Group保留及变异的可能性大,反之则舍弃掉。 -Group是器官的一种,所以蛋里存放着所有Group的位置、大小、内部参数等信息,但是蛋里面不保存具体的细胞。这样通过控制有多少个"播种机",就可以控制大脑的结构了,这样可以缩小蛋(eggs.ser)的尺寸。 原则上Group遗传的一下代与父代是同一个播种(算法)类型。 - -Group.java类的vary方法需要重写,每个Group实例负责自已的变异(如位置、大小、参数、数量变化),基本原则是用进废退,通常有小变异,但有极小的概率大变异。 -Group.java类的DrawOnBrainPicture方法需要重写,每个Group实例负责自已在脑图上的显示绘制。 - -目前青蛙脑还没有引入更多的算法(如记忆、模式识别功能),也就是说这个青蛙的开发还处在起步阶段,脑的结构是程序员要解决的问题,也是要获取的最终目标。脑模型的生成由电脑优胜夯汰、循环迭代进化生成,但这个模型的进化过程还是必须由程序员来掌控,一步步探索出来。 -另外这个项目不要局限于已有的编码,而要着眼于它的总体思路,大胆试错,现有的编码只是暂时的,随时都可能被推翻。 - +## 初学者入门介绍 +注意这个介绍只是针对码云"人工生命"(以后简称Frog)这个项目本身,并不是神经网络知识入门介绍。 +下面的介绍仅针对早期版本,因时间关系没有继续更新,后来陆续更新了好多版后,按时间顺序放在history目录下。大家可以参数Readme中的介绍一个目录一个目录地看,如果想研究源码的,建议看懂003b_simple目录。但是总的来说,源码并不重要,关键是思路。 +最近做的一个比较详细的项目文档请参见history目录下的"Frog_doc_012.doc"。 + +### 项目的运行环境 +Frog项目需要安装JDK8、Maven、GIT客户端,如果已经在本机上安装过的请跳过这一节: +* 首先电脑要安装JDK8运行环境,请百度"JDK8 安装"。 +* 然后需要安装Maven,请百度"Maven 安装"。Frog项目暂时没用到任何第三方库,但是安装Maven会方便它的运行。 +* 然后需要安装Git客户端,请百度"Git 客户端安装"。 +* 以下三项安装好后,打开windows命令行,在任一个目录下运行"git clone https://gitee.com/drinkjava2/frog", 就会从码云网站下载Frog项目的源码,然后打开frog目录,运行其中的run.bat批处理,就可以启动运行了。 +* 如果不能下载源码或启动运行,需要检查是否路径环境变量设置正确,请在桌面“我的电脑”图标上右击,选“属性”->"系统高级设置"->"环境变量设置",在系统变量Path里,确保三个软件的路径设置已加入,例如,在我的电脑上有,Path中包含了以下三个路径设置: +...(略)...;C:\jdk8\bin;F:\DevTools\git\Git\cmd;F:\DevTools\maven3.2.5\bin;...(略)... + 系统变量JAVA_HOME必须存且设为Java安装目录,如我的电脑里设为c:\jdk8 +对于git初学者来说,可用在命令行下输入gitk查看历史记录,并结合“版本提交记录.md”的介绍了解项目的演化过程,如果要回到以前的某个历史版本,可以用git reset命令, 例如以下: +git reset --hard 2fac4ae4084 +可以将项目源码重置到"人工生命v1.0.0"第一版。 + +git reset --hard ae34b07e +可以转回到分组测试的青蛙找食版。 + +在项目根目录,有一个"版本提交记录.md",简单介绍了一些提交的修改内容。 + +### 项目的设计思路 +这个项目的基本设计思路很简单,就是试图模拟大自然的生命进化过程,一步步地从低到高,利用生物的数量、随机变异、优胜劣汰来逐渐获取一个能够适应复杂环境的模拟生命体。 +它是模拟大自然进化,但是技术细节实现上和大自然有明显不同: +大自然的生命首先必须面临一个生存问题,这需要它进化出一系列生理功能如繁殖、进食、肌肉等,而电脑模拟可以完全忽略这些生理要素,只需要关注模拟脑的形成过程就可以了。 +大自然的进化是一个不存在人为设计的过程,这是它的劣势,随时可能因为环境突变灭绝所有生命;而电脑模拟可以人为参与设计,一门心思让模拟生命适应环境,人的参与可以极大地缩短进化模拟过程。 +大自然生命体脑细胞数量可以极其庞大,这是它的优势,电脑虽然速度快,可以用串行模拟并行的脑细胞运算,但是随着细胞数向海量发展,串行机最终还是会力不从心,需要移植到并行机硬件。 +大自然的进化生命样本数量极其庞大,所有的生命都是它的实验品,而电脑模拟在这一点是弱项,只能用极少的样本数量和粗糙的虚拟环境去模拟。 +大自然的进化没有目的,没有参照物,存在即合理,而电脑模拟可以通过解剖和学习大自然的样本来进行拷贝、模拟。 +考虑到以上差异,电脑模拟必须尽量发挥自已的优点,避开缺点,模拟但不是全盘照搬真实生命的进化过程。因为我们的最终目的是要获得一个智能脑。 + +### 算法 +这个项目的一个特点就是重视实践胜过重视算法。目前神经网络研究主要偏重于模式识别等算法,对于如何构造出一个类似人脑这样复杂的、整体化的脑结构并没有给出一个按步就班可以实现的途径,而且笔者认为利用算法的搭配,有可能在搭建脑结构过程陷入误区,复杂的大脑结构可能不能用人为搭建的方式来构造,而必须通过让电脑自动进化出脑结构这种方式来完成。因为人的参与有两个问题:1.人可能犯错. 2.人的速度太慢,不利于用电脑的高速度来循环迭代,淘汰错误模型。 +如果说大自然有算法的话,那就只有一个算法:随机试错。不合理的生理结构、不合理的脑结构,都被大自然给淘汰掉了,剩下的肯定是最合理的。 +Frog项目的模拟也一样,如果有算法的话,"随机数"就是算法之王,排在第一位。其它的算法或模型,如模式识别等,都受"随机数"这个算法之王管辖。 +"随机数"要和大样本数量、变异、优胜劣汰等原则一起使用,这样就可以保证正确的方案一定会出现,而且正确的方案一定会最后保留下来。例如: +当前版本中,Frog有四个运动脑细胞,可以控制青蛙进行上下左右移动,还有四个感光脑细胞,能够看到四个不同方向是否有食物,我想让这个青蛙在看到食物时向它移动,但是不能使用硬编码,在算法上是怎么实现的呢? 方法就是每次青蛙诞生时会随机生成许多脑细胞,它的输入和输出触突位于脑内的任意随机位置,如果碰巧输入区在感光细胞位置,输出区在运动细胞位置,而且方向正确,那么这个青蛙当然会看到食物就向这个方向运动,最终会吃到更多的食物,会在生存竞争中胜出。 +这是一个最简单的例子,随机数可以解决很多复杂的问题,今后的脑的结构自动分化、模式识别算法中各个参数的调整等,都可以考虑用随机试错、适者生存的方式来解决。一招鲜,吃遍天,大自然也就只有随机试错这一个算法,但是它就是用这唯一一个算法造出了人类。 + +### 程序源码导读 +这是一个Java项目,分为三大模块: Application模块、Env模块、Frog模块 + +#### Application模块 +见Application.java。这个模块用于项目的启动、关闭等基础服务,Windows环境下可以用run.bat来启动它查看演示。读它的源码需要了解一些Java Swing作图基本知识。 + +#### Env模块 +见Env.java,这个模块模拟一个生物生存区,用不同形状的图形点阵来表达和模拟食物、天敌、障碍等物体,这个虚拟空间由程序员全权控制和设计,将随着Frog的脑进化而不断变得越来越复杂,通过完成人为设计的一个又一个任务来引导青蛙的进化。 +Env.java中的可调整参数说明(请手工修改这些参数进行不同的测试,前5个参数很重要): +``` +SHOW_SPEED: 调整实验的速度(1~1000),值越小则越慢。 +DELETE_EGGS: 每次运行是否先删除保存的蛋,如果设为false,将不删除保存的蛋,会接着上次的测试结果续继运行。 +EGG_QTY: 每次允许Frog下多少个蛋,通常下蛋取值在10~1000之间。蛋保存着我们测试的结果。实验的最终目标就是获得一批蛋。 +FROG_PER_EGG: 每个蛋可以孵出多少个青蛙。 +SCREEN: 分屏测试,一轮测试可以分为多个批次进行,这里是屏数。每轮总的青蛙数量=EGG_QTY * FROG_PER_EGG, 每屏青蛙数=总数/SCREEN +ENV_WIDTH: 虚拟环境的宽度大小,通常取值100~1000左右 +ENV_HEIGHT: 虚拟环境高度大小,通常取值100~1000左右 +FROG_BRAIN_DISP_WIDTH: Frog的脑图在屏幕上的显示大小,通常取值100~1000左右 +STEPS_PER_ROUND: 每轮测试步数, 每一步相当于脑思考的一桢,所有青蛙的脑神经元被遍历一次。 +FOOD_QTY:食物的数量,食物越多,则Frog的生存率就越高,能量排名靠前的一批Frog可以下蛋,其余的被淘汰。 +``` +#### Frog模块 +见Frog.java, 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什么都一样。Frog不是生命的全盘模拟,它主要模拟生物的脑结构和功能,它主要具备以下与脑功能相关的器官: +* 运动器官: (见MoveUp.java,MoveDown.java等)与运动神经元相连,目前只有4个动作:上下左右。简单地,它只是随便在脑区域指定了四个区,当这些区激活后,青蛙会向相应方向移动。 +* 进食器官:(见Eat.java)当Frog与食物的坐标重合时,食物会被从Env中删除,并相应增加Frog的能量值,并激活Frog的进食感觉器官(Happy.java)。随时间流逝青蛙能量将减少,能量耗尽则Frog死亡。 +* 视觉感觉器官: (见Eye.java)这是脑模型的一部分,在实验中先固定随意取脑内一片神经元区作为视网膜,这个视网膜上目前只有四个感光细胞,今后眼的感光细胞数量会参与进化,但目前笔者还没有头绪。 +* 进食奖励感觉器官: (见Happy.java)当青蛙吃掉食物后,这个器官所在区域内的所有神经元的输入触突激活。 +* 痛觉感觉器官: (见Pain.java)当青蛙靠近边界时激发,这个器官的作用是模拟痛觉,将来青蛙接近天敌时,这个器官也会激活。至于青蛙本体是否感觉到痛苦,这不在程序员考虑范围之内,反正这个器官激活后,它应该表现出很痛苦的应激反应,表现不出来的已经被自然淘汰了。我们只从青蛙的外在表现判断它的主观感受。 +* 脑内细胞组: (见Group.java抽象类及其子类),Group类是器官的子类,它代表一组功能相似的脑细胞,目前只有一个随机连结实例(RandomConnectGroup.java),它实现的算法是在指定矩形区内随机选取两点作为脑细胞的触突感受区和触突输出区,这个连接会在下一代青蛙生成时遗传下来,如果这个连接从没有用到,则有很大的机率被丢弃掉。另一方面如果用到了但不确,这个连接会连带它的主人青蛙一起被淘汰掉。随机连接模仿生物体天生的、可以遗传的本能条件反射,这不是一个很有用的反射,将来会被更多的后天形成的条件反射覆盖掉。 + +Group在脑活动中不起作用,可以把Group比作播种机,把种子排列好后,就撒手不管了,在遗传过程中有一个fat参数,如果细胞活跃多,则Group保留及变异的可能性大,反之则舍弃掉。 +Group是器官的一种,所以蛋里存放着所有Group的位置、大小、内部参数等信息,但是蛋里面不保存具体的细胞。这样通过控制有多少个"播种机",就可以控制大脑的结构了,这样可以缩小蛋(eggs.ser)的尺寸。 原则上Group遗传的一下代与父代是同一个播种(算法)类型。 + +Group.java类的vary方法需要重写,每个Group实例负责自已的变异(如位置、大小、参数、数量变化),基本原则是用进废退,通常有小变异,但有极小的概率大变异。 +Group.java类的DrawOnBrainPicture方法需要重写,每个Group实例负责自已在脑图上的显示绘制。 + +目前青蛙脑还没有引入更多的算法(如记忆、模式识别功能),也就是说这个青蛙的开发还处在起步阶段,脑的结构是程序员要解决的问题,也是要获取的最终目标。脑模型的生成由电脑优胜夯汰、循环迭代进化生成,但这个模型的进化过程还是必须由程序员来掌控,一步步探索出来。 +另外这个项目不要局限于已有的编码,而要着眼于它的总体思路,大胆试错,现有的编码只是暂时的,随时都可能被推翻。 + 更多的文档类资料、外部链接资源等存放在other目录下,原创文章以作者ID加下划线打头,比如我写的文章是以drinkjava2_开头。也欢迎大家上传自己的文章或资源到这个目录里。 \ No newline at end of file diff --git a/捐款记录.md b/other/捐款记录.md similarity index 100% rename from 捐款记录.md rename to other/捐款记录.md diff --git a/版本提交记录.md b/other/版本提交记录.md similarity index 99% rename from 版本提交记录.md rename to other/版本提交记录.md index 8b728d7..2d17daf 100644 --- a/版本提交记录.md +++ b/other/版本提交记录.md @@ -1,248 +1,248 @@ -## 版本提交记录(只记录重要更新) - -在history目录里存放着比较重大的历史版本,可以直接运行这些子目录里的run.bat进行演示。 - -如果想要回到Frog项目的以前任一个提交,可以结合gitk命令和参考本提交记录对每个更新的简介,用git reset命令回复到以前任一个提交版本,例如用: -git reset --hard ae34b07e 可以转回到2019-08-04提交的分组测试的找食版本。 - - -### 2018-1-3 -项目启动,主要是文字方面的一些构想。 - -### 2019-3-11 1.0.0版, Commit:Go frog go! -开发环境完成,演示第一个人工生命诞生。但是所有Frog脑部为空,因为运动神经元被短路,只能固定向一个方向运动。 -这是第一个发布版,演示了生命的随机进化和优胜劣汰。 -![result1](https://gitee.com/drinkjava2/frog/raw/master/result1.gif) - -### 2019-3-18, Commit:Brain picture! -添加了脑结构图形,用于调试用,可以显示第一个胜出的Frog的脑结构,但是运动神经元依然被短路,只能固定向一个方向运动。 -有了脑结构图,就可以防止所有Frog都被淘汰掉,还不知道问题发生在哪里。可以有针对性地改进进化算法、环境的参数改进。 - -### 2019-03-20, 1.0.1版, Commit:Add a button -添加了一个按钮,可以显示、隐藏第一个胜出的Frog的脑结构图,但是运动神经元依然被短路 - -### 2019-03-21, 1.0.2版, Commit:I'm hungry -在脑区添加Hungry,删除随机运动的硬编码,改成由Hungry区来驱动,一旦frog能量小于10000,hungry区的所有脑神经元的input区激活,如果这些神经元的输出区位于move区,则作相应的移动。这是一个更随机一点的运动,不再总是固定向一个方向。 - -### 2019-03-27, 1.0.3版, Commit:Shrink & Sperm -添加了"卵+精子->受精卵"的模拟,这是为了实现生物多样性。添加了每次添加一批随机神经元,但是只保留激活过的,如果某组神经元从没被用到(激活过),则有很大的可能不会将这组神经元添加到蛋中(用进废退规则)。 - -### 2019-03-29, Commit:Rainbow -更正一个小Bug,BrainStructure的zone显示的半径是实际的一半,用彩虹色而不是随机数来表示CellGroup的细胞数量,色彩越靠后表示细胞数越多。 - -### 2019-04-01, Commit:Cleanup -做一些debug清理,每个Frog不再保留egg的副本,“卵+精子->受精卵”算法改进一下,不能简单两两相加,而是随机取一个精子的cellgroup。 - -### 2019-04-06, Commit:Windows align -还是做一些清理, 可以自由调整虚拟环境、脑图的显示大小。下面的打算是将mouth、leg、hungry等区移到蛋里去,允许进化,而不是作为Frog的硬编码存在。 - -### 2019-04-07, Commit:Organ -引入Organ类,将mouth、leg、hungry等作为Organ器官移到蛋里去,而不是作为Frog的硬编码存在,这样架构更清晰,而且便于以后将Organ参与遗传、变异、进化。 - -### 2019-04-08, Commit:Eye shortcut -添加眼睛,能够看到四个正方向的食物,但是自然选择的结果是眼睛和移动区短路了,眼睛起的作用并不大,因为如果有两个方向同时出现食物,目前青蛙是不能判断的。 -下面要考虑逻辑了,也就是思考判断能力(后天条件反射的建立)。 - -### 2019-04-12, Commit:Random frog -没做大改动,只是将青蛙改成按整个地图随机分布,看起来眼睛的作用就比较明显了,比起随机运动,明显食物被吃掉更多。 -![resut2](https://gitee.com/drinkjava2/frog/raw/master/result2.gif) - -### 2019-05-23, Commit:2 eyes no helps -没做大改动,只是演示添加两个眼睛后,对它的进化没有帮助,到此为此,逐渐看出问题了,没有记忆能力和模式识别能力。目前存在两个比较大的硬编码,导致它不能进一步进化:1.用CellGroup这种硬编码方式,导致在Frog的生存期不能产生记忆功能,而需要多次淘汰,这不符合现实中青蛙从小学到大这样的实际过程,它不需要死很多次。另一个问题是眼睛部分存在硬编码,因此只能起到感光作用,但是不具备根据外在图像进行模式识别能力。所以下面要开始进行非常大的结构改进,。将把CellGroup作为器官引入,但是它的内部细胞是动态生成的,而且不是随机生成的,而是任两个细胞在它的区内活跃就生成新的细胞(将来也可以参与新建细胞)。CellGroup的数量、大小、网格密度(直接影响到神经元生成多少和算法快慢)会参与遗传和进化,快乐和痛苦器官会对新细胞生成的类型有影响。fat参数用来指示它的肥胖度。Fat高的遗传时会保留,并可能变大、变小、内部允行连接数更多、分化成更多CellGroup,但是它的内部连接(新建的细胞)不会参与遗传,这样每个个体都出生时都是一张白纸,虽然也许CellGroup已经进化得很多层很复杂。同一个位置可以存在多个CellGroup,这样由多层大小、位置不同的Layer就同时具备了模式识别和记忆功能,而且这个算法比较简单,很好理解。大范围的Cellgroup可以解释条件反射的形成(两件不相干的事之间形成联系),小范围的Cellgroup可以解释模式识别(相邻感光细胞同时激活,经过层层处理后,汇总到最上层的某个Cellgroup的激活)。而所有这些CellGroup的形成(结构和层级)都可以用非常简单的"用进废退"规则(Fat值控制遗传、随机变异和适者生存来探索最优解)来最终进化出来。 - -### 2019-06-13, Commit: Happy & Pain -主要做了一些清理,将所有器官移到单独的类里,删除OrganDesc类。将一些类(如Applicaton移到根包下)移到不同的包下。这个版本是比较大的一个重构,最大的进步是将算法当成一个器官引入,当然,这个版本只存在一个随机连接两端的算法,以后会扩充。 -另外,顺手加上了Happy和Pain两个器官,分别对应进食愉快感和痛苦感,后者在靠近边界时激发。观查它的表现,果然不出所料,痛苦感立即生效,有一些Frog移动到边界后就不再前进,而是顺着边界溜下去了,不傻,但是Happy器官没有生效,这也很显然,因为Happy属于进食反射链的一部分,在没有记忆器官(算法)引入之前,是没有途径使用上进食奖励信号的。 -![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) - -### 2019-06-26, Commit: Back to many connections -找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,权值这种人为设计的算法居然比不过随机试错,失败。先暂时去掉Pain器官,Pain的加入并没有提高找食效率,必须与感光细胞合用才能知道是哪个方向的边界,下个版本急需引入记忆功能,也就是说要将感光细胞的活跃和痛苦器官的活跃关联起来。 - -### 2019-06-28, Commit: New eye & dynamic show brain -为了更方便青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能的开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。另外每次运行前打印往生咒,以示对生命的尊重。 - -### 2019-07-28, Commit: Trap & Active & Chance -这还是一个常规版本,建立在随机连接、优胜劣汰基础上。主要有以下改动: -1. 在Env区中间加了一个陷阱区Trap,以增加趣味性,青蛙如果走到陷阱区就死掉,结果自然选择的结果是青蛙会绕开陷阱区。 -2. 青蛙增加一个Active器官,它的作用是一直保持激活,如果有神经元触突位于这个区就会驱动神经元兴奋,这个器官经实践证明比Hungry器官驱动更能提高找食效率。 -3. 青蛙增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。 -从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 -以下为运行图像: -![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) - -### 2019-08-04, Commit: Screen group test -引入分屏测试功能,如果青蛙数量多,可以分屏来测试,每屏青蛙的数量可以少到只有1只。 - -### 2019-08-05 commit: Seesaw -有了分屏测试功能后,顺便随手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: -![result5](https://gitee.com/drinkjava2/frog/raw/master/result5_seesaw.gif) -这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,只会让青蛙找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,今后将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 -青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,但我认为这不重要,目前最高优先级是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 - -### 2019-09-09 commit: start work on 3d -开始进行3D脑的实际编程。 - -### 2019-9-09 到 2019-10-06 之间的6次提交 -主要进行脑框架的显示和字母试别测试环境的搭建,还没开始进行利用器官进行脑细胞播种的工作。这一阶段基本思路是在每一轮的测试过程前半段随机显示ABCD其中的一个字母(即激活视网膜所在的脑区),并同时激活一个任意脑区。在下半段则只激活这个字母的点阵,然后检测对应的这个脑区是否也会激活,如果激活的话,将会增加青蛙的能量值,让它在生存竟争中胜出,这一步是要完成基本的模式识别功能,框架已搭好,但器官的随机生成还没进行,这一步比较复杂,除了器官的大小位置等参数外,神经元的参数也多,比方说输入、输出光子的方向、正负、数量,能量吸收、释放比例,输入输出阀值、疲劳值、疲劳阀值等,这么多参数要利用随机生成器官的方式来筛选,需要的样本数量多,运行时间会比较长。早期是视网膜和识别区在脑长方体的同一侧,后来的提交改为将视网膜移到左侧,也就是说视觉与识别区(对应耳朵的语音区)在物理上呈90度正交,以方便观察和编程。 - -## 版本提交记录(只记录重要更新) - -如果想要运行Frog项目的以前版本,可以结合gitk命令和参考本提交记录对每个更新的简介,用git reset命令回复到以前任一个版本,例如用: -git reset --hard ae34b07e 可以转回到2019-08-04提交的分组测试的找食版本。 - - - -### 2018-1-3 -项目启动,主要是文字方面的一些构想。 - -### 2019-3-11 1.0.0版, Commit:Go frog go! -开发环境完成,演示第一个人工生命诞生。但是所有Frog脑部为空,因为运动神经元被短路,只能固定向一个方向运动。 -这是第一个发布版,演示了生命的随机进化和优胜劣汰。 -![result1](https://gitee.com/drinkjava2/frog/raw/master/result1.gif) - -### 2019-3-18, Commit:Brain picture! -添加了脑结构图形,用于调试用,可以显示第一个胜出的Frog的脑结构,但是运动神经元依然被短路,只能固定向一个方向运动。 -有了脑结构图,就可以防止所有Frog都被淘汰掉,还不知道问题发生在哪里。可以有针对性地改进进化算法、环境的参数改进。 - -### 2019-03-20, 1.0.1版, Commit:Add a button -添加了一个按钮,可以显示、隐藏第一个胜出的Frog的脑结构图,但是运动神经元依然被短路 - -### 2019-03-21, 1.0.2版, Commit:I'm hungry -在脑区添加Hungry,删除随机运动的硬编码,改成由Hungry区来驱动,一旦frog能量小于10000,hungry区的所有脑神经元的input区激活,如果这些神经元的输出区位于move区,则作相应的移动。这是一个更随机一点的运动,不再总是固定向一个方向。 - -### 2019-03-27, 1.0.3版, Commit:Shrink & Sperm -添加了"卵+精子->受精卵"的模拟,这是为了实现生物多样性。添加了每次添加一批随机神经元,但是只保留激活过的,如果某组神经元从没被用到(激活过),则有很大的可能不会将这组神经元添加到蛋中(用进废退规则)。 - -### 2019-03-29, Commit:Rainbow -更正一个小Bug,BrainStructure的zone显示的半径是实际的一半,用彩虹色而不是随机数来表示CellGroup的细胞数量,色彩越靠后表示细胞数越多。 - -### 2019-04-01, Commit:Cleanup -做一些debug清理,每个Frog不再保留egg的副本,“卵+精子->受精卵”算法改进一下,不能简单两两相加,而是随机取一个精子的cellgroup。 - -### 2019-04-06, Commit:Windows align -还是做一些清理, 可以自由调整虚拟环境、脑图的显示大小。下面的打算是将mouth、leg、hungry等区移到蛋里去,允许进化,而不是作为Frog的硬编码存在。 - -### 2019-04-07, Commit:Organ -引入Organ类,将mouth、leg、hungry等作为Organ器官移到蛋里去,而不是作为Frog的硬编码存在,这样架构更清晰,而且便于以后将Organ参与遗传、变异、进化。 - -### 2019-04-08, Commit:Eye shortcut -添加眼睛,能够看到四个正方向的食物,但是自然选择的结果是眼睛和移动区短路了,眼睛起的作用并不大,因为如果有两个方向同时出现食物,目前青蛙是不能判断的。 -下面要考虑逻辑了,也就是思考判断能力(后天条件反射的建立)。 - -### 2019-04-12, Commit:Random frog -没做大改动,只是将青蛙改成按整个地图随机分布,看起来眼睛的作用就比较明显了,比起随机运动,明显食物被吃掉更多。 -![resut2](https://gitee.com/drinkjava2/frog/raw/master/result2.gif) - -### 2019-05-23, Commit:2 eyes no helps -没做大改动,只是演示添加两个眼睛后,对它的进化没有帮助,到此为此,逐渐看出问题了,没有记忆能力和模式识别能力。目前存在两个比较大的硬编码,导致它不能进一步进化:1.用CellGroup这种硬编码方式,导致在Frog的生存期不能产生记忆功能,而需要多次淘汰,这不符合现实中青蛙从小学到大这样的实际过程,它不需要死很多次。另一个问题是眼睛部分存在硬编码,因此只能起到感光作用,但是不具备根据外在图像进行模式识别能力。所以下面要开始进行非常大的结构改进,。将把CellGroup作为器官引入,但是它的内部细胞是动态生成的,而且不是随机生成的,而是任两个细胞在它的区内活跃就生成新的细胞(将来也可以参与新建细胞)。CellGroup的数量、大小、网格密度(直接影响到神经元生成多少和算法快慢)会参与遗传和进化,快乐和痛苦器官会对新细胞生成的类型有影响。fat参数用来指示它的肥胖度。Fat高的遗传时会保留,并可能变大、变小、内部允行连接数更多、分化成更多CellGroup,但是它的内部连接(新建的细胞)不会参与遗传,这样每个个体都出生时都是一张白纸,虽然也许CellGroup已经进化得很多层很复杂。同一个位置可以存在多个CellGroup,这样由多层大小、位置不同的Layer就同时具备了模式识别和记忆功能,而且这个算法比较简单,很好理解。大范围的Cellgroup可以解释条件反射的形成(两件不相干的事之间形成联系),小范围的Cellgroup可以解释模式识别(相邻感光细胞同时激活,经过层层处理后,汇总到最上层的某个Cellgroup的激活)。而所有这些CellGroup的形成(结构和层级)都可以用非常简单的"用进废退"规则(Fat值控制遗传、随机变异和适者生存来探索最优解)来最终进化出来。 - -### 2019-06-13, Commit: Happy & Pain -主要做了一些清理,将所有器官移到单独的类里,删除OrganDesc类。将一些类(如Applicaton移到根包下)移到不同的包下。这个版本是比较大的一个重构,最大的进步是将算法当成一个器官引入,当然,这个版本只存在一个随机连接两端的算法,以后会扩充。 -另外,顺手加上了Happy和Pain两个器官,分别对应进食愉快感和痛苦感,后者在靠近边界时激发。观查它的表现,果然不出所料,痛苦感立即生效,有一些Frog移动到边界后就不再前进,而是顺着边界溜下去了,不傻,但是Happy器官没有生效,这也很显然,因为Happy属于进食反射链的一部分,在没有记忆器官(算法)引入之前,是没有途径使用上进食奖励信号的。 -![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) - -### 2019-06-26, Commit: Back to many connections -找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,权值这种人为设计的算法居然比不过随机试错,失败。先暂时去掉Pain器官,Pain的加入并没有提高找食效率,必须与感光细胞合用才能知道是哪个方向的边界,下个版本急需引入记忆功能,也就是说要将感光细胞的活跃和痛苦器官的活跃关联起来。 - -### 2019-06-28, Commit: New eye & dynamic show brain -为了更方便青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能的开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。另外每次运行前打印往生咒,以示对生命的尊重。 - -### 2019-07-28, Commit: Trap & Active & Chance -这还是一个常规版本,建立在随机连接、优胜劣汰基础上。主要有以下改动: -1. 在Env区中间加了一个陷阱区Trap,以增加趣味性,青蛙如果走到陷阱区就死掉,结果自然选择的结果是青蛙会绕开陷阱区。 -2. 青蛙增加一个Active器官,它的作用是一直保持激活,如果有神经元触突位于这个区就会驱动神经元兴奋,这个器官经实践证明比Hungry器官驱动更能提高找食效率。 -3. 青蛙增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。 -从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 -以下为运行图像: -![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) - -### 2019-08-04, Commit: Screen group test -引入分屏测试功能,如果青蛙数量多,可以分屏来测试,每屏青蛙的数量可以少到只有1只。 - -### 2019-08-05 commit: Seesaw -有了分屏测试功能后,顺便随手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: -![result5](https://gitee.com/drinkjava2/frog/raw/master/result5_seesaw.gif) -这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,只会让青蛙找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,今后将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 -青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,但我认为这不重要,目前最高优先级是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 - -### 2019-09-09 commit: start work on 3d -开始进行3D脑的实际编程。 - -### 2019-9-09 到 2019-10-06 之间的6次提交 -主要进行脑框架的显示和字母试别测试环境的搭建,还没开始进行利用器官进行脑细胞播种的工作。这一阶段基本思路是在每一轮的测试过程前半段随机显示ABCD其中的一个字母(即激活视网膜所在的脑区),并同时激活一个任意脑区。在下半段则只激活这个字母的点阵,然后检测对应的这个脑区是否也会激活,如果激活的话,将会增加青蛙的能量值,让它在生存竟争中胜出,这一步是要完成基本的模式识别功能,框架已搭好,但器官的随机生成还没进行,这一步比较复杂,除了器官的大小位置等参数外,神经元的参数也多,比方说输入、输出光子的方向、正负、数量,能量吸收、释放比例,输入输出阀值、疲劳值、疲劳阀值等,这么多参数要利用随机生成器官的方式来筛选,需要的样本数量多,运行时间会比较长。早期是视网膜和识别区在脑长方体的同一侧,后来的提交改为将视网膜移到左侧,也就是说视觉与识别区(对应耳朵的语音区)在物理上呈90度正交,以方便观察和编程。 - -### 2019-11-03 commit: Two waves -原来最小三维数组的单元格名为Cube,后改为Room,最后又改为cell。器官不再是直接播种Cell了,而是播种Cell或在已存在的Cell里添加行为Action,这个提交模拟了代表视觉信号和听力信号的两个波的传播,下一步的工作是将这两个波关联起来,实现模式识别,基本原理见评论中的果冻比喻,正在编程中。 -另个这个提交添加了t、f、l、r,x五个键来在脑图上选择顶视、前视、左视、右视、斜视这5个方向的视图。 - -### 2019-11-11 commit: Done letter test -这是个比较重要的更新,也是切换到3D脑的第一个正式版本更新,它实现了ABCD四个字母的识别。测试时分别用ABCD四个字母,并同时加上一个声音信号,模拟体全息存贮。识别时只激活视网膜区,并且采用变形后的字体,即小一号的斜体字,从显示结果来看,识别效果还是非常好的。另外这个模式识别的工作原理是双向的,如果只单单激活听力区,也会在视网膜区成像的。(如果要做到这点,需要将LetterTester.java中的seeImage和hearSound两行注释互换一下,并去除Cell.java中的59和60两行,这两行代码的作用是阻止光子逆向传播到视网膜上)。 -这个模式识别的原理比较简单,不仅算法简单,而且可能符合人脑的工作模式,它可以进行图像到声音的关联,也可以实现声音到图像成像的逆关联,另外还有两个重要优点:1.它可以同时处理多维的信号,也就是说可以同时处理多个图片、声音等信号。 2.它的训练速度非常快,没有采用什么海量的大数据来进行训练,只要有关联的信号,哪怕信号只出现一两次,它都会自动将它们关联起来。 -有了模式识别,以后的工作就好办了。今后将在这个模式识别基础上进行扩展,进行多参数优化自动生成器官,声音的编码,把小蛇引入到虚拟环境等等一系列更复杂有趣的任务。 - -### 2019-11-16 commit: Still done letter test -上次2019-11-11的更新有大bug,有多个字母出现时将不能区分,这次提交更正过来。到此为止,基本完成了模式识别的原理验证过程,即如果字母的象素点与训练图片的重点合越多,则听力区收到的反向红色光子数就越多,这是一个简单、直观的模式识别方法,以后可以通过将声音分成多个小区编码,并统计每个区收到多少反向的光子总数来判断是哪个字母图像输入。原理验证完成后,今后将考虑怎样才能让青蛙自动向这个方向进化,而不是由手工来搭建这个模式识别模型,因为一来参数太多,要减少手工的干预,二来这个原理不光是用到模式识别,其它信号处理(如快感、痛觉信号与行为信号之间的关联)都要用到类似的细胞逻辑。模式识别功能是无法绕过去的,但是一旦原理被证实,以后就可以有意地引导或者说设计青蛙向这个包含这个功能的方向进化。 -另外,这次更新暂停功能加强了,可以在任意时刻暂停,并加上脑图的剖面显示,以方便调试,新增了空格、方向快捷键,现在汇总所有脑图快捷键如下: -T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标操作:缩放旋转平移 - -### 2019-11-26 commit: Chinese test -这次更新用汉字"对酒当歌人生几何"来测试模式识别,优化了一下程序,目前这个图像识别基本没有容错性,图像像素多的会干拢像素少的文字的识别。下面考虑拉大听力信号间隔,以及引入侧抑制等机制(如果一个洞中砸进了光子,但是却和这个洞不同向,有可能产生负值的反向光子,这个负值与角度差有关),这和算法上的侧抑制很象,世界上的道理都是相通的。以后算法上的卷积、深度学习等现成的成果,也可以考虑融入进来,用图形化表示。反过来说,目前以算法进行的神经网络研究,如果借签这个项目的基本思路,把输入输出器官和适应环境进化做为重点,采用遗传淘汰的方式调整算法架构本身,尽量减少人为的设计,最后达到的行为表现可能和这个人工生命项目是一致的。我走图形化是没办法,因为基础差,但是精通算法的人如果明白我的意思,也可能很快做出表现比较复杂的人工生命来,毕竟算法研究已经到了很高的水平了,是现成的。 - -### 2019-12-05 commit: add history folder -重整理了一下目录,将当前工作版本放在core目录下, 比较重大的历史版本放在history目录下,以方便初学者直接运行各个历史版本,而不需要使用git reset命令去手工回到以前的历史版本。同时,如果有未完成的子功能研究(如模式识别,见005_letter_test目录),也可以开一个子目录在history里,以后有时间再去慢慢研究这个子功能。 - -2019-12-27 在history\003a_legs目录下(依然是2维脑)尝试给青蛙加两条腿,看它能不能自动学会走路。一条腿位于下方,负责左右移动,一条腿位于右侧,负责上下移动,每条腿有抬腿、落腿、转动和相应的感觉细胞。只有当腿落下且转动,而且另一条脚抬起来时青蛙才会位移,具体什么时候抬腿、什么时候转动腿完全由随机数决定。经过一段时间的生存汰淘之后,青蛙会进化出会利用两条腿走路了,但需要的时间非常长,约几个小时之后才达到最高吃食率50%左右,走路风格也比较诡异,是小碎步而不是大踏步。但至少这是青蛙第一次利用两条腿来走路,还是有点意义的,这证明生命进化中就算神经元随机排布,进化出眼睛和腿也是非常简单自然的事。这个实验只给青蛙加了两条腿,但同理如果有四条或更多的腿它应该也是可以随机进化出来的。 -![result7](result7_legs.gif) - -### 2020-06-03 加入小蛇Snake进来吃青蛙(未完成) -基本思路是小蛇是有形状的,青蛙要能进化到看到小蛇就跑开,这样就开始正式引入了模式识别 - -### 2020-06-19 正式完成加入小蛇进来,有多处bug改进 -蛇只能看到青蛙,青蛙只能看到蛇的图形。并改core目录下项目包名从github到gitee(码云),负值连线、中间连线引入,用一条斜线来暂时代替蛇的图形,好开始模式识别。 - -### 2020-06-20 更正SnakeBigEye的bug,并显示为两条线代表蛙的图像(或舌头),便于简化模式别。设定小蛇只能看到青蛙,青蛙只能看到蛇(严格说是蛇的舌头)。可以看到小蛇会追着青蛙,而青蛙会躲开小蛇,当然也有躲不开被吃掉的。除了引入负值连线用蓝色线条来表示外,技术细节上倒没有什么突破,但这个实验有趣的地方在于它证实了就算是完全随机的排列脑细胞,在长期的优胜劣汰后,生命也会进化出捕食和逃避行为。即然可以进化出捕食和逃避行为,而生命进化又会向越来越复杂的方向进化,所以这个原理可以解释为意识的萌芽了。高等生命的意识,本质上也无非就是大自然随机运动产生的一种复杂现象而已。 -![result8](result8_snake.gif) - -2020-06-26 更新readme.md. 下一步的工作将移回到体全息存贮的模式识别,因为青蛙需要这个模式识别功能来更好地分辨出蛇和食物的区别,而体全息存贮个人感觉有很多潜力可挖,它有两个最大的优点:一是可以小样本学习,二是可以同时处理多维的信息输入输出。 - -2021-01-23 语言的诞生。好不容易,告别漫长的2020,去年出的题目我自己解出来了,下面是答案,运行根目录或core目录下的run.bat,可能看到类似下面的运行画面: -![result9](result9_earthquake.gif) -详细解说:这个题目的模拟环境被简化成左右两个区,设定地震发生时(用红框表示)会扣除所有青蛙的能量,但是只有位于左侧的青蛙可以看到地震发生和停止,右区的青蛙不能看到地震发生和停止,但是青蛙有发音器官和听音器官,如果左侧的青蛙发出叫声是可以被右侧的青蛙听到的。看到地震发生、看到地震停止、发出叫声、听到叫声、跳起、落地这6个器官分别对应6种器官并都偶然进化出来(这个无需证明),这个实验的目的是要验证青蛙会不会在环境逼迫下,在这6种器官对应的脑细胞间形成神经关联。结果可以看到,左侧的青蛙看到地震后,跳在空中(用黄色表示),并发出叫声,然后右侧的青蛙听到叫声后也跳在空中。左侧的青蛙通过叫声信号传递了信息给右侧的青蛙,从而让右侧的青蛙避开了它看不见的地震伤害。这是一个成功的群体进化的演示,它证明了即使是随机生成神经细胞连线,也可以进化出生物的发音-听力功能,也就是说进化出初步的语言功能。 - -2021-05-15 细胞分裂的演示 -这是pama_1234做的,他的项目位于这里:[细胞画蛇](https://gitee.com/pama1234/cell-painting-snake), 此更新只更新了readme.md - -2021-07-04 依然是模式识别演示 -新增history\005a和005b两个分支目录,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来。这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有模式识别功能的模拟生命体,也就是说实现上面发布的任务。 -![result11](result11_letter_test.gif) - -2021-07-04 依然是模式识别演示 -新增history\005a和005b两个分支目录,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来: -![result11](result11_letter_test.gif) -这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但考虑到实现这些功能的复杂性,我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有简单模式识别功能的模拟生命体,也就是说实现上面发布的任务。 - -2021-08-13 演示同时处理多个方向的信号 -位于history\005a1目录下,演示用5个声母和5个韵母的组合来关联到25个图像的识别,这样可以减少声音输入区的数量。它的另一个目的是演示体全息存贮的工作模式可以同时处理多个方向的信号。这个演示分辨率极差,只有约一半的识别率,但我不打算继续改进了。 -![result12](result12_letter_test2.png) - -2021-10-13 失败的细胞分裂尝试 -这次本来想模仿生物细胞的分裂,从一个细胞开始分裂出任意指定的三维形状,并设计了split、goto等基因命令,但是做来做去做不出结果,细胞们就象跳蚤一样乱跑不听使唤,最终还是决定放弃,细胞分裂这个算法太难了。细胞分裂的优点是更“象”生物,而且估计可以利用分形原理缩小基因的长度,基因相当于一种自带循环和条件判断的计算机语言。 -最终生成三维形状这个目标还是借助简单遗传算法完成,通过细胞在相邻位置随机生成,并在基因里记录每个细胞的坐标的方式来实现,基因命令被删得只剩一个了,就是随机生成细胞。 -用遗传算法来生成任意形状,就好象一个画家在画画,但是画什么根本不知道,只知道听从旁边人打分,画的好就打高分,画的不好就打低分,这样一直循环下去,最终画的内容只由打分的人决定。目前速度上还有改进余地,比如让新细胞有更多变异率。 -![result13](result13_frog3d.gif) - -2021-11-08 成功的细胞分裂尝试 -这次的细胞分裂算法采用自顶向下的策略,也就是从单个细胞开始,一个细胞分裂成8个(因为1个正方体切三刀正好是8个小正方体)这种方式来进行。这种方案不是从目标形状的局部开始填充,而是从毛胚、从目标的粗轮廓开始利用遗传算法细化,直到细化出每个细节。这种方案的优点是更接近生物实际,符合“从总体到局部”的正常逻辑,而且有高效的基因压缩存储率,为了说明存储率这点,大家可以看看下图左面这个树结构,猜一猜要存储它最少需要多少个字节? -![depth_tree](depth_tree.png) -答案是最少只需要一个整数7就可以表达这个树结构了。说一下原理:首先所有树结构都可以用行号+深度的方式来表达,详见我的博客[基于前序遍历的无递归的树形结构](https://my.oschina.net/drinkjava2/blog/1818631),获取子树时可以避免递归访问,其次因为采用基因敲除的方式,只需要记录被敲除的树节点的行号和深度就就可以了,最后因为上例固定采用3叉树分形结构,根据行号就可以算出它的深度,所以深度值也可以省略,最后只用一个行号就可以表达这整棵树了。 -下图是这个算法的动画,可以看出它与上次的演示是不同的分裂模式,是先有总体后有细节。项目中实际采用的是8叉树,深度用细胞边长表示: -![result14](result14_wa3d.gif) -细胞分裂算法一方面可以利用来生成和优化物理形状(比方虚拟风叶、翅膀、受力结构等形状),另一方面它和神经网络的形成算法是有共通点的,因为众所周知心脏形状、血管网络、大脑神经网络都是由基因控制细胞分裂出来的。所以以后有可能利用这个算法来自动生成和优化神经网络触突三维空间分布结构。 -顺便说一下,自顶向下的问题是它一旦主分支被误敲除,就不容易补回去,实际的生物例子就是人眼结构还不如章鱼。自然界是用生物的多样化和环境的连续化来保证各种主分支都有尝试。我们电脑模拟要尽量保持环境(任务)的连续化,从低到高一步步走,个别时候考虑结合其它算法给错误的分支打补丁。 - -2021-11-26 多参数的细胞分裂 -这次更新放在history\009a_fish3d目录下,只是在上次细胞分裂基础上改进了速度,将三维cell对象数组改为long型数组,节省了对象创建和销毁开销,速度有明显改进。long类型有64位,所以一个细胞可以有64维独立参数,应该够用了。下图是一个6倍速显示的三维鱼分裂生成动图。它一共用到4维参数,分别是细胞的位置和三个不同颜色的细胞色彩参数,每一维分别由各自的细胞分裂算法单独控制: -![result15](result15_fish3d.gif) -这个动画的每一帧是细胞分裂到最小不可再分的最终结果,而且是从400个青蛙中生存下来的最佳个体,这就是遗传算法,等价于穷举法。这个16x16x16的大立方体要理解成第一个细胞,只是画的大了而已。以后等有时间可以做1个细胞分成8个,8个变64个的动画,能更好地演示分裂的中间过程。 -细胞分裂研究到此结束,下面要开始生成神经网络空间结构了。我的思路是,脑结构也无非就是三维细胞的空间排布而已,细胞有各种参数,比如触突长度、方向、密度、信号收发阀值、信号强度、信号遗忘曲线等,只要不超过64个参数,就可以用分裂算法来随机试错把神经网络的空间结构给试出来。分裂算法的优点是遵循从主干到细节的生成次序,如果要完成的任务(即外界信号输入输出)也是从简单到复杂,就很可能正好符合这个脑的空间结构生成顺序。 - +## 版本提交记录(只记录重要更新) + +在history目录里存放着比较重大的历史版本,可以直接运行这些子目录里的run.bat进行演示。 + +如果想要回到Frog项目的以前任一个提交,可以结合gitk命令和参考本提交记录对每个更新的简介,用git reset命令回复到以前任一个提交版本,例如用: +git reset --hard ae34b07e 可以转回到2019-08-04提交的分组测试的找食版本。 + + +### 2018-1-3 +项目启动,主要是文字方面的一些构想。 + +### 2019-3-11 1.0.0版, Commit:Go frog go! +开发环境完成,演示第一个人工生命诞生。但是所有Frog脑部为空,因为运动神经元被短路,只能固定向一个方向运动。 +这是第一个发布版,演示了生命的随机进化和优胜劣汰。 +![result1](https://gitee.com/drinkjava2/frog/raw/master/result1.gif) + +### 2019-3-18, Commit:Brain picture! +添加了脑结构图形,用于调试用,可以显示第一个胜出的Frog的脑结构,但是运动神经元依然被短路,只能固定向一个方向运动。 +有了脑结构图,就可以防止所有Frog都被淘汰掉,还不知道问题发生在哪里。可以有针对性地改进进化算法、环境的参数改进。 + +### 2019-03-20, 1.0.1版, Commit:Add a button +添加了一个按钮,可以显示、隐藏第一个胜出的Frog的脑结构图,但是运动神经元依然被短路 + +### 2019-03-21, 1.0.2版, Commit:I'm hungry +在脑区添加Hungry,删除随机运动的硬编码,改成由Hungry区来驱动,一旦frog能量小于10000,hungry区的所有脑神经元的input区激活,如果这些神经元的输出区位于move区,则作相应的移动。这是一个更随机一点的运动,不再总是固定向一个方向。 + +### 2019-03-27, 1.0.3版, Commit:Shrink & Sperm +添加了"卵+精子->受精卵"的模拟,这是为了实现生物多样性。添加了每次添加一批随机神经元,但是只保留激活过的,如果某组神经元从没被用到(激活过),则有很大的可能不会将这组神经元添加到蛋中(用进废退规则)。 + +### 2019-03-29, Commit:Rainbow +更正一个小Bug,BrainStructure的zone显示的半径是实际的一半,用彩虹色而不是随机数来表示CellGroup的细胞数量,色彩越靠后表示细胞数越多。 + +### 2019-04-01, Commit:Cleanup +做一些debug清理,每个Frog不再保留egg的副本,“卵+精子->受精卵”算法改进一下,不能简单两两相加,而是随机取一个精子的cellgroup。 + +### 2019-04-06, Commit:Windows align +还是做一些清理, 可以自由调整虚拟环境、脑图的显示大小。下面的打算是将mouth、leg、hungry等区移到蛋里去,允许进化,而不是作为Frog的硬编码存在。 + +### 2019-04-07, Commit:Organ +引入Organ类,将mouth、leg、hungry等作为Organ器官移到蛋里去,而不是作为Frog的硬编码存在,这样架构更清晰,而且便于以后将Organ参与遗传、变异、进化。 + +### 2019-04-08, Commit:Eye shortcut +添加眼睛,能够看到四个正方向的食物,但是自然选择的结果是眼睛和移动区短路了,眼睛起的作用并不大,因为如果有两个方向同时出现食物,目前青蛙是不能判断的。 +下面要考虑逻辑了,也就是思考判断能力(后天条件反射的建立)。 + +### 2019-04-12, Commit:Random frog +没做大改动,只是将青蛙改成按整个地图随机分布,看起来眼睛的作用就比较明显了,比起随机运动,明显食物被吃掉更多。 +![resut2](https://gitee.com/drinkjava2/frog/raw/master/result2.gif) + +### 2019-05-23, Commit:2 eyes no helps +没做大改动,只是演示添加两个眼睛后,对它的进化没有帮助,到此为此,逐渐看出问题了,没有记忆能力和模式识别能力。目前存在两个比较大的硬编码,导致它不能进一步进化:1.用CellGroup这种硬编码方式,导致在Frog的生存期不能产生记忆功能,而需要多次淘汰,这不符合现实中青蛙从小学到大这样的实际过程,它不需要死很多次。另一个问题是眼睛部分存在硬编码,因此只能起到感光作用,但是不具备根据外在图像进行模式识别能力。所以下面要开始进行非常大的结构改进,。将把CellGroup作为器官引入,但是它的内部细胞是动态生成的,而且不是随机生成的,而是任两个细胞在它的区内活跃就生成新的细胞(将来也可以参与新建细胞)。CellGroup的数量、大小、网格密度(直接影响到神经元生成多少和算法快慢)会参与遗传和进化,快乐和痛苦器官会对新细胞生成的类型有影响。fat参数用来指示它的肥胖度。Fat高的遗传时会保留,并可能变大、变小、内部允行连接数更多、分化成更多CellGroup,但是它的内部连接(新建的细胞)不会参与遗传,这样每个个体都出生时都是一张白纸,虽然也许CellGroup已经进化得很多层很复杂。同一个位置可以存在多个CellGroup,这样由多层大小、位置不同的Layer就同时具备了模式识别和记忆功能,而且这个算法比较简单,很好理解。大范围的Cellgroup可以解释条件反射的形成(两件不相干的事之间形成联系),小范围的Cellgroup可以解释模式识别(相邻感光细胞同时激活,经过层层处理后,汇总到最上层的某个Cellgroup的激活)。而所有这些CellGroup的形成(结构和层级)都可以用非常简单的"用进废退"规则(Fat值控制遗传、随机变异和适者生存来探索最优解)来最终进化出来。 + +### 2019-06-13, Commit: Happy & Pain +主要做了一些清理,将所有器官移到单独的类里,删除OrganDesc类。将一些类(如Applicaton移到根包下)移到不同的包下。这个版本是比较大的一个重构,最大的进步是将算法当成一个器官引入,当然,这个版本只存在一个随机连接两端的算法,以后会扩充。 +另外,顺手加上了Happy和Pain两个器官,分别对应进食愉快感和痛苦感,后者在靠近边界时激发。观查它的表现,果然不出所料,痛苦感立即生效,有一些Frog移动到边界后就不再前进,而是顺着边界溜下去了,不傻,但是Happy器官没有生效,这也很显然,因为Happy属于进食反射链的一部分,在没有记忆器官(算法)引入之前,是没有途径使用上进食奖励信号的。 +![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) + +### 2019-06-26, Commit: Back to many connections +找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,权值这种人为设计的算法居然比不过随机试错,失败。先暂时去掉Pain器官,Pain的加入并没有提高找食效率,必须与感光细胞合用才能知道是哪个方向的边界,下个版本急需引入记忆功能,也就是说要将感光细胞的活跃和痛苦器官的活跃关联起来。 + +### 2019-06-28, Commit: New eye & dynamic show brain +为了更方便青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能的开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。另外每次运行前打印往生咒,以示对生命的尊重。 + +### 2019-07-28, Commit: Trap & Active & Chance +这还是一个常规版本,建立在随机连接、优胜劣汰基础上。主要有以下改动: +1. 在Env区中间加了一个陷阱区Trap,以增加趣味性,青蛙如果走到陷阱区就死掉,结果自然选择的结果是青蛙会绕开陷阱区。 +2. 青蛙增加一个Active器官,它的作用是一直保持激活,如果有神经元触突位于这个区就会驱动神经元兴奋,这个器官经实践证明比Hungry器官驱动更能提高找食效率。 +3. 青蛙增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。 +从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 +以下为运行图像: +![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) + +### 2019-08-04, Commit: Screen group test +引入分屏测试功能,如果青蛙数量多,可以分屏来测试,每屏青蛙的数量可以少到只有1只。 + +### 2019-08-05 commit: Seesaw +有了分屏测试功能后,顺便随手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: +![result5](https://gitee.com/drinkjava2/frog/raw/master/result5_seesaw.gif) +这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,只会让青蛙找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,今后将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 +青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,但我认为这不重要,目前最高优先级是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 + +### 2019-09-09 commit: start work on 3d +开始进行3D脑的实际编程。 + +### 2019-9-09 到 2019-10-06 之间的6次提交 +主要进行脑框架的显示和字母试别测试环境的搭建,还没开始进行利用器官进行脑细胞播种的工作。这一阶段基本思路是在每一轮的测试过程前半段随机显示ABCD其中的一个字母(即激活视网膜所在的脑区),并同时激活一个任意脑区。在下半段则只激活这个字母的点阵,然后检测对应的这个脑区是否也会激活,如果激活的话,将会增加青蛙的能量值,让它在生存竟争中胜出,这一步是要完成基本的模式识别功能,框架已搭好,但器官的随机生成还没进行,这一步比较复杂,除了器官的大小位置等参数外,神经元的参数也多,比方说输入、输出光子的方向、正负、数量,能量吸收、释放比例,输入输出阀值、疲劳值、疲劳阀值等,这么多参数要利用随机生成器官的方式来筛选,需要的样本数量多,运行时间会比较长。早期是视网膜和识别区在脑长方体的同一侧,后来的提交改为将视网膜移到左侧,也就是说视觉与识别区(对应耳朵的语音区)在物理上呈90度正交,以方便观察和编程。 + +## 版本提交记录(只记录重要更新) + +如果想要运行Frog项目的以前版本,可以结合gitk命令和参考本提交记录对每个更新的简介,用git reset命令回复到以前任一个版本,例如用: +git reset --hard ae34b07e 可以转回到2019-08-04提交的分组测试的找食版本。 + + + +### 2018-1-3 +项目启动,主要是文字方面的一些构想。 + +### 2019-3-11 1.0.0版, Commit:Go frog go! +开发环境完成,演示第一个人工生命诞生。但是所有Frog脑部为空,因为运动神经元被短路,只能固定向一个方向运动。 +这是第一个发布版,演示了生命的随机进化和优胜劣汰。 +![result1](https://gitee.com/drinkjava2/frog/raw/master/result1.gif) + +### 2019-3-18, Commit:Brain picture! +添加了脑结构图形,用于调试用,可以显示第一个胜出的Frog的脑结构,但是运动神经元依然被短路,只能固定向一个方向运动。 +有了脑结构图,就可以防止所有Frog都被淘汰掉,还不知道问题发生在哪里。可以有针对性地改进进化算法、环境的参数改进。 + +### 2019-03-20, 1.0.1版, Commit:Add a button +添加了一个按钮,可以显示、隐藏第一个胜出的Frog的脑结构图,但是运动神经元依然被短路 + +### 2019-03-21, 1.0.2版, Commit:I'm hungry +在脑区添加Hungry,删除随机运动的硬编码,改成由Hungry区来驱动,一旦frog能量小于10000,hungry区的所有脑神经元的input区激活,如果这些神经元的输出区位于move区,则作相应的移动。这是一个更随机一点的运动,不再总是固定向一个方向。 + +### 2019-03-27, 1.0.3版, Commit:Shrink & Sperm +添加了"卵+精子->受精卵"的模拟,这是为了实现生物多样性。添加了每次添加一批随机神经元,但是只保留激活过的,如果某组神经元从没被用到(激活过),则有很大的可能不会将这组神经元添加到蛋中(用进废退规则)。 + +### 2019-03-29, Commit:Rainbow +更正一个小Bug,BrainStructure的zone显示的半径是实际的一半,用彩虹色而不是随机数来表示CellGroup的细胞数量,色彩越靠后表示细胞数越多。 + +### 2019-04-01, Commit:Cleanup +做一些debug清理,每个Frog不再保留egg的副本,“卵+精子->受精卵”算法改进一下,不能简单两两相加,而是随机取一个精子的cellgroup。 + +### 2019-04-06, Commit:Windows align +还是做一些清理, 可以自由调整虚拟环境、脑图的显示大小。下面的打算是将mouth、leg、hungry等区移到蛋里去,允许进化,而不是作为Frog的硬编码存在。 + +### 2019-04-07, Commit:Organ +引入Organ类,将mouth、leg、hungry等作为Organ器官移到蛋里去,而不是作为Frog的硬编码存在,这样架构更清晰,而且便于以后将Organ参与遗传、变异、进化。 + +### 2019-04-08, Commit:Eye shortcut +添加眼睛,能够看到四个正方向的食物,但是自然选择的结果是眼睛和移动区短路了,眼睛起的作用并不大,因为如果有两个方向同时出现食物,目前青蛙是不能判断的。 +下面要考虑逻辑了,也就是思考判断能力(后天条件反射的建立)。 + +### 2019-04-12, Commit:Random frog +没做大改动,只是将青蛙改成按整个地图随机分布,看起来眼睛的作用就比较明显了,比起随机运动,明显食物被吃掉更多。 +![resut2](https://gitee.com/drinkjava2/frog/raw/master/result2.gif) + +### 2019-05-23, Commit:2 eyes no helps +没做大改动,只是演示添加两个眼睛后,对它的进化没有帮助,到此为此,逐渐看出问题了,没有记忆能力和模式识别能力。目前存在两个比较大的硬编码,导致它不能进一步进化:1.用CellGroup这种硬编码方式,导致在Frog的生存期不能产生记忆功能,而需要多次淘汰,这不符合现实中青蛙从小学到大这样的实际过程,它不需要死很多次。另一个问题是眼睛部分存在硬编码,因此只能起到感光作用,但是不具备根据外在图像进行模式识别能力。所以下面要开始进行非常大的结构改进,。将把CellGroup作为器官引入,但是它的内部细胞是动态生成的,而且不是随机生成的,而是任两个细胞在它的区内活跃就生成新的细胞(将来也可以参与新建细胞)。CellGroup的数量、大小、网格密度(直接影响到神经元生成多少和算法快慢)会参与遗传和进化,快乐和痛苦器官会对新细胞生成的类型有影响。fat参数用来指示它的肥胖度。Fat高的遗传时会保留,并可能变大、变小、内部允行连接数更多、分化成更多CellGroup,但是它的内部连接(新建的细胞)不会参与遗传,这样每个个体都出生时都是一张白纸,虽然也许CellGroup已经进化得很多层很复杂。同一个位置可以存在多个CellGroup,这样由多层大小、位置不同的Layer就同时具备了模式识别和记忆功能,而且这个算法比较简单,很好理解。大范围的Cellgroup可以解释条件反射的形成(两件不相干的事之间形成联系),小范围的Cellgroup可以解释模式识别(相邻感光细胞同时激活,经过层层处理后,汇总到最上层的某个Cellgroup的激活)。而所有这些CellGroup的形成(结构和层级)都可以用非常简单的"用进废退"规则(Fat值控制遗传、随机变异和适者生存来探索最优解)来最终进化出来。 + +### 2019-06-13, Commit: Happy & Pain +主要做了一些清理,将所有器官移到单独的类里,删除OrganDesc类。将一些类(如Applicaton移到根包下)移到不同的包下。这个版本是比较大的一个重构,最大的进步是将算法当成一个器官引入,当然,这个版本只存在一个随机连接两端的算法,以后会扩充。 +另外,顺手加上了Happy和Pain两个器官,分别对应进食愉快感和痛苦感,后者在靠近边界时激发。观查它的表现,果然不出所料,痛苦感立即生效,有一些Frog移动到边界后就不再前进,而是顺着边界溜下去了,不傻,但是Happy器官没有生效,这也很显然,因为Happy属于进食反射链的一部分,在没有记忆器官(算法)引入之前,是没有途径使用上进食奖励信号的。 +![resut3](https://gitee.com/drinkjava2/frog/raw/master/result3.gif) + +### 2019-06-26, Commit: Back to many connections +找食效率太低,又改回到4.12的用连接数量代替权值这个逻辑,权值这种人为设计的算法居然比不过随机试错,失败。先暂时去掉Pain器官,Pain的加入并没有提高找食效率,必须与感光细胞合用才能知道是哪个方向的边界,下个版本急需引入记忆功能,也就是说要将感光细胞的活跃和痛苦器官的活跃关联起来。 + +### 2019-06-28, Commit: New eye & dynamic show brain +为了更方便青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能的开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。另外每次运行前打印往生咒,以示对生命的尊重。 + +### 2019-07-28, Commit: Trap & Active & Chance +这还是一个常规版本,建立在随机连接、优胜劣汰基础上。主要有以下改动: +1. 在Env区中间加了一个陷阱区Trap,以增加趣味性,青蛙如果走到陷阱区就死掉,结果自然选择的结果是青蛙会绕开陷阱区。 +2. 青蛙增加一个Active器官,它的作用是一直保持激活,如果有神经元触突位于这个区就会驱动神经元兴奋,这个器官经实践证明比Hungry器官驱动更能提高找食效率。 +3. 青蛙增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。 +从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 +以下为运行图像: +![resut4](https://gitee.com/drinkjava2/frog/raw/master/result4.gif) + +### 2019-08-04, Commit: Screen group test +引入分屏测试功能,如果青蛙数量多,可以分屏来测试,每屏青蛙的数量可以少到只有1只。 + +### 2019-08-05 commit: Seesaw +有了分屏测试功能后,顺便随手加上了一个青蛙走跷跷板自动平衡的演示,它每次只出场一个青蛙, 每轮包括100场测试,大约跑90多轮半个小时(电脑慢)后,出现了下面的画面: +![result5](https://gitee.com/drinkjava2/frog/raw/master/result5_seesaw.gif) +这个版本的目的是为了增加一点趣味性,显得青蛙还是有点"用处"的,省得让人以为这个项目不务正业,只会让青蛙找食。这个版本青蛙的脑结构和找食版的青蛙基本相同,区别只是在于环境不同,也就是说它的表现随着环境而变化,这符合"通用人工智能"的概念,即信号感受器官是统一的(通常是眼睛),但能根据不同的环境完成不同的任务。走跷跷板演示是最后一个2维脑的版本,今后这个项目将沉寂一段较长时间,今后将致力于将青蛙脑重构为3D金字塔形脑结构(见上文),因为这个项目的缺点已经很明显,它不具备对2维图像的模式识别能力,用随机试错的方式只能处理非常简单的、信号在视网膜的固定区域出现的图像信号。 +青蛙的找食效率以及走跷跷板平衡的能力都没有优化到顶点,一些构想中的复杂的器官如“与门”、“或门”(不要怀疑大自然能否进化出这些复杂器官)等都没加上,但我认为这不重要,目前最高优先级是先进行3D脑结构建模,让青蛙能具备2维图形的模式识别(和回忆)功能,这个大的架构重构是它能处理复杂图像信息的立足之本,它的图像识别能力和通常的用上千张图片来训练识别一个图片这种工作模式不同,它是一种通用的,可自动分类识别所有图像的模式,更符合动物脑的工作模式,记住并回忆出某个图像(或任意输入信号场景的组合),可能只需要这种场景重复出现过几次即可,它是一种无外界信号判定,自动分类的识别模式。 + +### 2019-09-09 commit: start work on 3d +开始进行3D脑的实际编程。 + +### 2019-9-09 到 2019-10-06 之间的6次提交 +主要进行脑框架的显示和字母试别测试环境的搭建,还没开始进行利用器官进行脑细胞播种的工作。这一阶段基本思路是在每一轮的测试过程前半段随机显示ABCD其中的一个字母(即激活视网膜所在的脑区),并同时激活一个任意脑区。在下半段则只激活这个字母的点阵,然后检测对应的这个脑区是否也会激活,如果激活的话,将会增加青蛙的能量值,让它在生存竟争中胜出,这一步是要完成基本的模式识别功能,框架已搭好,但器官的随机生成还没进行,这一步比较复杂,除了器官的大小位置等参数外,神经元的参数也多,比方说输入、输出光子的方向、正负、数量,能量吸收、释放比例,输入输出阀值、疲劳值、疲劳阀值等,这么多参数要利用随机生成器官的方式来筛选,需要的样本数量多,运行时间会比较长。早期是视网膜和识别区在脑长方体的同一侧,后来的提交改为将视网膜移到左侧,也就是说视觉与识别区(对应耳朵的语音区)在物理上呈90度正交,以方便观察和编程。 + +### 2019-11-03 commit: Two waves +原来最小三维数组的单元格名为Cube,后改为Room,最后又改为cell。器官不再是直接播种Cell了,而是播种Cell或在已存在的Cell里添加行为Action,这个提交模拟了代表视觉信号和听力信号的两个波的传播,下一步的工作是将这两个波关联起来,实现模式识别,基本原理见评论中的果冻比喻,正在编程中。 +另个这个提交添加了t、f、l、r,x五个键来在脑图上选择顶视、前视、左视、右视、斜视这5个方向的视图。 + +### 2019-11-11 commit: Done letter test +这是个比较重要的更新,也是切换到3D脑的第一个正式版本更新,它实现了ABCD四个字母的识别。测试时分别用ABCD四个字母,并同时加上一个声音信号,模拟体全息存贮。识别时只激活视网膜区,并且采用变形后的字体,即小一号的斜体字,从显示结果来看,识别效果还是非常好的。另外这个模式识别的工作原理是双向的,如果只单单激活听力区,也会在视网膜区成像的。(如果要做到这点,需要将LetterTester.java中的seeImage和hearSound两行注释互换一下,并去除Cell.java中的59和60两行,这两行代码的作用是阻止光子逆向传播到视网膜上)。 +这个模式识别的原理比较简单,不仅算法简单,而且可能符合人脑的工作模式,它可以进行图像到声音的关联,也可以实现声音到图像成像的逆关联,另外还有两个重要优点:1.它可以同时处理多维的信号,也就是说可以同时处理多个图片、声音等信号。 2.它的训练速度非常快,没有采用什么海量的大数据来进行训练,只要有关联的信号,哪怕信号只出现一两次,它都会自动将它们关联起来。 +有了模式识别,以后的工作就好办了。今后将在这个模式识别基础上进行扩展,进行多参数优化自动生成器官,声音的编码,把小蛇引入到虚拟环境等等一系列更复杂有趣的任务。 + +### 2019-11-16 commit: Still done letter test +上次2019-11-11的更新有大bug,有多个字母出现时将不能区分,这次提交更正过来。到此为止,基本完成了模式识别的原理验证过程,即如果字母的象素点与训练图片的重点合越多,则听力区收到的反向红色光子数就越多,这是一个简单、直观的模式识别方法,以后可以通过将声音分成多个小区编码,并统计每个区收到多少反向的光子总数来判断是哪个字母图像输入。原理验证完成后,今后将考虑怎样才能让青蛙自动向这个方向进化,而不是由手工来搭建这个模式识别模型,因为一来参数太多,要减少手工的干预,二来这个原理不光是用到模式识别,其它信号处理(如快感、痛觉信号与行为信号之间的关联)都要用到类似的细胞逻辑。模式识别功能是无法绕过去的,但是一旦原理被证实,以后就可以有意地引导或者说设计青蛙向这个包含这个功能的方向进化。 +另外,这次更新暂停功能加强了,可以在任意时刻暂停,并加上脑图的剖面显示,以方便调试,新增了空格、方向快捷键,现在汇总所有脑图快捷键如下: +T:顶视 F:前视 L:左视 R:右视 X:斜视 方向键:剖视 空格:暂停 鼠标操作:缩放旋转平移 + +### 2019-11-26 commit: Chinese test +这次更新用汉字"对酒当歌人生几何"来测试模式识别,优化了一下程序,目前这个图像识别基本没有容错性,图像像素多的会干拢像素少的文字的识别。下面考虑拉大听力信号间隔,以及引入侧抑制等机制(如果一个洞中砸进了光子,但是却和这个洞不同向,有可能产生负值的反向光子,这个负值与角度差有关),这和算法上的侧抑制很象,世界上的道理都是相通的。以后算法上的卷积、深度学习等现成的成果,也可以考虑融入进来,用图形化表示。反过来说,目前以算法进行的神经网络研究,如果借签这个项目的基本思路,把输入输出器官和适应环境进化做为重点,采用遗传淘汰的方式调整算法架构本身,尽量减少人为的设计,最后达到的行为表现可能和这个人工生命项目是一致的。我走图形化是没办法,因为基础差,但是精通算法的人如果明白我的意思,也可能很快做出表现比较复杂的人工生命来,毕竟算法研究已经到了很高的水平了,是现成的。 + +### 2019-12-05 commit: add history folder +重整理了一下目录,将当前工作版本放在core目录下, 比较重大的历史版本放在history目录下,以方便初学者直接运行各个历史版本,而不需要使用git reset命令去手工回到以前的历史版本。同时,如果有未完成的子功能研究(如模式识别,见005_letter_test目录),也可以开一个子目录在history里,以后有时间再去慢慢研究这个子功能。 + +2019-12-27 在history\003a_legs目录下(依然是2维脑)尝试给青蛙加两条腿,看它能不能自动学会走路。一条腿位于下方,负责左右移动,一条腿位于右侧,负责上下移动,每条腿有抬腿、落腿、转动和相应的感觉细胞。只有当腿落下且转动,而且另一条脚抬起来时青蛙才会位移,具体什么时候抬腿、什么时候转动腿完全由随机数决定。经过一段时间的生存汰淘之后,青蛙会进化出会利用两条腿走路了,但需要的时间非常长,约几个小时之后才达到最高吃食率50%左右,走路风格也比较诡异,是小碎步而不是大踏步。但至少这是青蛙第一次利用两条腿来走路,还是有点意义的,这证明生命进化中就算神经元随机排布,进化出眼睛和腿也是非常简单自然的事。这个实验只给青蛙加了两条腿,但同理如果有四条或更多的腿它应该也是可以随机进化出来的。 +![result7](result7_legs.gif) + +### 2020-06-03 加入小蛇Snake进来吃青蛙(未完成) +基本思路是小蛇是有形状的,青蛙要能进化到看到小蛇就跑开,这样就开始正式引入了模式识别 + +### 2020-06-19 正式完成加入小蛇进来,有多处bug改进 +蛇只能看到青蛙,青蛙只能看到蛇的图形。并改core目录下项目包名从github到gitee(码云),负值连线、中间连线引入,用一条斜线来暂时代替蛇的图形,好开始模式识别。 + +### 2020-06-20 更正SnakeBigEye的bug,并显示为两条线代表蛙的图像(或舌头),便于简化模式别。设定小蛇只能看到青蛙,青蛙只能看到蛇(严格说是蛇的舌头)。可以看到小蛇会追着青蛙,而青蛙会躲开小蛇,当然也有躲不开被吃掉的。除了引入负值连线用蓝色线条来表示外,技术细节上倒没有什么突破,但这个实验有趣的地方在于它证实了就算是完全随机的排列脑细胞,在长期的优胜劣汰后,生命也会进化出捕食和逃避行为。即然可以进化出捕食和逃避行为,而生命进化又会向越来越复杂的方向进化,所以这个原理可以解释为意识的萌芽了。高等生命的意识,本质上也无非就是大自然随机运动产生的一种复杂现象而已。 +![result8](result8_snake.gif) + +2020-06-26 更新readme.md. 下一步的工作将移回到体全息存贮的模式识别,因为青蛙需要这个模式识别功能来更好地分辨出蛇和食物的区别,而体全息存贮个人感觉有很多潜力可挖,它有两个最大的优点:一是可以小样本学习,二是可以同时处理多维的信息输入输出。 + +2021-01-23 语言的诞生。好不容易,告别漫长的2020,去年出的题目我自己解出来了,下面是答案,运行根目录或core目录下的run.bat,可能看到类似下面的运行画面: +![result9](result9_earthquake.gif) +详细解说:这个题目的模拟环境被简化成左右两个区,设定地震发生时(用红框表示)会扣除所有青蛙的能量,但是只有位于左侧的青蛙可以看到地震发生和停止,右区的青蛙不能看到地震发生和停止,但是青蛙有发音器官和听音器官,如果左侧的青蛙发出叫声是可以被右侧的青蛙听到的。看到地震发生、看到地震停止、发出叫声、听到叫声、跳起、落地这6个器官分别对应6种器官并都偶然进化出来(这个无需证明),这个实验的目的是要验证青蛙会不会在环境逼迫下,在这6种器官对应的脑细胞间形成神经关联。结果可以看到,左侧的青蛙看到地震后,跳在空中(用黄色表示),并发出叫声,然后右侧的青蛙听到叫声后也跳在空中。左侧的青蛙通过叫声信号传递了信息给右侧的青蛙,从而让右侧的青蛙避开了它看不见的地震伤害。这是一个成功的群体进化的演示,它证明了即使是随机生成神经细胞连线,也可以进化出生物的发音-听力功能,也就是说进化出初步的语言功能。 + +2021-05-15 细胞分裂的演示 +这是pama_1234做的,他的项目位于这里:[细胞画蛇](https://gitee.com/pama1234/cell-painting-snake), 此更新只更新了readme.md + +2021-07-04 依然是模式识别演示 +新增history\005a和005b两个分支目录,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来。这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有模式识别功能的模拟生命体,也就是说实现上面发布的任务。 +![result11](result11_letter_test.gif) + +2021-07-04 依然是模式识别演示 +新增history\005a和005b两个分支目录,分别演示利用改进版的体全息存贮方案和面全息存贮方案来进行模式识别。可以做到将25个任意图形和它对应的声音信号区关联起来: +![result11](result11_letter_test.gif) +这两个模式的基本原理是基于信号的反向传播,如果一个细胞的两个或多个不同方向同时(或短期内)收到信号,今后只要有一个信号传入,这个细胞将会向其它方向反向发送激活信号。这个模式识别原理非常简单,功能也比较原始,对于变形、扭曲、缩放、缺损的信号识别率很差,但考虑到实现这些功能的复杂性,我近期不打算进一步改进它了,而是打算另起炉灶,用三维空间的细胞分裂+遗传算法的模式,试试看能不能让电脑自动演化出具有简单模式识别功能的模拟生命体,也就是说实现上面发布的任务。 + +2021-08-13 演示同时处理多个方向的信号 +位于history\005a1目录下,演示用5个声母和5个韵母的组合来关联到25个图像的识别,这样可以减少声音输入区的数量。它的另一个目的是演示体全息存贮的工作模式可以同时处理多个方向的信号。这个演示分辨率极差,只有约一半的识别率,但我不打算继续改进了。 +![result12](result12_letter_test2.png) + +2021-10-13 失败的细胞分裂尝试 +这次本来想模仿生物细胞的分裂,从一个细胞开始分裂出任意指定的三维形状,并设计了split、goto等基因命令,但是做来做去做不出结果,细胞们就象跳蚤一样乱跑不听使唤,最终还是决定放弃,细胞分裂这个算法太难了。细胞分裂的优点是更“象”生物,而且估计可以利用分形原理缩小基因的长度,基因相当于一种自带循环和条件判断的计算机语言。 +最终生成三维形状这个目标还是借助简单遗传算法完成,通过细胞在相邻位置随机生成,并在基因里记录每个细胞的坐标的方式来实现,基因命令被删得只剩一个了,就是随机生成细胞。 +用遗传算法来生成任意形状,就好象一个画家在画画,但是画什么根本不知道,只知道听从旁边人打分,画的好就打高分,画的不好就打低分,这样一直循环下去,最终画的内容只由打分的人决定。目前速度上还有改进余地,比如让新细胞有更多变异率。 +![result13](result13_frog3d.gif) + +2021-11-08 成功的细胞分裂尝试 +这次的细胞分裂算法采用自顶向下的策略,也就是从单个细胞开始,一个细胞分裂成8个(因为1个正方体切三刀正好是8个小正方体)这种方式来进行。这种方案不是从目标形状的局部开始填充,而是从毛胚、从目标的粗轮廓开始利用遗传算法细化,直到细化出每个细节。这种方案的优点是更接近生物实际,符合“从总体到局部”的正常逻辑,而且有高效的基因压缩存储率,为了说明存储率这点,大家可以看看下图左面这个树结构,猜一猜要存储它最少需要多少个字节? +![depth_tree](depth_tree.png) +答案是最少只需要一个整数7就可以表达这个树结构了。说一下原理:首先所有树结构都可以用行号+深度的方式来表达,详见我的博客[基于前序遍历的无递归的树形结构](https://my.oschina.net/drinkjava2/blog/1818631),获取子树时可以避免递归访问,其次因为采用基因敲除的方式,只需要记录被敲除的树节点的行号和深度就就可以了,最后因为上例固定采用3叉树分形结构,根据行号就可以算出它的深度,所以深度值也可以省略,最后只用一个行号就可以表达这整棵树了。 +下图是这个算法的动画,可以看出它与上次的演示是不同的分裂模式,是先有总体后有细节。项目中实际采用的是8叉树,深度用细胞边长表示: +![result14](result14_wa3d.gif) +细胞分裂算法一方面可以利用来生成和优化物理形状(比方虚拟风叶、翅膀、受力结构等形状),另一方面它和神经网络的形成算法是有共通点的,因为众所周知心脏形状、血管网络、大脑神经网络都是由基因控制细胞分裂出来的。所以以后有可能利用这个算法来自动生成和优化神经网络触突三维空间分布结构。 +顺便说一下,自顶向下的问题是它一旦主分支被误敲除,就不容易补回去,实际的生物例子就是人眼结构还不如章鱼。自然界是用生物的多样化和环境的连续化来保证各种主分支都有尝试。我们电脑模拟要尽量保持环境(任务)的连续化,从低到高一步步走,个别时候考虑结合其它算法给错误的分支打补丁。 + +2021-11-26 多参数的细胞分裂 +这次更新放在history\009a_fish3d目录下,只是在上次细胞分裂基础上改进了速度,将三维cell对象数组改为long型数组,节省了对象创建和销毁开销,速度有明显改进。long类型有64位,所以一个细胞可以有64维独立参数,应该够用了。下图是一个6倍速显示的三维鱼分裂生成动图。它一共用到4维参数,分别是细胞的位置和三个不同颜色的细胞色彩参数,每一维分别由各自的细胞分裂算法单独控制: +![result15](result15_fish3d.gif) +这个动画的每一帧是细胞分裂到最小不可再分的最终结果,而且是从400个青蛙中生存下来的最佳个体,这就是遗传算法,等价于穷举法。这个16x16x16的大立方体要理解成第一个细胞,只是画的大了而已。以后等有时间可以做1个细胞分成8个,8个变64个的动画,能更好地演示分裂的中间过程。 +细胞分裂研究到此结束,下面要开始生成神经网络空间结构了。我的思路是,脑结构也无非就是三维细胞的空间排布而已,细胞有各种参数,比如触突长度、方向、密度、信号收发阀值、信号强度、信号遗忘曲线等,只要不超过64个参数,就可以用分裂算法来随机试错把神经网络的空间结构给试出来。分裂算法的优点是遵循从主干到细节的生成次序,如果要完成的任务(即外界信号输入输出)也是从简单到复杂,就很可能正好符合这个脑的空间结构生成顺序。 + diff --git a/result20_3cells1.gif b/result20_3cells1.gif new file mode 100644 index 0000000..4c2a405 Binary files /dev/null and b/result20_3cells1.gif differ diff --git a/result20_3cells2.gif b/result20_3cells2.gif new file mode 100644 index 0000000..51b69dd Binary files /dev/null and b/result20_3cells2.gif differ diff --git a/result20_3cells3.gif b/result20_3cells3.gif new file mode 100644 index 0000000..8a007b5 Binary files /dev/null and b/result20_3cells3.gif differ