一、名字的由来
逻辑回归(Logistic Regression)常用作二分类任务,并给出相应概率。
1、为什么叫做“逻辑”
"逻辑"这个词在这里与日常的逻辑思维无关,而是特指它所使用的逻辑函数(Logistic Function),也就是我们常说的Sigmoid函数。其形式为:
它能将任何实数映射到(0,1)区间,完美地表示概率。这个术语最早可以追溯到19世纪,用来描述某些对数增长曲线,后来被统计学家借用来命名这个特殊的函数。
2、为什么叫"回归"而不是"分类"
明明是一个处理分类任务的模型,为什么逻辑回归为什么不叫做逻辑分类。 从模型结构来看,逻辑回归其实是线性回归的自然延伸。逻辑回归首先计算特征的线性组合
其中x是自变量(特征向量),w是权重向量,b是偏置项,y是连续的因变量。
这和线性回归完全一样。不同之处在于,逻辑回归没有直接输出这个线性结果,而是通过Sigmoid函数将其转换为概率值。换句话说,它实际上是在用回归的方法来估计概率,而分类只是在这个概率估计基础上加了一个阈值判断(比如概率>0.5就判为正类)。
二、算法推导
1.二分类推导
逻辑回归是一个概率分类模型,其核心是通过线性组合 + Sigmoid 函数建模样本属于正类(y=1)的概率:
其中:
-
w 是权重向量,b 是偏置项。
-
σ(z) 是 Sigmoid 函数,其表达式为:
在二分类问题中,假设只有两个标签 1 和 0。我们把采集到的每一个样本看做一个事件的话,那么这个事件发生(即标签为 1)的概率假设为 p。因为标签不是 1 就是 0,因此标签为 0 的概率就是:。
对于单个样本,其发生的概率可以表示为:。
为了确定模型的参数w和b,我们使用极大似然估计。假设样本相互独立,似然函数为:
其中
为了方便计算,通常对似然函数取对数,得到对数似然函数:
我们的目标是最大化对数似然函数,这等价于最小化负对数似然函数:
使用梯度下降法来求解最小化问题,对J(w,b)分别关于w和b求偏导数:
然后根据梯度更新参数:
其中α是学习率,通过不断迭代更新参数,直到达到收敛条件,从而得到最优的模型参数。
2. 正则化(防止过拟合)
在损失函数中加入 L2正则化(Ridge):
其中,λ 是正则化强度,用于控制正则化的程度;d 为特征维度,表示权重向量 w 各元素平方和 ,正则化项对较大的权重进行惩罚,避免模型过度拟合训练数据。
梯度更新时需额外加上正则化项的导数,根据幂函数求导公式 ∂wj∂wj2=2wj ,对正则化项求偏导可得:
由于正则化项只与权重 w 有关,与偏置项 b 无关,因此偏置项 b 的梯度更新不受正则化影响。
3. 多分类扩展(Softmax回归)
对于 K 类分类问题,使用 Softmax 函数 替代 Sigmoid:
-
Softmax函数:
其中,W=[w1,w2,…,wK] 是权重矩阵,每一列 wk 对应第 k 类的权重向量;b=[b1,b2,…,bK] 是偏置向量,bk 是第 k 类的偏置项。该公式表示样本 x 属于类别 k 的概率,且满足 ∑k=1KP(y=k∣x)=1 ,即所有类别概率之和为 1 ,符合概率分布的基本性质。
-
交叉熵损失:
其中,N 为样本总数;I(yi=k) 为指示函数,当样本 i 的真实标签 yi 等于类别 k 时,I(yi=k) 取值为 1 ,否则为 0。交叉熵损失函数衡量了真实标签分布与模型预测概率分布之间的差异,通过最小化该损失函数,可使模型预测结果更接近真实情况。
三、算法实战
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score,confusion_matrix
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"]=False
%matplotlib inline
1.数据导入,数据质量检查
data = pd.read_excel(r"C:\Users\zyhd_\Desktop\周瑾\电信客户数据.xlsx")
data.info()
# 查看缺失值占比
data.isnull().mean()
# 查看标签分布
data['流失用户'].value_counts()
# 描述性统计
data.describe()
通过上述操作,我们对数据的基本情况有了初步了解,涵盖数据的类型、缺失值情况、标签的分布以及数值型特征的统计描述等。数据中标签分布呈现出明显的不均衡态势,这种情况可能导致模型在训练过程中过度关注多数类样本,从而弱化对少数类样本的学习效果,最终影响模型在实际应用中的泛化能力与预测准确性。可能需要采用过采样、欠采样或调整损失函数等方法来改善模型性能 。
2.数据探索可视化
# 绘制特征分布矩阵
num_cols = ['套餐金额','额外通话时长','额外流量','使用月数']
df[num_cols].hist(bins=20, figsize=(12,8))
plt.tight_layout()
plt.suptitle('数值特征分布', y=1.02)
plt.show()
plt.figure(figsize=(10, 6))
sns.violinplot(x='流失用户', y='套餐金额', data=df)
plt.title('不同流失状态的套餐金额分布(小提琴图)')
plt.xlabel('流失用户(0=未流失,1=流失)')
plt.ylabel('套餐金额')
plt.show()
# 创建通话类型标签
df['通话类型'] = pd.cut(df['额外通话时长'],
bins=[-float('inf'),-0.1,0.1,float('inf')],
labels=['未用完','刚好用完','超额使用'])
# 堆积条形图
pd.crosstab(df['通话类型'], df['流失用户'], normalize='index').plot.bar(stacked=True)
plt.title('不同通话模式用户的流失比例')
plt.ylabel('比例')
plt.show()
cat_cols = ['改变行为','服务合约','关联购买','集团用户']
fig, axes = plt.subplots(2, 2, figsize=(12,8))
for col, ax in zip(cat_cols, axes.flatten()):
sns.barplot(x=col, y='流失用户', data=df, ax=ax)
ax.set_title(f'{col} vs 流失率')
plt.tight_layout()
plt.show()
# 使用月数分段分析
df['使用阶段'] = pd.cut(df['使用月数'],
bins=[0,3,6,12,24,float('inf')],
labels=['新客','成长','稳定','成熟','老客'])
plt.figure(figsize=(8,5))
sns.lineplot(x='使用阶段', y='流失用户', data=df,
estimator='mean', errorbar=None, marker='o')
plt.title('不同生命周期用户的流失率变化')
plt.ylabel('平均流失率')
plt.show()
plt.figure(figsize=(10,6))
sns.scatterplot(x='使用月数', y='套餐金额', hue='流失用户',
data=df, alpha=0.6, palette=['blue','red'])
plt.title('使用时长-消费金额-流失状态三维关系')
plt.show()plt.figure(figsize=(10,6))
sns.scatterplot(x='使用月数', y='套餐金额', hue='流失用户',
data=df, alpha=0.6, palette=['blue','red'])
plt.title('使用时长-消费金额-流失状态三维关系')
plt.show()
import plotly.express as px
fig = px.parallel_coordinates(
df,
color="流失用户",
dimensions=['套餐金额','额外通话时长','使用月数'],
color_continuous_scale=px.colors.diverging.Tealrose
)
fig.show()
# 计算流失/未流失用户的特征均值
agg_df = df.groupby('流失用户')[num_cols].mean().reset_index()
# 标准化数据
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
agg_df[num_cols] = scaler.fit_transform(agg_df[num_cols])
# 绘制雷达图
categories = num_cols
N = len(categories)
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
plt.figure(figsize=(8,8))
ax = plt.subplot(111, polar=True)
for i in range(2):
values = agg_df.loc[i, num_cols].values.flatten().tolist()
values += values[:1]
ax.plot(angles, values, linewidth=1,
label=f'流失={i}', linestyle='solid')
ax.fill(angles, values, alpha=0.1)
plt.xticks(angles[:-1], categories)
plt.title('流失/未流失用户特征对比雷达图', y=1.1)
plt.legend(loc='upper right')
plt.show()
3.特征工程
# 计算相关系数
corr = df[['套餐金额','额外通话时长','额外流量','使用月数','流失用户']].corr()
# 绘制热力图
plt.figure(figsize=(8,6))
sns.heatmap(corr, annot=True, cmap='coolwarm', center=0)
plt.title('特征相关性热力图')
plt.show()
可以发现使用月数和流失用户强线性相关,各个变量间不存在高共线性。
4.数据清洗
# 等宽分箱
bins = [0, 500, 1000, 1500, float('inf')]
labels = ['<500', '500-1000', '1000-1500', '>1500']
df['金额分段'] = pd.cut(df['套餐金额'], bins=bins, labels=labels)
# 简单三分类
df['通话类型'] = pd.cut(df['额外通话时长'],
bins=[-float('inf'), 0, 100, float('inf')],
labels=['未用完', '正常使用', '超额使用'])
# 简单时间分段
df['用户阶段'] = pd.cut(df['使用月数'],
bins=[0, 3, 12, 24, float('inf')],
labels=['新用户', '成长用户', '稳定用户', '老用户'])
df = df.drop(['套餐金额', '额外通话时长', '使用月数'], axis=1)
# 进行独热编码
df = pd.get_dummies(df, columns=['金额分段', '通话类型', '用户阶段'])
# 划分训练集和测试集
X= df.drop("流失用户",axis=1)
y=df["流失用户"]
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=42)
在数据清洗阶段,通过分箱操作将连续型特征转换为离散型特征,有助于提升模型的鲁棒性和可解释性;独热编码则将类别型特征转换为适合模型处理的数值形式。划分训练集和测试集是为了评估模型在未知数据上的泛化能力,确保模型不会过拟合。
5.基线模型的建立
# 创建逻辑回归模型
model =LogisticRegression(max_iter=2000)
# 训练模型
model.fit(X_train,y_train)
# 在测试集上进行预测
y_pred = model.predict(X_test)
print(f"模型准确率: {accuracy_score(y_test, y_pred)}")
建立基线模型是为后续的模型优化提供一个参考标准。通过简单地使用默认参数训练逻辑回归模型,我们可以快速得到一个初步的模型性能指标,了解模型在当前数据和特征下的表现情况。
6.参数调优
逻辑回归可选参数:
- penalty:指定惩罚项,常用的有'l1'和'l2'。'l1'可以产生稀疏解,有助于特征选择;'l2'可以防止模型过拟合 。"elasticnet"弹性网正则化结合了l1和l2的特性,通过l1_ratio参数控制两者比例。
- C:正则化强度的倒数,值越小,正则化强度越大,模型越简单。
- solver:指定优化算法,如'liblinear'适用于小数据集,'lbfgs'适用于大数据集和多分类问题。"sag"适合大规模数据集,对特征缩放敏感,需提前标准化数据。
- class_weight:处理类别不平衡问题,可以选择None(不处理),'balanced'(自动调整权重)或以字典形式如class_weight = {0: 1, 1: 5},人为设定各分类权重。
- random_state:随机数种子,用于确保实验的可重复性。
from sklearn.model_selection import GridSearchCV
param_grid = [
# liblinear 支持 l1 和 l2 惩罚项
{
'penalty': ['l1', 'l2'],
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'solver': ['liblinear'],
'class_weight': [None, 'balanced'],
'random_state': [42]
},
# lbfgs 只支持 l2 惩罚项
{
'penalty': ['l2'],
'C': [0.001, 0.01, 0.1, 1, 10, 100],
'solver': ['lbfgs'],
'class_weight': [None, 'balanced'],
'random_state': [42]
}
]
lr = LogisticRegression(max_iter=2500)
grid_search = GridSearchCV(lr, param_grid, cv=5, scoring='roc_auc')
grid_search.fit(X_train, y_train)
# 输出最佳参数和最佳得分
print("Best parameters:", grid_search.best_params_)
print("Best score:", grid_search.best_score_)
# 使用最佳参数创建最终模型
final_model = grid_search.best_estimator_
7.模型评估
from sklearn.metrics import classification_report
# 分类报告和混淆矩阵
y_pred = final_model.predict(X_test)
y_proba = final_model.predict_proba(X_test)[:, 1]
print("\n分类报告:")
print(classification_report(y_test, y_pred))
# 混淆矩阵
plt.figure(figsize=(10, 6))
sns.heatmap(confusion_matrix(y_test, y_pred),
annot=True, fmt="d", cmap="Blues",
xticklabels=["未流失", "流失"],
yticklabels=["未流失", "流失"])
plt.title("逻辑回归混淆矩阵")
plt.ylabel("真实标签",rotation=90)
plt.xlabel("预测标签")
plt.tight_layout()
plt.show()
分类报告从精确率、召回率、F1 值等多个角度对模型的分类性能进行了详细评估,而混淆矩阵则直观地展示了模型在不同类别上的预测情况。通过这些评估指标,我们可以全面了解模型的性能,发现模型在哪些类别上表现较好,哪些类别上存在不足,从而为进一步优化模型提供方向。
四、小结
通过以上对逻辑回归从原理到实战的完整解析,我们不仅深入理解了逻辑回归的本质,还通过实际案例掌握了如何运用它解决实际问题,包括数据处理、模型构建、参数调优和性能评估等关键环节。