在上一章节,我们接触到的决策树,比较适合对数据的分类进行预测,以及我们之前学过的分类器也是如此。但是当我们对数值型结果进行预测的时候应该怎么办呢?
具体什么叫做对数值型结果进行预测首先需要明确一下。比如:我们要在拍卖行竞价购买一个笔记本电脑,这台笔记本电脑有一些参数:处理器的速度,RAM的容量,硬盘的大小,屏幕的分辨率以及其他因素。显然,我们最终对其的定价必然要考虑这些参数,而这些参数的重要性各不相同,比如硬盘大小与屏幕大小相比,可能大家都觉得屏幕大小更为重要。那么各个因素都影响着我们最终对该款笔记本的最终的定价,这个定价就是我们所说的对数值型结果进行预测。我们可以使用第五章研究过的优化技术,求出最佳的权重。书中提出:都不是应对这种情况的最佳算法。本章将会研究如何应对这样的情况。
from random import random,randint
import math
#rating代表酒的等级,age代表酒的年代。
def wineprice(rating,age):
#如果rating是代表酒的等级,同一等级的酒的峰值年是一样的。
#所以每一个峰值年是针对同一类等级的酒而言
peak_age=rating-50
#根据等级来计算价格
price=rating/2
if age>peak_age:
#经过“峰值年”之后,之后的5年,酒的品质会变差,价格降低
price=price*(5-(age-peak_age))
else:
#价格在接近“峰值年”时,会增加到原值的五倍
price=price*(5*(age+1)/peak_age)
if price<0:price=0
return price
print '(55,8):',wineprice(55,8)
print '(55,9):',wineprice(55,9)
>>>
(55,8): 54
(55,9): 27
>>>
我们可以看到,对于等级55的酒来说,峰值年为5,所以对于两个过了峰值年的酒来说,第9年显然比第8年更便宜。
def winesetl():
rows=[]
for i in range(300):
#随机产生年代和等级
rating=random()*50+50
age=random()*50
#得到一个参考价格
price=wineprice(rating,age)
#增加“噪声”,也就是让酒的价格随机波动一下
price*=(random()*0.4+0.8)#这个写法很高端
#加入到数据集中
rows.append({'input':(rating,age),'result':price})
return rows
代码如下:
#用欧几里得来计算两瓶酒的相似度
def euclidean(v1,v2):
d=0.0
for i in range(len(v1)):
d+=(v1[i]-v2[i])**2
return math.sqrt(d)
执行代码:
rows=winesetl()
print rows[0]
print rows[1]
print euclidean(rows[0]['input'],rows[1]['input'])
结果:
>>>
{'input': (74.43448365296825, 14.861090558583973), 'result': 128.835924597139}
{'input': (75.15124316082637, 20.12510819860881), 'result': 152.09186781602241}
5.31259126101
>>>
有了相似度计算公式之后,我们很容易就能够计算出两瓶酒的相似度了,下面的代码是用于计算需要预测的新酒和数据集中的每一个的酒的距离(也就是相似度),算出来之后,我们才能排序,抽取出其中k个最相似度。注意,该函数的计算量比较大。
代码如下:#得到需要预测的新酒与数据集中所有酒的相似度
def getdistances(data,vec1):
distancelist=[]
for i in range(len(data)):
vec2=data[i]['input']
distancelist.append((euclidean(vec1,vec2),i))
distancelist.sort()
return distancelist
def knnestimate(data,vec1,k=5):
#得到排序过后的相似度排序
dlist=getdistances(data,vec1)
avg=0.0
#对前k项结果求平均值
for i in range(k):
idx=dlist[i][1]#这里地方之所以是1的原因是取出在data列表里的序号
avg+=data[idx]['result']
avg=avg/k
return avg
rows=winesetl()
print knnestimate(rows,(95.0,3.0))
>>>
25.3277961848
>>>
上面的结果是使用了默认的k为5,那么不同的k,产生的结果肯定也是不一样的。
所以,我们要将得到相似度转化为权重。书中介绍了集中方式来完成这个功能:
书中对这个词的翻译应该有错,书上想将的是y=1/x的这样的函数,却翻译为了反函数。
使用inverse function就可以完成将距离转换为权重这个过程。因为用欧几里得算出来的是两个点之间的距离,如果距离越大,那么其倒数就越小,如果距离越近,那么其倒数越大。这个方法有一个特点,就是如果非常近的话,那么占的权重非常之大,以至于会忽略掉相距稍稍有一点远的邻居,而且相距有点点远,但是它所在占的比重会下降的非常快。这到底是好事还是坏事,要看具体的项目有什么要求。#使用倒数来将距离转为权重
#const的存在是为了防止两点非常近,而导致了其距离非常近,倒数特别大,大到其他数都不起作用
def inverseweight(dist,num=1.0,const=0.1):
return num/(dist+const)
#用减法函数将距离转化为权重
def subtractweight(dist,const=1.0):
if dist>const:
return 0
else:
return const-dist
#使用高斯函数将距离转化为权重
def gaussian(dist,sigma=10.0):
return math.e**(-dist**2/(2*sigma**2))
print inverseweight(0.1)
print subtractweight(0.1)
print gaussian(0.1)
>>>
5.0
0.9
0.99995000125
>>>
def weightedknn(data,vec1,k=5,weightf=gaussian):
#得到距离值
dlist=getdistances(data,vec1)
avg=0.0
totalweight=0.0
#得到加权平均值
for i in range(k):
dist=dlist[i][0]
idx=dlist[i][1]
weight=weightf(dist)
avg+=weight*data[idx]['result']
totalweight+=weight
avg=avg/totalweight
return avg
rows=winesetl()
print weightedknn(rows,(99.0,5.0))
>>>
32.3515253922
>>>
#将数据拆分为训练集和测试集
def dividedata(data,test=0.05):
trainset=[]
testset=[]
for row in data:
if random()
我们去测试算法,会得到产生对算法预测的误差。注意,本函数中,我们统计了是差值的平方,而不是单纯的差值。两者各有好处。
#测试算法的误差
#而是直接将训练集传入knnestimate函数,作为产生一个预测结果的基础,然后算出预测结果和真实结果之间的差距
def testalgorithm(algf,trainset,testset):
error=0.0
for row in testset:#这里只是拿testset来做个循环
guess=algf(trainset,row['input'])
error+=(row['result']-guess)**2#对数字求平方这样会突显其差距。
return error/len(testset)
def crossvalidate(algf,data,trials=100,test=0.05):
error=0.0
for i in range(trials):
trainset,testset=dividedata(data,test)
error+=testalgorithm(algf,trainset,testset)
return error/trials
>>> ================================ RESTART ================================
>>>
k=3时算法的误差: 534.299703506
k=3时算法的误差: 422.359768538
k=3时算法的误差: 460.892823922
k=3时算法的误差: 561.394791352
k=3时算法的误差: 438.566549999
>>> ================================ RESTART ================================
>>>
k=5时算法的误差: 356.420358448
k=5时算法的误差: 371.83561953
k=5时算法的误差: 299.178929108
k=5时算法的误差: 391.072240086
k=5时算法的误差: 352.400721703
>>>
我们发现k=5时,误差要低很多,所以觉得k=5不错。
def wineset2():
rows=[]
for i in range(300):
#随机产生年代和等级
rating=random()*50+50
age=random()*50
aisle=float(randint(1,20))#通道号
bottlesize=[375.0,750.0,1500.0,3000.0][randint(0,3)]#这个bottlesize的值会从第一个列表元素中选一个出来作为其值。
#得到一个参考价格
price=wineprice(rating,age)
price*=(bottlesize/750)
#增加“噪声”,也就是让酒的价格随机波动一下
price*=(random()*0.9+0.2)#这个写法很高端
#加入到数据集中
rows.append({'input':(rating,age,aisle,bottlesize),'result':price})
return rows
rows=wineset2()
print '加入酒瓶大小和通道号后的误差:',crossvalidate(knnestimate,rows)
结果:
>>>
加入酒瓶大小和通道号后的误差: 10510.9359299
>>>
可以看出,误差非常大,这就是因为此时算法还不知道如何应对酒的毫升数的数值非常大的时候,该怎么办?和通道号这种不会对价格产生影响的变量,又该怎么办?
def rescale(data,scale):
scaleddata=[]
for row in data:
scaled=[scale[i]*row['input'][i] for i in range(len(scale))]#把里面每一次取出乘以相应的比例
scaleddata.append({'input':scaled,'result':row['result']})
return scaleddata
rows=wineset2()
rdata=rescale(rows,[10,10,0,0.1])
print '缩放比例后的误差:',crossvalidate(knnestimate,rdata)
>>>
缩放比例后的误差: 4737.15168622
>>>
我们明显可以看出经过缩放之后,误差值明显降低了不少。所以对于这个问题,我们已经有了解决方案。
#封装一个成本函数。这是使用优化算法需要使用到的成本函数。
def createcostfuntion(algf,data):
def costf(scale):
sdata=rescale(data,scale)
return crossvalidate(algf,sdata)
return costf
使用以下代码:
import MyOptimization
data=wineset2()
costf=createcostfuntion(knnestimate,data)
weightdomain=[(0,20)]*4
print MyOptimization.annealingoptimize(weightdomain,costf,step=2)
执行结果,该函数执行的时间比较长:
#给定一个价格区间,判断相应的酒在这个价格区间的概率
#为了更标准,也就是使用权重的方式
#在给定范围内的权重值,除以邻域内的所有权重值。
def probguess(data,vec1,low,high,k=5,weightf=gaussian):
dlist=getdistances(data,vec1)
nweight=0.0
tweight=0.0
for i in range(k):
dist=dlist[i][0]
idx=dlist[i][1]
weight=weightf(dist)
v=data[idx]['result']
#当前数据点位于指定范围内吗?如果是比例加1
if v>=low and v<=high:
nweight+=weight
tweight+=weight
if tweight==0:return 0
return nweight/tweight#告诉了我们比例是多少
data=wineset3()
print '在40和80之间的概率:',probguess(data,[99,20],40,80)
print '在80和120之间的概率:',probguess(data,[99,20],80,120)
print '在120和1000之间的概率:',probguess(data,[99,20],120,1000)
print '在30和120之间的概率:',probguess(data,[99,20],30,120)
print '在0和100000之间的概率:',probguess(data,[99,20],0,100000)
>>>
在40和80之间的概率: 0.195233597431
在80和120之间的概率: 0.385248220468
在120和1000之间的概率: 0.419518182101
在30和120之间的概率: 0.580481817899
在0和100000之间的概率: 1.0
>>>
为了避免乱猜,我们使用一个图形化表达方式来绘制概率密度,这里有一个用于数学图形绘制的优秀软件:matplotlib。找到一个不错的下载python的windows函数库的网站。
注意,除了下载matplotlib,以外还要下载几个函数库才行。
from pylab import *
def cumulativegraph(data,vec1,high,k=5,weightf=gaussian):
t1=arange(0.0,high,0.1)#这里的arange()是定义一个数组,三个参数分别对应start, end, step
cprob=array([probguess(data,vec1,0,v,k,weightf)for v in t1])
plot(t1,cprob)#这个应该是t1是x坐标,cprob是y坐标
show()
data=wineset3()
cumulativegraph(data,(1,1),200)
#ss表示平滑程度
def probabilitygraph(data,vec1,high,k=5,weightf=gaussian,ss=5.0):
#首先建立价格范围,作为x轴
t1=arange(0.0,high,0.1)
#得到整个值域范围内的所有概率,注意范围只是0.1
probs=[probguess(data,vec1,v,v+0.1,k,weightf) for v in t1]
#通过加上近邻概率的高斯计算结果,做平滑处理
smoothed=[]
for i in range(len(probs)):
sv=0.0
for j in range(0,len(probs)):
dist=abs(i-j)*0.1
weight=gaussian(dist,sigma=ss)#距离越近,占的权重越大
sv+=weight*probs[j]
smoothed.append(sv)
smoothed=array(smoothed)
plot(t1,smoothed)
show()
data=wineset3()
probabilitygraph(data,(95,8),150)
# -*- coding: cp936 -*-
from random import random,randint
import math
#rating代表酒的等级,age代表酒的年代。
def wineprice(rating,age):
#如果rating是代表酒的等级,同一等级的酒的峰值年是一样的。
#所以每一个峰值年是针对同一类等级的酒而言
peak_age=rating-50
#根据等级来计算价格
price=rating/2
if age>peak_age:
#经过“峰值年”之后,之后的5年,酒的品质会变差,价格降低
price=price*(5-(age-peak_age))
else:
#价格在接近“峰值年”时,会增加到原值的五倍
price=price*(5*(age+1)/peak_age)
if price<0:price=0
return price
#模拟假设有一些酒是从折扣店,按照50%的价格购买的
#所以,我们需要将某些酒的价格降低一半
def wineset3():
rows=wineset1()
for row in rows:
if random<0.5:
#酒是从折扣店购买来的
row['result']*=0.5
return rows
def wineset2():
rows=[]
for i in range(300):
#随机产生年代和等级
rating=random()*50+50
age=random()*50
aisle=float(randint(1,20))#通道号
bottlesize=[375.0,750.0,1500.0,3000.0][randint(0,3)]#这个bottlesize的值会从第一个列表元素中选一个出来作为其值。
#得到一个参考价格
price=wineprice(rating,age)
price*=(bottlesize/750)
#增加“噪声”,也就是让酒的价格随机波动一下
price*=(random()*0.9+0.2)#这个写法很高端
#加入到数据集中
rows.append({'input':(rating,age,aisle,bottlesize),'result':price})
return rows
def wineset1():
rows=[]
for i in range(300):
#随机产生年代和等级
rating=random()*50+50
age=random()*50
#得到一个参考价格
price=wineprice(rating,age)
#增加“噪声”,也就是让酒的价格随机波动一下
price*=(random()*0.4+0.8)#这个写法很高端
#加入到数据集中
rows.append({'input':(rating,age),'result':price})
return rows
#用欧几里得来计算两瓶酒的相似度
def euclidean(v1,v2):
d=0.0
for i in range(len(v1)):
d+=(v1[i]-v2[i])**2
return math.sqrt(d)
#得到需要预测的新酒与数据集中所有酒的相似度
def getdistances(data,vec1):
distancelist=[]
for i in range(len(data)):
vec2=data[i]['input']
distancelist.append((euclidean(vec1,vec2),i))
distancelist.sort()
return distancelist
def knnestimate(data,vec1,k=5):
#得到排序过后的相似度排序
dlist=getdistances(data,vec1)
avg=0.0
#对前k项结果求平均值
for i in range(k):
idx=dlist[i][1]#这里地方之所以是1的原因是取出在data列表里的序号
avg+=data[idx]['result']
avg=avg/k
return avg
#使用倒数来将距离转为权重
#const的存在是为了防止两点非常近,而导致了其距离非常近,倒数特别大,大到其他数都不起作用
def inverseweight(dist,num=1.0,const=0.1):
return num/(dist+const)
#用减法函数将距离转化为权重
def subtractweight(dist,const=1.0):
if dist>const:
return 0
else:
return const-dist
#使用高斯函数将距离转化为权重
def gaussian(dist,sigma=10.0):
return math.e**(-dist**2/(2*sigma**2))
def weightedknn(data,vec1,k=3,weightf=gaussian):
#得到距离值
dlist=getdistances(data,vec1)
avg=0.0
totalweight=0.0
#得到加权平均值
for i in range(k):
dist=dlist[i][0]
idx=dlist[i][1]
weight=weightf(dist)
avg+=weight*data[idx]['result']
totalweight+=weight
avg=avg/totalweight
return avg
#将数据拆分为训练集和测试集
def dividedata(data,test=0.05):
trainset=[]
testset=[]
for row in data:
if random()=low and v<=high:
nweight+=weight
tweight+=weight
if tweight==0:return 0
return nweight/tweight#告诉了我们比例是多少
from pylab import *
def cumulativegraph(data,vec1,high,k=5,weightf=gaussian):
t1=arange(0.0,high,0.1)#这里的arange()是定义一个数组,三个参数分别对应start, end, step
cprob=array([probguess(data,vec1,0,v,k,weightf)for v in t1])
plot(t1,cprob)#这个应该是t1是x坐标,cprob是y坐标
show()
#ss表示平滑程度
def probabilitygraph(data,vec1,high,k=5,weightf=gaussian,ss=5.0):
#首先建立价格范围,作为x轴
t1=arange(0.0,high,0.1)
#得到整个值域范围内的所有概率,注意范围只是0.1
probs=[probguess(data,vec1,v,v+0.1,k,weightf) for v in t1]
#通过加上近邻概率的高斯计算结果,做平滑处理
smoothed=[]
for i in range(len(probs)):
sv=0.0
for j in range(0,len(probs)):
dist=abs(i-j)*0.1
weight=gaussian(dist,sigma=ss)#距离越近,占的权重越大
sv+=weight*probs[j]
smoothed.append(sv)
smoothed=array(smoothed)
plot(t1,smoothed)
show()
data=wineset3()
probabilitygraph(data,(95,8),150)