Bài viết này trình bày cách áp dụng thuật toán Nhảy Ếch Ngẫu nhiên (SFLA) để tối ưu hóa các hệ số trong mô hình hồi quy bình phương tối thiểu (Ordinary Least Squares - OLS) bằng MATLAB. SFLA là một thuật toán tối ưu hóa dựa trên quần thể, mô phỏng hành vi tìm kiếm thức ăn của một nhóm ếch, với mục tiêu tìm ra các tham số hồi quy giúp giảm thiểu sai số bình phương trung bình gốc (RMSE).
1. Mã nguồn MATLAB
Chúng ta sẽ sử dụng ba tệp MATLAB chính:
main_sfla_regression.m: Chương trình chính để chạy mô phỏng.sfla_optimizer.m: Hàm cốt lõi triển khai thuật toán SFLA.simple_sfla.m: Phiên bản SFLA đơn giản hóa để kiểm tra nhanh.
1.1. Chương trình chính (main_sfla_regression.m)
Tệp này thực hiện các bước sau:
- Tạo dữ liệu thử nghiệm: Sinh ra dữ liệu đầu vào
Xvà đầu raydựa trên một bộ tham số thựctrue_params, có thêm nhiễu ngẫu nhiên để mô phỏng dữ liệu thực tế. - Thiết lập tham số SFLA: Định nghĩa các tham số cho thuật toán SFLA như kích thước quần thể, số lượng tộc, số lần lặp, v.v.
- Chạy tối ưu hóa SFLA: Gọi hàm
sfla_optimizerđể tìm các tham số hồi quy tối ưu. - Xác minh bằng hồi quy bình phương tối thiểu chuẩn: Tính toán các hệ số hồi quy bằng phương pháp OLS thông thường để so sánh.
- Hiển thị kết quả SFLA: Trình bày các tham số tìm được và sai số RMSE tương ứng.
- Đánh giá mô hình: Tính toán các chỉ số hiệu suất khác như MAE và R² cho mô hình SFLA.
- Trực quan hóa kết quả: Vẽ biểu đồ minh họa quá trình hội tụ, so sánh giá trị dự đoán và thực tế, phân tích phần dư, so sánh tham số, phân bố sai số và kết quả dự đoán giữa SFLA và OLS chuẩn.
- Lưu kết quả: Lưu trữ các kết quả quan trọng vào tệp
.mat.
% --- main_sfla_regression.m ---
%% Thuật toán Nhảy Ếch Ngẫu nhiên Tối ưu Hồi quy Bình phương Tối thiểu
% Mục đích: Sử dụng SFLA để tìm hệ số hồi quy tối ưu, giảm thiểu lỗi bình phương tối thiểu
clear; clc; close all;
fprintf('=== Thuật toán Nhảy Ếch Ngẫu nhiên Tối ưu Hồi quy ===\n\n');
%% 1. Tạo dữ liệu thử nghiệm
rng(42); % Đặt seed cho tính tái lập
n_samples = 100; % Số lượng mẫu
n_features = 3; % Số lượng đặc trưng (bao gồm cả bias)
% Tham số thực tế (để tạo dữ liệu)
true_coeffs = [2.5; -1.8; 3.2; 5.0]; % [w1, w2, w3, bias]
% Tạo đặc trưng đầu vào
X_input = randn(n_samples, n_features - 1);
% Thêm cột bias (toàn số 1)
X_matrix = [ones(n_samples, 1), X_input];
% Tạo đầu ra với nhiễu
noise = 0.1 * randn(n_samples, 1);
y_output = X_matrix * true_coeffs + noise;
fprintf('Đã tạo dữ liệu:\n');
fprintf(' Số mẫu: %d\n', n_samples);
fprintf(' Số đặc trưng: %d\n', n_features);
fprintf(' Hệ số thực tế: [%s]\n\n', strjoin(cellstr(num2str(true_coeffs, '%.2f')), ', '));
%% 2. Thiết lập tham số SFLA
sfla_params = struct();
sfla_params.population_size = 100; % Tổng số ếch
sfla_params.num_memeplexes = 10; % Số lượng tộc
sfla_params.max_iterations = 200; % Số lần lặp tối đa
sfla_params.max_jumps = 15; % Số lần nhảy tối đa mỗi tộc
sfla_params.step_damping = 0.5; % Hệ số giảm bước nhảy
sfla_params.dimension = n_features; % Số chiều của không gian tìm kiếm
sfla_params.lower_bound = -10 * ones(1, n_features); % Giới hạn dưới
sfla_params.upper_bound = 10 * ones(1, n_features); % Giới hạn trên
fprintf('Tham số SFLA:\n');
fprintf(' Kích thước quần thể: %d\n', sfla_params.population_size);
fprintf(' Số lượng tộc: %d\n', sfla_params.num_memeplexes);
fprintf(' Số lần lặp: %d\n\n', sfla_params.max_iterations);
%% 3. Chạy tối ưu hóa SFLA
fprintf('Bắt đầu tối ưu hóa SFLA...\n');
tic;
[optimal_coeffs, min_rmse_sfla, convergence_plot] = sfla_optimizer(X_matrix, y_output, sfla_params);
opt_duration = toc;
fprintf('Hoàn thành tối ưu hóa! Thời gian: %.2f giây\n\n', opt_duration);
%% 4. Xác minh bằng Hồi quy Bình phương Tối thiểu Chuẩn
fprintf('Kết quả Hồi quy Bình phương Tối thiểu Chuẩn (OLS):\n');
standard_coeffs = (X_matrix' * X_matrix) \ (X_matrix' * y_output);
standard_rmse = sqrt(mean((y_output - X_matrix * standard_coeffs).^2));
fprintf(' Hệ số: [%s]\n', strjoin(cellstr(num2str(standard_coeffs', '%.4f')), ', '));
fprintf(' RMSE: %.6f\n\n', standard_rmse);
%% 5. Hiển thị kết quả tối ưu hóa SFLA
fprintf('Kết quả tối ưu hóa SFLA:\n');
fprintf(' Hệ số tối ưu: [%s]\n', strjoin(cellstr(num2str(optimal_coeffs', '%.4f')), ', '));
fprintf(' RMSE tối thiểu: %.6f\n', min_rmse_sfla);
% Tính sai số hệ số
coeff_errors = abs(optimal_coeffs - true_coeffs);
relative_errors = (coeff_errors ./ abs(true_coeffs)) * 100;
fprintf(' Sai số hệ số: [%s]\n', strjoin(cellstr(num2str(coeff_errors', '%.4f')), ', '));
fprintf(' Sai số tương đối: [%s]\n\n', strjoin(cellstr(num2str(relative_errors', '%.2f%%')), ', '));
%% 6. Dự đoán và đánh giá hiệu suất mô hình SFLA
y_pred_sfla = X_matrix * optimal_coeffs;
rmse_sfla_eval = sqrt(mean((y_output - y_pred_sfla).^2));
mae_sfla = mean(abs(y_output - y_pred_sfla));
r2_sfla = 1 - sum((y_output - y_pred_sfla).^2) / sum((y_output - mean(y_output)).^2);
fprintf('Chỉ số hiệu suất mô hình SFLA:\n');
fprintf(' RMSE: %.6f\n', rmse_sfla_eval);
fprintf(' MAE: %.6f\n', mae_sfla);
fprintf(' R²: %.6f\n\n', r2_sfla);
%% 7. Trực quan hóa kết quả
figure('Position', [100, 100, 1200, 800]);
% Biểu đồ 1: Đường cong hội tụ
subplot(2, 3, 1);
semilogy(convergence_plot, 'b-', 'LineWidth', 2);
grid on;
xlabel('Số lần lặp');
ylabel('RMSE (thang log)');
title('Đường cong hội tụ SFLA');
legend('RMSE tốt nhất');
% Biểu đồ 2: So sánh giá trị thực tế và dự đoán (biểu đồ tán xạ)
subplot(2, 3, 2);
plot(y_output, y_pred_sfla, 'bo', 'MarkerSize', 6, 'MarkerFaceColor', 'b');
hold on;
plot([min(y_output), max(y_output)], [min(y_output), max(y_output)], 'r--', 'LineWidth', 1.5);
grid on;
xlabel('Giá trị thực tế');
ylabel('Giá trị dự đoán');
title('Dự đoán SFLA vs Thực tế');
legend('Điểm dự đoán', 'Đường lý tưởng');
% Biểu đồ 3: Phân tích phần dư
subplot(2, 3, 3);
residuals = y_output - y_pred_sfla;
plot(y_pred_sfla, residuals, 'ro', 'MarkerSize', 6, 'MarkerFaceColor', 'r');
hold on;
plot([min(y_pred_sfla), max(y_pred_sfla)], [0, 0], 'k-', 'LineWidth', 1.5);
grid on;
xlabel('Giá trị dự đoán');
ylabel('Phần dư');
title('Phân tích phần dư');
% Biểu đồ 4: So sánh các hệ số
subplot(2, 3, 4);
bar_data = [true_coeffs', standard_coeffs', optimal_coeffs'];
bar_labels = {'w1', 'w2', 'w3', 'bias'};
bar(bar_data);
set(gca, 'XTickLabel', bar_labels);
grid on;
ylabel('Giá trị hệ số');
title('So sánh hệ số');
legend('Thực tế', 'OLS chuẩn', 'SFLA');
% Biểu đồ 5: Phân bố sai số dự đoán
subplot(2, 3, 5);
histogram(residuals, 20, 'FaceColor', 'g', 'EdgeColor', 'black');
grid on;
xlabel('Sai số dự đoán');
ylabel('Tần suất');
title('Phân bố sai số dự đoán');
% Biểu đồ 6: So sánh dự đoán SFLA và OLS chuẩn
subplot(2, 3, 6);
plot(y_output, 'k-', 'LineWidth', 1.5, 'DisplayName', 'Giá trị thực tế');
hold on;
plot(X_matrix * standard_coeffs, 'r--', 'LineWidth', 1.5, 'DisplayName', 'OLS chuẩn');
plot(y_pred_sfla, 'b:', 'LineWidth', 2, 'DisplayName', 'SFLA tối ưu');
grid on;
xlabel('Thứ tự mẫu');
ylabel('Giá trị đầu ra');
title('So sánh kết quả dự đoán');
legend('Location', 'best');
sgtitle('Kết quả Tối ưu hóa Hồi quy bằng SFLA');
%% 8. Lưu kết quả
simulation_results = struct();
simulation_results.true_coefficients = true_coeffs;
simulation_results.ols_coefficients = standard_coeffs;
simulation_results.sfla_coefficients = optimal_coeffs;
simulation_results.sfla_min_rmse = min_rmse_sfla;
simulation_results.ols_rmse = standard_rmse;
simulation_results.convergence_history = convergence_plot;
simulation_results.optimization_time = opt_duration;
save('sfla_regression_results.mat', 'simulation_results');
fprintf('Kết quả đã được lưu vào: sfla_regression_results.mat\n');
1.2. Hàm cốt lõi SFLA (sfla_optimizer.m)
Hàm này thực hiện logic chính của thuật toán SFLA:
- Khởi tạo quần thể: Tạo ra một tập hợp các cá thể (ếch) với các tham số ngẫu nhiên trong giới hạn cho phép.
- Đánh giá thích nghi: Tính toán độ "thích nghi" (ở đây là RMSE) cho mỗi cá thể.
- Vòng lặp chính:
- Sắp xếp các cá thể theo độ thích nghi.
- Chia quần thể thành các tộc (memeplexes).
- Thực hiện tìm kiếm cục bộ trong mỗi tộc: Mỗi ếch trong tộc di chuyển về phía ếch tốt nhất trong tộc đó. Vị trí của ếch tệ nhất được cập nhật dựa trên sự khác biệt giữa ếch tốt nhất và tệ nhất.
- Trộn lẫn toàn cục (shuffle): Sau khi các tộc thực hiện tìm kiếm cục bộ, các ếch được trộn lẫn lại với nhau.
- Đánh giá lại độ thích nghi và ghi nhận kết quả tốt nhất.
- Trả về cá thể tốt nhất và giá trị thích nghi tương ứng.
% --- sfla_optimizer.m ---
function [best_params, best_fitness, convergence_curve] = sfla_optimizer(X, y, params)
% Bộ tối ưu hóa thuật toán Nhảy Ếch Ngẫu nhiên (SFLA)
% Đầu vào:
% X - Ma trận đặc trưng đầu vào [số_mẫu, số_tham_số]
% y - Vector đầu ra [số_mẫu, 1]
% params - Cấu trúc chứa các tham số thuật toán
% Đầu ra:
% best_params - Các tham số tối ưu tìm được
% best_fitness - Giá trị thích nghi tốt nhất (RMSE)
% convergence_curve - Lịch sử giá trị thích nghi tốt nhất qua các vòng lặp
% Khởi tạo quần thể
population = initialize_population(params);
% Tính toán độ thích nghi ban đầu
fitness = evaluate_fitness(population, X, y);
% Khởi tạo vector lưu trữ đường cong hội tụ
convergence_curve = zeros(params.max_iterations, 1);
% Vòng lặp tối ưu hóa chính
for iter = 1:params.max_iterations
% Sắp xếp quần thể theo độ thích nghi (tăng dần)
[fitness, sort_idx] = sort(fitness);
population = population(sort_idx, :);
% Chia quần thể thành các tộc
memeplexes = divide_into_memeplexes(population, params.num_memeplexes);
% Thực hiện tìm kiếm cục bộ trong từng tộc
for m = 1:params.num_memeplexes
memeplexes{m} = local_search(memeplexes{m}, X, y, params);
end
% Trộn lẫn quần thể toàn cục
population = shuffle_population(memeplexes, params.num_memeplexes);
% Tính lại độ thích nghi cho quần thể mới
fitness = evaluate_fitness(population, X, y);
% Ghi nhận giá trị thích nghi tốt nhất của vòng lặp hiện tại
[current_best_fitness, best_idx] = min(fitness);
convergence_curve(iter) = current_best_fitness;
% Hiển thị tiến trình
if mod(iter, 20) == 0 || iter == 1
fprintf(' Vòng lặp %d/%d: RMSE tốt nhất = %.6f\n', iter, params.max_iterations, current_best_fitness);
end
end
% Trả về giải pháp tốt nhất cuối cùng
[~, final_best_idx] = min(fitness);
best_params = population(final_best_idx, :)';
best_fitness = fitness(final_best_idx);
end
% --- Hàm con ---
%% Khởi tạo quần thể
function population = initialize_population(params)
population = zeros(params.population_size, params.dimension);
for i = 1:params.population_size
for j = 1:params.dimension
population(i, j) = params.lower_bound(j) + rand() * (params.upper_bound(j) - params.lower_bound(j));
end
end
end
%% Đánh giá độ thích nghi (tính RMSE)
function fitness = evaluate_fitness(population, X, y)
[n_frogs, n_params] = size(population);
fitness = zeros(n_frogs, 1);
for i = 1:n_frogs
current_params = population(i, :)';
predictions = X * current_params;
errors = y - predictions;
fitness(i) = sqrt(mean(errors.^2)); % RMSE
end
end
%% Chia quần thể thành các tộc
function memeplexes = divide_into_memeplexes(population, n_memeplexes)
[n_frogs, ~] = size(population);
frogs_per_memeplex = floor(n_frogs / n_memeplexes);
memeplexes = cell(n_memeplexes, 1);
for m = 1:n_memeplexes
start_idx = (m - 1) * frogs_per_memeplex + 1;
end_idx = min(m * frogs_per_memeplex, n_frogs);
memeplexes{m} = population(start_idx:end_idx, :);
end
end
%% Tìm kiếm cục bộ (Bước nhảy của ếch)
function memeplex = local_search(memeplex, X, y, params)
[n_frogs, n_params] = size(memeplex);
% Thực hiện các bước nhảy cho từng ếch trong tộc
for jump_count = 1:params.max_jumps
% Tìm ếch tốt nhất và tệ nhất trong tộc
current_fitness = evaluate_fitness(memeplex, X, y);
[~, best_idx] = min(current_fitness);
[~, worst_idx] = max(current_fitness);
best_frog_pos = memeplex(best_idx, :)';
worst_frog_pos = memeplex(worst_idx, :)';
% Tính toán bước nhảy
jump_vector = params.step_damping * (best_frog_pos - worst_frog_pos);
% Cập nhật vị trí của ếch tệ nhất
new_worst_pos = worst_frog_pos + jump_vector;
% Kiểm tra giới hạn (bounding)
for dim_idx = 1:n_params
if new_worst_pos(dim_idx) < params.lower_bound(dim_idx)
new_worst_pos(dim_idx) = params.lower_bound(dim_idx);
elseif new_worst_pos(dim_idx) > params.upper_bound(dim_idx)
new_worst_pos(dim_idx) = params.upper_bound(dim_idx);
end
end
% Đánh giá vị trí mới
new_fitness_val = sqrt(mean((y - X * new_worst_pos).^2));
% Nếu vị trí mới tốt hơn, cập nhật
if new_fitness_val < current_fitness(worst_idx)
memeplex(worst_idx, :) = new_worst_pos';
end
end
end
%% Trộn lẫn quần thể toàn cục (Fisher-Yates shuffle)
function population = shuffle_population(memeplexes, n_memeplexes)
% Gộp tất cả các tộc lại
population = [];
for m = 1:n_memeplexes
population = [population; memeplexes{m}];
end
% Thuật toán Fisher-Yates để xáo trộn
[n_frogs, ~] = size(population);
for i = n_frogs:-1:2
j = randi(i);
% Hoán đổi hàng thứ i và hàng thứ j
temp_row = population(i, :);
population(i, :) = population(j, :);
population(j, :) = temp_row;
end
end
1.3. Phiên bản SFLA đơn giản (simple_sfla.m)
Phiên bản này cung cấp một cách triển khai SFLA rút gọn, chỉ tập trung vào việc cập nhật cá thể tệ nhất dựa trên cá thể tốt nhất, hữu ích cho việc kiểm tra nhanh hoặc các ứng dụng không yêu cầu sự phức tạp đầy đủ của thuật toán.
% --- simple_sfla.m ---
%% SFLA phiên bản đơn giản (Dùng để kiểm tra nhanh)
function [best_params, best_rmse] = simple_sfla(X, y, dim, pop_size, iterations)
% Thuật toán Nhảy Ếch Ngẫu nhiên phiên bản đơn giản
% Đầu vào:
% X - Ma trận đặc trưng đầu vào
% y - Vector đầu ra
% dim - Số chiều của tham số
% pop_size - Kích thước quần thể
% iterations - Số lần lặp
% Đầu ra:
% best_params - Tham số tối ưu
% best_rmse - RMSE tối thiểu
% Giới hạn tham số
lb = -10 * ones(1, dim);
ub = 10 * ones(1, dim);
% Khởi tạo quần thể
population = lb + rand(pop_size, dim) .* (ub - lb);
% Vòng lặp tối ưu hóa
for iter = 1:iterations
% Đánh giá độ thích nghi
fitness = zeros(pop_size, 1);
for i = 1:pop_size
predictions = X * population(i, :)';
fitness(i) = sqrt(mean((y - predictions).^2));
end
% Sắp xếp
[fitness, idx] = sort(fitness);
population = population(idx, :);
% Cập nhật cá thể tệ nhất (học từ cá thể tốt nhất)
best_solution = population(1, :)';
worst_solution = population(end, :)';
% Bước nhảy ngẫu nhiên
step_size = 0.5 * (best_solution - worst_solution) .* (2 * rand(dim, 1) - 1);
new_worst_solution = worst_solution + step_size;
% Kiểm tra giới hạn
new_worst_solution = max(new_worst_solution', lb);
new_worst_solution = min(new_worst_solution, ub');
% Lựa chọn tham lam (Greedy selection)
pred_new = X * new_worst_solution';
new_fitness_val = sqrt(mean((y - pred_new).^2));
if new_fitness_val < fitness(end)
population(end, :) = new_worst_solution;
end
% Hiển thị tiến trình
if mod(iter, 50) == 0
fprintf('Vòng lặp %d: RMSE = %.6f\n', iter, fitness(1));
end
end
% Trả về giải pháp tốt nhất
best_params = population(1, :)';
best_rmse = fitness(1);
end
2. Hướng dẫn sử dụng
2.1. Các bước thực hiện
- Lưu ba tệp mã nguồn trên (
main_sfla_regression.m,sfla_optimizer.m,simple_sfla.m) vào cùng một thư mục trong MATLAB. - Mở tệp
main_sfla_regression.mtrong MATLAB Editor. - Nhấn nút "Run" hoặc gõ lệnh
main_sfla_regressionvào Command Window để bắt đầu quá trình mô phỏng.
2.2. Điều chỉnh tham số
Bảng dưới đây đưa ra gợi ý về việc điều chỉnh các tham số của SFLA để đạt hiệu quả tốt hơn:
| Tham số | Giá trị gợi ý | Mô tả |
|---|---|---|
population_size |
50-200 | Tổng số ếch trong quần thể. Giá trị lớn hơn giúp khám phá không gian tìm kiếm toàn diện hơn nhưng tốn nhiều thời gian tính toán hơn. |
num_memeplexes |
5-20 | Số lượng tộc. Thường nên đặt bằng khoảng 1/10 kích thước quần thể. |
max_iterations |
100-500 | Số vòng lặp tối đa. Tăng nếu thuật toán chưa hội tụ, giảm nếu thấy không cần thiết. |
max_jumps |
10-20 | Số bước nhảy tối đa mà mỗi ếch thực hiện trong một vòng tìm kiếm cục bộ. |
step_damping |
0.3-0.7 | Hệ số giảm dần của bước nhảy. Ảnh hưởng đến tốc độ hội tụ và khả năng tránh cực tiểu địa phương. |
2.3. Đặc điểm của thuật toán SFLA
Ưu điểm:
- Khả năng tìm kiếm cực trị toàn cục mạnh mẽ, ít bị sa lầy vào các điểm cực tiểu cục bộ.
- Ít tham số cần điều chỉnh, dễ dàng cài đặt và sử dụng.
- Tốc độ hội tụ thường nhanh đối với nhiều bài toán.
Nhược điểm:
- Độ nhạy cảm với việc lựa chọn tham số.
- Có thể hội tụ chậm hơn trên các bài toán có số chiều quá cao.