changes to nb 00-03, added test_utilities
This commit is contained in:
Родитель
7e09708dd4
Коммит
5beb3818d8
|
@ -1665,7 +1665,9 @@
|
|||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"img = image.load_img(img_path, target_size=(224, 224))\n",
|
||||
|
|
|
@ -173,6 +173,13 @@
|
|||
"resp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Below, we create two functions. The first function loads the model and returns a scoring function. The second function process the images and uses the first function to score them."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
|
@ -410,7 +417,7 @@
|
|||
" \n",
|
||||
" logger.info(\"Predictions: {0}\".format(responses))\n",
|
||||
" logger.info(\"Predictions took {0} ms\".format(round((end-start)*1000, 2)))\n",
|
||||
" return (responses, \"Predictions took {0} ms\".format(round((end-start)*1000, 2)))\n",
|
||||
" return (responses, \"Computed in {0} ms\".format(round((end-start)*1000, 2)))\n",
|
||||
" return process_and_score\n",
|
||||
"\n",
|
||||
"def version():\n",
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here we write the configuration for the Nginx which creates a proxy between ports 80 and 5000."
|
||||
"Here, we write the configuration for the Nginx which creates a proxy between ports **80** and **5000**."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,372 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""ResNet152 model for Keras.
|
||||
|
||||
# Reference:
|
||||
|
||||
- [Deep Residual Learning for Image Recognition](https://arxiv.org/abs/1512.03385)
|
||||
|
||||
Adaptation of code from flyyufelix, mvoelk, BigMoyan, fchollet at https://github.com/adamcasson/resnet152
|
||||
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import warnings
|
||||
|
||||
from keras.layers import Input
|
||||
from keras.layers import Dense
|
||||
from keras.layers import Activation
|
||||
from keras.layers import Flatten
|
||||
from keras.layers import Conv2D
|
||||
from keras.layers import MaxPooling2D
|
||||
from keras.layers import GlobalMaxPooling2D
|
||||
from keras.layers import ZeroPadding2D
|
||||
from keras.layers import AveragePooling2D
|
||||
from keras.layers import GlobalAveragePooling2D
|
||||
from keras.layers import BatchNormalization
|
||||
from keras.layers import add
|
||||
from keras.models import Model
|
||||
import keras.backend as K
|
||||
from keras.engine.topology import get_source_inputs
|
||||
from keras.utils import layer_utils
|
||||
from keras import initializers
|
||||
from keras.engine import Layer, InputSpec
|
||||
from keras.preprocessing import image
|
||||
from keras.utils.data_utils import get_file
|
||||
from keras.applications.imagenet_utils import decode_predictions
|
||||
from keras.applications.imagenet_utils import preprocess_input
|
||||
from keras.applications.imagenet_utils import _obtain_input_shape
|
||||
|
||||
import sys
|
||||
sys.setrecursionlimit(3000)
|
||||
|
||||
WEIGHTS_PATH = 'https://github.com/adamcasson/resnet152/releases/download/v0.1/resnet152_weights_tf.h5'
|
||||
WEIGHTS_PATH_NO_TOP = 'https://github.com/adamcasson/resnet152/releases/download/v0.1/resnet152_weights_tf_notop.h5'
|
||||
|
||||
class Scale(Layer):
|
||||
"""Custom Layer for ResNet used for BatchNormalization.
|
||||
|
||||
Learns a set of weights and biases used for scaling the input data.
|
||||
the output consists simply in an element-wise multiplication of the input
|
||||
and a sum of a set of constants:
|
||||
|
||||
out = in * gamma + beta,
|
||||
|
||||
where 'gamma' and 'beta' are the weights and biases larned.
|
||||
|
||||
Keyword arguments:
|
||||
axis -- integer, axis along which to normalize in mode 0. For instance,
|
||||
if your input tensor has shape (samples, channels, rows, cols),
|
||||
set axis to 1 to normalize per feature map (channels axis).
|
||||
momentum -- momentum in the computation of the exponential average
|
||||
of the mean and standard deviation of the data, for
|
||||
feature-wise normalization.
|
||||
weights -- Initialization weights.
|
||||
List of 2 Numpy arrays, with shapes:
|
||||
`[(input_shape,), (input_shape,)]`
|
||||
beta_init -- name of initialization function for shift parameter
|
||||
(see [initializers](../initializers.md)), or alternatively,
|
||||
Theano/TensorFlow function to use for weights initialization.
|
||||
This parameter is only relevant if you don't pass a `weights` argument.
|
||||
gamma_init -- name of initialization function for scale parameter (see
|
||||
[initializers](../initializers.md)), or alternatively,
|
||||
Theano/TensorFlow function to use for weights initialization.
|
||||
This parameter is only relevant if you don't pass a `weights` argument.
|
||||
|
||||
"""
|
||||
def __init__(self, weights=None, axis=-1, momentum = 0.9, beta_init='zero', gamma_init='one', **kwargs):
|
||||
self.momentum = momentum
|
||||
self.axis = axis
|
||||
self.beta_init = initializers.get(beta_init)
|
||||
self.gamma_init = initializers.get(gamma_init)
|
||||
self.initial_weights = weights
|
||||
super(Scale, self).__init__(**kwargs)
|
||||
|
||||
def build(self, input_shape):
|
||||
self.input_spec = [InputSpec(shape=input_shape)]
|
||||
shape = (int(input_shape[self.axis]),)
|
||||
|
||||
self.gamma = K.variable(self.gamma_init(shape), name='%s_gamma'%self.name)
|
||||
self.beta = K.variable(self.beta_init(shape), name='%s_beta'%self.name)
|
||||
self.trainable_weights = [self.gamma, self.beta]
|
||||
|
||||
if self.initial_weights is not None:
|
||||
self.set_weights(self.initial_weights)
|
||||
del self.initial_weights
|
||||
|
||||
def call(self, x, mask=None):
|
||||
input_shape = self.input_spec[0].shape
|
||||
broadcast_shape = [1] * len(input_shape)
|
||||
broadcast_shape[self.axis] = input_shape[self.axis]
|
||||
|
||||
out = K.reshape(self.gamma, broadcast_shape) * x + K.reshape(self.beta, broadcast_shape)
|
||||
return out
|
||||
|
||||
def get_config(self):
|
||||
config = {"momentum": self.momentum, "axis": self.axis}
|
||||
base_config = super(Scale, self).get_config()
|
||||
return dict(list(base_config.items()) + list(config.items()))
|
||||
|
||||
def identity_block(input_tensor, kernel_size, filters, stage, block):
|
||||
"""The identity_block is the block that has no conv layer at shortcut
|
||||
|
||||
Keyword arguments
|
||||
input_tensor -- input tensor
|
||||
kernel_size -- defualt 3, the kernel size of middle conv layer at main path
|
||||
filters -- list of integers, the nb_filters of 3 conv layer at main path
|
||||
stage -- integer, current stage label, used for generating layer names
|
||||
block -- 'a','b'..., current block label, used for generating layer names
|
||||
|
||||
"""
|
||||
eps = 1.1e-5
|
||||
|
||||
if K.image_dim_ordering() == 'tf':
|
||||
bn_axis = 3
|
||||
else:
|
||||
bn_axis = 1
|
||||
|
||||
nb_filter1, nb_filter2, nb_filter3 = filters
|
||||
conv_name_base = 'res' + str(stage) + block + '_branch'
|
||||
bn_name_base = 'bn' + str(stage) + block + '_branch'
|
||||
scale_name_base = 'scale' + str(stage) + block + '_branch'
|
||||
|
||||
x = Conv2D(nb_filter1, (1, 1), name=conv_name_base + '2a', use_bias=False)(input_tensor)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
|
||||
x = Activation('relu', name=conv_name_base + '2a_relu')(x)
|
||||
|
||||
x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
|
||||
x = Conv2D(nb_filter2, (kernel_size, kernel_size), name=conv_name_base + '2b', use_bias=False)(x)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
|
||||
x = Activation('relu', name=conv_name_base + '2b_relu')(x)
|
||||
|
||||
x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
|
||||
|
||||
x = add([x, input_tensor], name='res' + str(stage) + block)
|
||||
x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
|
||||
return x
|
||||
|
||||
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
|
||||
"""conv_block is the block that has a conv layer at shortcut
|
||||
|
||||
Keyword arguments:
|
||||
input_tensor -- input tensor
|
||||
kernel_size -- defualt 3, the kernel size of middle conv layer at main path
|
||||
filters -- list of integers, the nb_filters of 3 conv layer at main path
|
||||
stage -- integer, current stage label, used for generating layer names
|
||||
block -- 'a','b'..., current block label, used for generating layer names
|
||||
|
||||
Note that from stage 3, the first conv layer at main path is with subsample=(2,2)
|
||||
And the shortcut should have subsample=(2,2) as well
|
||||
|
||||
"""
|
||||
eps = 1.1e-5
|
||||
|
||||
if K.image_dim_ordering() == 'tf':
|
||||
bn_axis = 3
|
||||
else:
|
||||
bn_axis = 1
|
||||
|
||||
nb_filter1, nb_filter2, nb_filter3 = filters
|
||||
conv_name_base = 'res' + str(stage) + block + '_branch'
|
||||
bn_name_base = 'bn' + str(stage) + block + '_branch'
|
||||
scale_name_base = 'scale' + str(stage) + block + '_branch'
|
||||
|
||||
x = Conv2D(nb_filter1, (1, 1), strides=strides, name=conv_name_base + '2a', use_bias=False)(input_tensor)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
|
||||
x = Activation('relu', name=conv_name_base + '2a_relu')(x)
|
||||
|
||||
x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
|
||||
x = Conv2D(nb_filter2, (kernel_size, kernel_size),
|
||||
name=conv_name_base + '2b', use_bias=False)(x)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
|
||||
x = Activation('relu', name=conv_name_base + '2b_relu')(x)
|
||||
|
||||
x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
|
||||
x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
|
||||
|
||||
shortcut = Conv2D(nb_filter3, (1, 1), strides=strides,
|
||||
name=conv_name_base + '1', use_bias=False)(input_tensor)
|
||||
shortcut = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '1')(shortcut)
|
||||
shortcut = Scale(axis=bn_axis, name=scale_name_base + '1')(shortcut)
|
||||
|
||||
x = add([x, shortcut], name='res' + str(stage) + block)
|
||||
x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
|
||||
return x
|
||||
|
||||
def ResNet152(include_top=True, weights=None,
|
||||
input_tensor=None, input_shape=None,
|
||||
large_input=False, pooling=None,
|
||||
classes=1000):
|
||||
"""Instantiate the ResNet152 architecture.
|
||||
|
||||
Keyword arguments:
|
||||
include_top -- whether to include the fully-connected layer at the
|
||||
top of the network. (default True)
|
||||
weights -- one of `None` (random initialization) or "imagenet"
|
||||
(pre-training on ImageNet). (default None)
|
||||
input_tensor -- optional Keras tensor (i.e. output of `layers.Input()`)
|
||||
to use as image input for the model.(default None)
|
||||
input_shape -- optional shape tuple, only to be specified if
|
||||
`include_top` is False (otherwise the input shape has to be
|
||||
`(224, 224, 3)` (with `channels_last` data format) or
|
||||
`(3, 224, 224)` (with `channels_first` data format). It should
|
||||
have exactly 3 inputs channels, and width and height should be
|
||||
no smaller than 197. E.g. `(200, 200, 3)` would be one valid value.
|
||||
(default None)
|
||||
large_input -- if True, then the input shape expected will be
|
||||
`(448, 448, 3)` (with `channels_last` data format) or
|
||||
`(3, 448, 448)` (with `channels_first` data format). (default False)
|
||||
pooling -- Optional pooling mode for feature extraction when
|
||||
`include_top` is `False`.
|
||||
- `None` means that the output of the model will be the 4D
|
||||
tensor output of the last convolutional layer.
|
||||
- `avg` means that global average pooling will be applied to
|
||||
the output of the last convolutional layer, and thus
|
||||
the output of the model will be a 2D tensor.
|
||||
- `max` means that global max pooling will be applied.
|
||||
(default None)
|
||||
classes -- optional number of classes to classify image into, only
|
||||
to be specified if `include_top` is True, and if no `weights`
|
||||
argument is specified. (default 1000)
|
||||
|
||||
Returns:
|
||||
A Keras model instance.
|
||||
|
||||
Raises:
|
||||
ValueError: in case of invalid argument for `weights`,
|
||||
or invalid input shape.
|
||||
"""
|
||||
if weights not in {'imagenet', None}:
|
||||
raise ValueError('The `weights` argument should be either '
|
||||
'`None` (random initialization) or `imagenet` '
|
||||
'(pre-training on ImageNet).')
|
||||
|
||||
if weights == 'imagenet' and include_top and classes != 1000:
|
||||
raise ValueError('If using `weights` as imagenet with `include_top`'
|
||||
' as true, `classes` should be 1000')
|
||||
|
||||
eps = 1.1e-5
|
||||
|
||||
if large_input:
|
||||
img_size = 448
|
||||
else:
|
||||
img_size = 224
|
||||
|
||||
# Determine proper input shape
|
||||
input_shape = _obtain_input_shape(input_shape,
|
||||
default_size=img_size,
|
||||
min_size=197,
|
||||
data_format=K.image_data_format(),
|
||||
require_flatten=include_top)
|
||||
|
||||
if input_tensor is None:
|
||||
img_input = Input(shape=input_shape)
|
||||
else:
|
||||
if not K.is_keras_tensor(input_tensor):
|
||||
img_input = Input(tensor=input_tensor, shape=input_shape)
|
||||
else:
|
||||
img_input = input_tensor
|
||||
|
||||
# handle dimension ordering for different backends
|
||||
if K.image_dim_ordering() == 'tf':
|
||||
bn_axis = 3
|
||||
else:
|
||||
bn_axis = 1
|
||||
|
||||
x = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input)
|
||||
x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=False)(x)
|
||||
x = BatchNormalization(epsilon=eps, axis=bn_axis, name='bn_conv1')(x)
|
||||
x = Scale(axis=bn_axis, name='scale_conv1')(x)
|
||||
x = Activation('relu', name='conv1_relu')(x)
|
||||
x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x)
|
||||
|
||||
x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
|
||||
x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
|
||||
x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
|
||||
|
||||
x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
|
||||
for i in range(1,8):
|
||||
x = identity_block(x, 3, [128, 128, 512], stage=3, block='b'+str(i))
|
||||
|
||||
x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
|
||||
for i in range(1,36):
|
||||
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b'+str(i))
|
||||
|
||||
x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
|
||||
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
|
||||
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
|
||||
|
||||
if large_input:
|
||||
x = AveragePooling2D((14, 14), name='avg_pool')(x)
|
||||
else:
|
||||
x = AveragePooling2D((7, 7), name='avg_pool')(x)
|
||||
|
||||
# include classification layer by default, not included for feature extraction
|
||||
if include_top:
|
||||
x = Flatten()(x)
|
||||
x = Dense(classes, activation='softmax', name='fc1000')(x)
|
||||
else:
|
||||
if pooling == 'avg':
|
||||
x = GlobalAveragePooling2D()(x)
|
||||
elif pooling == 'max':
|
||||
x = GlobalMaxPooling2D()(x)
|
||||
|
||||
# Ensure that the model takes into account
|
||||
# any potential predecessors of `input_tensor`.
|
||||
if input_tensor is not None:
|
||||
inputs = get_source_inputs(input_tensor)
|
||||
else:
|
||||
inputs = img_input
|
||||
# Create model.
|
||||
model = Model(inputs, x, name='resnet152')
|
||||
|
||||
# load weights
|
||||
if weights == 'imagenet':
|
||||
if include_top:
|
||||
weights_path = get_file('resnet152_weights_tf.h5',
|
||||
WEIGHTS_PATH,
|
||||
cache_subdir='models',
|
||||
md5_hash='cdb18a2158b88e392c0905d47dcef965')
|
||||
else:
|
||||
weights_path = get_file('resnet152_weights_tf_notop.h5',
|
||||
WEIGHTS_PATH_NO_TOP,
|
||||
cache_subdir='models',
|
||||
md5_hash='4a90dcdafacbd17d772af1fb44fc2660')
|
||||
model.load_weights(weights_path, by_name=True)
|
||||
if K.backend() == 'theano':
|
||||
layer_utils.convert_all_kernels_in_model(model)
|
||||
if include_top:
|
||||
maxpool = model.get_layer(name='avg_pool')
|
||||
shape = maxpool.output_shape[1:]
|
||||
dense = model.get_layer(name='fc1000')
|
||||
layer_utils.convert_dense_weights_data_format(dense, shape, 'channels_first')
|
||||
|
||||
if K.image_data_format() == 'channels_first' and K.backend() == 'tensorflow':
|
||||
warnings.warn('You are using the TensorFlow backend, yet you '
|
||||
'are using the Theano '
|
||||
'image data format convention '
|
||||
'(`image_data_format="channels_first"`). '
|
||||
'For best performance, set '
|
||||
'`image_data_format="channels_last"` in '
|
||||
'your Keras config '
|
||||
'at ~/.keras/keras.json.')
|
||||
return model
|
||||
|
||||
if __name__ == '__main__':
|
||||
model = ResNet152(include_top=True, weights='imagenet')
|
||||
|
||||
img_path = 'elephant.jpg'
|
||||
img = image.load_img(img_path, target_size=(224,224))
|
||||
x = image.img_to_array(img)
|
||||
x = np.expand_dims(x, axis=0)
|
||||
x = preprocess_input(x)
|
||||
print('Input image shape:', x.shape)
|
||||
|
||||
preds = model.predict(x)
|
||||
print('Predicted:', decode_predictions(preds))
|
|
@ -0,0 +1,98 @@
|
|||
import base64
|
||||
import json
|
||||
import urllib
|
||||
from io import BytesIO
|
||||
|
||||
import matplotlib.gridspec as gridspec
|
||||
import matplotlib.pyplot as plt
|
||||
import toolz
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
||||
def read_image_from(url):
|
||||
return toolz.pipe(url,
|
||||
urllib.request.urlopen,
|
||||
lambda x: x.read(),
|
||||
BytesIO)
|
||||
|
||||
|
||||
def to_rgb(img_bytes):
|
||||
return Image.open(img_bytes).convert('RGB')
|
||||
|
||||
|
||||
@toolz.curry
|
||||
def resize(img_file, new_size=(100, 100)):
|
||||
return ImageOps.fit(img_file, new_size, Image.ANTIALIAS)
|
||||
|
||||
|
||||
def to_base64(img):
|
||||
imgio = BytesIO()
|
||||
img.save(imgio, 'PNG')
|
||||
imgio.seek(0)
|
||||
dataimg = base64.b64encode(imgio.read())
|
||||
return dataimg.decode('utf-8')
|
||||
|
||||
|
||||
def to_img(img_url):
|
||||
return toolz.pipe(img_url,
|
||||
read_image_from,
|
||||
to_rgb,
|
||||
resize(new_size=(224,224)))
|
||||
|
||||
|
||||
def img_url_to_json(url):
|
||||
img_data = toolz.pipe(url,
|
||||
to_img,
|
||||
to_base64)
|
||||
return json.dumps({'input':'[\"{0}\"]'.format(img_data)})
|
||||
|
||||
def img_url_to_json_dict(url):
|
||||
img_url_name = url.split('/')[-1]
|
||||
encoded = to_base64(to_img(url))
|
||||
img_dict = {img_url_name: encoded}
|
||||
body = json.dumps(img_dict)
|
||||
return json.dumps({'input':'{}'.format(body)})
|
||||
|
||||
|
||||
def _plot_image(ax, img):
|
||||
ax.imshow(to_img(img))
|
||||
ax.tick_params(axis='both',
|
||||
which='both',
|
||||
bottom='off',
|
||||
top='off',
|
||||
left='off',
|
||||
right='off',
|
||||
labelleft='off',
|
||||
labelbottom='off')
|
||||
return ax
|
||||
|
||||
|
||||
def _plot_prediction_bar_dict(ax, r):
|
||||
res_dict = eval(r.json()['result'])[0][0]
|
||||
perf = list(c[2] for c in list(res_dict.values())[0])
|
||||
ax.barh(range(3, 0, -1), perf, align='center', color='#55DD55')
|
||||
ax.tick_params(axis='both',
|
||||
which='both',
|
||||
bottom='off',
|
||||
top='off',
|
||||
left='off',
|
||||
right='off',
|
||||
labelbottom='off')
|
||||
tick_labels = reversed(list(c[1] for c in list(res_dict.values())[0]))
|
||||
ax.yaxis.set_ticks([1,2,3])
|
||||
ax.yaxis.set_ticklabels(tick_labels, position=(0.5,0), minor=False, horizontalalignment='center')
|
||||
|
||||
|
||||
def plot_predictions_dict(images, classification_results):
|
||||
if len(images)!=6:
|
||||
raise Exception('This method is only designed for 6 images')
|
||||
gs = gridspec.GridSpec(2, 3)
|
||||
fig = plt.figure(figsize=(12, 9))
|
||||
gs.update(hspace=0.1, wspace=0.001)
|
||||
|
||||
for gg,r, img in zip(gs, classification_results, images):
|
||||
gg2 = gridspec.GridSpecFromSubplotSpec(4, 10, subplot_spec=gg)
|
||||
ax = fig.add_subplot(gg2[0:3, :])
|
||||
_plot_image(ax, img)
|
||||
ax = fig.add_subplot(gg2[3, 1:9])
|
||||
_plot_prediction_bar_dict(ax, r)
|
Загрузка…
Ссылка в новой задаче