Это моё решение тестового задания по вакансии от MYTONA. Сами задания и другие файлы расположены в репозитории: https://github.com/PasaOpasen/Garbage_code/tree/master/mytona

Задание 1

Зафиксируем нашу систему в некоторый момент времени. Схематически она будет выглядеть как-то так:

Здесь пунктирной линией изображен условный горизонт (земля), \(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}\)

Задание 2

Чтобы модель хотя бы начала работать для регрессии, нужно

  1. Поставить функцию потерь для регрессии (вместо categorical_crossentropy поставить, например, mean_squared_error)

  2. Убрать из метрик правильность (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()

Задание 3

Сразу переведём задачу на язык математики: имеется ансамбль из 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

Алгоритм симуляции заключается в следующем:

  1. для каждого классификатора проводим \(n\) симуляций, то есть получаем \(n\) случайных чисел из биномиального распределения с соответствующей вероятностью позитивного исхода

  2. для каждой конфигурации весов и всех симуляций проводим суммирование голосов и находим среднее число позитивных исходов у каждой конфигурации (позитивный исход - это когда сумма ответов по ансамблю больше чем 2)

  3. выбираем конфигурацию с наибольшим средним числом позитивных исходов

Пример

Сделаем 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 из последнего