2017-06-23 19:08:40 zkh880loLh3h21AJTH 阅读数 276


Strata Data Conference北京站大会还有一个月即将召开,

有需求的同学还请抓紧时间,

点击二维码即可登录会议官网报名。


Pluto:一款分布式异构深度学习框架 

讲师:杨军 (阿里巴巴)

11:15–11:55 Friday, 2017-07-14

数据工程和架构 (Data engineering and architecture)

地点: 报告厅(Auditorium)

观众水平 (Level): 中级

必要预备知识

  1. 对机器学习及深度学习基础知识有一定认识。 

  2. 对分布式系统优化的基本概念,比如内存优化、计算优化、通信优化有基本认识。 

  3. 如果本身对大规模机器学习领域有一定认识会更好。(optional)

您将学到什么

  1. 在一个快速变化,演进的技术工作领域中,怎样结合尚不完整的学术界和工业界的资讯资料,结合自身的业务场景,完成技术选型和推进执行。 

  2. 怎样对一个看起来复杂的系统、算法跨界优化问题进行抽象,加入principle层面的优化。

描述

本议题会涉及到如下内容分享:

1. 在Caffe和TensorFlow这两种不同设计理念的深度学习框架里,所采用的不同优化策略。

比如,在Caffe中,我们加入了late multiply和pipeline communication的优化策略,显著改善了多机升缩比,同时对Caffe默认的单机多卡的逻辑进行了调制,建立了同构的网络通信拓扑,为上述优化提供更为一致的切入视角。在TensorFlow中,我们结合其计算流图的设计思想,将优化问题抽象成一个placement优化问题,以非侵入式的方式插入了较为优雅的修改,在若干模型上获得了显著的多机加速效果。

2. 分享Pluto在阿里巴巴若干核心业务场景中的应用案例,包括:

  • 集团安全

  • 金融风险建模

  • 证件类图片识别

  • 客服问答

  • 机器翻译等。

不同场景中所用到的模型结构也存在较大的差异,既有DNN网络,也有CNN网络,还有时序网络。对于不同类型的网络,我们也会结合其具体应用场景分享我们对于优化细节差异的理解和实践经验。

3. 分享我们对大规模深度学习优化的知识体系的理解和梳理。

大规模机器学习,尤其是大规模深度学习是一个相对年轻的技术领域,也跟其他技术领域(比如分布式计算、数值优化)存在很强的overlap。在Pluto开发过程中,我们会也在不断梳理现有相关技术知识体系的同时,结合阿里具体场景去探索对大规模深度学习的理解并践行。这个过程也许对于相关技术领域的同学会有一定助益。



讲师介绍:

杨军 (阿里巴巴)

目前在阿里云iDST大规模算法团队负责大规模深度学习算法基础设施相关建设工作,对大规模分布式机器学习的开发、建设、优化以及在不同业务场景中的落地应用有较为深入的理解和认识。之前先后在奇虎360担当广告技术部门架构师,Yahoo北京研发中心担当效果广告系统技术负责人。


2016-05-22 17:10:48 hjimce 阅读数 3267

异构计算GLSL学习笔记(1)

原文地址http://blog.csdn.net/hjimce/article/details/51475644

作者:hjimce

最近开始学习深度学习的一些gpu编程,大体学了cuda后,感觉要在手机上跑深度学习,没有nvidia显卡,不能加速。所以只能老老实实的学习opengl的shader编程,进行gpu通用计算加速,总的感觉shader编程比cuda编程难,还好自己之前研究生的时候,经常用opengl的一些API函数,对opengl比较了解。对于每一种语言,最简单的学习程序就是:hello world,下面记录一下shader编程最简单的例子,利用gpu加速实现彩色图像转灰度图像。大体包含五个步骤:

1、初始化环境、创建shader程序等;

2、根据需求,编写片元着色器的纹理图像处理代码;

3、从cpu传入数据(利用uniform类型变量,传数据到shader中,如果是图片可以直接采用纹理传入);

4、设置渲染模型、纹理坐标,绘制矩形,进行渲染计算;

5、取回图片处理后的数据;

一、opengl与shader环境初始化、编译链接

(1)初始化环境

//1、初始化环境
	glutInit(&argc, argv);
	//glutInitWindowSize(512,512);  
	//glutInitWindowPosition(100,100); 
	glutCreateWindow("GLEW Test"); 
	glewExperimental = GL_TRUE;
	glewInit();
	if (!glewIsSupported("GL_VERSION_4_0")) 
	{
		std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
		return EXIT_FAILURE;
	}

(2)创建链接、编译shader程序

//2、读取、创建shader程序,编译连接等
	auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
	glUseProgram(program_id);

ShaerProgram函数的代码如下:

GLuint ShaderProgram(const std::string &vertex_shader_file, const std::string &fragment_shader_file) {
  // 创建shader程序
  auto vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
  auto fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
  auto result = GL_FALSE;
  auto info_length = 0;

  // 读取shader源码
  std::ifstream vertex_shader_stream(vertex_shader_file);
  std::string vertex_shader_code((std::istreambuf_iterator<char>(vertex_shader_stream)), std::istreambuf_iterator<char>());

  std::ifstream fragment_shader_stream(fragment_shader_file);
  std::string fragment_shader_code((std::istreambuf_iterator<char>(fragment_shader_stream)), std::istreambuf_iterator<char>());

  // 编译顶点shader代码
  std::cout << "Compiling Vertex Shader ..." << std::endl;
  auto vertex_shader_code_ptr = vertex_shader_code.c_str();
  glShaderSource(vertex_shader_id, 1, &vertex_shader_code_ptr, NULL);
  glCompileShader(vertex_shader_id);

  // Check vertex shader log
  glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
  if (result == GL_FALSE) {
    glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_length);
    std::string vertex_shader_log((unsigned int)info_length, ' ');
    glGetShaderInfoLog(vertex_shader_id, info_length, NULL, &vertex_shader_log[0]);
    std::cout << vertex_shader_log << std::endl;
  }

  // 编译片元着色器代码
  std::cout << "Compiling Fragment Shader ..." << std::endl;
  auto fragment_shader_code_ptr = fragment_shader_code.c_str();
  glShaderSource(fragment_shader_id, 1, &fragment_shader_code_ptr, NULL);
  glCompileShader(fragment_shader_id);

  // Check fragment shader log
  glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
  if (result == GL_FALSE) {
    glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_length);
    std::string fragment_shader_log((unsigned long)info_length, ' ');
    glGetShaderInfoLog(fragment_shader_id, info_length, NULL, &fragment_shader_log[0]);
    std::cout << fragment_shader_log << std::endl;
  }

  // 创建链接程序
  std::cout << "Linking Shader Program ..." << std::endl;
  auto program_id = glCreateProgram();
  glAttachShader(program_id, vertex_shader_id);
  glAttachShader(program_id, fragment_shader_id);
  glBindFragDataLocation(program_id, 0, "FragmentColor");
  glLinkProgram(program_id);

  //打印编译信息,如果编译错误,就可以看见错误信息了
  glGetProgramiv(program_id, GL_LINK_STATUS, &result);
  if (result == GL_FALSE) {
    glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_length);
    std::string program_log((unsigned long)info_length, ' ');
    glGetProgramInfoLog(program_id, info_length, NULL, &program_log[0]);
    std::cout << program_log << std::endl;
  }
  glDeleteShader(vertex_shader_id);
  glDeleteShader(fragment_shader_id);

  return program_id;
}

二、编写shader程序

上面在创建shader程序的时候,需要shader源码,对于图像处理来说,顶点着色器我们一般不需要用到,主要是利用片元着色器进行图像处理的相关计算。

(1)顶点着色器源码文件gl_texture.vert的代码如下:

#version 400
//顶点着色器输入
in vec2 Position;
in vec2 TexCoord;

//顶点着色器的输出,这个变量会进入片元着色器
out vec2 fragTexCoord;

void main() {
  // Copy the input to the fragment shader
  fragTexCoord =TexCoord.xy;

  // Calculate the final position on screen
  // Note the visible portion of the screen is in <-1,1> range for x and y coordinates
  gl_Position = vec4(Position.x,Position.y, 0.0, 1.0);
}
(2)片元着色器gl_texture.frag
#version 400
// 需要注意opengl纹理输入
uniform sampler2D Texture;

// 来自顶点着色器
in vec2 fragTexCoord;
//输出 
out vec4 FragmentColor;

void main() {
  // Lookup the color in Texture on coordinates given by fragTexCoord
  vec3 pColor = texture(Texture, fragTexCoord.xy).rgb;
	//转换成灰度图像
  float gray=pColor.r*0.2126+ 0.7152* pColor.g + 0.0722*pColor.b;
  FragmentColor =vec4(gray, gray,gray, 1.0);

}

三、opencv读取图片、输入纹理、启用纹理等

//3、设置纹理相关参数、或者输入shader计算所需要的数据
	auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
	auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
	glUniform1i(texture_attrib,0);
	glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
	glBindTexture(GL_TEXTURE_2D, texture_id);

LoadImage函数代码如下:

GLuint LoadImage(const std::string &image_file, unsigned int width, unsigned int height)
{
  // Create new texture object
  GLuint texture_id;
  glGenTextures(1, &texture_id);
  glBindTexture(GL_TEXTURE_2D, texture_id);
 
  // Set mipmaps
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

  cv::Mat texture_cv=cv::imread(image_file);
  if (texture_cv.empty())
  {
	  return -1;
  }
  else 
  {
	  glTexImage2D(GL_TEXTURE_2D, 0, 3, texture_cv.cols, texture_cv.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, texture_cv.data);
	  return texture_id;
  } 

}

四、绘制渲染、计算

通过绘制一个矩形,该矩形的纹理刚好就是我们的图片,这样就可以实现gpu图片处理了:

//4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
	InitializeGeometry(program_id);
//5、绘制、渲染
	glClearColor(0.f,0.f,0.f,1.f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

InitializeGeometry函数:

void InitializeGeometry(GLuint program_id) 
{
  // Generate a vertex array object
  GLuint vao;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  // 顶点缓存
  GLfloat tempv[12] = {1.0f,1.0f,-1.0f,1.0f,1.0f,-1.0f,-1.0f, -1.0f};  //二维顶点坐标,分别为矩形的四个顶点坐标
  std::vector<GLfloat> vertex_buffer(tempv , tempv+12);  
  // Generate a vertex buffer object
  GLuint vbo;
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, vertex_buffer.size() * sizeof(GLfloat), vertex_buffer.data(), GL_STATIC_DRAW);

  // Setup vertex array lookup
  auto position_attrib = glGetAttribLocation(program_id, "Position");
  glVertexAttribPointer(position_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
  glEnableVertexAttribArray(position_attrib);

  // Generate another vertex buffer object for texture coordinates
  GLfloat temptex[8] = {1.0f,0.0f,0.0f,0.0f,1.0f,1.0f,0.0f, 1.0f}; 
  std::vector<GLfloat> texcoord_buffer(temptex,temptex+8);

  GLuint tbo;
  glGenBuffers(1, &tbo);
  glBindBuffer(GL_ARRAY_BUFFER, tbo);
  glBufferData(GL_ARRAY_BUFFER, texcoord_buffer.size() * sizeof(GLfloat), texcoord_buffer.data(), GL_STATIC_DRAW);

  auto texcoord_attrib = glGetAttribLocation(program_id, "TexCoord");
  glVertexAttribPointer(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
  glEnableVertexAttribArray(texcoord_attrib);

  glBindVertexArray(vao);
}

这个需要设置好顶点坐标和纹理坐标。

五、取出数据到opencv中

渲染完毕后,我们就要把图像处理的结果保存回opencv的cv::Mat上,然后看看处理结果

void show(int height=height_width,int width=height_width)
{
	cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);

	//use fast 4-byte alignment (default anyway) if possible
	//glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);

	//set length of one complete row in destination data (doesn't need to equal img.cols)
//glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
	glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
	cv::flip(img, img, 0);//需要翻转
	cv::imshow("result",img);
	cv::waitKey(0);

}

最后贴一下主程序流程的完整代码:
#include "stdafx.h"
#include <stdlib.h>
#include "include/glew.h"
#include "include/GLUT.H"
#include <opencv2/opencv.hpp>
#include "shader.h"
#define height_width 512


void show(int height=height_width,int width=height_width)
{
	cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);

	//use fast 4-byte alignment (default anyway) if possible
	//glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);

	//set length of one complete row in destination data (doesn't need to equal img.cols)
//glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
	glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
	cv::flip(img, img, 0);//需要翻转
	cv::imshow("result",img);
	cv::waitKey(0);

}


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


//1、初始化环境
	glutInit(&argc, argv);
	//glutInitWindowSize(512,512);  
	//glutInitWindowPosition(100,100); 
	glutCreateWindow("GLEW Test"); 
	glewExperimental = GL_TRUE;
	glewInit();
	if (!glewIsSupported("GL_VERSION_4_0")) 
	{
		std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
		return EXIT_FAILURE;
	}
//2、读取、创建shader程序,编译连接等
	auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
	glUseProgram(program_id);

//3、设置纹理相关参数、或者输入shader计算所需要的数据
	auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
	auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
	glUniform1i(texture_attrib,0);
	glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
	glBindTexture(GL_TEXTURE_2D, texture_id);


//4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
	InitializeGeometry(program_id);
//5、绘制、渲染
	glClearColor(0.f,0.f,0.f,1.f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//6、取回数据到opencv,并显示结果

	show();//




	return 0;
}

结果:

       

原图                                                                                               处理结果

****************************转载请保留原文地址、作者等信息********************************

2016-03-03 22:49:27 chenqiuge1984 阅读数 816

文/张伟德,曲宁,刘少山

导读:本文介绍百度基于Spark的异构分布式深度学习系统,把Spark与深度学习平台PADDLE结合起来解决PADDLE与业务逻辑间的数据通路问题,在此基础上使用GPU与FPGA异构计算提升每台机器的数据处理能力,使用YARN对异构资源做分配,支持Multi-Tenancy,让资源的使用更有效。

深层神经网络技术最近几年取得了巨大的突破,特别在语音和图像识别应用上有质的飞跃,已经被验证能够使用到许多业务上。如何大规模分布式地执行深度学习程序,使其更好地支持不同的业务线成为当务之急。在过去两年,百度深度学习实验室在徐伟的带领下开发了分布式深度学习平台PADDLE(Parallel Asynchronous Distributed Deep Learning),很好地满足了许多业务需求。但由于PADDLE是独立的深度学习平台,不能很好地跟其他业务逻辑结合,导致PADDLE与其他业务逻辑间的数据通路成为了性能的瓶颈。为了让更多的业务使用上深度学习技术,我们开发了Spark on PADDLE平台,让PADDLE变成百度Spark生态系统的一个功能模块。在第一版完成之后,我们发现CPU计算能力已经满足不了百度巨大的数据量需求,于是我们在Spark on PADDLE的基础上增加了对异构的支持,充分利用了GPU和FPGA等资源去加速PADDLE上的作业。

深度学习系统PADDLE的设计

PADDLE是一个成熟的分布式深度学习平台,广泛应用于百度的图像识别、自然语言理解、语音、无人车等领域,其主要的特点是训练算法高度优化,支持多GPU/CPU训练,训练效率高,对稀疏特征有独特的优化。

现有的深度学习平台,一般都是通过单机方式进行训练,如开源的Caffe平台也是通过单机多卡的方式进行训练。但当数据或者模型规模上去以后,要提高训练效率,必然要进行分布式训练,主要有数据并行和模型并行两种方法。

数据并行是分布式深度学习用得最多的并行方法。所谓数据并行,就是因为训练数据规模非常大,需要把数据拆分,把模型分布到N个机器训练。但是因为最终训练的是一个模型,同时每个机器只能分配到一部分数据,训练的同步和收敛性必须得到保证。最经典的做法是在《Parameter Server for Distributed Machine Learning》中提到的用参数服务器(Parameter Server)的方法。具体的想法是用模型参数服务的方法来同步参数的更新,每个参数服务器只负责同步公共参数的一部分。举个例子来说,如果模型M,被分布到N个机器上面训练,每个机器拿到一部分数据图片描述,假设训练的参数集合是W,每个机器首先进行本地训练,假设他们初始化参数都是图片描述,根据图片描述,每台机器都能算出相应的代价函数的梯度,一般按照单机神经网络反向传播的方式,每个层都可以梯度来得到参数的修正值,这样参数就变成图片描述因为是多机,每个节点对参数的修正量不同,就会多了一个步骤把各自参数的修正量push给参数服务器,由它统一决策下个训练循环的修正量,这样大家的训练模型就会被统一起来。
图片描述

图1 数据并行

图1展示了深度学习数据并行的部署架构。一般分为以下步骤;

  • 训练数据预处理,把数据切分为data shards;
  • 每个机器得到同样的模型定义,并且统一初始化参数;
  • 对于每个训练循环,每个机器算各自的梯度,并且把梯度修正量push给参数服务器,参数服务器统一计算,并且把下一轮迭代的参数push给本地训练机器;
  • 不断循环,直到模型收敛。

参数服务器的更新算法还分为同步和异步的区别。因为严格同步的方法会让本地训练机在每一个训练迭代都会进行参数的同步更新,这样在有慢节点的情况下,整个训练都会被拖慢。异步参数更新的想法是让参数同步的频率变长,这样可以让本地训练机迭代好几个回合以后再进行参数同步,这样的做法有利有弊,好处是慢节点对这个训练的影响变小,坏处是每个模型训练可能会浪费训练周期,因为同步以后的修正量可能跟本地训练机做的修正量有很大的不同。这其中对于同步频率的把握和异步收敛性的问题都是研究的方向。

模型并行方法如图2所示,针对参数规模达到单机无法载入的量级或者模型间存在很少连接的区块的场景,可以考虑做模型并行,但是模型并行通信开销和同步消耗超过数据并行,效率可能没有数据并行高。
图片描述

图2 模型并行

PADDLE的设计主要采用了单机做到模型并行、多机做到数据并行的方式,从而达到亿级模型规模以上,大规模数据量的分布式训练。

PADDLE与业务逻辑结合的痛点

PADDLE是一个独立的深度学习平台,不能很好地支持把数据从其他平台接入的需求。研发人员通常要等上一阶段的工作完成产生PADDLE的输入数据后,把数据先存入HDFS,再读到PADDLE集群的本地内存与硬盘,等数据准备好以后再用PADDLE去训练模型。等模型训练好后,再把模型存在HDFS里,让下一个业务逻辑去读取。这个过程不仅耗时长,成为整个计算流程的瓶颈,并且都是重复性的枯燥工作,影响了PADDLE平台的推广,让很多有需要的团队没法用上深度学习技术。

为了解决这个问题,我们设计了Spark on PADDLE架构,把Spark与PADDLE耦合起来,让PADDLE成为Spark的一个模块。如图3所示,模型训练可以与前端的功能整合,比如特征提取通过RDD的形式进行数据传递,无需通过HDFS进行数据导流。这样一来,PADDLE与业务逻辑间的数据通路不再是性能瓶颈。
图片描述

图3 基于百度Spark的通用业务逻辑

Spark on PADDLE架构1.0版

Spark是近几年快速兴起的大数据处理平台,不仅仅在于它的计算模型比传统的Hadoop MapReduce要高效很多,同时在于它所带来的生态系统非常强大。基于Spark计算引擎构建的上层应用如Spark SQL、Spark Streaming、Spark MLlib等,都是很优秀的应用,比传统应用性能好几倍,并且更加稳定。同时与Yarn/Mesos的结合让Spark对计算资源的管理和分配更加灵活。

Spark在百度内部已经广泛应用,主要用于数据处理和数据分析。但是传统的数据处理平台必定会有根据数据训练模型的机制,广告系统的CTR预测就是一个例子,对于用户产生大量的点击和浏览日志,Spark可以进行处理和清洗。但是对于大规模模型的训练,Spark MLlib的支持还是有限,特别是对于深度学习的支持,所以需要解决在Spark上支持PADDLE的问题。

对于用户的应用程序,Spark叫驱动节点(Driver),可以视为Spark用户分布式程序调度和程序流控制的主节点。Spark程序的具体运算都分布在Worker Node上面的Executor跑。Spark还有一个非常重要的概念叫RDD,这是一个分布式的分区(partitioned)数据抽象集。Spark所有输入和输出数据都是以RDD为导向的,它不仅描述了数据集的依赖关系,同时还对数据进行了逻辑上的切分,对一个RDD操作一般都是partition来并行的。

图片描述

图4 Spark DNN训练运行构架

Spark DNN训练运行构架如图4所示,训练一般分为以下5个步骤:

  • DNN 数据预处理和训练特征准备

一般这是Spark的强项,不管是流式数据还是已经落盘的数据都通过Spark来进行数据处理,其中包括数据清洗、特征准备,然后把得到的训练数据用RDD输出。

  • 资源申请

Spark训练任务提交的时候先从Yarn那里拿到对于DNN训练任务的节点资源,比如说一个训练任务需要4个有4 GPU机器的节点。Yarn会对资源做Container式的管理,不管CPU还是GPU对于Yarn来说都是一个虚拟的资源。后文会做具体介绍。

  • 训练初始化

Driver会根据Yarn分配的资源相应分发模型配置。模型训练资源库,并且启动训练机和参数服务器,同时初始化模型的初始参数。

  • 模型训练

训练的数据会以RDD的方式输入到训练机接口,以数据并行的方式进行训练,并且启动的训练机会跟参数服务器通信,完成梯度交换和参数同步,当训练最大迭代达到或者模型收敛,则训练终止。

  • 模型预测

模型可以传入某一个服务器集群或者以Spark Streaming的方式进行载入并且预测。

在Spark on PADDLE 1.0开发的过程中,我们验证了Spark确实可以把ETL、训练数据预处理和深度学习训练结合起来,同时发现百度内部有很多深度学习需求,需要在1.0的基础上考虑把Spark on PADDLE平台化,做到Multi-Tenancy的资源管理、训练监控、训练容错等等。

Spark on PADDLE 架构2.0版

平台化是Spark on PADDLE 2.0的主要目标。它引入了更多的功能,主要包括在训练过程中引入了监控机制、容错机制,加入了ML决策模块做超参数选择等。下面是对Spark on PADDLE 2.0设计的分析。

如图5、图6所示,客户可以直接与Spark DNN Driver通信启动DNN训练,Spark DNN Driver启动一个训练实例(Training Instance),并且透传训练数据、训练网络配置等信息。一个训练实例包括了训练所需的整体服务,包括一组训练器以及对应的参数服务器。然后有一个训练Master(Training Master)来管理整个的训练进程。同时训练Master管理训练器和超参数服务器的生存周期和失败重启。参数服务器和训练器会定期给训练Master发送heartbeat,确保其正常运行。

图片描述

图5 Spark on PADDLE 2.0 总体架构

图片描述

图6 Spark on PADDLE 2.0 Training Instance架构

训练过程中的监控机制

当训练开始以后,用户会对训练过程中的一些数据进行监控,包括训练的每个迭代的loss值、错误率、所用的时间以及训练机和参数服务器的日志进行监控,我们在实现的过程中会在Worker端用消息传递的方式(AKKA)向Driver端汇报训练的数据。对于整个Spark Job的性能数据会依赖Spark本身提供的监控功能,所有信息都反馈在监控页面中(Web UI)。

训练过程中的容错机制

因为DNN在训练过程中,训练机和参数服务器都是有可能失败的地方。最简单的容错方式是定期对模型的参数和训练信息做备份,当模型训练失败以后,从备份点开始重启模型训练就可以。训练Master会把这些信息收集起来,并且汇报给Spark DNN Driver。对于参数服务器的容错,可以采取增加冗余的方法,如果一个参数服务器挂掉,训练Master会负责重启相应服务,但是会有一个备份的参数服务器去负责挂掉的参数服务器的参数更新。

超参数选择

图片描述

图7 超参数选择训练

超参数是确立模型训练的基础,Spark在MLlib中引入了超参数选择模块,主要的做法就是通过一定的超参数选择算法对模型进行并行训练,最终选择的超参数将会被用做最终的模型训练。超参数的选择对于深度学习很有意义,包括网络拓扑、参数的衰减率、触发函数的选择都是影响深度学习的超参数。图7显示了一个大概的超参数选择流程,模型的特征选择到归化系数(Regulation Parameter)一起配对来训练一个模型,最终评估模块选择最终超参数。在Spark的场景中,DNN Driver端会跟评估端通过RPC通信来决策需要尝试什么超参数。评估端逻辑是在Spark DNN Driver依赖的MLApplication服务。如果用户需要对DNN训练模型进行超参数选择,则Spark DNN Driver会根据不同参数配对启动多个训练实例,然后根据训练来是否需要进一步搜索。

Spark异构分布式计算平台架构

如上所述,我们已经看到Spark on PADDLE能够使得传统的深度学习可以在更大规模的分布式系统上运行。但是,百度面临非常现实的问题就是巨量的数据。在百度内部,每天处理的数据量都远远超出了传统平台的能力,会使用到巨量的模型参数、特征以及训练数据。这些巨量数据对分布式系统的性能和扩展性都提出了更高的要求。一方面,我们希望提供可以比拟传统MapReduce集群规模的深度学习计算集群,可以并行运行大量的深度学习任务;另一方面,每个深度学习模型不可能无限制地切分成更小的单元,因此每个节点的模型处理能力也是至关重要的。

目前以CPU为主的计算节点受到本身计算能力的限制,远远不能满足计算的需求,因此,我们需要通过更强大的异构计算来加速现在的计算平台。目前我们的项目主要涉及到两种计算资源:GPU和FPGA。GPU可以提供强大的计算能力,适用于高密度的计算类型;FPGA有低功耗、高度可定制的特点,适合加速很多特定的动态任务(本项目使用的FPGA硬件加速由百度美国研发中心的计算团队提供)。

我们的项目正是基于Spark on PADDLE,探索了如何有效地把异构资源整合到现在的大规模分布式系统,以提供高应用性能和易用性为目标。在满足前述要求的基础上,系统需要动态地对GPU/FPGA资源进行管理,进行无缝的调度,正如CPU和Memory等资源的调度一样。这一功能是通过把资源调度整合到开源的Yarn系统来实现的,而资源隔离方案基于业界流行的Container技术。

同时,我们还需要提供简单易用的编程接口,以便现有的应用程序可以更快地迁移到我们的系统上来。因为Spark所有的数据都是基于RDD的,我们创建了一类新的RDD,通过这个RDD,程序可以直接使用到底层的GPU/FPGA来加速相应的计算。我们知道,真正在GPU/FPGA上完成程序的功能,还需要提供Kernels,这里我们采用了业界最为流行的标准OpenCL接口,以便于将程序移植到不同的GPU/FPGA。可以看到,一个特定的功能实现需要3个部分:一个Scala Driver,一个C++的Worker以及一个OpenCL Kernel(on GPU/FPGA)。如果常用的功能已经集成在MLlib中,那么用户只需要创建自己的Scala Driver,通过新的RDD调用库里面已经支持的函数,就可以无缝享受到GPU/FPGA资源的加速。

图片描述

图8 Spark异构计算平台架构

异构系统架构如图8所示。系统的运行过程如下:

  • 首先用户应用程序(Scala Driver)会由App Master启动;
  • 然后用户应用程序会向Yarn请求其所需的资源,其中GPU、FPGA作为不同的资源类别,与请求CPU资源方式完全一致;
  • 用户应用程序取得所有资源,由App Master在相应的App Slave上启动Container运行用户程序的一个Scala Worker;
  • 这时,按照程序Scala Worker的需求,如果使用到了新的RDD,便会调用相应的C++的OpenCL程序,如果函数功能是MLlib内嵌的,那么这部分对用户也是完全透明的。
  • OpenCL程序启动后,会把所分配的数据传输到GPU或FPGA上,然后在GPU或者FPGA上动态启动特定的OpenCL Kernel,处理这些已经传输过来的数据。
  • OpenCL Kernel计算完成后,数据会自动被拉回到主存,这时OpenCL的程序就可以把结果返回给Scala Worker;
  • 最后所有Scala Worker把结果提交给在App Master上运行的用户程序Scala Driver。

可以看到,整个流程支持加入了新的GPU/FPGA计算资源,还有需要用户使用新的RDD。其他方面对用户程序来说没有任何额外的改动。

Spark异构平台性能评估

在异构平台架构搭建好后,我们首先测试了机器学习底层矩阵运算库的CPU与GPU性能对比。结果显示,在执行同一个计算方程时,GPU的加速效果很好,对CPU的加速比大约是30倍。与此同时,百度美国研发中心计算团队也对Kmeans算法用FPGA进行加速,取得了15到20倍的加速化,而且FPGA能耗只是CPU的20%。在第二个实验中,我们对比了Spark on PADDLE在训练ImageNet时的GPU与 CPU加速比,发现使用GPU可以加速30倍,也就是说,在使用异构平台后我们只用3%的机器资源就可以完成同样的计算。

在很好地了解了异构平台加速比后,我们也研究了异构平台的可扩展性。测试结果如图9所示,基本上随着GPU资源的增加,计算时间也在线性地降低,表现出很强的可扩展性,可以承受很大的数据量与计算量。
图片描述

图9 Spark异构计算平台性能数据

总结

本文介绍了百度基于Spark的异构分布式深度学习系统。把Spark与深度学习平台PADDLE结合起来解决了PADDLE与业务逻辑间的数据通路问题,使业务方可以很容易地使用深度学习技术。在此基础上,我们使用GPU与FPGA的异构平台极大地提升了每台机器的数据处理能力。在异构平台上,我们使用YARN对异构资源做分配,以支持Multi-Tenancy,让资源的使用更有效。下一步工作我们打算把平台推广到百度不同的业务平台,比如语音、百度秘书、百度图搜、百度无人车等,让平台在不同业务上锤炼。在平台更成熟后,我们打算把Spark on PADDLE以及异构计算平台开源,回馈社区。


张伟德:百度美国硅谷研发中心高级架构师,负责大数据、深度学习架构和开发。曾在Yahoo、微软等公司负责大型分布式搜索构架设计。

曲宁:百度美国研发中心高级架构师,负责基础架构以及异构计算平台架构与开发。CMU大学Cylab研究院研究员。曾在Nvidia以及Google工作。

刘少山:百度美国研发中心高级架构师,从事深度学习以及异构计算平台架构与开发。曾在LinkedIn、微软、微软研究院、INRIA、Intel以及Broadcom工作。

本文为《程序员》原创文章,未经允许不得转载,订阅2016年《程序员》请点击 http://dingyue.programmer.com.cn

该文已被收录至CSDN Spark知识库。了解更多Spark内容,可访问:http://lib.csdn.net/base/10

2019-03-14 15:44:34 weixin_45585364 阅读数 126

基于深度学习的异构时序事件患者数据表示学习框架

刘卢琛, 沈剑豪, 张铭,, 王子昌, 李浩然, 刘泽群

北京大学信息科学技术学院,北京 100871

 

摘要患者数据的表示学习可以将患者历史信息综合表达为一个向量,用于预测未来可能发生的疾病。患者的历史记录可以被建模为多来源数据构成的采样频率差异很大、包含非线性时序关系的异构时序事件。提出了一个新的异构事件长短期记忆表示学习框架,用于学习患者异构时序事件的联合表征。异构事件长短期记忆模型加入了一个可以控制事件访问频率的门,以对不同事件的不规则采样频率建模,同时抓住事件中的复杂时序依赖关系。真实临床数据的实验表明,该方法可以在一系列先进模型的基础上,提升死亡预测和异常实验结果预测的准确度。

关键词电子病历;患者数据表示学习;异构时序事件;深度学习

640?wx_fmt=jpeg

论文引用格式:

刘卢琛, 沈剑豪, 张铭, 王子昌, 李浩然, 刘泽群. 基于深度学习的异构时序事件患者数据表示学习框架. 大数据[J], 2019, 5(1): 25-38

LIU L C, SHEN J H, ZHANG M, WANG Z C, LI H Y, LIU Z Q. Deep learning based patient representation learning framework of heterogeneous temporal events data. Big data research[J], 2019, 5(1): 25-38

640?wx_fmt=jpeg

1 引言

电子病历的大量积累,为机器学习和数据挖掘的研究者们提供了很好的数据基础,以此服务于辅助诊断和智慧医疗,提供智能化的辅助诊疗服务。智慧医疗中一个重要的核心研究问题是患者数据的表示学习,它可以综合提炼、挖掘丰富历史病例数据中的信息,为疾病诊断、重要症状、指标异常等各种临床结果的预测提供支撑。

本文提出了一个基于深度学习的患者电子病历数据表示学习框架,用于临床终点(clinical endpoint)的预测任务。临床终点是指反映病人感觉、功能、生存的特征或目标变量。研究证明,深度学习在各类应用场景下有着比传统机器学习方法更优越的性能,比如图片分类、语音识别以及自然语言处理等。深度学习的主要思路是自动化地从基本数据中抽取特征,得到对样本的有效语义表征。在电子病历方面,也希望通过深度学习的方法对患者的历史记录进行有效的表征学习。

然而,患者历史数据表示学习问题非常有挑战性,因为患者的历史记录包含了异构的时序事件,比如化验结果、生理指标、药物注射、临床事件等(如图1所示)。不同事件的采样记录频率千差万别,比如患者可能每个早上都要化验血糖,每个小时都要测量体温和血氧。而且不同事件之间有复杂的时序依赖关系,例如一个诊断结果需要根据患者之前的一些症状以及化验结果的某种趋势才能够得出。可以看到,对于这种包含上千种事件、采样频率差异巨大、隐含着丰富时序依赖关系的异构时序事件,学习其向量表征确实是非常复杂的。


640?wx_fmt=jpeg

图1   异构时序事件的表示学习建模框架


很多文献探讨了对序列数据的表示学习方法,特别是在语音和自然语言处理领域。现有经典的长短期记忆(long short term memory,LSTM)模型可以用于同构的序列数据,然而对于异构的序列数据,直接使用LSTM模型并不方便。也有一些工作(如多过程高斯模型)对不同序列之间的相关度进行建模,然而它的计算复杂度非常大,在处理上千种时序数据时,计算代价不可承受。因此,笔者希望能够找到一种既可以对不同事件、不同采样频率建模,也能抓到不同事件之间的相关关系,并且可以比较方便地扩展应用到高维度的医疗病例数据中的方法。

本文提出了一个叫作异构事件长短期记忆(heterogeneous event long short term memory, HE-LSTM)网络的算法模型,用于学习异构时序事件的联合表征。本文算法是在相位LSTM(phased LSTM)等稀疏更新的循环神经网络基础上发展起来的。相位LSTM处理不规则的事件序列数据,使用一个相位门整合任意采样频率传感器收集的数据,但是它不能直接用于含有上千种事件类型的电子病历异构时序事件数据。

本文针对种类数量巨大并且采样频率差异很大的异构事件建模,每个事件及其属性被嵌入一个向量表征中,然后输入HE-LSTM模型。HE-LSTM模型依靠事件门分工合作,异步追踪记录不同采样频率下某些相关事件簇的时序信息,于是最终学习到的联合表征可以借助隐藏层延迟更新的模型结构自动整合,以此捕捉到各类事件相关的时序依赖关系。

本文在真实医疗数据上进行了一系列实验,在死亡预测和异常化验检测的任务上,都证明了本文的模型相比其他有竞争力的对照模型,预测效果提升显著。本文的模型也可以用于各类多源多频率的时序数据,比如移动传感器、慕课学生行为记录、手机应用中的用户行为记录等。

本文的主要贡献如下。

针对电子病历中多源异步采样的异构时序事件数据,提出了患者数据表示学习的问题建模框架。

提出了异构LSTM模型用于学习异构时序事件的表征向量,该模型可以适用于多规模采样频率的不同类型事件数据,并对其复杂时序依赖关系建模。

在真实临床数据中对死亡预测和异常化验结果预测的实验证明了模型的实用效果。


2 相关工作

2.1 电子病历数据的性质

电子病历数据的分析于2016年前后逐渐成为学术界研究的热点。电子病历可以抽象成一系列临床事件的集合,其主要性质可以从3个角度理解:信息广度(高维度、信息源异构)、时序性质(采样频率不规则、采样频率分布广)以及信息深度(医疗事件属性、先验知识丰富),从这3个基本的角度可以派生出这些医疗数据之间的多类型、多尺度、多层次的时序依赖关系。

2.1.1 信息广度

电子病历数据中包含不同来源的上万种事件(医疗特征),这就导致了信息的维度很高。比如诊断编码这一信息,在加州大学欧文(尔湾)分校(UCI)的机器学习数据库的糖尿病复诊数据中,有900个不同的诊断编码;在医疗急救服务中心的一个1万条患者数据样本中,有153个粗粒度的诊断编码。并且不同来源的数据之间有很大的差异,有多变量时间序列、不规则的事件序列、带属性的临床事件、文本等。

2.1.2 时序性质

临床事件的采样不规则性使得其采样的时间序列数据与一般等间隔采样的时间序列数据差别很大,并且事件发生的密度和患者的临床状态成一定的相关性。从时间序列的角度看,不规则采样的临床事件经常有缺失值,只包含前后顺序的序列无法反映采样频率的分布。不同类型的事件有不同规模的采样频率,如诊断事件的采样频率大约在月数量级,用药事件的采样频率大约在天数量级,生理信号的采样频率大约在小时数量级,脑电波信号的采样频率大约在毫秒数量级。


2.1.3 信息深度

有些临床事件包含了比一个数值或者一个医疗编码更复杂的信息。比如在用药事件中,既有药物类型,又有注射剂量和给药时间信息;在化验事件中,既有化验项目,又有化验结果指数、异常标记的信息;有时患者的既往病史中还包含和医疗知识相关的先验信息和人口统计描述。

2.1.4 时序依赖关系

电子病历数据中的时序依赖关系与文本词序列中的顺序依赖关系有很多不同之处。在自然语言处理研究中,词汇的短期顺序很重要,比如,“in the front”由于语法规则,后面大多情况会跟“of”。但是长期顺序(比如段落之间)往往不是特别重要,只要是关于同一主题的即可。但是在电子病历数据中,医疗事件的短期顺序往往和病历记录系统录入相关,顺序性不重要,比如在5 min之内先做化验或先进行注射都是可以的,它们的顺序可以认为是任意指定的。然而长期顺序却非常重要,当前要做的化验和用药往往是根据之前的患者症状或指标以及疾病的阶段性发展规律判断得出的。并且,临床事件的时间戳表达的是真实时间,而不仅仅是前后关系。在病例数据中,有很多事件发生于相同时间,这更印证了医疗事件的短期无序性和长期有序性。


2.2 传统统计特征抽取与机器学习方法

为了方便建模,传统关于临床终点预测的工作一般只使用部分电子病历记录数据,从而避免直接处理异构时序事件。

一些工作在专家指导下,只使用了一个全体医疗事件的子集作为患者的特征。例如,AlaaA M使用21个时序生理信号(包括11个生理指标以及10个化验结果)预测重症加强护理病房(intensive care unit,ICU)转诊。也有工作选取了50个时间序列,并把这个电子病历信息的子集用多任务高斯过程建模,将多任务高斯过程的超参数作为对患者的表示向量,在这个向量空间中,通过计算患者的相似度或输入传统分类器进行预测。值得注意的是,人工选取部分特征会被动地带来专家偏向性(expert bias),只能反映出电子病历数据的部分信息。这类工作很难完全利用到电子病历整体的信息。

另一大类工作是在处理病例数据时,忽略了临床事件中的属性信息,只使用临床事件的离散变量(如ICD 编码的序列)预测临床终点。例如一些工作为不同类型的临床事件分别训练语义嵌入向量,再综合预测后续的药物滥用事件(ADE)。Retain使用两个循环神经网络产生患者每次就诊过程中各个ICD编码的注意力权重,对原始ICD编码的嵌入向量进行加权,再预测心力衰竭的发生概率。也有工作使用卷积神经网络对不规则的医疗编码序列进行建模,预测未来的发病风险。

这类工作没有利用除了医疗编码之外的、包含丰富细粒度属性的临床事件信息,如包含注射剂量属性的药物使用事件等。本文提出的电子病历分析框架可以同时建模全体生理指标时间序列、医疗编码以及包含丰富属性的临床事件信息,以充分挖掘利用电子病历中蕴含的知识。


2.3 深度学习方法

利用深度学习技术的患者表示学习主要沿用了传统事件序列(event sequence)和多变量时间序列(multivariate time series)的建模方法,独立建模病例中的各类数据。如谷歌团队提出了对这种全体未加工数据的快速可交互资源存储格式(fast healthcare interoperability resources, FHIR),并以此格式为基础,进行深度学习建模,从而预测多种重要的结果性事件。在电子病历记录的基本格式层面,本文方法与其不谋而合,不仅可以比较宽松地适应各个医院的病例记录形式和解决预测问题,也不会因为数据规则化而丢失重要信息。

然而,在电子病历建模层面,传统方法会根据数据类型,对离散的数据(如ICD诊断编码)和连续时间序列变量(如血压时间序列)单独建模。比如时间注意力前馈模型(feed forward model with time-aware attention)直接对离散的事件序列(event sequence)的表征向量建模,同时时间序列自举嵌入模型(boosted embedded time-series model)对每个时间序列(time series)自举十大类时序谓词(如在某个时间点T之后值大于V等),最后筛选出10万种谓词,作为特征输入神经网络。在对记录时间的处理上,通常会用区间给固定时间间隔的事件做一个统一的时间戳,如加权循环神经网络(weighted RNN)模型把离散事件序列数据分成10个大类,固定12 h为时间间隔,把序列划分为事件组的序列,每一类序列分别学习向量表示,在预测结果层面进行整合输出。

本文的思路不是以病例的数据形式做最初的划分依据,而是以医疗过程中的事件为基本单位,保留精确的事件发生时间,进行建模。这样可以更大限度地保留和反映各个事件之间的关系以及不同事件发生的频率结构。本文使用异构时序事件的表示学习框架全面建模电子病历数据时,把所有的病例信息作为带有属性的临床事件,并按照时间排序,同时保留事件的精确发生时间。这样虽然可以全盘融合异构时序事件,但由异构事件组成的序列非常长,在MIMIC-III数据集中,绝大多数患者样本序列长度过万。于是抽取异构时序事件的复杂时序依赖的问题的难点就转化成了如何保持超长序列的长期依赖关系。

然而,由于梯度消失问题,传统的循环神经网络只能抓到10跳以内的依赖关系。通过设计反向传播中的无损反馈流结构,长短期记忆网络可以解决部分问题,但它的长期依赖关系大约只能维持在50跳以内。

时钟循环神经网络(clock-work-RNN)把隐藏层划分为多个模块,每个模块分别在不同的频率下处理输入信息,使得长期依赖容易被抽取,这种固定频率的稀疏化更新比传统LSTM更好地辅助了对长期依赖关系的建模。有一类自动决策延迟更新的神经网络——相位LSTM,也对长期依赖的保持起到了比较好的作用。相位LSTM是一个先进的循环网络,它给LSTM增加了一个计时的机制,稀疏地处理非常长的输入序列。这种机制可以使相位LSTM的训练误差在反向传播中被比较好地保持,从而达到非常快的收敛速度。跳跃循环神经网络(skip RNN)也可以根据输入自动地跳过不重要的输入数据,实现稀疏化采样,更好地抽取长序列的长期依赖关系。

然而,这类模型都是在同构的序列上把握长期依赖关系的,并不能直接在异构的时序事件中抓取更复杂的时序依赖关系,从而服务于对患者数据的表征学习。


3 模型设计


3.1 问题定义

3.1.1 异构事件序列

给定电子病历数据中的病人p, p的特征由序列长度为N的动态特征{Xt}1≤t≤N组成。{Xt}1≤t≤N可以看作一个异质医疗事件序列,Xt为一个三元组Xt=(type,value,time),type为事件的种类,value为事件的属性,time为事件被记录下来的时间。{Xt}1≤t≤N中的事件按照时间先后顺序排列。事件种类向量记为e=Xt. type。病人p对应一个二元标签0或1,表示在XN.time+24h发生的临床终点事件,例如病情平稳或患者死亡。

3.1.2 临床终点预测任务

本文目标是基于患者的历史临床数据(异构时序事件),动态地预测两个重要的临床结果。第一个任务是死亡预测,预测患者是否还在接受治疗,或是已经死亡。第二个任务是钾离子浓度异常预测,预测的目标是确认血钾化验是否为异常值。


3.2事件嵌入


对于时间点t的事件Xt=(type,value, time),分别对type和value进行编码。type向量为一个独热(one-hot)向量,令Ctype∈RN×M为type的编码矩阵,其中N为编码后的维数,M为type种类数,则type的编码为:


640?wx_fmt=png

事件属性value由两部分组成:value=[valueC, value_n]。这二者也都是独热向量, valueC为事件的离散型变量属性,value_n是数值属性。同样地,用VC∈RN×C,VN∈RN×U分别表示它们的编码矩阵,其中,U是离散型属性的总数,C是连续型属性的总数。属性的编码与事件类型p相加得到动态事件的总体编码:


640?wx_fmt=png

其中,Vc、Vn和C type都是待学习的参数。

3.3 异构事件LSTM

图2为基础LSTM的神经元基本结构,图3为异构事件LSTM(HE-LSTM)的神经元基本结构。两者主要区别在于,基础LSTM神经元中有3个门函数,分别为输入门(input gate)、输出门(output gate)、遗忘门(forget gate)。


640?wx_fmt=jpeg

图2   LSTM的神经元基本结构

640?wx_fmt=jpeg

图3   异构事件LSTM(HELSTM)的神经元基本结构


640?wx_fmt=png

其中,it、ft、ot分别表示t时刻的输入门、输出门和遗忘门函数。ct是激活向量,xt和ht分别为t时刻的输入向量和t时刻的隐藏层输出向量。σ(·)表示sigmoid激活函数,tanh(·)表示双曲正切激活函数。Wix、Wih、Wfx、Wfh、Wcx、Wch、Wox、Woh是神经网络的矩阵参数,wic、wfc、woc、bo、bi、bf、bc均为神经网络的向量参数。其中,所有参数的下标是为了区分各自所在的网络结构的位置而设置的,其命名规则是取自该参数连接的输入层和输出层的变量名,例如Wix连接了输入xt和输出it,故下标命名为ix,后文涉及的参数均按上述规则命名。

HE-LSTM模型的核心思路是分工合作。神经元分别以不同的周期、异步地追踪记录不同事件簇的信息。在经典LSTM的基础上,本文为每个隐藏层神经元设计了一个事件门(event gate) jl(下标l表示第l个输入,jl取值为0或1),如果它打开,那就正常按照LSTM更新,如果它关闭,就保持原来的值不变,如式(4)中的cl所示。其中,cl是经典LSTM计算出的更新值。

640?wx_fmt=png

事件门js,t由事件类型S和事件记录的时间t来决定开关状态。式(5)是事件门js,t的表达式。其由两部分构成,一部分是事件过滤器(event filter)es(由事件类型S决定),另一部分则是相位门(phased gate)kt(由时间t决定)。


640?wx_fmt=png

事件过滤器只允许特定的某些种类事件输入神经元,相位门使得该神经元只有特定周期下才是开放的。这样就保证了每个神经元只会抓取特定几类事件的特征,并对其进行采样,解决了时间的复杂多样性和医疗事件序列过长而导致的训练效果变差问题。事件门的开关由相位门中某个特定的周期控制,只有当门开放时才能更新式(3)和式(4)的各个参数。

事件过滤器的表达式如式(6)所示。其中,σ(·)表示sigmoid函数,tanh(·)表示双曲正切函数,Wms、Wem为训练中学习的矩阵参数,bm、be为训练中学习的向量参数。事件过滤器可以让每个神经元关注各自不同的一组事件种类,从而更好地学习出混合事件序列中的信息。


640?wx_fmt=png

相位门k是一个周期性变化的函数,如式(7)所示。给定了周期τ初始相位s,则kt是φt的函数,也随t以τ为周期变化,ron是一个超参数,控制相位门开放状态占全周期的比例。在0<φt<r on时,相位门打开;在其他时刻,由于又是一个非常接近于0的超参数,kt接近于0,相位门关闭。只有在相位门打开时,所有参数才可以更新,这样就可以对输入进行周期性的采样,从而解决输入序列过长的问题。


640?wx_fmt=png

综上,对于某个神经元,只有符合对应事件门的类型条件,并且在其采样周期中的事件信息才会被更新到神经元中,因此可以认为,这个神经元表示了某一类事件在某种采样周期下的状态。

式(8)是损 失函数,采用了交叉熵的形式。其中,yt为模型t时刻的预测结果,yˆt表示真实指标,N为总训练样本数。式(9)中,ht为t时刻隐藏层的输出,wp和pb为要在训练中学习的向量参数。


640?wx_fmt=png

4 实验结果

4.1 数据描述和实验设置


本文的实验在死亡预测数据集和异常化验结果预测(钾离子异常预测)数据集上进行,该数据集由美国的一个医疗健康中心(Beth Israel Deaconess Medical Center)的ICU患者病历数据(MIMIC-III)生成。

数据集抽取了MIMIC-III中的24 301个病人样本,共涵盖3 418种总数 达20 290 879个异质时序事件,平均时间跨度为87 h 58 min。各类型事件的统计信息见表1。

实验中,数据集被划分为训练集、验证集和测试集,比例为7:1:2。通过验证集选择超参数,使用“提前结束技术(early stop)”决定训练轮数。所有实验都由Theano实现,采用Adam优化算法进行优化,学习率设为0.001。


640?wx_fmt=png

4.2 比较方法


本文将HE-LSTM与独立序列模型(独立LSTM、共享参数的独立LSTM)、延迟更新循环神经网络模型(如clock-work RNN、phased LSTM)以及医疗领域的异构序列模型(LSTM+事件嵌入、Retain)这3类方法进行比较。前文介绍过clock-work RNN和phased LSTM,这里不再赘述。

(1)独立LSTM

该模型使用多个LSTM对每个同构的事件建模,然后平均所有的结果,用逻辑回归来预测。这种方法无法对上千种的事件直接建模,因此本文只选出了可以承受的25种重要事件,与很多重要工作的筛选方式一致。

(2)共享参数的独立LSTM

共享参数的独立LSTM和独立LSTM一致,但是其所有模型参数共享,从而能够支持建模所有的医疗事件。

(3)LSTM+事件嵌入

该模型使用本文提到的事件类型嵌入以及属性编码方法得到事件向量,作为传统LSTM的输入进行预测。

(4)Retain

Retain模仿内科医生逆序对病例记录建模,用两个循环神经网络输出注意力权重,并将其作为病例事件的加权权重,提供有可解释性的预测。

4.3 评测指标

由于数据集具有正负样本不平衡的问题,本文采用AUC(the area under ROC curve)和AP(average precision )作为评价指标。AUC是ROC曲线与x轴所围成的面积,AP是PRC(precision-recall curve)曲线与x轴所围成的面积,二者对正负样本不平衡的数据都具有顽健性。

为了验证事件门的收敛速度,使用测试集的交叉熵(cross entropy)衡量模型在测试集的拟合程度,具体计算方法按照式(8)进行。

4.4 量化实验结果

表2展示了不同方法在死亡预测和异常化验结果预测数据集上各自的AUC和AP。从表2的数据中可得出以下结论。


640?wx_fmt=png

首先,可以对不同事件相关性建模的模型,整体好于对各个数据源采样的时序数据单独建模的模型,其中HE-LSTM取得了最好的表现效果。例如,在患者死亡预测任务中,相对于表现最好的独立序列模型(独立LSTM),Retain、LSTM+事件嵌入和HE-LSTM对AP的提升分别为0.023 5、0.187 2和0.211 4。在其他数据集和评测指标上也有一致的结果。并且本文的模型在这类异构事件序列模型上,取得了最好的效果。例如,HE-LSTM在异常化验结果预测上,相比Retain和LSTM+事件嵌入,提升了0.081 8和0.089 3的AP。可以得出如下结论:异构时序事件的相关依赖关系对临床终点预测十分有效;相比对事件独立建模,学习不同事件的联合分布可以有效地捕捉不同事件的时序依赖关系。

其次,相比密集更新的RNN,可以适应采样频率系数更新的模型更优。例如clock-work RNN相比独立建模的序列模型,在死亡预测实验上AP分别提升了0.160 8和0.188。同时,在异常化验预测结果任务上,相对于表现最好的独立序列模型(独立LSTM),phased LSTM提升了0.052 6的AUC和0.060 6的AP。可以得出如下结论:多频率采样的模式对预测临床终点也是有帮助的,稀疏更新的模型可以充分利用这个性质,让不同单元聚焦更重要的输入,而非等同对待长序列中所有的输入。

再次,HE-LSTM在不同数据集和不同的评价指标上都取得了最高的表现。HE-LSTM不仅优于稀疏更新的RNN模型,也优于异构序列模型。仅利用多频率采样特性的稀疏更新模型和直接汇合所有不同类型事件的异构序列模型,都不是临床终点预测最好的选择。例如,HE-LSTM相比于最好的稀疏更新模型(clock-work RNN),在死亡预测任务上提升了0.111 6的AUC和0.050 6的AP。同时,在异常化验结果预测任务上,相对于异构事件序列模型Retain,HE-LSTM提升的AUC和AP分别是0.066 2和0.081 8。可以得出如下结论:HE-LSTM由于追踪了不同事件的时序依赖关系,并自动适应不同事件的不同采样频率,取得了最优秀的预测表现。


4.4.1 不同设置下的事件门销蚀实验

为了评测不同组成部分对事件门的影响,本文将事件门分别替换成它的两个因子,事件过滤器和相位门,其余部分和HE-LSTM一致。两个数据集的实验结果见表3,包括AUC、AP以及测试集交叉熵,还有在第一轮训练完成后的这3个指标。


640?wx_fmt=png

事件过滤器的主要作用是通过对异构事件的关联建模,提升模型效果。HELSTM和只用事件过滤器模块的模型都可以达到比较好的预测表现。在死亡预测数据集上,HE-LSTM和只用事件过滤器的模型相比于只有相位门的模型分别提升了0.004 5和0.004 7的AUC,提升了0.022和0.021 2的AP,交叉熵降低了0.007 2和0.006 8。

相位门模块帮助HE-LSTM达到一个比较快的收敛速度,因为它具备适应不同频率采样规模的系数更新效果,可以比较好地保留回传梯度。HE-LSTM和仅使用相位门的模型在所有数据集的第一轮训练结束后的评测指标上都优于仅使用事件过滤器模块的模型。对于化验异常预测任务来说,只使用相位门的模型和HE-LSTM在第一轮训练结束后相比单独使用事件过滤器的模型,AUC要高0.030 3和0.052 8, AP要高0.034 9和0.056 2,交叉熵大约低0.007 5和0.015。

通过上述比较,可以得出如下结论:事件过滤器和相位门在对尺度采样的异构时序事件建模时,可以协同合作,达到了准确预测表现和高效的训练速度,从而提升临床终点预测的准确度。

4.4.2 不同输入序列长度的模型效果比较

为了验证本文提出的模型以及其他序列模型捕捉异构事件时序依赖的能力,本文给模型输入不同长度的序列数据,长度范围为20~1 000个事件。从图4可以得出以下结论。


640?wx_fmt=jpeg

图4   不同输入序列长度的模型效果比较示意


首先,时序信息对预测临床终点是有效的。大多数模型会随着输入序列的长度增加而效果提升,特别是在输入长度小于200个的时候。

其次,HE-LSTM相比于其他模型,更擅长捕捉异构时序事件中的时序依赖关系。当输入长度较小时,各个模型的表现差不多,这种情况下时序依赖关系基本是同一段时间内出现的事件共现关系。因此各个事件单独的表示向量组合和异构事件联合表征反应的信息量基本一致。然而,当序列长度超过200并越来越长时,模型的表现稳步提升(AP从0.755提升到0.769,AUC从0.948提升到0.952)。而其他模型由于不能比较好地捕捉超长序列下的时序依赖关系,故而基本没有特别大的提升。


4.4.3 不同的初始化周期的效果比较

为了探索在事件门中事件过滤器的作用,本文比较了HE-LSTM和去掉事件过滤器(即只用相位门)的模型的作用。在给模型提供不同的初始化周期的情况下,训练死亡预测任务,这些周期是指数化后的4种均匀分布:exp(U(1,2)),exp(U(2,3)),exp(U(3,4)),exp (U(4,5))。

图5反映了两种模型在不同事件嵌入的维度及模型中门的类型设置下的表现。如“32相位门”表示事件嵌入向量的维度是32,并且模型中只包含相位门;“64事件门”表示事件嵌入维度为64,是完整的HELSTM模型。可以看到,完整的HE-LSTM对不同的初始化周期具有顽健性。比如在上述4种初始化设置下,完整的HE-LSTM相比于没有事件过滤器的模型,在死亡预测任务上平均提升了0.025、0.028、0.018和0.042的AP。因此可以得出如下的结论:事件门由于得到了事件过滤器的帮助,可以更容易地适应于异构时序事件的多尺度采样频率。


640?wx_fmt=jpeg图5   不同的初始化周期的效果比较


5 结束语

本文提出了一个异构时序事件的表示学习框架HE-LSTM模型,可以自动适应多源异构数据的多尺度采样频率,通过异步追踪不同事件的时序信息,该模型得到的患者表示向量可以捕捉到不同时间之间的时序依赖关系。并且本文的模型在真实数据集上也体现出了相对于其他典型方法的优越性。

本文的表示学习框架和方法有很多可以扩展推广的空间,例如在医疗大数据领域,可以借助医学知识图谱,整合通用先验知识和患者人口统计信息;可以加入脑电波、心电图、挖掘等多尺度的层次化时序依赖关系;服务于多任务元学习的综合动态疾病诊断。

此外,该框架可以向其他领域迁移,特别是应用于多源异步采样的传感器数据或行为记录数据等。例如在推荐系统研究中的用户行为的表征学习或者智能教育研究中的学生学习行为的表征学习等,都可以用该框架进行建模。


作者简介

刘卢琛(1991- ),男,北京大学信息科学技术学院博士生,主要研究方向为深度学习、医疗大数据等。

沈剑豪(1995- ),男,北京大学信息科学技术学院博士生,主要研究方向为机器学习、自然语言处理等。

张铭(1966- ),女,北京大学信息科学技术学院教授、博士生导师,教育部高等学校大学计算机课程教学指导委员会委员,中国计算机学会(CCF)教育工作委员会副主任,ACM教育专家委员会唯一的中国理事,中国ACM教育专家委员会主席。主要研究方向为数据挖掘、机器学习、知识图谱等。

王子昌(1996- ),男,北京大学信息科学技术学院硕士生,主要研究方向为深度学习、医疗大数据、知识图谱等。

李浩然(1993- ),男,北京大学信息科学技术学院硕士生,主要研究方向为知识图谱和医疗大数据。

刘泽群(1997- ),女,北京大学信息科学技术学院本科生,研究兴趣为深度学习、医疗数据挖掘等。


《大数据》期刊

《大数据(Big Data Research,BDR)》双月刊是由中华人民共和国工业和信息化部主管,人民邮电出版社主办,中国计算机学会大数据专家委员会学术指导,北京信通传媒有限责任公司出版的中文科技核心期刊。

640?wx_fmt=jpeg

关注《大数据》期刊微信公众号,获取更多内容


往期文章回顾

基于主动学习和克里金插值的空气质量推测

从数据的属性看数据资产

数据安全治理的几个基本问题

“全息数字人”——健康医疗 大数据应用的新模式

医疗数据治理——构建高质量医疗大数据智能分析数据基础


2018-12-25 17:37:58 qq_40570857 阅读数 91

摘要: 本文主要讨论CPU、GPU、FPGA、ASIC、异构计算等的一些概。,希望帮助入门者对相关知识有一些了解。

人工智能包括三个要素:算法、计算和数据。
计算所对应的硬件平台有:CPU、GPU、FPGA、ASIC。

我们先来举个例子:假如您是一个天才,天文、地理、文学、美术、数学等都很好很优秀,我们把你的大脑比作“CPU”,你能一个人做好各种工作,这就好比“CPU兼容性好”,但是某些工作你虽然能做,比如美术,你虽然能胜任,但没有专业做美术的人工作更快、更高效,同时也没有人家的专业素质,这时你是不是感觉力不从心?你是不是得找个专业的来帮助你?假如给你配个专家,协同你工作,这个专家就好比“GPU、FPGA等”这样你工作起来是不是更快。

上面的列子可以看出,你什么都精通,可能你更能胜任的不是去执行某项处理而是控制者。CPU作为通用处理器,也是更偏重支持控制流数据。CPU每个物理核中大部分的硬件资源被做成了控制电路和缓存,用来提高指令。只有小部分是真正用来计算的。在这样的架构下,CPU能兼容大量指令,但是实际的计算效率并不高。CPU本身实际运行中,CPU的代码都存在Memory中,需要经过取指令,译码,然后才能执行指令,在这个流程中,取指令,译码的时间,大大降低了数据处理速度。
这里有人会说,为什么不再请一个天才"两个CPU同时协同工作",这样我们是不是就可以整出个同构计算出来?其实没毛病当然是可以的,因为之前的芯片结构貌似就是这样迭代的。但是专业的事由专业的人来做,假如是处理图像,配一个更专业,性能更高的GPU协同CPU工作是不是一个更完美的解决方案?这就是我们所说的异构计算

异构计算可以做什么:阿里云异构计算,依托阿里云领先国际的技术,以其高性能高稳定的特点,可应用于AI深度学习、基因计算、渲染、多媒体编解码、计算流体动力学、计算金融学、基因组学研究、渲染、多媒体编解码&机器学习等复杂的计算环境。
阿里云异构计算直达:https://dwz.cn/tui9Nr8n

导构计算图上
异构计算图2

深度学习框架对比

阅读数 3552

没有更多推荐了,返回首页