Python实例题:基于 KNN 算法的手写数字识别

目录

Python实例题

题目

要求:

解题思路:

代码实现:

Python实例题

题目

基于 KNN 算法的手写数字识别

要求

  • 实现一个基于 K-Nearest Neighbors (KNN) 算法的手写数字识别系统。
  • 支持以下功能:
    • 使用 MNIST 数据集训练和测试模型
    • 实现 KNN 分类算法
    • 可视化手写数字样本
    • 评估模型性能(准确率、混淆矩阵等)
  • 添加用户交互界面,允许用户绘制数字并进行识别。

解题思路

  • 使用 sklearn 加载 MNIST 数据集。
  • 实现 KNN 算法进行分类。
  • 使用 matplotlib 可视化样本和结果。
  • 创建简单的 GUI 界面供用户交互。

代码实现

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix, classification_report
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk

class KNNDigitRecognizer:
    def __init__(self, n_neighbors=5):
        self.n_neighbors = n_neighbors
        self.model = KNeighborsClassifier(n_neighbors=n_neighbors)
        self.X_train = None
        self.X_test = None
        self.y_train = None
        self.y_test = None
    
    def load_data(self):
        """加载MNIST数据集"""
        print("正在加载MNIST数据集...")
        mnist = fetch_openml('mnist_784', version=1)
        X, y = mnist['data'], mnist['target']
        
        # 将数据分为训练集和测试集
        self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(
            X, y, test_size=0.2, random_state=42
        )
        
        print(f"训练集大小: {len(self.X_train)}")
        print(f"测试集大小: {len(self.X_test)}")
    
    def train(self):
        """训练KNN模型"""
        if self.X_train is None:
            self.load_data()
        
        print(f"正在训练KNN模型 (k={self.n_neighbors})...")
        self.model.fit(self.X_train, self.y_train)
        print("模型训练完成")
    
    def evaluate(self):
        """评估模型性能"""
        if self.model is None:
            print("请先训练模型")
            return
        
        print("正在评估模型性能...")
        accuracy = self.model.score(self.X_test, self.y_test)
        print(f"模型准确率: {accuracy:.4f}")
        
        # 预测测试集
        y_pred = self.model.predict(self.X_test)
        
        # 打印混淆矩阵和分类报告
        print("\n混淆矩阵:")
        print(confusion_matrix(self.y_test, y_pred))
        
        print("\n分类报告:")
        print(classification_report(self.y_test, y_pred))
    
    def predict(self, digit_image):
        """预测单个数字图像"""
        if self.model is None:
            print("请先训练模型")
            return None
        
        # 重塑图像为一维数组
        digit_image = digit_image.reshape(1, -1)
        
        # 预测
        prediction = self.model.predict(digit_image)
        return prediction[0]
    
    def visualize_sample(self, index, dataset='train'):
        """可视化样本"""
        if dataset == 'train':
            image = self.X_train[index].reshape(28, 28)
            label = self.y_train[index]
        else:
            image = self.X_test[index].reshape(28, 28)
            label = self.y_test[index]
        
        plt.figure(figsize=(2, 2))
        plt.imshow(image, cmap='gray')
        plt.title(f"标签: {label}")
        plt.axis('off')
        plt.show()

class DigitCanvas:
    def __init__(self, master, width=280, height=280):
        self.master = master
        self.width = width
        self.height = height
        self.pixel_size = 10
        
        # 创建画布
        self.canvas = tk.Canvas(
            master, 
            width=width, 
            height=height, 
            bg='black',
            highlightthickness=1,
            highlightbackground='gray'
        )
        self.canvas.pack()
        
        # 绑定鼠标事件
        self.canvas.bind("", self.paint)
        self.canvas.bind("", self.start_paint)
        
        # 初始化画布
        self.clear_canvas()
    
    def clear_canvas(self):
        """清空画布"""
        self.canvas.delete("all")
        self.pixels = [[0 for _ in range(28)] for _ in range(28)]
    
    def start_paint(self, event):
        """开始绘画"""
        x, y = event.x, event.y
        self.paint(event)
    
    def paint(self, event):
        """绘画"""
        x, y = event.x, event.y
        
        # 计算像素坐标
        pixel_x = min(x // self.pixel_size, 27)
        pixel_y = min(y // self.pixel_size, 27)
        
        # 绘制像素
        if 0 <= pixel_x < 28 and 0 <= pixel_y < 28:
            self.pixels[pixel_y][pixel_x] = 255
            self.canvas.create_rectangle(
                pixel_x * self.pixel_size,
                pixel_y * self.pixel_size,
                (pixel_x + 1) * self.pixel_size,
                (pixel_y + 1) * self.pixel_size,
                fill='white',
                outline=''
            )
    
    def get_image(self):
        """获取图像数据"""
        return np.array(self.pixels).reshape(784)

def main():
    # 创建KNN识别器
    recognizer = KNNDigitRecognizer(n_neighbors=5)
    
    # 训练模型
    recognizer.train()
    
    # 评估模型
    recognizer.evaluate()
    
    # 创建GUI
    root = tk.Tk()
    root.title("手写数字识别")
    root.geometry("500x400")
    
    # 创建左侧画布区域
    left_frame = ttk.Frame(root)
    left_frame.pack(side=tk.LEFT, padx=10, pady=10, fill=tk.BOTH, expand=True)
    
    canvas_label = ttk.Label(left_frame, text="请在画布上绘制数字 (0-9):")
    canvas_label.pack(pady=(0, 5))
    
    digit_canvas = DigitCanvas(left_frame)
    
    # 创建按钮区域
    button_frame = ttk.Frame(left_frame)
    button_frame.pack(pady=10)
    
    def clear_canvas():
        digit_canvas.clear_canvas()
        result_label.config(text="识别结果: ?")
    
    def recognize_digit():
        image = digit_canvas.get_image()
        prediction = recognizer.predict(image)
        result_label.config(text=f"识别结果: {prediction}")
    
    clear_button = ttk.Button(button_frame, text="清空", command=clear_canvas)
    clear_button.pack(side=tk.LEFT, padx=5)
    
    recognize_button = ttk.Button(button_frame, text="识别", command=recognize_digit)
    recognize_button.pack(side=tk.LEFT, padx=5)
    
    # 创建右侧结果区域
    right_frame = ttk.Frame(root)
    right_frame.pack(side=tk.RIGHT, padx=10, pady=10, fill=tk.BOTH, expand=True)
    
    result_label = ttk.Label(right_frame, text="识别结果: ?", font=("Arial", 24))
    result_label.pack(pady=50)
    
    info_label = ttk.Label(right_frame, text="提示:\n使用鼠标在左侧画布上绘制数字\n然后点击'识别'按钮查看结果", justify=tk.LEFT)
    info_label.pack(pady=20)
    
    # 运行GUI
    root.mainloop()

if __name__ == "__main__":
    main()

你可能感兴趣的:(Python实例题:基于 KNN 算法的手写数字识别)