机器学习第八篇

构造价格模型

利用多种不同属性(比如价格)对数值型数据进行预测时,贝叶斯分类器、决策树、支持向量机都不是最佳的算法。

本篇将对一系列算法进行考查,这些算法可以接受训练,根据之前见过的样本数据作出数据类的预测,而且它们还可以显示出预测的概率分布情况,以帮助用户对预测过程加以解释。后续部分将考查如何利用这些算法来构造价格预测模型。

进行数值预测的一项关键工作是确定哪些变量是重要的。

构造一个样本数据集

根据一个人为假设的简单模型来构造一个有关葡萄酒价格的数据集。酒的价格是根据酒的等级及其储藏的年代来共同决定的。该模型假设葡萄酒有‘峰值年’的现象,即:较之峰值年而言,年代稍早一些的酒的品质会比较好一些,而紧随其后的品质则稍差些

def wineprice(rating,age):
    peak_age=rating-50

    #根据等级来计算价格
    price=rating/2
    if age>peak_age:
        #经过‘峰值年’,后继5年里其品质将会变差
        price=price*(5-(age-peak_age))
    else:
        #价格在接近‘峰值年’时会增加到原值的5倍
        price=price*(5*((age+1)/peak_age))
    if price<0: price=0
    return price
'''
构造表示葡萄酒价格的数据集,
并在原有价格的基础上通过‘噪声’随机地加减了20%,
以此来表现诸如税收和价格局部变动的情况
'''

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

K-最邻近算法(kNN)

通过寻找与当前所关注的商品情况相似的一组商品,对这些商品的价格求均值,进而作出价格预测,k指的是为了求得最终结果而参与求平均值运算的商品数量。

邻近数

过少过多均不好

定义相似度

寻找一种衡量两件商品之间相似程度的方法。

补充:欧几里得距离算法

#欧几里得距离算法
def euclidean(v1,v2):
    d=0.0
    for i in range(len(v1)):
        d+=(v1[i]-v2[i])**2
    return math.sqrt(d)

k-最邻近算法

优点:1、每次有新数据加入时,都无需重新进行训练

扫描二维码关注公众号,回复: 4020444 查看本文章

缺点:1、计算量大    2、该函数在计算距离是对年代和等级是同等看待的,但现实是,某些变量对最终价格所产生的影响往往比其他变量更大

def getdistance(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=getdistance(data,vec1)
    avg=0.0

    #对前k项结果求平均
    for i in range(k):
        idx=dlist[i][1]
        avg+=data[idx]['result']
    avg=avg/k
    return avg
print(knnestimate(data,(95.0,3.0)))
print(knnestimate(data,(99.0,3.0)))
print(wineprice(95.0,5.0))
print(wineprice(99.0,3.0))
print("***kNN")
print(knnestimate(data,(99.0,5.0)))
print("***真实")
print(wineprice(99.0,5.0))#得到实际价格
print("***少")
print(knnestimate(data,(99.0,5.0),k=1)) #尝试更少的近邻
print("***多")
print(knnestimate(data,(99.0,5.0),k=10)) #尝试更少的近邻

为邻近分配权重

反函数

该函数最为简单的一种形式是返回距离的倒数,不过有时候,完全一样或非常接近的商品,会使权重值变得非常之大,甚至是无穷大,基于这样的原因,有必要在对距离求倒数之前先加上一个小小的常量

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

该函数克服了反函数对近邻项权重分配过大的潜在问题,但是由于权重值最终会跌至0,因此,我们有可能找不到距离足够近的项,将其视作近邻,即:对于某些项,算法根本就无法作出预测。

高斯函数

‘钟型曲线’

#高斯函数
def gaussian(dist,sigma=10.0):
    return math.e**(-dist**2/(2*sigma**2))

                                  

加权KNN

#加权KNN
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

交叉验证 

def dividedata(data,test=0.05):
    trainset=[]
    testset=[]
    for row in data:
        if random()<test:
            testset.append(row)
        else:
            trainset.append(row)
    return trainset,testset

def testalgorithm(algf,trainset,testset):
    error=0.0
    for row in 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



def knn3(d,v):
    return knnestimate(d,v,k=3)

def knn1(d,v):
    return knnestimate(d,v,k=1)

选择太少或太多的近邻都会导致效果不佳。

加权kNN算法似乎能够针对上述数据给出更好的结果。

对于一个特定的训练集而言,选择参数只须做一次即可,但随着训练集内容的增长,偶尔还须对其进行再次的更新。 

不同类型的变量

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)]
        #得到一个参考价格
        price=wineprice(rating,age)
        price*=(bottlesize/750)
        #增加‘噪声’    ##########################################################
        price*=(random()*0.2+0.9)
        #加入数据集
        rows.append({'input':(rating,age,aisle,bottlesize),'result':price})
    return rows

即使数据集现在包含了更多的信息,而且噪声也比以前更少了,但是crossvalidate函数实际返回的结果较之以前却更为糟糕。其原因在于,算法现在还不知道如何对不同的变量加以区别对待

因为之前的两个变量都位于同一值域范围内,因此,利用这些变量一次性算出距离值是有意义的。

酒瓶尺寸和原先的变量相比,对距离计算所产生的影响更为显著——其影响将超过任何其他变量对距离计算所构成的影响,这意味着,在计算距离的过程中其他变量根本就未被考虑在内。除此之外,还可能会遇到的另一个问题是数据集中引入了完全不相关的变量,如安放葡萄酒的通道号。

按比例缩放

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

对缩放结果进行优化

大多数时候我们所面对的数据集都不是自己构造的,而且我们也未必知道,到底哪些变量是不重要的,而哪些变量又对计算结果有着重大的影响。理论上我们可以尝试大量不同的组合,直到发现一个足够好的结果为止,不过也许有数以百计的变量须要考查,并且这项工作可能会非常地乏味。

def createcostfunction(algf,data):
    def costf(scale):
        sdata=rescale(data,scale)
        return crossvalidate(algf,sdata,trials=10)
    return costf

weightdomain=[(0,20)]*4
from optimization import annealingoptimize,geneticoptimize
data = wineset2()
costf=createcostfunction(knnestimate,data)
print(annealingoptimize(weightdomain,costf,step=5))
print(geneticoptimize(weightdomain,costf,popsize=50,step=1,mutprob=0.2,elite=0.2,maxiter=100))

实验做的有问题。。。。。。。。。 

利用模拟退火以及遗传算法等优化算法地一个好处在于,我们很快就能发觉哪些变量是重要地,并且其重要程度有多大。

不对称分布 

若葡萄酒购买者分别来自两个彼此独立的群组:一部分人是从小酒馆购得的葡萄酒,而另一部分人则是从折扣店购得,并且后者得到了50%的折扣。

def wineset3():
    rows=wineset1()
    for row in rows:
        if random()<0.5:
            #葡萄酒是从折扣店购得的
            row['result']*=0.5
    return rows

算法给出的评价值将同时涉及两组人群,这就相当于可能有25%的折扣。为了不只是简单地得到一个平均值,则需要一种方法能够在某些方面更近一步地对数据进行考查。

估计概率密度

假设输入条件为99%和20年,那么我们需要一个函数来告诉我们,价格介于40美元和80美元之间的几率是50%,而价格介于80美元和100美元之间的几率也是50%。

函数首先计算位于该范围内近邻的权重值,然后计算所有近邻的权重值,最终的概率等于在指定范围内的近邻权重之和除以所有权重之和。

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']

        #当前数据点位于指定范围内吗?
        if v>=low and v<=high:
            nweight+=weight
        tweight+=weight
    if tweight==0 : return 0

    #概率等于位于指定范围内的权重值除以所有权重值
    return nweight/tweight

 绘制概率分布

第一种方法:累计概率分布

图形从概率为0开始,尔后随着商品在某一价位出命中的概率值而逐级递增。直至最高位处,图形对应的概率值达到1

#绘制概率分布
from pylab import *
# a=array([1,2,3,4])
# b=array([4,2,3,1])
# plot(a,b)
# show()
# t1=arange(0.0,10.0,0.1)
# plot(t1,sin(t1))
# show()
#累积概率
def cumulativegraph(data,vec1,high,k=5,weightf=gaussian):
    t1=arange(0.0,high,0.1)
    cprob=array([probguess(data,vec1,0,v,k,weightf) for v in t1])
    plot(t1,cprob)
    show()

第二种:假设每个位点的概率都等于其周边概率的一个加权平均

def probabilitygraph(data,vec1,high,k=5,weightf=gaussian,ss=5.0):
    #建立一个代表价格的值域范围
    t1=arange(0.0,high,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()

                   

猜你喜欢

转载自blog.csdn.net/qq_35134144/article/details/83048053