Python纯手动搭建BP神经网络(手写数字识别)

news2025/1/11 12:01:15

来源:投稿 作者:张宇
编辑:学姐

 

实验介绍

实验要求:

实现一个手写数字识别程序,如下图所示,要求神经网络包含一个隐层,隐层的神经元个数为15。

整体思路:

主要参考西瓜书第五章神经网络部分的介绍,使用批量梯度下降对神经网络进行训练。

读取并处理数据

数据读取思路:

  • 数据集中给出的图片为28*28的灰度图,利用 plt.imread() 函数将图片读取出来后为 28x28 的数组,如果使用神经网络进行训练的话,我们可以将每一个像素视为一个特征,所以将图片利用numpy.reshape 方法将图片化为 1x784 的数组。读取全部数据并将其添加进入数据集 data 内,将对应的标签按同样的顺序添加进labels内。

  • 然后使用np.random.permutation方法将索引打乱,利用传入的测试数据所占比例test_ratio将数据划分为测试集与训练集。

  • 使用训练样本的均值和标准差对训练数据、测试数据进行标准化。标准化这一步很重要,开始忽视了标准化的环节,神经网络精度一直达不到效果。

  • 返回数据集

这部分代码如下:

# 读取图片数据 参数:数据路径 测试集所占的比例
def loadImageData(trainingDirName='data/', test_ratio=0.3):
    from os import listdir
    data = np.empty(shape=(0, 784))
    labels = np.empty(shape=(0, 1))

    for num in range(10):
        dirName = trainingDirName + '%s/' % (num)  # 获取当前数字文件路径
        # print(listdir(dirName))
        nowNumList = [i for i in listdir(dirName) if i[-3:] == 'bmp']  # 获取里面的图片文件
        labels = np.append(labels, np.full(shape=(len(nowNumList), 1), fill_value=num), axis=0)  # 将图片标签加入
        for aNumDir in nowNumList:  # 将每一张图片读入
            imageDir = dirName + aNumDir  # 构造图片路径
            image = plt.imread(imageDir).reshape((1, 784))  # 读取图片数据
            data = np.append(data, image, axis=0)  
    # 划分数据集
    m = data.shape[0]
    shuffled_indices = np.random.permutation(m)  # 打乱数据
    test_set_size = int(m * test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]

    trainData = data[train_indices]
    trainLabels = labels[train_indices]

    testData = data[test_indices]
    testLabels = labels[test_indices]

    # 对训练样本和测试样本使用统一的均值 标准差进行归一化
    tmean = np.mean(trainData)
    tstd = np.std(testData)

    trainData = (trainData - tmean) / tstd
    testData = (testData - tmean) / tstd
    return trainData, trainLabels, testData, testLabels

OneHot编码:

由于神经网络的特性,在进行多分类任务时,每一个神经元的输出对应于一个类,所以将每一个训练样本的标签转化为OneHot形式(将0-9的数据映射在长度为10的向量中,每个样本向量对应位置的值为1其余为0)。

# 对输出标签进行OneHot编码  参数:labels 待编码的标签  Label_class  编码类数
def OneHotEncoder(labels,Label_class):
    one_hot_label = np.array([[int(i == int(labels[j])) for i in range(Label_class)] for j in range(len(labels))])
    return one_hot_label

训练神经网络

激活函数:

激活函数使用sigmoid函数,但是使用定义式在训练过程中总是出现 overflow 警告,所以将函数进行了如下形式的转化。

# sigmoid激活函数
def sigmoid(z):
    for r in range(z.shape[0]):
        for c in range(z.shape[1]):
            if z[r,c] >= 0:
                z[r,c] = 1 / (1 + np.exp(-z[r,c]))
            else :
                z[r,c] = np.exp(z[r,c]) / (1 + np.exp(z[r,c]))
    return z

损失函数:

使用均方误差损失函数。其实我感觉在这个题目里面直接使用预测精度也是可以的。

def cost(prediction, labels):
    return np.mean(np.power(prediction - labels,2))

训练:

终于来到了紧张而又刺激的训练环节。神经网络从输入层通过隐层传递的输出层进而获得网络的输出叫做正向传播,而从输出层根据梯度下降的方法进行调整权重及偏置的过程叫做反向传播。

前向传播每层之间将每个结点的输出值乘对应的权重(对应函数中 omega1 omega2 )传输给下一层结点,下一层结点将上一层所有传来的数据进行求和,并减去偏置 theta (此时数据对应函数中 h_in o_in )。最后通过激活函数输出下一层(对应函数中 h_out o_out )。在前向传播完成后计算了一次损失,方便后面进行分析。

反向传播是用梯度下降法对权重和偏置进行更新,这里是最主要的部分。根据西瓜书可以推出输出层权重的调节量为 ,其中 为学习率, 分别为对应数据的真实值以及网络的输出, 为隐层的输出值。这里还要一个重要的地方在于如果将公式向量化的话需要重点关注输出矩阵形状以及每个矩阵数据之间的关系。代码中d2 对应于公式中 这一部分,这里需要对应值相乘。最后将这一部分与进行矩阵乘法,在乘学习率得到权重调节量,与权重相加即可(代码中除了训练集样本个数m是因为 d2 与 h_out 的乘积将所有训练样本进行了累加,所以需要求平均)。

对于其他权重及偏置的调节方式与此类似,不做过多介绍(其实是因为明天要上课,太晚了得睡觉,有空补上这里和其他不详细的地方),详见西瓜书。

# 训练一轮ANN 参数:训练数据 标签 输入层 隐层 输出层size 输入层隐层连接权重 隐层输出层连接权重 偏置1  偏置2 学习率
def trainANN(X, y, input_size, hidden_size, output_size, omega1, omega2, theta1, theta2, learningRate):
    # 获取样本个数
    m = X.shape[0]
    # 将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)
    y = np.matrix(y)

    # 前向传播 计算各层输出
    # 隐层输入 shape=m*hidden_size
    h_in = np.matmul(X, omega1.T) - theta1.T
    # 隐层输出 shape=m*hidden_size
    h_out = sigmoid(h_in)
    # 输出层的输入 shape=m*output_size
    o_in = np.matmul(h_out, omega2.T) - theta2.T
    # 输出层的输出 shape=m*output_size
    o_out = sigmoid(o_in)

    # 当前损失
    all_cost = cost(o_out, y)

    # 反向传播
    # 输出层参数更新
    d2 = np.multiply(np.multiply(o_out, (1 - o_out)), (y - o_out))
    omega2 += learningRate * np.matmul(d2.T, h_out) / m
    theta2 -= learningRate * np.sum(d2.T, axis=1) / m

    # 隐层参数更新
    d1 = np.multiply(h_out, (1 - h_out))
    omega1 += learningRate * (np.matmul(np.multiply(d1, np.matmul(d2, omega2)).T, X) / float(m))
    theta1 -= learningRate * (np.sum(np.multiply(d1, np.matmul(d2, omega2)).T, axis=1) / float(m))

    return omega1, omega2, theta1, theta2, all_cost

网络测试

预测函数:

这里比较简单,前向传播的部分和前面一样,因为最后网络输出的为样本x为每一类的概率,所以仅需要利用 np.argmax 函数求出概率值最大的下标即可,下标0-9正好对应数字的值。

# 数据预测
def predictionANN(X, omega1, omega2, theta1, theta2):
    # 获取样本个数
    m = X.shape[0]
    # 将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)

    # 前向传播 计算各层输出
    # 隐层输入 shape=m*hidden_size
    h_in = np.matmul(X, omega1.T) - theta1.T
    # 隐层输出 shape=m*hidden_size
    h_out = sigmoid(h_in)
    # 输出层的输入 shape=m*output_size
    o_in = np.matmul(h_out, omega2.T) - theta2.T
    # 输出层的输出 shape=m*output_size
    o_out = np.argmax(sigmoid(o_in), axis=1)

    return o_out

准确率计算:

这里将所有测试数据 X 进行预测,计算预测标签 y-hat 与真实标签 y 一致个数的均值得出准确率。

# 计算模型准确率
def computeAcc(X, y, omega1, omega2, theta1, theta2):
    y_hat = predictionANN(X,omega1, omega2,theta1,theta2)
    return np.mean(y_hat == y)

主函数:

在主函数中通过调用上面的函数对网络训练预测精度,并使用 loss_list acc_list 两个list保存训练过程中每一轮的精度与误差,利用 acc_max 跟踪最大精度的模型,并使用 pickle 将模型(其实就是神经网络的参数)进行保存,后面用到时可以读取。同样在训练完成时我也将 loss_listacc_list 进行了保存 (想的是可以利用训练的数据做出一点好看的图)。

最后部分将损失以及准确率随着训练次数的趋势进行了绘制。

结果如下:

可以看出,模型只是跑通了,效果并不好,训练好几个小时准确率只有91%。原因可能是因为没有对模型进行正则化、学习率没有做动态调节等。

相反使用SVM模型准确率轻松可以达到97%以上。

# 获取数据
from sklearn.datasets import fetch_openml
from sklearn.svm import SVC
mnist = fetch_openml('mnist_784', version=1, as_frame=False) # 默认返回Pandas的DF类型
# sklearn加载的数据集类似字典结构
from sklearn.preprocessing import StandardScaler
X, y = mnist["data"], mnist["target"]
stder = StandardScaler() 
X = stder.fit_transform(X)
# 划分训练集与测试集
test_ratio = 0.3

shuffled_indices = np.random.permutation(X.shape[0]) # 打乱数据
test_set_size = int(X.shape[0] * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]

X_train, X_test, y_train, y_test = X[train_indices], X[test_indices], y[train_indices], y[test_indices]
# 训练SVM

cls_svm = SVC(C=1.0, kernel='rbf')
cls_svm.fit(X_train,y_train)
y_pre = cls_svm.predict(X_test)
acc_rate = np.sum(y_pre == y_test) / float(y_pre.shape[0])
acc_rate

瑟瑟发抖~~~

最后将全部代码贴上:

import numpy as np
import matplotlib.pyplot as plt
import pickle

# 读取图片数据
def loadImageData(trainingDirName='data/', test_ratio=0.3):
    from os import listdir
    data = np.empty(shape=(0, 784))
    labels = np.empty(shape=(0, 1))

    for num in range(10):
        dirName = trainingDirName + '%s/' % (num)  # 获取当前数字文件路径
        # print(listdir(dirName))
        nowNumList = [i for i in listdir(dirName) if i[-3:] == 'bmp']  # 获取里面的图片文件
        labels = np.append(labels, np.full(shape=(len(nowNumList), 1), fill_value=num), axis=0)  # 将图片标签加入
        for aNumDir in nowNumList:  # 将每一张图片读入
            imageDir = dirName + aNumDir  # 构造图片路径
            image = plt.imread(imageDir).reshape((1, 784))  # 读取图片数据
            data = np.append(data, image, axis=0)  
    # 划分数据集
    m = data.shape[0]
    shuffled_indices = np.random.permutation(m)  # 打乱数据
    test_set_size = int(m * test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]

    trainData = data[train_indices]
    trainLabels = labels[train_indices]

    testData = data[test_indices]
    testLabels = labels[test_indices]

    # 对训练样本和测试样本使用统一的均值 标准差进行归一化
    tmean = np.mean(trainData)
    tstd = np.std(testData)

    trainData = (trainData - tmean) / tstd
    testData = (testData - tmean) / tstd
    return trainData, trainLabels, testData, testLabels

# 对输出标签进行OneHot编码
def OneHotEncoder(labels,Label_class):
    one_hot_label = np.array([[int(i == int(labels[j])) for i in range(Label_class)] for j in range(len(labels))])
    return one_hot_label

# sigmoid激活函数
def sigmoid(z):
    for r in range(z.shape[0]):
        for c in range(z.shape[1]):
            if z[r,c] >= 0:
                z[r,c] = 1 / (1 + np.exp(-z[r,c]))
            else :
                z[r,c] = np.exp(z[r,c]) / (1 + np.exp(z[r,c]))
    return z

# 计算均方误差 参数:预测值 真实值
def cost(prediction, labels):
    return np.mean(np.power(prediction - labels,2))


# 训练一轮ANN 参数:训练数据 标签 输入层 隐层 输出层size 输入层隐层连接权重 隐层输出层连接权重 偏置1  偏置2 学习率
def trainANN(X, y, input_size, hidden_size, output_size, omega1, omega2, theta1, theta2, learningRate):
    # 获取样本个数
    m = X.shape[0]
    # 将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)
    y = np.matrix(y)

    # 前向传播 计算各层输出
    # 隐层输入 shape=m*hidden_size
    h_in = np.matmul(X, omega1.T) - theta1.T
    # 隐层输出 shape=m*hidden_size
    h_out = sigmoid(h_in)
    # 输出层的输入 shape=m*output_size
    o_in = np.matmul(h_out, omega2.T) - theta2.T
    # 输出层的输出 shape=m*output_size
    o_out = sigmoid(o_in)

    # 当前损失
    all_cost = cost(o_out, y)

    # 反向传播
    # 输出层参数更新
    d2 = np.multiply(np.multiply(o_out, (1 - o_out)), (y - o_out))
    omega2 += learningRate * np.matmul(d2.T, h_out) / m
    theta2 -= learningRate * np.sum(d2.T, axis=1) / m

    # 隐层参数更新
    d1 = np.multiply(h_out, (1 - h_out))
    omega1 += learningRate * (np.matmul(np.multiply(d1, np.matmul(d2, omega2)).T, X) / float(m))
    theta1 -= learningRate * (np.sum(np.multiply(d1, np.matmul(d2, omega2)).T, axis=1) / float(m))

    return omega1, omega2, theta1, theta2, all_cost


# 数据预测
def predictionANN(X, omega1, omega2, theta1, theta2):
    # 获取样本个数
    m = X.shape[0]
    # 将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)

    # 前向传播 计算各层输出
    # 隐层输入 shape=m*hidden_size
    h_in = np.matmul(X, omega1.T) - theta1.T
    # 隐层输出 shape=m*hidden_size
    h_out = sigmoid(h_in)
    # 输出层的输入 shape=m*output_size
    o_in = np.matmul(h_out, omega2.T) - theta2.T
    # 输出层的输出 shape=m*output_size
    o_out = np.argmax(sigmoid(o_in), axis=1)

    return o_out


# 计算模型准确率
def computeAcc(X, y, omega1, omega2, theta1, theta2):
    y_hat = predictionANN(X,omega1, omega2,theta1,theta2)
    return np.mean(y_hat == y)


if __name__ == '__main__':
    # 载入模型数据
    trainData, trainLabels, testData, testLabels = loadImageData()

    # 初始化设置
    input_size = 784
    hidden_size = 15
    output_size = 10
    lamda = 1

    # 将网络参数进行随机初始化
    omega1 = np.matrix((np.random.random(size=(hidden_size,input_size)) - 0.5) * 0.25) # 15*784
    omega2 = np.matrix((np.random.random(size=(output_size,hidden_size)) - 0.5) * 0.25)  # 10*15

    # 初始化两个偏置
    theta1 = np.matrix((np.random.random(size=(hidden_size,1)) - 0.5) * 0.25) # 15*1
    theta2 = np.matrix((np.random.random(size=(output_size,1)) - 0.5) * 0.25) # 10*1
    # 学习率
    learningRate = 0.1
    # 数据集
    m = trainData.shape[0] # 样本个数
    X = np.matrix(trainData) # 输入数据 m*784
    y_onehot=OneHotEncoder(trainLabels,10) # 标签 m*10

    iters_num = 20000  # 设定循环的次数
    loss_list = []
    acc_list = []
    acc_max = 0.0  # 最大精度 在精度达到最大时保存模型
    acc_max_iters = 0

    for i in range(iters_num):
        omega1, omega2, theta1, theta2, loss = trainANN(X, y_onehot, input_size, hidden_size, output_size, omega1,
                                                        omega2, theta1, theta2, learningRate)
        loss_list.append(loss)
        acc_now = computeAcc(testData, testLabels, omega1, omega2, theta1, theta2)  # 计算精度
        acc_list.append(acc_now)
        if acc_now > acc_max:  # 如果精度达到最大 保存模型
            acc_max = acc_now
            acc_max_iters = i  # 保存坐标 方便在图上标注
            # 保存模型参数
            f = open(r"./best_model", 'wb')
            pickle.dump((omega1, omega2, theta1, theta2), f, 0)
            f.close()
        if i % 100 == 0:  # 每训练100轮打印一次精度信息
            print("%d  Now accuracy : %f"%(i,acc_now))

    # 保存训练数据 方便分析
    f = open(r"./loss_list", 'wb')
    pickle.dump(loss_list, f, 0)
    f.close()

    f = open(r"./acc_list", 'wb')
    pickle.dump(loss_list, f, 0)
    f.close()

    # 绘制图形
    plt.figure(figsize=(13, 6))
    plt.subplot(121)
    x1 = np.arange(len(loss_list))
    plt.plot(x1, loss_list, "r")
    plt.xlabel(r"Number of iterations", fontsize=16)
    plt.ylabel(r"Mean square error", fontsize=16)
    plt.grid(True, which='both')

    plt.subplot(122)
    x2 = np.arange(len(acc_list))
    plt.plot(x2, acc_list, "r")
    plt.xlabel(r"Number of iterations", fontsize=16)
    plt.ylabel(r"Accuracy", fontsize=16)
    plt.grid(True, which='both')
    plt.annotate('Max accuracy:%f' % (acc_max),  # 标注最大精度值
                 xy=(acc_max_iters, acc_max),
                 xytext=(acc_max_iters * 0.7, 0.5),
                 arrowprops=dict(facecolor='black', shrink=0.05),
                 ha="center",
                 fontsize=15,
                 )
    plt.plot(np.linspace(acc_max_iters, acc_max_iters, 200), np.linspace(0, 1, 200), "y--", linewidth=2, )  # 最大精度迭代次数
    plt.plot(np.linspace(0, len(acc_list), 200), np.linspace(acc_max, acc_max, 200), "y--", linewidth=2)  # 最大精度

    plt.scatter(acc_max_iters, acc_max, s=180, facecolors='#FFAAAA')  # 标注最大精度点
    plt.axis([0, len(acc_list), 0, 1.0])  # 设置坐标范围
    plt.savefig("ANN_plot")  # 保存图片
    plt.show()

深度学习神经网络系列🚀🚀🚀

关注下方《学姐带你玩AI》一起学习

码字不易,欢迎大家点赞评论收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一款超赞的算法可视化工具,让算法过程动态展示出来

从文字或者图片中学习算法还是一件很无聊的事。当然,现在有许多很棒的网站可以查看各种算法的动画。然而,对于开发人员来说,如果能将实现算法的代码的实际执行操作通过可视化展现出来,那就是最好不过了。推荐一款开源工具&#xf…

深度学习——编码器

1.复习CNN 在CNN中,输入一张图片,经过多层的卷积层,最后输出层判别图片中的物体的类别。 CNN使用卷积层做特征提取(编码),使用Softmax回归做预测(解码) ①编码器:将输入…

ArrayList扩容机制~

ArrayList()//会使用长度为零的数组 ArrayList(int initialCapacity)//会使用指定容量的数组 public ArrayList(Collection<?extends E>c>//会使用c的大小作为数组容量假设我们设置一个列表的最初容量为10&#xff0c;如下所示&#xff1a; ArrayList<Integer>…

【Python从入门到进阶】5、变量的定义及数据类型

接上篇《4、pycharm的安装及使用》 上一篇我们学习了python编程工具pycharm的安装及基本使用。后续篇章我们正式来学习Python语言的语法和特性&#xff0c;本篇我们主要学习Python变量的定义及数据类型。 一、注释 1、注释介绍 在工作编码的过程中&#xff0c;如果一段代码的…

(深度学习快速入门)第三章第三节4:深度学习必备组件之TensorBoard和标准化技术

文章目录一&#xff1a;TensorBoard&#xff08;1&#xff09;TensorBoard介绍&#xff08;2&#xff09;Pytorch安装TensorBoard&#xff08;3&#xff09;TensorBoard使用&#xff08;4&#xff09;服务器tensorboard本地显示&#xff08;5&#xff09;AutoDL等算力平台tenso…

07技术太卷我学APEX-动态菜单+URL传参数给页面

07技术太卷我学APEX-动态菜单URL传参数给页面 0 应用场景 《技术太卷我学APEX》收集的项目越来越多&#xff0c;我想把【类】【子类】加到导航菜单栏&#xff0c;点击不同的分类菜单栏&#xff0c;对列表进行过滤&#xff0c;也可以全部浏览&#xff0c;如下图&#xff1a; …

JAVA SE复习(第1章 Java概述)

本文笔记来自硅谷柴林燕老师的笔记 只为自己看笔记方便使用 不做他用 目录 第1章 Java概述 1.1 Java语言发展历史&#xff08;记关键点&#xff09; 1.2 Java语言特点&#xff08;后面需要关注和体会&#xff09; 1.3 Java语言跨平台原理&#xff08;理解&#xff09; 1…

4. 网络编程之TCP编程

1. 《计算机网络编程》 我们接触网络编程&#xff0c;肯定是要对网络编程的一些专业术语及基本理论知识是要有所认知的。python网络编程无非是在这些基础理论知识之上给我们提供了一些方便实用的网络库来供我们使用。尽管做了非常底层的封装&#xff0c;并且给我们暴露了上层的…

java IO流之缓冲流详解

缓冲流概述 缓冲流也称为高效流或者高级流。之前我们学习的字节流、字符流可以成为基本流。 作用&#xff1a;缓冲流自带缓冲区、可以提高基本字节流、字符流读写数据的性能。 分类&#xff1a; BufferedInputStream -->字节缓冲输入流 BufferedOutputStream–>字节缓冲输…

ch1_1计算机系统概论

1. 内容安排 1.1 概论 1.2 计算机系统的硬件结构 存储器&#xff0c;I/O 输入与输出&#xff1b;系统总线&#xff1b;CPU 1.3 第三篇 CPU 中央处理器中&#xff0c;所包含的内容&#xff1a; ALU&#xff0c; CUCPU 内部互连寄存器 1.4 CU CU &#xff1a; control uni…

最新虚幻5引擎(UE5)游戏性能的影响详解

Unreal Engine 5 是由 Unreal Engine 公司开发的一款游戏引擎。5 代表引擎的主要版本号。它专为创建多人在线游戏、手机游戏、高端游戏和虚幻应用程序而设计。从图中可以看出&#xff0c;随着场景复杂度的增加&#xff0c;UE5的内存占用会逐渐增加。当然&#xff0c;这并不意味…

Redis简介、数据类型和命令

1 Redis 简介Redis 是一个高性能的 key/value 数据库。它是完全开源免费的&#xff0c;并且遵守 BSD 协议。1.1 Redis 特点不仅支持 key/value 类型的数据&#xff0c;也支持 list,hash,set,zset 等等数据结构。支持持久化&#xff0c;可以把内存数据保存到磁盘上&#xff0c;重…

高并发异步多线程处理例子

用户请求流程 问题点 tomcat 线程资源占满&#xff0c;由于tomcat线程资源有限&#xff0c;每个请求都会经由tomcat线程处理&#xff0c;阻塞至web层处理完才能回收再利用。web层分发至后端服务可能会扩大几倍甚至数百倍的&#xff0c;譬如用户发起请求1w/s&#xff0c;到后端…

JavaEE10-Spring Boot配置文件

目录 1.配置文件作用 2.配置文件的格式 为配置文件安装提示插件 2.1. .properties&#xff08;旧版&#xff0c;默认的&#xff09; 2.1.1.基本语法 PS:配置文件中使用"#"来添加注释信息&#xff0c;2种添加方式&#xff1a; 2.1.2.缺点分析 2.2. .yml&#…

阿里“云开发“小程序(uniCloud)

博主ps&#xff1a; 网上资料少的可怜&#xff0c;哎&#xff0c;腾讯云涨价了&#xff0c;论服务器&#xff0c;我肯定选的阿里&#xff0c;再着你们对比下uniCloud的报价就知道了&#xff0c;如果有钱就另当别论了。 所以这片博文&#xff0c;博主试过之后&#xff0c;先抛出…

Git速成指南

文章目录版本管理工具概念版本管理工具介绍版本管理发展简史SVN(SubVersion)GitGit工作流程图Git安装基本配置为常用指令配置别名&#xff08;可选&#xff09;解决GitBash乱码问题Git常用命令获取本地仓库基础操作指令查看修改的状态&#xff08;status&#xff09;添加工作区…

[翻译]PostgreSQL中的WAL压缩以及版本15中的改进

[翻译]PostgreSQL中的WAL压缩以及版本15中的改进从以开始就一直在尝试对WAL进行不同级别的压缩。自2016年以来内置功能&#xff08;wal_compression&#xff09;就一直存在&#xff0c;几乎所有备份工具都会在传递到备机前对WAL进行压缩。但现在是时候再看看内置的wal_compress…

呦~,这不 SVG 映射反爬么,这你都会?厉害厉害 | 案例 25

在正式学习本篇博客前&#xff0c;先要了解一下什么是 SVG&#xff08;Scalable Vector Graphics&#xff09;&#xff0c;它是一种矢量图形格式&#xff0c;可以用来在网页上创建可伸缩的图形。 使用 SVG 技术实现反爬虫的方法有以下几种&#xff1a; 验证码&#xff1a;使用…

imx6ull Linux使用设备树配置LED

我们基于寄存器的方式已经编写了LED驱动&#xff0c;实现点亮/熄灭LED&#xff0c;但是你有没有发现一个问题&#xff0c;就是假设LED修改了一个GPIO&#xff0c;那么需要对应的修改寄存器代码&#xff0c;非常繁琐&#xff0c;而且随着改板次数增加&#xff0c;那么会带来一个…

从零开始的数模(五)插值与拟合

目录 一、概念 二、 插值 2.1方法 2.2MATLAB实现 例题1 ​编辑例题2 2.3python实现 2.3.1例题一的python解法 2.3.2二维网格节点插值 例题四 三、拟合篇&#xff1a; 3.1MATLAB实现 3.2python实现 一、概念 二、 插值 2.1方法 2.2MATLAB实现 在MATLAB中提供了一些…