《程式設計師的第一年》---------- 資料探勘之資料處理(C#基於熵的離散化演算法程式碼)
設D由屬性集和類標號屬性定義的資料元組組成。類標號屬性提供每個元組的類資訊。該集合中屬性A的基於熵的離散化基本方法如下:
(1)A的每個值都可以看作一個劃分A的值域的潛在的區間邊界或分裂點(記作split_ point)。也就是說,A的分裂點可以將D中的元組劃分成分別滿足條件A≤split_point和A > split_point的兩個子集,這樣就建立了一個二元離散化。
(2)正如上面提到的,基於熵的離散化使用元組的類標號資訊。為了解釋基於熵的離散化的基本思想,必須考察一下分類。假定要根據屬性A和某分裂點上的劃分將D中的元組分類。理想地,希望該劃分導致元組的準確分類。例如,如果有兩個類,希望類C1的所有元組落入一個劃分,而類C2的所有元組落入另一個劃分。然而,這不大可能。例如,第一個劃分可能包含許多C1的元組,但也包含某些C2的元組。在該劃分之後,為了得到完全的分類,我們還需要多少資訊?這個量稱作基於A的劃分對D的元組分類的期望資訊需求,由下式給出
其中,D1和D2分別對應於D中滿足條件A≤split_point和A > split_point的元組,|D|是D中元組的個數,如此等等。給定集合的熵函式Entropy根據集合中元組的類分佈來計算。例如,給定
m個類C1, C2, ., Cm,D1的熵是m
其中,pi是D1中類Ci的概率,由D1中Ci類的元組數除以D1中的元組總數|D1|確定。這樣,在選擇屬性A的分裂點時,我們希望選擇產生最小期望資訊需求(即min(InfoA(D)))的屬性值。這將導致在用A≤split_point和A > split_point劃分之後,對元組完全分類(還)需要的期望資訊量最小。這等價於具有最大資訊增益的屬性-值對(這方面的進一步細節在第6章討論分類時給出)。注意,Entropy(D2)的值可以類似於式(2-16)計算。
你可能會說,“但是我們的任務是離散化,而不是分類!”是這樣。我們使用分裂點將A的值域劃分成兩個區間,對應於A≤split_point和A > split_point。
(3)確定分裂點的過程遞迴地用於所得到的每個分劃,直到滿足某個終止標準,如當所有候選分裂點上的最小資訊需求小於一個小閾值ε,或者當區間的個數大於閾值max_interval 時終止。
基於熵的離散化可以減少資料量。與迄今為止提到的其他方法不同,基於熵的離散化使用類資訊。這使得它更有可能將區間邊界(分裂點)定義在準確位置,有助於提高分類的準確性。這裡介紹的熵和資訊增益度量也用於決策樹歸納
C#程式碼實現如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Data.OleDb; using Excel = Microsoft.Office.Interop.Excel; namespace 連續值離散化 { /// <summary> /// 連續型資料離散化。 /// 要求: /// /// 輸入:兩個序列,一個是待離散化的資料序列(如支付延遲時間) /// 一個是資料對應的分類(如是否復購); /// /// 操作:基於熵做有監督離散化操作; /// /// 輸出:分割點 /// /// </summary> public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } private List<Data> dataAll = new List<Data>(); private List<double> SaveTi ; private void btnOk_Click(object sender, EventArgs e) { SaveTi = new List<double>(); this.listBox2.Items.Clear(); //步驟 //1 根據屬性A值大小對資料集進行升序排序 //資料集 List<Data> datas = new List<Data>(); for (int j = 0, l = dataAll.Count ; j < l; j++) { Data tempData = new Data(); tempData.id = j; tempData.myClass = dataAll[j].myClass; tempData.value = dataAll[j].value; datas.Add(tempData); } #region 新增 測試 資料 //for (int i = 0; i < 5; i++) //{ // Data tempData = new Data(); // Random rd = new Random(); // string Myclass = "T"; // if (i % 3 == 0) // { // Myclass = "F"; // } // tempData.id = i; // tempData.myClass = Myclass; // tempData.value = i + 1; // //tempData.value = rd.Next(10) * (i + 1); // //if (i < 2) // //{ // // tempData.value = 1;//rd.Next(10) * (i + 1) // //} // listBox1.Items.Add(tempData.value); // datas.Add(tempData); //} #endregion int counts = datas.Count;//樣本總數 try { this.Print(GetTiByRecursionData(datas)); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } private List<Data> Sort(List<Data> datas) { listBox1.Items.Clear(); //升序排序 ,對datas類資料集中的屬性A的所有取值從小到大進行排序,不妨設得到的序列為:a1 ,a2,... for (int i = 0,l=datas.Count; i < l; i++) { for (int j = l - 1; i < j; j--) { if (datas[i].value > datas[j].value) { Data tempData = new Data(); tempData = datas[j]; datas[j] = datas[i]; datas[i] = tempData; } } } for (int j = 0, l = datas.Count; j < l; j++) { listBox1.Items.Add(datas[j].value); } return datas; } /// <summary> /// 取得Ti並做遞迴操作 /// </summary> /// <param name="datas"></param> private string GetTiByRecursionData(List<Data> datas) { int counts = datas.Count;//樣本總數 if (counts < 1 ) { return ""; } //dataTi 為Ti為候選分割點 資料集 List<double> dataTi = new List<double>(); for (int i = 0; i < counts; i++) { if (i < counts - 1) { dataTi.Add((double)(datas[i].value + datas[i + 1].value) / 2); } } ////計算每次劃分的資訊增益 Gain(A,T)=I(S1,S2,...Sm)-Ent(A,T) //List<double> gainList = new List<double>(); #region 選擇Ti,使得將其作為分割點劃分S後的熵最小 //最小熵值 double minEnt = -1; //最小熵值時的Ti double isMinEntTi = -1; for (int i = 0, l = dataTi.Count; i < l; i++) { double tempTi = dataTi[i]; //熵的計算 double tempMinEnt= Ent(datas, tempTi); if (minEnt > tempMinEnt || i==0) { minEnt = tempMinEnt; isMinEntTi = tempTi; } } if (SaveTi.Contains(isMinEntTi)) { return ""; } else { SaveTi.Add(isMinEntTi); } Dictionary<string, double> classDtAll = new Dictionary<string, double>(); //取得兩個劃分 List<Data> dataTiLeft = new List<Data>(); List<Data> dataTiRight = new List<Data>(); #region 取兩個劃分 資料集,所有樣本都在這兩個劃分中 for (int i = 0; i < counts; i++) { if (datas[i].value <= isMinEntTi) { dataTiLeft.Add(datas[i]); } else { dataTiRight.Add(datas[i]); } this.AddDictionaryValue(classDtAll, datas[i].myClass); } #endregion if (classDtAll.Count<=1) { return datas[counts-1].value.ToString()+" "; } //計算D(I)= f(I)/L(I) , f(I)=|S| double L_Left=0; double L_Right = 0; double D_I_Left = 0; double D_I_Right = 0; if (dataTiLeft.Count > 0) { L_Left = dataTiLeft[dataTiLeft.Count - 1].value - dataTiLeft[0].value + 1; D_I_Left = (double)dataTiLeft.Count / L_Left; } if (dataTiRight.Count > 0) { L_Right = dataTiRight[dataTiRight.Count - 1].value - dataTiRight[0].value + 1; D_I_Right = (double)dataTiRight.Count / L_Right; } //定thresholdValue0 =E(S) double thresholdValue0 = this.E(classDtAll, counts); //自動計算閥值 double thresholdValueLeft = D_I_Left * thresholdValue0; double thresholdValueRight = D_I_Right * thresholdValue0; string temp=string.Empty; //手動設定閾值 double thresholdValue = Convert.ToDouble(this.txt_gz.Text); if (!this.checkBox1.Checked) { thresholdValue = thresholdValueLeft; } if (minEnt > thresholdValue) { temp += this.GetTiByRecursionData(dataTiLeft) + " "; } if (!this.checkBox1.Checked) { thresholdValue = thresholdValueRight; } if (minEnt > thresholdValue) { temp += this.GetTiByRecursionData(dataTiRight) + " "; } if (temp!=string.Empty) { return temp; } else { return isMinEntTi.ToString() + " "; //Ent 為1時兩劃分中的屬性屬於各個不同類 ,為0時兩劃分中的屬性屬於相同類 } #endregion } /// <summary> /// 列印排序好的分割點 /// </summary> /// <param name="Ti"></param> private void Print(string Ti) { string[] arr = Ti.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0, l = arr.Length; i < l; i++) { for (int j = arr.Length-1; j >= i; j--) { if (Convert.ToDouble(arr[i]) > Convert.ToDouble(arr[j])) { string temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } foreach (string item in arr) { listBox2.Items.Add(item); } } /// <summary> /// 熵的計算 /// </summary> /// <param name="datas"></param> /// <param name="p"></param> private double Ent(List<Data> datas, double T) { //取得分類資料集 與 當前分類包涵的屬性A的樣本個數 Dictionary<string, double> classDtLeft = new Dictionary<string, double>(); Dictionary<string, double> classDtRight = new Dictionary<string, double>(); //Dictionary<string, double> classDtAll = new Dictionary<string, double>(); int counts = datas.Count; //取得兩個劃分 List<Data> dataTiLeft = new List<Data>(); List<Data> dataTiRight = new List<Data>(); #region 取兩個劃分 資料集,所有樣本都在這兩個劃分中 for (int i = 0; i < counts; i++) { if (datas[i].value <= T) { dataTiLeft.Add(datas[i]); this.AddDictionaryValue(classDtLeft, datas[i].myClass); } else { dataTiRight.Add(datas[i]); this.AddDictionaryValue(classDtRight, datas[i].myClass); } //this.AddDictionaryValue(classDtAll, datas[i].myClass); } #endregion //取得兩劃分的樣本個數 int countLeft = dataTiLeft.Count; int countRight = dataTiRight.Count; //計算兩劃分的期望值 //P_k_l為類別l在子集S_k中的概率 (k為引數 如 S_1 =左劃分,S_2 =右劃分) double expectLeft = this.E(classDtLeft, countLeft); double expectRight = this.E(classDtRight, countRight); double returnEnt = ((double)countLeft / counts) * expectLeft + ((double)countRight / counts) * expectRight; return returnEnt; } /// <summary> /// 新增值到Dictionary資料集內 /// </summary> /// <param name="classDt"></param> /// <param name="myClass"></param> private void AddDictionaryValue(Dictionary<string, double> classDt, string myClass) { if (classDt.ContainsKey(myClass)) { classDt[myClass] = classDt[myClass] + 1; } else { classDt.Add(myClass, 1); } } /// <summary> /// 計算集合內的期望值 /// </summary> /// <param name="classDt">樣本內部類別 資料集</param> /// <param name="sampleCount">劃分後的單個劃分樣本總數</param> /// <returns>當前劃分的期望值</returns> private double E(Dictionary<string, double> classDt, int sampleCount) { //計算兩劃分的期望值 //P_k_l為類別l在子集S_k中的概率 (k為引數 如 S_1 =左劃分) double expect = 0; foreach (KeyValuePair<string, double> item in classDt) { //概率 double tempProbability = item.Value / sampleCount; double tempExpect = tempProbability * Math.Log(tempProbability, 2); expect += tempExpect; } return -expect; } private void btn_InputExecel_Click(object sender, EventArgs e) { System.Windows.Forms.OpenFileDialog ofp = new OpenFileDialog(); ofp.DefaultExt = "xls"; ofp.Filter = "Excel檔案 (*.xlsx)|*.xlsx|(*.xls)|*.xls"; if (ofp.ShowDialog()==System.Windows.Forms.DialogResult.OK) { this.txt_InputExecel.Text=ofp.FileName; } } private void Import(string fileName) { List<string> columns = new List<string>(20); columns.Add(this.txt_Class.Text); columns.Add(this.txt_Number.Text); Excel.Application app = new Excel.Application(); Excel.Workbook book = app.Workbooks.Open(fileName); Excel.Worksheet srcSheet = (Excel.Worksheet)book.Sheets[this.txt_Sheet.Text]; int rows = Convert.ToInt32( this.txt_rows.Text); for (int i = 2; i <= rows+1; i++) { Data tempData = new Data(); Excel.Range srcRange = srcSheet.get_Range(columns[0] + i, Type.Missing); Excel.Range srcRange1 = srcSheet.get_Range(columns[1] + i, Type.Missing); tempData.myClass = Convert.ToString(srcRange.Value); tempData.value = Convert.ToDouble(srcRange1.Value); tempData.id = i; if (tempData.myClass==null) { break; } dataAll.Add(tempData); } app.ActiveWorkbook.Close(); } /// <summary> /// 匯入資料並排序 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_inputE_Click(object sender, EventArgs e) { this.listBox1.Items.Clear(); dataAll.Clear(); if (this.txt_Class.Text != "" && this.txt_InputExecel.Text != "" && this.txt_Number.Text != "" && this.txt_rows.Text != "" && this.txt_Sheet.Text != "") { try { Import(this.txt_InputExecel.Text); } catch (Exception ex) { MessageBox.Show(ex.ToString()); return; } dataAll = this.Sort(dataAll); //for (int j = 0, l = dataAll.Count; j < l; j++) //{ // listBox1.Items.Add(dataAll[j].value); //} } else { MessageBox.Show("請設定完引數再匯入"); return; } } private void btn_sort_Click(object sender, EventArgs e) { dataAll = this.Sort(dataAll); } private void checkBox1_CheckedChanged(object sender, EventArgs e) { CheckBox cb = (CheckBox)sender; if (cb.Checked) { this.txt_gz.Enabled = true; } else { this.txt_gz.Enabled = false; } } } }
還要優化,請讀者多加說說看法。小弟在此謝過
來自無憂返利網資料探勘編者:http://www.5ufanli.net