DM&ML_note.1-Apriori

這個學期要學DM&ML,用的是《數據挖掘算法原理與實現》王振武 本着造福同學的思想,開一個DM&ML的筆記系列,打算給書上的源代碼添加一點註釋,方便閱讀和理解。


前置知識要求:

C++,STL,離散數學
這份算法的實現,大量使用了STL的容器和迭代器,對於不熟悉C++的同學,請順便了解什麼是命名空間(using namespace std;Apriori:: 之類的),稍微看一下用到的vector,map,pair容器大概是怎麼樣,瞭解下模版(vector

具體實現:

//書中原註釋直接給出,我加的註釋應該都加上了hiro:的字樣,僅供參考。
#include<iostream>
#include<string>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

class Apriori
{
public:
    Apriori(size_t is =0,unsigned int mv=0)
    {
        item_size = is;
        min_value = mv;
    }
    //~Apriori() {};
    void getItem();
    map< vector<string>,unsigned int> find_freitem();//求事務的頻繁項
    //連接連個k-1級頻繁項,得到第k級頻繁項
    map< vector<string>,unsigned int > apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item);
    //展示頻繁項集
    void showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap);
private:
    map< int , vector<string> > item;//存儲所有最開始的事務及其項
    map< vector<string>,unsigned int > K_item;//存儲頻繁項集
    size_t item_size;//事務數目
    unsigned  int min_value;//最小閾值
};

void Apriori::getItem()//用戶輸入最初的事務集
{
    int ci = item_size;
    for (int i=0;i<ci;i++)
    {
        string str;
        vector<string> temp;
        cout<<"請輸入第 "<<i+1<<"個事務的項集(123 end):";
        while (cin>>str && str !="123")
        {
            temp.push_back(str);
        }
        sort(temp.begin(),temp.end());

        //hiro:map 的鍵值對 用pair來存放,插入後返回迭代器和bool組成的pair
        pair< map<int ,vector<string> >::iterator , bool> ret = item.insert(make_pair(i+1 ,temp)); 
        if (!ret.second)
        {
            --i;
            cout<<"你輸入的元素已存在!請重新輸入!"<<endl;
        }
    }
    cout<<"-------------運行結果如下:--------------"<<endl;
}

map< vector<string>,unsigned int> Apriori::find_freitem()
{
    /*hiro:1.檢查初始的事務集非空*/
    unsigned int i = 1;
    bool isEmpty = false;
    map< int , vector<string> >::iterator mit ;
    for (mit=item.begin();mit != item.end();mit++)
    {
        vector<string> vec = mit->second;
        if (vec.size() != 0)
            break;
    }
    if (mit == item.end())//hiro:事務集爲空
    {
        isEmpty = true;
        cout<<"事務集爲空!程序無法進行..."<<endl;
        map< vector<string>,unsigned int> empty;
        return empty;//hiro:局部變量不會被吞掉嗎,,,,不是等價於NULL嗎
    }
    /*hiro:2.*/
    while(1)
    {
        map< vector<string>,unsigned int > K_itemTemp = K_item;

        K_item = apri_gen(i++,K_item);//hiro:將i傳進去函數以後,i變量本身才+1s,

        if (K_itemTemp == K_item)//hiro:篩選到最後,結束
        {
            i = UINT_MAX;
            break;
        }
        //判斷是否需要進行下一次的尋找
        map< vector<string>,unsigned int > pre_K_item = K_item;
        size_t Kitemsize = K_item.size();
        //存儲應該刪除的第K級頻繁項集,不能和其他K級頻繁項集構成第K+1級項集的集合
        /*hiro:看了一些資料,都沒有提及都下面的這個要刪除的項到底是什麼,但是根據上面的註釋以及代碼閱讀得知
        這裏是打算刪除K級項中,兩兩間交集的所有集合?*/
        if (Kitemsize != 1 && i != 1)
        {
            vector< map< vector<string>,unsigned int >::iterator > eraseVecMit;//hiro:保存要刪除的項的迭代器
            map< vector<string>,unsigned int >::iterator pre_K_item_it1 = pre_K_item.begin() , pre_K_item_it2;
            //hiro:這個謎之pre_K_item_it2,上下文好像沒有再次出現過。。。
            while (pre_K_item_it1 != pre_K_item.end() )
            {
                map< vector<string>,unsigned int >::iterator mit = pre_K_item_it1;
                bool isExist = true;
                vector<string> vec1;
                vec1 = pre_K_item_it1->first;
                vector<string> vec11(vec1.begin(), vec1.end() - 1);
                /*hiro:end()本身指向vector末尾,相當於 定義了 int a[5],然後a[5]就是這裏的end()
                / 而這個vector的構造器構造的範圍是[begin,end),所以vec11複製了vec1的最後一個元素以外所有元素,即
                /int a[5]={0,1,2,3,4},b={0,1,2,3}*/
                while (mit != pre_K_item.end())
                {
                    vector<string> vec2;
                    vec2 = mit->first;              
                    vector<string> vec22(vec2.begin(),vec2.end()-1);
                    if (vec11 == vec22)
                        break;
                    ++mit;
                }
                if (mit == pre_K_item.end())
                    isExist = false;
                if (!isExist && pre_K_item_it1 != pre_K_item.end())
                    eraseVecMit.push_back(pre_K_item_it1);//該第K級頻繁項應該刪除
                ++pre_K_item_it1;           
            }
            size_t eraseSetSize = eraseVecMit.size();
            if (eraseSetSize == Kitemsize)
                break;
            else
            {
                vector< map< vector<string>,unsigned int >::iterator >::iterator currentErs = eraseVecMit.begin();
                while (currentErs != eraseVecMit.end())//刪除所有應該刪除的第K級頻繁項
                {
                    map< vector<string>,unsigned int >::iterator eraseMit = *currentErs;
                    K_item.erase(eraseMit);
                    ++currentErs;
                }
            }
        }
        else
            if(Kitemsize == 1 )
                break;
    }
    cout<<endl;
    showAprioriItem(i,K_item);
    return K_item;
}

/*hiro:完成了連接和剪枝,返回符合支持度的
    1.求候選集C_K:把兩個K級頻繁項集在滿足連接條件的情況下連接起來
    2.統計K+1級候選頻繁項集的支持度並進行剪枝

*/
map< vector<string>,unsigned int > Apriori::apri_gen(unsigned int K , map< vector<string>,unsigned int > K_item)
{
    if (1 == K)//求候選集C1
    {
        size_t c1 = item_size;
        map< int , vector<string> >::iterator mapit = item.begin();
        vector<string> vec;
        map<string,unsigned int> c1_itemtemp;
        while (mapit != item.end() )//將原事務中所有的單項統計出來
        {

            vector<string> temp = mapit->second;
            vector<string>::iterator vecit = temp.begin();
            while (vecit != temp.end() )
            {
                pair< map<string,unsigned int>::iterator , bool > ret = c1_itemtemp.insert(make_pair(*vecit++ , 1));
                if (!ret.second)
                {
                    ++ ret.first->second;
                }
            }
            ++mapit;
        }
        map<string,unsigned int>::iterator item_it = c1_itemtemp.begin();
        map< vector<string>,unsigned int > c1_item;
        while (item_it != c1_itemtemp.end() )//構造第一級頻繁項集
        {
            vector<string> temp;
            if ( item_it->second >= min_value)
            {
                temp.push_back(item_it->first);
                c1_item.insert(make_pair(temp , item_it->second) );
            }
            ++item_it;
        }
        return c1_item;
    }
    else
    {
        /*  hiro:1.求候選集C_K:把兩個K級頻繁項集在滿足連接條件的情況下連接起來*/
        cout<<endl;     
        showAprioriItem(K-1,K_item);
        map< vector<string>,unsigned int >::iterator ck_item_it1 = K_item.begin(),ck_item_it2;
        map< vector<string>,unsigned int > ck_item;
        while (ck_item_it1 != K_item.end() )
        {
            ck_item_it2 = ck_item_it1;
            ++ck_item_it2;
            map< vector<string>,unsigned int >::iterator mit = ck_item_it2;

            //取當前第K級頻繁項與其後面的第K級頻繁項集聯合,但要注意聯合條件
            //聯合條件:連個頻繁項的前K-1項完全相同,只是第K項不同,然後兩個聯合生成第K+1級候選頻繁項
            while(mit != K_item.end() )
            {
                vector<string> vec,vec1,vec2;
                vec1 = ck_item_it1->first;
                vec2 = mit->first;
                vector<string>::iterator vit1,vit2;

                vit1 = vec1.begin();
                vit2 = vec2.begin();
                while (vit1 < vec1.end() && vit2 < vec2.end() )
                {
                    string str1 = *vit1;
                    string str2 = *vit2;
                    ++vit1;
                    ++vit2;
                    if ( K ==2 || str1 == str2 )
                    {
                        if (vit1 != vec1.end() && vit2 != vec2.end() )
                        {
                            vec.push_back(str1);
                        }

                    }
                    else
                        break;
                }
                if (vit1 == vec1.end() && vit2 == vec2.end() )
                {
                    --vit1;
                    --vit2;
                    string str1 = *vit1;
                    string str2 = *vit2;
                    /*hiro:新的候選項按字典序存放 所以>*/
                    if (str1>str2)
                    {
                        vec.push_back(str2);
                        vec.push_back(str1);
                    }
                    else
                    {
                        vec.push_back(str1);
                        vec.push_back(str2);
                    }
                    /*hiro:至此爲止生成了一個新的候選項*/
                    /*2.統計K + 1級候選頻繁項集的支持度並進行剪枝*/
                    map< int , vector<string> >::iterator base_item = item.begin();
                    unsigned int Acount = 0 ;
                    /*hiro:子集的判斷*/
                    while (base_item != item.end() )//統計該K+1級候選項在原事務集出現次數
                    {
                        unsigned int count = 0 ,mincount = UINT_MAX;
                        vector<string> vv = base_item->second;
                        vector<string>::iterator vecit , bvit ;
                        for (vecit = vec.begin();vecit < vec.end();vecit++)
                        {
                            string t = *vecit;
                            count = 0;
                            for (bvit=vv.begin();bvit < vv.end();bvit++)
                            {
                                if (t == *bvit)
                                    count++;
                            }
                            /*hiro:這裏的數據設計很巧妙,一旦count曾經爲0,即有一個不相符,min就永遠爲0*/
                            /*然而是不是應該有更優雅的方式,,,?*/
                            mincount = (count < mincount ? count : mincount );
                        }
                        if (mincount >=1 && mincount != UINT_MAX)
                            Acount += mincount;
                        ++base_item;
                    }
                    /*hiro:這裏的Acount直接解決了樣例數據中 {I1,I2,I3,I5},子集{I2,I3,I5}不是頻繁的問題,
                    貌似可以證明這裏用Acount就足夠判斷{I1,I2,I3,I5}的子集{I2,I3,I5}不是頻繁集
                    因爲K+1級是由兩個K級的集合組成的,這兩個K級的頻繁項集不能保證鏈接後的結果也是頻繁,
                    而造成連接後的結果不頻繁,也正是因爲有不頻繁的子集存在;
                    對不起水平有限,或許再理一理清思路應該可以證明的,但是【攤手】,只能表達個大概意思了。
                    其實就是書上37頁關於剪枝步的說明。
                     */
                    if (Acount >= min_value && Acount != 0)
                    {
                        sort(vec.begin(),vec.end());
                        //該第K+1級候選項爲頻繁項,插入頻繁項集
                        pair< map< vector<string>,unsigned int >::iterator , bool> ret = ck_item.insert(make_pair(vec,Acount));
                        if (! ret.second)
                        {
                            ret.first->second += Acount;
                        }
                    }
                }
                ++mit;
            }
            ++ck_item_it1;
        }
        if (ck_item.empty())//該第K+1級頻繁項集爲空,說明調用結束,把上一級頻繁項集返回
            return K_item;
        else
            return ck_item;
    }
}

void Apriori::showAprioriItem(unsigned int K,map< vector<string>,unsigned int > showmap)
{
    map< vector<string>,unsigned int >::iterator showit = showmap.begin();
    if (K != UINT_MAX)
        cout<<endl<<"第 "<<K<<" 級頻繁項集爲:"<<endl;
    else
        cout<<"最終的頻繁項集爲:"<<endl;    
    cout<<"項  集"<<"  \t  "<<"頻率"<<endl;
    while (showit != showmap.end() )
    {
        vector<string> vec = showit->first;
        vector<string>::iterator vecit = vec.begin();
        cout<<"{ ";
        while (vecit != vec.end())
        {
            cout<<*vecit<<"  ";
            ++vecit;
        }
        cout<<"}"<<"  \t  ";
        cout<<showit->second<<endl;
        ++showit;
    }
}

unsigned int parseNumber(const char * str)//對用戶輸入的數字進行判斷和轉換
{
    if (str == NULL)
        return 0;   
    else
    {
        unsigned int num = 0;
        //hiro:size_t類型  用於跨平臺,取該硬件平臺下最大的數值,比如32位取4B,64位取8B
        size_t len = strlen(str);
        for (size_t i=0;i<len;i++)
        {
            num *= 10;
            if (str[i]>= '0' && str[i] <= '9')
                num += str[i] - '0';
            else
                return 0;           
        }
        return num;
    }
}

void main()
{
    //Apriori a;
    unsigned int itemsize = 0;
    unsigned int min;

    /*hiro:爲何不直接cin>>??爲了無限長輸入整數?不明所以*/
    do 
    {
        cout<<"請輸入事務數:";
        char * str = new char;
        cin>>str;
        itemsize = parseNumber(str);
        if (itemsize == 0)
        {
            cout<<"請輸入大於0正整數!"<<endl;
        }
    } while (itemsize == 0);

    do 
    {
        cout<<"請輸入最小閾值:";
        char * str = new char;
        cin>>str;
        min = parseNumber(str);
        if (min == 0)
        {
            cout<<"請輸入大於0正整數!"<<endl;
        }
    } while (min == 0);


    Apriori a(itemsize,min);
    a.getItem();
    map< vector<string>,unsigned int> AprioriMap = a.find_freitem();
    //a.showAprioriItem(UINT_MAX,AprioriMap);
    system("pause");
}

感想

首先這份代碼目前還有一段不是很明白他的目的,爲何要刪掉兩兩間沒有交集的頻繁項?這個留着給老師發郵件解決,我會更新這個問題【如果還記得】
正式接觸DM&ML,之前大致瞭解過,知道是做一些數據的統計和分析的。但是這個書上的實現版本,說實話不敢恭維。如果設計一下數據結構的話,應該不至於把遍歷寫得如此的彆扭,源碼中使用了很大量的局部變量進行副本存檔,效率也是各種堪憂。。。
當然我自己編碼經驗不豐富,也可能是我不習慣這種編碼的風格吧。
算法本身的效率也是很成問題的,空間開銷巨大,時間開銷也是N^2以上的級別,應該是會有優化的方案的,我暫時還沒有去了解。
今天就先到這裏吧。

附樣例數據:

9
2
I1 I2 I5 123
I2 I4 123
I2 I3 123
I1 I2 I4 123
I1 I3 123
I2 I3 123
I1 I3 123
I1 I2 I3 I5 123
I1 I2 I3 123

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章