Trials for best metrics for affine cipher breaks
authorNeil Smith <neil.git@njae.me.uk>
Mon, 21 Apr 2014 11:57:01 +0000 (12:57 +0100)
committerNeil Smith <neil.git@njae.me.uk>
Mon, 21 Apr 2014 11:57:01 +0000 (12:57 +0100)
affine_break_parameter_trials.csv [new file with mode: 0644]
find_best_affine_break_parameters.py [new file with mode: 0644]

diff --git a/affine_break_parameter_trials.csv b/affine_break_parameter_trials.csv
new file mode 100644 (file)
index 0000000..75f3f16
--- /dev/null
@@ -0,0 +1,10 @@
+"name",500,250,100,50,20\r
+"Pletters",2442,2425,2550,2461,2067\r
+"cosine_similarity + euclidean_scaled",2487,2492,2513,2455,1731\r
+"cosine_similarity + normalised",2503,2573,2454,2435,1680\r
+"l1 + euclidean_scaled",2495,2477,2506,2419,1812\r
+"l1 + normalised",2488,2521,2465,2466,1895\r
+"l2 + euclidean_scaled",2524,2467,2529,2470,1763\r
+"l2 + normalised",2566,2545,2481,2433,1756\r
+"l3 + euclidean_scaled",2444,2458,2560,2426,1582\r
+"l3 + normalised",2476,2524,2463,2354,1433\r
diff --git a/find_best_affine_break_parameters.py b/find_best_affine_break_parameters.py
new file mode 100644 (file)
index 0000000..e3d6bad
--- /dev/null
@@ -0,0 +1,85 @@
+import random
+import collections
+from cipher import *
+from cipherbreak import *
+import itertools
+import csv
+
+corpus = sanitise(''.join([open('shakespeare.txt', 'r').read(), 
+    open('sherlock-holmes.txt', 'r').read(), 
+    open('war-and-peace.txt', 'r').read()]))
+corpus_length = len(corpus)
+
+euclidean_scaled_english_counts = norms.euclidean_scale(english_counts)
+
+metrics = [{'func': norms.l1, 'invert': True, 'name': 'l1'}, 
+    {'func': norms.l2, 'invert': True, 'name': 'l2'},
+    {'func': norms.l3, 'invert': True, 'name': 'l3'},
+    {'func': norms.cosine_similarity, 'invert': False, 'name': 'cosine_similarity'}]
+    # {'func': norms.harmonic_mean, 'invert': True, 'name': 'harmonic_mean'},
+    # {'func': norms.geometric_mean, 'invert': True, 'name': 'geometric_mean'}]
+scalings = [{'corpus_frequency': normalised_english_counts, 
+         'scaling': norms.normalise,
+         'name': 'normalised'},
+        {'corpus_frequency': euclidean_scaled_english_counts, 
+         'scaling': norms.euclidean_scale,
+         'name': 'euclidean_scaled'}]
+message_lengths = [2000, 1000, 500, 250, 100, 50, 20]
+
+trials = 5000
+
+scores = {}
+
+
+def make_frequency_compare_function(target_frequency, frequency_scaling, metric, invert):
+    def frequency_compare(text):
+        counts = frequency_scaling(frequencies(text))
+        if invert:
+            score = -1 * metric(target_frequency, counts)
+        else:
+            score = metric(target_frequency, counts)
+        return score
+    return frequency_compare
+
+def scoring_functions():
+    return [{'func': make_frequency_compare_function(s['corpus_frequency'], 
+                s['scaling'], m['func'], m['invert']),
+            'name': '{} + {}'.format(m['name'], s['name'])}
+        for m in metrics
+        for s in scalings] + [{'func': Pletters, 'name': 'Pletters'}]
+
+def eval_scores():
+    [eval_one_score(f, l) 
+        for f in scoring_functions()
+        for l in message_lengths]
+
+def eval_one_score(scoring_function, message_length):
+    print(scoring_function['name'], message_length, ': ', end='', flush=True)
+    if scoring_function['name'] not in scores:
+        scores[scoring_function['name']] = collections.defaultdict(int)
+    for _ in range(trials):
+        sample_start = random.randint(0, corpus_length - message_length)
+        sample = corpus[sample_start:(sample_start + message_length)]
+        multiplier = random.choice([x for x in range(1, 26, 2) if x != 13])
+        adder = random.randint(0, 25)
+        one_based = random.choice([True, False])
+        key = (multiplier, adder, one_based)
+        ciphertext = affine_encipher(sample, multiplier, adder, one_based)
+        found_key, _ = affine_break(ciphertext, scoring_function['func'])
+        if found_key == key:
+            scores[scoring_function['name']][message_length] += 1 
+    print(scores[scoring_function['name']][message_length], '/', trials)
+    return scores[scoring_function['name']][message_length]
+
+def show_results():
+    with open('affine_break_parameter_trials.csv', 'w') as f:
+        writer = csv.DictWriter(f, ['name'] + message_lengths, 
+            quoting=csv.QUOTE_NONNUMERIC)
+        writer.writeheader()
+        for scoring in sorted(scores.keys()):
+            scores[scoring]['name'] = scoring
+            writer.writerow(scores[scoring])
+
+print('Starting...')
+eval_scores()
+show_results()