Мне нужно решить очень важную задачу в ML с использованием алгоритма Random Forest. Я новичок в ML, и задача, которую мне предстоит решить, не из самых простых, но другого способа, кроме как решить ее, нет.
У меня есть безликий набор данных с примерно 500 записями внутри и примерно 30 features в каждой записи. 300 из всех записей имеют статус 1, остальные 0. Мне нужно построить модель, которая будет предсказывать будущий статус.
Итак, даже будучи новичком в ML, я думаю, что попытался сделать все необходимое, чтобы решить эту проблему:
1) Features, предоставленные мне для этой задачи, как я уже написал, обезличенные, поэтому они все нужные. Перед этим они были проверены/верифицированы.
2) Тем не менее для построения качественной модели я провел их отбор, используя RFE.
3) Далее, найдя самые важные features, я приступил к тюнингу модели, используя GridSearch / Bayesian / Random Search (каждую отдельно, но все они привели к почти одинаковым результатам).
В итоге они все дали мне результаты с точностью 0,6-0,65 (а f1, precision recall находятся в том же диапазоне), которые не ужасны, но и не великолепны. Задача состоит в том, чтобы достичь как минимум более 75% точности, здорово, если она будет больше. Более того, до тюна обычно модель давала даже больше результатов точности.
Поэтому, пожалуйста, не могли бы вы дать мне советы, что делать дальше? Что изменить, что почитать, как сделать? И если вы суперпрофессионал или супер добрый человек, может быть, даже вы сможете попытаться чего-то добиться с моим набором данных и повышением точности алгоритма?
Features, предоставленные мне для этой задачи, как я уже написал, обезличенные, поэтому они все нужные
Данные могут быть числовые или категориальные. В свою очередь категориальные делятся на nominal и ordinal из чего следует что во первых если они присутствуют их надо закодировать и второе закодировать правильно, в sklearn есть OrdinalEncoder и OneHotEncoder. О том сделали ли вы это (и правильно ли сделали) вы нам конечно же не сообщаете, это действие относится к этапу Preprocessing data, там же Standard Scaler и другие возможно очень полезные трансформаторы вплоть до создания своего кастомного трансформатора. Просто данные не в один алгоритм не запихивают. Их приводят к такому виду (трансформируют) на которых алгоритм хорошо работает, а потом уже имеет смысл тюнить. Вам нужен результат. пожалуйста https://scikit-learn.org/stable/data_transforms.html . Вот это та глава в документации (а документация у sklearn как учебник) которая поправит дело. Результат достигается посредством трансформаций данных и в этой области лежит решение. Глава 6 что я вам скинул большая и за несколько часов или даже дней ее не осилить (если конечно же учится а не кусочки кода запускать). Начните с того что бы научится пользоваться объектом Pipeline (он не так прост как кажется) И с применением хотя бы простого трансформера StandardScaler и что бы без ошибок. Чтобы метод fit_transform вызывался на тренировочном сете а на тестовом только transform и т.д. или с правильным использованием Pipeline он это сам сделает. И приходите с кодом где вы перед тренировкой трансформируете данные.
На данный момент у вас нет ничего 0.6 - 0.65 точность, у вас 300 / 500 = 0.6 то есть абсолютно случайно без какого либо обучения в среднем на длинной дистанции в 60% случаях можно угадывать статус 1.
Мне нужно построить модель, которая будет предсказывать будущий статус.
Что значит будущий речь идет о временной последовательности, а не о классической классификации?. Тогда препроцессинг должен быть сделан совершенно другой начиная от сплитов и т.д. В таком случае все данные нужно агрегировать разными функциями в пределах окна и на них учить, обязательно это проясните.
Спасибо вам за такой подробный и хороший ответ, многое прояснили, буду пробовать. Уточнил: действительно, некоторые данные категориальные, а я с ними никак отдельно не работал. Подскажите вот что: получается, проверять данные после препроцессинга, качество препроцессинга, нужно на дефолтном алгоритме, с его дефолтными настройками. Как понять, что препроцессинг был сделан правильно, оценить его?
Kind_Man, Для проверки изменений включая препроцессинг вы фиксируете ваш random_state (число не важно) важно что бы не None как по умолчанию и сравниваете метрики ДО и ПОСЛЕ какого то изменения. По поводу правильного препроцессинга (возьмем немного шире Data Transformation) если в двух словах то это САМАЯ ВАЖНАЯ часть и это место где один Data Scientist будет лучше другого. Я коснусь лишь самых элементарных моментов потому что это огромный топик.
У вас есть ЧИСЛЕННЫЕ данные и КАТЕГОРИАЛЬНЫЕ. И те и те нуждаются в трансформациях для абсолютно любого алгоритма.
ЧИСЛЕННЫЕ наши колонки должны быть в одной шкале. Например в одной колонке у нас числа от 1 до 10 а в другой от 1 до 100. Те что от одного до ста стянут ВСЕ ВНИМАНИЕ на себя (вариативность ну я буду по простому писать пусть и потеряю в точности). А на самом деле та колонка в которой данные от 1 до 10 она более важная но алгоритм без препроцессинга обесценит ее. Пример такого трансформера это StandardSclaer. Но это не значит вот только он или он и все. ИЗУЧАЕМ трансформации числовых переменных.
КАТЕГОРИАЛЬНЫЕ например у нас есть переменная в которой есть три категории плохо, хорошо, прекрасно,
мы можем их так закодировать плохо = 0, хорошо = 1, прекрасно 2. То есть имеется некий порядок одна категория больше другой. Такую кодировку осуществит OrdbalEncoder. Если же например у нас категории зеленое, белое, красное то мы НЕ можем сказать что одна категория больше другой и это OneHotEncoder.
При препроцессинге необходимо соблюдать определенные правила например ВСЕ ТРАНСФОРМАЦИИ на ТРЕНИРОВОЧЕОМ СЕТЕ (я говорю о Х о features) должны быть осуществлены и на ТЕСТОВОМ сете.
При это на ТРЕНИРОВОЧНОМ мы вызывает метод fit_transform. И МЫ НИКОГДА И НЕ ПРИ КАКИХ обстоятельствах не вызывает FIT на тестовых данных то есть НА ТЕСТОВЫХ данных мы трансфомируем методом transform. То есть для X_train у нас метод fit_transform для X_test метод transform.
Правильное осуществление ДАЖЕ при условии ПРАВИЛЬНО выбранных траснформаций критично. Для этого я вам и сказал изучайте главу 6 и ссылку дал, это НЕ такой топик что вот здесь вот это поменяй и все получится, не получится.
Все что я написал, это простейшая очень сжатая версия элементарных трасформаций для того что бы у вас ну хоть какая то общая картинка была.
Максим Припадчев, большое спасибо, очень многое прояснили, изучаю и делаю по вашим советам и ссылкам. С категориальными данными разобрался, а вот насчёт численных - вопрос. Численные данные в датасете в каждой колонке "свои": где-то это значения от 1000 до 300000+, где-то настолько крошечные, что записаны как 0,..е-6. Очевидно же, что для каждой численной колонки нужны свои инструменты? Для каких-то данных логарифмирование, для каких-то min-max и тд. И это же нормально, так? Это их всех и сводит в единый вид для алгоритма машинного обучения? Численные значения в итоге после препроцессинга обязаны оказаться все примерно в одном диапазоне числовом или нет?
Kind_Man, Первое вы упомянули min и max это примеры агрегирующих функций (агрегирующая функция на вход получает вектор (для простоты и понятности несколько чисел да простят меня математики) а на выход одно число) так работает агрегирующую функции min, max, std,var, mean, median и другие. Так вот трансформации применяются по элементно. Это на всякий случай, думаю вы имеете ввиду (я на это надеюсь MinMaxScalar).
Ну вот видите те числовые данные которые у вас есть в том виде в котором они есть в одной колонке тысячи в другой меньше единицы, абсолютно без разницы в какой алгоритм пихать, он оставит предикторами большие а маленькие вообще не будут иметь никакого значения, а не факт что маленькие данные нам бесполезны. По этому вам нужно применить ну хотя бы StandardScalar для начала как я уже сказал, или MinMaxScalar.
Нормально ли применять для разных колонок разные трансформаторы ДА НОРМАЛЬНО (ваш y только не трогайте (что вы классифицировать собрались) Его тоже можно трансформировать но это отдельная тема для разговора пока туда не лезьте). Вы свободный художник здесь. И главное выполнить по технике правильно перечитайте что я говорил про transform и fit_transform. Наверное даже вам необходимо глянуть главу 10 https://scikit-learn.org/stable/common_pitfalls.html. Там рассмотрены ошибки частые, она небольшая и убережет от того что будете применять трансформации а они в силу того что вы не опытен работать не будут. Обязательно ее прочтите запустите и пошагово разберите простейшие примеры от туда они маленькие и понятные. А потом возвращайтесь к большой и трудной 6 главе.
Численные значения в итоге после препроцессинга обязаны оказаться все примерно в одном диапазоне числовом или нет?
Ну что бы обязаны такого прям нет. Исключения могут быть. Но лучше да приводить. Обратите внимание
StandardScaler - scaler шкала (как вы выразились диапозон) что он сделает для каждой колонки он посчитает mean и std. И затем сделает (x - mean(колонки в которой находится x)) / std (стандартное этой колонки). То есть каждое числовое значение становится количеством стандартных отклонений от среднего по этой колонке. То есть у нас становится одна единица измерения для всех числовых данных. И все они придут в среднем к одной шкале.
MinMaxScaler переведет все данные в формат от 0 до 1. по умолчанию этот диапозон как вы его называете можно поменять.
Более популярный StandardScaler примените его правильно и посмотрите на результаты. В sklearn тьма примеров с ним для некоторых алгоритмов он и вовсе обязателен.
По этому я вам и говорю наберитесь терпения и выучите объект Pypeline потому что у нас два трансформера один для категориальных а другой для числовых данных. И это только начало. Будет тяжело не запутаться в них и нигде не ошибиться.
Максим Припадчев, почитал документацию scikit'ов, и вот что понял и как стал делать.
1) По численным значениям: крайне маленькие и крайне большие значения логарифмировал, остальные minMaxScaler-ом свел к диапазону 0-1. Но теперь так получилось, что логарифмированные значения приняли либо отрицательные значения, либо около 10. В это же время остальные численные значения лежат в диапазоне 0-1. Значит ли это, что теперь на всех них разом стоит применить Standard Scaler, чтобы свести их все одному и тому же диапазону?
2) Как понял из документации и ваших ответов, сначала надо разделить данные на тренировочные и тестовые, и затем к обоим применить одинаковые "трансформации" (только на тренировочном fit_transform, а на тестовом просто transform). Но вот тут вопрос: Min-Max вызывается как minMax_scaler.fit_transform, и если я дальше буду ещё раз трансформировать данные (чтобы сопоставить их с логарифмированными), нормально ли снова применять fit_transform только от StandardScaler?
Kind_Man, А почему сразу не применить Standard Scaler?
смотрите раз уже не хотите Pipeline учить. любой трансформер имеет методы fit_transform и просто transform
только естественно ваш X_train и X_test должен быть трансформирован как трансформером для численных данных так и трансформером для категориальных.
ну и что бы такое делать не вручную есть такой ColumnTransformer.
в общем вот вам пример из документации который поможет вам не запутаться https://scikit-learn.org/stable/auto_examples/comp...
там рассказано как разные трансформаторы применять для разных fеtatures в рамках одного сета данных.
А то мы тихо-тихо идем к тому что я за ва начну код писать, этого не будет.
Максим Припадчев, хоть и хочется, но об этом я вас не попрошу) И так, большое вам спасибо за ваши советы, они работают.
В общем, дело такое, на примере алгоритма случайного леса:
- когда я запускаю его на вообще необработанных данных, просто сырых, взятых из log файла, я получаю точность 0,65.
- после своей обработки (категориальные, min-max для колонок с числами от 0 до 100, log для колонок с очень маленькими значениями, log10 для колонок с очень большими значениями) я получаю наибольшую точность 0,67
- если просто все численные колонки скидываю в одну кучу и обрабатываю их minMax, то получаю 0,53, standardScalerом получаю 0,52.
Главный вопрос у меня: почему настолько слабо отличаются точности сырого набора данных и полностью обработанного? В чем причина и как это изменить? Куда копать дальше?
Kind_Man, Во первых вы должны сказать точность как на ТРЕНИРОВОЧНОМ так и на ТЕСТОВОМ сете. Во вторых 0.65- 0.67 трансформации выполнены с ошибкой это не улучшение. Кода то нету откуда я могу знать где у вас в трансформациях ошибка.
Ответ на ваш главный вопрос трансформации выполнены с ошибками.
Может вы категориальные данные тоже потом трансформируете StandardScaler'ом или MinMax'ом. Может какая другая ошибка на трансформациях.
Максим Припадчев, хорошо, буду благодарен, если подскажете. И так.
1) Сперва создаю все сеты
X = df.drop(columns=['Status'])
y = df['Status']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
minMax_scaler = MinMaxScaler()
standard_scaler = StandardScaler()
2) Далее сохраняю названия колонок в соответствующие нормализациям действия и для каждой выполняю эти действия:
categorical_cols = ['',...]
log10_cols = ['',...]
log_cols = ['',...]
numerical_cols = ['',...]
for column in categorical_cols:
X_train[column] = pd.Categorical(X_train[column])
X_train = pd.get_dummies(X_train, columns=[column])
for column in log10_cols:
X_train[column] = np.log10(X_train[column])
for column in log_cols:
X_train[column] = np.log(X_train[column])
X_train[numerical_cols]=minMax_scaler.fit_transform(X_train[numerical_cols])
3) Делаю то же самое с тестовым сетом.
for column in categorical_cols:
X_test[column] = pd.Categorical(X_test[column])
X_test = pd.get_dummies(X_test, columns=[column])
for column in log10_cols:
X_test[column] = np.log10(X_test[column])
for column in log_cols:
X_test[column] = np.log(X_test[column])
X_test[numerical_cols]=minMax_scaler.transform(X_test[numerical_cols])
Да в трансформациях ошибка. Те что np.log не имеет смысла. У нас не происходит обучения на тренировочном сете. Уберите это и все что численные StandardScalerom трансформируйте. Сам трансофрмер вы применили "правильно" в кавычках потому что весь pandas код ужасный, так pandas не пишут, но что касается трансформера он у вас РАБОЧИЙ.
вот это я имею ввиду по исполнению конечно так не пишут но он РАБОЧИЙ. все что np.log вообще уберите.
и замените трансформер. StandardScaler в который все ваши чиленные переменные впихнете.
По категориальным у вас там что? много категорий, где одна не больше другой? расcкажите что там?
и по сути: Type и Network - просто, собственно виды типа и сети, никакой тип или сеть не имеет большего или меньшего веса среди себе подобных. От 5000 до 100 каждая колонка имеет значение 0 или 1: удается на эту сумму сделать покупку или нет.
Я всё сделал одним
for column in categorical_cols:
X_train[column] = pd.Categorical(X_train[column])
X_train = pd.get_dummies(X_train, columns=[column])
но сейчас, писав этот комментарий вам, подумал, что я не прав. Этот код подходит Type и Network - так как они просто виды - nominal, а вот с 5000-100 надо по-другому, ведь тут уже не столько виды, сколько результат, вес какой-то. Это же ordinal.
По поводу StandardScaler и всех численных в один эстиматор.
Kind_Man, так значит type и network там больше двух значений и одна не больше другой то есть OneHotEncoder, а остальные это ordinal если там 1 или 0 то они уже закодированы. Так?
Kind_Man, Так ну хорошо, для начала давайте все сделаем по человечески, и заодно исключим ошибки в pandas коде.
import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
X = df.drop(columns=['Status'])
y = df['Status']
numeric_features = [" ",..]
categorical_features = ["",..]
# Вот мы создаем Pipeline's для наших трансформаций, пока по одной на каждую часть но это для начала
numeric_transformer = Pipeline(
steps=[("scaler", StandardScaler()),]
)
categorical_transformer = Pipeline(
steps=[
("encoder", OneHotEncoder(handle_unknown="ignore")),
]
)
preprocessor = ColumnTransformer(
transformers=[
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features),
]
)
наш финальный пайп где мы собрали трансформеры который имеет поведение модели.
clf = Pipeline(
steps=[("preprocessor", preprocessor), ("classifier", RandomForestClassifier(random_state=42))]
)
#теперь когда мы создали план действий делим данные
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf.fit(X_train, y_train)
print("model score for training set: %.3f" % clf.score(X_train, y_train))
print("model score for testing set: %.3f" % clf.score(X_test, y_test))
Вот это правильная организация для вашей проблемы (здесь из коробки он где надо применит transform а где надо fit_transform) в общем код верный просто для не опытного человека может выглядеть необычно, что оно выдаст по метрикам вот так? теперь мы так же можем если нужно еще каких нибудь трансфомеров добавить в наш numerical и categorical трансформеры.
Это вместо всего вашего кода весь pandas выкидываем.
Максим Припадчев, огромное спасибо! Решил поменять эстиматор со Случайного Леса, т.к. он выдавал точность 1 на тренировочном сете. Заменил KNN и уже похоже на реальность:
model score for training set: 0.757
model score for testing set: 0.700
for column in log10_cols:
X[column] = np.log10(X[column])
for column in log_cols:
X[column] = np.log(X[column])
numeric_transformer = Pipeline(
steps=[("scaler", StandardScaler()),]
)
categorical_transformer = Pipeline(
steps=[
("encoder", OneHotEncoder(handle_unknown="ignore")),
]
)
preprocessor = ColumnTransformer(
transformers=[
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features),
]
)
clf = Pipeline(
steps=[ ("preprocessor", preprocessor),
("classifier", KNeighborsClassifier())
]
)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf.fit(X_train, y_train)
print("model score for training set: %.3f" % clf.score(X_train, y_train))
print("model score for testing set: %.3f" % clf.score(X_test, y_test))
Изначальные данные уже не знаю, как изменить, потому что, видимо, дальше и некуда. MinMaxScaler сводит точность до 0,52, если не делать предварительного логарифмирования нескольких колонок - тоже 0,52, с ними максимальные результаты, а дальше ничего уже никак не меняется.
Как понимаю, сейчас надо заняться Features Selection
Может быть более одного трансформера, просто добавляете кортеж состоящий из имени (придумываете его сами и самого трансформера) гуглите sklearn numeric transformers и соответственно categorical transformenrs. И пробуйте учитесь. Дальше без меня.
Максим Припадчев, уже начал играться с различными настройками. А подскажите, почему происходит следующая ситуация: у меня есть несколько колонок (в одних - огромные значения, в других - меньше 1, но больше 0), которые я сначала логарифмирую, а потом применяю к ним StandardScaler в препроцессоре. И вот, если в список названия колонок для этого препроцессора, эти "log" колонки включить дважды, то я получаю точность 0,7, а если один раз - 0,65.
Если логарифмирования этих колонок не делать, точность также будет у 0,6.
Однако, в свою очередь логарифмирование этих колонок вызывает проблемы далее, потому что большинство FeatureSelection алгоритмов (как SelectKBest) отказываются работать с отрицательными значениями, к которым приводит логарифмирование чисел меньше 1, но больше 0. Я подумал решить это, просто прибавив к ним единицу и тем самым избежать отрицательных значений, но после этого точность просела на 2 процента. Зато теперь можно сделать Features Selection.
Поэтому вопрос. Как следует шаг за шагом настраивать модель? Опираться на лучшую получившуюся и с нее двигаться на следующий шаг?