Науки о данных

И. В. Щуров, НИУ ВШЭ

На странице курса находятся дополнительные материалы.

Домашнее задание №6

За разные задачи можно получить разное число баллов. Если не указано обратное, задача весит 1 балл. Максимум за ДЗ можно набрать 13 баллов.

Чтобы сдать ДЗ, его надо загрузить в nbgr-x в виде ipynb-файла. Получить ipynb-файл можно, выбрав в Jupyter пункт меню File → Download as... → IPython Notebook (.ipynb).

In [ ]:
import numpy as np

Задача 1 (2 балла)

В задачах машинного обучения часто требуется нормализовать данные перед тем, как их использовать. Пусть в переменной X находится двумерный np.array, по строкам которого записаны разные объекты, а по столбцам — признаки. Вам необходимо написать функцию normalize(X), принимающую на вход массив X и нормализующий все переменные таким образом, чтобы их среднее равнялось 0, а стандартное отклонение 1. Иными словами, для каждого столбца необходимо из всех элементов вычесть среднее по этому столбцу и результат разделить на стандартное отклонение по этому столбцу. Более формально: если $X=(x_{ij})$ — наша матрица, $x_{ij}$ — элемент, который стоит в её $i$-й строке и $j$-м столбце, и $x_{\cdot j}$ — $j$-й столбец, то в новой матрице на $i$-й строке в $j$-м столбце будет стоять элемент

$$\widehat{x}_{ij}=\frac{x_{ij}-\overline{x_{\cdot j}}}{\sigma_{x_{\cdot j}}},$$

где $\overline{x_{\cdot j}}$ — выборочное среднее (среднее арифметическое) всех элементов $j$-го столбца, $\sigma_{x_{\cdot j}}$ — стандартное отклонение всех элементов $j$-го столбца.

Циклы использовать по-прежнему нельзя.

Подсказка. Вычислить среднее можно с помощью метода .mean(), стандартное отклонение — с помощью .std(). Обе функции принимают на вход параметр axis, с помощью которого можно применять их к строкам или столбцам двумерного массива. Использовать циклы, как обычно, запрещено. Задачу можно решить в одну строчку.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
assert np.isclose(normalize(np.array([[ 1.00766597, -1.1201796 ,  2.47274732, -0.33619288,  1.50555214],
       [ 1.48986823,  0.80894409,  0.55980545,  0.67813423, -0.3187493 ]])), np.array([[-1., -1.,  1., -1.,  1.],
       [ 1.,  1., -1.,  1., -1.]])).all()
assert np.isclose(normalize(np.array([[-0.98607026],
       [ 1.93312384],
       [-0.99905497],
       [-0.95934573],
       [ 0.05295053]])), np.array([[-0.69959273],
       [ 1.87124093],
       [-0.71102792],
       [-0.67605736],
       [ 0.21543708]])).all()
assert np.isclose(normalize(np.array([[-1.63419424],
       [ 0.39451389],
       [-0.11346483],
       [ 0.56117231],
       [ 0.35460207],
       [ 1.50836012],
       [ 0.5176692 ],
       [-1.20605276],
       [ 0.7904588 ],
       [ 1.28349441]])), np.array([[-1.9874883 ],
       [ 0.15738144],
       [-0.37968359],
       [ 0.33358254],
       [ 0.11518431],
       [ 1.33500529],
       [ 0.28758849],
       [-1.53483191],
       [ 0.57599773],
       [ 1.09726401]])).all()
assert np.isclose(normalize(np.array([[-1.31158329,  2.5954087 , -1.01662736, -0.27565263,  0.52639556,
         0.58218805, -0.35961103,  0.31096071,  0.52193677, -0.41754881],
       [-0.19218836, -0.03416295,  0.80408723, -1.18733572,  0.14422448,
         0.6091103 ,  0.67617586,  0.17732224,  0.99660189, -0.07798097]])), np.array([[-1.,  1., -1.,  1.,  1., -1., -1.,  1., -1., -1.],
       [ 1., -1.,  1., -1., -1.,  1.,  1., -1.,  1.,  1.]])).all()
assert np.isclose(normalize(np.array([[-0.28368534, -0.90928588, -1.35180963],
       [ 1.30199557,  1.32081835,  1.11951334]])), np.array([[-1., -1., -1.],
       [ 1.,  1.,  1.]])).all()
assert np.isclose(normalize(np.array([[-0.34089722,  0.93727935],
       [ 0.14410815, -0.96321317],
       [-1.98355493, -0.0310602 ]])), np.array([[ 0.42383229,  1.23244371],
       [ 0.95653353, -1.21689804],
       [-1.38036582, -0.01554567]])).all()
assert np.isclose(normalize(np.array([[ 1.53033913,  0.05456373,  0.22504087, -1.16687133, -0.23619502],
       [-0.81477156,  1.96405223, -1.5506048 , -2.08082958, -0.23459537],
       [-0.80961303, -0.55950949, -1.07953561,  0.571387  , -1.03341414],
       [ 0.10526012, -2.06172783, -1.1661957 , -1.00297227, -1.02432731],
       [ 0.04661   , -0.21104596, -0.84339233,  0.22806353, -0.34655384]])), np.array([[ 1.77181211,  0.16828692,  1.84979571, -0.49193269,  0.90886342],
       [-0.96400966,  1.64710001, -1.11468608, -1.43524085,  0.91315434],
       [-0.95799169, -0.30728521, -0.32822509,  1.30214625, -1.22961355],
       [ 0.10930543, -1.47068586, -0.47290614, -0.32277035, -1.20523884],
       [ 0.04088381, -0.03741586,  0.0660216 ,  0.94779764,  0.61283463]])).all()

Задача 2 (3 балла)

В двумерном массиве scores записаны баллы нескольких студентов, строка — студент, столбец — домашнее задание. Имеется также массив max_scores, который содержит столько же элементов, сколько столбцов в scores: в нём написано максимальное число баллов, которые можно было получить за соответствующее домашнее задание. Теоретически, студент мог нарешать задач на большее количество баллов, но те баллы, которые набраны сверх максимального, в зачёт не идут. Оценка за домашнюю работу является вещественным числом от 0 до 10 и определяется как набранные баллы / максимальное число баллов × 10. Например, если максимальное число баллов за какую-то домашнюю работу равно 8, а студент набрал за неё 4 балла, то есть оценка равна 4/8×10=5. А если бы он набрал 12 баллов, то в зачёт бы пошло 8 баллов и оценкой было бы число 10. Оценка по курсу вычисляется как среднее арифметическое от всех оценок за домашние работы, округлённое до целого числа с помощью функциюю np.round. Написать функцию get_grades(scores, max_scores), возвращающую массив итоговых оценок. Запрещено использовать циклы и if'ы.

Подсказка. Вам пригодится функция np.minimum.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 1])), np.array([ 10.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 2])), np.array([ 10.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 3])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([1, 6])), np.array([  7.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 1, 10])), np.array([ 6.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 1])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 2])), np.array([  8.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 3])), np.array([  6.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([2, 6])), np.array([  4.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 2, 10])), np.array([ 4.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 1])), np.array([  7.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 2])), np.array([  7.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 3])), np.array([  5.,  10.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([3, 6])), np.array([  3.,   8.,  10.])).all()
assert np.isclose(get_grades(np.array([[1, 2],
       [3, 4],
       [5, 6]]), np.array([ 3, 10])), np.array([ 3.,  7.,  8.])).all()
assert np.isclose(get_grades(np.array([[  9.,   9.,  10.],
       [  1.,   9.,   0.],
       [  1.,   3.,  10.],
       [  5.,   5.,   2.],
       [  3.,   9.,   3.]]), np.array([ 9.,  9.,  2.])), np.array([ 10.,   4.,   5.,   7.,   8.])).all()
assert np.isclose(get_grades(np.array([[  8.,   3.,   5.,  10.,   4.],
       [  9.,   0.,   5.,  10.,   6.],
       [  0.,   1.,   7.,   2.,   9.]]), np.array([ 9.,  3.,  3.,  2.,  6.])), np.array([ 9.,  8.,  7.])).all()
assert np.isclose(get_grades(np.array([[ 6.,  4.,  2.,  7.,  0.],
       [ 8.,  1.,  4.,  4.,  8.],
       [ 1.,  3.,  5.,  5.,  3.],
       [ 2.,  5.,  3.,  4.,  8.],
       [ 7.,  0.,  7.,  1.,  8.]]), np.array([ 5.,  5.,  8.,  3.,  8.])), np.array([ 6.,  7.,  6.,  8.,  6.])).all()

Задача 3 (1 балл)

Рассмотрим дискретные случайные величины $X$ и $Y$, заданные на одном и том же вероятностном пространства и принимающие значения из какого-то конечного подмножества множества целых неотрицательных чисел. Пусть они независимы и известны их маргинальные распределения: в массивах x_probs и y_probs содержатся вероятности: для всех целых неотрицательных k, x_probs[k] это $\mathbb P(X=k)$ и y_probs[k] это $\mathbb P(Y=k)$. Напишите функцию make_joint_indep(x_probs, y_probs), возвращающую матрицу xy_probs, в которой записано совместное распределение этих случайных величин, то есть xy_probs[k, m] — это $\mathbb P((X=k) \cap (Y=m))$.

Циклы использовать по-прежнему нельзя.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
assert np.isclose(
    make_joint_indep(np.array([0.1, 0.2, 0.7]), np.array([0.4, 0.6])),
    np.array([[0.04, 0.06], [0.08, 0.12], [0.28, 0.42]]),
).all()
assert np.isclose(
    make_joint_indep(np.array([0.1, 0.9]), np.array([1])),
    np.array([[0.1, 0.9]]).T
).all()
assert np.isclose(
    make_joint_indep(np.array([1]), np.array([0.2, 0.3, 0.4, 0.1])),
    np.array([[0.2, 0.3, 0.4, 0.1]])
).all()

Задача 4 (2 балла)

Рассмотрим дискретные случайные величины $X$ и $Y$, заданные на одном и том же вероятностном пространства и принимающие значения из какого-то конечного подмножества множества целых неотрицательных чисел. Пусть задано их совместное распределение xy_probs, определённое как в предыдущей задаче. Напишите функцию make_conditionals(xy_probs), возвращающую кортеж из двух матриц (x_conditional, y_conditional), заданных следующим образом: x_conditional[k, m] — это условная вероятность $\mathbb P(X=k \mid Y=m)$, а y_conditional[k, m] — это условная вероятность $\mathbb P(Y=m \mid X=k)$.

Циклы использовать по-прежнему нельзя.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
def test_conditionals(xy_probs, x_conditional, y_conditional):
    x_conditional_observed, y_conditional_observed = make_conditionals(xy_probs)
    assert np.isclose(x_conditional_observed, x_conditional).all
    assert np.isclose(y_conditional_observed, y_conditional).all


test_conditionals(
    make_joint_indep(np.array([0.3, 0.4, 0.3]), np.array([0.4, 0.6])),
    np.array([[0.3, 0.3], [0.4, 0.4], [0.3, 0.3]]),
    np.array([[0.4, 0.6], [0.4, 0.6], [0.4, 0.6]]),
)

test_conditionals(
    np.array(
        [
            [0.06050228, 0.08105023, 0.10730594],
            [0.04794521, 0.04452055, 0.11073059],
            [0.08219178, 0.05365297, 0.03196347],
            [0.04452055, 0.10502283, 0.0673516],
            [0.04223744, 0.02054795, 0.10045662],
        ]
    ),
    np.array(
        [
            [0.218107, 0.2659176, 0.2568306],
            [0.17283951, 0.14606742, 0.26502732],
            [0.2962963, 0.17602996, 0.07650273],
            [0.16049383, 0.34456929, 0.16120219],
            [0.15226337, 0.06741573, 0.24043716],
        ]
    ),
    np.array(
        [
            [0.24311927, 0.32568807, 0.43119266],
            [0.23595506, 0.21910112, 0.54494382],
            [0.48979592, 0.31972789, 0.19047619],
            [0.20526316, 0.48421053, 0.31052632],
            [0.25874126, 0.12587413, 0.61538462],
        ]
    ),
)

Задача 5 (1 балл)

С помощью библиотеки sympy напишите функцию find_critical(f, x), которая принимает на вход всюду дифференцируемую функцию f от вещественной переменной x (в виде выражения sympy) и возвращает множество (обычный питоновский set) всех её критических точек (предполагаем, что их конечное число).

Подсказки. Библиотека sympy работает с символическими переменными — объектами типа sympy.Symbol. Например:

In [ ]:
from sympy import Symbol
var1 = Symbol('x')
var2 = Symbol('y')
var1 ** 2 * var2 ** 3
In [ ]:
(var1 ** 2 * var2 ** 3).diff(var1)

Чтобы решить уравнение, можно использовать функции sympy.solve или sympy.solveset. В последнем случае можно легко избавиться от комплексных корней, передав аргумент domain=S.Reals.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
import sympy
x = Symbol('x')
assert find_critical(x ** 2 + 1, x) == {0}
assert find_critical(x ** 3, x) == {0}
assert find_critical(x ** 3 - 3 * x, x) == {-1, 1}
assert find_critical(sympy.exp(x ** 3 - 3 * x), x) == {-1, 1}
assert find_critical(x ** 3 + x, x) == set()

Задача 6 (4 балла)

С помощью библиотеки sympy напишите функцию find_mins(f, x), которая принимает на вход всюду дифференцируемую функцию f от вещественной переменной x (в виде выражения sympy) и возвращает множество (обычный питоновский set) всех её точек минимума (предполагаем, что их конечное число). Возвращать нужно значение аргумента.

Тут можно использовать циклы. Чтобы подставить вместо символьной переменной что-то другое нужно использовать метод .subs(). Например:

In [ ]:
x = Symbol('x')
print((x ** 2 + x).subs({x: 3}))
print((x ** 2 + x).subs({x: x + 1}))
In [ ]:
"# YOUR CODE HERE"
In [ ]:
assert find_mins(x ** 2, x) == {0}
assert find_mins(-(x ** 2), x) == set()
assert find_mins(x ** 4, x) == {0}

from sympy import integrate

assert find_mins(integrate((x - 1) * (x - 2) * (x - 3)), x) == {1, 3}
assert find_mins(integrate(-(x - 1) * (x - 2) * (x - 3)), x) == {2}
assert find_mins(integrate((x - 1) * (x - 2) ** 2 * (x - 3)), x) == {3}
assert find_mins(sympy.exp(integrate((x - 1) * (x - 2) ** 2 * (x - 3))), x) == {3}
assert find_mins(x, x) == set()