《机器学习实战》8.4 线性回归之乐高玩具套件二手交易价格预测

搜索微信公众号:‘AI-ming3526’或者’计算机视觉这件小事’ 获取更多人工智能、机器学习干货
csdn:https://blog.csdn.net/baidu_31657889/
github:https://github.com/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    :   2019/07/22 09:56:16
@Author  :   xiao ming 
@Version :   1.0
@Contact :   xiaoming3526@gmail.com
@Desc    :   乐高玩具套件html文件数据获取
@github  :   https://github.com/aimi-cn/AILearners
# here put the import lib
from bs4 import BeautifulSoup
import numpy as np
import random
@description: 从页面读取数据,生成retX和retY列表
@param: retX - 数据X
        retY - 数据Y
        inFile - 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].text
        lwrTitle = title.lower()
        # 查找是否有全新标签
        if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):
            newFlag = 1.0
        else:
            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.text
            priceStr = 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 += 1
        currentRow = 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', 2006, 800, 49.99)                #2006年的乐高8288,部件数目800,原价49.99
    scrapePage(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.99
    scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10179.html', 2007, 5195, 499.99)                #2007年的乐高10179,部件数目5195,原价499.99
    scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10181.html', 2007, 3428, 199.99)                #2007年的乐高10181,部件数目3428,原价199.99
    scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10189.html', 2008, 5922, 299.99)                #2008年的乐高10189,部件数目5922,原价299.99
    scrapePage(retX, retY, 'C:/Users/Administrator/Desktop/blog/github/AILearners/data/ml/jqxxsz/8.Regression/lego/lego10196.html', 2009, 3263, 249.99)                #2009年的乐高10196,部件数目3263,原价249.99
if __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).T xTx = xMat.T * xMat #根据文中推导的公示计算回归系数 if np.linalg.det(xTx) == 0.0: print("矩阵为奇异矩阵,不能转置") return ws = 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 * xMat denom = xTx + np.eye(np.shape(xMat)[1]) * lam if np.linalg.det(denom) == 0.0: print("矩阵为奇异矩阵,不能转置") return ws = 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 rows for 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).T meanX = 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 wMat if __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 线性回归之乐高玩具套件二手交易价格预测搜索微信公众号:‘AI-ming3526’或者’计算机视觉这件小事’ 获取更多人工智能、机器学习干货csdn:https://blog.csdn.net/baidu_31657889/github:https://github.com/aimi-cn/AILearners本文出现的所有代码,均可在github上下载,不妨来个...
机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深度学习500问机器学习/深
目录预测数值型数据:回归1 利用线性回归找到最佳拟合直线2 局部加权线性回归3 示例:预测鲍鱼的年龄4 缩减系数来“理解”数据4.1 岭回归4.2 lasso4.3 前向逐步回归5 权衡偏差与方差6 示例:预测乐高玩具套装的价格6.1 收集数据:使用google购物的API6.2 训练算法:建立模型7 本章结束本节涉及的相关代码和数据本章内容:线性回归的相关特点主要是回归的一般方法:采用最小平方误差 调用上述函数: 输出得到的结果为: 将得到的权重系数,画图表示为: 得到的输出图像为:可以看到该
乐高是一个很受欢迎的玩具积木品牌。它们通常是成套出售的,用来制作特定的物品。每一套都包含许多不同形状、大小和颜色的零件。这个数据库包含了不同的乐高组合中包含哪些部件的信息。它最初是为了帮助那些已经拥有一些乐高积木套的人弄清楚他们还能用自己的作品制造出什么其他玩具。 colors.csv downloads_schema.png inventories.csv inventory_parts.csv inventory_sets.csv part_categories.csv parts.csv sets.csv themes.csv