You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中Genome生成树形Body及神经网络Brain控制的技术实现问询

我的场景

我正在Unity中开发一个涉及遗传算法和神经网络的大型项目,目前刚启动,需要初始实现方面的帮助。我暂未使用任何库,但未来计划尝试用ml-agents实现部分Brain功能。

我的初始方案

我的初步想法是使用Genome来生成Body。Body是由部件组成的树形结构,其“躯干”为Body的根节点。Body的每个部件由一个Chromosome表示,部件的属性(如半径、旋转)分别由Gene表示。部分属性(Gene)是Brain(神经网络)的可观测输入,部分属性(Gene)是Brain可控制的输出。


问题1:如何将层级树形结构最佳表示为Genome?

在我见过的大多数实现中,Genome都以类数组结构表示。

解答

采用带父节点标记的扁平数组结构是最优方案,既兼容遗传算法常用的一维基因组操作,又能保留树形层级信息:

  • 给每个代表部件的Chromosome添加ParentID字段,根节点(躯干)的ParentID设为特殊值(如-1)。
  • 将所有Chromosome按固定遍历顺序(比如深度优先、广度优先)存入一维数组,每个Chromosome包含自身ID、ParentID、以及该部件对应的所有Gene。
  • 解析时,通过遍历数组、匹配ParentID即可重建Body的树形结构。
    这种方式既避免了复杂的嵌套序列化,又能让遗传算法的交叉、变异操作直接在一维数组上完成,效率更高。

问题2:以下这类C#类结构是否适用于我的需求?

假设我能解决问题1,我或许可以从字符串(或某种一维结构)解析Genome。

public class Gene {
    public int ID;
    public string Name;
    public string Description;
    public Chromosome ChromosomeReference { get; set; }
    public Genome GenomeReference { get; set; }
    public Gene(string name, string desc) {
        Name = name; Description = desc;
    }
}
public class Gene<T> : Gene {
    public T Value;
    public Gene(string name, string desc = "") : base(name, desc) =>
        Value = default;
    public Gene(string name, T value, string desc = "") : this(name, desc) =>
        Value = value;
}
public class Chromosome {
    public int ID;
    public string Name;
    public string Description;
    private Genome _genomeRef;
    public Genome GenomeReference {
        get => _genomeRef;
        set {
            _genomeRef = value;
            foreach(Gene gen in GeneDict.Values) {
                gen.GenomeReference = value;
            }
        }
    }
    public Dictionary<string, Gene> GeneDict = new();
    public Chromosome(string name, string desc = "") {
        Name = name;
        Description = desc;
        GeneDict = new();
    }
    public Chromosome(string name, List<Gene> genes, string desc = "") : this(name, desc) {
        GeneDict = new(genes.Count);
        foreach(Gene gen in genes) {
            gen.ChromosomeReference = this;
            AddGene(gen);
        }
    }
    public Gene AddGene(Gene gene) {
        if(GeneDict.ContainsKey(gene.Name)) GeneDict[gene.Name] = gene;
        else GeneDict.Add(gene.Name, gene);
        return gene;
    }
}
public class Genome {
    public string Name;
    public string Description;
    public Dictionary<string, Chromosome> ChromosomeDict;
    public Genome(string name, string desc = "") {
        Name = name;
        Description = desc;
    }
    public Genome(string name, List<Chromosome> chromosomes, string desc = "") : this(name, desc) {
        ChromosomeDict = new(chromosomes.Count);
        foreach(Chromosome chro in chromosomes) {
            chro.GenomeReference = this;
            ChromosomeDict.Add(chro.Name, chro);
        }
    }
    public Chromosome AddChromosome(Chromosome chrom) {
        if(ChromosomeDict.ContainsKey(chrom.Name)) ChromosomeDict[chrom.Name] = chrom;
        else ChromosomeDict.Add(chrom.Name, chrom);
        return chrom;
    }
}
public class TestGenome : Genome {
    public TestGenome() : base("TestGenome", new()) {
        AddChromosome(new("Body", "Chromosome for the Body"));
        ChromosomeDict["Body"].AddGene(new Gene<float>("Radius", 3f, "Body radius."));
        for(int i = 0; i < 3; i++) {
            ChromosomeDict["Body"].AddGene(new Gene<float>($"Anchor{i}", i / 3f, $"Location of anchor point {i}"));
        }
    }
}

解答

这个结构基础可行,但有几个关键优化点需要补充,以适配遗传算法和Unity开发场景:

  1. 泛型约束:给Gene<T>添加类型约束(如where T : struct, IConvertible),确保基因值是可序列化的数值类型,避免后续解析、交叉操作出现类型兼容问题。
  2. ID自动生成:给Gene和Chromosome的ID字段添加自动生成逻辑(比如用Guid或全局递增整数),避免手动赋值导致的ID冲突问题。
  3. 顺序维护:GeneDict用Dictionary方便按名称查找,但遗传算法操作需要固定顺序,建议新增List<Gene> Genes字段维护基因的添加顺序,确保扁平化时的一致性。
  4. 循环引用处理:ChromosomeReference和GenomeReference会导致序列化时的循环引用,建议给这些字段添加[JsonIgnore]标记(如果用Newtonsoft.Json序列化),或者在序列化时只保存ID而非对象引用。
  5. 基因类型标记:新增枚举GeneType(如Static/Observable/Controllable),给Gene添加该字段,方便后续快速筛选与神经网络交互的基因。

问题3:若采用上述类结构,如何表示数据结构以使其易于解析且适合交叉等操作?

解答

核心思路是将嵌套结构扁平化,分离元数据与基因值:

  1. 扁平化基因序列:
    • 按固定顺序(比如先根部件,再子部件的深度优先顺序)遍历Genome的所有Chromosome,将每个Gene<T>.Value转换为统一数值类型(如float,非数值类型可编码为整数映射值),存入一维float[]数组。
    • 该数组就是遗传算法中用于交叉、变异的核心“基因组”,操作完全遵循一维数组的逻辑(比如单点交叉:随机选一个位置,交换两个基因组该位置后的所有元素;变异:随机选位置修改数值)。
  2. 元数据存储:
    • 单独保存一份元数据,记录每个基因的位置索引、名称、类型、取值范围(如0~1、-π~π)、所属Chromosome的ParentID等信息。元数据只需保存一次,所有同类型的Genome共享该元数据。
  3. 解析与重构:
    • 解析时,根据元数据的索引映射,将一维数组的数值还原到对应的Gene<T>.Value中,再通过ParentID重建Body的树形结构。
  4. 序列化格式:
    • 用二进制格式序列化扁平化数组(效率高),或JSON格式(易调试),元数据可单独存为配置文件,避免重复存储。

问题4:最后,假设我用神经网络控制生成的Body,如何将部件属性与神经网络的输入输出关联?

解答

按观测输入和控制输出两类属性分别处理,结合Unity的生命周期和后续ML-Agents的集成需求:

  1. 观测输入(神经网络感知):
    • 筛选标记为Observable的Gene,收集其Value转换为float数组,作为神经网络的输入层。
    • 在Unity的Update或FixedUpdate中,定期更新该数组(比如包含部件的旋转角度、位置偏移、碰撞状态等)。
    • 若后续使用ML-Agents,可在Agent.CollectObservations()方法中调用AddObservation()添加这些数值。
  2. 控制输出(神经网络驱动):
    • 筛选标记为Controllable的Gene,将神经网络的输出数组元素一一映射到这些Gene的目标值。
    • 在Unity中添加平滑过渡逻辑(比如用Mathf.Lerp),将部件的当前属性值逐步过渡到目标值,避免刚体运动出现突兀变化。
    • 若使用ML-Agents,可在Agent.OnActionReceived()方法中接收输出值,直接赋值或过渡到对应的Gene属性。
  3. 优化建议:
    • 对观测输入做归一化处理(比如将角度转换为-1~1的范围),提升神经网络的训练效率。
    • 给可控制的Gene设置取值范围约束(比如旋转角度限制在-90°~90°),避免神经网络输出异常值导致Body行为失控。

内容的提问来源于stack exchange,提问作者Martin Bocanegra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 20:50:29