-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path007.py
240 lines (180 loc) · 11.9 KB
/
007.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import numpy as np
def sigmoid(x):
"""сигмоидальная функция, работает и с числами, и с векторами (поэлементно)"""
return 1 / (1 + np.exp(-x))
def sigmoid_prime(x):
"""производная сигмоидальной функции, работает и с числами, и с векторами (поэлементно)"""
return sigmoid(x) * (1 - sigmoid(x))
class Neuron:
def __init__(self, weights, activation_function=sigmoid, activation_function_derivative=sigmoid_prime):
"""
weights - вертикальный вектор весов нейрона формы (m, 1), weights[0][0] - смещение
activation_function - активационная функция нейрона, сигмоидальная функция по умолчанию
activation_function_derivative - производная активационной функции нейрона
"""
assert weights.shape[1] == 1, "Incorrect weight shape"
self.w = weights
self.activation_function = activation_function
self.activation_function_derivative = activation_function_derivative
def forward_pass(self, single_input):
"""
активационная функция логистического нейрона
single_input - вектор входов формы (m, 1),
первый элемент вектора single_input - единица (если вы хотите учитывать смещение)
"""
result = 0
for i in range(self.w.size):
result += float(self.w[i] * single_input[i])
return self.activation_function(result)
def summatory(self, input_matrix):
"""
Вычисляет результат сумматорной функции для каждого примера из input_matrix.
input_matrix - матрица примеров размера (n, m), каждая строка - отдельный пример,
n - количество примеров, m - количество переменных.
Возвращает вектор значений сумматорной функции размера (n, 1).
"""
return input_matrix.dot(self.w)
def activation(self, summatory_activation):
"""
Вычисляет для каждого примера результат активационной функции,
получив на вход вектор значений сумматорной функций
summatory_activation - вектор размера (n, 1),
где summatory_activation[i] - значение суммматорной функции для i-го примера.
Возвращает вектор размера (n, 1), содержащий в i-й строке
значение активационной функции для i-го примера.
"""
return self.activation_function(summatory_activation)
def vectorized_forward_pass(self, input_matrix):
"""
Векторизованная активационная функция логистического нейрона.
input_matrix - матрица примеров размера (n, m), каждая строка - отдельный пример,
n - количество примеров, m - количество переменных.
Возвращает вертикальный вектор размера (n, 1) с выходными активациями нейрона
(элементы вектора - float)
"""
return self.activation(self.summatory(input_matrix))
def SGD(self, X, y, batch_size, learning_rate=0.1, eps=1e-6, max_steps=200):
"""
Внешний цикл алгоритма градиентного спуска.
X - матрица входных активаций (n, m)
y - вектор правильных ответов (n, 1)
learning_rate - константа скорости обучения
batch_size - размер батча, на основании которого
рассчитывается градиент и совершается один шаг алгоритма
eps - критерий остановки номер один: если разница между значением целевой функции
до и после обновления весов меньше eps - алгоритм останавливается.
Вторым вариантом была бы проверка размера градиента, а не изменение функции,
что будет работать лучше - неочевидно. В заданиях используйте первый подход.
max_steps - критерий остановки номер два: если количество обновлений весов
достигло max_steps, то алгоритм останавливается
Метод возвращает 1, если отработал первый критерий остановки (спуск сошёлся)
и 0, если второй (спуск не достиг минимума за отведённое время).
"""
# Этот метод необходимо реализовать
for step in range(max_steps):
idx = np.random.choice(np.arange(X.shape[0]), batch_size, replace=False)
if self.update_mini_batch(X[idx], y[idx], learning_rate, eps):
return 1
return 0
def update_mini_batch(self, X, y, learning_rate, eps):
"""
X - матрица размера (batch_size, m)
y - вектор правильных ответов размера (batch_size, 1)
learning_rate - константа скорости обучения
eps - критерий остановки номер один: если разница между значением целевой функции
до и после обновления весов меньше eps - алгоритм останавливается.
Рассчитывает градиент (не забывайте использовать подготовленные заранее внешние функции)
и обновляет веса нейрона. Если ошибка изменилась меньше, чем на eps - возвращаем 1,
иначе возвращаем 0.
"""
# Этот метод необходимо реализовать
nabla_j = compute_grad_analytically(self, X, y)
delta_w = learning_rate * nabla_j
J_old = J_quadratic(self, X, y)
self.w -= delta_w
j_new = J_quadratic(self, X, y)
if J_old - j_new < eps:
return 1
else:
return 0
def J_quadratic(neuron, X, y):
"""
Оценивает значение квадратичной целевой функции.
Всё как в лекции, никаких хитростей.
neuron - нейрон, у которого есть метод vectorized_forward_pass, предсказывающий значения на выборке X
X - матрица входных активаций (n, m)
y - вектор правильных ответов (n, 1)
Возвращает значение J (число)
"""
assert y.shape[1] == 1, 'Incorrect y shape'
return 0.5 * np.mean((neuron.vectorized_forward_pass(X) - y) ** 2)
def J_quadratic_derivative(y, y_hat):
"""
Вычисляет вектор частных производных целевой функции по каждому из предсказаний.
y_hat - вертикальный вектор предсказаний,
y - вертикальный вектор правильных ответов,
В данном случае функция смехотворно простая, но если мы захотим поэкспериментировать
с целевыми функциями - полезно вынести эти вычисления в отдельный этап.
Возвращает вектор значений производной целевой функции для каждого примера отдельно.
"""
assert y_hat.shape == y.shape and y_hat.shape[1] == 1, 'Incorrect shapes'
return (y_hat - y) / len(y)
def compute_grad_analytically(neuron, X, y, J_prime=J_quadratic_derivative):
"""
Аналитическая производная целевой функции
neuron - объект класса Neuron
X - вертикальная матрица входов формы (n, m), на которой считается сумма квадратов отклонений
y - правильные ответы для примеров из матрицы X
J_prime - функция, считающая производные целевой функции по ответам
Возвращает вектор размера (m, 1)
"""
# Вычисляем активации
# z - вектор результатов сумматорной функции нейрона на разных примерах
z = neuron.summatory(X)
y_hat = neuron.activation(z)
# Вычисляем нужные нам частные производные
dy_dyhat = J_prime(y, y_hat)
dyhat_dz = neuron.activation_function_derivative(z)
# осознайте эту строчку:
dz_dw = X
# а главное, эту:
grad = ((dy_dyhat * dyhat_dz).T).dot(dz_dw)
# можно было написать в два этапа. Осознайте, почему получается одно и то же
# grad_matrix = dy_dyhat * dyhat_dz * dz_dw
# grad = np.sum(, axis=0)
# Сделаем из горизонтального вектора вертикальный
grad = grad.T
return grad
def compute_grad_numerically_2(neuron, X, y, J=J_quadratic, eps=10e-2):
"""
Численная производная целевой функции
neuron - объект класса Neuron
X - вертикальная матрица входов формы (n, m), на которой считается сумма квадратов отклонений
y - правильные ответы для тестовой выборки X
J - целевая функция, градиент которой мы хотим получить
eps - размер $\delta w$ (малого изменения весов)
"""
w_0 = neuron.w
num_grad = np.zeros(w_0.shape)
for i in range(len(w_0)):
old_wi = neuron.w[i].copy()
# Меняем вес
neuron.w[i] += eps
J1 = J(neuron, X, y)
neuron.w[i] = old_wi - eps
J2 = J(neuron, X, y)
# Считаем новое значение целевой функции и вычисляем приближенное значение градиента
num_grad[i] = (J1 - J2) / (2 * eps)
# Возвращаем вес обратно. Лучше так, чем -= eps, чтобы не накапливать ошибки округления
neuron.w[i] = old_wi
# проверим, что не испортили нейрону веса своими манипуляциями
assert np.allclose(neuron.w, w_0), "МЫ ИСПОРТИЛИ НЕЙРОНУ ВЕСА"
return num_grad
np.random.seed(42)
n = 10
m = 5
X = 20 * np.random.sample((n, m)) - 10
y = (np.random.random(n) < 0.5).astype(np.int)[:, np.newaxis]
w = 2 * np.random.random((m, 1)) - 1
neuron = Neuron(w)
print(compute_grad_analytically(neuron, X, y))