遗传算法(GA/NSGA)优化神经网络 GA-BP

目录

利用 GA 求神经网络最优的learning rate和隐藏层的神经元个数       GA-NET v1.0.py

对于图片的识别,进行了相关优化,加入了池化操作         GA-NET v2.0.py

在神经网络中也加入GA来加快神经网络训练速度           GA-NET v3.0.py

以上均为回归损失计算方法,要计算分类损失只需要修改如下代码。


 

查了网上一些论文和代码,自己写了三个版本的GA-BP优化代码,从简到繁,从易到难。该文章代码基于Python3 pytorch进行编写。

说明:主要便于方便代入自己的数据所以写了如下代码。自己用的时候主要可以修改Net中的网络结构,Train中的load_data变成自己要读的文件,选用合适的损失函数等等。geatpy为国内大佬写的遗传算法库,这里假设读者已经会用。关于GA和NSGA的区别只在于代码中运用模板的区别。

代码都有注释,可以试着读一读。代码gpu支持暂没测试与优化。

可供测试data文件。测试文件说明:最后一列为label,除最后一列外为data。

三个版本对比:对上述测试文件对R^2 >= 0.96为指标,在我的破电脑上运行时间v1.0≈1min,v2.0≈45s,v3.0≈20s,我没有测试过别的例子的速度,对于v3.0不能保证每个例子都能用,有一定缺陷,不同问题可以选用不同的版本进行使用。

利用 GA 求神经网络最优的learning rate和隐藏层的神经元个数     GA-NET v1.0.py

import torch.nn as nn
import torch
import geatpy as ea
import numpy as np
import os
from sklearn.model_selection import train_test_split

input_dimension = 7
output_dimension = 1


# 自定义网络
class Net(nn.Module):
    def __init__(self, neurons_num):
        super(Net, self).__init__()
        self.hidden0 = torch.nn.Linear(input_dimension, neurons_num)
        self.hidden1 = torch.nn.Linear(neurons_num, neurons_num)
        self.hidden2 = torch.nn.Linear(neurons_num, neurons_num)
        self.hidden3 = torch.nn.Linear(neurons_num, output_dimension)

    def forward(self, x):
        x = torch.relu(self.hidden0(x))
        x = torch.relu(self.hidden1(x))
        x = torch.relu(self.hidden2(x))
        x = self.hidden3(x)
        return x


# r^2 函数
def r2(y_test, y):
    return 1 - ((y_test - y) ** 2).sum() / ((y.mean() - y) ** 2).sum()


# 神经网络训练
class Train:
    train_x, train_y, test_x, test_y, model, lr, neurons_num, x, y, optimizer = None, None, None, None, None, None, None, None, None, None

    def __init__(self):
        self.use_gpu = torch.cuda.is_available()
        # 选用合适的 loss function
        # self.loss_fn = torch.nn.CrossEntropyLoss()
        self.loss_fn = torch.nn.MSELoss()
        self.load_data()

    # 自定义读入数据
    def load_data(self):
        with open('data.csv') as f:
            df = np.loadtxt(f, delimiter=",", skiprows=0)
            self.x = df[:, :-1]
            self.y = df[:, -1:]
        f.close()

    # 重新创建不一样的 train and test data set
    def reload(self, learing_rate, neurons_num):
        train_x, test_x, train_y, test_y = train_test_split(self.x, self.y, test_size=0.3, random_state=42)
        self.train_x = torch.from_numpy(train_x).float()
        self.train_y = torch.from_numpy(train_y).float()
        self.test_x = torch.from_numpy(test_x).float()
        self.test_y = torch.from_numpy(test_y).float()
        self.model = Net(neurons_num)
        if self.use_gpu:
            self.train_x, self.train_y, self.test_x, self.test_y = self.train_x.cuda(), self.train_y.cuda(), self.test_x.cuda(), self.test_y.cuda()
            self.model = self.model.cuda()
            self.loss_fn = self.loss_fn.cuda()
        self.lr = learing_rate
        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)

    # 训练神经网络,返回 r^2 值
    def train(self, n=10):
        for epoch in range(n):
            model_output = self.model(self.train_x)
            loss = self.loss_fn(model_output, self.train_y)
            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()
        model_output = self.model(self.test_x)
        return float(r2(model_output.data, self.test_y))


# 自定义 GA,对 learning rate and neurons num 经行改变
class My_nsga(ea.Problem):
    def __init__(self, epoch):
        if "result" not in os.listdir():
            os.makedirs("./result")
        name = 'GA-NET'
        M = 1
        maxormins = [-1] * M
        Dim = 2
        varTypes = [1] * Dim
        lb = [10, 10]
        ub = [5000, 100]
        lbin = [1] * Dim
        ubin = [1] * Dim
        self.epoch = epoch
        self.train = Train()

        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    # 目标函数即神经网络返回值
    def evalVars(self, Vars):
        ans = np.zeros(len(Vars), dtype=float).reshape(len(Vars), 1)
        for i in range(len(Vars)):
            self.train.reload(Vars[i][0] / 100000, Vars[i][1])
            # 括号内参数表示单个神经网络训练次数
            data = self.train.train(self.epoch)
            print("learning rate = {}, neurons num = {}, R^2 = {}".format(Vars[i][0] / 10000, Vars[i][1], data))
            torch.save(self.train.model, "./result/lr={}num={}epoch={}r2={}.pt".format(Vars[i][0] / 100000, Vars[i][1], self.epoch, round(data, 3)))
            # 达到一定准确率停止
            if data >= 1:
                torch.save(self.train.model, "lr{}=num={}epoch={}r2={}.pt".format(Vars[i][0] / 100000, Vars[i][1], self.epoch, round(data, 3)))
                exit("Find!")
            ans[i][0] = data
        return ans


# 运行 GA
class Run_nsga:
    def __init__(self, epoch=10, ndind=10, maxgen=10):
        problem = My_nsga(epoch)
        myAlgorithm = ea.soea_EGA_templet(problem, ea.Population(Encoding='RI', NIND=ndind), MAXGEN=maxgen, logTras=0)
        myAlgorithm.drawing = 0
        res = ea.optimize(myAlgorithm, seed=1, verbose=False, drawing=0, outputMsg=True, drawLog=False, saveFlag=False, dirName='result')
        print(res)
        print(res['Vars'][0])


if __name__ == "__main__":
    # 括号内参数表示单个神经网络训练次数,种群数,GA迭代数
    Run_nsga(10000, 10, 30)

对于图片的识别,进行了相关优化,加入了池化操作     GA-NET v2.0.py

import torch.nn as nn
import torch
import geatpy as ea
import numpy as np
import os
from sklearn.model_selection import train_test_split

input_dimension = 7
output_dimension = 1
batch_size = 500


# 自定义网络
class Net(nn.Module):
    def __init__(self, neurons_num):
        super(Net, self).__init__()
        self.hidden0 = torch.nn.Linear(input_dimension, neurons_num)
        self.hidden1 = torch.nn.Linear(neurons_num, neurons_num)
        self.hidden2 = torch.nn.Linear(neurons_num, neurons_num)
        self.hidden3 = torch.nn.Linear(neurons_num, output_dimension)

    def forward(self, x):
        x = torch.relu(self.hidden0(x))
        x = torch.relu(self.hidden1(x))
        x = torch.relu(self.hidden2(x))
        x = self.hidden3(x)
        return x


# r^2 函数
def r2(y_test, y):
    return 1 - ((y_test - y) ** 2).sum() / ((y.mean() - y) ** 2).sum()


# Data 类,以便带入自己的data
class Data(torch.utils.data.Dataset):
    def __init__(self, data, label):
        # 可选择归一化操作
        # data = (data - data.min(axis=0)) / (data.max(axis=0) - data.min(axis=0))
        # label = (label - label.min(axis=0)) / (label.max(axis=0) - label.min(axis=0))
        self.x = data
        self.y = label
        self.len = len(self.y)

    def __len__(self):
        return self.len

    def __getitem__(self, item):
        return self.x[item], self.y[item]


# 神经网络训练
class Train:
    trainsetloader, test_x, test_y, model, lr, neurons_num, optimizer, x, y = None, None, None, None, None, None, None, None, None

    def __init__(self):
        self.use_gpu = torch.cuda.is_available()
        # 选用合适的 loss function
        # self.loss_fn = torch.nn.CrossEntropyLoss()
        self.loss_fn = torch.nn.MSELoss()
        self.load_data()

    # 自定义读入数据
    def load_data(self):
        with open('data.csv') as f:
            df = np.loadtxt(f, delimiter=",", skiprows=0)
            self.x = df[:, :-1]
            self.y = df[:, -1:]
        f.close()
        # 创建 train and test
        self.re_data_split()

    # 重新创建不一样的 train and test data set,便于带入到 reload 函数中
    def re_data_split(self):
        train_x, test_x, train_y, test_y = train_test_split(self.x, self.y, test_size=0.3, random_state=42)
        train_x = torch.from_numpy(train_x).float()
        train_y = torch.from_numpy(train_y).float()
        # test 数据
        self.test_x = torch.from_numpy(test_x).float()
        self.test_y = torch.from_numpy(test_y).float()
        trainset = Data(train_x, train_y)
        # train 池化
        self.trainsetloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=False)

    # 重新载入 learning rate 和 neurons num
    def reload(self, learning_rate, neurons_num):
        # 可选择是否重新分类 test and train
        # self.re_data_split()
        self.model = Net(neurons_num)
        if self.use_gpu:
            self.model = self.model.cuda()
            self.loss_fn = self.loss_fn.cuda()
        self.lr = learning_rate
        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)

    # 训练神经网络,返回 r^2 值
    def train(self, n=10) -> float:
        for epoch in range(n):
            for i, (batch_x, batch_y) in enumerate(self.trainsetloader):
                model_output = self.model(batch_x)
                loss = self.loss_fn(model_output, batch_y)
                self.optimizer.zero_grad()
                loss.backward()
                self.optimizer.step()
            model_output = self.model(self.test_x)
            print(float(r2(model_output.data, self.test_y)))
        model_output = self.model(self.test_x)
        return float(r2(model_output.data, self.test_y))


# 自定义 GA,对 learning rate and neurons num 经行改变
class My_nsga(ea.Problem):
    def __init__(self, epoch):
        if "result" not in os.listdir():
            os.makedirs("./result")
        name = 'GA-NET'
        M = 1
        maxormins = [-1] * M
        Dim = 2
        varTypes = [1] * Dim
        lb = [10, 10]
        ub = [500, 100]
        lbin = [1] * Dim
        ubin = [1] * Dim
        self.count = 1
        self.epoch = epoch
        self.train = Train()

        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    # 目标函数即神经网络返回值
    def evalVars(self, Vars):
        ans = np.zeros(len(Vars), dtype=float).reshape(len(Vars), 1)
        for i in range(len(Vars)):
            self.train.reload(Vars[i][0] / 100000, Vars[i][1])
            # 括号内参数表示单个神经网络训练次数
            data = self.train.train(self.epoch)
            print("learning rate = {}, neurons num = {}, R^2 = {}".format(Vars[i][0] / 10000, Vars[i][1], data))
            torch.save(self.train.model, "./result/lr={}num={}epoch={}r2={}.pt".format(Vars[i][0] / 100000, Vars[i][1], self.epoch, round(data, 3)))
            # 达到一定准确率停止
            if data >= 1:
                torch.save(self.train.model, "lr{}=num={}epoch={}r2={}.pt".format(Vars[i][0] / 100000, Vars[i][1], self.epoch, round(data, 3)))
                exit("Find!")
            ans[i][0] = data
        return ans


# 运行 GA
class Run_nsga:
    def __init__(self, epoch=10, ndind=10, maxgen=10):
        problem = My_nsga(epoch)
        myAlgorithm = ea.soea_EGA_templet(problem, ea.Population(Encoding='RI', NIND=ndind), MAXGEN=maxgen, logTras=0)
        myAlgorithm.drawing = 0
        res = ea.optimize(myAlgorithm, seed=1, verbose=False, drawing=0, outputMsg=True, drawLog=False, saveFlag=False, dirName='result')
        print(res)
        print(res['Vars'][0])


if __name__ == "__main__":
    # 括号内参数表示单个神经网络训练次数,种群数,GA迭代数
    Run_nsga(10000, 10, 30)

在神经网络中也加入GA改变网络权值来加快神经网络训练速度     GA-NET v3.0.py

参考代码原地址:遗传算法(GA) – 优化神经网络(CNN) – pytorch(亲测可用)_Vertira的博客-CSDN博客_遗传算法优化cnn

import random
import torch.nn as nn
import torch
import geatpy as ea
import numpy as np
import os
import copy
from sklearn.model_selection import train_test_split
from torch.distributions import Categorical

input_dimension = 7
output_dimension = 1
# 该参数在数据较少的输入时也相应变少,最好使得 data_size / batch_size = NetGA_pop_size
batch_size = 100


# 自定义网络
class Net(torch.nn.Module):
    def __init__(self, neurons_num, lr):
        super(Net, self).__init__()
        self.layers = torch.nn.Sequential(
            torch.nn.Linear(input_dimension, neurons_num),
            torch.nn.ReLU(),
            torch.nn.Linear(neurons_num, neurons_num),
            torch.nn.ReLU(),
            torch.nn.Linear(neurons_num, neurons_num),
            torch.nn.ReLU(),
            torch.nn.Linear(neurons_num, output_dimension)
        )
        self.optimizer = torch.optim.Adam(self.parameters(), lr=lr)

    def forward(self, x):
        return self.layers(x)

    def set_layer(self, layers):
        self.layers = layers


# r^2 函数
def r2(y_test, y):
    return 1 - ((y_test - y) ** 2).sum() / ((y.mean() - y) ** 2).sum()


# Data 类,以便带入自己的data
class Data(torch.utils.data.Dataset):
    def __init__(self, data, label):
        self.x = data
        self.y = label
        self.len = len(self.y)

    def __len__(self):
        return self.len

    def __getitem__(self, item):
        return self.x[item], self.y[item]


# GA 优化的神经网络训练
class NetTrainGA:
    def __init__(self, _pop_size=10, _r_mutation=0.1, _p_mutation=0.1, _elite_num=6, stddev=0.1):
        self.test_x, self.test_y, self.trainSetLoader, self.x, self.y = None, None, None, None, None  # 数据存储
        self.pop_size = _pop_size  # 种群数
        self.r_mutation = _r_mutation  # 变异里,数据变异的概率
        self.p_mutation = _p_mutation  # 变异概率
        self.elite_num = _elite_num  # 精英数
        self.chroms = []  # 储存所有 model
        self.stddev = stddev  # 网络权值步进大小的最大值
        self.criterion = nn.MSELoss()  # 计算 loss 的方法
        self.model = None  # 全局最优解 model
        self.use_gpu = torch.cuda.is_available()  # 是否可以用 cuda 加速
        self.load_data()  # 加载数据
        self.lr = 0.001  # learning rate

    # 自定义读入数据
    def load_data(self):
        with open('data.csv') as f:
            df = np.loadtxt(f, delimiter=",", skiprows=0)
            self.x = df[:, :-1]
            self.y = df[:, -1:]
        f.close()
        # 创建 train and test
        self.re_data_split()

    # 重新创建不一样的 train and test data set,便于带入到 reload 函数中
    def re_data_split(self):
        train_x, test_x, train_y, test_y = train_test_split(self.x, self.y, test_size=0.3, random_state=42)
        train_x = torch.from_numpy(train_x).float()
        train_y = torch.from_numpy(train_y).float()
        # test 数据
        self.test_x = torch.from_numpy(test_x).float()
        self.test_y = torch.from_numpy(test_y).float()
        trainSet = Data(train_x, train_y)
        # train 池化
        self.trainSetLoader = torch.utils.data.DataLoader(trainSet, batch_size=batch_size, shuffle=False)

    def reload(self, learning_rate, neurons_num):
        # 可选择是否重新分类 test and train
        # self.re_data_split
        self.lr = learning_rate
        for i in range(self.pop_size):
            net = Net(neurons_num, learning_rate)
            if self.use_gpu:
                net = net.cuda()
            self.chroms.append(net)

    # 训练神经网络,返回R^2的值
    """
    对下列博客代码进行改进
    https://blog.csdn.net/Vertira/article/details/122561056
    """
    def train(self, n):
        for epoch in range(n):
            result = [{'pop': i, 'train_acc': float("-inf")} for i in range(self.pop_size)]
            # 为种群训练不同的数据
            for step, (batch_x, batch_y) in enumerate(self.trainSetLoader):
                self.netTrain(batch_x, batch_y, (step + epoch) % self.pop_size)
            # 计算 train accuracy
            for i in range(self.pop_size):
                output = self.chroms[i](self.test_x)
                result[i]["train_acc"] = float(r2(output.data, self.test_y))
            result = sorted(result, key=lambda x: x['train_acc'], reverse=True)
            # self.model 即为类中最优解,可直接套用 test 经行预测
            self.model = self.chroms[result[0]['pop']]
            self.selection(result)
        model_output = self.model(self.test_x)
        return float(r2(model_output.data, self.test_y))

    def netTrain(self, batch_x, batch_y, now):
        model = self.chroms[now]
        optimizer = model.optimizer
        # 选择每次神经网络训练次数,这个参数影响了训练速度,但跟多时候会影响梯度,很多时候我也不知道为什么梯度就没了,所以太小梯度可能变0或None导致训练停滞,太大训练的有可能变慢
        for j in range(100):
            output = model(batch_x)
            optimizer.zero_grad()
            train_loss = self.criterion(output, batch_y).requires_grad_()
            train_loss.backward()
            optimizer.step()

    # 保留精英个数,并进行交叉操作至种群数满,最后进行变异操作
    def selection(self, result):
        elites = [e['pop'] for e in result[:self.elite_num]]
        # 保留 elites 个精英
        children = [copy.deepcopy(self.chroms[i]) for i in elites]
        # 轮盘赌来选择交配的个体,使用 softmax 处理负数问题
        prob = torch.softmax(torch.tensor([i["train_acc"] for i in result]), dim=0)
        m = Categorical(prob)
        # 随机选择两个交配直至达到种群大小
        while len(children) < self.pop_size:
            # 随机选择两个进行 self.crossover交配
            pair = [result[m.sample()]['pop'], result[m.sample()]['pop']]
            children.append(self.crossover(pair))
        del self.chroms[:]
        self.chroms[:] = children
        # 变异且不变异精英
        for i in range(self.elite_num, self.pop_size):
            # 满足变异概率
            if random.random() < self.p_mutation:
                mutated_child = self.mutation(i)
                del self.chroms[i]
                self.chroms.insert(i, mutated_child)

    def crossover(self, _selected_pop):
        if _selected_pop[0] == _selected_pop[1]:
            return copy.deepcopy(self.chroms[_selected_pop[0]])

        chrom1 = copy.deepcopy(self.chroms[_selected_pop[0]])
        chrom2 = copy.deepcopy(self.chroms[_selected_pop[1]])

        chrom1_layers = nn.ModuleList(chrom1.modules())
        chrom2_layers = nn.ModuleList(chrom2.modules())

        child = torch.nn.Sequential()
        for i in range(len(chrom1_layers)):
            layer1 = chrom1_layers[i]
            layer2 = chrom2_layers[i]
            # 对 Linear 层随机交换
            if isinstance(layer1, nn.Linear):
                child.add_module(str(i - 2), layer1 if random.random() < 0.5 else layer2)

            elif isinstance(layer1, (torch.nn.Sequential, Net)):
                pass
            else:
                child.add_module(str(i - 2), layer1)
        chrom1.set_layer(child)
        chrom1.optimizer = torch.optim.Adam(chrom1.parameters(), lr=self.lr)
        return chrom1

    def mutation(self, _selected_pop):
        child = torch.nn.Sequential()
        chrom = copy.deepcopy(self.chroms[_selected_pop])
        chrom_layers = nn.ModuleList(chrom.modules())
        # 变异比例,选择几层进行变异
        for i, layer in enumerate(chrom_layers):
            if isinstance(layer, nn.Linear):
                # 变异 Linear 层,且有一定变异比例
                if random.random() < self.r_mutation:
                    # 提取权重
                    weights = layer.weight.detach().numpy()
                    # 更改权重
                    w = weights.astype(np.float32) + np.random.normal(0, self.stddev, weights.shape).astype(np.float32)
                    # 重新设置
                    layer.weight = torch.nn.Parameter(torch.from_numpy(w))
                child.add_module(str(i - 2), layer)
            elif isinstance(layer, (torch.nn.Sequential, Net)):
                pass
            else:
                child.add_module(str(i - 2), layer)
        chrom.set_layer(child)
        chrom.optimizer = torch.optim.Adam(chrom.parameters(), lr=self.lr)
        return chrom


# 自定义 GA,对 learning rate and neurons num 经行改变
class My_nsga(ea.Problem):
    def __init__(self, epoch):
        if "result" not in os.listdir():
            os.makedirs("./result")
        name = 'GA-NET'
        M = 1
        maxormins = [-1] * M
        Dim = 2
        varTypes = [1] * Dim
        lb = [10, 10]
        ub = [500, 100]
        lbin = [1] * Dim
        ubin = [1] * Dim
        self.count = 1
        self.epoch = epoch
        self.train = NetTrainGA()

        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    # 目标函数即神经网络返回值
    def evalVars(self, Vars):
        ans = np.zeros(len(Vars)).reshape(len(Vars), 1)
        for i in range(len(Vars)):
            self.train.reload(Vars[i][0] / 10000, Vars[i][1])
            # 括号内参数表示单个神经网络训练次数
            data = self.train.train(self.epoch)
            print("learning rate = {}, neurons num = {}, R^2 = {}".format(Vars[i][0] / 10000, Vars[i][1], round(data, 3)))
            torch.save(self.train.model, "./result/lr{}num{}epoch{}r2{}.pt".format(Vars[i][0] / 10000, Vars[i][1], self.epoch, round(data, 3)))
            # 达到一定准确率停止
            if data >= 1:
                torch.save(self.train.model, "lr{}num{}epoch{}r2{}.pt".format(Vars[i][0] / 10000, Vars[i][1], self.epoch, round(data, 3)))
                return 0
            ans[i] = float(data)
        return ans


# 运行 GA
class Run_nsga:
    def __init__(self, epoch=10, ndind=10, maxgen=10):
        problem = My_nsga(epoch)
        myAlgorithm = ea.soea_EGA_templet(problem, ea.Population(Encoding='RI', NIND=ndind), MAXGEN=maxgen, logTras=0)
        myAlgorithm.drawing = 0
        res = ea.optimize(myAlgorithm, seed=1, verbose=False, drawing=0, outputMsg=True, drawLog=False, saveFlag=False, dirName='result')
        print(res)
        print(res['Vars'][0])


if __name__ == "__main__":
    # 括号内参数表示单个神经网络训练次数,种群数,GA迭代数
    Run_nsga(100, 10, 10)
    """
    # 也可以单独调用 NetTrainGA,设置初始参数
    netga = NetTrainGA()
    # learning rate, neurons_num
    netga.reload(0.001, 30)
    # epoch
    print(netga.train(1000))
    """

以上均为回归损失计算方法,要计算分类损失只需要修改如下代码。

详情也可见该文章

# 修改输出 dimension
output_dimension = 28
# 修改 y 维度
self.y = df[:, -1]
# 修改损失函数
self.criterion = nn.CrossEntropyLoss()
# 修改 train_y 从 float 变成 long
train_y = torch.from_numpy(train_y).long()
# 修改 train_acc 计算方法
result[i]["train_acc"] = float((output.argmax(dim=1) == self.test_y).sum()) / len(self.test_y)
# 修改 train 输出
return float((model_output.argmax(dim=1) == self.test_y).sum() / len(self.test_y))

 v3.0版本有缺陷,optimizer是整个GA优化神经网络权值项目的bug所在,该文章最后更新时间为 2022.7.13,如果有更好的改进方法欢迎一起讨论。

个人博客地址,欢迎访问https://www.pancake2021.work/?p=1511

物联沃分享整理
物联沃-IOTWORD物联网 » 遗传算法(GA/NSGA)优化神经网络 GA-BP

发表评论