クイックノート

ちょっとした発見・アイデアから知識の発掘を

優性・劣性遺伝が多様性を生み出す

遺伝子の優性・劣性という言葉を聞いたことがあると思います。

2年ほど前には、日本遺伝子学会が言葉の誤解を避けるために、
優性・劣性の代わりに顕性・潜性という言葉を使うように提言したのも、
記憶に新しいところです。

優性・劣性とは、簡単に言ってしまうと、
親が持つ性質がそのまま子供にも現れやすいものと、
逆に、現れにくいものがあるということです。

優性の方が、子供に現れやすく、
劣性の方が現れにくく、
優性の遺伝子と劣性の遺伝子の両方を持った子供には、
優性の遺伝子による性質が現れます。

つまり、遺伝子毎にその遺伝子由来の性質が現れるかどうかには、
偏りがあるということです。

現れる性質に偏りがあるということは、
優性が多数派になり、劣性が少数派になるということですから、
典型的なマジョリティ・マイノリティ型の集団になり、
集団の中の多様性が損なわれるのではないかと予想できます。

ところが、ちょっとしたシミュレーションを行うと、
優性・劣性遺伝が存在した方が、
多様性が増す
という意外な結果になったので、
ここにまとめておきたいと思います。

優性・劣性遺伝とは

優性・劣性遺伝とは、決して遺伝される性質が、
優れている・劣っているということを指すものではありません。

私たちの遺伝情報は、染色体に記録されていますが、
それぞれの染色体は両親から1つずつ受け継いだ、
2本で一対となっています。

では、二本のうちどちらの性質が子供に現れるのでしょうか。
それを決めるのが、優性・劣性遺伝です。

遺伝子ごとに、優性・劣性が決まっており、
異なる性質の遺伝子がぶつかった場合には、
優性の遺伝子によって、子供の性質が決まるのです。

身近な例としては、血液型が挙げられるでしょう。
A型とO型では、A型の方が優性で、O型の方が劣性です。

A型を発現する遺伝子aと、
O型を発現する遺伝子oに対して、
一対のaaはA型を発現し、
ooはO型を発現します。

そして、これらがぶつかった、
aoの場合は優性であるA型が発現するのです。

逆に、血液型がA型だからと言って、
遺伝子は純粋にaaかと言うとそうとは限らず、
aoの人もいます。

このaoのA型の人同士が結婚して子供ができると、
組み合わせによっては、ooでO型の子供が生まれる場合もあるのです。

これが、遺伝子の優性・劣性で、
二つの異なる遺伝子がぶつかった時に、
どちらがの性質が優先的に発現するかを指しています。

遺伝的アルゴリズム

生物の遺伝の仕組みを模して、
問題を解決しようとする仕組みとして、
遺伝的アルゴリズムと呼ばれるものがあります。

この遺伝的アルゴリズムは、
ある程度、遺伝の仕組みを簡略化しているので、
優性・劣性遺伝の仕組みまでは取り入れていないことが多いのです。

今回は、遺伝的アルゴリズムに、
優性・劣性遺伝の仕組みを組み入れることで、
優性・劣性遺伝が世代毎の遺伝子の変化、
つまり、進化にどのように影響を与えるかを見ていきます。

二本一対の遺伝子

よくある遺伝的アルゴリズムでは、
遺伝子は一本の数値列として簡単化されることが多いです。

今回は、両親のから一本ずつ遺伝子を受け継ぎ、
その衝突が生じた場合の優性・劣性遺伝の概念を組み入れたいので、
遺伝子は、2本一対の数値列で表現することとします。

genGen = function(lenGen=lenGen){
  gen1 = sample(0:1,lenGen,replace = T)
  gen2 = sample(0:1,lenGen,replace = T)
  return(list(gen1=gen1,gen2=gen2))
}

上は、長さlenGenの二本一対の遺伝子をランダムに生成する関数です。
遺伝子は、0,1の2値の数字の列で表現することとします。

一対の遺伝子で、一体の個体を表すので、
この遺伝子を複数生成すれば、個体の集まり、
集団が作られます。

genGroup = function(N,lenGen=lenGen){
  lapply(1:N,function(i){genGen(lenGen)})
}

上の関数は、N体の個体の集団を生成する関数です。

表現型と遺伝型

劣性遺伝のように、遺伝子としては持っているけど、
その性質が表立って出てこない場合があります。

このような場合、遺伝子として持っている生物の情報を遺伝型
生物の性質として現れている生物の情報を表現型と言って、
区別すると便利です。

優性・劣性遺伝

優性・劣性遺伝の場合は、劣性同士の組み合わせ以外は、
優性遺伝子の性質が表現型として現れます。

つまり、0の値の遺伝子を優性、1の値の遺伝子を劣性として、
00,01なら優性の表現型、11の場合は劣性の表現型となります。

expGen = function(gen){
  return(gen$gen1 & gen$gen2)
}

上の関数は、遺伝型から表現型への変換を、
各遺伝子のANDを取ることで行なっています。

優性・劣性がない場合

上のような優性・劣性の区別がない場合、
異なる遺伝子がぶつかったら、
表現型はどちらかの性質をランダムに引き継ぐという風に考えられます。

実際、よくある遺伝的アルゴリズムでは、
優性・劣性の区別はなく、
ランダムに片方の親から遺伝子を部分・部分で引き継ぐようにされています。

expGen = function(gen){
  e = gen$gen1
  e[sample(0:1,length(e),replace = T)] = gen$gen2
  return(e)
}

このランダムな表現型の継承を上のような操作で実現します。
2本一対の遺伝子から、
どちらの性質が発現するかがランダムに決められます。

交叉

2人の両親はそれぞれ二本一対の遺伝子を持っており、
合計4本の遺伝子がありますが、
子供には、そのうちの2本が引き継がれます。

これは、両親から一本ずつ遺伝子を引き継ぐことで行われますが、
これを交叉と言います。

crossGen = function(genA,genB){
  gen1 = genA[[sample(2:1,1)]]
  gen2 = genB[[sample(2:1,1)]]
  return(list(gen1=gen1,gen2=gen2))
}

上は、交叉を行う関数です。
二つの二本一対の遺伝子から、
一本ずつランダムに取り出し、
それをまた一対にすることで、
子供の遺伝型を取り出します。

突然変異

遺伝子の中には、両親の遺伝子をそのまま引き継ぐだけではなく、
突然変異によって、 どちらの両親由来でもない遺伝子が生成されることもあります。

これは、それぞれの遺伝子について、一定の確率で、
遺伝子の0と1を反転させることで行います。

mutGen = function(gen, mutRate){
  rnd1 = runif(lenGen) < mutRate
  rnd2 = runif(lenGen) < mutRate
  gen$gen1[rnd1] = !gen$gen1[rnd1]
  gen$gen2[rnd2] = !gen$gen2[rnd2]
  return(gen)
}

上は、mutRateの確率で、各遺伝子を反転させることにより、
突然変異をシミュレーションする関数です。

淘汰・選択

遺伝的アルゴリズムでは、
より良い個体が子孫を残すことで、
世代が進むにつれて、適した個体が残るようになります。

個体の評価値

まずは、良い個体とは何かを決めるため、
個体の評価値を定義します。

今回は、単純に、それぞれの遺伝子毎に重みを割り当てて、
表現型と重みの積和を取ることで個体の評価値を計算することとします。

evalGen = function(gen,weights){
  sum(expGen(gen) * weights)
}

evalGens = function(Group,weights){
  scores = sapply(Group,function(gen){evalGen(gen,weights)})
  return(scores)
}

上は、個体の評価値を計算する関数です。
重みの値によって、その劣性遺伝が発現した時の評価値が上下します。
重みが正なら劣性遺伝が発現した方が良い個体に、
重みが負なら劣性遺伝が発現した方が悪い個体になるという具合です。

ペアの選択

N体の個体から、次の世代の個体を生み出す2人組みを選びます。
上記で定義した評価値の高い個体ほど、
選ばれやすくすることで、
より良い個体の遺伝子が世代を跨いで残るようにします。

normalizeScores = function(scores){
  if(max(scores) == min(scores)) return(rep(1,length(scores)))
  n_scores = (scores - min(scores)) / (max(scores) - min(scores))
  return(n_scores)
}

selectPair = function(Group,weights){
  scores = evalGens(Group,weights)
  n_scores = normalizeScores(scores)
  
  ids = sample(1:length(Group),2,replace = F,prob=n_scores / sum(n_scores))
  Group[ids]
}

上では、評価値の大きいものほど大きな確率を割り当てて、
N個の個体から確率的に2個の個体を選択しています。

一連の流れ

ここで、一連の世代交代の流れをまとめておきます。

  1. 各個体の評価値を計算する
  2. 評価値の大きさに従って両親を選択する
  3. 両親の遺伝子の交叉・突然変異により子供の遺伝子を生成する
  4. 定められた個体数まで上の手順を繰り返す

これで、1世代が進むことになります。

simulate_1step = function(Group,weights,mutRate){
  N = length(Group)
  newGroup = lapply(1:N,function(i){
    pair = selectPair(Group,weights)
    child = crossGen(pair[[1]],pair[[2]])
    mutGen(child,mutRate)})
}

上は、遺伝的アルゴリズムで1世代進めた個体群を生成する関数です。

この世代交代を繰り返すことで、集団が進化していき、
より適した個体が残っていくようになります。

simulate_GA = function(iniGroup,weights,iter=100,mutRate=0.0001){
  Group = iniGroup
  log_score = evalGens(Group,weights)
  searched = decGroup(Group)
  for(i in 1:iter){
    Group = simulate_1step(Group,weights,mutRate)
    log_score = rbind(log_score,evalGens(Group,weights))
    searched = rbind(searched,decGroup(Group))
  }
  
  return(list(Group=Group,score = evalGens(Group,weights),log_score=log_score,searched = searched))
}


bin2dec = function(bin){
  n = length(bin)
  ans = 0
  for(i in 1:n){
    ans = ans + bin[i]*2^(n-i)
  }
  return(ans)
}

decGroup = function(Group){
  sapply(Group,function(gen){
    bin2dec(expGen(gen))
  })
}

上は、遺伝的アルゴリズムで、
iter世代進めるシミュレーションを行う関数です。

途中で生成された個体やその評価値も記録しており、
関数の出力には次が含まれます。

  • Group : 最終的な個体の集団
  • score : 最終的な個体の集団の各評価値
  • log_score : 各世代・各個体の評価値
  • searched : 各世代・各個体の表現型(10進数)

シミュレーション

それでは、優性・劣性遺伝が世代間の遺伝にどのように影響を与えるか、
上記の遺伝的アルゴリズムを動かして確認してみます。

直感的には、優性・劣性遺伝があると、
個体の表現型は、優性:75%、劣性:25%と偏りが出てくるので、
50%:50%よりは、ある程度、特定の表現型がよく現れるようになり、
表現型の多様性が失われるのではないかと想像がつきます。

シミュレーション環境

ここでは、個体数を100、
各個体が持つ一本の遺伝子の長さを100、
世代数を100、
突然変異の確率を0.1%として、
シミュレーションを行います。

また、評価値を決める重みについては、
各遺伝子について、ランダムに、-1,0,1を割り当てることとします。

下は、上記のように環境を設定した上で、
シミュレーションを行うスクリプトです。

lenGen = 100
mutRate = 0.0001
  
weights = sample(1:-1,lenGen,replace = T)
Group = genGroup(N = 100,lenGen = lenGen)
res = simulate_GA(Group,weights,iter = 100, mutRate = mutRate)

各世代の評価値

下のグラフは、各世代での評価値をプロットしたものです。
左側が優性・劣性の区別のない場合、
右側が優性・劣性遺伝を組み込んだ場合の結果を表しています。

グラフ中の黒点は各個体の評価値を示しており、
赤線が最良の個体の評価値、
緑線が全固体の評価値の平均値を示しています。

f:id:u874072e:20190530144245p:plainf:id:u874072e:20190530144225p:plain

どちらも、徐々に評価値の個体が残っていくことが分かります。

最終的に得られる個体としては、
優性・劣性の区別がない場合の方が評価値が若干高いように見えます。

もう一つ大きな違いとしては、
優性・劣性遺伝を組み込んだ場合の方が、
より評価値の値が幅広くなっているように見えます。

このグラフからでは、個体の種類が多様で、
評価値が広がっているのか、
実は優性・劣性の区別がない場合も、
個体の種類は異なっているけど、
似たような評価値が得られているだけなのかの区別がつきません。

表現型の多様性

そこで、異なる表現型が何種類生み出されたのかを見ることにします。
下のグラフは、各世代でそれまでに、
何種類の表現型が得られたかを累積したものです。

左側が優性・劣性の区別のない場合、
右側が優性・劣性遺伝を組み込んだ場合の結果を表しています。

f:id:u874072e:20190530145102p:plainf:id:u874072e:20190530145126p:plain

明らかに、優性・劣性遺伝を組み込んだ方が、
より早い段階で、より多くの表現型が生み出されていることが分かります。

これは、予想に反して、
優性・劣性のような偏りを加えた方が、
帰って、多様な個体が生み出されることを示唆しています。

つまり、遺伝子の優性・劣性は、
生物の多様性に寄与している可能性がありそうだということです。

考察

なぜ、優性・劣性という偏りを加えた方が、
返って、様々な個体が生まれやすいのでしょうか。

そもそも、遺伝的アルゴリズムの中では、
良い個体の遺伝子を残すように淘汰・選択が働くので、
放って置くと、遺伝子の種類は減っていきます。

こうして、同じような遺伝子ばかり残るので、
多様性は徐々に減る方向に進みます。
いわゆる進化の袋小路と呼ばれる状況に引き込まれていきます。

すると、似通った遺伝子を持った個体同士が子供を作るようになります。

劣性遺伝は、両親が共に劣性遺伝子を持っていないと、
発現のしようがないため、似た遺伝子を持った両親の元には、
全く異なる遺伝子を持つ両親を持つ子供よりも、
劣性遺伝子を発現しやすい子供が生まれることになります。

近親婚が危険であるとされる理由の一つに、
劣性遺伝による珍しい遺伝病の発現のリスクが高まることが挙げられますが、
進化の袋小路に向かうにつれて、
まさにその劣性遺伝子が発現しやすくなる
のです。

遺伝型の種類が減るにつれて、
表現型では劣性遺伝という希少なはずの性質がよく現れるようになります。

まるで、袋小路に押さえこまれようとしている種の、
起爆剤のような働きを劣性遺伝子が担っているかのようです。

こう考えると、優性・劣性遺伝とは、
進化をうまく進めるために生物が獲得した、
実に巧妙な仕組みなのかもしれません。

まとめ

近親婚で劣性遺伝が発現しやすいということから、
劣性遺伝は、進化の袋小路で、
表現型の多様性を保つための働きをしているのではという考えと、
いやでも、普通に考えれば、バイアスがかかっていると、
多様性は減るよなという考えが衝突したので、
シミュレーションをしてみました。

シミュレーションの結果、バイアスをかけた方が、
返って多様性が増える場合があるという、
一見、不思議な状況が得られました。

生物システムの複雑さ、巧妙さの一端が見えたような、
面白いシミュレーションでした。

プライバシーポリシー