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

发布时间:2023-10-18 10:30

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

目录

一、问题描述

二、数据处理

        2.1 缺失值处理

        2.2 异常值处理

        2.3 特征工程

三、降维

四、模型建立

五、模型训练


一、问题描述

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

Python机器学习实例----用神经网络进行价格预测_第1张图片

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

data1部分数据如下所示:

Python机器学习实例----用神经网络进行价格预测_第2张图片

 data2部分数据如下所示:

Python机器学习实例----用神经网络进行价格预测_第3张图片

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

二、数据处理

        2.1 缺失值处理

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

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

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

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

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

Python机器学习实例----用神经网络进行价格预测_第4张图片

        通过上图可以看出,一共有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()来查看一下变量的数据类型,这里为了更直观,我还是使用了画图的方法:

Python机器学习实例----用神经网络进行价格预测_第5张图片

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

Python机器学习实例----用神经网络进行价格预测_第6张图片  

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

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

Combine['length'] = Combine['length'].astype(float)
Combine['width'] = Combine['width'].astype(float)
Combine['high'] = Combine['high'].astype(float)

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

Python机器学习实例----用神经网络进行价格预测_第7张图片

        由上图可以看出,变量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()查看一下各变量不同值的个数:

Python机器学习实例----用神经网络进行价格预测_第8张图片

        1.离散特征编码

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

        定义One-hot编码函数:

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

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

OneHotCol = ['carCode', 'color', 'country', 'maketype', 'oiltype', 'D_7', 'D_8', 'D_9', 'D_10', 'D_13']
OneHotCode = One_Hot(OneHotCol)
# 合并Combine和OneHotCode
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这种标准格式)

def date_proc(x):
    month = int(x[4:6])
    if month == 0:
        month = 1
    if len(x) == 6:
        return x[:4] + '-' + str(month)
    else:
        return x[:4] + '-' + str(month) + '-' + 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)

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

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

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

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

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

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

Python机器学习实例----用神经网络进行价格预测_第9张图片

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

# 对提取的日期特征进行One-hot编码
OneHotCol2 = ['registerDate_year', 'registerDate_month', 'registerDate_dayofweek', 'tradeTime_year', 'tradeTime_month',
             'tradeTime_dayofweek', 'licenseDate_year', 'licenseDate_month', 'licenseDate_dayofweek', 'D_12_year',
             'D_12_month']

OneHotCode2 = One_Hot(OneHotCol2)
Combine = pd.concat([Combine, OneHotCode2], axis=1)

        3.构建新特征

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

# 构建特征:汽车使用天数
Combine['used_time1'] = (pd.to_datetime(Combine['tradeTime'], format='%Y%m%d', errors='coerce') -
                      pd.to_datetime(Combine['registerDate'], format='%Y%m%d', errors='coerce')).dt.days
# 构建特征:汽车注册日期距今天数
Combine['used_time2'] = (
        pd.datetime.now() - pd.to_datetime(Combine['registerDate'], format='%Y%m%d', errors='coerce')).dt.days
# 构建特征:汽车上线日期距今天数
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'进行分桶:

# 数据分桶函数
def cut_group(df, cols, num_bins=50):
    for col in cols:
        all_range = int(df[col].max() - df[col].min())
        # ceil():返回一个数的上取整数;floor():返回一个数的下舍整数
        bin = [np.ceil(df[col].min() - 1) + np.floor(i * all_range / num_bins) for i in range(num_bins + 2)]
        # bin是一个列表,区间两端的选取就是跟据bin里的数据决定。如第一个区间就是[bin[0], bin[1]]
        df[col + '_bin'] = pd.cut(df[col], bin, labels=False)
    return df


# 对汽车使用天数,汽车注册日期距今天数 ,汽车上线日期距今天数进行数据分桶
CutCol = ['used_time1', 'used_time2', 'used_time3']
Combine = cut_group(Combine, CutCol, 50)

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

        5.特征扩充

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

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

        6.特征交叉

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

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

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

Python机器学习实例----用神经网络进行价格预测_第10张图片

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

# 特征交叉函数
def cross_feature(df, fea_col, Nfea_col):
    for i in tqdm(fea_col):  # 遍历分类特征
        for j in tqdm(Nfea_col):  # 遍历数值特征
            # 调用groupby()函数,以参数i分组,之后,用agg函数对数据做一些聚合操作(求最大值、最小值、中位数)
            feat = df.groupby(i, as_index=False)[j].agg({
                '{}_{}_max'.format(i, j): 'max',  # 最大值
                '{}_{}_min'.format(i, j): 'min',  # 最小值
                '{}_{}_median'.format(i, j): 'median',  # 中位数
            })
            df = df.merge(feat, on=i, how='left')
    return (df)


# 挑选与Price相关程度高的非匿名变量和匿名变量作特征交叉
Cross_fea = ['newprice', 'displacement', 'width', 'length', 'maketype', 'maketype_3', 'modelyear']
Cross_Nfea = ['D_1', 'D_10_3', 'D_7', 'D_7_5', 'D_10', 'D_4', 'D_12']
Combine = cross_feature(Combine, Cross_fea, Cross_Nfea)

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

# 还原训练集和测试集
InputCol = Combine.columns.drop('price')
XTrain = Combine.iloc[:len(Train), :][InputCol]
YTrain = Train['price']
XTest = Combine.iloc[len(Train):, :][InputCol]

print("XTrain shape: ", XTrain.shape)
print("XTestshape: ", XTest.shape)

        7.平均数编码

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

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

class MeanEncoder:
    def __init__(self, categorical_features, n_splits=10, target_type='classification', prior_weight_func=None):
        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(variable)['pred_temp'].agg(['mean', 'size']).rename(
            columns={'mean': 'mean', 'size': 'beta'})
        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):
        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):
        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

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

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

        8.目标编码

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

# K折目标编码,
# 回归场景中,对目标进行编码的常用方式:最小值、最大值、中位数、均值、求和、标准差、偏度、峰度、中位数绝对偏差
XTrain['price'] = Train['price']
EncCol = []
StatDefaultDict = {
    'max': XTrain['price'].max(),
    'min': XTrain['price'].min(),
    'median': XTrain['price'].median(),
    'mean': XTrain['price'].mean(),
    'sum': XTrain['price'].sum(),
    'std': XTrain['price'].std(),
    'skew': XTrain['price'].skew(),
    'kurt': XTrain['price'].kurt(),
    'mad': XTrain['price'].mad()
}
# 采用最大值、最小值、均值对目标特征price分别进行编码

EncStat = ['max', 'min', 'mean']
# 分为10折
KF = KFold(n_splits=10, shuffle=True, random_state=2022)
for f in tqdm(['serial', 'brand', 'registerDate_year', 'tradeTime_year', 'mileage', 'model']):
    EncDict = {}
    for stat in EncStat:
        EncDict['{}_target_{}'.format(f, stat)] = stat
        XTrain['{}_target_{}'.format(f, stat)] = 0
        XTest['{}_target_{}'.format(f, stat)] = 0
        EncCol.append('{}_target_{}'.format(f, stat))
    for i, (TrnIndex, ValIndex) in enumerate(KF.split(XTrain, YTrain)):
        TrnX, ValX = XTrain.iloc[TrnIndex].reset_index(drop=True), XTrain.iloc[ValIndex].reset_index(drop=True)
        EncDF = TrnX.groupby(f, as_index=False)['price'].agg(EncDict)
        ValX = ValX[[f]].merge(EncDF, on=f, how='left')
        TestX = XTest[[f]].merge(EncDF, on=f, how='left')
        for stat in EncStat:
            ValX['{}_target_{}'.format(f, stat)] = ValX['{}_target_{}'.format(f, stat)].fillna(StatDefaultDict[stat])
            TestX['{}_target_{}'.format(f, stat)] = TestX['{}_target_{}'.format(f, stat)].fillna(StatDefaultDict[stat])
            XTrain.loc[ValIndex, '{}_target_{}'.format(f, stat)] = ValX['{}_target_{}'.format(f, stat)].values
            XTest['{}_target_{}'.format(f, stat)] += TestX['{}_target_{}'.format(f, stat)].values / KF.n_splits

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

print("XTrain shape: ", XTrain.shape)
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个特征值分别对应的特征向量组成的矩阵。

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

# 归一化(极差法)
Scaler = MinMaxScaler()
Scaler.fit(pd.concat([XTrain, XTest]).values)
CombineScaler = Scaler.transform(pd.concat([XTrain, XTest]).values)
print('CombineScaler shape: ', CombineScaler.shape)
# 调用sklearn库中decomposition模块中的PCA算法包对数据进行降维操作
# PCA降维
PCA = decomposition.PCA(n_components=550)
CombinePCA = PCA.fit_transform(CombineScaler)
XTrainPCA = CombinePCA[:len(XTrain)]
XTestPCA = CombinePCA[len(XTrain):]

YTrain = Train['price'].values
print('CombinePCA shape: ', CombinePCA.shape)

 四、模型建立

        本次神经网络模型,我用的是知乎大佬定义的一个神经网络,使用效果还可以。

def NN_model(input_dim):
    # 参数随机初始化
    init = keras.initializers.glorot_uniform(seed=1)
    model = keras.models.Sequential()
    model.add(Dense(units=300, use_bias=True, input_dim=input_dim, kernel_initializer=init, activation='softplus'))
    model.add(Dense(units=300, use_bias=True, kernel_initializer=init, activation='softplus'))  # ReLU
    model.add(Dense(units=64, use_bias=True, kernel_initializer=init, activation='softplus'))
    model.add(Dense(units=32, use_bias=True, kernel_initializer=init, activation='softplus'))
    model.add(Dense(units=8, use_bias=True, kernel_initializer=init, activation='softplus'))
    model.add(Dense(units=1))
    return model


class Metric(Callback):
    def __init__(self, model, callbacks, Combine):
        super().__init__()
        self.model = model
        self.callbacks = callbacks
        self.Combine = Combine

    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.Combine[0][0], self.Combine[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 = metrics.mean_absolute_error(y_true, y_pred)
        logs['trn_score'] = trn_s

        X_val, y_val = self.Combine[1][0], self.Combine[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 = metrics.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)

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

Python机器学习实例----用神经网络进行价格预测_第11张图片

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

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

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

                                                              

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

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

代入得:

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

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

五、模型训练

动态调整学习率:

def scheduler(epoch):
    # 每隔20个epoch,学习率减小为原来的二分之一
    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)

模型训练:

N = 10  # 分10折交叉验证
kfold = KFold(n_splits=N, shuffle=True)
BSize = 2000
MaxEpochs = 140
RinPred = np.zeros((len(XTrainPCA),))

for fold, (trn_idx, val_idx) in enumerate(kfold.split(XTrainPCA, YTrain)):
    print('fold:', fold+1)
    X_train, y_train = XTrainPCA[trn_idx], YTrain[trn_idx]
    X_val, y_val = XTrainPCA[val_idx], YTrain[val_idx]

    model = NN_model(X_train.shape[1])
    # 学习率初始设为0.01
    simple_adam = Adam(lr=0.01)
    model.compile(loss='mae', optimizer=simple_adam, metrics=['mae'])
    es = EarlyStopping(monitor='val_score', patience=10, verbose=1, mode='min', restore_best_weights=True, )
    es.set_model(model)
    metric = Metric(model, [es], [(X_train, y_train), (X_val, y_val)])
    # batch_size:每一次权重更新需要batch_size个数据进行运算得到损失函数,每运算batch_size个数据相当于一次迭代,每次进行迭代将会更新参数的权重。
    # epochs:被定义为向前和向后传播中所有批次的单次训练迭代。简单说,epochs指的就是训练过程中数据将被“轮”多少次
    # 假设训练集有1000个样本,batchsize=10,那么训练完整个样本集需要: 100次iteration,1次epoch
    model.fit(X_train, y_train, batch_size=BSize, epochs=MaxEpochs,
              validation_data=(X_val, y_val),
              callbacks=[reduce_lr], shuffle=True, verbose=1)
    y_pred3 = model.predict(X_val)
    y_pred = np.zeros((len(y_pred3),))
    for i in range(len(y_pred3)):
        y_pred[i] = y_pred3[i]
    RinPred[val_idx] = y_pred

    np.set_printoptions(suppress=True)  # 不以科学计数法输出
    # 训练集真实值
    # print(np.around(YTrain[val_idx], 2))
    # 训练集预测值
    # print(np.around(y_pred, 2))
    # 输出data2中二手车价格的预测值
    print(np.around(model.predict(XTestPCA), 2))
    print(Evaluate(YTrain[val_idx], y_pred))

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

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

Python机器学习实例----用神经网络进行价格预测_第12张图片

定义模型评价函数: 

#  评价模型
def Evaluate(y_tre, y_pre):
# y_tre:真实值;y_pre:预测值
    m = len(y_tre)
    count1 = 0
    Ape = []
    for i in range(0, m):
        Ape.append(np.abs(y_pre[i] - y_tre[i]) / y_tre[i])
    Mape = sum(Ape) / m

    for i in Ape:
        if i <= 0.05:
            count1 += 1

    Accuracy = count1 / m
    print('Mape:', Mape)
    print('Accuracy', Accuracy)
    print('score', 0.2 * (1 - Mape) + 0.8 * Accuracy)

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

Python机器学习实例----用神经网络进行价格预测_第13张图片

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

Python机器学习实例----用神经网络进行价格预测_第14张图片

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

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

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号