哈工大机器学习实验一(基于GUI及多线程的机器学习随时调参工具)

一个旁听生做机器学习实验一(多项式拟合正弦曲线)有感,想做一个方便调参的工具来熟悉一下GUI和多线程的基本用法

运行结果如下

代码是两个py文件,放在同一目录下,运行main.py即可

main.py如下

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import numpy as np
import numpy as np
from threading import Thread
from lab1 import *

plt.rcParams["font.sans-serif"] = ["SimHei"]  # 用来正常显示中文标签SimHei
plt.rcParams["axes.unicode_minus"] = False  # 用来正常显示负号

# 默认参数
params = {
    "train_num": 10,
    "validation_num": 100,
    "test_num": 1000,
    "order": 7,
    "x_left": 0,
    "x_right": 1,
    "noise_scale": 0.25,
    "learning_rate": 0.01,
    "delta": 1e-6,
    "lambd": np.power(np.e, -7),
}


class UpdatePlotThread(Thread):
    def __init__(self, button):
        super(UpdatePlotThread, self).__init__()
        self.button = button

    def run(self):
        update_plot()
        self.button.config(state="normal")


class ComputeThread(Thread):
    def __init__(self, func, *args, **kwargs):
        super(ComputeThread, self).__init__()
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.result = None

    def run(self):
        self.result = self.func(*self.args, **self.kwargs)
        print(self.func, self.args, self.kwargs)

    def get_result(self):
        return self.result


def update_plot():
    button.config(state="disabled")
    # 读入数据
    train_num = int(train_num_var.get())
    validation_num = int(validation_num_var.get())
    test_num = int(test_num_var.get())
    order = int(order_var.get())
    x_left = float(x_left_var.get())
    x_right = float(x_right_var.get())
    noise_scale = float(noise_scale_var.get())
    learning_rate = float(learning_rate_var.get())
    delta = float(delta_var.get())
    lambd = float(lambd_var.get())

    # 初始化数据
    text.delete("1.0", tk.END)  # 清空文本框
    # 训练集
    text.insert(tk.END, "初始化数据...\n\n")
    train_data = get_data((x_left, x_right), train_num, base_func, noise_scale)
    x_train = train_data["X"]
    y_train = train_data["Y"]
    # 验证集
    x_validation = np.linspace(x_left, x_right, validation_num)
    y_validation = base_func(x_validation)
    # 测试集
    x_test = np.linspace(x_left, x_right, test_num)
    y_test = base_func(x_test)
    text.insert(tk.END, "数据初始化完毕\n\n")

    # 启动四个计算线程
    thread1 = ComputeThread(
        predict_analytical_without_penalty,
        x_train,
        y_train,
        x_test,
        x_validation,
        y_validation,
        order,
    )
    thread2 = ComputeThread(
        predict_analytical_with_penalty,
        x_train,
        y_train,
        x_test,
        x_validation,
        y_validation,
        order,
        lambd_penalty=lambd,
    )
    thread3 = ComputeThread(
        predict_gradient_descent,
        x_train,
        y_train,
        x_test,
        lambd,
        learning_rate,
        order,
        delta,
    )
    thread4 = ComputeThread(
        predict_conjugate_gradient,
        x_train,
        y_train,
        x_test,
        lambd,
        order,
        delta,
    )
    text.insert(tk.END, "计算不带惩罚项的解析解...\n\n")
    text.insert(tk.END, "计算带惩罚项的解析解...\n\n")
    text.insert(tk.END, "利用验证集来计算lambd...\n\n")
    text.insert(tk.END, "计算梯度下降法...\n\n")
    text.insert(tk.END, "计算共轭梯度法...\n\n")
    # 启动线程
    threads = [thread1, thread2, thread3, thread4]
    # threads = [thread4]
    for thread in threads:
        thread.start()

    # 等待所有线程完成
    for thread in threads:
        thread.join()
    button.config(state="normal")
    # 获取计算结果
    result1 = thread1.get_result()
    result2 = thread2.get_result()
    result3 = thread3.get_result()
    result4 = thread4.get_result()
    text.insert(tk.END, "不带惩罚项的解析解计算完毕\n\n")
    text.insert(tk.END, f"lambd={result2[2]} , MSE={result2[3]}\n\n")
    text.insert(tk.END, "带惩罚项的解析解计算完毕\n\n")
    text.insert(tk.END, "梯度下降法计算完毕\n\n")
    text.insert(tk.END, f"梯度下降法迭代次数为{result3[0]}\n\n")
    text.insert(tk.END, "共轭梯度法计算完毕\n\n")
    text.insert(tk.END, f"共轭梯度法迭代次数为{result4[0]}\n\n")

    # 更新 UI 和绘制图形
    update_ui(
        x_train, y_train, x_test, y_test, result1[0], "不带惩罚项的解析解", ax1, canvas1
    )
    update_ui(
        x_train, y_train, x_test, y_test, result2[0], "带惩罚项的解析解", ax2, canvas2
    )
    update_ui(x_train, y_train, x_test, y_test, result3[1], "梯度下降法", ax3, canvas3)
    update_ui(x_train, y_train, x_test, y_test, result4[1], "共轭梯度法", ax4, canvas4)


def update_ui(x_train, y_train, x_test, y_test, y_pred, title, ax, canvas):
    ax.clear()
    ax.scatter(x_train, y_train, label="训练集", color="white", edgecolors="darkblue")
    ax.plot(x_test, y_test, linewidth=2, label="测试集", color="gray", linestyle="-.")
    ax.plot(x_test, y_pred, linewidth=2, label="预测值", color="green", linestyle="-")
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_title(title)
    ax.legend()
    canvas.draw()


if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("1800x800")
    root.title("多项式拟合正弦曲线")
    for i in range(30):  # 有30列
        root.grid_columnconfigure(i, weight=1)
    for i in range(30):  # 有30行
        root.grid_rowconfigure(i, weight=1)

    # 创建参数输入框
    labels = [
        "train_num",
        "validation_num",
        "test_num",
        "order",
        "x_left",
        "x_right",
        "noise_scale",
        "learning_rate",
        "delta",
        "lambd",
    ]
    vars = {}
    for i, label in enumerate(labels):
        tk.Label(root, text=label).grid(row=i, column=15, sticky="e", padx=5, pady=5)
        vars[label] = tk.StringVar(value=str(params[label]))
        tk.Entry(root, textvariable=vars[label]).grid(
            row=i, column=16, sticky="w", padx=5, pady=5
        )

    train_num_var = vars["train_num"]
    validation_num_var = vars["validation_num"]
    test_num_var = vars["test_num"]
    order_var = vars["order"]
    x_left_var = vars["x_left"]
    x_right_var = vars["x_right"]
    noise_scale_var = vars["noise_scale"]
    learning_rate_var = vars["learning_rate"]
    delta_var = vars["delta"]
    lambd_var = vars["lambd"]

    # 文本框
    text = tk.Text(root, height=10, width=40)
    text.grid(row=3, column=22, columnspan=2, rowspan=2, sticky="nsew")

    # 绘制图形的初始设置
    fig1, ax1 = plt.subplots(figsize=(4, 3))
    canvas1 = FigureCanvasTkAgg(fig1, master=root)  # 创建画布对象
    canvas_widget1 = canvas1.get_tk_widget()
    canvas_widget1.grid(row=2, column=4, columnspan=2, rowspan=4, sticky="nsew")

    fig2, ax2 = plt.subplots(figsize=(4, 3))
    canvas2 = FigureCanvasTkAgg(fig2, master=root)  # 创建画布对象
    canvas_widget2 = canvas2.get_tk_widget()
    canvas_widget2.grid(row=2, column=6, columnspan=2, rowspan=4, sticky="nsew")

    fig3, ax3 = plt.subplots(figsize=(4, 3))
    canvas3 = FigureCanvasTkAgg(fig3, master=root)  # 创建画布对象
    canvas_widget3 = canvas3.get_tk_widget()
    canvas_widget3.grid(row=6, column=4, columnspan=2, rowspan=4, sticky="nsew")

    fig4, ax4 = plt.subplots(figsize=(4, 3))
    canvas4 = FigureCanvasTkAgg(fig4, master=root)  # 创建画布对象
    canvas_widget4 = canvas4.get_tk_widget()
    canvas_widget4.grid(row=6, column=6, columnspan=2, rowspan=4, sticky="nsew")

    # 创建更新按钮
    button = tk.Button(
        root, text="Update Plot", command=lambda: UpdatePlotThread(button).start()
    )
    button.grid(row=len(labels), column=15, columnspan=2, sticky="ew", padx=5, pady=5)

    root.mainloop()

 lab1.py如下

import numpy as np
import pandas as pd


# 基础函数*****************************************************************************
def base_func(x):
    """
    基函数 y = sin(2 * pi * x)
    """
    return np.sin(2 * np.pi * x)


def get_data(
    x_range: tuple[float, float] = (0, 1),
    sample_num: int = 10,
    base_func=base_func,
    noise_scale=0.25,
) -> pd.DataFrame:
    """
    生成数据 加入噪声
    """
    X = np.linspace(
        x_range[0], x_range[1], num=sample_num
    )  # np.linspace用于生成一个等间距的数值序列
    Y = base_func(X) + np.random.normal(loc=0, scale=noise_scale, size=X.shape)
    data = pd.DataFrame(data=np.dstack((X, Y))[0], columns=["X", "Y"])
    """np.dstack用于沿着数组深度来堆叠多个二维数组

        补充:
        X = np.array([1,2,3]).shape-------(3,)
        X = np.array([[1, 2, 3]]).shape-------(1,3)
        X = np.array([[1],[2],[3]]).shape-------(3,1)

        举例:
        二维数组
        a = np.array([[1, 2], [3, 4]])
        b = np.array([[5, 6], [7, 8]])
        result = np.dstack((a, b))
        print(result)
        print(result.shape)
        得到
        [  [  [1 5][2 6]  ]  [  [3 7][4 8]  ]  ]
        (2, 2, 2)

        一维数组
        X = np.array([1, 2, 3])
        Y = np.array([4, 5, 6])
        result = np.dstack((X, Y))
        print(result)
        print(result.shape)
        得到
        [  [  [1 4][2 5][3 6]  ]  ]
        (1, 3, 2)
    """
    return data


def get_params(X, T, lambda_penalty=0):
    """
    解析法
    """
    return np.linalg.pinv(X.T @ X + lambda_penalty * np.identity(X.shape[1])) @ X.T @ T
    # np.linalg.pinv用于计算矩阵的伪逆
    # np.identity用于创建单位矩阵


def calc_RMSE(y_true, y_pred):
    return np.sqrt(np.mean(np.square(y_true - y_pred)))


def calc_loss(X, T, lambda_penalty, W):
    """
    计算损失函数
    """
    return 0.5 * np.mean((X @ W - T).T @ (X @ W - T) + lambda_penalty * W.T * W)


def calc_derivative(X, T, lambda_penalty, W):
    """
    计算损失函数对参数的导数
    """
    return X.T @ X @ W - X.T @ T + lambda_penalty * W


def gradient_descent(X, T, lambda_penalty, W, learning_rate=0.1, delta=1e-6):
    """
    梯度下降法
    """
    loss_init = calc_loss(X, T, lambda_penalty, W)
    k = 0
    while True:
        W = W - learning_rate * calc_derivative(X, T, lambda_penalty, W)
        loss = calc_loss(X, T, lambda_penalty, W)
        if np.abs(loss - loss_init) < delta:
            break
        else:
            k += 1
            if loss > loss_init:  # 说明学习率可能太大,导致步长过大
                learning_rate *= 0.5
            loss_init = loss
    return k, W


def get_x_series(x, order) -> list[float]:
    """return 1, x^1, x^2,..., x^n, n = order"""
    series = [1.0]
    for _ in range(order):
        series.append(series[-1] * x)
    return series


def get_x_matrix(x_vec, order: int = 1) -> list[list[float]]:
    x_matrix = []
    for i in range(len(x_vec)):
        x_matrix.append(get_x_series(x_vec[i], order))
    return np.asarray(x_matrix)


def predict_analytical_without_penalty(
    x_train, y_train, x, x_validation, y_validation, order, lambd_penalty=0
):
    W = get_params(get_x_matrix(x_train, order=order), y_train, lambd_penalty)
    return np.dot(get_x_matrix(x, order=order), W), W


def predict_analytical_with_penalty(
    x_train, y_train, x, x_validation, y_validation, order, lambd_penalty
):
    # text.insert(tk.END, "利用验证集来计算lambd...\n\n")
    min_lambda, min_mse = calc_lambd(
        x_train, y_train, x_validation, y_validation, order
    )
    # text.insert(tk.END, f"lambd={min_lambda} , MSE={min_mse}\n\n")
    W = get_params(get_x_matrix(x_train, order=order), y_train, min_lambda)
    return np.dot(get_x_matrix(x, order=order), W), W, min_lambda, min_mse


def calc_lambd(x_train, y_train, x_validation, y_validation, order):
    error_ln_lambda = []
    for i in range(-50, 0):
        y, w = predict_analytical_without_penalty(
            x_train,
            y_train,
            x_validation,
            x_validation,
            y_validation,
            order,
            lambd_penalty=np.exp(i),
        )
        error_ln_lambda.append([i, calc_RMSE(y_validation, y), "validation"])
    # 创建数据框
    data = pd.DataFrame(error_ln_lambda, columns=["lambda", "MSE", "type"])
    # 找到MSE最小的点
    min_mse_row = data.loc[data["MSE"].idxmin()]
    # 获取最小的MSE对应的参数和MSE值
    min_lambda = np.exp(min_mse_row["lambda"])
    min_mse = min_mse_row["MSE"]
    # 返回最小的MSE对应的参数和MSE值
    return min_lambda, min_mse


def predict_gradient_descent(
    x_train, y_train, x, lambd_penalty, learning_rate, order, delta
):
    # text.insert(tk.END, "计算梯度下降法...\n\n")
    k, w = gradient_descent(
        get_x_matrix(x_train, order),
        y_train,
        lambd_penalty,
        np.zeros(order + 1),
        learning_rate,
        delta,
    )
    # text.insert(tk.END, f"迭代次数为{k}\n\n")
    # text.insert(tk.END, "梯度下降法计算完毕\n\n")
    return k, np.dot(get_x_matrix(x, order), w), w


def predict_conjugate_gradient(x_train, y_train, x, lambda_penalty, order, delta):
    A, x_0, b = switch_deri_func_for_conjugate_gradient(
        get_x_matrix(x_train, order),
        y_train,
        lambda_penalty,
        w=np.zeros(order + 1),
    )
    k, w = conjugate_gradient(A, x_0, b, delta)
    return k, np.dot(get_x_matrix(x, order), w), w


def switch_deri_func_for_conjugate_gradient(x_matrix, t, lambda_penalty, w):
    A = x_matrix.T @ x_matrix + lambda_penalty * np.identity(len(x_matrix.T))
    b = x_matrix.T @ t
    return A, w, b


def conjugate_gradient(A, x, b, delta=1e-6):
    """
    求解Ax=b
    """
    r_0 = b - A @ x
    p = r_0
    k = 0
    while True:
        alpha = (r_0.T @ r_0) / (p.T @ A @ p)
        x = x + alpha * p
        r = r_0 - alpha * A @ p
        if r_0.T @ r_0 < delta:
            break
        beta = (r.T @ r) / (r_0.T @ r_0)
        p = r + beta * p
        r_0 = r
        k += 1
    return k, x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值