实验使用到的库:numpy、matplotlib、scikit-learn
实验使用的开发环境:anaconda、jupyter
一、线性回归
线性回归就是使用一个线性函数(多项式回归可以是曲线)去拟合给定的训练集,测试时,对输入的x值,返回这个线性函数的y值。最终目标是找到y=Θ0 + Θ1x1 + Θ2x2 + …… + Θnxn 函数式中的Θ0、Θ1、Θ2、……、Θn值
由于上图中我们能看到存在一个Θ0并不是xi的系数,而Θi是从输入的标准化数据中获得的,所以在标准化数据中我们需要单独添加一列常数项1
实验步骤
1.产生数据
# 产生数据
import numpy as np
# 产生[0,2)的随机数据,作为x值
x = 2 * np.random.rand(100,1)
# 给定一个函数获得y值
y = 4 + 3 * x + np.random.randn(100,1)
2.展示产生的数据(这块只是一个数据展示,不影响整体流程)
# 绘制数据散点图图像
import matplotlib.pyplot as plt
plt.plot(x,y,'b.')
plt.xlabel('x1')
plt.ylabel('y')
plt.axis([0,2,0,15])
plt.show()
3.为数据添加常数项1,并求Θ
numpy.c_[矩阵1,矩阵2]可以讲矩阵1和矩阵2拼接成为一个矩阵
numpy.ones((100,1)) 可以生成一个100行1列的纯1矩阵
# 添加偏置项1
x_b= np.c_[np.ones((100,1)),x]
# 使用公式Θ = (xT ⋅ x)-1 ⋅ xT ⋅ y 求Θ
# xT表示矩阵x的转置
# (xT ⋅ x)-1 表示 xT ⋅ x的逆
theta_best = np.linalg.inv(x_b.T.dot(x_b)).dot(x_b.T).dot(y)
4.绘制函数图像
# 选取两个点(函数的两个端点)
x_new = np.array([[0],[2]])# 拼接偏置项1
x_new_b = np.c_[np.ones((2,1)),x_new]# 获得预测值
y_predict = x_new_b.dot(theta_best)# 数据绘制
plt.plot(x_new,y_predict,'r--')
plt.plot(x,y,'b.')
plt.xlabel('x1')
plt.ylabel('y')
plt.axis([0,2,0,15])
plt.show()
二、机器学习下的线性回归
在上面的实验中,我们直接使用公式Θ = (xT ⋅ x)-1 ⋅ xT ⋅ y得到了Θ值,但是在实际中我们的机器需要通过如梯度下降之类的算法,逐步减少误差,获取到Θ的值,这个获得Θ的过程就是学习。
scikit-learn给我们提供了线性回归的工具
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(x,y)
print(lin_reg.coef_)
print(lin_reg.intercept_)
通过这段代码我们最终能得到:
- lin_reg.coef_,学习结束后得到的Θ值,即每个特征值
- lin_reg.intercept_,函数的截距值,即Θ0
三、学习率的影响
我们知道。梯度下降就是沿着误差函数对Θ的偏导的负方向找到最小值Θimin
求导公式为 2/m * (ΘT ⋅ x - y) ⋅ xi
其中:ΘT 表示Θ的转置
注:Θ 和 x 在代码中都是矩阵形式,使用ΘT ⋅ x 就相当于对Θixi求和。
梯度下降实现
# 学习率
alpha = 0.1
# 迭代次数
n_iter = 1000
# 样本个数
count = 100
# 初始化theta位置
theta = np.random.randn(2,1)
for iter in range(n_iter):# 对thetaJ求导 = 2/m * 求和(thetaT * x - y) * xJgradients = 2 / count * x_b.T.dot(x_b.dot(theta) - y)# 更新thetatheta = theta - alpha * gradients
print(theta)
最后theta是一个2*1的矩阵存储着Θ0和x对应的权重Θ
定义学习率影响因素的函数
# 这个代码也是全量梯度下降的代码(全部的Θ值都参与梯度下降计算)
# theta_path_bgd存储每次梯度下降得到的Θ值
theta_path_bgd = []
def plot_gradigent_descent(theta, alpha, theta_path = None):count = len(x_b)plt.plot(x,y,'b.')n_iter = 1000# 进行1000次迭代计算for iter in range(n_iter):y_predict = x_new_b.dot(theta)plt.plot(x_new,y_predict,'r--')# 计算梯度gradients = 2 / count * x_b.T.dot(x_b.dot(theta) - y)# 新的Θ值为 旧Θ值 - 学习率 * 梯度theta = theta - alpha * gradientsif theta_path is not None:theta_path.append(theta)# 绘制图像plt.xlabel('x_1')plt.axis([0,2,0,15])plt.title('alpha={}'.format(alpha))
调用函数绘制图像
theta = np.random.randn(2,1)
plt.figure(figsize=(10,4))
plt.subplot(131)
plot_gradigent_descent(theta,alpha = 0.02)
plt.subplot(132)
# 存储全量梯度下降的过程
plot_gradigent_descent(theta,0.1,theta_path_bgd)
plt.subplot(133)
plot_gradigent_descent(theta,alpha = 0.5)
plt.show()
从图中可以看出:
- 学习率过小(0.02)可以获得正确的Θ值,但是每次学习效率极低
- 学习率过大(0.5)虽然效率高,但是计算结果有误
四、不同梯度下降的对比
全量梯度下降已经在学习率中做过了
随机梯度下降
每次去一个Θ值进行梯度下降
theta_path_sgd = []
count = len(x_b)
# 迭代次数
n_epochs = 50t0 = 5
t1 = 50# 学习率修改
def learning_schedule(t) :return t0 / (t1 + t)theta=np.random.randn(2,1)for epoch in range(n_epochs):# 遍历样本for i in range(count):# 只画第一批前二十个if epoch == 0 and i < 20:y_predict = x_new_b.dot(theta)plt.plot(x_new,y_predict,'r--')random_index = np.random.randint(count)# 选中一个值xi = x_b[random_index:random_index + 1]yi = y[random_index:random_index + 1]# 对选中值进行梯度下降gradients = 2 * xi.T.dot(xi.dot(theta) - yi)alpha = learning_schedule(epoch * count + i)theta = theta - alpha * gradientstheta_path_sgd.append(theta)
plt.plot(x,y,'b.')
plt.axis([0,2,0,15])
plt.show()
print(theta)
小批量梯度下降
即选中一批数据进行梯度下降
theta_path_mgd = []
count = len(x_b)
# 迭代次数
n_epochs = 50
# 一批数量
minibatch = 16theta=np.random.randn(2,1)t = 0
for epoch in range(n_epochs):# 洗牌shuffled_indices = np.random.permutation(count)x_b_shuffled = x_b[shuffled_indices]y_shuffled = y[shuffled_indices]for i in range(0,count,minibatch):t += 1# 获取一批数据xi = x_b_shuffled[i:i + minibatch]yi = y_shuffled[i:i + minibatch]# 对获取到的这批数据进行梯度下降gradients = 2 / minibatch * xi.T.dot(xi.dot(theta) - yi)alpha = learning_schedule(t)theta = theta - alpha * gradientstheta_path_mgd.append(theta)print(theta)
三种梯度下降方式的对比
数据处理
theta_path_bgd = np.array(theta_path_bgd)
theta_path_sgd = np.array(theta_path_sgd)
theta_path_mgd = np.array(theta_path_mgd)
图像绘制
plt.figure(figsize=(8,4))
plt.plot(theta_path_sgd[:,0],theta_path_sgd[:,1],'r-s',linewidth=1,label='SGD')
plt.plot(theta_path_mgd[:,0],theta_path_mgd[:,1],'b-+',linewidth=2,label='MGD')
plt.plot(theta_path_bgd[:,0],theta_path_bgd[:,1],'g-o',linewidth=3,label='BGD')
plt.legend(loc='upper left')
plt.axis([2,4,1,5.0])
plt.show()
可以看到:
- 全量梯度下降一直在向目标逼近
- 小批量梯度下降略有波动
- 随机梯度下降的波动很剧烈
五、多项式回归(非线性回归)
在实际情况中,我们得到的数据不一定能通过一次函数拟合,需要更高次的函数才能更好的拟合训练数据
创建数据
# 多项式回归
count = 100
# x(-3,+3)
x = 6 * np.random.rand(count,1) - 3
# y = 0.5 * x^2 + x
y = 0.5 * x**2 + x + np.random.randn(count,1)plt.plot(x,y,'r.')
plt.show()
数据处理
PolynomialFeatures可以把我们的输入的数据处理为更高幂次的形式
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree = 2, include_bias = False)x_poly = poly_features.fit_transform(x)print(x[0])
print(x_poly[0], x[0][0]**2)
这里我们把原本的x处理成x和x^2的形式
二次函数图像绘制
numpy.linspace(-3,3,100)生成[-3,3]的100个等距数值数组
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(x_poly, y)x_new = np.linspace(-3,3,100).reshape(100,1)
# 多项式转换
x_new_poly = poly_features.transform(x_new)
y_new = lin_reg.predict(x_new_poly)plt.plot(x,y,'b.')
plt.plot(x_new,y_new,'r--',label='predcition')
plt.legend()
plt.show()
六、模型复杂度的影响
这里的复杂度就是指模型的最高幂次
Pipeline用于将多个数据处理步骤和机器学习模型串联起来,形成一个有序的工作流程,即“机器学习流水线”
StandardScaler用于数据的标准化
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScalerplt.figure(figsize=(12,6))# 分别绘制50次,2次、1次的函数图像拟合训练数据
for style, width, degree in (('g-',1,50),('b--',2,2),('r-+',3,1)):poly_features = PolynomialFeatures(degree = degree, include_bias = False)std = StandardScaler()lin_reg = LinearRegression()pr= Pipeline([('poly_features', poly_features),('StandardScaler', std),('regression', lin_reg)])pr.fit(x,y)y_new_2 = pr.predict(x_new)plt.plot(x_new,y_new_2,style, label = str(degree), linewidth=width)
plt.plot(x,y,'b.')plt.axis([-3,3,-5,10])plt.legend()
plt.show()
从下图中可以看到高复杂度的模型试图拟合更多的数据导致波动极大,在测试集中的表现可能还不如低复杂度的模型,这种情况称为过拟合
七、样本数量的影响(过拟合的避免)
样本量影响实验
def plot_learning_curves(model,x,y):x_train,x_val,y_train,y_val = train_test_split(x,y,test_size=20,random_state=0)# 存储训练方差和实际方差train_errors,val_errors = [],[]# 取1个 到 count个 样本量for m in range(1, len(x_train)):model.fit(x_train[:m],y_train[:m])# 截取样本y_train_predict = model.predict(x_train[:m])y_val_predict = model.predict(x_val)# 存储结果train_errors.append(mean_squared_error(y_train[:m],y_train_predict[:m]))val_errors.append(mean_squared_error(y_val,y_val_predict))plt.plot(np.sqrt(train_errors),'r-+',linewidth=2,label='train_error')plt.plot(np.sqrt(val_errors),'b-',linewidth=3,label='val_error')plt.legend()lin_reg = LinearRegression()
plot_learning_curves(lin_reg,x,y)
在低样本数据量的时候两者的差距很大
而大样本量的时候两个差距会缩小
多项式的过拟合风险
# 多项式回归的拟合风险
# 使用10次函数尝试拟合
pr= Pipeline([('poly_features', PolynomialFeatures(degree = 10, include_bias = False)),('regression', LinearRegression())])
plot_learning_curves(pr,x,y)
plt.axis([0,80,0,45])
plt.show()
八、正则化
正则化的目的就是当两个模型对训练集给出的结果基本相同时,选取更稳定的模型
正则化通过对误差函数增加一个带权重的额外的值(岭回归中使用Θ^2求和,Lasso使用Θ的绝对值)
岭回归
from sklearn.linear_model import Ridge
np.random.seed(42)
count = 20
x = 3 * np.random.rand(count,1)
y = 0.5 * x + np.random.randn(count,1) / 1.5 + 1
x_new = np.linspace(0,3,100).reshape(100,1)# alpha在这里表示正则的权重,越大则倾向于越稳定的模型
def plot_model(model_class, polynomial, alphas, ** model_kargs):# alphas[0]-’b-‘:alphas[0]-'g--':alphas[2]-’r.‘for alpha,style in zip(alphas,('b-','g--','r.')):model = model_class(alpha,**model_kargs)if polynomial:model = Pipeline([('poly_features', PolynomialFeatures(degree = 10, include_bias = False)),('StandardScaler', StandardScaler()),('regression', model)])model.fit(x,y)y_new_regul = model.predict(x_new)width = 2 if alpha > 0 else 1plt.plot(x_new,y_new_regul,style,linewidth = width, label='alpha={}'.format(alpha))plt.plot(x,y,'b.',linewidth=3)plt.legend()
plt.figure(figsize=(10,5))
plt.subplot(121)
plot_model(Ridge,polynomial=False,alphas = (0,10,100))
plt.subplot(122)
plot_model(Ridge,polynomial=True,alphas = (0,10**-5,1))