<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # Estimator簡要介紹 上一節我們實現了DNN,你一定注意到了我們使用了tensorflow自定義的Estimator——DNNClassifier。 你可能會問Estimator是什么? 按照我的理解其實Estimator就是tensorflow一個高級API。 然后它有什么作用呢? 可極大地簡化機器學習編程的高階 TensorFlow API Estimator 會封裝下列操作: * 訓練 * 評估 * 預測 * 導出以供使用 我們可以使用提供的預創建的 Estimator,也可以編寫自定義 Estimator。所有 Estimator(無論是預創建還是自定義)都是基于 tf.estimator.Estimator 類的類。 :-: ![](https://box.kancloud.cn/a506cb0e53c510149635a27caeb8ef84_1104x465.png) :-: api層次結構 > 既然是API那我們使用時,就應該按照api定義的規則去使用,通過上一節DNN我們知道了預定義的Estimator都需要什么格式的輸入和初始化。其實就是按照api定義的格式我們去生成所要求的的格式而已——條用DatasetsAPI去生成輸入。 > 如果想要深入了解可以看官網:https://www.tensorflow.org/programmers_guide/estimators # CNN實例。 這一節我們將自己定義 Estimator來實現CNN網絡。首先我建議你首先看一下官網自定義Estimator這一節(中文的):https://www.tensorflow.org/get_started/custom_estimators 為了了解一下其中涉及的概念,以防下面忽然出現一個概念你就蒙了。 我們還是去手動實現官網上的例子手寫數字的識別。 ## 描述問題 :-: ![](https://box.kancloud.cn/80b7dc4e89d7b43cf469fd7228a0c2e5_330x137.png) 識別手寫數字-------就是判別手寫的數字是0、1、2、3、4、5、6、7、8、9中的哪一個。 ## 已有的條件 MINIST數據集——有60000張訓練圖片10000張測試圖片,每張圖片是格式化好的28* 28的單色圖片。 ## 解決問題的方法 我們自己構建卷積神經網絡,用上述的MINIST數據集進行訓練模型,然后進行測試效果。 我們使用的網絡結構如下: 1. 輸入層 2. 卷積層32個 5x5 filters ReLU激活函數 3. 最大池化層 2*2 filter 步長為2 4. 卷積層64 個 5x5 filters ReLU激活函數 5. 最大池化層 2*2 filter 步長為2、 6. 全連接層1,024 神經元, with dropout regularization rate of 0.4 7. 全連接層 10 神經元, one for each digit target class (0–9). ## 解決問題過程 ### 前期準備工作 首先我們用上一節的虛擬環境tensorflow1建立一個項目cnn_minist,如下圖: ![](https://box.kancloud.cn/d1f6b251c98a7b114dde38b37a6344ac_783x488.png) 讓我們首先建立我們的tensorflow程序的架子。新建一個cnn_mnist.py文件,然后添加上以下代碼: ~~~ from __future__ import absolute_import from __future__ import division from __future__ import print_function # 導入所需要的包 import numpy as np import tensorflow as tf tf.logging.set_verbosity(tf.logging.INFO) # 我們應用的邏輯將添加到這里 if __name__ == "__main__": tf.app.run() ~~~ 經過我們下面的進行,我們將添加構建網絡,訓練網絡、評估網絡的代碼到該項目。當然了你首先應該知道卷積神經網絡里面每個層的作用和具體操作。如果你還不是太了解那么我強烈建議您去瀏覽一下上面CNN介紹一節。 還記得我們上一節寫的輸入函數用的datasetAPI嗎?這一節我們使用tf.layersAPI它包含創建以上三層的接口。如果你在思考的多一點,那么一定會猜到Estimator就是在此api上再次抽象出來的。 我們先來介紹一下tf.layersAPI中我們要使用的函數。 * conv2():創建2維的卷積層以卷積核的數量、卷積核的大小、填充方式、激活函數為參數。 * max_pooling2d():用最大池化算法創建二維的池化層以池化核大小和步長作為參數 * dense():創建一個全連接層。以神經元的個數和激活函數為參數 以上的這些方法接受一個tensor對象為輸入,最后返回一個轉變的tensor.這樣會使層與層直接連接更加容易:僅僅把一層的輸出作為下一層的輸入。 > tensor對象是tensorflow低階的API,我們后期如果用estimitor實現不了時,就可以使用更低階的api來做,靈活性更大但是難度也更大。 ### 開始編寫Estimator的model函數 打開cnn_mnist.py文件然后添加下面cnn_model_fn方法——符合TensorFlow's Estimator API 的接口定義。cnn_model_fn以MINIST特征數據,標簽數據模型模式(TRAIN, EVAL, PREDICT)作為參數,配置CNN,然后返回預測,損失和訓練操作 > 如果你看過定義Estimator這一節,就知道預創建的estimator與自定義的estimitor唯一的區別就是model_fn函數是創建好的還是自己寫的。 #### 輸入層 在cnn_model_fn方法中添加輸入層代碼: ~~~ def cnn_model_fn(features, labels, mode): """Model function for CNN""" # 輸入層 input_layer = tf.reshape(features["x"], [-1, 28, 28, 1]) ~~~ 解釋:你是不是感覺有點蒙呀怎么上來就給出的輸入層是這個樣子呀,別慌!我馬上解釋。首先我們要知道tf.layer生成卷積層和池化層。需要輸入的默認張量是[batch_size, image_height, image_width, channels](batch_size:批量數量, image_height:圖片的高,image_width:圖片的寬,channels:通道的數量:RGB圖像是3灰度圖像是1, 當然你可以改變張量的格式那就必須制定data_format 的參數)。所以我們要給他們提供這種張量。而 tf.reshape可以實現這個功能你可以去參考:https://blog.csdn.net/m0_37592397/article/details/78695318 的用法。這里需要知道的一點是-1像是一個占位符它是根據具體傳入的圖片數量動態變化的。 **輸入張量大小是:[batch_size, 28, 28, 1]** #### 卷積層 接著在上面cnn_model_fn的方法中添加下面輸入第一層卷積層代碼: ~~~ # 第一層卷積層 conv1 = tf.layers.conv2d( inputs=input_layer, filters=32, kernel_size=[5,5], padding="same", activation=tf.nn.relu ) ~~~ 解釋:可以看到該卷積層是32個5* 5卷積核,使用tf.nn.relu激活函數,并且與輸入層的張量[batch_size, image_height, image_width, channels]連接上了。但是padding = "same"你可能不太理解,那我們就解釋一下————首先padding可以在“same”和“valid”兩個枚舉值直接選擇一個默認是“valid”,作用是:指示是否是經過填充是輸入張量和輸出張量一樣大小。我們這里選擇“same”表示在輸入張量邊緣添加0使輸出張量也保持跟輸入張量的大小一樣。如果不填充的化,輸出張量將變成24 * 24 (28-5 +1),從側面也表明了tf.layers.conv2d(),的步長是1,不能修改。 **到現在經過第一層卷積層輸出的張量大小為:[batch_size, 28, 28, 32]** #### 池化層 接著在上面cnn_model_fn的方法中添加下面第一個max_pool的代碼: ~~~ #第一個池化層 pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2,2], strides=2) ~~~ 解釋:這個很容易理解,我們就不解釋代碼了。但是需要注意一點strides是在兩個witch和high兩個方向都是2,你可以設置在兩個方向設置不同的值,如:[3, 6] **到現在輸出的張量的大小是:[batch_size, 14, 14, 32]** * * * * * 現在我們再在上面的cnn_model_fn的函數中添加第二個卷積層和第二個池化層,代碼如下: ~~~ #第二個卷積層 conv2 = tf.layers.conv2d( inputs=pool1, filters=64, kernel_size=[5,5], padding="same", activation=tf.nn.relu ) #第二個池化層 tf.layers.max_pooling2d( inputs=tf.layers.max_pooling2d(inputs=conv2, pool_size=[2,2], strides=2) ) ~~~ **經過第二個卷積層我們的張量大小為:[batch_size, 14, 14, 64] 經過第二個池化層后我們的張量大小為:[batch_size, 7, 7, 64]** * * * * * #### 全連接層 我們接著在cnn_model_fn的函數上添加全連接層代碼如下: ~~~ #全連接層 pool2_flat = tf.reshape(pool2, [-1, 7*7*64]) dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu) ~~~ 解釋: 因為全連接是每個神經元都加上一個權重然后生成下一個神經元。所以如果改變一個張量的維數將有助于計算(自己猜測)。 所以加上一個tf.reshape(pool2, [-1, 7*7*64])變成two dimensions張量。 **輸出的張量大小:[batch_size, 1024]** * * * * * **dropout正則化** 為了是模型最后的結果效果好,同時防止過擬合,我們添加dropout正則化。可以參考下面網站了解:https://blog.csdn.net/sinat_29819401/article/details/60885679 代碼如下: ~~~ #dropout正則化 dropout = tf.layer.dropout( inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN ) ~~~ 解釋:當我們訓練該模型時我們才dropout正則化,并且以40%的神經元的值被隨機丟棄。而且只有訓練時我們才去正則化。 **輸出的張量大小:[batch_size, 1024]** * * * * * #### 邏輯層 其實就是有特殊功能的全連接層。代碼如下: ~~~ #邏輯層 logits = tf.layers.dense(inputs=dropout, units=10) ~~~ 解釋:因為最后的分類是10個,所以我們選擇10個神經元,也就是生成10個數。 **輸出的張量大小:[batch_size, 10]** 網絡最后輸出的10個數的含義是什么呢,我們應該怎么使用呢?下面我們就來說一說! * * * * * 我們網絡的邏輯層會以原始的數據返回預測在[batch_size, 10]張量里面。讓我們轉變原始數據成兩個不同的格式。 * The predicted class for each example: a digit from 0–9. * The probabilities for each possible target class for each example: the probability that the example is a 0, is a 1, is a 2, etc. 有時候英文比中文說的準確。所以我不吝嗇的上了兩句英文。 好了廢話少說,我們先了解該句話的含義(不添加到函數里): ~~~ tf.argmax(input=logits,axis=1) ~~~ 解釋:因為我們所預測的類是每一行中最大的值所對應的類別。我們能用上面的函數去找到每一行最大數的索引值。(從0開始編碼)可以參考這個:https://blog.csdn.net/u011597050/article/details/80581461 我們添加下一個格式,代碼如下(同樣不添加到函數里): ~~~ tf.nn.softmax(logits, name=softmax_tensor) ~~~ 解釋:我們可以通過使用tf.nn.softmax應用softmax激活從我們的logits層中得出概率。把10個數變成變成每個類對應的概率。可以去查一下softmax函數的用法吧。可以參考:https://blog.csdn.net/l691899397/article/details/52291909 > 我們使用name參數來明確命名這個操作softmax_tensor,所以我們可以在稍后引用它。 > * * * * * #### 預測模塊 這一小部分我們寫estimator預測模塊。我們首先要知道estimator無論是預測還是訓練還是評估最后都返回一個EstimatorSpec對象。 我們先把代碼寫下來:(同樣是接著上面的寫) ~~~ #預測模塊 predictions = { "classes": tf.argmax(input=logits, axis=1), "probabilities": tf.nn.softmax(logits, name="softmax_tensor") } if mode == tf.estimator.ModeKeys.PREDICT: return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions) ~~~ 解釋:固定格式不做解釋。 #### 訓練模塊與評估模塊 當要訓練和評估時,我們首先要定義損失函數。對于多類別分類問題我們嘗試用交叉熵函數作為損失函數。如果不理解什么是交叉熵函數的可以參考這個網址:https://blog.csdn.net/allenlzcoder/article/details/78591535 損失函數添加代碼如下: ~~~ #訓練模塊與評估模塊 #損失函數 onehot_labels = tf.one_hot(indice=tf.cast(labels, tf.int32), depth=10) loss = tf.losses.softmax_cross_entropy( onehot_labels = onehot_labels, logits=logits ) ~~~ 解釋:讓我們看看上面代碼都發生了什么。例如我們的labels = [1, 9, ...],為了計算交叉熵,我們首先要變成【0,1,0.。。。】的形式。所以經過tf.one_hot()后我們的labels變成了[[0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1],...]——one-hot tensor。tf.one_hot()函數不理解可以參考:https://www.w3cschool.cn/tensorflow_python/tensorflow_python-fh1b2fsm.html 接下來,我們計算onehot_labels和來自logits層的預測的softmax的交叉熵。 tf.losses.softmax_cross_entropy()將onehot_labels和logits作為參數,對logits執行softmax激活,計算交叉熵,并將我們的損失作為標量張量返回。 **配置訓練操作** 前面我們已經通過標簽和logits layer層配置了交叉熵損失函數,下面讓我們通過訓練優化這個損失函數,并優化這個model. 我們設置學習率為0.001,并使用隨機梯度下降法來優化損失函數。 代碼如下: ~~~ #配置訓練 if mode == tf.estimator.ModeKeys.TRAIN: optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001) train_op = optimizer.minimize( loss=loss, global_step=tf.train.get_global_step() ) return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op) ~~~ 解釋:固定寫法,我們必須返回一個EstimatorSpec的對象。minimize 方法還具有 global_step 參數。TensorFlow 使用此參數來計算已經處理過的訓練步數(以了解何時結束訓練)。此外,global_step 對于 TensorBoard 圖能否正常運行至關重要。只需調用 tf.train.get_global_step 并將結果傳遞給 minimize 的 global_step 參數即可 **添加評價操作** 為了添加準確的評價指標到模型中去,我們定義字典類型eval_metric_ops 代碼如下: ~~~ eval_metric_ops = { "accuracy": tf.metrics.accuracy( labels=labels, predictions=predictions["classes"])} return tf.estimator.EstimatorSpec( mode=mode, loss=loss, eval_metric_ops=eval_metric_ops) ~~~ 解釋:固定寫法不做解釋。 **訓練和評估這個模型** 我們已經寫好我們的 MNIST CNN model函數,現在已經做好訓練和評估的準備了。 1. 載入訓練與測試數據 代碼如下: ~~~ def main(unused_argv): # Load training and eval data mnist = tf.contrib.learn.datasets.load_dataset("mnist") train_data = mnist.train.images # Returns np.array train_labels = np.asarray(mnist.train.labels, dtype=np.int32) eval_data = mnist.test.images # Returns np.array eval_labels = np.asarray(mnist.test.labels, dtype=np.int32) ~~~ 解釋:我們把訓練數據和訓練標簽存成為numpy arrays.訓練數據也是這個數據結構 **創建estimator** 接著寫上面的main函數 下面我們用自己寫的model去新建一個Estimator(a TensorFlow class for performing high-level model training, evaluation, and inference) 添加下面的代碼到main函數: ~~~ #創建Estimator對象 mnist_classifier = tf.estimator.Estimator( model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model" ) ~~~ 解釋:model_fn參數指明了使用哪個模型函數來進行訓練和評估和預測。這個model_dir參數model數據(checkpoint)講被存放的位置你可以選擇自己想要保存的位置。 **設置記錄鉤** 因為卷積神經網絡需要花費一些是時間來進行訓練,所以讓我們設置一些日志來跟蹤訓練過程。我們能用Tensorflow的tf.train.SessionRunHook去構建一個tf.train.LoggingTensorHook 它可以記錄來自 softmax layer的概率值 添加下面的代碼到main函數: ~~~ #為預測設置日志 tensor_to_log = {"probabilities":"softmax_tensor"} logging_hook = tf.train.LoggingTensorHook( tensors=tensor_to_log,every_n_iter=50 ) ~~~ 解釋: 我們存設置一個我們想記錄一些tensor字典類型tensor_to_log,該字典中每一個標簽都出打印到log窗口,而標簽值是一個tensor(如:softmax_tensor,上面我們在in cnn_model_fn定義過了)。 > If you don't explicitly assign a name to an operation via the name argument, TensorFlow will assign a default name. A couple easy ways to discover the names applied to operations are to visualize your graph on TensorBoard) or to enable the TensorFlow Debugger (tfdbg). > 還是英文寫的清楚,誰讓自己語言表達能力不強呢。應該能看懂得,看不懂也沒關系。以后會解釋。 > 接下來我們創建LoggingTensorHook對象,傳遞tensors_to_log 給tensors 參數,我們設置every_n_iter=50.指明訓練每50次記錄一次。 **訓練模型** 現在我們可以去訓練我們的模型了,我們通過創建train_input_fn和調用mnist_classifier的main函數。添加下面的代碼到main函數: ~~~ #訓練該模型 train_input_fn = tf.estimator.inputs.numpy_input_fn( x={"x": train_data}, y=train_labels, batch_size=100, num_epochs=None, #設置他將訓練模型,直到達到指定的步數 shuffle=True #隨機拖拽訓練數據 ) mnist_classifier.train( input_fn=train_input_fn, steps=2000, #訓練步數 hooks=[logging_hook] #設置日志鉤子,以在訓練過程中去記錄重要的信息 ) ~~~ 解釋:注釋寫的很明白了,這里不做太多解釋。 **評估模型** 一旦訓練完成,我們就像看看該卷積神經網絡在Mnist數據集上的準確率。我們調用evaluate 方法,該方法將評估我們指定的指標(在model_fn的參數eval_metric_ops指明的)。 添加下面的代碼到main函數: ~~~ #評價該模型 #評價模型并打印結果 eval_input_fn = tf.estimator.numpy_input_fn( x={"x": eval_data}, y=eval_labels, num_epochs=1, #設置模型在一個時期的數據上評估度量并返回結果。 shuffle=False #按順序遍歷數據 ) eval_results = mnist_classifier.evaluate( input_fn=eval_input_fn ) print(eval_results) ~~~ 解釋:不做解釋,注釋寫的很明白 **運行該模型** 到了最激動人心的時刻了,是不是有點小激動。我們下面就看看卷積神經網絡的神奇魅力吧。 我們編寫了CNN model方法,并創建了Estimator,還有訓練和評估的邏輯,現在讓我們看看效果吧,直接運行cnn_mnist.py。如果你覺得時間太長,你可以修改train的step參數變小一點,這樣程序會很快執行完成。但是會影響模型的準確性。 你會看到控制臺上的代碼如下(由于我的網絡出現了問題沒有出來結果,所以目前直接用的官網上的圖): :-: ![](https://box.kancloud.cn/bcd2457ba419ed8aadc48b46af45da99_948x306.png) 這里我們實現了在測試數據上 97.3% 的準確率 * * * * * 你可能會好奇我沒有下載數據怎么就獲得了呢? 其實是調用了tensorflow中的api來獲取的數據。也就是下面這段代碼起到的作用 ~~~ tf.contrib.learn.datasets.load_dataset("mnist") ~~~ 可以參考該網址了解:https://www.tensorflow.org/api_docs/python/tf/contrib/learn/datasets?hl=zh-cn * * * * * 這里我們給出完整的代碼: cnn_mnist.py ~~~ from __future__ import absolute_import from __future__ import division from __future__ import print_function # 導入所需要的包 import numpy as np import tensorflow as tf tf.logging.set_verbosity(tf.logging.INFO) def cnn_model_fn(features, labels, mode): """Model function for CNN""" # 輸入層 input_layer = tf.reshape(features["x"], [-1, 28, 28, 1]) # 第一層卷積層 conv1 = tf.layers.conv2d( inputs=input_layer, filters=32, kernel_size=[5, 5], padding="same", activation=tf.nn.relu ) # 第一個池化層 pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2) # 第二個卷積層 conv2 = tf.layers.conv2d( inputs=pool1, filters=64, kernel_size=[5, 5], padding="same", activation=tf.nn.relu ) # 第二個池化層 tf.layers.max_pooling2d( inputs=tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2) ) # 全連接層 pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64]) dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu) # dropout正則化 dropout = tf.layer.dropout( inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN ) # 邏輯層 logits = tf.layers.dense(inputs=dropout, units=10) if mode == tf.estimator.ModeKeys.PREDICT: return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions) #訓練模塊與評估模塊 #損失函數 onehot_labels = tf.one_hot(indice=tf.cast(labels, tf.int32), depth=10) loss = tf.losses.softmax_cross_entropy( onehot_labels=onehot_labels, logits=logits ) #配置訓練 if mode == tf.estimator.ModeKeys.TRAIN: optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001) train_op = optimizer.minimize( loss=loss, global_step=tf.train.get_global_step() ) return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op) # 預測模塊 predictions = { "classes": tf.argmax(input=logits, axis=1), "probabilities": tf.nn.softmax(logits, name="softmax_tensor") } eval_metric_ops = { "accuracy": tf.metrics.accuracy( labels=labels, predictions=predictions["classes"] ) } return tf.estimator.EstimatorSpec( mode=mode, loss=loss, eval_metric_ops=eval_metric_ops ) def main(unused_argv): #加載訓練數據和評估數據 mnist = tf.contrib.learn.datasets.load_dataset("mnist") train_data = mnist.train.images #返回np.array數組 train_labels = np.asarray(mnist.train.labels, dtype=np.int32) eval_data = mnist.test.images #返回np.array數組 eval_labels = np.asarray(mnist.test.labels, dtype=np.int32) #創建Estimator對象 mnist_classifier = tf.estimator.Estimator( model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model" ) #為預測設置日志 tensor_to_log = {"probabilities":"softmax_tensor"} logging_hook = tf.train.LoggingTensorHook( tensors=tensor_to_log,every_n_iter=50 ) #訓練該模型 train_input_fn = tf.estimator.inputs.numpy_input_fn( x={"x": train_data}, y=train_labels, batch_size=100, num_epochs=None, shuffle=True ) mnist_classifier.train( input_fn=train_input_fn, steps=2000, hooks=[logging_hook] ) #評價該模型 #評價模型并打印結果 eval_input_fn = tf.estimator.numpy_input_fn( x={"x": eval_data}, y=eval_labels, num_epochs=1, #設置模型在一個時期的數據上評估度量并返回結果。 shuffle=False #按順序遍歷數據 ) eval_results = mnist_classifier.evaluate( input_fn=eval_input_fn ) print(eval_results) if __name__ == "__main__": tf.app.run(main) ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看