手写数字识别(机器学习)

一:一对多分类方法(one-vs-all)

这里先上的代码,想看原理可以到代码下面。

在数据集中,y的取值为1~10,y=10表示当前数字为0

首先读取数据,并对数据进行切分。

python 复制代码
import pandas as pd
import numpy as np
import matplotlib

matplotlib.use('tKAgg')
import matplotlib.pyplot as plt

file_path = "D:\\JD\\Documents\\大学等等等\\自学部分\\机器学习自学画图\\手写数字识别\\ex3data1.xlsx"
data = pd.read_excel(file_path)
row_X = data.iloc[:, :-1]
row_y = data.iloc[:, -1]
print(row_X.shape, row_y.shape)

(4999, 400) (4999,)

我们来看一下是否显示图像:

python 复制代码
def plot_an_image(X):
    pick_one = np.random.randint(len(X))  # 使用len(X)确保随机选择的索引在数据范围内
    image = X.iloc[pick_one, :]
    plt.figure(figsize=(4, 4))  # 设置图像大小
    plt.imshow(image.values.reshape(20, 20).T, cmap='gray_r')  # image.values用于提取数据
    plt.xticks([])
    plt.yticks([])
    plt.show()  # 显示图像


def plot_100_image(X):
    # 随机选择100个样本
    pick_100 = np.random.choice(len(X), 100, replace=False)

    # 创建一个8x8的图形和10x10的子图网格
    fig, axes = plt.subplots(nrows=10, ncols=10, figsize=(8, 8))

    # 确保axes是一个2D数组(10x10)
    axes = axes.flatten()

    for i, idx in enumerate(pick_100):
        image = X.iloc[idx, :]
        # 将图像数据显示到相应的子图中
        axes[i].imshow(image.values.reshape(20, 20).T, cmap='gray_r')
        axes[i].axis('off')  # 关闭坐标轴

    plt.tight_layout()  # 自动调整子图参数
    plt.show()

调用显示100个图片的函数:

python 复制代码
plot_100_image(row_X)

构建模型:

python 复制代码
def sigmoid(z):
    return 1 / (1 + np.exp(-z))


def costFunction(theta, X, y, lamda):  # 损失函数,要求它最小
    A = sigmoid(X @ theta)  # 预测值
    first = y * np.log(A)  # 损失函数部分
    second = (1 - y) * np.log(1 - A)  # 损失函数部分
    reg = theta[1:] @ theta[1:] * lamda / (2 * len(X))  # 正则项
    return -np.sum(first + second) / len(X) + reg


def gradient_reg(theta, X, y, lamda):
    '''
    :param theta: 要训练的参数
    :param X:    数据集
    :param y:     结果集
    :param lamda:   正则化参数
    :return:
    '''
    reg = theta[1:] * lamda / len(X)
    reg = np.insert(reg, 0, values=0, axis=0)  # 正则项梯度(导数)
    first = (X.T @ (sigmoid(X @ theta) - y)) / len(X)  # 损失函数梯度(导数)
    return reg + first


X = np.insert(row_X, 0, values=1, axis=1)
y = row_y[:]


def one_vs_all(X, y, lamda, K):
    '''
    :param X: 数据集
    :param y:  真实值
    :param lamda: 正则化项参数
    :param K:    标签个数
    :return:
    '''
    n = X.shape[1]  # 维度
    theta_all = np.zeros((K, n))
    for i in range(1, K + 1):
        theta_i = np.zeros(n,)
        res = minimize(costFunction,
                       x0=theta_i,
                       args=(X, y == i, lamda),
                       method='TNC',
                       jac=gradient_reg)
        theta_all[i - 1, :] = res.x
    return theta_all


lamda =1
K=10
theta_final = one_vs_all(X,y,lamda,K)
print((theta_final))


def predict(X,theta_final):
    h = sigmoid(X@theta_final.T)
    h_argmax = np.argmax(h,axis=1)
    return h_argmax+1
y_pred = predict(X,theta_final)
acc = np.mean(y_pred==y)
print(acc)

[[-2.37986344e+00 0.00000000e+00 0.00000000e+00 ... 1.30436171e-03

-7.36236836e-10 0.00000000e+00]

[-3.18544317e+00 0.00000000e+00 0.00000000e+00 ... 4.45792974e-03

-5.08241654e-04 0.00000000e+00]

[-4.79780226e+00 0.00000000e+00 0.00000000e+00 ... -2.86754653e-05

-2.47292717e-07 0.00000000e+00]

...

[-7.98708564e+00 0.00000000e+00 0.00000000e+00 ... -8.95567756e-05

7.21960016e-06 0.00000000e+00]

[-4.57327215e+00 0.00000000e+00 0.00000000e+00 ... -1.33515788e-03

9.98385662e-05 0.00000000e+00]

[-5.40538138e+00 0.00000000e+00 0.00000000e+00 ... -1.16772407e-04

7.89384343e-06 0.00000000e+00]]

0.9445889177835567

可以看到准确率为0.9445889177835567

这里大家可能不知道minimize函数。这里给大家讲一下:

minimize 函数是 SciPy 库中的一个优化函数,主要用于求解最优化问题。它能够找到给定目标函数的最小值,并返回对应的变量值。理解 minimize 函数的工作原理对于优化模型和算法至关重要。下面,我会详细讲解 minimize 函数的各个参数及其使用方法。

minimize 函数的参数详解

  1. fun:

    • 这是一个必须的参数,指定了需要最小化的目标函数。该函数必须接受参数并返回一个标量值(即目标函数的值)。
    • 在代码中,funcostFunction。它计算了逻辑回归的代价(损失)函数,计算了预测值与真实值之间的误差以及正则化项。
  2. x0:

    • 初始猜测值。x0 是一个初始的变量值,用于开始优化过程。优化算法会从这些初始值出发,尝试找到使目标函数最小的最佳值。
    • 在代码中,x0theta_i(初始化为全零向量)。它代表了逻辑回归模型的初始参数。
  3. args:

    • 这是一个元组,用于将额外的参数传递给目标函数。args 中的参数会被传递给 fun 函数。
    • 在你的代码中,args(X, y == i, lamda),它将数据 X、标签的二值化结果 y == i 和正则化参数 lamda 传递给 costFunction
  4. method:

    • 指定使用的优化算法。SciPy 提供了多种优化算法,例如 'BFGS''Nelder-Mead''TNC''L-BFGS-B' 等等。
    • 在代码中,使用的是 'TNC'(Truncated Newton Conjugate Gradient)。这种方法适用于大规模问题,并在处理非线性优化时表现良好。
  5. jac:

    • 这是目标函数的梯度(即目标函数对各变量的偏导数)的函数。如果提供,minimize 会利用这些梯度信息来加速优化过程。
    • 在代码中,jacgradient_reg,它计算了代价函数的梯度,包括正则化项的梯度。
  6. constraints:

    • 用于设置约束条件,通常用于优化问题中需要满足的条件(例如,变量的范围)。这是一个可选参数。
    • 代码中没有使用这个参数,因此可以忽略。
  7. bounds:

    • 用于设置每个变量的范围(例如,变量的上下界)。这是一个可选参数。
    • 代码中没有使用这个参数,因此在你的情况中可以忽略。
  8. tol:

    • 优化过程的容差。优化算法在达到设定的精度后停止。容差值越小,算法越精确,但计算可能更耗时。
    • 代码中没有设置,使用默认值。
  9. options:

    • 这个参数用于设置优化过程中的其他选项,例如最大迭代次数、输出选项等。
    • 代码中没有设置这个参数,使用默认值。

minimize 函数的工作流程

  1. 初始化:

    • minimize 函数首先使用 x0 作为初始值来开始优化过程。
  2. 计算目标函数:

    • 使用 fun 参数提供的函数来计算当前 x 值下的目标函数值。你的目标函数是 costFunction,它计算了逻辑回归的损失。
  3. 计算梯度:

    • 如果 jac 参数提供了梯度函数,minimize 会计算目标函数的梯度,这有助于加速优化过程。
  4. 选择优化算法:

    • minimize 根据 method 参数选择的优化算法来进行优化。优化算法会尝试逐步调整 x 的值,以找到使目标函数值最小的点。
  5. 迭代优化:

    • 优化算法会迭代地更新 x 的值,并计算目标函数和梯度,直到满足停止准则(如最大迭代次数或目标函数变化小于容差值)。
  6. 返回结果:

    • 优化完成后,minimize 返回一个包含最优解和其他信息的结果对象,包括最优参数 x、目标函数值、退出状态等。

假设你有一个简单的二次函数需要最小化,例如

python 复制代码
from scipy.optimize import minimize

# 定义目标函数
def objective_function(x):
    return (x - 3)**2

# 初始猜测值
x0 = [0]

# 调用 minimize 函数
result = minimize(objective_function, x0)

# 输出结果
print("最优解:", result.x)
print("目标函数值:", result.fun)

注意minimize要求参数的顺序,x0是objective_function的第一个参数。

假设我们有一个目标函数,目的是找到使得以下函数值最小化的 x:

其中 ab 是我们在计算函数值时需要用到的额外参数。假设 ab 是已知的常数,我们希望优化 x

  1. 定义目标函数

首先,我们定义目标函数 f。这个函数需要额外的参数 ab

python 复制代码
def objective_function(x, a, b):
    return (x - a)**2 + b
  1. 传递额外参数

为了优化 x,我们需要将 ab 传递给 objective_function。这里我们使用 scipy.optimize.minimize 函数,其中 args 参数用于传递这些额外的参数。

例如,我们希望将 a 设置为 5,b 设置为 2,初始值 x0 为 0。加上损失函数的梯度可能更快一些。

python 复制代码
from scipy.optimize import minimize

# 额外参数
a = 5
b = 2

#定义目标函数导数(关于目标参数的梯度)
# 定义梯度函数
def gradient_function(x):
    return 2 * (x - a)


# 初始猜测值
x0 = 0

# 调用 minimize 函数
result = minimize(objective_function, x0, args=(a, b),method='TNC', jac=gradient_function)

print("最优解:", result.x)
print("最小值:", result.fun)
  1. 结果解释
  • objective_function : 我们定义的目标函数,计算 (x - a)^2 + b
  • x0: 优化算法的初始猜测值。
  • args=(a, b) : 传递给目标函数的额外参数 ab

通过上述代码,minimize 会自动调用 objective_function,并将 xab 传递给它。优化过程会在 x 上进行,以最小化目标函数的值。

这里一定要注意:当我们使用 minimize 函数时,args 中的参数顺序需要与目标函数定义中的顺序一致。例如,在上述目标函数中,args 应该按照 (a, b) 的顺序传递,因为目标函数期望 ab 分别作为第二和第三个参数:

一对多原理

可能大家也不知道为什么要用"y == i"表达式。

大家应该理解二分类,如果上面是预测("是","否"),那么X不变,y的值域为("是","否"),只需要写成args=(X,y,lamda)就行了。

那如果有多个呢(假如有3个A,B,C)?我们是不是可以先预测A,讲A=1,BorC=0。这样以此类推。下面给出一些推理过程:

为标签数组,其中 的每个元素表示样本的真实类别

我们要训练的类别是 i。例如,若 i = 1,则我们要训练一个模型来区分类别 1 和其他类别。

布尔数组 生成一个表示每个样本是否属于类别 i的数组。数学上可以写作:

代价函数 用于计算当前类别 i的分类误差,包括正则化项。可以表示为:

其中 是模型的预测概率,为 sigmoid 函数。

梯度的计算公式为:

优化算法(例如梯度下降)用于最小化代价函数 ,得到优化后的参数

对于新的样本,使用优化后的参数进行预测:

注意我们得到的是一个矩阵:

一共10行,每i行是对于第i个标签的判定。一行共有n个元素,对应着一条记录的n个特征。

通俗的说,第i行是用来预测这个记录属于i类标签的概率,因为一共有10类标签,所以每一记录都会训练10次,得出每一记录属于每一种标签的概率。(或者说,每一行都会训练一次所有的样本,属于这类标签的是正向,否则负向。最终得到每一类标签的权重参数)

注意:

这个M是结果。每一个记录都会有10列,找到每一列中最大的就是预测值。

python 复制代码
def predict(X, theta_final):
    h = sigmoid(X @ theta_final.T)
    h_argmax = np.argmax(h, axis=1)
    return h_argmax + 1


y_pred = predict(X, theta_final)
acc = np.mean(y_pred == y)
print(acc)

np.argmax(h, axis=1) 返回每行中最大值的索引。对于每个样本,h_argmax 是该样本最可能的类别的索引(从 0 开始)

h_argmax 返回的是类别索引(从 0 开始),所以需要加 1 将其转换为从 1 开始的类别标签。

二:Softmax方法

对于这个我上一篇文章有介绍原理

Softmax函数:

交叉熵损失度量的是预测的概率分布与实际标签之间的差异。对于样本 i和类别 k,交叉熵损失为:

当样本 i属于类别 k(即)时,损失函数为:

否则(即 ),损失函数为 0。

将每个样本的损失求和得到总损失:

为了得到每个样本的平均损失,最终的代价函数为:

python 复制代码
import pandas as pd
import numpy as np
import matplotlib
from scipy.optimize import minimize

matplotlib.use('tkAgg')
import matplotlib.pyplot as plt

file_path = "D:\\JD\\Documents\\大学等等等\\自学部分\\机器学习自学画图\\手写数字识别\\ex3data1.xlsx"
data = pd.read_excel(file_path)
row_X = data.iloc[:, :-1]
row_y = data.iloc[:, -1]
print(row_X.shape, row_y.shape)


def plot_an_image(X):
    pick_one = np.random.randint(len(X))  # 使用len(X)确保随机选择的索引在数据范围内
    image = X.iloc[pick_one, :]
    plt.figure(figsize=(4, 4))  # 设置图像大小
    plt.imshow(image.values.reshape(20, 20).T, cmap='gray_r')  # image.values用于提取数据
    plt.xticks([])
    plt.yticks([])
    plt.show()  # 显示图像


def plot_100_image(X):
    # 随机选择100个样本
    pick_100 = np.random.choice(len(X), 100, replace=False)

    # 创建一个8x8的图形和10x10的子图网格
    fig, axes = plt.subplots(nrows=10, ncols=10, figsize=(8, 8))

    # 确保axes是一个2D数组(10x10)
    axes = axes.flatten()

    for i, idx in enumerate(pick_100):
        image = X.iloc[idx, :]
        # 将图像数据显示到相应的子图中
        axes[i].imshow(image.values.reshape(20, 20).T, cmap='gray_r')
        axes[i].axis('off')  # 关闭坐标轴

    plt.tight_layout()  # 自动调整子图参数
    plt.show()


def softmax(z):
    e_z = np.exp(z - np.max(z, axis=1, keepdims=True))  # 稳定计算softmax
    return e_z / np.sum(e_z, axis=1, keepdims=True)


def costFunction(theta, X, y, lamda, K):
    m = X.shape[0]
    Theta = theta.reshape((K, X.shape[1]))  # 转换theta为K x n的矩阵
    A = softmax(X @ Theta.T)  # 计算预测值
    Y = np.eye(K)[y]  # one-hot 编码真实标签
    reg = lamda * np.sum(Theta[:, 1:] ** 2) / (2 * m)  # 正则项
    return -np.sum(Y * np.log(A)) / m + reg


def gradient_reg(theta, X, y, lamda, K):
    m = X.shape[0]
    Theta = theta.reshape((K, X.shape[1]))
    A = softmax(X @ Theta.T)
    Y = np.eye(K)[y]
    grad = (A - Y).T @ X / m
    reg = lamda * np.concatenate([np.zeros((K, 1)), Theta[:, 1:]], axis=1) / m
    return (grad + reg).ravel()


def one_vs_all(X, y, lamda, K):
    n = X.shape[1]  # 维度
    initial_theta = np.zeros((K * n,))
    res = minimize(costFunction,
                   x0=initial_theta,
                   args=(X, y, lamda, K),
                   method='TNC',
                   jac=gradient_reg)
    return res.x.reshape((K, n))


def predict(X, theta_final):
    h = softmax(X @ theta_final.T)
    return np.argmax(h, axis=1)


X = np.insert(row_X, 0, values=1, axis=1)  # 添加偏置项
y = row_y - 1  # 转换标签从1-10到0-9

lamda = 1
K = 10
theta_final = one_vs_all(X, y, lamda, K)
print(theta_final)

y_pred = predict(X, theta_final)
acc = np.mean(y_pred == y)
print(acc)

(4999, 400) (4999,)

[[ 6.02649610e-01 0.00000000e+00 0.00000000e+00 ... 1.10517407e-03

-3.37000911e-10 0.00000000e+00]

[ 1.60407084e-01 0.00000000e+00 0.00000000e+00 ... 2.69379180e-03

-3.10863107e-04 0.00000000e+00]

[-1.01989081e+00 0.00000000e+00 0.00000000e+00 ... -1.40438599e-04

1.22128124e-07 0.00000000e+00]

...

[-3.11440704e+00 0.00000000e+00 0.00000000e+00 ... -1.46182140e-04

8.23433590e-06 0.00000000e+00]

[-3.81197348e-01 0.00000000e+00 0.00000000e+00 ... -1.62596349e-03

1.21163807e-04 0.00000000e+00]

[-1.26535166e+00 0.00000000e+00 0.00000000e+00 ... -7.32969625e-05

7.50789066e-06 0.00000000e+00]]

0.9623924784956991

可以看到这个比上面的准确率要高。

相关推荐
边缘计算社区35 分钟前
首个!艾灵参编的工业边缘计算国家标准正式发布
大数据·人工智能·边缘计算
游客5201 小时前
opencv中的各种滤波器简介
图像处理·人工智能·python·opencv·计算机视觉
一位小说男主1 小时前
编码器与解码器:从‘乱码’到‘通话’
人工智能·深度学习
深圳南柯电子1 小时前
深圳南柯电子|电子设备EMC测试整改:常见问题与解决方案
人工智能
Kai HVZ1 小时前
《OpenCV计算机视觉》--介绍及基础操作
人工智能·opencv·计算机视觉
biter00881 小时前
opencv(15) OpenCV背景减除器(Background Subtractors)学习
人工智能·opencv·学习
吃个糖糖1 小时前
35 Opencv 亚像素角点检测
人工智能·opencv·计算机视觉
qq_529025292 小时前
Torch.gather
python·深度学习·机器学习