1. 程式人生 > >《程式設計師的第一年》---------- 資料探勘之資料處理(C#基於熵的離散化演算法程式碼)

《程式設計師的第一年》---------- 資料探勘之資料處理(C#基於熵的離散化演算法程式碼)

熵(entropy)是最常用的離散化度量之一。它由Claude Shannon在資訊理論和資訊增益概念的開創性工作中首次引進。基於熵的離散化是一種監督的、自頂向下的分裂技術。它在計算和確定分裂點(劃分屬性區間的資料值)時利用類分佈資訊。為了離散數值屬性A,該方法選擇A的具有最小熵的值作為分裂點,並遞迴地劃分結果區間,得到分層離散化。這種離散化形成A的概念分層。

設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