Cargo Load Optimization with a Genetic Algorithm

Translated from the original Portuguese article. Ler o original em português

Cover of the article Cargo Load Optimization with a Genetic Algorithm

Figure 1 of the article Cargo Load Optimization with a Genetic Algorithm

A few days ago, I came across a problem in an area I had not yet studied, when a friend who owns a fleet of trucks asked me to work out how to distribute deliveries across a region. He wanted to use as much of his trucks' space as possible, carry as much weight as they could support, and maximize his profit on the deliveries, considering that the higher the total value of the cargo, the higher the profit from the deliveries.

So... While researching how to solve this problem, I found an area that I now spend a good part of my time studying, called Genetic Algorithms. This type of computational intelligence algorithm works like the process of species evolution (yes, the one you are thinking of), Darwin's famous theory of evolution, but not quite like the image below, which only considers a sort of "evolutionary leap".

Figure 2 of the article Cargo Load Optimization with a Genetic Algorithm

It is more like a genetic explanation, especially of the reproductive process of species, which undergo a phenomenon called crossover that "mixes" part of the DNA of a father and a mother, plus the environmental effects that can cause the famous mutation (but not the X-Men kind). These two phenomena modify the genetics over time, or generations, adapting species to the environment through "survival of the fittest", in which those that survive have an easier time reproducing.

Figure 3 of the article Cargo Load Optimization with a Genetic Algorithm

This is exactly the process we simulate when designing a genetic algorithm, and how do we do it? I can tell you it is very simple. All you need is to create a population of individuals whose genes hold binary values indicating whether or not the truck will carry each product.

Figure 4 of the article Cargo Load Optimization with a Genetic Algorithm

The whole basic process of a genetic algorithm is essentially defined by the flowchart below, from population creation, fitness and selection all the way to optimization.

Figure 5 of the article Cargo Load Optimization with a Genetic Algorithm

Adapted from Eyal Wirsansky, 2020

The products loaded onto the truck were taken from a Kaggle competition called brazilian-ecommerce, which has several separate datasets, so a join between the products was needed to make all the variables required for the load optimization available, as shown in the table below.

Figure 6 of the article Cargo Load Optimization with a Genetic Algorithm

Another thing we need for the simulation are our constraints: the maximum volume of the VUC truck (a small urban cargo vehicle), which is 5,544.00 cm³, and a maximum weight load of 4,000,000.00 grams. So the next step is to create the product class in Python and turn the dataset into lists, with the following code.

#Criar Classe do Produto
class Produto():
    def __init__(self, nome, pesos, volume, valores):
        self.nome = nome
        self.pesos = pesos
        self.volume = volume
        self.valor = valor

#Transformar em lista
espacos = list(produtos.volume)
valores = list(produtos.preco)
nome = list(produtos.product_id)
pesos = list(produtos.product_weight_g)

This brings us to one of the trickiest parts of the algorithm, the evaluation function, which assesses which population is best and gives it a score (just like in evolution), thereby giving it priority to reproduce in the next generation. In this case, I used the "biased roulette wheel" evaluation function, considering the truck's weight and space constraints.

#Criar a função de avaliação
def avaliacao(individual):
    nota=0
    soma_espacos=0
    soma_pesos=0

    for i in range(len(individual)):
        if individual[i]==1:
            nota+=valores[i]
            soma_espacos+=espacos[i]
            soma_pesos+=pesos[i]

           if soma_espacos > limtie_espacos and soma_pesos>limtie_peso:
            nota=1
return nota/100000,

With all that done, we just need to call the DEAP evolutionary algorithms framework and create what the framework calls a Toolbox, adding and registering everything in this toolbox, which will be called later to run the optimization.

#Criar a toolbox
toolbox=base.Toolbox()

#Criando parâmetros na toolbox
creator.create("FitnessMax",base.Fitness,weights=(1.0,))creator.create("Individual",list,fitness=creator.FitnessMax)

#Registrando na toolbox
toolbox.register("attr_bool",random.randint,0,1)
toolbox.register("individual",tools.initRepeat,creator.Individual,toolbox.attr_bool,n=len(espacos))
toolbox.register("population",tools.initRepeat,list,toolbox.individual)toolbox.register("evaluate",avaliacao)
toolbox.register("mate",tools.cxOnePoint)
toolbox.register("mutate",tools.mutFlipBit,indpb=0.05)
toolbox.register("select",tools.selRoulette)

With all of that in place, all that is left is to run the algorithm, specifying the statistics to be tracked, the number of individuals in each generation (population), the crossover probability and the number of generations.

#Iniciando a toolbox
if__name__=="__main__":
    population=toolbox.population(n=24)
    probabilidade_crossover=5.0
    probabilidade_mutação=0.03
    numero_gerações=250


    estatisticas = tools.Statistics(key=lambdaindividuo:
            individuo.fitness.values)

    estatisticas.register("max",np.max)
    estatisticas.register("min",np.min)
    estatisticas.register("med",np.mean)
    estatisticas.register("std",np.std)

    populacao,info=algorithms.eaSimple(population,toolbox,
                                         probabilidade_crossover,
                                        probabilidade_mutação,
                                        numero_gerações, estatisticas)

After 250 generations, we arrive at the best individual, with a value of R$ 7968387.8, adding up to 56604 items, some of which you can see in the first five products.

product_id product_category_name product_weight_g volume preco
1 00088930e925c41fd95ebfe695fd2655 automotivo 1225.0 14300.0 143.83
2 0009406fd7479715e4bef61dd91f2462 cama_mesa_banho 300.0 23625.0 242.10
4 000b8f95fcb9e0096488278317764d19 utilidades_domesticas 550.0 5472.0 78.50
6 0011c512eb256aa0dbbb544d8dffcf6e automotivo 100.0 3840.0 67.80
7 00126f27c813603687e6ce486d909d01 cool_stuff 700.0 1875.0 262.24

We can also see how the algorithm learned over each epoch by plotting the maximum value of the best individual in each generation.

#Plotando o aprendizado
valores_pop = info.select("max")
plt.figure(figsize = (12,6))
plt.plot(valores_pop)
plt.show()

As we can see, the best individual was found very early in the run, more specifically in generation 23.

Figure 7 of the article Cargo Load Optimization with a Genetic Algorithm

So that's it. This was a simple example of using a genetic algorithm, but it showed that with simple programming it is possible to maximize a company's profit, and the approach can be improved and applied to many business problems. You can find the full notebook on my GitHub.

I hope you enjoyed the article; if you did, please share it. :)

Keep reading

All articles

Free Data Sources

One of the most common questions among people starting out in data analysis is where to get the data they need for their work. Here I list some of th…