AI Cho Mọi Người

AI Cho Mọi Người

Áp dụng GA cho dự đoán doanh thu và giá nhà

 

 

Tiên đoán doanh thu bán hàng

Trong phần này, chúng ta sẽ dùng GA để dự đoán doanh thu bán hàng (sale) dựa vào các loại marketing khác nhau. Chúng ta sẽ sử dụng dataset advertising sau

https://www.kaggle.com/ashydv/advertising-dataset/downloads/advertising.csv/data

Các bạn có thể download data ở link sau

https://www.dropbox.com/s/ejes8s6x87dhfif/advertising.csv?dl=0

Bộ dữ liệu này bao gồm 200 mẫu, và mỗi mẫu gồm 3 đặc trưng gồm số tiền quảng cáo trên TV, trên radio, và trên newspaper. Giá trị sale (thực) được cung cấp cho mỗi mẫu. Hình sau hiển thị minh họa một số mẫu từ bộ dữ liệu advertising.

Chúng ta giả sử bộ dữ liệu này có dạng phân bố theo phương trình (1) như sau \(sale = c_1*TV + c_2*Radio + c_3*Newspaper + c_4\). Do đó, chúng ta sẽ dùng GA để tìm ra các giá trị tham số \(c_1\), \(c_2\), \(c_3\), và \(c_4\) tốt nhất cho bộ dữ liệu advertising.

Từ yêu cầu bài toán, chúng ta xác định được một số thông tin cho GA

– Chiều dài của chromosome là 4
– Gen có kiểu dữ liệu là floating-point
– Miền giá trị của các tham số \(c_i \ge 0\)

Để thuận tiện cho việc cài đặt, phương trình (1) có thể viết lại như sau \(sale = c_1*TV + c_2*Radio + c_3*Newspaper + c_4*1.0\). Chúng ta sẽ thêm giá trị 1.0 vào mỗi mẫu để số lượng đặc trưng là 4, bằng với số lượng tham số.

Code sau đọc dữ liệu lên và lưu các đặc trưng vào list features (mỗi phần tử gồm 4 đặc trưng: TV, Radio, Newspaper, và 1.0). List prices chứa giá trị sale cho từng mẫu.

Hàm load_data() đọc dữ liệu từ file advertising.csv, và đọc tất cả các dòng lên với kiểu string. Các đặc trưng được tách ra và lưu vào biến list feature; sau đó thêm giá trị 1.0 ở cuối list. Cuối cùng, list feature và giá trị doanh thu được lưu vào biến  featuresprices.

Một việc quan trọng khi áp dụng GA vào một bài toán nào đó là việc viết hàm tính fitness cho chromosome. Như đã phân tích ở trên, chúng ta sẽ dựa vào phương trình (1) để tính doanh thu ước lượng. Sau đó so sánh với doanh thu thực tế để tính loss cho một dự đoán. Code cho hàm tính fitness như sau

Các phần code còn lại gần không thay đổi. Dưới đây là source code đầy đủ của chương trình

# aivietnam.ai - advertising
import random

n = 4 # size of individual (chromosome); 3 coefficients and 1 bias          
    
m = 200                # size of population
n_generations = 2000   # number of generations
losses = []            # để vẽ biểu đồ quá trình tối ưu

# Hàm load data
def load_data():
    # kết nối với file
    file = open('advertising.csv','r')

    # readlines giúp việc đọc file theo từng dòng , mỗi dòng là 1 chuỗi
    lines = file.readlines()
        
    features = []
    prices   = []
    for i in range(1, 201): 
        strings = lines[i].split(',')
        feature = [float(s.strip()) for s in strings[:len(strings)-1]]
        feature.append(1.0) # for bias
        features.append(feature)
        prices.append(float(strings[-1]))

    # Đóng kết nối với file
    file.close()
    
    return features, prices

# load data
features, prices = load_data()

def generate_random_value(bound = 100):
    return (random.random())*bound

def compute_loss(individual):
    estimated_prices = []
    for feature in features:        
        estimated_price = sum(c*x for x, c in zip(feature, individual))
        estimated_prices.append(estimated_price)
          
    losses = [abs(y_est-y_gt) for y_est, y_gt in zip(estimated_prices, prices)]
    return sum(losses)

def compute_fitness(individual):
    loss = compute_loss(individual)
    fitness = 1 / (loss + 1)
    return fitness

def create_individual():
    return [generate_random_value() for _ in range(n)]

def crossover(individual1, individual2, crossover_rate = 0.9):
    individual1_new = individual1.copy()
    individual2_new = individual2.copy()
    
    for i in range(n):
        if random.random() < crossover_rate:
            individual1_new[i] = individual2[i]
            individual2_new[i] = individual1[i]            
    
    return individual1_new, individual2_new

def mutate(individual, mutation_rate = 0.05):
    individual_m = individual.copy()
    
    for i in range(n):
        if random.random() < mutation_rate:
            individual_m[i] = generate_random_value()
        
    return individual_m

def selection(sorted_old_population):    
    index1 = random.randint(0, m-1)    
    while True:
        index2 = random.randint(0, m-1)    
        if (index2 != index1):
            break
            
    individual_s = sorted_old_population[index1]
    if index2 > index1:
        individual_s = sorted_old_population[index2]
    
    return individual_s 

def create_new_population(old_population, elitism=2, gen=1):
    sorted_population = sorted(old_population, key=compute_fitness)
        
    if gen%1 == 0:
        losses.append(compute_loss(sorted_population[m-1]))
        #print("Best loss:", compute_loss(sorted_population[m-1]))
    
    new_population = []
    while len(new_population) < m-elitism:
        # selection
        individual_s1 = selection(sorted_population)
        individual_s2 = selection(sorted_population) # duplication
        
        # crossover
        individual_c1, individual_c2 = crossover(individual_s1, individual_s2)
        
        # mutation
        individual_m1 = mutate(individual_c1)
        individual_m2 = mutate(individual_c2)
        
        new_population.append(individual_m1)
        new_population.append(individual_m2)            
    
    for ind in sorted_population[m-elitism:]:
        new_population.append(ind.copy())
    
    return new_population

population = [create_individual() for _ in range(m)]
for i in range(n_generations):
    population = create_new_population(population, 2, i)

 

Sau khi chạy chương trình trên để GA tối ưu hóa các tham số \(c_i\). Chúng ta cần hiển thị các giá trị loss để kiểm tra xem quá trình tối ưu hóa. Hình sau hiển thị giá trị loss cho 200 generation đầu tiên.

Source để hiển thị biểu đồ loss ở trên.

Để kiểm tra một cách trực quan độ chính xác của chương trình. Chúng ta sẽ hiển thị các giá trị sale tiên đoán (màu blue) bởi chương trình và giá trị sale thực (màu green) như sau

Source để hiển thị biểu đồ trên.

 

Tiên đoán giá nhà dùng bộ dữ liệu Boston House Price

Bộ dữ liệu Boston có 506 dòng và 14 cột, trong đó cột cuối cùng medv chứa giá trị giá nhà (thực). Các cột được trình bày ở bảng sau.

Bài toán yêu cầu dựa vào dữ liệu trên để viết chương trình tiên đoán giá nhà (cho những data mới, không bao gồm ở trên). Các bạn có thể download file dữ liệu ở link sau

https://www.dropbox.com/s/yyqlzj2yqum45re/Boston_Dataset.csv?dl=0

Hình sau hiển thị một số dòng từ file dữ liệu Boston_Dataset.csv.

Chúng ta thấy cột đầu tiên ID không quan trọng và chúng ta cần loại bỏ khi xử lý dữ liệu. Còn lại, dữ liệu có 13 đặc trưng và một cột label (giá nhà thực). Chúng ta giả định dữ liệu phân bố theo phương trình sau

$$price = \sum_{i=1}^{14} c_i*f_i$$

trong đó \(c_i\) là các tham số cần tìm và \(f_i\) bao gồm 13 đặc trưng theo thứ tự xuất hiện theo cột dữ liệu, và \(f_{14} = 1.0\).

Từ công thức trên, chúng ta xác định được chiều dài của chromosome là 14 và có kiểu floating-point; cũng như biết được cách thức tính fitness cho mỗi chromosome.

Dưới đây là code cho chương trình

# aivietnam.ai - estimation of Boston house prices
import random

# size of individual (chromosome); 13 coefficients and 1 bias
n = 14                 
    
m = 600                # size of population
n_generations = 4000   # number of generations
losses = []            # để vẽ biểu đồ quá trình tối ưu

# Hàm load data
def load_data():
    # kết nối với file
    file = open('Boston_Dataset.csv','r')

    # readlines giúp việc đọc file theo từng dòng , mỗi dòng là 1 chuỗi
    lines = file.readlines()
    
    print(len(lines))
    
    features = []
    prices   = []
    for i in range(1, 334): 
        strings = lines[i].split(',')
        feature = [float(s.strip()) for s in strings[1:len(strings)-1]]
        feature.append(1.0) # for bias
        features.append(feature)
        prices.append(float(strings[-1]))

    # Đóng kết nối với file
    file.close()
    
    return features, prices

# load data
features, prices = load_data()

def generate_random_value(bound = 100):
    return (random.random())*bound

def compute_loss(individual):    
    estimated_prices = []
    for feature in features:        
        estimated_price = sum(c*x for x, c in zip(feature, individual))
        estimated_prices.append(estimated_price)
           
    losses = [abs(y_est-y_gt) for y_est, y_gt in zip(estimated_prices, prices)]    
    return sum(losses)

def compute_fitness(individual):
    loss = compute_loss(individual)
    fitness = 1 / (loss + 1)
    return fitness

def create_individual():
    return [generate_random_value() for _ in range(n)]

def crossover(individual1, individual2, crossover_rate = 0.9):
    individual1_new = individual1.copy()
    individual2_new = individual2.copy()
    
    for i in range(n):
        if random.random() < crossover_rate:
            individual1_new[i] = individual2[i]
            individual2_new[i] = individual1[i]            
    
    return individual1_new, individual2_new

def mutate(individual, mutation_rate = 0.05):
    individual_m = individual.copy()
    
    for i in range(n):
        if random.random() < mutation_rate:
            individual_m[i] = generate_random_value()
        
    return individual_m

def selection(sorted_old_population):    
    index1 = random.randint(0, m-1)    
    while True:
        index2 = random.randint(0, m-1)    
        if (index2 != index1):
            break
            
    individual_s = sorted_old_population[index1]
    if index2 > index1:
        individual_s = sorted_old_population[index2]
    
    return individual_s 

def create_new_population(old_population, elitism=2, gen=1):
    sorted_population = sorted(old_population, key=compute_fitness)
        
    if gen%50 == 0:
        losses.append(compute_loss(sorted_population[m-1]))
        print("Best loss:", compute_loss(sorted_population[m-1]))    
        #print(sorted_population[m-1])
    
    new_population = []
    while len(new_population) < m-elitism:
        # selection
        individual_s1 = selection(sorted_population)
        individual_s2 = selection(sorted_population) # duplication
        
        # crossover
        individual_c1, individual_c2 = crossover(individual_s1, individual_s2)
        
        # mutation
        individual_m1 = mutate(individual_c1)
        individual_m2 = mutate(individual_c2)
        
        new_population.append(individual_m1)
        new_population.append(individual_m2)            
    
    for ind in sorted_population[m-elitism:]:
        new_population.append(ind.copy())
    
    return new_population

population = [create_individual() for _ in range(m)]
for i in range(n_generations):
    population = create_new_population(population, 2, i)

 

Hình sau hiển thị giá trị loss cho 200 generation đầu tiên.

Hình sau hiển thị các giá trị sale dự đoán và sale thực

Chúng ta có thể quan sát thấy kết quả không tốt như bài advertising. Có một số cách để nâng cao kết quả

1) Tăng population size

2) Tăng generation

3) Dùng thuật toán mạnh hơn GA như Artificial Bee Colony, hay LSHADE