當TensorFlow遇見CNTK

本文從工程師的角度對CNTK和TensorFlow做高層次的對比。本文也不屬於性能分析,而是編程模型分析。文中會夾雜著大量的代碼。


當TensorFlow遇見CNTK

CNTK是微軟用於搭建深度神經網路的計算網路工具包,此項目已在Github上開源。因為我最近寫了關於TensorFlow的文章,所以想比較一下這兩個系統的相似和差異之處。畢竟,CNTK也是許多圖像識別挑戰賽的衛冕冠軍。為了內容的完整性,我應該也對比一下Theano、Torch和Caffe。後三者也是現在非常流行的框架。但是本文僅限於討論CNTK和TensorFlow,其餘的框架將在今後討論。Kenneth Tran對這五個深度學習工具包做過一次高水平(以他個人觀點)的分析。本文並不是一個CNTK或者TensorFlow的使用教程。我的目的在於從工程師的角度對它們做高層次的對比。本文也不屬於性能分析,而是編程模型分析。文中會夾雜著大量的代碼,如果你討厭閱讀代碼,請直接跳到結論部分。

CNTK有一套極度優化的運行系統來訓練和測試神經網路,它是以抽象的計算圖形式構建。如此看來,CNTK和TensorFlow長得非常相似。但是,它們有一些本質上的區別。為了演示這些特性和區別,我會用到兩個標準示例,它們分別包括了兩個系統及調用各自系統完成的任務。第一個例子是用較淺的卷積神經網路來解決標準的MNIST手寫數字集的識別任務。我會針對它們兩種遞歸神經網路方法的差異性做一些點評總結。

TensorFlow和CNTK都屬於腳本驅動型的。我的意思是說神經網路構建的流程圖都是在一個腳本裡完成,並調用一些智能的自動化步驟完成訓練。TensorFlow的腳本是與Python語言捆綁的,Python操作符能夠用來控制計算圖的執行過程。CNTK目前還沒有和Python或是C++綁定(盡管已經承諾過),所以它目前訓練和測試的流程控制還是需要精心編制設計的。等會我將展示,這個過程並不能算是一種限制。CNTK網路需要用到兩個腳本:一個控制訓練和測試參數的配置文件和一個用於構建網路的網路定義語言(Network Definition Language, NDL)文件。

我會首先描述神經網路的流程圖,因為這是與TensorFlow最相似之處。CNTK支持兩種方式來定義網路。一種是使用「Simple Network Builder」,只需設置幾個參數就能生成一個簡單的標準神經網路。另一種是使用網路定義語言(NDL)。此處例子(直接從Github下載的)使用的是NDL。下面就是Convolution.ndl文件的縮略版本。(為了節省頁面空間,我把多行文件合併到同一行,並用逗號分隔)

CNTK網路圖有一些特殊的節點。它們是描述輸入數據和訓練標籤的FeatureNodes和LabelNodes,用來評估訓練結果的CriterionNodes和EvalNodes,和表示輸出的OutputNodes。當我們在下文中遇到它們的時候我再具體解釋。在文件頂部還有一些用來加載數據(特徵)和標籤的宏定義。如下所示,我們將MNIST數據集的圖像作為特徵讀入,經過歸一化之後轉化為若干浮點數組。得到的數組「featScaled」將作為神經網路的輸入值。

load = ndlMnistMacros
# the actual NDL that defines the network
run = DNN
ndlMnistMacros = [
    imageW = 28, imageH = 28
    labelDim = 10
    features = ImageInput(imageW, imageH, 1)
    featScale = Const(0.00390625)
    featScaled = Scale(featScale, features)
    labels = Input(labelDim)
]
DNN=[
    # conv1
    kW1 = 5, kH1 = 5
    cMap1 = 16
    hStride1 = 1, vStride1 = 1
    conv1_act = ConvReLULayer(featScaled,cMap1,25,kW1,kH1,hStride1,vStride1,10, 1)
    # pool1
    pool1W = 2, pool1H = 2
    pool1hStride = 2, pool1vStride = 2
    pool1 = MaxPooling(conv1_act, pool1W, pool1H, pool1hStride, pool1vStride)
    # conv2
    kW2 = 5, kH2 = 5
    cMap2 = 32
    hStride2 = 1, vStride2 = 1
    conv2_act = ConvReLULayer(pool1,cMap2,400,kW2, kH2, hStride2, vStride2,10, 1)
    # pool2
    pool2W = 2, pool2H = 2
    pool2hStride = 2,  pool2vStride = 2
    pool2 = MaxPooling(conv2_act, pool2W, pool2H, pool2hStride, pool2vStride)
    h1Dim = 128
    h1 = DNNSigmoidLayer(512, h1Dim, pool2, 1)
    ol = DNNLayer(h1Dim, labelDim, h1, 1)
    ce = CrossEntropyWithSoftmax(labels, ol)
    err = ErrorPrediction(labels, ol)
    # Special Nodes
    FeatureNodes = (features)
    LabelNodes = (labels)
    CriterionNodes = (ce)
    EvalNodes = (err)
    OutputNodes = (ol)
]

DNN小節定義了網路的結構。此神經網路包括了兩個卷積-最大池化層,接著是有一個128節點隱藏層的全連接標準網路。

在卷積層I 我們使用5×5的卷積核函數,並且在參數空間定義了16個(cMap1)。操作符ConvReLULayer實際上是在宏文件中定義的另一個子網路的縮寫。

在計算時,我們想把卷積的參數用矩陣W和向量B來表示,那麼如果輸入的是X,網路的輸出將是f(op(W, X) + B)的形式。在這裡操作符op就是卷積運算,f是標準規則化函數relu(x)=max(x,0)。

ConvReLULayer的NDL代碼如下圖所示:
ConvReLULayer(inp, outMap, inWCount, kW, kH, hStride, vStride, wScale, bValue) = 
[
    convW = Parameter(outMap, inWCount, init=”uniform”, initValueScale=wScale)
    convB = Parameter(outMap, 1,        init=”fixedValue”, value=bValue)
    conv = Convolution(convW, inp, kW, kH, outMap, hStride,vStride,
                zeroPadding=false)
    convPlusB = Plus(conv, convB);
    act = RectifiedLinear(convPlusB);
]

矩陣W和向量B是模型的參數,它們會被賦予一個初始值,並在訓練的過程中不斷更新直到生成最終模型。這裡,convW是一個16行25列的矩陣,B是長度為16的向量。Convolution是內置的卷積函數,默認不使用補零的方法。也就是說對28×28的圖像做卷積運算,實際上只是對24×24的中心區域操作,得到的結果是16個24×24的sudo-image。

接著我們用2×2的區域應用最大池化操作,最後得到的結果是16個12×12的矩陣。

當TensorFlow遇見CNTK

對於第二個卷積層,我們把卷積濾波器的個數由16個提升到32個。這一次我們有16通道的輸入數據,因此W矩陣的尺寸為32行25×16 = 400列,向量B的長度為32。這次的卷積運算針對12×12圖像幀的中心區域,所以得到的結果是32個8×8的矩陣。第二次池化操作的結果是32個4×4的幀,或者32×16=512。

最後兩層,是由512個池化輸出結果經過128個節點的隱藏層連接到10個輸出節點,經歷了兩次運算操作。

DNNSigmoidLayer(inDim, outDim, x, parmScale) = [
    W = Parameter(outDim, inDim, init=”uniform”, initValueScale=parmScale)
    b = Parameter(outDim, 1,     init=”uniform”, initValueScale=parmScale)
    t = Times(W, x)
    z = Plus(t, b)
    y = Sigmoid(z)
]
DNNLayer(inDim, outDim, x, parmScale) = [
    W = Parameter(outDim, inDim, init=”uniform”, initValueScale=parmScale)
    b = Parameter(outDim, 1,     init=”uniform”, initValueScale=parmScale)
    t = Times(W, x)
    z = Plus(t, b)
]

如你所見,這些運算步驟都是標準的線性代數運算形式W*x+b。

圖定義的最後部分是交叉熵和誤差節點,以及將它們綁定到特殊的節點名稱。

我們接著要來定義訓練的過程,但是先把它與用TensorFlow構建相似的網路模型做個比較。我們在之前的文章裡討論過這部分內容,這裡再討論一次。你是否注意到我們使用了與CNTK相同的一組變量,只不過這裡我們把它稱作變量,而在CNTK稱作參數。維度也略有不同。盡管卷積濾波器都是5×5,在CNTK我們前後兩級分別使用了16個和32個濾波器,但是在TensorFlow的例子裡我們用的是32個和64個。

def weight_variable(shape, names):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial, name=names)
def bias_variable(shape, names):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial, name=names)
x = tf.placeholder(tf.float32, [None, 784], name=”x”)
sess = tf.InteractiveSession()
W_conv1 = weight_variable([5, 5, 1, 32], “wconv”)
b_conv1 = bias_variable([32], “bconv”)
W_conv2 = weight_variable([5, 5, 32, 64], “wconv2”)
b_conv2 = bias_variable([64], “bconv2”)
W_fc1 = weight_variable([7 * 7 * 64, 1024], “wfc1”)
b_fc1 = bias_variable([1024], “bfcl”)
W_fc2 = weight_variable([1024, 10], “wfc2”)
b_fc2 = bias_variable([10], “bfc2”)

網路的構建過程也大同小異。

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')
#first convolutional layer
x_image = tf.reshape(x, [-1,28,28,1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
#second convolutional layer
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
#final layer
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

卷積運算的唯一不同之處是這裡定義了補零,因此第一次卷積運算的輸出是28×28,經過池化後,降為14×14。第二次卷積運算和池化之後的結果降為了7×7,所以最後一層的輸入是7x7x64 = 3136維,有1024個隱藏節點(使用relu而不是sigmoid函數)。(在訓練時,最後一步用到了dropout函數將模型數值隨機地置零。如果keep_prob=1則忽略這步操作。)

當TensorFlow遇見CNTK

網路訓練

CNTK中設置網路模型訓練的方式與TensorFlow差別巨大。訓練和測試步驟是在一個convolution.config的文件內設置。CNTK和TensorFlow都是通過符號化分析流程圖來計算梯度下降訓練算法中所用到的梯度值。CNTK組給出了一本非常讚的「書」來闡述梯度是如何計算的。現階段CNTK只支持一種學習方法:Mini-batch隨機梯度下降法,但他們承諾今後加入更多的算法。He, Zhang, Ren 和 Sun發表了一篇優秀的論文介紹他們是如何用嵌套殘留還原法(nested residual reduction)來訓練極度深層(深達1000層)的網路模型,所以讓我們拭目以待這個方法被融入到CNTK中。配置文件的縮略版如下所示。

command = train:test
modelPath = “$ModelDir$/02_Convolution”
ndlMacros = “$ConfigDir$/Macros.ndl”
train = [
    action = “train”
    NDLNetworkBuilder = [
        networkDescription = “$ConfigDir$/02_Convolution.ndl”
    ]
    SGD = [
        epochSize = 60000
        minibatchSize = 32
        learningRatesPerMB = 0.5
        momentumPerMB = 0*10:0.7
        maxEpochs = 15
    ]
    reader = [
        readerType = “UCIFastReader”
        file = “$DataDir$/Train-28×28.txt”
        features = [
            dim = 784
            start = 1
        ]
        labels = [
            # details deleted
        ]
    ]
]
test = [
   ….
]

命令行顯示了執行的順序:先訓練後測試。先聲明了各種文件的路徑,然後訓練模塊設置了待訓練的網路模型以及隨機梯度下降(SGD)的參數。讀取模塊根據NDL文件中的設置讀取了「特徵」和「標籤」數據。測試模塊設置了用於測試的參數。

16核(沒有GPU)的Linux VM需要消耗62.95分鐘來執行訓練和測試過程,999.01分鐘的用戶時間和4分鐘的系統時間。用戶時間指的是所有16個核都在滿負荷運轉(999/63 = 15.85)。但這並不算什麼,因為CNTK是為並行計算而設計的,大規模GPU支持才是真正的設計點。

TensorFlow的訓練步驟在Python控制流程中設置得更清晰。而使用的算法Adam也是基於梯度計算的,由Kingma和Ba發明。TensorFlow的函數庫裡有大量基於梯度的優化方法,但我沒有嘗試其它的方法。

如下所以,cross_entropy是按照標準形式定義的,然後傳入優化器生成一個 「train_step」對象。

y_ = tf.placeholder(tf.float32, [None, 10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, “float”))
sess.run(tf.initialize_all_variables())
for i in range(20000):
  batch = mnist.train.next_batch(50)
  train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
print(“test accuracy %g”%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))


隨後Python腳本每批處理50條數據,以50%的捨棄率執行train_step,迭代20000次。測試步驟在整個測試集上評估準確率。

除了巧妙的自動求積分和Adam優化器的構建,一切都是直截了當的。我在16核的服務器上用CNTK例子中相同的數據集又跑了一遍。出乎我意料的是所需的時間與CNTK幾乎一模一樣。實際運行時間是62.02分鐘,用戶時間為160.45分鐘,所以幾乎沒用利用並行運算。我覺得這些數字不能說明什麼。CNTK和TensorFlow都是為大規模GPU運算而設計的,它們運行的訓練算法並不完全一致。

遞歸神經網路在CNTK和TensorFlow的做到

遞歸神經網路(RNNs)在語言建模方面用途廣泛,例如打字時預測下一個輸入單詞,或是用於自動翻譯系統。(更多例子請參見Andrej Karpathy的博客)真是個有趣的想法。系統的輸入是一個單詞(或者一組單詞)以及系統基於目前所出現單詞而更新的狀態,輸出的是一個預測單詞列表和系統的新狀態,如圖1所示。

當TensorFlow遇見CNTK

圖1

當然,RNN還有許多變種形式。其中一種常見的形式是長短期記憶模型(LSTM),其定義公式如下:

當TensorFlow遇見CNTK

圖2:LSTM方程組(來源於CNTKBook)

此處 \sigma 表示sigmoid函數。

如果你有興趣讀一篇關於LSTM及其工作原理的博文,我推薦Christopher Olah所寫的這篇。他繪制了一張示意圖,使得上面的等式更容易理解。我把它稍作修改,使它符合CNTK版本的方程式,結果如下圖所示。

當TensorFlow遇見CNTK

圖3:改編自Christopher Olah的優秀文章

圖中使用了sigmoid和tanh函數,並且級聯變量得到了下面的表達式:

當TensorFlow遇見CNTK

其中W和b是學習得到的權重。

CNTK版本

下面是為LSTM模型設置的NDL。有兩件事需要注意。一個是網路模型中使用了一個稱作「PastValue」的延遲操作符直接處理了遞歸的邏輯,它用到了維度和延遲時間兩個變量,返回該值的一個副本。第二件事情是注意W矩陣的處理方式,它與上面以及圖3中所示的級聯操作有何區別。在這裡,它們把屬於x和h的所有W壓入堆棧,把所有b值也存入堆棧。然後計算一個W*x和一個W*h並求和,再加上b的值。然後再使用一個行切分操作符,分別用獨立的sigmoid函數處理它們。還需關注的是針對c的W矩陣都是對角陣。

LSTMPComponent(inputDim, outputDim, cellDim, inputx, cellDimX2, cellDimX3, cellDimX4) = [
        wx = Parameter(cellDimX4, inputDim,  init=”uniform”, initValueScale=1);
        b = Parameter(cellDimX4,  1,         init=”fixedValue”, value=0.0);
        Wh = Parameter(cellDimX4, outputDim, init=”uniform”, initValueScale=1);
        Wci = Parameter(cellDim, init=”uniform”, initValueScale=1);
        Wcf = Parameter(cellDim, init=”uniform”, initValueScale=1);
        Wco = Parameter(cellDim, init=”uniform”, initValueScale=1);
        dh = PastValue(outputDim, output, timeStep=1);
        dc = PastValue(cellDim, ct, timeStep=1);
        wxx = Times(wx, inputx);
        wxxpb = Plus(wxx, b);
        whh = Times(wh, dh);
        wxxpbpwhh = Plus(wxxpb,whh)
        G1 = RowSlice(0, cellDim, wxxpbpwhh)
        G2 = RowSlice(cellDim, cellDim, wxxpbpwhh)
        G3 = RowSlice(cellDimX2, cellDim, wxxpbpwhh);
        G4 = RowSlice(cellDimX3, cellDim, wxxpbpwhh);
        Wcidc = DiagTimes(Wci, dc);
        it = Sigmoid (Plus ( G1, Wcidc));
        bit = ElementTimes(it, Tanh( G2 ));
        Wcfdc = DiagTimes(Wcf, dc);
        ft = Sigmoid( Plus (G3, Wcfdc));
        bft = ElementTimes(ft, dc);
        ct = Plus(bft, bit);
        Wcoct = DiagTimes(Wco, ct);
        ot = Sigmoid( Plus( G4, Wcoct));
        mt = ElementTimes(ot, Tanh(ct));
        Wmr = Parameter(outputDim, cellDim, init=”uniform”, initValueScale=1);
        output = Times(Wmr, mt); 
]

TensorFlow版本

TensorFlow版本的LSTM遞歸神經網路模型與CNTK版本完全不同。盡管它們所執行的操作符都一樣,但TensorFlow的表示方式充分發揮了Python控制流的作用。這個概念模型非常簡單。我們創建了一個LSTM單元,並且定義一個「狀態」作為此單元的輸入,同時也是此單元的輸出。偽代碼如下所示:

cell = rnn_cell.BasicLSTMCell(lstm_size)
# Initial state of the LSTM memory.
state = tf.zeros([batch_size, lstm.state_size])
for current_batch_of_words in words_in_dataset:
    # The value of state is updated after processing each batch of words.
output, state = cell(current_batch_of_words, state)

這段摘自教程的偽代碼版本很好地反映了圖1的內容。折磨人的地方在於微妙細節的處理。記住大部分時間TensorFlow的python代碼是在搭建流程圖,所以我們需要下一點功夫來繪制用於訓練和執行的循環流程圖。

這裡最大的挑戰在於如何在一個循環內創建並重復使用權重矩陣和偏置向量。CNTK使用了「PastValue」操作符來創建所需的循環。TensorFlow則使用了上面提到的所謂遞歸機制,和一個非常聰明的變量保存和調用機制來完成同樣的任務。「PastValue」在TensorFlow中對應的是一個函數, tf.get_variable( 「name」, size, initializer = None) ,它的行為取決於當前變量域中的「reuse」這個標誌位。如果reuse==False而且在當時不存在其它的同名變量,那麼get_variable 用那個變量名返回一個新的變量,並用初始化器對其初始化。否則將會返回錯誤。如果reuse == True,那麼get_variable返回之前已經存在的那個變量。如果不存在這樣的變量,則返回一個錯誤。

為了演示這種用法,以下是TensorFlow的一個函數的簡化版本,用來創建上面等式一的sigmoid函數。它只是W*x+b 的一個演化版本,其中x是一個list[a,b,c,…]

def linear(args, output_size, scope=None):
   #Linear map: sum_i(args[i] * W[i]), where W[i] is a variable.
   with vs.variable_scope(scope):
    matrix = vs.get_variable(“Matrix”, [total_arg_size, output_size])
    res = math_ops.matmul(array_ops.concat(1, args), matrix)
    bias_term = vs.get_variable(
        “Bias”, [output_size],
        initializer=init_ops.constant_initializer(1.))
  return res + bias_term

接下來定義BasicLSTMCell,大致的寫法如下所示。(想要查看這些函數的完整版本,請前往TensorFlow Github代碼庫裡的rnn_cell.py腳本。)

class BasicLSTMCell(RNNCell):
  def __call__(self, inputs, state, scope=None):
    with vs.variable_scope(scope): 
      c, h = array_ops.split(1, 2, state)
      concat = linear([inputs, h], 4 * self._num_units)
      i, j, f, o = array_ops.split(1, 4, concat)
      new_c = c * sigmoid(f) + sigmoid(i) * tanh(j)
      new_h = tanh(new_c) * sigmoid(o)
   return new_h, array_ops.concat(1, [new_c, new_h])


你可以看到,這裡相當準確地再現圖3中的圖示。你會注意到上面的split操作符正是對應於CNTK的row slice操作符。

現在我們可以創建一個可以用於訓練的遞歸神經網路模型,在同樣的變量域我們能用共享的W和b變量創建另一個網路模型用於測試。具體的做法在TensorFlow的遞歸神經網路教程ptb_word_lm.py腳本中有介紹。還有兩點值得留意。(應該說它們對於我理解這個例子,有著至關重要的作用)他們創建一個lstmModel類來訓練和測試網路模型。

class lstmModel:
  def __init__(self, is_training, num_steps):
    self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
    self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])
    cell = rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
    outputs = []
    states = []
    state = self._initial_state
    with tf.variable_scope(“RNN”):
      for time_step in range(num_steps):
        if time_step > 0: 
            tf.get_variable_scope().reuse_variables()
        (cell_output, state) = cell(inputs[:, time_step, :], state)
        outputs.append(cell_output)
        states.append(state)
        … many details omitted …

我們在主程序中創建一個訓練實例和一個測試實例,並調用它們(事實上還要創建一個實例,為了簡化過程我暫時先把它忽略)。

with tf.variable_scope(“model”, reuse=None, initializer=initializer):
  m = PTBModel(is_training=True, 20)
with tf.variable_scope(“model”, reuse=True, initializer=initializer):
   mtest = PTBModel(is_training=False, 1)


在上述代碼中,創建了實例m,初始化設置20步且不用reuse。從初始化這一步你能觀察到,在計算流程圖中該單元被展開成20個副本,並且在首次迭代後reuse標誌置為True,此時所有的實例都將共享同一組W和b。訓練過程在這個展開的版本上完成。第二個版本mtest設置reuse=True,且在圖中只有該單元的一個實例。但是變量域和m相同,因此它與m共享同一組訓練得到的變量。

一旦訓練完成,我們可以用一個內核來調用這個網路模型。

cost, state = sess.run([mtest.cost, mtest.final_state],
                                 {mtest.input_data: x,
                                  mtest.targets: y,
                                  mtest.initial_state: state})

x和y是輸入變量。這和教程示例中的完整過程相去甚遠。舉個例子,我完全沒有深入到訓練過程的細節中去,完整的示例使用了stacked LSTM並設置了dropout的比例。我的希望是,我在此羅列的細節將有助於讀者了解代碼的最基本結構。

總結

我對兩個系統的編程模型做了比較。這裡是一些頂層的想法。

1.  TensorFlow和CNTK在卷積神經網路那個簡單例子中的做法非常相似。然而,我發現tensorflow版本更容易進行實驗,因為它是由Python驅動的。我能用IPython notebook加載它並做一些其它嘗試。而CNTK則需要用戶完全理解如何用配置文件表達想法。我覺得這很困難。我用TensorFlow能很容易寫一個簡單的k-means聚類算法(詳見我之前關於TensorFlow的文章)。我卻無法用CNTK來做到,不過這可能是由於我的無知,而不是CNTK的局限性。如果有人能提示我該怎麼做,我會很感激的)。

2.  在LSTM遞歸神經網路的例子裡,我發現CNTK的版本相當的透明。我發現TensorFlow版本的頂層想法非常優雅,但我也發現想了解它的所有細節卻非常困難,因為涉及到了變量作用域和變量共享的巧妙用法。我不得不深入地了解它的工作原理。但到現在我也不是十分清楚!我在TensorFlow版本裡確實發現了一個微小但很容易修復的bug,而且我不相信變量作用域和reuse標誌是解決封裝問題的最好方法。但是TensorFlow的好處在於我能很容易地修改實驗。

3.  我也必須說CNTK書和TensorFlow教程都是優秀入門級讀物。我相信有更多詳細的、深入的書馬上就會面世。

我也相信,隨著兩個系統的不斷成熟,它們都會有改進,並且能更容易地使用。我在此不討論性能問題,但CNTK目前在解決某些挑戰難題的速度方面略勝一籌。但隨著這些系統的快速發展,我希望看到競爭也隨之升溫。

原文:TensorFlow Meets Microsoft’s CNTK 
作者:Dennis Gannon(MSR退休數據科學家,印第安納大學計算機科學榮譽退休教授) 
譯者:趙屹華 審校:劉翔宇 
責編:周建丁(投稿請聯繫[email protected],優稿優酬)


想了解IT產品研發背後的那些人、技術和故事,請關注CSDN(資訊)微信公眾號:CSDNnews

當TensorFlow遇見CNTK

閱讀原文


關於作者:
CSDN精彩內容每日推薦。我們關注IT產品研發背後的那些人、技術和故事。

微信號:CSDNnews