使用深度学习进行生存分析

相关资源

原论文地址:here

论文中使用的深度生存分析库:DeepSurv,是基于Theano 和 Lasagne库实现的,支持训练网络模型,预测等功能。

考虑到DeepSurv库中存在着一些错误以及未实现的功能,博主使用目前主流的深度学习框架Tensorflow实现了深度生存分析库:TFDeepSurv。欢迎有兴趣的同学Star和Fork,指出错误,相互交流!

TFDeepSurv简介:基于tensorflow的深度生存分析框架,经过模拟数据和真实数据的测试。支持生存分析数据事件时间出现ties的建模,自定义神经网络结构及参数,可视化训练过程,输入训练数据特征重要性分析,病人生存函数的估计。还有支持使用科学的贝叶斯超参数优化方法来调整网络参数。

博主有空会给出TFDeepSurv各个功能实现参考的源论文!

前言

本文主要的目的为了介绍深度学习是如何运用到生存分析中的,包括其基本原理。然后介绍目前实现了利用深度学习进行生存分析的开源软件包 DeepSurv,它实现了生存分析模型,使用Deep Neural Networks来训练学习参数,并且还实现了风险人群的划分。

还是一样的,强烈建议你去读一下原论文DeepSurv: Personalized Treatment Recommender System Using A Cox Proportional Hazards Deep Neural Network.,相信你会收获很大,至少比看我的好一万倍。写这篇文章的动力有几点,一是不想自己学过的知识什么很快就忘了,感觉记录一下比较重要(博主比较蠢),当作是看论文的笔记吧;二是看完文章之后,觉得我们平时还是要多思考,论文里的思想其实也不是完全原创的,神经网络不是,生存分析cox比例风险模型1972年就有了,但是别人就能洞察到使用深度学习的思想去学习COX模型中需要估计的参数,个人觉得这是一个有科学素养的人才能做到的吧;三是博主在为了PR收集生存分析资料的时候,深感不易,这方面的中文的介绍很少,所以为了方便大家的交流讨论,还是写一下吧。

博主知识水平有限,不吝赐教!欢迎提出错误!

问题来源

假设你已经知道了生存分析主要是在做哪些工作。我们都知道在进行生存分析的时候,有这么几种方法:

  • 参数法:当生存时间符合某一个已知分布时,知道了分布函数,那么剩下的就是求解该分布的参数了。
  • 非参数法:用KM估计去求生存函数,作生存曲线,这里面不涉及任何参数,主要思想就是频率代替概率。
  • 半参数法:也就是使用COX比例风险模型来求生存函数,这个也是本文的重点。

关于COX比例风险模型是怎么提出的,这个是1972年前辈的智慧,本文不打算介绍,这里给出一个链接:hazard-curve,可以帮助你快速了解生存分析和生存函数以及风险曲线的数学定义,然后你就可以去看COX比例风险模型是怎么提出来的了。确保自己懂了COX比例风险模型的原理,可以问自己几个问题:比例两个字是体现在那个地方?为什么风险函数会是 h ( t ) = h 0 ( t ) e θ x 这种形式?

COX比例风险模型,直接给出了风险函数的数学表达式(假设你已经学会了懂了其背后的数学原理):

h ( t ) = h 0 ( t ) e θ x

其中, θ = ( θ 1 , , θ m ) 是线性模型的系数或未知参数, h 0 ( t ) 是基准风险函数。 e θ x 描述了患者观察到回归变量 x 时的死亡风险比例。对 i N , θ i > 0 ,表示该协变量是危险因素,越大使生存时间越短。 i N , θ i < 0 表示该协变量是保护因素,越大使得生存时间越长。

现在需要去求取参数 θ ,其思想就是偏似然估计法。假定在某死亡时间没有重复事件发生,设 t 1 < t 2 < < t k 表示在观察数据中有 k 个不同的死亡事件。设 x i 表示对应时间 t i 的观察协变量。设 R ( t i ) 表示在 t i 时间仍然处于观察研究的个体集合。则风险函数 h ( t ) 的参数估计可以用以下偏似然概率估计方法:

p l ( θ ) = i = 1 k e θ x i j R ( t i ) e θ x j

其中 q i = e θ x i j R ( t i ) e θ x j 表示的是第 i 个死亡个体,其死亡条件概率。其实通俗一点的解释就是:我已经观察到时间 t i 了,现在有一群人,我可以利用风险公式 h ( t i ) 求出这群人每一个个体的死亡风险,其中有一个人恰好在 t i 时刻发生了死亡事件,那么这个人的死亡条件概率就写为:

q i = h i ( t i ) j R ( t i ) h j ( t i ) = e θ x i j R ( t i ) e θ x j

现在就是利用偏似然估计的思想,将所有死亡时刻 t 1 , t 2 , . . . , t k 的死亡条件概率相乘,求取是这个乘积最大的参数值 θ ,把它作为估计量。

注意COX模型给出的前提:假设协变量的总影响可以表示为它们的线性组合。例如,我评价一个人的颜值 v ,你告诉我颜值可以这么计算 v = 2 x 1 + 9 x 2 + 1.3 x 3 x 1 , x 2 , x 3 表示眼睛大小,脸型,鼻子高度(当然,这里是打个比方QAQ)。事实上,很多情况下,协变量的线性组合不能准确衡量它们对某个目标值的影响! 关于这点例子很多(例XOR问题),就不一一介绍了。

问题根源就是在 θ x ,我们把它记为 r 。那么我们可不可以把它表示为非线性组合呢?但是好像它的数学表达式公式不太好给出,无论我们怎么表示 r ,其目标都是使 p l ( θ ) 最小。这个时候,神经网络的作用就显现出来了,它对于表示一组协变量的非线性组合简直太擅长了!假设网络的输入为一组协变量 x = ( x 1 , x 2 , . . . , x n ) ,那么网络的输出表示为 r ^ w , b w , b 为神经网络的参数。然后,损失函数就很显而易见了:

L = l o g ( p l ( r ^ w , b ) ) = l o g ( i = 1 k e r ^ w , b i j R ( t i ) e r ^ w , b j )

h ( x ) 表示为网络的输出,那么上式可以化简为:
L = i = 1 k [ h i ( x ) l o g ( j R ( t i ) e h j ( x ) ) ]

剩下的工作就交给神经网络去训练样本学习到这样的非线性组合 r ^ w , b = f ( x , θ ) 。其实思路还是很好懂的嘛。

DeepSurv网络框架实现

DeepSurv 的工作就是实现了上面介绍的所有内容(最重要的是损失函数),还实现了一些其他的功能(比如划分风险人群)。下面介绍一下这个框架的实现。

这里是DeepSurv类下面定义的方法:

class DeepSurv:
    def __init__()
    # 计算Loss function值
    def _negative_log_likelihood()
    # 得到当前网络的loss值同时更新网络参数
    def _get_loss_updates()
    # 得到可调用的函数:训练集上,网络进行一次正向和反向传播
    #                验证集上,一遍正向传播,计算Loss function值
    def _get_train_valid_fn()
    # 计算评估指标:C Index
    def get_concordance_index()
    def _standardize_x()
    def prepare_data()
    def train()
    def to_json()
    def save_model()
    def save_weights()
    def load_weights()
    # 得到网络的输出值
    def risk()
    def predict_risk()
    # 划分风险人群
    def recommend_treatment()
    def plot_risk_surface()

初始化函数:初始化网络结构,并且记录一些参数

def __init__(self, n_in,
    learning_rate, hidden_layers_sizes = None,
    lr_decay = 0.0, momentum = 0.9,
    L2_reg = 0.0, L1_reg = 0.0,
    activation = "rectify",
    dropout = None,
    batch_norm = False,
    standardize = False,
    ):

按照给定hidden_layers_sizes的搭建指定的网络结构:
输入层:network = lasagne.layers.InputLayer(shape=(None,n_in),input_var = self.X)
隐藏层:network = lasagne.layers.DenseLayer(network, num_units = n_layer, nonlinearity activation_fn, W = W_init)(参数决定该层是否dropout或者BatchNorm)
输出层:network = lasagne.layers.DenseLayer(network, num_units = 1, nonlinearity = lasagne.nonlinearities.linear, W = lasagne.init.GlorotUniform())

训练函数:在给定的训练数据上进行训练,并且在验证集上进行评估

def train(self,
    train_data, valid_data= None,
    n_epochs = 500,
    validation_frequency = 250,
    patience = 2000, improvement_threshold = 0.99999, patience_increase = 2,
    logger = None,
    update_fn = lasagne.updates.nesterov_momentum,
    verbose = True,
    **kwargs):

训练函数里的内容就是通用的一套了:
- 准备好训练数据
- 每个epoch迭代训练网络
- 计算Loss,反向传播更新网络参数

具体地,源代码里还有很多细节的地方,自己亲身学习一下还不错啊!

猜你喜欢

转载自blog.csdn.net/u014686462/article/details/78183562