python - 从 node.js 调用 Sagemaker Tensorflow Resnet50 Endpoint
问题描述
问题
我想知道 node.js 中这个 Python 代码的等价物:
from keras.preprocessing import image
from PIL import Image
from keras.applications.resnet50 import preprocess_input
raw_img = image.load_img("some/path").resize((224, 224), Image.NEAREST)
img = preprocess_input(image.img_to_array(raw_img))
语境
我将 Keras 的 ResNet50 模型上传到 SageMaker 端点。我可以使用以下代码从 Python 调用它:
import json
import boto3
import numpy as np
import io
client = boto3.client('runtime.sagemaker')
from keras.preprocessing import image
from PIL import Image
from keras.applications.resnet50 import preprocess_input
raw_img = image.load_img("some/path").resize((224, 224), Image.NEAREST)
img = preprocess_input(image.img_to_array(raw_img))
response = client.invoke_endpoint(
EndpointName="SAGEMAKER_ENDPOINT_NAME",
Body=json.dumps({ "instances": [ img.tolist() ] }),
ContentType="application/json"
)
现在我需要从 node.js 做同样的事情。我想出了如何使用aws-sdk
:
import * as aws from 'aws-sdk';
const sageMaker = new aws.SageMakerRuntime({
region: 'ap-northeast-1'
});
sageMaker.invokeEndpoint({
EndpointName: endpointName,
Body: input,
ContentType: "application/json",
}, (error, res) => {
if (error) { return reject(error); }
// YEAH
})
但我无法弄清楚我们如何生成input
json,即相当于这个 python 片段:
from keras.preprocessing import image
from PIL import Image
from keras.applications.resnet50 import preprocess_input
raw_img = image.load_img("some/path").resize((224, 224), Image.NEAREST)
img = preprocess_input(image.img_to_array(raw_img))
是否有任何图书馆可以实现相同的目标,还是我需要重新发明轮子?
解决方案
答案是:我不得不重新发明轮子。这是到达 Endpoint 所需的工作(在 ResNet50 网络的情况下):
- 将源图像解码为像素
- 转换为 3 个通道(删除
- 调整为 224x224
- 将像素标准化为 [-1,1]
- 转换为 3D 数组(SageMaker 中的默认格式)
这是我最终得到的代码(这使用打字稿):
import * as tf from '@tensorflow/tfjs';
interface INormalizationOptions {
numberOfChannels: number;
imageSize: number;
}
interface IImagePixels {
width: number, height: number, numberOfChannels: number,
pixels: ndarray
}
export async function preprocessImage(filePath: string, o: INormalizationOptions) {
const img = await getImagePixels(filePath);
// Converts the ndarray to tensor, while ignoring extra channels
const rawTensor = imageToTensor(img, o);
console.log(`Size of image: ${img.width} ${img.height}`)
console.log(`Number of color components actually read: ${img.pixels.data.length} (expected: ${img.width*img.height*img.numberOfChannels})`)
console.log(`Number of pixels: ${img.width*img.height}`);
// Normalizes the Pixels from [0,255] to [-1,1]
const normalizedTensor: tf.Tensor3D = rawTensor.toFloat().sub(255/2).div(255/2);
// Resizes the inage Size to square of IMAGE_SIZE
const alignCorner = true;
const resizedInput = tf.image.resizeBilinear(
normalizedTensor,
[o.imageSize, o.imageSize],
alignCorner
)
const reshapedTensor: tf.Tensor3D = resizedInput.reshape([ o.imageSize, o.imageSize, o.numberOfChannels ])
return tensor3dToArray3d(reshapedTensor);
}
这使用了一些实用功能。
获取图像像素
async function getImagePixels(filePath: string) {
const _getPixels = require('get-pixels');
return new Promise<IImagePixels>( (resolve, reject) => {
_getPixels( filePath, (error: Error | null, pixels: ndarray) => {
if (error) { return reject(error); }
resolve({
width: pixels.shape[0],
height: pixels.shape[1],
numberOfChannels: pixels.shape[2],
pixels: pixels
})
});
})
}
图像到张量
import ndarray from 'ndarray';
function imageToTensor(img: IImagePixels, o: INormalizationOptions) {
const rawTensorValues = new Int32Array(img.width * img.height * o.numberOfChannels);
const rawTensor = tf.tensor3d(rawTensorValues, [ img.width, img.height, o.numberOfChannels ], 'int32');
// This only uses the first CHANNEL_ND
for (let row=0; row<img.height; row++) {
for (let col=0; col<img.width; col++){
for (let channel =0; channel<o.numberOfChannels; channel++) {
const pixel = img.pixels.get(row,col,channel);
if (!isNumeric(pixel)) { throw new Error(`Bad pixel: ${pixel}`) }
const offset = row * img.width * img.numberOfChannels + col * img.numberOfChannels + channel
rawTensorValues[offset] = pixel;
}
}
}
return rawTensor;
}
tensor3dToArray3d
async function tensor3dToArray3d(t: tf.Tensor3D) {
const dataAs3dArray = new Array<number[][]>();
const data = await t.data();
const expectedSizeOfArray = t.shape[0] * t.shape[1] * t.shape[2];
console.log(`Got vector of size ${t.shape[0]} ${t.shape[1]} ${t.shape[2]}, that is, ${expectedSizeOfArray} data`);
console.log(`Actual size of data: ${data.length}`);
for (let i=0; i<t.shape[0]; i++) {
dataAs3dArray[i] = new Array<number[]>();
for (let j=0; j<t.shape[1]; j++) {
dataAs3dArray[i][j] = new Array<number>();
for (let k=0; k<t.shape[2]; k++ ) {
const ijk =
i * t.shape[0] * t.shape[1] * t.shape[2]
+ j * t.shape[1] * t.shape[2]
+ k;
const datum = Math.random(); // Number( data[ijk] );
if (!isNumeric(datum)) { throw new Error(`Invalid datum at ${i},${j},${k}: ${datum}`) }
dataAs3dArray[i][j][k] = datum;
}
}
}
return dataAs3dArray;
}
function isNumeric(n: any) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
推荐阅读
- reactjs - TypeError:国家是未定义的 ReactJS covid19 应用程序
- javascript - 盖茨比:为什么我(或者我什至?)需要使用exports.onCreateNode 来创建页面?
- python - Python - 如果文本文件中存在正则表达式匹配,则在文本文件中附加一行列表
- oracle - 从 Windows Oracle 12c 迁移到 Linux Oracle 18c
- powershell - 使用 PowerShell 将文件从共享驱动器复制到远程桌面
- android - 如何在 Flutter 中保持服务活动以运行套接字 io
- ruby-on-rails - 如何在 Capybara 中填充隐藏的输入字段?
- redux - 如何在切片中使用存储?
- sql - 将序号添加到数据
- selenium - 如何将 Selenium IDE 测试集成到 Bamboo 中?