가우시안 나이브 베이즈 알고리즘의 MPI 병렬 최적화 - 키, 몸무게, 폐활량을 통한 성별 추론(머신러닝)

목차

MPI 병렬 설계

MPI_Scatterv를 사용하여 다양한 크기의 데이터 청크 분산

MPI_Scatterv 함수 소개

MPI_Scatterv 매개변수 선택

Sendcounts 배열 생성

디스플레이 배열 생성

MPI 기능이 협력하여 병렬 컴퓨팅을 완료합니다.

MPI 병렬 프로그램

결과


이 프로젝트는 다음 네 부분으로 구성되며 이 섹션은 섹션 3입니다.

1. 키와 몸무게로 성별 추론

2. 신장, 체중, 폐활량을 기준으로 성별 추정

3. MPI 최적화

4. OpenMP 최적화

MPI 병렬 설계

데이터를 읽을 때는 직렬을 사용하고 나머지 for 루프에는 MPI 병렬을 사용합니다. 구체적인 단계는 다음과 같습니다.
1. 프로세스 0은 데이터 파일을 읽고 dataSet 배열을 생성합니다.
2. MPI_Scatterv() 함수를 통해 dataSet 배열을 길이가 다른 여러 개의 데이터 블록으로 나누고 각 프로세스에 데이터 블록을 분산시킵니다.
3. 각 과정을 합산하고 그 결과를 MPI_Gather() 함수를 통해 0 과정 누적으로 되돌려 평균값을 구한다.
4. 프로세스 0은 평균값(r)을 MPI_Bcast() 함수를 통해 모든 프로세스에 브로드캐스트합니다.
5. 공정별 표준편차 산출 내용 중 일부 : .
6. 0 처리가 완료된 후 남은 표준편차를 계산하여 표준편차 SD를 구한다.
7. 가우시안 분포의 확률밀도함수 f(x)=[1/ sqrt(2*π*δ²)]*exp(-(x-μ)²/2δ²)를 구한다. 8. P(height=170|female을 구한다.
) , P(신장=170|남성), P(체중=60|여성), P(체중=60|남성) 및 기타 확률.
9. 모든 해당 확률을 곱하고 최대값에 해당하는 특징을 예측 결과로 취합니다.

 

MPI_Scatterv를 사용하여 다양한 크기의 데이터 청크 분산

MPI_Scatterv 함수 소개

이 실험은 MPI_Scatterv를 사용하여 데이터 블록을 분산시키고 송수신하는데, 이 방법은 브로드캐스팅을 위한 큰 메모리 오버헤드 문제를 해결할 수 있을 뿐만 아니라 다양한 크기의 데이터 블록을 송수신할 수 있습니다. MPI_Scatterv 함수 는 다음과 같이 mpich_MPI_Scatterv 에서 찾을 수 있습니다. int MPI_Scatterv(const void *sendbuf, const int *sendcounts, const int *displs,

                 MPI_Datatype sendtype, 무효 *recvbuf, int recvcount,

                 MPI_Datatype recvtype, int root, MPI_Comm 통신)

함수 내부의 입력 매개변수:

Sendbuf: 배열 포인터, 산란에 의해 전송된 배열의 주소.

Sendcounts: 정수 배열, 배열의 각 요소는 각 프로세스에서 보낸 데이터의 길이에 해당합니다.

Displs: 정수 배열, 배열의 각 요소는 sendbuf에서 데이터를 송수신하는 각 프로세스의 시작 위치에 해당합니다.

Sendtype: MPI 데이터 유형, 전송된 데이터의 데이터 유형.

Recvbuf: 정수 배열, 청크 데이터를 받는 배열.

Recvcount: Sendcounts에 해당하는 정수로 프로세스에서 수락한 데이터의 길이를 나타냅니다.

Recvtype: MPI 데이터 유형, 수신된 데이터의 데이터 유형.

루트: 정수, 데이터를 보내는 과정, 흩어진 데이터를 채우는 과정, 일반적으로 0을 채웁니다.

Comm: Communicator, 일반적으로 MPI_COMM_WORLD를 입력합니다.

MPI_Scatterv 매개변수 선택

Sendcounts 배열 생성

Sendcounts 배열은 각 프로세스가 받은 데이터의 길이를 저장하기 위해 사용함 배열의 길이는 프로세스의 수와 같음 MPI_Scatterv()에서 배열 포인터를 전달하므로 배열의 각 숫자의 메모리 주소 연속적이어야 하며 직접 선언하거나 malloc() 함수를 사용하여 배열을 선언할 수 있습니다.
각 프로세스에 할당된 데이터의 길이는 다를 수 있으며, 본 프로젝트의 후반부에서 Displs 어레이 생성을 용이하게 하기 위해 이 방법은 모든 프로세스가 (dataLen/comm_sz) 데이터를 수신하고 마지막 프로세스가 더 많은 데이터(dataLen)를 수신하도록 하는 것입니다. %comm_sz) ) 데이터.
아래와 같이 코드 쇼:

int *Sendcounts; //对每个进程分发的数据长度
Sendcounts = (int *) malloc(comm_sz * sizeof(int));//分配内存
for(i=0;i<comm_sz;i++){
    if(i==comm_sz-1){
Sendcounts[i]=(int) (dataLen/comm_sz+(dataLen%comm_sz))*EIGEN_NUM;}
    else{Sendcounts[i]=(int) (dataLen/comm_sz)*EIGEN_NUM;}
}

디스플레이 배열 생성

Displs 배열은 각 프로세스가 받은 데이터의 메모리 오프셋을 저장하는데 사용함 배열의 길이는 프로세스의 개수와 같음 배열 포인터는 MPI_Scatterv()에 전달되기 때문에 배열의 각 숫자의 메모리 주소는 배열은 연속적이어야 하며 직접 명령문으로 사용할 수 있으며 malloc() 함수를 사용하여 배열을 선언할 수도 있습니다.
각 프로세스에 할당된 데이터는 서로 다르며, 첫 번째 프로세스는 dataSet[0] - dataSet[Sendcounts[0]-1]에 데이터를 받고, 두 번째 프로세스는 dataSet[Sendcounts[0]] - data를 받는다. 위치 데이터 세트[Sendcounts[1]-1]. 첫 번째 프로세스가 수신한 초기 위치는 0이고, 두 번째 프로세스가 수신한 초기 위치는 Sendcounts[0]이며, n 번째 배열이 수신한 초기 위치는 displs[n-1]+Sendcounts[n-1]입니다. 이 규칙에 따라 다음 코드를 사용하여 메모리 오프셋 배열을 생성할 수 있습니다.

int *displs;     //相对于dataSet的内存偏移量
    displs = (int *) malloc(comm_sz * sizeof(int)); //分配内存
    displs[0]=0;
    for(i=1;i<comm_sz;i++)
    {
        displs[i]=displs[i-1]+Sendcounts[i-1];
        //printf("displs[i]=%d",displs[i]);
        //printf("分发给进程%d的内存偏移量:%d\n",i,displs[i]);
    }

MPI 기능이 협력하여 병렬 컴퓨팅을 완료합니다.

MPI_Scatterv() 함수를 사용하여 배열을 receiveBuf라는 여러 하위 배열로 나눈 후 각 프로세스는 for 루프에서 receiveBuf 배열의 합계를 계산하고 sendSum 배열은 프로세스의 계산 결과를 수신하고 모든 프로세스에 sendSum을 보냅니다. MPI_Gather() 함수를 통한 [feature number] 배열 0을 반환하는 과정은 reciveSum[comm_sz*feature number]가 되고 배열을 누적하여 해당 합계로 나누어 평균값(r)을 구합니다.
0프로세스는 MPI_Bcast() 함수를 통해 평균값(r)을 모든 프로세스에 브로드캐스트하고, 각 프로세스는 계산하고, 그 결과를 MPI_Gather() 함수를 통해 0프로세스에 반환합니다. 반환된 데이터를 누적한 후 완료하는 것과 같습니다. 직렬 Sigma=,
standardDeviation =sqrt(Sigma/sexNum).
나머지 단계는 2.2.1과 유사하며 여기서는 자세히 설명하지 않습니다. 지금까지 모든 MPI 계산 프로세스가 완료되었습니다.
MPI를 최대한 활용하기 위해 후속 데이터 모델 평가에도 MPI를 사용할 수 있으며, MPI_Scatterv() 함수를 사용하여 모든 프로세스에 검증 데이터 세트를 배포하여 각 프로세스에서 올바른 번호와 잘못된 번호를 찾아내고, 결과를 0 프로세스로 반환합니다. , 0 프로세스로 정확도를 얻습니다.

MPI 병렬 프로그램

#include <iostream>
#include <vector>
#include <cstdlib>
#include <time.h>
#include <mpi.h>
#include <cassert>
#include <cstring>
#include <cmath>

#define PI 3.1415926535898

//单条数据的长度
#define MAX_LINE 20
//数据集的长度(从1开始计算)
#define DATA_LEN 11000000

#define EIGEN_NUM 4

//float dataSet[DATA_LEN * EIGEN_NUM];	//数据集
float (*dataSet)=(float(*))malloc(sizeof(float)*DATA_LEN*EIGEN_NUM);  

int dataLen;//数据集的行数
double maleNum=0;//男性总数
double femaleNum=0;//女性总数

int main(int argc, char** argv) {


	int i=0;
	int j=0;

	int my_rank;       //当前进程id
    int comm_sz;       //进程的数目

    MPI_Init(&argc, &argv); 
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);

	double start, end,readTime;
	MPI_Barrier(MPI_COMM_WORLD); /* IMPORTANT */
	start = MPI_Wtime();

/************************进程0读取文件************************/
	if(my_rank==0)
	{
		char buf[MAX_LINE];		//缓冲区
		FILE *fp;				//文件指针s
		int len;				//行字符个数

		//读取文件
		const char* fileLocation="E:\\test\\addVitalCapacityData.csv";
		fp = fopen(fileLocation,"r");
		if(fp  == NULL)
		{
			perror("fp  == NULL");
			exit (1) ;
		}

		//逐行读取及写入数组
		char *token;
		const char s[2] = ",";
		while(fgets(buf,MAX_LINE,fp) != NULL && i< DATA_LEN)
		{
			len = strlen(buf);
			//删去换行符
			buf[len-1] = '\0';
			//分割字符串
			token = strtok(buf, s);
			//继续分割字符串
			j = 0;
			while( token != NULL ) 
			{
				dataSet[i*EIGEN_NUM + j]=atof(token);
				token = strtok(NULL, s);
				j = j+1;
			 }
			i = i + 1;
		}
		dataLen=i;
		printf("%d行4列的数据读取完毕\n",dataLen);
		fclose(fp);

		//计算男女个数
		for(i=0;i<dataLen;i++){
			if(dataSet[i*4]==1){maleNum=maleNum+1;}
			if(dataSet[i*4]==2){femaleNum=femaleNum+1;}
		}

		readTime = MPI_Wtime();
		//printf("Read data time = %f", readTime-start);
	}


	MPI_Bcast(&dataLen,1,MPI_INT,0,MPI_COMM_WORLD);


/************************并行计算************************/

/***********计算高斯分布***********/

	/*向个进程散射分发数组*/
	int *Sendcounts; //对每个进程分发的数据长度
	Sendcounts = (int *) malloc(comm_sz * sizeof(int));//分配内存

	for(i=0;i<comm_sz;i++)
	{
		if(i==comm_sz-1){Sendcounts[i]=(int) (dataLen/comm_sz+(dataLen%comm_sz))*EIGEN_NUM;}
		else{Sendcounts[i]=(int) (dataLen/comm_sz)*EIGEN_NUM;}
		//printf("进程%d分发到的数据长度:%d\n",i,Sendcounts[i]);
	}

	int receiveDataNum; //接收的数据长度
	receiveDataNum=Sendcounts[my_rank];

	int *displs;	 //相对于dataSet的内存偏移量
	displs = (int *) malloc(comm_sz * sizeof(int)); //分配内存
	displs[0]=0;
	for(i=1;i<comm_sz;i++)
	{
		displs[i]=displs[i-1]+Sendcounts[i-1];
		//printf("displs[i]=%d",displs[i]);
		//printf("分发给进程%d的内存偏移量:%d\n",i,displs[i]);
	}


	//用来保存所接收到的数组
	float (*receiveBuf)= (float*) malloc((receiveDataNum) * sizeof(float)); 

	//printf("my_rank=%d,Sendcounts=%d,displs=%d,receiveDataNum=%d \n",my_rank,Sendcounts[my_rank],displs[my_rank],receiveDataNum);
	MPI_Scatterv(dataSet,Sendcounts,displs,MPI_FLOAT,receiveBuf,receiveDataNum,MPI_FLOAT,0,MPI_COMM_WORLD);
	
/****求和****/
	char *maenInf[6]={"maleLength","maleWeight","maleVC","femaleLength","femaleWeight","femaleVC"};
	//声明求和函数
	double getSum(float *data,int datalen,int sex,int column);
	//男性身高、体重、肺活量
	double maleLength=getSum(receiveBuf,receiveDataNum,1,1);
	double maleWeight=getSum(receiveBuf,receiveDataNum,1,2);
	double maleVC=getSum(receiveBuf,receiveDataNum,1,3);
	//女性身高、体重、肺活量
	double femaleLength=getSum(receiveBuf,receiveDataNum,2,1);
	double femaleWeight=getSum(receiveBuf,receiveDataNum,2,2);
	double femaleVC=getSum(receiveBuf,receiveDataNum,2,3);

	//printf("p-maleLength=%f p-maleWeight=%f p-maleVC=%f\n",maleLength,maleWeight,maleVC);
	//printf("p-femaleLength=%f p-femaleWeight=%f p-femaleVC=%f\n\n",femaleLength,femaleWeight,femaleVC);

	double sendSum[]={maleLength,maleWeight,maleVC,femaleLength,femaleWeight,femaleVC};//每个进程所计算出的和
	double *reciveSum=(double*) malloc((6*comm_sz) * sizeof(double)); //传给进程0的数组

/****求平均值****/
	double mean[6]={0,0,0,0,0,0};
	if(my_rank==0){
		MPI_Gather(sendSum,6,MPI_DOUBLE,reciveSum,6,MPI_DOUBLE,0,MPI_COMM_WORLD);

		for(i=0;i<comm_sz;i++){
			for(j=0;j<6;j++){
				mean[j]=mean[j]+reciveSum[i*6+j];
			}
		}
		for(i=0;i<6;i++){
			if(i<3){mean[i]=mean[i]/maleNum;}
			if(i>=3){mean[i]=mean[i]/femaleNum;}
		}
		//打印平均值的最终结果
		for(i=0;i<6;i++){
			//printf("mean-%s=%.16f\n",maenInf[i],mean[i]);
			if(i==5){printf("\n");}
		}
	}
	else{
		MPI_Gather(sendSum,6,MPI_DOUBLE,reciveSum,6,MPI_DOUBLE,0,MPI_COMM_WORLD);
	}

	//把平均值广播到所有进程
	MPI_Bcast(&mean,6,MPI_DOUBLE,0,MPI_COMM_WORLD);

	//打印每个进程获得的平均值
	/*for(i=0;i<6;i++)
	{
		printf("my_rank=%d mean[%d]=%f\n",my_rank,i,mean[i]);
	}*/

/****求标准差****/
	double sendSigma[6]={0,0,0,0,0,0}; //每个进程上的局部累加
	double *reciveSigma=(double*) malloc((6*comm_sz) * sizeof(double)); //传给进程0的数组
	//声明求累加函数
	double getSigma(float *data,int datalen,double mean,int sex,int column);
	i=0;
	for(int s=1;s<=2;s++){
		for(int j=1;j<=3;j++){
			sendSigma[i]=getSigma(receiveBuf,receiveDataNum,mean[i],s,j);
			//printf("sendSigma[%d]=%f\n",i,sendSigma[i]);
			i=i+1;
		}
	}
	
	double standardDeviation[6];	//标准差
	if(my_rank==0)
	{
		MPI_Gather(sendSigma,6,MPI_DOUBLE,reciveSigma,6,MPI_DOUBLE,0,MPI_COMM_WORLD);
		double Sigma[6]={0,0,0,0,0,0};	//累加
		for(i=0;i<comm_sz;i++){
			for(j=0;j<6;j++){
				Sigma[j]=Sigma[j]+reciveSigma[i*6+j];
			}
		}
		double sexNum;
		for(i=0;i<6;i++){
			if(i<3)
			{sexNum=maleNum;}
			if(i>=3)
			{sexNum=femaleNum;}
			standardDeviation[i]=sqrt(Sigma[i]/sexNum);
			//printf("Sigma[%d]=%f maleNum=%f",i,Sigma[i],sexNum);
			//printf("第%d个标准差=%f\n",i,standardDeviation[i]);
		}
	}
	else{
		MPI_Gather(sendSigma,6,MPI_DOUBLE,reciveSigma,6,MPI_DOUBLE,0,MPI_COMM_WORLD);
	}

	MPI_Bcast(&standardDeviation,6,MPI_DOUBLE,0,MPI_COMM_WORLD);

	//打印每个进程获得的标准差
	/*for(i=0;i<6;i++)
	{
		printf("my_rank=%d standardDeviation[%d]=%f\n",my_rank,i,standardDeviation[i]);
		if(i==5){printf("\n");}
	}*/


/*********** 朴素贝叶斯 & 准确率测试 ***********/
	//数据集有肺活量(VC),准确度判断
	float preSexID;
	float right=0;
	float error=0;
	//声明性别ID判断函数
	int sexIDResult(float height,float weight,float VC,double *mean,double *standardDeviation);
	for(int i=0;i<receiveDataNum/EIGEN_NUM;i++){
		preSexID=sexIDResult(receiveBuf[i*EIGEN_NUM+1],receiveBuf[i*EIGEN_NUM+2],receiveBuf[i*EIGEN_NUM+3],mean,standardDeviation);
		if(receiveBuf[i*EIGEN_NUM]==preSexID){right=right+1;}
		else{
			//printf("预测ID:%.0f  实际ID:%.0f \n",preSexID,receiveBuf[i*EIGEN_NUM]);
			//printf("性别:%.0f,身高:%.2f,体重:%.2f,肺活量:%.0f \n",receiveBuf[i*EIGEN_NUM],receiveBuf[i*EIGEN_NUM+1],receiveBuf[i*EIGEN_NUM+2],receiveBuf[i*EIGEN_NUM+3]);
			error=error+1;}
	}
	//printf("Right:%f\nError:%f\n",right,error);

	float sendRuslt[2]={right,error};
	/*for(i=0;i<comm_sz*2;i++)
		{
			printf("sendRuslt[%d]=%f\n",i,sendRuslt[i]);
		}*/
	float *reciveRuslt=(float*) malloc((2*comm_sz) * sizeof(float)); //传给进程0的数组
	if (my_rank==0)
	{
		MPI_Gather(sendRuslt,2,MPI_FLOAT,reciveRuslt,2,MPI_FLOAT,0,MPI_COMM_WORLD);

		float lastResult[2]={0,0};
		float right;
		float error;
		for(i=0;i<comm_sz;i++){
			lastResult[0]=lastResult[0]+reciveRuslt[2*i];
			lastResult[1]=lastResult[1]+reciveRuslt[2*i+1];
		}
		double accuracy  = lastResult[0]/(lastResult[0]+lastResult[1]);
		printf("Accuracy:%f\n",accuracy);
			
	}
	else{
		MPI_Gather(sendRuslt,2,MPI_FLOAT,reciveRuslt,2,MPI_FLOAT,0,MPI_COMM_WORLD);
	}

	MPI_Barrier(MPI_COMM_WORLD); /* IMPORTANT */
	end = MPI_Wtime();

	MPI_Finalize();

	if (my_rank == 0) { /* use time on master node */
		printf("Read data time = %f\n", readTime-start);
		printf("Calculate time = %f\n",end-readTime);
		printf("Run time = %f\n", end-start);
	}
}




/*****************函数*****************/

/***********高斯分布函数***********/
//求和
double getSum(float *data,int recDatalen,int sex,int column)
{
	double Sum=0;
	for(int i=0;i<(recDatalen/EIGEN_NUM);i++)
	{
		if(data[i*EIGEN_NUM]==sex){
			Sum=Sum+data[i*EIGEN_NUM+column];
		}
	}
	return Sum;
}

//求pow((data[i]-mean),2)的累加
double getSigma(float *data,int recDatalen,double mean,int sex,int column){
	double Sigma=0;
	for(int i=0;i<(recDatalen/EIGEN_NUM);i++){
		if(data[i*EIGEN_NUM]==sex){
			Sigma=Sigma+pow(data[i*EIGEN_NUM+column]-mean , 2 );
			//printf("sex=%d data[i]=%f mean=%f \n",sex,data[i*EIGEN_NUM+column],mean);
		}
	}
	return Sigma;
}

/***********朴素贝叶斯函数***********/

//计算概率p(特征列column = x | 性别)
double getProbability(double x,int column,int sex,double mean,double standardDeviation)
{
	double Probability;	//计算出的概率
	double u = mean;
	double p = standardDeviation;

	//高数分布概率密度函数 x:预测变量 u:样本平均值 p:标准差
	p=pow(p,2);
	Probability = (1 / (2*PI*p)) * exp( -pow((x-u),2) / (2*p) );


	//printf("p(%s=%lf|性别=%s)=%.16lf\n",basicInfo[column],x,gender,Probability);

	return Probability;
}


//返回性别ID结果
int sexIDResult(float height,float weight,float VC,double *mean,double *standardDeviation)
{
	double maleP;//男性概率
	double femaleP;//女性概率
	double a=0.5; //男女比例各50%

	maleP = a * getProbability(height,1,1,mean[0],standardDeviation[0]) * getProbability(weight,2,1,mean[1],standardDeviation[1]) 
		* getProbability(VC,3,1,mean[2],standardDeviation[2]);

	femaleP = a * getProbability(height,1,2,mean[3],standardDeviation[3]) * getProbability(weight,2,2,mean[4],standardDeviation[4]) 
		* getProbability(VC,3,2,mean[5],standardDeviation[5]);

	if(maleP > femaleP){return 1;}
	if(maleP < femaleP){return 2;}
	if(maleP == femaleP){return 0;}
}

결과

로컬 실행 결과는 아래 그림과 같습니다. 계산 시간이 6.3초에서 2.5초로 변경되었습니다. 이 최적화 비율은 매우 분명합니다.

서버 운영 결과는 아래 그림과 같으며, 계산 시간이 7.8초에서 3.9초로 변경됨 이 최적화 비율은 매우 분명합니다.

코드: https://download.csdn.net/download/admiz/16162449

 

 

추천

출처blog.csdn.net/admiz/article/details/109828277