⑥エニグマ暗号機のローター結線解読プロジェクト 機械学習
業務上の必要性から機械学習を勉強していたのだが、ふと思いついたのでClaudeに相談してみる。できれば、趣味でやる事が仕事になればより面白いのでね。
いわゆるバッチ学習的な機械学習しか知らなければこんな発想は出てこない。オンライン学習や強化学習の様な動的に動く仕組みを知っているとこんな突拍子も無いアイデアが出てくる。
まずは、教師データではなくてもっと直接的な初期値を与える方法はあるだろうか?ここで、通常の機械学習からは離れる。
これは… ペナルティと書いてるので強化学習みたいなものだろうか。
まずは気にせず進む。
import numpy as np
from typing import List, Dict, Tuple
class EnigmaMLDecoder:
def __init__(self, alphabet_size: int = 26):
self.alphabet_size = alphabet_size
self.known_transformations = {} # 既知の変換を保存
self.position_patterns = {} # 各ローター位置でのパターン
self.probability_matrix = np.ones((alphabet_size, alphabet_size)) / alphabet_size
def update_probabilities(self, plaintext: str, ciphertext: str, position: int) -> None:
"""新しいクリブ(既知の平文と暗号文のペア)から確率を更新"""
for p, c in zip(plaintext, ciphertext):
p_idx = ord(p) - ord('A')
c_idx = ord(c) - ord('A')
# この位置での変換確率を更新
position_key = position % 26
if position_key not in self.position_patterns:
self.position_patterns[position_key] = np.ones((self.alphabet_size, self.alphabet_size))
# この変換の確率を上げる
self.position_patterns[position_key][p_idx, c_idx] += 1
# 正規化
self.position_patterns[position_key] /= self.position_patterns[position_key].sum(axis=1, keepdims=True)
def predict_rotor_settings(self, ciphertext: str, position: int) -> List[Tuple[str, float]]:
"""現在の位置での最も可能性の高いローター設定を予測"""
position_key = position % 26
if position_key not in self.position_patterns:
return [('Unknown', 0.0)]
# 各文字の変換確率を計算
probabilities = []
pattern = self.position_patterns[position_key]
for i in range(self.alphabet_size):
prob = pattern[i].max()
most_likely_char = chr(pattern[i].argmax() + ord('A'))
probabilities.append((most_likely_char, prob))
# 確率で降順ソート
return sorted(probabilities, key=lambda x: x[1], reverse=True)
def decode_message(self, ciphertext: str, start_position: int) -> str:
"""暗号文の解読を試みる"""
decoded = []
position = start_position
for c in ciphertext:
predictions = self.predict_rotor_settings(c, position)
decoded.append(predictions[0][0]) # 最も確率の高い文字を選択
position += 1
return ''.join(decoded)
def train_with_crib(self, crib_text: str, cipher_text: str, position: int) -> None:
"""クリブ(既知の平文と暗号文のペア)で学習"""
self.update_probabilities(crib_text, cipher_text, position)
import numpy as np
from typing import List, Dict, Tuple
class EnigmaMLDecoder:
def __init__(self, alphabet_size: int = 26):
self.alphabet_size = alphabet_size
self.known_transformations = {} # 既知の変換を保存
self.position_patterns = {} # 各ローター位置でのパターン
self.probability_matrix = np.ones((alphabet_size, alphabet_size)) / alphabet_size
def update_probabilities(self, plaintext: str, ciphertext: str, position: int) -> None:
"""新しいクリブ(既知の平文と暗号文のペア)から確率を更新"""
for p, c in zip(plaintext, ciphertext):
p_idx = ord(p) - ord('A')
c_idx = ord(c) - ord('A')
# この位置での変換確率を更新
position_key = position % 26
if position_key not in self.position_patterns:
self.position_patterns[position_key] = np.ones((self.alphabet_size, self.alphabet_size))
# この変換の確率を上げる
self.position_patterns[position_key][p_idx, c_idx] += 1
# 正規化
self.position_patterns[position_key] /= self.position_patterns[position_key].sum(axis=1, keepdims=True)
def predict_rotor_settings(self, ciphertext: str, position: int) -> List[Tuple[str, float]]:
"""現在の位置での最も可能性の高いローター設定を予測"""
position_key = position % 26
if position_key not in self.position_patterns:
return [('Unknown', 0.0)]
# 各文字の変換確率を計算
probabilities = []
pattern = self.position_patterns[position_key]
for i in range(self.alphabet_size):
prob = pattern[i].max()
most_likely_char = chr(pattern[i].argmax() + ord('A'))
probabilities.append((most_likely_char, prob))
# 確率で降順ソート
return sorted(probabilities, key=lambda x: x[1], reverse=True)
def decode_message(self, ciphertext: str, start_position: int) -> str:
"""暗号文の解読を試みる"""
decoded = []
position = start_position
for c in ciphertext:
predictions = self.predict_rotor_settings(c, position)
decoded.append(predictions[0][0]) # 最も確率の高い文字を選択
position += 1
return ''.join(decoded)
def train_with_crib(self, crib_text: str, cipher_text: str, position: int) -> None:
"""クリブ(既知の平文と暗号文のペア)で学習"""
self.update_probabilities(crib_text, cipher_text, position)
import numpy as np
from collections import defaultdict
class RLEnigmaDecoder:
def __init__(self, alphabet_size=26, learning_rate=0.1, discount_factor=0.9, epsilon=0.1):
self.alphabet_size = alphabet_size
self.lr = learning_rate
self.gamma = discount_factor
self.epsilon = epsilon
# Q値テーブル: (現在の状態, アクション) -> 期待報酬
self.Q = defaultdict(lambda: np.zeros(alphabet_size))
# 状態は (現在位置, 直前のn文字) のタプル
self.state_memory = 3 # 直前3文字を記憶
def get_state(self, position, previous_chars):
"""状態の取得"""
return (position % 26, tuple(previous_chars[-self.state_memory:]))
def choose_action(self, state):
"""ε-greedy戦略による行動選択"""
if np.random.random() < self.epsilon:
return np.random.randint(self.alphabet_size)
return np.argmax(self.Q[state])
def update(self, state, action, reward, next_state):
"""Q学習によるアップデート"""
best_next_action = np.argmax(self.Q[next_state])
td_target = reward + self.gamma * self.Q[next_state][best_next_action]
td_error = td_target - self.Q[state][action]
self.Q[state][action] += self.lr * td_error
def decode(self, ciphertext, known_pairs=None):
"""暗号文の解読"""
position = 0
previous_chars = [''] * self.state_memory
decoded = []
for cipher_char in ciphertext:
state = self.get_state(position, previous_chars)
action = self.choose_action(state)
decoded_char = chr(action + ord('A'))
# 報酬の計算
reward = 0
if known_pairs and position < len(known_pairs):
if decoded_char == known_pairs[position]:
reward = 1
else:
reward = -0.1
# 次の状態の準備
next_previous = previous_chars[1:] + [decoded_char]
next_state = self.get_state(position + 1, next_previous)
# Q値の更新
self.update(state, action, reward, next_state)
# 状態の更新
previous_chars = next_previous
position += 1
decoded.append(decoded_char)
return ''.join(decoded)
def train(self, training_data, episodes=1000):
"""既知の平文-暗号文ペアで学習"""
for _ in range(episodes):
for plaintext, ciphertext in training_data:
self.decode(ciphertext, known_pairs=plaintext)
# HMMを使用した例(概念的なコード)
from hmmlearn import hmm
class HMMEnigmaDecoder:
def __init__(self):
self.model = hmm.MultinomialHMM(n_components=26) # ローター位置
def train(self, observations, states):
self.model.fit(observations, states)
def decode(self, ciphertext):
# Viterbiアルゴリズムで最も可能性の高い状態系列を推定
states = self.model.decode(ciphertext)
return self.convert_states_to_plaintext(states)
class HybridEnigmaDecoder:
def __init__(self):
self.rl_decoder = RLEnigmaDecoder()
self.bayes_decoder = BayesianEnigmaDecoder()
self.hmm_decoder = HMMEnigmaDecoder()
def decode(self, ciphertext):
# 各デコーダーの予測を取得
rl_pred = self.rl_decoder.decode(ciphertext)
bayes_pred = self.bayes_decoder.decode(ciphertext)
hmm_pred = self.hmm_decoder.decode(ciphertext)
# 信頼度スコアに基づいて予測を統合
final_prediction = self.combine_predictions([
(rl_pred, self.get_rl_confidence()),
(bayes_pred, self.get_bayes_confidence()),
(hmm_pred, self.get_hmm_confidence())
])
return final_prediction
気になって「機械学習でパターン認識をする技術は何だ?」と聞いたら、教師あり学習と答えてきた。まー、確かにそうなんだけど…
ここで最後の質問。手元にある機械学習の資料のタイトルを入れて、有効な物を訊いてみる。