def __init__(self, loss_function, step_alpha=1, step_beta=0, tolerance=1e-5, max_iter=1000, **kwargs): """ loss_function - строка, отвечающая за функцию потерь классификатора. Может принимать значения: - 'binary_logistic' - бинарная логистическая регрессия step_alpha - float, параметр выбора шага из текста задания step_beta- float, параметр выбора шага из текста задания tolerance - точность, по достижении которой, необходимо прекратить оптимизацию. Необходимо использовать критерий выхода по модулю разности соседних значений функции: если |f(x_{k+1}) - f(x_{k})| < tolerance: то выход max_iter - максимальное число итераций **kwargs - аргументы, необходимые для инициализации """ self.step_alpha = step_alpha self.step_beta = step_beta self.tolerance = tolerance self.max_iter = max_iter if loss_function == 'binary_logistic': self.oracle = BinaryLogistic(**kwargs)
def __init__(self, loss_function, batch_size, step_alpha=1, step_beta=0, tolerance=1e-5, max_iter=1000, random_seed=153, **kwargs): """ loss_function - строка, отвечающая за функцию потерь классификатора. Может принимать значения: - 'binary_logistic' - бинарная логистическая регрессия batch_size - размер подвыборки, по которой считается градиент step_alpha - float, параметр выбора шага из текста задания step_beta- float, параметр выбора шага из текста задания tolerance - точность, по достижении которой, необходимо прекратить оптимизацию Необходимо использовать критерий выхода по модулю разности соседних значений функции: если |f(x_{k+1}) - f(x_{k})| < tolerance: то выход max_iter - максимальное число итераций (эпох) random_seed - в начале метода fit необходимо вызвать np.random.seed(random_seed). Этот параметр нужен для воспроизводимости результатов на разных машинах. **kwargs - аргументы, необходимые для инициализации """ self.step_alpha = step_alpha self.step_beta = step_beta self.tolerance = tolerance self.max_epoch = max_iter self.batch_size = batch_size np.random.seed(random_seed) if loss_function == 'binary_logistic': self.oracle = BinaryLogistic(**kwargs)
def fit(self, X, y, X_test=np.zeros(1), y_test=np.zeros(1), w_0=None, trace=False): if (self.loss_function == 'binary_logistic'): if (w_0 is None): w_0 = np.zeros(np.size(X, 1)) self.lr = BinaryLogistic(**self.kwargs) elif (self.loss_function == 'multinomial_logistic'): if (w_0 is None): w_0 = np.zeros((np.size(np.unique(y)), np.size(X, 1))) self.lr = MulticlassLogistic(**self.kwargs) self.w = w_0.copy() last_func = self.lr.func(X, y, self.w) curr_func = last_func if (trace): self.history = dict() self.history['time'] = [0.0] self.history['func'] = [last_func] self.history['acc'] = [ np.sum(np.equal(y_test, self.predict(X_test))) / np.size(y_test) ] start = time.time() num_iter = 0 while (num_iter == 0 or (np.abs(curr_func - last_func) >= self.tolerance and num_iter < self.max_iter)): num_iter += 1 self.w -= self.lr.grad(X, y, self.w) * \ self.step_alpha / num_iter ** self.step_beta last_func = curr_func curr_func = self.lr.func(X, y, self.w) if (trace): end = time.time() self.history['time'].append(end - start) self.history['func'].append(curr_func) self.history['acc'].append( np.sum(np.equal(y_test, self.predict(X_test))) / np.size(y_test)) if (trace): return self.history
def fit(self, X, y, X_test, y_test, w_0=None, trace=False, log_freq=1): np.random.seed(self.random_seed) if (self.loss_function == 'binary_logistic'): if (w_0 is None): w_0 = np.zeros(np.size(X, 1)) self.lr = BinaryLogistic(**self.kwargs) elif (self.loss_function == 'multinomial_logistic'): if (w_0 is None): w_0 = np.zeros((np.size(np.unique(y)), np.size(X, 1))) self.lr = MulticlassLogistic(**self.kwargs) self.w = w_0.copy() last_func = self.lr.func(X, y, self.w) curr_func = last_func if (trace): self.history = dict() self.history['epoch_num'] = [0.0] self.history['time'] = [0.0] self.history['func'] = [last_func] self.history['weights_diff'] = [0.0] self.history['acc'] = [ np.sum(np.equal(y_test, self.predict(X_test))) / np.size(y_test) ] start = time.time() last_epoch_num = 0 curr_epoch_num = 0 last_w = self.w.copy() curr_w = last_w.copy() num_iter = 0 ind_list = np.arange(np.size(X, 0)) np.random.shuffle(ind_list) curr_ind = 0 while (num_iter == 0 or (np.abs(curr_func - last_func) >= self.tolerance and num_iter < self.max_iter)): if (curr_ind >= np.size(X, 0)): np.random.shuffle(ind_list) curr_ind = 0 num_iter += 1 self.w -= self.lr.grad( X[curr_ind:curr_ind + self.batch_size, :], y[curr_ind:curr_ind + self.batch_size], self.w) * self.step_alpha / num_iter**self.step_beta last_func = curr_func.copy() curr_func = self.lr.func(X, y, self.w) if (trace): if (curr_ind + self.batch_size >= np.size(ind_list)): curr_epoch_num += (np.size(ind_list) - curr_ind) / \ np.size(ind_list) else: curr_epoch_num += self.batch_size / np.size(ind_list) if (curr_epoch_num - last_epoch_num >= log_freq): end = time.time() last_w = curr_w.copy() curr_w = self.w self.history['epoch_num'].append(curr_epoch_num) self.history['time'].append(end - start) self.history['func'].append(curr_func) self.history['acc'].append( np.sum(np.equal(y_test, self.predict(X_test))) / np.size(y_test)) self.history['weights_diff'].append( np.sum((last_w - curr_w)**2, axis=-1)) last_epoch_num = curr_epoch_num curr_ind += self.batch_size if (trace): return self.history
class GDClassifier: """ Реализация метода градиентного спуска для произвольного оракула, соответствующего спецификации оракулов из модуля oracles.py """ def __init__(self, loss_function, step_alpha=1, step_beta=0, tolerance=1e-5, max_iter=1000, **kwargs): """ loss_function - строка, отвечающая за функцию потерь классификатора. Может принимать значения: - 'binary_logistic' - бинарная логистическая регрессия step_alpha - float, параметр выбора шага из текста задания step_beta- float, параметр выбора шага из текста задания tolerance - точность, по достижении которой, необходимо прекратить оптимизацию. Необходимо использовать критерий выхода по модулю разности соседних значений функции: если |f(x_{k+1}) - f(x_{k})| < tolerance: то выход max_iter - максимальное число итераций **kwargs - аргументы, необходимые для инициализации """ self.step_alpha = step_alpha self.step_beta = step_beta self.tolerance = tolerance self.max_iter = max_iter if loss_function == 'binary_logistic': self.oracle = BinaryLogistic(**kwargs) def fit(self, X, y, w_0=None, trace=False): """ Обучение метода по выборке X с ответами y X - scipy.sparse.csr_matrix или двумерный numpy.array y - одномерный numpy array ВАЖНО! Вектор y должен состоять из 1 и -1, а не 1 и 0. w_0 - начальное приближение в методе trace - переменная типа bool Если trace = True, то метод должен вернуть словарь history, содержащий информацию о поведении метода. Длина словаря history = количество итераций + 1 (начальное приближение) history['time']: list of floats, содержит интервалы времени между двумя итерациями метода history['func']: list of floats, содержит значения функции на каждой итерации (0 для самой первой точки) """ # initial value self.w = w_0 if not w_0 is None else np.zeros(X.shape[1]) loss_value = self.oracle.func(X, y, self.w) history = { 'time': [], 'func': [loss_value], 'accuracy': [(y == self.predict(X)).sum() / len(y)] } prev_time = time.time() for i in range(1, self.max_iter + 1): new_w = self.w - self.step_alpha / ( i**self.step_beta) * self.oracle.grad(X, y, self.w) new_loss_value = self.oracle.func(X, y, new_w) if trace: history['func'].append(new_loss_value) history['time'].append(time.time() - prev_time) history['accuracy'].append( (y == self.predict(X)).sum() / len(y)) prev_time = time.time() if abs(loss_value - new_loss_value) < self.tolerance: self.w = new_w break loss_value = new_loss_value self.w = new_w if trace: return history def predict(self, X, threshold=0.5): """ Получение меток ответов на выборке X X - scipy.sparse.csr_matrix или двумерный numpy.array return: одномерный numpy array с предсказаниями """ result = (self.predict_proba(X) > threshold).astype('int') result[result == 0] = -1 return result def predict_proba(self, X): """ Получение вероятностей принадлежности X к классу k X - scipy.sparse.csr_matrix или двумерный numpy.array return: двумерной numpy array, [i, k] значение соответветствует вероятности принадлежности i-го объекта к классу k """ return expit(X.dot(self.w)) def get_objective(self, X, y): """ Получение значения целевой функции на выборке X с ответами y X - scipy.sparse.csr_matrix или двумерный numpy.array y - одномерный numpy array return: float """ return self.oracle.func(X, y, self.w) def get_gradient(self, X, y): """ Получение значения градиента функции на выборке X с ответами y X - scipy.sparse.csr_matrix или двумерный numpy.array y - одномерный numpy array return: numpy array, размерность зависит от задачи """ return self.oracle.grad(X, y, self.w) def get_weights(self): """ Получение значения весов функционала """ return self.w
class SGDClassifier(GDClassifier): """ Реализация метода стохастического градиентного спуска для произвольного оракула, соответствующего спецификации оракулов из модуля oracles.py """ def __init__(self, loss_function, batch_size, step_alpha=1, step_beta=0, tolerance=1e-5, max_iter=1000, random_seed=153, **kwargs): """ loss_function - строка, отвечающая за функцию потерь классификатора. Может принимать значения: - 'binary_logistic' - бинарная логистическая регрессия batch_size - размер подвыборки, по которой считается градиент step_alpha - float, параметр выбора шага из текста задания step_beta- float, параметр выбора шага из текста задания tolerance - точность, по достижении которой, необходимо прекратить оптимизацию Необходимо использовать критерий выхода по модулю разности соседних значений функции: если |f(x_{k+1}) - f(x_{k})| < tolerance: то выход max_iter - максимальное число итераций (эпох) random_seed - в начале метода fit необходимо вызвать np.random.seed(random_seed). Этот параметр нужен для воспроизводимости результатов на разных машинах. **kwargs - аргументы, необходимые для инициализации """ self.step_alpha = step_alpha self.step_beta = step_beta self.tolerance = tolerance self.max_epoch = max_iter self.batch_size = batch_size np.random.seed(random_seed) if loss_function == 'binary_logistic': self.oracle = BinaryLogistic(**kwargs) def fit(self, X, y, w_0=None, trace=False, log_freq=1): """ Обучение метода по выборке X с ответами y ВАЖНО! Вектор y должен состоять из 1 и -1, а не 1 и 0. X - scipy.sparse.csr_matrix или двумерный numpy.array y - одномерный numpy array w_0 - начальное приближение в методе Если trace = True, то метод должен вернуть словарь history, содержащий информацию о поведении метода. Если обновлять history после каждой итерации, метод перестанет превосходить в скорости метод GD. Поэтому, необходимо обновлять историю метода лишь после некоторого числа обработанных объектов в зависимости от приближённого номера эпохи. Приближённый номер эпохи: {количество объектов, обработанных методом SGD} / {количество объектов в выборке} log_freq - float от 0 до 1, параметр, отвечающий за частоту обновления. Обновление должно проиходить каждый раз, когда разница между двумя значениями приближённого номера эпохи будет превосходить log_freq. history['epoch_num']: list of floats, в каждом элементе списка будет записан приближённый номер эпохи: history['time']: list of floats, содержит интервалы времени между двумя соседними замерами history['func']: list of floats, содержит значения функции после текущего приближённого номера эпохи history['weights_diff']: list of floats, содержит квадрат нормы разности векторов весов с соседних замеров (0 для самой первой точки) """ # initial value self.w = w_0 if not w_0 is None else np.zeros(X.shape[1]) if isinstance(X, scipy.sparse.coo.coo_matrix): X = X.tocsr() loss_value = self.oracle.func(X, y, self.w) history = { 'epoch_num': [0], 'time': [], 'func': [loss_value], 'weights_diff': [0], 'accuracy': [(y == self.predict(X)).sum() / len(y)] } prev_time = time.time() iter_id = 1 calc = time.time() - time.time() for epoch_i in range(1, self.max_epoch + 1): permutation = np.random.permutation(X.shape[0]) X_shuffled = X[permutation] y_shuffled = y[permutation] self.w_prev = np.copy(self.w) for batch_i in range(int(np.ceil(X.shape[0] / self.batch_size))): X_batch = X_shuffled[batch_i * self.batch_size:(batch_i + 1) * self.batch_size] y_batch = y_shuffled[batch_i * self.batch_size:(batch_i + 1) * self.batch_size] self.w = self.w - self.step_alpha / ( iter_id**self.step_beta) * self.oracle.grad( X_batch, y_batch, self.w) iter_id += 1 new_loss_value = self.oracle.func(X, y, self.w) if trace: history['epoch_num'].append(epoch_i) history['time'].append(time.time() - prev_time) history['func'].append(new_loss_value) history['accuracy'].append( (y == self.predict(X)).sum() / len(y)) diff = self.w - self.w_prev history['weights_diff'].append(np.dot(diff, diff)) prev_time = time.time() if abs(loss_value - new_loss_value) < self.tolerance: break loss_value = new_loss_value if trace: return history def predict(self, X, threshold=0.5): """ Получение меток ответов на выборке X X - scipy.sparse.csr_matrix или двумерный numpy.array return: одномерный numpy array с предсказаниями """ result = (self.predict_proba(X) > threshold).astype('int') result[result == 0] = -1 return result def predict_proba(self, X): """ Получение вероятностей принадлежности X к классу k X - scipy.sparse.csr_matrix или двумерный numpy.array return: двумерной numpy array, [i, k] значение соответветствует вероятности принадлежности i-го объекта к классу k """ return expit(X.dot(self.w)) def get_weights(self): """ Получение значения весов функционала """ return self.w