引言

TensorFlow 是 Google 基于 DistBelief 进行研发的第二代人工智能学习系统,被广泛用于语音识别或图像识别等多项机器深度学习领域。其命名来源于本身的运行原理。Tensor(张量)意味着 N 维数组,Flow(流)意味着基于数据流图的计算,TensorFlow 代表着张量从图象的一端流动到另一端计算过程,是将复杂的数据结构传输至人工智能神经网中进行分析和处理的过程。

TensorFlow 完全开源,任何人都可以使用。可在小到一部智能手机、大到数千台数据中心服务器的各种设备上运行。

『机器学习进阶笔记』系列将深入解析 TensorFlow 系统的技术实践,从零开始,由浅入深,与大家一起走上机器学习的进阶之路。

前面我们看了一些 Tensorflow 的文档和一些比较有意思的项目(《机器学习进阶笔记之二 | 深入理解 Neural Style》),发现这里面水很深的,需要多花时间好好从头了解下,尤其是 cv 这块的东西,特别感兴趣,接下来一段时间会开始深入了解 ImageNet 比赛中中获得好成绩的那些模型: AlexNet、GoogLeNet、VGG(对就是之前在 nerual network 用的 pretrained 的 model)、deep residual networks。

ImageNet Classification with Deep Convolutional Neural Networks

ImageNet Classification with Deep Convolutional Neural Networks 是 Hinton 和他的学生 Alex Krizhevsky 在 12 年 ImageNet Challenge 使用的模型结构,刷新了 Image Classification 的几率,从此 deep learning 在 Image 这块开始一次次超过 state-of-art,甚至于搭到打败人类的地步,看这边文章的过程中,发现了很多以前零零散散看到的一些优化技术,但是很多没有深入了解,这篇文章讲解了他们 alexnet 如何做到能达到那么好的成绩,好的废话不多说,来开始看文章

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图1

这张图是基本的 caffe 中 alexnet 的网络结构,这里比较抽象,我用 caffe 的 draw_net 把 alexnet 的网络结构画出来了

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图2

AlexNet 的基本结构

alexnet 总共包括 8 层,其中前 5 层 convolutional,后面 3 层是 full-connected,文章里面说的是减少任何一个卷积结果会变得很差,下面我来具体讲讲每一层的构成:

  • 第一层卷积层 输入图像为 2272273(paper 上貌似有点问题 2242243) 的图像,使用了 96 个 kernels(96,11,11,3),以 4 个 pixel 为一个单位来右移或者下移,能够产生 55_55 个卷积后的矩形框值,然后进行 response-normalized(其实是 Local Response Normalized,后面我会讲下这里)和 pooled 之后,pool 这一层好像 caffe 里面的 alexnet 和 paper 里面不太一样,alexnet 里面采样了两个 GPU,所以从图上面看第一层卷积层厚度有两部分,池化 pool_size=(3,3), 滑动步长为 2 个 pixels,得到 96 个 27_27 个 feature。
  • 第二层卷积层使用 256 个(同样,分布在两个 GPU 上,每个 128kernels(5548)),做 pad_size(2,2) 的处理,以 1 个 pixel 为单位移动(感谢网友指出),能够产生 2727 个卷积后的矩阵框,做 LRN 处理,然后 pooled,池化以 33 矩形框,2 个 pixel 为步长,得到 256 个 13*13 个 features。
  • 第三层、第四层都没有 LRN 和 pool,第五层只有 pool,其中第三层使用 384 个 kernels(33256,pad_size=(1,1), 得到 2561515,kernel_size 为(3,3), 以 1 个 pixel 为步长,得到 2561313);第四层使用 384 个 kernels(pad_size(1,1) 得到 2561515,核大小为(3,3)步长为 1 个 pixel,得到 3841313);第五层使用 256 个 kernels(pad_size(1,1) 得到 3841515,kernel_size(3,3),得到 2561313,pool_size(3,3)步长 2 个 pixels,得到 25666)。

全连接层: 前两层分别有 4096 个神经元,最后输出 softmax 为 1000 个(ImageNet),注意 caffe 图中全连接层中有 relu、dropout、innerProduct。

(感谢 AnaZou 指出上面之前的一些问题) paper 里面也指出了这张图是在两个 GPU 下做的,其中和 caffe 里面的 alexnet 可能还真有点差异,但这可能不是重点,各位在使用的时候,直接参考 caffe 中的 alexnet 的网络结果,每一层都十分详细,基本的结构理解和上面是一致的。

AlexNet 为啥取得比较好的结果

前面讲了下 AlexNet 的基本网络结构,大家肯定会对其中的一些点产生疑问,比如 LRN、Relu、dropout, 相信接触过 dl 的小伙伴们都有听说或者了解过这些。这里我讲按 paper 中的描述详细讲述这些东西为什么能提高最终网络的性能。

ReLU Nonlinearity

一般来说,刚接触神经网络还没有深入了解深度学习的小伙伴们对这个都不会太熟,一般都会更了解另外两个激活函数(真正往神经网络中引入非线性关系,使神经网络能够有效拟合非线性函数)tanh(x) 和 (1+e(-1), 而 ReLU(Rectified Linear Units) f(x)=max(0,x)。基于 ReLU 的深度卷积网络比基于 tanh 的网络训练块数倍,下图是一个基于 CIFAR-10 的四层卷积网络在 tanh 和 ReLU 达到 25% 的 training error 的迭代次数:

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图3

实线、间断线分别代表的是 ReLU、tanh 的 training error,可见 ReLU 比 tanh 能够更快的收敛

Local Response Normalization

使用 ReLU f(x)=max(0,x) 后,你会发现激活函数之后的值没有了 tanh、sigmoid 函数那样有一个值域区间,所以一般在 ReLU 之后会做一个 normalization,LRU 就是稳重提出(这里不确定,应该是提出?)一种方法,在神经科学中有个概念叫 “Lateral inhibition”,讲的是活跃的神经元对它周边神经元的影响。

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图4

Dropout

Dropout 也是经常挺说的一个概念,能够比较有效地防止神经网络的过拟合。 相对于一般如线性模型使用正则的方法来防止模型过拟合,而在神经网络中 Dropout 通过修改神经网络本身结构来实现。对于某一层神经元,通过定义的概率来随机删除一些神经元,同时保持输入层与输出层神经元的个人不变,然后按照神经网络的学习方法进行参数更新,下一次迭代中,重新随机删除一些神经元,直至训练结束

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图5

Data Augmentation

其实,最简单的增强模型性能,防止模型过拟合的方法是增加数据,但是其实增加数据也是有策略的,paper 当中从 256256 中随机提出 227227 的 patches(paper 里面是 224*224),还有就是通过 PCA 来扩展数据集。这样就很有效地扩展了数据集,其实还有更多的方法视你的业务场景去使用,比如做基本的图像转换如增加减少亮度,一些滤光算法等等之类的,这是一种特别有效地手段,尤其是当数据量不够大的时候。

文章里面,我认为的基本内容就是这个了,基本的网络结构和一些防止过拟合的小的技巧方法,对自己在后面的项目有很多指示作用。

AlexNet On Tensorflow

caffe 的 AlexNet 可以到 / models/bvlc_alexnet/train_val.prototxt 去看看具体的网络结构,这里我会弄点基于 Tensorflow 的 AlexNet, 代码在:http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/

  1. from numpy import
  2. import os
  3. from pylab import
  4. import numpy as np
  5. import matplotlib.pyplot as plt
  6. import matplotlib.cbook as cbook
  7. import time
  8. from scipy.misc import imread
  9. from scipy.misc import imresize
  10. import matplotlib.image as mpimg
  11. from scipy.ndimage import filters
  12. import urllib
  13. from numpy import random
  14. import tensorflow as tf
  15. from caffe_classes import class_names
  16. train_x = zeros((1, 227,227,3)).astype(float32)
  17. train_y = zeros((1, 1000))
  18. xdim = train_x.shape[1:]
  19. ydim = train_y.shape[1]
  20. net_data = load("bvlc_alexnet.npy").item()
  21. def conv(input, kernel, biases, k_h, k_w, c_o, s_h, s_w, padding="VALID", group=1):
  22. '''From [https://github.com/ethereon/caffe-tensorflow](https://github.com/ethereon/caffe-tensorflow)
  23. '''
  24. c_i = input.get_shape()[-1]
  25. assert c_i%group==0
  26. assert c_o%group==0
  27. convolve = lambda i, k: tf.nn.conv2d(i, k, [1, s_h, s_w, 1], padding=padding)
  28. if group==1:
  29. conv = convolve(input, kernel)
  30. else:
  31. input_groups = tf.split(3, group, input)
  32. kernel_groups = tf.split(3, group, kernel)
  33. output_groups = [convolve(i, k) for i,k in zip(input_groups, kernel_groups)]
  34. conv = tf.concat(3, output_groups)
  35. return tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape().as_list())
  36. x = tf.Variable(i)
  37. #conv1
  38. #conv(11, 11, 96, 4, 4, padding='VALID', name='conv1')
  39. k_h = 11; k_w = 11; c_o = 96; s_h = 4; s_w = 4
  40. conv1W = tf.Variable(net_data["conv1"][0])
  41. conv1b = tf.Variable(net_data["conv1"][1])
  42. conv1_in = conv(x, conv1W, conv1b, k_h, k_w, c_o, s_h, s_w, padding="SAME", group=1)
  43. conv1 = tf.nn.relu(conv1_in)
  44. #lrn1
  45. #lrn(2, 2e-05, 0.75, name='norm1')
  46. radius = 2; alpha = 2e-05; beta = 0.75; bias = 1.0
  47. lrn1 = tf.nn.local_response_normalization(conv1,
  48. depth_radius=radius,
  49. alpha=alpha,
  50. beta=beta,
  51. bias=bias)
  52. #maxpool1
  53. #max_pool(3, 3, 2, 2, padding='VALID', name='pool1')
  54. k_h = 3; k_w = 3; s_h = 2; s_w = 2; padding = 'VALID'
  55. maxpool1 = tf.nn.max_pool(lrn1, ksize=[1, k_h, k_w, 1], strides=[1, s_h, s_w, 1], padding=padding)
  56. #conv2
  57. #conv(5, 5, 256, 1, 1, group=2, name='conv2')
  58. k_h = 5; k_w = 5; c_o = 256; s_h = 1; s_w = 1; group = 2
  59. conv2W = tf.Variable(net_data["conv2"][0])
  60. conv2b = tf.Variable(net_data["conv2"][1])
  61. conv2_in = conv(maxpool1, conv2W, conv2b, k_h, k_w, c_o, s_h, s_w, padding="SAME", group=group)
  62. conv2 = tf.nn.relu(conv2_in)
  63. #lrn2
  64. #lrn(2, 2e-05, 0.75, name='norm2')
  65. radius = 2; alpha = 2e-05; beta = 0.75; bias = 1.0
  66. lrn2 = tf.nn.local_response_normalization(conv2,
  67. depth_radius=radius,
  68. alpha=alpha,
  69. beta=beta,
  70. bias=bias)
  71. #maxpool2
  72. #max_pool(3, 3, 2, 2, padding='VALID', name='pool2')
  73. k_h = 3; k_w = 3; s_h = 2; s_w = 2; padding = 'VALID'
  74. maxpool2 = tf.nn.max_pool(lrn2, ksize=[1, k_h, k_w, 1], strides=[1, s_h, s_w, 1], padding=padding)
  75. #conv3
  76. #conv(3, 3, 384, 1, 1, name='conv3')
  77. k_h = 3; k_w = 3; c_o = 384; s_h = 1; s_w = 1; group = 1
  78. conv3W = tf.Variable(net_data["conv3"][0])
  79. conv3b = tf.Variable(net_data["conv3"][1])
  80. conv3_in = conv(maxpool2, conv3W, conv3b, k_h, k_w, c_o, s_h, s_w, padding="SAME", group=group)
  81. conv3 = tf.nn.relu(conv3_in)
  82. #conv4
  83. #conv(3, 3, 384, 1, 1, group=2, name='conv4')
  84. k_h = 3; k_w = 3; c_o = 384; s_h = 1; s_w = 1; group = 2
  85. conv4W = tf.Variable(net_data["conv4"][0])
  86. conv4b = tf.Variable(net_data["conv4"][1])
  87. conv4_in = conv(conv3, conv4W, conv4b, k_h, k_w, c_o, s_h, s_w, padding="SAME", group=group)
  88. conv4 = tf.nn.relu(conv4_in)
  89. #conv5
  90. #conv(3, 3, 256, 1, 1, group=2, name='conv5')
  91. k_h = 3; k_w = 3; c_o = 256; s_h = 1; s_w = 1; group = 2
  92. conv5W = tf.Variable(net_data["conv5"][0])
  93. conv5b = tf.Variable(net_data["conv5"][1])
  94. conv5_in = conv(conv4, conv5W, conv5b, k_h, k_w, c_o, s_h, s_w, padding="SAME", group=group)
  95. conv5 = tf.nn.relu(conv5_in)
  96. #maxpool5
  97. #max_pool(3, 3, 2, 2, padding='VALID', name='pool5')
  98. k_h = 3; k_w = 3; s_h = 2; s_w = 2; padding = 'VALID'
  99. maxpool5 = tf.nn.max_pool(conv5, ksize=[1, k_h, k_w, 1], strides=[1, s_h, s_w, 1], padding=padding)
  100. #fc6
  101. #fc(4096, name='fc6')
  102. fc6W = tf.Variable(net_data["fc6"][0])
  103. fc6b = tf.Variable(net_data["fc6"][1])
  104. fc6 = tf.nn.relu_layer(tf.reshape(maxpool5, [1, int(prod(maxpool5.get_shape()[1:]))]), fc6W, fc6b)
  105. #fc7
  106. #fc(4096, name='fc7')
  107. fc7W = tf.Variable(net_data["fc7"][0])
  108. fc7b = tf.Variable(net_data["fc7"][1])
  109. fc7 = tf.nn.relu_layer(fc6, fc7W, fc7b)
  110. #fc8
  111. #fc(1000, relu=False, name='fc8')
  112. fc8W = tf.Variable(net_data["fc8"][0])
  113. fc8b = tf.Variable(net_data["fc8"][1])
  114. fc8 = tf.nn.xw_plus_b(fc7, fc8W, fc8b)
  115. #prob
  116. #softmax(name='prob'))
  117. prob = tf.nn.softmax(fc8)
  118. init = tf.initialize_all_variables()
  119. sess = tf.Session()
  120. sess.run(init)
  121. output = sess.run(prob)
  122. ################################################################################
  123. #Output:
  124. inds = argsort(output)[0,:]
  125. for i in range(5):
  126. print class_names[inds[-1-i]], output[0, inds[-1-i]]

这个是基于原生 tensorflow 的一版代码,好长而且看着比较麻烦一点,还 load 了 caffe 里面生成的网络模型,比较麻烦,这里找了一版稍微简单的 <【机器学习】AlexNet 的 tensorflow 实现>:

  1. # 输入数据
  2. import input_data
  3. mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
  4. import tensorflow as tf
  5. # 定义网络超参数
  6. learning_rate = 0.001
  7. training_iters = 200000
  8. batch_size = 64
  9. display_step = 20
  10. # 定义网络参数
  11. n_input = 784 # 输入的维度
  12. n_classes = 10 # 标签的维度
  13. dropout = 0.8 # Dropout 的概率
  14. # 占位符输入
  15. x = tf.placeholder(tf.types.float32, [None, n_input])
  16. y = tf.placeholder(tf.types.float32, [None, n_classes])
  17. keep_prob = tf.placeholder(tf.types.float32)
  18. # 卷积操作
  19. def conv2d(name, l_input, w, b):
  20. return tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(l_input, w, strides=[1, 1, 1, 1], padding='SAME'),b), name=name)
  21. # 最大下采样操作
  22. def max_pool(name, l_input, k):
  23. return tf.nn.max_pool(l_input, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME', name=name)
  24. # 归一化操作
  25. def norm(name, l_input, lsize=4):
  26. return tf.nn.lrn(l_input, lsize, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name=name)
  27. # 定义整个网络
  28. def alex_net(_X, _weights, _biases, _dropout):
  29. # 向量转为矩阵
  30. _X = tf.reshape(_X, shape=[-1, 28, 28, 1])
  31. # 卷积层
  32. conv1 = conv2d('conv1', _X, _weights['wc1'], _biases['bc1'])
  33. # 下采样层
  34. pool1 = max_pool('pool1', conv1, k=2)
  35. # 归一化层
  36. norm1 = norm('norm1', pool1, lsize=4)
  37. # Dropout
  38. norm1 = tf.nn.dropout(norm1, _dropout)
  39. # 卷积
  40. conv2 = conv2d('conv2', norm1, _weights['wc2'], _biases['bc2'])
  41. # 下采样
  42. pool2 = max_pool('pool2', conv2, k=2)
  43. # 归一化
  44. norm2 = norm('norm2', pool2, lsize=4)
  45. # Dropout
  46. norm2 = tf.nn.dropout(norm2, _dropout)
  47. # 卷积
  48. conv3 = conv2d('conv3', norm2, _weights['wc3'], _biases['bc3'])
  49. # 下采样
  50. pool3 = max_pool('pool3', conv3, k=2)
  51. # 归一化
  52. norm3 = norm('norm3', pool3, lsize=4)
  53. # Dropout
  54. norm3 = tf.nn.dropout(norm3, _dropout)
  55. # 全连接层,先把特征图转为向量
  56. dense1 = tf.reshape(norm3, [-1, _weights['wd1'].get_shape().as_list()[0]])
  57. dense1 = tf.nn.relu(tf.matmul(dense1, _weights['wd1']) + _biases['bd1'], name='fc1')
  58. # 全连接层
  59. dense2 = tf.nn.relu(tf.matmul(dense1, _weights['wd2']) + _biases['bd2'], name='fc2') # Relu activation
  60. # 网络输出层
  61. out = tf.matmul(dense2, _weights['out']) + _biases['out']
  62. return out
  63. # 存储所有的网络参数
  64. weights = {
  65. 'wc1': tf.Variable(tf.random_normal([3, 3, 1, 64])),
  66. 'wc2': tf.Variable(tf.random_normal([3, 3, 64, 128])),
  67. 'wc3': tf.Variable(tf.random_normal([3, 3, 128, 256])),
  68. 'wd1': tf.Variable(tf.random_normal([4\*4\*256, 1024])),
  69. 'wd2': tf.Variable(tf.random_normal([1024, 1024])),
  70. 'out': tf.Variable(tf.random_normal([1024, 10]))
  71. }
  72. biases = {
  73. 'bc1': tf.Variable(tf.random_normal([64])),
  74. 'bc2': tf.Variable(tf.random_normal([128])),
  75. 'bc3': tf.Variable(tf.random_normal([256])),
  76. 'bd1': tf.Variable(tf.random_normal([1024])),
  77. 'bd2': tf.Variable(tf.random_normal([1024])),
  78. 'out': tf.Variable(tf.random_normal([n_classes]))
  79. }
  80. # 构建模型
  81. pred = alex_net(x, weights, biases, keep_prob)
  82. # 定义损失函数和学习步骤
  83. cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(pred, y))
  84. optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
  85. # 测试网络
  86. correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
  87. accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
  88. # 初始化所有的共享变量
  89. init = tf.initialize_all_variables()
  90. # 开启一个训练
  91. with tf.Session() as sess:
  92. sess.run(init)
  93. step = 1
  94. # Keep training until reach max iterations
  95. while step \* batch_size < training_iters:
  96. batch_xs, batch_ys = mnist.train.next_batch(batch_size)
  97. # 获取批数据
  98. sess.run(optimizer, feed_dict={x: batch_xs, y: batch_ys, keep_prob: dropout})
  99. if step % display_step == 0:
  100. # 计算精度
  101. acc = sess.run(accuracy, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
  102. # 计算损失值
  103. loss = sess.run(cost, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 1.})
  104. print "Iter " + str(step\*batch_size) + ", Minibatch Loss= " + "{:.6f}".format(loss) + ", Training Accuracy= " + "{:.5f}".format(acc)
  105. step += 1
  106. print "Optimization Finished!"
  107. # 计算测试精度
  108. print "Testing Accuracy:", sess.run(accuracy, feed_dict={x: mnist.test.images[:256], y: mnist.test.labels[:256], keep_prob: 1.})

基于 mnist 构建 alexnet,这里的 input 可以去 tensorflow 的 github 上去找找,这一版代码比较简单。

后来发现了 tflearn 里面有一个 alexnet 来分类 Oxford 的例子,好开心,在基于 tflearn 对一些日常 layer 的封装,代码量只有不到 50 行,看了下内部 layer 的实现,挺不错的,写代码的时候可以多参考参考,代码地址:https://github.com/tflearn/tflearn/blob/master/examples/images/alexnet.py.

  1. from __future__ import division, print_function, absolute_import
  2. import tflearn
  3. from tflearn.layers.core import input_data, dropout, fully_connected
  4. from tflearn.layers.conv import conv_2d, max_pool_2d
  5. from tflearn.layers.normalization import local_response_normalization
  6. from tflearn.layers.estimator import regression
  7. import tflearn.datasets.oxflower17 as oxflower17
  8. X, Y = oxflower17.load_data(one_hot=True, resize_pics=(227, 227))
  9. # Building 'AlexNet'
  10. network = input_data(shape=[None, 227, 227, 3])
  11. network = conv_2d(network, 96, 11, strides=4, activation='relu')
  12. network = max_pool_2d(network, 3, strides=2)
  13. network = local_response_normalization(network)
  14. network = conv_2d(network, 256, 5, activation='relu')
  15. network = max_pool_2d(network, 3, strides=2)
  16. network = local_response_normalization(network)
  17. network = conv_2d(network, 384, 3, activation='relu')
  18. network = conv_2d(network, 384, 3, activation='relu')
  19. network = conv_2d(network, 256, 3, activation='relu')
  20. network = max_pool_2d(network, 3, strides=2)
  21. network = local_response_normalization(network)
  22. network = fully_connected(network, 4096, activation='tanh')
  23. network = dropout(network, 0.5)
  24. network = fully_connected(network, 4096, activation='tanh')
  25. network = dropout(network, 0.5)
  26. network = fully_connected(network, 17, activation='softmax')
  27. network = regression(network, optimizer='momentum',
  28. loss='categorical_crossentropy',
  29. learning_rate=0.001)
  30. # Training
  31. model = tflearn.DNN(network, checkpoint_path='model_alexnet',
  32. max_checkpoints=1, tensorboard_verbose=2)
  33. model.fit(X, Y, n_epoch=1000, validation_set=0.1, shuffle=True,
  34. show_metric=True, batch_size=64, snapshot_step=200,
  35. snapshot_epoch=False, run_id='alexnet_oxflowers17')

使用 tflearn 版本的 alexnet 来做实验,从 TensorBoard 上得到的基本效果如下, alexnet graph 如下:

机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图6
机器学习进阶笔记之三 | 深入理解Alexnet - 知乎 - 图7

Reference

1,ImageNet Classification with Deep Convolutional Neural Networks

2,<【机器学习】AlexNet 的 tensorflow 实现>

3,http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/

4,https://github.com/tflearn/tflearn/blob/master/examples/images/alexnet.py

5,https://github.com/BVLC/caffe/blob/master/python/draw_net.py

相关阅读推荐:

机器学习进阶笔记之二 | 深入理解 Neural Style

机器学习进阶笔记之一 | TensorFlow 安装与入门

本文由『UCloud 内核与虚拟化研发团队』提供。

关于作者:

Burness( ), UCloud 平台研发中心深度学习研发工程师,tflearn Contributor,做过电商推荐、精准化营销相关算法工作,专注于分布式深度学习框架、计算机视觉算法研究,平时喜欢玩玩算法,研究研究开源的项目,偶尔也会去一些数据比赛打打酱油,生活中是个极客,对新技术、新技能痴迷。

你可以在 Github 上找到他:http://hacker.duanshishi.com/

「UCloud 机构号」将独家分享云计算领域的技术洞见、行业资讯以及一切你想知道的相关讯息。

欢迎提问 & 求关注 o(////▽////)q~

以上。
https://zhuanlan.zhihu.com/p/22659166