日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > python >内容正文

python

python mpi开销_GitHub - hustpython/MPIK-Means

發(fā)布時(shí)間:2025/3/11 python 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python mpi开销_GitHub - hustpython/MPIK-Means 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

并行計(jì)算的K-Means聚類算法實(shí)現(xiàn)

一,實(shí)驗(yàn)介紹

聚類是擁有相同屬性的對象或記錄的集合,屬于無監(jiān)督學(xué)習(xí),K-Means聚類算法是其中較為簡單的聚類算法之一,具有易理解,運(yùn)算深度塊的特點(diǎn).

1.1 實(shí)驗(yàn)內(nèi)容

通過本次課程我們將使用C++語言實(shí)現(xiàn)一個(gè)完整的面向?qū)ο蟮目刹⑿蠯-Means算法.這里我們一起圍繞著算法需求實(shí)現(xiàn)各種類,最終打造出一個(gè)健壯的程序.所以為了更好地完成這個(gè)實(shí)驗(yàn),需要你有C++語言基礎(chǔ),會(huì)安裝一些常用庫,喜歡或愿意學(xué)習(xí)面向?qū)ο蟮木幊趟季S.

1.2 實(shí)驗(yàn)知識(shí)點(diǎn)

C++語言語法

K-Means算法思路與實(shí)現(xiàn)

并行計(jì)算思路與實(shí)現(xiàn)

boost庫的常用技巧(Smart Pointers,Variant,tokenizer)

1.3 實(shí)驗(yàn)環(huán)境

Xfce 終端(Xfce Terminal):

Linux 命令行終端,打開后會(huì)進(jìn)入 Bash 環(huán)境,可以用來執(zhí)行 Linux 命令和調(diào)用系統(tǒng)調(diào)用.

GVim:非常好用的編輯器,不會(huì)使用的可以參考課程 《Vim編輯器》.

boost,MPICH2庫

1.4 適合人群

本課程適合有C++語言基礎(chǔ),對聚類算法感興趣并希望在動(dòng)手能力上得到提升的同學(xué).

1.5 代碼獲取

1.6 效果圖

完成時(shí)間顯示:

單進(jìn)程

completed in 31.9997 seconds

number of processes: 1

4進(jìn)程

completed in 9.87732 seconds

number of processes: 4

輸出結(jié)果文件

圖1 輸出文件圖

1.7 項(xiàng)目結(jié)構(gòu)與框架

項(xiàng)目的整個(gè)文件目錄:

├── clusters

│ ├── distance.hpp

│ └── record.hpp

├── datasets

│ ├── attrinfo.hpp

│ ├── dataset.hpp

│ └── dcattrinfo.hpp

├── mainalgorithm

│ ├── kmean.hpp

│ └── kmeanmain.cpp

└── utilities

├── datasetreader.hpp

├── exceptions.hpp

├── null.hpp

└── types.hpp

這里簡單介紹一下功能模塊,在具體實(shí)踐每一個(gè)類的時(shí)候會(huì)有詳細(xì)UML圖或流程圖.

主要分為4個(gè)模塊:數(shù)據(jù)集類,聚集類,實(shí)用工具類,算法類.

實(shí)用工具類:定義各種需要的數(shù)據(jù)類型;常用的異常處理;文件讀取.

數(shù)據(jù)集類:將文件中的數(shù)據(jù)通過智能指針建立一個(gè)統(tǒng)一數(shù)據(jù)類,擁有豐富的屬性和操作.

聚集類:在數(shù)據(jù)類基礎(chǔ)上實(shí)現(xiàn)中心簇.

算法類:完成對聚集類的初始化,通過算法進(jìn)行更新迭代,最終實(shí)現(xiàn)數(shù)據(jù)集的聚類并輸出聚類結(jié)果.

二, 實(shí)驗(yàn)原理

這一章我們將配置好我們的實(shí)驗(yàn)環(huán)境并介紹一些基礎(chǔ)知識(shí).

2.1 依賴庫安裝

安裝boost和mpich2

mpich2下載:

wget -c http://www.mpich.org/static/downloads/3.2.1/mpich-3.2.1.tar.gz

解壓:

tar xvfz mpich-3.2.1.tar.gz

配置:

cd mpich-3.2.1

./configure

編譯:

make

安裝:

make install

boost下載:

wget -c https://dl.bintray.com/boostorg/release/1.68.0/source/boost_1_68_0.tar.gz

解壓

tar xvfz boost_1_68_0.tar.gz

cd boost_1_68_0

編譯:

sh bootstrap.sh

修改project-config.jam 文件

第24行添加一句:using mpi ;(注意中間的空格)

安裝:

sudo ./bjam --with-program_options --with-mpi install

mpi支持的進(jìn)程數(shù)和計(jì)算機(jī)的配置有關(guān),通過cat /proc/cpuinfo |grep "processor"|wc -l命令,可以查看得知實(shí)驗(yàn)樓支持4個(gè)進(jìn)程.

檢驗(yàn)boost是否安裝成功,可以檢測一下,這里我們測試開啟3個(gè)進(jìn)程:

運(yùn)行源碼,test/mpitest.cpp

mpic++ -o mpitest mpitest.cpp -L/usr/local/lib -lboost_mpi -lboost_serialization

mpirun -n 3 ./mpitest

若結(jié)果如下,有三個(gè)Process則證明安裝成功!

Process 1: a msg from master

Process 2: a msg from master

Process 2:

Process 1:

Process 0: zero one two

Process 0: zero one two

Process 1: zero one two

Process 2: zero one two

2.2 boost的小技巧

Smart Pointers

在Boost中,智能指針是存儲(chǔ)指向動(dòng)態(tài)分配對象的指針的對象.智能指針非常有用,因?yàn)樗鼈兇_保正確銷毀動(dòng)態(tài)分配的對象,即使在異常情況下也是如此.事實(shí)上,智能指針被視為擁有指向的對象,因此負(fù)責(zé)在不再需要時(shí)刪除對象.Boost智能指針庫提供了六個(gè)智能指針類模板.表給出了這些類模板的描述.本實(shí)驗(yàn)中將大量使用智能指針.

描述

scoped_ptr

單個(gè)對象的簡單唯一所有權(quán),不可復(fù)制.

scoped_array

數(shù)組的簡單唯一所有權(quán).不可復(fù)制

shared_ptr

對象所有權(quán)在多個(gè)指針之間共享

shared_array

多個(gè)指針共享的數(shù)組所有權(quán)

weak_ptr

shared_ptr擁有的對象的非擁有觀察者

intrusive_ptr

具有嵌入引用計(jì)數(shù)的對象的共享所有權(quán).

表1 智能指針類型簡介

Variant versus Any

Boost Variant類模板是一個(gè)安全通用的聯(lián)合容器,和std::vector不同儲(chǔ)存單個(gè)類型的多個(gè)值,variant可以儲(chǔ)存多個(gè)類型的單個(gè)值,本實(shí)驗(yàn)中將使用variant儲(chǔ)存雙精度和整數(shù)類型來表示不同類型的數(shù)據(jù).

與variant一樣,Boost any是另一個(gè)異構(gòu)容器.雖然Boost anys有許多與Boost variant相同的功能.

根據(jù)Boost庫文檔,Boost variant比Boost any具有以下優(yōu)勢:

1,variant保證其內(nèi)容的類型是用戶指定的有限類型集之一.

2,variant提供對其內(nèi)容的編譯時(shí)檢查訪問.

3,variant通過提供有效的,基于堆棧的存儲(chǔ)方案,可以避免動(dòng)態(tài)分配的開銷.

同樣Boost any也有一些優(yōu)勢:

1,any幾乎允許任何類型的內(nèi)容.

2,很少使用模板元編程技術(shù).

3,any 對交換操作提供安全的不拋出異常保證.

Tokenizer

Tokenizer提供了一種靈活而簡單的方法通過分割符(如:" , ")將一個(gè)完整的string分隔開.

字符串為:”A flexible,easy tokenizer“

如果通過","分割,則結(jié)果為:

[A flexible] [ easy tokenizer>]

以" " 為分隔符:

分割結(jié)果為:

[A] [flexible,] [easy] [tokenizer]

三,實(shí)驗(yàn)步驟

接下來將具體實(shí)踐各個(gè)類,會(huì)給出每一個(gè)類的聲明并解釋其成員函數(shù)和數(shù)據(jù)成員以及相關(guān)聯(lián)類之間的繼承關(guān)系和邏輯關(guān)系.涉及到重要的成員函數(shù)的實(shí)現(xiàn)會(huì)給出其定義代碼,一些普通的成員函數(shù)的源碼可以到下載的源文件中查看,里面也會(huì)有詳細(xì)的注解.

3.1 數(shù)據(jù)集的構(gòu)建

數(shù)據(jù)對于一個(gè)聚類算法來說非常重要,在這里我們將一個(gè)數(shù)據(jù)集描述為一個(gè)記錄(record),一個(gè)記錄由一些屬性(Attribute)表征.因此自然而然將依次建立attributes,records,最后是數(shù)據(jù)集datasets.

在此之前我們需要了解一下我們在聚類中實(shí)際接觸到的數(shù)據(jù)類型.

這里有一個(gè)示例,心臟數(shù)據(jù)集.

//heart.data

70.0,1.0,4.0,130.0,322.0,0.0,2.0,109.0,0.0,2.4,2.0,3.0,3.0,2

67.0,0.0,3.0,115.0,564.0,0.0,2.0,160.0,0.0,1.6,2.0,0.0,7.0,1

57.0,1.0,2.0,124.0,261.0,0.0,0.0,141.0,0.0,0.3,1.0,0.0,7.0,2

64.0,1.0,4.0,128.0,263.0,0.0,0.0,105.0,1.0,0.2,2.0,1.0,7.0,1

74.0,0.0,2.0,120.0,269.0,0.0,2.0,121.0,1.0,0.2,1.0,1.0,3.0,1

65.0,1.0,4.0,120.0,177.0,0.0,0.0,140.0,0.0,0.4,1.0,0.0,7.0,1

......

包含13個(gè)屬性,age,sex,chest pain type(4 values),resting blood pressure......

為了更好地表述不同數(shù)據(jù)相同屬性的差異,我們需要對這些數(shù)據(jù)進(jìn)行離散/連續(xù)處理,即對于有些數(shù)據(jù)我們認(rèn)為它是連續(xù)的如:age,有些是離散的如:年齡.這樣我們建立一個(gè)描述數(shù)據(jù)類型的文件:

//heart.names

schema file for heart.dat

///: schema

1, Continuous

2, Discrete

3, Discrete

4, Continuous

5, Continuous

6, Discrete

7, Discrete

8, Continuous

9, Discrete

10, Continuous

11, Discrete

12, Continuous

13, Discrete

14, Class

3.1.1 AttrValue類

AttrValue類有一個(gè)私有變量,有兩個(gè)友元函數(shù),一個(gè)公有成員函數(shù).

_value是一個(gè)variant類型變量,它可以存儲(chǔ)一個(gè)雙精度或無符號整形的數(shù)據(jù),分類數(shù)據(jù)用無符號整形數(shù)據(jù)表示.

AttrValue類自身無法存儲(chǔ)或獲取數(shù)據(jù).它的兩個(gè)友元函數(shù)可以獲取和修改數(shù)據(jù)_value.

圖2 數(shù)據(jù)類UML關(guān)系圖

//source:datasets.attrinfo.hpp

class AttrValue

{

public:

friend class DAttrInfo;//友元函數(shù)可以訪問_value

friend class CAttrInfo;//友元函數(shù)可以訪問_value

typedef boost::variant value_type;//可存儲(chǔ)雙精度和無符號整形數(shù)據(jù)

AttrValue();

private:

value_type _value;

};

inline AttrValue::AttrValue(): _value(Null()) {

}//構(gòu)造函數(shù),將_value初始化為Null(定義在utillities/null.hpp中)

3.1.2 AttrInfo類

AttrInfo是一個(gè)基類,包括了許多虛函數(shù)和純虛函數(shù).這些函數(shù)都將在它的派生類中具體實(shí)現(xiàn),基類中僅進(jìn)行聲明和簡單定義.

//source:datasets.attrinfo.hpp

//三種數(shù)據(jù)類型:未知型,連續(xù)型(雙精度),離散型(無符號整形)

enum AttrType

{

Unknow,

Continuous,

Discrete

};

class DAttrInfo;

class CAttrInfo;

class AttrInfo

{

public:

AttrInfo(const std::string &name,AttrType type);//每一欄的屬性名(id,attr,label,...)和該屬性的數(shù)據(jù)類型(離散或連續(xù))

virtual ~AttrInfo(){}//虛析構(gòu)函數(shù)

std::string &name();//返回標(biāo)簽

AttrType type() const;//返回?cái)?shù)據(jù)類型

virtual Real distance(const AttrValue&,const AttrValue&) const = 0;

virtual void set_d_val(AttrValue&, Size) const;//AttrValue賦值;適用于DAttrInfo

virtual Size get_d_val(const AttrValue&) const;//獲取_value

virtual void set_c_val(AttrValue&, Real) const;//AttrValue賦值;適用于CAttrInfo

virtual Real get_c_val(const AttrValue&) const;//獲取_value

virtual bool can_cast_to_d() const;//布爾值,對于DAttrInfo類來說其返回值為true,相反為false.在基類的聲明中全部初始化為false.

virtual bool can_cast_to_c() const;

virtual DAttrInfo& cast_to_d();//返回DAttrInfo本身

virtual bool is_unknown(const AttrValue&) const = 0;

virtual void set_unknown(AttrValue&) const = 0;

private:

std::string _name;

AttrType _type;

};

3.1.3 CAttrInfo類和DAttrInfo類

CAttrInfo主要是用來表示連續(xù)型數(shù)據(jù)的一些屬性和方法.有兩個(gè)數(shù)據(jù)成員:_min和_max.表示最小值和最大值屬性,在初始化時(shí)都將設(shè)置為Null .這兩個(gè)屬性將在歸一化的時(shí)候用到.CAttrInfo將會(huì)繼承AttrInfo的一些函數(shù),并且重新定義.

//source:datasets/dcattrinfo.hpp

class CAttrInfo: public AttrInfo

{

public:

CAttrInfo(const std::string& name);//構(gòu)造函數(shù)

Real distance(const AttrValue&,const AttrValue&)const;//兩個(gè)距離

void set_c_val(AttrValue &, Real) const;

void set_min(Real);//設(shè)置最小值

void set_max(Real);//設(shè)置最大值

Real get_min() const;//獲取最小值

Real get_max() const;//獲取最大值

Real get_c_val(const AttrValue&) const;

bool is_unknown(const AttrValue&) const;

bool can_cast_to_c() const;

void set_unknown(AttrValue&) const;

protected:

Real _min;

Real _max;

};

CAttrInfo::CAttrInfo(const std::string& name)

: AttrInfo(name, Continuous) {

_min = Null();

_max = Null();

}

DAttrInfo類有一個(gè)私有變量_values,它是一個(gè)string類型的vector,用來存儲(chǔ)一些離散的字符串.在DAttrInfo對象中所有的離散值都將由字符串轉(zhuǎn)化為唯一的無符號整形.

//source:datasets/dcattrinfo.hpp

class DAttrInfo: public AttrInfo //繼承AttrInfo

{

public:

DAttrInfo(const std::string& name);//構(gòu)造函數(shù),傳入屬性字符串

const std::string& int_to_str(Size i) const;

Size num_values() const;//獲取長度

Size get_d_val(const AttrValue&) const; //接口定義

void set_d_val(AttrValue& , Size)const;//接口定義

Size add_value(const std::string&,

bool bAllowDuplicate = true);//將一組離散值加入到_values中,比如“X,X,Y,Z",

//則values=[X,Y,Z],對應(yīng)的二進(jìn)制數(shù)字為[0,0,1,2]

//對于屬性值,則可以重復(fù),但對于id則具有唯一性,不能重復(fù)

DAttrInfo& cast_to_d();

Real distance(const AttrValue&, const AttrValue&) const; //比較兩個(gè)離散型變量的距離

bool is_unknown(const AttrValue& av) const;//值有缺省

bool can_cast_to_d() const;

void set_unknown(AttrValue&) const;

protected:

std::vector<:string> _values;

};

add_value 是一個(gè)將字符串轉(zhuǎn)化為無符號整形數(shù)據(jù)的重要函數(shù),返回值為該字符所表示的整形,并將為出現(xiàn)的字符添加進(jìn)_values.

Record

Attribute

AttrValue

1

"A"

0

2

"B"

1

3

"A"

0

4

"C"

2

5

"B"

1

Record

Attribute

0

"A"

1

"B"

2

"C"

表2 DAttrInfo的一個(gè)具體實(shí)例

通過上面表格中我們可以看到一組字符類型的數(shù)據(jù)被存儲(chǔ)為該字符串所在的inex,如果該字符串第一次出現(xiàn)則為上一個(gè)字符串的index+1.這樣相同的字符串都被轉(zhuǎn)化為唯一的無符號整形._value這個(gè)輔助變量可以幫助實(shí)現(xiàn)這一功能.

//source:datasets/dcattrinfo.hpp

Size DAttrInfo::add_value(const std::string& s,

bool bAllowDuplicate) {

Size ind = Null();

//如果該字符串已經(jīng)出現(xiàn),則返回該字符串在_values中的index

for(Size i=0;i<_values.size>

if(_values[i] == s) {

ind = i;

break;

}

}

//如果未出現(xiàn),則返回_values的大小-1.

//同時(shí)對于不允許重復(fù)字符串的數(shù)據(jù),如ID,當(dāng)出現(xiàn)重復(fù)字符串時(shí)則會(huì)錯(cuò)誤提示.

if(ind == Null()) {

_values.push_back(s);

return _values.size()-1;

} else {

if(bAllowDuplicate) {

return ind;

} else {

FAIL("value "<

return Null();

}

}

}

這里需要看一下distance這個(gè)函數(shù)的定義,它返回的是一個(gè)雙精度類型數(shù)值.如果傳入的兩個(gè)數(shù)據(jù)類型為Unknow則返回為0.0,其中一個(gè)為Unknow則為1,對于兩個(gè)雙精度類型的數(shù)據(jù)返回其差值.

//source:datasets/dcattrinfo.hpp

Real CAttrInfo::distance(const AttrValue& av1,const AttrValue& av2) const {

if(is_unknown(av1) && is_unknown(av2)){

return 0.0;

}

if(is_unknown(av1) ^ is_unknown(av2)){

return 1.0;

}

return boost::get(av1._value) -

boost::get(av2._value);

}

對于離散型數(shù)據(jù),兩個(gè)離散數(shù)據(jù)之間的距離定義也會(huì)不同,這里主要是考慮到離散型數(shù)據(jù)都轉(zhuǎn)化為相差為1的整形,所以只要兩個(gè)DAttrInfo的值不同則距離就為1.0,所以在含有離散型和連續(xù)型數(shù)據(jù)的混合數(shù)據(jù)中連續(xù)型數(shù)據(jù)要進(jìn)行歸一化處理以滿足量綱統(tǒng)一.

//source:datasets/dcattrinfo.hpp

Real DAttrInfo::distance(const AttrValue& av1,

const AttrValue& av2) const {

if(is_unknown(av1) && is_unknown(av2)) {

return 0.0; //如果兩個(gè)值都有缺省,則距離為0

}

if(is_unknown(av1) ^ is_unknown(av2)) {

return 1.0;//如果有一個(gè)值缺省,距離為1

}

if(boost::get(av1._value) ==

boost::get(av2._value) ) {

return 0.0;//如果兩個(gè)值相等,則無差距

} else {

return 1.0;//否則為最大距離1

}

}

3.1.4 Container類

Container類是一個(gè)基類模板,有一個(gè)vector的數(shù)據(jù)成員_data.add函數(shù)可以將T類型的數(shù)據(jù)添加進(jìn)入_data,同樣erase可以刪除數(shù)據(jù).[]是一個(gè)操作符重載,返回索引i對應(yīng)的數(shù)據(jù).

//source:clusters/record.hpp

template

class Container//基類模板

{

public:

typedef typename std::vector::iterator iterator;

iterator begin();

iterator end();

void erase(const T& val);

void add(const T&val);//將val添加到向量中

Size size() const; //返回_data的長度

T& operator[](Size i);//下標(biāo)索引,建立Schema與data的關(guān)系

protected:

~Container(){}

std::vector_data;

};

Record和Schema是繼承Container類的兩個(gè)重要的類,他們之間的關(guān)系如下:

圖3 Container關(guān)系圖

3.1.5 Schema類

Schema有兩個(gè)保護(hù)數(shù)據(jù)成員_labelInfo,_idInfo.和一個(gè)繼承父類的成員_data,_data是一個(gè)元素為AttrInfo的vector,表示每一個(gè)數(shù)據(jù)的屬性(離散/連續(xù))._labelInfo是一個(gè)指向DattrInfo的共享指針,其包含了輸入數(shù)據(jù)的分類情況.

Schema的目的是為一個(gè)Record對象設(shè)置label和id.set_id和set_label函數(shù)是為了實(shí)現(xiàn)此功能,但是他們又依賴與Record所以我們在Record類中具體定義.

//source:clusters/record.hpp

class Record;

class Schema:public Container<:shared_ptr> >

{

public:

const boost::shared_ptr& labelInfo() const;//標(biāo)簽信息,整形

const boost::shared_ptr& idInfo() const;//id信息,整形

boost::shared_ptr& idInfo();//可以修改成員變量,_labelInfo

boost::shared_ptr& labelInfo();//可以修改成員變量,_idInfo

void set_label(const boost::shared_ptr& r,const std::string& val);

//設(shè)置記錄的label

void set_id(boost::shared_ptr& r,const std::string& val);

//設(shè)置記錄的id

protected:

boost::shared_ptr _labelInfo;

boost::shared_ptr _idInfo;

};

3.1.6 Record類

Record繼承帶參數(shù)AttrValue的模板類Container,有四個(gè)私有數(shù)據(jù)成員_label,_data,id和_schema._data繼承自父類.每一個(gè)Record類都有一個(gè)指向Schema類的共享指針,可以將類型為AttrValue的數(shù)據(jù)儲(chǔ)存在_data中,同樣每一個(gè)record都有一個(gè)label和id.Record的構(gòu)造函數(shù)需要傳入一個(gè)指向Schema的共享指針,并將_data的長度設(shè)置為與_schema一樣,將_data里的值設(shè)置為默認(rèn)值.我們就可以通過Schema來操控Record,因?yàn)镾chema的_data類型為AttrInfo有很多函數(shù)如add,set_c_val,add_value等函數(shù)可以對離散/類型數(shù)據(jù)進(jìn)行操作.所以Record和Schema的關(guān)系為通過Schema定義了每一條數(shù)據(jù)的規(guī)范(label,id,每一條屬性的類型),然后按照這個(gè)規(guī)范將數(shù)據(jù)填充到record中,因?yàn)閞ecord直接接觸的類型是AttrValue.

//source:clusters/record.hpp

class Record:public Container

{

public:

Record(const boost::shared_ptr& schema);//構(gòu)造函數(shù)

const boost::shared_ptr& schema() const;

const AttrValue& labelValue() const;

const AttrValue& idValue() const;

AttrValue& labelValue();

AttrValue& idValue();

Size get_id() const;

Size get_label() const;

private:

boost::shared_ptr _schema;//通過_schema創(chuàng)建記錄

AttrValue _label;

AttrValue _id;

};

3.1.7 dataset類

上面已經(jīng)實(shí)現(xiàn)了一條數(shù)據(jù)的儲(chǔ)存就是一個(gè)Record,我們最終需要n條數(shù)據(jù).這里新定義一個(gè)類Dataset.很明顯按照上面的思路,Record依賴Schema,則Dataset依賴Record.

所以Dataset類繼承類型為Record的Container.因?yàn)樽詈笪覀兪褂玫牡腄ataset類,我們一些我們需要用到的屬性可以在這里直接給出.num_attr(),返回屬性的個(gè)數(shù),is_numeric()判斷該列屬性值是否是連續(xù)行(對于Kmeans算法這里需要連續(xù)型數(shù)據(jù)),為了更加方便第獲取每一個(gè)數(shù)據(jù),使用操作符重載.

//source:datasets/dataset.hpp

inline const AttrValue& Dataset::operator()(Size i, Size j) const {

return (*_data[i])[j];

}

//source:datasets/dataset.hpp

class Dataset:public Container<:shared_ptr> >

{

public:

Dataset(const boost::shared_ptr&);//構(gòu)造函數(shù),傳入含有屬性值的schema

Size num_attr() const;//返回屬性個(gè)數(shù)

const boost::shared_ptr &schema() const;//返回_schrma

const AttrValue& operator()(Size i, Size j) const;//返回第i條第j個(gè)屬性的值

std::vector get_CM() const;

bool is_numeric() const;

bool is_categorical() const;

protected:

boost::shared_ptr _schema;

};

3.2 創(chuàng)建一個(gè)數(shù)據(jù)實(shí)例

前面關(guān)于如何構(gòu)建dataset相關(guān)類已經(jīng)花了很多時(shí)間,下面就讓我們實(shí)際操作如何創(chuàng)建一個(gè)具體的dataset.

假設(shè)我們有這樣的一組數(shù)據(jù):

ID

Attr1

Attr2

Attr3

Label

r1

1.2

A

-0.5

1

r2

-2.1

B

1.5

2

r3

1.5

A

-0.1

1

表3 數(shù)據(jù)實(shí)例

那么我們?nèi)绾螌⒁陨蠑?shù)據(jù)用我們的dataset類來表示呢?

//test/datasettest.cpp

#include"../clusters/record.hpp"

#include "../datasets/dataset.hpp"

#include

#include

#include

using namespace std;

int main()

{

boost::shared_ptr schema(new Schema);

boost::shared_ptr labelInfo(new DAttrInfo("Label"));

boost::shared_ptridInfo(new DAttrInfo("id"));

schema->labelInfo() = labelInfo;

schema->idInfo() = idInfo;

stringstream ss;

boost::shared_ptr ai;

for(Size j=0;j<3;++j)

{

ss.str("");

ss<

if(j==0||j==2)

{

ai = boost::shared_ptr(new CAttrInfo(ss.str()));

}

else{

ai = boost::shared_ptr(new DAttrInfo(ss.str()));

}

schema->add(ai);

}

boost::shared_ptr ds(new Dataset(schema));

Size val;

boost::shared_ptr r;

r = boost::shared_ptr(new Record(schema));

schema->set_id(r,"r1");

schema->set_label(r,"1");

(*schema)[0]->set_c_val((*r)[0],1.2);

val = (*schema)[1]->cast_to_d().add_value("A");

(*schema)[1]->set_d_val((*r)[1],val);

(*schema)[2]->set_c_val((*r)[2],-0.5);

ds->add(r);

r = boost::shared_ptr(new Record(schema));

schema->set_id(r, "r2");

schema->set_label(r, "2");

(*schema)[0]->set_c_val((*r)[0], -2.1);

val = (*schema)[1]->cast_to_d().add_value("B");

(*schema)[1]->set_d_val((*r)[1], val);

(*schema)[2]->set_c_val((*r)[2], 1.5);

ds->add(r);

r = boost::shared_ptr(new Record(schema));

schema->set_id(r, "r3");

schema->set_label(r, "1");

(*schema)[0]->set_c_val((*r)[0], 1.5);

val = (*schema)[1]->cast_to_d().add_value("A");

(*schema)[1]->set_d_val((*r)[1], val);

(*schema)[2]->set_c_val((*r)[2], -0.1);

ds->add(r);

cout<

cout<

for(Size j=0; jnum_attr(); ++j) {

stringstream ss;

ss<

cout<

}

cout<

for(Size i=0; isize(); ++i) {

cout<get_id();

for(Size j=0; jnum_attr(); ++j) {

if((*schema)[j]->can_cast_to_c()) {

cout<

<get_c_val((*ds)(i,j));

} else {

cout<

<get_d_val((*ds)(i,j));

}

}

cout<get_label()<

}

return 0;

}

輸出結(jié)果與我們預(yù)想的一樣:

Data:

RecordID Attr(1) Attr(2) Attr(3) Label

0 1.2 0 -0.5 0

1 -2.1 1 1.5 1

2 1.5 0 -0.1 0

3.3 構(gòu)建簇

構(gòu)建簇的目的就是為了將dataset中的record進(jìn)行重新組合,所以我們定義一個(gè)基類Cluster直接接觸Record,

有一個(gè)數(shù)據(jù)成員id.

class Cluster:public Container<:shared_ptr> >

{

public:

virtual ~Cluster() {}

void set_id(Size id);

Size get_id() const;

protected:

Size _id;

};

inline void Cluster::set_id(Size id) {

_id = id;

}

inline Size Cluster::get_id() const {

return _id;

}

定義一個(gè)中心簇,來表示一個(gè)簇的中心.中心簇只有一個(gè)數(shù)據(jù)成員_center即表示中心簇的指向Record的共享指針.

//clusters/record.hpp

class CenterCluster : public Cluster

{

public:

CenterCluster(){}

CenterCluster(const boost::shared_ptr& center);//構(gòu)造函數(shù)傳入一個(gè)record

const boost::shared_ptr& center() const;//返回中心點(diǎn)的record,不可更改

protected:

boost::shared_ptr_center; //成員變量,中心點(diǎn)的record

};

CenterCluster::CenterCluster(const boost::shared_ptr& center):_center(center){}

const boost::shared_ptr& CenterCluster::center()

const {

return _center;

}

為了實(shí)現(xiàn)更多豐富的功能,我們需要再定義一個(gè)類PClustering.

圖4 PClustering 關(guān)系圖

PClustering繼承Container,通過add函數(shù)添加了中心簇Center.Center也擁有add函數(shù),它添加屬于和他同一簇的record,每一個(gè)record都有自己的id信息.這樣我們就能通過PClustering儲(chǔ)存了聚類信息.PClustering的一個(gè)數(shù)據(jù)成員為_CM,是用來儲(chǔ)存每一條record的所屬聚類.如:[1,1,1,2,2,2],同一簇?fù)碛邢嗤臄?shù)值.calculate函數(shù)是用來從_data中提取相關(guān)聚類信息,然后更新_CM.

//clusters/record.hpp

class PClustering:public Container<:shared_ptr> >

{

public:

PClustering();//構(gòu)造函數(shù)

friend std::ostream& operator<

PClustering& pc);//操作符重載,輸出聚類結(jié)構(gòu)相關(guān)信息

void removeEmptyClusters();//移除空的record

void createClusterID();//創(chuàng)建聚類id

void save(const std::string& filename);//保存聚類結(jié)果相關(guān)信息至文件

private:

void print(std::ostream &os);//打印聚類結(jié)果相關(guān)信息

void calculate();//更新_CM和_CMGiven

void crosstab();//將一些聚類結(jié)果儲(chǔ)存為交叉表

bool _bCalculated;/如果數(shù)據(jù)文件沒有標(biāo)簽信息,則不需要計(jì)算_numclustGiven

Size _numclust;//聚類數(shù)

Size _numclustGiven;//文件提供的label數(shù)

std::vector _clustsize;//記錄每一簇的數(shù)據(jù)量

std::vector<:string> _clustLabel;//記錄原文件中的每個(gè)分類的數(shù)量

std::vector _CM;//每一條記錄數(shù)據(jù)的所屬index

std::vector _CMGiven;//原文件每一條記錄所屬標(biāo)簽

iiiMapB _crosstab;//交叉表儲(chǔ)存數(shù)據(jù)

};

這里我們介紹一個(gè)模板鍵-值映射類nnmap(utilities/nnmap.hpp),在這里我們用來儲(chǔ)存聚類和原標(biāo)簽的數(shù)量信息.

如有6條數(shù)據(jù),計(jì)算的_CM為[1,1,2,2,2,3],所給標(biāo)簽為[0,0,1,1,2,2].

我們需要通過下面_crosstab.填充下面的表格

Cluster ID 1 2 3

0 # # #

1 # # #

2 # # #

_crosstab(1,0)表示聚類為1,標(biāo)簽為0的數(shù)量.通過下面的函數(shù),可以為2.同理_crosstab(2,0)=0,_crosstab(3,0) = 0.最終可以打印交叉表:

Cluster ID 1 2 3

0 2 0 0

1 0 2 0

2 0 1 1

如果以標(biāo)簽信息為準(zhǔn)的化,則(2,2)那個(gè)信息有誤,每一行只能有一個(gè)數(shù)據(jù)占據(jù),且不能與之前有相同的列.

//clusters/record.hpp

void PClustering::crosstab() {

Size c1, c2;

for(Size i=0; i<_cm.size>

c1 = _CM[i];

c2 = _CMGiven[i];

if (_crosstab.contain_key(c1,c2)) {

_crosstab(c1,c2) += 1;

} else {

_crosstab.add_item(c1,c2,1);

}

}

}

3.4 K-Means算法

3.4.1 算法思路

圖5 K-Means算法流程圖

在代碼實(shí)現(xiàn)中我們還會(huì)有一個(gè)多次運(yùn)行循環(huán)的結(jié)構(gòu),這樣的目的是通過多次的隨機(jī)初始化確保能夠消除一些極端的情況,最后取這些循環(huán)中最好的結(jié)果輸出.

3.4.2 并行化思路

我們使用一種序列 - 均值算法的思路.即計(jì)算所有記錄n和所有中心之間的距離.p個(gè)進(jìn)程,讓每一個(gè)參與計(jì)算的進(jìn)程處理 n/p條數(shù)據(jù).主要步驟如下:

(a)主進(jìn)程:讀取數(shù)據(jù)文件,并將數(shù)據(jù)塊發(fā)送至每一個(gè)進(jìn)程.

(b)主進(jìn)程:初始化簇中心,并將這些簇中心發(fā)送至每一個(gè)程.

(c)所有進(jìn)程:計(jì)算所給數(shù)據(jù)塊與簇中心的距離,并將這些數(shù)據(jù)塊歸屬到與它距離最近的中心.

(d)所有進(jìn)程:更新新的簇中心.

(e)所有進(jìn)程:重復(fù)(c)和(4)直至滿足停止條件.

(f)主進(jìn)程:收集聚類結(jié)果.

3.4.3 MPIKmean類

將所有的中心簇的數(shù)據(jù)編碼成一個(gè)向量_clusters,這樣可以很方便第從一個(gè)進(jìn)程發(fā)送至其他進(jìn)程.同樣_data表示所有的數(shù)據(jù)的值.

//source:mainalgorithm/mpikmean.hpp

class MPIKmean

{

public:

Arguments& getArguments();//獲取初始參數(shù)

const Results& getResults() const;//獲取結(jié)果_CM

void reset() const;//清除結(jié)果

void clusterize();//執(zhí)行計(jì)算(初始化,更新,迭代,...)

protected:

void setupArguments();//設(shè)置初始參數(shù)

void fetchResults() const;//獲取結(jié)果

virtual void initialization() const;//隨機(jī)初始中心簇

virtual void iteration() const;//迭代更新

virtual Real dist(Size i, Size j) const;//返回與中心簇的距離

mutable vector _centers;//中心簇的屬性值

mutable vector _data;//數(shù)據(jù)值

mutable Size _numObj;//分發(fā)給每一個(gè)進(jìn)程的數(shù)據(jù)量

mutable Size _numAttr;//數(shù)據(jù)屬性量

mutable vector _CM;//數(shù)據(jù)的所屬簇index

mutable vector<:shared_ptr> >

_clusters;//中心簇

mutable Real _error;//簇之間的總距離

mutable Size _numiter;//迭代次數(shù)

mutable Results _results;//結(jié)果

boost::shared_ptr _ds;//dataset

Arguments _arguments;

Size _numclust;//聚類數(shù)目

Size _maxiter;//最大迭代數(shù)目

Size _seed;//種子

boost::mpi::communicator _world;//mpi通信

};

主進(jìn)程負(fù)責(zé)初始化中心簇(4-33行),一旦中心簇被初始化,就會(huì)將中心簇(_centers)和每個(gè)進(jìn)程的數(shù)據(jù)的數(shù)目(_numRecords)和屬性數(shù)(_numAttr)發(fā)送給所有進(jìn)程(34-36行).

一旦這些數(shù)據(jù)被進(jìn)程接收到,每個(gè)進(jìn)程就會(huì)劃分自己的數(shù)據(jù)塊數(shù)量和剩余量(37-38行).首先主進(jìn)程會(huì)將第一個(gè)數(shù)據(jù)塊分配給自己(40-49行),剩余的數(shù)據(jù)通過send函數(shù)發(fā)送給其他進(jìn)程(51-63行).其他進(jìn)程通過recv進(jìn)行接收數(shù)據(jù)(67行).

void MPIKmean::initialization() const {

Size numRecords;

Size rank = _world.rank();

if (rank == 0) {

numRecords = _ds->size();

_numAttr = _ds->num_attr();

_centers.resize(_numclust * _numAttr);

vector index(numRecords,0);

for(Size i=0;i

index[i] = i;

}

boost::shared_ptr schema = _ds->schema();

boost::minstd_rand generator(_seed);

for(Size i=0;i<_numclust>

boost::uniform_int<> uni_dist(0,numRecords-i-1);

boost::variate_generator<:minstd_rand>

boost::uniform_int<> >

uni(generator,uni_dist);

Integer r = uni();

boost::shared_ptr cr = boost::shared_ptr

(new Record(*(*_ds)[r]));

boost::shared_ptr c =

boost::shared_ptr(

new CenterCluster(cr));

c->set_id(i);

_clusters.push_back(c);

for(Size j=0; j<_numattr>

_centers[i*_numAttr + j] =

(*schema)[j]->get_c_val((*_ds)(r,j));

}

index.erase(index.begin()+r);

}

}

boost::mpi::broadcast(_world, _centers, 0);

boost::mpi::broadcast(_world, numRecords, 0);

boost::mpi::broadcast(_world, _numAttr, 0);

Size nDiv = numRecords / _world.size();

Size nRem = numRecords % _world.size();

if(rank == 0) {

boost::shared_ptr schema = _ds->schema();

_numObj = (nRem >0) ? nDiv+1: nDiv;

_data.resize(_numObj * _numAttr);

_CM.resize(_numObj);

for(Size i=0; i<_numobj>

for(Size j=0; j<_numattr>

_data[i*_numAttr +j] =

(*schema)[j]->get_c_val((*_ds)(i, j));

}

}

Size nCount = _numObj;

for(Size p=1; p<_world.size>

Size s = (p< nRem) ? nDiv +1 : nDiv;

vector dv(s*_numAttr);

for(Size i=0; i

for(Size j=0; j<_numattr>

dv[i*_numAttr+j] =

(*schema)[j]->get_c_val(

(*_ds)(i+nCount,j));

}

}

nCount += s;

_world.send(p, 0, dv);

}

} else {

_numObj = (rank < nRem) ? nDiv+1: nDiv;

_CM.resize(_numObj);

_world.recv(0,0,_data);

}

}

進(jìn)行初始化之后,就開始迭代中心簇.

首先定義一個(gè)單元素的vector來控制循環(huán)(2行).在while循環(huán)內(nèi),定義三個(gè)局部變量nChangedLocal,newCenters,newSize.每一個(gè)進(jìn)程將會(huì)處理自己的數(shù)據(jù)塊與每一個(gè)中心簇的距離(11-30行).變量newCenters包含了一個(gè)聚類中所有數(shù)據(jù)的和.newSize包含了一個(gè)聚類中的數(shù)據(jù)的數(shù)量.一旦所有的數(shù)據(jù)通過并行處理完畢.all_reduce方法將會(huì)對所有的進(jìn)程的數(shù)據(jù)進(jìn)行收集,如

all_reduce(_world, nChangedLocal, nChanged,vplus());

對所有進(jìn)程中的nChangedLocal進(jìn)行相加(通過操作符vplus,具體見源文件定義),只有有一個(gè)進(jìn)程的nChangedLocal>0(中心簇未收斂)則nChange都會(huì)>0,整個(gè)迭代都會(huì)繼續(xù)進(jìn)行.(31-36行),在對這些數(shù)據(jù)進(jìn)行收集之后會(huì)更新_center(37-41行).

收斂之后,所有進(jìn)程會(huì)將聚類的index _CM發(fā)送給主進(jìn)程.主進(jìn)程會(huì)將自己的_CM添加進(jìn)去就形成了整個(gè)數(shù)據(jù)集的_CM(47-58行).

void MPIKmean::iteration() const {

vector nChanged(1,1);//初始化nChanged,表示中心簇是否有變化.

_numiter = 1;//初始迭代次數(shù)

while(nChanged[0] > 0) {

nChanged[0] = 0;

Size s;

Real dMin,dDist;

vector nChangedLocal(1,0);

vector newCenters(_numclust*_numAttr,0.0);

vector newSize(_numclust,0);

for(Size i=0;i<_numobj>

dMin = MAX_REAL;

for(Size k=0;k<_numclust>

dDist = dist(i, k);

if (dMin > dDist) {

dMin = dDist;

s = k;

}

}

for(Size j=0; j<_numattr>

newCenters[s*_numAttr+j] +=

_data[i*_numAttr+j];

}

newSize[s] +=1;

if (_CM[i] != s){

_CM[i] = s;

nChangedLocal[0]++;

}

}

all_reduce(_world, nChangedLocal, nChanged,

vplus());

all_reduce(_world, newCenters, _centers,

vplus());

vector totalSize(_numclust,0);

all_reduce(_world, newSize, totalSize, vplus());

for(Size k=0; k<_numclust>

for(Size j=0; j<_numattr>

_centers[k*_numAttr+j] /= totalSize[k];

}

}

++_numiter;

if (_numiter > _maxiter){

break;

}

}

if(_world.rank() > 0) {

_world.send(0,0,_CM);

} else {

for(Size p=1; p<_world.size>

vector msg;

_world.recv(p,0,msg);

for(Size j=0; j

_CM.push_back(msg[j]);

}

}

}

}

其他幾個(gè)函數(shù)的定義就不再贅述,相信通過看源文件一定可以看懂.

3.4.5 主函數(shù)

從前面建立數(shù)據(jù)集,到構(gòu)建簇類,編寫一些輔助類到算法的實(shí)現(xiàn).最后我們將具體對一個(gè)文件數(shù)據(jù)進(jìn)行聚類.

代碼如下:

//source:mainalgorithm/mpikmeanmain.cpp

#include

#include

#include

#include

#include

#include

#include

#include "mpikmean.hpp"

#include "../utilities/datasetreader.hpp"

using namespace std;

using namespace boost::program_options;

namespace mpi=boost::mpi;

int main(int ac, char* av[]){

try{

mpi::environment env(ac, av);

mpi::communicator world;

options_description desc("Allowed options");

desc.add_options()

("help", "produce help message")

("datafile", value(), "the data file")

("k", value()->default_value(3),

"number of clusters")

("seed", value()->default_value(1),

"seed used to choose random initial centers")

("maxiter", value()->default_value(100),

"maximum number of iterations")

("numrun", value()->default_value(1),

"number of runs");

variables_map vm;

store(parse_command_line(ac, av, desc), vm);

notify(vm);

if (vm.count("help") || ac==1) {

cout << desc << "\n";

return 1;

}

Size numclust = vm["k"].as();

Size maxiter = vm["maxiter"].as();

Size numrun = vm["numrun"].as();

Size seed = vm["seed"].as();

string datafile;

if (vm.count("datafile")) {

datafile = vm["datafile"].as();

} else {

cout << "Please provide a data file\n";

return 1;

}

boost::shared_ptr ds;

if (world.rank() ==0) {

DatasetReader reader(datafile);

reader.fill(ds);

}

boost::timer t;

t.restart();

Results Res;

Real avgiter = 0.0;

Real avgerror = 0.0;

Real dMin = MAX_REAL;

Real error;

for(Size i=1; i<=numrun; ++i) {

MPIKmean ca;

Arguments &Arg = ca.getArguments();

Arg.ds = ds;

Arg.insert("numclust", numclust);

Arg.insert("maxiter", maxiter);

Arg.insert("seed", seed);

if (numrun == 1) {

Arg.additional["seed"] = seed;

} else {

Arg.additional["seed"] = i;

}

ca.clusterize();

if(world.rank() == 0) {

const Results &tmp = ca.getResults();

avgiter +=

boost::any_cast(tmp.get("numiter"));

error = boost::any_cast(tmp.get("error"));

avgerror += error;

if (error < dMin) {

dMin = error;

Res = tmp;

}

}

}

double seconds = t.elapsed();

if(world.rank() == 0) {

avgiter /= numrun;

avgerror /= numrun;

std::cout<

<

std::cout<

<

PClustering pc =

boost::any_cast(Res.get("pc"));

std::cout<

std::cout<

std::cout<

<

std::cout<

std::cout<

std::string prefix;

size_t ind = datafile.find_last_of('.');

if(ind != std::string::npos ) {

prefix = datafile.substr(0,ind);

} else {

prefix = datafile;

}

std::stringstream ss;

ss<

pc.save(ss.str());

}

return 0;

} catch (std::exception& e) {

std::cout<

return 1;

} catch (...){

std::cout<

return 2;

}

}

編譯:

mpic++ -o mpikmean mpikmeanmain.cpp -L/usr/local/lib -lboost_program_options -lboost_mpi -lboost_serialization

運(yùn)行時(shí)需要傳入一些參數(shù):datafile(文件路徑),k(聚類數(shù)目),seed(隨機(jī)初始化種子),maxiter(最大迭代次數(shù)),numrun(運(yùn)行次數(shù)).除了datafile之外其他參數(shù)在主函數(shù)中均有默認(rèn)值.下面我們進(jìn)行一個(gè)15000條數(shù)據(jù)的3分類,執(zhí)行50次.

運(yùn)行:

mpirun -n 4 ./mpikmean --datafile=../testdata/15000points.csv --k=3 --numrun=50

運(yùn)行結(jié)果如第一章所示.

可以比較使用不同的進(jìn)程數(shù)目的不同運(yùn)行時(shí)間,使用多進(jìn)程確實(shí)可以提高運(yùn)行速度,但是因?yàn)镮/O操作會(huì)占用一些時(shí)間,運(yùn)行效率并沒有出現(xiàn)倍數(shù)的提升.

對于小數(shù)據(jù)集,I/O操作的開銷與數(shù)據(jù)計(jì)算開銷相差無幾,多進(jìn)程沒有明顯優(yōu)勢.對于大數(shù)據(jù)集,I/O操作開銷會(huì)小于數(shù)據(jù)計(jì)算的時(shí)間,這時(shí)候多進(jìn)程會(huì)帶來效率上的提升.

四 ,實(shí)驗(yàn)總結(jié)

到此,我們的K-Means算法的實(shí)驗(yàn)就到此結(jié)束了.由于考慮到整個(gè)內(nèi)容的繁雜度,有很多小的細(xì)節(jié)可能沒有拿出來細(xì)講,如果小伙伴對有些地方?jīng)]有弄懂,希望自己能夠繼續(xù)從源碼中尋找答案.雖然我們最后只實(shí)現(xiàn)了一個(gè)簡單的聚類算法,但前面介紹的關(guān)于構(gòu)建聚類數(shù)據(jù)集卻具有一定的通用性,對于其他聚類算法也很適用,如果小伙伴愿意嘗試其他聚類算法,也可以按照此思路進(jìn)行改寫.并行處理是一種技巧,如果使用恰當(dāng),能夠給計(jì)算效率帶來很大的提升,本例的并行處理思路同樣可以推廣到其他算法當(dāng)中.

感謝你能夠看到最后,希望你有所收獲!

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

總結(jié)

以上是生活随笔為你收集整理的python mpi开销_GitHub - hustpython/MPIK-Means的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。