关于tensorflow的分布式训练和部署, 官方有个英文的文档介绍,但是写的比较简单, 给的例子也比较简单,刚接触分布式深度学习的可能不太容易理解。在网上看到一些资料,总感觉说的不够通俗易懂,不如自己写一个通俗易懂给大家分享一下。

如果大家有看不懂的,欢迎留言,我再改文章,改到大学一年级的学生可以看懂的程度。

1. 单机多GPU训练
先简单介绍下单机的多GPU训练,然后再介绍分布式的多机多GPU训练。
单机的多GPU训练, tensorflow的官方已经给了一个cifar的例子,已经有比较详细的代码和文档介绍, 这里大致说下多GPU的过程,以便方便引入到多机多GPU的介绍。
单机多GPU的训练过程:
a) 假设你的机器上有3个GPU;
b) 在单机单GPU的训练中,数据是一个batch一个batch的训练。 在单机多GPU中,数据一次处理3个batch(假设是3个GPU训练), 每个GPU处理一个batch的数据计算。
c) 变量,或者说参数,保存在CPU上
d) 刚开始的时候数据由CPU分发给3个GPU, 在GPU上完成了计算,得到每个batch要更新的梯度。
e) 然后在CPU上收集完了3个GPU上的要更新的梯度, 计算一下平均梯度,然后更新参数。
f) 然后继续循环这个过程。
通过这个过程,处理的速度取决于最慢的那个GPU的速度。如果3个GPU的处理速度差不多的话, 处理速度就相当于单机单GPU的速度的3倍减去数据在CPU和GPU之间传输的开销,实际的效率提升看CPU和GPU之间数据的速度和处理数据的大小。
写到这里觉得自己写的还是不同通俗易懂, 下面就打一个更加通俗的比方来解释一下:
老师给小明和小华布置了10000张纸的乘法题并且把所有的乘法的结果加起来, 每张纸上有128道乘法题。 这里一张纸就是一个batch, batch_size就是128. 小明算加法比较快, 小华算乘法比较快,于是小华就负责计算乘法, 小明负责把小华的乘法结果加起来 。 这样小明就是CPU,小华就是GPU.
这样计算的话, 预计小明和小华两个人得要花费一个星期的时间才能完成老师布置的题目。 于是小明就招来2个算乘法也很快的小红和小亮。 于是每次小明就给小华,小红,小亮各分发一张纸,让他们算乘法, 他们三个人算完了之后, 把结果告诉小明, 小明把他们的结果加起来,然后再给他们没人分发一张算乘法的纸,依次循环,知道所有的算完。
这里小明采用的是同步模式,就是每次要等他们三个都算完了之后, 再统一算加法,算完了加法之后,再给他们三个分发纸张。这样速度就取决于他们三个中算乘法算的最慢的那个人, 和分发纸张的速度。
2. 分布式多机多GPU训练
随着设计的模型越来越复杂,模型参数越来越多,越来越大, 大到什么程度?多到什么程度? 多参数的个数上百亿个, 训练的数据多到按TB级别来衡量。大家知道每次计算一轮,都要计算梯度,更新参数。 当参数的量级上升到百亿量级甚至更大之后, 参数的更新的性能都是问题。 如果是单机16个GPU, 一个step最多也是处理16个batch, 这对于上TB级别的数据来说,不知道要训练到什么时候。于是就有了分布式的深度学习训练方法,或者说框架。
参数服务器
在介绍tensorflow的分布式训练之前,先说下参数服务器的概念。
前面说道, 当你的模型越来越大, 模型的参数越来越多,多到模型参数的更新,一台机器的性能都不够的时候, 很自然的我们就会想到把参数分开放到不同的机器去存储和更新。
因为碰到上面提到的那些问题, 所有参数服务器就被单独拧出来, 于是就有了参数服务器的概念。 参数服务器可以是多台机器组成的集群, 这个就有点类似分布式的存储架构了, 涉及到数据的同步,一致性等等, 一般是key-value的形式,可以理解为一个分布式的key-value内存数据库,然后再加上一些参数更新的操作。 详细的细节可以去google一下, 这里就不详细说了。 反正就是当性能不够的时候, 几百亿的参数分散到不同的机器上去保存和更新,解决参数存储和更新的性能问题。
借用上面的小明算题的例子,小明觉得自己算加法都算不过来了, 于是就叫了10个小明过来一起帮忙算。
tensorflow的分布式
不过据说tensorflow的分布式没有用参数服务器,用的是数据流图, 这个暂时还没研究,不过应该和参数服务器有很多相似的地方,这里介绍先按照参数服务器的结构来介绍。
tensorflow的分布式有in-graph和between-gragh两种架构模式。 这里分别介绍一下。
in-graph 模式:
in-graph模式和单机多GPU模型有点类似。 还是一个小明算加法, 但是算乘法的就可以不止是他们一个教室的小华,小红,小亮了。 可以是其他教师的小张,小李。。。。.
in-graph模式, 把计算已经从单机多GPU,已经扩展到了多机多GPU了, 不过数据分发还是在一个节点。 这样的好处是配置简单, 其他多机多GPU的计算节点,只要起个join操作, 暴露一个网络接口,等在那里接受任务就好了。 这些计算节点暴露出来的网络接口,使用起来就跟本机的一个GPU的使用一样, 只要在操作的时候指定tf.device(“/job:worker/task:n”), 就可以向指定GPU一样,把操作指定到一个计算节点上计算,使用起来和多GPU的类似。 但是这样的坏处是训练数据的分发依然在一个节点上, 要把训练数据分发到不同的机器上, 严重影响并发训练速度。在大数据训练的情况下, 不推荐使用这种模式。
between-graph模式
between-graph模式下,训练的参数保存在参数服务器, 数据不用分发, 数据分片的保存在各个计算节点, 各个计算节点自己算自己的, 算完了之后, 把要更新的参数告诉参数服务器,参数服务器更新参数。这种模式的优点是不用训练数据的分发了, 尤其是在数据量在TB级的时候, 节省了大量的时间,所以大数据深度学习还是推荐使用between-graph模式。
同步更新和异步更新
in-graph模式和between-graph模式都支持同步和异步更新
在同步更新的时候, 每次梯度更新,要等所有分发出去的数据计算完成后,返回回来结果之后,把梯度累加算了均值之后,再更新参数。 这样的好处是loss的下降比较稳定, 但是这个的坏处也很明显, 处理的速度取决于最慢的那个分片计算的时间。
在异步更新的时候, 所有的计算节点,各自算自己的, 更新参数也是自己更新自己计算的结果, 这样的优点就是计算速度快, 计算资源能得到充分利用,但是缺点是loss的下降不稳定, 抖动大。
在数据量小的情况下, 各个节点的计算能力比较均衡的情况下, 推荐使用同步模式;数据量很大,各个机器的计算性能掺差不齐的情况下,推荐使用异步的方式。
例子
tensorflow官方有个分布式tensorflow的文档,但是例子没有完整的代码, 这里写了一个最简单的可以跑起来的例子,供大家参考,这里也傻瓜式给大家解释一下代码,以便更加通俗的理解。
代码位置:
https://github.com/thewintersun/distributeTensorflowExample
功能说明:
代码实现的功能: 对于表达式 Y = 2 * X + 10, 其中X是输入,Y是输出, 现在有很多X和Y的样本, 怎么估算出来weight是2和biasis是10.
所有的节点,不管是ps节点还是worker节点,运行的都是同一份代码, 只是命令参数指定不一样。
执行的命令示例:
ps 节点执行:
[plain] view plaincopyprint?
  1. CUDA_VISIBLE_DEVICES=” python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=ps –task_index=0
CUDA_VISIBLE_DEVICES='' python distribute.py --ps_hosts=192.168.100.42:2222 --worker_hosts=192.168.100.42:2224,192.168.100.253:2225 --job_name=ps --task_index=0

worker 节点执行:
[plain] view plaincopyprint?
  1. CUDA_VISIBLE_DEVICES=0 python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=worker –task_index=0
  2. CUDA_VISIBLE_DEVICES=0 python distribute.py –ps_hosts=192.168.100.42:2222 –worker_hosts=192.168.100.42:2224,192.168.100.253:2225 –job_name=worker –task_index=1
CUDA_VISIBLE_DEVICES=0 python distribute.py --ps_hosts=192.168.100.42:2222 --worker_hosts=192.168.100.42:2224,192.168.100.253:2225 --job_name=worker --task_index=0
CUDA_VISIBLE_DEVICES=0 python distribute.py --ps_hosts=192.168.100.42:2222 --worker_hosts=192.168.100.42:2224,192.168.100.253:2225 --job_name=worker --task_index=1

前面是参数定义,这里大家应该都知道,:
[python] view plaincopyprint?
  1. # Define parameters
  2. FLAGS = tf.app.flags.FLAGS
  3. tf.app.flags.DEFINE_float(’learning_rate’, 0.00003, ‘Initial learning rate.’)
  4. tf.app.flags.DEFINE_integer(’steps_to_validate’, 1000,
  5. ’Steps to validate and print loss’)
  6. # For distributed
  7. tf.app.flags.DEFINE_string(”ps_hosts”, ”“,
  8. ”Comma-separated list of hostname:port pairs”)
  9. tf.app.flags.DEFINE_string(”worker_hosts”, ”“,
  10. ”Comma-separated list of hostname:port pairs”)
  11. tf.app.flags.DEFINE_string(”job_name”, ”“, ”One of ‘ps’, ‘worker’“)
  12. tf.app.flags.DEFINE_integer(”task_index”, 0, “Index of task within the job”)
  13. # Hyperparameters
  14. learning_rate = FLAGS.learning_rate
  15. steps_to_validate = FLAGS.steps_to_validate
# Define parameters
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_float('learning_rate', 0.00003, 'Initial learning rate.')
tf.app.flags.DEFINE_integer('steps_to_validate', 1000,
'Steps to validate and print loss')

# For distributed tf.app.flags.DEFINE_string("ps_hosts", "", "Comma-separated list of hostname:port pairs") tf.app.flags.DEFINE_string("worker_hosts", "", "Comma-separated list of hostname:port pairs") tf.app.flags.DEFINE_string("job_name", "", "One of 'ps', 'worker'") tf.app.flags.DEFINE_integer("task_index", 0, "Index of task within the job") # Hyperparameters learning_rate = FLAGS.learning_rate steps_to_validate = FLAGS.steps_to_validate

代码说明:
1. 故意把学习率设置的特别小,是想让它算慢点,好看见过程;
2. 通过命令行参数可以传入ps节点的ip和端口, worker节点的ip和端口。ps节点就是paramter server的缩写, 主要是保存和更新参数的节点, worker节点主要是负责计算的节点。这里说的节点都是虚拟的节点,不一定是物理上的节点;
3. 多个节点用逗号分隔;
[python] view plaincopyprint?
  1. ps_hosts = FLAGS.ps_hosts.split(“,”)
  2. worker_hosts = FLAGS.worker_hosts.split(”,”)
  3. cluster = tf.train.ClusterSpec({”ps”: ps_hosts, “worker”: worker_hosts})
  4. server = tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_index)
  5. if FLAGS.job_name == “ps”:
  6. server.join()
  7. elif FLAGS.job_name == “worker”:
  8. with tf.device(tf.train.replica_device_setter(
  9. worker_device=”/job:worker/task:%d” % FLAGS.task_index,
  10. cluster=cluster)):
ps_hosts = FLAGS.ps_hosts.split(",")
worker_hosts = FLAGS.worker_hosts.split(",")
cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})
server = tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_index)if FLAGS.job_name == "ps":
server.join()
elif FLAGS.job_name == "worker":
with tf.device(tf.train.replica_device_setter(
worker_device="/job:worker/task:%d" % FLAGS.task_index,
cluster=cluster)):

代码说明:

1. ClusterSpec的定义,需要把你要跑这个任务的所有的ps和worker 的节点的ip和端口的信息都包含进去,所有的节点都要执行这段代码, 就大家互相知道了, 这个集群里面都有哪些成员,不同的成员的类型是什么, 是ps节点还是worker节点。
2. tf.train.Server这个的定义开始,就每个节点不一样了。 根据执行的命令的参数不同,决定了这个任务是哪个任务。
如果任务名字是ps的话, 程序就join到这里,作为参数更新的服务, 等待其他worker节点给他提交参数更新的数据。
如果是worker任务,就执行后面的计算任务。
3. replica_device_setter, 这个大家可以注意一下, 可以看看tensorflow的文档对这个的解释和python的源码。 在这个with语句之下定义的参数, 会自动分配到参数服务器上去定义,如果有多个参数服务器, 就轮流循环分配。
[python] view plaincopyprint?
  1. global_step = tf.Variable(0, name=‘global_step’, trainable=False)
  2. input = tf.placeholder(”float”)
  3. label = tf.placeholder(”float”)
  4. weight = tf.get_variable(”weight”, [1], tf.float32, initializer=tf.random_normal_initializer())
  5. biase = tf.get_variable(”biase”, [1], tf.float32, initializer=tf.random_normal_initializer())
  6. pred = tf.mul(input, weight) + biase
  7. loss_value = loss(label, pred)
  8. train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_value, global_step=global_step)
  9. init_op = tf.initialize_all_variables()
  10. saver = tf.train.Saver()
  11. tf.scalar_summary(’cost’, loss_value)
  12. summary_op = tf.merge_all_summaries()
global_step = tf.Variable(0, name='global_step', trainable=False)input = tf.placeholder("float")
label = tf.placeholder("float")weight = tf.get_variable("weight", [1], tf.float32, initializer=tf.random_normal_initializer())
biase = tf.get_variable("biase", [1], tf.float32, initializer=tf.random_normal_initializer())
pred = tf.mul(input, weight) + biaseloss_value = loss(label, pred)train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_value, global_step=global_step)
init_op = tf.initialize_all_variables()
saver = tf.train.Saver()
tf.scalar_summary('cost', loss_value)
summary_op = tf.merge_all_summaries()

这块的代码和普通的单机单GPU的代码一样,就是定义计算逻辑,没什么区别。

[python] view plaincopyprint?
  1. sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0),
  2. logdir=”./checkpoint/”,
  3. init_op=init_op,
  4. summary_op=None,
  5. saver=saver,
  6. global_step=global_step,
  7. save_model_secs=60)
  8. with sv.managed_session(server.target) as sess:
  9. step = 0
  10. while step < 1000000:
  11. train_x = np.random.randn(1)
  12. train_y = 2 * train_x + np.random.randn(1) * 0.33 + 10
  13. _, loss_v, step = sess.run([train_op, loss_value,global_step], feed_dict={input:train_x, label:train_y})
  14. if step % steps_to_validate == 0:
  15. w,b = sess.run([weight,biase])
  16. print(“step: %d, weight: %f, biase: %f, loss: %f” %(step, w, b, loss_v))
sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0),
logdir="./checkpoint/",
init_op=init_op,
summary_op=None,
saver=saver,
global_step=global_step,
save_model_secs=60)
with sv.managed_session(server.target) as sess:
step = 0
while step < 1000000:
train_x = np.random.randn(1)
train_y = 2 * train_x + np.random.randn(1) * 0.33 + 10
_, loss_v, step = sess.run([train_op, loss_value,global_step], feed_dict={input:train_x, label:train_y})
if step % steps_to_validate == 0:
w,b = sess.run([weight,biase])
print("step: %d, weight: %f, biase: %f, loss: %f" %(step, w, b, loss_v))

代码说明:

1. Supervisor。 含义类似一个监督者, 就是因为分布式了, 很多机器都在运行, 像什么参数初始化, 保存模型, 写summary什么的,这个supervisoer帮你一起弄起来了, 就不用自己去手工去做这些事情了,而且在分布式的环境下设计到各种参数的共享, 其中的过程自己手工写也不好写, 于是tensorflow就给大家包装好这么一个东西了。 这里的参数is_chief比较重要, 在所有的计算节点里还是有一个主节点的, 这个主节点来负责初始化参数, 模型的保存,summary的保存。 logdir就是保存和装载模型的路径。 不过这个似乎的启动就会去这个logdir的目录去看有没有checkpoint的文件,有的话就自动装载了,没有就用init_op指定的初始化参数, 好像没有参数指定不让它自动load的;
2. 主的worker节点负责模型参数初始化等工作, 在这个过程中, 其他worker节点等待主节点完成初始化工作, 等主节点初始化完成后, 好了, 大家一起开心的跑数据。
3. 这里的global_step的值,是可以所有计算节点共享的, 在执行optimizer的minimize的时候, 会自动加1, 所以可以通过这个可以知道所有的计算节点一共计算了多少步了。
程序结果示例:
好了,然后我们就开始跑,结果显示如下:
worker节点1:
worker节点2打印信息:
最后算出来的weight的值接近于2, biasis的值接近于10 。

Demo中用到的源码:

#coding=utf-8
import numpy as np
import tensorflow as tf# Define parameters
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_float('learning_rate', 0.00003, 'Initial learning rate.')
tf.app.flags.DEFINE_integer('steps_to_validate', 1000,'Steps to validate and print loss')# For distributed
tf.app.flags.DEFINE_string("ps_hosts", "","Comma-separated list of hostname:port pairs")
tf.app.flags.DEFINE_string("worker_hosts", "","Comma-separated list of hostname:port pairs")
tf.app.flags.DEFINE_string("job_name", "", "One of 'ps', 'worker'")
tf.app.flags.DEFINE_integer("task_index", 0, "Index of task within the job")
tf.app.flags.DEFINE_integer("issync", 0, "是否采用分布式的同步模式,1表示同步模式,0表示异步模式")# Hyperparameters
learning_rate = FLAGS.learning_rate
steps_to_validate = FLAGS.steps_to_validatedef main(_):ps_hosts = FLAGS.ps_hosts.split(",")worker_hosts = FLAGS.worker_hosts.split(",")cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})server = tf.train.Server(cluster,job_name=FLAGS.job_name,task_index=FLAGS.task_index)issync = FLAGS.issyncif FLAGS.job_name == "ps":server.join()elif FLAGS.job_name == "worker":with tf.device(tf.train.replica_device_setter(worker_device="/job:worker/task:%d" % FLAGS.task_index,cluster=cluster)):global_step = tf.Variable(0, name='global_step', trainable=False)input = tf.placeholder("float")label = tf.placeholder("float")weight = tf.get_variable("weight", [1], tf.float32, initializer=tf.random_normal_initializer())biase  = tf.get_variable("biase", [1], tf.float32, initializer=tf.random_normal_initializer())pred = tf.multiply(input, weight) + biaseloss_value = loss(label, pred)optimizer = tf.train.GradientDescentOptimizer(learning_rate)grads_and_vars = optimizer.compute_gradients(loss_value)if issync == 1:#同步模式计算更新梯度rep_op = tf.train.SyncReplicasOptimizer(optimizer,replicas_to_aggregate=len(worker_hosts),replica_id=FLAGS.task_index,total_num_replicas=len(worker_hosts),use_locking=True)train_op = rep_op.apply_gradients(grads_and_vars,global_step=global_step)init_token_op = rep_op.get_init_tokens_op()chief_queue_runner = rep_op.get_chief_queue_runner()else:#异步模式计算更新梯度train_op = optimizer.apply_gradients(grads_and_vars,global_step=global_step)init_op = tf.initialize_all_variables()saver = tf.train.Saver()tf.summary.scalar('cost', loss_value)summary_op = tf.summary.merge_all()sv = tf.train.Supervisor(is_chief=(FLAGS.task_index == 0),logdir="./checkpoint/",init_op=init_op,summary_op=None,saver=saver,global_step=global_step,save_model_secs=60)with sv.prepare_or_wait_for_session(server.target) as sess:# 如果是同步模式if FLAGS.task_index == 0 and issync == 1:sv.start_queue_runners(sess, [chief_queue_runner])sess.run(init_token_op)step = 0while  step < 1000000:train_x = np.random.randn(1)train_y = 2 * train_x + np.random.randn(1) * 0.33  + 10_, loss_v, step = sess.run([train_op, loss_value,global_step], feed_dict={input:train_x, label:train_y})if step % steps_to_validate == 0:w,b = sess.run([weight,biase])print("step: %d, weight: %f, biase: %f, loss: %f" %(step, w, b, loss_v))sv.stop()def loss(label, pred):return tf.square(label - pred)if __name__ == "__main__":tf.app.run

转载来源:http://blog.csdn.net/luodongri/article/details/52596780

分布式Tensorflow入门Demo相关推荐

  1. TensorFlow入门:第一个机器学习Demo

    TensorFlow入门:第一个机器学习Demo 2017年12月13日 20:10:23 阅读数:8604 本文主要通过一个简单的 Demo 介绍 TensorFlow 初级 API 的使用方法,因 ...

  2. dubbo web工程示例_分布式开发-Zooker+dubbo入门-Demo

    作者:知了堂-刘阳 1.什么是SOA架构 SOA 是Service-Oriented Architecture的首字母简称,它是一个面向服务的架构模式(俗称:分布式:面服务的分布式) 为什么互联网项目 ...

  3. 分布式事务框架lcn入门demo

    文章目录 简介 实现原理 入门demo 简介 LCN分布式事务框架其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果. LCN5.0.2有3种模式,分别是LCN模式,TCC模式,TX ...

  4. tensorflow 入门

    基本使用 使用 TensorFlow, 你必须明白 TensorFlow: 使用图 (graph) 来表示计算任务. 在被称之为 会话 (Session) 的上下文 (context) 中执行图. 使 ...

  5. Dubbo入门Demo

    2019独角兽企业重金招聘Python工程师标准>>> 1.Dubbo简单介绍 Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方 ...

  6. tensorflow+入门笔记︱基本张量tensor理解与tensorflow运行结构与相关报错

    欢迎登陆官网(附https://tensorflow.google.cn/)了解更多 TensorFlow 内容,也可关注 TensorFlow 官方公众号获取更多资讯. Gokula Krishna ...

  7. TensorFlow入门教程集合

    TensorFlow入门教程之0: BigPicture&极速入门 TensorFlow入门教程之1: 基本概念以及理解 TensorFlow入门教程之2: 安装和使用 TensorFlow入 ...

  8. 理解和实现分布式TensorFlow集群完整教程

    手把手教你搭建分布式集群,进入生产环境的TensorFlow 分布式TensorFlow简介 前一篇<分布式TensorFlow集群local server使用详解>我们介绍了分布式Ten ...

  9. 分布式TensorFlow集群local server使用详解

    通过local server理解分布式TensorFlow集群的应用与实现. ​​简介 TensorFlow从0.8版本开始,支持分布式集群,并且自带了local server方便测试. Local ...

最新文章

  1. 电脑主板跳线_电脑基础进阶必学知识,详解电脑主板跳线!
  2. Centos搭建FTP服务
  3. Java初阶知识总结
  4. .NET Core 下使用 Exceptionless 记录日志
  5. 开车走吗?朋友......
  6. MFC程序打开控制台
  7. mediarecorder 录制的文件无法拖动进度条_录制课程不用愁,小V手把手教学
  8. 机器学习之使用sklearn构造决策树模型
  9. tomcat报错无法启动组件_微软正在修复Windows Server无法启动的0xc0000001报错故障
  10. vue各种组件(不断增加中...)
  11. 搜索引擎html和css,CSS样式对搜索引擎排名的影响
  12. 推荐的Python电子书资源
  13. InstallShield2022程序构建可靠
  14. 网吧服务器维护工具,某某网吧专用维护工具(网吧维护管理助手)V5.1 最新版
  15. Java 打印某年某月的月日历
  16. php自我介绍50字,简短自我介绍50字
  17. Taro 3.x 开发 APP 记录 (持续记录中。。。)
  18. 天津大学计算机软件学院,2019计算机考研天津大学数据科学与服务工程团队(与软件学院共建)...
  19. 【C++】阶段性学习总结(一)
  20. 衡量风控模型优劣的曲线-PR曲线、ROC曲线、K-S曲线、Lift曲线

热门文章

  1. 【项目管理】风险分析
  2. 【MySQL】系统命令与基础查询
  3. 【Python】PyCharm中Matplotlib绘图不能显示UI效果的问题解决
  4. day13-递归函数、匿名函数、内置函数
  5. python __future__ 的几种特性
  6. Ubuntu 16.04添加启动图标到Dash Home中
  7. 求两个数组中不同的元素
  8. 人生没有后悔药,云主机可以有“时光机”
  9. Aix vmstat命令解析
  10. 前辈学习C语言的四种方法,实际上不管学什么语言,都行之有效!