#python #pandas #dataframe #knn
Вопрос:
У меня есть фрейм данных с атрибутами разных городов. Я пытаюсь заполнить недостающие данные с помощью KNNImputer. У меня возникли проблемы с выбором значения K. Итак, для каждого значения K я попытался использовать KNNImputer для подгонки и преобразования исходного кадра данных. Затем я удалил случайные записи из полного фрейма данных и использовал исходный вменитель для заполнения новых отсутствующих значений. Затем я вычислил разницу между исходным заполненным кадром данных и случайным образом заполненным кадром данных, чтобы найти ошибку, предполагая, что iI выберет значение K с наименьшей ошибкой.
error=[]
for s in strategies:
imputer = KNNImputer(n_neighbors=int(s))
transformed_df = pd.DataFrame(imputer.fit_transform(X))
dropped_rows, dropped_cols = np.random.choice(ma_water_numeric.shape[0], 10, replace=False), np.random.choice(ma_water_numeric.shape[1], 10, replace=False)
compare_df = transformed_df.copy()
for i in np.arange(10):
compare_df.iat[dropped_rows[i], dropped_cols[i]]=np.nan
compare_df = imputer.transform(compare_df)
error.append(sum(abs(compare_df-transformed_df)))
Теперь я получаю одну и ту же ошибку для всех значений K.
Есть ли лучший способ сделать это или встроенный способ проверить соответствующее значение K?
Спасибо
Ответ №1:
Почему одна и та же ошибка на протяжении всех итераций ?
Причина, по которой у вас одна и та же ошибка на каждой итерации, заключается в том, что вы не вызываете правильный sum
метод.
for n_rows in range(100):
range_df = pd.DataFrame(np.random.randint(0, 15, (10,n_rows)))
range_df2 = pd.DataFrame(np.random.randint(0, 15, (10,n_rows))
assert sum(abs(range_df-range_df2)) == sum(range(n_rows))
# > No AssertionError
То, что вы делаете, — это суммирование индексов, которые в данном случае представляют собой диапазон целых чисел от 0 до n_rows. Если вы получите 66
за все ошибки, то ваш фрейм данных, очевидно, содержит 12 строк:
sum(range(12))
# > 66
Чтобы получить желаемую ошибку, вы можете заменить свою последнюю строку на :
error.append(abs(compare_df-transformed_df).sum().sum())
Кроме этого, есть ли что — то не так ?
1. Объекты сравнения
Вы сравниваете два предсказания вместе, вместо того, чтобы сравнивать предсказания с основной истиной.
Чем «тупее» ваш вменяемый, тем больше вероятность того, что он сделает те же самые прогнозы.
Допустим, он всегда предсказывает одно и то же значение, что является самым глупым, что может получить модель. Его прогнозируемые значения, без учета инвариантных значений, будут на 100% точными друг относительно друга.
We must compare predictions against expected values to be able to judge its accuracy, which is what we often want.
You would typically split your training data in two (not necessarily with the same length) : one split for the fitting and one split for the validation.
2. Randomization is inside the loop
Randomization is… well, random. You cannot say consistently if a model is better than another if the data on which we compare them keep changing.
You should do the «nan values filling» outside of the training loop.
3. Using DataFrames instead of numpy arrays
Usually, we use the most convenient form then format it, when needed.
На самом деле, если бы вы использовали массивы numpy внутри итераций, вы бы не получили ошибку, упомянутую в первой части этого ответа.
Есть ли лучший способ сделать это ?
Некоторый контекст
То, что вы намереваетесь, называется настройкой гиперпараметров. Для этого существует несколько стратегий, из которых мы делаем свой выбор относительно общего времени процесса. Вот некоторые из этих стратегий. Можно также создать свою собственную стратегию, если она не существует в реализованной форме, в соответствии с конкретными потребностями, но они все равно найдут в scikit_learn удобные классы, которые помогут им.
Поскольку объем ваших данных невелик, мы можем попробовать все комбинации в определенном диапазоне.
Давайте подведем итоги
- Мы хотим иметь достоверные данные для сравнения всех прогнозов.
- Мы хотим разделить, чтобы мы могли измерить истинную точность моделей. Я буду считать, что ваш фрейм данных-это доступные данные для всего процесса обучения.
- Нам нужны точные способы измерения точности и их сравнения.
Давайте потренируемся
Импорт
import numpy as np
from sklearn.model_selection import train_test_split, ParameterGrid
from sklearn.impute import KNNImputer
Подготовка данных
Мы будем использовать всемогущий train_test_split. Наш полный набор данных-это y_true
(ground_truth). Набор данных, заполненный nans, является нашим X. Мы разделим оба на два: один раздел X_train
для обучения (с y_train в качестве значений истинности), один раздел X_val
для проверки (с y_val в качестве значений истинности).
# ma_water_numeric is our complete validation dataframe.
# X_copy will be our training data.
# Let's use numpy arrays.
X = ma_water_numeric.values
# Now we fill with nans with your method, but outside the loop.
dropped_rows = np.random.choice(X.shape[0], 10, replace=False)
dropped_cols = np.random.choice(X.shape[1], 10, replace=False)
for r, c in zip(dropped_rows, dropped_cols):
X[r, c]=np.nan
# We split: 20% spare for the validation, 80% for the learning part,
# with a constant random state to make it reproducible.
X_train, X_val, y_train, y_val = train_test_split(X, ma_water_numeric.values, test_size=0.2, random_state=117)
Поиск по сетке
Обычный способ найти наилучшее сочетание параметров-использовать GridSearchCV. К сожалению, насколько я знаю, нет простого способа использовать его с моделями, в которых нет метода «предсказания». (Хотя это не невозможно, но для этого требуется более глубокий объектно-ориентированный дизайн, который вы можете найти в Интернете, если вам интересно.)
Вместо этого мы можем использовать удобный класс ParameterGrid для создания каждой комбинации параметров. Чтобы лучше понять его практичность, я добавил еще один параметр для оптимизации: weights
. (См. документацию по KNNImputer)
# Designing the parameters grid
param_grid = ParameterGrid({"n_neighbors": list(range(1, 15)),
"weights": ["uniform", "distance"]})
# > [{'n_neighbors': 1, 'weights': 'uniform'}, {'n_neighbors': 1, 'weights': 'distance'},
# {'n_neighbors': 2, 'weights': 'uniform'}, {'n_neighbors': 2, 'weights': 'distance'},
# and so on ...]
Подсчет очков
Здесь нет ничего особенного. Мы сохраним вашу (исправленную) операцию, которую можно назвать «Абсолютной суммой ошибок».
# Our custom scoring, as a function.
# Here a lambda function, because it is a quite simple one.
aes = lambda y_true, y_pred: np.abs(y_true-y_pred).sum().sum()
Обучение
Наконец, мы можем сделать это в цикле. Это в основном то, как работает поиск по сетке sklearn, за исключением того, что он использует перекрестную проверку вместо разделения одного теста на поезд.
# Let's train our models.
# We will keep the parameter and score of every combination.
results = []
for param_combination in param_grid:
imputer = KNNImputer(**param_combination)
train_pred = imputer.fit_transform(X_train)
score = aes(y_train, train_pred)
param_combination["train_score"]= score
param_combination["val_score"] = aes(y_val, imputer.transform(X_val))
results.append(param_combination)
print(results)
# > [{'n_neighbors': 1, 'weights': 'uniform', 'train_score': 41.0, 'val_score': 13.0},
# {'n_neighbors': 1, 'weights': 'distance', 'train_score': 41.0, 'val_score': 13.0},
# {'n_neighbors': 2, 'weights': 'uniform', 'train_score': 24.0, 'val_score': 18.0},
# {'n_neighbors': 2, 'weights': 'distance', 'train_score': 24.682170521568423, 'val_score': 17.91644578836907},
# and so on ...]
We stored the results as a list of dict so we can easily get them as a pandas DataFrame.
results_df = pd.DataFrame(results)
results_df
# > n_neighbors weights train_score val_score
# 0 1 uniform 32.000000 16.000000
# 1 1 distance 32.000000 16.000000
# 2 2 uniform 40.000000 16.500000
# 3 2 distance 39.451212 15.841781
# and so on ...
results_df.sort_values("val_score")
# > n_neighbors weights train_score val_score
# 10 6 uniform 36.000000 8.666667
# 11 6 distance 35.024333 8.986608
# 24 13 uniform 32.000000 9.111111
# 22 12 uniform 32.000000 9.111111
# and so on ...
Теперь мы закончили. Мы знаем наилучший оптимальный набор параметров, но давайте иметь в виду, что результат может сильно отличаться в разных розыгрышах.
Идем дальше
Как избежать переобучения
Cross validation
это способ убедиться, что производительность модели не ограничивается специфическими данными обучения. Такое явление известно как переобучение. Это во многом относится к таким моделям, как KNN, которые часто имеют тенденцию перестраиваться с более низкими значениями k.
Получение наилучшей оценки
Мы могли бы сохранить в памяти лучшую оценку до сих пор, внутри цикла обучения.
results = []
best_estimator, best_params, best_val_score = None, None, np.inf
for param_combination in param_grid:
imputer = KNNImputer(**param_combination)
train_pred = imputer.fit_transform(X_train)
score = aes(y_train, train_pred)
val_score = aes(y_val, imputer.transform(X_val))
param_combination["train_score"]= score
param_combination["val_score"] = val_score
results.append(param_combination)
if val_score < best_val_score:
best_estimator = imputer
best_params = param_combination
best_val_score = val_score
# Now we can use directly the best estimator
print(best_params)
# > {'n_neighbors': 6,
# 'weights': 'uniform',
# 'train_score': 36.000000,
# 'val_score': 8.666667}
some_other_pred = best_estimator.transform(some_other_data)
Комментарии:
1. Привет, большое вам спасибо за подробный ответ! Я предполагаю, что основная проблема, с которой я столкнулся, заключается в том, что у меня нет y_train или y_val, из которых можно было бы выйти — в моем исходном наборе данных есть nan, которые я пытаюсь заполнить. Было бы допустимо сделать это со значениями, отличными от nan, а затем использовать эти параметры для заполнения исходных nan?
2. Проблема в том, что каждый набор параметров был бы правильным по-своему. Вы также можете выбрать функцию для каждого атрибута. Например, чтобы заполнить столбец «заработная плата», вы можете сгруппировать города по штатам, затем отсортировать по населению, а затем интерполировать nans. Чтобы заполнить «среднюю температуру», она будет основана на геолокации. И т.д…