笔记参考自B站的两个教程,都讲得特别好。
线性回归是一种要求很严格的回归预测模型,如标签必须服从正态分布,特征之间的多重共线性需要消除等。
逻辑回归是一种概率判别模型,是在线性回归的基础上增加了一个激活函数,使得预测结果分布在0-1之间,可以看作回归结果概率。对应的激活函数是sigmoid函数 : σ ( z ) = 1 / ( 1 + e − z ) \sigma(z)=1/(1+e^{-z}) σ(z)=1/(1+e−z)
对于给定样本 X = x 1 , x 2 , . . . , x N X={x_1, x_2,...,x_N} X=x1,x2,...,xN,以及对应标签 Y = y 1 , y 2 , . . . y N Y={y_1,y_2,...y_N} Y=y1,y2,...yN有:
p 1 = P ( y = 1 ∣ x ) = σ ( W T x ) = 1 / ( 1 + e − W T x ) , y = 1 p_1=P(y=1|x)=\sigma(W^{T}x)=1/(1+e^{-W^{T}x}), y=1 p1=P(y=1∣x)=σ(WTx)=1/(1+e−WTx),y=1
p 0 = P ( y = 0 ∣ x ) = 1 − P ( y = 1 ∣ x ) = 1 − σ ( W T x ) = 1 − 1 / ( 1 + e − W T x ) = e − W T x / ( 1 + e − W T x ) , y = 0 p_0=P(y=0|x)=1-P(y=1|x)=1-\sigma(W^{T}x)=1-1/(1+e^{-W^{T}x})=e^{-W^{T}x}/(1+e^{-W^{T}x}), y=0 p0=P(y=0∣x)=1−P(y=1∣x)=1−σ(WTx)=1−1/(1+e−WTx)=e−WTx/(1+e−WTx),y=0
其中W是模型参数,得:
P ( y ∣ x ) = p 1 y p 0 1 − y P(y|x)=p_1^{y}p_0^{1-y} P(y∣x)=p1yp01−y
根据最大似然估计:
w ′ = a r g m a x w l o g P ( Y ∣ x ) w'=\mathop {argmax} \limits_wlogP(Y|x) w′=wargmaxlogP(Y∣x)
= a r g m a x w Σ l o g P ( y i ∣ x i ) =\mathop {argmax} \limits_w\Sigma logP(y_i|x_i) =wargmaxΣlogP(yi∣xi)
两边取对数求解:
l o g w ′ = a r g m a x w Σ ( y i l o g p 1 + ( 1 − y i ) l o g p 0 ) logw'=\mathop {argmax} \limits_w\Sigma (y_ilogp_1+(1-y_i)logp_0) logw′=wargmaxΣ(yilogp1+(1−yi)logp0)
把p1和p0表达式带进去不难发现与这个结果对应的就是交叉熵损失函数的负数:
L o s s c r o s s _ e n t r o p y = a r g m i n ω − ∑ ( l o g y i p 1 + l o g ( 1 − y i ) p 0 ) Loss_{cross\_entropy}=\mathop {argmin} \limits_{\omega}-\sum (log{y_i}p_1+log(1-y_i)p_0) Losscross_entropy=ωargmin−∑(logyip1+log(1−yi)p0)
逻辑回归在具有线性关系的数据中进行预测效果很好且拟合速度快,一般在小数据集上表现更好,但是对于非线性的就不太行。
如果没有sklearn用pip或者conda安装就好。
防止模型过拟合的方法:
正则化:在损失函数后加入,L1(参数W绝对值之和)或者L2(参数W平方和开根号的值)正则化。
L1正则化会把参数压缩到0,L2则只会将参数压缩到很小的值。因此L1正则化会导致参数变稀疏,因此特征量比较大,参数很多的情况下,选择L1正则化,可以进行特征选择。
举个例子:
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
data = load_breast_cancer
X = data.data
y = data.target
lrl1 = LR(penalty="l1", solver="liblinear", max_iter=1000)
lrl2 = LR(penalty="l2", solver="liblinear", max_iter=1000)
lrl1 = lrl1.fit(X, y)
# 查看参数,即不同特征的系数
print(lrl1.coef_)
# 查看系数不为0的参数个数
print((lrl1.coef_ != 0).sum(axis=1))
lrl2 = lrl2.fit(X, y)
# 查看参数,即不同特征的系数
print(lrl2.coef_)
# 查看系数不为0的参数个数
print((lrl2.coef_ != 0).sum(axis=1))
可以很直观地看出L1和L2正则化的区别。
进一步划分训练集和测试集进行训练,调整正则化系数,目的是调优参数C,并画图查看结果:
l1 = []
l2 = []
l1test = []
l2test = []
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
for i in np.linespace(0.05, 1, 19):
lrl1 = LR(penalty="l1", solver="liblinear", C=i, max_iter=1000)
lrl2 = LR(penalty="l2", solver="liblinear", C=i, max_iter=1000)
lrl1 = lrl1.fit(Xtrain, Ytrain)
l1.append(accuracy_score(lrl1.predict(Xtrain), Ytrain))
l1test.append(accuracy_score(lrl1.predict(Xtest), Ytest))
lrl2 = lrl2.fit(Xtrain, Ytrain)
l2.append(accuracy_score(lrl2.predict(Xtrain), Ytrain))
l2test.append(accuracy_score(lrl2.predict(Xtest), Ytest))
graph = [l1, l2, l1test, l2test]
color = ["green", "black", "lightgreen", "gray"]
label = ["L1", "L2", "L1test", "L2test"]
plt.figutr(figsize=(6, 6))
for i in range(len(graph):
plt.plot(np.linespace(0.05, 1, 19), graph[i], color[i], label=label[i])
plt.legend(loc=4) # 4表示右下角
plt.show()
高效的嵌入法embedded:
可以直接采用范数计算进行特征选择:
from sklearn.feature_selection import SelectFromModel
data = load_breast_cancer
print(data.data.shape)
LR_ = LR(solver="liblinear", C=0.9, random_state=420)
cross_val_score(LR_, data.data, data.target, cv=10).mean()
# 内置特征工程,采用norm_order,L1范式来筛选特征
X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)
print(X_embedded.shape)
或者通过threshold参数来进行特征选择,表示重要性小于threshold的特征会被筛选掉,参数调优:
fullx = []
fsx = []
threshold = np.linespace(0, (LR_.fit(data.data, data.target).coef_).max(), 20)
k=0
for i in threshold:
X_embedded = SelectFromModel(LR_, threshold=i, norm_order=1).fit_transform(data.data, data.target)
fullx.append(cross_val_score(LR_, data.data, data.target, cv=5).mean())
fsx.append(cross_val_score(LR_, X_embedded, data.target, cv=5).mean())
print((threshold[k], X_embedded.shape[1]))
k+=1
plt.figure(figsize=(20,5))
plt.plot(threshold, fullx, label="full")
plt.plot(threshold, fsx, label="feature selection")
plt.xticks(threshold)
plt.legend()
plt.show()
但是这样到后面特征会被删除很多,或者在可接受范围内的特征数依然很大,因此对threshold进行调参意义不大。
因此另一种调参方式使用L1范数进行特征选择,然后再调整LR模型中的C的学习曲线:
fullx = []
fsx = []
C = np.arange(0.01, 10.01, 0.5)
# 根据记录的值进一步缩小调参范围,下一次调参范围:
# C = np.arange(6.05, 7.05, 0.005)
for i in C:
LR_ = LR(solver="liblinear",C=i,random_state=420)
fullx.appoend(cross_val_score(LR_, data.data, data.target, cv=10).mean())
X_embedded = SelectFromModel(LR_, norm_order=1).fit_transform(data.data, data.target)
fsx.append(cross_val_score(LR_,X_embedded, data.targetm, cv=10).mean())
# 打印记录使得精确率较高的参数值
print(max(fsx), C[fsx.index(max(fsx))])
plt.figure(figsize=(20,5))
plt.plot(C, fullx, label="full")
plt.plot(C, fsx, label="feature selection")
plt.xticks(C)
plt.legend()
plt.show()
梯度:在多元函数对各个自变量求偏导,然后以向量的形式写出就是梯度,如损失函数 J ( θ 1 , θ 2 ) J(\theta_1,\theta_2) J(θ1,θ2)对两个参数求偏导的梯度向量 d d d就是 [ ∂ J ∂ θ 1 , ∂ J ∂ θ 2 ] T [\frac {\partial J}{\partial \theta_1},\frac {\partial J}{\partial \theta_2}]^{T} [∂θ1∂J,∂θ2∂J]T,简称 g r a d J ( θ 1 , θ 2 ) gradJ(\theta_1,\theta_2) gradJ(θ1,θ2)或者 ▽ J ( θ 1 , θ 2 ) \triangledown J(\theta_1,\theta_2) ▽J(θ1,θ2)。
梯度下降:沿着梯度下降的反方向能够更快更容易地找到使得损失函数最小的参数值,则参数的计算过程为: θ j + 1 = θ j − α ∗ d j \theta_{j+1}=\theta_j-\alpha*d_j θj+1=θj−α∗dj。
步长 α \alpha α:也称为学习率,用于控制参数变化的速度。在sklearn中可以设置参数max_iter最大迭代次数来代替步长。max_iter越大表示步长越小,模型迭代时间越长,反之,则代表步长设置很大,模型迭代时间短。
max_iter调参实例:
l2 = []
l2test = []
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
for i in np.arange(1, 201, 10):
lrl2 = LR(penalty="l2",solver="liblinear",C=0.8,max_iter=i)
lrl2 = lrl2.fit(Xtrain, Ytrain)
l2.append(accuracy_score(lrl2.predict(Xtrain),Ytrain))
l2test.append(accuracy_score(lrl2.predict(Xtest),Ytest))
graph = [l2, l2test]
color = ["black", "gray"]
label = ["L2", "L2test"]
plt.figure(figsize=(20,5))
for i in range(len(graph)):
plt.plot(np.arange(1, 201, 10),graph[i],color[i],label=label[i])
plt.legend(loc=4)
plt.xticks(np.arange(1, 201, 10))
plt.show()
# 使用属性.n_iter_来调用本次求解中真正实现的迭代次数
lr = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=300).fit(Xtrain,Ytrain)
print(lr.n_iter_) # 25次
若参数没有收敛会有warnings,显示最大迭代次数不够,但是不影响结果。
multi_class:若设置为"ovr"则表示二分类,或让模型使用一对多的形式来处理多分类问题;“multinomial"表示多分类,这种输入参数solver的"liblinear"不可用;“auto"表示会根据数据分类情况和其他参数来确定,即solver为"liblinear”(坐标下降法)时会选择"ovr”,否则选择"multinomial"。注意0.22版本中默认值从"ovr"改为了"auto"。
求解器详解:
比较两种不同求解器的差异:
from sklearn.datasets import load_iris
iris = load_iris()
for multi_class in ("multinomial","ovr"):
clf = LR(solver="sag", max_iter=100, random_state=42, multi_class=multi_class).fit(iris.data, iris.target)
print("training score: %.3f (%s)" %(clf.score(iris.data, iris.target)), multi_class) # 0.987(multinomial)/0.960(ovr)
在不同的场景下会有不一样的目的,也就是有的场景注重精确率,有的场景注重召回率。(如罪犯和普通人的分类,更倾向于将普通人错分成罪犯,然后人工筛选,而不愿将罪犯错分为普通人)。也就是要最小化误分类的代价。
class_weight:该参数就是对样本进行均衡,采用不同的方法扩充样本,从而达到侧重于某种结果的目的。
评分卡是银行借贷过程中的评对用户的评级。
均值、众数、中位数,随机森林填充,删除:
import numpy as np
import pandas as pd
from sklearn.linear_mdoel import LogisticRegression as LR
data = pd.read_csv(r"文件路径", index_col=0)
# 观察数据类型
print(data.head())
# 观察数据结构
print(data.shape())
print(data.info())
# 去除重复值
data.drop_duplicates(inplace=True)
print(data.info())
# 删除后恢复索引
data.index = range(data.shape[0])
print(data.info())
print(data.info())
# 探索每一列缺失值的个数
print(data.isnull().sum()/data.shape[0])
# data.isnull().mean()
# 使用均值来填补比较重要的缺失值
data["特征名称"].fillna(data["特征名称"].mean(), inplace=True)
# 若是删除了某些特征,需要进行索引的恢复
print(data.info())
print(data.isnull().sum()/data.shape[0])
# 采用随机森林来填补无法用普通方式来填补的缺失值
def fill_missing_rf(X, y, to_fill):
‘’‘
使用随机森林填补一个特征的缺失值的函数
参数:
X;要填补的特征矩阵
y:完整的,没有缺失值的标签
to_fill:字符串,要填补的那一列的名称
’‘’
# 构建新的特征矩阵和新标签
df = X.copy()
fill = df.loc[:,to_fill]
df = pd.concat([df.loc[:,df.columns != to_fill], pd.DataFrame(y)],axis=1)
# 找出训练集和测试集
Xtrain = fill[fill.notnull()]
Ytest = fill[fill.isnull()]
Xtrain = df.iloc[Ytrain.index,:]
Xtest = df.iloc[Ytest.index,:]
# 用随机森林来填补缺失值
from sklearn.ensemble import RandomForestRegressor as rfr
rfr = rfr(n_estimators=100)
rfr = rfr.fit(Xtrain, Ytrain)
Ypredict = rfr.predict(Xtest)
return Ypredict
X = data.iloc[:, 1:]
y = data["要填充的特征名"]
y_pred = fill_missing_rf(X, y, "要填充的特征名")
# 确认结果合理后就可以覆盖随机森林填充的预测值
print(y_pred)
data.loc[data.iloc[:,"要填充的属性名"].isnull(), "要填充的属性名"] = y_pred
箱线图或3 σ \sigma σ法则:
# 描述性统计:包括各个特征的均值、最小值等信息(参数列表设置了返回对应占比位置的结果)
print(data.describe([0.01,0.1,0.25,0.5,0.75,0.9,0.99]).T)
# 观察到异常值在age列
print((data["age"] == 0).sum()) # 1
# 删除该异常样本
data = data[data["age"] != 0]
print(data.shape)
# 观察到不合理的样本
print(data[data.loc[:, "NumberOfTime90DaysLate"] > 90]).count())
print((data.loc[:, "NumberOfTime90DaysLate"]).value_counts())
data = data[data.loc[:, "NumberOfTime90DaysLate"] < 90]
# 恢复索引
data.index = range(data.shape[0])
print(data.info())
由于业务要求,不统一量纲是为了保持数据原貌。
用imblearn库解决样本不均衡:
X = data.iloc[:,1:]
y = data.iloc[:,0]
# 查看同一特征的不同值样本的分布情况
print(y.value_counts())
import imblearn
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42)
X,y = sm.fit_sample(X,y)
n_sample = X.shape[0]
n_1_sample = y.value_counts()[1]
n_0_smaple = y.value_counts()[0]
print("样本个数:{}; 1占{:.2%}; 0占{:.2%}".format(n_sample,n_1_sample/n_sample,n_0_sample/n_sample))
from sklearn.model_selection import train_test_split
X = pd.Dataframe(X)
y = pd.DataFrame(y)
X_train, X_vali, Y_train, Y_vali = train_test_split(X, y, test_size=0.3)
model_data = pd.concat([Y_train, X_train], axis=1)
model_data.index = range(model_data.shape[0])
model_data.colums = data.columns
vali_data = pd.concat([Y_vali, X_vali], axis=1)
vali_data.index = range(vali_data.shape[0])
vali_data.colums = data.columns
model_data.to_csv(r"存储路径")
vali_data.to_csv(r"存储路径")
分箱也就是对特征进行分档。Information Value(IV):
I V = ∑ i = 0 N ( g o o d % − b a d % ) ∗ W O E i IV=\sum_{i=0}^N (good\%-bad\%)*WOE_i IV=∑i=0N(good%−bad%)∗WOEi
W O E i = l n ( g o o d % b a d % ) WOE_i=ln(\frac{good\%}{bad\%}) WOEi=ln(bad%good%)
其中N是这个特征上箱子的个数,i代表每个箱子,good%是优质客户占整个特征中所有优质客户的比例。 W O E i WOE_i WOEi表示证据权重。
因此可以依赖于IV值进行特征降维。
# 按照等频对需要分箱的列进行分箱
# 返回元素的分箱结果,以及每个箱子的上限和下限
model_data["qcut"], updown = pd.qcut(mode_data["age"],rebins=True,q=20)
# 统计每个分箱0,1的数量
# 使用数据透视表的功能groupby
coount_y0 = model_data[model_data["SeriousDlqin2yrs"] == 0].groupby(by="qcut").count()["SeriousDlqin2yrs"]
coount_y1 = model_data[model_data["SeriousDlqin2yrs"] == 1].groupby(by="qcut").count()["SeriousDlqin2yrs"]
# num_bins为每个区间的上下界,0出现的次数,1出现的次数
num_bins = [*zip(updown,updown[1:], coount_y0, coount_y1)]
def get_woe(num_bins):
df["total"] = df.count_0 + df.count_1
df["percentage"] = df.total / df.total_sum()
df["bad_rate"] = df.count_1+ df.total
df["good%"] = df.count_0 + df.count_0.sum()
df["bad%"] = df.count_1 + df.count_0.sum()
df["woe"] = np.log(df["good%"] / df["bad%"])
return df
def get_iv(bins_df):
rate = bins_df["good%"] - bins_df["bad%"]
iv = np.sum(rate * bins_df.woe)
return iv
import matplotlib.pyplot as plt
import scipy
IV = []
axisx = []
def get_bin(num_bins_, n):
while len(num_bins_) > 2:
psv = []
for i in range(len(num_bins_)-1):
x1 = num_bins_[i][2:]
x1 = num_bins_[i+1][2:]
# 0返回chi2值,1返回p值
pv = scipy.stats.chi2_contingency([x1,x2])[1]
chi2 = scipy.stats.chi2_contingency([x1,x2])[1]
pvs.append(pv)
i = pvs.index(max(pvs))
num_bins_[i:i+2] = [
num_bins_[i][0],
num_bins_[i+1][1],
num_bins_[i][2]+num_bins_[i+1][2],
num_bins_[i][3]num_bins_[i+1][3]]
return num_bins_
num_bins_ = get_bin(num_bins, 4)
bins_df = get_woe(num_bins_)
axisx.append(len(num_)bins_))
IV.append(get_iv(bins_df))
plt.figure()
plt.p[lot(axisx, IV)
plt.xticks(axisx)
plt.show()
接下来的内容太多了,等要用到的时候再看:TO DO…