# -*- coding：utf-8 -*-
# &Author  AnFany

# 两层的Stacking回归
from wxpy import *
bot = Bot(cache_path=True)

#  第一层6个模型：随机森林，AdaBoost，GBDT，LightGBM，XGBoost，CatBoost
#  第二层模型：BP神经网络回归

# 引入数据文件
import pm25_Stacking_Data as pm25

# 引入绘图库包
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong']  # 显示中文
mpl.rcParams['axes.unicode_minus'] = False  # 显示负号

# 引入需要用到的模型的库包
# 随机森林
from sklearn.ensemble import RandomForestRegressor as RF
# AdaBoost
from sklearn.ensemble import AdaBoostRegressor
from sklearn.tree import DecisionTreeRegressor
# GBDT
from sklearn.ensemble import GradientBoostingRegressor
# XGBoost
import xgboost as xgb
# LightGBM
import lightgbm as lgbm
# CatBoost
import catboost as cb
# BP神经网络回归
import BP_Regression as bp

# 其他库包
import tensorflow as tf
import numpy as np
import pandas as pd
from collections import OrderedDict  # python字典是无序的，此包是有序的
import os
os.chdir(r'E:\tensorflow_Learn\Stacking\pm25')
'''
第一部分：数据处理模型
'''

class DATA:

    def __init__(self, datadict=pm25.data_dict, mubiao='pm2.5'):
        self.data = datadict
        self.k = 8  # 因为需要对每个模型进行单独的数据处理，因此这个折数对于每个模型都必须是一样的

        # 训练数据
        self.chidata = self.data['train']

        # 预测数据
        self.nodata = self.data['predict']

        # 类别型数据的编号
        self.catsign = self.Sign()

        # 目标字段
        self.ziduan = mubiao

        # 对于归一化化和标准化的处理，要记录转化的值，在这里将2者统一。反处理时，预测值需要乘以self.fenmu 加上self.cha
        self.fenmu = 1  # 相当于标准化的标准差, 归一化的最大值减去最小值
        self.cha = 0   # 相当于标准化的平均值, 归一化的最小值

    # 因为对于CatBoost而言，不需要进行类别型特征的处理，但是需要类别型特征的标号
    def Sign(self):
        sign = []
        numlist = self.chidata.values[0][: -1]  # 不包括最后的目标字段
        for jj in range(len(numlist)):
            try:
                numlist[jj] + 9
            except TypeError:
                sign.append(jj)
        return sign

    # 类别型特征数字标签化函数，
    def CAtoDI(self):
        # 如果特征值不能执行数字加法运算，则视为类别型特征
        for tezheng in self.chidata:
            try:
                self.chidata[tezheng].values[0] + 1
            except TypeError:
                numlist = sorted(list(set(list(self.chidata[tezheng]))))
                self.chidata[tezheng] = [numlist.index(hh) for hh in self.chidata[tezheng]]
                try:
                    self.nodata[tezheng] = [numlist.index(ss) for ss in self.nodata[tezheng]]
                except ValueError:
                    print('特征%s：预测比训练的多了值' % (tezheng))
        return print('数字化处理完毕')

    # 对于归一化和标准化的函数，要记录目标字段的转化值，因为要进行反归一化

    # 归一化函数
    def Normal(self):
        # 在此之前需要把类别型标签去掉,否则会报错
        for tezheng in self.chidata:
            maxnum = max(list(self.chidata[tezheng]))
            minum = min(list(self.chidata[tezheng]))
            if maxnum == minum:
                self.chidata[tezheng] = [1 for hh in self.chidata[tezheng]]
                self.nodata[tezheng] = [1 for ss in self.nodata[tezheng]]
            else:
                self.chidata[tezheng] = [(hh - minum) / (maxnum - minum) for hh in self.chidata[tezheng]]
                self.nodata[tezheng] = [(ss - minum) / (maxnum - minum) for ss in self.nodata[tezheng]]
                if tezheng == self.ziduan:
                    self.fenmu = maxnum - minum
                    self.cha = minum
        return print('归一化处理完毕')

    # 标准化函数
    def Stand(self):
        # 在此之前需要把类别型标签去掉,否则会报错
        for tezheng in self.chidata:
            standnum = np.std(np.array(list(self.chidata[tezheng])), ddof=1)  # 计算有偏的标准差
            meanum = np.mean(np.array(list(self.chidata[tezheng])))

            if meanum == 0:
                self.chidata[tezheng] = [1 for hh in self.chidata[tezheng]]
                self.nodata[tezheng] = [1 for ss in self.nodata[tezheng]]
            else:
                self.chidata[tezheng] = [(hh - standnum) / meanum for hh in self.chidata[tezheng]]
                self.nodata[tezheng] = [(ss - standnum) / meanum for ss in self.nodata[tezheng]]
                if tezheng == self.ziduan:
                    self.fenmu = standnum
                    self.cha = meanum
        return print('标准化处理完毕')

    # 定义Kfold的函数，也就是将原始的训练数据集分为k对训练数据和验证数据的组合
    def Kfold(self):
        # 因为每个模型需要将验证数据结合的结果集成起来，为了方便起见，在这里固定每一折数中的数据集合
        datanum = self.chidata.values
        # 数据集总长度
        length = len(datanum)
        alist = np.arange(length)
        np.random.seed(1990)
        np.random.shuffle(alist)  # 随机打乱数据对BPNN,SVM而言是有益处的，而对于决策树之类的模型而言没有影响

        # 验证数据的长度
        yanlem = int(length / self.k)

        # 存储数据集的字典
        datai = {}
        datai['predict'] = self.nodata.values

        # 开始处理Kfold
        for kk in range(self.k):
            datai[kk] = OrderedDict()
            if kk == 0:
                datai[kk]['train'] = datanum[alist[(kk + 1) * yanlem:]]
                datai[kk]['test'] = datanum[alist[: (kk + 1) * yanlem]]
            elif kk == self.k - 1:
                datai[kk]['train'] = datanum[alist[: kk * yanlem]]
                datai[kk]['test'] = datanum[alist[kk * yanlem:]]
            else:
                datai[kk]['test'] = datanum[alist[kk * yanlem: (kk + 1) * yanlem]]
                signlist = list(alist[: kk * yanlem]) + list(alist[(kk + 1) * yanlem:])
                datai[kk]['train'] = datanum[signlist]
        # 返回的数据集形式{0：{'train':data, 'test':data}，……，self.k-1:{'train':data, 'test':data}, 'predict':data}
        print('K折处理完毕')
        return datai


'''
第二部分：第一层的模型运行阶段
'''
# 可以任意添加模型
class MODELONE:

    def __init__(self, zidan='pm2.5'):

        # 验证数据集的预测结果
        self.yanzhneg_pr = []

        # 验证数据集的真实结果
        self.yanzhneg_real = []

        # 预测数据集的预测结果
        self.predi = []

        # 预测数据集的真实结果
        self.preal = []

        # 目标字段名称
        self.zi = zidan

        # 数据结构和数据处理类的保持一致，要把验证数据集的输入和真实的输出合二为一
        self.datai = {}

        # 记录每个模型最终误差的字典
        self.error_dict = OrderedDict()

    # 将第一层的结果转换为何数据结构处理类中一样的数据结构的函数
    #  也就是{'train':dataframe, 'predict':dataframe}样式的字典
    def DataStru(self):
        self.datai['train'] = np.row_stack((np.array(self.yanzhneg_pr), np.array(self.yanzhneg_real)))  # 此处添加行
        self.datai['predict'] = np.row_stack((np.array(self.predi), np.array(self.preal)))
        # 将训练数据转置
        datapst = self.datai['train'].T
        # 为训练数据定义DataFrame的列名
        mingcheng = ['第%s个模型列' % str(dd) for dd in list(range(len(self.datai['train']) - 1))] + [self.zi]
        self.datai['train'] = pd.DataFrame(datapst, columns=mingcheng)

        # 将预测数据转置
        dapst = self.datai['predict'].T
        # 为训练数据定义DataFrame的列名
        mingche= ['第%s个模型列' % str(dd) for dd in list(range(len(self.datai['predict']) - 1))] + [self.zi]
        self.datai['predict'] = pd.DataFrame(dapst, columns=mingche)
        return print('二层的数据准备完毕')

    # 定义均方误差的函数
    def RMSE(self, data1, data2):
        data1, data2 = np.array(data1), np.array(data2)
        subdata = np.power(data1 - data2, 2)
        return np.sqrt(np.sum(subdata) / len(subdata - 1))

    # 随机森林
    def RF_First(self, data, n_estimators=4000, max_features='auto'):
        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []

        # 存储训练、验证、预测数据的误差
        errorlsit = []
        # 开始每一折的训练,因为这个折数的字典是有序的,因此不用考虑每一折的顺序。
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = RF(n_estimators=n_estimators, max_features=max_features)
            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1])
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            #预测的预测结果
            prer = model.predict(data['predict'][:, :-1])

            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)
            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])

            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])

            pp = self.RMSE(prer, data['predict'][:, -1])

            errorlsit.append([xx, yy, pp])
        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]

        # 储存误差
        self.error_dict['随机森林'] = np.mean(np.array(errorlsit), axis=0)
        return print('1层中的随机森林运行完毕')

    # AdaBoost
    def Adaboost_First(self, data, max_depth=50, n_estimators=1000):
        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []

        # 存储训练、验证、预测数据的误差
        errorlsit = []

        # 开始每一折的训练
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = AdaBoostRegressor(DecisionTreeRegressor(max_depth=max_depth),
                                      n_estimators=n_estimators, learning_rate=0.8)
            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1])
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            #预测的预测结果
            prer = model.predict(data['predict'][:, :-1])
            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)

            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])

            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])

            pp = self.RMSE(prer, data['predict'][:, -1])

            errorlsit.append([xx, yy, pp])

        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]
        # 储存误差
        self.error_dict['AdaBoost'] = np.mean(np.array(errorlsit), axis=0)

        return print('1层中的AdaBoost运行完毕')

    # GBDT
    def GBDT_First(self, data, max_depth=17, n_estimators=57):
        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []

        # 存储训练、验证、预测数据的误差
        errorlsit = []
        # 开始每一折的训练
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = GradientBoostingRegressor(loss='ls', n_estimators=n_estimators, max_depth=max_depth,
                                              learning_rate=0.12, subsample=0.8)
            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1])
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            # 预测的预测结果
            prer = model.predict(data['predict'][:, :-1])
            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)

            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])

            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])

            pp = self.RMSE(prer, data['predict'][:, -1])

            errorlsit.append([xx, yy, pp])

        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]
        # 储存误差
        self.error_dict['GBDT'] = np.mean(np.array(errorlsit), axis=0)

        return print('1层中的GBDT运行完毕')

    # LightGBM
    def LightGBM_First(self, data, max_depth=9, n_estimators=380):
        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []

        # 存储训练、验证、预测数据的误差
        errorlsit = []
        # 开始每一折的训练
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = lgbm.LGBMRegressor(boosting_type='gbdt', objective='regression', num_leaves=1200,
                                       learning_rate=0.17, n_estimators=n_estimators, max_depth=max_depth,
                                       metric='rmse', bagging_fraction=0.8, feature_fraction=0.8, reg_lambda=0.9)

            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1])
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            # 预测的预测结果
            prer = model.predict(data['predict'][:, :-1])
            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)
            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])
            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])
            pp = self.RMSE(prer, data['predict'][:, -1])
            errorlsit.append([xx, yy, pp])
        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]
        # 储存误差
        self.error_dict['LightGBM'] = np.mean(np.array(errorlsit), axis=0)

        return print('1层中的LightGBM运行完毕')

    # XGBoost
    def XGBoost_First(self, data, max_depth=50, n_estimators=220):
        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []
        # 存储训练、验证、预测数据的误差
        errorlsit = []
        # 开始每一折的训练
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = xgb.XGBRegressor(max_depth=max_depth, learning_rate=0.1, n_estimators=n_estimators,
                                     silent=True, objective='reg:gamma')
            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1])
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            # 预测的预测结果
            prer = model.predict(data['predict'][:, :-1])
            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)
            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])
            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])
            pp = self.RMSE(prer, data['predict'][:, -1])
            errorlsit.append([xx, yy, pp])
        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]
        # 储存误差
        self.error_dict['XGBoost'] = np.mean(np.array(errorlsit), axis=0)
        return print('1层中的XGBoost运行完毕')

    # CatBoost
    def CatBoost_First(self, data, catsign, depth=8, iterations=80000):

        # 存储每一折中验证数据集的预测结果
        yanzhenglist = []
        # 存储每一折中验证数据集的真实结果
        yanzhenglist_real = []
        # 存储每一折中预测数据集的预测结果
        prelist = []
        # 存储训练、验证、预测数据的误差
        errorlsit = []
        # 开始每一折的训练
        for zhe in [zheshu for zheshu in data.keys() if zheshu != 'predict']:
            model = cb.CatBoostRegressor(iterations=iterations, depth=depth, learning_rate=0.8, loss_function='RMSE')

            model.fit(data[zhe]['train'][:, :-1], data[zhe]['train'][:, -1], cat_features=catsign)
            # 注意存储验证数据集结果和预测数据集结果的不同
            # 训练数据集的预测结果
            xul = model.predict(data[zhe]['train'][:, :-1])
            # 验证的预测结果
            yanre = model.predict(data[zhe]['test'][:, :-1])
            # 预测的预测结果
            prer = model.predict(data['predict'][:, :-1])
            yanzhenglist += list(yanre)
            yanzhenglist_real += list(data[zhe]['test'][:, -1])
            prelist.append(prer)
            # 每计算一折后，要计算训练、验证、预测数据的误差
            xx = self.RMSE(xul, data[zhe]['train'][:, -1])
            yy = self.RMSE(yanre, data[zhe]['test'][:, -1])
            pp = self.RMSE(prer, data['predict'][:, -1])
            errorlsit.append([xx, yy, pp])
        # 针对预测数据集的预测结果计算均值
        meanPre = np.mean(np.array(prelist), axis=0)
        # 开始结合
        self.yanzhneg_pr.append(yanzhenglist)
        self.yanzhneg_real = yanzhenglist_real
        self.predi.append(meanPre)
        self.preal = data['predict'][:, -1]
        # 储存误差
        self.error_dict['CatBoost'] = np.mean(np.array(errorlsit), axis=0)
        return print('1层中的CatBoost运行完毕')

'''
第三部分：第二层的模型运行阶段 可以任意更换模型
'''
class MODETWO:

    def __init__(self, in_tr_data, out_tr_data, in_pre_data, out_pre, fenmu, cha):
        self.xdata = in_tr_data
        self.ydata = out_tr_data

        self.xdatapre = in_pre_data
        self.ydapre = out_pre

        self.fen = fenmu

        self.cha = cha
        pass

    # BP神经网络回归
    def BP(self, hiddenlayers=3, hiddennodes=100, learn_rate=0.05, itertimes=50000,
           batch_size=200, activate_func='sigmoid', break_error=0.00000043):
        loss_trrr, loss_pree, sign, fir = bp.Ten_train(self.xdata, self.ydata, self.xdatapre, self.ydapre,
                                                       hiddenlayers=hiddenlayers, hiddennodes=hiddennodes,
                                                       learn_rate=learn_rate, itertimes=itertimes,
                                                       batch_size=batch_size, activate_func=activate_func,
                                                       break_error=break_error)
        # 因为上面的得出的RMSE是数据变换后的，因此要转换到原始的维度
        loss_trrr = np.array(loss_trrr) * self.fen

        loss_pree = np.array(loss_pree) * self.fen

        return loss_trrr, loss_pree, sign, fir

'''
第四部分：绘制图，绘制第一层各个模型中训练，验证数据的误差，
以及最终的预测数据的真实值和误差值的对比
'''
# 定义绘制第一层模型训练、验证、预测数据的误差的函数
# 根据字典绘制不同参数下评分的对比柱状图
def Plot_RMSE_ONE_Stacking(exdict, kaudu=0.2):
    '''
    :param exdict: 不同模型的RMSE 最小二乘回归误差的平方根
    :return: 柱状图
    '''
    # 参数组合列表
    palist = exdict.keys()
    # 对应的训练数据的评分
    trsore = [exdict[hh][0] for hh in palist]
    # 对应的测试数据的评分
    tesore = [exdict[hh][1] for hh in palist]
    # 对应的预测数据的评分
    presore = [exdict[hh][2] for hh in palist]

    # 开始绘制柱状图
    fig, ax = plt.subplots()
    # 柱的个数
    ind = np.array(list(range(len(trsore))))
    # 绘制柱状
    ax.bar(ind - kaudu, trsore, kaudu, color='SkyBlue', label='训练')
    ax.bar(ind, tesore, kaudu, color='IndianRed', label='测试')
    ax.bar(ind + kaudu, presore, kaudu, color='slateblue', label='预测')
    # xy轴的标签
    ax.set_ylabel('RMSE')
    ax.set_xlabel('Stacking第一层中的模型')
    # 设置刻度
    ax.set_xticks(ind)
    ax.set_xticklabels(palist)

    ax.grid()

    leg = ax.legend(loc='best', ncol=3, shadow=True, fancybox=True)
    leg.get_frame().set_alpha(0.8)
    plt.title('Stacking第一层中模型的RMSE')
    plt.savefig(r'C:\Users\GWT9\Desktop\Stacking_pm25.jpg')
    bot.file_helper.send_image(r'C:\Users\GWT9\Desktop\Stacking_pm25.jpg')
    return '一层不同模型对比'

# 最终的预测数据的真实值和误差值的对比
# 按照误差值从小到大排列的数据
def Pailie(realr, modelout, count=90):
    '''
    :param real: 预测数据集真实的数据
    :param modelout: 预测数据集的模型的输出值
    :param count: 进行数据对比的条数
    :return: 按照差值从小到大排列的数据
    '''
    relal_num = np.array(realr)
    modelout_num = np.array(modelout)
    # 随机选取
    fu = np.random.choice(list(range(len(realr))), count, replace=False)
    show_real, show_model = relal_num[fu], modelout_num[fu]
    # 计算差值
    sunnum = show_real - show_model
    # 首先组合三个数据列表为字典
    zuhedict = {ii: [show_real[ii], show_model[ii], sunnum[ii]] for ii in range(len(show_model))}
    # 字典按着值排序
    zhenshi = []
    yucede = []
    chazhi = []
    # 按着差值从大到小
    for jj in sorted(zuhedict.items(), key=lambda gy: gy[1][2]):
        zhenshi.append(jj[1][0])
        yucede.append(jj[1][1])
        chazhi.append(jj[1][2])
    return zhenshi, yucede, chazhi

# 绘制预测值，真实值对比折线，以及与两值的误差柱状图
def recspre(yzhenshide, yyucede):
    #  获得展示的数据
    yreal, ypre, cha = Pailie(yzhenshide, yyucede)
    plt.figure()
    ax = plt.subplot(111)
    plt.grid()
    dign = np.arange(len(yreal))
    #  绘制真实值
    ax.scatter(dign, yreal, label='真实值', lw=2, color='blue', marker='*')
    #  绘制预测值
    ax.plot(dign, ypre, label='预测值', lw=2, color='red', linestyle='--', marker='.')
    #  绘制误差柱状图
    ax.bar(dign, cha, 0.1, label='真实值减去预测值', color='k')
    # 绘制0线
    ax.plot(dign, [0] * len(dign), lw=2, color='k')

    ax.set_ylim((int(min(cha)) - 1, int(max([max(yreal), max(ypre)]))))
    ax.set_xlim((0, len(dign)))

    ax.legend(loc='best')
    ax.set_title('北京市Pm2.5预测数据集结果对比')
    plt.savefig(r'C:\Users\GWT9\Desktop\Stacking_duibi.jpg')
    bot.file_helper.send_image(r'C:\Users\GWT9\Desktop\Stacking_duibi.jpg')
    return '完毕'

# 绘制2条对比曲线
def plotcurve(trainone, pretwo):
    fig, axs = plt.subplots(2, 1, sharex=True)
    fig.subplots_adjust(hspace=0.1)
    axs[0].plot(range(len(trainone)), trainone, label='训练', lw=3, color='maroon')
    axs[0].legend()
    axs[1].plot(range(len(pretwo)), pretwo, label='验证', lw=3, color='sienna')
    axs[1].legend()
    plt.xlabel('迭代次数')
    axs[0].set_title('第2层模型：BPNN中训练和验证数据的成本函数值')
    plt.savefig(r'C:\Users\GWT9\Desktop\Stacking_errorr.jpg')
    bot.file_helper.send_image(r'C:\Users\GWT9\Desktop\Stacking_errorr.jpg')

'''
第五部分：Stacking主函数
'''

if __name__ == "__main__":
    #  第一层6个模型：随机森林，AdaBoost，GBDT，LightGBM，XGBoost，CatBoost

    # 下面依次为每个模型建立数据
    # 随机森林、AdaBoost，GBDT，LIghtGNM，XGBoost都是一样的
    rf_data = DATA()
    rf_data.CAtoDI()  # 标签数字化
    data_rf = rf_data.Kfold()  # 折数

    # CatBoost
    cat_data = DATA()  # 不用处理
    data_cat = cat_data.Kfold()  # 折数

    # 开始建立Stacking第一层的模型
    one_stacking = MODELONE()
    # 随机森林
    one_stacking.RF_First(data_rf)
    # AdaBoost
    one_stacking.Adaboost_First(data_rf)
    # GBDT
    one_stacking.GBDT_First(data_rf)
    # LightGBM
    one_stacking.LightGBM_First(data_rf)
    # XGBoost
    one_stacking.XGBoost_First(data_rf)
    # CatBoost
    one_stacking.CatBoost_First(data_cat, cat_data.catsign)

    # 第二层的数据准备
    one_stacking.DataStru()
    data_two = one_stacking.datai

    # 第二层的数据处理
    erce_data = DATA(datadict=data_two)
    erce_data.Normal()
    #  将训练数据集分为训练和验证数据集
    redata = erce_data.Kfold()
    #

    # 第二层建模,在这里不在进行交叉验证，因此只选一个数据集
    stacking_two = MODETWO(redata[0]['train'][:, :-1],
                           np.array([redata[0]['train'][:, -1]]).T,
                           redata[0]['test'][:, :-1],
                           np.array([redata[0]['test'][:, -1]]).T,
                           erce_data.fenmu, erce_data.cha)

    # 训练的输出值，预测的输出值, 每一次迭代训练和预测的误差
    lossrain, losspre, signi, gir = stacking_two.BP()



    # 训练完成后读取最优的参数，在计算最终的预测结果
    graph = tf.train.import_meta_graph("./pm25-%s.meta" % signi)
    ses = tf.Session()
    graph.restore(ses, tf.train.latest_checkpoint('./'))
    op_to_restore = tf.get_default_graph().get_tensor_by_name("Sigmoid_%s:0" % gir)  # 这个tensor的名称和激活函数有关系，需要去BP的程序中获得
    w1 = tf.get_default_graph().get_tensor_by_name("x_data:0")
    feed_dict = {w1: redata['predict'][:, :-1]}
    dgsio = ses.run(op_to_restore, feed_dict)

    preout = erce_data.fenmu * dgsio.T[0] + erce_data.cha

    # 绘制第一层中各个模型的误差图
    Plot_RMSE_ONE_Stacking(one_stacking.error_dict)
    # 绘制预测值，真实值对比折线，以及与两值的误差柱状图
    recspre(erce_data.fenmu * erce_data.nodata.values[:, -1] + erce_data.cha, preout)
    # 绘制第二层模型中的训练和验证误差
    plotcurve(lossrain, losspre)