【编者按】已安装、配置好Tensorflow Seving,调试客户端代码,全程在ChatGPT支持下完成。
最后一个问题是关于gRPC和REST API区别,可以作为知识点学习。
1问:已编写Tensorflow serving客户端代码,要求读入图片文件请求服务器识别图片,返回结果,修改一下代码。(附源代码,此处不贴出,另:隐藏了真实IP)
ChatGPT
你的客户端代码中出现了 DNS 解析错误,这是因为 grpc.insecure_channel
方法只接受主机名或 IP 地址和端口号的形式,而不接受带有 http://
前缀的 URL。
请修改以下部分:
原代码
channel = grpc.insecure_channel('http://IP:8501/')
修改后
channel = grpc.insecure_channel('IP:8501')
去掉 http://
前缀后,再运行代码试试。
完整的修改后代码
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
from google.protobuf import json_format
from PIL import Image
import numpy as np
# 定义读取和预处理图片的函数
def load_image(image_path):
img = Image.open(image_path).resize((224, 224))
img = np.array(img).astype(np.float32)
img = np.expand_dims(img, axis=0) # 扩展维度以符合模型输入
return img
# 创建 gRPC 频道和 stub
channel = grpc.insecure_channel('IP:8501')
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 创建预测请求
request = predict_pb2.PredictRequest()
request.model_spec.name = 'ai_flower'
request.model_spec.signature_name = 'serving_default'
# 读取并预处理图片
image_path = 'test_images/rose.jpg'
input_image = load_image(image_path)
# 将预处理后的图片设置为请求的输入张量
request.inputs['input_tensor_name'].CopyFrom(
tf.make_tensor_proto(input_image, shape=input_image.shape))
# 发送请求并获取响应
response = stub.Predict(request)
print(response)
运行此代码,应该会解决 DNS 解析错误的问题。如果还有其他问题,随时告诉我!
2问:有错:Traceback (most recent call last):
File “E:\WorkspacePython\TensorFlow_Serving\gRPC_client.py”, line 33, in
response = stub.Predict(request)
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\grpc_channel.py”, line 946, in call
return _end_unary_response_blocking(state, call, False, None)
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\grpc_channel.py”, line 849, in _end_unary_response_blocking
raise _InactiveRpcError(state)
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.UNAVAILABLE
details = “failed to connect to all addresses; last error: INTERNAL: ipv4:XXXX:8501: Trying to connect an http1.x server”
debug_error_string = “UNKNOWN:failed to connect to all addresses; last error: INTERNAL: ipv4:XXXX:8501: Trying to connect an http1.x server {grpc_status:14, created_time:“2024-07-31T15:29:54.366014749+00:00”}”
ChatGPT
看起来你正在使用的 TensorFlow Serving 服务器可能配置为使用 REST API 而不是 gRPC 服务。要使用 gRPC,你需要确保 TensorFlow Serving 服务器正确配置并运行在 gRPC 端口。
你可以尝试以下步骤来解决这个问题:
-
确保 TensorFlow Serving 使用 gRPC 端口运行:
启动 TensorFlow Serving 时,确保正确配置 gRPC 端口。例如:tensorflow_model_server --port=8500 --rest_api_port=8501 --model_name=ai_flower --model_base_path=/xxxx/TensorFlow_Serving/saved_model/
在这个示例中,
--port=8500
是 gRPC 端口,--rest_api_port=8501
是 REST API 端口。 -
修改客户端代码以使用正确的 gRPC 端口:
确保客户端代码中的端口号与服务器上的 gRPC 端口号一致。例如:channel = grpc.insecure_channel('XXXX:8500')
-
检查防火墙设置:
确保服务器的 gRPC 端口(如 8500)在防火墙中是开放的,并且可以从客户端计算机访问。 -
确保模型输入名称正确:
确保request.inputs['input_tensor_name']
中的'input_tensor_name'
是你在 TensorFlow SavedModel 中定义的输入张量的名称。
修改后的完整代码示例
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
from google.protobuf import json_format
from PIL import Image
import numpy as np
# 定义读取和预处理图片的函数
def load_image(image_path):
img = Image.open(image_path).resize((224, 224))
img = np.array(img).astype(np.float32)
img = np.expand_dims(img, axis=0) # 扩展维度以符合模型输入
return img
# 创建 gRPC 频道和 stub
channel = grpc.insecure_channel('XXXX:8500')
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 创建预测请求
request = predict_pb2.PredictRequest()
request.model_spec.name = 'ai_flower'
request.model_spec.signature_name = 'serving_default'
# 读取并预处理图片
image_path = 'test_images/rose.jpg'
input_image = load_image(image_path)
# 将预处理后的图片设置为请求的输入张量
request.inputs['input_tensor_name'].CopyFrom(
tf.make_tensor_proto(input_image, shape=input_image.shape))
# 发送请求并获取响应
response = stub.Predict(request)
print(response)
确保 TensorFlow Serving 服务器和客户端配置的一致性,并且服务器端正确运行 gRPC 服务。如果还有问题,请提供更多详细信息,以便进一步诊断。
3问:有新错误:Traceback (most recent call last):
File “E:\WorkspacePython\TensorFlow_Serving\gRPC_client.py”, line 33, in
response = stub.Predict(request)
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\grpc_channel.py”, line 946, in call
return _end_unary_response_blocking(state, call, False, None)
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\grpc_channel.py”, line 849, in _end_unary_response_blocking
raise _InactiveRpcError(state)
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.INVALID_ARGUMENT
details = “input tensor alias not found in signature: input_tensor_name. Inputs expected to be in the set {keras_layer_input}.”
debug_error_string = “UNKNOWN:Error received from peer ipv4:XXXX:8500 {created_time:“2024-07-31T15:33:22.315742596+00:00”, grpc_status:3, grpc_message:“input tensor alias not found in signature: input_tensor_name. Inputs expected to be in the set {keras_layer_input}.”}”
ChatGPT:
错误信息表明,在你的模型签名中,输入张量的名称是 keras_layer_input
,而不是 input_tensor_name
。你需要将客户端代码中请求输入张量的名称改为 keras_layer_input
。
修改后的客户端代码如下:
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
from PIL import Image
import numpy as np
# 定义读取和预处理图片的函数
def load_image(image_path):
img = Image.open(image_path).resize((224, 224))
img = np.array(img).astype(np.float32)
img = np.expand_dims(img, axis=0) # 扩展维度以符合模型输入
return img
# 创建 gRPC 频道和 stub
channel = grpc.insecure_channel('XXXX:8500')
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 创建预测请求
request = predict_pb2.PredictRequest()
request.model_spec.name = 'ai_flower'
request.model_spec.signature_name = 'serving_default'
# 读取并预处理图片
image_path = 'test_images/rose.jpg'
input_image = load_image(image_path)
# 将预处理后的图片设置为请求的输入张量
request.inputs['keras_layer_input'].CopyFrom(
tf.make_tensor_proto(input_image, shape=input_image.shape))
# 发送请求并获取响应
response = stub.Predict(request)
print(response)
确认模型签名
如果你不确定输入张量的确切名称,可以使用以下命令来查看模型的签名信息:
saved_model_cli show --dir /xxxx/TensorFlow_Serving/saved_model --all
这个命令会输出模型的输入和输出签名信息。根据这个信息,你可以确认输入张量的正确名称并在客户端代码中使用它。
进一步检查
- 确保输入张量名称正确: 根据
saved_model_cli
命令输出的签名信息,确认输入张量的名称。 - 模型签名的一致性: 确保模型的签名名称与客户端代码中的一致。
通过这些步骤,应该能解决输入张量名称不匹配的问题。如果还有问题,请提供更多详细信息,以便进一步诊断。
4问:正确了。下面是客户端代码,修改一下代码:要求:标签编号与类别名称对应文件在当前目录label_map-encn.json中,分析返回信心,print前5为的预测结果。
ChatGPT
下面是修改后的客户端代码,包含从 label_map-encn.json
文件中读取标签编号与类别名称的对应关系,并分析返回的结果,打印前5位的预测结果:
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
from PIL import Image
import numpy as np
import json
# 定义读取和预处理图片的函数
def load_image(image_path):
img = Image.open(image_path).resize((224, 224))
img = np.array(img).astype(np.float32)
img = np.expand_dims(img, axis=0) # 扩展维度以符合模型输入
return img
# 从JSON文件中加载标签映射
def load_label_map(label_map_path):
with open(label_map_path, 'r', encoding='utf-8') as f:
label_map = json.load(f)
return label_map
# 创建 gRPC 频道和 stub
channel = grpc.insecure_channel('XXXX:8500')
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 创建预测请求
request = predict_pb2.PredictRequest()
request.model_spec.name = 'ai_flower'
request.model_spec.signature_name = 'serving_default'
# 读取并预处理图片
image_path = 'test_images/rose.jpg'
input_image = load_image(image_path)
# 将预处理后的图片设置为请求的输入张量
request.inputs['keras_layer_input'].CopyFrom(
tf.make_tensor_proto(input_image, shape=input_image.shape))
# 发送请求并获取响应
response = stub.Predict(request)
# 解析响应
predictions = tf.make_ndarray(response.outputs['predictions'])
# 加载标签映射
label_map = load_label_map('label_map-encn.json')
# 获取前5位预测结果
top_k = 5
top_k_indices = np.argsort(predictions[0])[::-1][:top_k]
top_k_labels = [label_map[str(index)] for index in top_k_indices]
top_k_scores = predictions[0][top_k_indices]
# 打印前5位预测结果
for i in range(top_k):
print(f"Rank {i + 1}: {top_k_labels[i]} (score: {top_k_scores[i]:.4f})")
说明
- 读取并预处理图片:
load_image
函数读取并预处理图片。 - 加载标签映射:
load_label_map
函数从label_map-encn.json
文件中加载标签映射。 - 创建 gRPC 频道和 stub: 与 TensorFlow Serving 服务器建立 gRPC 连接。
- 创建预测请求: 构造预测请求并发送到服务器。
- 解析响应: 从响应中解析预测结果,并获取前5位的预测类别及其对应的分数。
- 打印结果: 打印前5位预测结果及其分数。
确保 label_map-encn.json
文件在脚本的当前目录中,并且其内容格式正确。例如:
{
"0": "daisy",
"1": "dandelion",
"2": "roses",
"3": "sunflowers",
"4": "tulips"
}
5问:Traceback (most recent call last):
File “E:\WorkspacePython\TensorFlow_Serving\gRPC_client.py”, line 42, in
predictions = tf.make_ndarray(response.outputs[‘predictions’])
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\tensorflow\python\framework\tensor_util.py”, line 662, in MakeNdarray
tensor_dtype = dtypes.as_dtype(tensor.dtype)
File “e:\ProgramData\Anaconda3\envs\tensorflow27\lib\site-packages\tensorflow\python\framework\dtypes.py”, line 852, in as_dtype
raise TypeError(f"Cannot convert the argument type_value: {type_value!r} "
TypeError: Cannot convert the argument type_value: 0 to a TensorFlow DType.
ChatGPT
这个错误通常表示在从响应中提取张量时,找不到指定的键。确保响应中的键名与模型导出的签名一致。
你可以通过调试代码来检查响应的具体内容,从而确定输出张量的名称。先打印响应内容,找到包含预测结果的键。修改如下:
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
from PIL import Image
import numpy as np
import json
# 定义读取和预处理图片的函数
def load_image(image_path):
img = Image.open(image_path).resize((224, 224))
img = np.array(img).astype(np.float32)
img = np.expand_dims(img, axis=0) # 扩展维度以符合模型输入
return img
# 从JSON文件中加载标签映射
def load_label_map(label_map_path):
with open(label_map_path, 'r', encoding='utf-8') as f:
label_map = json.load(f)
return label_map
# 创建 gRPC 频道和 stub
channel = grpc.insecure_channel('XXXX:8500')
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 创建预测请求
request = predict_pb2.PredictRequest()
request.model_spec.name = 'ai_flower'
request.model_spec.signature_name = 'serving_default'
# 读取并预处理图片
image_path = 'test_images/rose.jpg'
input_image = load_image(image_path)
# 将预处理后的图片设置为请求的输入张量
request.inputs['keras_layer_input'].CopyFrom(
tf.make_tensor_proto(input_image, shape=input_image.shape))
# 发送请求并获取响应
response = stub.Predict(request)
# 打印响应内容,检查输出张量的键名
print("Response outputs:", response.outputs)
# 假设输出张量的键名是 'predictions',从响应中提取预测结果
# 如果键名不同,请根据打印结果进行修改
output_tensor_name = 'predictions' # 修改为实际的键名
if output_tensor_name in response.outputs:
predictions = tf.make_ndarray(response.outputs[output_tensor_name])
else:
print(f"Output tensor '{output_tensor_name}' not found in the response.")
predictions = []
# 加载标签映射
label_map = load_label_map('label_map-encn.json')
# 获取前5位预测结果
top_k = 5
if predictions.size > 0:
top_k_indices = np.argsort(predictions[0])[::-1][:top_k]
top_k_labels = [label_map[str(index + 1)] for index in top_k_indices]
top_k_scores = predictions[0][top_k_indices]
# 打印前5位预测结果
for i in range(top_k):
print(f"Rank {i + 1}: {top_k_labels[i]} (score: {top_k_scores[i]:.4f})")
else:
print("No predictions found.")
说明
- 打印响应内容: 通过
print("Response outputs:", response.outputs)
打印响应内容,找到包含预测结果的键。 - 检查输出张量的键名: 根据打印结果,修改
output_tensor_name
变量为实际的键名。
执行此代码,观察打印的响应内容,确保找到了正确的键名。然后,修改 output_tensor_name
变量,重新运行代码进行预测和结果打印。
6问:正确了。再讨论下一个问题:gRPC与REST API有什么区别?
ChatGPT
gRPC 和 REST API 是两种用于服务间通信的不同协议。它们各有优缺点,适用于不同的场景。以下是它们的主要区别:
1. 协议与传输方式
-
gRPC:
- 协议: gRPC 基于 HTTP/2 协议。它使用 Protocol Buffers(Protobuf)作为接口定义语言和消息交换格式。
- 传输方式: 支持流式传输(双向流、客户端流、服务端流)。
-
REST API:
- 协议: REST 通常基于 HTTP/1.1 或 HTTP/2 协议。
- 传输方式: 请求和响应是基于 HTTP 方法(如 GET、POST、PUT、DELETE)。
2. 数据格式
-
gRPC:
- 使用 Protocol Buffers(二进制格式),这使得数据序列化和反序列化非常高效。
- 由于是二进制格式,数据传输较小且速度快,但不如 JSON 直观易读。
-
REST API:
- 通常使用 JSON 或 XML 作为数据格式。
- JSON 易于阅读和调试,但数据传输较大且可能比二进制格式慢。
3. 性能
-
gRPC:
- 支持高效的二进制序列化,通常比 JSON 更节省带宽。
- 支持 HTTP/2 的多路复用和压缩,可以提高性能和减少延迟。
- 适合需要高效、低延迟通信的场景,如微服务之间的通信。
-
REST API:
- 使用文本格式的 JSON,可能导致更大的数据传输量。
- HTTP/1.1 不支持多路复用,可能会导致性能瓶颈,尤其是在高并发情况下。
4. 接口定义
-
gRPC:
- 使用 Protobuf 定义接口,接口定义文件(
.proto
文件)描述了服务、方法和消息格式。 - 自动生成客户端和服务端代码,减少了手动编码错误和工作量。
- 使用 Protobuf 定义接口,接口定义文件(
-
REST API:
- 通常使用 OpenAPI(Swagger)或其他文档工具来描述接口。
- 接口文档可能不如 Protobuf 那样强类型,但可以使用工具生成客户端代码。
5. 错误处理
-
gRPC:
- 提供了更丰富的错误处理机制,可以通过 gRPC 状态码进行详细的错误描述。
-
REST API:
- 错误通常通过 HTTP 状态码和响应体中的错误信息进行描述。
6. 流式通信
-
gRPC:
- 支持流式通信,可以进行双向流传输,适合实时数据传输和复杂的请求/响应模式。
-
REST API:
- 通常不支持流式传输,主要是一次请求-一次响应模式。
7. 工具和语言支持
-
gRPC:
- 支持多种编程语言,并且提供了强类型的接口定义和代码生成工具。
-
REST API:
- 语言和框架支持广泛,许多工具和库可以用于实现和测试 REST API。
总结
- gRPC 适合对性能和效率有较高要求的场景,如微服务架构和实时应用。
- REST API 更加成熟,易于使用和调试,适合需要简单、易读的接口和广泛兼容性的场景。
选择哪种技术取决于具体的应用需求和环境。