Почему моя реализация AdaBoost повторяет одни и те же два разделения снова и снова?

#algorithm #machine-learning #classification #adaboost #boosting

Вопрос:

Я сам пытался написать алгоритм AdaBoost, используя пни решений и индекс Джини для расщеплений. Вот код:

 class AdaBoost:
    def main(x,y):
        # set the weights to 1/n
        weights=np.empty(x.shape[0])
        weights.fill(1/x.shape[0])
        
        classifier_weights=[]
        features=[]
        thresholds=[]

        for i in range(4):
            # create decision stump
            feature,threshold=AdaBoost.find_split(x,y,weights)
            features.append(feature)
            thresholds.append(threshold)
        
            # evaluate decision stump
            final_say,predictions=AdaBoost.evaluate_classifier(x,y,weights,feature,threshold)
            classifier_weights.append(final_say)
        
            # adjust sample weights
            weights=AdaBoost.adjust_sample_weights(x,y,weights,final_say,predictions)

        # classification
        classification=0
        predictions=[]
        for i in range(x.shape[0]):
            for j in range(len(features)):
                if x[i,features[j]]>thresholds[j]:
                    y_hat=1
                else:
                    y_hat=-1
                classification =classifier_weights[j]*y_hat
            if classification>0:
                predictions.append(1)
            else:
                predictions.append(-1)
            classification=0
        
        # classification accuracy
        correct=0
        for i in range(x.shape[0]):
            if predictions[i]==y[i]:
                correct =1
        accuracy=correct/x.shape[0]
        
        return accuracy, features, thresholds

    def find_split(x,y,weights):
        gini=[]
        feature_gini=[]
        thresholds=[]
        for i in range(x.shape[1]): # cycle through features
            for j in range(x.shape[0]): # cycle through all values and evaluate as thresholds
                gini.append(AdaBoost.evaluate_num_split(x,y,weights,i,x[j,i]))
            feature_gini.append(min(gini))
            thresholds.append(gini.index(min(gini)))
            gini=[]
        # feature_gini is a list containing the lowest gini value for every feature
        # therefore, the split occurs on the feature with the lowest min gini
        feature=feature_gini.index(min(feature_gini))
        # we also need to know which threshold lead to this lowest gini
        threshold=x[thresholds[feature],feature]
        return feature, threshold
                      
    def evaluate_num_split(x,y,weights,feature,threshold): # evaluate split with numeric values
        ye=[]
        nah=[]
        
        # loop puts index of samples into corresponding lists so that
        # we can access both x and y via the index
        for i in range(x.shape[0]):
            if x[i,feature]>threshold:
                ye.append(i)
            else:
                nah.append(i)
        
        return AdaBoost.evaluate_gini(ye,nah,x,y,weights)
     
    def evaluate_gini(ye,nah,x,y,weights):
        # evaluate Gini index
        weights_ye=0
        weights_nah=0
        corr=0
        wrong=0
        
        # determine weights for yes and no
        for i in ye:
            weights_ye =weights[i]
        for i in nah:
            weights_nah =weights[i]
        
        # prevent an error for dividing by zero later
        if weights_ye==0:
            return 100
        elif weights_nah==0:
            return 100
        else:
            pass
        
        # determine gini for 'yes' leaf
        for i in ye:
            if y[i]==1:
                corr =weights[i]
            else:
                wrong =weights[i]
        gini_ye=1-(corr/weights_ye)**2-(wrong/weights_ye)**2
        
        # determine gini for 'no' leaf
        corr=0
        wrong=0
        for i in nah:
            if y[i]==1:
                corr =weights[i]
            else:
                wrong =weights[i]
        gini_nah=1-(corr/weights_nah)**2-(wrong/weights_nah)**2
        
        return weights_ye*gini_ye weights_nah*gini_nah # return weighted gini between both leaves
        
    def evaluate_classifier(x,y,weights,feature,threshold):
        total_error=0
        predictions=[]
        for i in range(x.shape[0]):
            if x[i,feature]>threshold:
                y_hat=1
            else:
                y_hat=-1
            if y_hat!=y[i]:
                total_error =weights[i]
            else:
                pass
            predictions.append(y_hat)
        return 0.5*np.log((1-total_error)/total_error), predictions
    
    def adjust_sample_weights(x,y,weights,final_say, predictions):
        summation=0
        for i in range(x.shape[0]):
            weights[i]=weights[i] np.exp(-y[i]*predictions[i]*final_say)
        for i in weights:
            summation =i
        for i in range(x.shape[0]):
            weights[i]=weights[i]/summation
        return weights
 

Я использовал набор данных о диабете с 12 функциями и более чем 400 образцами. После создания первого пня принятия решения точность составляет 60%. Вторая культя также отлично работает и повышает точность до 68%. Но затем следующие пни повторяют эти же два раскола снова и снова. Они разделяются на одну и ту же функцию с одинаковым порогом. Может быть, я неправильно регулирую вес. Я весь день пытался устранить неполадки в коде и обнаружил некоторые ошибки, но не могу понять, что здесь не так.

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

AdaBoost.main(x,y)

(0.6780045351473923, [5, 2, 5, 2], [44.0, 88.0, 44.0, 88.0])

P. S: Я уверен, что код слишком запутан и сложен для того, что я пытаюсь здесь сделать. Я новичок в программировании, поэтому, если я систематически делаю что-то слишком сложное или неправильное, пожалуйста, скажите мне.

Большое спасибо за чтение всего этого грязного кода. Сделал все возможное, чтобы прокомментировать это.