数据描述:
商城的网页包含如下的乐高玩具的相关信息
页面个数:
页面详细:
包含的特征:
年份、乐高的部件数目、全新与否、乐高玩具的原价
预测:
当前的售价
页面的解析
使用 BeautifulSoup
lgX=[]#存储特征值 lgY=[]#存放标签(实际销售价格) setDataCollect(lgX,lgY)
""" 函数说明:依次读取六种乐高套装的数据,并生成数据矩阵 Parameters: """ def setDataCollect(retX, retY): scrapePage(retX, retY, './lego/lego8288.html', 2006, 800, 49.99)#2006年的乐高8288,部件数目800,原价49.99 scrapePage(retX, retY, './lego/lego10030.html', 2002, 3096, 269.99)#2002年的乐高10030,部件数目3096,原价269.99 scrapePage(retX, retY, './lego/lego10179.html', 2007, 5195, 499.99)#2007年的乐高10179,部件数目5195,原价499.99 scrapePage(retX, retY, './lego/lego10181.html', 2007, 3428, 199.99)#2007年的乐高10181,部件数目3428,原价199.99 scrapePage(retX, retY, './lego/lego10189.html', 2008, 5922, 299.99)#2008年的乐高10189,部件数目5922,原价299.99 scrapePage(retX, retY, './lego/lego10196.html', 2009, 3263, 249.99)#2009年的乐高10196,部件数目3263,原价249.99
""" 函数说明:从页面读取数据,生成retX和retY列表 Parameters: retX - 数据X retY - 数据Y inFile - HTML文件 yr - 年份 numPce - 乐高部件数目 origPrc - 原价 Returns: no Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-16 """ import numpy as np from bs4 import BeautifulSoup import random def scrapePage(retX, retY, inFile, yr, numPce, origPrc): # fr = open(inFile); fw=open(outFile,'a') #a is append mode writing # soup = BeautifulSoup(fr.read()) # 打开并读取HTML文件 with open(inFile, encoding='utf-8') as f: html = f.read() soup = BeautifulSoup(html) i=1 # 根据HTML页面结构进行解析 currentRow = soup.findAll('table', r="%d" % i) while(len(currentRow)!=0): #查找标题 title = currentRow[0].findAll('a')[1].text lwrTitle = title.lower()#拷贝string的副本并转换为小写 # 查找是否有全新标签 if (lwrTitle.find('new') > -1) or (lwrTitle.find('nisb') > -1):#不存在则返回-1,否则返回当前子串所在字符串位置的索引 newFlag = 1.0 else: newFlag = 0.0 # 查找是否已经标志出售,我们只收集已出售的数据 soldUnicde = currentRow[0].findAll('td')[3].findAll('span')#是否在售 sold/unsold if len(soldUnicde)==0: print("item #%d did not sell" % i)#第几项--对应第几table else:# 解析页面获取当前价格 soldPrice = currentRow[0].findAll('td')[4] priceStr = soldPrice.text priceStr = priceStr.replace('$','') #strips out $ priceStr = priceStr.replace(',','') #strips out , if len(soldPrice)>1: priceStr = priceStr.replace('Free shipping', '') #strips out Free Shipping print("%s\t%d\t%s" % (priceStr,newFlag,title))#价格 全新否 题目 #fw.write("%d\t%d\t%d\t%f\t%s\n" % (yr,numPce,newFlag,origPrc,priceStr)) 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.findAll('table', r="%d" % i)
页面的表格结构:
1)解析标题
2)全新与否的解析
3)是否出售,只解析待销售的乐高玩具
–解析页面获取当前价格
注意的是:去掉不完整的套装价格(判断依据售价大于原价的一半则留下,否则认为套装不完整)
将以上每个条目(Table):年份、乐高的部件数目、全新与否、乐高玩具的原价构成特征向量
售价作为标签
建立模型预测
数据预处理
添加常数项特征:总共5个特征
shape(lgX) lgX1=mat(ones((63,5)))#年份--乐高部件数目--全新与否--原价(特征) +常数项特征x0=1 lgX1[:,1:5]=mat(lgX) lgX[0]#测试 lgX1[0]#测试
数据回归分析
标准回归及测试
##标准回归 ws=standRegres(lgX1,lgY) ws#测试 ###单个样本预测 lgX1[0]*ws lgX1[-1]*ws lgX1[43]*ws ###多个样本的预测误差 yHat=lgX1*ws#标准线性回归预测 rssError(lgY,yHat.T.A)
""" 函数说明:标准回归 Parameters: xArr - 特征矩阵 yArr -响应值 Returns: ws- 回归系数 Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-10 """ def standRegres(xArr,yArr): xMat = mat(xArr); yMat = mat(yArr).T xTx = xMat.T*xMat#计算xTx if linalg.det(xTx) == 0.0:#判断行列式是否为0 print("This matrix is singular, cannot do inverse") return ws = xTx.I * (xMat.T*yMat)#计算回归系数 return ws
误差计算函数
""" 函数说明:平方误差函数 Parameters: Returns: Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-10 """ def rssError(yArr,yHatArr): #yArr and yHatArr both need to be arrays return ((yArr-yHatArr)**2).sum()
实验结果:
和标准的售价的均方误差和
交叉验证–岭回归
1)采用10折交叉验证以评估岭回归参数;
2)每一折验证:随机打乱数据,其中训练集和测试集的划分 90%训练 10%测试;
3)10(10折)x30(岭回归参数),通过按照列取平均可获得每个岭回归参数的10折交叉验证平均值,找出误差最小的岭回归参数。
4)数据的还原
可参考博客的数据标准化与去标准化部分的描述:回归分析及实际案例:预测鲍鱼年龄
在岭回归中要求数据要标准化再参与计算,那么在训练完成后新的数据如何进行预测?这个新的数据怎么利用训练的数据进行标准化?
解决方法是:利用在训练数据中得出的回归参数,通过变换实现变相的在新数据预测时的标准化
新的数据一般预测过程:
数据标准化:XT=(XTest-mean(XTrain))/Var(XTrain)
预测:Ytest=XT*Ws+mean(YTrain)
将上述的公式变换:
Ytest=((XTest-mean(XTrain))/Var(XTrain))*Ws+mean(YTrain)
设UnReg=Ws/Var(XTrain)
constantTerm=-mean(XTrain)*Ws/Var(XTrain)+mean(YTrain)
则Ytest=XTest*UnReg+constantTerm(现在的新变换后的预测过程)
##交叉验证--岭回归 ridgeWs,ridgeunReg,ridgeConstantTerm=crossValidation(lgX,lgY,10)#目的是找出最佳的岭回归系数 ##测试均方误差 ####和标准线性回归的比较 ##第一种计算思路: ridgeW=mat(ones((1,5)))#回归系数的重新组合 ridgeW[0,0]=ridgeConstantTerm ridgeW[0,1:5]=ridgeunReg ridgeyHat=lgX1*ridgeW.T#岭回归预测 rssError(lgY,ridgeyHat.T.A)#误差计算 ##第二种计算的思路: xMat=mat(lgX) yMat=mat(lgY) ridgeyHat=xMat*ridgeunReg.T+ridgeConstantTerm#岭回归预测 rssError(yMat.A,ridgeyHat.T.A)#误差计算
""" 函数说明:交叉验证测试岭回归 Parameters: xArr - 特征 yArr - 标签 numVal=10 - 交叉验证的次数 Returns: bestWeights 最佳的岭回归参数 为了和标准线性回归比较 unReg,constantTerm 数据标准化还原后的特征参数和常量参数 Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-28 """ def crossValidation(xArr,yArr,numVal=10): m = len(yArr)#样本点个数 indexList = list(range(m)) errorMat = zeros((numVal,30))#create error mat 30columns numVal rows for i in range(numVal):#交叉验证 trainX=[]; trainY=[] testX = []; testY = [] random.shuffle(indexList)#随机打乱样本索引 #训练集和测试集的划分 90%训练 10%测试 for j in range(m):#create training set based on first 90% of values in indexList 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*特征数 get 30 weight vectors from ridge #30组回归系数 for k in range(30):#loop over all of the ridge estimates matTestX = mat(testX); matTrainX=mat(trainX) meanTrain = mean(matTrainX,0) varTrain = var(matTrainX,0) matTestX = (matTestX-meanTrain)/varTrain #regularize test with training params yEst = matTestX * mat(wMat[k,:]).T + mean(trainY)#test ridge results and store errorMat[i,k]=rssError(yEst.T.A,array(testY)) #print errorMat[i,k] #计算所有这些误差值的均值 meanErrors = mean(errorMat,0)#errorMat为 10*30 30个岭回归参数 10次交叉验证 按照把轴向数据求平均 得到每列数据的平均值,也即是10折交叉验证的平均 calc avg performance of the different ridge weight vectors minMean = float(min(meanErrors))#哪个岭回归参数下的误差最小 bestWeights = wMat[nonzero(meanErrors==minMean)]#找出误差最小的回归参数 #can unregularize to get model #when we regularized we wrote Xreg = (x-meanX)/var(x) #we can now write in terms of x not Xreg: x*w/var(x) - meanX/var(x) +meanY xMat = mat(xArr); yMat=mat(yArr).T meanX = mean(xMat,0); varX = var(xMat,0) unReg = bestWeights/varX print("the best model from Ridge Regression is:\n",unReg) #标准化后数据还原 constantTerm=-1*sum(multiply(meanX,unReg)) + mean(yMat) print("with constant term: ",constantTerm) return bestWeights,unReg,constantTerm
实验结果
标准线性回归和岭回归得出的拟合结果差不多,没有实现很好的拟合效果!
可进一步观察岭回归过程中的参数变换情况:
ridgeTest(lgX,lgY)#返回30组不同参数下的回归系数
""" 函数说明:岭回归 Parameters: xMat- 数据的特征 假设有n 样本个数有m yMat- 响应值 lam-- 调节的参数 Returns: ws-- 计算出的回归系数 Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-10 """ def ridgeRegres(xMat,yMat,lam=0.2): xTx = xMat.T*xMat#2*2 n*n denom = xTx + eye(shape(xMat)[1])*lam#n*n if linalg.det(denom) == 0.0: print("This matrix is singular, cannot do inverse") return ws = denom.I * (xMat.T*yMat) return ws """ 函数说明:岭回归参数lambda调节 Parameters: xArr- 特征矩阵 yArr- 响应值 Returns: wMat - 返回一组w(维数和特征数对应)系数 Author: heda3 Blog: https://blog.csdn.net/heda3 Modify: 2020-01-10 """ def ridgeTest(xArr,yArr): xMat = mat(xArr); yMat=mat(yArr).T #数据标准化处理 yMean = mean(yMat,0) yMat = yMat - yMean #to eliminate X0 take mean off of Y #regularize X's xMeans = mean(xMat,0) #calc mean then subtract it off xVar = var(xMat,0) #calc variance of Xi then divide by it xMat = (xMat - xMeans)/xVar numTestPts = 30#设置lambda参数迭代次数 wMat = zeros((numTestPts,shape(xMat)[1]))#30*2的矩阵 for i in range(numTestPts): ws = ridgeRegres(xMat,yMat,exp(i-10)) wMat[i,:]=ws.T return wMat
《机器学习实战》