-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenetic_algorithm.py
More file actions
131 lines (106 loc) · 4.37 KB
/
Copy pathgenetic_algorithm.py
File metadata and controls
131 lines (106 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import random
import copy
from cost_function import calculate_layout_cost
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
def get_random_layout() -> str:
lst = list(ALPHABET)
random.shuffle(lst)
return "".join(lst)
def order_crossover(parent1: str, parent2: str) -> str:
# OX 교차: parent1 구간 보존 후 나머지를 parent2 순서로 채움
size = len(parent1)
cx1 = random.randint(0, size - 2)
cx2 = random.randint(cx1 + 1, size - 1)
child = [None] * size
child[cx1:cx2+1] = list(parent1[cx1:cx2+1])
p2_idx = (cx2 + 1) % size
child_idx = (cx2 + 1) % size
filled_count = cx2 - cx1 + 1
while filled_count < size:
char_to_insert = parent2[p2_idx]
if char_to_insert not in child:
child[child_idx] = char_to_insert
child_idx = (child_idx + 1) % size
filled_count += 1
p2_idx = (p2_idx + 1) % size
return "".join(child)
def mutate_swap(layout: str) -> str:
lst = list(layout)
i, j = random.sample(range(len(lst)), 2)
lst[i], lst[j] = lst[j], lst[i]
return "".join(lst)
def mutate_inversion(layout: str) -> str:
lst = list(layout)
cx1 = random.randint(0, len(lst) - 2)
cx2 = random.randint(cx1 + 1, len(lst) - 1)
lst[cx1:cx2+1] = reversed(lst[cx1:cx2+1])
return "".join(lst)
def select_tournament(population: list, fitnesses: list, tournament_size: int = 5) -> str:
selected_indices = random.sample(range(len(population)), tournament_size)
best_idx = max(selected_indices, key=lambda idx: fitnesses[idx])
return population[best_idx]
def run_genetic_algorithm(
unigram_counts: dict,
bigram_counts: dict,
total_chars: int,
weights: tuple = (1.0, 2.0, 1.5),
pop_size: int = 100,
generations: int = 150,
crossover_rate: float = 0.8,
mutation_rate: float = 0.2,
mutation_decay: float = 0.99,
elitism_count: int = 5,
tournament_size: int = 5,
seed_layouts: list = None
) -> tuple:
population = []
if seed_layouts:
for seed in seed_layouts:
if len(seed) == 26:
population.append(seed)
while len(population) < pop_size:
population.append(get_random_layout())
history = []
current_mutation_rate = mutation_rate
for _ in range(generations):
costs = []
fitnesses = []
for layout in population:
cost_details = calculate_layout_cost(layout, unigram_counts, bigram_counts, total_chars, weights)
cost_val = cost_details["total_cost"]
costs.append(cost_val)
fitnesses.append(1.0 / (cost_val + 1e-6))
best_idx = min(range(pop_size), key=lambda idx: costs[idx])
best_layout = population[best_idx]
best_cost_val = costs[best_idx]
history.append(best_cost_val)
sorted_indices = sorted(range(pop_size), key=lambda idx: costs[idx])
new_population = [population[idx] for idx in sorted_indices[:elitism_count]]
while len(new_population) < pop_size:
parent1 = select_tournament(population, fitnesses, tournament_size)
parent2 = select_tournament(population, fitnesses, tournament_size)
if random.random() < crossover_rate:
child = order_crossover(parent1, parent2)
else:
child = parent1 if random.random() < 0.5 else parent2
if random.random() < current_mutation_rate:
if random.random() < 0.5:
child = mutate_swap(child)
else:
child = mutate_inversion(child)
new_population.append(child)
population = new_population
current_mutation_rate *= mutation_decay
final_costs = [calculate_layout_cost(layout, unigram_counts, bigram_counts, total_chars, weights)["total_cost"] for layout in population]
best_final_idx = min(range(pop_size), key=lambda idx: final_costs[idx])
best_layout = population[best_final_idx]
best_cost_details = calculate_layout_cost(best_layout, unigram_counts, bigram_counts, total_chars, weights)
total_calls = pop_size * generations + pop_size + 1
return best_layout, best_cost_details, history, total_calls
if __name__ == "__main__":
p1 = ALPHABET
p2 = ALPHABET[::-1]
print(f"부모1: {p1}")
print(f"부모2: {p2}")
print(f"OX교차: {order_crossover(p1, p2)}")
print(f"교환돌연변이: {mutate_swap(p1)}")