サイコロを振ってモチーフを見つける方法(超詳細版)- なぜランダムが正解を導くのか
🎯 まず、この講義で何を学ぶのか
最終ゴール:サイコロを振る(ランダムに選ぶ)という一見いい加減な方法で、正確にDNAモチーフを見つける方法を学びます。
でも、ちょっと待ってください。そもそも..。
🤔 ステップ0:なぜサイコロを振る必要があるの?
前回の問題を思い出してみよう
前回学んだモチーフ列挙アルゴリズム:
✓ 確実に正解を見つける
✗ でも計算量が膨大(指数的)
✗ k=15が限界
現実の問題:
- モチーフの長さが分からない(k=8?20?)
- 全てを試すには時間がかかりすぎる
サイコロを振る? 本当に?
あなたの直感:
「サイコロ = ランダム = いい加減 = 正確じゃない」
でも実は...
「サイコロ × 1000回 = 統計的に正しい答えに収束」
例:コイン投げ
1回:表か裏か分からない(50%)
1000回:約500回ずつ(確率の法則)
📖 ステップ1:ランダム化アルゴリズムの基本アイデア
1-1. 普通のアルゴリズムとの違い
1-2. なぜランダムが有効なの?
アナロジー:迷路を解く方法
方法1:総当たり(前回の方法)
全ての道を試す → 確実だが時間がかかる
方法2:ランダムウォーク(今回の方法)
適当に歩き回る → 運が良ければすぐ出口
→ 何度も試せば必ず出口に到達
1-3. 生物学でのランダムの重要性
実は生物もランダムを使っている!
免疫システム:
- ランダムに抗体を作る
- 病原体に合うものが偶然できる
- それを大量生産
進化:
- ランダムな突然変異
- 有利なものが生き残る
- 結果的に適応
🔄 ステップ2:プロファイルとモチーフの相互変換
2-1. まず用語を整理しよう
# モチーフ(Motifs):各配列から選んだk-merの集合
motifs = [
"ATGC", # 配列1から選んだ4-mer
"ATGC", # 配列2から選んだ4-mer
"ACGC", # 配列3から選んだ4-mer
"ATGT" # 配列4から選んだ4-mer
]
# プロファイル(Profile):各位置の文字の頻度
profile = {
# 位置1 位置2 位置3 位置4
'A': [1.0, 0.0, 0.0, 0.0],
'C': [0.0, 0.25, 0.0, 0.75],
'G': [0.0, 0.0, 1.0, 0.0],
'T': [0.0, 0.75, 0.0, 0.25]
}
2-2. プロファイル → モチーフの変換
def profile_to_motifs(profile, dna_list):
"""
プロファイルを使って、各配列から最も確率の高いk-merを選ぶ
なぜこれができる?
プロファイル = 「理想的なパターン」の統計情報
→ 各配列でこれに最も近いものを選べばいい
"""
motifs = []
for dna in dna_list:
best_kmer = None
best_probability = -1
# 各位置のk-merの確率を計算
for i in range(len(dna) - k + 1):
kmer = dna[i:i+k]
# このk-merがプロファイルにどれだけ合うか
probability = 1.0
for j, nucleotide in enumerate(kmer):
probability *= profile[nucleotide][j]
if probability > best_probability:
best_probability = probability
best_kmer = kmer
motifs.append(best_kmer)
return motifs
2-3. モチーフ → プロファイルの変換
def motifs_to_profile(motifs):
"""
モチーフ集合から頻度プロファイルを作る
なぜこれが重要?
複数のモチーフの「平均的な特徴」を抽出
→ 次の探索の指針になる
"""
k = len(motifs[0])
t = len(motifs)
profile = {'A': [], 'C': [], 'G': [], 'T': []}
for j in range(k): # 各位置について
column = [motifs[i][j] for i in range(t)]
for nucleotide in 'ACGT':
count = column.count(nucleotide)
frequency = count / t
profile[nucleotide].append(frequency)
return profile
💡 ステップ3:ランダム化モチーフ探索の仕組み
3-1. アルゴリズムの全体像
def randomized_motif_search(dna_list, k, t):
"""
ランダムから始めて、徐々に改善していく
"""
# ステップ1:完全にランダムなモチーフから始める
motifs = select_random_motifs(dna_list, k)
best_motifs = motifs
# ステップ2:改善ループ
while True:
# プロファイルを作る
profile = motifs_to_profile(motifs)
# プロファイルから新しいモチーフを作る
motifs = profile_to_motifs(profile, dna_list)
# 改善されたか確認
if score(motifs) < score(best_motifs):
best_motifs = motifs
else:
# 改善されなくなったら終了
return best_motifs
3-2. でも、なぜこれで上手くいくの?
ここが魔法のような部分です!
疑問:完全にランダムから始めて、なぜ正解にたどり着ける?
🎲 ステップ4:ランダムの魔法を理解する
4-1. 具体例で考えてみよう
# 例:4つの配列に埋め込まれたモチーフ "ACGT"(1文字変異あり)
sequences = [
"TTACGTAA", # 位置2にACGT
"GGACGCTT", # 位置2にACGC(1文字変異)
"CCAACGTG", # 位置3にAACGT(1文字変異)
"AAACGTCC" # 位置2にACGT
]
# ランダムに選んだ初期モチーフ(ほとんど外れ)
random_motifs = [
"TTAC", # 間違い(正解はACGT)
"GGAC", # 間違い(正解はACGC)
"CCAA", # 間違い(正解はACGT)
"ACGT" # 偶然正解!
]
4-2. たった1つの正解がもたらす偏り
# このランダムモチーフからプロファイルを作ると...
profile = motifs_to_profile(random_motifs)
# 位置1の頻度を見てみると:
# T: 1/4, G: 1/4, C: 1/4, A: 1/4 → でも待って!
# 実際は:
# T: 1/4, G: 1/4, C: 1/4, A: 1/4
# あれ?均等?
# でも位置2を見ると:
# T: 1/4, G: 1/4, A: 1/4, C: 1/4
# 実際は:
# T: 1/4, G: 1/4, C: 2/4, A: 0/4
# Cが多い!これが埋め込まれたパターンのC!
4-3. 偏りが正解を引き寄せる
🔍 ステップ5:なぜ「偏り」が重要なのか
5-1. 偏りの数学的意味
def calculate_bias(profile, true_pattern="ACGT"):
"""
プロファイルがどれだけ真のパターンに偏っているか
"""
bias_score = 0
for i, nucleotide in enumerate(true_pattern):
# その位置でのnucleotideの確率
probability = profile[nucleotide][i]
# 期待値(ランダムなら0.25)との差
bias = probability - 0.25
bias_score += bias
return bias_score
# 完全にランダムなプロファイル
random_profile = {
'A': [0.25, 0.25, 0.25, 0.25],
'C': [0.25, 0.25, 0.25, 0.25],
'G': [0.25, 0.25, 0.25, 0.25],
'T': [0.25, 0.25, 0.25, 0.25]
}
print(f"ランダムの偏り:{calculate_bias(random_profile)}")
# 結果:0
# 1つ正解を含むプロファイル
biased_profile = {
'A': [0.5, 0.25, 0.25, 0.25], # Aが多い
'C': [0.25, 0.5, 0.25, 0.25], # Cが多い
'G': [0.25, 0.25, 0.5, 0.25], # Gが多い
'T': [0.25, 0.25, 0.25, 0.5] # Tが多い
}
print(f"偏りありプロファイル:{calculate_bias(biased_profile)}")
# 結果:1.0(正の偏り!)
5-2. 偏りの増幅効果
初回:1/10のモチーフが正解
↓
プロファイルに小さな偏り(10%)
↓
次回:3/10のモチーフが正解に近づく
↓
プロファイルの偏りが増大(30%)
↓
その次:6/10のモチーフが正解に
↓
偏りがさらに増大(60%)
↓
最終的に:10/10が正解!
🦠 ステップ6:実例 - 結核菌はどうやって冬眠するのか
6-1. 結核という病気の恐ろしさ
19世紀ヨーロッパ:人口の1/4が結核で死亡
現在:世界人口の1/3が結核菌を保有
(でも発病するのは一部)
なぜ?
→ 結核菌は「冬眠」できる!
6-2. 結核菌の生存戦略
6-3. 冬眠の秘密を探る
# 実験:酸素を奪うとどの遺伝子が活性化する?
hypoxia_genes = [
"gene1_upstream_sequence...", # 低酸素で活性化
"gene2_upstream_sequence...", # 低酸素で活性化
# ... 約25個の遺伝子
]
# 疑問:これらの遺伝子に共通するモチーフは?
# → 冬眠スイッチの正体!
📊 ステップ7:どのモチーフが正しいか - エントロピーという指標
7-1. モチーフの長さ問題
# k=8で探索
result_k8 = randomized_motif_search(hypoxia_genes, k=8)
# 結果:ATGATCAA
# k=12で探索
result_k12 = randomized_motif_search(hypoxia_genes, k=12)
# 結果:ATGATCAAGCTT
# k=20で探索
result_k20 = randomized_motif_search(hypoxia_genes, k=20)
# 結果:ATGATCAAGCTTGCGTATGC
# どれが正しい?
7-2. エントロピーとは何か
まず日常的な例から:
例1:全員が同じ答えのアンケート
「朝食は?」→ 全員「パン」
→ 情報量少ない(予測可能)
→ エントロピー低い
例2:バラバラな答えのアンケート
「朝食は?」→ パン、ご飯、シリアル、なし...
→ 情報量多い(予測困難)
→ エントロピー高い
7-3. モチーフのエントロピー計算
import math
def calculate_entropy(column):
"""
1つの位置(列)のエントロピーを計算
エントロピー = -Σ(p_i × log2(p_i))
"""
counts = {'A': 0, 'C': 0, 'G': 0, 'T': 0}
# 各文字の出現回数を数える
for nucleotide in column:
counts[nucleotide] += 1
# 確率に変換してエントロピー計算
entropy = 0
total = len(column)
for count in counts.values():
if count > 0:
p = count / total
entropy -= p * math.log2(p)
return entropy
# 例:完全に保存された列
conserved = ['A', 'A', 'A', 'A']
print(f"保存列のエントロピー:{calculate_entropy(conserved)}")
# 結果:0(情報が確定的)
# 例:完全にランダムな列
random = ['A', 'C', 'G', 'T']
print(f"ランダム列のエントロピー:{calculate_entropy(random)}")
# 結果:2(最大の不確実性)
7-4. なぜエントロピーが重要か
生物学的に意味のあるモチーフ
↓
進化的に保存されている
↓
変異が少ない
↓
エントロピーが低い
逆に、偶然見つかったパターン
↓
保存されていない
↓
バラバラ
↓
エントロピーが高い
🎨 ステップ8:モチーフロゴ - 視覚的な理解
8-1. モチーフロゴとは
各位置での文字の重要度を視覚化
高さ = 2 - エントロピー
(情報量を表す)
文字の大きさ = その文字の頻度
8-2. ロゴの読み方
位置1: A(巨大)
C,G,T(極小)
→ ほぼ必ずA
位置2: C(大)
G(中)
A,T(小)
→ Cが多いが、Gもあり
位置3: 全部同じ大きさ
→ どの文字でもOK(重要でない位置)
🔬 ステップ9:結核菌データでの実際の結果
9-1. アルゴリズムの比較
# 実際の結核菌データで実行
true_motif = "TCGGGGATTTCC" # 実験で確認された真のモチーフ
# モチーフ列挙(前回の方法)
enumeration_result = "TCGGGGAT----" # 後半が見つからない
# ランダム化モチーフ探索(今回の方法)
random_search_result = "TCGGGGATTTCC" # 完全に一致!
# なぜランダムの方が良い結果?
# → 局所的な最適解にとらわれない
# → 何度も試すことで真の答えに近づく
9-2. 成功の理由
1. 完全性よりも確率的な正しさ
列挙:全て調べるが、計算量の制限でk≤12
ランダム:k=20でも探索可能
2. ノイズへの耐性
列挙:ノイズも真のパターンも同等に扱う
ランダム:統計的に真のパターンが浮かび上がる
3. 実用性
列挙:1回の実行に数時間
ランダム:数秒×1000回 = 実用的な時間
💭 ステップ10:なぜランダムが生物学で重要か
10-1. 生物自身もランダムを使う
DNA修復:
- ランダムにDNAをチェック
- 傷を見つけたら修復
- 統計的に全体をカバー
免疫システム:
- ランダムに抗体を生成
- 病原体に合うものを選択
- 効率的な防御
進化:
- ランダムな突然変異
- 有利なものが生存
- 最適化without設計
10-2. ランダムアルゴリズムの哲学
決定的アルゴリズム:
「必ず正解を見つける」
→ でも時間がかかりすぎることも
ランダムアルゴリズム:
「高確率で正解を見つける」
→ 実用的な時間で解ける
どちらを選ぶ?
→ 問題による!
📝 まとめ:今日学んだことを整理
レベル1:基本概念の理解
ランダム化アルゴリズム
↓
サイコロを振って初期値を選ぶ
↓
でも統計的に正解に収束
レベル2:アルゴリズムの仕組み
1. ランダムなモチーフから開始
2. モチーフ→プロファイル変換
3. プロファイル→より良いモチーフ
4. 改善がなくなるまで繰り返し
5. 全体を何度も試行
レベル3:なぜ成功するか
埋め込まれたパターンの存在
↓
ランダム選択でも一部は正解に近い
↓
プロファイルに偏りが生じる
↓
偏りが正解を引き寄せる
↓
収束!
レベル4:実用的な知見
- エントロピーでモチーフの質を評価
- モチーフロゴで視覚化
- 実際の生物学データで有効
- 計算時間が現実的
🚀 次回予告:ギブスサンプラー
さらに賢いランダム化
今回:全モチーフを一度に更新
次回:1つずつ更新(ギブスサンプラー)
なぜ1つずつ?
→ より細かい調整が可能
→ 局所最適を避けやすい
→ 収束が速い
考えてみてください
- なぜ全部一度に更新しない方が良い場合がある?
- サンプリングの順番は重要?
- 温度パラメータとは?
これらの答えは次回明らかに!
🎲 最後に:ランダムの深い意味
ランダム = 無秩序?
いいえ!
ランダム = 全ての可能性を公平に探索
だから...
ランダム × 繰り返し = 真実の発見
これが確率的アルゴリズムの美しさです。