屏幕截图 2025-12-26 143720

屏幕截图 2025-12-26 143656

点击查看代码
# 导入基础库(初学者易安装,功能满足需求)
import numpy as np
import matplotlib.pyplot as plt
import random

# --------------------------
# 步骤1:生成模拟手写汉字(一到十)数据集(模拟MNIST格式)
# --------------------------
def generate_chinese_char_data():
    # 汉字一到十对应标签0-9
    char_names = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]
    n_classes = 10  # 10个汉字类别
    n_samples = 800  # 总样本数(适中,训练速度快)
    img_size = 28  # 图片尺寸28*28(和MNIST一致,便于理解)

    # 生成28*28像素特征,添加类别差异和少量噪声
    X = np.random.rand(n_samples, img_size, img_size) * 0.5  # 基础噪声
    y = np.zeros(n_samples, dtype=int)

    for i in range(n_samples):
        label = i % n_classes  # 均匀分配标签
        y[i] = label
        # 为每个汉字添加独特的"笔画"特征(区分不同类别)
        if label == 0:  # 一:横向直线
            X[i, 14, 5:23] = 1.0
        elif label == 1:  # 二:两条横线
            X[i, 10, 5:23] = 1.0
            X[i, 18, 5:23] = 1.0
        elif label == 2:  # 三:三条横线
            X[i, 8, 5:23] = 1.0
            X[i, 14, 5:23] = 1.0
            X[i, 20, 5:23] = 1.0
        elif label == 3:  # 四:竖+横+竖
            X[i, 6:22, 8] = 1.0
            X[i, 10, 8:20] = 1.0
            X[i, 18, 15:20] = 1.0
        elif label == 4:  # 五:横+竖+横
            X[i, 8, 5:23] = 1.0
            X[i, 12:20, 14] = 1.0
            X[i, 20, 5:23] = 1.0
        elif label == 5:  # 六:点+横+竖
            X[i, 7, 10] = 1.0
            X[i, 10, 8:20] = 1.0
            X[i, 12:20, 10] = 1.0
        elif label == 6:  # 七:横+竖弯钩
            X[i, 10, 5:15] = 1.0
            X[i, 12:20, 15] = 1.0
        elif label == 7:  # 八:撇+捺
            X[i, 10:18, 10:18] = 1.0
            X[i, 10:18, 18:10:-1] = 1.0
        elif label == 8:  # 九:撇+横折弯钩
            X[i, 10:16, 10:16] = 1.0
            X[i, 16, 10:20] = 1.0
            X[i, 12:16, 20] = 1.0
        elif label == 9:  # 十:横+竖
            X[i, 14, 5:23] = 1.0
            X[i, 5:23, 14] = 1.0

    return X, y, char_names, img_size

# 生成数据并打印基本信息
X, y, char_names, img_size = generate_chinese_char_data()
print(f"数据集形状:特征 {X.shape},标签 {y.shape}")
print(f"汉字类别:{char_names}")

# --------------------------
# 步骤2:数据预处理(对齐CSDN手写数字流程)
# --------------------------
# 2.1 手动划分训练集(70%)和测试集(30%)(无需第三方库,易懂)
def split_train_test(X, y, test_ratio=0.3, random_seed=42):
    random.seed(random_seed)
    n_samples = len(X)
    # 生成随机索引
    indices = list(range(n_samples))
    random.shuffle(indices)
    # 划分索引
    test_size = int(n_samples * test_ratio)
    test_indices = indices[:test_size]
    train_indices = indices[test_size:]
    # 返回划分后的数据
    X_train = X[train_indices]
    X_test = X[test_indices]
    y_train = y[train_indices]
    y_test = y[test_indices]
    return X_train, X_test, y_train, y_test

# 扁平化特征(用于Sigmoid和SVM,CNN需要28*28二维特征)
X_flat = X.reshape(len(X), -1)  # (800, 784)
# 划分数据
X_train_cnn, X_test_cnn, y_train, y_test = split_train_test(X, y)
X_train_flat, X_test_flat, _, _ = split_train_test(X_flat, y)

# 2.2 特征归一化(缩放到0-1之间,提升模型效果)
def min_max_normalize(X_train, X_test):
    # 按特征计算最大值和最小值
    max_val = X_train.max(axis=0)
    min_val = X_train.min(axis=0)
    # 避免除以0
    max_val[max_val == min_val] = 1
    # 归一化
    X_train_norm = (X_train - min_val) / (max_val - min_val)
    X_test_norm = (X_test - min_val) / (max_val - min_val)
    return X_train_norm, X_test_norm

# 归一化扁平化特征
X_train_flat_norm, X_test_flat_norm = min_max_normalize(X_train_flat, X_test_flat)

# 2.3 标签one-hot编码(用于CNN和Sigmoid的多分类输出)
def one_hot_encode(y, n_classes):
    n_samples = len(y)
    one_hot = np.zeros((n_samples, n_classes))
    for i in range(n_samples):
        one_hot[i, y[i]] = 1
    return one_hot

y_train_onehot = one_hot_encode(y_train, 10)
y_test_onehot = one_hot_encode(y_test, 10)

# 为CNN添加通道维度(灰度图,通道数=1)
X_train_cnn = np.expand_dims(X_train_cnn, axis=-1)
X_test_cnn = np.expand_dims(X_test_cnn, axis=-1)
print(f"CNN训练集形状:{X_train_cnn.shape},Sigmoid/SVM训练集形状:{X_train_flat_norm.shape}")

# --------------------------
# 步骤3:手动实现核心工具(激活函数、损失函数等)
# --------------------------
# 激活函数:Sigmoid(前向+反向)
def sigmoid(x, derivative=False):
    if derivative:
        return sigmoid(x) * (1 - sigmoid(x))
    # 防止指数爆炸
    x = np.clip(x, -500, 500)
    return 1 / (1 + np.exp(-x))

# 激活函数:ReLU(前向+反向,用于CNN)
def relu(x, derivative=False):
    if derivative:
        return np.where(x > 0, 1, 0)
    return np.maximum(0, x)

# 激活函数:Softmax(前向,用于多分类输出)
def softmax(x):
    # 数值稳定:减去每行最大值
    x_max = np.max(x, axis=1, keepdims=True)
    exp_x = np.exp(x - x_max)
    return exp_x / np.sum(exp_x, axis=1, keepdims=True)

# 损失函数:交叉熵损失(多分类)
def cross_entropy(y_true, y_pred):
    y_pred = np.clip(y_pred, 1e-10, 1 - 1e-10)  # 防止log(0)
    return -np.mean(np.sum(y_true * np.log(y_pred), axis=1))

# --------------------------
# 步骤4:构建并训练三种模型(修正维度不匹配问题)
# --------------------------
# 4.1 模型1:CNN + Softmax(参考MNIST CNN结构,修复维度问题)
class SimpleCNN:
    def __init__(self, img_size, n_classes=10, lr=0.001):
        self.img_size = img_size
        self.n_classes = n_classes
        self.lr = lr  # 学习率
        # 初始化权重和偏置(先不初始化W3/W4,在第一次前向传播时动态初始化)
        # 卷积层1:32个3*3卷积核,添加padding=1保持尺寸
        self.W1 = np.random.randn(3, 3, 1, 32) * 0.01
        self.b1 = np.zeros(32)
        # 卷积层2:64个3*3卷积核,添加padding=1保持尺寸
        self.W2 = np.random.randn(3, 3, 32, 64) * 0.01
        self.b2 = np.zeros(64)
        # 全连接层(动态初始化,解决维度不匹配)
        self.W3 = None
        self.b3 = None
        self.W4 = None
        self.b4 = None

    # 带padding的卷积运算(padding=1,保持特征尺寸)
    def conv(self, X, W, b, padding=1, stride=1):
        # 补零
        X_padded = np.pad(X, ((0,0), (padding,padding), (padding,padding), (0,0)), mode='constant')
        n_samples, h, w, c = X_padded.shape
        f_h, f_w, f_c, out_c = W.shape
        # 计算输出尺寸
        out_h = (h - f_h) // stride + 1
        out_w = (w - f_w) // stride + 1
        output = np.zeros((n_samples, out_h, out_w, out_c))
        # 卷积计算
        for i in range(out_h):
            for j in range(out_w):
                patch = X_padded[:, i:i+f_h, j:j+f_w, :]
                for k in range(out_c):
                    output[:, i, j, k] = np.sum(patch * W[:, :, :, k], axis=(1,2,3)) + b[k]
        return output

    # 简化最大池化
    def max_pool(self, X, pool_size=2, stride=2):
        n_samples, h, w, c = X.shape
        out_h = (h - pool_size) // stride + 1
        out_w = (w - pool_size) // stride + 1
        output = np.zeros((n_samples, out_h, out_w, c))
        for i in range(out_h):
            for j in range(out_w):
                patch = X[:, i*stride:i*stride+pool_size, j*stride:j*stride+pool_size, :]
                output[:, i, j, :] = np.max(patch, axis=(1,2))
        return output

    # 前向传播(动态初始化全连接层权重,解决维度不匹配)
    def forward(self, X):
        # 卷积1 + ReLU + 池化(padding=1,保持尺寸)
        self.conv1 = self.conv(X, self.W1, self.b1, padding=1)
        self.relu1 = relu(self.conv1)
        self.pool1 = self.max_pool(self.relu1)
        # 卷积2 + ReLU + 池化(padding=1,保持尺寸)
        self.conv2 = self.conv(self.pool1, self.W2, self.b2, padding=1)
        self.relu2 = relu(self.conv2)
        self.pool2 = self.max_pool(self.relu2)
        # 扁平化
        self.flat = self.pool2.reshape(len(self.pool2), -1)
        # 动态初始化全连接层(仅第一次前向传播时执行)
        if self.W3 is None:
            flat_dim = self.flat.shape[1]  # 获取实际扁平化维度
            self.W3 = np.random.randn(flat_dim, 64) * 0.01
            self.b3 = np.zeros(64)
            self.W4 = np.random.randn(64, self.n_classes) * 0.01
            self.b4 = np.zeros(self.n_classes)
        # 全连接1 + ReLU
        self.fc1 = np.dot(self.flat, self.W3) + self.b3
        self.relu3 = relu(self.fc1)
        # 全连接2 + Softmax
        self.fc2 = np.dot(self.relu3, self.W4) + self.b4
        self.y_pred = softmax(self.fc2)
        return self.y_pred

    # 反向传播(简化版,仅更新权重)
    def backward(self, X, y_true):
        batch_size = len(X)
        # 输出层梯度
        delta_fc2 = (self.y_pred - y_true) / batch_size
        # 更新全连接2
        self.W4 -= self.lr * np.dot(self.relu3.T, delta_fc2)
        self.b4 -= self.lr * np.sum(delta_fc2, axis=0)
        # 全连接1梯度
        delta_fc1 = np.dot(delta_fc2, self.W4.T) * relu(self.fc1, derivative=True)
        self.W3 -= self.lr * np.dot(self.flat.T, delta_fc1)
        self.b3 -= self.lr * np.sum(delta_fc1, axis=0)

    # 训练模型
    def train(self, X_train, y_train, epochs=8, batch_size=32):
        print("\n=== 训练 CNN+Softmax 模型 ===")
        for epoch in range(epochs):
            loss_list = []
            # 批量训练
            for i in range(0, len(X_train), batch_size):
                batch_X = X_train[i:i+batch_size]
                batch_y = y_train[i:i+batch_size]
                # 前向传播
                y_pred = self.forward(batch_X)
                # 计算损失
                loss = cross_entropy(batch_y, y_pred)
                loss_list.append(loss)
                # 反向传播
                self.backward(batch_X, batch_y)
            # 打印每轮损失
            avg_loss = np.mean(loss_list)
            print(f"Epoch {epoch+1}/{epochs},平均损失:{avg_loss:.4f}")

    # 预测
    def predict(self, X):
        y_pred = self.forward(X)
        return np.argmax(y_pred, axis=1)
    print(f"24信计1麦麦提力 2024310143034")<details>
<summary>点击查看代码</summary>

import torch
import torch.nn as nn
import torch.nn.functional as F

第一步:先定义 SimpleCNN 类(必须在调用之前)

class SimpleCNN(nn.Module):
def init(self, img_size, in_channels=3, num_classes=10):
super(SimpleCNN, self).init()
self.img_size = img_size # 图像尺寸(假设是正方形,如 224 表示 224x224)

    # 卷积层1:提取基础特征
    self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=32, kernel_size=3, padding=1)
    self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)  # 下采样,缩小图像尺寸

    # 卷积层2:提取更复杂的特征
    self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
    self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

    # 计算全连接层的输入维度(经过两次池化,尺寸缩小为原来的 1/4)
    self.fc1_input_dim = 64 * (img_size // 4) * (img_size // 4)

    # 全连接层:分类/回归
    self.fc1 = nn.Linear(self.fc1_input_dim, 512)
    self.fc2 = nn.Linear(512, num_classes)

    # Dropout:防止过拟合
    self.dropout = nn.Dropout(0.5)

def forward(self, x):
    # 卷积层1 + 激活 + 池化
    x = self.pool1(F.relu(self.conv1(x)))
    # 卷积层2 + 激活 + 池化
    x = self.pool2(F.relu(self.conv2(x)))
    # 展平:将 4D 特征图 (batch, channels, h, w) 转为 2D (batch, features)
    x = x.view(-1, self.fc1_input_dim)
    # 全连接层1 + 激活 + Dropout
    x = self.dropout(F.relu(self.fc1(x)))
    # 全连接层2:输出结果
    x = self.fc2(x)
    return x

第二步:调用 SimpleCNN(此时类已定义,不会报错)

if name == "main":
# 定义图像尺寸(例如 224x224)
img_size = 224
# 创建模型实例
cnn_model = SimpleCNN(img_size)

# 测试模型:生成一个随机输入 (batch_size=8, 3通道, img_size x img_size)
test_input = torch.randn(8, 3, img_size, img_size)
# 前向传播
test_output = cnn_model(test_input)

# 打印模型结构和输出形状
print("SimpleCNN 模型结构:")
print(cnn_model)
print(f"\n输入形状:{test_input.shape}")
print(f"输出形状:{test_output.shape}")
</details>
posted on 2025-12-26 14:40  986614  阅读(0)  评论(0)    收藏  举报