|
|
|
|
import math
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
import cv2
|
|
|
|
|
import numpy as np
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
from tkinter import filedialog
|
|
|
|
|
from read_data import *
|
|
|
|
|
from PIL import Image
|
|
|
|
|
# from X1 import *
|
|
|
|
|
import shutil
|
|
|
|
|
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 ttkbootstrap import Style
|
|
|
|
|
from tkinter import messagebox
|
|
|
|
|
import tkinter.scrolledtext as st
|
|
|
|
|
from PIL import Image, ImageTk
|
|
|
|
|
from matplotlib import pyplot as plt
|
|
|
|
|
from tkinter import Canvas, Frame, BOTH, PhotoImage, filedialog
|
|
|
|
|
|
|
|
|
|
class Interactive_CNN:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.Dragged_item = () # 记录被拖拽的图元id
|
|
|
|
|
self.Yt = 0 # 新图元的y坐标
|
|
|
|
|
self.Original_x = 0 # 记录图元原来的x坐标
|
|
|
|
|
self.Original_y = 0 # 记录图元原来的y坐标
|
|
|
|
|
self.Offset_x = 0 # 记录鼠标相对于图元的x偏移量
|
|
|
|
|
self.Offset_y = 0 # 记录鼠标相对于图元的y偏移量
|
|
|
|
|
self.Root = tk.Tk()
|
|
|
|
|
# 设置窗口的大小为1200*750
|
|
|
|
|
window_width = 1400 # 窗口的宽度
|
|
|
|
|
window_height = 900 # 窗口的高度
|
|
|
|
|
self.Root.title("神经网络可视化")
|
|
|
|
|
self.Root.geometry("1400x900") # 设置窗口的大小和位置
|
|
|
|
|
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/adjust.png", "img/adjust.png", "img/arrow1.png", "img/arrow2.png"]
|
|
|
|
|
self.List_image = []
|
|
|
|
|
for path in img_path[:-2]:
|
|
|
|
|
self.List_image.append(self.element(path, 70, 60))
|
|
|
|
|
self.List_image.append(self.element(img_path[-2], 70, 30))
|
|
|
|
|
self.List_image.append(self.element(img_path[-1], 70, 30))
|
|
|
|
|
# 创建一个画布,用于绘制矩形框,设置画布的大小和背景色
|
|
|
|
|
self.Viewcanvas = tk.Canvas(self.Root, width=window_width, height=window_height, bg="white")
|
|
|
|
|
self.Viewcanvas.pack() # 将画布添加到主窗口中
|
|
|
|
|
# 绘制矩形框,使用不同的颜色和线宽,指定矩形框的左上角和右下角坐标,填充色,边框色和边框宽度
|
|
|
|
|
self.Viewcanvas.create_rectangle(20, 20, 370, 580, fill=None, outline="lightblue", width=2)
|
|
|
|
|
self.Viewcanvas.create_rectangle(380, 40, 1390, 580, fill=None, outline="lightblue", width=2)
|
|
|
|
|
self.Viewcanvas.create_rectangle(20, 620, 1390, 880, fill=None, outline="lightblue", width=2)
|
|
|
|
|
self.Text = st.ScrolledText(self.Root) # 创建一个带滚动条的文本框,用于展示计算过程
|
|
|
|
|
self.Text.place(x=20, y=620, width=1365, height=260)
|
|
|
|
|
self.button = {}
|
|
|
|
|
# 按钮顺序:加载已有模型、新建模型、图元参数设置、保存模型、模型正确性检查、退出、训练参数设置、训练、输出模型程序
|
|
|
|
|
names = ['加载已有模型', '新建模型', '图元参数设置', '保存模型', '模型正确性检查', '退出', '训练参数设置', '训练', '输出模型程序']
|
|
|
|
|
positions = [(385, 10), (590, 10), (760, 10), (950, 10), (1100, 10), (1310, 10), (20, 590), (200, 590),
|
|
|
|
|
(300, 590)]
|
|
|
|
|
bg = ['#32CD32', '#32CD32', '#32CD32', '#32CD32', '#32CD32', '#FF0000', '#436EEE', '#436EEE', '#436EEE']
|
|
|
|
|
for i in range(9):
|
|
|
|
|
button = tk.Button(self.Root, text=' '+names[i]+' ', bg=bg[i], bd=0, relief=tk.FLAT,
|
|
|
|
|
highlightthickness=0, font=("黑体", 14))
|
|
|
|
|
button.place(x=positions[i][0], y=positions[i][1])
|
|
|
|
|
self.button['button' + str(i+1)] = button
|
|
|
|
|
def button_link(self):
|
|
|
|
|
# 替每个主界面的按钮绑定各自的函数
|
|
|
|
|
self.button['button1'].config(command=Mod.load_model) # 加载已有模型
|
|
|
|
|
self.button['button2'].config(command=self.restart_new_model) # 新建模型
|
|
|
|
|
self.button['button3'].config(command=Net.setpara_func) # 图元参数设置
|
|
|
|
|
self.button['button4'].config(command=Mod.save_model) # 保存模型
|
|
|
|
|
self.button['button5'].config(command=Net.check_connection) # 模型正确性检查
|
|
|
|
|
self.button['button6'].config(command=self.close_program) # 退出
|
|
|
|
|
self.button['button7'].config(command=Mod.train_setting) # 训练参数
|
|
|
|
|
self.button['button8'].config(command=Mod.train) # 训练
|
|
|
|
|
def close_program(self):
|
|
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def element(self, path, width, height):
|
|
|
|
|
# 加载图元对应的图片文件
|
|
|
|
|
img = Image.open(path)
|
|
|
|
|
# 使用resize方法调整图片
|
|
|
|
|
img = img.resize((width, height))
|
|
|
|
|
# 把Image对象转换成PhotoImage对象
|
|
|
|
|
img = ImageTk.PhotoImage(img)
|
|
|
|
|
# 保存图片的引用,防止被垃圾回收
|
|
|
|
|
self.Root.img = img
|
|
|
|
|
return img
|
|
|
|
|
|
|
|
|
|
def redirector(self, inputStr):
|
|
|
|
|
self.Text.configure(state="normal") # 设置文本框为可编辑状态
|
|
|
|
|
self.Text.insert("end", inputStr) # 在文本框末尾插入内容
|
|
|
|
|
self.Text.see("end") # 滚动到末尾
|
|
|
|
|
self.Text.configure(state="disabled") # 设置文本框为只读状态
|
|
|
|
|
|
|
|
|
|
def create_left_element(self, List_image):
|
|
|
|
|
# 图元列表,在窗口左侧创建图元菜单、
|
|
|
|
|
element_list = [(135, 60), (320, 60), (135, 156), (320, 156), (135, 252), (320, 252),
|
|
|
|
|
(135, 348), (320, 348), (135, 444), (320, 444), (135, 540), (320, 540)]
|
|
|
|
|
for i in range(12):
|
|
|
|
|
# 获取图元对象的类型、标签等信息
|
|
|
|
|
obj_type = i+1
|
|
|
|
|
# 并且要根据需求调整每个对象的位置
|
|
|
|
|
obj_x = element_list[i][0] # 根据对象的id计算x坐标
|
|
|
|
|
obj_y = element_list[i][1] # 根据对象的id计算y坐标
|
|
|
|
|
# 根据对象的类型,绘制相应的图形
|
|
|
|
|
if obj_type == 1: # 加载数据集
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[0], 3, " 加载" + "\n" + "数据集")
|
|
|
|
|
elif obj_type == 2: # 卷积
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[1], 0, "卷积")
|
|
|
|
|
elif obj_type == 3: # 池化
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[2], 0, "池化")
|
|
|
|
|
elif obj_type == 4: # 全连接
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[3], 0, "全连接" + "\n" + " 函数")
|
|
|
|
|
elif obj_type == 5: # 非线性
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[4], 0, "非线性" + "\n" + " 函数")
|
|
|
|
|
elif obj_type == 6: # 分类
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[5], 0, "类别")
|
|
|
|
|
elif obj_type == 7: # 误差计算
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[6], 0, "误差")
|
|
|
|
|
elif obj_type == 8: # 调整
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[7], 0, "调整1")
|
|
|
|
|
elif obj_type == 9: # 调整
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[8], 0, "调整2")
|
|
|
|
|
elif obj_type == 10: # 调整
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[9], 0, "调整3")
|
|
|
|
|
elif obj_type == 11: # 调整
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[10], 3, "主数据" + "\n" + "连接线")
|
|
|
|
|
elif obj_type == 12: # 调整
|
|
|
|
|
l1 = self.element_binding(obj_x, obj_y, List_image[11], 3, " 参数" + "\n" + "连接线")
|
|
|
|
|
# 为左边菜单的每个图元绑定鼠标按下、移动和松开的事件
|
|
|
|
|
self.Viewcanvas.tag_bind(l1, "<Button-1>", self.on_drag_start)
|
|
|
|
|
self.Viewcanvas.tag_bind(l1, "<B1-Motion>", self.on_drag_motion)
|
|
|
|
|
self.Viewcanvas.tag_bind(l1, "<ButtonRelease-1>", self.on_drag_end)
|
|
|
|
|
|
|
|
|
|
def element_binding(self, obj_x, obj_y, image, y, text):
|
|
|
|
|
l1 = self.Viewcanvas.create_image(obj_x, obj_y, image=image) # 创建图元对象
|
|
|
|
|
# 创建图元对象的标签
|
|
|
|
|
self.Viewcanvas.create_text(obj_x - 80, obj_y + y, text=text, font=("黑体", 14))
|
|
|
|
|
return l1
|
|
|
|
|
|
|
|
|
|
# 定义图元拖拽开始的函数
|
|
|
|
|
def on_drag_start(self, event):
|
|
|
|
|
# 获取当前图元的id
|
|
|
|
|
# 这个物体的id是由tkinter自动分配的。当在画布上创建一个物体时,它会返回一个唯一的整数id,可以用它来引用这个物体
|
|
|
|
|
self.Dragged_item = event.widget.find_closest(event.x, event.y)
|
|
|
|
|
|
|
|
|
|
self.Original_x = event.widget.coords(self.Dragged_item)[0] # 获取当前图元的x坐标
|
|
|
|
|
self.Original_y = event.widget.coords(self.Dragged_item)[1] # 获取当前图元的y坐标
|
|
|
|
|
self.Offset_x = self.Original_x - event.x # 计算鼠标相对于图元的x偏移量
|
|
|
|
|
self.Offset_y = self.Original_y - event.y # 计算鼠标相对于图元的y偏移量
|
|
|
|
|
|
|
|
|
|
# 定义图元拖拽移动的函数
|
|
|
|
|
def on_drag_motion(self, event):
|
|
|
|
|
x = event.x + self.Offset_x # 计算图元新的x坐标
|
|
|
|
|
y = event.y + self.Offset_y # 计算图元新的y坐标
|
|
|
|
|
event.widget.coords(self.Dragged_item, x, y) # 更新图元的位置
|
|
|
|
|
# 定义图元拖拽结束的函数
|
|
|
|
|
def on_drag_end(self, event):
|
|
|
|
|
obj_id1 = self.Dragged_item[0] # 获取被拖拽的图元的id
|
|
|
|
|
# 使用 print(obj_id1) 从而查看各个图元的id,此步省略
|
|
|
|
|
# 根据图元的类型,复制相应的图片到右侧画布
|
|
|
|
|
self.on_drag(obj_id1, event)
|
|
|
|
|
|
|
|
|
|
def on_drag(self, obj_id1, event): # 左侧函数绑定
|
|
|
|
|
if obj_id1 == 4: # 数据集
|
|
|
|
|
# 调用 entity_loading_dataset 方法
|
|
|
|
|
Net.entity_loading_dataset(event)
|
|
|
|
|
elif obj_id1 == 6: # 卷积
|
|
|
|
|
# 调用 convolution 方法
|
|
|
|
|
Net.convolution(event)
|
|
|
|
|
elif obj_id1 == 8: # 池化
|
|
|
|
|
# 调用 pooling 方法
|
|
|
|
|
Net.pooling(event)
|
|
|
|
|
elif obj_id1 == 10: # 全连接
|
|
|
|
|
# 调用 fully_connected 方法
|
|
|
|
|
Net.fully_connected(event)
|
|
|
|
|
elif obj_id1 == 12: # 非线性
|
|
|
|
|
# 调用 nonlinear 方法
|
|
|
|
|
Net.nonlinear(event)
|
|
|
|
|
elif obj_id1 == 14: # 分类
|
|
|
|
|
# 调用 class_ification 方法
|
|
|
|
|
Net.class_ification(event)
|
|
|
|
|
elif obj_id1 == 16: # 误差
|
|
|
|
|
# 调用 error_calculation 方法
|
|
|
|
|
Net.error_calculation(event)
|
|
|
|
|
elif obj_id1 == 18: # 调整1
|
|
|
|
|
# 调用 adjust_one 方法
|
|
|
|
|
Net.adjust_one(event)
|
|
|
|
|
elif obj_id1 == 20: # 调整2
|
|
|
|
|
# 调用 adjust_two 方法
|
|
|
|
|
Net.adjust_two(event)
|
|
|
|
|
elif obj_id1 == 22: # 调整3
|
|
|
|
|
# 调用 adjust_three 方法
|
|
|
|
|
Net.adjust_three(event)
|
|
|
|
|
elif obj_id1 == 24: # 主数据线
|
|
|
|
|
# 调用 main_line 方法,参数为 1
|
|
|
|
|
Net.main_line(event, 1)
|
|
|
|
|
elif obj_id1 == 26: # 参数数据线
|
|
|
|
|
# 调用 main_line 方法,参数为 0
|
|
|
|
|
Net.main_line(event, 0)
|
|
|
|
|
|
|
|
|
|
# 创建弹出窗口
|
|
|
|
|
def creat_window(self, title):
|
|
|
|
|
top = tk.Toplevel(self.Root)
|
|
|
|
|
top.geometry("300x350")
|
|
|
|
|
top.title(title)
|
|
|
|
|
return top
|
|
|
|
|
|
|
|
|
|
# 输入框
|
|
|
|
|
def create_input_box(self, top, text, value):
|
|
|
|
|
# 创建一个标签,显示指定的文本
|
|
|
|
|
box_label = tk.Label(top, text=text)
|
|
|
|
|
box_label.pack(padx=10, pady=10) # 设置标签的外边距
|
|
|
|
|
# 创建一个 IntVar 对象,并设置默认值为传入的 value 参数
|
|
|
|
|
box_size = tk.IntVar(top, value=value)
|
|
|
|
|
# 创建一个输入框,并关联到上面创建的 IntVar 对象
|
|
|
|
|
box_size_entry = tk.Entry(top, textvariable=box_size)
|
|
|
|
|
# 设置输入框的外边距
|
|
|
|
|
box_size_entry.pack(padx=20, pady=20)
|
|
|
|
|
# 返回创建的输入框,以便在调用该方法的地方使用
|
|
|
|
|
return box_size_entry
|
|
|
|
|
|
|
|
|
|
def create_dropdown_box(self, top, text, listvalues, default_value):
|
|
|
|
|
# 创建一个标签,显示指定的文本
|
|
|
|
|
box_mode_label = tk.Label(top, text=text)
|
|
|
|
|
box_mode_label.pack(padx=10, pady=10) # 设置标签的外边距
|
|
|
|
|
# 创建一个下拉框(Combobox),用于选择
|
|
|
|
|
box_mode_combobox = ttk.Combobox(top)
|
|
|
|
|
# 设置下拉框的可选值
|
|
|
|
|
box_mode_combobox["values"] = listvalues
|
|
|
|
|
# 设置下拉框的默认值
|
|
|
|
|
box_mode_combobox.set(default_value)
|
|
|
|
|
# 设置下拉框的外边距
|
|
|
|
|
box_mode_combobox.pack(padx=20, pady=15)
|
|
|
|
|
# 返回创建的下拉框对象,以便在调用该方法的地方使用
|
|
|
|
|
return box_mode_combobox
|
|
|
|
|
|
|
|
|
|
# 清空当前模型展示区
|
|
|
|
|
def restart_new_model(self):
|
|
|
|
|
# 这将在子进程中调用当前Python解释器,并传递当前文件的路径作为参数
|
|
|
|
|
subprocess.call([sys.executable, __file__])
|
|
|
|
|
|
|
|
|
|
class ModelConn:
|
|
|
|
|
def __init__(self, ConnObjID, ConnType, NobjS, NobjE):
|
|
|
|
|
self.ConnObjID = ConnObjID # 连接线编号
|
|
|
|
|
self.ConnType = ConnType # 连接线类别
|
|
|
|
|
self.NobjS = NobjS # 源图元对象
|
|
|
|
|
self.NobjE = NobjE # 目标图元对象
|
|
|
|
|
|
|
|
|
|
def output(self): # 输出方法
|
|
|
|
|
# 创建一个空列表
|
|
|
|
|
result = [self.ConnObjID, self.ConnType, self.NobjS, self.NobjE]
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
class Networking:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.AllModelObj = [] # 网络对象总表
|
|
|
|
|
self.AllModelConn = [] # 网络连接对象总表
|
|
|
|
|
self.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]
|
|
|
|
|
]
|
|
|
|
|
self.Items1 = [] # 存储被拖拽的图元的列表
|
|
|
|
|
self.Second_Items = [[], [], []] # 记录第二个框中画布创建的序号(图元,标签,连线)
|
|
|
|
|
self.SubFolders = [] # 存储数据集类别
|
|
|
|
|
self.Element = [] # 记录图元名称
|
|
|
|
|
self.Train_images = [] # 存储训练集的列表
|
|
|
|
|
self.Test_images = [] # 存储测试集的列表
|
|
|
|
|
self.ImgPath = [] # 储存数据集路径
|
|
|
|
|
self.Item_Record = []
|
|
|
|
|
self.Item_Name = []
|
|
|
|
|
|
|
|
|
|
def entity_loading_dataset(self, event):
|
|
|
|
|
CNN.Yt = event.y + CNN.Offset_y
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '数据集', 0)
|
|
|
|
|
name = '数据集' if i == 1 else '数据集' + str(i)
|
|
|
|
|
# 复制图元对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
|
|
|
|
|
|
|
|
|
|
DataSet = Data_Class("DataSet" + str(i), 1, '数据集' + str(i), ".", x, y)
|
|
|
|
|
self.AllModelObj.append(DataSet)
|
|
|
|
|
# 创建一个tkinter根窗口,并隐藏它
|
|
|
|
|
root1 = tk.Tk()
|
|
|
|
|
root1.withdraw()
|
|
|
|
|
# setload_data()函数,获取加载数据集的参数
|
|
|
|
|
DataPara = DataSet.SetDataPara()
|
|
|
|
|
self.ImgPath.append(DataPara['train_imgPath'])
|
|
|
|
|
self.ImgPath.append(DataPara['test_imgPath'])
|
|
|
|
|
# 调用X3中load_data()函数,根据参数加载数据集
|
|
|
|
|
self.Train_images, self.Test_images = DataSet.LoadData(DataPara)
|
|
|
|
|
# 打印一些信息,检查是否正确加载
|
|
|
|
|
print("训练集图片的形状:", self.Train_images.shape)
|
|
|
|
|
DataSet.images_statistics(DataPara['train_imgPath'])
|
|
|
|
|
print("测试集图片的形状:", self.Test_images.shape)
|
|
|
|
|
DataSet.images_statistics(DataPara['test_imgPath'])
|
|
|
|
|
|
|
|
|
|
def convolution(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '卷积', 1)
|
|
|
|
|
|
|
|
|
|
Conv = Conv_Class("Conv" + str(i), 2, "卷积"+str(i), [], x, CNN.Yt)
|
|
|
|
|
self.AllModelObj.append(Conv)
|
|
|
|
|
top = CNN.creat_window('卷积参数配置') # 创建弹出窗口
|
|
|
|
|
kernel_size_entry = CNN.create_input_box(top, "卷积核大小:", 3) # 创建一个输入框,获取卷积核大小
|
|
|
|
|
stride_entry = CNN.create_input_box(top, "卷积步长:", 1) # 创建一个输入框,获取卷积步长
|
|
|
|
|
name = '卷积:' if i == 1 else '卷积' + str(i) + ':'
|
|
|
|
|
def get_input(): # 创建
|
|
|
|
|
# 获取输入框的内容
|
|
|
|
|
result1 = int(kernel_size_entry.get())
|
|
|
|
|
result2 = int(stride_entry.get())
|
|
|
|
|
text = str(result1) + "*" + str(result1)
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name+text), name]) # 复制图元对象的标签
|
|
|
|
|
# 参数赋值
|
|
|
|
|
self.AllModelObj[-1].ParaString.append([result1, result2])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetConvPara(self.AllModelObj[-1]))
|
|
|
|
|
# print(self.AllModelObj[-1].ParaString)
|
|
|
|
|
top.destroy() # 关闭窗口
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def pooling(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '池化', 2)
|
|
|
|
|
|
|
|
|
|
Pool = Pool_Class("Pool" + str(i), 3, "池化" + str(i), [], x, CNN.Yt)
|
|
|
|
|
self.AllModelObj.append(Pool)
|
|
|
|
|
# 创建弹出窗口
|
|
|
|
|
top = CNN.creat_window("池化参数配置")
|
|
|
|
|
pool_type_combobox = CNN.create_dropdown_box(top, "池化池类型:", ["max", "avg", "min"], 'max') # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
pool_size_entry = CNN.create_input_box(top, "池化池大小:", 2) # 创建一个输入框,用于输入池大小
|
|
|
|
|
name = '池化:' if i == 1 else '池化' + str(i) + ':'
|
|
|
|
|
# 创建
|
|
|
|
|
def get_input():
|
|
|
|
|
# 从下拉框中获取池化类型
|
|
|
|
|
pool_type = pool_type_combobox.get()
|
|
|
|
|
# 从输入框中获取池大小
|
|
|
|
|
pool_size = int(pool_size_entry.get())
|
|
|
|
|
text = str(pool_size) + "*" + str(pool_size) + str(pool_type)
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name+text), name]) # 复制图元对象的标签
|
|
|
|
|
self.AllModelObj[-1].ParaString.append([pool_type, pool_size])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetPollPara(self.AllModelObj[-1]))
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fully_connected(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '全连接', 3)
|
|
|
|
|
|
|
|
|
|
FullConn = FullConn_Class("FullConn" + str(i), 4, "全连接" + str(i), [], x, CNN.Yt)
|
|
|
|
|
self.AllModelObj.append(FullConn)
|
|
|
|
|
top = CNN.creat_window("全连接参数配置")
|
|
|
|
|
num_outputs_entry = CNN.create_input_box(top, "全连接输出维度:", 10)
|
|
|
|
|
name = '全连接' if i == 1 else '全连接' + str(i)
|
|
|
|
|
def get_input():
|
|
|
|
|
global Listinstance
|
|
|
|
|
# 从下拉框中获取输出维度
|
|
|
|
|
num = int(num_outputs_entry.get())
|
|
|
|
|
# 复制对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name), name])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(num)
|
|
|
|
|
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def nonlinear(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '非线性', 4)
|
|
|
|
|
|
|
|
|
|
Nonline = Nonline_Class("Nonline" + str(i), 5, "非线性函数" + str(i), [], x, CNN.Yt)
|
|
|
|
|
self.AllModelObj.append(Nonline)
|
|
|
|
|
top = CNN.creat_window('非线性参数配置') # 创建弹出窗口
|
|
|
|
|
# 创建一个下拉框,用于选择非线性类型
|
|
|
|
|
nonlinear_mode_combobox = CNN.create_dropdown_box(top, "非线性类型", ["Sigmoid", "ReLU", "Tanh"], "Sigmoid") # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
name = '非线性' if i == 1 else '非线性' + str(i)
|
|
|
|
|
# 创建
|
|
|
|
|
def get_input():
|
|
|
|
|
global Listinstance
|
|
|
|
|
# 从下拉框中获取池化类型
|
|
|
|
|
nonlinear_mode = nonlinear_mode_combobox.get()
|
|
|
|
|
# 复制对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name), name])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(nonlinear_mode)
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetNonLPara(self.AllModelObj[-1]))
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def class_ification(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, True, '分类', 5)
|
|
|
|
|
|
|
|
|
|
Classifier = Classifier_Class("Classifier" + str(i), 6, "分类" + str(i), [], x, CNN.Yt)
|
|
|
|
|
self.AllModelObj.append(Classifier)
|
|
|
|
|
top = CNN.creat_window('分类参数配置') # 创建弹出窗口
|
|
|
|
|
# 创建一个下拉框,用于选择非线性类型
|
|
|
|
|
class_box_combobox = CNN.create_input_box(top, "阈值:", 0.001) # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
name = '输出分类结果' if i == 1 else '输出分类' + str(i) + '结果'
|
|
|
|
|
# 创建
|
|
|
|
|
def get_input():
|
|
|
|
|
# 从输入框中获取阈值
|
|
|
|
|
threshold = float(class_box_combobox.get())
|
|
|
|
|
# 复制对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name), name])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(threshold)
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetClassifyPara(self.AllModelObj[-1]))
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def error_calculation(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, False, '误差', 6)
|
|
|
|
|
|
|
|
|
|
Error = Error_Class("Error" + str(i), 7, "误差计算" + str(i), [], x, y)
|
|
|
|
|
self.AllModelObj.append(Error)
|
|
|
|
|
name = '误差' if i == 1 else '误差' + str(i)
|
|
|
|
|
# 创建弹出窗口
|
|
|
|
|
top = CNN.creat_window('误差参数配置')
|
|
|
|
|
# 创建一个下拉框,用于选择误差类型
|
|
|
|
|
loss_type_combobox = CNN.create_dropdown_box(top, '误差类型', ["CEE", "MSE", "MAE"],"CEE")
|
|
|
|
|
# 创建
|
|
|
|
|
def get_input():
|
|
|
|
|
# 从下拉框中获取池化类型
|
|
|
|
|
loss_type = loss_type_combobox.get()
|
|
|
|
|
# 复制对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name), name])
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(loss_type)
|
|
|
|
|
self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetErrorPara(self.AllModelObj[-1]))
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def adjust_one(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, False, '调整1', 7)
|
|
|
|
|
|
|
|
|
|
AjConv = AjConv_Class("AjConv" + str(i), 8, "卷积调整" + str(i), [], x, y)
|
|
|
|
|
self.AllModelObj.append(AjConv)
|
|
|
|
|
name = '调整1' if i == 1 else '调整1 ' + str(i)
|
|
|
|
|
# 复制图元对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
|
|
|
|
|
|
|
|
|
|
def adjust_two(self, event):
|
|
|
|
|
i, x, y = self.element_adjustment(event, False, '调整2', 8)
|
|
|
|
|
|
|
|
|
|
AjFullconn = AjFullconn_Class("AjFullconn" + str(i), 9, "全连接调整" + str(i), [], x, y)
|
|
|
|
|
self.AllModelObj.append(AjFullconn)
|
|
|
|
|
name = '调整2' if i == 1 else '调整2 ' + str(i)
|
|
|
|
|
# 复制图元对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
|
|
|
|
|
|
|
|
|
|
def adjust_three(self, event): # 调整3
|
|
|
|
|
print(self.AllModelObj)
|
|
|
|
|
i, x, y = self.element_adjustment(event, False, '调整3', 9)
|
|
|
|
|
|
|
|
|
|
name = '调整3' if i == 1 else '调整3 ' + str(i)
|
|
|
|
|
# 复制图元对象的标签
|
|
|
|
|
self.Second_Items[1].append([CNN.Viewcanvas.create_text(x, y + 45, text=name), name])
|
|
|
|
|
|
|
|
|
|
# 此函数将图元从左侧框复制到右侧框,返回同类图元编号与坐标
|
|
|
|
|
def element_adjustment(self, event, regular, name, img_index):
|
|
|
|
|
'''
|
|
|
|
|
Function: element_adjustment Description: This function copies the element from the left box to the right box and returns the number of the same type of element and its coordinates.
|
|
|
|
|
Input:
|
|
|
|
|
event: the event object
|
|
|
|
|
regular: a boolean value indicating whether y is a fixed value
|
|
|
|
|
img_index: the index of the image in the list
|
|
|
|
|
name: the name of the element
|
|
|
|
|
Output:
|
|
|
|
|
i: the number of the same type of element
|
|
|
|
|
x: the x-coordinate of the new element
|
|
|
|
|
y: the y-coordinate of the new element
|
|
|
|
|
'''
|
|
|
|
|
x = event.x + CNN.Offset_x
|
|
|
|
|
# y是否是固定值
|
|
|
|
|
if regular:
|
|
|
|
|
y = CNN.Yt
|
|
|
|
|
else:
|
|
|
|
|
y = event.y + CNN.Offset_y
|
|
|
|
|
# 复制一个图片,表示调整操作
|
|
|
|
|
new_item = CNN.Viewcanvas.create_image(x, y, image=CNN.List_image[img_index])
|
|
|
|
|
self.Second_Items[0].append([new_item, name])
|
|
|
|
|
# 同类图元编号
|
|
|
|
|
i = self.count_string(name) + 1
|
|
|
|
|
# 添加图元名称
|
|
|
|
|
self.Element.append(name + ' ' + str(i))
|
|
|
|
|
# 恢复图元的原位置
|
|
|
|
|
event.widget.coords(CNN.Dragged_item, CNN.Original_x, CNN.Original_y)
|
|
|
|
|
# 将新图元的id和坐标添加到列表中
|
|
|
|
|
self.Items1.append((new_item, x, y))
|
|
|
|
|
# 返回同类图元编号,与坐标
|
|
|
|
|
return i, x, y
|
|
|
|
|
|
|
|
|
|
def main_line(self, event, index):
|
|
|
|
|
te = ['参数连接线', '主数据连接线']
|
|
|
|
|
choose = [2, 1]
|
|
|
|
|
print(self.AllModelConn)
|
|
|
|
|
if len(self.Element) > 2:
|
|
|
|
|
starting, ending = self.closest_coordinate(event)
|
|
|
|
|
top = CNN.creat_window(te[index]) # 创建弹出窗口
|
|
|
|
|
# 创建一个下拉框,用于选择图源
|
|
|
|
|
start_mode_combobox = CNN.create_dropdown_box(top, "起始图元", self.Element, self.Element[starting])
|
|
|
|
|
end_mode_combobox = CNN.create_dropdown_box(top, "终点图元", self.Element, self.Element[ending])
|
|
|
|
|
def get_input(): # 创建
|
|
|
|
|
result1 = start_mode_combobox.get() # 获取输入框的内容
|
|
|
|
|
result2 = end_mode_combobox.get()
|
|
|
|
|
start = self.Element.index(result1)
|
|
|
|
|
end = self.Element.index(result2)
|
|
|
|
|
starting = (self.Items1[start][1], self.Items1[start][2])
|
|
|
|
|
ending = (self.Items1[end][1], self.Items1[end][2])
|
|
|
|
|
self.conn_lines(starting, ending, index)
|
|
|
|
|
i = len(self.AllModelConn)
|
|
|
|
|
start_name = Net.AllModelObj[start].ObjID
|
|
|
|
|
end_name = Net.AllModelObj[end].ObjID
|
|
|
|
|
self.AllModelConn.append(ModelConn(1+i, choose[index], start_name, end_name).output())
|
|
|
|
|
top.destroy() # 关闭窗口
|
|
|
|
|
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
event.widget.coords(CNN.Dragged_item, CNN.Original_x, CNN.Original_y) # 恢复图元的原位置
|
|
|
|
|
|
|
|
|
|
def closest_coordinate(self, event):
|
|
|
|
|
x = event.x + CNN.Offset_x
|
|
|
|
|
y = event.y + CNN.Offset_y
|
|
|
|
|
given_coord = (x, y)
|
|
|
|
|
coords_list = []
|
|
|
|
|
for item in self.Items1:
|
|
|
|
|
coords_list.append((item[1], item[2]))
|
|
|
|
|
# 初始化最小距离和对应的坐标
|
|
|
|
|
min_distance1 = float('inf')
|
|
|
|
|
min_distance2 = float('inf')
|
|
|
|
|
closest_coord1 = None
|
|
|
|
|
closest_coord2 = None
|
|
|
|
|
# 遍历列表中的坐标,找到最接近的两个坐标
|
|
|
|
|
for coord in coords_list:
|
|
|
|
|
dist = math.sqrt((given_coord[0] - coord[0]) ** 2 + (given_coord[1] - coord[1]) ** 2)
|
|
|
|
|
if dist < min_distance1:
|
|
|
|
|
min_distance2 = min_distance1
|
|
|
|
|
closest_coord2 = closest_coord1
|
|
|
|
|
min_distance1 = dist
|
|
|
|
|
closest_coord1 = coord
|
|
|
|
|
elif dist < min_distance2:
|
|
|
|
|
min_distance2 = dist
|
|
|
|
|
closest_coord2 = coord
|
|
|
|
|
coord1 = coords_list.index(closest_coord1)
|
|
|
|
|
coord2 = coords_list.index(closest_coord2)
|
|
|
|
|
return coord1, coord2
|
|
|
|
|
# return min(coord1, coord2), max(coord1, coord2)
|
|
|
|
|
|
|
|
|
|
def count_string(self, sub_string):
|
|
|
|
|
"""
|
|
|
|
|
计算列表中包含指定子字符串的元素数量。
|
|
|
|
|
Parameters:
|
|
|
|
|
- sub_string (str): 要查找的子字符串。
|
|
|
|
|
Returns:
|
|
|
|
|
- int: 包含指定子字符串的元素数量。
|
|
|
|
|
"""
|
|
|
|
|
count = 0
|
|
|
|
|
for string in self.Element:
|
|
|
|
|
if sub_string in string:
|
|
|
|
|
count += 1
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
def conn_lines(self, start, end, index):
|
|
|
|
|
smooth = [False, True]
|
|
|
|
|
width = [2, 4]
|
|
|
|
|
if start[0] == end[0]:
|
|
|
|
|
CNN.Viewcanvas.create_line(start[0], start[1] + 30, end[0], end[1] - 30, arrow=tk.LAST,
|
|
|
|
|
arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index], width=width[index])
|
|
|
|
|
elif start[1] == end[1]:
|
|
|
|
|
CNN.Viewcanvas.create_line(start[0] + 30, start[1], end[0] - 30, end[1], arrow=tk.LAST,
|
|
|
|
|
arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index], width=width[index])
|
|
|
|
|
else:
|
|
|
|
|
if abs(start[0] - end[0]) > abs(start[1] - end[1]):
|
|
|
|
|
# 创建数据线箭头
|
|
|
|
|
CNN.Viewcanvas.create_line(start[0] - 15, start[1], end[0] + 30,end[1], arrow=tk.LAST,
|
|
|
|
|
arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index],
|
|
|
|
|
width=width[index])
|
|
|
|
|
else:
|
|
|
|
|
# 创建数据线箭头
|
|
|
|
|
CNN.Viewcanvas.create_line(start[0], start[1] - 20, end[0] + 30, end[1], arrow=tk.LAST,arrowshape=(16, 20, 4), fill='lightblue', smooth=smooth[index],
|
|
|
|
|
width=width[index])
|
|
|
|
|
|
|
|
|
|
def check_connection(self):
|
|
|
|
|
obj_names = ['DataSet', 'Conv', 'Pool', 'FullConn',
|
|
|
|
|
'Nonline', 'Classifier', 'Error',
|
|
|
|
|
'AjConv', 'AjFullconn']
|
|
|
|
|
if len(Net.AllModelConn) == 0:
|
|
|
|
|
messagebox.showerror("错误", "请连接图元!")
|
|
|
|
|
return
|
|
|
|
|
# print(Net.AllModelConn)
|
|
|
|
|
for model_conn in Net.AllModelConn:
|
|
|
|
|
start_name = re.sub(r'\d+', '', model_conn[2])
|
|
|
|
|
end_name = re.sub(r'\d+', '', model_conn[3])
|
|
|
|
|
start = obj_names.index(start_name)
|
|
|
|
|
end = obj_names.index(end_name)
|
|
|
|
|
if self.RIGHT_MODEL[start][end] == 0:
|
|
|
|
|
messagebox.showerror("错误", model_conn[2]+'与' + model_conn[3]+"连接不正确,请检查!")
|
|
|
|
|
return
|
|
|
|
|
messagebox.showinfo("成功", "连接正确,恭喜!")
|
|
|
|
|
|
|
|
|
|
def connecting_lines(self, obj_x, obj_y, text, text_record, image):
|
|
|
|
|
CNN.Viewcanvas.create_image(obj_x, obj_y, image=image) # 创建图元对象
|
|
|
|
|
CNN.Viewcanvas.create_text(obj_x + text_record[0], obj_y + text_record[1], text=text, font=("黑体", 14)) # 创建图元对象的标签
|
|
|
|
|
|
|
|
|
|
def creating_elements(self):
|
|
|
|
|
text_record = [(0, -50), (0, 50), (-80, 0)]
|
|
|
|
|
# 遍历AllModelObj列表,在窗口左侧创建图元菜单
|
|
|
|
|
for obj in self.AllModelObj:
|
|
|
|
|
# 并且要根据需求调整每个对象的位置
|
|
|
|
|
obj_x = obj.ObjX # 根据对象的id计算x坐标
|
|
|
|
|
obj_y = obj.ObjY # 根据对象的id计算y坐标
|
|
|
|
|
self.Item_Record.append((obj_x, obj_y))
|
|
|
|
|
self.Item_Name.append(obj.ObjID)
|
|
|
|
|
# 根据对象的类型,绘制相应的图形
|
|
|
|
|
if 'Error' in obj.ObjID:
|
|
|
|
|
self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[0], CNN.List_image[obj.ObjType - 1])
|
|
|
|
|
elif 'Aj' in obj.ObjID:
|
|
|
|
|
self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[2], CNN.List_image[-1])
|
|
|
|
|
else:
|
|
|
|
|
self.connecting_lines(obj_x, obj_y, obj.ObjLable, text_record[1], CNN.List_image[obj.ObjType - 1])
|
|
|
|
|
|
|
|
|
|
def ligature(self): # 连接线
|
|
|
|
|
# print(Item_Record)
|
|
|
|
|
for conn in self.AllModelConn:
|
|
|
|
|
starting = self.Item_Name.index(conn[2])
|
|
|
|
|
# print(starting)
|
|
|
|
|
ending = self.Item_Name.index(conn[3])
|
|
|
|
|
if conn[1] == 1:
|
|
|
|
|
# print(Item_Record[starting])
|
|
|
|
|
self.conn_lines(self.Item_Record[starting], self.Item_Record[ending], 1)
|
|
|
|
|
else:
|
|
|
|
|
self.conn_lines(self.Item_Record[starting], self.Item_Record[ending], 0)
|
|
|
|
|
def setpara_func(self):
|
|
|
|
|
for i in range(len(self.AllModelObj)):
|
|
|
|
|
if self.AllModelObj[i].ObjType == 2: # 卷积
|
|
|
|
|
self.modify_conv(i)
|
|
|
|
|
elif self.AllModelObj[i].ObjType == 3: # 池化层
|
|
|
|
|
pass
|
|
|
|
|
elif self.AllModelObj[i].ObjType == 4: # 全连接层
|
|
|
|
|
pass
|
|
|
|
|
elif self.AllModelObj[i].ObjType == 5: # 非线性层
|
|
|
|
|
pass
|
|
|
|
|
elif self.AllModelObj[i].ObjType == 6: #分类层
|
|
|
|
|
pass
|
|
|
|
|
elif self.AllModelObj[i].ObjType == 7: # 误差
|
|
|
|
|
pass
|
|
|
|
|
def query(self, name, i):
|
|
|
|
|
letters = ''.join([char for char in name if char.isalpha()])
|
|
|
|
|
numbers = ''.join([char for char in name if char.isdigit()])
|
|
|
|
|
x, y, z = -1, -1, -1
|
|
|
|
|
for j in range(len(self.Second_Items[1])):
|
|
|
|
|
if letters in self.Second_Items[1][j][1]:
|
|
|
|
|
z = j
|
|
|
|
|
CNN.Viewcanvas.delete(self.Second_Items[1][j][0])
|
|
|
|
|
break
|
|
|
|
|
if z == -1: # 不存在图元
|
|
|
|
|
return -1, -1, -1
|
|
|
|
|
l = []
|
|
|
|
|
for j in range(len(self.Second_Items[0])):
|
|
|
|
|
if letters in self.Second_Items[0][j][1]:
|
|
|
|
|
l.append(self.Second_Items[0][j][0])
|
|
|
|
|
for item in self.Items1:
|
|
|
|
|
if item[0] == l[int(numbers)-1]:
|
|
|
|
|
x, y = item[1], item[2]
|
|
|
|
|
return x, y, z
|
|
|
|
|
|
|
|
|
|
def modify_conv(self, i):
|
|
|
|
|
"""
|
|
|
|
|
右框修改卷积图元参数及AllModelObj参数
|
|
|
|
|
Parameters: None
|
|
|
|
|
Returns: None
|
|
|
|
|
"""
|
|
|
|
|
top = CNN.creat_window('卷积参数配置') # 创建弹出窗口
|
|
|
|
|
kernel_size_entry = CNN.create_input_box(top, "卷积核大小:", 3) # 创建一个输入框,获取卷积核大小
|
|
|
|
|
stride_entry = CNN.create_input_box(top, "卷积步长:", 1) # 创建一个输入框,获取卷积步长
|
|
|
|
|
name = self.AllModelObj[i].ObjLable
|
|
|
|
|
x, y, z= self.query(name, i)
|
|
|
|
|
if x == -1:
|
|
|
|
|
return
|
|
|
|
|
name = name+':'
|
|
|
|
|
def get_input(): # 创建
|
|
|
|
|
# 获取输入框的内容
|
|
|
|
|
result1 = int(kernel_size_entry.get())
|
|
|
|
|
result2 = int(stride_entry.get())
|
|
|
|
|
text = str(result1) + "*" + str(result1)
|
|
|
|
|
self.Second_Items[1][z] = [CNN.Viewcanvas.create_text(x, y + 45, text=name+text), name] # 复制图元对象的标签
|
|
|
|
|
self.AllModelObj[i].ParaString[0] = [result1, result2]
|
|
|
|
|
self.AllModelObj[i].ParaString[1] = self.AllModelObj[i].SetConvPara(self.AllModelObj[i])
|
|
|
|
|
# print(self.AllModelObj[-1].ParaString)
|
|
|
|
|
top.destroy() # 关闭窗口
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
# def modify_pooling(self, id):
|
|
|
|
|
# i, x, y = self.element_adjustment(event, True, '池化', 2)
|
|
|
|
|
#
|
|
|
|
|
# Pool = Pool_Class("Pool" + str(i), 3, "池化" + str(i), [], x, CNN.Yt)
|
|
|
|
|
# self.AllModelObj.append(Pool)
|
|
|
|
|
# # 创建弹出窗口
|
|
|
|
|
# top = CNN.creat_window("池化参数配置")
|
|
|
|
|
# pool_type_combobox = CNN.create_dropdown_box(top, "池化池类型:", ["max", "avg", "min"], 'max') # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
# pool_size_entry = CNN.create_input_box(top, "池化池大小:", 2) # 创建一个输入框,用于输入池大小
|
|
|
|
|
# name = '池化:' if i == 1 else '池化' + str(i) + ':'
|
|
|
|
|
# # 创建
|
|
|
|
|
# def get_input():
|
|
|
|
|
# # 从下拉框中获取池化类型
|
|
|
|
|
# pool_type = pool_type_combobox.get()
|
|
|
|
|
# # 从输入框中获取池大小
|
|
|
|
|
# pool_size = int(pool_size_entry.get())
|
|
|
|
|
# text = str(pool_size) + "*" + str(pool_size) + str(pool_type)
|
|
|
|
|
# self.Second_Items[1].append(CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name+text)) # 复制图元对象的标签
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append([pool_type, pool_size])
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetPollPara(self.AllModelObj[-1]))
|
|
|
|
|
# # 关闭窗口
|
|
|
|
|
# top.destroy()
|
|
|
|
|
# button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
# button.pack()
|
|
|
|
|
#
|
|
|
|
|
#
|
|
|
|
|
# def modify_fully(self, id):
|
|
|
|
|
# i, x, y = self.element_adjustment(event, True, '全连接', 3)
|
|
|
|
|
#
|
|
|
|
|
# FullConn = FullConn_Class("FullConn" + str(i), 4, "全连接" + str(i), [], x, CNN.Yt)
|
|
|
|
|
# self.AllModelObj.append(FullConn)
|
|
|
|
|
# top = CNN.creat_window("全连接参数配置")
|
|
|
|
|
# num_outputs_entry = CNN.create_input_box(top, "全连接输出维度:", 10)
|
|
|
|
|
# name = '全连接' if i == 1 else '全连接' + str(i)
|
|
|
|
|
# def get_input():
|
|
|
|
|
# global Listinstance
|
|
|
|
|
# # 从下拉框中获取输出维度
|
|
|
|
|
# num = int(num_outputs_entry.get())
|
|
|
|
|
# # 复制对象的标签
|
|
|
|
|
# self.Second_Items[1].append(CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name))
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(num)
|
|
|
|
|
#
|
|
|
|
|
# # 关闭窗口
|
|
|
|
|
# top.destroy()
|
|
|
|
|
# button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
# button.pack()
|
|
|
|
|
#
|
|
|
|
|
# def modify_nonlinear(self, id):
|
|
|
|
|
# i, x, y = self.element_adjustment(event, True, '非线性', 4)
|
|
|
|
|
#
|
|
|
|
|
# Nonline = Nonline_Class("Nonline" + str(i), 5, "非线性函数" + str(i), [], x, CNN.Yt)
|
|
|
|
|
# self.AllModelObj.append(Nonline)
|
|
|
|
|
# top = CNN.creat_window('非线性参数配置') # 创建弹出窗口
|
|
|
|
|
# # 创建一个下拉框,用于选择非线性类型
|
|
|
|
|
# nonlinear_mode_combobox = CNN.create_dropdown_box(top, "非线性类型", ["Sigmoid", "ReLU", "Tanh"], "Sigmoid") # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
# name = '非线性' if i == 1 else '非线性' + str(i)
|
|
|
|
|
# # 创建
|
|
|
|
|
# def get_input():
|
|
|
|
|
# global Listinstance
|
|
|
|
|
# # 从下拉框中获取池化类型
|
|
|
|
|
# nonlinear_mode = nonlinear_mode_combobox.get()
|
|
|
|
|
# # 复制对象的标签
|
|
|
|
|
# self.Second_Items[1].append(CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name))
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(nonlinear_mode)
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetNonLPara(self.AllModelObj[-1]))
|
|
|
|
|
# # 关闭窗口
|
|
|
|
|
# top.destroy()
|
|
|
|
|
# button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
# button.pack()
|
|
|
|
|
#
|
|
|
|
|
# def modify_class(self, id):
|
|
|
|
|
# i, x, y = self.element_adjustment(event, True, '分类', 5)
|
|
|
|
|
#
|
|
|
|
|
# Classifier = Classifier_Class("Classifier" + str(i), 6, "分类" + str(i), [], x, CNN.Yt)
|
|
|
|
|
# self.AllModelObj.append(Classifier)
|
|
|
|
|
# top = CNN.creat_window('分类参数配置') # 创建弹出窗口
|
|
|
|
|
# # 创建一个下拉框,用于选择非线性类型
|
|
|
|
|
# class_box_combobox = CNN.create_input_box(top, "阈值:", 0.001) # 创建一个下拉框,用于选择池化类型
|
|
|
|
|
# name = '输出分类结果' if i == 1 else '输出分类' + str(i) + '结果'
|
|
|
|
|
# # 创建
|
|
|
|
|
# def get_input():
|
|
|
|
|
# # 从输入框中获取阈值
|
|
|
|
|
# threshold = float(class_box_combobox.get())
|
|
|
|
|
# # 复制对象的标签
|
|
|
|
|
# self.Second_Items[1].append(CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name))
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(threshold)
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetClassifyPara(self.AllModelObj[-1]))
|
|
|
|
|
# # 关闭窗口
|
|
|
|
|
# top.destroy()
|
|
|
|
|
# button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
# button.pack()
|
|
|
|
|
#
|
|
|
|
|
# def modify_error(self, id):
|
|
|
|
|
# i, x, y = self.element_adjustment(event, False, '误差', 6)
|
|
|
|
|
#
|
|
|
|
|
# Error = Error_Class("Error" + str(i), 7, "误差计算" + str(i), [], x, y)
|
|
|
|
|
# self.AllModelObj.append(Error)
|
|
|
|
|
# name = '误差' if i == 1 else '误差' + str(i)
|
|
|
|
|
# # 创建弹出窗口
|
|
|
|
|
# top = CNN.creat_window('误差参数配置')
|
|
|
|
|
# # 创建一个下拉框,用于选择误差类型
|
|
|
|
|
# loss_type_combobox = CNN.create_dropdown_box(top, '误差类型', ["CEE", "MSE", "MAE"],"CEE")
|
|
|
|
|
# # 创建
|
|
|
|
|
# def get_input():
|
|
|
|
|
# # 从下拉框中获取池化类型
|
|
|
|
|
# loss_type = loss_type_combobox.get()
|
|
|
|
|
# # 复制对象的标签
|
|
|
|
|
# self.Second_Items[1].append(CNN.Viewcanvas.create_text(x, CNN.Yt + 45, text=name))
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(loss_type)
|
|
|
|
|
# self.AllModelObj[-1].ParaString.append(self.AllModelObj[-1].SetErrorPara(self.AllModelObj[-1]))
|
|
|
|
|
# # 关闭窗口
|
|
|
|
|
# top.destroy()
|
|
|
|
|
# button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
# button.pack()
|
|
|
|
|
|
|
|
|
|
class ModelObj: # 网络对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
self.ObjID = ObjID # 图元号
|
|
|
|
|
self.ObjType = ObjType # 图元类别
|
|
|
|
|
self.ObjLable = ObjLable # 对象标签
|
|
|
|
|
self.ParaString = ParaString # 参数字符串
|
|
|
|
|
self.ObjX = ObjX # 对象位置x坐标
|
|
|
|
|
self.ObjY = ObjY # 对象位置y坐标
|
|
|
|
|
|
|
|
|
|
class Data_Class(ModelObj): # 数据集网络对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.LoadData = self.load_data # 基本操作函数
|
|
|
|
|
self.SetDataPara = self.set_data_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
# 定义加载数据集load_data()
|
|
|
|
|
def load_data(self, DataPara):
|
|
|
|
|
global SubFolders
|
|
|
|
|
listimages = [] # 存储图片的列表
|
|
|
|
|
list_path = []
|
|
|
|
|
SubFolders, train_Path = read_folders(DataPara["train_imgPath"])
|
|
|
|
|
list_path.append(train_Path)
|
|
|
|
|
_, path_list = read_folders(DataPara["test_imgPath"])
|
|
|
|
|
list_path.append(path_list)
|
|
|
|
|
for data_path in list_path:
|
|
|
|
|
images = [] # 存储图片的列表
|
|
|
|
|
for path in data_path:
|
|
|
|
|
# print(path)
|
|
|
|
|
img = self.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]
|
|
|
|
|
|
|
|
|
|
def set_data_para(self):# 定义加载数据集的参数SetLoadData()
|
|
|
|
|
# 设置数据集路径信息
|
|
|
|
|
train_imgPath = filedialog.askdirectory(title="请选择训练集") + "/" # 训练集文件夹的位置
|
|
|
|
|
test_imgPath = filedialog.askdirectory(title="请选择测试集") + "/" # 测试集文件夹的位置
|
|
|
|
|
# 设置图片大小
|
|
|
|
|
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(self, 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 image_to_array(self, 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
|
|
|
|
|
|
|
|
|
|
class Conv_Class(ModelObj): # 卷积对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.ConvProc = self.conv_proc # 基本操作函数
|
|
|
|
|
self.SetConvPara = self.setconv_para # 参数设置函数
|
|
|
|
|
# 定义卷积函数ConvProc()
|
|
|
|
|
def conv_proc(self, 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 setconv_para(self, model_obj):# 定义设置卷积参数的函数SetConvPara()
|
|
|
|
|
# 从Conv的ParaString得到一个包含所有卷积参数的列表
|
|
|
|
|
conv_list = model_obj.ParaString[0]
|
|
|
|
|
height, stride = conv_list[0], conv_list[1]
|
|
|
|
|
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 = {"kernel": kernel,
|
|
|
|
|
"kernel_h": height,
|
|
|
|
|
"kernel_w": height,
|
|
|
|
|
"stride": stride,
|
|
|
|
|
"padding": padding,
|
|
|
|
|
"bias": bias}
|
|
|
|
|
return ConvPara
|
|
|
|
|
|
|
|
|
|
class Pool_Class(ModelObj): # 池化对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.PoolProc = self.pool_proc # 基本操作函数
|
|
|
|
|
self.SetPollPara = self.setpool_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def pool_proc(self, 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 # 返回特征图。
|
|
|
|
|
|
|
|
|
|
# 定义设置池化参数的函数
|
|
|
|
|
def setpool_para(self, model_obj):
|
|
|
|
|
# 从Pool的ParaString得到一个包含所有池化参数的列表
|
|
|
|
|
pool_list = model_obj.ParaString[0]
|
|
|
|
|
# 池大小和池类型
|
|
|
|
|
pool_mode, pool_size = pool_list[0], pool_list[1]
|
|
|
|
|
pool_size = int(pool_size)
|
|
|
|
|
stride = 2 # 设置步长,这里假设是2
|
|
|
|
|
PoolPara = {"pool_mode": pool_mode, # 返回PoolPara参数,这里用一个字典来存储
|
|
|
|
|
"pool_size": pool_size,
|
|
|
|
|
"stride": stride}
|
|
|
|
|
return PoolPara # 返回PoolPara参数
|
|
|
|
|
|
|
|
|
|
class FullConn_Class(ModelObj): # 全连接对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.FullConnProc = self.fullconn_proc # 基本操作函数
|
|
|
|
|
self.SetFullConnPara = self.setfullconn_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def fullconn_proc(self, 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 # 返回全连接计算后的数组
|
|
|
|
|
|
|
|
|
|
# 定义一个函数来设置全连接层的相关参数,这里可以根据实际情况修改或随机生成
|
|
|
|
|
def setfullconn_para(self, data, model_obj):
|
|
|
|
|
# 从FullConn的ParaString得到一个包含所有全连接参数的列表
|
|
|
|
|
num_outputs = model_obj.ParaString[0]
|
|
|
|
|
# 设置权重矩阵及其输出维度
|
|
|
|
|
num_outputs = int(num_outputs)
|
|
|
|
|
# 获取池化后的图片数组的长度和宽度
|
|
|
|
|
dim = len(data.shape)
|
|
|
|
|
if dim == 2:
|
|
|
|
|
height, width = data.shape
|
|
|
|
|
elif dim == 3:
|
|
|
|
|
c, height, width = data.shape
|
|
|
|
|
num_outputs = num_outputs
|
|
|
|
|
weights = np.random.randn(num_outputs, height * width)
|
|
|
|
|
bias = np.random.randn(1, num_outputs)
|
|
|
|
|
# 返回FullConnPara参数,这里用一个字典来存储
|
|
|
|
|
FullConnPara = {"weights": weights, "bias": bias, "num_outputs": num_outputs}
|
|
|
|
|
return FullConnPara
|
|
|
|
|
|
|
|
|
|
class Nonline_Class(ModelObj): # 非线性对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.NonlinearProc = self.nonlinear_proc # 基本操作函数
|
|
|
|
|
self.SetNonLPara = self.setnonl_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
# 定义非线性函数
|
|
|
|
|
def nonlinear_proc(self, 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 setnonl_para(self, model_obj):
|
|
|
|
|
# 从Nonline的ParaString得到一个包含所有非线性参数的列表
|
|
|
|
|
nonlinearmode = model_obj.ParaString[0]
|
|
|
|
|
NonLPara = {"nonlinearmode": nonlinearmode} # 返回NonLPara参数,这里用一个字典来存储
|
|
|
|
|
return NonLPara # 返回NonLPara参数
|
|
|
|
|
|
|
|
|
|
class Label: # 标签
|
|
|
|
|
# 设置标签类别列表并将其转化为one-hot向量的形式
|
|
|
|
|
def setlabel_para(self, label_list):
|
|
|
|
|
num_classes = len(label_list)
|
|
|
|
|
identity_matrix = np.eye(num_classes)
|
|
|
|
|
label_dict = {label: identity_matrix[i] for i, label in enumerate(label_list)}
|
|
|
|
|
return label_dict
|
|
|
|
|
# 读取样本数据集,遍历每个样本,并将样本和对应的标签组成元组,返回标记好标签的样本列表
|
|
|
|
|
def label_proc(self, samples, labels, label_dict):
|
|
|
|
|
labeled_samples = [(sample, label_dict[label]) for sample, label in zip(samples, labels)]
|
|
|
|
|
return labeled_samples
|
|
|
|
|
def label_array(self, i, batch):
|
|
|
|
|
# 读取标签数据
|
|
|
|
|
path_csv = Net.ImgPath[0].split('/')[-2] + '.csv'
|
|
|
|
|
df = pd.read_csv(path_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
|
|
|
|
|
|
|
|
|
|
class Classifier_Class(ModelObj): # 分类对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.ClassifierProc = self.classifier_proc # 基本操作函数
|
|
|
|
|
self.SetClassifyPara = self.setclassify_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def classifier_proc(self, 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 setclassify_para(self, model_obj):
|
|
|
|
|
# 设定阈值,可以根据你的数据和任务来调整阈值
|
|
|
|
|
threshold = float(model_obj.ParaString[0])
|
|
|
|
|
# 返回ClassifyPara参数,这里用一个字典来存储
|
|
|
|
|
ClassifyPara = {"threshold": threshold}
|
|
|
|
|
# 返回ClassifyPara参数
|
|
|
|
|
return ClassifyPara
|
|
|
|
|
|
|
|
|
|
class Error_Class(ModelObj): # 误差计算对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.ErrorProc = self.error_proc # 基本操作函数
|
|
|
|
|
self.SetErrorPara = self.seterror_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def error_proc(self, input, label, ErrorPara):
|
|
|
|
|
label_list, loss_type = ErrorPara # 读取标签列表和损失函数类型
|
|
|
|
|
one_hot_matrix = np.eye(len(label_list)) # 创建一个单位矩阵,大小为标签类别的个数
|
|
|
|
|
index = [x for x in label]
|
|
|
|
|
# print(label)
|
|
|
|
|
label_one_hot = np.take(one_hot_matrix, index, axis=0) # 从one-hot矩阵中取出对应的向量
|
|
|
|
|
# print(label_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
|
|
|
|
|
|
|
|
|
|
# 定义设置误差参数的函数
|
|
|
|
|
def seterror_para(self, model_obj):
|
|
|
|
|
label_list = [i for i in range(len(SubFolders))]# 确定参数信息: 标签类别,损失函数类型
|
|
|
|
|
# 假设损失函数类型为交叉熵(Cross Entropy Error,CEE)
|
|
|
|
|
# 从Error的ParaString得到
|
|
|
|
|
loss_type = model_obj.ParaString[0]
|
|
|
|
|
# 返回ErrorProc参数,这里用一个元组来存储
|
|
|
|
|
ErrorPara = (label_list, loss_type)
|
|
|
|
|
# 返回ErrorPara参数
|
|
|
|
|
return ErrorPara
|
|
|
|
|
|
|
|
|
|
class AjConv_Class(ModelObj): # 卷积调整对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.AjConvProc = self.ajconv_proc # 基本操作函数
|
|
|
|
|
self.SetAjConvPara = self.setajconv_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def ajconv_proc(self, images, AjConvPara):
|
|
|
|
|
kernel_grad_list = []
|
|
|
|
|
bias_grad = 0 # 初始化偏置项梯度为零
|
|
|
|
|
for c in images:
|
|
|
|
|
# 计算卷积核和偏置项的梯度
|
|
|
|
|
kernel_grad = np.zeros_like(AjConvPara['kernel_info']) # 初始化卷积核梯度为零矩阵
|
|
|
|
|
for i in range(AjConvPara['loss'].shape[0]): # 遍历误差值矩阵的行
|
|
|
|
|
for j in range(AjConvPara['loss'].shape[1]): # 遍历误差值矩阵的列
|
|
|
|
|
# 将输入数据数组中对应的子矩阵旋转180度,与误差值相乘,累加到卷积核梯度上
|
|
|
|
|
kernel_grad += np.rot90(c[i:i + kernel_grad.shape[0], j:j + kernel_grad.shape[0]], 2) * AjConvPara['loss'][i, j]
|
|
|
|
|
# 将误差值累加到偏置项梯度上
|
|
|
|
|
bias_grad += AjConvPara['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(images) # 沿着第0个维度求和
|
|
|
|
|
# 更新卷积核和偏置项参数
|
|
|
|
|
kernel = AjConvPara['kernel_info'] - AjConvPara['learning_rate'] * kernel_grad # 卷积核参数减去学习率乘以卷积核梯度
|
|
|
|
|
bias = AjConvPara['bias'] - AjConvPara['learning_rate'] * bias_grad # 偏置项参数减去学习率乘以偏置项梯度
|
|
|
|
|
return kernel, bias # 返回更新后的卷积参数
|
|
|
|
|
|
|
|
|
|
def setajconv_para(self, loss, ConvPara):
|
|
|
|
|
# print(model_obj)
|
|
|
|
|
# ConvPara = model_obj.ParaString[1]
|
|
|
|
|
kernel = ConvPara['kernel'] # 卷积核信息
|
|
|
|
|
bias = 0.5
|
|
|
|
|
learning_rate = 0.01 # 学习率
|
|
|
|
|
loss = np.array([[loss]])
|
|
|
|
|
AjConvPara = {'kernel_info': kernel, 'bias': bias, 'learning_rate': learning_rate, 'loss': loss}
|
|
|
|
|
return AjConvPara
|
|
|
|
|
|
|
|
|
|
class AjFullconn_Class(ModelObj): # 全连接调整对象
|
|
|
|
|
def __init__(self, ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY):
|
|
|
|
|
super().__init__(ObjID, ObjType, ObjLable, ParaString, ObjX, ObjY)
|
|
|
|
|
self.AjFullconnProc = self.ajfullconn_proc # 基本操作函数
|
|
|
|
|
self.SetAjFCPara = self.setajfc_para # 参数设置函数
|
|
|
|
|
|
|
|
|
|
def ajfullconn_proc(self, AjFCPara):
|
|
|
|
|
# 根据激活函数的参数选择相应的函数和导数
|
|
|
|
|
# 计算权重矩阵和偏置向量的梯度,使用链式法则
|
|
|
|
|
gradient_weights = np.outer(AjFCPara['loss'], AjFCPara['learning_rate'])
|
|
|
|
|
# 更新权重矩阵和偏置向量
|
|
|
|
|
weight_matrix = AjFCPara['weights'] - gradient_weights
|
|
|
|
|
bias_vector = AjFCPara['bias'] - AjFCPara['learning_rate'] * AjFCPara['bias']
|
|
|
|
|
# 返回更新后的权重矩阵和偏置向量
|
|
|
|
|
return weight_matrix, bias_vector
|
|
|
|
|
|
|
|
|
|
def setajfc_para(self, loss, FullConnPara):
|
|
|
|
|
# FullConnPara = model_obj.ParaString[1]
|
|
|
|
|
weights = FullConnPara["weights"]
|
|
|
|
|
bias = FullConnPara["bias"]
|
|
|
|
|
loss = np.array([loss])
|
|
|
|
|
AjFCPara = {
|
|
|
|
|
'weights': weights, # 全连接权重
|
|
|
|
|
'bias': bias, # 全连接偏置
|
|
|
|
|
'learning_rate': 0.01, # 学习率
|
|
|
|
|
'loss': loss # 误差值
|
|
|
|
|
}
|
|
|
|
|
return AjFCPara
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Model:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.Batch_First = [] # 每个批次第一张数据
|
|
|
|
|
self.Batch_First_Sheet = [] # 批次第一张数据
|
|
|
|
|
self.Flage = 0 # 是否存在模型
|
|
|
|
|
self.Train_list = [] # 存储训练设置参数
|
|
|
|
|
self.Prob_images = [] # 存储分类的数据
|
|
|
|
|
# def button_link(self):
|
|
|
|
|
# # 替每个主界面的按钮绑定各自的函数
|
|
|
|
|
# CNN.button['button1'].config(command=self.load_model) # 加载已有模型
|
|
|
|
|
# CNN.button['button2'].config(command=CNN.restart_new_model) # 新建模型
|
|
|
|
|
# CNN.button['button3'].config(command=Net.setpara_func) # 图元参数设置
|
|
|
|
|
# CNN.button['button4'].config(command=self.save_model) # 保存模型
|
|
|
|
|
# CNN.button['button5'].config(command=Net.check_connection) # 模型正确性检查
|
|
|
|
|
# CNN.button['button6'].config(command=CNN.close_program) # 退出
|
|
|
|
|
# CNN.button['button7'].config(command=self.train_setting) # 训练参数
|
|
|
|
|
# CNN.button['button8'].config(command=self.train) # 训练
|
|
|
|
|
|
|
|
|
|
def train_setting(self):
|
|
|
|
|
top = CNN.creat_window("训练参数配置") # 创建弹出的参数配置窗口
|
|
|
|
|
epoch_entry = CNN.create_input_box(top, "训练轮数:", 4) # 创建一个输入框,获取训练轮数
|
|
|
|
|
rate_entry = CNN.create_input_box(top, '学习率:', 0.1) # 创建一个输入框,获取学习率
|
|
|
|
|
batch_size_entry = CNN.create_input_box(top, "批次:", 32) # 创建一个输入框,获取批次
|
|
|
|
|
# 创建
|
|
|
|
|
def get_input():
|
|
|
|
|
# 获取输入框的内容
|
|
|
|
|
result1 = int(epoch_entry.get())
|
|
|
|
|
result2 = float(rate_entry.get())
|
|
|
|
|
result3 = int(batch_size_entry.get())
|
|
|
|
|
self.Train_list.append(result1)
|
|
|
|
|
self.Train_list.append(result2)
|
|
|
|
|
self.Train_list.append(result3)
|
|
|
|
|
# 关闭窗口
|
|
|
|
|
top.destroy()
|
|
|
|
|
button = tk.Button(top, text="获取信息", command=get_input)
|
|
|
|
|
button.pack()
|
|
|
|
|
|
|
|
|
|
def determine(self):
|
|
|
|
|
if len(self.Train_list) == 0:
|
|
|
|
|
messagebox.showerror("错误", "请设置训练参数!")
|
|
|
|
|
return 0
|
|
|
|
|
if len(Net.AllModelObj) == 0:
|
|
|
|
|
messagebox.showerror("错误", "请先建立网络!")
|
|
|
|
|
return 0
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
def train(self): # 训练
|
|
|
|
|
if self.determine() == 0:
|
|
|
|
|
return
|
|
|
|
|
epoch, rate, batch_size = self.Train_list
|
|
|
|
|
first_epoch = 1
|
|
|
|
|
LabelPara = Label()
|
|
|
|
|
count = 0
|
|
|
|
|
# 查询误差对象位置
|
|
|
|
|
for modelobj in Net.AllModelObj:
|
|
|
|
|
# 如果是误差层,结束
|
|
|
|
|
if modelobj.ObjType == 7:
|
|
|
|
|
break
|
|
|
|
|
count += 1
|
|
|
|
|
ErrorPara = Net.AllModelObj[count].ParaString[1]
|
|
|
|
|
while first_epoch <= epoch:
|
|
|
|
|
first_epoch = first_epoch + 1
|
|
|
|
|
for i in range(len(Net.Train_images) // batch_size):
|
|
|
|
|
images = Net.Train_images[i * batch_size:(i + 1) * batch_size]
|
|
|
|
|
self.forward_propagation(images) # 前向传播
|
|
|
|
|
prob_images = np.squeeze(self.Prob_images) # 获取分类结果的概率向量数组
|
|
|
|
|
batch = len(prob_images)
|
|
|
|
|
right_label = LabelPara.label_array(i, batch) # 读取标签数据,将标签数据转化成数组
|
|
|
|
|
loss = Net.AllModelObj[count].ErrorProc(prob_images, right_label, ErrorPara) * 0.1 # 计算误差值
|
|
|
|
|
print("当前epoch:", first_epoch, i * batch_size, ":", (i + 1) * batch_size, "误差值:", loss)
|
|
|
|
|
print("")
|
|
|
|
|
self.back_propagation(loss, images) # 反向传播
|
|
|
|
|
self.Flage = 1
|
|
|
|
|
# 删除指定目录下的所有文件
|
|
|
|
|
self.delete_all_files("test_category/")
|
|
|
|
|
# 遍历训练图像数据集
|
|
|
|
|
for i in range(len(Net.Train_images) // batch_size):
|
|
|
|
|
# 从训练图像数据集中获取当前批次的图像
|
|
|
|
|
images = Net.Train_images[i * batch_size:(i + 1) * batch_size]
|
|
|
|
|
# 进行前向传播,获取图像结果
|
|
|
|
|
image_results = self.forward_propagation(images)
|
|
|
|
|
# 打印图像结果
|
|
|
|
|
print(image_results)
|
|
|
|
|
# 遍历每个图像结果
|
|
|
|
|
for j in range(len(image_results)):
|
|
|
|
|
# 将图像保存到指定目录的子文件夹中
|
|
|
|
|
self.save_images_to_folders("test_category/", SubFolders[int(image_results[j]) - 1], images[j])
|
|
|
|
|
|
|
|
|
|
def delete_all_files(self, folder_path):
|
|
|
|
|
# 遍历文件夹中的所有子文件和子文件夹
|
|
|
|
|
for root, dirs, files in os.walk(folder_path, topdown=False):
|
|
|
|
|
for file_name in files:
|
|
|
|
|
file_path = os.path.join(root, file_name)
|
|
|
|
|
os.remove(file_path) # 删除文件
|
|
|
|
|
for dir_name in dirs:
|
|
|
|
|
dir_path = os.path.join(root, dir_name)
|
|
|
|
|
shutil.rmtree(dir_path) # 递归删除文件夹
|
|
|
|
|
|
|
|
|
|
def save_images_to_folders(self, folder_path, number, image_data):
|
|
|
|
|
sub_folder_path = os.path.join(folder_path, number)
|
|
|
|
|
# 检查数字子文件夹是否存在,若不存在则创建
|
|
|
|
|
if not os.path.exists(sub_folder_path):
|
|
|
|
|
os.makedirs(sub_folder_path)
|
|
|
|
|
# 创建并保存图像到相应的子文件夹中
|
|
|
|
|
image = Image.fromarray((image_data * 255).astype(np.uint8))
|
|
|
|
|
# 顺时针旋转90度
|
|
|
|
|
rotated_image = image.rotate(-90)
|
|
|
|
|
# 获取数字子文件夹中已有的照片数量
|
|
|
|
|
existing_images = os.listdir(sub_folder_path)
|
|
|
|
|
image_count = len(existing_images)
|
|
|
|
|
image_path = os.path.join(sub_folder_path, str(image_count + 1) + ".jpg")
|
|
|
|
|
rotated_image.save(image_path)
|
|
|
|
|
# 前向传播
|
|
|
|
|
def forward_propagation(self, images):
|
|
|
|
|
outputs = images # 存储每一层的输出结果的列表
|
|
|
|
|
data_flow = self.flow() # 网络数据流列表
|
|
|
|
|
for modelobj in Net.AllModelObj:
|
|
|
|
|
# 如果是卷积层,就调用ConvProc函数,根据卷积核和步长进行卷积计算,并更新输出结果
|
|
|
|
|
if modelobj.ObjType == 2:
|
|
|
|
|
outputs = self.conn_forward(modelobj, outputs)
|
|
|
|
|
# 如果是池化层,就调用PoolProc函数,进行池化计算,并更新输出结果
|
|
|
|
|
elif modelobj.ObjType == 3:
|
|
|
|
|
outputs = self.pool_forward(modelobj, outputs)
|
|
|
|
|
# 如果是全连接层,就调用FullConnProc函数,根据权重矩阵和偏置向量进行全连接计算,并更新输出结果
|
|
|
|
|
elif modelobj.ObjType == 4:
|
|
|
|
|
outputs = self.fullconn_forward(modelobj, outputs)
|
|
|
|
|
# 如果是非线性层,就调用NonlinearProc函数,根据激活函数进行非线性变换,并更新输出结果
|
|
|
|
|
elif modelobj.ObjType == 5:
|
|
|
|
|
outputs = self.nonlinear_forward(modelobj, outputs)
|
|
|
|
|
# 如果是分类层,就调用ClassifierProc函数,根据权重矩阵和偏置向量进行分类计算,并更新输出结果
|
|
|
|
|
elif modelobj.ObjType == 6:
|
|
|
|
|
outputs = self.classifier_forward(modelobj, outputs)
|
|
|
|
|
outputs = np.array(outputs) # 将输出结果列表转换为数组形式,方便后续处理
|
|
|
|
|
return outputs # 返回输出结果数组
|
|
|
|
|
# 这里是需要连线才使用
|
|
|
|
|
# 前向传播
|
|
|
|
|
# def forward_propagation(self, images):
|
|
|
|
|
# outputs = images # 存储每一层的输出结果的列表
|
|
|
|
|
# data_flow = self.flow() # 网络数据流列表
|
|
|
|
|
# for model_conn in data_flow:
|
|
|
|
|
# obj_count = self.query_obj(model_conn)
|
|
|
|
|
# modelobj = Net.AllModelObj[obj_count]
|
|
|
|
|
# # 如果是卷积层,就调用ConvProc函数,根据卷积核和步长进行卷积计算,并更新输出结果
|
|
|
|
|
# if modelobj.ObjType == 2:
|
|
|
|
|
# outputs = self.conn_forward(modelobj, outputs)
|
|
|
|
|
# # 如果是池化层,就调用PoolProc函数,进行池化计算,并更新输出结果
|
|
|
|
|
# elif modelobj.ObjType == 3:
|
|
|
|
|
# outputs = self.pool_forward(modelobj, outputs)
|
|
|
|
|
# # 如果是全连接层,就调用FullConnProc函数,根据权重矩阵和偏置向量进行全连接计算,并更新输出结果
|
|
|
|
|
# elif modelobj.ObjType == 4:
|
|
|
|
|
# outputs = self.fullconn_forward(modelobj, outputs)
|
|
|
|
|
# # 如果是非线性层,就调用NonlinearProc函数,根据激活函数进行非线性变换,并更新输出结果
|
|
|
|
|
# elif modelobj.ObjType == 5:
|
|
|
|
|
# outputs = self.nonlinear_forward(modelobj, outputs)
|
|
|
|
|
# # 如果是分类层,就调用ClassifierProc函数,根据权重矩阵和偏置向量进行分类计算,并更新输出结果
|
|
|
|
|
# elif modelobj.ObjType == 6:
|
|
|
|
|
# outputs = self.classifier_forward(modelobj, outputs)
|
|
|
|
|
#
|
|
|
|
|
# outputs = np.array(outputs) # 将输出结果列表转换为数组形式,方便后续处理
|
|
|
|
|
# return outputs # 返回输出结果数组
|
|
|
|
|
|
|
|
|
|
def flow(self):
|
|
|
|
|
"""
|
|
|
|
|
构建网络数据流列表。
|
|
|
|
|
Returns:
|
|
|
|
|
- list: 包含网络数据流的列表。
|
|
|
|
|
"""
|
|
|
|
|
data_flow = [] # 网络数据流列表
|
|
|
|
|
# 定义可能出现在数据流中的前向模块名称
|
|
|
|
|
forward_name = ['Conv', 'Pool', 'FullConn', 'Nonline', 'Classifier']
|
|
|
|
|
# 遍历所有模型连接
|
|
|
|
|
for model_conn in Net.AllModelConn:
|
|
|
|
|
start_data = re.sub(r'\d+', '', model_conn[2]) # 获取连接的起始模型名称(去除数字部分)
|
|
|
|
|
# 如果起始数据是 'DataSet',将连接的目标数据添加到数据流中
|
|
|
|
|
if start_data == 'DataSet':
|
|
|
|
|
data_flow.append(model_conn[3])
|
|
|
|
|
# 如果起始数据是前向模块名称之一
|
|
|
|
|
if start_data in forward_name:
|
|
|
|
|
# 如果数据流的最后一个元素与连接的起始模型名称相同,将连接的目标数据添加到数据流中
|
|
|
|
|
if data_flow[-1] == model_conn[2]:
|
|
|
|
|
data_flow.append(model_conn[3])
|
|
|
|
|
return data_flow
|
|
|
|
|
|
|
|
|
|
def query_obj(self, conn_name):
|
|
|
|
|
count = -1
|
|
|
|
|
for model_obj in Net.AllModelObj:
|
|
|
|
|
count += 1
|
|
|
|
|
if model_obj.ObjID == conn_name:
|
|
|
|
|
break
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
def conn_forward(self, modelobj, outputs):
|
|
|
|
|
ConvPara = modelobj.ParaString[1]
|
|
|
|
|
# 初始化一个空列表来存储池化后的结果
|
|
|
|
|
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 = modelobj.ConvProc(output, ConvPara)
|
|
|
|
|
new_outputs.append(output)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(output)
|
|
|
|
|
# 若为三维矩阵,则保持不变直接卷积处理
|
|
|
|
|
elif dim == 3:
|
|
|
|
|
output = modelobj.ConvProc(output, ConvPara)
|
|
|
|
|
new_outputs.append(output)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(output)
|
|
|
|
|
# 将new_outputs赋值给outputs
|
|
|
|
|
outputs = new_outputs
|
|
|
|
|
# 只打印第一张图片信息,检查是否正确计算
|
|
|
|
|
print("卷积处理结果:")
|
|
|
|
|
print(outputs[0])
|
|
|
|
|
print("")
|
|
|
|
|
return outputs
|
|
|
|
|
def pool_forward(self, modelobj, outputs):
|
|
|
|
|
PoolPara = modelobj.ParaString[1]
|
|
|
|
|
# 初始化一个空列表来存储池化后的结果
|
|
|
|
|
new_outputs = []
|
|
|
|
|
# 对outputs列表中的每个元素调用PoolProc函数,并将结果添加到new_outputs列表中
|
|
|
|
|
i = 0
|
|
|
|
|
for output in outputs:
|
|
|
|
|
output = modelobj.PoolProc(output, PoolPara)
|
|
|
|
|
new_outputs.append(output)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(output)
|
|
|
|
|
# 将new_outputs赋值给outputs
|
|
|
|
|
outputs = new_outputs
|
|
|
|
|
print("池化处理结果:")
|
|
|
|
|
print(outputs[0])
|
|
|
|
|
print("")
|
|
|
|
|
return outputs
|
|
|
|
|
def fullconn_forward(self, modelobj, outputs):
|
|
|
|
|
# 判断FullConn全连接是否存在参数
|
|
|
|
|
if len(modelobj.ParaString) < 2:
|
|
|
|
|
FullConnPara = modelobj.SetFullConnPara(outputs[0], modelobj)
|
|
|
|
|
no = self.query_obj(modelobj.ObjID)
|
|
|
|
|
Net.AllModelObj[no].ParaString.append(FullConnPara)
|
|
|
|
|
else:
|
|
|
|
|
FullConnPara = modelobj.ParaString[1]
|
|
|
|
|
# 初始化一个空列表来存储池化后的结果
|
|
|
|
|
new_outputs = []
|
|
|
|
|
i = 0
|
|
|
|
|
# 对outputs列表中的每个元素调用FullConnProc函数,并将结果添加到new_outputs列表中
|
|
|
|
|
for output in outputs:
|
|
|
|
|
output = modelobj.FullConnProc(output, FullConnPara)
|
|
|
|
|
new_outputs.append(output)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(output)
|
|
|
|
|
# 将new_outputs赋值给outputs
|
|
|
|
|
outputs = new_outputs
|
|
|
|
|
# 只打印第一张图片信息,检查是否正确计算
|
|
|
|
|
print("全连接处理结果:")
|
|
|
|
|
print(outputs[0])
|
|
|
|
|
print("")
|
|
|
|
|
return outputs
|
|
|
|
|
def nonlinear_forward(self, modelobj, outputs):
|
|
|
|
|
# 调用设置非线性参数的函数,获取非线性参数
|
|
|
|
|
NonLPara = modelobj.ParaString[1]
|
|
|
|
|
# 初始化一个空列表来存储池化后的结果
|
|
|
|
|
new_outputs = []
|
|
|
|
|
# 对outputs列表中的每个元素调用NonlinearProc函数,并将结果添加到new_outputs列表中
|
|
|
|
|
i = 0
|
|
|
|
|
for output in outputs:
|
|
|
|
|
output, output_derivative = modelobj.NonlinearProc(output, NonLPara)
|
|
|
|
|
new_outputs.append(output)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(output)
|
|
|
|
|
# 将new_outputs赋值给outputs
|
|
|
|
|
outputs = new_outputs
|
|
|
|
|
# 只打印第一张图片信息,检查是否正确计算
|
|
|
|
|
print("非线性处理结果:")
|
|
|
|
|
print(outputs[0])
|
|
|
|
|
print("")
|
|
|
|
|
return outputs
|
|
|
|
|
|
|
|
|
|
def classifier_forward(self, modelobj, outputs):
|
|
|
|
|
# 设置分类函数参数
|
|
|
|
|
ClassifyPara = modelobj.ParaString[1]
|
|
|
|
|
# 初始化一个空列表来存储池化后的结果
|
|
|
|
|
new_outputs = []
|
|
|
|
|
self.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 = modelobj.ClassifierProc(output, ClassifyPara)
|
|
|
|
|
new_outputs.append(outputx)
|
|
|
|
|
# 调用softmax函数,得到概率分布向量
|
|
|
|
|
prob = softmax(output)
|
|
|
|
|
# 将概率向量结果存储到列表
|
|
|
|
|
self.Prob_images.append(prob)
|
|
|
|
|
if i == 0:
|
|
|
|
|
i += 1
|
|
|
|
|
self.Batch_First.append(outputx)
|
|
|
|
|
# 将new_outputs赋值给outputs
|
|
|
|
|
outputs = new_outputs
|
|
|
|
|
# 只打印第一张图片信息,检查是否正确计算
|
|
|
|
|
print("输出示例概率向量:")
|
|
|
|
|
print(self.Prob_images[0:3])
|
|
|
|
|
print("")
|
|
|
|
|
return outputs
|
|
|
|
|
|
|
|
|
|
def back_propagation(self, loss, images):
|
|
|
|
|
for modelobj in Net.AllModelObj:
|
|
|
|
|
if modelobj.ObjType == 8:
|
|
|
|
|
self.conv_propagation('Conv1', loss, images)
|
|
|
|
|
if modelobj.ObjType == 9:
|
|
|
|
|
self.full_propagation('FullConn1', loss)
|
|
|
|
|
# 这里是需要连线才使用
|
|
|
|
|
# def back_propagation(self, loss, images):
|
|
|
|
|
# data_back = [] # 网络反向传播列表
|
|
|
|
|
# for model_conn in Net.data_flow:
|
|
|
|
|
# forword_name = ['AjConv', 'AjFullconn']
|
|
|
|
|
# start_data = re.sub(r'\d+', '', model_conn[2])
|
|
|
|
|
# if start_data == 'Error':
|
|
|
|
|
# data_back.append([model_conn[3]])
|
|
|
|
|
# if start_data in forword_name:
|
|
|
|
|
# for i in range(len(data_back)):
|
|
|
|
|
# if data_back[i][-1] == model_conn[2]:
|
|
|
|
|
# data_back[i].append(model_conn[3])
|
|
|
|
|
# for i in data_back:
|
|
|
|
|
# if 'AjConv' in i[0]:
|
|
|
|
|
# self.conv_propagation(i[1], loss, images)
|
|
|
|
|
# if 'AjFullconn' in i[0]:
|
|
|
|
|
# self.full_propagation(i[1], loss)
|
|
|
|
|
def conv_propagation(self, conv_name, loss, images):
|
|
|
|
|
positioning = 0
|
|
|
|
|
for i in range(len(Net.AllModelObj)):
|
|
|
|
|
if Net.AllModelObj[i].ObjID == conv_name:
|
|
|
|
|
positioning = i
|
|
|
|
|
if Net.AllModelObj[i].ObjType == 8:
|
|
|
|
|
modelobj = Net.AllModelObj[i]
|
|
|
|
|
ConvPara = Net.AllModelObj[positioning].ParaString[1]
|
|
|
|
|
AjConvPara = modelobj.SetAjConvPara(loss, ConvPara)
|
|
|
|
|
kernel, bias = modelobj.AjConvProc(images, AjConvPara)
|
|
|
|
|
Net.AllModelObj[positioning].ParaString[1]['kernel'] = kernel
|
|
|
|
|
Net.AllModelObj[positioning].ParaString[1]['bias'] = bias
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def full_propagation(self, full_name, loss):
|
|
|
|
|
# 初始化变量 positioning 为 0,用于记录 full_name 在 Net.AllModelObj 中的位置
|
|
|
|
|
positioning = 0
|
|
|
|
|
# 遍历 Net.AllModelObj 列表
|
|
|
|
|
for i in range(len(Net.AllModelObj)):
|
|
|
|
|
# 查找 full_name 在 Net.AllModelObj 中的位置
|
|
|
|
|
if Net.AllModelObj[i].ObjID == full_name:
|
|
|
|
|
positioning = i
|
|
|
|
|
if Net.AllModelObj[i].ObjType == 9:
|
|
|
|
|
# 获取当前模型对象
|
|
|
|
|
modelobj = Net.AllModelObj[i]
|
|
|
|
|
# 获取当前模型对象的参数字符串(FullConnPara 是参数的一部分)
|
|
|
|
|
FullConnPara = Net.AllModelObj[positioning].ParaString[1]
|
|
|
|
|
# 调用 SetAjFCPara 方法设置特定类型模型对象的参数
|
|
|
|
|
AjFCPara = modelobj.SetAjFCPara(loss, FullConnPara)
|
|
|
|
|
# 调用 AjFullconnProc 方法计算权重和偏置
|
|
|
|
|
weight, bias = modelobj.AjFullconnProc(AjFCPara)
|
|
|
|
|
# 将计算得到的权重和偏置更新到 Net.AllModelObj 中的相应位置
|
|
|
|
|
Net.AllModelObj[positioning].ParaString[1]['weights'] = weight
|
|
|
|
|
Net.AllModelObj[positioning].ParaString[1]['bias'] = bias
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def save_model(self): # 保存网络模型
|
|
|
|
|
if len(Net.AllModelConn) == 0:
|
|
|
|
|
messagebox.showerror("错误", "请连接图元!")
|
|
|
|
|
return
|
|
|
|
|
if len(Net.AllModelObj) == 0:
|
|
|
|
|
messagebox.showerror("错误", "请添加图元!")
|
|
|
|
|
return
|
|
|
|
|
if self.Flage==0:
|
|
|
|
|
messagebox.showerror("错误", "请先训练模型!")
|
|
|
|
|
return
|
|
|
|
|
Path = filedialog.askdirectory(title="请选择模型保存") + "/" # 训练集文件夹的位置
|
|
|
|
|
file_path = Path + 'AllModelConn.pkl'
|
|
|
|
|
# 检查文件是否存在
|
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
|
# 删除文件
|
|
|
|
|
os.remove(file_path)
|
|
|
|
|
with open(file_path, "wb") as f:
|
|
|
|
|
pickle.dump(Net.AllModelConn, f) # 保存模型
|
|
|
|
|
file_path = Path + 'AllModelObj.pkl'
|
|
|
|
|
# 检查文件是否存在
|
|
|
|
|
if os.path.exists(file_path):
|
|
|
|
|
# 删除文件
|
|
|
|
|
os.remove(file_path)
|
|
|
|
|
with open(file_path, "wb") as f:
|
|
|
|
|
pickle.dump(Net.AllModelObj, f) # 保存模型
|
|
|
|
|
# # 生成试验数据集,用于保存模型
|
|
|
|
|
# test_images = np.random.rand(100, 48, 48) * 0.01
|
|
|
|
|
# # 对试验数据集进行前向传播的计算
|
|
|
|
|
# print(forward_propagation(test_images))
|
|
|
|
|
# # 打开一个本地文件,用w模式或者wb模式
|
|
|
|
|
|
|
|
|
|
# # 遍历网络结构
|
|
|
|
|
# for layer in items3[1:]:
|
|
|
|
|
# count_model(layer)
|
|
|
|
|
# messagebox.showinfo("成功", "保存正确,恭喜!")
|
|
|
|
|
def load_model(self):
|
|
|
|
|
# 弹出文件对话框,允许用户选择.pkl文件
|
|
|
|
|
file_path1 = filedialog.askopenfilename(filetypes=[("选择网络对象总表", "*.pkl")])
|
|
|
|
|
# 判断用户是否选择了文件
|
|
|
|
|
if file_path1:
|
|
|
|
|
# 读取.pkl文件内容
|
|
|
|
|
with open(file_path1, 'rb') as file:
|
|
|
|
|
Net.AllModelObj = pickle.load(file)
|
|
|
|
|
print(Net.AllModelObj)
|
|
|
|
|
# 弹出文件对话框,允许用户选择.pkl文件
|
|
|
|
|
file_path2 = filedialog.askopenfilename(filetypes=[("网络连接对象总表", "*.pkl")])
|
|
|
|
|
# 判断用户是否选择了文件
|
|
|
|
|
if file_path2:
|
|
|
|
|
# 读取.pkl文件内容
|
|
|
|
|
with open(file_path2, 'rb') as file:
|
|
|
|
|
Net.AllModelConn = pickle.load(file)
|
|
|
|
|
print(Net.AllModelConn)
|
|
|
|
|
Net.creating_elements()
|
|
|
|
|
Net.ligature()
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
CNN = Interactive_CNN()
|
|
|
|
|
Net = Networking()
|
|
|
|
|
Mod = Model()
|
|
|
|
|
CNN.create_left_element(CNN.List_image)
|
|
|
|
|
CNN.button_link()
|
|
|
|
|
sys.stdout.write = CNN.redirector
|
|
|
|
|
CNN.Root.mainloop()
|