# 三、圖像檢索
深度學習也可以稱為**表示學習**,因為模型的特征或表示是在訓練期間學習的。 在隱藏層的訓練過程中生成的**視覺特征**可用于計算距離度量。 這些模型學習如何根據分類任務在各個層上檢測邊緣,圖案等。 在本章中,我們將研究以下內容:
* 如何從經過分類訓練的模型中提取特征
* 如何使用 TensorFlow Serving 在生產系統中進行更快的推斷
* 如何使用這些特征計算查詢圖像和目標集之間的相似度
* 將分類模型用于排名
* 如何提高檢索系統的速度
* 從整體上看系統的架構
* 當目標圖像過多時,使用自編碼器學習緊湊的描述
* 訓練去噪自編碼器
# 了解視覺特征
深度學習模型經常因無法解釋而受到批評。 基于神經網絡的模型通常被認為像黑匣子,因為人類很難推理出深度學習模型的工作原理。 由于激活函數,深度學習模型對圖像進行的層轉換是非線性的,因此不容易可視化。 已經開發出了通過可視化深層網絡的層來解決對不可解釋性的批評的方法。 在本節中,我們將研究可視化深層的嘗試,以便了解模型的工作原理。
可視化可以使用模型的激活和梯度來完成。 可以使用以下技術可視化激活:
* **最近鄰**:可以對圖像進行層激活,并且可以一起看到該激活的最近圖像。
* **降維**:激活的尺寸可以通過**主成分分析**(**PCA**)或 **T 分布隨機鄰居嵌入**(**t-SNE**),可在二維或三維中可視化。 PCA 通過將值投影到最大方差方向來減小尺寸。 t-SNE 通過將最接近的點映射到三個維度來減小維度。 降維的使用及其技術超出了本書的范圍。 建議您參考基本的機器學習材料,以了解有關降維的更多信息。
維基百科是了解降維技術的良好來源。 您可以參考以下鏈接:
* <https://en.wikipedia.org/wiki/Dimensionality_reduction>
* <https://en.wikipedia.org/wiki/Principal_component_analysis>
* <https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding>
* <https://en.wikipedia.org/wiki/Locality-sensitive_hashing>
* **最大補丁**:激活一個神經元,并捕獲最大激活的相應補丁。
* **遮擋**:在各個位置遮擋(遮擋)圖像,并且激活以熱圖顯示,以了解圖像的哪些部分很重要。
在以下各節中,我們將看到如何實現這些特征的可視化。
# 可視化深度學習模型的激活
任何層的過濾器都可以可視化任何模型架構。 使用該技術只能理解初始層。 最后一層對于最近鄰方法很有用。 當`ImageNet`數據集與最近鄰排列在一起時,其外觀如下:

查看此圖像,您可以看到相同的對象一起出現。 有趣的事情之一是,諸如狗,猴子和獵豹之類的動物雖然沒有經過一個標簽的訓練卻同時出現。 當對象相似時,圖像的最近鄰可視化非常有用,因此,我們可以了解模型的預測。 最后一層也可以通過降維技術(例如主成分分析和 t-SNE)進行可視化。 在下一節中,我們將看到使用降維的可視化實現。
# 嵌入可視化
可以使用 TensorBoard 以二維或三維可視化嵌入層(即預最終層)。 假定本節中的代碼段位于圖像分類一章中訓練的卷積神經網絡模型之后。 首先,我們需要一個元數據文件,它是一個制表符分隔的文件。 元數據文件的每一行都應具有將要可視化的圖像標簽。 需要一個新變量來存儲在會話創建和初始化之間定義的嵌入,如以下代碼所示:
```py
no_embedding_data = 1000 embedding_variable = tf.Variable(tf.stack(
mnist.test.images[:no_embedding_data], axis=0), trainable=False)
```
我們將獲取 MNIST 測試數據,并創建用于可視化的元數據文件,如下所示:
```py
metadata_path = '/tmp/train/metadata.tsv' with open(metadata_path, 'w') as metadata_file:
for i in range(no_embedding_data):
metadata_file.write('{}\n'.format(
np.nonzero(mnist.test.labels[::1])[1:][0][i]))
```
如上代碼所示,應通過設置參數使嵌入變量不可訓練。 接下來,必須定義投影儀配置。 它必須具有`tensor_name`,它是嵌入變量名稱,元數據文件的路徑和子畫面圖像。 子畫面圖像是一個帶有小圖像的圖像,表示要通過嵌入可視化的標簽。 以下是用于定義嵌入投影的代碼:
```py
from tensorflow.contrib.tensorboard.plugins import projector
projector_config = projector.ProjectorConfig()
embedding_projection = projector_config.embeddings.add()
embedding_projection.tensor_name = embedding_variable.name
embedding_projection.metadata_path = metadata_path
embedding_projection.sprite.image_path = os.path.join(work_dir + '/mnist_10k_sprite.png')
embedding_projection.sprite.single_image_dim.extend([28, 28])
```
必須指定子畫面圖像尺寸。 然后,可以使用投影機通過摘要編寫器和配置來可視化嵌入,如以下代碼所示:
```py
projector.visualize_embeddings(train_summary_writer, projector_config)
tf.train.Saver().save(session, '/tmp/train/model.ckpt', global_step=1)
```
然后,將模型與會話一起保存。 然后轉到 TensorBoard 查看以下可視化效果:

TensorBoard 說明了代碼的輸出
您必須通過按鈕選擇 T-SNE 和顏色,如屏幕截圖所示,以獲得類似的可視化效果。 您可以看到數字如何一起出現。 該可視化對于檢查數據和經過訓練的嵌入非常有用。 這是 TensorBoard 的另一個強大功能。 在下一部分中,我們將實現可視化的引導反向傳播。
# 引導反向傳播
直接將特征可視化可能會減少信息量。 因此,我們使用反向傳播的訓練過程來激活濾鏡以實現更好的可視化。 由于我們選擇了要激活的神經元以進行反向傳播,因此稱為引導反向傳播。 在本節中,我們將實現引導式反向傳播以可視化特征。
我們將定義大小并加載 VGG 模型,如下所示:
```py
image_width, image_height = 128, 128 vgg_model = tf.keras.applications.vgg16.VGG16(include_top=False)
```
層由以層名稱作為鍵的字典組成,模型中的層以權重作為鍵值,以方便訪問。 現在,我們將從第五個塊 `block5_conv1` 中獲取第一卷積層,以計算可視化效果。 輸入和輸出在此處定義:
```py
input_image = vgg_model.input
vgg_layer_dict = dict([(vgg_layer.name, vgg_layer) for vgg_layer in vgg_model.layers[1:]])
vgg_layer_output = vgg_layer_dict['block5_conv1'].output
```
我們必須定義損失函數。 損失函數將最大化特定層的激活。 這是一個梯度上升過程,而不是通常的梯度下降過程,因為我們正在嘗試使損失函數最大化。 對于梯度上升,平滑梯度很重要。 因此,在這種情況下,我們通過歸一化像素梯度來平滑梯度。 該損失函數快速收斂而不是。
應該對圖像的輸出進行歸一化以可視化,在優化過程中使用 g 輻射上升來獲得函數的最大值。 現在,我們可以通過定義評估器和梯度來開始梯度上升優化,如下所示。 現在,必須定義損失函數,并要計算的梯度。 迭代器通過迭代計算損耗和梯度值,如下所示:
```py
filters = []
for filter_idx in range(20):
loss = tf.keras.backend.mean(vgg_layer_output[:, :, :, filter_idx])
gradients = tf.keras.backend.gradients(loss, input_image)[0]
gradient_mean_square = tf.keras.backend.mean(tf.keras.backend.square(gradients))
gradients /= (tf.keras.backend.sqrt(gradient_mean_square) + 1e-5)
evaluator = tf.keras.backend.function([input_image], [loss, gradients])
```
輸入是隨機的灰度圖像,并添加了一些噪聲。 如此處所示,將生成隨機圖像并完成縮放。
```py
gradient_ascent_step = 1.
input_image_data = np.random.random((1, image_width, image_height, 3))
input_image_data = (input_image_data - 0.5) * 20 + 128
```
現在開始對損失函數進行優化,對于某些過濾器,損失值可能為 0,應將其忽略,如下所示:
```py
for i in range(20):
loss_value, gradient_values = evaluator([input_image_data])
input_image_data += gradient_values * gradient_ascent_step
# print('Loss :', loss_value)
if loss_value <= 0.:
break
```
優化之后,通過均值減去并調整標準差來完成歸一化。 然后,可以按比例縮小濾鏡并將其裁剪到其梯度值,如下所示:
```py
if loss_value > 0:
filter = input_image_data[0]
filter -= filter.mean()
filter /= (filter.std() + 1e-5)
filter *= 0.1
filter += 0.5
filter = np.clip(filter, 0, 1)
filter *= 255
filter = np.clip(filter, 0, 255).astype('uint8')
filters.append((filter, loss_value))
```
這些過濾器是隨機選擇的,并在此處可視化:

如圖所示,用于縫合圖像并產生輸出的代碼與代碼束一起提供。 由于修道院的接受區域變大,因此可視化在以后的層變得復雜。 一些濾鏡看起來很相似,但只是旋轉而已。 在這種情況下,可視化的層次結構可以清楚地看到,[如 Zeiler 等人所示](https://arxiv.org/pdf/1412.6572.pdf)。 下圖顯示了不同層的直接可視化:

經 Zeiler 等人許可復制。
前兩層看起來像邊緣和角落檢測器。 類似于 Gabor 的濾鏡僅出現在第三層中。 Gabor 過濾器是線性的,傳統上用于紋理分析。 我們已經直接通過引導反向傳播看到了特征的可視化。 接下來,我們將看到如何實現 DeepDream 進行可視化。
# DeepDream
可以在網絡中的某些層上放大神經元激活,而不是合成圖像。 放大原始圖像以查看特征效果的概念稱為 **DeepDream**。 創建 DeepDream 的步驟是:
1. 拍攝圖像并從 CNN 中選擇一個層。
2. 在特定的層進行激活。
3. 修改梯度,以使梯度和激活相等。
4. 計算圖像和反向傳播的梯度。
5. 必須將正則化用于圖像的抖動和歸一化。
6. 像素值應修剪。
7. 為了實現分形效果,對圖像進行了多尺度處理。
讓我們從導入相關的包開始:
```py
import os
import numpy as np
import PIL.Image
import urllib.request
from tensorflow.python.platform import gfile
import zipfile
```
初始模型在`Imagenet`數據集和 Google 提供的模型文件上進行了預訓練。 我們可以下載該模型并將其用于本示例。 模型文件的 ZIP 歸檔文件已下載并解壓縮到一個文件夾中,如下所示:
```py
model_url = 'https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip' file_name = model_url.split('/')[-1]
file_path = os.path.join(work_dir, file_name)
if not os.path.exists(file_path):
file_path, _ = urllib.request.urlretrieve(model_url, file_path)
zip_handle = zipfile.ZipFile(file_path, 'r')
zip_handle.extractall(work_dir)
zip_handle.close()
```
這些命令應該在工作目錄中創建了三個新文件。 可以將此預訓練的模型加載到會話中,如下所示:
```py
graph = tf.Graph()
session = tf.InteractiveSession(graph=graph)
model_path = os.path.join(work_dir, 'tensorflow_inception_graph.pb')
with gfile.FastGFile(model_path, 'rb') as f:
graph_defnition = tf.GraphDef()
graph_defnition.ParseFromString(f.read())
```
會話從圖初始化開始。 然后,將下載的模型的圖定義加載到內存中。 作為預處理步驟,必須從輸入中減去`ImageNet`平均值,如下所示。 預處理后的圖像隨后被饋送到該圖,如下所示:
```py
input_placeholder = tf.placeholder(np.float32, name='input')
imagenet_mean_value = 117.0 preprocessed_input = tf.expand_dims(input_placeholder-imagenet_mean_value, 0)
tf.import_graph_def(graph_defnition, {'input': preprocessed_input})
```
現在,會話和圖已準備好進行推斷。 雙線性插值需要`resize_image`函數。 可以添加`resize`函數方法,該函數通過 TensorFlow 會話來調整圖像的大小,如下所示:
```py
def resize_image(image, size):
resize_placeholder = tf.placeholder(tf.float32)
resize_placeholder_expanded = tf.expand_dims(resize_placeholder, 0)
resized_image = tf.image.resize_bilinear(resize_placeholder_expanded, size)[0, :, :, :]
return session.run(resized_image, feed_dict={resize_placeholder: image})
```
可以將工作目錄中的圖像加載到內存中并轉換為浮點值,如下所示:
```py
image_name = 'mountain.jpg' image = PIL.Image.open(image_name)
image = np.float32(image)
```
此處顯示了已加載的圖像,供您參考:

音階空間的八度音階數,大小和音階在此處定義:
```py
no_octave = 4 scale = 1.4 window_size = 51
```
這些值在此處顯示的示例中效果很好,因此需要根據其大小調整其他圖像。 可以選擇一個層來做夢,該層的平均平均值將是`objective`函數,如下所示:
```py
score = tf.reduce_mean(objective_fn)
gradients = tf.gradients(score, input_placeholder)[0]
```
計算圖像的梯度以進行優化。 可以通過將圖像調整為各種比例并找到差異來計算八度圖像,如下所示:
```py
octave_images = []
for i in range(no_octave - 1):
image_height_width = image.shape[:2]
scaled_image = resize_image(image, np.int32(np.float32(image_height_width) / scale))
image_difference = image - resize_image(scaled_image, image_height_width)
image = scaled_image
octave_images.append(image_difference)
```
現在可以使用所有八度圖像運行優化。 窗口在圖像上滑動,計算梯度激活以創建夢,如下所示:
```py
for octave_idx in range(no_octave):
if octave_idx > 0:
image_difference = octave_images[-octave_idx]
image = resize_image(image, image_difference.shape[:2]) + image_difference
for i in range(10):
image_heigth, image_width = image.shape[:2]
sx, sy = np.random.randint(window_size, size=2)
shifted_image = np.roll(np.roll(image, sx, 1), sy, 0)
gradient_values = np.zeros_like(image)
for y in range(0, max(image_heigth - window_size // 2, window_size), window_size):
for x in range(0, max(image_width - window_size // 2, window_size), window_size):
sub = shifted_image[y:y + window_size, x:x + window_size]
gradient_windows = session.run(gradients, {input_placeholder: sub})
gradient_values[y:y + window_size, x:x + window_size] = gradient_windows
gradient_windows = np.roll(np.roll(gradient_values, -sx, 1), -sy, 0)
image += gradient_windows * (1.5 / (np.abs(gradient_windows).mean() + 1e-7))
```
現在,創建 DeepDream 的優化已完成,可以通過剪切值來保存,如下所示:
```py
image /= 255.0 image = np.uint8(np.clip(image, 0, 1) * 255)
PIL.Image.fromarray(image).save('dream_' + image_name, 'jpeg')
```
在本節中,我們已經看到了創建 DeepDream 的過程。 結果顯示在這里:

如我們所見,狗到處都被激活。 您可以嘗試其他各種層并查看結果。 這些結果可用于藝術目的。 類似地,可以激活其他層以產生不同的偽像。 在下一節中,我們將看到一些對抗性示例,這些示例可能會欺騙深度學習模型。
# 對抗性示例
在幾個數據集上,圖像分類算法已達到人類水平的準確率。 但是它們可以被對抗性例子輕易地欺騙。 對抗示例是合成圖像,它們使模型無法產生所需的結果。 拍攝任何圖像,然后選擇不正確的隨機目標類別。 可以用噪聲修改該圖像,[直到網絡被 Goodfellow 等人所欺騙](https://arxiv.org/pdf/1412.6572.pdf)。 該模型的對抗攻擊示例如下所示:

經 Goodfellow 等人許可復制。
在此圖中,左側顯示的圖像具有特定標簽的 58% 可信度。 左邊的圖像與中間顯示的噪聲結合在一起時,在右邊形成圖像。 對于人來說,帶有噪點的圖像看起來還是一樣。 但是帶有噪點的圖像可以通過具有 97% 置信度的其他標簽來預測。 盡管圖像具有非常不同的對象,但仍將高置信度分配給特定示例。 這是深度學習模型的問題,因此,您應該了解這在哪里適用:
* 甚至可以在不訪問模型的情況下生成對抗性示例。 您可以訓練自己的模型,生成對抗性示例,但仍然可以欺騙其他模型。
* 在實踐中這種情況很少發生,但是當有人試圖欺騙系統來發送垃圾郵件或崩潰時,這將成為一個真正的問題。
* 所有機器學習模型都容易受到此問題的影響,而不僅僅是深度學習模型。
您應該考慮對抗性示例,了解在安全關鍵系統上部署深度學習模型的后果。 在下一節中,我們將看到如何利用 TensorFlow Serving 獲得更快的推斷。
# 模型推理
任何新數據都可以傳遞給模型以獲取結果。 從圖像獲取分類結果或特征的過程稱為**推理**。 訓練和推理通常在不同的計算機上和不同的時間進行。 我們將學習如何存儲模型,運行推理以及如何使用 TensorFlow Serving 作為具有良好延遲和吞吐量的服務器。
# 導出模型
訓練后的模型必須導出并保存。 權重,偏差和圖都存儲用于推斷。 我們將訓練 MNIST 模型并將其存儲。 首先使用以下代碼定義所需的常量:
```py
work_dir = '/tmp' model_version = 9 training_iteration = 1000 input_size = 784 no_classes = 10 batch_size = 100 total_batches = 200
```
`model_version`可以是一個整數,用于指定我們要導出以供服務的模型。 `feature config`存儲為具有占位符名稱及其對應數據類型的字典。 應該映射預測類及其標簽。 身份占位符可與 API 配合使用:
```py
tf_example = tf.parse_example(tf.placeholder(tf.string, name='tf_example'),
{'x': tf.FixedLenFeature(shape=[784], dtype=tf.float32), })
x_input = tf.identity(tf_example['x'], name='x')
```
可以使用以下代碼使用權重,偏差,對數和優化器定義一個簡單的分類器:
```py
y_input = tf.placeholder(tf.float32, shape=[None, no_classes])
weights = tf.Variable(tf.random_normal([input_size, no_classes]))
bias = tf.Variable(tf.random_normal([no_classes]))
logits = tf.matmul(x_input, weights) + bias
softmax_cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_input, logits=logits)
loss_operation = tf.reduce_mean(softmax_cross_entropy)
optimiser = tf.train.GradientDescentOptimizer(0.5).minimize(loss_operation)
```
訓練模型,如以下代碼所示:
```py
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
for batch_no in range(total_batches):
mnist_batch = mnist.train.next_batch(batch_size)
_, loss_value = session.run([optimiser, loss_operation], feed_dict={
x_input: mnist_batch[0],
y_input: mnist_batch[1]
})
print(loss_value)
```
定義預測簽名,并導出模型。 將模型保存到持久性存儲中,以便可以在以后的時間點進行推理。 這將通過反序列化導出數據,并將其存儲為其他系統可以理解的格式。 具有不同變量和占位符的多個圖可用于導出。 它還支持`signature_defs` 和素材。 `signature_defs`指定了輸入和輸出,因為將從外部客戶端訪問輸入和輸出。 素材是將用于推理的非圖組件,例如詞匯表等。
分類簽名使用對 TensorFlow 分類 API 的訪問權限。 輸入是強制性的,并且有兩個可選輸出(預測類別和預測概率),其中至少一個是強制性的。 預測簽名提供輸入和輸出數量的靈活性。 可以定義多個輸出并從客戶端顯式查詢。 `signature_def`顯示在此處:
```py
signature_def = (
tf.saved_model.signature_def_utils.build_signature_def(
inputs={'x': tf.saved_model.utils.build_tensor_info(x_input)},
outputs={'y': tf.saved_model.utils.build_tensor_info(y_input)},
method_name="tensorflow/serving/predict"))
```
最后,使用預測簽名將元圖和變量添加到構建器中:
```py
model_path = os.path.join(work_dir, str(model_version))
saved_model_builder = tf.saved_model.builder.SavedModelBuilder(model_path)
saved_model_builder.add_meta_graph_and_variables(
session, [tf.saved_model.tag_constants.SERVING],
signature_def_map={
'prediction': signature_def
},
legacy_init_op=tf.group(tf.tables_initializer(), name='legacy_init_op'))
saved_model_builder.save()
```
該構建器已保存,可以由服務器使用。 所示示例適用于任何模型,并可用于導出。 在下一部分中,我們將服務并查詢導出的模型。
# 服務訓練過的模型
可以使用以下命令通過 TensorFlow Serving 服務上一節中導出的模型:
```py
tensorflow_model_server --port=9000 --model_name=mnist --model_base_path=/tmp/mnist_model/
```
`model_base_path` 指向導出模型的目錄。 現在可以與客戶端一起測試服務器。 請注意,這不是 HTTP 服務器,因此需要此處顯示的客戶端而不是 HTTP 客戶端。 導入所需的庫:
```py
from grpc.beta import implementations
import numpy
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2
```
添加并發常數,測試數量和工作目錄。 定義了一個類,用于對返回的結果進行計數。 定義了**遠程過程調用**(**RPC**)回調,并帶有用于對預測計數的計數器,如下所示:
```py
concurrency = 1 num_tests = 100 host = '' port = 8000 work_dir = '/tmp' def _create_rpc_callback():
def _callback(result):
response = numpy.array(
result.result().outputs['y'].float_val)
prediction = numpy.argmax(response)
print(prediction)
return _callback
```
根據您的要求修改 `host` 和 `port` 。 `_callback`方法定義了從服務器返回響應時所需的步驟。 在這種情況下,將計算最大概率。 通過調用服務器來運行推斷:
```py
test_data_set = mnist.test
test_image = mnist.test.images[0]
predict_request = predict_pb2.PredictRequest()
predict_request.model_spec.name = 'mnist' predict_request.model_spec.signature_name = 'prediction' predict_channel = implementations.insecure_channel(host, int(port))
predict_stub = prediction_service_pb2.beta_create_PredictionService_stub(predict_channel)
predict_request.inputs['x'].CopyFrom(
tf.contrib.util.make_tensor_proto(test_image, shape=[1, test_image.size]))
result = predict_stub.Predict.future(predict_request, 3.0)
result.add_done_callback(
_create_rpc_callback())
```
反復調用推理以評估準確率,延遲和吞吐量。 推斷錯誤率應該在 90% 左右,并且并發性應該很高。 導出和客戶端方法可用于任何模型,以從模型獲得結果和特征。 在下一節中,我們將構建檢索流水線。
# 基于內容的圖像檢索
**基于內容的圖像檢索**(**CBIR**)的技術將查詢圖像作為輸入,并對目標圖像數據庫中的圖像進行排名,從而產生輸出。 CBIR 是具有特定目標的圖像到圖像搜索引擎。 要檢索需要目標圖像數據庫。 返回距查詢圖像最小距離的目標圖像。 我們可以直接將圖像用于相似性,但是問題如下:
* 圖像尺寸巨大
* 像素中有很多冗余
* 像素不攜帶語義信息
因此,我們訓練了一個用于對象分類的模型,并使用該模型中的特征進行檢索。 然后,我們通過相同的模型傳遞查詢圖像和目標數據庫以獲得特征。 這些模型也可以稱為**編碼器**,因為它們對特定任務的圖像信息進行編碼。 編碼器應該能夠捕獲全局和局部特征。 我們可以使用我們在圖像分類一章中研究過的模型,這些模型經過訓練可以進行分類任務。 由于強力掃描或線性掃描速度較慢,因此圖像搜索可能會花費大量時間。 因此,需要一些用于更快檢索的方法。 以下是一些加快匹配速度的方法:
* **局部敏感哈希**(**LSH**):LSH 將特征投影到其子空間,并可以向候選對象提供列表,并在以后進行精細特征排名。 這也是我們本章前面介紹的降維技術,例如 PCA 和 t-SNE。 它具有較小尺寸的鏟斗。
* **多索引哈希**:此方法對特征進行哈希處理,就像信鴿擬合一樣,可以使其更快。 它使用漢明距離來加快計算速度。 漢明距離不過是以二進制表示的數字的位置差異的數量。
這些方法更快,需要更少的內存,但要權衡準確率。 這些方法也沒有捕獲語義上的差異。 可以根據查詢對匹配結果進行重新排名以獲得更好的結果。 重新排序可以通過對返回的目標圖像重新排序來改善結果。 重新排序可以使用以下技術之一:
* **幾何驗證**:此方法將幾何圖形和目標圖像與僅返回相似幾何圖形的目標圖像進行匹配。
* **查詢擴展**:這將擴展目標圖像列表并詳盡搜索它們。
* **相關性反饋**:此方法從使用中獲取反饋并返回結果。 根據用戶輸入,將進行重新排名。
這些技術已針對文本進行了很好的開發,可用于圖像。 在本章中,我們將重點介紹提取特征并將其用于 CBIR。 在下一節中,我們將學習如何進行模型推斷。
# 建立檢索流水線
從查詢圖像的目標圖像中獲得最佳匹配的步驟序列稱為**檢索流水線**。 檢索流水線具有多個步驟或組件。 圖像數據庫的特征必須脫機提取并存儲在數據庫中。 對于每個查詢圖像,必須提取特征并且必須在所有目標圖像之間計算相似度。 然后,可以對圖像進行排名以最終輸出。 檢索流水線如下所示:

特征提取步驟必須快速,為此可以使用 TensorFlow Serving。 您可以根據應用選擇使用哪些特征。 例如,當需要基于紋理的匹配時可以使用初始層,而當必須在對象級別進行匹配時可以使用更高的層。 在下一部分中,我們將看到如何從預訓練的初始模型中提取特征。
# 提取圖像的瓶頸特征
瓶頸特征是在預分類層中計算的值。 在本節中,我們將看到如何使用 TensorFlow 從預訓練的模型中提取瓶頸特征。 首先,使用以下代碼導入所需的庫:
```py
import os
import urllib.request
from tensorflow.python.platform import gfile
import tarfile
```
然后,我們需要下載帶有圖定義及其權重的預訓練模型。 TensorFlow 已使用初始架構在`ImageNet`數據集上訓練了一個模型,并提供了該模型。 我們將使用以下代碼下載該模型并將其解壓縮到本地文件夾中:
```py
model_url = 'http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz' file_name = model_url.split('/')[-1]
file_path = os.path.join(work_dir, file_name)
if not os.path.exists(file_path):
file_path, _ = urllib.request.urlretrieve(model_url, file_path)
tarfile.open(file_path, 'r:gz').extractall(work_dir)
```
僅當模型不存在時,這會創建一個文件夾并下載模型。 如果重復執行代碼,則不會每次都下載模型。 該圖以**協議緩沖區**(**protobuf**)格式存儲在文件中。 必須將其讀取為字符串,然后傳遞給`tf.GraphDef()`對象以將其帶入內存:
```py
model_path = os.path.join(work_dir, 'classify_image_graph_def.pb')
with gfile.FastGFile(model_path, 'rb') as f:
graph_defnition = tf.GraphDef()
graph_defnition.ParseFromString(f.read())
```
在初始模型中,瓶頸層名為`pool_3/_reshape:0`,并且該層的尺寸為 2,048。 輸入的占位符名稱為`DecodeJpeg/contents:0`,調整大小張量名稱為`ResizeBilinear:0`。 我們可以使用`tf.import_graph_def`和所需的返回張量導入圖定義,以進行進一步的操作:
```py
bottleneck, image, resized_input = (
tf.import_graph_def(
graph_defnition,
name='',
return_elements=['pool_3/_reshape:0',
'DecodeJpeg/contents:0',
'ResizeBilinear:0'])
)
```
進行查詢和目標圖像并將其加載到內存中。 `gfile`函數提供了一種更快的方式將圖像加載到內存中。
```py
query_image_path = os.path.join(work_dir, 'cat.1000.jpg')
query_image = gfile.FastGFile(query_image_path, 'rb').read()
target_image_path = os.path.join(work_dir, 'cat.1001.jpg')
target_image = gfile.FastGFile(target_image_path, 'rb').read()
```
讓我們定義一個使用`session`和圖像從圖像中提取瓶頸特征的函數:
```py
def get_bottleneck_data(session, image_data):
bottleneck_data = session.run(bottleneck, {image: image_data})
bottleneck_data = np.squeeze(bottleneck_data)
return bottleneck_data
```
啟動會話,并傳遞圖像以運行前向推理,以從預先訓練的模型中獲取瓶頸值:
```py
query_feature = get_bottleneck_data(session, query_image)
print(query_feature)
target_feature = get_bottleneck_data(session, target_image)
print(target_feature)
```
運行上面的代碼應顯示如下:
```py
[ 0.55705792 0.36785451 1.06618118 ..., 0.6011821 0.36407694
0.0996572 ]
[ 0.30421323 0.0926369 0.26213276 ..., 0.72273785 0.30847171
0.08719242]
```
該計算特征的過程可以按比例縮放以獲取更多目標圖像。 使用這些值,可以在查詢圖像和目標數據庫之間計算相似度,如以下部分所述。
# 計算查詢圖像與目標數據庫之間的相似度
NumPy 的`linalg.norm`可用于計算**歐幾里德距離**。 可以通過計算特征之間的歐幾里得距離來計算查詢圖像與目標數據庫之間的相似度,如下所示:
```py
dist = np.linalg.norm(np.asarray(query_feature) - np.asarray(target_feature))
print(dist)
```
運行此命令應打印以下內容:
```py
16.9965
```
這是可用于相似度計算的度量。 查詢與目標圖像之間的歐幾里得距離越小,圖像越相似。 因此,計算歐幾里得距離是相似度的量度。 使用特征來計算歐幾里得距離是基于這樣的假設:在訓練模型的過程中學習了這些特征。 將這種計算擴展成數百萬個圖像效率不高。 在生產系統中,期望以毫秒為單位返回結果。 在下一節中,我們將看到如何提高檢索效率。
# 高效檢索
檢索可能很慢,因為它是蠻力方法。 使用近似最近鄰可以使匹配更快。 維度的詛咒也開始出現,如下圖所示:

隨著維數的增加,復雜度也從二維維增加到三個維。 距離的計算也變慢。 為了使距離搜索更快,我們將在下一部分中討論一種近似方法。
# 使用近似最近鄰更快地匹配
**近似最近鄰**(**ANNOY**)是一種用于更快進行最近鄰搜索的方法。 ANNOY 通過隨機投影來構建樹。 樹結構使查找最接近的匹配更加容易。 您可以創建`ANNOYIndex`以便快速檢索,如下所示:
```py
def create_annoy(target_features):
t = AnnoyIndex(layer_dimension)
for idx, target_feature in enumerate(target_features):
t.add_item(idx, target_feature)
t.build(10)
t.save(os.path.join(work_dir, 'annoy.ann'))
create_annoy(target_features)
```
創建索引需要特征的尺寸。 然后將項目添加到索引并構建樹。 樹木的數量越多,在時間和空間復雜度之間進行權衡的結果將越準確。 可以創建索引并將其加載到內存中。 可以查詢 ANNOY,如下所示:
```py
annoy_index = AnnoyIndex(10)
annoy_index.load(os.path.join(work_dir, 'annoy.ann'))
matches = annoy_index.get_nns_by_vector(query_feature, 20)
```
匹配項列表可用于檢索圖像詳細信息。 項目的索引將被返回。
請訪問[這里](https://github.com/spotify/annoy)以獲取`ANNOY`的完整實現,以及其在準確率和速度方面與其他近似最近鄰算法的基準比較。
# ANNOY 的優點
使用 ANNOY 的原因很多。 主要優點如下:
* 具有內存映射的數據結構,因此對 RAM 的占用較少。 因此,可以在多個進程之間共享同一文件。
* 可以使用曼哈頓,余弦或歐幾里得等多種距離來計算查詢圖像和目標數據庫之間的相似度。
# 原始圖像的自編碼器
自編碼器是一種用于生成有效編碼的無監督算法。 輸入層和目標輸出通常相同。 減少和增加之間的層以下列方式:

**瓶頸**層是尺寸減小的中間層。 瓶頸層的左側稱為**編碼器**,右側稱為**解碼器**。 編碼器通常減小數據的尺寸,而解碼器增大尺寸。 編碼器和解碼器的這種組合稱為自編碼器。 整個網絡都經過重建誤差訓練。 從理論上講,可以存儲瓶頸層,并可以通過解碼器網絡重建原始數據。 如下所示,這可以減小尺寸并易于編程。 使用以下代碼定義卷積,解卷積和全連接層:
```py
def fully_connected_layer(input_layer, units):
return tf.layers.dense(
input_layer,
units=units,
activation=tf.nn.relu
)
def convolution_layer(input_layer, filter_size):
return tf.layers.conv2d(
input_layer,
filters=filter_size,
kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
kernel_size=3,
strides=2
)
def deconvolution_layer(input_layer, filter_size, activation=tf.nn.relu):
return tf.layers.conv2d_transpose(
input_layer,
filters=filter_size,
kernel_initializer=tf.contrib.layers.xavier_initializer_conv2d(),
kernel_size=3,
activation=activation,
strides=2
)
```
定義具有五層卷積的會聚編碼器,如以下代碼所示:
```py
input_layer = tf.placeholder(tf.float32, [None, 128, 128, 3])
convolution_layer_1 = convolution_layer(input_layer, 1024)
convolution_layer_2 = convolution_layer(convolution_layer_1, 512)
convolution_layer_3 = convolution_layer(convolution_layer_2, 256)
convolution_layer_4 = convolution_layer(convolution_layer_3, 128)
convolution_layer_5 = convolution_layer(convolution_layer_4, 32)
```
通過展平第五個卷積層來計算瓶頸層。 再次將瓶頸層重新成形為卷積層,如下所示:
```py
convolution_layer_5_flattened = tf.layers.flatten(convolution_layer_5)
bottleneck_layer = fully_connected_layer(convolution_layer_5_flattened, 16)
c5_shape = convolution_layer_5.get_shape().as_list()
c5f_flat_shape = convolution_layer_5_flattened.get_shape().as_list()[1]
fully_connected = fully_connected_layer(bottleneck_layer, c5f_flat_shape)
fully_connected = tf.reshape(fully_connected,
[-1, c5_shape[1], c5_shape[2], c5_shape[3]])
```
計算可以重建圖像的發散或解碼器部分,如以下代碼所示:
```py
deconvolution_layer_1 = deconvolution_layer(fully_connected, 128)
deconvolution_layer_2 = deconvolution_layer(deconvolution_layer_1, 256)
deconvolution_layer_3 = deconvolution_layer(deconvolution_layer_2, 512)
deconvolution_layer_4 = deconvolution_layer(deconvolution_layer_3, 1024)
deconvolution_layer_5 = deconvolution_layer(deconvolution_layer_4, 3,
activation=tf.nn.tanh)
```
該網絡經過訓練,可以快速收斂。 傳遞圖像特征時可以存儲瓶頸層。 這有助于減少可用于檢索的數據庫的大小。 僅需要編碼器部分即可為特征建立索引。 自編碼器是一種有損壓縮算法。 它與其他壓縮算法不同,因為它從數據中學習壓縮模式。 因此,自編碼器模型特定于數據。 自編碼器可以與 t-SNE 結合使用以獲得更好的可視化效果。 自編碼器學習的瓶頸層可能對其他任務沒有用。 瓶頸層的大小可以大于以前的層。 在這種分叉和收斂連接的情況下,稀疏的自編碼器就會出現。 在下一節中,我們將學習自編碼器的另一種應用。
# 使用自編碼器進行降噪
自編碼器也可以用于圖像去噪。 去噪是從圖像中去除噪點的過程。 去噪編碼器可以無監督的方式進行訓練。 可以在正常圖像中引入噪聲,并針對原始圖像訓練自編碼器。 以后,可以使用完整的自編碼器生成無噪聲的圖像。 在本節中,我們將逐步說明如何去噪 MNIST 圖像。 導入所需的庫并定義占位符,如下所示:
```py
x_input = tf.placeholder(tf.float32, shape=[None, input_size])
y_input = tf.placeholder(tf.float32, shape=[None, input_size])
```
`x_input`和`y_input`的形狀與自編碼器中的形狀相同。 然后,定義一個密集層,如下所示,默認激活為`tanh`激活函數。 `add_variable_summary`方法是從圖像分類章節示例中導入的。 密集層的定義如下所示:
```py
def dense_layer(input_layer, units, activation=tf.nn.tanh):
layer = tf.layers.dense(
inputs=input_layer,
units=units,
activation=activation
)
add_variable_summary(layer, 'dense')
return layer
```
接下來,可以定義自編碼器層。 該自編碼器僅具有全連接層。 編碼器部分具有減小尺寸的三層。 解碼器部分具有增加尺寸的三層。 編碼器和解碼器都是對稱的,如下所示:
```py
layer_1 = dense_layer(x_input, 500)
layer_2 = dense_layer(layer_1, 250)
layer_3 = dense_layer(layer_2, 50)
layer_4 = dense_layer(layer_3, 250)
layer_5 = dense_layer(layer_4, 500)
layer_6 = dense_layer(layer_5, 784)
```
隱藏層的尺寸是任意選擇的。 接下來,定義`loss`和`optimiser`。 這里我們使用 Sigmoid 代替 softmax 作為分類,如下所示:
```py
with tf.name_scope('loss'):
softmax_cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(
labels=y_input, logits=layer_6)
loss_operation = tf.reduce_mean(softmax_cross_entropy, name='loss')
tf.summary.scalar('loss', loss_operation)
with tf.name_scope('optimiser'):
optimiser = tf.train.AdamOptimizer().minimize(loss_operation)
```
TensorBoard 提供了另一種稱為`image,`的摘要,可用于可視化圖像。 我們將使用輸入`layer_6`并將其重塑形狀以將其添加到摘要中,如下所示:
```py
x_input_reshaped = tf.reshape(x_input, [-1, 28, 28, 1])
tf.summary.image("noisy_images", x_input_reshaped)
y_input_reshaped = tf.reshape(y_input, [-1, 28, 28, 1])
tf.summary.image("original_images", y_input_reshaped)
layer_6_reshaped = tf.reshape(layer_6, [-1, 28, 28, 1])
tf.summary.image("reconstructed_images", layer_6_reshaped)
```
圖像數量默認限制為三張,并且可以更改。 這是為了限制其將所有圖像都寫入摘要文件夾。 接下來,合并所有摘要,并將圖添加到摘要編寫器,如下所示:
```py
merged_summary_operation = tf.summary.merge_all()
train_summary_writer = tf.summary.FileWriter('/tmp/train', session.graph)
```
可以將正常的隨機噪聲添加到圖像中并作為輸入張量饋入。 添加噪聲后,多余的值將被裁剪。 目標將是原始圖像本身。 此處顯示了噪聲和訓練過程的附加信息:
```py
for batch_no in range(total_batches):
mnist_batch = mnist_data.train.next_batch(batch_size)
train_images, _ = mnist_batch[0], mnist_batch[1]
train_images_noise = train_images + 0.2 * np.random.normal(size=train_images.shape)
train_images_noise = np.clip(train_images_noise, 0., 1.)
_, merged_summary = session.run([optimiser, merged_summary_operation],
feed_dict={
x_input: train_images_noise,
y_input: train_images,
})
train_summary_writer.add_summary(merged_summary, batch_no)
```
開始此訓練后,可以在 TensorBoard 中查看結果。 損失顯示在此處:

Tensorboard 說明了輸出圖
損耗穩步下降,并將在迭代過程中保持緩慢下降。 這顯示了自編碼器如何快速收斂。 接下來,原始圖像顯示三位數:

以下是添加了噪點的相同圖像:

您會注意到有很大的噪音,這是作為輸入給出的。 接下來,是使用去噪自編碼器重建的相同編號的圖像:

您會注意到,去噪自編碼器在消除噪聲方面做得非常出色。 您可以在測試圖像上運行它,并可以看到質量得到保持。 對于更復雜的數據集,可以使用卷積神經網絡以獲得更好的結果。 該示例展示了計算機視覺深度學習的強大功能,因為它是在無監督的情況下進行訓練的。
# 總結
在本章中,您學習了如何從圖像中提取特征并將其用于 CBIR。 您還學習了如何使用 TensorFlow Serving 來推斷圖像特征。 我們看到了如何利用近似最近鄰或更快的匹配而不是線性掃描。 您了解了散列如何仍可以改善結果。 引入了自編碼器的概念,我們看到了如何訓練較小的特征向量以進行搜索。 還顯示了使用自編碼器進行圖像降噪的示例。 我們看到了使用基于位的比較的可能性,該比較可以將其擴展到數十億張圖像。
在下一章中,我們將看到如何訓練對象檢測問題的模型。 我們將利用開源模型來獲得良好的準確率,并了解其背后的所有算法。 最后,我們將使用所有想法來訓練行人檢測模型。
- TensorFlow 1.x 深度學習秘籍
- 零、前言
- 一、TensorFlow 簡介
- 二、回歸
- 三、神經網絡:感知器
- 四、卷積神經網絡
- 五、高級卷積神經網絡
- 六、循環神經網絡
- 七、無監督學習
- 八、自編碼器
- 九、強化學習
- 十、移動計算
- 十一、生成模型和 CapsNet
- 十二、分布式 TensorFlow 和云深度學習
- 十三、AutoML 和學習如何學習(元學習)
- 十四、TensorFlow 處理單元
- 使用 TensorFlow 構建機器學習項目中文版
- 一、探索和轉換數據
- 二、聚類
- 三、線性回歸
- 四、邏輯回歸
- 五、簡單的前饋神經網絡
- 六、卷積神經網絡
- 七、循環神經網絡和 LSTM
- 八、深度神經網絡
- 九、大規模運行模型 -- GPU 和服務
- 十、庫安裝和其他提示
- TensorFlow 深度學習中文第二版
- 一、人工神經網絡
- 二、TensorFlow v1.6 的新功能是什么?
- 三、實現前饋神經網絡
- 四、CNN 實戰
- 五、使用 TensorFlow 實現自編碼器
- 六、RNN 和梯度消失或爆炸問題
- 七、TensorFlow GPU 配置
- 八、TFLearn
- 九、使用協同過濾的電影推薦
- 十、OpenAI Gym
- TensorFlow 深度學習實戰指南中文版
- 一、入門
- 二、深度神經網絡
- 三、卷積神經網絡
- 四、循環神經網絡介紹
- 五、總結
- 精通 TensorFlow 1.x
- 一、TensorFlow 101
- 二、TensorFlow 的高級庫
- 三、Keras 101
- 四、TensorFlow 中的經典機器學習
- 五、TensorFlow 和 Keras 中的神經網絡和 MLP
- 六、TensorFlow 和 Keras 中的 RNN
- 七、TensorFlow 和 Keras 中的用于時間序列數據的 RNN
- 八、TensorFlow 和 Keras 中的用于文本數據的 RNN
- 九、TensorFlow 和 Keras 中的 CNN
- 十、TensorFlow 和 Keras 中的自編碼器
- 十一、TF 服務:生產中的 TensorFlow 模型
- 十二、遷移學習和預訓練模型
- 十三、深度強化學習
- 十四、生成對抗網絡
- 十五、TensorFlow 集群的分布式模型
- 十六、移動和嵌入式平臺上的 TensorFlow 模型
- 十七、R 中的 TensorFlow 和 Keras
- 十八、調試 TensorFlow 模型
- 十九、張量處理單元
- TensorFlow 機器學習秘籍中文第二版
- 一、TensorFlow 入門
- 二、TensorFlow 的方式
- 三、線性回歸
- 四、支持向量機
- 五、最近鄰方法
- 六、神經網絡
- 七、自然語言處理
- 八、卷積神經網絡
- 九、循環神經網絡
- 十、將 TensorFlow 投入生產
- 十一、更多 TensorFlow
- 與 TensorFlow 的初次接觸
- 前言
- 1.?TensorFlow 基礎知識
- 2. TensorFlow 中的線性回歸
- 3. TensorFlow 中的聚類
- 4. TensorFlow 中的單層神經網絡
- 5. TensorFlow 中的多層神經網絡
- 6. 并行
- 后記
- TensorFlow 學習指南
- 一、基礎
- 二、線性模型
- 三、學習
- 四、分布式
- TensorFlow Rager 教程
- 一、如何使用 TensorFlow Eager 構建簡單的神經網絡
- 二、在 Eager 模式中使用指標
- 三、如何保存和恢復訓練模型
- 四、文本序列到 TFRecords
- 五、如何將原始圖片數據轉換為 TFRecords
- 六、如何使用 TensorFlow Eager 從 TFRecords 批量讀取數據
- 七、使用 TensorFlow Eager 構建用于情感識別的卷積神經網絡(CNN)
- 八、用于 TensorFlow Eager 序列分類的動態循壞神經網絡
- 九、用于 TensorFlow Eager 時間序列回歸的遞歸神經網絡
- TensorFlow 高效編程
- 圖嵌入綜述:問題,技術與應用
- 一、引言
- 三、圖嵌入的問題設定
- 四、圖嵌入技術
- 基于邊重構的優化問題
- 應用
- 基于深度學習的推薦系統:綜述和新視角
- 引言
- 基于深度學習的推薦:最先進的技術
- 基于卷積神經網絡的推薦
- 關于卷積神經網絡我們理解了什么
- 第1章概論
- 第2章多層網絡
- 2.1.4生成對抗網絡
- 2.2.1最近ConvNets演變中的關鍵架構
- 2.2.2走向ConvNet不變性
- 2.3時空卷積網絡
- 第3章了解ConvNets構建塊
- 3.2整改
- 3.3規范化
- 3.4匯集
- 第四章現狀
- 4.2打開問題
- 參考
- 機器學習超級復習筆記
- Python 遷移學習實用指南
- 零、前言
- 一、機器學習基礎
- 二、深度學習基礎
- 三、了解深度學習架構
- 四、遷移學習基礎
- 五、釋放遷移學習的力量
- 六、圖像識別與分類
- 七、文本文件分類
- 八、音頻事件識別與分類
- 九、DeepDream
- 十、自動圖像字幕生成器
- 十一、圖像著色
- 面向計算機視覺的深度學習
- 零、前言
- 一、入門
- 二、圖像分類
- 三、圖像檢索
- 四、對象檢測
- 五、語義分割
- 六、相似性學習
- 七、圖像字幕
- 八、生成模型
- 九、視頻分類
- 十、部署
- 深度學習快速參考
- 零、前言
- 一、深度學習的基礎
- 二、使用深度學習解決回歸問題
- 三、使用 TensorBoard 監控網絡訓練
- 四、使用深度學習解決二分類問題
- 五、使用 Keras 解決多分類問題
- 六、超參數優化
- 七、從頭開始訓練 CNN
- 八、將預訓練的 CNN 用于遷移學習
- 九、從頭開始訓練 RNN
- 十、使用詞嵌入從頭開始訓練 LSTM
- 十一、訓練 Seq2Seq 模型
- 十二、深度強化學習
- 十三、生成對抗網絡
- TensorFlow 2.0 快速入門指南
- 零、前言
- 第 1 部分:TensorFlow 2.00 Alpha 簡介
- 一、TensorFlow 2 簡介
- 二、Keras:TensorFlow 2 的高級 API
- 三、TensorFlow 2 和 ANN 技術
- 第 2 部分:TensorFlow 2.00 Alpha 中的監督和無監督學習
- 四、TensorFlow 2 和監督機器學習
- 五、TensorFlow 2 和無監督學習
- 第 3 部分:TensorFlow 2.00 Alpha 的神經網絡應用
- 六、使用 TensorFlow 2 識別圖像
- 七、TensorFlow 2 和神經風格遷移
- 八、TensorFlow 2 和循環神經網絡
- 九、TensorFlow 估計器和 TensorFlow HUB
- 十、從 tf1.12 轉換為 tf2
- TensorFlow 入門
- 零、前言
- 一、TensorFlow 基本概念
- 二、TensorFlow 數學運算
- 三、機器學習入門
- 四、神經網絡簡介
- 五、深度學習
- 六、TensorFlow GPU 編程和服務
- TensorFlow 卷積神經網絡實用指南
- 零、前言
- 一、TensorFlow 的設置和介紹
- 二、深度學習和卷積神經網絡
- 三、TensorFlow 中的圖像分類
- 四、目標檢測與分割
- 五、VGG,Inception,ResNet 和 MobileNets
- 六、自編碼器,變分自編碼器和生成對抗網絡
- 七、遷移學習
- 八、機器學習最佳實踐和故障排除
- 九、大規模訓練
- 十、參考文獻