Введение в интеллектуальный анализ данных — на примере прогнозирования цен на подержанные автомобили.
Автор: Чжан Цзе
этапы интеллектуального анализа данных
- Data Analysis
- Feature Engineering
- Feature Selection
- Model Building
- Model Deployment
1. Data Analysis
Для части анализа данных необходимо изучить следующие моменты: 1) Отсутствующие значения
2)All The Numerical Variables
3)Distribution of the Numerical Variables
4)Categorical Variables
5)Cardinality of Categorical Variables
6)Outliers
Relationship between independent and dependent feature(SalePrice)
2. Feature Engineering
Данные и функции определяют верхний предел машинного обучения, а модели и алгоритмы лишь приближаются к этому верхнему пределу. Так что же такое фиче-инжиниринг? Как следует из названия, это, по сути, инженерная деятельность, направленная на максимальное извлечение функций из необработанных данных для использования алгоритмами и моделями.
При разработке признаков могут возникнуть следующие проблемы:
- Не принадлежит к одному и тому же измерению: то есть характеристики функций различны и не могут сравниваться вместе;
- Качественные признаки нельзя использовать напрямую: некоторые алгоритмы и модели машинного обучения могут принимать ввод только количественных признаков, поэтому необходимо преобразовать качественные признаки в количественные. Проще всего указать количественное значение для каждого качественного значения, но этот метод слишком гибкий и увеличивает работу по настройке параметров. Фиктивное кодирование обычно используется для преобразования качественных признаков в количественные признаки: предполагается, что имеется N видов признаков, когда исходное значение признака равно i-му качественному значению, i-му расширенному признаку равно 1, а другим расширенным признакам присваивается 0. По сравнению с непосредственно указанным методом метод фиктивного кодирования не требует увеличения работы по настройке параметров.Для линейных моделей использование функций фиктивного кодирования может привести к нелинейным эффектам;
- Имеются пропущенные значения: пропущенные значения необходимо дополнить;
- Низкое использование информации: разные алгоритмы и модели машинного обучения используют разную информацию в данных.Как упоминалось ранее, в линейной модели использование фиктивного кодирования качественных признаков может привести к нелинейным эффектам. Точно так же полиномиализация количественных переменных или другие преобразования могут привести к нелинейным эффектам.
В частности, необходимо сосредоточиться на таких ключевых вопросах, как обработка пропущенных значений, обработка выбросов, нормализация данных и кодирование данных.
3. Feature Selection
Выбор функций означает выбор тех переменных функций, которые улучшают производительность нашей модели. Можно использовать несколько методов машинного обучения и статистических методов, чтобы выбрать наиболее важные функции для повышения производительности модели.
4. Model Building
В части модели вы обычно можете выбрать модель машинного обучения или использовать модель глубокого обучения, В частности, интеграция модели часто имеет неожиданные эффекты.
Анализ конкурсных вопросов
Данные вопроса
Задачей конкурса является прогнозирование цены сделки с подержанными автомобилями.Данные берутся из записей сделок с подержанными автомобилями на торговой платформе.Общий объем данных превышает 40w и содержит 31 столбец переменной информации, 15 из которых являются анонимными переменными. . Чтобы обеспечить честность конкурса, 150 000 записей будут выбраны в качестве обучающего набора, 50 000 будут использованы в качестве тестового набора, а такая информация, как имя, модель, бренд и региональный код, будет десенсибилизирована. Канал передачи данных: [Tianchi.aliyun.com/competition…]
Стандарт оценки
Стандартом оценки является MAE (средняя абсолютная ошибка).
импортировать базовый модуль
# 基础工具
import numpy as np
import pandas as pd
import warnings
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.special import jn
from IPython.display import display, clear_output
import time
from tqdm import tqdm
import itertools
warnings.filterwarnings('ignore')
%matplotlib inline
## 模型预测的
from sklearn import linear_model
from sklearn import preprocessing
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor
## 数据降维处理的
from sklearn.decomposition import PCA,FastICA,FactorAnalysis,SparsePCA
## 参数搜索和评价的
from sklearn.model_selection import GridSearchCV,cross_val_score,StratifiedKFold,train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error
import scipy.signal as signal
Анализ данных и разработка функций
def reduce_mem_usage(df):
""" iterate through all the columns of a dataframe and modify the data type
to reduce memory usage.
"""
start_mem = df.memory_usage().sum()
print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
df[col] = df[col].astype(np.int64)
else:
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
else:
df[col] = df[col].astype('category')
end_mem = df.memory_usage().sum()
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
return df
Train_data = reduce_mem_usage(pd.read_csv('used_car_train_20200313.csv', sep=' '))
Test_data = reduce_mem_usage(pd.read_csv('used_car_testB_20200421.csv', sep=' '))
## 输出数据的大小信息
print('Train data shape:',Train_data.shape)
print('TestA data shape:',Test_data.shape)
#合并数据集
concat_data = pd.concat([Train_data,Test_data])
concat_data.isnull().sum()
Здесь мы обнаруживаем, что bodyType, FuelType и коробка передач пропущены очень много, а в модели отсутствует строка, так как цена является выходом, то здесь не требуется дополнительной обработки.
Анализ анонимных функций серии V и функций, не относящихся к серии V
Для анонимных переменных есть только числовая информация, которая требует большего внимания.Здесь переменные делятся на анонимные переменные и неанонимные переменные, которые анализируются отдельно.
concat_data.columns
Сначала для анализа извлекаются неанонимные переменные и случайным образом отбираются 10 строк данных.
concat_data[['bodyType', 'brand', 'creatDate', 'fuelType', 'gearbox',
'kilometer', 'model', 'name', 'notRepairedDamage', 'offerType', 'power',
'regDate', 'regionCode', 'seller']].sample(10)
concat_data[['bodyType', 'brand', 'creatDate', 'fuelType', 'gearbox',
'kilometer', 'model', 'name', 'notRepairedDamage', 'offerType', 'power',
'regDate', 'regionCode', 'seller']].describe()
Здесь обнаружено, что значение столбца с именем notRepairedDamage содержит «-» выбросы, и режим используется здесь для замены
concat_data['notRepairedDamage'].value_counts()
concat_data['notRepairedDamage'] = concat_data['notRepairedDamage'].replace('-',0).astype('float16')
Затем продолжайте анализировать анонимные переменные
concat_data[['v_0', 'v_1', 'v_2', 'v_3',
'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12',
'v_13', 'v_14']].sample(10)
concat_data[['v_0', 'v_1', 'v_2', 'v_3',
'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12',
'v_13', 'v_14']].describe()
Для отсутствующих значений просто используйте режим для заполнения. После заполнения данные больше не содержат пропущенных значений.
concat_data = concat_data.fillna(concat_data.mode().iloc[0,:])
print('concat_data shape:',concat_data.shape)
concat_data.isnull().sum()
Горячее кодирование дискретных числовых значений
Для каждого признака, если он имеет m возможных значений, то после однократного кодирования он становится m бинарными признаками (например, характеристика степени хорошая, средняя и плохая, а однократное кодирование равно 100, 010, 001). . Кроме того, эти функции являются взаимоисключающими и активируются только по одному разу. Таким образом, данные становятся разреженными. Основными преимуществами этого являются:
-
Решена проблема, связанная с тем, что классификатор плохо обрабатывает атрибутивные данные.
-
В определенной степени это также играет роль расширения возможностей.
Ссылка на ссылку [Блог Woohoo.cn на.com/patriarchal/afraid/93…]
Численное распределение можно нарисовать с помощью df.value_counts().plot.bar
def plot_discrete_bar(data) :
cnt = data.value_counts()
p1 = plt.bar(cnt.index, height=list(cnt) , width=0.8)
for x,y in zip(cnt.index,list(cnt)):
plt.text(x+0.05,y+0.05,'%.2f' %y, ha='center',va='bottom')
clo_list = ['bodyType','fuelType','gearbox','notRepairedDamage']
i = 1
fig = plt.figure(figsize=(8,8))
for col in clo_list:
plt.subplot(2,2,i)
plot_discrete_bar(concat_data[col])
i = i + 1
Горячее кодирование используется для объектов с меньшим количеством категорий, а количество объектов после кодирования изменено с 31 на 50.
one_hot_list = ['gearbox','notRepairedDamage','bodyType','fuelType']
for col in one_hot_list:
one_hot = pd.get_dummies(concat_data[col])
one_hot.columns = [col+'_'+str(i) for i in range(len(one_hot.columns))]
concat_data = pd.concat([concat_data,one_hot],axis=1)
Здесь обнаружено, что, хотя продавец и offerType должны быть значениями двух категорий, распределение смещено в сторону результата с одним значением, которое можно удалить напрямую.
concat_data['seller'].value_counts()
concat_data['offerType'].value_counts()
concat_data.drop(['offerType','seller'],axis=1,inplace=True)
Для анонимных переменных можно надеяться, что числовая информация здесь может быть использована больше, а характеристики данных могут быть расширены за счет выбора нескольких неанонимных переменных и анонимных переменных для числовых операций сложения и умножения.
for i in ['v_' +str(t) for t in range(14)]:
for j in ['v_' +str(k) for k in range(int(i[2:])+1,15)]:
concat_data[str(i)+'+'+str(j)] = concat_data[str(i)]+concat_data[str(j)]
for i in ['model','brand', 'bodyType', 'fuelType','gearbox', 'power', 'kilometer', 'notRepairedDamage', 'regionCode']:
for j in ['v_' +str(i) for i in range(14)]:
concat_data[str(i)+'*'+str(j)] = concat_data[i]*concat_data[j]
concat_data.shape
Обработка данных даты
Данные о дате также являются очень важными данными, имеющими конкретное практическое значение. Здесь мы сначала извлекаем год, месяц и день из даты, а затем подробно анализируем данные каждой даты.
# 设置日期的格式,例如20160404,设为2016-04-04,其中月份从1-12
def date_proc(x):
m = int(x[4:6])
if m == 0:
m = 1
return x[:4] + '-' + str(m) + '-' + x[6:]
#定义日期提取函数
def date_transform(df,fea_col):
for f in tqdm(fea_col):
df[f] = pd.to_datetime(df[f].astype('str').apply(date_proc))
df[f + '_year'] = df[f].dt.year
df[f + '_month'] = df[f].dt.month
df[f + '_day'] = df[f].dt.day
df[f + '_dayofweek'] = df[f].dt.dayofweek
return (df)
#提取日期信息
date_cols = ['regDate', 'creatDate']
concat_data = date_transform(concat_data,date_cols)
Продолжайте использовать данные даты для построения других объектов. Проанализируйте значение var=data['creatDate'] - data['regDate'], var представляет количество дней между датой регистрации транспортного средства и датой создания транзакции, что может отражать продолжительность времени, в течение которого автомобиль используется, как правило, цена обратно пропорциональна времени использования Обратите внимание, однако, что в формате данных есть временные ошибки, поэтому нам нужны errors='coerce'
data = concat_data.copy()
# 统计使用天数
data['used_time1'] = (pd.to_datetime(data['creatDate'], format='%Y%m%d', errors='coerce') -
pd.to_datetime(data['regDate'], format='%Y%m%d', errors='coerce')).dt.days
data['used_time2'] = (pd.datetime.now() - pd.to_datetime(data['regDate'], format='%Y%m%d', errors='coerce')).dt.days
data['used_time3'] = (pd.datetime.now() - pd.to_datetime(data['creatDate'], format='%Y%m%d', errors='coerce') ).dt.days
#分桶操作,划分到区间内
def cut_group(df,cols,num_bins=50):
for col in cols:
all_range = int(df[col].max()-df[col].min())
# print(all_range)
bin = [i*all_range/num_bins for i in range(all_range)]
df[col+'_bin'] = pd.cut(df[col], bin, labels=False) # 使用cut方法进行分箱
return df
#分桶操作
cut_cols = ['used_time1','used_time2','used_time3']
data = cut_group(data,cut_cols,50)
#分桶操作
data = cut_group(data,['kilometer'],10)
Обработайте год и месяц, продолжайте использовать однократное кодирование
data['creatDate_year'].value_counts()
data['creatDate_month'].value_counts()
# data['regDate_year'].value_counts()
# 对类别较少的特征采用one-hot编码
one_hot_list = ['creatDate_year','creatDate_month','regDate_month','regDate_year']
for col in one_hot_list:
one_hot = pd.get_dummies(data[col])
one_hot.columns = [col+'_'+str(i) for i in range(len(one_hot.columns))]
data = pd.concat([data,one_hot],axis=1)
# 删除无用的SaleID
data.drop(['SaleID'],axis=1,inplace=True)
Увеличьте количество функций
Для увеличения количества признаков с точки зрения математической статистики можно увеличить размерность данных, выбрав математические характеристики некоторых переменных.
# count编码
def count_coding(df,fea_col):
for f in fea_col:
df[f + '_count'] = df[f].map(df[f].value_counts())
return(df)
#count编码
count_list = ['model', 'brand', 'regionCode','bodyType','fuelType','name','regDate_year', 'regDate_month', 'regDate_day',
'regDate_dayofweek' , 'creatDate_month','creatDate_day', 'creatDate_dayofweek','kilometer']
data = count_coding(data,count_list)
Нарисуйте тепловую карту для анализа корреляции между анонимными переменными и ценой, где v_0, v_8, v_12 сильно коррелированы.
temp = Train_data[['v_0', 'v_1', 'v_2', 'v_3',
'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12',
'v_13', 'v_14','price']]
# Zoomed heatmap, correlation matrix
sns.set(rc={'figure.figsize':(8,6)})
correlation_matrix = temp.corr()
k = 8 #number of variables for heatmap
cols = correlation_matrix.nlargest(k, 'price')['price'].index
cm = np.corrcoef(temp[cols].values.T)
sns.set(font_scale=1.25)
hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f', annot_kws={'size': 10}, yticklabels=cols.values, xticklabels=cols.values)
plt.show()
#定义交叉特征统计
def cross_cat_num(df,num_col,cat_col):
for f1 in tqdm(cat_col): # 对类别特征遍历
g = df.groupby(f1, as_index=False)
for f2 in tqdm(num_col): # 对数值特征遍历
feat = g[f2].agg({
'{}_{}_max'.format(f1, f2): 'max',
'{}_{}_min'.format(f1, f2): 'min',
'{}_{}_median'.format(f1, f2): 'median',
})
df = df.merge(feat, on=f1, how='left')
return(df)
# 用数值特征对类别特征做统计刻画,挑了几个跟price相关性最高的匿名特征
cross_cat = ['model', 'brand','regDate_year']
cross_num = ['v_0','v_3', 'v_4', 'v_8', 'v_12','power']
data = cross_cat_num(data,cross_num,cross_cat)#一阶交叉
Разделить набор данных
## 选择特征列
numerical_cols = data.columns
feature_cols = [col for col in numerical_cols if col not in ['price']]
## 提前特征列,标签列构造训练样本和测试样本
X_data = data.iloc[:len(Train_data),:][feature_cols]
Y_data = Train_data['price']
X_test = data.iloc[len(Train_data):,:][feature_cols]
print("X_data: ",X_data.shape)
print("X_test: ",X_test.shape)
Среднее кодирование: предварительная обработка данных для качественных признаков высокой мощности (категориальные признаки)
Под мощностью качественного признака понимается количество всех возможных различных значений этого качественного признака. При наличии качественных характеристик высокой мощности эти методы предварительной обработки данных часто не дают удовлетворительных результатов.
Примеры качественных характеристик высокой мощности: IP-адрес, доменное имя электронной почты, название города, домашний адрес, улица, номер продукта.
основная причина:
- LabelEncoder кодирует качественные признаки высокой мощности.Хотя требуется только один столбец, каждое натуральное число имеет разное значение и линейно неразделимо для y. В простой модели легко недообучиться, и она не может полностью отразить разницу между разными классами; в сложной модели легко переобучить в другом месте.
- OneHotEncoder кодирует качественные характеристики высокой мощности и неизбежно генерирует разреженные матрицы с десятками тысяч столбцов, что легко потребляет много памяти и времени обучения, если только сам алгоритм не оптимизирован (например: SVM).
Следовательно, мы можем попытаться использовать метод кодирования среднего кодирования в байесовской архитектуре, используя прогнозируемую целевую переменную и контролируемую, чтобы определить метод кодирования, наиболее подходящий для этой качественной характеристики. Это также распространенный способ улучшить результаты в соревнованиях по данным Kaggle. Ссылка на ссылку [blog.CSDN.net/Home/art…]
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold,KFold
from itertools import product
class MeanEncoder:
def __init__(self, categorical_features, n_splits=10, target_type='classification', prior_weight_func=None):
"""
:param categorical_features: list of str, the name of the categorical columns to encode
:param n_splits: the number of splits used in mean encoding
:param target_type: str, 'regression' or 'classification'
:param prior_weight_func:
a function that takes in the number of observations, and outputs prior weight
when a dict is passed, the default exponential decay function will be used:
k: the number of observations needed for the posterior to be weighted equally as the prior
f: larger f --> smaller slope
"""
self.categorical_features = categorical_features
self.n_splits = n_splits
self.learned_stats = {}
if target_type == 'classification':
self.target_type = target_type
self.target_values = []
else:
self.target_type = 'regression'
self.target_values = None
if isinstance(prior_weight_func, dict):
self.prior_weight_func = eval('lambda x: 1 / (1 + np.exp((x - k) / f))', dict(prior_weight_func, np=np))
elif callable(prior_weight_func):
self.prior_weight_func = prior_weight_func
else:
self.prior_weight_func = lambda x: 1 / (1 + np.exp((x - 2) / 1))
@staticmethod
def mean_encode_subroutine(X_train, y_train, X_test, variable, target, prior_weight_func):
X_train = X_train[[variable]].copy()
X_test = X_test[[variable]].copy()
if target is not None:
nf_name = '{}_pred_{}'.format(variable, target)
X_train['pred_temp'] = (y_train == target).astype(int) # classification
else:
nf_name = '{}_pred'.format(variable)
X_train['pred_temp'] = y_train # regression
prior = X_train['pred_temp'].mean()
col_avg_y = X_train.groupby(by=variable, axis=0)['pred_temp'].agg({'mean': 'mean', 'beta': 'size'})
col_avg_y['beta'] = prior_weight_func(col_avg_y['beta'])
col_avg_y[nf_name] = col_avg_y['beta'] * prior + (1 - col_avg_y['beta']) * col_avg_y['mean']
col_avg_y.drop(['beta', 'mean'], axis=1, inplace=True)
nf_train = X_train.join(col_avg_y, on=variable)[nf_name].values
nf_test = X_test.join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name].values
return nf_train, nf_test, prior, col_avg_y
def fit_transform(self, X, y):
"""
:param X: pandas DataFrame, n_samples * n_features
:param y: pandas Series or numpy array, n_samples
:return X_new: the transformed pandas DataFrame containing mean-encoded categorical features
"""
X_new = X.copy()
if self.target_type == 'classification':
skf = StratifiedKFold(self.n_splits)
else:
skf = KFold(self.n_splits)
if self.target_type == 'classification':
self.target_values = sorted(set(y))
self.learned_stats = {'{}_pred_{}'.format(variable, target): [] for variable, target in
product(self.categorical_features, self.target_values)}
for variable, target in product(self.categorical_features, self.target_values):
nf_name = '{}_pred_{}'.format(variable, target)
X_new.loc[:, nf_name] = np.nan
for large_ind, small_ind in skf.split(y, y):
nf_large, nf_small, prior, col_avg_y = MeanEncoder.mean_encode_subroutine(
X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, target, self.prior_weight_func)
X_new.iloc[small_ind, -1] = nf_small
self.learned_stats[nf_name].append((prior, col_avg_y))
else:
self.learned_stats = {'{}_pred'.format(variable): [] for variable in self.categorical_features}
for variable in self.categorical_features:
nf_name = '{}_pred'.format(variable)
X_new.loc[:, nf_name] = np.nan
for large_ind, small_ind in skf.split(y, y):
nf_large, nf_small, prior, col_avg_y = MeanEncoder.mean_encode_subroutine(
X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, None, self.prior_weight_func)
X_new.iloc[small_ind, -1] = nf_small
self.learned_stats[nf_name].append((prior, col_avg_y))
return X_new
def transform(self, X):
"""
:param X: pandas DataFrame, n_samples * n_features
:return X_new: the transformed pandas DataFrame containing mean-encoded categorical features
"""
X_new = X.copy()
if self.target_type == 'classification':
for variable, target in product(self.categorical_features, self.target_values):
nf_name = '{}_pred_{}'.format(variable, target)
X_new[nf_name] = 0
for prior, col_avg_y in self.learned_stats[nf_name]:
X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[
nf_name]
X_new[nf_name] /= self.n_splits
else:
for variable in self.categorical_features:
nf_name = '{}_pred'.format(variable)
X_new[nf_name] = 0
for prior, col_avg_y in self.learned_stats[nf_name]:
X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[
nf_name]
X_new[nf_name] /= self.n_splits
return X_new
# 高基数定性特征:name汽车交易名称,brand汽车品牌,regionCode地区编码
class_list = ['model','brand','name','regionCode']+date_cols # date_cols = ['regDate', 'creatDate']
MeanEnocodeFeature = class_list # 声明需要平均数编码的特征
ME = MeanEncoder(MeanEnocodeFeature,target_type='regression') # 声明平均数编码的类
X_data = ME.fit_transform(X_data,Y_data) # 对训练数据集的X和y进行拟合
X_test = ME.transform(X_test)#对测试集进行编码
X_data['price'] = Train_data['price']
from sklearn.model_selection import KFold
# target encoding目标编码,回归场景相对来说做目标编码的选择更多,不仅可以做均值编码,还可以做标准差编码、中位数编码等
enc_cols = []
stats_default_dict = {
'max': X_data['price'].max(),
'min': X_data['price'].min(),
'median': X_data['price'].median(),
'mean': X_data['price'].mean(),
'sum': X_data['price'].sum(),
'std': X_data['price'].std(),
'skew': X_data['price'].skew(),
'kurt': X_data['price'].kurt(),
'mad': X_data['price'].mad()
}
### 暂且选择这三种编码
enc_stats = ['max','min','mean']
skf = KFold(n_splits=10, shuffle=True, random_state=42)
for f in tqdm(['regionCode','brand','regDate_year','creatDate_year','kilometer','model']):
enc_dict = {}
for stat in enc_stats:
enc_dict['{}_target_{}'.format(f, stat)] = stat
X_data['{}_target_{}'.format(f, stat)] = 0
X_test['{}_target_{}'.format(f, stat)] = 0
enc_cols.append('{}_target_{}'.format(f, stat))
for i, (trn_idx, val_idx) in enumerate(skf.split(X_data, Y_data)):
trn_x, val_x = X_data.iloc[trn_idx].reset_index(drop=True), X_data.iloc[val_idx].reset_index(drop=True)
enc_df = trn_x.groupby(f, as_index=False)['price'].agg(enc_dict)
val_x = val_x[[f]].merge(enc_df, on=f, how='left')
test_x = X_test[[f]].merge(enc_df, on=f, how='left')
for stat in enc_stats:
val_x['{}_target_{}'.format(f, stat)] = val_x['{}_target_{}'.format(f, stat)].fillna(stats_default_dict[stat])
test_x['{}_target_{}'.format(f, stat)] = test_x['{}_target_{}'.format(f, stat)].fillna(stats_default_dict[stat])
X_data.loc[val_idx, '{}_target_{}'.format(f, stat)] = val_x['{}_target_{}'.format(f, stat)].values
X_test['{}_target_{}'.format(f, stat)] += test_x['{}_target_{}'.format(f, stat)].values / skf.n_splits
drop_list = ['regDate', 'creatDate','brand_power_min', 'regDate_year_power_min']
x_train = X_data.drop(drop_list+['price'],axis=1)
x_test = X_test.drop(drop_list,axis=1)
x_train.shape
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
Используйте MinMaxScaler для обработки данных, а затем используйте PCA для уменьшения размерности.
from sklearn.preprocessing import MinMaxScaler
#特征归一化
min_max_scaler = MinMaxScaler()
min_max_scaler.fit(pd.concat([x_train,x_test]).values)
all_data = min_max_scaler.transform(pd.concat([x_train,x_test]).values)
print(all_data.shape)
from sklearn import decomposition
pca = decomposition.PCA(n_components=400)
all_pca = pca.fit_transform(all_data)
X_pca = all_pca[:len(x_train)]
test = all_pca[len(x_train):]
y = Train_data['price'].values
print(all_pca.shape)
выбор модели
Здесь базовая модель нейронной сети построена с помощью keras, а структура модели представляет собой полносвязную нейронную сеть.
from keras.layers import Conv1D, Activation, MaxPool1D, Flatten, Dense
from keras.layers import Input, Dense, Concatenate, Reshape, Dropout, merge, Add
def NN_model(input_dim):
init = keras.initializers.glorot_uniform(seed=1)
model = keras.models.Sequential()
model.add(Dense(units=300, input_dim=input_dim, kernel_initializer=init, activation='softplus'))
#model.add(Dropout(0.2))
model.add(Dense(units=300, kernel_initializer=init, activation='softplus'))
#model.add(Dropout(0.2))
model.add(Dense(units=64, kernel_initializer=init, activation='softplus'))
model.add(Dense(units=32, kernel_initializer=init, activation='softplus'))
model.add(Dense(units=8, kernel_initializer=init, activation='softplus'))
model.add(Dense(units=1))
return model
from keras.callbacks import Callback, EarlyStopping
class Metric(Callback):
def __init__(self, model, callbacks, data):
super().__init__()
self.model = model
self.callbacks = callbacks
self.data = data
def on_train_begin(self, logs=None):
for callback in self.callbacks:
callback.on_train_begin(logs)
def on_train_end(self, logs=None):
for callback in self.callbacks:
callback.on_train_end(logs)
def on_epoch_end(self, batch, logs=None):
X_train, y_train = self.data[0][0], self.data[0][1]
y_pred3 = self.model.predict(X_train)
y_pred = np.zeros((len(y_pred3), ))
y_true = np.zeros((len(y_pred3), ))
for i in range(len(y_pred3)):
y_pred[i] = y_pred3[i]
for i in range(len(y_pred3)):
y_true[i] = y_train[i]
trn_s = mean_absolute_error(y_true, y_pred)
logs['trn_score'] = trn_s
X_val, y_val = self.data[1][0], self.data[1][1]
y_pred3 = self.model.predict(X_val)
y_pred = np.zeros((len(y_pred3), ))
y_true = np.zeros((len(y_pred3), ))
for i in range(len(y_pred3)):
y_pred[i] = y_pred3[i]
for i in range(len(y_pred3)):
y_true[i] = y_val[i]
val_s = mean_absolute_error(y_true, y_pred)
logs['val_score'] = val_s
print('trn_score', trn_s, 'val_score', val_s)
for callback in self.callbacks:
callback.on_epoch_end(batch, logs)
import keras.backend as K
from keras.callbacks import LearningRateScheduler
def scheduler(epoch):
# 每隔20个epoch,学习率减小为原来的0.5
if epoch % 20 == 0 and epoch != 0:
lr = K.get_value(model.optimizer.lr)
K.set_value(model.optimizer.lr, lr * 0.5)
print("lr changed to {}".format(lr * 0.5))
return K.get_value(model.optimizer.lr)
reduce_lr = LearningRateScheduler(scheduler)
#model.fit(train_x, train_y, batch_size=32, epochs=5, callbacks=[reduce_lr])
n_splits = 5
kf = KFold(n_splits=n_splits, shuffle=True)
import keras
b_size = 2000
max_epochs = 145
oof_pred = np.zeros((len(X_pca), ))
sub = pd.read_csv('used_car_testB_20200421.csv',sep = ' ')[['SaleID']].copy()
sub['price'] = 0
avg_mae = 0
for fold, (trn_idx, val_idx) in enumerate(kf.split(X_pca, y)):
print('fold:', fold)
X_train, y_train = X_pca[trn_idx], y[trn_idx]
X_val, y_val = X_pca[val_idx], y[val_idx]
model = NN_model(X_train.shape[1])
simple_adam = keras.optimizers.Adam(lr = 0.01)
model.compile(loss='mae', optimizer=simple_adam,metrics=['mae'])
es = EarlyStopping(monitor='val_score', patience=10, verbose=0, mode='min', restore_best_weights=True,)
es.set_model(model)
metric = Metric(model, [es], [(X_train, y_train), (X_val, y_val)])
model.fit(X_train, y_train, batch_size=b_size, epochs=max_epochs,
validation_data = [X_val, y_val],
callbacks=[reduce_lr], shuffle=True, verbose=0)
y_pred3 = model.predict(X_val)
y_pred = np.zeros((len(y_pred3), ))
sub['price'] += model.predict(test).reshape(-1,)/n_splits
for i in range(len(y_pred3)):
y_pred[i] = y_pred3[i]
oof_pred[val_idx] = y_pred
val_mae = mean_absolute_error(y[val_idx], y_pred)
avg_mae += val_mae/n_splits
print()
print('val_mae is:{}'.format(val_mae))
print()
mean_absolute_error(y, oof_pred)