使用Keras和Google Cloud ML的Base64图像

use*_*174 6 keras google-cloud-ml

我正在使用Keras预测图像类.它适用于Google Cloud ML(GCML),但为了提高效率,需要将其更改为传递base64字符串而不是json数组. 相关文档

我可以轻松运行python代码将base64字符串解码为json数组,但是当使用GCML时,我没有机会运行预处理步骤(除非在Keras中使用Lambda层,但我认为不是正确的方法).

另一个答案建议添加tf.placeholder类型tf.string,这是有道理的,但如何将其纳入Keras模型?

以下是培训模型和保存GCML导出模型的完整代码...

import os
import numpy as np
import tensorflow as tf
import keras
from keras import backend as K
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.preprocessing import image
from tensorflow.python.platform import gfile

IMAGE_HEIGHT = 138
IMAGE_WIDTH = 106
NUM_CLASSES = 329

def preprocess(filename):
    # decode the image file starting from the filename
    # end up with pixel values that are in the -1, 1 range
    image_contents = tf.read_file(filename)
    image = tf.image.decode_png(image_contents, channels=1)
    image = tf.image.convert_image_dtype(image, dtype=tf.float32) # 0-1
    image = tf.expand_dims(image, 0) # resize_bilinear needs batches
    image = tf.image.resize_bilinear(image, [IMAGE_HEIGHT, IMAGE_WIDTH], align_corners=False)
    image = tf.subtract(image, 0.5)
    image = tf.multiply(image, 2.0) # -1 to 1
    image = tf.squeeze(image,[0])
    return image



filelist = gfile.ListDirectory("images")
sess = tf.Session()
with sess.as_default():
    x = np.array([np.array(     preprocess(os.path.join("images", filename)).eval()      ) for filename in filelist])

input_shape = (IMAGE_HEIGHT, IMAGE_WIDTH, 1)   # 1, because preprocessing made grayscale

# in our case the labels come from part of the filename
y = np.array([int(filename[filename.index('_')+1:-4]) for filename in filelist])
# convert class labels to numbers
y = keras.utils.to_categorical(y, NUM_CLASSES)

########## TODO: something here? ##########
image = K.placeholder(shape=(), dtype=tf.string)
decoded = tf.image.decode_jpeg(image, channels=3)
# scores = build_model(decoded)


model = Sequential()

# model.add(decoded)

model.add(Conv2D(32, kernel_size=(2, 2), activation='relu', input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
            optimizer=keras.optimizers.Adadelta(),
            metrics=['accuracy'])

model.fit(
    x,
    y,
    batch_size=64,
    epochs=20,
    verbose=1,
    validation_split=0.2,
    shuffle=False
    )

predict_signature = tf.saved_model.signature_def_utils.build_signature_def(
    inputs={'input_bytes':tf.saved_model.utils.build_tensor_info(model.input)},
    ########## TODO: something here? ##########
    # inputs={'input': image },    # input name must have "_bytes" suffix to use base64.
    outputs={'formId': tf.saved_model.utils.build_tensor_info(model.output)},
    method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
)

builder = tf.saved_model.builder.SavedModelBuilder("exported_model")

builder.add_meta_graph_and_variables(
    sess=K.get_session(),
    tags=[tf.saved_model.tag_constants.SERVING],
    signature_def_map={
        tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: predict_signature
    },
    legacy_init_op=tf.group(tf.tables_initializer(), name='legacy_init_op')
)

builder.save()
Run Code Online (Sandbox Code Playgroud)

这与我之前的问题有关.

更新:

问题的核心是如何将调用解码的占位符合并到Keras模型中.换句话说,在创建将base64字符串解码为张量的占位符之后,如何将其合并到Keras运行的内容中?我认为它需要是一个层.

image = K.placeholder(shape=(), dtype=tf.string)
decoded = tf.image.decode_jpeg(image, channels=3)
model = Sequential()

# Something like this, but this fails because it is a tensor, not a Keras layer.  Possibly this is where a Lambda layer comes in?
model.add(decoded)
model.add(Conv2D(32, kernel_size=(2, 2), activation='relu', input_shape=input_shape))
...
Run Code Online (Sandbox Code Playgroud)

更新2:

试图使用lambda层来实现这一目标......

import keras
from keras.models import Sequential
from keras.layers import Lambda
from keras import backend as K
import tensorflow as tf

image = K.placeholder(shape=(), dtype=tf.string)
model = Sequential()
model.add(Lambda(lambda image: tf.image.decode_jpeg(image, channels=3), input_shape=() ))
Run Code Online (Sandbox Code Playgroud)

给出错误: TypeError: Input 'contents' of 'DecodeJpeg' Op has type float32 that does not match expected type of string.

Fed*_*rov 6

首先我使用 tf.keras 但这应该不是一个大问题。因此,这是一个如何读取 base64 解码 jpeg 的示例:

def preprocess_and_decode(img_str, new_shape=[299,299]):
    img = tf.io.decode_base64(img_str)
    img = tf.image.decode_jpeg(img, channels=3)
    img = tf.image.resize_images(img, new_shape, method=tf.image.ResizeMethod.BILINEAR, align_corners=False)
    # if you need to squeeze your input range to [0,1] or [-1,1] do it here
    return img
InputLayer = Input(shape = (1,),dtype="string")
OutputLayer = Lambda(lambda img : tf.map_fn(lambda im : preprocess_and_decode(im[0]), img, dtype="float32"))(InputLayer)
base64_model = tf.keras.Model(InputLayer,OutputLayer)   
Run Code Online (Sandbox Code Playgroud)

上面的代码创建了一个模型,该模型采用任意大小的 jpeg,将其大小调整为 299x299 并返回为 299x299x3 张量。此模型可以直接导出到 saved_model 并用于 Cloud ML Engine 服务。这有点愚蠢,因为它唯一做的就是将 base64 转换为张量。

如果您需要将此模型的输出重定向到现有训练和编译模型(例如 inception_v3)的输入,您必须执行以下操作:

base64_input = base64_model.input
final_output = inception_v3(base64_model.output)
new_model = tf.keras.Model(base64_input,final_output)
Run Code Online (Sandbox Code Playgroud)

这个 new_model 可以保存。它采用 base64 jpeg 并返回由 inception_v3 部分标识的类。


Dar*_*nus 1

另一个答案建议添加tf.placeholdertype of tf.string,这是有道理的,但如何将其合并到 Keras 模型中?

在 Keras 中,您可以通过执行以下操作来访问所选后端(在本例中为 Tensorflow):

from keras import backend as K
Run Code Online (Sandbox Code Playgroud)

您似乎已经将其导入到您的代码中。这将使您能够访问您选择的后端上可用的一些本机方法和资源。Keras 后端包含一个用于创建占位符的方法以及其他实用程序。关于占位符,我们可以看到 Keras文档对它们的说明:

占位符

keras.backend.placeholder(形状=无,ndim=无,dtype=无,稀疏=假,名称=无)

实例化占位符张量并返回它。

它还给出了一些关于其使用的示例:

>>> from keras import backend as K
>>> input_ph = K.placeholder(shape=(2, 4, 5))
>>> input_ph._keras_shape
(2, 4, 5)
>>> input_ph
<tf.Tensor 'Placeholder_4:0' shape=(2, 4, 5) dtype=float32>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这将返回一个 Tensorflow 张量,其形状为 (2,4,5),dtype 为 float。如果您在执行示例时有另一个后端,您将获得另一个张量对象(肯定是 Theano 对象)。因此,您可以使用它placeholder()来调整您在上一个问题中获得的解决方案

总之,您可以使用导入为K(或任何您想要的)的后端,通过执行所需的方法来调用您选择的后端上可用的方法和对象K.foo.bar()。我建议您阅读Keras 后端,以探索更多对您未来情况有用的内容。

更新:根据您的编辑。是的,这个占位符应该是模型中的一个层。具体来说,它应该是模型的输入层,因为它保存要分类的解码图像(因为 Keras 需要这种方式)。