决策树模型根据当前数据集 D D D和属性集 A A A不断选定最优划分属性 a ∗ a_* a∗,根据最优属性 a ∗ a_* a∗的每个取值 a ∗ v a_*^v a∗v产生一个分支子决策树,直到:
每个节点包含两个集合当前数据集 D D D和属性集 A A A,为了产生最优属性,一般希望 D D D尽可能属于同一类别,即纯度高,信息熵 E ( D ) = − ∑ p k l o g 2 p k E(D)=-\sum p_klog_2p_k E(D)=−∑pklog2pk(其中 p k p_k pk是第 k k k类样本的比例)小。利用属性 a a a来划分数据集得到取值为 a v a^v av的数据集 D v D^v Dv,可以计算信息增益 G a i n ( D , a ) = E ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E ( D v ) Gain(D,a)=E(D)-\sum_{v=1}^{V}\frac{|D^v|}{|D|}E(D^v) Gain(D,a)=E(D)−∑v=1V∣D∣∣Dv∣E(Dv)( V V V是 a a a取值的个数), I D 3 ID3 ID3决策树就是每次取信息增益最大的属性来划分。实际上信息增益对取值数目较多的属性有偏好(因为取值较多的那个属性产生的分支多,每个分支的类别更有可能纯度更高),这使得决策树不具有泛化能力, C 4.5 C4.5 C4.5还依据增益率选择划分属性 G a i n _ r a d i o ( D , a ) = G a i n ( D , a ) I V ( a ) Gain\_radio(D,a)=\frac{Gain(D,a)}{IV(a)} Gain_radio(D,a)=IV(a)Gain(D,a),其中 I V ( a ) IV(a) IV(a)是属性 a a a的固有值(intrinsic value),定义为 I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ l o g 2 ∣ D v ∣ ∣ D ∣ IV(a)=-\sum_{v=1}^{V}\frac{|D^v|}{|D|}log_2\frac{|D^v|}{|D|} IV(a)=−∑v=1V∣D∣∣Dv∣log2∣D∣∣Dv∣, a a a的取值数目越多这个值越大,信息增益率对取值数目较少的属性有偏好。实际 C 4.5 C4.5 C4.5使用了启发式:从 A A A中找到信息增益高于平均水平的属性,再从这些属性中找增益率最高的。 C A R T CART CART决策树利用基尼指数来选择属性: G i n i ( D ) = 1 − ∑ p k 2 Gini(D)=1-\sum p_k^2 Gini(D)=1−∑pk2,基尼值越小,数据集纯度越高, C A R T CART CART决策树使用基尼指数 G i n i _ i n d e x ( D , a ) = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ G i n i ( D v ) Gini\_index(D,a)=\sum_{v=1}^{V}\frac{|D^v|}{|D|}Gini(D^v) Gini_index(D,a)=∑v=1V∣D∣∣Dv∣Gini(Dv)最大的那个属性作为最优属性。
决策树的分支太多会过拟合,通过决策树生成过程的预剪枝(结点划分不能提升泛化性能就不再分支)和后剪枝(自底向上对非叶结点考察,如果替换成叶节点泛化性能提升,就替换为叶结点)。预剪枝是在每次分支之前判断用验证集判断一下评估指标,如果有提高就分支,否则直接定为叶结点,预剪枝基于当下的“贪心”策略,可能导致欠拟合,因为这次分支可能不能提升性能,但这次分支后再分支可能使性能大大提升。后剪枝通过自底向上尝试替换非叶结点,如果验证集精度提高就进行替换,这样做欠拟合风险很小,泛化能力也优于预剪枝,但后剪枝操作的训练时间大大多余预剪枝和决策树生成。
对于有连续值的属性,可以对 D D D中 a a a的取值排序,尝试每个划分点 T a = { a i + a i + 1 2 ∣ 1 ≤ i ≤ n − 1 } T_a=\{\frac{a^i+a^{i+1}}{2}|1\le i\le n-1\} Ta={2ai+ai+1∣1≤i≤n−1},其中 n n n是连续值属性 a a a在数据集 D D D上的取值个数,根据划分点二分离散化 D D D后,即可根据离散值的判断方法来选择最优属性,当前结点划分属性为连续属性,该属性还可以作为后代结点的划分属性,只是分割点不同
若某些样本的某些属性有缺失值,可以定义 D ~ \tilde D D~表示 D D D在属性 a a a上没有缺失的那些样本子集, D ~ v \tilde D^v D~v表示取值为 a = v a=v a=v的 D ~ \tilde D D~的样本子集, D ~ k \tilde D_k D~k表示分类为第 k k k类的 D ~ \tilde D D~的样本子集,为每个样本 x x x定义一个权重 w x w_x wx(初始化为1),并定义三个比例:
ρ = ∑ x ∈ D ~ w x ∑ x ∈ D w x p ~ k = ∑ x ∈ D ~ k w x ∑ x ∈ D ~ w x r ~ v = ∑ x ∈ D ~ v w x ∑ x ∈ D ~ w x \rho=\frac{\sum_{x\in\tilde D}w_x}{\sum_{x\in D}w_x}\\ \tilde p_k=\frac{\sum_{x\in\tilde D_k}w_x}{\sum_{x\in\tilde D}w_x}\\ \tilde r_v=\frac{\sum_{x\in\tilde D^v}w_x}{\sum_{x\in\tilde D}w_x} ρ=∑x∈Dwx∑x∈D~wxp~k=∑x∈D~wx∑x∈D~kwxr~v=∑x∈D~wx∑x∈D~vwx
即 ρ \rho ρ代表无缺失值样本的比例, p ~ k \tilde p_k p~k代表无缺失值样本中第 k k k类的比例, r ~ v \tilde r^v r~v代表无缺失值样本 a = a v a=a^v a=av的比例,信息增益即可推广为 G a i n ( D , a ) = ρ × G a i n ( D ~ , a ) = ρ × ( E ( D ~ ) − ∑ v = 1 V r ~ v ( D ~ v ) ) Gain(D,a)=\rho \times Gain(\tilde D,a)=\rho\times (E(\tilde D)-\sum_{v=1}^{V}\tilde r^v(\tilde D^v)) Gain(D,a)=ρ×Gain(D~,a)=ρ×(E(D~)−∑v=1Vr~v(D~v)),信息熵推广为 E ( D ~ ) = − ∑ p ~ k l o g 2 p ~ k E(\tilde D)=-\sum \tilde p_klog_2\tilde p_k E(D~)=−∑p~klog2p~k,这样即可在有缺失值的属性集上选择任意属性,若已经选择到了最优属性,分割数据集的时候 x x x在最优属性上没有缺失,就正常划分,若缺失了值,就把 x x x划分到所有分支数据集中,并且调整 x x x在这些取值为 v v v的分支中的权重为 w x = r ~ v w x w_x=\tilde r_vw_x wx=r~vwx,直观上看,这就是让同一个样本以不同的概率划分到不同子结点中
直观上看,这就是让同一个样本以不同的概率划分到不同子结点中 不理解什么意思
如果把每个属性视为一个维度坐标轴, d d d个属性描述的样本就对应了 d d d维空间中的一个点,每次产生分支实际是在寻找一个分类边界,而这个分类边界一定是与坐标轴平行的,对于复杂的问题,这样以坐标轴平行的分段来做出分割边界会导致时间开销很大,如果能采用斜的划分边界,就能使决策树模型大大简化,这样的决策树就是“多变量决策树”。这样的决策树每次划分数据集并不是以某个维度(即某个属性)的平行于坐标轴的横线(即具体取值)来做的,而是根据某些维度的线性组合来分割的,这样每个非叶结点是一个 ∑ i = 1 d w i a i = t \sum_{i=1}^dw_ia_i=t ∑i=1dwiai=t的线性分类器,根据这个分类器来划分节点就能产生斜的决策边界,其中 w i w_i wi和 t t t也是由训练得到的
for i in range(numFeatures): #iterate over all the features
featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
uniqueVals = set(featList) #get a set of unique values
newEntropy = 0.0
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
《机器学习实战》第三章利用信息熵增益选择最优特征,下面的代码递归创建决策树
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]#stop splitting when all of the classes are equal
if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
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[:] #copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
《机器学习实战》第九章构建 C A R T CART CART决策树,根据切分的误差最小来选择最优特征和阈值
bestS = inf; bestIndex = 0; bestValue = 0
for featIndex in range(n-1):
for splitVal in set(dataSet[:,featIndex]):
mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
newS = errType(mat0) + errType(mat1)
if newS < bestS:
bestIndex = featIndex
bestValue = splitVal
bestS = newS
没有实现预剪枝,而实现了后剪枝
def prune(tree, testData):
if shape(testData)[0] == 0: return getMean(tree) #if we have no test data collapse the tree
if (isTree(tree['right']) or isTree(tree['left'])):#if the branches are not trees try to prune them
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
if isTree(tree['left']): tree['left'] = prune(tree['left'], lSet)
if isTree(tree['right']): tree['right'] = prune(tree['right'], rSet)
#if they are now both leafs, see if we can merge them
if not isTree(tree['left']) and not isTree(tree['right']):
lSet, rSet = binSplitDataSet(testData, tree['spInd'], tree['spVal'])
errorNoMerge = sum(power(lSet[:,-1] - tree['left'],2)) +\
sum(power(rSet[:,-1] - tree['right'],2))
treeMean = (tree['left']+tree['right'])/2.0
errorMerge = sum(power(testData[:,-1] - treeMean,2))
if errorMerge < errorNoMerge:
print "merging"
return treeMean
else: return tree
else: return tree
对于回归树,可以在叶节点用线性模型代替常数,这样是“模型树”
def linearSolve(dataSet): #helper function used in two places
m,n = shape(dataSet)
X = mat(ones((m,n))); Y = mat(ones((m,1)))#create a copy of data with 1 in 0th postion
X[:,1:n] = dataSet[:,0:n-1]; Y = dataSet[:,-1]#and strip out Y
xTx = X.T*X
if linalg.det(xTx) == 0.0:
raise NameError('This matrix is singular, cannot do inverse,\n\
try increasing the second value of ops')
ws = xTx.I * (X.T * Y)
return ws,X,Y
def modelLeaf(dataSet):#create linear model and return coeficients
ws,X,Y = linearSolve(dataSet)
return ws
def modelErr(dataSet):
ws,X,Y = linearSolve(dataSet)
yHat = X * ws
return sum(power(Y - yHat,2))