使用C ++ API
您可以在C ++ 中运行相同的Inception-v3模型,以便在生产环境中使用。您可以下载包含定义模型的GraphDef的存档(从TensorFlow存储库的根目录运行):模型,以便在生产环境(production environments)中使用。您可以下载包含定义模型的GraphDef的存档(从TensorFlow存储库的根目录运行):
代码块 |
---|
|
curl -L "https://storage.googleapis.com/download.tensorflow.org/models/inception_v3_2016_08_28_frozen.pb.tar.gz" |
tar -C tensorflow/examples/label_image/data -xz |
接下来,我们需要编译包含加载和运行图形代码的C接下来,我们需要编译包含加载和运行图代码的C ++二进制文件。如果您按照 说明下载适用 于您的平台的TensorFlow的源代码安装,您应该能够通过从您的shell终端运行此命令来构建该示例:二进制文件。如果您已经在开发平台按照 源代码安装TensorFlow的说明安装好tensorflow,您应该能够
通过从您的shell终端运行此命令来构建该示例:
代码块 |
---|
|
bazel build tensorflow/examples/label_image/... |
那应该创建一个二进制可执行文件,然后你可以这样运行:创建一个二进制可执行文件,然后你可以这样运行:
代码块 |
---|
|
bazel-bin/tensorflow/examples/label_image/label_image |
这使用框架附带的默认示例图像,并应输出类似于此的内容:
代码块 |
---|
|
I tensorflow/examples/label_image/main.cc:206] military uniform (653): 0.834306
I tensorflow/examples/label_image/main.cc:206] mortarboard (668): 0.0218692
I tensorflow/examples/label_image/main.cc:206] academic gown (401): 0.0103579
I tensorflow/examples/label_image/main.cc:206] pickelhaube (716): 0.00800814
I tensorflow/examples/label_image/main.cc:206] bulletproof vest (466): 0.00535088 |
在这种情况下,我们正在使用海军上将魔法师的默认图像 ,您可以看到网络正确识别她穿着军装,得分高达0在这种情况下,我们正在使用默认的Admiral Grace Hopper图像 ,您可以看到网络正确识别她穿着军装,得分高达0.8。
接下来,通过提供接下来,尝试在自己的图像应用--image =参数,例如,尝试在自己的图像上参数,例如,上
代码块 |
---|
|
bazel-bin/tensorflow/examples/label_image/label_image --image=my_image.png |
如果您查看tensorflow/examples/label_image/main.cc
文件内容,可以了解它的工作原理。我们希望这段代码可以帮助您将TensorFlow集成到您自己的应用程序中,因此我们将逐步介绍主要功能: 文件内容,可以了解它的工作原理。我们希望这段代码可以帮助您将TensorFlow集成到您自己
命令行标志控制文件的加载位置以及输入图像的属性。该模型希望获得299x299的RGB图像,所以这些是的应用程序中,因此我们将逐步介绍主要功能:
命令行标志控制从哪加载的文件以及输入图像的属性。该模型希望获得299x299的RGB图像,这是input_width
和input_height
标志。我们还需要将从0到255之间的整数的像素值缩放到图形运算的浮点值。我们使用input_mean
和input_std
标志控制缩放:我们首先标志控制缩放:我们首先从每个像素值中减去input_mean
从每个像素值中减去 ,然后除以 ,然后除以input_std
。
这些值可能看起来有点神奇,但是它们只是由原始模型作者根据他这些值可能看起来有点奇怪,但是它们只是由原始模型的作者根据他/她想用作输入图像进行培训而定义的。如果您有一个自己训练过的图表,那么您只需要调整这些值,使其与您在培训过程中使用的任何值相匹配。她想用输入图像进行训练而定义的。如果您有一个自己训练过的图表,那么您只需要调整这些值,使其与您在训练过程中使用的任何值相匹配。
您可以看到它们如何应用于您可以看到它们在ReadTensorFromImageFile()
函数中的图像 。 函数中如何应用图像 。
代码块 |
---|
|
// Given an image file name, read in the data, try to decode it as an image,
// resize it to the requested size, and then scale the values as desired.
Status ReadTensorFromImageFile(string file_name, const int input_height,
const int input_width, const float input_mean,
const float input_std,
std::vector<Tensor>* out_tensors) {
tensorflow::GraphDefBuilder b; |
我们首先创建一个GraphDefBuilder
可以用来指定运行或加载的模型的对象。
代码块 |
---|
|
string input_name = "file_reader";
string output_name = "normalized";
tensorflow::Node* file_reader =
tensorflow::ops::ReadFile(tensorflow::ops::Const(file_name, b.opts()),
b.opts().WithName(input_name)); |
然后,我们开始创建我们要运行的小型模型的节点,以加载,调整大小和缩放像素值,以获得主模型期望作为其输入的结果。我们创建的第一个节点只是一个Const
op,它包含我们要加载的图像的文件名的张量。那就是作为第一个输入ReadFile
。您可能会注意到我们然后,我们开始创建我们要运行的小型模型的节点,通过加载、调整大小和缩放像素值,以获得主模型期望作为输入的结果。我们创建的第一个节点只
是一个Const
操作,它包含我们要加载的图片文件名的张量。把第一个输入传给ReadFile操作
。您可能会注意到我们把b.opts()
作为最后一个参数传递给所有op创建函数。该参数确保将节点添加到模型定义中GraphDefBuilder
。我们也打电话给ReadFile
运营商。这给了一个节点的名字,这并不是绝对必要的,因为如果你不这样做,就会分配一个自动的名字,但这样会使调试变得更容易一些。作为最后一个参数传递给
所有操作创建的函数。该参数确保在 GraphDefBuilder
中,将节点添加到模型定义中。我们通过给 WithName()
传入 b.opts()
命名ReadFile
操作。
这并不是必要的,因为如果你不这样做,就会分配一个自动的名字,但这样做会使调试变得更容易一些。
代码块 |
---|
|
// Now try to figure out what kind of file it is and decode it.
const int wanted_channels = 3;
tensorflow::Node* image_reader;
if (tensorflow::StringPiece(file_name).ends_with(".png")) {
image_reader = tensorflow::ops::DecodePng(
file_reader,
b.opts().WithAttr("channels", wanted_channels).WithName("png_reader"));
} else {
// Assume if it's not a PNG then it must be a JPEG.
image_reader = tensorflow::ops::DecodeJpeg(
file_reader,
b.opts().WithAttr("channels", wanted_channels).WithName("jpeg_reader"));
}
// Now cast the image data to float so we can do normal math on it.
tensorflow::Node* float_caster = tensorflow::ops::Cast(
image_reader, tensorflow::DT_FLOAT, b.opts().WithName("float_caster"));
// The convention for image ops in TensorFlow is that all images are expected
// to be in batches, so that they're four-dimensional arrays with indices of
// [batch, height, width, channel]. Because we only have a single image, we
// have to add a batch dimension of 1 to the start with ExpandDims().
tensorflow::Node* dims_expander = tensorflow::ops::ExpandDims(
float_caster, tensorflow::ops::Const(0, b.opts()), b.opts());
// Bilinearly resize the image to fit the required dimensions.
tensorflow::Node* resized = tensorflow::ops::ResizeBilinear(
dims_expander, tensorflow::ops::Const({input_height, input_width},
b.opts().WithName("size")),
b.opts());
// Subtract the mean and divide by the scale.
tensorflow::ops::Div(
tensorflow::ops::Sub(
resized, tensorflow::ops::Const({input_mean}, b.opts()), b.opts()),
tensorflow::ops::Const({input_std}, b.opts()),
b.opts().WithName(output_name)); |
然后,我们继续添加更多的节点,将文件数据解码为图像,将整数转换为浮点值,调整其大小,然后最终对像素值执行减法和除法运算。
代码块 |
---|
|
// This runs the GraphDef network definition that we've just constructed, and
// returns the results in the output tensor.
tensorflow::GraphDef graph;
TF_RETURN_IF_ERROR(b.ToGraphDef(&graph)); |
最后,我们有一个存储在b变量中的模型定义,我们将其转换成具有该ToGraphDef()
函数的完整图形定义。
代码块 |
---|
|
std::unique_ptr<tensorflow::Session> session(
tensorflow::NewSession(tensorflow::SessionOptions()));
TF_RETURN_IF_ERROR(session->Create(graph));
TF_RETURN_IF_ERROR(session->Run({}, {output_name}, {}, out_tensors));
return Status::OK(); |
然后我们创建一个tf.Session
对象,它是实际运行图形的接口,并运行它,指定要获取输出的节点以及输出数据的位置。
这给我们一个给我们一个Tensor
对象的向量,在这种情况下,我们知道只有一个对象很长。对象的向量,在这种情况下,我们知道只有一个long对象。在这种情况下,您可以将Tensor
在这种情况下,您可以将其视为多维数组,并将其作为浮点值保存299像素高,299像素宽,3通道图像。如果您已经在产品中拥有自己的图像处理框架,那么只要在将图像输入主图形之前应用相同的变换即可使用。视为多维数组,并将299像素高,299像素宽,3通道的图像作为浮点值。如果您已经拥有自己的图像处理框架,那么只要在将图像输入主图形之前应用相同的变换即可使用。
这是一个在C ++中动态创建小TensorFlow图的简单示例,但是对于预先训练的Inception模型,我们要从文件中加载更大的定义。你可以看到我们如何在中动态创建小型TensorFlow图的简单示例,但是我们要从文件中加载更大的预训练的Inception模型。你可以看看我们在LoadGraph()
函数中这样做。函数中是如何做的。
代码块 |
---|
|
// Reads a model graph definition from disk, and creates a session object you
// can use to run it.
Status LoadGraph(string graph_file_name,
std::unique_ptr<tensorflow::Session>* session) {
tensorflow::GraphDef graph_def;
Status load_graph_status =
ReadBinaryProto(tensorflow::Env::Default(), graph_file_name, &graph_def);
if (!load_graph_status.ok()) {
return tensorflow::errors::NotFound("Failed to load compute graph at '",
graph_file_name, "'");
} |
如果您浏览了图像加载代码,则很多术语应该看起来很熟悉。而不是使用a 如果您浏览了图像加载代码,那么很多术语应该看起来很熟悉。而不是使用GraphDefBuilder
来生成一个GraphDef
对象,我们加载一个直接包含的protobuf文件对象,我们加载一个包含GraphDef
的protobuf文
件。
代码块 |
---|
|
session->reset(tensorflow::NewSession(tensorflow::SessionOptions()));
Status session_create_status = (*session)->Create(graph_def);
if (!session_create_status.ok()) {
return session_create_status;
}
return Status::OK();
} |
然后我们创建一个Session对象,然后我们从GraphDef
创建一个Session对象,并将其传递给调用者,以便以后可以运行它。
该GetTopLabels()
功能非常像图像加载,除了在这种情况下,我们要获取运行主图的结果,并将其转换为最高评分标签的排序列表。就像图像加载器一样,它创建一个 函数非常像图像加载,除了我们要获取运行主图的结果,并将其转换为最高评分标签的排序列表。就像图像加载器一样,它创建一个 GraphDefBuilder
,添加了几个节点,然后运行短图来获得一对输出张量。在这种情况下,它们表示最高结果的排序分数和索引位置。
代码块 |
---|
|
// Analyzes the output of the Inception graph to retrieve the highest scores and
// their positions in the tensor, which correspond to categories.
Status GetTopLabels(const std::vector<Tensor>& outputs, int how_many_labels,
Tensor* indices, Tensor* scores) {
tensorflow::GraphDefBuilder b;
string output_name = "top_k";
tensorflow::ops::TopK(tensorflow::ops::Const(outputs[0], b.opts()),
how_many_labels, b.opts().WithName(output_name));
// This runs the GraphDef network definition that we've just constructed, and
// returns the results in the output tensors.
tensorflow::GraphDef graph;
TF_RETURN_IF_ERROR(b.ToGraphDef(&graph));
std::unique_ptr<tensorflow::Session> session(
tensorflow::NewSession(tensorflow::SessionOptions()));
TF_RETURN_IF_ERROR(session->Create(graph));
// The TopK node returns two outputs, the scores and their original indices,
// so we have to append :0 and :1 to specify them both.
std::vector<Tensor> out_tensors;
TF_RETURN_IF_ERROR(session->Run({}, {output_name + ":0", output_name + ":1"},
{}, &out_tensors));
*scores = out_tensors[0];
*indices = out_tensors[1];
return Status::OK(); |
该PrintTopLabels()
功能采用这些排序结果,并以友好的方式打印出来。该函数接受这些排序结果,并以用户友好的方式打印出来。与
CheckTopLabel()
功能非常相似,但只是确保顶级标签是我们期望的标签,用于调试目的。函数非常相似,但只是确保顶级标签是我们期望的标签,以便
用于调试。最后,main()
将所有这些电话联系在一起。 将所有函数调用联系在一起。
代码块 |
---|
|
int main(int argc, char* argv[]) {
// We need to call this to set up global state for TensorFlow.
tensorflow::port::InitMain(argv[0], &argc, &argv);
Status s = tensorflow::ParseCommandLineFlags(&argc, argv);
if (!s.ok()) {
LOG(ERROR) << "Error parsing command line flags: " << s.ToString();
return -1;
}
// First we load and initialize the model.
std::unique_ptr<tensorflow::Session> session;
string graph_path = tensorflow::io::JoinPath(FLAGS_root_dir, FLAGS_graph);
Status load_graph_status = LoadGraph(graph_path, &session);
if (!load_graph_status.ok()) {
LOG(ERROR) << load_graph_status;
return -1;
} |
我们加载主图。
代码块 |
---|
|
// Get the image from disk as a float array of numbers, resized and normalized
// to the specifications the main graph expects.
std::vector<Tensor> resized_tensors;
string image_path = tensorflow::io::JoinPath(FLAGS_root_dir, FLAGS_image);
Status read_tensor_status = ReadTensorFromImageFile(
image_path, FLAGS_input_height, FLAGS_input_width, FLAGS_input_mean,
FLAGS_input_std, &resized_tensors);
if (!read_tensor_status.ok()) {
LOG(ERROR) << read_tensor_status;
return -1;
}
const Tensor& resized_tensor = resized_tensors[0]; |
加载,调整大小并处理输入图像。
代码块 |
---|
|
// Actually run the image through the model.
std::vector<Tensor> outputs;
Status run_status = session->Run({ {FLAGS_input_layer, resized_tensor}},
{FLAGS_output_layer}, {}, &outputs);
if (!run_status.ok()) {
LOG(ERROR) << "Running model failed: " << run_status;
return -1;
} |
在这里,我们运行加载的图形作为输入图像。
代码块 |
---|
|
// This is for automated testing to make sure we get the expected result with
// the default settings. We know that label 866 (military uniform) should be
// the top label for the Admiral Hopper image.
if (FLAGS_self_test) {
bool expected_matches;
Status check_status = CheckTopLabel(outputs, 866, &expected_matches);
if (!check_status.ok()) {
LOG(ERROR) << "Running check failed: " << check_status;
return -1;
}
if (!expected_matches) {
LOG(ERROR) << "Self-test failed!";
return -1;
}
} |
为了测试的目的,我们可以检查以确保我们在这里得到我们预期的输出。为了测试,我们可以检查以确保我们在这里得到我们预期的输出。
代码块 |
---|
|
// Do something interesting with the results we've generated.
Status print_status = PrintTopLabels(outputs, FLAGS_labels); |
最后我们打印出我们发现的标签。最后我们打印出标签。
代码块 |
---|
|
if (!print_status.ok()) {
LOG(ERROR) << "Running print failed: " << print_status;
return -1;
} |
这里的错误处理是使用TensorFlow的Status
对象,这是非常方便的,因为它可以让您知道ok()
检查器是否发生任何错误,然后可以打印出来,提供可读的错误消息。检查器是否发生任何错误,然后可以打印出来,
在这种情况下,我们正在演示对象识别,但是您应该可以在各种领域中使用与您已经找到或训练过的其他型号相似的代码。我们希望这个小例子为您提供如何在您自己的产品中使用TensorFlow的一些想法。
练习:转移学习是一个想法,如果你知道如何解决一个很好的任务,你应该能够转移一些理解来解决相关的问题。执行传输学习的一种方法是去除网络的最终分类层,并提取CNN的下一层(最后一层),在这种情况下为2048维向量。在how-to部分中有一个指导。使错误消息更具可读性。
本例中,我们在演示对象识别,但是您应该可以在各种领域中使用与您已经找到或训练过的其他模型相似的代码。我们希望这个小例子为您提供如何在您自己的平台上使用TensorFlow的一些想法。
EXERCISE:迁移学习是一个想法,如果你知道如何很好的解决一个任务,你应该能够转移一些理解来解决相关的问题。迁移学习的一种方法是去除网络的最后一个分类层,并提取next-to-last layer of the CNN,在这种情况下为2048维向量。 在 how-to section中有一个关于这样操作的指导。