# -*- coding: utf-8 -*-
# Time : 2023/10/12 9:29
# Author : lirunsheng
# User : l'r's
# Software: PyCharm
# File : X5_1.py

# coding = UTF-8
import os
import sys
import pickle
import subprocess
import textwrap

import numpy as np
import pandas as pd
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import tkinter.scrolledtext as st
from PIL import Image, ImageTk
from matplotlib import pyplot as plt
from ttkbootstrap import *
from tkinter import Canvas, Frame, BOTH, PhotoImage, filedialog
from X3 import load_data
from X2 import ModelObj
import glob

# 网络模型正确的图元和连接关系的数据结构，用一个字典表示
from classification import *

RIGHT_MODEL = [
    [0, 1, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 1, 0, 1, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 1, 1],
    [0, 1, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 0, 0]
]

# 创建三个空的卷积层图元实例和三个空的全连接层图元实例
Conv1 = None
Conv2 = None
Conv3 = None
Pool1 = None
Pool2 = None
Pool3 = None
Fullconn1 = None
Fullconn2 = None
Fullconn3 = None

global Viewcanvas  # 定义画布
global Text  # 带滚动条
global Root  # 主窗口
global AllModelObj  # 网络对象
Record_Data = [] # 记录批次照片数据
Flage = 0
List_image = []  # 存储图元的列表
Prob_images = []  # 存储图元的列表
Train_images = []  # 存储训练集的列表
Test_images = []  # 存储测试集的列表
Items1 = []  # 存储新图元的列表
Items2 = []  # 存储新数据线的列表
Items3 = []  # 存储被拖拽的图元的列表
Items4 = []  # 存储新图元文字标签的列表
Counts = {}  # 用来存储Items3每个数字出现过的次数
Conv_xy_list = []  # 存储卷积图元的坐标
XY_list = []  # 存储图元的坐标
Fullconn_xy_list = []  # 存储全连接图元的坐标
Train_list = []  # 存储训练设置参数
Dragged_item = ()  # 记录被拖拽的图元id
Original_x = 0  # 记录图元原来的x坐标
Original_y = 0  # 记录图元原来的y坐标
Offset_x = 0  # 记录鼠标相对于图元的x偏移量
Offset_y = 0  # 记录鼠标相对于图元的y偏移量
Yt = 0  # 新图元的y坐标
Batch = 0  # 批次
Listinstance = []  # 图元对象实例
Second_Items = [[], [], []]  # 记录第二个框中画布创建的序号(照片，标签，连线）
Batch_First = []  # 每个批次第一张数据
Batch_First_Sheet = [] # 批次第一张数据

def create_instance():
    # 创建图元对象实例
    DataSet = ModelObj("DataSet", 1, "数据集", "LoadData", "SetDataPara", ".", 120, 70).output()
    Conv = ModelObj("Conv", 2, "卷积", "ConvProc", "SetConvPara", ".", 280, 70).output()
    Pool = ModelObj("Pool", 3, "最大池化", "MaxPoolProc", "SetPollPara", ".", 120, 165).output()
    FullConn = ModelObj("FullConn", 4, "全连接", "FullConnProc", "SetFullConnPara", ".", 280, 165).output()
    Nonline = ModelObj("Nonline", 5, "非线性函数", "NonlinearProc", "SetNonLPara", ".", 120, 260).output()
    Classifier = ModelObj("Classifier", 6, "分类", "ClassifierProc", "SetClassifyPara", ".", 280, 260).output()
    Error = ModelObj("Error", 7, "误差计算", "ErrorProc", "SetErrorPara", ".", 120, 355).output()
    AjConv = ModelObj("AjConv", 8, "卷积调整", "AjConvProc", "SetAjConvPara", ".", 280, 355).output()
    AjFullconn = ModelObj("AjFullconn", 9, "全连接调整", "AjFullconnProc", "SetAjFCPara", ".", 120, 450).output()
    AjNonline = ModelObj("AjNonline", 10, "非线性调整", "AjNonlineProc", "SetAjNLPara", ".", 280, 450).output()
    listinstance = [DataSet, Conv, Pool, FullConn, Nonline, Classifier, Error, AjConv, AjFullconn, AjNonline]

    return listinstance  # 还回图元对象实例列表

def photo_image(conv_image):
    image_width = 200
    image_height = 200
    conv_image_flipped = np.fliplr(conv_image.squeeze())  # 左右对换
    conv_image_rotated = np.rot90(conv_image_flipped, 1)  # 右转90度
    # 还原归一化
    restored_data = conv_image_rotated * 255
    # 转换为图像数据
    image_data = np.uint8(restored_data)
    image = Image.fromarray(image_data)
    image = image.resize((image_width, image_height))
    photo = ImageTk.PhotoImage(image)
    return photo

def show_subwindow():
    if len(Batch_First_Sheet):
        subwindow = tk.Toplevel(Root)
        subwindow.geometry('1400x400')
        subwindow.title('Subwindow')
        # 创建画布
        canvas_width = 1300
        canvas_height = 400
        image_x = 50
        image_y = 50
        canvas = Canvas(subwindow, width=canvas_width, height=canvas_height, bg="white")
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar = Scrollbar(subwindow, orient="vertical", command=canvas.yview)
        scrollbar.pack(fill="y", side="right")
        canvas.configure(yscrollcommand=scrollbar.set)
        canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        photo_list = []
        item_list = [x for x in Items3 if x in range(1, 6)]
        pixel = ['数据集', '卷积', '池化', '全连接', '非线性', '分类', '误差', '调整1', '调整2', '调整3']
        index = 0
        for i in range(len(Batch_First_Sheet)):
            for j in range(len(Batch_First_Sheet[i])):
                flage = 0
                for k in range(len(item_list)):
                    flage += 1
                    if flage == 1:
                        canvas.create_text(20, 10 + index * 300, text='第 ' + str(i+1) + ' 轮，第 ' + str(j+1) + ' 批次')
                    canvas.create_text(70 + k * 300, 30 + index * 300, text=pixel[item_list[k]])
                    if item_list[k] == 1:
                        photo = photo_image(Batch_First_Sheet[i][j][k])
                        photo_list.append(photo)
                        canvas.create_image(20 + k*300, 50 + index * 300, anchor="nw", image=photo)
                    elif item_list[k] == 2:
                        photo = photo_image(Batch_First_Sheet[i][j][k])
                        photo_list.append(photo)
                        canvas.create_image(20 + k*300, 50 + index * 300, anchor="nw", image=photo)
                    else:
                        canvas.create_text(60 + k * 300, 80 + index * 300, text=textwrap.fill(str(Batch_First_Sheet[i][j][k]), width=40))
                index = index+1
                canvas_height += 350  # 或根据需要调整适当的高度增加值
        canvas.configure(scrollregion=(0, 0, canvas_width, canvas_height))
        # 更新画布配置以适应图像
        # canvas.configure(scrollregion=canvas.bbox("all"))
        subwindow.mainloop()
    else:
        messagebox.showinfo('警告', '请先训练再查看')

def picture_frame():
    global Root
    Root = tk.Tk()
    # 设置窗口的大小为1200*750
    window_width = 1450  # 窗口的宽度
    window_height = 900  # 窗口的高度
    Root.title("神经网络可视化")
    Root.geometry("1450x900")  # 设置窗口的大小和位置
    gap = 20  # 设置矩形框之间的间隔为10像素
    # 计算矩形框的坐标和尺寸
    rect1_x = gap  # 第一个矩形框的左上角x坐标
    rect1_y = gap  # 第一个矩形框的左上角y坐标
    rect1_width = window_width * 1 / 3 - gap - 50  # 第一个矩形框的宽度
    rect1_height = window_height * 3 / 4 - gap * 2 - 50  # 第一个矩形框的高度
    rect2_x = window_width * 1 / 3 + gap - 50  # 第二个矩形框的左上角x坐标
    rect2_y = gap  # 第二个矩形框的左上角y坐标
    rect2_width = window_width * 2 / 3 - gap + 50  # 第二个矩形框的宽度
    rect2_height = window_height * 3 / 4 - gap * 2 - 50  # 第二个矩形框的高度
    rect3_x = gap  # 第三个矩形框的左上角x坐标
    rect3_y = window_height * 3 / 4 + gap - 50  # 第三个矩形框的左上角y坐标
    rect3_width = window_width - gap * 2  # 第三个矩形框的宽度
    rect3_height = window_height * 1 / 4 - gap * 2 + 50  # 第三个矩形框的高度
    global Viewcanvas
    # 创建一个画布，用于绘制矩形框，设置画布的大小和背景色
    Viewcanvas = tk.Canvas(Root, width=window_width, height=window_height, bg="white")
    Viewcanvas.pack()    # 将画布添加到主窗口中
    # 绘制矩形框，使用不同的颜色和线宽，指定矩形框的左上角和右下角坐标，填充色，边框色和边框宽度
    Viewcanvas.create_rectangle(rect1_x, rect1_y, rect1_x + rect1_width, rect1_y + rect1_height,
                            fill=None, outline="lightblue", width=2)
    Viewcanvas.create_rectangle(rect2_x, rect2_y + 20, rect2_x + rect2_width - 20, rect2_y + rect2_height,
                            fill=None, outline="lightblue", width=2)
    # print(rect2_x, rect2_y + 20, rect2_x + rect2_width - 20, rect2_y + rect2_height)
    Viewcanvas.create_rectangle(rect3_x, rect3_y, rect3_x + rect3_width, rect3_y + rect3_height,
                            fill=None, outline="lightblue", width=2)
    global Text
    Text = st.ScrolledText(Root)  # 创建一个带滚动条的文本框，用于展示计算过程
    Text.place(x=rect3_x, y=rect3_y, width=1410, height=rect3_height - 1)


def element(path, width, height):
    # 加载图元对应的图片文件
    img = Image.open(path)
    # 使用resize方法调整图片
    # img = img.resize((70, 60))
    img = img.resize((width, height))
    # 把Image对象转换成PhotoImage对象
    img = ImageTk.PhotoImage(img)
    # 保存图片的引用，防止被垃圾回收
    Root.img = img
    return img


def push_button():
    button = {}
    # 指定按钮主题为 litera
    style = Style(theme='litera')
    # 设置blue.TButton样式的背景色为亮蓝灰，前景色为白色
    style.configure("blue.TButton", background="LightSlateGray", foreground="white", font=("黑体", 13))
    style.map("blue.TButton",
              background=[('active', 'PowderBlue'), ('pressed', 'red')],  # 设置激活状态下的背景色为粉蓝色，按下状态下的背景色为红色
              foreground=[('active', 'black'), ('pressed', 'white')])     # 设置激活状态下的前景色为黑色，按下状态下的前景色为白色
    # 设置green.TButton样式的背景色为中海洋绿，前景色为白色
    style.configure("green.TButton", background="MediumSeaGreen", foreground="white", font=("黑体", 13))
    style.map("green.TButton",
              background=[('active', 'PaleGreen'), ('pressed', 'red')],  # 设置激活状态下的背景色为弱绿色，按下状态下的背景色为红色
              foreground=[('active', 'black'), ('pressed', 'white')])     # 设置激活状态下的前景色为黑色，按下状态下的前景色为白色
    # 创建三个按钮，用于触发训练参数的设置、训练、输出模型程序的操作
    button1 = ttk.Button(Root, text=" 训练参数设置 ", style="blue.TButton")
    button1.place(x=20, y=610)
    button['button1'] = button1
    button2 = ttk.Button(Root, text=" 训练 ", style="blue.TButton")
    button2.place(x=180, y=610)
    button['button2'] = button2
    button3 = ttk.Button(Root, text=" 输出模型程序 ", style="blue.TButton")
    button3.place(x=290, y=610)
    button['button3'] = button3
    button10 = ttk.Button(Root, text=" 测试 ", style="blue.TButton")
    button10.place(x=470, y=610)
    button['button10'] = button10
    # 创建五个按钮，用于触发操作
    button4 = ttk.Button(Root, text=" 加载已有模型 ", style="green.TButton")
    button4.place(x=455, y=5)
    button['button4'] = button4
    button5 = ttk.Button(Root, text=" 新建模型 ", style="green.TButton")
    button5.place(x=610, y=5)
    button['button5'] = button5
    button6 = ttk.Button(Root, text=" 图元参数设置 ", style="green.TButton")
    button6.place(x=730, y=5)
    button['button6'] = button6
    button7 = ttk.Button(Root, text=" 保存模型 ", style="green.TButton")
    button7.place(x=885, y=5)
    button['button7'] = button7
    button8 = ttk.Button(Root, text=" 模型正确性检查 ", style="green.TButton")
    button8.place(x=1005, y=5)
    button['button8'] = button8
    button9 = ttk.Button(Root, text="刷新", style="green.TButton")
    button9.place(x=1180, y=5)
    button['button9'] = button9
    button11 = ttk.Button(Root, text="训练数据图像展示", style="green.TButton")
    button11.place(x=1250, y=5)
    button['button11'] = button11
    return button


'''
【编程16.19】编制程序：训练函数。
【目的及编程说明】读者通过编程16.19可掌握卷积神经网络的训练过程。其基本流程为输入数据通过前向传播预测出标签值，预测标签与真实标签进行比较得出误差，
反向传播误差值更新卷积层、全连接层参数，直到误差值达到阈值或迭代次数完毕。
基本实践内容：
（1）使用 AllModelObj 中的 SetParaFunc 函数初始化所有需要学习的网络参数。
（2）对于每个 epoch，使用AllModelConn 中的连接对象确定图元或是操作函数之间的关系，使用 AllModelObj 中的 ProcFunc
 函数进行前向传播，并使用 AllModelObj 中的 ProcFunc 函数计算误差。再使用 AllModelObj 中的 ProcFunc 函数进行反向传播，
 并使用 AllModelObj 中的 SetParaFunc函数更新网络参数。
 （3）直到迭代次数停止，将生成的网络结构保存到一个文件中，以便以后使用或加载到其他神经网络中。
 （4）若更新训练过程参数，请用户点击训练参数设置，进行更改参数，设置完毕，用户点击保存。
 （5）获取训练参数展示区的字符串，解析字符串，对训练函数进行设置。
（6）点击训练按钮，进行训练。
'''
# X3所有基本图元函数
# 定义加载数据集的参数SetLoadData()
# 定义设置卷积参数的函数SetConvPara()
def setconv_para(numbers):
    # 从Conv的ParaString得到一个包含所有卷积参数的列表
    conv_list = Listinstance[1][5].split()
    height, stride = conv_list[numbers - 1].split(',')
    height = int(height)  # 设置卷积核大小
    stride = int(stride)  # 设置步长
    # 卷积核为3x3的单位矩阵(高斯分布)，通常再乘上一个小的数，比如0.01
    kernel = np.random.randn(height, height) * 0.1
    padding = 0  # 设置填充，这里假设是0
    bias = 0.5  # 设置偏置，这里假设是0.5
    # 返回ConvPara参数,这里用一个字典来存储
    ConvPara = {numbers: {"kernel": kernel,
                          "kernel_h": height,
                          "kernel_w": height,
                          "stride": stride,
                          "padding": padding,
                          "bias": bias}}
    Conv0 = ModelObj("Conv", 2, "卷积", "ConvProc", "SetConvPara", ".", 340, 60).output()
    # 若为第一个卷积层：
    if numbers == 1:
        # 创建全局变量的卷积图元对象实例
        global Conv1
        Conv1 = Conv0
        # 令Conv1[5]等于{1: {'kernel': array(), 'kernel_h': 5, 'kernel_w': 5, 'stride': 1, 'padding': 0}
        Conv1[5] = ConvPara
        # 将Conv1[5][1]重新赋值到ConvPara
        ConvPara = Conv1[5][1]
    # 若为第二个卷积层：
    elif numbers == 2:
        global Conv2
        Conv2 = Conv0
        Conv2[5] = ConvPara
        ConvPara = Conv2[5][2]
    # 若为第三个卷积层：
    elif numbers == 3:
        global Conv3
        Conv3 = Conv0
        Conv3[5] = ConvPara
        ConvPara = Conv3[5][3]
    # 返回ConvPara,即{'kernel': array(), 'kernel_h': 5, 'kernel_w': 5, 'stride': 1, 'padding': 0}
    return ConvPara


# 定义卷积函数ConvProc()
def conv_proc(image, ConvPara):
    # 获取输入数据的大小，这里假设是单通道的图片
    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"]
                bias = ConvPara["bias"]
                # 计算卷积操作
                output[k, i, j] = np.sum(
                    image[k, i * stride:i * stride + kernel_h, j * stride:j * stride + kernel_w] * kernel) + bias
    # 返回卷积计算后的数组（特征向量）
    return output


# 定义设置池化参数的函数
def setpool_para(numbers):
    # 从Pool的ParaString得到一个包含所有池化参数的列表
    pool_list = Listinstance[2][5].split()
    # 池大小和池类型
    pool_mode, pool_size = pool_list[numbers - 1].split(',')
    pool_size = int(pool_size)
    stride = 2  # 设置步长，这里假设是2
    PoolPara = {numbers: {"pool_mode": pool_mode,  # 返回PoolPara参数，这里用一个字典来存储
                          "pool_size": pool_size,
                          "stride": stride}}
    Pool0 = ModelObj("Pool", 3, "最大池化", "MaxPoolProc", "SetPollPara", ".", 150, 150).output()
    # 若为第一个池化层：
    if numbers == 1:
        # 创建全局变量的卷积图元对象实例
        global Pool1
        Pool1 = Pool0
        Pool1[5] = PoolPara
        PoolPara = Pool1[5][1]
    # 若为第二个卷积层：
    elif numbers == 2:
        global Pool2
        Pool2 = Pool0
        Pool2[5] = PoolPara
        PoolPara = Pool2[5][2]
    # 若为第三个卷积层：
    elif numbers == 3:
        global Pool3
        Pool3 = Pool0
        Pool3[5] = PoolPara
        PoolPara = Pool3[5][3]

    # 返回PoolPara参数
    return PoolPara


# 定义池化函数
def pool_proc(image, PoolPara):
    # 输入：输入特征图image，PoolPara参数（字典）
    # 输出：池化后特征图（数组）
    # 从PoolPara参数中获取池化类型，池化窗口大小和步长
    pool_mode = PoolPara["pool_mode"]
    pool_size = PoolPara["pool_size"]
    stride = PoolPara["stride"]
    # 计算输出特征图out大小：
    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


# 定义一个函数来设置全连接层的相关参数，这里可以根据实际情况修改或随机生成
def setfullconn_para(image, numbers):
    # 获取池化后的图片数组的长度和宽度
    data = image
    dim = len(data.shape)
    if dim == 2:
        height, width = data.shape
    elif dim == 3:
        c, height, width = data.shape
    # 从FullConn的ParaString得到一个包含所有全连接参数的列表
    fullconn_list = Listinstance[3][5].split()
    # print(fullconn_list)
    # 设置权重矩阵及其输出维度和偏置向量
    num_outputs = fullconn_list[numbers - 1]
    num_outputs = int(num_outputs)
    weights = np.random.randn(num_outputs, height * width)
    bias = np.random.randn(1, num_outputs)
    # 返回FullConnPara参数，这里用一个字典来存储
    FullConnPara = {numbers: {"weights": weights,
                              "bias": bias,
                              "num_outputs": num_outputs}}
    FullConn0 = ModelObj("FullConn", 4, "全连接", "FullConnProc", "SetFullConnPara", ".", 340, 150).output()
    # 第一个卷积层：
    if numbers == 1:
        # 创建全局变量的全连接图元实例
        global Fullconn1
        Fullconn1 = FullConn0
        # 令Fullconn1[5]等于{1: {'weight': array(), 'bias': array(), 'num_outputs': 7}
        Fullconn1[5] = FullConnPara
        # 将Fullconn1[5][1]即{'weight': array(), 'bias': array(), 'num_outputs': 7}重新赋值到FullConnPara
        FullConnPara = Fullconn1[5][1]
    # 第二个卷积层：
    elif numbers == 2:
        global Fullconn2
        Fullconn2 = FullConn0
        Fullconn2[5] = FullConnPara
        FullConnPara = Fullconn2[5][2]
    # 第三个卷积层：
    elif numbers == 3:
        global Fullconn3
        Fullconn3 = FullConn0
        Fullconn3[5] = FullConnPara
        FullConnPara = Fullconn3[5][3]
    # 返回FullConnPara参数
    return FullConnPara


# 定义一个函数来完成全连接操作
def fullconn_proc(inputdata, FullConnPara):
    # 从FullConnPara参数中获取权重矩阵和偏置向量
    weights = FullConnPara["weights"]
    bias = FullConnPara["bias"]
    # 对输入进行展平处理，变换为单通道的一维数组格式
    dim = len(inputdata.shape)
    if dim == 2:
        inputdata = inputdata.reshape(1, inputdata.shape[0] * inputdata.shape[1])
    elif dim == 3:
        inputdata = inputdata.reshape(1, inputdata.shape[1] * inputdata.shape[2])
    # 计算全连接层的线性变换：inputdata与权重矩阵w进行乘法，再加上偏置向量b
    output = np.dot(inputdata, weights.T) + bias
    # 返回全连接计算后的数组
    return output


# 定义设置非线性参数的函数
def setnonl_para():
    # 可以选择"Sigmoid", "ReLU" 或 "Tanh"
    # 从Nonline的ParaString得到一个包含所有非线性参数的列表
    nonlinearmode = Listinstance[4][5]
    # 返回NonLPara参数，这里用一个字典来存储
    NonLPara = {"nonlinearmode": nonlinearmode}
    # 返回NonLPara参数
    return NonLPara


# 定义非线性函数
def nonlinear_proc(inputdata, NonLPara):
    # 从NonLPara参数中获取非线性函数类型
    nonlinearmode = NonLPara["nonlinearmode"]
    # 判断nonlinearmode，进行相应的计算
    if nonlinearmode == "Sigmoid":
        # Sigmoid函数，将任何实数的输入映射到0和1之间的输出
        output = 1 / (1 + np.exp(-inputdata))
        # 求出Sigmoid函数的导数，用于全连接层权重矩阵和偏执向量的调整
        output_derivative = (1 / (1 + np.exp(-inputdata))) * (1 - (1 / (1 + np.exp(-inputdata))))
    elif nonlinearmode == "ReLU":
        # ReLU函数，将负数输入置为0，而正数输入保持不变
        output = np.maximum(inputdata, 0)
        # 求出ReLU函数的导数，用于全连接层权重矩阵和偏执向量的调整
        output_derivative = np.where(inputdata > 0, 1, 0)
    elif nonlinearmode == "Tanh":
        # Tanh函数，将任何实数的输入映射到-1和1之间的输出
        output = np.tanh(inputdata)
        # 求出Tanh函数的导数，用于全连接层权重矩阵和偏执向量的调整
        output_derivative = 1 - np.tanh(inputdata) ** 2
    else:
        # 非法的非线性类型，抛出异常
        raise ValueError("Invalid nonlinear mode")
    # 返回计算后的值
    return output, output_derivative


# 定义设置分类函数参数的函数
def setclassify_para():
    # 设定阈值,可以根据你的数据和任务来调整阈值
    threshold = float(Listinstance[5][5])
    # 返回ClassifyPara参数，这里用一个字典来存储
    ClassifyPara = {"threshold": threshold}
    # 返回ClassifyPara参数
    return ClassifyPara


# 定义分类函数
def classifier_proc(inputdata, ClassifyPara):
    # 找到概率超过阈值的标签分类，就是分类结果
    # 假设输入数据是一个一维数组，表示每个类别的概率
    # 假设输出数据是一个整数，表示预测的类别编号
    # 如果没有任何类别的概率超过阈值，就返回-1表示无法分类
    # 定义softmax函数
    def softmax(x):
        # 减去最大值，防止数值溢出
        x -= np.max(x)
        # 计算指数和归一化
        return np.exp(x) / np.sum(np.exp(x))

    # 从ClassifyPara参数中获取阈值
    threshold = ClassifyPara["threshold"]
    # 调用softmax函数，得到概率分布向量
    prob = softmax(inputdata)
    # 如果概率高于阈值，就将该类别加入输出结果
    prob1 = prob[prob >= threshold]
    # 使用where()函数来返回等于概率最大值的元素的索引
    index = np.where(prob == max(prob1))
    # 使用item()方法来将索引转换为标准Python标量
    output = index[1].item(0) + 1
    # 返回分类标签
    return output
def folder_names():
    path_to_folder = "data_classification/train/"  # 指定文件夹路径
    # 获取指定文件夹内的所有文件名（包括子文件夹的名称）
    file_names = os.listdir(path_to_folder)
    # 筛选出其中是文件夹的名称
    folder_list = [int(name) for name in file_names if os.path.isdir(os.path.join(path_to_folder, name))]
    return folder_list

# 定义设置误差参数的函数
def seterror_para():
    # 确定参数信息: 标签类别，损失函数类型
    # 从FullConn的ParaString得到一个包含所有全连接参数的列表
    # fullconn_list = Listinstance[3][5].split()
    # # 标签类别为最后一个全连接层的输出维度
    # num_outputs = fullconn_list[-1]
    # num_outputs = int(num_outputs)
    # label_list = list(range(1, num_outputs + 1))
    label_list = folder_names()
    # print(label_list)
    # 假设损失函数类型为交叉熵（Cross Entropy Error，CEE）
    # 从Error的ParaString得到一个包含所有全连接参数的列表
    error_list = Listinstance[6][5].split()
    # 设置权重矩阵及其输出维度和偏置向量
    loss_type = error_list[-1]
    # 返回ErrorProc参数，这里用一个元组来存储
    ErrorPara = (label_list, loss_type)
    # 返回ErrorPara参数
    return ErrorPara


# 定义计算误差的函数
def error_proc(input, right_label, ErrorPara):
    # 将真实标签类别label转换为one-hot编码形式
    # 读取标签列表和损失函数类型
    label_list, loss_type = ErrorPara
    # print(label_list)
    # 创建一个单位矩阵，大小为标签类别的个数
    one_hot_matrix = np.eye(len(label_list))
    # 找到标签在标签列表中的索引位置
    index = [label_list.index(l) for l in right_label]
    index = [x - 1 for x in right_label]
    # 从one-hot矩阵中取出对应的向量
    label_one_hot = np.take(one_hot_matrix, index, axis=0)
    # 确定损失函数类别，实现不同的损失函数，计算输入值与label之间的误差
    if loss_type == "CEE":
        # 使用交叉熵损失函数，公式为：-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")
        # 返回误差值loss
    return loss


# 定义一个卷积核调整函数,输入为卷积层的输入数据数组、卷积核、偏置、误差值，输出为更新后的卷积核、偏置
def update_conv_layer(input, kernel, bias, loss):
    kernel_grad_list = []
    bias_grad = 0  # 初始化偏置项梯度为零
    for c in input:
        # 计算卷积核和偏置项的梯度
        kernel_grad = np.zeros_like(kernel)  # 初始化卷积核梯度为零矩阵
        for i in range(loss.shape[0]):  # 遍历误差值矩阵的行
            for j in range(loss.shape[1]):  # 遍历误差值矩阵的列
                # 将输入数据数组中对应的子矩阵旋转180度，与误差值相乘，累加到卷积核梯度上
                kernel_grad += np.rot90(c[i:i + kernel_grad.shape[0], j:j + kernel_grad.shape[0]], 2) * loss[i, j]
                # 将误差值累加到偏置项梯度上
                bias_grad += loss[i, j]
        kernel_grad_list.append(kernel_grad)
    # 使用stack函数沿着第0个轴把一百个a数组堆叠起来
    result = np.stack(kernel_grad_list, axis=0)
    kernel_grad = np.sum(result, axis=0) / len(input)  # 沿着第0个维度求和
    # 设置学习率为0.01
    learning_rate = 0.01
    # 更新卷积核和偏置项参数
    kernel = kernel - learning_rate * kernel_grad  # 卷积核参数减去学习率乘以卷积核梯度
    bias = bias - learning_rate * bias_grad  # 偏置项参数减去学习率乘以偏置项梯度
    # 返回更新后的卷积核、偏置
    return kernel, bias


def conv_one(loss, images):
    global Conv1
    kernel = Conv1[5][1]["kernel"]
    bias = 0.5
    loss = np.array([[loss]])
    new_kernel, new_bias = update_conv_layer(images, kernel, bias, loss)
    Conv1[5][1]["kernel"] = new_kernel
    Conv1[5][1]["bias"] = new_bias


def conv_two(loss, images):
    global Conv1
    global Conv2
    kernel1 = Conv1[5][1]["kernel"]
    bias1 = 0.5
    loss = np.array([[loss]])
    new_kernel1, new_bias1 = update_conv_layer(images, kernel1, bias1, loss)
    Conv1[5][1]["kernel"] = new_kernel1
    Conv1[5][1]["bias"] = new_bias1

    kernel2 = Conv2[5][2]["kernel"]
    bias2 = 0.5
    new_kernel2, new_bias2 = update_conv_layer(images, kernel2, bias2, loss)
    Conv2[5][2]["kernel"] = new_kernel2
    Conv2[5][2]["bias"] = new_bias2


def conv_three(loss, images):
    global Conv1
    global Conv2
    global Conv3
    kernel1 = Conv1[5][1]["kernel"]
    bias1 = 0.5
    loss = np.array([[loss]])
    new_kernel1, new_bias1 = update_conv_layer(images, kernel1, bias1, loss)
    Conv1[5][1]["kernel"] = new_kernel1
    Conv1[5][1]["bias"] = new_bias1
    kernel2 = Conv2[5][2]["kernel"]
    bias2 = 0.5
    new_kernel2, new_bias2 = update_conv_layer(images, kernel2, bias2, loss)
    Conv2[5][2]["kernel"] = new_kernel2
    Conv2[5][2]["bias"] = new_bias2
    kernel3 = Conv3[5][3]["kernel"]
    bias3 = 0.5
    new_kernel3, new_bias3 = update_conv_layer(images, kernel3, bias3, loss)
    Conv3[5][3]["kernel"] = new_kernel3
    Conv3[5][3]["bias"] = new_bias3


# 定义一个全连接层的权重矩阵和偏置向量的调整函数，可以选择不同的激活函数
def update_parameters(weight_matrix, bias_vector, loss):
    # 根据激活函数的参数选择相应的函数和导数
    # 计算权重矩阵和偏置向量的梯度，使用链式法则
    learning_rate = 0.01
    gradient_weights = np.outer(loss, learning_rate)
    # 更新权重矩阵和偏置向量
    weight_matrix = weight_matrix - gradient_weights
    bias_vector = bias_vector - learning_rate * bias_vector
    # 返回更新后的权重矩阵和偏置向量
    return weight_matrix, bias_vector


def fully_one(loss):
    global Fullconn1
    weight1 = Fullconn1[5][1]["weights"]
    bias1 = Fullconn1[5][1]["bias"]
    loss = np.array([loss])
    new_weight1, new_bias1 = update_parameters(weight1, bias1, loss)
    Fullconn1[5][1]["weights"] = new_weight1
    Fullconn1[5][1]["bias"] = new_bias1


def fully_two(loss):
    global Fullconn1
    global Fullconn2
    weight1 = Fullconn1[5][1]["weights"]
    bias1 = Fullconn1[5][1]["bias"]
    loss = np.array([loss])
    new_weight1, new_bias1 = update_parameters(weight1, bias1, loss)
    Fullconn1[5][1]["weights"] = new_weight1
    Fullconn1[5][1]["bias"] = new_bias1

    weight2 = Fullconn2[5][1]["weights"]
    bias2 = Fullconn2[5][1]["bias"]
    new_weight2, new_bias2 = update_parameters(weight2, bias2, loss)
    Fullconn2[5][1]["weights"] = new_weight2
    Fullconn2[5][1]["bias"] = new_bias2


def fully_three(loss):
    global Fullconn1
    global Fullconn2
    global Fullconn3
    weight1 = Fullconn1[5][1]["weights"]
    bias1 = Fullconn1[5][1]["bias"]
    loss = np.array([loss])
    new_weight1, new_bias1 = update_parameters(weight1, bias1, loss)
    Fullconn1[5][1]["weights"] = new_weight1
    Fullconn1[5][1]["bias"] = new_bias1

    weight2 = Fullconn2[5][1]["weights"]
    bias2 = Fullconn2[5][1]["bias"]
    new_weight2, new_bias2 = update_parameters(weight2, bias2, loss)
    Fullconn2[5][1]["weights"] = new_weight2
    Fullconn2[5][1]["bias"] = new_bias2

    weight3 = Fullconn3[5][3]["weights"]
    bias3 = Fullconn3[5][1]["bias"]
    new_weight3, new_bias3 = update_parameters(weight3, bias3, loss)
    Fullconn3[5][3]["weights"] = new_weight3
    Fullconn3[5][3]["bias"] = new_bias3


# X4交互式构建卷积神经网络模型
# 定义图元拖拽开始的函数
def on_drag_start(event):
    global Dragged_item  # 记录被拖拽的图元id
    global Original_x  # 记录图元原来的x坐标
    global Original_y  # 记录图元原来的y坐标
    global Offset_x  # 记录鼠标相对于图元的x偏移量
    global Offset_y  # 记录鼠标相对于图元的y偏移量
    # 获取当前图元的id
    # 这个物体的id是由tkinter自动分配的。当在画布上创建一个物体时，它会返回一个唯一的整数id，可以用它来引用这个物体
    Dragged_item = event.widget.find_closest(event.x, event.y)

    Original_x = event.widget.coords(Dragged_item)[0]  # 获取当前图元的x坐标
    Original_y = event.widget.coords(Dragged_item)[1]  # 获取当前图元的y坐标
    Offset_x = Original_x - event.x  # 计算鼠标相对于图元的x偏移量
    Offset_y = Original_y - event.y  # 计算鼠标相对于图元的y偏移量


# 定义图元拖拽移动的函数
def on_drag_motion(event):
    x = event.x + Offset_x  # 计算图元新的x坐标
    y = event.y + Offset_y  # 计算图元新的y坐标
    event.widget.coords(Dragged_item, x, y)  # 更新图元的位置

def setload_data():
    # 设置数据集路径信息
    train_imgPath = filedialog.askdirectory(title="请选择训练集") + "/"  # 训练集文件夹的位置
    test_imgPath = filedialog.askdirectory(title="请选择测试集") + "/"  # 测试集文件夹的位置
    index_label(train_imgPath, 'train')
    image_classification(train_imgPath, 'train')
    index_label(test_imgPath, 'test')
    image_classification(test_imgPath, 'test')
    # 设置图片大小
    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
def images_statistics(folder_path):
    # 获取所有子文件夹路径
    subfolders = glob.glob(os.path.join(folder_path, '*'))
    # 遍历子文件夹并统计照片数量
    for subfolder in subfolders:
        if os.path.isdir(subfolder):
            subfolder_name = os.path.basename(subfolder)
            photos = glob.glob(os.path.join(subfolder, '*.jpg'))  # 假设照片文件是jpg格式，可根据实际情况修改
            print(f'类别 {subfolder_name} 中的照片数量：{len(photos)}')

def entity_loading_dataset(event):
    global Viewcanvas
    x = event.x + Offset_x
    y = event.y + Offset_y
    Items3.append(0)  # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    new_item1 = Viewcanvas.create_image(x, y, image=List_image[0])  # 复制一个图片，表示数据集1
    Second_Items[0].append(new_item1)
    Second_Items[1].append(Viewcanvas.create_text(x, y + 45, text='数据集'))  # 复制图元对象的标签
    event.widget.coords(Dragged_item, Original_x, Original_y)  # 恢复图元的原位置
    Items1.append((new_item1, x, y))  # 将新图元的id和坐标添加到列表中
    global Yt
    Items_y = Items1[0]  # 将新图元的y坐标取出，方便后续图元水平排列
    Yt = Items_y[2]
    XY_list.append((x,Yt))
    # 创建一个tkinter根窗口，并隐藏它
    root1 = tk.Tk()
    root1.withdraw()

    DataPara = setload_data()  # 调用X3中setload_data()函数，获取加载数据集的参数
    global Train_images
    global Test_images
    Train_images, Test_images = load_data(DataPara)  # 调用X3中load_data()函数，根据参数加载数据集
    # 打印一些信息，检查是否正确加载
    print("训练集图片的形状：", Train_images.shape)
    images_statistics('data_classification/train')
    print("测试集图片的形状：", Test_images.shape)
    images_statistics('data_classification/test')


# 创建弹出窗口
def creat_window(title):
    top = tk.Toplevel(Root)
    top.geometry("300x350")
    top.title(title)
    return top


# 输入框
def create_input_box(top, text, value):
    box_label = tk.Label(top, text=text)
    box_label.pack(padx=10, pady=10)
    box_size = tk.IntVar(top, value=value)  # 创建一个IntVar对象，并设置默认值为3
    box_size_entry = tk.Entry(top, textvariable=box_size)  # 关联IntVar对象
    box_size_entry.pack(padx=20, pady=20)
    return box_size_entry


# 下拉框
def create_dropdown_box(top, text, listvalues):
    # 创建一个下拉框，用于选择
    box_mode_label = tk.Label(top, text=text)
    box_mode_label.pack(padx=10, pady=10)
    box_mode_combobox = ttk.Combobox(top)
    box_mode_combobox["values"] = listvalues
    box_mode_combobox.pack(padx=20, pady=15)
    return box_mode_combobox


def convolution(event):
    global Items3
    global Viewcanvas
    x = event.x + Offset_x
    Items3.append(1)  # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    new_item1 = Viewcanvas.create_image(x, Yt, image=List_image[1])  # 复制一个图片，表示卷积层
    Second_Items[0].append(new_item1)
    event.widget.coords(Dragged_item, Original_x, Original_y)  # 恢复图元的原位置
    Items1.append((new_item1, x, Yt))  # 将新图元的id和坐标添加到列表中
    top = creat_window('卷积参数配置')  # 创建弹出窗口
    kernel_size_entry = create_input_box(top, "卷积核大小：", 3)  # 创建一个输入框，获取卷积核大小
    stride_entry = create_input_box(top, "卷积步长：", 1)  # 创建一个输入框，获取卷积步长
    def get_input():  # 创建
        # 获取输入框的内容
        global Listinstance
        result1 = int(kernel_size_entry.get())
        result2 = int(stride_entry.get())
        label = Viewcanvas.create_text(x, Yt + 40, text=str(result1) + "*" + str(result1) + "卷积")  # 创建图元对象的标签
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x,Yt))
        if Listinstance[1][5] == ".":
            Listinstance[1][5] = str(result1) + "," + str(result2)
        else:
            Listinstance[1][5] = Listinstance[1][5] + "  " + str(result1) + "," + str(result2)
        top.destroy()  # 关闭窗口

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def pooling(event):
    global Items3
    global Viewcanvas
    x = event.x + Offset_x
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(2)
    # 复制一个图片，表示池化层
    new_item1 = Viewcanvas.create_image(x, Yt, image=List_image[2])
    Second_Items[0].append(new_item1)
    # 恢复图元的原位置
    event.widget.coords(Dragged_item, Original_x, Original_y)
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, Yt))
    # 创建弹出窗口
    top = creat_window("池化参数配置")
    pool_type_combobox = create_dropdown_box(top, "池化池类型：", ["max", "avg", "min"])  # 创建一个下拉框，用于选择池化类型
    pool_size_entry = create_input_box(top, "池化池大小：", 2)  # 创建一个输入框，用于输入池大小

    # 创建
    def get_input():
        # 从下拉框中获取池化类型
        pool_type = pool_type_combobox.get()
        # 从输入框中获取池大小
        pool_size = int(pool_size_entry.get())
        # 复制对象的标签
        label = Viewcanvas.create_text(x, Yt + 40, text=str(pool_size) + "*" + str(pool_size) + str(pool_type) + "池化")
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x, Yt))
        if Listinstance[2][5] == ".":
            Listinstance[2][5] = str(pool_type) + "," + str(pool_size)
        else:
            Listinstance[2][5] = Listinstance[2][5] + "  " + str(pool_type) + "," + str(pool_size)
        # 关闭窗口
        top.destroy()
    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def fully_connected(event):
    global Items3
    global Viewcanvas
    x = event.x + Offset_x
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(3)
    # 复制一个图片，表示全连接层
    new_item1 = Viewcanvas.create_image(x, Yt, image=List_image[3])
    Second_Items[0].append(new_item1)
    # 恢复图元的原位置
    event.widget.coords(Dragged_item, Original_x, Original_y)
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, Yt))
    top = creat_window("全连接参数配置")
    num_outputs_entry = create_input_box(top, "全连接输出维度：", 10)

    # 创建
    def get_input():
        global Listinstance
        # 从下拉框中获取输出维度
        num = int(num_outputs_entry.get())
        # 复制对象的标签
        label = Viewcanvas.create_text(x, Yt + 40, text=str(num) + "--全连接")
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x, Yt))
        if Listinstance[3][5] == ".":
            Listinstance[3][5] = str(num)
        else:
            Listinstance[3][5] = Listinstance[3][5] + "  " + str(num)
        # print(Listinstance[3][5])
        # 关闭窗口
        top.destroy()

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack(padx=20, pady=50)


def nonlinear(event):
    global Items3
    global Viewcanvas
    x = event.x + Offset_x
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(4)
    # 复制一个图片，表示非线性函数图元
    new_item1 = Viewcanvas.create_image(x, Yt, image=List_image[4])
    Second_Items[0].append(new_item1)
    # 恢复图元的原位置
    event.widget.coords(Dragged_item, Original_x, Original_y)
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, Yt))
    top = creat_window('非线性参数配置')  # 创建弹出窗口
    # 创建一个下拉框，用于选择非线性类型
    nonlinear_mode_combobox = create_dropdown_box(top, "非线性类型", ["Sigmoid", "ReLU", "Tanh"])  # 创建一个下拉框，用于选择池化类型

    # 创建
    def get_input():
        global Listinstance
        # 从下拉框中获取池化类型
        nonlinear_mode = nonlinear_mode_combobox.get()
        # 复制对象的标签
        label = Viewcanvas.create_text(x, Yt + 40, text=str(nonlinear_mode) + "处理")
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x, Yt))
        if Listinstance[4][5] == ".":
            Listinstance[4][5] = str(nonlinear_mode)
        else:
            Listinstance[4][5] = Listinstance[4][5] + "  " + str(nonlinear_mode)
        # 关闭窗口
        top.destroy()

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def class_ification(event):
    global Listinstance
    global Viewcanvas
    global Items3
    x = event.x + Offset_x
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(5)
    # 复制一个图片，表示分类器
    new_item1 = Viewcanvas.create_image(x, Yt, image=List_image[5])
    Second_Items[0].append(new_item1)
    # 恢复图元的原位置
    event.widget.coords(Dragged_item, Original_x, Original_y)
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, Yt))
    top = creat_window('分类参数配置')  # 创建弹出窗口
    # 创建一个下拉框，用于选择非线性类型
    class_box_combobox = create_input_box(top, "阈值：", 0.001)  # 创建一个下拉框，用于选择池化类型

    # 创建
    def get_input():
        # 从输入框中获取阈值
        threshold = float(class_box_combobox.get())
        # 复制对象的标签
        label = Viewcanvas.create_text(x, Yt + 40, text="输出分类结果")
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x, Yt))
        if Listinstance[5][5] == ".":
            Listinstance[5][5] = str(threshold)
        else:
            Listinstance[5][5] = Listinstance[5][5] + "  " + str(threshold)
        # 关闭窗口
        top.destroy()

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def error_calculation(event):
    global Items3
    global Viewcanvas
    x = event.x + Offset_x
    y = event.y + Offset_y
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(6)
    # 复制一个图片，表示误差计算
    new_item1 = Viewcanvas.create_image(x, y, image=List_image[6])
    Second_Items[0].append(new_item1)
    # 恢复图元的原位置
    event.widget.coords(Dragged_item, Original_x, Original_y)
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, y))
    global x_loss
    global y_loss
    x_loss = x
    y_loss = y
    # 创建弹出窗口
    top = creat_window('误差参数配置')
    # 创建一个下拉框，用于选择误差类型
    loss_type_combobox = create_dropdown_box(top, '', ["CEE", "MSE", "MAE"])

    # 创建
    def get_input():
        # 从下拉框中获取池化类型
        loss_type = loss_type_combobox.get()
        # 复制对象的标签
        label = Viewcanvas.create_text(x, y_loss + 40,text=str(loss_type) + "误差计算")
        Items4.append(label)
        Second_Items[1].append(label)
        XY_list.append((x, y_loss))
        if Listinstance[6][5] == ".":
            Listinstance[6][5] = str(loss_type)
        else:
            Listinstance[6][5] = Listinstance[6][5] + "  " + str(loss_type)
        # 关闭窗口
        top.destroy()

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def adjust_one(event):
    global Viewcanvas
    global Items1
    global Items3
    x = event.x + Offset_x
    y = event.y + Offset_y
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(7)
    # 复制一个图片，表示调整操作
    new_item1 = Viewcanvas.create_image(x, y_loss - 20, image=List_image[7])
    Second_Items[0].append(new_item1)
    # 复制对象的标签
    Second_Items[1].append(Viewcanvas.create_text(x, y_loss + 20, text='调整1'))
    event.widget.coords(Dragged_item, Original_x, Original_y)  # 恢复图元的原位置
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, y))


def adjust_two(event):
    global Viewcanvas
    global Items1
    global Items3
    x = event.x + Offset_x
    y = event.y + Offset_y
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(8)
    # 复制一个图片，表示调整操作
    new_item1 = Viewcanvas.create_image(x, y_loss + 20, image=List_image[7])
    Second_Items[0].append(new_item1)
    # 复制对象的标签
    Second_Items[1].append(Viewcanvas.create_text(x, y_loss + 60, text='调整2'))
    event.widget.coords(Dragged_item, Original_x, Original_y)  # 恢复图元的原位置
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, y))


def adjust_three(event):
    global Viewcanvas
    global Items1
    global Items3
    x = event.x + Offset_x
    y = event.y + Offset_y
    # 记录被拖拽的原始图元的id，方便后续进行模型的正确性检查
    Items3.append(9)
    # 复制一个图片，表示调整操作
    new_item1 = Viewcanvas.create_image(x, y, image=List_image[7])
    Second_Items[0].append(new_item1)
    # 复制对象的标签
    Second_Items[1].append(Viewcanvas.create_text(x, y + 40, text='调整3'))
    event.widget.coords(Dragged_item, Original_x, Original_y)  # 恢复图元的原位置
    # 将新图元的id和坐标添加到列表中
    Items1.append((new_item1, x, y))


def connecting_line():
    global Viewcanvas
    global Items2
    last_item = Items1[-1]  # 获取最后一个图元的id和坐标
    prev_item = Items1[-2]  # 获取倒数第二个图元的id和坐标
    # 获取最后一个图元的边界框坐标，形式为(x1,y1,x2,y2)，其中(x1,y1)是左上角，(x2,y2)是右下角
    last_bbox = Viewcanvas.bbox(last_item[0])
    # 获取倒数第二个图元的边界框坐标，形式同上
    prev_bbox = Viewcanvas.bbox(prev_item[0])
    # 计算线条的起点和终点，使其与图元的边界对齐，并留出一定距离以避免重叠
    start_x = prev_bbox[2] + 5  # 起点x坐标为倒数第二个图元右边界加10像素
    start_y = prev_bbox[3] - (prev_bbox[3] - prev_bbox[1]) / 2  # 起点y坐标为倒数第二个图元垂直中心
    end_x = last_bbox[0]  # 终点x坐标为最后一个图元左边界
    end_y = last_bbox[3] - (last_bbox[3] - last_bbox[1]) / 2  # 终点y坐标为最后一个图元垂直中心
    # 创建一条线，带有箭头，并设置箭头形状、大小、颜色和平滑度等参数
    if Items3[-1] < 6:
        new_item = Viewcanvas.create_line(start_x, start_y, end_x, end_y, arrow=tk.LAST,
                                          arrowshape=(16, 20, 4), fill='lightblue', smooth=True)
        Second_Items[2].append(new_item)
        if Items3[-1] == 1:
            x = last_bbox[2] - (last_bbox[2] - last_bbox[0]) / 2
            y = last_bbox[1]
            Conv_xy_list.append((x, y))
        if Items3[-1] == 3:
            x = last_bbox[2] - (last_bbox[2] - last_bbox[0]) / 2
            y = last_bbox[1]
            Fullconn_xy_list.append((x, y))

    elif Items3[-1] == 6:
        # 计算折线的中间点，使其与图元的边界对齐，并留出一定距离以避免重叠
        mid_x = prev_bbox[2] - (prev_bbox[2] - prev_bbox[0]) / 2  # 中间点x坐标为起点和终点的平均值
        mid_y = prev_bbox[1] - 80  # 中间点y坐标为起点y坐标减20像素
        # 创建一条折线，带有箭头，并设置箭头形状、大小、颜色和平滑度等参数
        x1 = prev_bbox[2] - (prev_bbox[2] - prev_bbox[0]) / 2
        y1 = prev_bbox[1]
        x2 = last_bbox[2]
        y2 = last_bbox[3] - (last_bbox[3] - last_bbox[1]) / 2
        new_item = Viewcanvas.create_line(x1, y1, mid_x, mid_y, x2, y2, arrow=tk.LAST,
                                          arrowshape=(16, 20, 4), fill='lightblue', smooth=False)
        Second_Items[2].append(new_item)
    elif Items3[-1] == 7:
        x1 = x_loss
        y1 = y_loss
        x2, y2 = Conv_xy_list[0]
        new_item = Viewcanvas.create_line(x1, y1, last_bbox[2] - (last_bbox[2] - last_bbox[0]) / 2,
                                          last_bbox[3] - (last_bbox[3] - last_bbox[1]) / 2, x2, y2, arrow=tk.LAST,
                                          arrowshape=(16, 20, 4), fill='lightblue', smooth=False)
        Second_Items[2].append(new_item)
        Conv_xy_list.remove(Conv_xy_list[0])
    elif Items3[-1] == 8:
        x1 = x_loss
        y1 = y_loss
        x2, y2 = Fullconn_xy_list[0]
        new_item = Viewcanvas.create_line(x1, y1, last_bbox[2] - (last_bbox[2] - last_bbox[0]) / 2,
                                          last_bbox[3] - (last_bbox[3] - last_bbox[1]) / 2, x2, y2, arrow=tk.LAST,
                                          arrowshape=(16, 20, 4), fill='lightblue', smooth=False)
        Second_Items[2].append(new_item)
        Fullconn_xy_list.remove(Fullconn_xy_list[0])
    Items2.append(new_item)


def on_drag(obj_id1, event):
    while obj_id1 == 4:  # 如果拖拽的是图元加载数据集
        entity_loading_dataset(event)
        break
    while obj_id1 == 6:  # 卷积
        convolution(event)
        break
    while obj_id1 == 8:  # 池化
        pooling(event)
        break
    while obj_id1 == 10:  # 全连接
        fully_connected(event)
        break
    while obj_id1 == 12:  # 非线性
        nonlinear(event)
        break
    while obj_id1 == 14:  # 分类
        class_ification(event)
        break
    while obj_id1 == 16:  # 误差计算
        error_calculation(event)
        break
    while obj_id1 == 18:  # 调整1
        adjust_one(event)
        break
    while obj_id1 == 20:  # 调整2
        adjust_two(event)
        break
    while obj_id1 == 22:  # 调整3
        adjust_three(event)
        break


# 定义图元拖拽结束的函数
def on_drag_end(event):
    obj_id1 = Dragged_item[0]  # 获取被拖拽的图元的id
    #  使用 print(obj_id1) 从而查看各个图元的id,此步省略
    # 根据图元的类型，复制相应的图片到右侧画布
    on_drag(obj_id1, event)
    # 如果列表中有至少两个图元，就创建一条线，连接最后两个图元的中心点
    while len(Items1) >= 2:
        connecting_line()
        break


def element_binding(obj_x, obj_y, image, y, text):
    global Viewcanvas,Flage
    # 创建图元对象
    l1 = Viewcanvas.create_image(obj_x, obj_y, image=image)
    # 创建图元对象的标签
    # if Flage:
    #     Second_Items[1].append(Viewcanvas.create_text(obj_x - 70, obj_y + y, text=text, font=("黑体", 14)))
    # else:
    #     Viewcanvas.create_text(obj_x - 70, obj_y + y, text=text, font=("黑体", 14))
    Viewcanvas.create_text(obj_x - 70, obj_y + y, text=text, font=("黑体", 14))
    return l1


def create_left_element(AllModelObj, List_image):
    global Viewcanvas
    # 遍历AllModelObj列表，在窗口左侧创建图元菜单
    for obj in AllModelObj:
        # 获取图元对象的类型、标签等信息
        obj_id = obj[0]
        obj_type = obj[1]
        obj_label = obj[2]
        # 并且要根据需求调整每个对象的位置
        obj_x = obj[6]  # 根据对象的id计算x坐标
        obj_y = obj[7]  # 根据对象的id计算y坐标
        # 根据对象的类型，绘制相应的图形
        if obj_type == 1:  # 加载数据集
            l1 = element_binding(obj_x, obj_y, List_image[0], 3, " 加载" + "\n" + "数据集")
        elif obj_type == 2:  # 卷积
            l1 = element_binding(obj_x, obj_y, List_image[1], 0, "卷积")
        elif obj_type == 3:  # 池化
            l1 = element_binding(obj_x, obj_y, List_image[2], 0, "池化")
        elif obj_type == 4:  # 全连接
            l1 = element_binding(obj_x, obj_y, List_image[3], 0, "全连接" + "\n" + " 函数")
        elif obj_type == 5:  # 非线性
            l1 = element_binding(obj_x, obj_y, List_image[4], 0, "非线性" + "\n" + " 函数")
        elif obj_type == 6:  # 分类
            l1 = element_binding(obj_x, obj_y, List_image[5], 0, "类别")
        elif obj_type == 7:  # 误差计算
            l1 = element_binding(obj_x, obj_y, List_image[6], 0, "误差")
        elif obj_type == 8:  # 调整
            l1 = element_binding(obj_x, obj_y, List_image[7], 0, "调整1")
        elif obj_type == 9:  # 调整
            l1 = element_binding(obj_x, obj_y, List_image[7], 0, "调整2")
        elif obj_type == 10:  # 调整
            l1 = element_binding(obj_x, obj_y, List_image[7], 0, "调整3")
        elif obj_type == 11:  # 调整
            l1 = element_binding(obj_x, obj_y, List_image[8], 3, "主数据连接线")
        elif obj_type == 12:  # 调整
            l1 = element_binding(obj_x, obj_y, List_image[9], 3, "参数连接线")
        # 为左边菜单的每个图元绑定鼠标按下、移动和松开的事件
        Viewcanvas.tag_bind(l1, "<Button-1>", on_drag_start)
        Viewcanvas.tag_bind(l1, "<B1-Motion>", on_drag_motion)
        Viewcanvas.tag_bind(l1, "<ButtonRelease-1>", on_drag_end)


def train_setting():
    global Train_list
    Train_list = []
    top = creat_window("训练参数配置")  # 创建弹出的参数配置窗口
    epoch_entry = create_input_box(top, "训练轮数：", 4)  # 创建一个输入框，获取训练轮数
    rate_entry = create_input_box(top, '学习率：', 0.1)  # 创建一个输入框，获取学习率
    batch_size_entry = create_input_box(top, "批次：", 32)  # 创建一个输入框，获取批次

    # 创建
    def get_input():
        # 获取输入框的内容
        result1 = int(epoch_entry.get())
        result2 = float(rate_entry.get())
        result3 = int(batch_size_entry.get())
        Train_list.append(result1)
        Train_list.append(result2)
        Train_list.append(result3)
        # 关闭窗口
        top.destroy()

    button = tk.Button(top, text="获取信息", command=get_input)
    button.pack()


def conn_forward(count, layer, outputs):
    # 若为第一层卷积层且该层的图元实例Conv1还未创建
    if count[layer] == 1 and Conv1 == None:
        # 调用SetConvPara()函数，获取卷积层1参数
        ConvPara = setconv_para(1)
    # 若图元实例Conv1已经被定义，说明需要根据反向传播调整卷积核
    if count[layer] == 1 and Conv1 != None:
        ConvPara = Conv1[5][1]
    if count[layer] == 2 and Conv2 == None:
        ConvPara = setconv_para(count[layer])
    if count[layer] == 2 and Conv2 != None:
        ConvPara = Conv2[5][2]
    if count[layer] == 3 and Conv3 == None:
        ConvPara = setconv_para(count[layer])
    if count[layer] == 3 and Conv3 != None:
        ConvPara = Conv3[5][3]
    # 初始化一个空列表来存储池化后的结果
    new_outputs = []
    # 对每一张图片进行前向传播
    i = 0
    for output in outputs:
        # 获取矩阵的维度
        dim = len(output.shape)
        # 如果是二维矩阵,则转化为三维矩阵
        if dim == 2:
            image_h, image_w = output.shape
            output = np.reshape(output, (1, image_h, image_w))
            # 调用ConvProc()函数，根据ConvPara参数完成卷积计算
            output = conv_proc(output, ConvPara)
            new_outputs.append(output)
            if i == 0:
                i += 1
                Batch_First.append(output)
        # 若为三维矩阵，则保持不变直接卷积处理
        elif dim == 3:
            output = conv_proc(output, ConvPara)
            new_outputs.append(output)
            if i == 0:
                i += 1
                Batch_First.append(output)
    # 将new_outputs赋值给outputs
    outputs = new_outputs
    # 只打印第一张图片信息，检查是否正确计算
    print("卷积处理结果：")
    print(outputs[0])
    print("")
    return outputs


def pool_forward(count, layer, outputs):
    global Pool1
    global Pool2
    global Pool3
    if count[layer] == 1 and Pool1 == None:
        # 调用设置池化层参数的函数，获取池化参数
        PoolPara = setpool_para(count[layer])
    if count[layer] == 1 and Pool1 != None:
        PoolPara = Pool1[5][1]
    if count[layer] == 2 and Pool2 == None:
        PoolPara = setpool_para(count[layer])
    if count[layer] == 2 and Pool2 != None:
        PoolPara = Pool2[5][2]
    if count[layer] == 3 and Pool3 == None:
        PoolPara = setpool_para(count[layer])
    if count[layer] == 3 and Pool3 != None:
        PoolPara = Pool3[5][3]
    # 初始化一个空列表来存储池化后的结果
    new_outputs = []
    # 对outputs列表中的每个元素调用PoolProc函数，并将结果添加到new_outputs列表中
    i = 0
    for output in outputs:
        output=pool_proc(output, PoolPara)
        new_outputs.append(output)
        if i == 0:
            i += 1
            Batch_First.append(output)
    # 将new_outputs赋值给outputs
    outputs = new_outputs
    if count[layer] == 1:
        Pool1.append(outputs)
    if count[layer] == 2:
        Pool2.append(outputs)
    if count[layer] == 3:
        Pool3.append(outputs)
    print("池化处理结果：")
    print(outputs[0])
    print("")
    return outputs


def fullconn_forward(count, layer, outputs):
    output = outputs[0]
    # 若图元实例Fullconn1未被定义
    if count[layer] == 1 and Fullconn1 == None:
        # 调用设置全连接层参数的函数，获取全连接参数
        FullConnPara = setfullconn_para(output, count[layer])
    # 若图元实例Fullconn1已经被定义，说明需要根据反向传播调整权重矩阵和偏置向量
    if count[layer] == 1 and Fullconn1 != None:
        FullConnPara = Fullconn1[5][1]
    if count[layer] == 2 and Fullconn2 == None:
        FullConnPara = setfullconn_para(output, count[layer])
    if count[layer] == 2 and Fullconn2 != None:
        FullConnPara = Fullconn2[5][2]
    if count[layer] == 3 and Fullconn3 == None:
        FullConnPara = setfullconn_para(output, count[layer])
    if count[layer] == 3 and Fullconn3 != None:
        FullConnPara = Fullconn3[5][3]
    # 初始化一个空列表来存储池化后的结果
    new_outputs = []
    i=0
    # 对outputs列表中的每个元素调用FullConnProc函数，并将结果添加到new_outputs列表中
    for output in outputs:
        output = fullconn_proc(output, FullConnPara)
        new_outputs.append(output)
        if i == 0:
            i += 1
            Batch_First.append(output)
    # 将new_outputs赋值给outputs
    outputs = new_outputs
    # 只打印第一张图片信息，检查是否正确计算
    print("全连接处理结果：")
    print(outputs[0])
    print("")
    return outputs


def nonlinear_forward(outputs):
    # 调用设置非线性参数的函数，获取非线性参数
    NonLPara = setnonl_para()
    # 初始化一个空列表来存储池化后的结果
    new_outputs = []
    # 对outputs列表中的每个元素调用NonlinearProc函数，并将结果添加到new_outputs列表中
    i=0
    for output in outputs:
        output, output_derivative = nonlinear_proc(output, NonLPara)
        new_outputs.append(output)
        if i == 0:
            i += 1
            Batch_First.append(output)
    # 将new_outputs赋值给outputs
    outputs = new_outputs
    # 只打印第一张图片信息，检查是否正确计算
    print("非线性处理结果：")
    print(outputs[0])
    print("")

    return outputs


def classifier_forward(outputs):
    # 设置分类函数参数
    ClassifyPara = setclassify_para()
    # 初始化一个空列表来存储池化后的结果
    new_outputs = []
    global Prob_images
    Prob_images = []

    # 定义softmax函数
    def softmax(x):
        # 减去最大值，防止数值溢出
        x -= np.max(x)
        # 计算指数和归一化
        return np.exp(x) / np.sum(np.exp(x))

    # 对outputs列表中的每个元素调用ClassifierProc函数，并将结果添加到new_outputs列表中
    i = 0
    for output in outputs:
        outputx = classifier_proc(output, ClassifyPara)
        new_outputs.append(outputx)
        # 调用softmax函数，得到概率分布向量
        prob = softmax(output)
        # 将概率向量结果存储到列表
        Prob_images.append(prob)
        if i == 0:
            i += 1
            Batch_First.append(outputx)
    # 将new_outputs赋值给outputs
    outputs = new_outputs
    # 只打印第一张图片信息，检查是否正确计算
    print("输出示例概率向量：")
    print(Prob_images[0:3])
    print("")
    return outputs


# 前向传播
def forward_propagation(Train_images):
    images = Train_images  # 获取训练集
    outputs = images  # 存储每一层的输出结果的列表
    Counts = {}  # 根据前向传播顺序列表，依次进行卷积层、池化层、全连接层、非线性层和分类层的操作
    for layer in Items3[1:]:
        if layer not in Counts:  # 如果layer是第一次出现，就把它加入字典，并设置计数器为1
            Counts[layer] = 1
        else:  # 如果layer已经出现过，就把它的计数器加一
            Counts[layer] += 1
        if layer == 1:  # 如果是卷积层，就调用ConvProc函数，根据卷积核和步长进行卷积计算，并更新输出结果
            outputs = conn_forward(Counts, layer, outputs)
        elif layer == 2:  # 如果是池化层，就调用PoolProc函数，进行池化计算，并更新输出结果
            outputs = pool_forward(Counts, layer, outputs)
        elif layer == 3:  # 如果是全连接层，就调用FullConnProc函数，根据权重矩阵和偏置向量进行全连接计算，并更新输出结果
            outputs = fullconn_forward(Counts, layer, outputs)
        elif layer == 4:  # 如果是非线性层，就调用NonlinearProc函数，根据激活函数进行非线性变换，并更新输出结果
            outputs = nonlinear_forward(outputs)
        elif layer == 5:  # 如果是分类层，就调用ClassifierProc函数，根据权重矩阵和偏置向量进行分类计算，并更新输出结果
            outputs = classifier_forward(outputs)
    outputs = np.array(outputs)  # 将输出结果列表转换为数组形式，方便后续处理
    return outputs  # 返回输出结果数组


def determine(Train_list, Items3):
    if len(Train_list) == 0:
        messagebox.showerror("错误", "请设置训练参数！")
        return 0
    if len(Items3) == 0:
        messagebox.showerror("错误", "请添加图元！")
        return 0
    return 1


def label_array(i):
    # 读取标签数据
    df = pd.read_csv('data/emotion.csv', header=None, skiprows=range(0, i * batch), nrows=(i + 1) * batch - i * batch)
    # print(df)
    # 将标签数据转化成数组
    right_label = df.iloc[:, 0].tolist()
    right_label = list(map(int, right_label))
    right_label = [x for x in right_label]
    # print(right_label)
    return right_label


def train():  # 训练
    global Batch_First_Sheet
    Batch_First_Sheet = []
    if determine(Train_list, Items3) == 0:
        return
    epoch, rate, batch_size = Train_list
    first_epoch = 1
    while first_epoch <= epoch:
        Batch = []
        first_epoch = first_epoch + 1
        for i in range(len(Train_images) // batch_size):
            global Batch_First
            Batch_First = []
            images = Train_images[i * batch_size:(i + 1) * batch_size]
            forward_propagation(images)
            Batch.append(Batch_First)
            global Prob_images
            Prob_images = np.squeeze(Prob_images)  # 获取分类结果的概率向量数组
            global batch
            batch = len(Prob_images)
            right_label = label_array(i)  # 读取标签数据,将标签数据转化成数组
            ErrorPara = seterror_para()  # 设置误差参数
            loss = error_proc(Prob_images, right_label, ErrorPara) * 0.1  # 计算误差值
            print("当前epoch:", first_epoch, i * batch_size, ":", (i + 1) * batch_size, "误差值：", loss)
            print("")
            if (Conv2 == None) and (Conv3 == None):
                conv_one(loss, images)
            elif (Conv2 != None) and (Conv3 == None):
                conv_two(loss, images)
            elif (Conv3 != None):
                conv_three(loss, images)
            if (Fullconn2 == None) and (Fullconn3 == None):
                fully_one(loss)
            elif (Fullconn2 != None) and (Fullconn3 == None):
                fully_two(loss)
            elif (Fullconn3 != None):
                fully_three(loss)
        Batch_First_Sheet.append(Batch)
    for i in range(len(Test_images) // batch_size):
        images = Test_images[i * batch_size:(i + 1) * batch_size]
        image_results = forward_propagation(images)
        print(image_results)


# 输出模型程序
def model_show():
    pixel = ['数据集', '卷积', '池化', '全连接', '非线性', '分类', '误差', '调整1', '调整2', '调整3']
    pixel_items3 = []
    for i in Items3:
        pixel_items3.append(pixel[i])
    print(pixel_items3)


def connecting_lines(obj_x, obj_y, index, x, y, x1, x2, y1, y2, y3, image, text, smooth, width):
    # 创建图元对象
    Second_Items[0].append(Viewcanvas.create_image(obj_x + (index + x) * 110, obj_y, image=image))
    # 创建图元对象的标签
    Second_Items[1].append(Viewcanvas.create_text(obj_x + (index + x) * 110, obj_y + y1, text=text, font=("黑体", 14)))
    # 创建数据线箭头
    Second_Items[2].append(Viewcanvas.create_line(obj_x + (index + x) * 110 + x1, obj_y + y2, obj_x + (index + y) * 110 + x2, obj_y + y3,
                           arrow=tk.LAST,
                           arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth, width=width))


def number_judgments(lsts):
    global Items3
    count = []
    sequence = []
    for index, lll in enumerate(lsts):
        if type(lll) == list:
            count.append(lll[1])
            sequence.append(index)
            if lll[1] == 7:
                break
    count_2, count_3, count_4 = 0, 0, 0
    list_count = []
    for i in count:
        if i == 2:
            count_2 += 1
            list_count.append(i * 10 + count_2)
        elif i == 3:
            count_3 += 1
            list_count.append(i * 10 + count_3)
        elif i == 4:
            count_4 += 1
            list_count.append(i * 10 + count_4)
        else:
            list_count.append(i * 10)
    Items3 = [0]
    for i in count:
        Items3.append(i - 1)
    return count, list_count, sequence


def load_file():
    filename = filedialog.askopenfilename(filetypes=[("Pickle files", "*.pkl")])
    # 从本地文件中读取两个列表，用rb模式打开文件
    with open(filename, "rb") as f:
        # 创建一个空列表，用来存储读取的列表
        lsts = []
        # 用一个循环来读取所有的列表，直到文件结束
        while True:
            try:
                # 读取一个列表，并追加到大列表中
                lst = pickle.load(f)
                lsts.append(lst)
            except EOFError:
                # 如果遇到文件结束的错误，就跳出循环
                break
    return lsts


def connecting(obj_x, obj_y, index, x, y1, image, text):
    # 创建图元对象
    Second_Items[0].append(Viewcanvas.create_image(obj_x + (index + x) * 110, obj_y, image=image))
    # 创建图元对象的标签
    Second_Items[1].append(Viewcanvas.create_text(obj_x + (index + x) * 110, obj_y + y1, text=text, font=("黑体", 14)))


# 加载已有模型
def load_model():
    import copy
    lsts = load_file()
    DataPara = setload_data()
    global Train_images
    global Test_images, Flage
    global Viewcanvas
    global Listinstance
    Flage = 1
    # create_left_element(AllModelObj, List_image)
    Train_images, Test_images = load_data(DataPara)  # load_data()函数，根据参数加载数据集
    # 打印一些信息，检查是否正确加载
    print("训练集图片的形状：", Train_images.shape)
    print("测试集图片的形状：", Test_images.shape)
    # 根据pkl文件的前向传播顺序列表，依次识别卷积层、池化层、全连接层、非线性层和分类层的操作
    count, counts, sequence = number_judgments(lsts)
    x, y = 520, 300
    connecting_lines(x, y, 0, 0, 0, 25, 60, 45, 0, 0, List_image[0], '数据集', True, 2)
    for layer in range(len(count)):
        if count[layer] == 2:  # 如果是卷积层，将读取的卷积层的信息存储到Conv实例
            if counts[layer] % 10 == 1:
                global Conv1
                Listinstance[1] = copy.deepcopy(lsts[sequence[layer]])
                Conv1 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[1], '卷积层1', True, 2)
            if counts[layer] % 10 == 2:
                global Conv2
                Conv2 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[1], '卷积层2', True, 2)
            if counts[layer] % 10 == 3:
                global Conv3
                Conv3 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[1], '卷积层3', True, 2)
        if count[layer] == 3:  # 如果是池化层，将读取的池化层的信息存储到Pool实例
            if counts[layer] % 10 == 1:
                global Pool1
                Listinstance[2] = copy.deepcopy(lsts[sequence[layer]])
                Pool1 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[2], '池化1', True, 2)
            if counts[layer] % 10 == 2:
                global Pool2
                Pool2 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[2], '池化2', True, 2)
            if counts[layer] % 10 == 3:
                global Pool3
                Pool3 = lsts[sequence[layer]]
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[2], '池化3', True, 2)
        if count[layer] == 4:  # 如果是全连接层，将读取的全连接层的信息存储到Pool实例
            if counts[layer] % 10 == 1:
                global Fullconn1
                Listinstance[3] = lsts[sequence[layer]]
                Fullconn1 = copy.deepcopy(lsts[sequence[layer]])  # 深拷贝创建了一个全新的对象,要使用深拷贝，不然报错
                Listinstance[3][5] = str(Fullconn1[5][1]["num_outputs"])
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[3], '全连接1', True, 2)
            if counts[layer] % 10 == 2:
                global Fullconn2
                Fullconn2 = lsts[sequence[layer]]
                # FullConn[5] = FullConn[5] + "  " + str(Fullconn2[5][2]["num_outputs"])
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[3], '全连接3', True, 2)
            if counts[layer] % 10 == 3:
                global Fullconn3
                Fullconn3 = lsts[sequence[layer]]
                # FullConn[5] = FullConn[5] + "  " + str(Fullconn3[5][3]["num_outputs"])
                connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[3], '全连接3', True, 2)
        if count[layer] == 5:  # 如果是非线性层，将读取的非线性层的信息存储到Nonline实例
            Listinstance[4] = lsts[sequence[layer]]
            connecting_lines(x, y, layer, 1, 2, 25, -25, 45, 0, 0, List_image[4], '非线性', True, 2)
        if count[layer] == 6:  # 如果是分类输出层，将读取的信息存储到Nonline实例
            Listinstance[5] = lsts[sequence[layer]]
            connecting(x, y, layer, 1, 45, List_image[5], '分类')
        if count[layer] == 7:  # 如果是计算误差层，将读取的信息存储到Error实例
            Listinstance[6] = lsts[sequence[layer]]
            connecting(x - 30, y - 120, layer, 0, 45, List_image[6], '计算误差')
            Second_Items[2].append(Viewcanvas.create_line(x + layer * 110 + 25, y, x + layer * 110 + 10, y - 60,
                                   x + 5 * 110 - 20, y - 120, arrow=tk.LAST, arrowshape=(16, 20, 4),
                                   fill='lightblue', smooth=True, width=2))


def create_window(index, number, count, list1, list2, list3, list4, list5, list6):
    global Listinstance
    if number == 1:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取第一层卷积层的参数
            para_list = list1[count[number] - 1].split(",")
            para_list = [int(x) for x in para_list]
            window = creat_window('卷积1参数调整')  # 创建一个新的窗口
            kernel_size_entry = create_input_box(window, "卷积核大小：", para_list[0])  # 创建一个输入框，获取卷积核大小
            stride_entry = create_input_box(window, "卷积步长：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 获取输入框的内容
                result1 = int(kernel_size_entry.get())
                result2 = int(stride_entry.get())
                new_para = str(result1) + ',' + str(result2)
                Listinstance[1][5] = Listinstance[1][5].replace(list1[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(result1) + "*" + str(result1) + "卷积"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()
            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="卷积参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 2:
            # 获取第一层卷积层的参数
            para_list = list1[count[number] - 1].split(",")
            para_list = [int(x) for x in para_list]
            window = creat_window('卷积2参数调整')  # 创建一个新的窗口
            kernel_size_entry = create_input_box(window, "卷积核大小：", para_list[0])  # 创建一个输入框，获取卷积核大小
            stride_entry = create_input_box(window, "卷积步长：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 获取输入框的内容
                result1 = int(kernel_size_entry.get())
                result2 = int(stride_entry.get())
                new_para = str(result1) + ',' + str(result2)
                Listinstance[1][5] = Listinstance[1][5].replace(list1[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(result1) + "*" + str(result1) + "卷积2"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="卷积参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 3:
            # 获取第一层卷积层的参数
            para_list = list1[count[number] - 1].split(",")
            para_list = [int(x) for x in para_list]
            # 创建一个输入框，获取卷积核大小
            window = creat_window('卷积3参数调整')  # 创建一个新的窗口
            kernel_size_entry = create_input_box(window, "卷积核大小：", para_list[0])  # 创建一个输入框，获取卷积核大小
            stride_entry = create_input_box(window, "卷积步长：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 获取输入框的内容
                result1 = int(kernel_size_entry.get())
                result2 = int(stride_entry.get())
                new_para = str(result1) + ',' + str(result2)
                Listinstance[1][5] = Listinstance[1][5].replace(list1[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(result1) + "*" + str(result1) + "卷积3"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="卷积参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        # 返回窗口对象
        return window
    # 如果是池化层
    if number == 2:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取第一层卷积层的参数
            para_list = list2[count[number] - 1].split(",")
            para_list[1] = int(para_list[1])
            window = creat_window('池化1参数调整')  # 创建一个新的窗口
            pool_type_combobox = create_dropdown_box(window, "池化池类型：", ["max", "avg", "min"])  # 创建一个下拉框，用于选择池化类型
            pool_size_entry = create_input_box(window, "池化池大小：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 从下拉框中获取池化类型
                pool_type = pool_type_combobox.get()
                # 从输入框中获取池大小
                pool_size = int(pool_size_entry.get())
                # 关闭窗口
                new_para = str(pool_type) + ',' + str(pool_size)
                Listinstance[2][5] = Listinstance[2][5].replace(list2[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(pool_size) + "*" + str(pool_size) + str(pool_type) + "池化"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="池化参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 2:
            # 获取第一层卷积层的参数
            para_list = list2[count[number] - 1].split(",")
            para_list[1] = int(para_list[1])
            window = creat_window('池化2参数调整')  # 创建一个新的窗口
            pool_type_combobox = create_dropdown_box(window, "池化池类型：", ["max", "avg", "min"])  # 创建一个下拉框，用于选择池化类型
            pool_size_entry = create_input_box(window, "池化池大小：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 从下拉框中获取池化类型
                pool_type = pool_type_combobox.get()
                # 从输入框中获取池大小
                pool_size = int(pool_size_entry.get())
                # 关闭窗口
                new_para = str(pool_type) + ',' + str(pool_size)
                Listinstance[2][5] = Listinstance[2][5].replace(list2[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(pool_size) + "*" + str(pool_size) + str(pool_type) + "池化2"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()
            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="池化参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 3:
            # 获取第一层卷积层的参数
            para_list = list2[count[number] - 1].split(",")
            para_list[1] = int(para_list[1])
            window = creat_window('池化3参数调整')  # 创建一个新的窗口
            pool_type_combobox = create_dropdown_box(window, "池化池类型：", ["max", "avg", "min"])  # 创建一个下拉框，用于选择池化类型
            pool_size_entry = create_input_box(window, "池化池大小：", para_list[1])  # 创建一个输入框，获取卷积步

            def modify_para():
                # 从下拉框中获取池化类型
                pool_type = pool_type_combobox.get()
                # 从输入框中获取池大小
                pool_size = int(pool_size_entry.get())
                # 关闭窗口
                new_para = str(pool_type) + ',' + str(pool_size)
                Listinstance[2][5] = Listinstance[2][5].replace(list2[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(pool_size) + "*" + str(pool_size) + str(pool_type) + "池化3"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="池化参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        # 返回窗口对象
        return window
    if number == 3:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取第一层全连接层的参数
            para = list3[count[number] - 1]
            para = int(para)
            window = creat_window('全连接1参数调整')  # 创建一个新的窗口
            num_outputs_entry = create_input_box(window, "输出维度：", para)  # 创建一个输入框，获取卷积步

            def modify_para():
                # 获取输入框的内容
                num = int(num_outputs_entry.get())
                new_para = str(num)
                Listinstance[3][5] = Listinstance[3][5].replace(list3[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(num) + "--全连接"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="全连接参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 2:
            # 获取第一层全连接层的参数
            para = list3[count[number] - 1]
            para = int(para)
            window = creat_window('全连接2参数调整')  # 创建一个新的窗口
            num_outputs_entry = create_input_box(window, "输出维度：", para)  # 创建一个输入框，获取全连接

            def modify_para():
                # 获取输入框的内容
                num = int(num_outputs_entry.get())
                new_para = str(num)
                Listinstance[3][5] = Listinstance[3][5].replace(list3[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(num) + "--全连接"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="全连接参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        if count[number] == 3:
            # 获取第一层全连接层的参数
            para = list3[count[number] - 1]
            para = int(para)
            window = creat_window('全连接3参数调整')  # 创建一个新的窗口
            num_outputs_entry = create_input_box(window, "输出维度：", para)  # 创建一个输入框，获取全连接

            def modify_para():
                # 获取输入框的内容
                num = int(num_outputs_entry.get())
                new_para = str(num)
                Listinstance[3][5] = Listinstance[3][5].replace(list3[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(num) + "--全连接"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="全连接参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        # 返回窗口对象
        return window
    if number == 4:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取第一层非线性层的参数
            para = list3[count[number] - 1]
            window = creat_window('非线性1参数调整')  # 创建一个新的窗口
            nonlinear_mode_combobox = create_dropdown_box(window, "非线性类型：",
                                                          ["Sigmoid", "ReLU", "Tanh"])  # 创建一个下拉框，用于选择非线性类型

            def modify_para():
                # 获取输入框的内容
                nonlinear_mode = nonlinear_mode_combobox.get()
                new_para = str(nonlinear_mode)
                Listinstance[4][5] = Listinstance[4][5].replace(list4[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(nonlinear_mode) + "处理"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="非线性参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        # 返回窗口对象
        return window
    if number == 5:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取分类的参数
            para = list5
            para = float(para)
            window = creat_window('分类参数调整')  # 创建一个新的窗口
            threshold_entry = create_input_box(window, "阈值：", para)  # 创建一个输入框，用于输入阈值

            def modify_para():
                # 获取输入框的内容
                threshold = threshold_entry.get()
                new_para = str(threshold)
                Listinstance[5][5] = Listinstance[5][5].replace(list5, new_para)
                # 更新标签的内容
                label_text = "输出分类结果"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="分类参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        # 返回窗口对象
        return window
    if number == 6:
        # 把数字的出现次数加一
        count[number] = count.get(number, 0) + 1
        if count[number] == 1:
            # 获取分类的参数
            para = list6
            window = creat_window('误差参数调整')  # 创建一个新的窗口
            loss_type_combobox = create_dropdown_box(window, "误差类型：",
                                                     ["CEE", "MSE", "MAE"])  # 创建一个下拉框，用于选择误差类型
            def modify_para():
                # 获取输入框的内容
                loss_type = loss_type_combobox.get()
                new_para = str(loss_type)
                Listinstance[6][5] = Listinstance[6][5].replace(list6[count[number] - 1], new_para)
                # 更新标签的内容
                label_text = str(loss_type) + "误差计算"
                label = Viewcanvas.create_text(XY_list[index][0], XY_list[index][1] + 40, text=label_text)
                Items4.append(label)
                Second_Items[1].append(label)
                Viewcanvas.itemconfig(label, text=label_text)
                # print(Listinstance[6])
                # 关闭窗口
                window.destroy()

            # 创建一个按钮，文本为数字
            button = tk.Button(window, text="误差参数修改")
            # 绑定按钮的点击事件，使得点击后销毁窗口
            button.config(command=modify_para)
            # 把按钮放在窗口中
            button.pack()
        return window  # 返回窗口对象

# 清空当前模型展示区
def restart_new_model():
    # 这将在子进程中调用当前Python解释器，并传递当前文件的路径作为参数
    subprocess.call([sys.executable, __file__])

def setpara_func():  # 图元参数配置与修改
    global Viewcanvas
    # 一个字典，用来记录每个数字出现的次数
    count = {}
    list1 = Listinstance[1][5].split()
    list2 = Listinstance[2][5].split()
    list3 = Listinstance[3][5].split()
    list4 = Listinstance[4][5].split()
    list5 = Listinstance[5][5]
    list6 = Listinstance[6][5].split()
    for i in range(len(Items4)):  # 清除所有文字标签
        item = Items4.pop(0)
        Viewcanvas.delete(item)
    numbers = [x for x in Items3 if x <= 6]  # 一个函数，用来创建一个带有一个数字标签的按钮的窗口
    numbers = numbers[1:]
    global index
    index = 1
    def iterate_numbers():  # 一个函数，用来遍历数字列表，依次创建窗口
        global index
        if numbers:  # 如果数字列表不为空
            number = numbers.pop(0)  # 从列表中弹出第一个数字
            window = create_window(index,number, count, list1, list2, list3, list4, list5, list6)
            window.wait_window()  # 等待窗口被关闭
            index += 1
            iterate_numbers()  # 再次调用这个函数，创建下一个窗口

    iterate_numbers()  # 调用遍历数字的函数


'''
【编程16.20】编制程序：输出函数。 
【目的及编程说明】读者通过编程16.20，可掌握图片分类过程。该函数的输入为1000幅图像的集合，输出为模型结构（模型的参数）。
基本实践内容： 
（1）定义输入数据格式，即图像数据的维度和格式，然后加载数据集对象，使用加载数据集对象的ProcFunc将图像数据加载到网络模型中。
（2）使用SetParaFunc函数对每个节点进行参数赋值，通过AllModelConn表中的连接信息，使用训练好的神经网络进行预测。
（3）计算预测误差：使用AllModelObj表中的ProcFunc函数对预测结果与真实标签进行比较，计算误差。
（4）进行网络优化：使用AllModelObj表中的ProcFunc函数对误差进行反向传播，并调整神经网络中的参数和连接权重，以最小化误差。
（5）使用AllModelObj表中的ProcFunc函数对1000幅图像数据进行分类，得到最终的分类结果。
'''


def count_model(layer):
    # 如果layer是第一次出现，就把它加入字典，并设置计数器为1
    if layer not in Counts:
        Counts[layer] = 1
    # 如果layer已经出现过，就把它的计数器加一
    else:
        Counts[layer] += 1
    # 如果是卷积层
    if layer == 1:
        if Counts[layer] == 1:
            with open("list.pkl", "ab") as f:
                pickle.dump(Conv1, f)
        elif Counts[layer] == 2:
            with open("list.pkl", "ab") as f:
                pickle.dump(Conv2, f)
        elif Counts[layer] == 3:
            with open("list.pkl", "ab") as f:
                pickle.dump(Conv3, f)
    # 如果是池化层
    if layer == 2:
        if Counts[layer] == 1:
            with open("list.pkl", "ab") as f:
                pickle.dump(Pool1, f)
        elif Counts[layer] == 2:
            with open("list.pkl", "ab") as f:
                pickle.dump(Pool2, f)
        elif Counts[layer] == 3:
            with open("list.pkl", "ab") as f:
                pickle.dump(Pool3, f)
    # 如果是全连接层
    if layer == 3:
        if Counts[layer] == 1:
            with open("list.pkl", "ab") as f:
                pickle.dump(Fullconn1, f)
        elif Counts[layer] == 2:
            with open("list.pkl", "ab") as f:
                pickle.dump(Fullconn2, f)
        elif Counts[layer] == 3:
            with open("list.pkl", "ab") as f:
                pickle.dump(Fullconn3, f)
    # 如果是非线性处理层
    if layer == 4:
        with open("list.pkl", "ab") as f:
            pickle.dump(Listinstance[4], f)
    # 如果是输出分类层
    if layer == 5:
        with open("list.pkl", "ab") as f:
            pickle.dump(Listinstance[5], f)
    # 如果是计算误差层
    if layer == 6:
        with open("list.pkl", "ab") as f:
            pickle.dump(Listinstance[6], f)


# 保存网络模型
def save_model():
    if len(Items3) == 0:
        messagebox.showerror("错误", "请添加图元！")
        return
    file_path = 'list.pkl'
    # 检查文件是否存在
    if os.path.exists(file_path):
        # 删除文件
        os.remove(file_path)
    # 生成试验数据集，用于保存模型
    # Test_images = np.random.rand(100, 48, 48) * 0.01
    # 对试验数据集进行前向传播的计算
    print(forward_propagation(Test_images))
    # 打开一个本地文件，用w模式或者wb模式
    with open("model.pkl", "wb") as f:
        pickle.dump(Items3, f)  # 保存模型
    # 遍历网络结构
    for layer in Items3[1:]:
        count_model(layer)
    messagebox.showinfo("成功", "保存正确，恭喜！")


def check_connection():
    connection = [x for x in Items3 if x < 6]
    length = len(connection)
    if length == 0:
        messagebox.showerror("错误", "请添加图元！")
        return
    for i in range(length - 1):
        right_model_x = connection[i]
        right_model_y = connection[i + 1]
        if RIGHT_MODEL[right_model_x][right_model_y] == 0:
            messagebox.showerror("错误", "连接不正确，请检查！")
            return
    messagebox.showinfo("成功", "连接正确，恭喜！")


def redirector(inputStr):
    global Text
    Text.configure(state="normal")  # 设置文本框为可编辑状态
    Text.insert("end", inputStr)  # 在文本框末尾插入内容
    Text.see("end")  # 滚动到末尾
    Text.configure(state="disabled")  # 设置文本框为只读状态

def close_program():
    sys.exit()
def refresh():
    global Viewcanvas, Flage, Batch_First_Sheet
    global Listinstance
    global Conv1, Conv2, Conv3, Pool1, Pool2, Pool3, Fullconn1, Fullconn2, Fullconn3
    for i in range(3):
        for j in range(len(Second_Items[i])):  # 清除所有文字标签
            item = Second_Items[i].pop(0)
            Viewcanvas.delete(item)
    flage = Flage
    Batch_First_Sheet = []
    # print(flage)
    if flage:
        Listinstance = create_instance()
        Conv1 = None
        Conv2 = None
        Conv3 = None
        Pool1 = None
        Pool2 = None
        Pool3 = None
        Fullconn1 = None
        Fullconn2 = None
        Fullconn3 = None
        Flage = 0
    else:
        global Prob_images, Train_images, Test_images, Items1, Items2, Items3, Items4
        global Counts, Conv_xy_list, XY_list, Fullconn_xy_list, Train_list, Dragged_item
        Prob_images = []  # 存储图元的列表
        Train_images = []  # 存储训练集的列表
        Test_images = []  # 存储测试集的列表
        Items1 = []  # 存储新图元的列表
        Items2 = []  # 存储新数据线的列表
        Items3 = []  # 存储被拖拽的图元的列表
        Items4 = []  # 存储新图元文字标签的列表
        Counts = {}  # 用来存储Items3每个数字出现过的次数
        Conv_xy_list = []  # 存储卷积图元的坐标
        XY_list = []  # 存储图元的坐标
        Fullconn_xy_list = []  # 存储全连接图元的坐标
        Train_list = []  # 存储训练设置参数
        Dragged_item = ()  # 记录被拖拽的图元id
        Listinstance = create_instance()
        Conv1 = None
        Conv2 = None
        Conv3 = None
        Pool1 = None
        Pool2 = None
        Pool3 = None
        Fullconn1 = None
        Fullconn2 = None
        Fullconn3 = None

def button_link():
    global button
    # 替每个主界面的按钮绑定各自的函数
    button['button1'].config(command=train_setting)
    button['button2'].config(command=train)   # 训练
    button['button3'].config(command=model_show)  # 模型显示
    button['button4'].config(command=load_model)
    button['button5'].config(command=restart_new_model)
    button['button6'].config(command=setpara_func)
    button['button7'].config(command=save_model)
    button['button8'].config(command=check_connection)
    button['button9'].config(command=refresh)
    button['button10'].config(command=model_application)
    button['button11'].config(command=show_subwindow)

def model_application():
    from X1 import image_to_array
    # 打开文件选择对话框
    file_path = filedialog.askopenfilename(filetypes=[("Image Files", "")])
    # file_path = 'data/verify/908.jpg'
    if file_path:
        img = image_to_array(file_path, 48, 48)
        img = img.reshape((1, 48, 48))
        image_results = forward_propagation(img)
        print(image_results)

def model_applications():
    from X1 import image_to_array
    # 打开文件选择对话框
    file_path = filedialog.askopenfilename(filetypes=[("Image Files", "")])
    # file_path = 'data/verify/908.jpg'
    if file_path:
        img = image_to_array(file_path, 48, 48)
        img = img.reshape((1, 48, 48))
        image_results = forward_propagation(img)
        print(image_results)

def main():
    # X1-X2编制赋值程序及输出界面
    global Listinstance
    global List_image
    global AllModelObj
    global button
    picture_frame()  # 创建一个窗口
    Listinstance = create_instance()
    # 创建网络对象总表和网络连接对象总表
    AllModelObj = [Listinstance[0], Listinstance[1], Listinstance[2], Listinstance[3], Listinstance[4], Listinstance[5],
                   Listinstance[6], Listinstance[7], Listinstance[8], Listinstance[9]]

    img_path = ["img/data.png", "img/conv.png", "img/pool.png", "img/full_connect.png", "img/nonlinear.png",
                "img/classifier.png", "img/error.png", "img/adjust.png", "img/arrow1.png", "img/arrow2.png"]

    element_path = ["background/左边--加载数据集.png", "background/左边卷积.png", "background/左边池化.png",
                    "background/左边--全数据函数.png", "background/左边-非线性函数.png", "background/左边--类别.png",
                    "background/左边--误差.png", "background/左边--调整1.png", "background/左边--调整1.png",
                    "background/左边--调整1.png"]
    button_path = ["background/加载已有模型.png", "background/新建模型.png", "background/图元参数设置.png",
                    "background/左边--全数据函数.png", "background/左边-非线性函数.png", "background/左边--类别.png",
                    "background/左边--误差.png", "background/左边--调整1.png", "background/左边--调整1.png",
                    "background/左边--调整1.png"]
    List_image = []
    for path in element_path:
        List_image.append(element(path, 70, 60))
    button = push_button()
    create_left_element(AllModelObj, List_image)
    button_link()
    sys.stdout.write = redirector
    Root.protocol("WM_DELETE_WINDOW", close_program)
    Root.mainloop()

if __name__ == '__main__':
    main()

