Создавайте категории High, Medium, Low из неравномерного распределения - PullRequest
1 голос
/ 17 июня 2020

Я работал над вариантом использования прогнозирования оттока в Python с использованием XGBoost. Данные, обученные по различным параметрам, таким как возраст, срок пребывания в должности, доход за последние 6 месяцев и т. Д. c, дают нам прогноз, если сотрудник может уволиться, на основе его идентификатора сотрудника. Кроме того, если пользователь хочет понять, почему эта система машинного обучения классифицировала сотрудника как такового, он может увидеть функции, которые способствовали этому, которые извлекаются из модели через библиотеку eli5. Чтобы сделать это более понятным для пользователей, мы создали несколько диапазонов для каждой функции:

Tenure (in days)
[0-100]   = High Risk
[101-300] = Medium Risk
[301-800] = Low Risk

Чтобы определить эти диапазоны, мы проанализировали распределения каждой функции и вручную определили диапазоны для использования в система. Мы увидели влияние каждой функции на целевую переменную IsTerminated в данных обучения. Ниже приведен пример распределения владения.

enter image description here

Здесь зеленая полоса представляет сотрудников, которые уволены или уволены, а розовая - тех, кто этого не сделал.

Итак, вопрос в том, что по мере того, как проходит время и в модель будут добавляться новые данные, диапазоны риска таких функций будут меняться. В этом случае срока пребывания в должности, если у сотрудника срок пребывания в должности составляет 780 дней, через месяц его характеристика срока пребывания в должности будет показывать 810. Очевидно, мы оставляем верхний предел для «Низкого риска» как открытый. Но настоящая проблема в том, как мы можем программно определить внутренние границы / диапазоны?

1 Ответ

2 голосов
/ 23 июня 2020

EDIT: Спасибо за разъяснения. Я изменил ответ.

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

Вы не предоставили никаких образцы данных, поэтому я сгенерирую некоторые из набора данных рака груди.

Сначала давайте импортируем то, что нам нужно:

from sklearn import datasets
from xgboost import XGBClassifier
import pandas as pd
import numpy as np

А теперь импортируйте набор данных и обучите очень простую модель XGBoost

cancer = datasets.load_breast_cancer()

X = cancer.data
y = cancer.target

xgb_model = XGBClassifier(n_estimators=5,
                          objective="binary:logistic", 
                          random_state=42)
xgb_model.fit(X, y)

y_prob = pd.DataFrame(xgb_model.predict_proba(X))[0] 

Есть несколько способов решить эту проблему.

Один из подходов - разделить вероятность, заданную моделью. Таким образом, вы решите, какие вероятности вы считаете «высоким риском», «средним риском» и «низким риском», а интервалы данных можно классифицировать. В этом примере я выбрал низкое значение 0 <= p <= 0.5, среднее значение 0.5 < p <= 0.8 и высокое значение 0.8 < p <= 1.

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

y_prob = pd.DataFrame(xgb_model.predict_proba(X))[0]
df = pd.DataFrame(X, columns=cancer.feature_names)
# Stores the probability of a malignant cancer
df['probability'] = y_prob

Затем вам нужно объединить свои данные и рассчитать средние вероятности для каждого из этих интервалов. Я бы предложил объединить ваши данные, используя np.histogram_bin_edges automati c вычисление:

def calculate_mean_prob(feat):
    """Calculates mean probability for a feature value, binning it."""
    # Bins from the automatic rules from numpy, check docs for details
    bins = np.histogram_bin_edges(df[feat], bins='auto')
    binned_values = pd.cut(df[feat], bins)
    return df['probability'].groupby(binned_values).mean()

Теперь вы можете классифицировать каждую ячейку в соответствии с тем, что вы считаете низким / средним / высокая вероятность:

def classify_probability(prob, medium=0.5, high=0.8, fillna_method= 'ffill'):
    """Classify the output of each bin into a risk group, 
       according to the probability.
    
    Following the follow rules:
    0 <= p <= medium: Low risk
    medium < p <= high: Medium risk
    high < p <= 1: High Risk
    
    If a bin has no entries, it will be filled using fillna with the method
    specified in fillna_method
    """
    risk = pd.cut(prob, [0., medium, high, 1.0], include_lowest=True, 
                  labels=['Low Risk', 'Medium Risk', 'High Risk'])
    
    risk.fillna(method=fillna_method, inplace=True)
    
    return risk

Это вернет вам риск для каждой ячейки, на которую вы разделили свои данные. Поскольку у вас, вероятно, будет несколько интервалов с последовательными значениями, вы можете объединить последовательные интервалы pd.Interval. Код для этого показан ниже:

def sum_interval(i1, i2):
    if i2 is None:
        return None
    if i1.right == i2.left:
        return pd.Interval(i1.left, i2.right)
    return None

def sum_intervals(args):
    """Given a list of pd.Intervals, 
       returns a list summing consecutive intervals."""
    result = list()
    current_interval = args[0]
    
    for next_interval in list(args[1:]) + [None]:
        # Try to sum the current interval and nex interval
        # The None in necessary for the last interval
        sum_int = sum_interval(current_interval, next_interval)
        
        if sum_int is not None:
            # Update the current_interval in case if it is
            # possible to sum
            current_interval = sum_int
        else:
            # Otherwise tries to start a new interval 
            result.append(current_interval)
            current_interval = next_interval
    if len(result) == 1:
        return result[0]
    
    return result

def combine_bins(df):
    # Group them by label
    grouped = df.groupby(df).apply(lambda x: sorted(list(x.index)))
    # Sum each category in intervals, if consecutive
    merged_intervals = grouped.apply(sum_intervals)
    return merged_intervals

Теперь вы можете объединить все функции для вычисления интервалов для каждой функции:

def generate_risk_class(feature, medium=0.5, high=0.8):
    mean_prob = calculate_mean_prob(feature)
    classification = classify_probability(mean_prob, medium=medium, high=high)
    merged_bins = combine_bins(classification)
    return merged_bins

Например, generate_risk_class('worst radius') приводит к:

Low Risk          (7.93, 17.3]
Medium Risk     (17.3, 18.639]
High Risk      (18.639, 36.04]

Но в случае, если вы получаете функции, которые не являются очень хорошими дискриминаторами (или которые не разделяют высокий / низкий риск линейно), у вас будут более сложные области. Например, generate_risk_class('mean symmetry') дает:

Low Risk       [(0.114, 0.209], (0.241, 0.249], (0.272, 0.288]]
Medium Risk    [(0.209, 0.225], (0.233, 0.241], (0.249, 0.264]]
High Risk      [(0.225, 0.233], (0.264, 0.272], (0.288, 0.304]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...