利己的な遺伝子

注目を集める画像(http://xkcd.com/534/) それはすべて、進化生物学者のリチャード・ドーキンスの本「幻想としての神」を読みながら、ある夏の夜に始まりました。 この本は宗教、信仰、無神論について書かれていますが、著者は別の本「The Selfish Gene」に簡単に言及し、同じ名前の概念を紹介しています。 長い間、私は遺伝的アルゴリズムの恵みに魅了されていました。 そして今、一ヶ月後、再びミニプロジェクトを考え出そうとしましたが、それは突然思いつきました-遺伝的アルゴリズムを使用して進化をシミュレートし、それが何をどのように発展するかを見てみましょう。 これは難しい作業であり、IT開発のこの段階では解決できないと思うので、もっと簡単なことをしなければなりませんでした。 つまり、利己的な遺伝子の仮説をテストします。 興味を持ってください、猫の下で…



まず、進化の標準表現を決定します。 ウィキペディアによると:「生物進化は、個体群の遺伝的組成の変化、種の適応、種分化と絶滅、生態系と生物圏全体の変化を伴う野生生物の自然な成長プロセスです。」 そして重要なことは、進化の単位は人口です。 リチャード・ドーキンスは、進化の単位はあらゆる種の個体の集団ではなく、遺伝子自体であるという理論を提唱しました(これが利己的な理由です)。 そして、遺伝子の「目的」は、個人を周囲の環境に適応させることではなく(それが生き残り、子孫を与える)、遺伝子そのものが「生き残る」ためにすべてを行うことです。 この問題に関する別の見方は、プログラミングにおける遺伝的アルゴリズムです。それらでは、個別の個人が「進化の単位」として認識されます。



免責事項
生物学と進化に関する私の知識は、学校のカリキュラムと一般的な科学映画に限られているため、テキストに誤りがある場合は、コメントまたはPMで退会してください。



したがって、どの遺伝子がどの集​​団の遺伝子プールで優勢になるかを調査(およびシミュレート)することができます。



  1. 人口全体に利益をもたらすもの。
  2. 現在の個人の親族に利益をもたらす[遺伝子](同じ遺伝子を「保存」する可能性が最も高いのは親relativeであるため)。
  3. 現在の個人のみに利益をもたらすもの。





C#6および.NET 4.6でのシミュレーション用のプログラムが開発されました。



プログラムのデータモデルは非常に単純です。 WorldおよびCreatureクラスと、RelationおよびGeneというヘルパー列挙型があります。 特定の反復における世界の状態に関する必要なデータをカプセル化するStatisticクラスもあります。



世界
以下では、メインコードクリッピングのみをリストします。 すべてのコードは、記事の最後にあるリンクのgithubに投稿されています。



public class World { public readonly List<Creature>[] Species = new List<Creature>[8]; public void Run(int generations) { for (int i = 0; i < generations; i++, this.age = this.Age + 1) { this.SelectBest(); this.MakeChildren(); this.Mutate(); Debug.Print("Age: {0}", i); } } private void SelectBest() { var allCreatures = new List<Creature>(this.Species.Sum(kind => kind.Count)); allCreatures.AddRange(this.Species.SelectMany(kind => kind)); allCreatures = allCreatures.OrderByDescending(creature => creature.SummaryStrength).Take(allCreatures.Count >> 1).ToList(); for (int i = 0; i < this.Species.Length; i++) { this.Species[i].ForEach(creature => creature.BreakRedundantConnections()); this.Species[i].Clear(); this.Species[i].AddRange(allCreatures.Where(creature => creature.IdOfSpecies == i)); } } private void MakeChildren() { Parallel.For( 0, this.Species.Length, i => { var temp = new List<Creature>(this.Species[i].Count << 1); // Random parents (of same species) - for supporting different genes this.Species[i].Shuffle(); Random rnd = RandomProvider.GetThreadRandom(); for (int j = 1; j < this.Species[i].Count; j += 2) { double value = rnd.NextDouble(); if (value < 0.33) { temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); } else if (value < 0.665) { temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); } else { temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); temp.Add(new Creature(this.Species[i][j - 1], this.Species[i][j])); } } this.Species[i].ForEach(creature => creature.BreakRedundantConnections()); this.Species[i].Clear(); this.Species[i] = temp; }); } private void Mutate() { Parallel.ForEach(this.Species, list => list.ForEach(creature => creature.Mutate())); } }
      
      







Worldには、1つのパブリックRunメソッドのみが含まれます。これは、「世界のモデリング」の反復を所定の回数実行します。 世界には8つの種があります。 最初は、それぞれに1024人の個人がいます。 これにはいくつかの種が存在します。まず、種間の競争をシミュレートします(生存のための戦い)。 第二に、極大値を見つける確率を減らすため。



この実験のフィットネス関数は単純です-各クリーチャーには特定の強度パラメーターがあります。 高いほど、クリーチャーはシミュレートされた世界で生き残るためにより適応しています。 世界(「環境」)は静的です。 すべてのクリーチャーのうち、適者の50%が選択されます。

補助世代は、親世代の「生き残った」個人に基づいて作成されます。 (同じ種からの)ランダムペアでは、娘個体が作成される遺伝子に基づいて、親世代の個体が選択されます。 さらに、33%の確率で3人が作成されます。33.5%-4人、33.5%-5人です。 したがって、個人の数は世代から世代へと徐々に増加します。 私のシミュレーションでは、世界中の個人の数は、1024回の反復で8.192から〜30.000に増えました。



突然変異は、新しく作成されたすべての個人に適用されます。



列挙型
  public enum Gene : byte { SelfishGene = 1, AltruisticGene = 2, CreatureLevelGene = 4 } public enum Relation { Child, BrotherOrSister, GrandChild, NephewOrNiece, Cousin }
      
      







遺伝子は、3つの意味の列挙によって単純に表されます。



遺伝子の影響を受ける親Relativeは、5つの意味で表されます。 制限は次のとおりでした:遺伝子の親族の遺伝子との類似性は少なくとも10%である必要があり、古い世代の個人はこの世代または若い世代の個人を助けることができます(各個人(世代)はこのシミュレーションの選択にのみ参加するため)、世代nの個人は助けることができますn + 2以下の世代の個人(これは、人間や他の哺乳類の平均的な実際の仮定だと思います)。



クリーチャー
  public class Creature { private const int GeneStrength = 128; private readonly Gene[] genes = new Gene[128]; private readonly List<Creature> childs = new List<Creature>(8); private Creature mother; private Creature father; public Creature(int idOfSpecies, World world) { Contract.Requires<ArgumentNullException>(world != null); Contract.Ensures(this.IdOfSpecies == idOfSpecies); this.IdOfSpecies = idOfSpecies; this.world = world; for (int i = 0; i < this.genes.Length; i++) { this.genes[i] = EnumHelper.CreateRandomGene(); } } public Creature(Creature mommy, Creature daddy) { Debug.Assert(mommy.IdOfSpecies == daddy.IdOfSpecies, "Interspecies relation are FORBIDDEN!!!"); this.mother = mommy; this.father = daddy; mommy.childs.Add(this); daddy.childs.Add(this); this.world = mommy.world; this.IdOfSpecies = mommy.IdOfSpecies; for (int i = 0; i < this.genes.Length; i++) { this.genes[i] = EnumHelper.ChooseRandomGene(mommy.genes[i], daddy.genes[i]); } } public int SummaryStrength { get { double sum = 0.0; World world = this.world; string cacheKey = $"AltruisticGenesOutStrength{this.IdOfSpecies}"; object cachedValue = Cache.Get(cacheKey, world.Age); if (cachedValue != null) { sum = (double)cachedValue; } else { for (int i = 0; i < world.Species[this.IdOfSpecies].Count; i++) { if (world.Species[this.IdOfSpecies][i] != this) { sum += world.Species[this.IdOfSpecies][i].AltruisticGenesOutStrength; } } Cache.Put(cacheKey, world.Age, sum); } return this.ThisCreatureGenesStrength + (int)sum + (int)this.HelpFromRelations; } } private int ThisCreatureGenesStrength => this.genes.Sum(g => g == Gene.CreatureLevelGene ? GeneStrength : GeneStrength >> 1); private double AltruisticGenesOutStrength { get { int sum = 0; for (int i = 0; i < this.genes.Length; i++) { Gene gene = this.genes[i]; if (gene == Gene.AltruisticGene) { sum += GeneStrength >> 1; } } return (double)sum / (this.world.Species[this.IdOfSpecies].Count - 1); } } private double HelpFromRelations { get { Creature mommy = this.mother; Creature daddy = this.father; if (mommy == null) { return 0; } if (mommy.mother == null) { return mommy.GetSelfishGenesOutStrength(Relation.Child) + daddy.GetSelfishGenesOutStrength(Relation.Child) + mommy.childs.Sum( brother => brother == this ? 0 : brother.GetSelfishGenesOutStrength(Relation.BrotherOrSister)); } return mommy.GetSelfishGenesOutStrength(Relation.Child) + daddy.GetSelfishGenesOutStrength(Relation.Child) + mommy.childs.Sum( brother => brother == this ? 0 : brother.GetSelfishGenesOutStrength(Relation.BrotherOrSister)) + mommy.mother.GetSelfishGenesOutStrength(Relation.GrandChild) + mommy.father.GetSelfishGenesOutStrength(Relation.GrandChild) + daddy.mother.GetSelfishGenesOutStrength(Relation.GrandChild) + daddy.father.GetSelfishGenesOutStrength(Relation.GrandChild) + mommy.mother.childs.Sum( aunt => aunt == mommy ? 0 : aunt.GetSelfishGenesOutStrength(Relation.NephewOrNiece)) + daddy.mother.childs.Sum( uncle => uncle == daddy ? 0 : uncle.GetSelfishGenesOutStrength(Relation.NephewOrNiece)) + mommy.mother.childs.Sum( aunt => aunt == mommy ? 0 : aunt.childs.Sum(cousin => cousin.GetSelfishGenesOutStrength(Relation.Cousin))) + daddy.mother.childs.Sum( uncle => uncle == daddy ? 0 : uncle.childs.Sum(cousin => cousin.GetSelfishGenesOutStrength(Relation.Cousin))); } } public void Mutate() { // Tries to change 6 genes with 50% probability int length = this.genes.Length; int rnd = RandomProvider.GetThreadRandom().Next(length << 1); int limit = Math.Min(length, rnd + 6); for (; rnd < limit; rnd++) { this.genes[rnd] = EnumHelper.CreateRandomGene(); } } public void BreakRedundantConnections() { Creature mommy = this.mother; Creature daddy = this.father; if (mommy?.mother?.mother != null) { mommy.mother.mother?.childs.Clear(); mommy.mother.mother = null; mommy.mother.father?.childs.Clear(); mommy.mother.father = null; mommy.father.mother?.childs.Clear(); mommy.father.mother = null; mommy.father.father?.childs.Clear(); mommy.father.father = null; daddy.mother.mother?.childs.Clear(); daddy.mother.mother = null; daddy.mother.father?.childs.Clear(); daddy.mother.father = null; daddy.father.mother?.childs.Clear(); daddy.father.mother = null; daddy.father.father?.childs.Clear(); daddy.father.father = null; } } private double GetSelfishGenesOutStrength(Relation whoAreYou) { Creature mommy = this.mother; Creature daddy = this.father; int summarySelfishStrength = this.genes.Sum(g => g == Gene.SelfishGene ? GeneStrength >> 1 : 0); switch (whoAreYou) { case Relation.Child: return summarySelfishStrength / this.childs.Count * 30.78; case Relation.BrotherOrSister: Debug.Assert(mommy.childs.Count > 1, "LIER! He is not our brother!"); return summarySelfishStrength / (mommy.childs.Count - 1) * 30.78; case Relation.GrandChild: return summarySelfishStrength / this.childs.Sum(creature => creature.childs.Count) * 15.38; case Relation.NephewOrNiece: Debug.Assert(mommy.childs.Count > 1, "LIER! We don't have any brothers!"); return summarySelfishStrength / mommy.childs.Sum(brother => brother == this ? 0 : brother.childs.Count) * 15.38; case Relation.Cousin: return summarySelfishStrength / (mommy.mother.childs.Sum(aunt => aunt == mommy ? 0 : aunt.childs.Count) + daddy.mother.childs.Sum(uncle => uncle == daddy ? 0 : uncle.childs.Count)) * 7.68; default: throw new NotImplementedException("Unknown enum value"); } } }
      
      







各個体には正確に128個の遺伝子が含まれています。 ゼロ世代の個人の場合、遺伝子はランダムに選択されます。 各次世代の個人は、ランダムな親遺伝子を受け取ります。 各遺伝子の基本強度は128です。遺伝子は次のように機能します。

  1. AltruisticGene-遺伝子の力の50%は、遺伝子を保持するクリーチャーの「貯金箱」に送られ、残りの50%はこの種のすべてのクリーチャーに均等に分配されます。
  2. CreatureLevelGene-遺伝子の力のすべてが、遺伝子を保有するクリーチャーの「貯金箱」に送られます。
  3. SelfishGene-遺伝子の強さの50%が遺伝子を保有するクリーチャーの「貯金箱」に送られ、残りの50%はこの個人の親族間で次の比率で分割されます。 %といとこ-遺伝子の強度の3.84%。


比率は、近親者に同じ遺伝子が存在する確率に関連しています。
確率



確率が50%の突然変異は4つの遺伝子を変化させます。



最後の興味深いメソッドはBreakRedundantConnectionsです。 ガベージコレクターがメモリを収集できるようにするには、親が不要になったときに親への参照を削除する必要があります(世代の違いは3つ以上です)。



メモリリーク
この段階で、面白いメモリリークが発生しました。 理論的には、GCは到達できないヒープ内のすべてのオブジェクトを収集する必要があります。オブジェクトのグラフをトラバースするアルゴリズムに従って動作するためです。 このためには、子世代が親にnull参照を設定するだけで十分でした(これは(古い)クリーチャーへのリンクが保存される唯一の場所であるため)。 ただし、これは機能せず、親の子への参照の配列もクリアする必要がありました。 この動作の理由は何であるか言えません。これが.NETのバグであるとは信じたくありません。 おそらく、私は何かを知らないか、LINQとそれが作成するヘルパークラスに精通しています。






プログラムは(Intel Core i7 2.4 GHzプロセッサを搭載したラップトップ上で)〜20分で1024回の反復を実行し、最大50 MBのRAMを消費します。 平均して、反復ごとに1秒が費やされます。 各反復での個人の数は、〜10.000〜〜30.000の範囲です。 シミュレーション全体では、約20,000,000個の個人と2,500,000,000個の遺伝子が計算されます。



既存の遺伝子の68%は利己的な遺伝子です。 Povovna(各16%)の絶対に良い遺伝子と個々のキャリアのみに利益をもたらす遺伝子。 この比率は、89世代目に到達しました。 201世代では、1種しか残っていませんでした(9世代全体(最初の統計が取得された最初の世代)を先に破りました)。



非常に美的でないスクリーンショット


...







...





これからどのような結論を導き出すことができますか:



  1. 私たちは、親toへの援助のために遺伝的にプログラムされています。
  2. 進化の特性の1つは、ランダムではないことです。 これが法律です。 単純な生命と遺伝情報のキャリアがある宇宙の惑星では、地球と同様に進化が進みます。 シミュレーションをいくら実行しても、同じ結果が得られます。 これは少し確認です。 実際、これにより、最適化された列挙(遺伝的アルゴリズムと呼ばれる)が何らかの関数の最大値を見つけることが確認されます。 しかし、進化は同じです!
  3. おそらく、将来、遺伝的アルゴリズムを使用して実際の問題を解決する場合、この進化の特徴を考慮することを検討する価値があります。





ソースはgithubで入手できます(無料のCC 4.0ライセンスの下)。



私は建設的な批判を喜んでいます。 ご質問がある場合は、コメントでお尋ねください。



ご清聴ありがとうございました!



この記事に興味がある場合
PS本「The Selfish Gene」の配信を見込んで、次のバージョン(プログラムや記事)を次の変更とともに計画します。生産性の向上(現時点では、コードに極端に最適化されていないセクションがいくつかあり、メモリトラフィックが激増しています)視覚化、おそらく、遺伝的アルゴリズムを使用してプロセスをシミュレートするための(おそらく次の)ミニフレームワークのリリースと、私たちの素晴らしさのより正確な定義。 また、希望がある場合(自然選択のより正確なモデリング、遺伝子ではなくミームに適用される遺伝的アルゴリズム、一夫多妻制/一夫一婦制の長所/短所の研究、別の遺伝子モデリングのパラメーターの遺伝子モデリング...)、登録解除してくださいコメント。




All Articles