import pandas as pd # 工具
import numpy as np
import joblib
import os
from sklearn.preprocessing import PowerTransformer, PolynomialFeatures, StandardScaler # 预处理
from sklearn.ensemble import RandomForestRegressor # 离群点和错误值筛选
from sklearn.ensemble import IsolationForest
from sklearn.feature_selection import variance_threshold # 特征选择
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split, cross_val_score, KFold # 切分数据集
from sklearn.linear_model import Lasso, Ridge, LinearRegression # 算法
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from mlxtend import regressor
from sklearn.metrics import mean_absolute_error, r2_score, mean_squared_error # 评分
from hyperopt import Trials, tpe, pyll, fmin, hp # 动态调参
import functools
import time
import matplotlib.pyplot as plt
pd.set_option("display.max_columns", 500)
pd.set_option("display.width", 1000)
pd.set_option("display.max_rows", 500)
# pd.set_option("display.height", 1000) # 测试时不能进行行高设置
TIME_DISPLAY_FLAG = True # 是否显示函数运行时间
'''
注:
房价预测A是后来写的,限于时间关系没有严格调参,没有进行主观特征合并(假设自己为房价小白)训练集和测试集的R2都为91.4%。
房价预测B是自学以来第一次写机器学习项目,中间有些数据处理的逻辑不对,导致准确率较低,
好在都是分块的,并不影响其他部分的正确性。
房价预测B虽然准确率低,但是考虑了模块化,自动化,和自动调参,所以放上来。
创新点(由于准确率较低,并不能证明有效或无效,写出来仅供发散思维):
1.构造data_of_stats对数据进行归纳,用来自动化条件判定,对数据的预处理包含了对表格的完善。
2.部分避免了引入random_state的特殊性。如:Base_m.rf_choose()函数
3.对孤立森林选择离群点的优化:见Base.iso_check()函数
4.对PCA的部分推测:1)对非相关特征数据A进行PCA后得到B,对A、B同时去除相等数量的相关系数较小的特征,
相关系数越小,信息损失越小,得到的预处理数据会越好。(我的验证中是成立的)
2)对相关特征数据A进行PCA后得到B,对A、B同时去除相等数量的相关系数较小的特征,
结果是否会有规律,如果没有规律,有没有办法转为1)。(未测试)
'''
'''random_state 引入的特殊性。
random_state 本身是随机的, 但是本身的随机会引入特殊因素。所以train_test_split才会有分层筛选(但只是对标签的分层)。
类似于:九个中国人一个日本人,random_state 选到 日本人的话,如果固定用日本人来做训练集,会造成很大的误差。
10个数据的数据集,如果按照7:3划分数据集,有C(7, 10)=120种排序。换个角度,就是有120个人,random_state代表选择其中一个人。
如果固定random_state的话要获得高预测准确率,则必须对data进行更大幅度的数据预处理,且必定会引入特殊性。
我的想法是在小项目中不固定 random_state, 将 random_state 和其他参数视作相同地位的参数,筛选出最普遍的人。
'''
# 数据划分示意
# [data: [train:[x_train, y_train], test: [x_test, y_test]]]
# [data: [train: [train_num, train_str], test: [test_num, test_str]]]
# [data: [train: [train_num, train_one_hot], test: [test_num, test_one_hot]]]
# 测量时间的装饰器
def time_metric(func):
@functools.wraps(func)
def wrapper(*args, **kw):
__time_start = time.time()
__f = func(*args, **kw)
__time_close = time.time()
__time_spent = __time_close - __time_start
if TIME_DISPLAY_FLAG:
print('%s cost time: %f' % (func.__name__, __time_spent))
return __f
return wrapper
# 用"_m"后缀建立所有Class, 方便以后更改。所有改良的函数也用"_m"后缀注明,方便以后更改改良方法。
class Base_m(object):
@staticmethod
def isDataframe(data=None):
assert isinstance(data, pd.core.frame.DataFrame), "请输入pandas.Dataframe格式数据"
@staticmethod
def isndarray(data=None):
assert isinstance(data, np.ndarray), "请输入np.ndarray格式数据"
@staticmethod
def isarrayorDataFrame(data=None):
assert isinstance(data, pd.core.frame.DataFrame) or isinstance(data, np.ndarray), "请输入Dataframe或者ndarray格式数据"
@staticmethod
def change_train_shape_0(drop_list=None):
global TRAIN_SHAPE_0 # 全局变量 train 个数
for i in drop_list:
del_num = TRAIN_SHAPE_0
if i < del_num:
TRAIN_SHAPE_0 -= 1
@staticmethod
@time_metric
def modify(data, modify_list=None, number=2020):
"""
针对年份相关的修改,将变化幅度过小的数据进行重新整理,在有意义的条件下尽量增加其变化幅度
Parameters
----------
data: pd.core.DataFrame
modify_list: list, data中需要修改的列的列表
number: int, 年份。
比如 2010年,改为与2020年差10,会增大标准差。易于观察变化。
"""
if modify_list is None:
modify_list = ["YearBuilt", "YearRemodAdd", "GarageYrBlt", "YrSold"]
for i in modify_list:
data[i] = number - data[i]
return data
@time_metric
def data_of_stats(self, data=None):
"""
对数据进行统计
想法: 将图像表格化,可以根据表格统计进行自动化,对房价的预测的数据预处理其实就是对 data_stats 的完善。
Parameters
----------
data: pd.core.DataFrame
Returns
-------
data_stats: pd.core.DataFrame, 数据统计表。size为 len(data.columns) * 7,
"name" 为 data对应列名称,
"mean_std" 为data对应列平均值与标准差比值。
"dtypes" 为data对应列中的数据类型。
"value_counts" 为data对应列中的不同数据的数值个数。
"nan_counts" 为data对应列中的未识别数据个数。
"max_ratio" 为data对应列中的数据中占比最大的数据所占的百分比。
"corr" 为data对应列与目标列的相似度。
"""
self.isarrayorDataFrame(data)
if type(data) == np.ndarray:
data = pd.DataFrame(data)
all_col = data.columns
dtypes_list = []
value_counts_list = []
nan_counts_list = []
max_ratio_list = []
mean_std_list = []
for i in all_col:
dtypes_list.append(data.loc[:, i].dtypes.name)
value_counts_list.append(len(data.loc[:, i].value_counts()))
nan_counts_list.append(data.loc[:, i].isnull().sum())
max_ratio_list.append(data.loc[:, i].value_counts().max() / data.shape[0])
if data.loc[:, i].dtype.name != 'object':
mean_std_list.append(data.loc[:, i].mean() / data.loc[:, i].std())
else:
mean_std_list.append(0)
data_stats = pd.DataFrame()
data_stats["name"] = all_col
data_stats["mean_std"] = mean_std_list
data_stats["dtypes"] = dtypes_list
data_stats["value_counts"] = value_counts_list
data_stats["nan_counts"] = nan_counts_list
data_stats["max_ratio"] = max_ratio_list
data_stats["corr"] = np.nan
data_stats.loc[data_stats.loc[:, 'dtypes'] != 'object', 'corr'] = data.corr().iloc[:, -1].values
return data_stats
@time_metric
def iso_check(self, data, iso_num=10, iso_length=50, iso_estimators=500):
"""
进行多次孤立森林再重新筛选出离群点。
原因:孤立森林每次都会得到很多不一样的离群点。解决方法一:设定同一random_state, 这并没有根本性改变其偶然性。
解决方法二:多次求取孤立森林再根据结果取舍,保证最终离群点稳定,可以增加范性。
另外:非关键性特征和关键性特征的偏离效果应该不同,构造孤立森林时赋予不同特征权重(LASSO和随机森林获取),权重大的划分范围更细,权重小的划分范围更大。
Parameters
----------
data: pd.core.DataFrame
iso_num: 进行孤立森林的次数,越大范性越大,时间越长。
iso_length: 假定的离群点数量。
iso_estimators:每次孤立森林的树数量, 越大范性越大,时间越长。
Returns
-------
iso_stats: pd.core.DataFrame, 离群点统计表,可以根据此表进行离群点判定。
"""
self.isDataframe(data)
if data.shape[0] < iso_length:
iso_length = data.shape[0]
iso_frame = pd.DataFrame(index=range(iso_length), columns=range(iso_num))
num = 0
for i in range(iso_num):
iso = IsolationForest(iso_estimators, n_jobs=-1)
iso_a = iso.fit_predict(data.iloc[:, 0:-1], data.iloc[:, -1])
frame_index = 0
for index, value in enumerate(iso_a):
if value != 1:
iso_frame.iloc[frame_index, num] = index
frame_index += 1
num += 1
iso_frame.dropna(axis=0, how="all", inplace=True)
iso_stats = pd.DataFrame(iso_frame.values.reshape(-1, 1)).iloc[:, 0].value_counts() / iso_num
return iso_stats
@time_metric
def rf_choose(self, data, rf_num):
"""
采用随机森林回归对 data 筛选合适的column
Parameters
----------
data: pd.core.DataFrame
rf_num: int, 需要保留的columns数量
Returns
-------
data: pd.core.DataFrame
imp: pd.core.DataFrame,权重表格, size为 rf_num * 3。 第一列 "str" 为 data 列的名字,
第二列 "int" 为 data 列 的下标, 第三列 "order"为 data 列的重要性排序。
"""
self.isDataframe(data)
imp = pd.DataFrame()
imp["str"] = data.columns
imp.drop([len(imp) - 1], axis=0, inplace=True)
rfr = RandomForestRegressor(500, n_jobs=-1, random_state=42)
rfr.fit(data.iloc[:, 0:-1], data.iloc[:, -1])
imp["int"] = rfr.feature_importances_
imp.sort_values("int", inplace=True, ascending=False)
imp["order"] = imp.index
# 删除imp较小的列
imp.drop(imp["order"][rf_num:imp.shape[0]], axis=0, inplace=True)
imp.sort_index(inplace=True)
data = data.loc[:, imp["str"].values.tolist() + ["SalePrice"]]
return data, imp
@staticmethod
def one_hot_of_stats(train_one_hot, target):
"""
Parameters
----------
train_one_hot: DataFrame, 训练集字符型one_hot编码后的数据
target: DataFrame, 预测值列
对data字符型列编码成的 one_hot 进行统计。
可以通过人工统计重组 train_one_hot,也可以用 self.regroup() 自动重组 train_one_hot。
"""
one_hot_stats = pd.DataFrame()
name_list = []
one_hot_mean = []
one_hot_std = []
one_hot_group = []
zero_one_list = []
zero_percnt_list = []
for i in train_one_hot.columns:
name_list.append(i)
one_hot_mean.append(float(np.mean(target[train_one_hot.loc[:, i] == 1])))
one_hot_std.append(float(np.std(target[train_one_hot.loc[:, i] == 1])))
one_hot_group.append(i.split('_')[0])
a = train_one_hot.loc[:, i].value_counts()
zero_one_list.append(a.index[0])
zero_percnt_list.append(a[0] / (train_one_hot.shape[0]))
one_hot_stats['name'] = name_list
one_hot_stats['zero_one'] = zero_one_list
one_hot_stats['zero_percnt'] = zero_percnt_list
one_hot_stats['group'] = one_hot_group
one_hot_stats['mean_price'] = one_hot_mean
one_hot_stats['std_price'] = one_hot_std
one_hot_stats = one_hot_stats.sort_values(['group', 'mean_price'], ascending=[True, True])
# 添加['determine']
one_hot_stats['sml'] = 0
one_hot_stats['big'] = 0
sml_list = []
big_list = []
for _, j in one_hot_stats.groupby('group'):
mean_col_ = j.columns.to_list().index('mean_price')
for index in range(j.shape[0]):
if index == 0:
if j.shape[0] == 1:
sml_list.append(0)
big_list.append(1)
else:
sml_list.append(0)
big_list.append((j.iloc[index + 1, mean_col_] - j.iloc[index, mean_col_]) /
j.iloc[index, mean_col_])
elif index == (j.shape[0] - 1):
big_list.append(1)
sml_list.append((j.iloc[index, mean_col_] - j.iloc[index - 1, mean_col_]) /
j.iloc[index - 1, mean_col_])
else:
big_list.append((j.iloc[index + 1, mean_col_] - j.iloc[index, mean_col_]) /
j.iloc[index, mean_col_])
sml_list.append((j.iloc[index, mean_col_] - j.iloc[index - 1, mean_col_]) /
j.iloc[index - 1, mean_col_])
one_hot_stats.loc[:, 'sml'] = sml_list
one_hot_stats.loc[:, 'big'] = big_list
return one_hot_stats
@staticmethod
@time_metric
def concat_col(one_hot, old_col_list, index_name=None):
"""
建议看作私有函数, 对data中的字符型列编码成的 one_hot 进行操作。
Parameters
----------
one_hot: 数据集字符型one_hot编码后的数据
old_col_list: 数据集未进行one_hot前的column的列表。
index_name: 右半部分名称。
例如:初始列名为 people, 变换后为 people_1, people_2.
"""
# global change_list
# change_list = []
col_list = one_hot.iloc[:, old_col_list].columns
tol = 0
n = 0
for i in col_list:
index_1, index_2 = i.split('_')
index_1_name = index_1
if index_name is not None:
index_2_name = str(index_name)
break
try:
n += 1
index_2 = int(index_2)
tol += index_2
index_2_name = str(int((tol / n)))
except (ValueError, TypeError):
index_2_name = str(np.random.randint(100))
break
new_col = index_1_name + '_' + index_2_name
one_hot[new_col] = one_hot.iloc[:, old_col_list].apply(lambda x: x.sum(), axis=1)
# change_list = change_list + list(col_list)
return None
def regroup(self, train_one_hot, target, df_size=100, max_percnt=0.8, scope_percnt=0.5):
"""
将data中的字符型列编码成 one_hot, 对 one_hot 进行重新分组。
原因: 如果直接删除的话可能会少很多信息.
分类的原则: 如果离散的数值 zero_percnt 特征大于 max_percnt,则不进行合并。
如果离散的数值大小范围在 scope_percnt 范围内则进行合并,范围外则不合并。
分组后应自己删除残余的离群点(默认小于5%应删除)
Parameters
----------
train_one_hot: DataFrame, 训练集字符型one_hot编码后的数据
target: DataFrame, 预测值列
df_size: 预定义DataFrame大小。
max_percnt:float, 看示例
scope_percnt: float, 看示例
实例:room 进行 one_hot 分为 room_0, room_1, room_2三个属性。
如果room_x中的占比大于max_percnt,该列就不再重组;
如果room_0和room_1代指数量0和1,若(1-0)/1 < scope_percnt则合并该两列,否则不合并。
"""
one_hot_stats = self.one_hot_of_stats(train_one_hot, target)
group_df = pd.DataFrame(index=range(df_size))
for group, one_hot in one_hot_stats.groupby('group'):
times = 0
index = 0
one_list = []
for i in list(one_hot.index):
if one_hot.loc[i, 'zero_percnt'] >= max_percnt:
if times == 0:
one_list.append(i)
times += 1
elif times == 1:
if one_hot.loc[i, 'sml'] >= scope_percnt:
one_list = []
times = 0
elif one_hot.loc[i, 'sml'] < scope_percnt:
one_list.append(i)
times += 1
elif times >= 2:
if one_hot.loc[i, 'sml'] >= scope_percnt:
self.concat_col(train_one_hot, one_list, index)
group_df[group + '_' + str(index)] = pd.Series(one_list)
one_list = []
times = 0
index += 1
elif one_hot.loc[i, 'sml'] < scope_percnt:
one_list.append(i)
times += 1
elif one_hot.loc[i, 'zero_percnt'] <= max_percnt:
if times < 2:
one_list = []
times = 0
elif times >= 2:
self.concat_col(train_one_hot, one_list, index)
group_df[group + '_' + str(index)] = pd.Series(one_list)
one_list = []
times = 0
index += 1
group_df.dropna(axis=0, how='all', inplace=True)
group_df.dropna(axis=1, how='all', inplace=True)
return group_df
# 原则:尽量先保持数据原有信息。然后增加数据信息,最后减少数据信息。
# 离群点和错误值筛选
# 预处理。先去除错误信息和无效信息。
class Preprocesing_m(Base_m):
"""
对 data 进行修改。
有两个原则:
1.在保证准确率的情况下能自动则优先自动。
2.在自动准确率不高的情况下人工观察,应做到只需向 Preprocessing_m 的函数 提供人工观察得到的参数,函数就能自动调节。
3.函数中的参数如果以 observe_ 命名的,则代表它需要经过观察分析确定。求取方法会在注释中声明。
4.带有_trimming字样的函数需进入函数内部进行调试,函数注释中会有调试方法,
5.带有 _num(eg:_1, _2)的函数代表其默认顺序,请谨慎调整位置。
"""
@time_metric
def change_small_1(self, data, observe_modify_list=None):
# # 第一步将变化太小数据的进行变换。(mean/std 越大说明变化越小)
# data_stats = self.data_of_stats(data)
# modify_list = data_stats.loc[data_stats['mean_std'] > 10, :].loc[:, "name"].values
data = self.modify(data, observe_modify_list)
return data
@time_metric
def float2object_2(self, data, observe_str_col_list=None):
# # 第二步,将那些本应是“字符型”,却用“数字”表示的列进行正确转换,我会在 data_stats 中观察
# data_stats = self.data_of_stats(data)
# # 观察value值较小的data_description.txt文件,确定其类型
# data_stats[data_stats.loc[:, "dtypes"] != "object"].sort_values("value_counts")
# str_col_list = ["MSSubClass", "OverallCond", "YrSold", "MoSold", "OverallQual"]
for col in observe_str_col_list:
data.loc[:, col] = data.loc[:, col].astype(str)
return data
@time_metric
def missing_check_3_trimming(self, data, observe_data_unmodify):
# 第三步,将缺失值补全,缺失值有两个原因,一个是本身为空,另一个是因为 pandas 读取了“NA” 的字符串。
# 对于本身为空的,应该使用0或均值或众数补全;对于本身是“NA”字符串的,应该观察每列具体意义。
# 为了区分这两类,我是按以下操作做的:
# 先用 pandas 读取了原始数据,用 .info() 观察存在了很多空值。
# 然后在 sublime 中修改所有“NA”字符为“AN”,再次用 .info() 观察空值。
# 如果不同,则文档本身存在了“NA”字符串, 如果相同,则内容本身为空。
data_stats = self.data_of_stats(data)
""" data_unmodify:
train_unmodify = pd.read_csv("data_unmodify/train.csv")
x_test_unmodify = pd.read_csv("data_unmodify/test.csv")
data_unmodify = pd.concat([train_unmodify, pd.concat([x_test_unmodify, y_test.iloc[:, [1]]], axis=1)], axis=0)
"""
data_stats["data_unmodify_nan_counts"] = self.data_of_stats(observe_data_unmodify).loc[:, "nan_counts"]
# 观察发现都是“NA”字符串
# 且本次预测中数字型中的“NA”字符串代表0,将用0代替, 字符型中的“NA”字符串代表无,我将会用“AN”代替他
int_col = data_stats[data_stats.loc[:, "dtypes"] != "object"].loc[:, "name"].values
data.loc[:, set(int_col) - {'SalePrice'}] = data.loc[:, set(int_col) - {'SalePrice'}].fillna(0)
str_col = set(data.columns) - set(int_col)
data.loc[:, str_col] = data.loc[:, str_col].fillna("AN")
return data
@time_metric
def delete_radio_big_or_small_4(self, data, observe_drop_list=None):
# # 第四步,再次求 data_stats, 观察其 max_ratio 项,过大则说明数据较单一,过小则很可能类似“ID”,都要进行观察筛选
# data_stats = self.data_of_stats(data)
# data_stats.iloc[0:-1, :].sort_values(by="max_ratio")
# drop_list = ['Id', 'PoolQC', 'Street', 'PoolArea'] # , 14, 69, 45, 22, 39
data.drop(observe_drop_list, axis=1, inplace=True)
return data
@time_metric
def delete_corr_small_5(self, data, observe_corr_drop_list=None):
# # 第五步,除去corr太小的列。
# train = data.iloc[0:train_shape_0, :]
# data_stats = pre.data_of_stats(train)
# data_stats.sort_values(by='corr')
# # 训练集corr值小于0.1的列表(测试集SalePrice未知,所以应该用train观察,data最终改变)
# corr_drop_list = data_stats.loc[abs(data_stats.loc[:, 'corr']) < 0.1, 'name'].values.tolist()
data.drop(observe_corr_drop_list, axis=1, inplace=True)
return data
@time_metric
def rebuilt_data_6_trimming(self, data):
data_stats = self.data_of_stats(data)
# 第六步,对剩余的数据进行处理,数值型减小偏度, 字符型 one_hot 编码,共同构建 data_new
data_one_hot = pd.get_dummies(data.iloc[:, data_stats.loc[data_stats.loc[:, "dtypes"] == 'object', :].index],
prefix=data_stats.loc[data_stats.loc[:, "dtypes"] == 'object', :].index)
data_num = data.iloc[:, data_stats.loc[data_stats.loc[:, "dtypes"] != 'object', :].index.tolist()]
# 调整偏度。
# pt = PowerTransformer(standardize=True)
# data_new_num = data_num.copy()
# for i in data_num.columns:
# print(i)
# data_new_num.loc[:, i] = pt.fit_transform(data_num.loc[:, [i]])
# # RuntimeWarning: dividebyzeroencountered in loglike = -n_samples / 2 * np.log(x_trans.var())
# # LotArea 1stFlrSF GrLivArea
# for i in ["1stFlrSF", "LotArea", "GrLivArea"]:
# counts = data.loc[:, i].value_counts().sort_index()
# plt.plot(counts.index.to_list(), counts.values, "ro")
# plt.title(i)
# plt.show()
# del data_new_num
drop_col = set(list(data_num.loc[data_num.loc[:, "1stFlrSF"] > 3500, :].index)
+ list(data_num.loc[data_num.loc[:, "LotArea"] > 100000, :].index)
+ list(data_num.loc[data_num.loc[:, "GrLivArea"] > 4000, :].index))
self.change_train_shape_0(drop_col)
data_one_hot.drop(drop_col, axis=0, inplace=True)
data_one_hot.reset_index(drop=True, inplace=True)
data_one_hot.drop(data_one_hot.loc[:, data_one_hot.std() == 0].columns, axis=1, inplace=True)
data_num = data_num.drop(drop_col, axis=0, inplace=False)
data_num.reset_index(drop=True, inplace=True)
x_num_data = data_num.iloc[:, 0:-1]
y_num_train = data_num.iloc[0:TRAIN_SHAPE_0, [-1]]
y_num_test = data_num.iloc[TRAIN_SHAPE_0::, [-1]]
pt_x = PowerTransformer(standardize=True)
x_num_data = pd.DataFrame(pt_x.fit_transform(x_num_data), columns=x_num_data.columns, index=x_num_data.index)
pt_y = PowerTransformer(standardize=False)
y_num_train = pd.DataFrame(pt_y.fit_transform(y_num_train), columns=y_num_train.columns, index=y_num_train.index)
y_num_test = pd.DataFrame(pt_y.transform(y_num_test), columns=y_num_test.columns, index=y_num_test.index)
data = pd.concat([data_one_hot,
pd.concat([x_num_data,
pd.concat([y_num_train, y_num_test], axis=0)], axis=1)], axis=1)
data_one_hot_columns = data_one_hot.columns
x_num_data_columns = x_num_data.columns
return data, data_one_hot_columns, x_num_data_columns
@time_metric
def findoutlier_7_trimming(self, data, x_num_data_columns=None): # 六七不分开
# # 第七步,用IsolationForest对数据进行离群值进行检测(只对数值型数据操作)
# 其实还有一种新的设想。因为IsolationForest分两个步骤:(由定义可知,其只对'连续性数据'有意义)
# 1.随机选出训练数据,随机选择特征,随机选择划分点。
# 2.遍历测试数据,求出测试数据在每棵树的路径长度,再求出均值。
# 缺点:建树过程是对特征随机选择,但是忽视了各个特征对训练集的权重不同,如果用Lasso或者随机森林特征选择赋予权重,
# 则能更好的求解多维数据求离群点。(涉于时间关系,本次并没有重做)
#
# self.isDataframe(data), 'Please input DataFrame'
# if x_num_data_columns is None:
# iso_stats = self.iso_check(data, iso_length=200)
# else:
# iso_stats = self.iso_check(data.loc[:, x_num_data_columns], iso_length=200)
# iso_drop = iso_stats[iso_stats >= 0.7].index # 0.7为观测值,对同一数据多次测定,iso_drop不变说明异常点提取稳定。
# 0.8, [2207, 631, 1826, 1585, 632, 529, 88, 700, 1828]
iso_drop = [2207, 631, 1826, 1585, 632, 529, 88, 700, 1828]
self.change_train_shape_0(iso_drop)
data.drop(iso_drop, axis=0, inplace=True)
data.reset_index(drop=True, inplace=True)
return data
# 特征选择,增加信息,再减去信息。
class Feature_selection_m(Base_m):
"""
对标准化后的 train 进行 pca 提取信息,至于很多人担心的PCA后每列具体的意义已经不确定,对我来说是毫无压力的,
每个人收集的资料本身就是主观片面的,列的意义也只是针对这部分人有效。我认为,只需要知道这些数据投影到某一位空间,
或者以另一个角度看会更有效,这就可以了。提取的信息的过程中做了进一步的优化。类似于膨胀和腐蚀,先膨胀再腐蚀去除杂乱点。
"""
def __init__(self, data, corr_default=0.1):
"""
corr_default: int, 如果为空,则利用全部数据; 如果不为空,则选取数据中corr大于corr_default的列。
"""
self.data = data
self.train = data.iloc[0:TRAIN_SHAPE_0, :] # 提取 train
self.data = self.data.loc[:, self.train.std() != 0]
self.train = self.train.loc[:, self.train.std() != 0] # 去除train当中std为0的列。one_hot时用的是data, train可能数值都相同。
if corr_default is None:
pass
else:
self.data = self.data.loc[:, self.train.corr().iloc[:, -1] > corr_default]
self.train = self.train.loc[:, self.train.corr().iloc[:, -1] > corr_default]
self.isDataframe(self.train)
self.pca = PCA()
self.std = StandardScaler()
self.base_list = list(abs(self.train.corr().iloc[:, -1]))[0:-1]
self.sort_list = sorted(self.base_list, reverse=True)
self.index_list = []
for i in self.sort_list:
self.index_list.append(self.base_list.index(i))
# 筛选corr较大的列
def one(self):
# 第一种方法,构造一个方形Dataframe,行列相同,都是index_list,表格内每个数字表示该两列数据pca后corr变化趋势。
pass
# 筛选corr较大的列
@time_metric
def two(self, flag_num_corr_change=True, flag_times=2, corr_default=0.3): # flag_num_corr_change和 flag_times双重循环控制标识符FLAG,
"""
:param flag_num_corr_change:
:param flag_times:
:param corr_default: 如果将要新增列的corr()小于corr_default,则停止增加该列。
:return:
"""
while flag_num_corr_change and flag_times: # 一个自动调节结束次数,一个根据默认次数结束循环。
flag_num_corr_change = False
# 第二种方法,对index_list按顺序两两pca,如果corr值比原来大,则用pca产生的新列替换掉原来的列。
cal_times = 0
for index_i, i in enumerate(self.index_list):
# print("outer", index_i, i)
for index_j, j in enumerate(self.index_list):
# print("inter", index_j, j)
if index_i >= index_j:
continue
# print(index_i, i)
# print(index_j, j)
try:
__pca_train = pd.DataFrame(self.pca.fit_transform(self.train.iloc[:, [i, j]]))
__pca_train = pd.DataFrame(self.std.fit_transform(__pca_train))
__pca_data = pd.DataFrame(self.pca.fit_transform(self.data.iloc[:, [i, j]]))
__pca_data = pd.DataFrame(self.std.fit_transform(__pca_data))
a_last = (abs(pd.concat([__pca_train, self.train.iloc[:, [-1]]], axis=1).corr().iloc[:, -1])
- self.sort_list[index_i]).to_list() # 增长或者减少。
if max(a_last[0], a_last[1], 0) == 0:
pass
if max(a_last[0], a_last[1], 0) == a_last[0]: # a_last[0]包含了原先绝大部分信息。
flag_num_corr_change = True
# self.train.iloc[:, i] = __pca_train.iloc[:, 0] # 所以直接用a_last[0] 替换 掉原有列。
self.data.iloc[:, i] = __pca_data.iloc[:, 0]
if max(a_last[0], a_last[1], 0) == a_last[1] and a_last[1] > corr_default: # a_last[1]仅包含了原先的少量信息。
flag_num_corr_change = True # 所以在原有列基础上 追加 a_last[1]
# self.train["corr" + str(self.train.shape[1])] = __pca_train.iloc[:, 1]
# self.train = self.train.iloc[:, list(range(self.train.shape[1]-2))+[-1, -2]]
self.data["corr" + str(self.data.shape[1])] = __pca_data.iloc[:, 1]
self.data = self.data.iloc[:, list(range(self.data.shape[1] - 2)) + [-1, -2]]
cal_times += 1
if cal_times % 1000 == 0:
print(self.data.shape)
print('round {} times'.format(cal_times))
except IndexError:
print(i, j, index_i, index_j)
flag_times -= 1
# self.two(flag_num_corr_change, flag_times)
# 切分数据集
class Train_test_split_m(Base_m):
def split(self, data=None, random_state=50): # 调参时可随机选取random_state,获取更大范性
self.isarrayorDataFrame(data)
train_x, test_x, train_y, test_y = train_test_split(data.iloc[:, 0:-1], data.iloc[:, -1],
test_size=0.2, random_state=random_state, shuffle=False)
return train_x, test_x, train_y, test_y
# 评分
class Score_m(Base_m):
def __init__(self, train_x, train_y, test_x, test_y):
self.train_x = train_x
self.train_y = train_y
self.test_x = test_x
self.test_y = test_y
self.best_score = 0
self.stack_r2_list = []
self.stack_square_list = []
self.stack_abs_list = []
self.stack_score = 0
self.stack_file_index = os.listdir("dump/stack").__len__()
self.ls_r2_list = []
self.ls_square_list = []
self.ls_abs_list = []
self.ls_score = 0
self.ls_file_index = os.listdir("dump/ls").__len__()
self.rg_r2_list = []
self.rg_square_list = []
self.rg_abs_list = []
self.rg_score = 0
self.rg_file_index = os.listdir("dump/rg").__len__()
self.lgbm_r2_list = []
self.lgbm_square_list = []
self.lgbm_abs_list = []
self.lgbm_score = 0
self.lgbm_file_index = os.listdir("dump/lgbm").__len__()
self.xgb_r2_list = []
self.xgb_square_list = []
self.xgb_abs_list = []
self.xgb_score = 0
self.xgb_file_index = os.listdir("dump/xgb").__len__()
def my_ls(self, params):
ls = Lasso(max_iter=100000, **params["ls_1"])
ls.fit(self.train_x, self.train_y)
self.ls_score = np.sqrt(-cross_val_score(ls, self.train_x.values, self.train_y.values,
scoring='neg_mean_squared_error', cv=5, ).mean())
if self.ls_score > self.best_score:
self.best_score = self.ls_score
joblib.dump(ls, os.path.join('dump/ls', "model_" + str(self.ls_file_index) + ".pkl")) # 保存模型
self.ls_r2_list.append(r2_score(self.test_y, ls.predict(self.test_x)))
self.ls_square_list.append(mean_squared_error(self.test_y, ls.predict(self.test_x)))
self.ls_abs_list.append(mean_absolute_error(self.test_y, ls.predict(self.test_x)))
return self.ls_score
def my_rg(self, params):
rg = Ridge(max_iter=100000, **params["rg_1"])
rg.fit(self.train_x, self.train_y)
self.rg_score = np.sqrt(-cross_val_score(rg, self.train_x.values, self.train_y.values,
scoring='neg_mean_squared_error', cv=5, ).mean())
if self.rg_score > self.best_score:
self.best_score = self.rg_score
joblib.dump(rg, os.path.join('dump/rg', "model_" + str(self.rg_file_index) + ".pkl")) # 保存模型
self.rg_r2_list.append(r2_score(self.test_y, rg.predict(self.test_x)))
self.rg_square_list.append(mean_squared_error(self.test_y, rg.predict(self.test_x)))
self.rg_abs_list.append(mean_absolute_error(self.test_y, rg.predict(self.test_x)))
return self.rg_score
def my_lgbm(self, params):
lgbm = LGBMRegressor(**params["lgbm_1"])
lgbm.fit(self.train_x, self.train_y)
self.lgbm_score = np.sqrt(-cross_val_score(lgbm, self.train_x.values, self.train_y.values,
scoring='neg_mean_squared_error', cv=5, ).mean())
if self.lgbm_score > self.best_score:
self.best_score = self.lgbm_score
joblib.dump(lgbm, os.path.join('dump/lgbm', "model_" + str(self.lgbm_file_index) + ".pkl")) # 保存模型
self.lgbm_r2_list.append(r2_score(self.test_y, lgbm.predict(self.test_x)))
self.lgbm_square_list.append(mean_squared_error(self.test_y, lgbm.predict(self.test_x)))
self.lgbm_abs_list.append(mean_absolute_error(self.test_y, lgbm.predict(self.test_x)))
return self.lgbm_score
def my_xgb(self, params):
xgb = XGBRegressor(**params["xgb_1"])
xgb.fit(self.train_x, self.train_y)
self.xgb_score = np.sqrt(-cross_val_score(xgb, self.train_x.values, self.train_y.values,
scoring='neg_mean_squared_error', cv=5, ).mean())
if self.xgb_score > self.best_score:
self.best_score = self.xgb_score
joblib.dump(xgb, os.path.join('dump/xgb', "model_" + str(self.xgb_file_index) + ".pkl")) # 保存模型
self.xgb_r2_list.append(r2_score(self.test_y, xgb.predict(self.test_x)))
self.xgb_square_list.append(mean_squared_error(self.test_y, xgb.predict(self.test_x)))
self.xgb_abs_list.append(mean_absolute_error(self.test_y, xgb.predict(self.test_x)))
return self.xgb_score
def my_stack(self, params):
lr = LinearRegression()
ls_1 = Lasso(max_iter=100000, **params["ls_1"])
rg_1 = Ridge(**params["rg_1"])
lgbm_1 = LGBMRegressor(**params["lgbm_1"])
xgb_1 = XGBRegressor(**params["xgb_1"])
stack = regressor.StackingRegressor(regressors=[ls_1, rg_1, xgb_1, lgbm_1], meta_regressor=lr, verbose=0)
stack.fit(self.train_x, self.train_y)
self.stack_score = np.sqrt(-cross_val_score(stack, self.train_x.values, self.train_y.values,
scoring='neg_mean_squared_error', cv=5, ).mean())
if self.stack_score > self.best_score:
self.best_score = self.stack_score
joblib.dump(stack, os.path.join('dump/stack', "model_" + str(self.stack_file_index) + ".pkl")) # 保存模型
self.stack_r2_list.append(r2_score(self.test_y, stack.predict(self.test_x)))
self.stack_square_list.append(mean_squared_error(self.test_y, stack.predict(self.test_x)))
self.stack_abs_list.append(mean_absolute_error(self.test_y, stack.predict(self.test_x)))
return self.stack_score
# 调参
class Opt(object):
def __init__(self, train_x, train_y, test_x, test_y):
self.space_ls = {
"ls_1": {
'alpha': hp.uniform('ls_1_alpha', 0.001, 100)
}
}
self.space_rg = {
"rg_1": {
'alpha': hp.uniform('rg_1_alpha', 0.001, 100),
}
}
self.space_lgbm = {
"lgbm_1": {
"num_leaves": hp.choice('lgbm_1_nl', [i for i in range(12, 64)]),
"learning_rate": hp.choice('lgbm_1_lr', [0.01, 0.03, 0.05, 0.07, 0.1, 0.13, 0.15, 0.17, 0.2]),
"n_estimators": hp.choice('lgbm_1_estima', [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]),
"subsample": hp.choice('lgbm_1_subsample', [0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6]),
}
}
self.space_xgb = {
"xgb_1": {
"learning_rate": hp.choice("xgb_1_lr", [i / 10 for i in range(1, 5)]),
"max_depth": hp.choice("xgb_1_depth", range(1, 7)),
"n_estimators": hp.choice("xgb_1_estimators", [100, 300, 500, 700, 1000]),
"subsample": hp.choice("xgb_1_subsample", [i / 10 for i in range(5, 10)]),
}
}
self.space_stack = {
'ls_1': {
'alpha': hp.uniform('ls_1_alpha', 0.001, 100),
},
'rg_1': {
'alpha': hp.uniform('rg_1_alpha', 0.001, 100),
},
'lgbm_1': {
"num_leaves": hp.choice('lgbm_1_nl', [i for i in range(12, 64)]),
"learning_rate": hp.choice('lgbm_1_lr', [0.01, 0.03, 0.05, 0.07, 0.1, 0.13, 0.15, 0.17, 0.2]),
"n_estimators": hp.choice('lgbm_1_estima', [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]),
"subsample": hp.choice('lgbm_1_subsample', [0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6]),
},
'xgb_1': {
"learning_rate": hp.choice("xgb_1_lr", [i / 10 for i in range(1, 5)]),
"max_depth": hp.choice("xgb_1_depth", range(1, 7)),
"reg_lambda": hp.choice("xgb_1_lambda", [0, 1, 10, 100]),
"reg_alpha": hp.choice("xgb_1_alpha", [0, 1, 10, 100]),
"min_child_weight": hp.choice("xgb_1_weight", [i / 10 for i in range(5, 50, 1)]),
"n_estimators": hp.choice("xgb_1_estimators", [100, 300, 500, 700, 1000]),
"max_delta_step": hp.choice("xgb_1_step", [0, ]),
"gamma": hp.choice("xgb_1_gamma", [0, 1, 2, 3, 10, 100]),
"subsample": hp.choice("xgb_1_subsample", [i / 10 for i in range(5, 10)]),
"colsample_bylevel": hp.choice("xgb_1_bylever", [i / 10 for i in range(5, 10)]),
"colsample_bynode": hp.choice("xgb_1_bynode", [i / 10 for i in range(5, 10)]),
"colsample_bytree": hp.choice("xgb_1_bytree", [i / 10 for i in range(5, 10)]),
},
}
self.max_evals = None
self.space_choose = None
self.score_m = Score_m(train_x, train_y, test_x, test_y)
# def cal_f(self, score_m, params):
def cal_f(self, params):
if self.space_choose == "ls":
return self.score_m.my_ls(params)
elif self.space_choose == "rg":
return self.score_m.my_rg(params)
elif self.space_choose == "xgb":
return self.score_m.my_xgb(params)
elif self.space_choose == "lgbm":
return self.score_m.my_lgbm(params)
elif self.space_choose == "stack":
return self.score_m.my_stack(params)
else:
return 1
def cal_best(self):
for space_choose in [self.space_ls, self.space_lgbm, self.space_rg, self.space_stack, self.space_xgb]:
trials = Trials()
if space_choose == self.space_lgbm:
self.max_evals = 100
self.space_choose = "lgbm"
elif space_choose == self.space_xgb:
self.max_evals = 100
self.space_choose = "xgb"
elif space_choose == self.space_stack:
self.max_evals = 100
self.space_choose = "stack"
elif space_choose == self.space_rg:
self.max_evals = 2500
self.space_choose = "rg"
else:
self.max_evals = 2500
self.space_choose = "ls"
best = fmin(fn=self.cal_f, space=space_choose, algo=tpe.suggest, max_evals=self.max_evals, trials=trials)
if __name__ == "__main__":
# data
train = pd.read_csv("data/train.csv")
x_test = pd.read_csv("data/test.csv")
y_test = pd.read_csv('data/sample_submission.csv')
# 合并了 y_test,对 data 操作的话,注意避免 运用 y_test 数据。
data = pd.concat([train, pd.concat([x_test, y_test.iloc[:, [1]]], axis=1)], axis=0)
data.index = range(data.shape[0])
TRAIN_SHAPE_0 = train.shape[0]
# # preprocessing
pre = Preprocesing_m()
# data_stats = pre.data_of_stats(data) #
# data_stats = pre.data_of_stats(data)
# observe_modify_list = data_stats.loc[data_stats['mean_std'] > 10, :].loc[:, "name"].values
observe_modify_list = ['YearBuilt', 'YearRemodAdd', 'GarageYrBlt', 'YrSold']
data = pre.change_small_1(data, observe_modify_list)
# data_stats = pre.data_of_stats(data) #
# data_stats[data_stats.loc[:, "dtypes"] != "object"].sort_values("value_counts") #
observe_str_col_list = ["MSSubClass", "OverallCond", "YrSold", "MoSold", "OverallQual"]
data = pre.float2object_2(data, observe_str_col_list)
train_unmodify = pd.read_csv("data_unmodify/train.csv")
x_test_unmodify = pd.read_csv("data_unmodify/test.csv")
data_unmodify = pd.concat([train_unmodify, pd.concat([x_test_unmodify, y_test.iloc[:, [1]]], axis=1)], axis=0)
data = pre.missing_check_3_trimming(data, observe_data_unmodify=data_unmodify)
# data_stats = pre.data_of_stats(data)
# data_stats.iloc[0:-1, :].sort_values(by="max_ratio")
observe_drop_list = ['Id', 'PoolQC', 'Street', 'PoolArea']
data = pre.delete_radio_big_or_small_4(data, observe_drop_list)
# train = data.iloc[0: train_shape_0, :]
# data_stats = pre.data_of_stats(train)
# data_stats.sort_values(by='corr')
# # 训练集corr值小于0.1的列表(测试集SalePrice未知,所以应该用train观察,data最终改变)
# corr_drop_list = data_stats.loc[abs(data_stats.loc[:, 'corr']) < 0.1, 'name'].values.tolist()
observe_corr_drop_list = ['BsmtFinSF2', 'LowQualFinSF', 'BsmtHalfBath', '3SsnPorch', 'MiscVal']
data = pre.delete_corr_small_5(data, observe_corr_drop_list)
data, data_one_hot_columns, x_num_data_columns = pre.rebuilt_data_6_trimming(data)
# train_one_hot = data.iloc[0: train_shape_0, :].loc[:, data_one_hot_columns]
# train_one_hot.drop(train_one_hot.loc[:, train_one_hot.std() == 0].columns, axis=1, inplace=True)
# pre.regroup(train_one_hot, target=data.iloc[0: train_shape_0, [-1]])
data = pre.findoutlier_7_trimming(data, x_num_data_columns)
fts = Feature_selection_m(data)
fts.two()
data = fts.data
# 用随机森林获取权重较大的列 rand_col, 用 corr() 获取相关性高的列 corr_col
# set(rand_col) - set(corr_col) 可以获得非相关性特征small_corr_col。
# 然后将 corr_col 进行 pca 整合为 corr 更高的特征 new_corr_col。
# 最后set(new_corr_col) + set(corr_col) 就可以获取得到最后的特征。
# pre.rf_choose(data=data, rf_num=20)
data.to_csv('data/mydata.csv', index=0)
data = pd.read_csv('data/mydata.csv')
train_x = data.iloc[0: TRAIN_SHAPE_0, 0:-1]
train_y = data.iloc[0: TRAIN_SHAPE_0, -1]
test_x = data.iloc[TRAIN_SHAPE_0::, 0:-1]
test_y = data.iloc[TRAIN_SHAPE_0::, -1]
# test_x.reset_index(drop=True, inplace=True)
# test_y.reset_index(drop=True, inplace=True)
op = Opt(train_x, train_y, test_x, test_y)
op.cal_best()
房价预测B
最新推荐文章于 2024-10-13 22:47:48 发布