from matplotlib import pyplot as plt
import os
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog
from PIL import Image
from X1 import image_to_array

'''
【编程16.6】编制程序：设置加载数据集的参数SetLoadData()及加载数据集LoadData()。
【目的及编程说明】读者通过编程16.6可掌握读取文件及数据预处理的相关方法。SetLoadData()设置加载数据集的相关参数，
LoadData()按所设置的参数读取数据集。SetDataPara函数实现的具体要求为：
（1）确定参数信息，如数据集路径信息、图片大小、每批次读入图片的数量等；
（2）返回DataPara参数。
LoadData函数实现的具体要求为：
（1）根据路径信息读入图片；
（2）将图片缩放为固定大小；
（3）将图片转换为数组的形式；
（4）将数组中具体数值归一化；
（5）返回转换后的数组。
'''

# 定义一个函数，实现图片转换为数组的功能
def image_to_array(path,height,width):
  img = Image.open(path).convert("L") #转换为灰度模式
  img = img.resize((height, width))   # 将图片缩放为height*width的固定大小
  data = np.array(img)  # 将图片转换为数组格式
  data = data / 255.0   # 将数组中的数值归一化，除以255
  return data

def setload_data(): # 定义加载数据集的参数SetLoadData()
    # 设置数据集路径信息
    train_imgPath = 'data/train/'  # 训练集文件夹的位置
    test_imgPath = 'data/verify/'  # 测试集文件夹的位置
    img_width = 48  # 图片宽度
    img_height = 48  # 图片高度
    # 设置每批次读入图片的数量
    batch_size = 32  # 批次大小
    # 返回DataPara参数，这里用一个字典来存储
    DataPara = {"train_imgPath": train_imgPath,
                "test_imgPath": test_imgPath,
                "img_width": img_width,
                "img_height": img_height,
                "batch_size": batch_size}
    return DataPara

# if __name__ == '__main__':
#     root = tk.Tk()
#     root.withdraw()
#     DataPara = setload_data()
#     print(DataPara)
# 定义加载数据集load_data()
def load_data(DataPara):
    imgPaths=["train_imgPath","test_imgPath"] # DataPara的键名
    listimages=[]    # 存储图片的列表
    for datapath in imgPaths:
        filenames = os.listdir(DataPara[datapath])
        images = []  # 存储图片的列表
        for tfname in filenames:
            if os.path.isdir(tfname) == False:      # 如果不是子文件夹，就是图片文件
                path = DataPara[datapath] + tfname  # 拼接图片路径
	            # 引用X1里面的image_to_array()方法
                img = image_to_array(path, DataPara["img_width"], DataPara["img_height"])  # 读取图片数据
                img = img.T  # 转置，图像的行和列将互换位置。
                images.append(img)  # 将图片数组添加到训练集图片列表中
        listimages.append(np.array(images)) # 返回转换后的数组
    return listimages[0],listimages[1]
# if __name__ == '__main__':
#     root = tk.Tk()
#     root.withdraw()
#     DataPara = setload_data()
#     train_images, test_images = load_data(DataPara)
#     print(train_images.shape)
#     print(test_images.shape)
'''
【编程16.7】编制程序：设置卷积参数SetConvPara()及卷积函数ConvProc()。
【目的及编程说明】读者通过编程16.7可实现卷积神经网络中的重要步骤—卷积，它将一个卷积核应用于输入图像，
以提取特征。SetConvPara()设置相关参数，ConvProc()按相关参数完成卷积计算。
SetConvPara ()实现的具体要求为：
（1）确定参数信息：卷积核大小、步长、填充；
（2）返回ConvPara参数。
ConvProc() 实现的具体要求为：
（1）获取输入数据的大小；
（2）获取卷积核的大小；
（3）计算输出数据的大小；
（4）对于每一个输出位置，进行卷积操作；
（5）返回卷积计算后的数组（特征向量）。
'''
# 定义设置卷积参数的函数SetConvPara()
def setconv_para():
    kernel_h = 3 # 设置卷积核大小，这里假设是3x3
    kernel_w = 3
    kernel = [[1.289202, -1.471377, -0.238452],# 卷积核为3x3的单位矩阵(高斯分布)，通常再乘上一个小的数，比如0.1
              [-0.562343, -0.019988, -0.441446],
              [1.627381, 1.390266, 0.812486]]
    stride = 1 # 设置步长，这里假设是1
    padding = 0 # 设置填充，这里假设是0
    ConvPara = {"kernel": kernel,# 返回ConvPara参数，这里用一个字典来存储
                "kernel_h": kernel_h,
                "kernel_w": kernel_w,
                "stride": stride,
                "padding": padding}
    return ConvPara

# if __name__ == '__main__':
#     ConvPara = setconv_para()
#     print(ConvPara)

def conv_proc(image, ConvPara): # 定义卷积函数ConvProc()
    c, image_h, image_w = image.shape # 获取输入数据的大小，这里假设是单通道的图片
    kernel_h = ConvPara["kernel_h"]   # 获取卷积核的大小和卷积核
    kernel_w = ConvPara["kernel_w"]
    kernel = ConvPara["kernel"]
    out_h = (image_h - kernel_h) // ConvPara["stride"] + 1 # 计算输出数据的大小
    out_w = (image_w - kernel_w) // ConvPara["stride"] + 1
    output = np.zeros((c, out_h, out_w)) # 初始化输出数据为零矩阵
    for k in range(c): # 遍历每个通道
        for i in range(out_h):# 遍历每个输出位置
            for j in range(out_w):
                stride = ConvPara["stride"] # 获得步长
                output[k, i, j] = np.sum(image[k, i * stride:i * stride + 3, j * stride:j * stride + 3] * kernel)# 计算卷积操作

    return output # 返回卷积计算后的数组（特征向量）

# if __name__ == '__main__':
#     root = tk.Tk()
#     root.withdraw()
#     DataPara = setload_data()
#     train_images, test_images = load_data(DataPara)
#     ConvPara = setconv_para()
#     conv_images = []            # 存储卷积处理后的图片的列表
#     for image in train_images:  # 获取训练集的图片数据
#         dim = len(image.shape)  # 获取矩阵的维度
#         if dim == 2:            # 如果是二维矩阵,则转化为三维矩阵
#             image_h, image_w = image.shape
#             image = np.reshape(image, (1, image_h, image_w))
#         output = conv_proc(image, ConvPara) # 调用ConvProc()函数，根据ConvPara参数完成卷积计算
#         conv_images.append(output)          # 将卷积结果存储到列表
#     conv_images = np.array(conv_images)     # 将卷积处理后的图片列表转换为数组形式，方便后续处理
#     print(conv_images)

def convolutional_operation(images):
    global ConvPara
    # 存储卷积处理后的图片的列表
    conv_images = []
    # 获取训练集的图片数据
    for image in images:
        # 获取矩阵的维度
        dim = len(image.shape)
        # 如果是二维矩阵,则转化为三维矩阵
        if dim == 2:
            image_h, image_w = image.shape
            image = np.reshape(image, (1, image_h, image_w))
            # 调用ConvProc()函数，根据ConvPara参数完成卷积计算
            output = conv_proc(image, ConvPara)
            # 将卷积结果存储到列表
            conv_images.append(output)
        # 若为三维矩阵，则保持不变直接卷积处理
        elif dim == 3:
            output = conv_proc(image, ConvPara)
            conv_images.append(output)
    Image_Show.append(conv_images[0])
    # 将卷积处理后的图片列表转换为数组形式，方便后续处理
    conv_images = np.array(conv_images)
    return conv_images
'''
【编程16.8】编制程序：设置池化参数SetPoolPara()及池化函数PoolProc()。
【目的及编程说明】读者通过编程16.8可掌握池化函数的基本方法。池化的作用是在卷积神经网络的特征提取过程中，
对卷积后提取到的数组进行降维处理，从而降低特征数组的维度和计算量。SetPoolPara()设置相关参数，
PoolProc ()按相关参数完成池化处理。
SetPoolPara()实现的具体要求为：
（1）确定参数信息：池化的类型Poolmode、池化窗口大小pool_size、步长stride；
（2）返回PoolPara参数。
PoolProc()实现的具体要求为：
（1）获取输入数据的形状；
（2）获取池化窗口的大小；
（3）创建一个输出结果数组，计算池化后的结果大小；
（4）对每个池化窗口应用池化函数，判断池化类型：池化模式为'max'，
则使用窗口中的最大值；池化模式为'avg'，则使用窗口中的平均值；
池化模式为'min'，则使用窗口中的最小值；
（5）返回池化计算后的数组。
'''
def setpool_para(): # 定义设置池化参数的函数
    pool_mode = "max" # 设置池大小和池类型，这里假设是2x2最大池化
    pool_size = 2
    stride = 2 # 设置步长，这里假设是2
    PoolPara = {"pool_mode": pool_mode,"pool_size": pool_size,"stride": stride} # 返回PoolPara参数，这里用一个字典来存储
    return PoolPara # 返回PoolPara参数
# if __name__ == '__main__':
#     PoolPara = setpool_para()
#     print(PoolPara)

def pool_proc(image, PoolPara): # 定义池化函数
    pool_mode = PoolPara["pool_mode"]
    pool_size = PoolPara["pool_size"]
    stride = PoolPara["stride"]
    c, h, w = image.shape  # 获取输入特征图的高度和宽度
    out_h = int((h - pool_size) / stride) + 1  # 计算输出特征图的高度
    out_w = int((w - pool_size) / stride) + 1  # 计算输出特征图的宽度
    out = np.zeros((c, out_h, out_w))  # 初始化输出特征图为全零数组
    for k in range(c): # 对于输出的每一个位置上计算：
        for i in range(out_h):
            for j in range(out_w):
                window = image[k, i * stride:i * stride + pool_size, j * stride:j * stride + pool_size]
                if pool_mode == "max":  # 最大池化
                    out[k][i][j] = np.max(window)
                elif pool_mode == "avg":  # 平均池化
                    out[k][i][j] = np.mean(window)
                elif pool_mode == "min":  # 最小池化
                    out[k][i][j] = np.min(window)
                else:  # 无效的池化类型
                    raise ValueError("Invalid pooling mode")
    return out # 返回特征图。
# if __name__ == '__main__':
#    ···
#     PoolPara = setpool_para()
#     pool_images = []  # 存储池化处理后的图片的列表
#     for image in conv_images:  # 获取卷积后的图片数据
#         output = pool_proc(image, PoolPara)
#         pool_images.append(output)  # 将池化结果存储到列表
#     pool_images = np.array(pool_images)  # 将池化处理后的图片列表转换为数组形式，方便后续处理
#     print(pool_images)

def pooling_treatment(conv_images):
    global PoolPara
    pool_images = [] # 存储池化处理后的图片的列表
    for image in conv_images: # 获取卷积后的图片数据
        output = pool_proc(image, PoolPara)
        pool_images.append(output) # 将池化结果存储到列表
    Image_Show.append(pool_images[0])
    pool_images = np.array(pool_images) # 将池化处理后的图片列表转换为数组形式，方便后续处理
    return pool_images
'''
【编程16.9】编制程序：设置全连接参数SetFullConnPara()及全连接函数FullConnProc()。
【目的及编程说明】读者通过编程16.9可掌握全连接处理的基本方法。全连接是将前面卷积和池化层提取到的数组转换为一个一维的数组，
并将其传递给后面的分类器进行分类。全连接层中的每个神经元都与前一层中的所有神经元相连，
因此全连接层可以将前一层中提取的所有特征信息汇总到一个向量中，为后续的分类任务提供更加丰富的特征表示。
全连接层具有较高的参数数量和计算量。SetFullConnPara()设置全连接相关参数，FullConnProc()按相关参数完成全连接操作。
SetFullConnPara()实现的具体要求为：
（1）确定参数信息: 权重矩阵、偏置向量；
（2）返回：FullConnPara参数。
FullConnProc() 实现的具体要求为：
（1）对输入进行展平处理，变换为单通道的二维数组格式（如果输入数据为三通道，需要将其转换为单通道的二维数组形式）；
（2）计算全连接层的线性变换：inputdata与权重矩阵w进行乘法，再加上偏置向量b；
（3）返回全连接计算后的数组。
'''
def setfullconn_para(data): # 定义一个函数来设置全连接层的相关参数，这里可以根据实际情况修改或随机生成
    c, height, width = data.shape# 获取池化后的图片数组的长度和宽度
    num_outputs = 10  # 输出维度为10，可以设置
    weights = np.random.randn(num_outputs, height * width) * 0.1 # 设置权重矩阵
    bias = np.random.randn(1, num_outputs) # 偏置向量
    FullConnPara = {"weights": weights, "bias": bias, "num_outputs": num_outputs}
    return FullConnPara # 返回FullConnPara参数
def fullconn_proc(inputdata, FullConnPara): # 定义一个函数来完成全连接操作
    weights = FullConnPara["weights"] # 从FullConnPara参数中获取权重矩阵
    bias = FullConnPara["bias"]       # 偏置向量
    inputdata = inputdata.reshape(1, inputdata.shape[1] * inputdata.shape[2]) # 对输入进行展平处理，变换为单通道的一维数组格式
    output = np.dot(inputdata, weights.T) + bias # 计算全连接层的线性变换：inputdata与权重矩阵w进行乘法，再加上偏置向量b
    return output # 返回全连接计算后的数组
# if __name__ == '__main__':
#    ···
#     FullConnPara = setfullconn_para(pool_images[0])
#     fullconn_images = [] # 存储全连接处理后的图片的列表
#     for image in pool_images: # 获取池化后的图片数据
#         output = fullconn_proc(image, FullConnPara)
#         fullconn_images.append(output) # 将全连接处理后的结果存储到列表
#     fullconn_images = np.array(fullconn_images) # 将全连接处理后的图片列表转换为数组形式，方便后续处理

def fullyconnected_operation(pool_images):
    global FullConnPara
    # 存储全连接处理后的图片的列表
    fullconn_images = []
    # 获取池化后的图片数据
    for image in pool_images:
        output = fullconn_proc(image, FullConnPara)
        # 将全连接处理后的结果存储到列表
        fullconn_images.append(output)
    # 将全连接处理后的图片列表转换为数组形式，方便后续处理
    Image_Show.append(fullconn_images[0])
    fullconn_images = np.array(fullconn_images)
    return fullconn_images
'''
【编程16.10】编制程序：设置非线性参数SetNonLPara()及非线性函数NonlinearProc()。
【目的及编程说明】读者通过编程16.10可掌握非线性函数使用的基本方法。它的主要作用是引入非线性特性，从而使网络能够学习更加复杂的特征和模式。
通过激活函数将线性组合的输出进行非线性变换，使得网络可以处理非线性关系和非线性变换，从而更好地适应数据的复杂性。
主要包含的类型：常用的非线性函数包括ReLU、tanh和sigmoid等。非线性函数的输入数据是特征向量。非线性函数应该返回应用激活函数后的特征向量。
SetNonLPara()设置相关参数，NonlinearProc()按相关参数完成对输入数据的非线性处理操作。
SetNonLPara()实现的具体要求为：
（1）确定参数信息:非线性函数的类型；
（2）返回：NonLPara参数。
NonlinearProc(inputdata，NonLPara) 实现的具体要求为：
(1)判断NonLPara，进行相应的计算：Sigmoid\Relu\Tanh;
(2)返回计算后的值。
'''
def setnonl_para(): # 定义设置非线性参数的函数
    # 可以选择"Sigmoid", "ReLU" 或 "Tanh"
    nonlinearmode = "ReLU" # 确定参数信息:非线性函数的类型
    NonLPara = {"nonlinearmode": nonlinearmode} # 返回NonLPara参数，这里用一个字典来存储
    return NonLPara # 返回NonLPara参数

def nonlinear_proc(inputdata, NonLPara): # 定义非线性函数
    nonlinearmode = NonLPara["nonlinearmode"] # 从NonLPara参数中获取非线性函数类型
    if nonlinearmode == "Sigmoid": # 判断nonlinearmode，进行相应的计算
        output = 1 / (1 + np.exp(-inputdata)) # Sigmoid函数，将任何实数的输入映射到0和1之间的输出
    elif nonlinearmode == "ReLU":
        output = np.maximum(inputdata, 0) # ReLU函数，将负数输入置为0，而正数输入保持不变
    elif nonlinearmode == "Tanh":
        output = np.tanh(inputdata) # Tanh函数，将任何实数的输入映射到-1和1之间的输出
    else:
        raise ValueError("Invalid nonlinear mode") # 非法的非线性类型，抛出异常
    return output # 返回计算后的值

# if __name__ == '__main__':
#    ···
#     NonLPara = setnonl_para()
#     nonlinear_images = []
#     for image in fullconn_images: # 获取全连接处理后的图片数据
#         output = nonlinear_proc(image, NonLPara)
#         nonlinear_images.append(output) # 将非线性处理后的结果存储到列表
#     nonlinear_images = np.array(nonlinear_images) # 将非线性处理后的图片列表转换为数组形式，方便后续处理

def activation(fullconn_images):
    global NonLPara
    # 存储非线性处理后的图片的列表
    nonlinear_images = []
    for image in fullconn_images: # 获取全连接处理后的图片数据
        output = nonlinear_proc(image, NonLPara)
        # 将非线性处理后的结果存储到列表
        nonlinear_images.append(output)
    # 将非线性处理后的图片列表转换为数组形式，方便后续处理
    Image_Show.append(nonlinear_images[0])
    nonlinear_images = np.array(nonlinear_images)
    return nonlinear_images

'''
【编程16.11】编制程序：设置分类函数参数SetClassifyPara()及分类函数ClassifierProc()。
【目的及编程说明】读者通过编程16.11可掌握输出分类函数的基本方法。
SetClassifyPara()设置相关参数，ClassifierProc ()按相关参数完成对输入数据的分类标签设置。
SetClassifyPara()实现的具体要求为：
（1）设定阈值；
（2）返回：ClassifyPara参数。
ClassifierProc()实现的具体要求为：
（1）找到概率超过阈值的标签分类，就是分类结果；
（2）返回分类标签。
'''
def setclassify_para(): # 定义设置分类函数参数的函数
    threshold = 0.1     # 设定阈值,可以根据你的数据和任务来调整阈值
    ClassifyPara = {"threshold": threshold} # 返回ClassifyPara参数，这里用一个字典来存储
    return ClassifyPara # 返回ClassifyPara参数
def classifier_proc(inputdata, ClassifyPara): # 定义分类函数
    def softmax(x):    # 定义softmax函数
        x -= np.max(x) # 减去最大值，防止数值溢出
        return np.exp(x) / np.sum(np.exp(x)) # 计算指数和归一化
    threshold = ClassifyPara["threshold"]    # 从ClassifyPara参数中获取阈值
    output = -1 # 初始化输出为-1
    prob = softmax(inputdata)            # 调用softmax函数，得到概率分布向量
    prob1 = prob[prob >= threshold]      # 如果概率高于阈值，就将该类别加入输出结果
    index = np.where(prob == max(prob1)) # 使用where()函数来返回等于概率最大值的元素的索引
    output = index[1].item(0) + 1        # 使用item()方法来将索引转换为标准Python标量
    return output # 返回分类标签

# if __name__ == '__main__':
#    ···
#     classifier_images = []  # 存储分类处理后的图片的列表
#     prob_images = []  # 存储分类处理后的概率向量
#     for image in nonlinear_images:  # 获取非线性处理后的图片数据
#         def softmax(x):  # 定义softmax函数
#             x -= np.max(x)  # 减去最大值，防止数值溢出
#             return np.exp(x) / np.sum(np.exp(x))  # 计算指数和归一化
#         prob = softmax(image)  # 调用softmax函数，得到概率分布向量
#         prob_images.append(prob)  # 将概率向量结果存储到列表
#         classifypara = setclassify_para()
#         output = classifier_proc(image, classifypara)  # 进行分类处理
#         classifier_images.append(output)  # 将分类结果存储到列表
#     classifier_images = np.array(classifier_images)  # 将分类的结果列表转换为数组形式，方便后续处理

def classify(nonlinear_images):
    global ClassifyPara
    classifier_images = [] # 存储分类处理后的图片的列表
    prob_images = [] # 存储分类处理后的概率向量
    for image in nonlinear_images:  # 获取非线性处理后的图片数据
        def softmax(x):             # 定义softmax函数
            x -= np.max(x)          # 减去最大值，防止数值溢出
            return np.exp(x) / np.sum(np.exp(x)) # 计算指数和归一化
        prob = softmax(image)    # 调用softmax函数，得到概率分布向量
        prob_images.append(prob) # 将概率向量结果存储到列表
        output = classifier_proc(image, ClassifyPara) # 进行分类处理
        classifier_images.append(output)             # 将分类结果存储到列表
    Image_Show.append(classifier_images[0])
    classifier_images = np.array(classifier_images)  # 将分类的结果列表转换为数组形式，方便后续处理
    return classifier_images, prob_images
'''
【编程16.12】编制程序：设置标签类别SetLabelPara()及为样本标记标签函数LabelProc()。
【目的及编程说明】读者通过编程16.12为样本标记标签的过程。SetLabelPara()实现的具体要求为：
设置标签类别列表，将标签列表转化为one-hot向量的形式。
LabelProc()实现的具体要求为：
（1）读取样本数据集，遍历每个样本；
（2）读取标签列表；
（3）将样本和对应的标签组成元组，返回标记好标签的样本列表。
'''
def setlabel_para(): # 定义设置标签类别的函数
    label_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 设置标签类别列表，可以根据你的数据和任务来调整
    one_hot_matrix = np.eye(len(label_list)) # 创建一个单位矩阵，大小为标签类别的个数
    LabelPara = (label_list, one_hot_matrix) # 返回标签类别列表和one-hot矩阵，这里用一个元组来存储
    return LabelPara # 返回LabelPara参数

def label_proc(nonlinear_images,prob_images, classifier_images):# 定义为样本标记标签的函数
    global LabelPara
    label = []
    for i in range(nonlinear_images.shape[0]):
        label_list = np.append(prob_images[i], classifier_images[i]) # 连接起来形成一个新的数组
        label.append(label_list) # label_list添加到label列表中
        LABEL = np.array(label)  # 使用np.array()将label列表转换为LABEL数组
    sampledata = np.array(LABEL) # 读取样本数据集，假设是一个二维数组，每一行是一个样本，每一列是一个特征
    label_list, one_hot_matrix = LabelPara # 读取标签列表和one-hot矩阵，假设样本的最后一列是标签值
    labeled_samples = [] # 遍历每个样本，将样本和对应的标签组成元组，返回标记好标签的样本列表
    for sample in sampledata:
        features = sample[:-1] # 获取样本的特征部分和标签部分
        label = sample[-1]
        index = label_list.index(label) # 先找到标签在标签列表中的索引位置
        label = np.take(one_hot_matrix, index, axis=0) # 然后从one-hot矩阵中取出对应的向量
        labeled_sample = (features, label) # 将样本和标签组成元组，并添加到列表中
        print(labeled_sample)
        labeled_samples.append(labeled_sample)
    return labeled_samples # 返回标记好标签的样本列表
# if __name__ == '__main__':
#    ···
#     classifier_images = np.array(classifier_images)  # 将分类的结果列表转换为数组形式，方便后续处理
#     labeled_samples = label_proc(nonlinear_images, prob_images, classifier_images)
#     print(labeled_samples)
'''
【编程16.13】编制程序：设置误差参数SetErrorPara()及计算误差函数ErrorProc()。
【目的及编程说明】读者通过编程20.13可掌握计算误差的基本方法，
计算误差的函数（损失函数）通常有交叉熵（Cross Entropy Error，CEE）、
                                均方误差（Mean Squared Error，MSE）、
                                平均绝对误差（Mean Absolute Error，MAE）
使用损失函数计算预测值与真实值之间的误差。SetErrorPara()设置相关参数，ErrorProc()按相关参数完成误差相关的计算。
SetErrorPara()实现的具体要求为：（1）确定参数信息: 标签类别，损失函数类型；
                                （2）返回：ErrorProc参数。

ErrorProc() 实现的具体要求为：  （1）将真实标签类别label转换为one-hot编码形式；
                                （2）确定损失函数类别，实现不同的损失函数，计算输入值与label之间的误差：
                                （3）返回误差值loss。
'''
def seterror_para(): # 定义设置误差参数的函数
    label_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 确定参数信息: 标签类别，损失函数类型
    loss_type = "CEE" # 假设损失函数类型为交叉熵（Cross Entropy Error，CEE）
    ErrorPara = (label_list, loss_type) # 返回ErrorProc参数，这里用一个元组来存储
    return ErrorPara # 返回ErrorPara参数
def error_proc(input, label, ErrorPara): # 定义计算误差的函数
    label_list, loss_type = ErrorPara # 读取标签列表和损失函数类型
    one_hot_matrix = np.eye(len(label_list))     # 创建一个单位矩阵，大小为标签类别的个数
    index = [label_list.index(l) for l in label] # 找到标签在标签列表中的索引位置
    label_one_hot = np.take(one_hot_matrix, index, axis=0) # 从one-hot矩阵中取出对应的向量
    if loss_type == "CEE": # 确定损失函数类别，实现不同的损失函数，计算输入值与label之间的误差
        # 使用交叉熵损失函数，公式为：-sum(label_one_hot * log(input)) / n
        loss = -np.sum(label_one_hot * np.log(input)) / len(input)
    elif loss_type == "MSE":
        # 使用均方误差损失函数，公式为：sum((input - label_one_hot) ** 2) / n
        loss = np.sum((input - label_one_hot) ** 2) / len(input)
    elif loss_type == "MAE":
        # 使用平均绝对误差损失函数，公式为：sum(abs(input - label_one_hot)) / n
        loss = np.sum(np.abs(input - label_one_hot)) / len(input)
    else:
        raise ValueError("Invalid loss type") # 如果损失函数类型不在以上三种中，抛出异常
    return loss # 返回误差值loss
# if __name__ == '__main__':
#    ···
#     prob_images = np.squeeze(prob_images)
#     input = prob_images
#     array = np.arange(1, 11) # 定义一个一维数组，包含1到10的整数
#     right_label = np.random.choice(array, 900) # 从a中随机抽取100个元素，有放回，每个元素的概率相同
#     ErrorPara = seterror_para()  # 设置误差参数
#     loss = error_proc(input, right_label, ErrorPara) # 计算误差值
#     print(loss)
def loss_count(prob_images):
    global ErrorPara
    # 假设有以下输入值和真实标签值，输入值是一个概率矩阵，真实标签值是一个类别列表
    prob_images = np.squeeze(prob_images)
    input = prob_images # print(len(input))
    array = np.arange(1, 11) # 定义一个一维数组，包含1到10的整数
    # 从a中随机抽取100个元素，有放回，每个元素的概率相同
    right_label = np.random.choice(array, 900)
    loss = error_proc(input, right_label, ErrorPara) # 计算误差值
    return loss

'''
【编程16.14】编制程序：设置卷积核调整函数的参数SetAjConvPara()、卷积核调整函数AjConvProc()。
【目的及编程说明】读者通过编程20.14可掌握更新卷积核的基本方法，反向传播到卷积层的输出，对于卷积核的每一个权重，
可以计算出它对误差的贡献，然后使用梯度下降算法更新该权重。SetAjConvPara()设置相关参数，AjConvProc()按相关参数完成卷积核调整相关的计算。
SetAjConvPara()实现的具体要求为:（1）确定卷积调整的参数：卷积核信息，学习率，误差值；
                             （2）返回参数AjConvPara。4.028404024493524
AjConvProc()的具体实现要求为：（1）使用误差值计算卷积层的误差项和梯度；（2）梯度下降算法更新卷积层参数；（3）返回新的卷积层参数。
'''
def setajconv_para():
    kernel = np.array([[1.0,2.0],[3.0,4.0]])  # 卷积核信息
    learning_rate = 0.01  # 学习率
    bias = 0.5 # 偏置
    loss = np.array([[0.1, 0.2], [0.3, 0.4]])
    AjConvPara={'kernel_info': kernel,'learning_rate': learning_rate,'bias': bias,'loss': loss}
    return AjConvPara
def ajconv_proc(input, AjConvPara):
    k = [] # 计算卷积核和偏置项的梯度
    bias_grad = np.sum(AjConvPara['loss'])  # 计算偏置项梯度为误差值之和
    for c in input:
        kernel_grad = np.zeros_like(AjConvPara['kernel_info'])  # 初始化卷积核梯度为零矩阵
        for i in range(AjConvPara['loss'].shape[0]):      # 遍历误差值矩阵的每一行
            for j in range(AjConvPara['loss'].shape[1]):  # 遍历误差值矩阵的每一列
                kernel_grad += np.rot90(c[i:i + 2, j:j + 2], 2) * AjConvPara['loss'][i, j]
        k.append(kernel_grad)
    result = np.stack(k, axis=0)
    kernel_grad = np.sum(result, axis=0) / len(input)
    kernel = AjConvPara['kernel_info'] - AjConvPara['learning_rate'] * kernel_grad
    bias = AjConvPara['bias'] - AjConvPara['learning_rate'] * bias_grad
    return kernel, bias# 返回更新后的卷积核和偏置项
# if __name__ == '__main__':
#     input = np.array([[[1,2,3],[4,5,6],[7,8,9]],
#                       [[10,11,12],[13,14,15],[16,17,18]],
#                       [[19,20,21],[22,23,24],[25,26,27]],
#                       [[28,29,30],[31,32,33],[34,35,36]]])
#     AjConvPara=setajconv_para()
#     kernel_new, bias_new = ajconv_proc(input, AjConvPara)
#     print("\nkernel_new =\n", kernel_new,"\n")
#     print("bias_new =", bias_new)

def setajfc_para():
    AjFCPara = {
        'weights': np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]),  # 全连接权重
        'bias': np.array([0.5, 0.6]),  # 全连接偏置
        'learning_rate': 0.01,  # 学习率
        'error': np.array([0.001, 0.002])  # 误差值
    }
    return AjFCPara
def ajfullconn_proc(AjFCPara):
    error_value = AjFCPara['error']
    learning_rate = AjFCPara['learning_rate']
    error_term = error_value  # 全连接层的误差项等于误差值
    gradient_weights = np.outer(error_term, learning_rate)  # 计算权重梯度
    gradient_bias = error_term * learning_rate  # 计算偏置梯度
    # 更新全连接层参数（梯度下降算法）
    AjFCPara['weights'] -= gradient_weights
    AjFCPara['bias'] -= gradient_bias
    return AjFCPara
# if __name__ == '__main__':
#     AjFCPara = setajfc_para()# 调用SetAjFCPara()设置参数
#     print("初始化的全连接层参数：", AjFCPara)
#     newajfcpara = ajfullconn_proc(AjFCPara)# 调用AjFullconnProc()更新全连接层参数
#     print("更新后的全连接层参数：", newajfcpara)
def parameter():
    global DataPara
    global ConvPara
    global PoolPara
    global NonLPara
    global ClassifyPara
    global LabelPara
    global ErrorPara
    DataPara = setload_data()  # 调用setload_data()函数，获取加载数据集的参数
    ConvPara = setconv_para()  # 调用SetConvPara()函数，获取卷积层参数
    PoolPara = setpool_para()  # 调用设置池化层参数的函数，获取池化参数
    NonLPara = setnonl_para()  # 调用设置非线性参数的函数，获取非线性参数
    ClassifyPara = setclassify_para()  # 设置分类函数参数
    LabelPara = setlabel_para()  # 设置标签类别参数
    ErrorPara = seterror_para()  # 设置误差参数

def show_subwindow(conv_image):
    # 使用Matplotlib的imshow函数显示归一化的卷积后的数据
    # 将数据从形状 (1, 46, 46) 转换为 (46, 46)
    conv_image_flipped = np.fliplr(conv_image.squeeze())  # 左右对换
    conv_image_rotated = np.rot90(conv_image_flipped, 1)  # 右转90度

    plt.imshow(conv_image_rotated, cmap='gray')  # 使用灰度色彩映射

    # 可选：设置坐标轴标题和颜色条
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.colorbar()
    # 设置图像的色彩范围为[0, 1]
    plt.clim(0, 1)
    # 保存图像为png格式
    plt.savefig('conv_image.png', dpi=300, format='png')
    # 显示图像
    plt.show()
def main():
    # 创建一个tkinter根窗口，并隐藏它
    root = tk.Tk()
    root.withdraw()
    parameter()
    global DataPara, Image_Show
    Image_Show = []
    train_images, test_images = load_data(DataPara)  # 调用LoadData()函数，根据参数加载数据集
    conv_images = convolutional_operation(train_images)  # 存储卷积处理后的图片的列表
    pool_images = pooling_treatment(conv_images)  # 存储还回池化处理后的图片的列表
    global FullConnPara
    FullConnPara = setfullconn_para(pool_images[0])  # 调用设置全连接层参数的函数，获取全连接参数
    fullconn_images = fullyconnected_operation(pool_images)  # 存储全连接处理后的图片的列表
    nonlinear_images = activation(fullconn_images)  # 存储非线性处理后的图片的列表
    classifier_images, prob_images = classify(nonlinear_images)  # 存储分类处理后的图片的列表
    labeled_samples = label_proc(nonlinear_images, prob_images, classifier_images)  # 读取之前的分类，每一行是一个样本，每一列是一个特征，将分类标签放在最后一列
    # print(labeled_samples)
    loss = loss_count(prob_images)
    # 打印结果
    print(loss)
    show_subwindow(Image_Show[0])
    show_subwindow(Image_Show[1])

if __name__ == '__main__':
    main()



# if __name__ == '__main__':
#     root = tk.Tk()
#     root.withdraw()
#     DataPara = setload_data()
#     train_images, test_images = load_data(DataPara)
#     ConvPara = setconv_para()
#     conv_image = []            # 存储卷积处理后的图片的列表
#     for image in train_images:  # 获取训练集的图片数据
#         dim = len(image.shape)  # 获取矩阵的维度
#         if dim == 2:            # 如果是二维矩阵,则转化为三维矩阵
#             image_h, image_w = image.shape
#             image = np.reshape(image, (1, image_h, image_w))
#         output = conv_proc(image, ConvPara) # 调用ConvProc()函数，根据ConvPara参数完成卷积计算
#         conv_image.append(output)          # 将卷积结果存储到列表
#     conv_images = np.array(conv_image)     # 将卷积处理后的图片列表转换为数组形式，方便后续处理
#     print(conv_images)
#     show_subwindow()