《机器学习实战》8.4 线性回归之乐高玩具套件二手交易价格预测
搜索微信公众号:‘AI-ming3526’或者’计算机视觉这件小事’ 获取更多人工智能、机器学习干货
csdn:/baidu_31657889/
github:/aimi-cn/AILearners
本文出现的所有代码,均可在github上下载,不妨来个Star把谢谢~:Github代码地址
一、引言
本篇文章继续对线性回归进行研究,对乐高玩具套件二手交易价格做出预测。
乐高玩具我们应该都知道,当然最熟悉的还是现在00后的小孩子们,90后的我都没玩过这样的拼装类玩具,许多大小不同的塑料插块组成拼接成不同的场景如房子机器人等等。。乐高公司每个套装包含的部件数目从10件到5000件不等。
一种乐高套件基本上在几年后就会停产,但乐高的收藏者之间仍会在停产后彼此交易。本次实例,就是使用回归方法对收藏者之间的交易价格进行预测。
二、乐高玩具套件二手交易价格预测
2.1 获取数据
书上说的是使用google的API获取我们需要的数据,但是这个api现在应该是处于关闭状态,而且google的api我们貌似也访问不了哈哈
但是我们有需要用到的html文件
原始数据下载地址:数据下载
分析这些html文件 获取我们所需要的信息 创建lego.py文件 代码如下:
#!/usr/bin/env python# -*- encoding: utf-8 -*-'''@File : getData.py@Time : /07/22 09:56:16@Author : xiao ming @Version : 1.0@Contact : xiaoming3526@@Desc : 乐高玩具套件html文件数据获取@github : /aimi-cn/AILearners'''# here put the import libfrom bs4 import BeautifulSoupimport numpy as npimport random'''@description: 从页面读取数据,生成retX和retY列表@param: retX - 数据XretY - 数据YinFile - HTML文件yr - 年份numPce - 乐高部件数目origPrc - 原价@return: '''def scrapePage(retX, retY, inFile, yr, numPce, origPrc):# 打开并读取HTML文件with open(inFile, encoding='utf-8') as f:html = f.read()soup = BeautifulSoup(html)i = 1# 根据HTML页面结构进行解析currentRow = soup.find_all('table', r = "%d" % i)while(len(currentRow) != 0):currentRow = soup.find_all('table', r = "%d" % i)title = currentRow[0].find_all('a')[1].textlwrTitle = title.lower()# 查找是否有全新标签if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):newFlag = 1.0else:newFlag = 0.0# 查找是否已经标志出售,我们只收集已出售的数据soldUnicde = currentRow[0].find_all('td')[3].find_all('span')if len(soldUnicde) == 0:print("商品 #%d 没有出售" % i)else:# 解析页面获取当前价格soldPrice = currentRow[0].find_all('td')[4]priceStr = soldPrice.textpriceStr = priceStr.replace('$','')priceStr = priceStr.replace(',','')if len(soldPrice) > 1:priceStr = priceStr.replace('Free shipping', '')sellingPrice = float(priceStr)# 去掉不完整的套装价格if sellingPrice > origPrc * 0.5:print("%d\t%d\t%d\t%f\t%f" % (yr, numPce, newFlag, origPrc, sellingPrice))retX.append([yr, numPce, newFlag, origPrc])retY.append(sellingPrice)i += 1currentRow = soup.find_all('table', r = "%d" % i)def setDataCollect(retX, retY):scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego8288.html', , 800, 49.99)#的乐高8288,部件数目800,原价49.99scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10030.html', 2002, 3096, 269.99)#2002年的乐高10030,部件数目3096,原价269.99scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10179.html', , 5195, 499.99)#的乐高10179,部件数目5195,原价499.99scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10181.html', , 3428, 199.99)#的乐高10181,部件数目3428,原价199.99scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10189.html', , 5922, 299.99)#的乐高10189,部件数目5922,原价299.99scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10196.html', , 3263, 249.99)#的乐高10196,部件数目3263,原价249.99if __name__ == '__main__':lgX = []lgY = []setDataCollect(lgX, lgY)
结果如下:
我们对没有的商品做了处理。这些特征分别为:出品年份、部件数目、是否为全新(1为全新)、原价、售价(二手交易价格)。
2.2 建立模型
我们已经处理好了数据集,接下来就是训练模型。首先我们需要添加全为0的特征X0列。因为线性回归的第一列特征要求都是1.0。然后使用最简单的普通线性回归i,在lego.py文件下添加代码如下:
'''@description: 数据标准化@param: xMat - x数据集yMat - y数据集@return: inxMat - 标准化后的x数据集inyMat - 标准化后的y数据集'''def regularize(xMat, yMat):inxMat = xMat.copy()#数据拷贝inyMat = yMat.copy()yMean = np.mean(yMat, 0) #行与行操作,求均值inyMat = yMat - yMean#数据减去均值inMeans = np.mean(inxMat, 0) #行与行操作,求均值inVar = np.var(inxMat, 0) #行与行操作,求方差# print(inxMat)print(inMeans)# print(inVar)inxMat = (inxMat - inMeans) / inVar #数据减去均值除以方差实现标准化return inxMat, inyMat'''@description: 计算平方误差@param: yArr - 预测值yHatArr - 真实值@return: 平方误差'''def rssError(yArr,yHatArr):return ((yArr-yHatArr)**2).sum()'''@description: 计算回归系数w@param: xArr - x数据集yArr - y数据集@return: ws - 回归系数'''def standRegres(xArr,yArr):xMat = np.mat(xArr); yMat = np.mat(yArr).TxTx = xMat.T * xMat #根据文中推导的公示计算回归系数if np.linalg.det(xTx) == 0.0:print("矩阵为奇异矩阵,不能转置")returnws = xTx.I * (xMat.T*yMat)return ws'''@description: 使用简单的线性回归@param: None@return: None '''def useStandRegres():lgX = []lgY = []setDataCollect(lgX, lgY)data_num, features_num = np.shape(lgX)lgX1 = np.mat(np.ones((data_num, features_num + 1)))lgX1[:, 1:5] = np.mat(lgX)ws = standRegres(lgX1, lgY)print('%f%+f*年份%+f*部件数量%+f*是否为全新%+f*原价' % (ws[0],ws[1],ws[2],ws[3],ws[4]))if __name__ == '__main__':useStandRegres()
运行结果如下:
可以看到,模型预测采用的公式如上图红色框所示。虽然这个模型对于数据拟合得很好,但是看上不没有什么道理。套件里的部件数量越多,售价反而降低了,这是不合理的。
然后我们使用岭回归,通过交叉验证,找到使误差最小的λ对应的回归系数。在lego.py文件下添加代码如下:
'''@description: 岭回归@param: xMat - x数据集yMat - y数据集lam - 缩减系数@return: ws - 回归系数'''def ridgeRegres(xMat, yMat, lam = 0.2):xTx = xMat.T * xMatdenom = xTx + np.eye(np.shape(xMat)[1]) * lamif np.linalg.det(denom) == 0.0:print("矩阵为奇异矩阵,不能转置")returnws = denom.I * (xMat.T * yMat)return ws'''@description: 交叉验证岭回归@param: xArr - x数据集yArr - y数据集numVal - 交叉验证次数@return: wMat - 回归系数矩阵'''def crossValidation(xArr, yArr, numVal = 10):m = len(yArr) #统计样本个数 indexList = list(range(m))#生成索引值列表errorMat = np.zeros((numVal,30)) #create error mat 30columns numVal rowsfor i in range(numVal): #交叉验证numVal次trainX = []; trainY = []#训练集testX = []; testY = []#测试集random.shuffle(indexList)#打乱次序for j in range(m): #划分数据集:90%训练集,10%测试集if j < m * 0.9:trainX.append(xArr[indexList[j]])trainY.append(yArr[indexList[j]])else:testX.append(xArr[indexList[j]])testY.append(yArr[indexList[j]])wMat = ridgeTest(trainX, trainY) #获得30个不同lambda下的岭回归系数for k in range(30): #遍历所有的岭回归系数matTestX = np.mat(testX); matTrainX = np.mat(trainX) #测试集meanTrain = np.mean(matTrainX,0) #测试集均值varTrain = np.var(matTrainX,0) #测试集方差matTestX = (matTestX - meanTrain) / varTrain #测试集标准化yEst = matTestX * np.mat(wMat[k,:]).T + np.mean(trainY) #根据ws预测y值errorMat[i, k] = rssError(yEst.T.A, np.array(testY)) #统计误差meanErrors = np.mean(errorMat,0) #计算每次交叉验证的平均误差minMean = float(min(meanErrors)) #找到最小误差bestWeights = wMat[np.nonzero(meanErrors == minMean)] #找到最佳回归系数xMat = np.mat(xArr); yMat = np.mat(yArr).TmeanX = np.mean(xMat,0); varX = np.var(xMat,0)unReg = bestWeights / varX#数据经过标准化,因此需要还原print('%f%+f*年份%+f*部件数量%+f*是否为全新%+f*原价' % ((-1 * np.sum(np.multiply(meanX,unReg)) + np.mean(yMat)), unReg[0,0], unReg[0,1], unReg[0,2], unReg[0,3])) '''@description: 岭回归测试@param: xMat - x数据集yMat - y数据集@return: wMat - 回归系数矩阵'''def ridgeTest(xArr, yArr):xMat = np.mat(xArr); yMat = np.mat(yArr).T#数据标准化yMean = np.mean(yMat, axis = 0) #行与行操作,求均值yMat = yMat - yMean#数据减去均值xMeans = np.mean(xMat, axis = 0)#行与行操作,求均值xVar = np.var(xMat, axis = 0) #行与行操作,求方差xMat = (xMat - xMeans) / xVar #数据减去均值除以方差实现标准化numTestPts = 30#30个不同的lambda测试wMat = np.zeros((numTestPts, np.shape(xMat)[1])) #初始回归系数矩阵for i in range(numTestPts): #改变lambda计算回归系数ws = ridgeRegres(xMat, yMat, np.exp(i - 10)) #lambda以e的指数变化,最初是一个非常小的数,wMat[i, :] = ws.T #计算回归系数矩阵return wMatif __name__ == '__main__':lgX = []lgY = []setDataCollect(lgX, lgY)crossValidation(lgX, lgY)
运行结果如下:
这里随机选取样本,因为其随机性,所以每次运行的结果可能略有不同。不过整体如上图所示,可以看出,它与常规的最小二乘法,即普通的线性回归没有太大差异。我们本期望找到一个更易于理解的模型,显然没有达到预期效果。
现在,我们看一下在缩减过程中回归系数是如何变化的。在lego.py添加代码如下:
if __name__ == '__main__':lgX = []lgY = []setDataCollect(lgX, lgY)print(ridgeTest(lgX, lgY))
运行结果如下图所示:
看运行结果的第一行,可以看到最大的是第4项,第二大的是第1项。
因此,如果只选择一个特征来做预测的话,我们应该选择第4个特征,也就是原始加个。如果可以选择2个特征的话,应该选择第4个和第1个特征。
这种分析方法使得我们可以挖掘大量数据的内在规律。在仅有4个特征时,该方法的效果也许并不明显;但如果有100个以上的特征,该方法就会变得十分有效:它可以指出哪个特征是关键的,而哪些特征是不重要的。
当然我们也可以使用简单的Sklearn来实现岭回归。
代码地址
AIMI-CN AI学习交流群【1015286623】 获取更多AI资料
扫码加群:
分享技术,乐享生活:我们的公众号计算机视觉这件小事每周推送“AI”系列资讯类文章,欢迎您的关注!
如果觉得《《机器学习实战》8.4 线性回归之乐高玩具套件二手交易价格预测》对你有帮助,请点赞、收藏,并留下你的观点哦!