Это моё решение тестового задания по вакансии от MYTONA. Сами задания и другие файлы расположены в репозитории: https://github.com/PasaOpasen/Garbage_code/tree/master/mytona
Зафиксируем нашу систему в некоторый момент времени. Схематически она будет выглядеть как-то так:
Здесь пунктирной линией изображен условный горизонт (земля), \(R_1\), \(R_2\) - расстояния от земли до левого и правого конца оси. Из условия задачи помним, что эти расстояния взяты из независимых нормальных распределений со средним \(R\) и дисперсиями \(n\) и \(m\) соответственно.
Требуется определить положение точки C, на которой меньше всего трясёт (дисперсия минимальна).
Сначала выразим \(R_3\) через \(R_1, R_2\). Поскольку здесь прямоугольная трапеция, это можно сделать через формулу её площади:
\[S = \frac{1}{2} (R_1 + R_2) \cdot H = \frac{1}{2} (R_1 + R_3) \cdot H_1 + \frac{1}{2} (R_3 + R_2) \cdot H_2 \]
Если умножить на 2 и раскрыть скобки, получим:
\[R_1H_1 + R_1H_2+R_2H_1+R_2H_2 = R_1H_1 + R_3H_1+R_3H_2+R_2H_2\]
\[R_1H_2+R_2H_1 = R_3H_1+R_3H_2\]
Известно, что \(\frac{AC}{H_1} = \frac{BC}{H_2} \rightarrow \frac{AC}{BC} = \frac{H_1}{H_2}\). Поделим последнее полученное уравнение на \(H_2\) и обозначим искомое соотношение за \(k\):
\[k = \frac{H_1}{H_2} = \frac{AC}{BC}\] \[R_1+R_2 \cdot k = R_3 \cdot k+R_3 \rightarrow R_3 = \frac{R_1+R_2 \cdot k}{1 + k}\]
Найдём дисперсию \(R_3\), используя свойста дисперсии для независимых случайных величин:
\[D(R_3) = D(\frac{R_1+R_2 \cdot k}{1 + k}) = \frac{1}{(1+k)^2} D(R_1+R_2 \cdot k) = \frac{1}{(1+k)^2} (D(R_1)+D(R_2) \cdot k^2) = \frac{n+ k^2 m}{(1+k)^2}\]
Чтобы найти минимум этой функции, требуется найти точки экстремума (нули производной). Я сразу буду избавляться от слагаемых, которые ни на что не влияют:
\[(\frac{n+ k^2 m}{(1+k)^2} )' = \frac{2km(1+k)^2 - 2(k+1)(n+k^2m)}{(1+k)^4} = 0\]
\[2km(1+k) - 2(n+k^2m) = 0\]
\[2km + 2k^2m - 2n - 2k^2 m = 0\]
\[k = \frac{n}{m}\]
Нетрудно проверить, что это именно точка минимума.
Ответ: \(\frac{n}{m}\)
Чтобы модель хотя бы начала работать для регрессии, нужно
Поставить функцию потерь для регрессии (вместо categorical_crossentropy поставить, например, mean_squared_error)
Убрать из метрик правильность (acc), так как это метрика для классификаторов.
Сейчас модель выдаёт два числа с суммой 1 (вероятности принадлежности к каждому из двух классов). Для осмысленной регрессии потребуется на последнем слое заменить функцию активации (например, на relu, но зависит от задачи) и установить один нейрон вместо двух (если требуется).
Таким образом, я бы предложил следующий код:
from keras.models import Sequential
from keras.layers import Dense
from keras.layers.normalization import BatchNormalization
model = Sequential()
neurons_count = 32
model.add(Dense(neurons_count, input_dim=X_train.shape[1], activation='relu'))
model.add(BatchNormalization())
model.add(Dense(1, activation='relu'))
model.compile(loss='mean_squared_error', optimizer='adam')
model.summary()
Сразу переведём задачу на язык математики: имеется ансамбль из 5 классификаторов, каждый из которых выдаёт правильный ответ 1 со своей вероятностью. При этом каждый классификатор может иметь целый вес от 0 до ‘5’, причем сумма всех весов равна 5. Требуется подобрать оптимальные веса (чтоб точность ансамбля была максимальной).
Решим эту задачу, проделав большое число симуляций.
Первым делом запишем вероятности:
probs = c(0.8, 0.7, 0.6, 0.5, 0.3)
print(probs)
## [1] 0.8 0.7 0.6 0.5 0.3
Определим всевозможные конфигурации весов, подходящие условию (все комбинации 5 чисел от 0 до 5, дающих в сумме 5):
library(tidyr)
facilities = 0:5
all_combs = crossing(coef1 = facilities, coef2 = facilities, coef3 = facilities,
coef4 = facilities, coef5 = facilities)
is5 = apply(all_combs, 1, function(row) sum(row) == 5)
all_combs = all_combs[is5, ]
all_combs
Алгоритм симуляции заключается в следующем:
для каждого классификатора проводим \(n\) симуляций, то есть получаем \(n\) случайных чисел из биномиального распределения с соответствующей вероятностью позитивного исхода
для каждой конфигурации весов и всех симуляций проводим суммирование голосов и находим среднее число позитивных исходов у каждой конфигурации (позитивный исход - это когда сумма ответов по ансамблю больше чем 2)
выбираем конфигурацию с наибольшим средним числом позитивных исходов
Сделаем 10 симуляций с нашими классификаторами:
sim_results = sapply(probs, function(p) rbinom(10, 1, p))
sim_results
## [,1] [,2] [,3] [,4] [,5]
## [1,] 0 1 1 1 0
## [2,] 1 1 0 1 0
## [3,] 0 1 1 0 0
## [4,] 1 0 1 1 1
## [5,] 0 1 0 1 0
## [6,] 1 1 0 0 0
## [7,] 1 1 0 0 0
## [8,] 1 0 0 0 1
## [9,] 1 1 0 1 0
## [10,] 1 1 1 1 1
Возьмем конфигурацию номер 67:
comb = all_combs[67, ]
comb
Перемножим полученные матрицу и вектор:
ans = sim_results %*% t(comb)
ans
## [,1]
## [1,] 3
## [2,] 2
## [3,] 2
## [4,] 5
## [5,] 1
## [6,] 1
## [7,] 1
## [8,] 2
## [9,] 2
## [10,] 5
Преобразуем ответ в категории правильно-неправильно:
ans = ans > 2
ans
## [,1]
## [1,] TRUE
## [2,] FALSE
## [3,] FALSE
## [4,] TRUE
## [5,] FALSE
## [6,] FALSE
## [7,] FALSE
## [8,] FALSE
## [9,] FALSE
## [10,] TRUE
Найдём среднее количество верных ответов, это и будет оценка нашей конфигурации:
mean(ans)
## [1] 0.3
Симуляцию выполняет следующая функция:
get_best_combination = function(count) {
sim_results = sapply(probs, function(p) rbinom(count, 1, p))
answers = numeric(nrow(all_combs))
for (i in 1:nrow(all_combs)) {
comb = all_combs[i, ]
nums = (sim_results %*% t(comb)) > 2
answers[i] = mean(nums)
}
result = as.numeric(all_combs[which.max(answers), ])
cat("best accuracy:", max(answers), "on", result, "combination\n")
return(result)
}
Запустим 10 млн симуляций и получим ответ:
get_best_combination(10^7)
## best accuracy: 0.8001093 on 3 0 0 0 2 combination
## [1] 3 0 0 0 2
Ответ неожиданный, но я получал его много раз. Его можно объяснить тем, что раз пятый классификатор так часто ошибается, его мнение очень необычное и при некотором раскладе весов может быть полезно.
Ответ: 3 из первого, 2 из последнего