实习笔记Day10(2022.8.18)

一.总结

在网上找了一份基于MPI实现的 k-means 聚类算法C代码,对其进行分析和学习,以巩固MPI和k-means算法相关知识 

二.学习笔记 

1.并行思路和算法流程

采用主从模式,主节点负责数据的划分与分配以及初始质心的生成,其他从节点完成本地数据的计算,并将结果返回给主节点,主节点更新质心再次广播给从节点,如此反复迭代

2.输入和输出文件样例 

(1)输入文件zoo.data 

在这里插入图片描述

(2)输出文件clusters-mpi.txt 

在这里插入图片描述

3.代码解析 

//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;//建立动物名字到整型的映射

//自定义结构体,一个animal结构体就是一个数据点(16维)
struct animal{
    int name;//在idx2name中的索引,可根据索引值找到名字
    int type;//动物所属的簇(0~K-1)
    int characters[D];//动物的16个特征
};

//从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; //声明一个元素为结构体animal的向量
    while(infile>>str){ //按空格读取,遇到空白符结束
        animal curline; //参照zoo.data格式可知一次循环读取一行
        int i=0;
        //变量name保存动物名字
        string name="";
        while(str[i]!=',')name+=str[i++];
        i++;
        //确定名字到整数索引的映射,即初始化idx2name数组
        idx2name[dataNum]=name;
        //保存名字的索引到结构体中,dataNum记录了数据点总数
        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[]){
/* 输入data存储了所有数据点,其每一个元素是一个animal结构体
   输入dataSize是数据点总数
   输入data_center是一个K*D二维数组,记录了当前的K个质心的D维数据
   输出new_data_center也是一个K*D二维数组,记录了本轮迭代结束后K个质心的D维数据
   输出cnt数组记录了本轮迭代后每个质心包含的数据点个数 */
    /* 遍历每一个数据点(具体为animal结构体) */
    for(int i=0;i<dataSize;i++){
        double min_dis=10000.0;
        int clusterId=-1;
        /* 对于当前数据点,遍历K个质心,找到距离其最近的一个质心,
        并记录最短距离和质心编号(0-6) */
        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));//先全部初始化为0
    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);//rank记录了当前进程的编号
    MPI_Comm_size(MPI_COMM_WORLD,&size);//size记录了总的进程数

    //进程0(主进程)读取数据,同时告知每个进程它需要处理的数据量
    if(rank==0){
        //读取数据到data中,同时记录数据点总数到dataNum中
        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];
        //为什么是dataNum而不是nums呢?
        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;
            //该数据没被选择过
            //count返回vis中值为idx的元素个数
            if(vis.count(idx)==0){
                //将生成的质心信息存储到cluster_center中
                for(int j=0;j<D;j++)
                    cluster_center[i][j]=data[idx].characters[j];
                //vis存储了随机生成的K个质心在data中的索引
                vis.insert(idx);
                i++;
            }
        }
    }
    //广播数据中心,也即将初始质心的信息广播给所有进程(包括0号进程本身)
    MPI_Bcast(cluster_center,K*D,MPI_DOUBLE,0,MPI_COMM_WORLD);
    
    //开始做聚类
    int local_cnt[K],total_cnt[K];
    //每一次for循环进行一轮迭代
    for(int round=0;round<epoch;round++){
        //先把两个数组填充为全0
        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);
        //各个从进程将local_cnt规约到进程0以便计算新的聚类中心
        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)//total_cnt[i]代表第i个质心代表的簇内的数据点数
                        cluster_center[i][j]/=total_cnt[i];
                }
            }
        }
        //广播新的中心
        MPI_Bcast(cluster_center,K*D,MPI_DOUBLE,0,MPI_COMM_WORLD);
       
    }
    
    //收集数据,打印结果
    if(rank){
        //只需要一次if,dataNum就有了?
        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);
        //各个从进程将name和type信息发送给主进程
    }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]]);
        }
        //clusters[i]存储了第i个质心包含的数据点的名称(字符串)
        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) //每行6个
                    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;
}

三.工作记录 

暂无

注:代码来自http://t.csdn.cn/36Phy 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Burp Suite 2022.6.1是Burp Suite的一个版本,它是一个用于Web应用程序渗透测试的工具。根据引用,您可以通过关注VX公众号401SecNote并回复"burp"来获取Burp Suite Professional v2022.6.1及其运行环境。此外,根据引用,您还可以通过百度网盘链接https://sysin.org/blog/burp-suite-pro-2022-6/下载Burp Suite Professional / Community 2022.6版本。如果您正在考虑安装高版本的Burp Suite,但担心与其他软件的兼容性问题,根据引用,您可以使用高版本的jdk来解决兼容性问题,并记录解决过程以备将来参考。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Burp Suite Professional 2022.6 (macOS, Linux, Windows) - Web 应用安全、测试和扫描](https://blog.csdn.net/netgc/article/details/125592616)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [笔记 | 在JAVA1.8环境下安装高版本Burp Suite Pro](https://blog.csdn.net/dust_hk/article/details/126489797)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值