当前位置:   article > 正文

Python机器学习实例----用神经网络进行价格预测_python神经网络预测模型

python神经网络预测模型

本篇文章主要来记录一下使用机器学习解决我遇到的问题的过程。

目录

一、问题描述

二、数据处理

        2.1 缺失值处理

        2.2 异常值处理

        2.3 特征工程

三、降维

四、模型建立

五、模型训练


一、问题描述

        首先,先简要描述一下这个问题:题目要求我们进行二手车价格的预测,题中给了data1和data2两个数据集,data1数据集有30000条数据,data2数据集有5000条数据。这两个数据集中都含有36列变量,各变量字段如下图所示:

        其中anonymousFeature是匿名变量,一共有15个。数据集data1和data2的不同之处就是data1的price变量有数据,而data2的price是需要我们填写的。也就是说,题目要求我们使用data1数据集来建立模型,用建立好的模型预测data2中的二手车价格。

data1部分数据如下所示:

 data2部分数据如下所示:

        可以看出,15个匿名变量的话我是对其编了号,从D_0到D_14。data2中price变量没有数据,这是要求我们进行预测的。还要提一句的是,其实变量D_14是有数据的,只不过它缺失值太多,截图的话正好截到了它没有数据的情况。接下来就开始分步骤进行解决。

二、数据处理

        2.1 缺失值处理

        首先肯定是检查一下数据的缺失值并进行处理。我们先分别读取数据为训练集和测试集,然后将这两个数据集合并:

  1. Train = pd.read_csv('E:\\data1.csv', delimiter=',') # 训练集
  2. Test = pd.read_csv('E:\\data2.csv', delimiter=',') # 测试集
  3. print('Train shape: ', Train.shape)
  4. print('Test shape: ', Test.shape)
  5. Combine = pd.concat([Train, Test]) # 将测试集和训练集合并
  6. print('Combine shape: ', Combine.shape)

        上图显示的是训练集和测试集的数据维度,以及合并后的维度。接着,使用isna().sum()函数查看一下数据集各列的缺失值。

print(Combine.isna().sum())  # 统计数据集各列缺失值个数

        为了更直观地看出数据确实情况,我将有缺失值的变量跟据缺失值的多少从高到低排列,并进行可视化:

        通过上图可以看出,一共有16列变量有缺失值,其中price变量的5000个缺失值是要我们进行预测的,所以不进行额外处理。对于D_14、D_6和D_3,可以看到它们的缺失值过多,所以对模型的训练毫无帮助,甚至可以说是噪音,于是我们将它们删除。对于D_9、D_7、D_8、country、maketype这几列,为了避免对其整体数据的分布产生较大的影响,我们用缺失值的上一个单元格的数据进行填充,如果上一个单元格也是缺失值,那么就用该数据列的众数填充。而对于D_0、D_12、D_10、modelyear、carCode、gearbox、D_11这几列数据,由于缺失值相对较少,所以我们分别用它们对应列的众数填充。

        众数填充的话可以使用如下代码操作,方便快捷:

Combine = Combine.fillna(Combine.mode().iloc[0, :])  # 用每一列出现最多的数据填充。

        2.2 异常值处理

        首先,可以使用Combine.info()来查看一下变量的数据类型,这里为了更直观,我还是使用了画图的方法:

        如上图所示,33列(上文说过的缺失值较多的3列先不考虑)变量中D_11是object类型,通过查看数据集,我们发现变量D_11的数据如下所示:

  

        我们大概可以猜出,变量D_11表示的是车的三围。所以我们对其进行处理,新建长、宽、高三列变量来拆分变量D_11,代码如下:

  1. # 匿名特征处理:D_11。拆分为length、width和high
  2. series1 = Combine['D_11'].str.split('*', expand=True)
  3. Combine['length'] = series1[0]
  4. Combine['width'] = series1[1]
  5. Combine['high'] = series1[2]
  6. Combine['length'] = Combine['length'].astype(float)
  7. Combine['width'] = Combine['width'].astype(float)
  8. Combine['high'] = Combine['high'].astype(float)

        接着,我们再观察数据集。其中,变量carid表示的是车辆的id号,是唯一值,对我们的模型训练没有帮助,可以删掉;匿名变量D_0的话,我们使用Combine.D_0.value_counts()查看该变量值的分布:

        由上图可以看出,变量D_0共有1、3、4三个值,但值为一的数据有34935个,占比超过了99%,所以,我们可以说:变量D_0的值的分布极度不平衡,无法为模型训练提供有用信息,可以将其删除。

        通过以上分析后,我们将需要删除的变量进行删除操作:

Combine.drop(['D_0', 'D_3', 'carid', 'D_6', 'D_11', 'D_14'], axis=1, inplace=True)  # 删除不利于模型训练的变量

        其中inplace=True表示的是原数组的值被直接修改。默认的话是inplace=False,它表示的是进行drop操作后将得到一个返回值,这个返回值是修改后的数组,也就是说你要新定义一个变量来接收这个返回值,才能得到修改后的数组。而inplace=True的话呢,就不需要接收返回值,目标数组将会被直接修改。

        2.3 特征工程

        首先,我们使用Combine.nunique()查看一下各变量不同值的个数:

        1.离散特征编码

        跟据上图,挑选离散变量'carCode', 'color', 'country', 'maketype', 'oiltype', 'D_7', 'D_8', 'D_9', 'D_10', 'D_13'进行One-hot编码:

        定义One-hot编码函数:

  1. def One_Hot(OneHotCol):
  2. new_cols = []
  3. for old_col in OneHotCol:
  4. new_cols += sorted(['{0}_{1}'.format(old_col, str(x).lower()) for x in set(Combine[old_col].values)])
  5. ec = OneHotEncoder()
  6. ec.fit(Combine[OneHotCol].values)
  7. # list(Combine.index.values) # 取出Combine的索引
  8. OneHotCode = pd.DataFrame(ec.transform(Combine[OneHotCol]).toarray(), columns=new_cols,
  9. index=list(Combine.index.values)).astype(int)
  10. return OneHotCode
'
运行

           调用编写的函数,对上述离散变量进行One-hot编码:

  1. OneHotCol = ['carCode', 'color', 'country', 'maketype', 'oiltype', 'D_7', 'D_8', 'D_9', 'D_10', 'D_13']
  2. OneHotCode = One_Hot(OneHotCol)
  3. # 合并Combine和OneHotCode
  4. Combine = pd.concat([Combine, OneHotCode], axis=1)

           为什么要使用One-hot编码呢?简单地说,可以理解为:One-hot编码使离散特征的取值扩展到了欧式空间,使离散特征里各类别之间的距离在欧式空间里相等,比普通的用数字进行类别的编码方式更加合理。本代码中,我使用的是sklearn库中preprocessing模块里的OneHotEncoder()函数进行的One-hot编码,所以需要导包。

        2.日期特征编码

        通过观察数据集,我们发现变量tradeTime、registerDtae、licenseDate、和D_12都表示的是日期信息,所以我们可以考虑将它们转化为日期的标准格式,再提取它们的年、月、日、周几这些特征信息。

日期格式转换函数(将日期转换为xxxx-xx-xx这种标准格式)

  1. def date_proc(x):
  2. month = int(x[4:6])
  3. if month == 0:
  4. month = 1
  5. if len(x) == 6:
  6. return x[:4] + '-' + str(month)
  7. else:
  8. return x[:4] + '-' + str(month) + '-' + x[6:]
'
运行

日期特征提取函数(提取年、月、日、周几这些特征)

  1. def date_transform(df, fea_col):
  2. for f in tqdm(fea_col):
  3. df[f] = pd.to_datetime(df[f].astype('str').apply(date_proc))
  4. df[f + '_year'] = df[f].dt.year
  5. df[f + '_month'] = df[f].dt.month
  6. df[f + '_day'] = df[f].dt.day
  7. df[f + '_dayofweek'] = df[f].dt.dayofweek
  8. return (df)
'
运行

        其中,tqdm()函数作用是添加进度条,参数是一个可迭代对象。

        对'registerDate', 'tradeTime', 'licenseDate'这三个变量进行标准日期格式转换并提取日期特征:

  1. Date = ['registerDate', 'tradeTime', 'licenseDate']
  2. Combine = date_transform(Combine, Date)

        由于变量D_12表示的日期信息只有年和月,所以我们对其进行标准日期格式转换后单独对其进行日期特征提取(只提取年和月):

  1. # 匿名特征处理 D_12
  2. Combine = Combine[Combine['D_12'].notna()]
  3. Combine['D_12'].astype('str').apply(date_proc)
  4. Combine['D_12'] = pd.to_datetime(Train['D_12'])
  5. Combine['D_12_year'] = Combine['D_12'].dt.year
  6. Combine['D_12_month'] = Combine['D_12'].dt.month

       上述的日期特征提取完毕后,Combine中的这些特征变量名如下所示:

         同样,再次调用我们编写的One-hot编码函数,对这些特征进行One-hot编码:

  1. # 对提取的日期特征进行One-hot编码
  2. OneHotCol2 = ['registerDate_year', 'registerDate_month', 'registerDate_dayofweek', 'tradeTime_year', 'tradeTime_month',
  3. 'tradeTime_dayofweek', 'licenseDate_year', 'licenseDate_month', 'licenseDate_dayofweek', 'D_12_year',
  4. 'D_12_month']
  5. OneHotCode2 = One_Hot(OneHotCol2)
  6. Combine = pd.concat([Combine, OneHotCode2], axis=1)

        3.构建新特征

        通过上述的几个日期信息变量,我们可以考虑构建一些有价值的新的特征。在本题中,我构建的特征是汽车使用天数,汽车注册日期距今天数,汽车上线日期距今天数。

  1. # 构建特征:汽车使用天数
  2. Combine['used_time1'] = (pd.to_datetime(Combine['tradeTime'], format='%Y%m%d', errors='coerce') -
  3. pd.to_datetime(Combine['registerDate'], format='%Y%m%d', errors='coerce')).dt.days
  4. # 构建特征:汽车注册日期距今天数
  5. Combine['used_time2'] = (
  6. pd.datetime.now() - pd.to_datetime(Combine['registerDate'], format='%Y%m%d', errors='coerce')).dt.days
  7. # 构建特征:汽车上线日期距今天数
  8. Combine['used_time3'] = (pd.datetime.now() - pd.to_datetime(Combine['tradeTime'], format='%Y%m%d', errors='coerce')).dt.days

        4.数据分桶

        编写日期分桶函数函数,并对'used_time1', 'used_time2', 'used_time3'进行分桶:

  1. # 数据分桶函数
  2. def cut_group(df, cols, num_bins=50):
  3. for col in cols:
  4. all_range = int(df[col].max() - df[col].min())
  5. # ceil():返回一个数的上取整数;floor():返回一个数的下舍整数
  6. bin = [np.ceil(df[col].min() - 1) + np.floor(i * all_range / num_bins) for i in range(num_bins + 2)]
  7. # bin是一个列表,区间两端的选取就是跟据bin里的数据决定。如第一个区间就是[bin[0], bin[1]]
  8. df[col + '_bin'] = pd.cut(df[col], bin, labels=False)
  9. return df
  10. # 对汽车使用天数,汽车注册日期距今天数 ,汽车上线日期距今天数进行数据分桶
  11. CutCol = ['used_time1', 'used_time2', 'used_time3']
  12. Combine = cut_group(Combine, CutCol, 50)

        数据分桶是一种将多个连续值分组为较少数量的“桶”的方法,也就是将连续的多个值分成区间的方法,可以减小数据量。

        5.特征扩充

        将匿名特征之间相加,非匿名特征与匿名特征相乘,通过特征扩充进而获得更多信息:

  1. list1 = [1, 2, 4, 5, 7, 8, 9, 10, 12, 13]
  2. for i in ['D_' + str(m) for m in list1]:
  3. for j in ['D_' + str(n) for n in list1]:
  4. Combine[str(i) + '+' + str(j)] = Combine[i] + Combine[j]
  5. for i in ['brand', 'serial', 'model', 'mileage', 'color', 'cityId', 'carCode', 'transferCount', 'seatings', 'country',
  6. 'maketype', 'modelyear', 'displacement', 'gearbox', 'oiltype', 'newprice', 'length', 'width', 'high']:
  7. for j in ['D_' + str(n) for n in list1]:
  8. Combine[str(i) + '*' + str(j)] = Combine[i] * Combine[j]

        6.特征交叉

        首先,我们分析一下匿名变量和非匿名变量与price的相关性:

  1. AllCol = Combine.columns
  2. Train = Combine.iloc[:len(Train), :][AllCol]
  3. a = dict(Train.corr()['price']) # 各变量与price变量的相关性
  4. asortlist = sorted(a.items(), key=lambda x: x[1], reverse=True) # 以字典的值为基准对字典的项进行排序
  5. for i in asortlist:
  6. print(i)

        我将各变量按照其与price变量相关性的大小从高到低排列,如下图:

         上图中,变量名后的数值越大,表示其与price的相关性越大。然后,我们挑选与price相关性较大的匿名变量和非匿名变量,让它们进行特征交叉,从而得到更复杂的非线性特征:

  1. # 特征交叉函数
  2. def cross_feature(df, fea_col, Nfea_col):
  3. for i in tqdm(fea_col): # 遍历分类特征
  4. for j in tqdm(Nfea_col): # 遍历数值特征
  5. # 调用groupby()函数,以参数i分组,之后,用agg函数对数据做一些聚合操作(求最大值、最小值、中位数)
  6. feat = df.groupby(i, as_index=False)[j].agg({
  7. '{}_{}_max'.format(i, j): 'max', # 最大值
  8. '{}_{}_min'.format(i, j): 'min', # 最小值
  9. '{}_{}_median'.format(i, j): 'median', # 中位数
  10. })
  11. df = df.merge(feat, on=i, how='left')
  12. return (df)
  13. # 挑选与Price相关程度高的非匿名变量和匿名变量作特征交叉
  14. Cross_fea = ['newprice', 'displacement', 'width', 'length', 'maketype', 'maketype_3', 'modelyear']
  15. Cross_Nfea = ['D_1', 'D_10_3', 'D_7', 'D_7_5', 'D_10', 'D_4', 'D_12']
  16. Combine = cross_feature(Combine, Cross_fea, Cross_Nfea)

进行完上述操作后,为便于后面的操作,先将训练集和测试集进行还原:

  1. # 还原训练集和测试集
  2. InputCol = Combine.columns.drop('price')
  3. XTrain = Combine.iloc[:len(Train), :][InputCol]
  4. YTrain = Train['price']
  5. XTest = Combine.iloc[len(Train):, :][InputCol]
  6. print("XTrain shape: ", XTrain.shape)
  7. print("XTestshape: ", XTest.shape)

        7.平均数编码

        对于高基数特征,可以使用平均数编码, 有监督地确定最适合这个定性特征的编码方式。高基数特征,简单地说就是一个特征有很多个取值。对于这些不适合使用One-hot编码的特征,有人提出了平均数编码这一概念。如果想详细了解平均数编码的话可以看一下这篇博客,我就是照搬这个大佬写的平均数编码的代码:

平均数编码:针对高基数定性特征(类别特征)的数据预处理/特征工程https://blog.csdn.net/juzexia/article/details/78581462?spm=1001.2014.3001.5506        平均数编码实现代码:

  1. class MeanEncoder:
  2. def __init__(self, categorical_features, n_splits=10, target_type='classification', prior_weight_func=None):
  3. self.categorical_features = categorical_features
  4. self.n_splits = n_splits
  5. self.learned_stats = {}
  6. if target_type == 'classification':
  7. self.target_type = target_type
  8. self.target_values = []
  9. else:
  10. self.target_type = 'regression'
  11. self.target_values = None
  12. if isinstance(prior_weight_func, dict):
  13. self.prior_weight_func = eval('lambda x: 1 / (1 + np.exp((x - k) / f))', dict(prior_weight_func, np=np))
  14. elif callable(prior_weight_func):
  15. self.prior_weight_func = prior_weight_func
  16. else:
  17. self.prior_weight_func = lambda x: 1 / (1 + np.exp((x - 2) / 1))
  18. @staticmethod
  19. def mean_encode_subroutine(X_train, y_train, X_test, variable, target, prior_weight_func):
  20. X_train = X_train[[variable]].copy()
  21. X_test = X_test[[variable]].copy()
  22. if target is not None:
  23. nf_name = '{}_pred_{}'.format(variable, target)
  24. X_train['pred_temp'] = (y_train == target).astype(int) # classification
  25. else:
  26. nf_name = '{}_pred'.format(variable)
  27. X_train['pred_temp'] = y_train # regression
  28. prior = X_train['pred_temp'].mean()
  29. col_avg_y = X_train.groupby(variable)['pred_temp'].agg(['mean', 'size']).rename(
  30. columns={'mean': 'mean', 'size': 'beta'})
  31. col_avg_y['beta'] = prior_weight_func(col_avg_y['beta'])
  32. col_avg_y[nf_name] = col_avg_y['beta'] * prior + (1 - col_avg_y['beta']) * col_avg_y['mean']
  33. col_avg_y.drop(['beta', 'mean'], axis=1, inplace=True)
  34. nf_train = X_train.join(col_avg_y, on=variable)[nf_name].values
  35. nf_test = X_test.join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name].values
  36. return nf_train, nf_test, prior, col_avg_y
  37. def fit_transform(self, X, y):
  38. X_new = X.copy()
  39. if self.target_type == 'classification':
  40. skf = StratifiedKFold(self.n_splits)
  41. else:
  42. skf = KFold(self.n_splits)
  43. if self.target_type == 'classification':
  44. self.target_values = sorted(set(y))
  45. self.learned_stats = {'{}_pred_{}'.format(variable, target): [] for variable, target in
  46. product(self.categorical_features, self.target_values)}
  47. for variable, target in product(self.categorical_features, self.target_values):
  48. nf_name = '{}_pred_{}'.format(variable, target)
  49. X_new.loc[:, nf_name] = np.nan
  50. for large_ind, small_ind in skf.split(y, y):
  51. nf_large, nf_small, prior, col_avg_y = MeanEncoder.mean_encode_subroutine(
  52. X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, target,
  53. self.prior_weight_func)
  54. X_new.iloc[small_ind, -1] = nf_small
  55. self.learned_stats[nf_name].append((prior, col_avg_y))
  56. else:
  57. self.learned_stats = {'{}_pred'.format(variable): [] for variable in self.categorical_features}
  58. for variable in self.categorical_features:
  59. nf_name = '{}_pred'.format(variable)
  60. X_new.loc[:, nf_name] = np.nan
  61. for large_ind, small_ind in skf.split(y, y):
  62. nf_large, nf_small, prior, col_avg_y = MeanEncoder.mean_encode_subroutine(
  63. X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, None,
  64. self.prior_weight_func)
  65. X_new.iloc[small_ind, -1] = nf_small
  66. self.learned_stats[nf_name].append((prior, col_avg_y))
  67. return X_new
  68. def transform(self, X):
  69. X_new = X.copy()
  70. if self.target_type == 'classification':
  71. for variable, target in product(self.categorical_features, self.target_values):
  72. nf_name = '{}_pred_{}'.format(variable, target)
  73. X_new[nf_name] = 0
  74. for prior, col_avg_y in self.learned_stats[nf_name]:
  75. X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[
  76. nf_name]
  77. X_new[nf_name] /= self.n_splits
  78. else:
  79. for variable in self.categorical_features:
  80. nf_name = '{}_pred'.format(variable)
  81. X_new[nf_name] = 0
  82. for prior, col_avg_y in self.learned_stats[nf_name]:
  83. X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[
  84. nf_name]
  85. X_new[nf_name] /= self.n_splits
  86. return X_new
'
运行

之后,对部分特征进行平均数编码:

  1. MeanEncol = ['model', 'brand', 'registerDate', 'tradeTime']
  2. # 如果是回归场景,那么target_type='regression';如果是分类场景,那么target_type='classification'
  3. MeanFit = MeanEncoder(MeanEncol, target_type='regression')
  4. XTrain = MeanFit.fit_transform(XTrain, YTrain)
  5. XTest = MeanFit.transform(XTest)

        8.目标编码

        目标编码是基于特征和目标值之间的对应关系的一种编码,下图是K折目标编码,K折目标编码将要编码的样本分成K份,每其中一份中的样本的目标编码,使用的是另外K-1份数据中相同类别的那些样本的对应属性,如最大最小值等,比普通的目标编码效果更好。

  1. # K折目标编码,
  2. # 回归场景中,对目标进行编码的常用方式:最小值、最大值、中位数、均值、求和、标准差、偏度、峰度、中位数绝对偏差
  3. XTrain['price'] = Train['price']
  4. EncCol = []
  5. StatDefaultDict = {
  6. 'max': XTrain['price'].max(),
  7. 'min': XTrain['price'].min(),
  8. 'median': XTrain['price'].median(),
  9. 'mean': XTrain['price'].mean(),
  10. 'sum': XTrain['price'].sum(),
  11. 'std': XTrain['price'].std(),
  12. 'skew': XTrain['price'].skew(),
  13. 'kurt': XTrain['price'].kurt(),
  14. 'mad': XTrain['price'].mad()
  15. }
  16. # 采用最大值、最小值、均值对目标特征price分别进行编码
  17. EncStat = ['max', 'min', 'mean']
  18. # 分为10折
  19. KF = KFold(n_splits=10, shuffle=True, random_state=2022)
  20. for f in tqdm(['serial', 'brand', 'registerDate_year', 'tradeTime_year', 'mileage', 'model']):
  21. EncDict = {}
  22. for stat in EncStat:
  23. EncDict['{}_target_{}'.format(f, stat)] = stat
  24. XTrain['{}_target_{}'.format(f, stat)] = 0
  25. XTest['{}_target_{}'.format(f, stat)] = 0
  26. EncCol.append('{}_target_{}'.format(f, stat))
  27. for i, (TrnIndex, ValIndex) in enumerate(KF.split(XTrain, YTrain)):
  28. TrnX, ValX = XTrain.iloc[TrnIndex].reset_index(drop=True), XTrain.iloc[ValIndex].reset_index(drop=True)
  29. EncDF = TrnX.groupby(f, as_index=False)['price'].agg(EncDict)
  30. ValX = ValX[[f]].merge(EncDF, on=f, how='left')
  31. TestX = XTest[[f]].merge(EncDF, on=f, how='left')
  32. for stat in EncStat:
  33. ValX['{}_target_{}'.format(f, stat)] = ValX['{}_target_{}'.format(f, stat)].fillna(StatDefaultDict[stat])
  34. TestX['{}_target_{}'.format(f, stat)] = TestX['{}_target_{}'.format(f, stat)].fillna(StatDefaultDict[stat])
  35. XTrain.loc[ValIndex, '{}_target_{}'.format(f, stat)] = ValX['{}_target_{}'.format(f, stat)].values
  36. XTest['{}_target_{}'.format(f, stat)] += TestX['{}_target_{}'.format(f, stat)].values / KF.n_splits

        上述过程结束后,我们查看一下现在数据的维度:

  1. print("XTrain shape: ", XTrain.shape)
  2. print("XTest shape: ", XTest.shape)

三、降维

        由上图可以看到,现在的特征个数已经达到了666个,但如果把这些全部喂给模型的话,其中的一些无用特征可能会影响我们的模型训练,所以我们就需要降维,最大程度地保留主要特征分量,筛除噪音。

        本代码中,我使用的是PCA降维。关于PCA降维的步骤,我上一篇文章有说,所以这次我说的会简单一点。

        首先,为了需要确保特征度量的比例尺度一致,我们使用极差法进行特征的归一化。

归一化公式如下:

        上式中, 为样本数据最大值, 为样本数据最小值。

        基于上述分析,我们先调用 sklearn 库中 preprocessing 模块中的 MinMaxScalar()函数对特征进行归一化处理,接着调用sklearn库中decomposition模块中的PCA算法包对数据进行降维。其步骤为:

1.去均值:

        其中,为标准化后的矩阵, 为矩阵A的均值。

2.求标准化数据集的协方差矩阵:

3.计算协方差矩阵的特征值和特征向量。设数λ和n维非0列向量x满足下式:

        则λ为C的特征值,x称为C的对应于特征值λ的特征向量。C为数据集的协方差矩阵。

4.保留最重要的前k个特征。k即为你想降维到的维数。

5.找到这k个特征值对应的特征向量

6.将标准化数据集乘以这k个特征向量,得到降维后的结果

        上式中,表示上述的k个特征值分别对应的特征向量组成的矩阵。

上述完整过程代码实现的话如下:

  1. # 归一化(极差法)
  2. Scaler = MinMaxScaler()
  3. Scaler.fit(pd.concat([XTrain, XTest]).values)
  4. CombineScaler = Scaler.transform(pd.concat([XTrain, XTest]).values)
  5. print('CombineScaler shape: ', CombineScaler.shape)
  6. # 调用sklearn库中decomposition模块中的PCA算法包对数据进行降维操作
  7. # PCA降维
  8. PCA = decomposition.PCA(n_components=550)
  9. CombinePCA = PCA.fit_transform(CombineScaler)
  10. XTrainPCA = CombinePCA[:len(XTrain)]
  11. XTestPCA = CombinePCA[len(XTrain):]
  12. YTrain = Train['price'].values
  13. print('CombinePCA shape: ', CombinePCA.shape)

 四、模型建立

        本次神经网络模型,我用的是知乎大佬定义的一个神经网络,根据实际问题做了些修改。

  1. def NN_model(input_dim):
  2. # 参数随机初始化
  3. init = keras.initializers.glorot_uniform(seed=1)
  4. model = keras.models.Sequential()
  5. model.add(Dense(units=300, use_bias=True, input_dim=input_dim, kernel_initializer=init, activation='softplus'))
  6. model.add(Dense(units=300, use_bias=True, kernel_initializer=init, activation='softplus')) # ReLU
  7. model.add(Dense(units=64, use_bias=True, kernel_initializer=init, activation='softplus'))
  8. model.add(Dense(units=32, use_bias=True, kernel_initializer=init, activation='softplus'))
  9. model.add(Dense(units=8, use_bias=True, kernel_initializer=init, activation='softplus'))
  10. model.add(Dense(units=1))
  11. return model
  12. class Metric(Callback):
  13. def __init__(self, model, callbacks, Combine):
  14. super().__init__()
  15. self.model = model
  16. self.callbacks = callbacks
  17. self.Combine = Combine
  18. def on_train_begin(self, logs=None):
  19. for callback in self.callbacks:
  20. callback.on_train_begin(logs)
  21. def on_train_end(self, logs=None):
  22. for callback in self.callbacks:
  23. callback.on_train_end(logs)
  24. def on_epoch_end(self, batch, logs=None):
  25. X_train, y_train = self.Combine[0][0], self.Combine[0][1]
  26. y_pred3 = self.model.predict(X_train)
  27. y_pred = np.zeros((len(y_pred3),))
  28. y_true = np.zeros((len(y_pred3),))
  29. for i in range(len(y_pred3)):
  30. y_pred[i] = y_pred3[i]
  31. for i in range(len(y_pred3)):
  32. y_true[i] = y_train[i]
  33. trn_s = metrics.mean_absolute_error(y_true, y_pred)
  34. logs['trn_score'] = trn_s
  35. X_val, y_val = self.Combine[1][0], self.Combine[1][1]
  36. y_pred3 = self.model.predict(X_val)
  37. y_pred = np.zeros((len(y_pred3),))
  38. y_true = np.zeros((len(y_pred3),))
  39. for i in range(len(y_pred3)):
  40. y_pred[i] = y_pred3[i]
  41. for i in range(len(y_pred3)):
  42. y_true[i] = y_val[i]
  43. val_s = metrics.mean_absolute_error(y_true, y_pred)
  44. logs['val_score'] = val_s
  45. print('trn_score', trn_s, 'val_score', val_s)
  46. for callback in self.callbacks:
  47. callback.on_epoch_end(batch, logs)

神经网络模型的结构图我画了一下,大概如下所示:

        因为神经元比较多,所以权值w并未在图中标出。下面我以自己的理解简单介绍一下这个神经网络:

        在该神经网络中,人造神经元可以接受n个实数值输入,他们构成输入向量。输入的下一级是一个线性单元。线性单元的输出为:

         为偏置项,可以理解为y轴上的截距,如果没有偏置项的话,那么对于不过原点的函数,神经元将永远无法对其拟合。为第i个输入对应的权值。为了表达方便,我们令=bias,并为输入向量添加一个值为1的特征,此时的输出为:

                                                              

        可以看出是对神经元的电位水平的模拟,而则是阈值。线性单元的单值输出会送给下一级激活单元(激活函数),我们选用的激活函数为softplus函数,该函数的数学表达式为:

所以最终得到神经元的输出函数为:

代入得:

我们选用的损失函数是MAE(平均绝对误差),其误差计算如下:

        其中,m表示样本点个数,表示第i个真实值,表示第i个预测值。在神经网络中,损失函数是为了计算误差,然后根据误差对神经网络训练中的权值进行更新。

五、模型训练

动态调整学习率:

  1. def scheduler(epoch):
  2. # 每隔20个epoch,学习率减小为原来的二分之一
  3. if epoch % 20 == 0 and epoch != 0:
  4. lr = K.get_value(model.optimizer.lr)
  5. K.set_value(model.optimizer.lr, lr * 0.5)
  6. print("lr changed to {}".format(lr * 0.5))
  7. return K.get_value(model.optimizer.lr)
  8. reduce_lr = LearningRateScheduler(scheduler)

模型训练:

  1. N = 10 # 分10折交叉验证
  2. kfold = KFold(n_splits=N, shuffle=True)
  3. BSize = 2000
  4. MaxEpochs = 140
  5. RinPred = np.zeros((len(XTrainPCA),))
  6. for fold, (trn_idx, val_idx) in enumerate(kfold.split(XTrainPCA, YTrain)):
  7. print('fold:', fold+1)
  8. X_train, y_train = XTrainPCA[trn_idx], YTrain[trn_idx]
  9. X_val, y_val = XTrainPCA[val_idx], YTrain[val_idx]
  10. model = NN_model(X_train.shape[1])
  11. # 学习率初始设为0.01
  12. simple_adam = Adam(lr=0.01)
  13. model.compile(loss='mae', optimizer=simple_adam, metrics=['mae'])
  14. es = EarlyStopping(monitor='val_score', patience=10, verbose=1, mode='min', restore_best_weights=True, )
  15. es.set_model(model)
  16. metric = Metric(model, [es], [(X_train, y_train), (X_val, y_val)])
  17. # batch_size:每一次权重更新需要batch_size个数据进行运算得到损失函数,每运算batch_size个数据相当于一次迭代,每次进行迭代将会更新参数的权重。
  18. # epochs:被定义为向前和向后传播中所有批次的单次训练迭代。简单说,epochs指的就是训练过程中数据将被“轮”多少次
  19. # 假设训练集有1000个样本,batchsize=10,那么训练完整个样本集需要: 100次iteration,1次epoch
  20. model.fit(X_train, y_train, batch_size=BSize, epochs=MaxEpochs,
  21. validation_data=(X_val, y_val),
  22. callbacks=[reduce_lr], shuffle=True, verbose=1)
  23. y_pred3 = model.predict(X_val)
  24. y_pred = np.zeros((len(y_pred3),))
  25. for i in range(len(y_pred3)):
  26. y_pred[i] = y_pred3[i]
  27. RinPred[val_idx] = y_pred
  28. np.set_printoptions(suppress=True) # 不以科学计数法输出
  29. # 训练集真实值
  30. # print(np.around(YTrain[val_idx], 2))
  31. # 训练集预测值
  32. # print(np.around(y_pred, 2))
  33. # 输出data2中二手车价格的预测值
  34. print(np.around(model.predict(XTestPCA), 2))
  35. print(Evaluate(YTrain[val_idx], y_pred))

        模型训练中,优化器我们使用的是Adam。神经网络网络会根据损失函数,利用Adam优化器,使用反向传播算法来更新网络参数权重,以此训练网络模型,对神经网络进行优化。Adam优化器进行权值更新的公式较为复杂,所以我就不再列出,想了解的话在网上搜索一下就能够找到。

跟据题目给出的模型评价标准:

定义模型评价函数: 

  1. # 评价模型
  2. def Evaluate(y_tre, y_pre):
  3. # y_tre:真实值;y_pre:预测值
  4. m = len(y_tre)
  5. count1 = 0
  6. Ape = []
  7. for i in range(0, m):
  8. Ape.append(np.abs(y_pre[i] - y_tre[i]) / y_tre[i])
  9. Mape = sum(Ape) / m
  10. for i in Ape:
  11. if i <= 0.05:
  12. count1 += 1
  13. Accuracy = count1 / m
  14. print('Mape:', Mape)
  15. print('Accuracy', Accuracy)
  16. print('score', 0.2 * (1 - Mape) + 0.8 * Accuracy)
'
运行

运行代码,跟据评价函数的评判,结果较好的一次为:

         为了更好地展示模型的预测结果,我随机抽取了训练集中的部分数据,将它们的真实值和预测值作比较,可视化后如下图所示:

       可以看出,模型的预测结果整体还是不错的。

       我也是刚开始接触机器学习,如果有错误的地方,希望能够指正。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/892094?site
推荐阅读
相关标签
  

闽ICP备14008679号