MPI并行化实现K-means算法,使用zoo数据集

2023-10-13 07:10

本文主要是介绍MPI并行化实现K-means算法,使用zoo数据集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 详细算法设计
  • 算法流程
  • 主要函数及其功能说明
  • 输入及输出文件格式
  • 程序运行实验结果
  • 算法源代码

该算法的并行化中使用了zoo数据集,数据集地址:(http://archive.ics.uci.edu/ml/datasets/Zoo)

详细算法设计

采用主从方式,由一个进程充当主节点负责数据的划分与分发,其他进程完成本地数据的计算,并将结果返回给主节点做聚合。

1、选定进程0为主节点,首先调用loadDate函数从数据集文件zoo.data中读取数据集,每次读取一行数据,并把数据按animal结构体指定的结构保存到内存中,结构体如下所示:

struct animal{int name;int type;int characters[D];
};

zoo.data每一行是个18维的数据。其中第一维表示动物的名字,是个字符串,为了方便MPI中数据的传递,将动物名映射成一个int型索引保存到属性name中,而索引到字符串名称的映射通过另一个变量idx2name,它是一个哈希表。中间的16维代表了该动物的特征,全为整数,因此保存到一个长度维D=16的数组characters中。最后一维代表了该动物所属的类型,也为整数,保存到属性type中。

2、完成了数据的读取后,主节点0向其他从节点分发数据,首先告知各个节点需要处理的数据量,假设进程数为size,数据总量为N,那么除主节点不处理数据外,其他从节点处理的数据量为dataNum = N/(size-1),节点i处理数据的范围为(i-1)*dataNum~i*dataNum。确定好每个从节点处理的数据量和数据范围后,进程0将对应的数据分发给这个节点。

3、进程0随机选择每个聚类的中心点,并发送给其他进程。

4、其他从节点进程根据自己分配得到的数据,计算数据块中每个点到各个聚类中心点的距离,取距离最小的那个类为该点所属的聚类,并计算每个聚类包含的数据量local_cnt,同时将每个数据的属性值叠加到对应聚类i的属性和local_cluster_center[i]上,最后将这些结果返回给进程0方便计算新的聚类中心。这些结果的传递采用MPI_Reduce函数进行规约,规约操作op为求和MPI_SUM。该步骤要调用两个函数clusterdistance,其中cluster计算每个数据点所属的新的类型,它会调用distance计算数据点到聚类中心点的距离。

5、进程0根据local_cntlocal_cluster_center计算新的聚类中心,新的聚类中心isum(local_cluster_center[i])/sum(local_cnt[i]),然后发送给其他进程。

6、返回步骤进行新的一轮迭代,直到达到指定的迭代轮数epoch

7、将聚类结果保存到文件中,将属于同一类的动物保存在一个cluster中。

算法流程

在这里插入图片描述

主要函数及其功能说明

1、int loadData(string filename,animal* &data):从文件filename中读取数据保存到data

2、double distance(int charc[],double center_charc[]):求数据点charc和聚类中心center_charc之间的欧式距离 d i s = ∑ i = 1 D ( c h a r c [ i ] − c e n t e r _ c h a r c [ i ] ) 2 dis=\sqrt{\sum_{i=1}^{D}(charc[i]-center\_charc[i])^2} dis=i=1D(charc[i]center_charc[i])2

double distance(int charc[],double center_charc[]){double dis=0.0;for(int i=0;i<D;i++){dis+=(charc[i]*1.0-center_charc[i])*(charc[i]*1.0-center_charc[i]);}return sqrt(dis);
}

3、void cluster(animal* &data,int dataSize,double data_center[][D],double new_data_center[][D],int cnt[]):判断data中每个数据点所属的类型,data_center为当前的聚类中心,new_data_center为每个聚类包含的所有数据点的属性之和,cnt每个聚类包含的数据个数

void cluster(animal* &data,int dataSize,double data_center[][D],double new_data_center[][D],int cnt[]){for(int i=0;i<dataSize;i++){double min_dis=10000.0;int clusterId=-1;for(int j=0;j<K;j++){double cur_dis=distance(data[i].characters,data_center[j]);if(cur_dis<min_dis){min_dis=cur_dis;clusterId=j;}}//便于后续计算新的聚类中心for(int j=0;j<D;j++){new_data_center[clusterId][j]+=data[i].characters[j];}cnt[clusterId]++;//每一类包含的个数data[i].type=clusterId;//保存新的所属的类别}
}

4、计算新的聚类中心,并分发给其他进程

MPI_Reduce(local_cluster_center,cluster_center,D*K,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);MPI_Reduce(local_cnt,total_cnt,K,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);if(rank==0){//计算新的聚类中心for(int i=0;i<K;i++){for(int j=0;j<D;j++){   if(total_cnt[i]!=0)cluster_center[i][j]/=total_cnt[i];}}
}
//广播新的中心
MPI_Bcast(cluster_center,K*D,MPI_DOUBLE,0,MPI_COMM_WORLD);

输入及输出文件格式

输入文件数据格式zoo.data每一行是个18维的数据,如下图所示。其中第一维表示动物的名字,为一字符串;中间的16维代表了该动物的特征,全为整数;最后一维代表了该动物所属的类型,属于1~7之中的某个数。每一维数据之间用逗号隔开。
在这里插入图片描述
输出文件数据格式: 输出结果保存在文件clusters-mpi.txt中,一共聚类成为7大类,属于同一类的所有动物名保存在一起,由前导cluster-X引出,X为0~6之间的整数,如下图所示。
在这里插入图片描述

程序运行实验结果

程序由c++实现,迭代10000次,分别对比了串行k-means算法不同进程数运行的MPI并行化的k-means算法的运行时间:

k-means算法运行方式运行时间
串行k-means算法0.813s
2个进程运行的MPI并行化的k-means算法0.837s
3个进程运行的MPI并行化的k-means算法0.496s
4个进程运行的MPI并行化的k-means算法0.413s
5个进程运行的MPI并行化的k-means算法0.365s
6个进程运行的MPI并行化的k-means算法0.359s
7个进程运行的MPI并行化的k-means算法0.379s
8个进程运行的MPI并行化的k-means算法0.391s
9个进程运行的MPI并行化的k-means算法0.398s
10个进程运行的MPI并行化的k-means算法0.410s

在这里插入图片描述
可以看到,当进程个数从2增加到6时,运行时间逐渐减少;当进程个数从6增加到10时,运行时间逐渐增大。这是因为我的笔记本电脑是6核的,当进程数小于等于核数时,运行时间会随着进程数的增加而减小;但是当进程数大于核数时,由于CPU核不能调度这些进程同时运行,所以需要在不同时间段切换不同进程运行,上下文切换需要花费时间,所以运行时间会随着进程数的增加而增大。另外,当进程数为2时,由于只有1个进程在做运算,所以运行时间和串行化的运行时间差不多。

算法源代码

//kmeans算法mpi实现
#include <mpi.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <cstring>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <math.h>using namespace std;const int K=7; //聚类的数目
const int D=16;//数据的维数
const int epoch=10000;//迭代轮数unordered_map<int,string> idx2name;//自定义结构体
struct animal{int name;//在idx2name中的索引int type;int characters[D];
};//从zoo.data中读取数据
int loadData(string filename,animal* &data){ifstream infile;infile.open(filename);if(!infile) cout<<"failed to open file "+filename+" !\n";string str;int dataNum=0;vector<animal> tmp;while(infile>>str){animal curline;int i=0;//保存名字string name="";while(str[i]!=',')name+=str[i++];i++;//确定名字到整数索引的映射idx2name[dataNum]=name;curline.name=dataNum++;//保存特征for(int j=0;j<D;j++){curline.characters[j]=str[i]-'0';i+=2;}//保存所属类型int type=str[i]-'0';curline.type=type;tmp.push_back(curline);}data=new animal[dataNum];for(int i=0;i<dataNum;i++){data[i]=tmp[i];}return dataNum;
}//求欧式距离
double distance(int charc[],double center_charc[]){double dis=0.0;for(int i=0;i<D;i++){dis+=(charc[i]*1.0-center_charc[i])*(charc[i]*1.0-center_charc[i]);}return sqrt(dis);
}//聚类
void cluster(animal* &data,int dataSize,double data_center[][D],double new_data_center[][D],int cnt[]){for(int i=0;i<dataSize;i++){double min_dis=10000.0;int clusterId=-1;for(int j=0;j<K;j++){double cur_dis=distance(data[i].characters,data_center[j]);if(cur_dis<min_dis){min_dis=cur_dis;clusterId=j;}}//便于后续计算新的聚类中心for(int j=0;j<D;j++){new_data_center[clusterId][j]+=data[i].characters[j];}cnt[clusterId]++;//每一类包含的个数data[i].type=clusterId;//保存新的所属的类别}
}int main(int argc,char *argv[]){int rank,size;int dataNum;//每个进程处理的数据数animal* data;//保存数据double cluster_center[K][D];//数据聚类中心点memset(cluster_center,0,sizeof(cluster_center));double local_cluster_center[K][D];//每次聚类得到的新聚类中心MPI_Status status;clock_t startTime,endTime;startTime = clock();MPI_Init(&argc,&argv);MPI_Comm_rank(MPI_COMM_WORLD,&rank);MPI_Comm_size(MPI_COMM_WORLD,&size);//进程0读取数据,同时告知每个进程它需要处理的数据量if(rank==0){dataNum=loadData("zoo.data",data);for(int i=1;i<size;i++){int nums=dataNum/(size-1);int start=(i-1)*nums;int end=i*nums;if(i==size-1)end=dataNum;int sendNum=end-start;MPI_Send(&sendNum,1,MPI_INT,i,99,MPI_COMM_WORLD);}}else{MPI_Recv(&dataNum,1,MPI_INT,0,99,MPI_COMM_WORLD,&status);}MPI_Barrier(MPI_COMM_WORLD);  //同步一下if(rank==0){//分发数据,以字节的类型发送,一次send将所有数据发送给接收方for(int i=1;i<size;i++){int nums=dataNum/(size-1);int start=(i-1)*nums;int end=i*nums;if(i==size-1)end=dataNum;MPI_Send((void*)(data+start),sizeof(animal)*(end-start),MPI_BYTE,i,99,MPI_COMM_WORLD);}}else{data=new animal[dataNum];MPI_Recv(data,sizeof(animal)*dataNum,MPI_BYTE,0,99,MPI_COMM_WORLD,&status);}MPI_Barrier(MPI_COMM_WORLD);  //同步一下//进程0产生随机中心点if(rank==0){srand((unsigned int)(time(NULL)));  unordered_set<int> vis;int i=0;while(i<K){int idx=rand()%dataNum;//该数据没被选择过if(vis.count(idx)==0){for(int j=0;j<D;j++)cluster_center[i][j]=data[idx].characters[j];vis.insert(idx);i++;}}}//广播数据中心MPI_Bcast(cluster_center,K*D,MPI_DOUBLE,0,MPI_COMM_WORLD);//开始做聚类int local_cnt[K],total_cnt[K];for(int round=0;round<epoch;round++){memset(local_cluster_center,0,sizeof(local_cluster_center));memset(local_cnt,0,sizeof(local_cnt));if(rank){cluster(data,dataNum,cluster_center,local_cluster_center,local_cnt);}memset(cluster_center,0,sizeof(cluster_center));memset(total_cnt,0,sizeof(total_cnt));//将local_cluster_center规约到进程0以便计算新的聚类中心MPI_Reduce(local_cluster_center,cluster_center,D*K,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);MPI_Reduce(local_cnt,total_cnt,K,MPI_INT,MPI_SUM,0,MPI_COMM_WORLD);MPI_Barrier(MPI_COMM_WORLD);  //同步一下if(rank==0){//计算新的聚类中心for(int i=0;i<K;i++){for(int j=0;j<D;j++){   if(total_cnt[i]!=0)cluster_center[i][j]/=total_cnt[i];}}}//广播新的中心MPI_Bcast(cluster_center,K*D,MPI_DOUBLE,0,MPI_COMM_WORLD);}//收集数据,打印结果if(rank){int buf[dataNum*2];for(int i=0;i<dataNum;i++){buf[i*2]=data[i].name;buf[i*2+1]=data[i].type;}MPI_Send(buf,dataNum*2,MPI_INT,0,99,MPI_COMM_WORLD);}else{int buf[dataNum*2];for(int i=1;i<size;i++){int nums=dataNum/(size-1);int start=(i-1)*nums;int end=i*nums;if(i==size-1)end=dataNum;int sendNum=end-start;MPI_Recv(&buf[start*2],sendNum*2,MPI_INT,i,99,MPI_COMM_WORLD,&status);}vector<string> clusters[K];for(int i=0;i<dataNum;i++){clusters[buf[i*2+1]].push_back(idx2name[buf[i*2]]);}string filename="clusters-mpi.txt";ofstream out(filename);for(int i=0;i<K;i++){out<<"cluster-"<<i<<":"<<endl;int cnts=1;for(string name:clusters[i]){if(cnts%6==0)out<<name<<endl;else out<<name<<" ,";cnts++;}out<<endl<<endl;}out.close();}delete []data;MPI_Barrier(MPI_COMM_WORLD);MPI_Finalize();endTime = clock();cout <<rank<< " : The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;return 0;
}

这篇关于MPI并行化实现K-means算法,使用zoo数据集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/201579

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件