【TensorFlow】

綜合

tf api
TensorFlow學習筆記1:graph、session和op

Session與變量的關係:變量的具體值是依存於session的,下面第二個sess會報未初始化的錯,即使第一個sess裏已經初始化了變量,但隨着第一個sess的關閉,變量的值會隨着sess的關閉而消失,但圖上的節點只要定義了就一直存在,這是因爲Tensor是隻是數據的一個引用,sess關閉,引用雖然存在,但是數據已經不存在了,新開一個sess,是取不到前一個sess裏的變量的值

import tensorflow as tf
v1=tf.Variable([[1,2],[3,4]])
v2=tf.Variable([[1,2],[5,6]])
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(tf.global_variables())
    sess.run(tf.assign(v1,[[10,20],[5,6]]))
    print(sess.run(tf.global_variables()))
with tf.Session() as sess:
#     報錯,因爲沒有初始化
    print(sess.run(tf.global_variables()))

TensorFlow基礎知識:計算圖中的Op,邊,和張量

之前搞不清楚op和tensor的關係,op是操作(比如加減乘除),有輸入有輸出,tensor是對數據(比如輸入和計算結果)的引用。下面這句話對理解tf的工作模式非常有幫助
計算圖的定義和圖的運算是分開的.tensorflow是一個’符號主義的庫’.編程模式分爲兩類,命令式(imperative style)和符號式(symbolic style).命令式的程序很容易理解和調試,它按照原有的邏輯運行.符號式則相反,在現有的深度學習框架中,torch是命令式的,Caffe,Mxnet是兩種模式的混合,tensorflow完全採用符號式.符號式編程,一般先定義各種變量,然後建立一個計算圖(數據流圖),計算圖指定了各個變量之間的計算關係,此時對計算圖進行編譯,沒有任何輸出,計算圖還是一個空殼,只有把需要運算的輸入放入後,纔會在模型中形成數據流.形成輸出.

看下面簡單的一段代碼,之前我一直會懷疑,run了兩次,那是不是就inference這個函數在圖上就執行了兩次啊?不是的,執行的不是inference函數,而是數據向前流動,inference函數是僅僅在定義圖上的計算節點,雖然說最後train_op會被run兩次,run是在graph上,數據有兩次流入。而inference這個函數也只是在圖上定義op而已,其他的活並不是在inference函數裏完成的,而是在graph上完成的

import tensorflow as tf
def inference(x):
    net = tf.layers.dense(x, 1)
    return net
x = tf.placeholder(tf.float32, shape=[None, 4])
y = tf.placeholder(tf.float32, shape=[None, 1])
y_ = inference(x)
loss = tf.reduce_mean(y - y_)
train_op = tf.train.AdamOptimizer().minimize(loss)
with tf.Session() as sess:
    sess.run(train_op,feed_dict={x:[[1,2,3,4]],y:[[1]]})
    sess.run(train_op,feed_dict={x:[[1,2,3,4]],y:[[1]]})

Tensorflow一些常用基本概念與函數(2)
tensorflow編程: Inputs and Readers,有place_holder相關和FIFOQueue相關
Inside TF-Slim(13) preprocessing(圖像增強相關)

數據基礎

在tensorflow裏,類型很重要,很多操作裏,只接受float或者int,而且很多時候要求兩個tensor的類型一致

Variable和get_variable的用法以及區別

tf.reset_default_graph()
a=tf.get_variable('13',shape=[],initializer=tf.constant_initializer(12))
# 下一個語句報錯
# a=tf.get_variable('13',shape=[],initializer=tf.constant_initializer(12))
b=tf.Variable(12,name='13')
print(b)
b=tf.Variable(12,name='13')
print(b)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(sess.run(a))
    print(sess.run(b))

Why do we use tf.name_scope()
tf.variable_scope和tf.name_scope的用法

下面的代碼可以看出,name_scope只對op和Variable有效,即對其名稱可以添加scope頭,get_variable是先嚐試獲取,如果已經存在並且此時不是複用(沒有指定reuse)那就報錯,如果沒有就創建。

with tf.name_scope('V1'):
    a1 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1))
    a2 = tf.Variable(tf.random_normal(shape=[2, 3], mean=0, stddev=1), name='a2')
    add = tf.add(a1, a2)
    a3 = tf.Variable(tf.random_normal(shape=[2, 3], mean=0, stddev=1), name='a2')
with tf.name_scope('V2'):
    a4 = tf.Variable(tf.random_normal(shape=[2, 3], mean=0, stddev=1), name='a2')
    # a5 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1))報錯,因爲a1已經存在了
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(a1.name)  # a1:0
    print(a2.name)  # V1/a2:0
    print(add)  # Tensor("V1/Add:0", shape=(2, 3), dtype=float32)
    print(a3.name)  # V1/a2_1:0
    print(a4.name)  # V2/a2:0

下面的代碼可以看出,只要外層variable_scope是reuse,內層無論reuse是什麼,都是reuse

with tf.variable_scope('name') as scope:
    net = tf.get_variable('net',shape=[],initializer=tf.ones_initializer)
    with tf.variable_scope('in'):
        net1 = tf.get_variable('net1',shape=[],initializer=tf.ones_initializer)
print(net)
print(net1)
with tf.variable_scope('name',reuse=True) as scope:
    net = tf.get_variable('net',shape=[],initializer=tf.ones_initializer)
    with tf.variable_scope('in',reuse=False):
        net1 = tf.get_variable('net1',shape=[],initializer=tf.ones_initializer)
print(net)
print(net1)

tensorflow更改變量的值

tf之中對於數據的處理一定要用tf的操作,比如判斷某個tensor的值是否與某個值相等,一定要用tf.equal(tensor,1)這樣來比較,不能直接==

Tensorflow變量與張量

Tensor不存數據,只是存儲數據的來源,因此一切取出tensor的值的操作都要在sess裏進行

Spatial Dropout

import numpy as np
import tensorflow as tf

ary = np.arange(70).reshape((2, 7, 5))
inputs = tf.Variable(ary,dtype=tf.float32)
output = tf.nn.dropout(inputs,keep_prob=0.5,noise_shape=[2,1,5])

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(sess.run(output))

noise_shape:不指定的話,就是隨機drop值,如果指定了,比如第二位爲1,代表着第二維度的每一列要drop整個第二維全drop,要是不drop那就全留着

tf.gradients()中grad_ys的作用

可以把文中例子改了,改成grad_ys=None看輸出有什麼變化
grad_ys is a list of tensors of the same length as ys that holds the initial gradients for each y in ys
這個參數的意義在於對xs中的每個元素的求導加權種

關於word2vec的skip-gram模型使用負例採樣nce_loss損失函數的源碼剖析

  • weights: A Tensor of shape [num_classes, dim], or a list of Tensor objects whose concatenation along dimension 0 has shape [num_classes, dim]. The (possibly-partitioned) class embeddings.
    dim就是embedding_size,嵌入的維度
  • biases: A Tensor of shape [num_classes]. The class biases.
  • labels: A Tensor of type int64 and shape [batch_size, num_true]. The target classes.
    訓練數據樣本對應的類別矩陣,比如skip-gram裏,每個中心詞的上下文都是這個中心詞的label,以
  • inputs: A Tensor of shape [batch_size, dim]. The forward activations of the input network.
    就是在基於輸入的嵌入後的向量,每個樣本都是中心詞經過嵌入後的向量
    inputs和labels是兩兩配對的一個格式,比如一箇中心詞,上下文爲1,文本爲‘ABC’,那麼就可以生成兩個樣本B->A,B->C;在輸入的時候,要將B轉化成嵌入向量,而A,C不用,直接是字典中的單次編號就可以,也就是0~num_classes中的一個值
  • num_sampled: An int. The number of classes to randomly sample per batch.
  • num_classes: An int. The number of possible classes.
    num_class是單詞表的大小
  • num_true: An int. The number of target classes per training example…
    每個訓練數據對應的類別數目,這個我不太理解,難道一個樣本里,一箇中心詞可以對應多個上下文?比如。。B->(A,C)???
tf.reset_default_graph()
g=tf.Graph()
with g.as_default():
    tf.reset_default_graph()

上述代碼報錯,但如果我要清空當前nest graph怎麼辦,Do not use tf.reset_default_graph() to clear nested graphs. If you need a cleared graph, exit the nesting and create a new graph.

tf.reset_default_graph()
g=tf.Graph()
with g.as_default():
    pass
g=tf.Graph()
# g裏面的東西就清空了

tf.newaxis的用法

#[:, tf.newaxis]操作相當於把這個一維向量變成了一個很高的二維矩陣
arr=tf.constant([1,2])
tf.Session() as sess:
	print(sess.run(arr))
    print(sess.run(arr[:,tf.newaxis]))

TensorFlow queue多線程讀取數據

運算操作

tf.control_dependencies()
TensorFlow筆記——(2) tf.group(), tf.tuple 和 tf.identity()
tf.identity的意義以及用例
tensorflow中batch normalization的用法

博主有提到說,“一般來講,這些參數都是基於channel來做的”,那麼也就是說,在CV領域做BN的時候,求平均值是對於通道來求的,因爲假設“輸入x是一個16*32*32*128(NWHC格式)的feature map”,那麼當前層就可以看做一個輸入爲32*32*128的神經層,每個神經元可以relu或者其他,當對這玩意做bn的時候,是對每個通道求均值和方差,比如第一個通道是一個16*32*32的feature map,對這16*32*32個數求均值和方差,用這個均值和方差處理當前通道的feature map,以上都是個人理解,但我覺得是對的,因爲
其中axis的用法,推薦去看tf.layers.BatchNormalization中API的描述,比如輸入的是一個batch,這個batch是100*15,也就是100個樣本,每個樣本15維度,當axis=1的時候,就是說,認爲第一個軸是特徵軸,對這個軸進行bn,那麼算得的均值也是15維度的,就是說有15個均值,這也正是batch_normalization所做的。
至於爲什麼剛纔圖片裏的是128個均值和方差,我猜是進行了廣播吧
6.20得到驗證,bn.weights是個列表,有四個Tensor,分別是均值方差以及對應的滑動量,他們的形狀都是一維度的長度和channal一樣,都是3

import tensorflow as tf
tf.reset_default_graph()
input_x = tf.Variable([[[[1.,2.,3.]],[[4.,5.,6.]]]])
bn = tf.layers.BatchNormalization()
output=bn(input_x)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(output)
    print(bn.weights)
    print(sess.run(output))

tensorflow中的batch_norm以及tf.control_dependencies和tf.GraphKeys.UPDATE_OPS的探究

爲什麼要updata_op,簡單說,就是訓練的時候,每次用的是本次batch裏的數據計算mean和variance,但是預測的時候,用的是好多個mean和variance的滑動平均值來作爲mean和variance的,而根據《google實戰》那本書裏講,滑動平均實際上是維護的新的影子變量,因此需要手動更新這些影子變量

TENSORFLOW GUIDE: BATCH NORMALIZATION
tensorflow在函數中用tf.Print輸出中間值的方法

WARNING:tensorflow:From E:/txtingwang/python/feature_extraction/zxy_test.py:5: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:
就是說,tf.Print已經被刪除了,換成tf.print了。tf.print就方便多了,就只是一個打印操作,不像tf.Print那麼麻煩了,不過上面博客裏講的道理還是適用的

import tensorflow as tf
def test():
    a = tf.constant(0)
    # 如果把下一行放在了for裏面,那麼a_print就會打印10次了
    # 想象運算是一個流,在run的時候,a_print只被定義了一次,只流了一次
    a_print = tf.print(['a_value: ', a])
    for i in range(10):
        with tf.control_dependencies([a_print]):
            a = tf.add(a, 1)
    return a
if __name__ == '__main__':
	sess = tf.Session()
    with sess.as_default():
        tensor = tf.range(10)
        print_op = tf.print(tensor)
        with tf.control_dependencies([print_op]):
          out = tf.add(tensor, tensor)
        sess.run(out)
    with tf.Session() as sess:
        print(sess.run(test()))

tile函數的用法,其實就是堆疊,下面的例子就是第一維度擴充重複兩次,第二維度重複3次

import tensorflow as tf

a = tf.constant([[1, 2], [3, 4], [5, 6]], dtype=tf.float32)
a1 = tf.tile(a, [2, 3])
with tf.Session() as sess:
    print(sess.run(a))
    print(sess.run(a1))

經驗乾貨:使用tf.py_func函數增加Tensorflow程序的靈活性

py_func在v2將會被廢棄,更新方法推薦使用py_function,官方給的說明是tf.py_function, which takes a python function which manipulates tf eager tensors instead of numpy arrays. It’s easy to convert a tf eager tensor to a ndarray (just call tensor.numpy()) but having access to eager tensors means tf.py_functions can use accelerators such as GPUs as well as being differentiable using a gradient tape.也就是說,原來py_func處理的是ndarray,而新的py_function處理的是eager tensor,並且也告訴你怎麼使用了,就是調用tensor.numpy()就行.其中有個參數叫Tout,如果說你的方法返回了兩個list,比如說是[1,2,3],[4,5,6],那麼Tout就應該是(tf.int32,tf.int32),代表返回了兩個值,第一個值內的數字是tf.int32類型的,因爲這兩個list最終都會被包裹成tensor,而tensor的dtype就是這塊Tout所需要指明的

import tensorflow as tf
import numpy as np
def tile_tensor(tensor_a, tensor_b):
    tile_tensor_a = tf.py_function(_tile_tensor, [tensor_a, tensor_b], tf.float32)
    return tile_tensor_a
def _tile_tensor(a, b):
#這裏a和b不再是ndarray了,因爲用了py_function
    tile_a = np.tile(a.numpy(), (b.numpy().shape[0], 1))
    return tile_a

損失函數

TensorFlow裏的losses包下求loss只能求一個樣本的pre和true之間的loss,以hinge loss爲例
SVM(支持向量機)之Hinge Loss解釋

tf.reset_default_graph()
#下面是個multilabel問題,但仍然是一個樣本計算loss
loss = tf.losses.hinge_loss([1,1],[1,1])
loss = tf.losses.hinge_loss([0,1],[1,1])
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(sess.run(loss))

【TensorFlow】sparse_softmax_cross_entropy_with_logits 和softmax_cross_entropy_with_logits選用技巧

tf.losses.get_loss和tf.losses.get_losses,regularization,從下面可以看出,get_xxx_loss是獲取某種損失的列表,get_xxx_losses是獲取某種損失的和。然後,正則化損失並不會被添加到loss_collection裏,從下面可以看到,但是get_total_losses可以獲得全部的損失,包括正則化損失和損失集合裏的損失。API裏描述get_total_losses有如下一句話

In particular, this adds any losses you have added with tf.add_loss() to any regularization losses that have been added by regularization parameters on layers constructors e.g. tf.layers.
特別強調,這個方法會將任何以tf.add_loss()方式添加的損失,和,所有的在定義神經層裏時候定義的正則化損失加起來。

net = tf.Variable([[2.,3.],[1.,2.]])
dense = tf.layers.Dense(1, kernel_regularizer=tf.keras.regularizers.l2(l=0.0005 / 2))
net = dense(net)
print(tf.losses.get_regularization_loss())
# [<tf.Tensor 'dense/kernel/Regularizer/add:0' shape=() dtype=float32>]
print(tf.losses.get_regularization_losses())
# Tensor("total_regularization_loss:0", shape=(), dtype=float32)
print(tf.losses.get_losses())
# []
print(tf.losses.get_total_loss())
# Tensor("total_loss:0", shape=(), dtype=float32)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(tf.losses.get_regularization_loss().eval())
    # 0.000184326 有可能不同
    print(sess.run(tf.losses.get_total_loss()))
    # 0.000184326

下面的代碼展示了sparse_softmax_cross_entropysparse_softmax_cross_entropy_with_logits的區別,前者是後者的一個高層封裝,默認求和求平均,不加其他參數的話,前者返回一個數,後者返回一個列表分別代表每個樣本的損失;前者默認將這個損失加入到tf.GraphKeys.LOSSES這個集合裏,後者沒這功能。

cross_entropy1 = tf.losses.sparse_softmax_cross_entropy(labels = [1,0,1],logits=[[1/2,1/2],[1/2,1/2],[0.9,1/3]])
cross_entropy2 = tf.nn.sparse_softmax_cross_entropy_with_logits(labels = [1,0,1],logits=[[1/2,1/2],[1/2,1/2],[0.9,1/3]])
total_loss=tf.losses.get_total_loss()
with tf.Session() as sess:
    print(sess.run(cross_entropy1))
    print(sess.run(cross_entropy2))
    print(sess.run(total_loss))

Feature column

feature column這個東西真的很好用,把feature columns送入estimator裏,然後,我們不用再去關注如何對輸入數據進行處理了,這些feature column會自動幫我們進行feature transformation
比如說現在有一個feature column是一個分桶形的feature column
讀入dataset的時候,直接讀取數字就行,不需要人工對dataset裏的數字進行處理手動分桶

Estimators

同樣用iris_data爲例,定義Estimator需要一個model_fn,model_fn裏以前向傳播爲主要部分
在調用estimator的訓練步驟中,即estimator.train,需要一個input_fn,根據官方doc這個input_fn的返回需要是一個dataset,其中每個元素都是(features,labels),這個兩個東西直接作爲model_fn的features和labels輸入
dataset經過batch後,features就是一個(?,feature_shape)的張量元素,如果每個樣本是一個28*28的圖片,那麼feature就是(?,28,28),而label也一樣,如果僅僅是一個標記,那label就是(?,)的一個一維向量元素
inference的時候,你直接就可以吧feature想象成placeholder這個東西


def input_fn_closure2(file_path, epoch, batch_size):
    FIELD_DEFAULTS = [[0.0], [0.0], [0.0], [0.0], [0]]

    def input_fn():
        def _parse_line(line):
            # 解析每個元素
            fields = tf.decode_csv(line, FIELD_DEFAULTS)
            # fields的類型是list,與設想的不同,這個函數只會被執行一次
            # 你可以在這個地方打印一個東西,你會發現這個打印操作只進行了1次
            return fields[:-1], fields[-1]

        # 跳過一個元素
        dataset = tf.data.TextLineDataset(file_path).skip(1)
        dataset = dataset.map(_parse_line)
        print(dataset)
        # 下面一行的作用是取類別爲1的所有樣本
        # dataset = dataset.filter(lambda a, b: tf.equal(b, 1))
        # 注意batch之後,dataset中存儲的單位就變了
        dataset = dataset.shuffle(1000).repeat(epoch).batch(batch_size)
        print(dataset)
        return dataset

    return input_fn


def inference2(features, params):
    print(features)
    net = features
    for num_units in params['hidden_units']:
        net = tf.layers.dense(net, num_units, activation=tf.nn.relu)
    logits = tf.layers.dense(net, params['n_classes'], activation=None)
    return logits


def model_fn2(features, labels, mode, params):
    logits = inference2(features, params)

    predicted_class = tf.argmax(logits, axis=1)
    if mode == tf.estimator.ModeKeys.PREDICT:
        predictions = {
            # 請比較帶[:, tf.newaxis]和不帶[:, tf.newaxis]的區別
            'class_ids': predicted_class[:, tf.newaxis],
            'probabilities': tf.nn.softmax(logits),
            'logits': logits
        }
        return tf.estimator.EstimatorSpec(mode, predictions=predictions)
    loss = tf.losses.sparse_softmax_cross_entropy(labels, logits)

    accuracy = tf.metrics.accuracy(labels, predicted_class, name='acc_op')
    metrics = {
        'accuracy': accuracy
    }
    if mode == tf.estimator.ModeKeys.EVAL:
        return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics)

    assert mode == tf.estimator.ModeKeys.TRAIN

    optimizer = tf.train.AdamOptimizer(learning_rate=0.1)
    train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)


def build_estimator2():
    feature_columns = iris.get_feature_columns()
    # 當然由於這個模型訓練的太快= =2秒一存也存不夠10個
    my_checkpointing_config = tf.estimator.RunConfig(
        save_checkpoints_secs=2,  # Save checkpoints every 2 seconds.
        keep_checkpoint_max=10,  # Retain the 10 most recent checkpoints.
    )
    # 如果model_dir存在模型,那麼就會直接讀取文件夾下面最新的模型,並且檢查文件模型和代碼中模型是否兼容
    classifier = tf.estimator.Estimator(model_fn=model_fn2, model_dir='guide/estimator_test/model/iris',
                                        config=my_checkpointing_config,
                                        params={'feature_columns': feature_columns,
                                                # Two hidden layers of 10 nodes each.
                                                'hidden_units': [10, 10],
                                                # The model must choose between 3 classes.
                                                'n_classes': 3,
                                                })
    return classifier


def train_eval_test2(train_file_path, epoch, batch_size, test_file_path):
    model = build_estimator2()
    model.train(input_fn=input_fn_closure2(train_file_path, epoch, batch_size), steps=1000)
    print(model.evaluate(input_fn=iris.input_fn_closure2(test_file_path, 1, batch_size)))

    expected = ['Setosa', 'Versicolor', 'Virginica']
    predict_x = {
        'SepalLength': [5.1, 5.9, 6.9],
        'SepalWidth': [3.3, 3.0, 3.1],
        'PetalLength': [1.7, 4.2, 5.4],
        'PetalWidth': [0.5, 1.5, 2.1],
    }
    predict_x = [[5.1, 5.9, 6.9],
                 [3.3, 3.0, 3.1],
                 [1.7, 4.2, 5.4],

                 [0.5, 1.5, 2.1]
                 ]
    predict_x = [[l[i] for l in predict_x] for i in range(3)]
    # 由於訓練的時候進行了batch,爲了保證訓練和測試的一致性,因此測試的時候也要batch
    # 因爲對於dataset來講,batch前後存儲的數據格式是不一樣的
    # 並且測試的時候,dataset裏不需要label
    # 訓練集的dataset的格式是(dict:[string,1維array],),因爲會batch
    predictions = model.predict(
        input_fn=lambda: tf.data.Dataset.from_tensor_slices(predict_x).batch(1))

    template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"')

    for pred_dict, expec in zip(predictions, expected):
        # class_id = pred_dict['class_ids'][0]
        # probability = pred_dict['probabilities'][class_id]
        #
        # print(template.format(iris.SPECIES[class_id],
        #                       100 * probability, expec))
        print(pred_dict)
        print(expec)
        
train_eval_test2('data/iris/iris_training.csv', None, 100, 'data/iris/iris_training.csv')

Dataset

Tensorflow中數據集的使用方法(tf.data.Dataset)
iris_train,iris_test兩個文件介紹TextlineDataset的用法

FIELD_DEFAULTS = [[0.0], [0.0], [0.0], [0.0], [0]]
def input_fn(file_path):
    def _parse_line(line):
        # 解析每個元素
        fields = tf.decode_csv(line, FIELD_DEFAULTS)
        # fields的類型是list,與設想的不同,這個函數只會被執行一次
        # 你可以在這個地方打印一個東西,你會發現這個打印操作只進行了1次
        return fields[:-1], fields[-1]
    # 跳過一個元素
    dataset = tf.data.TextLineDataset(file_path).skip(1)
    dataset = dataset.map(_parse_line)
    # 注意,filter函數裏的接受參數數量一定要和dataset裏的每個元素包括的元素數量一致
    # 另外,判斷相等的時候,一定要用tf.equal,因爲實際上ab都是tensor,不能直接用==
    dataset = dataset.filter(lambda a, b: tf.equal(b, 1))
    #把下面的註釋再打開看看結果有什麼不同,體會一下
    #dataset = dataset.batch(2)
    return dataset

if __name__ == '__main__':
    data = input_fn('data/iris/iris_test.csv')
    iter = data.make_one_shot_iterator()
    x = iter.get_next()
    # x是(<tf.Tensor 'IteratorGetNext:0' shape=(4,) dtype=float32>, <tf.Tensor 'IteratorGetNext:1' shape=() dtype=int32>)
    # 也就是說x裏有兩個元素
    print(x)
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))

from_tensor_slices代碼,第一個是以元組方式輸入,第二個是以列表形式輸入,第一個dataset的第一個元素就是元組中4個數字裏的第一個數字(也就是4個1),而第二個dataset的第一個元素,就是[1,2]

features_dataset = tf.data.Dataset.from_tensor_slices(([1,2], [1,2], [1,2], [1,2]))
print(features_dataset)
features_dataset = tf.data.Dataset.from_tensor_slices([[1,2], [1,2], [1,2], [1,2]])
print(features_dataset)

TFRecordWriter

A tf.Operation that, when run, writes contents of dataset to a file.write方法返回的是一個op,需要run的,官方教程裏用了tf.enable_eager_execution(),所以直接就執行了

注意dataset的迭代器的性質,下面兩個session的結果是不一樣的,每次run都相當於請求數據,每次請求的數據,只要用到了dataset中的數據,都會導致有數據流開始flow,所以第一個session纔是正確的訪問數據的方式,而第二個session,每次run都會導致get_next的執行

tf.reset_default_graph()
ds = tf.data.Dataset.from_tensor_slices([[1,2],[2,4],[3,6],[4,8],[5,10]])
ds=ds.map(lambda x:(x[0],x[1]))
i=ds.make_one_shot_iterator()
x,y=i.get_next()
a=tf.add(x,1)
b=tf.multiply(y,2)
with tf.Session() as sess:
    try:
        while True:
            print(sess.run([x,y,a,b]))
    except tf.errors.OutOfRangeError:
            print('循環正常結束')
with tf.Session() as sess:
    try:
        while True:
            print(sess.run([x,y]))
            print(sess.run([a,b]))
    except tf.errors.OutOfRangeError:
            print('循環正常結束')

Tensorflow踩坑記之頭疼的tf.data 各種iterator

TFRecord

轉化爲TFRecord

iris_train爲例介紹用法
下面的程序演示瞭如何將一個文件轉化成TFRecord文件,需要注意的是,tf.train.Feature,tf.train.Int64List,tf.train.Example對象構建的時候,他們的傳入參數名稱都不能省略,比如float_list=,int64_list=,features=等等,並且value=接的參數必須是可迭代對象,比如說列表,並且floatlist的存儲方式是tf.float32
寫入的時候tf.train.Features的feature參數存的是一個dict,key爲特徵名,value爲Feature對象
下面這種方法是以python_io的方式來寫入tf_rec文件

import tensorflow as tf

CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth',
                    'PetalLength', 'PetalWidth', 'Species']
SPECIES = ['Setosa', 'Versicolor', 'Virginica']


def _float_feature(value):
    return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))


def _int64_feature(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[int(value)]))


def convert_to_tf_record(file_path='data/iris/iris_training.csv', output_path='data/tf_record/iris_training.tfr'):
    import pandas as pd
    def feature_dict(row: pd.Series):
        d = {CSV_COLUMN_NAMES[i]: _float_feature(row[i]) for i in range(4)}
        d[CSV_COLUMN_NAMES[-1]] = _int64_feature(row[-1])
        return d

    iris = pd.read_csv(file_path, header=None, names=CSV_COLUMN_NAMES, skiprows=1)
    with tf.python_io.TFRecordWriter(output_path) as writer:
        for index, row in iris.iterrows():
            example = tf.train.Example(features=tf.train.Features(feature=feature_dict(row)))
            writer.write(example.SerializeToString())
        writer.close()

過些陣子我補一個用dataset寫tfrec文件的代碼

用dataset讀取tfrecord文件

以下是用dataset讀取tfrecord文件的第一種方式,在這裏,剛開始讀取的dataset裏,每個元素都是一個Example對象,並且通過map每次只對一個example進行解析,其中dataset的形狀爲DatasetV1Adapter shapes: (( ), ( ), (), (), ()), types: (tf.float32, tf.float32, tf.float32, tf.float32, tf.int64)

import tensorflow as tf

CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth',
                    'PetalLength', 'PetalWidth', 'Species']
def get_iris_tfrecord_dataset_with_single_parse(file_path='data/tf_record/iris_training.tfr'):
    feature_dict = {CSV_COLUMN_NAMES[i]: tf.FixedLenFeature([], tf.float32) for i in range(4)}
    feature_dict[CSV_COLUMN_NAMES[-1]] = tf.FixedLenFeature([], tf.int64)

    def _parse(example):
        features = tf.parse_single_example(example, features=feature_dict)
        return [features[name] for name in CSV_COLUMN_NAMES]

    dataset = tf.data.TFRecordDataset(file_path)
    dataset = dataset.map(_parse)
	print(dataset)
    iter = dataset.make_one_shot_iterator()
    x = iter.get_next()
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))
    return dataset

以下是用dataset讀取tfrecord文件的第二種方式,在這裏,剛開始讀取的dataset裏,每個元素都是一個Example對象,然後,先進行了batch操作通過map每次只對每個batch內的多個example進行解析,dataset的形狀會變成DatasetV1Adapter shapes: ((?, ), ( ?,), (?, ), (?, ), (?, )), types: (tf.float32, tf.float32, tf.float32, tf.float32, tf.int64),也就是說,parse_example是對每個batch內所有example進行解析操作

import tensorflow as tf

CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth',
                    'PetalLength', 'PetalWidth', 'Species']
def get_iris_tfrecord_dataset_with_parse(file_path='data/tf_record/iris_training.tfr'):
    feature_dict = {CSV_COLUMN_NAMES[i]: tf.FixedLenFeature([], tf.float32) for i in range(4)}
    feature_dict[CSV_COLUMN_NAMES[-1]] = tf.FixedLenFeature([], tf.int64)

    def _parse(example):
        features = tf.parse_example(example, features=feature_dict)
        return [features[name] for name in CSV_COLUMN_NAMES]

    dataset = tf.data.TFRecordDataset(file_path)
    dataset = dataset.batch(10)
    dataset = dataset.map(_parse)
	print(dataset)
    iter = dataset.make_one_shot_iterator()
    x = iter.get_next()
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))
    return dataset

FixedLenFeature

從tfrecord文件解析的時候,需要指定features=這個參數,這個參數是一個dict,key爲特徵名,value爲一個FixedLenFeature或者VarLenFeature,通過這兩個東西,我們可以確定每個特徵的形狀以及類型
上一節,在第一種使用parse_single_example解析example的代碼中,我使用了FixedLenFeature([], tf.float32),意思就是,某個特徵,他的形狀是[],這代表的是,該特徵裏存的就是一個,並且是tf.float32類型的,你把這個FixedLenFeature([], tf.float32)換成FixedLenFeature([1], tf.float32),這樣解析的時候,就會認爲這個特徵裏存的是一個1維度長度爲1的向量,重新跑第一個程序,dataset的變成DatasetV1Adapter shapes: ((1, ), (1, ), (1, ), (1, ), (1, )), types: (tf.float32, tf.float32, tf.float32, tf.float32, tf.int64)

Tensorflow高階讀寫教程,中有提到,example裏的一個特徵裏可以簡單的存一個數,也可以存多個數,但這個特徵裏的多個值的類型一定要相同

但對於第二種使用parse_example解析example的代碼中,FixedLenFeature([], tf.float32)換成FixedLenFeature([1], tf.float32),結果會不同,代碼如下,dataset的形狀同樣會變成<DatasetV1Adapter shapes: ((?, 1), (?, 1), (?, 1), (?, 1), (?, 1)), types: (tf.float32, tf.float32, tf.float32, tf.float32, tf.int64)>,和之前的是一個道理
另外,在解析對象的時候,即使文件中的某個特徵是int類型存的,讀取的時候仍然可以按照float類型來解析,但反過來不行

import tensorflow as tf

CSV_COLUMN_NAMES = ['SepalLength', 'SepalWidth',
                    'PetalLength', 'PetalWidth', 'Species']
def get_iris_tfrecord_dataset_with_parse(file_path='data/tf_record/iris_training.tfr'):
    feature_dict = {CSV_COLUMN_NAMES[i]: tf.FixedLenFeature([1], tf.float32) for i in range(4)}
    feature_dict[CSV_COLUMN_NAMES[-1]] = tf.FixedLenFeature([1], tf.int64)

    def _parse(example):
        features = tf.parse_example(example, features=feature_dict)
        return [features[name] for name in CSV_COLUMN_NAMES]

    dataset = tf.data.TFRecordDataset(file_path)
    dataset = dataset.batch(10)
    dataset = dataset.map(_parse)
	print(dataset)
    iter = dataset.make_one_shot_iterator()
    x = iter.get_next()
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))
    return dataset

下面的代碼演示了,一個特徵裏存多個數的方法,如代碼所示,一個example裏只有一個名爲data的特徵,這個特徵裏存了兩個數,那麼讀取的時候,就要指定data這個特徵裏的存的數字的數目是2

def multi_rank_convert_to_tf_record(output_path='data/tf_record/multirank.tfr'):
    data = [[1,2],[3,4]]
    with tf.python_io.TFRecordWriter(output_path) as writer:
        for row in data:
            example = tf.train.Example(features=tf.train.Features(feature={
                'data':tf.train.Feature(int64_list=tf.train.Int64List(value=row))
            }))
            writer.write(example.SerializeToString())
        writer.close()

def get_multi_rank_tf_dataset(file_path='data/tf_record/multirank.tfr'):
    def _parse(example):
        features = tf.parse_single_example(example,features={'data':tf.FixedLenFeature([2],tf.int64)})
        return features
    dataset = tf.data.TFRecordDataset(file_path)
    dataset = dataset.map(_parse)

    print(dataset)
    iter = dataset.make_one_shot_iterator()
    x = iter.get_next()
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))
    return dataset

VarLenFeature

顧名思義,就是變長特徵,返回的是SparseTensorValue

def multi_rank_convert_to_tf_record(output_path='data/tf_record/multirank.tfr'):
    # data = [[1,2],[3,4]]
    data = [[1],[3,4,5]]
    with tf.python_io.TFRecordWriter(output_path) as writer:
        for row in data:
            example = tf.train.Example(features=tf.train.Features(feature={
                'data':tf.train.Feature(int64_list=tf.train.Int64List(value=row))
            }))
            writer.write(example.SerializeToString())
        writer.close()

def get_multi_rank_tf_dataset(file_path='data/tf_record/multirank.tfr'):
    def _parse(example):
        # features = tf.parse_single_example(example,features={'data':tf.FixedLenFeature([2],tf.int64)})
        features = tf.parse_single_example(example,features={'data':tf.VarLenFeature(tf.int64)})
        return features
    dataset = tf.data.TFRecordDataset(file_path)
    dataset = dataset.map(_parse)

    print(dataset)
    iter = dataset.make_one_shot_iterator()
    x = iter.get_next()
    with tf.Session() as sess:
        print(sess.run(x))
        print(sess.run(x))
    return dataset

CNN

TensorFlow中CNN的兩種padding方式“SAME”和“VALID”

import tensorflow as tf

input = tf.ones([1, 5, 5, 1])
filter_weight = tf.Variable(tf.ones([3, 3, 1, 4]))
conv1 = tf.nn.conv2d(input, filter_weight, strides=[1, 1, 1, 1], padding='SAME')
conv2 = tf.nn.conv2d(input, filter_weight, strides=[1, 1, 1, 1], padding='VALID')
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print(sess.run(conv1))
    print(sess.run(conv2))

你可以打印一下input是啥,這個input和我想象的不太一致,不過input[0,:,:,0]代表的就是第一個圖片的第一個channal
conv1的輸出和conv2不一樣,SAME是因爲補充零了,怎麼補充的呢,你把input想象成一張55並且只有一個通道的圖片,然後用一個33並且輸出爲4通道的filter卷積,print(sess.run(conv1)[0,:,:,0])就是這張圖片的第一層輸出,可以看到,在四周都進行了padding的補0操作

RNN

RNNCell使用

tf.layers

下面兩個conv2d_layer是參數共享的

net = tf.Variable([[[[2.,3.]]]])
with tf.variable_scope('name') as scope:
    conv2d_layer = tf.layers.Conv2D(2,[1,1])
    out = conv2d_layer(net)
    print(conv2d_layer.variables)
with tf.variable_scope('name',reuse=True) as scope:
    conv2d_layer = tf.layers.Conv2D(2,[1,1])
    out = conv2d_layer(net)
    print(conv2d_layer.variables)

tf.contrib.slim

tf.contrib.slim簡介
Slim下的函數介紹(一)
TensorFlow-slim 訓練 CNN 分類模型
TensorFlow 使用預訓練模型 ResNet-50

get_init_fn(checkpoint_exclude_scopes)是不恢復exclude裏的變量
get_trainable_variables(scopes_to_freeze )是獲取除了scopes_to_freeze包含的變量,送給minimize,就可以保證不更新scopes_to_freeze裏的參數了

Queue與Reader

十圖詳解tensorflow數據讀取機制(附代碼)
Tensorflow–tf.FIFOQueue詳解
tensorflow隊列操作詳解

下面這兩段代碼都會block,第一段是因爲,用的是enqueue,每次入隊只進一個元素,就是[3,2,1],後面的並沒有入隊。第二段是因爲隊列滿了,enqueue被block了,所以後面的操作都進行不了

import tensorflow as tf

input_data=[[3.,2.,1.],[11.,22.,33.],[111.,222.,333.]]
q=tf.FIFOQueue(3,dtypes=[tf.float32])
init=q.enqueue(input_data)
output_data=q.dequeue()

with tf.Session() as sess:
    init.run()
    init.run()
    print('1:',sess.run(output_data))
    print('2:',sess.run(output_data))
    print('3:',sess.run(output_data))
    sess.run(q.close(cancel_pending_enqueues=True))
    print(sess.run(q.is_closed()))
with tf.Session() as sess:
	qr = tf.FIFOQueue(capacity=3, dtypes=[tf.uint8], shapes=((), ))
    en_qr = qr.enqueue_many([[1, 2, 3,4,5,6]])
    sess.run(en_qr)
    de_qr = qr.dequeue()
    res = sess.run(de_qr)
    print(res)
    de_qr = qr.dequeue()
    res = sess.run(de_qr)
    print(res)
import tensorflow as tf

if __name__ == '__main__':
    with tf.Session() as sess:
        qr = tf.FIFOQueue(capacity=6, dtypes=[tf.uint8], shapes=((),))
        en_qr = qr.enqueue_many([[1, 2, 3, 4, 5, 6]])
        sess.run(en_qr)
        de_qr = qr.dequeue()
        res = sess.run(de_qr)
        print(res)
        de_qr = qr.dequeue()
        res = sess.run(de_qr)
        print(res)
        
    with tf.Session() as sess:
        qr = tf.FIFOQueue(capacity=6, dtypes=[tf.uint8], shapes=((),))
        en_qr = qr.enqueue([1])
        sess.run(en_qr)
        de_qr = qr.dequeue()
        res = sess.run(de_qr)
        print(res)
        en_qr = qr.enqueue([2])
        sess.run(en_qr)
        de_qr = qr.dequeue()
        res = sess.run(de_qr)
        print(res)

Word2Vec

tensorflow word2vec demo詳解

Saver

一個快速完整的教程,以保存和恢復Tensorflow模型

import tensorflow as tf
# 代碼1,儲存模型
v1 = tf.Variable(tf.constant(2.0, shape=[1]), name='v1')
v2 = tf.Variable(tf.constant(2.0, shape=[1]), name='v2')
result = v1 + v2
saver = tf.train.Saver()
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.save(sess,'ckpt_dir/model.ckpt')
import tensorflow as tf

# 代碼2 讀取數據
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name='v1')
v2 = tf.Variable(tf.constant(2.0, shape=[1]), name='v2')
result = v1 + v2
saver = tf.train.Saver()

with tf.Session() as sess:
    saver.restore(sess, 'ckpt_dir/model.ckpt')
    print(sess.run(v1))
    print(sess.run(result))
# [2.]
# [4.]

下面代碼如果把初始化放在restore後面,那麼結果返回的就是1和3

import tensorflow as tf

# 代碼3 讀取只讀取v1
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name='v1')
v2 = tf.Variable(tf.constant(2.0, shape=[1]), name='v2')
result = v1 + v2
saver = tf.train.Saver([v1])

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.restore(sess, 'ckpt_dir/model.ckpt')
    print(sess.run(v1))
    print(sess.run(result))

# [2.]
# [4.]

tensorflow 只恢復部分模型參數
淺談Tensorflow模型的保存與恢復加載
我改了一下代碼
restore的時候需要指定具體哪個model,所以latest_checkpoint這個方法就很好用了

#存儲pb
import tensorflow as tf
import os
from tensorflow.python.framework import graph_util

pb_file_path = 'ckpt_dir/combined_model.pb'
x = tf.placeholder(tf.int32, shape=[1, 3], name='x')
weights = tf.Variable([[2], [2], [1]])
b = tf.Variable(1, name='b')
wx = tf.matmul(x, weights)
# 這裏的輸出需要加上name屬性
op = tf.add(wx, b, name='op_to_store')

sess = tf.Session()
sess.run(tf.global_variables_initializer())

path = os.path.dirname(os.path.abspath(pb_file_path))
if os.path.isdir(path) is False:
    os.makedirs(path)

# convert_variables_to_constants 需要指定output_node_names,list(),可以多個
constant_graph = graph_util.convert_variables_to_constants(sess, sess.graph_def, ['op_to_store'])
with tf.gfile.FastGFile(pb_file_path, mode='wb') as f:
    f.write(constant_graph.SerializeToString())

# test
feed_dict = {x: [[1, 2, 3]]}
print(sess.run(op, feed_dict))
#讀取pb
import tensorflow as tf
from tensorflow.python.platform import gfile

def restore_mode_pb(pb_file_path):
  sess = tf.Session()
  with gfile.FastGFile(pb_file_path, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')

  print(sess.run('b:0'))

  input_x = sess.graph.get_tensor_by_name('x:0')
  # input_y = sess.graph.get_tensor_by_name('y:0')

  op = sess.graph.get_tensor_by_name('op_to_store:0')

  ret = sess.run(op, {input_x: [[2,2,3]]})
  print(ret)

restore_mode_pb('ckpt_dir/combined_model.pb')

tensorflow saver和checkpoint總結

image

draw_bounding_boxes

就是一個給圖片上畫框框的方法,bbox是一個[batch, num_bounding_boxes, 4]的張量,可以畫num_bounding_boxes個框框,比如說下面代碼,這個bbox就是畫兩個框框,但是我發現總有時候畫不上去,我也不太懂咋回事

bbox = tf.constant([[[0.1, 0.35, 0.6, 0.75],[0.15, 0.3, 0.3, 0.4]]],
                         dtype=tf.float32,
                         shape=[1, 2, 4])
image_with_box = tf.image.draw_bounding_boxes(tf.expand_dims(image, 0),
                                                  bbox)

tf.image.sample_distorted_bounding_box

其實《google實戰》那本書裏有提到過這個方法,就是隨機生成一個框框,至於bounding_boxes,講得就是圖片裏哪些範圍內是有信息量的,然後min_object_covered參數指的是The cropped area of the image must contain at least this fraction of any bounding box supplied.就是輸出的框框至少應該包括百分之多少的bounding_boxes

找不到API的各種方法

tensorflow中的control_flow_ops.switch函數介紹
Tensorflow(4)-control_flow_ops.cond
How to use the function merge and switch of tensorflow?

圖片處理

深入學習圖像處理——圖像相似度算法——基於PILLOW

問題與解決

  1. 報錯LossTensor is inf or nan,網上說都是什麼學習率太大了,梯度跑飛了,這個可以理解,但是我遇到這個問題,是因爲用了tf.losses.sparse_softmax_cross_entropy(labels=label_batch, logits=out)這個方法,在這個方法裏,labels是標籤,比如可以是[0,1,2],代表三個樣本,第一個樣本的類標籤是0;logits就是輸出的機率,比如說可以是[[0.5,0.4,0.1],[0.4,0.5,0.1],[0.2,0.2,0.6]]。(這個方法實際上就在調用tf.nn.sparse_softmax_cross_entropy_with_logits.
    但是我爲什麼會報上面那個錯呢,是因爲樣本類別的數目是456,就是從0到455,所以labels的輸入的類標籤就應該是0到455,但是我把輸出層的神經元節點數目搞錯了,輸出層的神經元節點數目變成了455,也就是說,每個樣本的logits的維度數目是455。而恰好我又是在GPU上跑的,官方文檔對於labels這個參數有說明Tensor of shape [d_0, d_1, ..., d_{r-1}] (where r is rank of labels and result) and dtype int32 or int64. Each entry in labels must be an index in [0, num_classes). Other values will raise an exception when this op is run on CPU, and return NaN for corresponding loss and gradient rows on GPU.,就是說如果labels和logits的類數目對應不上,在CPU上會報錯,但是GPU上會導致返回值爲NaN,所以就導致了上面的錯誤

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章