#!/usr/bin/env python3
# encoding: utf-8
'''
@file: ridgeWine.py
@time: 2020/5/31 0031 17:45
@author: Jack
@contact: [email protected]
'''
import urllib.request
import numpy as np
from sklearn import datasets, linear_model
from math import sqrt
import matplotlib.pyplot as plt
## 读取数据集
target_url = ("http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv")
data = urllib.request.urlopen(target_url)
xList = []
labels = []
names = []
firstLine = True
for line in data:
if firstLine:
names = str(line, encoding='utf-8').strip().split(";")
firstLine = False
else:
row = str(line, encoding='utf-8').strip().split(";")
labels.append(float(row[-1]))
row.pop()
floatRow = [float(num) for num in row]
xList.append(floatRow)
## 将属性矩阵和label分成训练集training set(2/3 of data)和测试集test sets (1/3 of data)
indices = range(len(xList))
xListTest = [xList[i] for i in indices if i % 3 == 0]
xListTrain = [xList[i] for i in indices if i % 3 != 0]
labelsTest = [labels[i] for i in indices if i % 3 == 0]
labelsTrain = [labels[i] for i in indices if i % 3 != 0]
xTrain = np.array(xListTrain)
yTrain = np.array(labelsTrain)
xTest = np.array(xListTest)
yTest = np.array(labelsTest)
alphaList = [0.1 ** i for i in [0, 1, 2, 3, 4, 5, 6]]
rmsError = []
for alph in alphaList:
wineRidgeModel = linear_model.Ridge(alpha=alph)
wineRidgeModel.fit(xTrain, yTrain)
rmsError.append(np.linalg.norm((yTest - wineRidgeModel.predict(xTest)), 2) / sqrt(len(yTest)))
print("RMS Error alpha")
for i in range(len(rmsError)):
print(rmsError[i], alphaList[i])
## 绘制测试集error vs alpha曲线
x = range(len(rmsError))
plt.plot(x, rmsError, 'k')
plt.xlabel('-log(alpha)')
plt.ylabel('Error (RMS)')
plt.show()
## 绘制最佳Alpha值的测试集误差直方图和实际值与预测值的散点图
## 识别最小值对应指标,用对应的alpha值重新训练,使用结果模型对测试集数据进行预测,绘制误差(又称残差)
indexBest = rmsError.index(min(rmsError))
alph = alphaList[indexBest]
wineRidgeModel = linear_model.Ridge(alpha=alph)
wineRidgeModel.fit(xTrain, yTrain)
errorVector = yTest - wineRidgeModel.predict(xTest)
plt.hist(errorVector)
plt.xlabel("Bin Boundaries")
plt.ylabel("Counts")
plt.show()
plt.scatter(wineRidgeModel.predict(xTest), yTest, s=100, alpha=0.10)
plt.xlabel('Predicted Taste Score')
plt.ylabel('Actual Taste Score')
plt.show()
RMS Error alpha
0.6595788176342458 1.0
0.6578610918808592 0.1
0.6576172144640243 0.010000000000000002
0.6575216482641756 0.0010000000000000002
0.6574190680109292 0.00010000000000000002
0.6573941628851252 1.0000000000000003e-05
0.6573913087155857 1.0000000000000004e-06
普通最小二乘法的目标是找到能够满足公式3-14的标量β0以及向量β。
符号argmin是指“能够最小化表达式的β0以及β”。系数β0以及β是最小二乘法的解。最佳子集回归以及前向逐步回归通过限制使用的属性个数来控制回归的复杂度。另外一种方法称作惩罚系数回归。惩罚系数回归是使系数变小,而不是将其中一些系数设为0。
一种惩罚线性回归的方法被称作岭回归。岭回归问题的定义见公式3-15。
公式3-15以及普通最小二乘法(公式3-14)的差别在αβTβ项上。βTβ项是β(系数向量)的欧几里得范数的平方。变量β是这类问题的复杂度参数。如果α= 0,问题变为普通最小二乘法。如果α变大,β(系数的瓶阀)接近于0,那么通过常数项β0就可以预测标签yi。scikit-learn包给出了岭回归的实现。
前向逐步回归算法生成了一系列不同的模型,第一个模型只包含一个属性,第二个模型包含两个属性,等等,直到最后的模型包含所有属性。岭回归代码也包含一系列的模型。岭回归通过不同的α值来控制模型数量,而不是通过属性个数来控制。α参数决定了对β的惩罚力度。α的一系列值按照10的倍数递减。α的取值范围一般要设置得足够宽,往往需要通过实验来确定。
上述图1为RMSE与岭回归的复杂度参数α的对应关系。参数按照从左到右、从大到小排列。传统习惯是一般在左边绘制简单模型,在右边绘制复杂模型。图1显示了与逐步前向回归类似的特点。错误几乎是一样的,但前向逐步回归的错误相比来说更大一些。图3为在红酒数据集上使用岭回归的实际口感得分与预测得分的散点图。图2为预测错误的直方图。