Esempio n. 1
0
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
Esempio n. 2
0
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