Используйте статистические методы, чтобы выбрать правильные функции для вашей модели.

Это вторая часть серии из двух частей. Вам следует сначала прочитать первую часть.

Мы говорим о способе сравнения наборов слов по категориям без построения моделей машинного обучения и разработки признаков.

Итак, пока мы рассмотрели:

  1. Рассматривать частоту слов по категориям как отдельные распределения
  2. Примените U-критерий Манна-Уитни — непараметрический тест распределения каждого слова, чтобы проверить их значимость.
  3. Проанализируйте результаты, т.е. сравните частоты и p-значения значимых слов.

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

Test1 — Обучите модель со всеми функциями

  • Во-первых, я буду строить модель со всеми функциями, которые мы получили от CountVectorizer, и проверять коэффициенты слов, которые имеют незначительную разницу в их частотных распределениях.
  • Цель этого состоит в том, чтобы проверить, получаем ли мы очень низкие значения коэффициентов (ближе к 0) или нет, и это подтвердит нашу гипотезу, действительно ли эти функции/слова могут быть исключены из модели или нет.
  • Кроме того, я буду использовать Lasso, так как он придает нулевой вес функциям, которые вообще не важны.
# split the data into test and train
X = train_df_w_ft.iloc[:, 1:]
Y = train_df_w_ft.iloc[:, 0]

print("Shape of X: ", X.shape)
print("Shape of Y: ", Y.shape)

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, stratify=Y, test_size=0.3, random_state=42)

print("Size of X_train - {} and Y_train - {}".format(X_train.shape[0], Y_train.shape[0]))
print("Size of X_test - {} and Y_test - {}".format(X_test.shape[0], Y_test.shape[0]))

# training a Logistic Regression model with L1-penalty (Lasso)
log_reg1 = LogisticRegression(penalty='l1', random_state=42)\
            .fit(X_train, Y_train)

print("Accuracy on Train dataset: ", accuracy_score(Y_train, log_reg1.predict(X_train)))
print("Accuracy on Test dataset: ", accuracy_score(Y_test, log_reg1.predict(X_test)))

print("Test Data Classification Report:\n\n", classification_report(Y_test, log_reg1.predict(X_test)))

Теперь сравним коэффициенты значимых и незначимых слов.

Коэффициенты LR находятся в диапазоне (-INF, +INF), где коэффициенты -INF означают, что такие признаки имеют обратную связь с целевой/зависимой переменной. Коэффициенты, отличные от 0, указывают на то, что функции более важны, а 0 означает, что они вообще не важны. Итак, мы возьмем абсолютные значения коэффициентов, а затем нормализуем их, чтобы сделать их сопоставимыми.

def normalize_coefs(coefs):
    # normalizing the coefficients
    
    abs_coef = [abs(x) for x in coefs]
    
    _max = max(abs_coef)
    _min = min(abs_coef)
    
    return [(x - _min)/(_max - _min) for x in abs_coef]


feature_coef = dict([(ft, coef) for ft, coef in zip(X_train.columns, normalize_coefs(log_reg1.coef_[0]))])

## get the list of words which were not significant and their coefficients from the mode

top_x = 50

# get the list of significant words
sig_words= [x[0] for x in words_significance]
sig_words_coef = [feature_coef[ft] for ft in X_train.columns if ft in sig_words]

# get the list of insignificant words and their coefficients
insig_words = [ft for ft in X_train.columns if ft not in sig_words]
insig_words_coef = [feature_coef[ft] for ft in X_train.columns if ft not in sig_words]

# plot the words and their coefficients
plot_bar_graph([sig_words[: top_x], insig_words[: top_x]], [sig_words_coef[: top_x], insig_words_coef[: top_x]], 
               ['Significant', 'Insignificant'], "Insignificant Words", "Model Coefficients", "")

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

# to plot the histograms
def plot_histograms(xs, names, xlabel, ylabel, title, _min=0.0, _max=1.0, step=0.05):
# create figure object
    fig = go.Figure()
# create bar chart for each of the series provided 
    for x, name in zip(xs, names):
fig.add_trace(go.Histogram(
            x=x, 
            histnorm='percent', 
            name=name, 
            xbins=dict(start=_min, end=_max, size=step), 
            opacity=0.75)
        )
# Here we modify the tickangle of the xaxis, resulting in rotated labels.
    fig.update_layout(
        barmode='group',
        autosize=False,
        width=1300,
        height=500,
        margin=dict(l=5, r=5, b=5, t=50, pad=5),
        yaxis_title=ylabel,
        xaxis_title=xlabel,
        title=title,
        bargap=0.2,
        bargroupgap=0.1
    )
    fig.show()
plot_histograms([sig_words_coef, insig_words_coef], ['Significant', 'Insignificant'], "Coefficients", "Percentage of occurances", "")

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

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

Test2 — Обучить модель только существенным функциям и сравнить точность

# split the data into train and test
X = train_df_w_ft.loc[:, sig_words]
Y = train_df_w_ft.iloc[:, 0]

print("Shape of X: ", X.shape)
print("Shape of Y: ", Y.shape)

X_train, X_test, Y_train, Y_test = train_test_split(X, Y, stratify=Y, test_size=0.3, random_state=42)
print("Size of X_train - {} and Y_train - {}".format(X_train.shape, Y_train.shape[0]))
print("Size of X_test - {} and Y_test - {}".format(X_test.shape, Y_test.shape[0]))

# train the Logistic Regression only on the Significant features
log_reg_sig = LogisticRegression(penalty='l1', random_state=42)\
            .fit(X_train, Y_train)
print("Accuracy on Train dataset: ", accuracy_score(Y_train, log_reg_sig.predict(X_train)))
print("Accuracy on Test dataset: ", accuracy_score(Y_test, log_reg_sig.predict(X_test)))

Раньше наша точность обучения составляла 0,824, а точность теста — 0,778. Таким образом, точность обучающего набора данных снизилась примерно на 1%. Но есть аналогичное повышение точности тестовых данных примерно на 1%. Кроме того, давайте посмотрим на отчет о классификации.

print("Test Data Classification Report:\n\n", classification_report(Y_test, log_reg_sig.predict(X_test)))

Раньше у нас была оценка f1 0,81 для Target-0 и 0,73 для Target-1. Здесь мы можем четко наблюдать улучшение показателя f1 на 0,01 для обоих целевых классов.

Итак, наши окончательные выводы об этой методике таковы:

  1. Мы наблюдали как незначительное улучшение, так и снижение точности, но когда мы сравнили показатель f1 для обеих целей («0» и «1»), они показали небольшое улучшение на 0,01.
  2. Мы можем сделать вывод, что после удаления признаков из использования рассматриваемой техники существенного влияния на производительность модели не было, и, следовательно, ее можно использовать в качестве одного из методов выбора признаков. В случаях, когда у нас есть тысячи функций, это определенно может сократить время обучения без особого влияния на производительность.
  3. Я считаю, что это также может работать с непрерывными переменными, и я обязательно протестирую его на таких данных и поделюсь результатами со всеми вами.

Рабочий код для анализа размещен на Kaggle, и на него можно сослаться по ссылке — https://www.kaggle.com/pikkupr/a-new-way-to-bow-analysis-and-feature-engg.

Надеюсь, вы найдете этот анализ полезным, и, пожалуйста, поделитесь своей историей, если вы в конечном итоге внедрите его в какой-либо из своих анализов.

Я хотел бы услышать ваши мысли и предложения, так что оставляйте комментарии.