文章目录
1.Python代码
#!/usr/bin/env python3
# encoding: utf-8
'''
@file: larsWineCV.py
@time: 2020/6/13 0013 10:22
@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)
nrows = len(xList)
ncols = len(xList[0])
## 计算均值方差
xMean = []
xSD = []
for i in range(ncols):
col = [xList[j][i] for j in range(nrows)]
mean = sum(col) / nrows
xMean.append(mean)
colDiff = [(xList[j][i] - mean) for j in range(nrows)]
sumSq = sum([colDiff[i] * colDiff[i] for i in range(nrows)])
stdDev = sqrt(sumSq / nrows)
xSD.append(stdDev)
## 归一化属性和标签
xNormalized = []
for i in range(nrows):
rowNormalized = [(xList[i][j] - xMean[j]) / xSD[j] for j in range(ncols)]
xNormalized.append(rowNormalized)
meanLabel = sum(labels) / nrows
sdLabel = sqrt(sum([(labels[i] - meanLabel) * (labels[i] - meanLabel) for i in range(nrows)]) / nrows)
labelNormalized = [(labels[i] - meanLabel) / sdLabel for i in range(nrows)]
## 通过交叉验证确定最佳系数
nxval = 10 ## 交叉验证折数
nSteps = 350 ## 迭代次数
stepSize = 0.004 ## 步长
## 初始化error列表
errors = []
for i in range(nSteps):
b = []
errors.append(b)
for ixval in range(nxval):
idxTest = [a for a in range(nrows) if a % nxval == ixval * nxval]
idxTrain = [a for a in range(nrows) if a % nxval != ixval * nxval]
xTrain = [xNormalized[r] for r in idxTrain]
xTest = [xNormalized[r] for r in idxTest]
labelTrain = [labelNormalized[r] for r in idxTrain]
labelTest = [labelNormalized[r] for r in idxTest]
nrowsTrain = len(idxTrain)
nrowsTest = len(idxTest)
beta = [0.0] * ncols
betaMat = []
betaMat.append(list(beta))
for iStep in range(nSteps):
residuals = [0.0] * nrows
for j in range(nrowsTrain):
labelsHat = sum([xTrain[j][k] * beta[k]
for k in range(ncols)])
residuals[j] = labelTrain[j] - labelsHat
corr = [0.0] * ncols
for j in range(ncols):
corr[j] = sum([xTrain[k][j] * residuals[k] for k in range(nrowsTrain)]) / nrowsTrain
iStar = 0
corrStar = corr[0]
for j in range(1, (ncols)):
if abs(corrStar) < abs(corr[j]):
iStar = j
corrStar = corr[j]
beta[iStar] += stepSize * corrStar / abs(corrStar)
betaMat.append(list(beta))
for j in range(nrowsTest):
labelsHat = sum([xTest[j][k] * beta[k] for k in range(ncols)])
err = labelTest[j] - labelsHat
errors[iStep].append(err)
cvCurve = []
for errVect in errors:
mse = sum([x * x for x in errVect]) / len(errVect)
cvCurve.append(mse)
minMse = min(cvCurve)
minPt = [i for i in range(len(cvCurve)) if cvCurve[i] == minMse][0]
print("Minimum Mean Square Error", minMse)
print("Index of Minimum Mean Square Error", minPt)
xaxis = range(len(cvCurve))
plt.plot(xaxis, cvCurve)
plt.xlabel("Steps Taken")
plt.ylabel(("Mean Square Error"))
plt.show()
Minimum Mean Square Error 0.5873018933136459
Index of Minimum Mean Square Error 311
2.结果分析
目前关于利用红酒的化学属性上来预测红酒口感得分,已经有 350 个可能的解。如何选择最佳的解?为了选择使用的曲线,需要了解350个选择中每种选择的优劣。本例中通过10折交叉验证的方法来决定部署使用的最佳系数集合。10折交叉验证是将输入数据切分为10份几乎均等的数据,将其中一份数据移除,使用剩下数据进行训练,然后再在移除的数据上进行测试。通过遍历10份数据,每次移除一份数据用于测试,从而可以对错误进行估计从而估计预测的变化情况。关于交叉验证份数的选择,如果份数较少,那么每次训练使用的数据也较少。如果设为 5 折,那么每次训练时会预留 20% 的数据。如果使用 10 折,只会留出 10% 的数据。在较少的数据上训练会降低算法的准确性。然而,切分份数过多也意味着在训练过程中需要多次遍历,这会显著增加训练时间。
在进行交叉验证循环前,首先初始化一个错误列表。该错误列表包含算法中每一步迭代的错误。算法会对所有 10 折交叉验证的每折错误进行累加。这里使用一个取模函数来定义这些集合。在有些情况下,需要使用分层抽样。例如要在一个非平衡数据集上构建分类器,其中某一类的样本点很少。如果希望训练集能够代表整个数据集,那么需要按照类来抽样数据,从而确保样本内和样本外的类大小比例一致。
结果图中曲线展示了一个非常普遍的模式。基本上 MSE 随着迭代步数的增加而单调递减。严格来讲,从程序的输出来看,最小值在第 311 步左右。但是该图显示最小值比较稳定,相对周围没有明显突出。在某些情况下,该曲线会在某个点上到达一个明显的最小值,往左或者往右都会显著增加。交叉验证在训练阶段的第311步取到 MSE 的最小值 0.59。图中系数曲线是在整个数据集上训练得到的。因为并不了解 350 个系数集合的哪一个应该被部署使用,所以使用交叉验证确定。交叉验证会生成对 MSE 的合理估计,告诉我们将第311个模型从训练集部署到整个数据集上。