1. 程式人生 > >機器學習小組知識點27:資料預處理之資料離散化(Data Discretization)

機器學習小組知識點27:資料預處理之資料離散化(Data Discretization)

離散化和概念分層產生

通過將屬性域劃分為區間,離散化技術可以用來減少給定連續屬性值的個數。區間的標號可以替代實際的資料值。如果使用基於判定樹的分類挖掘方法,減少屬性值的數量特別有好處。通常,這種方法是遞迴的,大量的時間花在每一步的資料排序上。因此,待排序的不同值越少,這種方法就應當越快。許多離散化技術都可以使用,以便提供屬性值的分層或多維劃分——概念分層

對於給定的數值屬性,概念分層定義了該屬性的一個離散化。通過收集並用較高層的概念(對
於年齡屬性,如young, middle-age 和senior)替換較低層的概念(如,年齡的數值值),概念分層可以用來歸約資料。通過這種泛化,儘管細節丟失了,但泛化後的資料更有意義、更容易解釋,並且所需的空間比原資料少。在歸約的資料上進行挖掘,與在大的、未泛化的資料上挖掘相比,所需的I/O 操作更少,並且更有效

對於使用者或領域專家,人工地定義概念分層可能是一項令人乏味、耗時的任務。幸而,許多分層蘊涵在資料庫模式中,並且可以在模式定義級定義。概念分層常常自動地產生,或根據資料分佈的統計分析動態地加以提煉。

數值屬性的概念分層可以根據資料分佈分析自動地構造。五種數值概念分層產生方法:分
箱、直方圖分析、聚類分析、基於熵的離散化和通過“自然劃分”的資料分段。

分箱

分箱方法。這些方法也是離散化形式。例如,通過將資料分佈到箱中,並用箱中的平均值或中值替換箱中的每個值,可以將屬性值離散化。就象用箱的平均值或箱的中值平滑一樣。這些技術可以遞迴地作用於結果劃分,產生概念分層。

直方圖分析

直方圖分析演算法遞迴地用於每一部分,自動地產生多級概念分層,直到到達一個預先設定的概念層數,過程終止。也可以對每一層使用最小區間長度來控制遞迴過程。最小區間長度設定每層每部分的最小寬度,或每層每部分中值的最少數目。

聚類分析

聚類演算法可以用來將資料劃分成聚類或群。每一個聚類形成概念分層的一個結點,而所有的結點在同一概念層。每一個聚類可以進一步分成若干子聚類,形成較低的概念層。聚類也可以聚集在一起,以形成分層結構中較高的概念層

基於熵的離散化

一種基於資訊的度量稱作熵,可以用來遞迴地劃分數值屬性A 的值,產生分層的離散化。這種離散化形成屬性的數值概念分層。給定一個數據元組的集合S,基於熵對A 離散化的方法如下:
A 的每個值可以認為是一個潛在的區間邊界或閾值T。例如,A 的值v 可以將樣本S劃分成分別滿足條件A<vAv 的兩個子集,這樣就建立了一個二元離散化。
給定S,所選擇的閾值是這樣的值,它使其後劃分得到的資訊增益最大。
其中,S

1S2 分別對應於S 中滿足條件A<TAT 的樣本。對於給定的集合,它的熵函式根據集合中樣本的類分佈來計算

演算法描述如下:

STEP 1:

(1)初始化分裂點集合為空,屬性所有取值集合為全集。

(2)按照屬性值的大小順序,將屬性值集合和對應的類標號集合排序。

(3)選擇最佳分類點(熵值最小)將屬性分類為兩個區間。

(4)遞迴處理所得到的兩個區間。

STEP 2:

(1)合併所有的相鄰、區間類資訊熵為0且區間裡屬性類別相同的區間。

(2)計算所有的相鄰區間合併後的區間類資訊嫡,合併計算得到的區間類資訊嫡最小且不超過閾值心的相鄰區間

(3)重複(2)直到不再滿足條件

具體實現如下:

#coding=utf-8
#以下為需要調節的引數
######################################################
ClassNum = 33 #設定類的總數,以生成統計不同類樣本數量
######################################################
FeatureNum = 145 #生成需要離散化的屬性陣列
#######################################################
InfoThreshold = 0.5 #熵值大於此值時不再合併
#######################################################
MaxGroup = 70 #最大離散化成多少組(分割點數加1)
#######################################################
MinGroup = 5 #最少離散化成多少組(分割點數加1)
#######################################################
BlockSize = 5 #抽樣大小,每BlockSize個數據抽取一個
#######################################################
MaxDeep = 7 #對大遞迴深度
#######################################################
MinSplit = 3000 #葉節點小於此值時不再分裂
#######################################################
MinLeaf = 100 #控制最小葉節點大小
#######################################################
NeedDisCol = [....] #需要離散化的維列表,預設維數從1開始(0對應id號)
######################################################
#訓練集及類標
fTrain = open("...")
fLabel = open("...") #轉化後的單列類標
######################################################
#寫入檔案
fDis = open("...", "a+")

#將NeedDisCol轉變為0開始
for i in range(len(NeedDisCol)):
 NeedDisCol[i] = NeedDisCol[i] - 1

from pandas import Series, DataFrame
import pandas as pd
import random
import math
import numpy as np

ItTrain = pd.read_csv(fTrain, chunksize = BlockSize)
ItLabel = pd.read_csv(fLabel, chunksize = BlockSize)

#定義同時迭代兩個檔案的函式,返回list
def getLine(It, rd):
 for df in It:
 aList = df.values[:, 1:].tolist() #去掉id列,返回指定行
 if rd >= len(aList):
 return aList[0]
 else:
 return aList[rd]
 return False

#抽取樣本資料
TrainSet = []
LabelSet = []
count = 0
while True:
 #產生隨機數,並更新已讀取檔案的行數
 rd = int(random.random() * BlockSize)
 count = count + BlockSize

 TrainList = getLine(ItTrain, rd)
 if not TrainList:
 break
 else:
 TrainSet.append(TrainList)
 LabelList = getLine(ItLabel, rd)
 if not LabelList:
 break
 else:
 LabelSet.append(LabelList)
 ########################################
 if count % 100000 == 0:
 print "讀取了%d行" % count
 ########################################



#將類標按照特徵升序排列
def SortTogether(theFeature , theLabel):
 arr = np.array(theFeature)
 NumSort = np.argsort(arr)
 #複製thelabel\theFeature
 copyOfFeature = [0]*len(theFeature)
 copyOfLabel = [0]*len(theLabel)
 for i in range(len(theFeature)):
 copyOfFeature[i] = theFeature[i]
 copyOfLabel[i] = theLabel[i]
 #按獲得的索引NumSort排序
 for i in range(len(theFeature)):
 theFeature[i] = copyOfFeature[NumSort[i]]
 theLabel[i] = copyOfLabel[NumSort[i]]


#將每個類標標上對應的tip(表示其(包含)前面的各類統計)
def TipTheLabel(theLabel):
 TipLabel = []
 for i in range(len(theLabel)):
 inTip = [0] * (ClassNum + 1) #從一開始
 TipLabel.append(inTip)
 TipLabel[0][theLabel[0]] = 1
 for i in range(1, len(theLabel)):
 for j in range(1, ClassNum + 1):
 TipLabel[i][j] = TipLabel[i - 1][j]
 TipLabel[i][theLabel[i]] = TipLabel[i][theLabel[i]] + 1
 return TipLabel


#獲取對應區間的類的數量列表
def GetPointNumList(theLabel, start, end, TipLabel):
 result = []
 if start >= end or start < 0 or end >= len(theLabel):
 return [0] * ClassNum
 for i in range(1,ClassNum + 1):
 classCounter = TipLabel[end][i] - TipLabel[start][i]
 result.append(classCounter)
 return result

#計算特點的熵值
def GetEntropyOfCertainPoint(theLabel, start, end, thePoint, TipLabel):
 if start == end:
 return 0
 LList = GetPointNumList(theLabel, start, thePoint - 1, TipLabel)
 RList = GetPointNumList(theLabel, thePoint, end, TipLabel)
 NL = float(thePoint - start)
 NR = float(end - thePoint + 1)
 EntropyL = 0
 EntropyR = 0
 for aClassNum in LList:
 if aClassNum == 0:
 continue
 else:
 p = aClassNum / NL
 EntropyL = EntropyL - p * math.log(p)
 for aClassNum in RList:
 if aClassNum == 0:
 continue
 else:
 p = aClassNum / NR
 EntropyR = EntropyR - p * math.log(p)
 result = (NL * EntropyL) / (NL + NR) + (NR * EntropyR) / (NL + NR)
 return result


#獲取有最大熵值的點,越小越好
def GetMaxEntropyPoint(theFeature, theLabel, start, end, TipLabel, ThisEntropy):
 MinPoint = start + MinLeaf
 MinEntropy = GetEntropyOfCertainPoint(theLabel, start, end, MinPoint, TipLabel)

 #i = MinPoint + 20
 #while True: 

 for i in range(MinPoint + 1, end - MinLeaf):
 theEntropy = GetEntropyOfCertainPoint(theLabel, start, end, i, TipLabel)
 if MinEntropy >= theEntropy:
 MinEntropy = theEntropy
 MinPoint = i
 #print "執行到球maxpoint", MaxPoint
 ThisEntropy[0] = MinEntropy
 return MinPoint

#合併左右分裂列表及本分裂點
def GetCombinList(LList, thePoint, RList):
 theList = []
 for aNum in LList:
 theList.append(aNum)
 theList.append(thePoint)
 for aNum in RList:
 theList.append(aNum)
 return theList

#遞迴離散化函式
def GetDisPointList(theFeature , theLabel, start, end, Deep, TipLabel):
 #大於最大遞迴深度,返回
 if Deep > MaxDeep:
 return []
 #小於最小分裂數,返回
 if end - start < MinSplit:
 return []
 #分割之前的資訊熵
 #BeforeEntropy = GetEntropyOfCertainPoint( theLabel, start, end, start + 1, TipLabel)
 #分割
 ThisEntropy = [0] #留作擴充套件
 thePoint = GetMaxEntropyPoint(theFeature, theLabel, start, end, TipLabel, ThisEntropy)
 LList = GetDisPointList(theFeature, theLabel, start, thePoint - 1, Deep + 1, TipLabel)
 RList = GetDisPointList(theFeature, theLabel, thePoint, end, Deep + 1, TipLabel)
 CombinList = GetCombinList(LList, thePoint, RList)
 return CombinList

#最大熵離散化函式
def DisACol(theFeature , theLabel, ToTipLabel): 
 SortTogether(theFeature, theLabel)
 TipLabel = TipTheLabel(theLabel)
 DisPointList = GetDisPointList(theFeature , theLabel, 0, len(theFeature) - 1, 0, TipLabel)
 #print 'DisACol'
 ToTipLabel[0] = TipLabel
 return DisPointList

#由分裂點下表獲取分裂點值
def GetDisNumFromPoint(theDisPointList, theFeature):
 result = []
 for aPoint in theDisPointList:
 result.append(theFeature[aPoint])
 return result

#獲取列對應的拷bei
def getCertainCol(theSet, theCol):
 result = []
 for item in theSet:
 result.append(item[theCol])
 return result

#刪除相同的點
def DelEqual(alist, theFeature):
 i = len(alist) - 1
 while True:
 if i<1:
 break
 #如果相鄰分割點對應的值相同,則刪除一個
 if theFeature[alist[i]] == theFeature[alist[i-1]]:
 del alist[i-1]
 i = i - 1
 return alist

#獲取列表中臨近分割區間的最小合併資訊熵
def GetMinSplitPoint(List, theLabel, MinEntropy, TipLabel):
 MinPoint = 0
 TheEntropy = GetEntropyOfCertainPoint(theLabel, 0, List[1], 1, TipLabel)
 for i in range(1, len(List)-1):
 iEntropy = GetEntropyOfCertainPoint(theLabel, List[i-1], List[i+1], List[i-1] + 1, TipLabel)
 if iEntropy < TheEntropy:
 MinPoint = i
 TheEntropy = iEntropy
 iEntropy = GetEntropyOfCertainPoint(theLabel, List[-2], len(theLabel) - 1, List[-2] + 1, TipLabel)
 if iEntropy < TheEntropy:
 MinPoint = len(List) - 1
 TheEntropy = iEntropy
 MinEntropy[0] = TheEntropy
 return MinPoint

#合併資訊熵小於閾值的區間InfoThreshold = 0.5,MinGroup = 20
def CombineResult(List, theLabel, TipLabel):
 MinEntropy = [0]
 while True:
 if len(List) <= MinGroup:
 break
 MinPoint = GetMinSplitPoint(List, theLabel, MinEntropy, TipLabel)
 if MinEntropy[0] >= InfoThreshold and len(List) < MaxGroup:
 break
 else:
 del List[MinPoint]
 print "結束合併,資訊熵= %f" % MinEntropy[0]
 return List

#逐列檢查,判斷離散點(用於分割離散區間)
SaveAllColDisPoint = []
for i in range(FeatureNum):
 newlist = [-100000]
 SaveAllColDisPoint.append(newlist)
for aCol in NeedDisCol:
 theFeature = getCertainCol(TrainSet, aCol) #深度拷貝
 theLabel = getCertainCol(LabelSet, 0)
 TipLabel = [0]
 theDisPointList = DisACol(theFeature , theLabel, TipLabel)
 TipLabel = TipLabel[0]
 theDisPointList = DelEqual(theDisPointList, theFeature)
 theDisPointList = CombineResult(theDisPointList, theLabel, TipLabel)
 theDisNumList = GetDisNumFromPoint(theDisPointList, theFeature)
 SaveAllColDisPoint[aCol] = theDisNumList
 print SaveAllColDisPoint[aCol]
 print "判斷%d列完成" % aCol

#由列表獲取寫入行
count = 1
def getLineFromList(aItem, count):
 theLine = 'X' + str(count) + ','
 for num in aItem:
 theLine = theLine + str(num) + ','
 return theLine[:-1] + '\n'

for aItem in SaveAllColDisPoint:
 fDis.write(getLineFromList(aItem, count))
 count = count + 1

fTrain.close()
fDis.close()
fLabel.close()

通過自然劃分分段

3-4-5 規則可以用於將數值資料劃分成相對一致、“自然的”區間。一般地,該規則根據最重要的數字上的值區域,遞迴地、逐層地將給定的資料區域劃分為3、4 或5 個等長的區間。該規則如下:
如果一個區間在最重要的數字上包含3、6、7 或9 個不同的值,則將該區間劃分成3 個區間(對於3、6 和9,劃分成3 個等寬的區間;而對於7,按2-3-2 分組,劃分成3 個區間);
如果它在最重要的數字上包含2、4 或8 個不同的值,則將區間劃分成4 個等寬的區間;
如果它在最重要的數字上包含1、5 或10 個不同的值,則將區間劃分成5 個等寬的區間。
該規則可以遞迴地用於每個區間,為給定的數值屬性建立概念分層。由於在資料集中可能有特別大的正值和負值,最高層分段簡單地按最小和最大值可能導致扭曲的結果。例如,在資產資料集中,少數人的資產可能比其他人高几個數量級。按照最高資產值分段可能導致高度傾斜的分層。這樣,頂層分段可以根據代表給定資料大多數的資料區間(例如,第5 個百分位數到第95 個百分位數)進行。越出頂層分段的特別高和特別低的值將用類似的方法形成單獨的區間。此處類似於分位數劃分區間。