Group genetic algorithm was proposed based on the weaknesses of GA applied to the grouping problems. The crossover operation is the biggest difference.
Suppose there are two parent:
(1) Select the position of the insertion in the group part of the first parent, and then randomly select a group section of the other one
(2) Copy all the contents of the first parent to an empty chromosome, and then copy and insert the selected group section of the other chosen parent to the new chromosome.
(3) Eliminate duplicate objects which are copied from the first parent—object redundancy checking
(4) Adjust the resulting groups according to the hard constraints of the problem to be solved
(5) Apply Steps 1–4 to the pair of parents with their roles exchanged to generate another child.
The whole procedure can be described as follows:
In order to help the crossover operator select different combinations of groups to exchange between two parents, the inversion operator works on the object part, namely the rearrangement of the positions of groups. Groups that are closer with regard to a sequence position have a greater chance of being transferred together.
def cross_operation(first_parent, second_parent, group_start):
group_part = second_parent[group_start:]
# select the position of the insertion-first parent, group_start: feature_nums
insert_pos = random.randint(0, len(group_part))
# select a section of the group of another parent
points = random.sample(range(0, len(group_part)+1), 2)
section = group_part[min(points): max(points)]
# object redundancy checking on the new chromosome
second_index = {key: list(np.where(second_parent[:group_part] == key)[0]) for key in section}
first_index = {key: list(set(np.where(first_parent[:group_part] == key)[0]) - set(second_index.values()))
for key in set(first_parent[:group_part])}
non_empty = filter(lambda key: len(first_index[key]) > 0, first_index.keys())
first_index = {key: first_index[key] for key in non_empty}
# situation I
new_clusters = len(first_index) + len(second_index)
while new_clusters < len(group_part):
alternative = filter(lambda key: len(first_index[key]) > group_start / len(group_part), first_index)
choice = random.choice(alternative)
count = random.randint(1, len(first_index[choice]) - 1)
first_index[choice + len(group_part)] = random.sample(first_index[choice], count)
first_index[choice] = filter(lambda x: x not in first_index[choice + len(group_part)], first_index[choice])
new_clusters += 1
# situation II
while new_clusters > len(group_part):
'''
A group with fewer attributes has a higher probability of being selected or removal
'''
count = {key: len(first_index[key]) / len(first_index.values()) for key in non_empty}
sort_item = sorted(count.items(), key=lambda d: d[1])
key_list = map(lambda x: x[1], sorted(sort_item, key=lambda d: d[1], reverse=True))
cum_prob = np.array(map(lambda x: x[1], sort_item)).cumsum()
index = np.where(cum_prob > random.random())[0][0]
del first_index[key_list[index]]
new_clusters -= 1
# organize the child chromosome
group_new = {}
for i in range(len(section)):
group_new[i] = second_index[section[i]]
first_key = first_index.keys()
for j in range(len(first_key)):
group_new[j + len(section)] = first_index[first_key[j]]
new_chrome = np.zeros(group_start)
# inversion operation: rearrangement of the positions of groups randomly
groups = group_new.keys()
groups = groups[len(section): len(section)+insert_pos] + groups[:len(section)] + groups[len(section)+insert_pos:]
for key in groups:
new_chrome[group_new[key]] = key
return new_chrome.tolist() + groups