特征选择:Lasso和Boruta算法的结合应用
背景
Lasso算法是一种用于回归分析的线性模型方法,具有变量选择和正则化的功能,Lasso通过在损失函数中加入 正则项来约束回归系数的大小,从而达到简化模型、提高预测精度的目的,以下是Lasso算法的关键要点:
目标函数
Lasso的目标是最小化以下目标函数:
正则化项(L1 范数)
优势
- 特征选择:Lasso能够自动选择重要的特征,忽略不重要的特征,从而简化模型,减少过拟合。
- 易解释性:由于Lasso产生的模型较为稀疏,因此更易于解释和分析。
局限性
- 如果特征之间存在高度相关性(多重共线性),Lasso可能会随机选择其中一个特征,而忽略其他高度相关的特征。
- 当特征数量 p大于样本数量n 时,Lasso最多只能选择n个特征。
总结
Lasso算法通过引入L1 正则化实现了变量选择和模型稀疏化的目的,是处理高维数据和减少模型复杂度的一种有效方法,正则化参数 的选择至关重要,可以通过交叉验证来优化。
代码实现
数据导入处理
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
df = pd.read_csv('Chabuhou.csv')
# 划分特征和目标变量
X = df.drop(['Electrical_cardioversion'], axis=1)
y = df['Electrical_cardioversion']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=42, stratify=df['Electrical_cardioversion'])
df.head()
读取数据,将其分为特征(X)和目标变量(y),然后将数据集按80%训练集和20%测试集进行划分,使用的是一个心脏电复律的数据集包含46个特征变量一个目标变量为二分类任务,和前文特征选择:基于随机森林的Boruta算法应用为同一个数据集。
Lasso算法实现
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error
# 标准化数据
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 训练Lasso模型
lasso = Lasso(alpha=0.1) # alpha是正则化强度的参数
lasso.fit(X_train_scaled, y_train)
# 打印Lasso模型的系数
coefficients = pd.Series(lasso.coef_, index=X.columns)
selected_features = coefficients[coefficients != 0].index
print("Selected features:")
print(selected_features)
# 计算测试集的均方误差
y_pred = lasso.predict(X_test_scaled)
mse = mean_squared_error(y_test, y_pred)
print(f"\nMean Squared Error on Test Set: {mse}")
标准化数据后(标准化确保各特征在相同尺度上,使Lasso回归在特征选择时更加公平和有效)训练一个Lasso回归模型来选择重要特征,并计算测试集上的均方误差,当然这里只是人为随意确定了一个正则化强度值;接下来可以使用交叉验证来优化正则化参数 alpha,从而提高模型的性能。
交叉验证优化正则化参数
from sklearn.linear_model import LassoCV
from sklearn.model_selection import RepeatedKFold
# 假设特征名存储在 feature_names 列表中
feature_names = X.columns
# 定义一组 alpha 值的范围
alphas = np.logspace(-4, 0, 50) # 生成 50 个在 10^-4 到 10^0 之间的 alpha 值
# 使用交叉验证的 LassoCV
lasso_cv = LassoCV(alphas=alphas, cv=RepeatedKFold(n_splits=10, n_repeats=3, random_state=42), random_state=42)
lasso_cv.fit(X_train_scaled, y_train)
# 计算均方误差路径和标准差
mse_path = lasso_cv.mse_path_.mean(axis=1) # 每个 alpha 的均方误差
mse_std = lasso_cv.mse_path_.std(axis=1) # 每个 alpha 的均方误差的标准差
# 找到最佳 alpha 和 1-SE 规则的 alpha
best_alpha_index = np.argmin(mse_path) # 最小均方误差的索引
best_alpha = lasso_cv.alphas_[best_alpha_index] # 最佳 alpha 值
one_se_index = np.where(mse_path <= mse_path[best_alpha_index] + mse_std[best_alpha_index])[0][0] # 1-SE 规则的 alpha 索引
one_se_alpha = lasso_cv.alphas_[one_se_index] # 1-SE 规则的 alpha 值
# 打印最佳 alpha 值
print(f"Best alpha (λ_min): {best_alpha}")
print(f"1-SE rule alpha (λ_1se): {one_se_alpha}")
# 为两个 alpha 值进行特征选择
lasso_best_alpha = LassoCV(alphas=[best_alpha], cv=RepeatedKFold(n_splits=10, n_repeats=3, random_state=42), random_state=42)
lasso_best_alpha.fit(X_train_scaled, y_train)
selected_features_best = [feature_names[i] for i in np.where(lasso_best_alpha.coef_ != 0)[0]] # 获取最佳 alpha 下的特征名
print(f"Selected features with λ_min: {selected_features_best}") # 打印 λ_min 下选择的特征名
lasso_one_se_alpha = LassoCV(alphas=[one_se_alpha], cv=RepeatedKFold(n_splits=10, n_repeats=3, random_state=42), random_state=42)
lasso_one_se_alpha.fit(X_train_scaled, y_train)
selected_features_one_se = [feature_names[i] for i in np.where(lasso_one_se_alpha.coef_ != 0)[0]] # 获取 1-SE 规则下的特征名
print(f"Selected features with λ_1se: {selected_features_one_se}") # 打印 λ_1se 下选择的特征名
# 绘图
plt.figure(figsize=(10, 6))
plt.errorbar(lasso_cv.alphas_, mse_path, yerr=mse_std, fmt='o', color='red', ecolor='gray', capsize=3)
plt.axvline(lasso_cv.alphas_[best_alpha_index], linestyle='--', color='black', label=r'$\lambda_{min}$')
plt.axvline(lasso_cv.alphas_[one_se_index], linestyle='--', color='blue', label=r'$\lambda_{1se}$')
plt.xscale('log') # 使用对数刻度显示 alpha 值
plt.xlabel('Alpha (α) value')
plt.ylabel('Mean Squared Error (MSE)')
plt.title('Lasso Regression: MSE vs Alpha (α) value')
plt.xticks(rotation=45)
plt.legend()
plt.tight_layout()
使用Lasso回归和交叉验证(LassoCV)来选择最优的正则化参数 alpha,并基于该参数进行特征选择,目的是找到最小均方误差对应的 alpha 值,以及应用1-SE规则找到更为保守的 alpha 值。
- 结果显示最小均方误差对应的α值为约0.0339,使用此α 值的Lasso模型选择了多个特征,表明在此正则化强度下,模型认为这些特征对预测有显著贡献。
1-SE规则的 值( ):
- 根据1-SE规则,选择的α 值为约0.184,这一规则旨在通过选择稍大的 α值来简化模型,同时保持误差接近最小误差,以提高模型的稳健性,结果表明,在此正则化强度下,模型仅选择了一个特征,这意味着模型更为稀疏、解释性更强,但可能略微牺牲了一些预测性能。
Lasso系数路径图
coefs = []
for a in alphas:
lasso = Lasso(alpha=a, max_iter=10000)
lasso.fit(X_train_scaled, y_train)
coefs.append(lasso.coef_)
# 绘制系数路径
plt.figure(figsize=(10, 6))
ax = plt.gca()
# 使用 log scale 显示 alpha 值
ax.plot(np.log10(alphas), coefs)
plt.xlabel('Log Lambda')
plt.ylabel('Coefficients')
plt.title('Lasso Paths')
plt.axis('tight')
plt.show()
Lasso路径图展示了Lasso算法如何通过调整正则化参数实现特征选择(每条线代表一个特征的回归系数):随着 alpha 值的增大,不重要的特征被逐渐排除,仅保留对目标预测有显著影响的特征,使得模型更稀疏、更易解释。
结合Boruta算法得到两个模型特征筛选交集
import networkx as nx
# 定义Boruta和Lasso选择的特征
boruta_features = [
'Type_of_atrial_fibrillation', 'BMI', 'Left_atrial_diameter',
'Systolic_blood_pressure', 'NtproBNP'
]
lasso_features = [
'Early_relapse', 'Late_relapse', 'Type_of_atrial_fibrillation',
'Sleep_apnea_syndrome', 'Heart_valve_disease', 'SGLT2i', 'B', 'BMI',
'Left_atrial_diameter', 'Systolic_blood_pressure', 'ALT', 'TSH'
]
# 创建集合用于求交集
boruta_set = set(boruta_features) # Boruta特征集合
lasso_set = set(lasso_features) # Lasso特征集合
intersection = boruta_set.intersection(lasso_set) # 两个集合的交集
boruta_only = boruta_set - intersection # 仅Boruta选择的特征
lasso_only = lasso_set - intersection # 仅Lasso选择的特征
# 创建图
G = nx.Graph()
# 添加节点和边
for feature in boruta_only:
G.add_edge('Boruta', feature, color='lightcoral') # 淡红色表示仅被Boruta选择的特征
for feature in lasso_only:
G.add_edge('Lasso', feature, color='lightblue') # 淡蓝色表示仅被Lasso选择的特征
for feature in intersection:
G.add_edge('Boruta', feature, color='lightcoral') # 淡红色边连接交集特征到Boruta
G.add_edge('Lasso', feature, color='lightblue') # 淡蓝色边连接交集特征到Lasso
# 获取边的颜色
edge_colors = [data['color'] for _, _, data in G.edges(data=True)]
# 设置节点的颜色
node_colors = []
for node in G.nodes():
if node == 'Boruta':
node_colors.append('lightcoral') # Boruta节点淡红色
elif node == 'Lasso':
node_colors.append('lightblue') # Lasso节点淡蓝色
elif node in boruta_only:
node_colors.append('lightcoral') # 仅被Boruta选择的特征淡红色
elif node in lasso_only:
node_colors.append('lightblue') # 仅被Lasso选择的特征淡蓝色
elif node in intersection:
node_colors.append('plum') # 交集特征节点用淡紫色表示
# 绘制图形
plt.figure(figsize=(10, 10))
pos = nx.spring_layout(G, seed=42) # 使用spring布局
nx.draw_networkx(
G,
pos,
edge_color=edge_colors,
node_color=node_colors,
with_labels=True,
node_size=1000,
font_size=10,
edgecolors='none' # 移除节点边框
)
plt.title('Feature Selection by Boruta and Lasso')
plt.show()
这里通过网络图的形式直观展示Boruta和Lasso在特征选择上的差异和重叠情况,有助于理解这两种方法各自偏好选择的特征以及它们的共同选择,当着重考虑其交集。