好文链接:
https://blog.csdn.net/csqazwsxedc/article/details/65697652
https://blog.csdn.net/Daycym/article/details/80768217
https://blog.csdn.net/qq_40845344/article/details/98983875
预备知识:
1.熵是对平均不确定性的度量
2.平均互信息:得知特征Y的信息而使得对标签X的信息的不确定性减少的程度(衡量相似性)
基本概念
(1)决策树采用自顶向下的递归方法
(2)基本思想是以信息熵为度量构造一棵熵值下降最快的树,到叶子节点处的熵值为0
决策树的三种算法:ID3 ,C4.5 ,CART
ID3
(1)特征A对训练数据集D的信息增益 g(D,A)=H(D)-H(D|A)
D代表标签,A代表特征。叫做信息增益熵
(2) 遍历所有特征,选择信息增益最大的特征作为当前的分裂特征。然后对剩下的特征重复(1)步骤继续进行选择
(3)到最后只有一个特征可供选择时,根据少数服从多数原则进行分类;或者已经分离出的样本全都属于一类时
C4.5
gr(D,A)=g(D,A) / H(A) ;这个叫做信息增益率。其中H(A)是一个惩罚项,避免特征被分得过细,当属性子集越多, 则H(A)就会越大, 得到的Gain_ratio就会越小, 从而达到: 避免因为属性子集过多而导致信息增长过大影响判断结果.例如经典的相亲例子中将工资这一特征分得过细,1K-2K,2K-3K…工资这一个属性给你分个1000个范围,不合适吧。这样就导致泛化能力的下降,是对于ID3算法的改进
CART
CART决策树使用基尼指数GiNi 系数(所谓的信息熵)作为判断属性是否可以作为划分依据。
特点:
CART 既能是分类树,又能是回归树.
当CART是分类树时,采用GINI值作为节点分裂的依据;当CART是回归树时,采用样本的最小方差作为节点分裂的依据.
/******************************************************************/
决策树对训练集有很好的分类能力,但对于未知的未必,泛化能力弱。
from math import log
import operator
def calcShannonEnt(dataSet): # 计算数据的熵(entropy)的函数
numEntries=len(dataSet) # 数据条数,numEntries存储数据个数
labelCounts={} # labelCounts存储有几种类别
for featVec in dataSet:
currentLabel=featVec[-1] # currentLabel循环存储每一个样本的标签
if currentLabel not in labelCounts.keys():# labelCounts统计有几种标签,以及每一种标签有几个
labelCounts[currentLabel]=0 #这一行不在if循环内
labelCounts[currentLabel]+=1
shannonEnt=0
for key in labelCounts:
prob=float(labelCounts[key])/numEntries # 计算类别概率
shannonEnt-=prob*log(prob,2) # 累加每个类的熵值,注意这里是 -= 而不是 =,所以没有错误,
return shannonEnt
def createDataSet1(): # 创造示例数据,dataSet是一个矩阵,带有特征值以及标签
dataSet = [['长', '粗', '男'],
['短', '粗', '男'],
['短', '粗', '男'],
['长', '细', '女'],
['短', '细', '女'],
['短', '粗', '女'],
['长', '粗', '女'],
['长', '粗', '女']]
labels = ['头发','声音'] #两个特征名,头发以及声音
return dataSet,labels
def splitDataSet(dataSet,axis,value): # Vaule 是‘粗’‘细’类似类别中的一个;axis是指第几个特征
retDataSet=[]
for featVec in dataSet:
if featVec[axis]==value: # 如果一条样本的某个维度=value
reducedFeatVec =featVec[:axis] #featVec[:axis]代表一直到第几个维度,featVec=[1,2,3,4]featVec[:1]结果就是[1]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
def chooseBestFeatureToSplit(dataSet): # 选择最优的分类特征
numFeatures = len(dataSet[0])-1 #numFeatures表示每个样本有几个特征
baseEntropy = calcShannonEnt(dataSet) # 原始的熵(最开始的熵)
bestInfoGain = 0
bestFeature = -1
for i in range(numFeatures):
featList = [example[i] for example in dataSet]
# featList 代表所有样本的每一列特征,如['粗', '粗', '粗', '细', '细', '粗', '粗', '粗']
uniqueVals = set(featList) # uniqueVals在头发这一维度指 ['细', '粗']
newEntropy = 0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet,i,value)
prob =len(subDataSet)/float(len(dataSet))
#计算满足某一特征的样本数占总体的比例
newEntropy +=prob*calcShannonEnt(subDataSet) # 按某一特征分类后的熵,注意这里是+= 而不是=
infoGain = baseEntropy - newEntropy # 原始熵与按某一特征分类后的熵的差值,也就是对于不确定性的减少
if (infoGain>bestInfoGain): # 若按某特征划分后,熵值减少的最大,则次特征为最优分类特征
bestInfoGain=infoGain
bestFeature = i
return bestFeature
def majorityCnt(classList): #按分类后类别数量排序,比如:最后分类为3男5女,[('女', 5), ('男', 3)] return 回'女'
classCount={}
for vote in classList:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote]+=1
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
def createTree(dataSet,labels):
classList=[example[-1] for example in dataSet] # classList是类别标签列表['男', '男', '男', '女', '女', '女', '女', '女']
if classList.count(classList[0])==len(classList): #如果第一个标签数量与样本数相等
return classList[0]
if len(dataSet[0])==1:
return majorityCnt(classList)
bestFeat=chooseBestFeatureToSplit(dataSet) #选择最优特征的序号
bestFeatLabel=labels[bestFeat] #选择最优特征
myTree={bestFeatLabel:{}} #分类结果以字典形式保存
del(labels[bestFeat]) #删除标签中选出来的最好特征
featValues=[example[bestFeat] for example in dataSet]
uniqueVals=set(featValues)
for value in uniqueVals:
subLabels=labels[:]
myTree[bestFeatLabel][value]=createTree(splitDataSet\
(dataSet,bestFeat,value),subLabels)
return myTree
if __name__=='__main__':
dataSet, labels=createDataSet1() # 创造示列数据
print(createTree(dataSet, labels)) # 输出决策树模型结果
用sklearn 内置函数CART
from sklearn import datasets
from sklearn import tree
from sklearn.externals.six import StringIO
import pydot
# 加载Iris数据集
iris = datasets.load_iris()
#不传入参数, 使用默认算法CRAT实现分类
clf = tree.DecisionTreeClassifier()
clf = clf.fit(iris.data, iris.target)
dot_data = StringIO()
tree.export_graphviz(clf, out_file=dot_data, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, rounded=True, special_characters=True)
# tree.export_graphviz(clf, out_file=r"tree.dot") #把这行代码放开可以生成决策树的文件
(graph,) = pydot.graph_from_dot_data(dot_data.getvalue())
graph.write_png('iris.png')
剪枝:防止过拟合
同时还存在其他问题:连续值,缺失值如何处理?
还有多变量决策树使得模型更加简单,下面的链接有提到
https://blog.csdn.net/m0_37167788/article/details/78794833?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase
adaboost :https://blog.csdn.net/px_528/article/details/72963977
上面的链接讲得很好,唯一的缺陷就是参数是如何更新的没有用文字记录,下面放上他的代码并加上备注。
目的:为了找到一个比随机猜测略好的弱学习算法就可以直接将其提升为强学习算法,而不必直接去找很难获得的强学习算法。
思想:学习算法A在a情况下失效,学习算法B在b情况下失效,那么在a情况下可以用B算法,在b情况下可以用A算法解决。这说明通过某种合适的方式把各种算法组合起来,可以提高准确率。
X=np.array([0,12,15,23,33,46,51,72,82,100]).reshape([10,1])
y=np.array([1,1,1,-1,-1,-1,1,1,1,-1])
M=15
W={}
weak={}
alpha={}
pred={}
def cal_W(self,W,alpha,y,pred): #更新权重所用函数,权重更新公式为w_new= w .e^(-a*y*pred)
ret=0
new_W=[]
for i in range(len(y)):
new_W.append(W[i]*np.exp(-alpha*y[i]*pred[i]))
return np.array(new_W/sum(new_W)).reshape([len(y),1])
#calculate error rate per iteration
def cal_e(self,y,pred,W): # 分类错误的样本权重相加作为错误率,每个样本都有不同的权重,上一次分错的样本有更高的权重,
ret=0 #因为各个样本权重之和为1,所以计算权重和即为错误率
for i in range(len(y)):
if y[i]!=pred[i]:
ret+=W[i]
return ret
#calculate alpha
def cal_alpha(self,e): #这个alpha 为每个分类器的系数,分类器误差率越低赋予分类器的系数当然越大
if e==0: # 这里有个很有意思的点,误差率为0就是个完美分类器,权重极大,碾压其他分类器
return 10000 # 而为什么错误率为0.5是最差的呢?因为如果低于0.5就直接反过来,你0.5就是个废物分类器,和我直接猜一个概率
elif e==0.5:
return 0.001
else:
return 0.5*np.log((1-e)/e)
#calculate final predict value
def cal_final_pred(self,i,alpha,weak,y):#每次循环都要用到这个函数,和上次循环得到的分类器及其权重相加
ret=np.array([0.0]*len(y))
for j in range(i+1):
ret+=alpha[j]*weak[j].pred
return np.sign(ret)
#calculate final error rate
def cal_final_e(self,y,cal_final_predict):#计算最终分类器的错误率
ret=0
for i in range(len(y)):
if y[i]!=cal_final_predict[i]:
ret+=1
return ret/len(y)
for i in range(M): #这四个变量都是字典,结果是
W.setdefault(i) #{0: None, 1: None, 2: None, 3: None, 4: None, 5: None, 6: None,
alpha.setdefault(i) #7: None, 8: None, 9: None, 10: None, 11: None,
weak.setdefault(i) #12: None, 13: None, 14: None}
pred.setdefault(i)
#per iteration (all:M times) 迭代次数M次
for i in range(M):
#for the first iteration,initial W
if i == 0:
W[i]=np.array([1]*len(y))/len(y) #还没开始的时候权重都为1,
W[i]=W[i].reshape([len(y),1])
#if not the first iteration,calculate new Weight
else:
W[i]=self.cal_W(W[i-1],alpha[i-1],y,pred[i-1])
#using train weak learner and get this learner predict value
self.weak[i]=WeakClassifier()
self.weak[i].fit(X,y,W[i])
pred[i]=self.weak[i].pred
#calculate error rate this iteration
e=self.cal_e(y,pred[i],W[i])
#calculate alpha this iteration
alpha[i]=self.cal_alpha(e)
#calculate the final predict value
cal_final_predict=self.cal_final_pred(i,alpha,self.weak,y)