對於機器學習和人工智能研究人員而言,好多人都只是構建好模型後就沒有進一步處理了,停留在一個比較粗糙的模型上面,沒有將其變成一個產品,其實好多創業型人工智能公司都是設計好模型後,將其轉化成產品,以後再推向市場。每個深度學習研究者心中或多或少都想成爲一名創業者,但不知道超哪一個方向發展。那麼,本文將從最簡單的網頁應用開始,一步一步帶領你使用TensorFlow建立一個卷積神經網絡(CNN)模型後,使用Flash RESTful API將模型變成一個網頁應用產品。
本文使用TensorFlow NN模塊構建CNN模型,並在CIFAR-10數據集上進行訓練和測試。爲了使模型能夠遠程訪問,使用Python建立Flask web應用來接收上傳的圖像,並使用HTTP返回其分類標籤。javascript
雖然能夠安裝傳統的方法安裝Python,可是建議使用相似於Anaconda這樣完整的包,由於裏面已經安裝了一些好的庫可供你直接調用。本文中使用的是Anaconda3版本,對於Windows系統,能夠從該網站下載並安裝。
爲了確保Anaconda3是否安裝成功,在CMD命令行中輸入(where Python),若是結果相似於下圖,則代表安裝成功。html
在上一步Anaconda3安裝完畢後,接下來是安裝TensorFlow(TF)。本文使用的是Windows系統下CPU版本的TF,安裝指導能夠見此連接。
TF的安裝步驟以下:java
C:> conda create -n tensorflow pip python=3.5
C:> activate tensorflow
上行命令告訴咱們venv和所需安裝的全部庫,輸入這行命令後,命令行將變(tensorflow)C:>,接下來是安裝TensorFlow包。git
代碼
爲了測試TF是否安裝成功,能夠導入TensorFlow,若結果與下圖同樣,則代表安裝成功。可是在導入TF以前,請確保venv被激活。程序員
最後的一個工具是安裝Flask RESTful API,安裝命令以下:web
C:> pip install Flask-API
CIFAR-10數據集能夠在此下載,該數據集包含60,000張圖像,並將其劃分爲訓練集和測試集。其中訓練集有5個文件夾,分別命名爲data_batch_一、data_batch_2...,data_batch_5,每一個文件夾中包含10,000張,每張都是32x32x3的RGB圖像。測試集只有一個文件夾,命名爲batches.meta,包含10,000張圖像。訓練集和測試集中包含的圖像類別爲飛機(airplane)、手機(automobile)、鳥(bird)、貓(cat)、鹿(deer)、狗(dog)、青蛙(frog)、馬(horse)、船(ship)以及truck(卡車)。
因爲數據集中的每一個文件都是二進制文件,所以應該對其進行解碼以檢索實際的圖像數據。基於此,建立unpickle_patch函數來執行以下操做:
def unpickle_patch(file): """ Decoding the binary file. :param file:File to decode it data. :return:Dictionary of the file holding details including input data and output labels. """ patch_bin_file = open(file, 'rb')#Reading the binary file. patch_dict = pickle.load(patch_bin_file, encoding='bytes')#Loading the details of the binary file into a dictionary. return patch_dict#Returning the dictionary.
該方法接收二進制文件並返回一個包含有關此文件詳細信息的字典。該字典除了標籤以外還包含文件中全部的10,000個樣本的數據。
爲了解碼整個訓練集,建立get_dataset_images函數。該函數接收數據集路徑並僅對訓練數據起做用。所以,它會過濾一些文件並只返回以data_batch_開頭的文件。測試數據在模型訓練好後再進行處理。
對於每個訓練文件夾,使用unpickle_patch函數解碼,該函數輸出一個字典。以後使用get_dataset_images函數獲取圖像數據以及其對應的類別標籤。圖像數據是從「data」鍵中檢索,類別標籤從「labels」鍵中檢索。
因爲數據圖形是以一維向量的形式保存,此外TensorFlow接收的是三維形式,所以應對其進行變換處理。基於此,get_dataset_images函數接收的參數爲圖像的文件路徑、行/列數以及圖像的通道數。
def get_dataset_images(dataset_path, im_dim=32, num_channels=3): """ This function accepts the dataset path, reads the data, and returns it after being reshaped to match the requierments of the CNN. :param dataset_path:Path of the CIFAR10 dataset binary files. :param im_dim:Number of rows and columns in each image. The image is expected to be rectangular. :param num_channels:Number of color channels in the image. :return:Returns the input data after being reshaped and output labels. """ num_files = 5#Number of training binary files in the CIFAR10 dataset. images_per_file = 10000#Number of samples withing each binary file. files_names = os.listdir(patches_dir)#Listing the binary files in the dataset path. """ Creating an empty array to hold the entire training data after being reshaped. The dataset has 5 binary files holding the data. Each binary file has 10,000 samples. Total number of samples in the dataset is 5*10,000=50,000. Each sample has a total of 3,072 pixels. These pixels are reshaped to form a RGB image of shape 32x32x3. Finally, the entire dataset has 50,000 samples and each sample of shape 32x32x3 (50,000x32x32x3). """ dataset_array = numpy.zeros(shape=(num_files * images_per_file, im_dim, im_dim, num_channels)) #Creating an empty array to hold the labels of each input sample. Its size is 50,000 to hold the label of each sample in the dataset. dataset_labels = numpy.zeros(shape=(num_files * images_per_file), dtype=numpy.uint8) index = 0#Index variable to count number of training binary files being processed. for file_name in files_names: """ Because the CIFAR10 directory does not only contain the desired training files and has some other files, it is required to filter the required files. Training files start by 'data_batch_' which is used to test whether the file is for training or not. """ if file_name[0:len(file_name) - 1] == "data_batch_": print("Working on : ", file_name) """ Appending the path of the binary files to the name of the current file. Then the complete path of the binary file is used to decoded the file and return the actual pixels values. """ data_dict = unpickle_patch(dataset_path+file_name) """ Returning the data using its key 'data' in the dictionary. Character b is used before the key to tell it is binary string. """ images_data = data_dict[b"data"] #Reshaping all samples in the current binary file to be of 32x32x3 shape. images_data_reshaped = numpy.reshape(images_data, newshape=(len(images_data), im_dim, im_dim, num_channels)) #Appending the data of the current file after being reshaped. dataset_array[index * images_per_file:(index + 1) * images_per_file, :, :, :] = images_data_reshaped #Appening the labels of the current file. dataset_labels[index * images_per_file:(index + 1) * images_per_file] = data_dict[b"labels"] index = index + 1#Incrementing the counter of the processed training files by 1 to accept new file. return dataset_array, dataset_labels#Returning the training input data and output labels.
def create_CNN(input_data, num_classes, keep_prop): """ Builds the CNN architecture by stacking conv, relu, pool, dropout, and fully connected layers. :param input_data:patch data to be processed. :param num_classes:Number of classes in the dataset. It helps determining the number of outputs in the last fully connected layer. :param keep_prop:probability of dropping neurons in the dropout layer. :return: last fully connected layer. """ #Preparing the first convolution layer. filters1, conv_layer1 = create_conv_layer(input_data=input_data, filter_size=5, num_filters=4) """ Applying ReLU activation function over the conv layer output. It returns a new array of the same shape as the input array. """ relu_layer1 = tensorflow.nn.relu(conv_layer1) print("Size of relu1 result : ", relu_layer1.shape) """ Max pooling is applied to the ReLU layer result to achieve translation invariance. It returns a new array of a different shape from the the input array relative to the strides and kernel size used. """ max_pooling_layer1 = tensorflow.nn.max_pool(value=relu_layer1, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding="VALID") print("Size of maxpool1 result : ", max_pooling_layer1.shape) #Similar to the previous conv-relu-pool layers, new layers are just stacked to complete the CNN architecture. #Conv layer with 3 filters and each filter is of sisze of 5x5. filters2, conv_layer2 = create_conv_layer(input_data=max_pooling_layer1, filter_size=7, num_filters=3) relu_layer2 = tensorflow.nn.relu(conv_layer2) print("Size of relu2 result : ", relu_layer2.shape) max_pooling_layer2 = tensorflow.nn.max_pool(value=relu_layer2, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding="VALID") print("Size of maxpool2 result : ", max_pooling_layer2.shape) #Conv layer with 2 filters and a filter sisze of 5x5. filters3, conv_layer3 = create_conv_layer(input_data=max_pooling_layer2, filter_size=5, num_filters=2) relu_layer3 = tensorflow.nn.relu(conv_layer3) print("Size of relu3 result : ", relu_layer3.shape) max_pooling_layer3 = tensorflow.nn.max_pool(value=relu_layer3, ksize=[1, 2, 2, 1], strides=[1, 1, 1, 1], padding="VALID") print("Size of maxpool3 result : ", max_pooling_layer3.shape) #Adding dropout layer before the fully connected layers to avoid overfitting. flattened_layer = dropout_flatten_layer(previous_layer=max_pooling_layer3, keep_prop=keep_prop) #First fully connected (FC) layer. It accepts the result of the dropout layer after being flattened (1D). fc_resultl = fc_layer(flattened_layer=flattened_layer, num_inputs=flattened_layer.get_shape()[1:].num_elements(), num_outputs=200) #Second fully connected layer accepting the output of the previous fully connected layer. Number of outputs is equal to the number of dataset classes. fc_result2 = fc_layer(flattened_layer=fc_resultl, num_inputs=fc_resultl.get_shape()[1:].num_elements(), num_outputs=num_classes) print("Fully connected layer results : ", fc_result2) return fc_result2#Returning the result of the last FC layer.
def create_conv_layer(input_data, filter_size, num_filters): """ Builds the CNN convolution (conv) layer. :param input_data:patch data to be processed. :param filter_size:#Number of rows and columns of each filter. It is expected to have a rectangular filter. :param num_filters:Number of filters. :return:The last fully connected layer of the network. """ """ Preparing the filters of the conv layer by specifiying its shape. Number of channels in both input image and each filter must match. Because number of channels is specified in the shape of the input image as the last value, index of -1 works fine. """ filters = tensorflow.Variable(tensorflow.truncated_normal(shape=(filter_size, filter_size, tensorflow.cast(input_data.shape[-1], dtype=tensorflow.int32), num_filters), stddev=0.05)) print("Size of conv filters bank : ", filters.shape) """ Building the convolution layer by specifying the input data, filters, strides along each of the 4 dimensions, and the padding. Padding value of 'VALID' means the some borders of the input image will be lost in the result based on the filter size. """ conv_layer = tensorflow.nn.conv2d(input=input_data, filter=filters, strides=[1, 1, 1, 1], padding="VALID") print("Size of conv result : ", conv_layer.shape) return filters, conv_layer#Returing the filters and the convolution layer result.
對於dropout層,接收一個保持神經元的機率參數,它代表會有多少神經元在dropout層被丟棄。 dropout層是使用dropout_flatten_layer函數實現,以下所示:
ef dropout_flatten_layer(previous_layer, keep_prop): """ Applying the dropout layer. :param previous_layer: Result of the previous layer to the dropout layer. :param keep_prop: Probability of keeping neurons. :return: flattened array. """ dropout = tensorflow.nn.dropout(x=previous_layer, keep_prob=keep_prop) num_features = dropout.get_shape()[1:].num_elements() layer = tensorflow.reshape(previous_layer, shape=(-1, num_features))#Flattening the results. return layer
def fc_layer(flattened_layer, num_inputs, num_outputs): """ uilds a fully connected (FC) layer. :param flattened_layer: Previous layer after being flattened. :param num_inputs: Number of inputs in the previous layer. :param num_outputs: Number of outputs to be returned in such FC layer. :return: """ #Preparing the set of weights for the FC layer. It depends on the number of inputs and number of outputs. fc_weights = tensorflow.Variable(tensorflow.truncated_normal(shape=(num_inputs, num_outputs), stddev=0.05)) #Matrix multiplication between the flattened array and the set of weights. fc_resultl = tensorflow.matmul(flattened_layer, fc_weights) return fc_resultl#Output of the FC layer (result of matrix multiplication).
使用TensorBoard能夠可視化網絡模型結構,以下圖所示:
在構建好CNN模型以後,下一步就是使用以前處理的訓練數據進行模型訓練,代碼以下所示。代碼首先準備訓練數據的路徑,而後調用以前的討論過的函數,訓練的CNN使用梯度降低算法,優化方式是儘量最小化代價函數。
#Nnumber of classes in the dataset. Used to specify number of outputs in the last fully connected layer. num_datatset_classes = 10 #Number of rows & columns in each input image. The image is expected to be rectangular Used to reshape the images and specify the input tensor shape. im_dim = 32 #Number of channels in rach input image. Used to reshape the images and specify the input tensor shape. num_channels = 3 #Directory at which the training binary files of the CIFAR10 dataset are saved. patches_dir = "C:\\Users\\Dell\\Downloads\\Compressed\\cifar-10-python\\cifar-10-batches-py\\" #Reading the CIFAR10 training binary files and returning the input data and output labels. Output labels are used to test the CNN prediction accuracy. dataset_array, dataset_labels = get_dataset_images(dataset_path=patches_dir, im_dim=im_dim, num_channels=num_channels) print("Size of data : ", dataset_array.shape) """ Input tensor to hold the data read above. It is the entry point of the computational graph. The given name of 'data_tensor' is useful for retreiving it when restoring the trained model graph for testing. """ data_tensor = tensorflow.placeholder(tensorflow.float32, shape=[None, im_dim, im_dim, num_channels], name='data_tensor') """ Tensor to hold the outputs label. The name "label_tensor" is used for accessing the tensor when tesing the saved trained model after being restored. """ label_tensor = tensorflow.placeholder(tensorflow.float32, shape=[None], name='label_tensor') #The probability of dropping neurons in the dropout layer. It is given a name for accessing it later. keep_prop = tensorflow.Variable(initial_value=0.5, name="keep_prop") #Building the CNN architecure and returning the last layer which is the fully connected layer. fc_result2 = create_CNN(input_data=data_tensor, num_classes=num_datatset_classes, keep_prop=keep_prop) """ Predicitions probabilities of the CNN for each training sample. Each sample has a probability for each of the 10 classes in the dataset. Such tensor is given a name for accessing it later. """ softmax_propabilities = tensorflow.nn.softmax(fc_result2, name="softmax_probs") """ Predicitions labels of the CNN for each training sample. The input sample is classified as the class of the highest probability. axis=1 indicates that maximum of values in the second axis is to be returned. This returns that maximum class probability fo each sample. """ softmax_predictions = tensorflow.argmax(softmax_propabilities, axis=1) #Cross entropy of the CNN based on its calculated probabilities. cross_entropy = tensorflow.nn.softmax_cross_entropy_with_logits(logits=tensorflow.reduce_max(input_tensor=softmax_propabilities, reduction_indices=[1]), labels=label_tensor) #Summarizing the cross entropy into a single value (cost) to be minimized by the learning algorithm. cost = tensorflow.reduce_mean(cross_entropy) #Minimizng the network cost using the Gradient Descent optimizer with a learning rate is 0.01. error = tensorflow.train.GradientDescentOptimizer(learning_rate=.01).minimize(cost) #Creating a new TensorFlow Session to process the computational graph. sess = tensorflow.Session() #Wiriting summary of the graph to visualize it using TensorBoard. tensorflow.summary.FileWriter(logdir="./log/", graph=sess.graph) #Initializing the variables of the graph. sess.run(tensorflow.global_variables_initializer()) """ Because it may be impossible to feed the complete data to the CNN on normal machines, it is recommended to split the data into a number of patches. A percent of traning samples is used to create each path. Samples for each path can be randomly selected. """ num_patches = 5#Number of patches for patch_num in numpy.arange(num_patches): print("Patch : ", str(patch_num)) percent = 80 #percent of samples to be included in each path. #Getting the input-output data of the current path. shuffled_data, shuffled_labels = get_patch(data=dataset_array, labels=dataset_labels, percent=percent) #Data required for cnn operation. 1)Input Images, 2)Output Labels, and 3)Dropout probability cnn_feed_dict = {data_tensor: shuffled_data, label_tensor: shuffled_labels, keep_prop: 0.5} """ Training the CNN based on the current patch. CNN error is used as input in the run to minimize it. SoftMax predictions are returned to compute the classification accuracy. """ softmax_predictions_, _ = sess.run([softmax_predictions, error], feed_dict=cnn_feed_dict) #Calculating number of correctly classified samples. correct = numpy.array(numpy.where(softmax_predictions_ == shuffled_labels)) correct = correct.size print("Correct predictions/", str(percent * 50000/100), ' : ', correct)
def get_patch(data, labels, percent=70): """ Returning patch to train the CNN. :param data: Complete input data after being encoded and reshaped. :param labels: Labels of the entire dataset. :param percent: Percent of samples to get returned in each patch. :return: Subset of the data (patch) to train the CNN model. """ #Using the percent of samples per patch to return the actual number of samples to get returned. num_elements = numpy.uint32(percent*data.shape[0]/100) shuffled_labels = labels#Temporary variable to hold the data after being shuffled. numpy.random.shuffle(shuffled_labels)#Randomly reordering the labels. """ The previously specified percent of the data is returned starting from the beginning until meeting the required number of samples. The labels indices are also used to return their corresponding input images samples. """ return data[shuffled_labels[:num_elements], :, :, :], shuffled_labels[:num_elements]
在訓練好CNN模型以後,須要保存訓練好的參數以便測試時使用,保存路徑由你指定,代碼以下:
#Saving the model after being trained. saver = tensorflow.train.Saver() save_model_path = "C:\\model\\" save_path = saver.save(sess=sess, save_path=save_model_path+"model.ckpt") print("Model saved in : ", save_path)
def get_dataset_images(test_path_path, im_dim=32, num_channels=3): """ Similar to the one used in training except that there is just a single testing binary file for testing the CIFAR10 trained models. """ print("Working on testing patch") data_dict = unpickle_patch(test_path_path) images_data = data_dict[b"data"] dataset_array = numpy.reshape(images_data, newshape=(len(images_data), im_dim, im_dim, num_channels)) return dataset_array, data_dict[b"labels"]
#Dataset path containing the testing binary file to be decoded. patches_dir = "C:\\Users\\Dell\\Downloads\\Compressed\\cifar-10-python\\cifar-10-batches-py\\" dataset_array, dataset_labels = get_dataset_images(test_path_path=patches_dir + "test_batch", im_dim=32, num_channels=3) print("Size of data : ", dataset_array.shape) sess = tensorflow.Session() #Restoring the previously saved trained model. saved_model_path = 'C:\\Users\\Dell\\Desktop\\model\\' saver = tensorflow.train.import_meta_graph(saved_model_path+'model.ckpt.meta') saver.restore(sess=sess, save_path=saved_model_path+'model.ckpt') #Initalizing the varaibales. sess.run(tensorflow.global_variables_initializer()) graph = tensorflow.get_default_graph() """ Restoring previous created tensors in the training phase based on their given tensor names in the training phase. Some of such tensors will be assigned the testing input data and their outcomes (data_tensor, label_tensor, and keep_prop). Others are helpful in assessing the model prediction accuracy (softmax_propabilities and softmax_predictions). """ softmax_propabilities = graph.get_tensor_by_name(name="softmax_probs:0") softmax_predictions = tensorflow.argmax(softmax_propabilities, axis=1) data_tensor = graph.get_tensor_by_name(name="data_tensor:0") label_tensor = graph.get_tensor_by_name(name="label_tensor:0") keep_prop = graph.get_tensor_by_name(name="keep_prop:0") #keep_prop is equal to 1 because there is no more interest to remove neurons in the testing phase. feed_dict_testing = {data_tensor: dataset_array, label_tensor: dataset_labels, keep_prop: 1.0} #Running the session to predict the outcomes of the testing samples. softmax_propabilities_, softmax_predictions_ = sess.run([softmax_propabilities, softmax_predictions], feed_dict=feed_dict_testing) #Assessing the model accuracy by counting number of correctly classified samples. correct = numpy.array(numpy.where(softmax_predictions_ == dataset_labels)) correct = correct.size print("Correct predictions/10,000 : ", correct)
import flask #Creating a new Flask Web application. It accepts the package name. app = flask.Flask("CIFAR10_Flask_Web_App") """ To activate the Web server to receive requests, the application must run. A good practice is to check whether the file is whether the file called from an external Python file or not. If not, then it will run. """ if __name__ == "__main__": """ In this example, the app will run based on the following properties: host: localhost port: 7777 debug: flag set to True to return debugging information. """ app.run(host="localhost", port=7777, debug=True)
app.add_url_rule(rule="/", endpoint="homepage", view_func=redirect_upload)
def redirect_upload(): """ A viewer function that redirects the Web application from the root to a HTML page for uploading an image to get classified. The HTML page is located under the /templates directory of the application. :return: HTML page used for uploading an image. It is 'upload_image.html' in this exmaple. """ return flask.render_template(template_name_or_list="upload_image.html") """ Creating a route between the homepage URL (http://localhost:7777) to a viewer function that is called after getting to such URL. Endpoint 'homepage' is used to make the route reusable without hard-coding it later. """ app.add_url_rule(rule="/", endpoint="homepage", view_func=redirect_upload)
如下代碼是上圖頁面的HTML代碼,實現的功能也很簡單,容許用戶上傳一張圖像,當提交此類表單時,POST HTTP消息將被返回給URL:
http://localhost:7777/upload/
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" type="text/css" href="{{url_for(endpoint='static', filename='project_styles.css')}}"> <meta charset="UTF-8"> <title>Upload Image</title> </head> <body> <form enctype="multipart/form-data" method="post" action="http://localhost:7777/upload/"> <center> <h3>Select CIFAR10 image to predict its label.</h3> <input type="file" name="image_file" accept="image/*"><br> <input type="submit" value="Upload"> </center> </form> </body> </html>
在從HTML表單返回到服務器以後,將調用與action表單屬性中指定URL關聯的查看函數upload_image,該函數獲取用戶選擇的圖像並將其保存到服務器。
def upload_image(): """ Viewer function that is called in response to getting to the 'http://localhost:7777/upload' URL. It uploads the selected image to the server. :return: redirects the application to a new page for predicting the class of the image. """ #Global variable to hold the name of the image file for reuse later in prediction by the 'CNN_predict' viewer functions. global secure_filename if flask.request.method == "POST":#Checking of the HTTP method initiating the request is POST. img_file = flask.request.files["image_file"]#Getting the file name to get uploaded. secure_filename = werkzeug.secure_filename(img_file.filename)#Getting a secure file name. It is a good practice to use it. img_path = os.path.join(app.root_path, secure_filename)#Preparing the full path under which the image will get saved. img_file.save(img_path)#Saving the image in the specified path. print("Image uploaded successfully.") """ After uploading the image file successfully, next is to predict the class label of it. The application will fetch the URL that is tied to the HTML page responsible for prediction and redirects the browser to it. The URL is fetched using the endpoint 'predict'. """ return flask.redirect(flask.url_for(endpoint="predict")) return "Image upload failed." """ Creating a route between the URL (http://localhost:7777/upload) to a viewer function that is called after navigating to such URL. Endpoint 'upload' is used to make the route reusable without hard-coding it later. The set of HTTP method the viewer function is to respond to is added using the 'methods' argument. In this case, the function will just respond to requests of method of type POST. """ app.add_url_rule(rule="/upload/", endpoint="upload", view_func=upload_image, methods=["POST"])
return flask.redirect(flask.url_for(endpoint="predict"))
def CNN_predict(): """ Reads the uploaded image file and predicts its label using the saved pre-trained CNN model. :return: Either an error if the image is not for CIFAR10 dataset or redirects the browser to a new page to show the prediction result if no error occurred. """ """ Setting the previously created 'secure_filename' to global. This is because to be able invoke a global variable created in another function, it must be defined global in the caller function. """ global secure_filename #Reading the image file from the path it was saved in previously. img = scipy.misc.imread(os.path.join(app.root_path, secure_filename)) """ Checking whether the image dimensions match the CIFAR10 specifications. CIFAR10 images are RGB (i.e. they have 3 dimensions). It number of dimenions was not equal to 3, then a message will be returned. """ if(img.ndim) == 3: """ Checking if the number of rows and columns of the read image matched CIFAR10 (32 rows and 32 columns). """ if img.shape[0] == img.shape[1] and img.shape[0] == 32: """ Checking whether the last dimension of the image has just 3 channels (Red, Green, and Blue). """ if img.shape[-1] == 3: """ Passing all conditions above, the image is proved to be of CIFAR10. This is why it is passed to the predictor. """ predicted_class = CIFAR10_CNN_Predict_Image.main(img) """ After predicting the class label of the input image, the prediction label is rendered on an HTML page. The HTML page is fetched from the /templates directory. The HTML page accepts an input which is the predicted class. """ return flask.render_template(template_name_or_list="prediction_result.html", predicted_class=predicted_class) else: # If the image dimensions do not match the CIFAR10 specifications, then an HTML page is rendered to show the problem. return flask.render_template(template_name_or_list="error.html", img_shape=img.shape) else: # If the image dimensions do not match the CIFAR10 specifications, then an HTML page is rendered to show the problem. return flask.render_template(template_name_or_list="error.html", img_shape=img.shape) return "An error occurred."#Returned if there is a different error other than wrong image dimensions. """ Creating a route between the URL (http://localhost:7777/predict) to a viewer function that is called after navigating to such URL. Endpoint 'predict' is used to make the route reusable without hard-coding it later. """ app.add_url_rule(rule="/predict/", endpoint="predict", view_func=CNN_predict)
負責預測圖像類別標籤的主函數定義以下,它加載訓練好的模型並運行會話,返回圖像的預測類別,預測的類別將返回到Flask Web應用程序。
def main(img): """ The 'main' method accepts an input image array of size 32x32x3 and returns its class label. :param img:RGB image of size 32x32x3. :return:Predicted class label. """ #Dataset path containing a binary file with the labels of classes. Useful to decode the prediction code into a significant textual label. patches_dir = "C:\\cifar-10-python\\cifar-10-batches-py\\" dataset_array = numpy.random.rand(1, 32, 32, 3) dataset_array[0, :, :, :] = img sess = tensorflow.Session() #Restoring the previously saved trained model. saved_model_path = 'C:\\model\\' saver = tensorflow.train.import_meta_graph(saved_model_path+'model.ckpt.meta') saver.restore(sess=sess, save_path=saved_model_path+'model.ckpt') #Initalizing the varaibales. sess.run(tensorflow.global_variables_initializer()) graph = tensorflow.get_default_graph() """ Restoring previous created tensors in the training phase based on their given tensor names in the training phase. Some of such tensors will be assigned the testing input data and their outcomes (data_tensor, label_tensor, and keep_prop). Others are helpful in assessing the model prediction accuracy (softmax_propabilities and softmax_predictions). """ softmax_propabilities = graph.get_tensor_by_name(name="softmax_probs:0") softmax_predictions = tensorflow.argmax(softmax_propabilities, axis=1) data_tensor = graph.get_tensor_by_name(name="data_tensor:0") label_tensor = graph.get_tensor_by_name(name="label_tensor:0") keep_prop = graph.get_tensor_by_name(name="keep_prop:0") #keep_prop is equal to 1 because there is no more interest to remove neurons in the testing phase. feed_dict_testing = {data_tensor: dataset_array, keep_prop: 1.0} #Running the session to predict the outcomes of the testing samples. softmax_propabilities_, softmax_predictions_ = sess.run([softmax_propabilities, softmax_predictions], feed_dict=feed_dict_testing) label_names_dict = unpickle_patch(patches_dir + "batches.meta") dataset_label_names = label_names_dict[b"label_names"] return dataset_label_names[softmax_predictions_[0]].decode('utf-8')
CNN_predict函數預測圖像的返回類標籤將按照下圖在prediction_result.html的新HTML頁面上呈現。
注意到,Flask應用程序使用容許HTML頁面接收輸入參數的Jinja2模板引擎,在這種狀況下傳遞的輸入參數是predict_class = predicted_class。
return flask.render_template(template_name_or_list="prediction_result.html", predicted_class=predicted_class)
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" type="text/css" href="{{url_for(endpoint='static', filename='project_styles.css')}}"> <script type="text/javascript" src="{{url_for(endpoint='static', filename='result.js')}}"></script> <meta charset="UTF-8"> <title>Prediction Result</title> </head> <body onload="show_alert('{{predicted_class}}')"> <center><h1>Predicted Class Label : <span>{{predicted_class}}</span></h1> <br> <a href="{{url_for(endpoint='homepage')}}"><span>Return to homepage</span>.</a> </center> </body> </html>
它是一個由預測的圖像類填充的模板,該圖像做爲參數傳遞給HTML頁面,相似於下面的代碼:
<span>{{predicted_class}}</span>
Ahmed Gad,教師,專一於深度學習、機器學習、計算機視覺。
我的主頁:https://www.linkedin.com/in/ahmedfgad/
本文由阿里云云棲社區組織翻譯。
文章原標題《Complete Guide to Build ConvNet HTTP-Based Application using TensorFlow and Flask RESTful Python API》,譯者:海棠,審校:Uncle_LLD。
文章爲簡譯,更爲詳細的內容,請查看原文。