Lasso线性回归(Least Absolute Shrinkage and Selection Operator)是一种能够进行特征选择和正则化的线性回归方法。其重要的思想是L1正则化:其基本原理为在损失函数中加上模型权重系数的绝对值,要想让模型的拟合效果比较好,就要使损失函数尽可能的小,因此这样会使很多权重变为0或者权重值变得尽可能小,这样通过减少权重、降低权重值的方法能够防止模型过拟合,增加模型的泛化能力。其中还有另外一种类似的方法叫做岭回归,其基本原理是在损失函数中加上含有模型权重系数的平方的惩罚项,其他原理基本相同。
Lasso线性回归的目标是最小化以下损失函数:
其中:
理论上来说当模型的损失函数越小时,构建的模型越接近真实分布,但是当模型的参数远远大于用来训练模型的数量的时候,很容易造成过拟合(模型学到的特征不是样本之间的共同特征,而是样本自身的特异性特征)。
通过L1正则化,可以约束线性模型中的权重系数,而且会减少特征量,保留权重系数有效的特征,同时能够减少系数之间的差距,让模型更具有泛化性。
当模型的损失函数达到最小的时候,惩罚项λ达到最优,模型的性能达到最优。
本文同样使用留一交叉验证的方法验证模型的分类性能,具体应用场景可以参考:机器学习(一)——递归特征消除法实现SVM(matlab)。
本文还提供了均方误差与特征数量的变化关系曲线、λ与均方误差的变化关系曲线以及分类结果的ROC曲线画法,如有需要请选择对应模块进行调整作图。
matlab实现代码如下:
labels = res(:, 1); % 第一列是标签
features = res(:, 2:end); % 后面的列是特征
features = zscore(features); %归一化处理
%% 使用留一法交叉验证选择最优的 Lambda 值及其对应的特征
numSamples = size(features, 1);
lambda_values = logspace(-2, 2, 60); % 选择一系列 Lambda 值
mse_values = zeros(size(lambda_values)); % 存储每个 Lambda 值的均方误差
selected_features_all = cell(length(lambda_values), 1); % 存储每个 Lambda 值选择的特征
for i = 1:length(lambda_values)
mse_fold = zeros(numSamples, 1);
selected_feature = []; % 重置选择的特征
for j = 1:numSamples
% 划分训练集和验证集
X_train = features([1:j-1, j+1:end], :); % 使用除了当前样本之外的所有样本作为训练集
y_train = labels([1:j-1, j+1:end]); % 训练集标签
X_valid = features(j, :); % 当前样本作为验证集
y_valid = labels(j); % 验证集标签
% 训练 LASSO 模型
B = lasso(X_train, y_train, 'Lambda', lambda_values(i));
% 在验证集上进行预测
y_pred = X_valid * B;
% 计算均方误差
mse_fold(j) = mean((y_pred - y_valid).^2);
% 将选择的特征添加到列表中
selected_feature = [selected_feature; find(B ~= 0)]; % 使用分号进行垂直串联
end
% 计算当前 Lambda 值的平均均方误差
mse_values(i) = mean(mse_fold);
selected_feature= unique(selected_feature);%消除重复的特征
% 保存每个 Lambda 值选择的特征
selected_features_all{i} = selected_feature;
end
% 找到使均方误差最小的 Lambda 值
[min_mse, min_idx] = min(mse_values);
min_idx=min_idx-2; %相当于当λ达到最优时,特征数量为0,所以要找最靠近最优λ的λ值来构建模型,此处可调
min_mse=mse_values(min_idx);
optimal_lambda = lambda_values(min_idx);
% 找到最优 Lambda 值对应的特征
selected_features_optimal = selected_features_all{min_idx};
selected_features_optimal = unique(selected_features_optimal); %%%%%%%保留不同的特征,防止特征重复出现
% 显示最优的 Lambda 值及其对应的特征
disp(['最优的 Lambda 值:', num2str(optimal_lambda)]);
%% 对挑选出来的向量构建模型,并使用留一交叉验证计算其平均准确率
% 初始化变量来存储准确率
num_samples = size(features, 1);
accuracies = zeros(num_samples, 1);
Prediction_probability=zeros(num_samples, 1);%声明一个空数组用于储存预测的概率
% 遍历每个样本
for i = 1:num_samples
% 留出第 i 个样本作为验证集,其余样本作为训练集
X_train = features([1:i-1, i+1:end], selected_features_optimal); % 选择选出来的特征
y_train = labels([1:i-1, i+1:end]);
X_valid = features(i, selected_features_optimal);
y_valid = labels(i);
% 训练逻辑回归模型
mdl = fitglm(X_train, y_train, 'Distribution', 'binomial', 'Link', 'logit');
% 在验证集上进行预测
y_pred = predict(mdl, X_valid);
% 将预测概率转换为类别标签
y_pred_label = round(y_pred);
Prediction_probability(i)=y_pred;
% 计算准确率
accuracies(i) = (y_pred_label == y_valid);
end
% 计算平均准确率
average_accuracy = mean(accuracies);
% 显示平均准确率
disp(['平均准确率:', num2str(average_accuracy)]);
% 计算选取的特征个数
num_selected_features = cellfun(@length, selected_features_all);
% 绘制三维图像
figure;
plot3(lambda_values, mse_values, num_selected_features, 'b-', 'LineWidth', 2);
xlabel('λ值');
ylabel('均方误差');
zlabel('特征数量');
title('λ值与均方误差以及特征数量的变化关系');
grid on;
% 绘制 λ 和平均误差曲线
figure;
semilogx(lambda_values, mse_values, 'b-', 'LineWidth', 2);
xlabel('λ值');
ylabel('均方误差');
title('λ值与均方误差的变化关系');
grid on;
%计算ROC模型的相关参数
[fpr, tpr, thresholds] = perfcurve(labels, Prediction_probability, 1);
% 绘制 ROC 曲线
figure;
plot(fpr, tpr);
xlabel('假阳性率 (FPR)');
ylabel('真阳性率 (TPR)');
title('Lasso回归模型下ROC 曲线');
% 计算曲线下面积(AUC)
auc = trapz(fpr, tpr);
legend(['AUC = ' num2str(auc)]);
%% 统计每次迭代后选择的特征的,然后出一个排名,迭代过程中出现最多次的特征认为是可靠的特征
% 初始化一个空的结构数组
unique_elements_struct = struct('value', {}, 'count', {});
% 遍历每个单元格中的数组,统计每个元素出现的次数
for i = 1:numel(selected_features_all)
current_array = selected_features_all{i};
unique_elements = unique(current_array);
for j = 1:numel(unique_elements)
element = unique_elements(j);
idx = find([unique_elements_struct.value] == element);
if isempty(idx)
% 如果元素不在结构数组中,则添加它
unique_elements_struct(end+1).value = element;
unique_elements_struct(end).count = sum(current_array == element);
else
% 否则更新该元素的计数
unique_elements_struct(idx).count = unique_elements_struct(idx).count + sum(current_array == element);
end
end
end
% 按照出现次数进行降序排序
[~, sorted_indices] = sort([unique_elements_struct.count], 'descend');
unique_elements_struct = unique_elements_struct(sorted_indices);
% 显示结果
for i = 1:numel(unique_elements_struct)
disp(['Element ', num2str(unique_elements_struct(i).value), ' appeared ', num2str(unique_elements_struct(i).count), ' times.']);
end