机器学习常见概念理解
原理、公式、图像、程序
一、损失函数
1、损失函数的源起由来
机器学习本质上是在数据中找规律,这个规律就可以看作理想的模型,而损失函数就是数据评价我们现有模型的好坏的量化指标,这些数据来使用模型后,对我们现有模型评价的量化指标。
2、常见的损失函数
熵是用来衡量混乱程度的,熵越大,越混乱。
相对熵 = 交叉熵-信息熵;
3、学习准则
(1)经验风险最小化
经验风险指模型在训练集上平均损失,经验风险最小化指选择一个在训练集上平均损失最小的模型来拟合数据,作为我们训练完成的模型;
这种学习准则的缺点就是如果训练数据不足或者模型复杂度较高,容易导致过拟合。
(2)结构风险最小化
为了防止经验风险最小化导致的模型过拟合问题,通过加入正则化项,提高模型的泛化能力,这种学习准则称为结构风险最小化
Remp(f) 是经验风险,Ω(f)是对模型复杂度的正则化项,λ 是控制正则化强度的超参数。
(3)两者的核心区别在于,结构风险最小化额外考虑了对模型的复杂度的约束,以避免经验风险最小化可能带来的过拟合问题。
二、梯度下降
是机器学习中参数的优化算法,梯度就是损失函数对于参数的导数,要找到损失函数的最小值,就要把参数往梯度的反方向进行调整,直到梯度为0或者参数不发生变化,同时通过设置学习率来控制参数调整的快慢;
公式:
w
t
+
1
=
w
t
−
α
∇
J
其中
∇
J
是梯度,
α
是学习率
\mathbf{w}_{t+1}=\mathbf{w}_t-\alpha\nabla J\\ 其中\nabla J是梯度,\alpha是学习率
wt+1=wt−α∇J其中∇J是梯度,α是学习率
学习率设置得太大,可能不收敛,设置得太小,收敛得很慢;
1、常见的梯度下降策略
批量梯度下降(BGD):全部训练样本都参与计算梯度,梯度方向准确,但是计算量很大;
随机梯度下降(SGD):随机选一个样本来计算梯度,梯度方向不稳定,计算量很小,对于非凸函数,在一定程度上能够避免陷入局部最优;
小批量梯度下降(MBSD):结合了批量梯度下降和随机梯度下降的优点,优化方法较稳定,计算量相对也较小,不过批量的大小需要自己设置,现在一般都使用小批量梯度下降;
PS:凸优化中,梯度为零的地方就是唯一的全局最优解,没有其他局部最优。
非凸优化中,可能存在多个梯度为零的点,其中一些是局部最优,另一些可能是鞍点。非凸优化问题更加复杂,因为需要区分不同的梯度为零的点并找到全局最优解。
三、决策边界
决策边界是在特征空间中将不同类别区分开来的分界线或超平面。对于二分类问题,它将特征空间分为两部分,每一部分对应一个分类标签。
对于多分类问题,决策边界可能是多个分割线或超平面,将特征空间分割成多个区域。
四、过拟合和欠拟合
1、过拟合
模型过于复杂,过于贴合训练集,很多弯弯绕绕,具体表现就是在训练集上表现很好,但是测试集上表现很差;
解决办法是降低模型的复杂度,加入正则化的操作;
2、欠拟合
模型过于简单或者训练不到位,导致数据和训练集拟合表现很差,具体表现是在训练集和测试集上表现都很差;
解决办法是增加模型复杂度,更换模型,增加训练次数;
五、学习曲线(Learning Curve)
不能绘制出高维度的拟合曲线,这时候就需要通过学习曲线来评估模型的效果
代码:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(233)
X = np.random.uniform(-4, 2, size = (100))
y = X ** 2 + 4 * X + 3 + 2 * np.random.randn(100)
plt.scatter(X,y)
# 用不同的多项式去拟合试试
plt.rcParams["figure.figsize"] = (12, 8)
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
degrees = [1,3,5,7,8,10]
for i,degree in enumerate(degrees):
poly = PolynomialFeatures(degree=degree)
X_new = poly.fit_transform(X.reshape(-1,1))
lreg = LinearRegression()
lreg = lreg.fit(X_new,y.reshape(-1,1))
print(lreg.score(X_new,y))
y_predict = lreg.predict(X_new)
plt.subplot(2,3,i+1)
plt.title('degree:{0}'.format(degree))
plt.scatter(X,y)
plt.plot(np.sort(X),y_predict[np.argsort(X)],color='red')
# 绘制学习曲线
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
X_train,X_test,y_train,y_test = train_test_split(X.reshape(-1,1),y.reshape(-1,1),train_size=0.7,shuffle=True)
for i,degree in enumerate(degrees):
train_error = []
test_error = []
poly = PolynomialFeatures(degree=degree)
X_train_new = poly.fit_transform(X_train)
X_test_new = poly.transform(X_test)
for k in range(len(X_train)):
lreg = LinearRegression()
lreg = lreg.fit(X_train_new[:k+1],y_train[:k+1])
train_error.append(mean_squared_error(y_train[:k+1],lreg.predict(X_train_new[:k+1])))
test_error.append(mean_squared_error(y_test,lreg.predict(X_test_new)))
# y_predict = lreg.predict(X_test_new)
plt.subplot(2,3,i+1)
plt.title('degree:{0}'.format(degree))
# plt.ylim(-5, 50)
plt.plot([k+1 for k in range(len(X_train))],train_error,color='blue')
plt.plot([k+1 for k in range(len(X_train))],test_error,color='red')
六、交叉验证
交叉验证常用于模型选择和超参数调优。例如,使用K折交叉验证评估不同模型的性能,并选择平均性能最好的模型;或在网格搜索中结合交叉验证,选择最佳的超参数组合。
训练集用于训练调整模型参数;
验证集用于调整模型超参数;
测试集用于验证最终模型的效果;
代码:
# 利用iris数据集进行交叉验证实验
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
iris = load_iris()
data = iris.data
target = iris.target
X = data
y=target
X_train,X_test,y_train,y_test = train_test_split(X,y,train_size=0.7,shuffle=True)
X_train.shape,X_test.shape
# 网格搜索最佳参数,在不使用交叉验证的情况下
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
params = {
'n_neighbors':[n for n in range(1,20)],
'weights':['uniform','distance'],
'p':[n for n in range(1,7)]
}
knn = KNeighborsClassifier()
grid = GridSearchCV(param_grid=params,estimator=knn,cv=2)
grid.fit(X_train,y_train)
best_knn = grid.best_estimator_
best_param = grid.best_params_
print('best_param:',best_param)
best_knn.score(X_test,y_test)
七、模型误差
1、误差、偏差、方差、噪声的解释
准:在正确的位置,射中正确的位置,而不关心集中程度,指偏差
确:有明确的位置,是否集中,而不关心位置是否正确,只关心集中程度,指方差
模型误差
=
偏
差
2
+
方差
+
噪声
模型误差=偏差^2+方差+噪声
模型误差=偏差2+方差+噪声
偏差:量化模型预测值和真实值的差距的指标;
方差:评价模型在不同数据集上的预测结果的变化程度
噪声:数据标记的真实值和实际真实值的误差,这个是客观存在的,无论模型怎么训练也无法改变,是模型误差的下界
上图的解释:
b
i
a
s
2
(
x
)
=
[
f
ˉ
(
x
)
−
y
]
2
,
其中
y
表示数据
x
的真实标签,
f
ˉ
(
x
)
表示在不同训练集上学习到的模型(模型类别相同,但是内部参数不同,比如都是一元线性模型,但是内部参数不相同)对
x
预测的期望值
以一元线性回归模型为例,模型的偏差就是数据
x
的真实标签
y
到我们通过不同训练集(从同一分布中采样得到的多份数据集)训练得到的多条直线(模型)对数据
x
预测的平均值的距离;
v
a
r
(
x
)
=
E
D
[
(
f
(
x
:
D
)
−
f
ˉ
(
x
)
)
2
]
,其中
f
(
x
:
D
)
表示在训练集
D
上训练得到的模型对
x
的预测值
,
以一元线性回归为例,模型通过不同的训练集得到不同的参数的一元线性模型,然后拿这些不同参数的一元线性模型对数据
x
进行预测,一元线性模型的方差就是这些预测值之间的方差,表明了一元线性模型在这个任务上的稳定性,和正确度无关
\mathrm{bias}^2(x)=[\bar{f}(x)-y]^2,其中y表示数据x的真实标签,\bar{f}(x)表示在不同训练集上学习到的模型(模型类别相同,但是内部参数不同,比如都是一元线性模型,但是内部参数不相同)对x预测的期望值\\ 以一元线性回归模型为例,模型的偏差就是数据x的真实标签y到我们通过不同训练集(从同一分布中采样得到的多份数据集)训练得到的多条直线(模型)对数据x预测的平均值的距离;\\ \mathrm{var}(x)=\mathrm{E}_D[(f(x:D)-\bar{f}(x))^2],其中f(x:D)表示在训练集D上训练得到的模型对x的预测值,以一元线性回归为例,模型通过不同的训练集得到不同的参数的一元线性模型,然后拿这些不同参数的一元线性模型对数据x进行预测,一元线性模型的方差就是这些预测值之间的方差,表明了一元线性模型在这个任务上的稳定性,和正确度无关\\
bias2(x)=[fˉ(x)−y]2,其中y表示数据x的真实标签,fˉ(x)表示在不同训练集上学习到的模型(模型类别相同,但是内部参数不同,比如都是一元线性模型,但是内部参数不相同)对x预测的期望值以一元线性回归模型为例,模型的偏差就是数据x的真实标签y到我们通过不同训练集(从同一分布中采样得到的多份数据集)训练得到的多条直线(模型)对数据x预测的平均值的距离;var(x)=ED[(f(x:D)−fˉ(x))2],其中f(x:D)表示在训练集D上训练得到的模型对x的预测值,以一元线性回归为例,模型通过不同的训练集得到不同的参数的一元线性模型,然后拿这些不同参数的一元线性模型对数据x进行预测,一元线性模型的方差就是这些预测值之间的方差,表明了一元线性模型在这个任务上的稳定性,和正确度无关
总结:偏差表示模型对于数据的拟合能力;方差表示模型对训练集变化的敏感性;噪声是数据标记的标签和真实值的差距,客观存在无法避免的。
2、原因分析
高偏差:模型欠拟合,模型不合适,高偏差情况下把模型看作一个新手,做事能力很差;比如该模型对一堆数据进行预测,这个预测的值和标签差距很大;
高方差:模型过拟合(把噪声也拟合进去了),模型过于复杂,高方差的情况下把模型当作一个高敏感的人,容易受到影响,情绪不稳定,每次预测的位置变化较大
3、偏差和方差的关系
上图称为偏差—方差窘境,模型的训练程度通常指的是模型对训练数据的拟合程度,随着训练程度的加深,偏差越来越小,方差越来越大,
PS:这里面的训练程度,可以指相同类别模型的训练轮数越来越多,也可以指模型复杂程度越来越高(模型类别已经不同),比如由一元线性模型变为多元线性模型。
原因:训练程度低的时候,模型就像一个新手(欠拟合),做事能力差,偏差大,但是差得很稳定(方差小);训练程度高的时候,模型就像一个练功走火入魔的人(过拟合),做事能力强,偏差小,但是敏感,情绪不稳定,稍微有点影响,波动就很大(方差大)
4、如何降低偏差和方差
降低偏差:找到更好的特征、增加特征维度、增加模型复杂度
降低方差:减少特征维度、降低模型复杂度、增加数据样本数、验证集、使用正则化
降低偏差和降低方差是相悖的,我们需要在二者之间找到平衡;
八、正则化
1、L1正则化
通过使得某些参数为零来减少模型复杂度,防止过拟合,比如Lasso回归就采用了L1正则
2、L2正则化
通过均匀减小模型参数来降低模型复杂度,防止过拟合,Ridge就采用了L2正则
3、总结
正则化为什么有效?
答:正则化减小了或者去除了模型中某些特征对于预测值的影响,这些特征往往是我们不需要的特征,甚至是和预测值不相关的特征,比如噪声特征,降低了这种特征对预测值的影响后,模型的泛化能力就能得到进一步的提高;
正则化是可以减小参数大小,可是为什么减少了参数大小就可以降低模型复杂度呢?
答:参数大小减小后,导致在特征空间中,参数小的特征影响力降低,使得拟合曲线变得更为平滑,平滑后,模型看起来就不那么复杂了,实际参数数量可能并没有改变。
代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 加载波士顿房屋数据集
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
data = data[:,12]
target = raw_df.values[1::2, 2]
data.shape,target.shape
plt.scatter(data,target)
# 不使用正则化
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures,StandardScaler
from sklearn.linear_model import LinearRegression
X_train,X_test,y_train,y_test = train_test_split(data.reshape(-1,1),target.reshape(-1,1),train_size=0.7)
# 定义一个多项式回归工作流
def PolynomicalRegression(degree):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('scale',StandardScaler()),
('reg',LinearRegression())
])
# 问题,pipeline如何对测试集进行标准化的?
from sklearn.metrics import mean_squared_error
preg = PolynomicalRegression(20)
preg.fit(X_train,y_train)
y_predict = preg.predict(X_test)
mean_squared_error(y_test,y_predict)
结果:3445.526018546065
# 画图看一下
plt.scatter(X_train.reshape(-1),y_train.reshape(-1))
a = np.linspace(0,50,100)
plt.plot(a,preg.predict(a.reshape(-1,1)),color='red')
plt.axis([0, 40, -10, 80])
# 加入L1正则化试试
from sklearn.linear_model import Lasso
def LassoRegression(degree,alpha):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('scale',StandardScaler()),
('lasso',Lasso(alpha=alpha))
])
lassoreg = LassoRegression(20,1)
lassoreg.fit(X_train,y_train)
y_pre_lasso = lassoreg.predict(X_test)
mean_squared_error(y_test,y_pre_lasso)
结果:60.03404539743367
# 画图看一下
# 画图看一下
plt.scatter(X_train.reshape(-1),y_train.reshape(-1))
a = np.linspace(0,50,100)
plt.plot(a,lassoreg.predict(a.reshape(-1,1)),color='red')
plt.axis([0, 40, -10, 80])
# 加入L2正则化试试
from sklearn.linear_model import Ridge
def RidgeRegression(degree,alpha):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('scale',StandardScaler()),
('ridge',Ridge(alpha=alpha))
])
ridgereg = RidgeRegression(20,1)
ridgereg.fit(X_train,y_train)
y_pre_ridge = ridgereg.predict(X_test)
mean_squared_error(y_test,y_pre_ridge)
结果:43.36716722812661
# 画图看一下
# 画图看一下
plt.scatter(X_train.reshape(-1),y_train.reshape(-1))
a = np.linspace(0,50,100)
plt.plot(a,ridgereg.predict(a.reshape(-1,1)),color='red')
plt.axis([0, 40, -10, 80])
九、模型泛化
模型泛化能力指模型在不同数据上的表现,泛化能力强在多种数据上都具有好的效果,泛化能力弱则只在一些数据上表现好
十、评价指标
1、Precise、Recall、F1_score
混淆矩阵(Confusion Matrix)
TP:模型检测出来是正例且实际也是正例的数量;
TN:模型检测出来是负例且实际也是负例的数量;
FP:模型检测出来是正例但实际不是正例的数量;
FN:模型检测出来是负例但实际不是负例的数量
P
r
e
c
i
s
e
代表误检,阈值越大,误检的正例就越少,
p
r
e
c
i
s
e
就越大,
P
r
e
c
i
s
e
=
T
P
T
P
+
F
P
R
e
c
a
l
l
代表漏检,阈值越大,漏检的正例就越多,
r
e
c
a
l
l
就越小,
R
e
c
a
l
l
=
T
P
T
P
+
F
N
A
c
c
u
r
a
c
y
表示整体准确率,
A
c
c
u
r
a
c
y
=
T
P
+
T
N
T
P
+
T
N
+
F
P
+
F
N
F1_score 是综合体现 Precision 和 Recall 的指标,
1
F1_score
=
1
2
⋅
(
1
Precision
+
1
Recall
)
F1_score
=
2
⋅
Precision
⋅
Recall
Precision
+
Recall
Precise代表误检,阈值越大,误检的正例就越少,precise就越大,Precise=\frac{TP}{TP+FP}\\ Recall代表漏检,阈值越大,漏检的正例就越多,recall就越小,Recall=\frac{TP}{TP+FN}\\ Accuracy表示整体准确率,Accuracy=\frac{TP+TN}{TP+TN+FP+FN}\\ \text{F1\_score} \text{ 是综合体现 Precision 和 Recall 的指标,} \frac{1}{\text{F1\_score}} = \frac{1}{2} \cdot \left( \frac{1}\\{\text{Precision}} + \frac{1}{\text{Recall}} \right) \\ \text{F1\_score} = \frac{2 \cdot \text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}
Precise代表误检,阈值越大,误检的正例就越少,precise就越大,Precise=TP+FPTPRecall代表漏检,阈值越大,漏检的正例就越多,recall就越小,Recall=TP+FNTPAccuracy表示整体准确率,Accuracy=TP+TN+FP+FNTP+TNF1_score 是综合体现 Precision 和 Recall 的指标,F1_score1=21⋅(1Precision+Recall1)F1_score=Precision+Recall2⋅Precision⋅Recall
代码:
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
iris = load_iris()
data = iris.data
target = iris.target
X = data
y = target
X_train,X_test,y_train,y_test = train_test_split(X,y,train_size=0.7)
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsOneClassifier
lgreg = LogisticRegression()
ovo = OneVsOneClassifier(lgreg)
ovo.fit(X_train,y_train)
y_predict = ovo.predict(X_test)
y_predict
# 混淆矩阵,precise recall
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,y_predict)
这里,横坐标代表预测值,纵坐标代表真实值
from sklearn.metrics import precision_score
precision_score(y_test,y_predict,average=None)
array([1. , 0.86666667, 1. ])
from sklearn.metrics import recall_score
recall_score(y_test,y_predict,average=None)
array([1. , 1. , 0.86666667])
from sklearn.metrics import f1_score
f1_score(y_test,y_predict,average=None)
array([1. , 0.92857143, 0.92857143])
2、PR曲线和ROC曲线
1、PR曲线:Precise-Recall,分类模型预测每个样本的置信度,然后根据置信度进行排序,然后依次选择排序后的每一个样本作为阈值,该样本之前的就判为负例,该样本之后的就判为正例,然后依次计算每个阈值下的Precise-Recall,得到PR曲线
PS:PR曲线可以很好的反应模型在正类(可以把自己关注的类别看为正类,去计算该类的precise和recall来绘制PR curve)上表现,因此可以反应模型在不均衡数据上的表现;
同时可以根据PR曲线来找到自己想要的分类阈值
代码:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
iris = load_iris()
data = iris.data
target = iris.target
X = data
y = target
y[y!=0]=1
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,train_size=0.7)
from sklearn.linear_model import LogisticRegression
lgg = LogisticRegression()
lgg.fit(X_train,y_train)
y_proba= lgg.predict_proba(X_test)[:,1] # 预测为正类1的概率
from sklearn.metrics import precision_recall_curve
precise,recall,threshold = precision_recall_curve(y_test,y_proba)
threshold.shape
plt.plot(threshold,precise[:-1],color='red',label="precise")
plt.plot(threshold,recall[:-1],color='blue',label="recall")
plt.legend()
plt.plot(precise,recall)
plt.ylabel('recall')
plt.xlabel('precise')
从中可以看出precise和recall随阈值的变化趋势是相反的;
2、ROC曲线:Reciver Operation Characteristic Curve
TPR:表示正例的漏检,和Recall相同,阈值越大,漏检的正例就越多,TPR就越小;
FPR:表示全部实际负例中,被误判为正例(检测出来是正例实际不是正例)的比例,阈值越大,被误判为正例的就越少,FPR就越小;
AUC:Area Under Curve,表示ROC曲线和坐标轴围成的面积
代码:
# roc曲线和auc
from sklearn.metrics import roc_curve
roc_curve?
fpr,tpr,threshold = roc_curve(y_test,y_proba)
plt.plot(threshold,fpr,label='fpr',color='red')
plt.plot(threshold,tpr,label='tpr',color='blue')
plt.legend()
plt.show()
plt.plot(fpr,tpr,color='red')
plt.xlabel('fpr')
plt.ylabel('tpr')
from sklearn.metrics import roc_auc_score
roc_auc_score(y_test,y_proba)