Здесь я демонстрирую результаты простой многоуровневой модели для распознавания цифр с помощью ансамблевого обучения. Этот проект направлен на определение одиночных цифр в проблеме классификации строковых цифр. В моем случае набор данных был частным, но здесь я разделяю общий подход.
Используя ансамблевое обучение, мы могли бы извлечь выгоду из производительности комбинированной модели, здесь это простой метод усреднения:
Общий подход к подготовке изображений заключается в следующем: измените масштаб каждого изображения до фиксированного размера (28 * n) * 28, где n - количество цифр, содержащихся в строке, а затем предскажите результат с помощью метода скользящего окна:
def prepare_image( img, show = False): """ prepares the training and testing as well as the partial images used in partial_img_rec by transforming them into numpy arrays that the network will be able to process """ # convert to greyscale img = img.convert("L") # rescale image to 28 * 28 dimension if neccessary if img.size != (28,28): img = img.resize((28,28), PIL.Image.ANTIALIAS) # transform to vector img = np.asarray(img, "float32") img = img / 255. # threshold eliminates background noise img[img < 0.4] = 0. if show: plt.imshow(img, cmap = "Greys") img = img.reshape((1, 28, 28, 1)) return img def predict_result( img, show = False): """ predicts the number in a picture (vector) """ #assert type(img) == np.ndarray and img.shape == (1, 28, 28, 1) # 1, 28, 28, 1 is the image shape the input layer demands. 28, 28 are the dimensions and 1 stands for greyscale (channel) if show: img = img.reshape((28, 28)) # show the picture plt.imshow(img, cmap='Greys') plt.show() img = img.reshape((1, 28, 28, 1)) # the probabilities res_probabilities = predict_stacked_model(model, img) # the value with the hightest probability res_number = np.argmax(res_probabilities) return res_number, res_probabilities def test_all(): """ evaluates the success rate using all the test data """ scores = model.evaluate(test_img, test_res, verbose=0) print("Baseline Error: %.2f%%" % (100-scores[1]*100)) def partial_img_rec( image, upper_left, lower_right, results=[], show = True): """ passes square parts of images to predict_result """ left_x, left_y = upper_left right_x, right_y = lower_right print("current test part: ", upper_left, lower_right) print("results: ", results) # condition to stop recursion: we've reached the full width of the picture width, height = image.size if right_x > width: return results partial = image.crop((left_x, left_y, right_x, right_y)) if show: partial.show() partial = prepare_image(partial) # is there a number or operator in this part of the image? res, prob = predict_result(partial) print("result: ", res, ". probabilities: ", prob) # only count this result if the network is at least 40% sure if prob[0][res] >= 0.4: step = int(height * 1) results.append(res) # step is 80% of the partial image's size (which is equivalent to the original image's height) print("found valid result") else: # if there is no number or operator found we take smaller steps step = height // 20 #print("step: ", step) # recursive call with modified positions ( move on step variables ) return partial_img_rec(image, (left_x + step, left_y), (right_x + step, right_y), results = results) def individual_digits( img): """ uses partial_img_rec to predict individual digits in square images """ #assert type(img) == PIL.JpegImagePlugin.JpegImageFile or type(img) == PIL.PngImagePlugin.PngImageFile or type(img) == PIL.Image.Image if img.size[0] != img.size[1]: print(img, " has the wrong proportions: ", img.size,". It has to be a square.") return partial_img_rec(img, (0,0), (img.size[0], img.size[1]), results=[]) def multiple_digits( img): """ takes as input an image without unnecessary whitespace surrounding the digits """ #assert type(imgName) == str #img = cuttingImage(imgName) #img = PIL.Image.open(imgName) width, height = img.size # start with the first square part of the image. This can work because there is no unneccessary whitespace. res_list = partial_img_rec(img, (0,0),(height ,height), results = []) res_str = "" for elem in res_list: res_str += str(elem) return res_str
Для создания модели я объединил простую сверточную сеть с сетью LSTM, чтобы создать окончательную интегрированную модель:
В этом подходе мы обучаем каждую сеть отдельно, затем сохраняем веса сетей, и на заключительном этапе создания модели с накоплением мы загружаем веса первого этапа, а затем обучаем модель с накоплением:
# load models from file def load_all_models( n_models): all_models = list() for i in range(n_models): # define filename for this ensemble filename = 'model_' + str(i + 1) + '.h5' # load model from file model = load_model(filename) # add to list of members all_models.append(model) print('>loaded %s' % filename) return all_models # define stacked model from multiple member input models def define_stacked_model( members): # update all layers in all models to not be trainable for i in range(len(members)): model = members[i] for layer in model.layers: # make not trainable layer.trainable = False # rename to avoid 'unique layer name' issue layer.name = 'ensemble_' + str(i+1) + '_' + layer.name # define multi-headed input ensemble_visible = [model.input for model in members] # concatenate merge output from each model ensemble_outputs = [model.output for model in members] merge = concatenate(ensemble_outputs) #print(ensemble_visible.shape, ensemble_outputs.shape) print(merge.shape) output = Dense(10, activation='softmax')(merge) #print(hidden.shape) #output = Dense(10, activation='softmax')(hidden) model = Model(inputs=ensemble_visible, outputs=output) # plot graph of ensemble plot_model(model, show_shapes=True, to_file='model_graph.png') # compile model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) return model # fit a stacked model def fit_stacked_model( model, inputX, inputy): # prepare input data X = [inputX for _ in range(len(model.input))] # encode output data #inputy_enc = to_categorical(inputy) # fit model model.fit(X, inputy, epochs=10, verbose=1) # make a prediction with a stacked model def predict_stacked_model(model, inputX): # prepare input data X = [inputX for _ in range(len(model.input))] # make prediction return model.predict(X, verbose=0)
После определения необходимых функций, наконец, мы обучаем финальную модель:
from sklearn.metrics import accuracy_score from keras.models import load_model from keras.utils import to_categorical from keras.utils import plot_model from keras.layers.merge import concatenate from numpy import argmax n_members = 2 members = load_all_models(n_members) print('Loaded %d models' % len(members)) # define ensemble model model = define_stacked_model(members) # fit stacked model on test dataset fit_stacked_model(model, X_train, Y_train)
Результаты для простого использования Convloutional Network:
И результаты использования модели с накоплением следующие:
Как мы видим в этом проекте по распознаванию цифр, в большинстве случаев на 10 процентов производительность возросла, что действительно является хорошим результатом.
Ссылки:
1- https://keras.io/examples/mnist_hierarchical_rnn/
2- https://machinelearningmastery.com/stacking-ensemble-for-deep-learning-neural-networks/