"""监督学习常见的两类问题:
1.分类:预测值是离散的
2.回归:预测值是连续的"""

"""下面利用keras解决回归问题"""
import tensorflow as tf
import tensorflow.keras as keras
import numpy as np

"""数据加载:
采用keras内置的波士顿房价数据集,只含有506个样本
训练样本404个,测试样本102个"""

from tensorflow.keras.datasets import boston_housing

(train_data,train_targets),(test_data,test_targets)=boston_housing.load_data()

print(train_data.shape,test_data.shape)
print(train_targets.shape,test_targets.shape)
# print(train_targets)

"""数据预处理:
各个特征维度的数据取值存在比较大的差异,为了减少网络的训练难度,
最好对特征维度数据做标准化,即使各个维度数据服从(0,1)的正态分布
注意的是:在实际过程中,测试集上数据标准化是依据训练集的,即不能从测试集分布上获取任何的先验知识"""
mean=train_data.mean(axis=0)
std=train_data.std(axis=0)

train_data=(train_data-mean)/std
test_data=(test_data-mean)/std
print(train_data.shape,test_data.shape)
print(train_targets.shape,test_targets.shape)

"""网络的构建:
由于数据量有限,所以尽量使用一个较小的网络结构"""
from tensorflow.keras import models
from tensorflow.keras import layers
from tensorflow.keras import optimizers
from tensorflow.keras import losses

def build_model():
    model=models.Sequential()
    model.add(layers.Dense(64,activation="relu",input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64,activation="relu"))
    model.add(layers.Dense(1))
    """配置学习过程:
    回归问题,损失函数采用均方误差,MSE
    监控的指标采用平均绝对误差(mae),即预测值与目标值之差的绝对值"""
    model.compile(optimizer=optimizers.RMSprop(1e-3),loss=losses.MSE,metrics=['mae'])
    return model

"""验证集与训练集的划分:
由于整体数据集的规模都比较小,所以验证集就更小,这会导致在验证集上的评估有很大的波动
所以一般应采用k折交叉验证
k折交叉验证:
将数据划分为k个分区(k通常取4或5),实例化k个相同模型,将每个模型在
k-1个分区上训练,并在剩下一个分区上进行评估,
最后整个模型的验证分数等于k个验证分数的平均值"""

"""k折交叉验证的实现"""
k=4 #4z折交叉验证
num_val_samples=len(train_data)//k  #数据划分,每个分区上样本的数量
num_epoch=500
all_scores=[]  #存储验证集评估结果
print(f"开始进行{k}折交叉验证")
for i in range(k):
    print(f"current item:{i}")
    x_val=train_data[i*num_val_samples:(i+1)*num_val_samples]
    y_val=train_targets[i*num_val_samples:(i+1)*num_val_samples]

    x_train=np.concatenate([train_data[:i*num_val_samples],train_data[(i+1)*num_val_samples:]],axis=0)
    y_train=np.concatenate([train_targets[:i*num_val_samples],train_targets[(i+1)*num_val_samples:]],axis=0)

    model=build_model() #模型编译
    history=model.fit(x_train,y_train,batch_size=1,epochs=num_epoch,validation_data=(x_val,y_val),verbose=0)  #verbose:0为静默模式
    # val_mse,val_mae=model.evaluate(x_val,y_val)
    # print(history.history.keys())
    # input()
    mae_history=history.history['val_mae']
    all_scores.append(mae_history)

"""计算每个轮次中所有k折交叉验证的平均值"""
aver_epoch_mae=np.array(all_scores).mean(axis=0)

"""验证集评估曲线不够光滑,不易找出模型过拟合开始出现的点
所以需要对验证集做一定的处理
删除前10个数值比较大的点,将每个值替换成其与前面值的加权平均,可以使曲线变得更加平滑"""
def smooth_mae(points,factor=0.9):
    results=[]
    for point in points:
        if results:
            previous=results[-1]
            current=point*0.1+previous*0.9
            results.append(current)
        else:
            results.append(point)
    return results
smooth_mae_values=smooth_mae(aver_epoch_mae[10:])
print(smooth_mae_values)
"""绘制轮次的验证分数"""
import matplotlib.pyplot as plt

plt.figure(1)
plt.plot(list(range(1,num_epoch+1)),aver_epoch_mae)
plt.title("Every Epoch Val Aver MAE")
plt.xlabel("Epoch")
plt.ylabel("Val MAE")

plt.figure(2)
plt.plot(list(range(1,len(smooth_mae_values)+1)),smooth_mae_values)
plt.title("Every Epoch Val Aver Smooth MAE")
plt.xlabel("Epoch")
plt.ylabel("Val MAE")

plt.show()