diff --git a/README.md b/README.md index 45bef15..fb664e5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ ## Frog | 人工生命 -(English introduction please see "README_EN.md") 这是一个人工生命试验项目,最终目标是创建“有自我意识表现”的模拟生命体,技术架构基于02年提出的 [一个人工脑模型](一个人工脑模型.md)。 这个项目永远没有结束的时候,开始于模拟一个简单的生命体,然后是青蛙、狗......, 结束于有“自我意识表现”的人工脑,或者说,结束于被机器人代替人类的那一天。 @@ -56,9 +55,10 @@ Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什 ## 目前进展和成绩 2019.03.11 虚拟环境已建好,可以模拟低等生命的遗传、繁殖、变异、进化现象,但只能往一个方向运动,相当于一个最简单的单细胞生物,还不具备视觉能力,不具备主动找食能力。 -运行run.bat可以查看演示(需要安装Java8和Maven)。 +运行run.bat可以查看演示(需要安装Java8和Maven)。 ![result1](https://gitee.com/drinkjava2/frog/raw/master/result1.gif) 另外每步演示的结果(egg)会存盘在根目根目录下,名为egg.ser,可以删除这个文件以从头开始新的测试。因为还没涉及脑模型的搭建,可以看到有些青蛙跑得飞快,这是自然选择的结果,因为跑在最前面的吃得多。 +一些重要的测试参数如显示区大小、是否每次测试要删除保存的蛋等,请参见Env.java中开头的常量设定,可以手工修改进行不同参数的测试。 2019.03.21 添加了脑图,改进随机运动模式为Hungry区驱动。从脑图上可以直观地观察脑结构,方便调试。 2019.04.01 改进脑图的显示bug, 每一次生成Frog时添加随机神经元,并简单实现"卵+精子->受精卵"算法,以促进种群多样性。 2019-04-12 添加一个简单的眼睛(只有四个感光细胞),自然选择的结果是眼睛被选中,但是和运动区短路了,谈不上智能。但有眼睛后找食效率明显提高了,见下图: @@ -66,7 +66,7 @@ Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什 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-06-28 为了让青蛙看到边界,又加了个新的眼睛,它是一个可自进化的nxn点阵的眼睛,将来会取代只有四个象素点(但能看得远)的老眼睛。到目前为止,依然还没有进行模式识别和记忆功能开发。另外脑图可以动态显示了,用一个红圈标记出被动态跟踪显示的青蛙。 2019-07-28 有以下改动:1.在Env区中间加了一个陷阱区Trap,以增加趣味性,自然选择的结果是青蛙会自动绕开陷阱区。2.增加一个Active器官,它的作用是一直保持激活,发现比Hungry器官驱动更能提高找食效率。3.增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。目前进食奖励信号没用到,白白浪费了。 另外Chance和Eye类里也再次运用了随机试错原理去确定关键参数,效果还不错,有兴趣的可以看一看源码。 @@ -75,13 +75,14 @@ Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什 另外发现青蛙其实是有记忆能力的,因为连接本身就是一种记忆,只不过它没有复杂的模式识别能力,例如给个蛇的图片它就认不出来。以后的工作将以模式识别为重点(当然随机连接看起来很有用,以后还可能保留),基本原理是见note中提到的仿照波传播及全息存储原理,在思维区逆向成像。而且脑可能改成三维结构,并根据逆向成像原理,要将所有输入输出器官全移到三维结构的同一侧(即思维区)。这将会是一个非常大的改动,下面我简单画了一个3D示意图来说明我想象中的这个模式识别和记忆的原理,至于对不对还需要实验来验证: ![3d-model](https://gitee.com/drinkjava2/frog/raw/master/3d-model.gif) 这个模型的最顶层表示眼睛的感光细胞(或任意输入输出细胞),同时也是思维区,红色表示一个长条的图形,兰色表示一个三角图形,如果这两个图形经常有规律地同时出现,就会把它们共有的节点撑大,见紫色的节点,当红色图形单独出现,就会强烈激活紫色节点,然后紫色节点的信号反向传播,就会激活三角图形,反之亦然。这就同时解释了模式识别和记忆(或者说回忆)功能的的原理。一个节点可以被多个感光细胞共享,所以它的存储能力是很强的。而且可能这个原理比较符合生物脑结构。当然,实际编程时,虚拟神经元不一定要排成正立方三角,而可能通过胡乱排放,大致上过得去就行了,也许能乱拳打死老师傅,最终要靠电脑自动随机的排放,然后用优胜劣汰来筛选。目前有个难点是这个记忆功能在思维区成像是如何有条不紊地大体上按串行进行工作的,这个问题先放一放。 +2019-08-04 更新了一个分组测试功能,如果测试青蛙数量太多,可以分批来测试,每轮测试最少的青蛙数量可以少到只有一个,这是用时间来换空间。 ## 版权 | License [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) ## 期望 | Futures -欢迎发issue提出建意或加入开发组,尤其欢迎能接触到超级计算机的同学加入,随着神经元数量增多,对电脑的速度要求会越来越高,很快将达到台式机性能瓶颈,只能通过拖长运行时间来换取大样本数量了。 -另外本项目正式宣布开启哭穷模式,比提交一个pull request还能帮助这个项目开发的,莫过于提交一个红包了,您的赞助将实实在在地转化成我(或其它开发者)的项目开发时间,本项目将建立赞助者名单及收支明细账。 +欢迎发issue、私信等方式提出建议或加入开发组。 +另外本项目开启了哭穷模式,比提交一个pull request还能帮助这个项目开发的,莫过于提交一个红包了,金钱就是时间,您的捐助将只会用于回馈本项目的实际参与开发者。 ## 作者其它开源项目 | Other Projects - [Java持久层工具 jSqlBox](https://gitee.com/drinkjava2/jSqlBox) @@ -92,4 +93,4 @@ Frog: 这是人工生命的主体,目前起名叫青蛙(Frog),其实叫什 ## 关注我 | About Me [Github](https://github.com/drinkjava2) [码云](https://gitee.com/drinkjava2) -微信:yong99819981(如想长期关注本项目、交流信息,或想参与具体项目开发的,请留言加入"人工生命群",如果只是想临时私聊也可以加我好友后再删掉,我不介意) \ No newline at end of file +微信:yong99819981(如想长期关注本项目、交流信息,或想参与具体开发的,请留言加"人工生命群",如果只是想临时私聊也可加我好友后再删掉,我不介意) \ No newline at end of file diff --git a/core/src/main/java/com/github/drinkjava2/frog/Env.java b/core/src/main/java/com/github/drinkjava2/frog/Env.java index 34e8ab5..8cdca51 100644 --- a/core/src/main/java/com/github/drinkjava2/frog/Env.java +++ b/core/src/main/java/com/github/drinkjava2/frog/Env.java @@ -21,14 +21,22 @@ import com.github.drinkjava2.frog.util.RandomUtils; * @author Yong Zhu * @since 1.0 */ -@SuppressWarnings("serial") +@SuppressWarnings("all") public class Env extends JPanel { /** Speed of test */ - public static int SHOW_SPEED = 20; // 测试速度,-1000~1000,可调, 数值越小,速度越慢 + public static final int SHOW_SPEED = 300; // 测试速度,-1000~1000,可调, 数值越小,速度越慢 /** Delete eggs at beginning of each run */ public static final boolean DELETE_EGGS = true;// 每次运行是否先删除保存的蛋 + public static final int EGG_QTY = 30; // 每轮下n个蛋,可调,只有最优秀的前n个青蛙们才允许下蛋 + + public static final int FROG_PER_EGG = 3; // 每个蛋可以孵出几个青蛙 + + public static final int SCREEN = 3; // 分几屏测完, 所以每轮待测青蛙总数=EGG_QTY*FROG_PER_EGG*SCREEN + + public static final int FROG_PER_SCREEN = EGG_QTY * FROG_PER_EGG / SCREEN; // 每屏上显示几个青蛙,这个数值由上面三个参数计算得来 + /** Debug mode will print more debug info */ public static final boolean DEBUG_MODE = false; // Debug 模式下会打印出更多的调试信息 @@ -52,8 +60,6 @@ public class Env extends JPanel { public static final int FOOD_QTY = 1500; // 食物数量, 可调 - public static final int EGG_QTY = 50; // 每轮下n个蛋,可调,只有最优秀的前n个青蛙们才允许下蛋 - private static final Random r = new Random(); // 随机数发生器 public static boolean pause = false; // 暂停按钮按下将暂停测试 @@ -64,12 +70,12 @@ public class Env extends JPanel { private static final int TRAP_HEIGHT = 10; // 陷阱宽, 0~200 - public List frogs = new ArrayList<>(); + public static List frogs = new ArrayList<>(); // 这里存放所有待测的青蛙,可能分几次测完,由FROG_PER_SCREEN大小来决定 - public List eggs; + public static List eggs = new ArrayList<>(); // 这里存放从磁盘载入或上轮下的蛋,每个蛋可能生成1~n个青蛙, static { - System.out.println("唵缚悉波罗摩尼莎诃!"); // 往生咒 + System.out.println("唵缚悉波罗摩尼莎诃!"); // 杀生前先打印往生咒,见码云issue#IW4H8 if (DELETE_EGGS) EggTool.deleteEggs(); } @@ -113,29 +119,30 @@ public class Env extends JPanel { return false; } - private void rebuildFrogAndFood() { - frogs.clear(); + private void rebuildFood() { for (int i = 0; i < ENV_WIDTH; i++) {// 清除食物 - for (int j = 0; j < ENV_HEIGHT; j++) { + for (int j = 0; j < ENV_HEIGHT; j++) foods[i][j] = false; - } } - Random rand = new Random(); - for (int i = 0; i < eggs.size(); i++) {// 创建青蛙,每个蛋生成4个蛙,并随机取一个别的蛋作为精子 - int loop = 4; - if (i <= 3)// 0,1,2,3 - loop = 6; - if (i >= (eggs.size() - 4)) - loop = 2; + for (int i = 0; i < Env.FOOD_QTY; i++) // 生成食物 + foods[RandomUtils.nextInt(ENV_WIDTH)][RandomUtils.nextInt(ENV_HEIGHT)] = true; + } + + private void rebuildFrogs() { + frogs.clear(); + for (int i = 0; i < eggs.size(); i++) {// 创建青蛙,每个蛋生成n个蛙,并随机取一个别的蛋作为精子 + int loop = FROG_PER_EGG; + if (eggs.size() > 20) { // 如果数量多,进行一些优化,让排名靠前的Egg多孵出青蛙 + if (i < FROG_PER_EGG)// 0,1,2,3 + loop = FROG_PER_EGG + 1; + if (i >= (eggs.size() - FROG_PER_EGG)) + loop = FROG_PER_EGG - 1; + } for (int j = 0; j < loop; j++) { Egg zygote = new Egg(eggs.get(i), eggs.get(r.nextInt(eggs.size()))); - frogs.add(new Frog(rand.nextInt(ENV_WIDTH), rand.nextInt(ENV_HEIGHT), zygote)); + frogs.add(new Frog(RandomUtils.nextInt(ENV_WIDTH), RandomUtils.nextInt(ENV_HEIGHT), zygote)); } } - - System.out.println("Created " + frogs.size() + " frogs"); - for (int i = 0; i < Env.FOOD_QTY; i++) // 生成食物 - foods[rand.nextInt(ENV_WIDTH)][rand.nextInt(ENV_HEIGHT)] = true; } private void drawFood(Graphics g) { @@ -159,90 +166,104 @@ public class Env extends JPanel { format100.setMaximumFractionDigits(2); } - private String foodFoundPercent() {// 计算找食效率 + private static int foodFoundAmount() {// 统计找食数等 int leftfood = 0; for (int x = 0; x < ENV_WIDTH; x++) for (int y = 0; y < ENV_HEIGHT; y++) if (foods[x][y]) leftfood++; - return format100.format((FOOD_QTY - leftfood) * 1.00 / FOOD_QTY); + return FOOD_QTY - leftfood; + } + + private String foodFoundCountText() {// 统计找食数等 + int foodFound = foodFoundAmount(); + int maxFound = 0; + for (Frog f : frogs) + if (f.ateFood > maxFound) + maxFound = f.ateFood; + return new StringBuilder("找食率:").append(format100.format(foodFound * 1.00 / FOOD_QTY)).append(", 平均: ") + .append(foodFound * 1.0f / (EGG_QTY * FROG_PER_EGG)).append(",最多:").append(maxFound).toString(); } private static void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } - public void run() throws InterruptedException { - EggTool.loadEggs(this); // 从磁盘加载egg,或新建一批egg - int round = 1; + public void run() { + EggTool.loadEggs(); // 从磁盘加载egg,或新建一批egg Image buffImg = createImage(this.getWidth(), this.getHeight()); Graphics g = buffImg.getGraphics(); - long t1, t2; + long time0;// 计时用 + int round = 1; do { - if (pause) { - sleep(300); - continue; - } - t1 = System.currentTimeMillis(); - rebuildFrogAndFood(); - boolean allDead = false; - Frog firstFrog = frogs.get(0); - for (int i = 0; i < STEPS_PER_ROUND; i++) { - if (allDead) { - System.out.println("All dead at round:" + i); - break; // 青蛙全死光了就直接跳到下一轮,以节省时间 - } - allDead = true; - for (Frog frog : frogs) - if (frog.active(this)) - allDead = false; - - for (Frog frog : frogs) - if (frog.alive && RandomUtils.percent(0.2f)) {// 有很小的机率在青蛙活着时就创建新的器官 - RandomConnectGroup newConGrp = new RandomConnectGroup(); - newConGrp.initFrog(frog); - frog.organs.add(newConGrp); + rebuildFrogs(); + for (int screen = 0; screen < SCREEN; screen++) {// 分屏测试,每屏FROG_PER_SCREEN个蛙 + time0 = System.currentTimeMillis(); + if (pause) + do { + sleep(300); + } while (pause); + rebuildFood(); + boolean allDead = false; + Frog firstFrog = frogs.get(screen * FROG_PER_SCREEN); + for (int i = 0; i < STEPS_PER_ROUND; i++) { + if (allDead) + break; // 青蛙全死光了就直接跳到下一轮,以节省时间 + + allDead = true; + for (int j = 0; j < FROG_PER_SCREEN; j++) { + Frog f = frogs.get(screen * FROG_PER_SCREEN + j); + if (f.active(this)) + allDead = false; + if (f.alive && RandomUtils.percent(0.2f)) {// 有很小的机率在青蛙活着时就创建新的器官 + RandomConnectGroup newConGrp = new RandomConnectGroup(); + newConGrp.initFrog(f); + f.organs.add(newConGrp); + } } - if (SHOW_SPEED > 0 && i % SHOW_SPEED != 0) // 画青蛙会拖慢速度 - continue; - - if (SHOW_SPEED < 0) // 如果speed小于0,人为加入延迟 - sleep(-SHOW_SPEED); + if (SHOW_SPEED > 0 && i % SHOW_SPEED != 0) // 用画青蛙的方式来拖慢速度 + continue; - // 开始画青蛙 - g.setColor(Color.white); - g.fillRect(0, 0, this.getWidth(), this.getHeight()); - g.setColor(Color.BLACK); - for (Frog frog : frogs) - frog.show(g); + if (SHOW_SPEED < 0) // 如果speed小于0,人为加入延迟 + sleep(-SHOW_SPEED); - if (firstFrog.alive) { // 开始显示第一个Frog的动态脑图 - if (Application.SHOW_FIRST_FROG_BRAIN) { - g.setColor(Color.red); - g.drawArc(firstFrog.x - 15, firstFrog.y - 15, 30, 30, 0, 360); - g.setColor(Color.BLACK); + // 开始画青蛙 + g.setColor(Color.white); + g.fillRect(0, 0, this.getWidth(), this.getHeight()); + g.setColor(Color.BLACK); + for (int j = 0; j < FROG_PER_SCREEN; j++) { + Frog f = frogs.get(screen * FROG_PER_SCREEN + j); + f.show(g); } - if (DRAW_BRAIN_AFTER_STEPS > 0 && i % DRAW_BRAIN_AFTER_STEPS == 0) - Application.brainPic.drawBrainPicture(firstFrog); - } - drawTrap(g); - drawFood(g); - Graphics g2 = this.getGraphics(); - g2.drawImage(buffImg, 0, 0, this); + if (firstFrog.alive) { // 开始显示第一个Frog的动态脑图 + if (Application.SHOW_FIRST_FROG_BRAIN) { + g.setColor(Color.red); + g.drawArc(firstFrog.x - 15, firstFrog.y - 15, 30, 30, 0, 360); + g.setColor(Color.BLACK); + } + if (DRAW_BRAIN_AFTER_STEPS > 0 && i % DRAW_BRAIN_AFTER_STEPS == 0) + Application.brainPic.drawBrainPicture(firstFrog); + } + drawTrap(g); + drawFood(g); + Graphics g2 = this.getGraphics(); + g2.drawImage(buffImg, 0, 0, this); + } + Application.brainPic.drawBrainPicture(firstFrog); + Application.mainFrame.setTitle(new StringBuilder("Round: ").append(round).append(", screen:") + .append(screen).append(", ").append(foodFoundCountText()).append(", 用时: ") + .append(System.currentTimeMillis() - time0).append("ms").toString()); } - Application.brainPic.drawBrainPicture(firstFrog); - EggTool.layEggs(this); - t2 = System.currentTimeMillis(); - Application.mainFrame.setTitle("Frog test round: " + round++ + ", 找食效率:" + foodFoundPercent() - + ", time used: " + (t2 - t1) + " ms"); + round++; + EggTool.layEggs(); } while (true); } + } diff --git a/core/src/main/java/com/github/drinkjava2/frog/egg/EggTool.java b/core/src/main/java/com/github/drinkjava2/frog/egg/EggTool.java index 4dffcdd..d384f0d 100644 --- a/core/src/main/java/com/github/drinkjava2/frog/egg/EggTool.java +++ b/core/src/main/java/com/github/drinkjava2/frog/egg/EggTool.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; @@ -39,46 +38,41 @@ public class EggTool { /** * Frogs which have higher energy lay eggs * - * 利用Java串行机制存盘。 能量多(也就是吃的更多,更fat)的Frog下蛋并存盘, 以进行下一伦测试,能量少的Frog被淘汰,没有下蛋的资格。 - * 用能量的多少来简化生存竟争模拟 + * 利用Java串行机制存盘。 能量多(也就是吃的更多)的Frog下蛋并存盘, 以进行下一轮测试,能量少的Frog被淘汰,没有下蛋的资格。 + * 用能量的多少来简化生存竟争模拟,每次下蛋数量固定为EGG_QTY个 */ - public static void layEggs(Env env) { - sortFrogsOrderByEnergyDesc(env); + public static void layEggs() { + sortFrogsOrderByEnergyDesc(); - Frog first = env.frogs.get(0); - Frog last = env.frogs.get(env.frogs.size() - 1); + Frog first = Env.frogs.get(0); + Frog last = Env.frogs.get(Env.frogs.size() - 1); if (Env.DEBUG_MODE) for (int i = 0; i < first.organs.size(); i++) { Organ org = first.organs.get(i); - System.out.println("Organ(" + i + ")=" + org + ", fat=" + org.fat + ", organWasteEnergy=" + System.out.println("Organ(" + i + ")=" + org + ", fat=" + org.fat + ", organWasteEnergy=" + org.organActiveEnergy + ", outputEnergy=" + org.organOutputEnergy); } - System.out.println("1st frog has " + first.organs.size() + " organs, energy=" + first.energy + ", seeDist=" - + ((Eye) first.organs.get(6)).seeDistance + ", chance=" + ((Chance) first.organs.get(10)).percent); - System.out.println("Last frog has " + last.organs.size() + " organs, energy=" + last.energy); - try { - List newEggs = new ArrayList<>(); + Env.eggs.clear(); for (int i = 0; i < Env.EGG_QTY; i++) - newEggs.add(new Egg(env.frogs.get(i))); - + Env.eggs.add(new Egg(Env.frogs.get(i))); FileOutputStream fo = new FileOutputStream(Application.CLASSPATH + "eggs.ser"); ObjectOutputStream so = new ObjectOutputStream(fo); - so.writeObject(newEggs); + so.writeObject(Env.eggs); so.close(); - - env.eggs = newEggs; - System.out - .println("Saved " + env.eggs.size() + " eggs to file '" + Application.CLASSPATH + "eggs.ser" + "'"); + System.out.print("\r1st frog has " + first.organs.size() + " organs, energy=" + first.energy + ", seeDist=" + + ((Eye) first.organs.get(6)).seeDistance + ", chance=" + ((Chance) first.organs.get(10)).percent); + System.out.println(", Last frog has " + last.organs.size() + " organs, energy=" + last.energy); + System.out.println("Saved 1 group eggs to file '" + Application.CLASSPATH + "eggs.ser'"); } catch (IOException e) { System.out.println(e); } } - private static void sortFrogsOrderByEnergyDesc(Env env) {// 按能量多少给青蛙排序 - Collections.sort(env.frogs, new Comparator() { + private static void sortFrogsOrderByEnergyDesc() {// 按能量多少给青蛙排序 + Collections.sort(Env.frogs, new Comparator() { public int compare(Frog a, Frog b) { if (a.energy > b.energy) return -1; @@ -90,24 +84,6 @@ public class EggTool { }); } - // private static void sortFrogsOrderByEnergyDesc(Env env) {// - // 按吃到食物数量、剩余能量多少给青蛙排序 - // Collections.sort(env.frogs, new Comparator() { - // public int compare(Frog a, Frog b) { - // if (a.ateFood > b.ateFood) - // return -1; - // else if (a.ateFood == b.ateFood) { - // // if (a.energy > b.energy) - // // return -1; - // // if (a.energy < b.energy) - // // return 1; - // return 0; - // } else - // return 1; - // } - // }); - // } - public static void deleteEggs() { System.out.println("Delete exist egg file: '" + Application.CLASSPATH + "eggs.ser'"); FrogFileUtils.deleteFile(Application.CLASSPATH + "eggs.ser"); @@ -117,25 +93,26 @@ public class EggTool { * 从磁盘读入一批Egg */ @SuppressWarnings("unchecked") - public static void loadEggs(Env env) { + public static void loadEggs() { boolean errorfound = false; try { FileInputStream eggsFile = new FileInputStream(Application.CLASSPATH + "eggs.ser"); ObjectInputStream eggsInputStream = new ObjectInputStream(eggsFile); - env.eggs = (List) eggsInputStream.readObject(); + Env.eggs = (List) eggsInputStream.readObject(); System.out.println( - "Loaded " + env.eggs.size() + " eggs from file '" + Application.CLASSPATH + "eggs.ser" + "'."); + "Loaded " + Env.eggs.size() + " eggs from file '" + Application.CLASSPATH + "eggs.ser" + "'.\n"); eggsInputStream.close(); } catch (Exception e) { errorfound = true; } if (errorfound) { - System.out.println("Fail to load egg file at path '" + Application.CLASSPATH + "', created " + Env.EGG_QTY - + " new eggs to do test."); - env.eggs = new ArrayList(); - for (int i = 0; i < Env.EGG_QTY; i++) - env.eggs.add(new Egg()); + Env.eggs.clear(); + for (int j = 0; j < Env.EGG_QTY; j++) + Env.eggs.add(new Egg()); + System.out.println("Fail to load egg file at path '" + Application.CLASSPATH + "', created " + + Env.eggs.size() + " eggs to do test.\n"); } + } } diff --git a/初学者入门介绍.md b/初学者入门介绍.md index 6cba8df..cc6dc20 100644 --- a/初学者入门介绍.md +++ b/初学者入门介绍.md @@ -47,9 +47,11 @@ ENV_WIDTH: ENV_HEIGHT: ⻷߶ȴСͨȡֵ100~1000 FROG_BRAIN_DISP_WIDTH: FrogͼĻϵʾС,ͨȡֵ100~1000 STEPS_PER_ROUND: ÿֲԲ, ÿһ൱˼һ壬ܵԪһΡ -EGG_QTY: ÿFrog¶ٸÿԷ4ܡͨµȡֵ10~1000֮䡣DzԵĽʵĿǻһ -FOOD_QTYʳʳԽ࣬FrogʾԽߣǰһFrogµı̭ -DELETE_EGGS: ÿǷɾĵ +EGG_QTY: ÿFrog¶ٸͨµȡֵ10~1000֮䡣DzԵĽʵĿǻһ +FROG_PER_EGG ÿԷٸܡ +SCREEN ԣһֲԿԷΪνУÿܵ=EGG_QTY * FROG_PER_EGG * SCREEN +FOOD_QTYʳʳԽ࣬FrogʾԽߣǰһFrogµı̭ +DELETE_EGGS: ÿǷɾĵ,ΪfalseɾĵϴεIJԽС DEBUG_MODE: ģʽأ翪ӡĵϢ ``` #### Frogģ diff --git a/开发思路.md b/开发思路.md index 190482c..759999a 100644 --- a/开发思路.md +++ b/开发思路.md @@ -3,7 +3,6 @@ =======以下是杂七八拉的一些想法,想到什么就记下来========== -* 到达台式机性能瓶颈后,如果找不到快的超级计算机,只能通过存盘多个文件,按文件分组,人为地拉长测试时间来换取大样本数量,也就是说,同一个环境里的Frog数量可以很少,这样对于单个测试来说速度可以接受,也不会撑爆内存。当然了,开发体验就差了。 * 判断Frog胜出,可以不用能量,而用吃了多少个食物来判断,一个测试周期吃的多的胜出,用能量多少是不公平的,因为吃的多的,有可能因为脑细胞或器官多,消耗的能量也多,最后剩下的能量反而不如那些不运动或死掉的Frog。 * 将来训练者和生物体可以对话,表现形式为弹出一个24x24点阵图形,每次只能弹出一种图形。训练者由人或电脑操纵,生物体则可以任意自主行动和说话(输出任意图形),训练者和生物体具有相似权限的输出(开始阶段输出图形仅限于100个汉字和数字),行动仅限于说话、移动、打击、保存食物、拿出食物。 * 训练者在生物体模拟正确或行为(输出文字或图形,行走,吃,睡等)正确后,给予食物奖励。训练者在生物体说话或行为错误时,不给予奖励或给予打击惩罚。 @@ -29,215 +28,7 @@ =======以下是码云上评论的备份,抄在下面,防止万一码云挂掉以前的评论丢失========== -drinkjava2owner3 days ago - -看来JQ暂时是搞不完了。下面先清理一下这个项目,有些垃圾类要删掉。网格形式的模式识别先不急,先把奖励信号利用上,目前奖励信号被白白浪费了。要改成每次吃到食物之后,近期活跃过的所有神经元的fat值增加,就好象训兽师每次在海豹完成一个动作后,就给它条鱼吃一样,吃完鱼之后,海豹就会把刚才的动作记得更牢。另外相应地,动态生成随机连接(目前是RandomConnectionGroup),也改成每隔几步step之后就动态生成一些,而不是等青蛙死掉后再由它的后代去生成,由后代生成新的随机联接虽然可以体现优胜劣汰,但是太滞后了,根本无法利用上宝贵的奖励信号。创造性思维就是将不相干的神经元联系起来,在这里具体就是随机产生新的联接,然后由食物奖励来强化(保留)。 -Copy URL -5177524_longfer -LongFer3 days ago - -我觉得项目很好,请问大佬怎么加群呢 -Copy URL -920504_drinkjava2 -drinkjava2owner3 days ago - -如果想长期关注这个研发方向,请加我微信yong99819981并留言人工生命群,谢谢! -Copy URL -someome -someome6 days ago - -大神可以了解一下深度强化学习和OpenAI gym,感觉模拟大脑神经元和生物进化的思路是一条歪路。生命的本质无非就是复制和变异,实现是很简单的。最重要的是人类的意识和思维,这也是人工智能这个学科的最终目的。深度强化学习是目前最有可能取得突破的方向领域,大神可以关注一下这方面的信息。“意识从来就不存在,意识只是一种现象”我不同意这个观点。我的观点是“意识是一种状态,对过去和现在的记忆的自我感觉的一种状态”。其实几百年前笛卡尔就已经解决了意识是什么的问题。是的,就是笛卡尔的哲学命题“我思故我在”。 -Copy URL -920504_drinkjava2 -drinkjava2owner6 days ago - -模拟大脑的进化是一种手段,指望让电脑来自动生成复杂的脑结构,这个有投机成份,能不能成功有点靠运气,所以这个项目也不排除人为参与设计,允许吸收其它算法如深度强化学习(这个我是弱项,因为我是外行,等我有时间一定学习,或其它人也可以参与改进这个项目),编程者也需要较高的编程技巧,一条路走不通再试另一条。 至于是不是歪路并不重要,只要能逮着耗子就是好猫。 -关于意识是什么的问题,这个项目的看法是认为讨论意识本质之类的话题是无意义的,纯属浪费时间,"杯子"、“自行车”、"意识"、"智能"、“勇气”这些都不是存在的实物,只是人类语言中对一类现象的归纳而已。这个项目只从程序能实现的功能来判断它是否足够"智能",足够有用,直到能够实现解决一些现实问题,例如模拟动物实现一些复杂的工作如模拟一只狗来实现垃圾分类,当然,如这个项目开头所说,"有用"只是暂时的,最终它的目标是要造出有自我意识表现的通用人工智能。 -Copy URL -920504_drinkjava2 -drinkjava2owner3 days ago - -这个OpenAI项目,我查了一下,它的目标和这个项目的目标是完全一致的,就是造出一个通用的人工智能。看来国外已经认识到,单纯通过算法来解决开放性的复杂问题(如自动驾驶、翻译等),是一条走不通的路,最终人工智能的解决方案是"通用"智能,一个模型能解决所有问题,也就是说能造出最终智力表现超出人类的机器人。关于如何防止人工智能反制人类,相应地他们的看法就是“更多人拥有同样的能力来达到相互制衡”,这个观点也与我的观点有点接近,只不过没我这么明确彻底地倒向"投降派",人工智能研发不能被阻止,但可以通过人工智能立法保护人工智能人权、防止人类伤害机器人等方式防止人类被灭绝。 -另外OpenAI最终实现途径和Frog在算法逻辑上可能是等同的,都需要电脑来生成脑结构,而不是人来搭建。不同的是Frog给出了具体的研发路线和考核目标,每一步人工生命的进展,都是可以考核评价的,从低等生命的行为一直模拟到高等生命的行为表现就行了。 -Copy URL -heychina -heychina14 days ago - -伟大的目标,大佬,我想加入开发团队,贡献自己的一点力量. -Copy URL -920504_drinkjava2 -drinkjava2owner13 days ago - -谢谢你及最近好多同学的点赞,看来大家都对人工生命这个研发方向很感兴趣。但最近关注这个项目的请耐心等待一段时间,不光因为我最近没时间,而是因为这个项目架构方面会有极大调整(添加模式识别和记忆功能,见评论),这可能需要较长的编码时间,我一般会在有青蛙的行为有明显改进时才提交到码云,防止同学们down下来却看不出什么名堂,一头雾水。当然了,如果有同学自认为思路和编码能力较强的,也可以试着加入模式识别和记忆功能,争取能走在我的前面。目前的目标有两个:1。青蛙不能出界 2。青蛙要吃光所有食物。第一个目标青蛙必须看到并识别出边界的存在,第二个目标要求青蛙的眼睛能进化到看到屏幕上的任一点。 -Copy URL -筑梦前行 -筑梦前行16 days ago - -目标远大,我辈学习之榜样! -Copy URL -小仔 -小仔18 days ago - -太棒了,持续关注大佬 -Copy URL - -drinkjava2owner24 days ago - -这个项目消停两天,这一两周要集中精力完成jSqlBox的分布式事务。 -Copy URL -920504_drinkjava2 -drinkjava2owner25 days ago - -手机收到了,如果大家只是想临时私聊的可以加我微信yong99819981,聊完删掉好友即可。如果有想加入开发团队或长期关注这个项目的,可以留言加入"人工生命群"。 -Copy URL -妖孽 -妖孽26 days ago - -持续关注中 -Copy URL -920504_drinkjava2 -drinkjava2owner26 days ago - -谢谢关注!但这个项目快不起来,一方面作者本人是个外行,另一方面它可能是走在了无人区(至少从这个外行的眼里看来),“外行”+“无人区”注定了它举步为艰,随时卡壳。 这个项目唯一的亮点可能就是作者本人坚信这条路能一直走到头的信心了。现在的神经网络研究要么信心不足,不觉得从模拟蚂蚁脑、青蛙脑可以一直走到模拟人脑。要么就是信心足过了头,连蚂蚁脑都没搞明白,就要上高大上的人脑模拟项目(欧洲人脑项目)。 -Copy URL -920504_drinkjava2 -drinkjava2owner28 days ago - -随机联结只是演示了"会自主运动觅食“的微生物可以简单地通过随机进化中产生,但不能解释模式识别和记忆功能的产生。下面要开始另一种脑细胞分布模型的尝试了,可能要完全推翻目前随机联结这个架构,而且似乎更符合实际生物的脑结构,这就是网状结构: -打比方说,目前如果一个眼睛有100个象素点,分别对应A,B,C,D....等感光细胞,另外有上、下、左、右四个运动细胞。可以假设每个感光细胞收到光信号后,会象一个波源发生器一样向四周的脑细胞传递信号,越远的细胞收到的信号越弱,如果两个或多个感光细胞的信号同时到达远处的一个细胞,则形成波峰,如果多个波峰同时激活,这几个波峰从波的角度来说,可以看成是独立的信号源,于是又会在更远处形成波峰,于是不同形状的图形就会被大脑归类于一个个特点的波峰激活区,这就是模式识别的基本原理了。 一个波峰形成后会以反指数曲线形式消退,在此期间如果有个别波到达,会很容易再次激活,即使只是图像(或其它内、外信号)的局部信号,也可能使这个波峰激活到很高的值,这就是记忆的基本原理了。如果某个感觉细胞的输入转辗到达了运动细胞,可以说是一种天生的本能反射,如果这个本能反射链被加强或削弱,例如被脑内进食奖励区兴奋(多巴胺产生会加强最近的触突联接)、痛觉信号(可能产生强烈负信号),或是被脑内其它波峰的信号覆盖,那么这种后天形成的信号-运动反应,可以称为后天的条件反射。 对于电脑模拟来说,只需要构造一个模拟波形传播的网装结构即可,一个节点可以接受多个方向的波,也可以向多个方向发送波,这就要求它具备多个输入、输出触突(也可以是电脑虚拟触突),以及对应的奖惩调节机制即可,激活多的脑区,在下次遗传时把网格变异成更密一点就可以了,以体现用进废退。这就是下面要进行的工作的大概思路,这是一个整体化的解决方案。 -Copy URL -920504_drinkjava2 -drinkjava2owner27 days ago - -与波的发散形成多个波峰相反,多个波峰激活会形成脑内的思维区成像(多个波峰激活会在原波源处成像,还有一种可能是在反侧镜像处成像),脑内的思维区就是人脑的CPU快速缓存区,它永远不停地被周围的波干扰,生成各种图像(或声音等),思维区内容永不停止地在变幻,思维成像区驱动着其它脑区,反过来,其它脑区又在思维成像区形成新的图像,这就是大脑工作的流程,在做梦时这个流程更清晰,因为没有了外部信号输入,思维成像区永远不会是空白,所以大脑永远不会当机。 -波峰的激活会在思维区成像,但是这个成像是模糊的,这就是为什么大脑中永远不能想象出一幅高清画面的根源(脑部变异者除外)。 -现在的问题是,思维区是位于感光细胞区和大脑存贮细胞区之间的,还是之后? 换句话说,视觉光信号是先到达思维成像区神经元还是先到达波峰区神经元? 从波的成像象度来说,个人倾向于前者(因为镜像这个条件很难满足,它要求波峰位于同一个平面上),也就是说外部信号必须先经过思维成像区之后,再传递给波峰(大脑的存贮细胞),在回忆时,波峰的信号必须原路逆向返回到思维成像区。这就需要与视觉、听觉信号相关的脑内神经元的信号必须是双向传播的。但我google了一下,生物神经元只能单向传播信号,如果是这样,就只能通过两个相反方向的神经元来模拟一个双向传导功能,这是个疑问。先把这个问题放下,对于这个电脑模拟项目来说,先考虑直接上双向,因为目标是找食,并不需要全盘照搬生物底层机制。 -Copy URL -920504_drinkjava2 -drinkjava2owner26 days ago - -当然了,这里的“波”只是个比喻,实际编程时是不可能也没必要模拟波在360度所有角度上的传播的,只能用n(可能少到1、2条)条放射线分布在一个小于180度的扇面上来近似代表波的传播路径,甚至可以用"横5格,竖2格"之类的网格比率来简化射线斜率的传播方向,两个射线的交点就是一个脑细胞存贮单元。而且可以用信号向右(水平方向的分量)传播表示波的扩张,向左表示波峰到原点的逆向信号传播。这适用于单维信号的输入和存贮,二维的(图像)输入还要再想想。 -另外,神经元如果处在波的传播链中间,应该也具有波的叠加特性,即使一个神经元已被激活,当另一个信号到达时,它也应当具备将这个信号中继给下一级神经元的能力。 所以处于中点的神经元其饱和值很高(通过用进废退原则进化得来),处于信号末端的神经元饱和值低。 -Copy URL -920504_drinkjava2 -drinkjava2owner26 days ago - -痛苦、愉悦感觉细胞本身也可以视作一个波源,简单说,可以视为图像的一个象素点,和视觉信号发生干涉,在大脑里形成波峰。于是每当看到特定的钉子、刀、针等图像时,这些波峰区被激活,信号再逆向传回到信号源后会激活痛苦成像区(或者说痛苦感受区)。 痛苦区激活可能会回忆到缩手区(缩手区是一个运动细胞,但同时也是一个波输出信号发生源),当这个过程持续足够长或这个回忆检索收集到的信号足够强,就有可能超过缩手区运动细胞的执行阀值,真的作出缩手运动。类似地,看到蛋糕图像会激活愉悦区,愉悦区可能会回忆到伸手区,当蛋糕足够大或在我面前晃动时间足够长时,蛋糕就被抢走了。蛋糕吃到嘴后,大脑会分泌多巴胺奖励所有最近活跃过的脑细胞,好让下次的抢蛋糕反射链发生得更流畅一点。 -Copy URL -920504_drinkjava2 -drinkjava2owner29 days ago - -yeah!满300赞了,谢谢最后一位点赞的同学。 -Copy URL -萧萧雨声 -萧萧雨声29 days ago - -非常有创意的项目,代码中使用的随机数是普通的伪随机数,如果有必要可以考虑真随机数,这里有个网站可以获取真随机数https://www.random.org -Copy URL - -Copy URL -920504_drinkjava2 -drinkjava2owner29 days ago - -谢谢提醒,不过神经网络应该不在乎伪随机数还是真随机数的,因为从它的需求来看,伪还是真随机数起到的作用是一样的,因为它不象密码一样需要防着人家去破解,只是需要一个均匀分布的随机概率而已。 -Copy URL -920504_drinkjava2 -drinkjava2owner30 days ago - -加了个新的眼睛,用nxn点阵形式代表,脑图也可以动态显示了。蝼蚁尚且贪生,青蛙到了边界就不走了,往生咒加上了。 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -找食效率太低,又改回到用连接数量代替权值这个逻辑,看起来舒服多了。权值这种人为设计的算法居然比不过随机试错,失败。 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -猜测:大脑的结构可能是一个分形结构,在宏观上具备的记忆、信号输入和模式识别、条件反射等功能和结构,在微观上也具有相似的结构,也就是说只需要设计出一套脑细胞排布算法就可以了,每一个低层的脑结构就是高一层脑结构的分形式复制,在活跃度高的节点上,分形式遗传变异复制发生的概率高(用进废退),最终形成一个复杂的树状脑结构。这个猜测的意义在于如果能够完成基本的青蛙脑的设计,完成基本的找食、分辨天敌(需要增加感光细胞)等功能,更复杂的如人脑就不用再去单独设计了,利用计算机的虚拟环境训练就可以自动让这个青蛙脑进化成更复杂的人脑了。算法是一样的,只是脑的总细胞数量和分形层数不一样而已。在蛋(Egg)里,只保存着一套算法,和一棵分形树的每个节点。打个比方来说,用最好的条件和环境来训练一群狗、一群猴子、一群恐龙,最终几十万年后它们都会进化成和人一样聪明,因为这些动物的受精卵里保存的脑的进化方案是一样的。 -另外现在这个版本的找食效率不如4.12的提交,这可能是因为权值虽然可以简化模型,但悬殊巨大的权值可能抑制了新连接的建立,正在调试将4.12的逻辑照抄到RandomConnectionGroup中去,增加细胞数量,弱化权值自动调整范围。等到引入记忆器官和利用上 Happy器官的奖励信号之后再说。目前这个青蛙的任务很明确:1.遇到边界要掉头,而不是出界或顺着边界往下溜 2.发现并吃光Env中的所有食物。 第一个任务必须引入记忆功能,第二个任务必须进化出感光细胞非常多的眼睛,因为如果测试环境中只有一个食物和一个青蛙时,如果青蛙感光细胞很少就会根本看不到这个食物而饿死。 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -当然,如果食物太多,眼睛的感光细胞可能不会进化出很多,例如矿井里的有一种老鼠眼睛基本看不到,不是因为大自然觉得即然用不到眼睛,就让它退化吧,而是因为有了眼睛的老鼠需要消耗更多的食物,在生存竞争中被淘汰。所以环境决定了生物的形态,环境决定了脑的进化方向。 -Copy URL -1320504_fzwise -码瘾少年a month ago - -nice job,这个想法我之前就想过,随机才是王道,再复杂的逻辑写出来的人工智能只是人思维的规律总结,算不上智能 -Copy URL -xinyi -xinyia month ago - -求加微信xx17610115570 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -报歉手机坏了,正在等网购的手机到,等拿到手再加你,顺便也会把微信号挂在"关注我"栏里面。这几天可以在这里发评论或私信,我每天都会登录码云看看的。 -Copy URL -天崖 -天崖a month ago - -忍不住推荐一个视频:https://www.bilibili.com/video/av54874176/。或者说是推荐这个up主。他的系列视频讲了一些神经网络和人工智能学习机制的科普,我觉得讲得太棒了。而且里面关于“意识是客观现象”的观点与楼主不谋而合。 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -受教了,才知道草履虫的纤毛能感觉到外界输入,up主的生物知识很渊博。我是根据任务来假设生物必须具备某种信号处理能力,而生物学家可以直接从内因来解释生物为什么能实现这个任务。 -Copy URL - -drinkjava2ownera month ago - -加了个“初学者入门介绍“,方便Java零基础的人看源码。 -Copy URL -Master_H -Master_Ha month ago - -厉害 -Copy URL -Jackchars -Jackcharsa month ago - -看着就牛逼哄哄 -Copy URL -妖孽 -妖孽a month ago - -大佬牛逼啊 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -微信号(yong99819981),但正在换手机,请过两天再加。这两天只是想聊天的请发在这里或私信。 -Copy URL -1489318_lnsooxd -LnsooXDa month ago - -怎么加入你? Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -开了个微信"人工生命群”,可以在那聊。我没有QQ,你也可以直接在这里评论或发issue、私信。 -Copy URL -1727136_sunziren -sunzirena month ago - -老弟,你QQ多少,我对你这个项目很感兴趣,咱们应该有的聊的 -Copy URL -jdkhome -jdkhomea month ago - -带我一个 -Copy URL -920504_drinkjava2 -drinkjava2ownera month ago - -开了个微信"人工生命群”,可以在那聊。我没有QQ,你也可以直接在这里评论或发issue、私信。 - -Copy URL 143517_vebai /vebaia day ago diff --git a/版本提交记录.md b/版本提交记录.md index b511993..d0ed2d2 100644 --- a/版本提交记录.md +++ b/版本提交记录.md @@ -56,4 +56,7 @@ 1. 在Env区中间加了一个陷阱区Trap,以增加趣味性,青蛙如果走到陷阱区就死掉,结果自然选择的结果是青蛙会绕开陷阱区。 2. 青蛙增加一个Active器官,它的作用是一直保持激活,如果有神经元触突位于这个区就会驱动神经元兴奋,这个器官经实践证明比Hungry器官驱动更能提高找食效率。 3. 青蛙增加一个Chance器官,它的作用是引入随机扰动,打破青蛙有时候围着一个食物打转就是吃不着的死循环。 -从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 \ No newline at end of file +从当前这个版本可以看出,实际上青蛙是有一定的记忆能力的,连接就=记忆,只不过没有模式识别能力,以后的工作将以模式识别为重点,基本原理是见note中提到的仿照全息存储原理,在思维区逆向成像。因为逆向成像的限制,以后的版本,所有的器官会被移到脑图的同一侧,不再是随意分布在脑图上了,这将是一个比较明显的改动。当然随机连接这个算法看起来比较有用,以后还是可能保留的。 + +### 2019-08-04, Commit: Screen group test +引入分屏测试功能,如果青蛙数量多,可以分屏来测试,每屏青蛙的数量可以少到只有1只。 \ No newline at end of file