На популярном в узких кругах ресурсе kaggle.com недавно проходил конкурс от компании Draper. Суть его заключалась в определении хронологического порядка снимков, снятых со спутника. Draper предоставил снимки в высоком разрешении и низком, общим объемом около 35 ГБ. Сет данных для обучения — это набор фотографий разных мест в хронологическом порядке. Для каждого места сделано 5 снимков. В тестовом сете снимки идут в случайном порядке.
Много времени уделить конкурсу я не мог, а поучаствовать хотел, поэтому реализовал быстрое решение, финальный результат которого попал в топ 40 мест в leaderboard.
Исходный код решения загружен в репозиторий на githab.com.
Ниже кратко опишу порядок работы.
Изображения
Первым этапом уменьшим размер изображения и сразу убрать цвета на них.
1 2 3 4 5 | r = 750.0 / image.shape[1] dim = (750, int(image.shape[0] * r)) resized = cv2.resize(image, dim, interpolation=cv2.INTER_AREA) resized = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY) cv2.imwrite(path_new + filename, resized) |
Далее я решил высчитывать меры сходства изображений. Для этого существует ряд методов, например, сравнение SIFT-дескрипторов или ORB-дескрипторов. Я использовал последние. Код методов сравнение в файле utils.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def match_bf(img1path, img2path, num_keypoints=1000): """ Match two images using ORB :param img1path: :param img2path: :return: """ orb = cv2.ORB(num_keypoints, 1.2) img_from = cv2.imread(img1path) img_to = cv2.imread(img2path) # comparision (kp1, des1) = orb.detectAndCompute(img_from, None) (kp2, des2) = orb.detectAndCompute(img_to, None) # matcher bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) # sort matches matches = sorted(matches, key=lambda val: val.distance) return matches |
Подготовка сета
Для обучения алгоритма необходимо подготовить сет. Я сгенерировал множество цепочек файлов, которые были негативными примерами. Затем рассчитывал совпадающие дескрипторы для фотографий и собрал из них векторы. Для каждой цепочки по вектору. Подготовка описана в файле prepareset.py .
Далее, я добавил штрафной коэффициент на мало совпадающие дескрипторы, обратно пропорциональный от позиции в векторе.
1 2 3 4 5 6 7 8 9 | def order_importance(dataset): new_X = [] for row in dataset: nrow = [] for idx, elem in enumerate(row): koeff = (100 / ((idx % 100) + 0.01) ** 2) nrow.append(elem * koeff) new_X.append(nrow) return new_X |
Нейросеть
В качестве алгоритма для обучения я выбрал нейросеть и реализовал ее при помощи библиотеки keras. Нейросеть следующей архитектуры:
1 2 3 4 5 6 7 8 9 10 | model = Sequential() model.add(Dense(400, input_dim=400, init='normal', activation='relu')) model.add(Dropout(0.2)) model.add(Dense(150, init='normal', activation='relu')) model.add(Dropout(0.2)) model.add(Dense(1, init='normal', activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(X, Y, validation_split=0.33, nb_epoch=300, batch_size=5, verbose=1) scores = model.evaluate(X, Y) print("%s: %.2f%%" % (model.metrics_names[1], scores[1]*100)) |
Нейронная сеть оценивает то, какая из сгенерированных цепочек является наиболее подходящей. Результат работы сети будет представлять собой список с этой вероятностью:
8.112269607767741018e-27
7.505920423548040978e-18
0.000000000000000000e+00
2.311770085126284875e-21
9.976116418838500977e-01
0.000000000000000000e+00
4.197147265218103152e-25
6.884424436637920689e-35
Последним этапом в формировании сабмишена будет выбор лучших цепочек и выставление порядка фотографий. Этот процесс описан в файле submit.py. Фрагмент результата представлен ниже.
day,setId
2 3 1 5 4,1
3 1 2 5 4,100
5 4 3 2 1,101
2 5 1 3 4,102
4 3 2 1 5,103