Повышение точности мультиномиальной модели логистической регрессии, построенной с нуля

#python #numpy #logistic-regression

#python #numpy #логистическая регрессия

Вопрос:

В настоящее время я работаю над созданием многоклассового классификатора с использованием numpy и, наконец, получил рабочую модель с использованием softmax следующим образом:

 class MultinomialLogReg:
    def fit(self, X, y, lr=0.00001, epochs=1000):
        self.X = self.norm_x(np.insert(X, 0, 1, axis=1))
        self.y = y
        self.classes = np.unique(y)
        self.theta = np.zeros((len(self.classes), self.X.shape[1]))
        self.o_h_y = self.one_hot(y)
        
        for e in range(epochs):
            preds = self.probs(self.X)

            l, grad = self.get_loss(self.theta, self.X, self.o_h_y, preds)
            
            if e%10000 == 0:
                print("epoch: ", e, "loss: ", l)
            
            self.theta -= (lr*grad)
        
        return self
    
    def norm_x(self, X):
        for i in range(X.shape[0]):
            mn = np.amin(X[i])
            mx = np.amax(X[i])
            X[i] = (X[i] - mn)/(mx-mn)
        return X
    
    def one_hot(self, y):
        Y = np.zeros((y.shape[0], len(self.classes)))
        for i in range(Y.shape[0]):
            to_put = [0]*len(self.classes)
            to_put[y[i]] = 1
            Y[i] = to_put
        return Y
    
    def probs(self, X):
        return self.softmax(np.dot(X, self.theta.T))
    
    def get_loss(self, w,x,y,preds):
        m = x.shape[0]
        
        loss = (-1 / m) * np.sum(y * np.log(preds)   (1-y) * np.log(1-preds))
        
        grad = (1 / m) * (np.dot((preds - y).T, x)) #And compute the gradient for that loss
        
        return loss,grad

    def softmax(self, z):
        return np.exp(z) / np.sum(np.exp(z), axis=1).reshape(-1,1)
    
    def predict(self, X):
        X = np.insert(X, 0, 1, axis=1)
        return np.argmax(self.probs(X), axis=1)
        #return np.vectorize(lambda i: self.classes[i])(np.argmax(self.probs(X), axis=1))
        
    def score(self, X, y):
        return np.mean(self.predict(X) == y)
 

И у меня было несколько вопросов:

  1. Является ли это правильной реализацией многочленной логистической регрессии?
  2. Требуется 100 000 эпох с использованием скорости обучения 0,1, чтобы потери составили 1-0, 5 и получили точность 70-90% в тестовом наборе. Будет ли это считаться плохой производительностью?
  3. Каковы некоторые способы повышения производительности или ускорения обучения (чтобы требовалось меньше эпох)?
  4. Я видел эту функцию затрат онлайн, которая дает лучшую точность, она выглядит как кросс-энтропия, но она отличается от уравнений кросс-энтропийной оптимизации, которые я видел, может кто-нибудь объяснить, чем они отличаются:
 error = preds - self.o_h_y
grad = np.dot(error.T, self.X)
self.theta -= (lr*grad)
 

Ответ №1:

  1. Это выглядит правильно, но я думаю, что предварительная обработка, которую вы выполняете в функции подгонки, должна выполняться вне модели.
  2. Трудно понять, хорошо это или плохо. Хотя ландшафт потерь выпуклый, время, необходимое для получения минимумов, варьируется для разных задач. Один из способов убедиться, что вы получили оптимальное решение, — это добавить пороговое значение, которое проверяет размер нормы градиента, которая мала, когда вы близки к оптимуму. Что — то вроде np.linalg.norm(grad) < 1e-8 .
  3. Вы можете использовать лучший оптимизатор, такой как метод Ньютона, или квази-ньютоновский метод, такой как LBFGS. Я бы начал с метода Ньютона, поскольку его проще реализовать. LBFGS — это нетривиальный алгоритм, который аппроксимирует гессиан, необходимый для выполнения метода Ньютона.
  4. Это то же самое; градиенты не усредняются. Поскольку вы выполняете градиентный спуск, усреднение является константой, которую можно игнорировать, поскольку в любом случае требуется правильно настроенная скорость обучения. В целом, я думаю, что усреднение немного упрощает получение стабильной скорости обучения по разным разделениям одного и того же набора данных.

Вопрос к вам: когда вы оцениваете свой набор тестов, вы предварительно обрабатываете их так же, как вы выполняете обучающий набор в своей функции fit?

Комментарии:

1. Спасибо за ваш ответ. Я выполняю предварительную обработку всех данных одинаково, потому что все данные взяты из одного файла. Я читаю file> random shuffle> выполняю стратифицированное разделение, чтобы получить поезд и тестовые наборы. Перед тестированием моей модели я использую mlr.norm_x в тестовом наборе. Я хотел спросить, будет ли лучшим подходом использование подхода «один против всех»?

2. При использовании подхода «один против всех» в вашем пространстве решений могут быть области, которые классифицируются неоднозначно (Бишоп 4.1.2). С другой стороны, подход «один против всех» обладает лучшими свойствами распараллеливания. Например, scikit-learn может вычислить функцию принятия решения «один против всех», используя k потоков для задачи логистической регрессии k-класса.

3. Хорошо, поэтому я просто продолжу использовать регрессию softmax. Знаете ли вы, как закодировать векторизованную реализацию, чтобы найти гессиан? Все реализации, которые я видел в Интернете, проходят через это с помощью циклов

4. Я не могу придумать решение с головы до ног, но я думаю, вы могли бы справиться с этим с einsum помощью (см. numpy.org/doc/stable/reference/generated/numpy.einsum.html ).