基於Python的遺傳演算法特徵約簡(附程式碼)

資料派THU發表於2019-05-08

基於Python的遺傳演算法特徵約簡(附程式碼)導言

在某些情況下,使用原始資料訓練機器學習演算法可能不是合適的選擇。該演算法在接受原始資料訓練時,必須進行特徵挖掘,以檢測不同組之間的差異。但這需要大量的資料來自動執行特徵挖掘。對於小資料集,資料科學家最好自己進行特徵挖掘步驟,之後告訴機器學習演算法要使用哪個特徵集。

使用的特徵集必須能代表資料樣本,因此我們必須注意選擇最佳特徵。資料科學家建議使用一些型別的特徵,這些特徵似乎有助於根據以前的經驗來表示資料樣本。一些特徵可以證明它們在表示樣本時的穩健性,而其他特徵則不能。

可能存在一些型別的特徵,會降低分類問題的準確性或增加回歸問題的誤差,進而影響訓練模型的結果。例如,特徵向量中可能存在一些噪音元素,因此它們應該被刪除。特徵向量也可能包含2個或更多相關元素。只使用一個元素就可以替代另一個元素。為了刪除這些型別的元素,有兩個有用的步驟,即特徵選擇和約簡。本教程重點介紹特徵約簡。

假設有3個特徵f1、f2和f3,每個特徵都有3個特徵元素。因此,特徵向量長度為3x3=9。特徵選擇只選擇特定型別的特徵,不包括其他型別的特徵。例如,只需選擇f1和f3並刪除f3。特徵向量長度變成了6而不是9。在特徵約簡中,可以排除每個特徵的特定元素。例如,此步驟可能會在保留第二個元素的同時從f3中刪除第一個和第三個元素。因此,特徵向量長度從9減少到7。

在開始本教程之前,值得一提的是,它是我的LinkedIn配置檔案中先前釋出的2個教程的擴充套件。

第一個教程的標題是“使用numpy的人工神經網路實現Fruits360影像資料集的分類”。它首先從Fruits360資料集的4個類中提取長度為360的特徵向量。然後,利用numpy從零開始構建人工神經網路(ANN),對資料集進行分類。

第一個教程可從以下網址獲取:

https://www.linkedin.com/pulse/artificial-neural-network-implementation-using-numpy-fruits360-gad

其Github專案可從以下網址獲得:

https://github.com/ahmedfgad/NumPyAN

第二個教程是“使用遺傳演算法的人工神經網路優化”。建立並使用遺傳演算法神經網路引數進行優化,以提高分類精度。

第二個教程可從以下網址獲取:

https://www.linkedin.com/pulse/artificial-neural-networks-optimization-using-genetic-ahmed-gad。

其Github專案也可從以下網址獲得:

https://github.com/ahmedfgad/NeuralGeneti

本教程討論瞭如何使用遺傳演算法來減少從長度360的Fruits360資料集中提取的特徵向量。本教程首先討論要遵循的步驟。其次通過使用NumPy和Sklearn在python實現這些步驟。

本教程的實現可在我的Github頁面中找到:

https://github.com/ahmedfgad/FeatureReductionGeneti

遺傳演算法從一個初始群體開始,該群體由若干染色體(即解決方法)組成,其中每個染色體都有一系列基因。使用適應函式,遺傳演算法選擇最佳的解決方案作為父母來建立一個新的群體。在這樣一個新的群體中,通過在雙親上應用兩個操作,即雜交和變異來建立新的解決方案。當把遺傳演算法應用到一個給定的問題上時,我們必須確定基因的表示、合適的適應函式以及雜交和變異是如何應用的。接下來讓我們看看執行原理。

更多關於遺傳演算法的資訊

你可以從我準備的如下資源中讀到關於遺傳演算法的更多知識:

1. 遺傳演算法優化介紹 

https://www.linkedin.com/pulse/introduction-optimization-genetic-algorithm-ahmed-gad/

https://www.kdnuggets.com/2018/03/introduction-optimization-with-genetic-algorithm.html

https://towardsdatascience.com/introduction-to-optimization-with-genetic-algorithm-2f5001d9964b

2. 遺傳演算法優化-逐步示例

https://www.slideshare.net/AhmedGadFCIT/genetic-algorithm-ga-optimization-stepbystep-example

3. python中的遺傳演算法實現

https://www.linkedin.com/pulse/genetic-algorithm-implementation-python-ahmed-gad/

https://www.kdnuggets.com/2018/07/genetic-algorithm-implementation-python.html

https://towardsdatascience.com/genetic-algorithm-implementation-in-python-5ab67bb124a6

https://github.com/ahmedfgad/GeneticAlgorithmPython

我在2018年還寫了一本書,其中一章介紹了遺傳演算法。這本書的標題是“利用CNN進行深度學習的實用計算機視覺應用”,可在Springer上找到。

Springer連結:

https://www.springer.com/us/book/978148424166

染色體的表達

遺傳演算法中的基因是染色體的組成部分。首先,我們需要確定染色體內的基因。為此,考慮到可能影響結果的每一種屬性都應被視為一個基因。因為我們問題的目標是選擇最好的一組特徵元素,所以如果選擇或不選擇,每個特徵元素都可能影響結果。因此,每個特徵元素都被視為一個基因。染色體將由所有基因(即所有特徵元素)組成。因為有360個特徵元素,那麼就有360個基因。一個很好的資訊現在很清楚,染色體的長度是360

在確定所選基因是什麼之後,下一步就是確定基因的表達。有不同的表示形式,如十進位制、二進位制、浮點、字串等。我們的目標是知道基因(即特徵元素)是否在減少的特徵集中被選擇。因此,分配給基因的值應該反映它是否被選擇。基於這種描述,很明顯每個基因有兩個可能的值。一個值表示該基因已被選中,另一個值表示未被選中。因此,二進位制表示是最佳選擇。當基因值為1時,將在減少的特徵集中進行選擇。當為0時,則忽略它。

總之,染色體將由360個基因組成,以二進位制表示。根據下一個圖,特徵向量和染色體之間有一對一的對映。這是染色體中的第一個基因與特徵向量中的第一個元素相連。當該基因的值為1時,這意味著選擇了特徵向量中的第一個元素。

基於Python的遺傳演算法特徵約簡(附程式碼)

適應函式

通過了解如何建立染色體,可以很容易地對初始種群進行隨機初始化。初始化後,將選擇父級。遺傳演算法基於達爾文的“適者生存”理論。這是目前選擇的最佳解決方案進行組合,以產生更好的解決方案。通過保留好的解和消除壞的解,我們可以得到最優或半最優解。

選擇雙親的標準是與每個解決方案(即染色體)相關聯的適應值。適合度越高,解決方案越好。使用適應度函式計算適應度值。那麼,在我們的問題中,最適合使用的功能是什麼?我們問題的目標是建立一個約簡的特徵向量,以提高分類精度。因此,判斷一個解是否好的標準是分類的準確性。因此,fitness函式將返回一個數字,指定每個解決方案的分類精度。精度越高,解決方案越好。

為了返回分類的準確度,必須有一個機器學習模型來通過每個解決方案返回的特徵元素進行訓練。對於這種情況,我們將使用支援向量分類器(SVC)。

資料集分為訓練樣本和測試樣本。根據訓練資料,SVC將使用人群中每個解決方案選擇的特徵元素進行訓練。經過訓練後,根據測試資料進行測試。

根據每個解的適合度值,我們可以選擇其中最好的作為父母。這些父母被放在一起組合以產生後代,這將是下一代的新人口的成員。這種後代是通過對選定的親本應用交叉和突變操作而產生的。讓我們按照下面討論的方式配置這些操作。

遺傳和變異

基於適應度函式,我們可以篩選出當前群體中的最優解,即父輩。遺傳演算法假設匹配2個好的解決方案將產生第三個更好的解決方案。組合意味著從兩個父母那裡交換一些基因。使用遺傳操作交換基因。有不同的方法可以應用這種操作。本教程使用單點交叉,其中一個點分割染色體。點前的基因取自一組解,點後的基因取自另一組解。

通過應用遺傳,所有的基因都來自於以前的父母。在新的後代中沒有引入新的基因。如果所有的父母都有一個壞基因,那麼這個基因就會轉移到後代身上。正因為如此,為了在後代中引入新的基因,採用了突變操作。在基因的二元表示中,突變是通過翻轉一些隨機選擇的基因的值來實現的。如果基因值為1,則為0,反之亦然。

在產生後代之後,我們可以創造下一代的新種群。除了後代之外,這個群體還包括以前的父輩。

此時,將討論所有步驟。接下來是用Python實現它們。注意,我以前寫過一篇題為“Python中的遺傳演算法實現”的教程,用於在Python中實現遺傳演算法,我將修改它的程式碼來解決我們的問題。最好讀一下。

利用Python實現

該專案分為兩個檔案。一個檔名為GA.py,它將遺傳演算法步驟的實現儲存為函式。另一個檔案是主檔案,它只匯入這個檔案,並在迴圈中呼叫它的函式,該迴圈將迭代幾代。

根據下面的程式碼,主檔案首先讀取從Fruits360資料集提取的特性。這些特性返回到資料輸入變數中。有關提取這些功能的詳細資訊,請參閱本教程開頭提到的2個教程。該檔案還讀取與資料輸出變數中的樣本相關聯的類標籤。

選擇一些樣本進行訓練,其索引儲存在train_indices變數中。同樣,測試樣本索引儲存在test_indices變數中。

import numpy

import GA

import pickle

import matplotlib.pyplot

f = open("dataset_features.pkl", "rb")

data_inputs = pickle.load(f)

f.close()

f = open("outputs.pkl", "rb")

data_outputs = pickle.load(f)

f.close()

num_samples = data_inputs.shape[0]

num_feature_elements = data_inputs.shape[1]

train_indices = numpy.arange(1, num_samples, 4)

test_indices = numpy.arange(0, num_samples, 4)

print("Number of training samples: ", train_indices.shape[0])

print("Number of test samples: ", test_indices.shape[0])

"""

Genetic algorithm parameters:

    Population size

    Mating pool size

    Number of mutations

"""

sol_per_pop = 8 # Population size.

num_parents_mating = 4 # Number of parents inside the mating pool.

num_mutations = 3 # Number of elements to mutate.

# Defining the population shape.

pop_shape = (sol_per_pop, num_feature_elements)

# Creating the initial population.

new_population = numpy.random.randint(low=0, high=2, size=pop_shape)

print(new_population.shape)

best_outputs = []

num_generations = 100

它初始化了遺傳演算法的所有引數。這包括根據sol_per_pop變數設定為8的每個群體的解的數量、num _parents_mating變數設定為4的子代數量以及num_mutations變數設定為3的突變數量。之後,它會在一個名為“new_population”的變數中隨機建立初始總體。

有一個名為best_outputs的空列表,它在每一代之後都儲存著最好的結果。這有助於視覺化遺傳演算法在完成所有代之後的進展。num_generations變數中的代數設定為100。請注意,您可以更改所有這些引數,從而獲得更好的結果。

在準備好特性、類標籤和演算法引數之後,我們可以根據下一個程式碼對演算法進行迭代。首先,通過呼叫GA檔案中定義的名為cal_pop_fitness()的適應函式來計算所有解決方案的適應值。此函式接受當前總體、提取的特徵、類標籤、列車索引和測試索引。函式返回名為fitness的變數中所有解的適應值。請記住,適合度值表示分類精度。最佳(即最高)分類精度儲存在最佳輸出列表中。

根據計算出的適合度值,使用GA.py檔案中定義的select_matching_pool()函式選擇分類精度最高的最佳解決方案作為匹配池中的父級。它接受當前的人口、適合度值和要返回的父母人數。它將所選雙親返回到父級變數中。

for generation in range(num_generations):

    print("Generation : ", generation)  

    # Measuring the fitness of each chromosome in the population.

    fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)

    best_outputs.append(numpy.max(fitness))

    # The best result in the current iteration.

    print("Best result : ", best_outputs[-1])    

    # Selecting the best parents in the population for mating

    parents = GA.select_mating_pool(new_population, fitness, num_parents_mating)   

    # Generating next generation using crossover

    offspring_crossover = GA.crossover(parents, offspring_size=(pop_shape[0]-parents.shape[0], num_feature_elements))  

    # Adding some variations to the offspring using mutation.

    offspring_mutation = GA.mutation(offspring_crossover, num_mutations=num_mutations)

    # Creating the new population based on the parents and offspring.

    new_population[0:parents.shape[0], :] = parents

    new_population[parents.shape[0]:, :] = offspring_mutation

接下來是對選定的父代應用組合操作以建立子代。這是在GA.py檔案中定義的crossover()函式內完成的。它接受父陣列和子陣列的形狀,以便稍後返回到offspring_crossover變數中。然後,使用在GA.py檔案中也可用的mutation()函式在該陣列上應用突變操作。除了交叉結果,這個函式接受突變的數量。

因為新的種群由選定的親本和後代組成,所以親本和offspring_crossover陣列都儲存到new_population變數中。在那之後,新一代被應用於新的人口。

在所有代完成後,將執行下一個程式碼,以返回最佳選擇的功能元素集和所選元素的數量。在100代完成後,該演算法使用174個特徵元素,以達到99.59%的精度。

fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)

# Then return the index of that solution corresponding to the best fitness.

best_match_idx = numpy.where(fitness == numpy.max(fitness))[0]

best_match_idx = best_match_idx[0]

best_solution = new_population[best_match_idx, :]

best_solution_indices = numpy.where(best_solution == 1)[0]

best_solution_num_elements = best_solution_indices.shape[0]

best_solution_fitness = fitness[best_match_idx]

print("best_match_idx : ", best_match_idx)

print("best_solution : ", best_solution)

print("Selected indices : ", best_solution_indices)

print("Number of selected elements : ", best_solution_num_elements)

print("Best solution fitness : ", best_solution_fitness)

matplotlib.pyplot.plot(best_outputs)

matplotlib.pyplot.xlabel("Iteration")

matplotlib.pyplot.ylabel("Fitness")

matplotlib.pyplot.show()

上面的程式碼展示了一張圖,顯示了演算法在所有代中的進度,如下所示。

以下是主檔案中的完整程式碼。

import numpy

import GA

import pickle

import matplotlib.pyplot

f = open("dataset_features.pkl", "rb")

data_inputs = pickle.load(f)

f.close()

f = open("outputs.pkl", "rb")

data_outputs = pickle.load(f)

f.close()

num_samples = data_inputs.shape[0]

num_feature_elements = data_inputs.shape[1]

train_indices = numpy.arange(1, num_samples, 4)

test_indices = numpy.arange(0, num_samples, 4)

print("Number of training samples: ", train_indices.shape[0])

print("Number of test samples: ", test_indices.shape[0])

"""

Genetic algorithm parameters:

    Population size

    Mating pool size

    Number of mutations

"""

sol_per_pop = 8 # Population size

num_parents_mating = 4 # Number of parents inside the mating pool.

num_mutations = 3 # Number of elements to mutate.

# Defining the population shape.

pop_shape = (sol_per_pop, num_feature_elements)

# Creating the initial population.

new_population = numpy.random.randint(low=0, high=2, size=pop_shape)

print(new_population.shape)

best_outputs = []

num_generations = 100

for generation in range(num_generations):

    print("Generation : ", generation)

    # Measuring the fitness of each chromosome in the population.

    fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)

    best_outputs.append(numpy.max(fitness))

    # The best result in the current iteration.

    print("Best result : ", best_outputs[-1])

    # Selecting the best parents in the population for mating.

    parents = GA.select_mating_pool(new_population, fitness, num_parents_mating)

   

    # Generating next generation using crossover.

    offspring_crossover = GA.crossover(parents, offspring_size=(pop_shape[0]-parents.shape[0], num_feature_elements))

    # Adding some variations to the offspring using mutation.

    offspring_mutation = GA.mutation(offspring_crossover, num_mutations=num_mutations)

    # Creating the new population based on the parents and offspring.

    new_population[0:parents.shape[0], :] = parents

    new_population[parents.shape[0]:, :] = offspring_mutation

 

# Getting the best solution after iterating finishing all generations.

# At first, the fitness is calculated for each solution in the final generation.

fitness = GA.cal_pop_fitness(new_population, data_inputs, data_outputs, train_indices, test_indices)

# Then return the index of that solution corresponding to the best fitness.

best_match_idx = numpy.where(fitness == numpy.max(fitness))[0]

best_match_idx = best_match_idx[0]

best_solution = new_population[best_match_idx, :]

best_solution_indices = numpy.where(best_solution == 1)[0]

best_solution_num_elements = best_solution_indices.shape[0]

best_solution_fitness = fitness[best_match_idx]

print("best_match_idx : ", best_match_idx)

print("best_solution : ", best_solution)

print("Selected indices : ", best_solution_indices)

print("Number of selected elements : ", best_solution_num_elements)

print("Best solution fitness : ", best_solution_fitness)

matplotlib.pyplot.plot(best_outputs)

matplotlib.pyplot.xlabel("Iteration")

matplotlib.pyplot.ylabel("Fitness")

matplotlib.pyplot.show()

GA.py的實現

GA.py檔案的實現如下所示。在cal_pop_fitness()函式中,SVC根據每個解決方案選擇的特徵元素進行培訓。在訓練前,根據所選的基因值為1的元素過濾特徵。這是在reduce_features()函式中完成的。除了所有示例的完整功能外,它還接受當前的解決方案。

訓練後,使用reduce_features()函式計算分類精度。此函式返回儲存在cal pop_fitness()函式中名為accuracies的陣列中的精度。

crossover()和mutation()函式的實現與我之前的教程“Python中的遺傳演算法實現”中討論的非常相似。一個主要的區別是,mutation()函式通過翻轉隨機選擇的基因的值來改變它們,因為我們使用的是二進位制表示。

import numpy

import sklearn.svm

def reduce_features(solution, features):

    selected_elements_indices = numpy.where(solution == 1)[0]

    reduced_features = features[:, selected_elements_indices]

    return reduced_features

def classification_accuracy(labels, predictions):

    correct = numpy.where(labels == predictions)[0]

    accuracy = correct.shape[0]/labels.shape[0]

    return accuracy

def cal_pop_fitness(pop, features, labels, train_indices, test_indices):

    accuracies = numpy.zeros(pop.shape[0])

    idx = 0

    for curr_solution in pop:

        reduced_features = reduce_features(curr_solution, features)

        train_data = reduced_features[train_indices, :]

        test_data = reduced_features[test_indices, :]

        train_labels = labels[train_indices]

        test_labels = labels[test_indices]

        SV_classifier = sklearn.svm.SVC(gamma='scale')

        SV_classifier.fit(X=train_data, y=train_labels)

        predictions = SV_classifier.predict(test_data)

        accuracies[idx] = classification_accuracy(test_labels, predictions)

        idx = idx + 1

    return accuracies

def select_mating_pool(pop, fitness, num_parents):

    # Selecting the best individuals in the current generation as parents for producing the offspring of the next generation.

    parents = numpy.empty((num_parents, pop.shape[1]))

    for parent_num in range(num_parents):

        max_fitness_idx = numpy.where(fitness == numpy.max(fitness))

        max_fitness_idx = max_fitness_idx[0][0]

        parents[parent_num, :] = pop[max_fitness_idx, :]

        fitness[max_fitness_idx] = -99999999999

    return parents

def crossover(parents, offspring_size):

    offspring = numpy.empty(offspring_size)

    # The point at which crossover takes place between two parents. Usually, it is at the center.

    crossover_point = numpy.uint8(offspring_size[1]/2)

    for k in range(offspring_size[0]):

        # Index of the first parent to mate.

        parent1_idx = k%parents.shape[0]

        # Index of the second parent to mate.

        parent2_idx = (k+1)%parents.shape[0]

        

        # The new offspring will have its first half of its genes taken from the first parent.

        offspring[k, 0:crossover_point] = parents[parent1_idx, 0:crossover_point]

        # The new offspring will have its second half of its genes taken from the second parent.

        offspring[k, crossover_point:] = parents[parent2_idx, crossover_point:]

    return offspring

def mutation(offspring_crossover, num_mutations=2):

    mutation_idx = numpy.random.randint(low=0, high=offspring_crossover.shape[1], size=num_mutations)

    # Mutation changes a single gene in each offspring randomly.

    for idx in range(offspring_crossover.shape[0]):

        # The random value to be added to the gene.

         offspring_crossover[idx, mutation_idx] = 1 - offspring_crossover[idx, mutation_idx]

    return offspring_crossover

原文標題:

Feature Reduction using Genetic Algorithm with Python

原文連結:

https://www.kdnuggets.com/2019/03/feature-reduction-genetic-algorithm-python.html

相關文章