卷积神经网络(1)

目录

卷积

1 自定义二维卷积算子

2 自定义带步长和零填充的二维卷积算子

3 实现图像边缘检测

4 自定义卷积层算子和汇聚层算子

        4.1 卷积算子

        4.2 汇聚层算子

5 学习torch.nn.Conv2d()、torch.nn.MaxPool2d();torch.nn.avg_pool2d(),简要介绍使用方法。

6 分别用自定义卷积算子和torch.nn.Conv2d()编程实现下面的卷积运算

总结


卷积

        考虑到使用全连接前馈网络来处理图像时,会出现如下问题:

        1. 模型参数过多,容易发生过拟合。在全连接前馈网络中,隐藏层的每个神经元都要跟该层所有输入的神经元相连接。随着隐藏层神经元数量的增多,参数的规模也会急剧增加,导致整个神经网络的训练效率非常低,也很容易发生过拟合。

        2. 难以提取图像中的局部不变性特征。 自然图像中的物体都具有局部不变性特征,比如尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变性特征。

        卷积神经网络有三个结构上的特性:局部连接、权重共享和汇聚。这些特性使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。和前馈神经网络相比,卷积神经网络的参数也更少。因此,通常会使用卷积神经网络来处理图像信息。

        卷积是分析数学中的一种重要运算,常用于信号处理或图像处理任务。本节以二维卷积为例来进行实践。

1 自定义二维卷积算子

        在机器学习和图像处理领域,卷积的主要功能是在一个图像(或特征图)上滑动一个卷积核,通过卷积操作得到一组新的特征。在计算卷积的过程中,需要进行卷积核的翻转,而这也会带来一些不必要的操作和开销。因此,在具体实现上,一般会以数学中的互相关(Cross-Correlatio)运算来代替卷积。

        在神经网络中,卷积运算的主要作用是抽取特征,卷积核是否进行翻转并不会影响其特征抽取的能力。特别是当卷积核是可学习的参数时,卷积和互相关在能力上是等价的。因此,很多时候,为方便起见,会直接用互相关来代替卷积。

        在本案例之后的描述中,除非特别声明,卷积一般指“互相关”。

        对于一个输入矩阵和一个滤波器,他们的卷积为:

        此时图片的输出大小为:

        计算量为: 

    

import torch
import numpy as np
import torch.nn as nnclass Conv2D(nn.Module):def __init__(self, kernel_size, stride=1, padding=0, ):super(Conv2D, self).__init__()w = torch.tensor(np.array([[0., 1.], [2., 3.]], dtype='float32').reshape([kernel_size, kernel_size]))self.weight = torch.nn.Parameter(w, requires_grad=True)def forward(self, X):"""输入:- X:输入矩阵,shape=[B, M, N],B为样本数量输出:- output:输出矩阵"""u, v = self.weight.shapeoutput = torch.zeros([X.shape[0], X.shape[1] - u + 1, X.shape[2] - v + 1])for i in range(output.shape[1]):for j in range(output.shape[2]):output[:, i, j] = torch.sum(X[:, i:i + u, j:j + v] * self.weight, dim=[1, 2])return output# 随机构造一个二维输入矩阵inputs = torch.tensor([[[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]])
conv2d = Conv2D(kernel_size=2)
outputs = conv2d(inputs)
print("input: {}, \noutput: {}".format(inputs, outputs))

 

2 自定义带步长和零填充的二维卷积算子

        在计算卷积时,可以在所有维度上每间隔$S$个元素计算一次,$S$称为卷积运算的步长(Stride),也就是卷积核在滑动时的间隔。

        在二维卷积运算中,零填充(Zero Padding)是指在输入矩阵周围对称地补上$P$$0$

        对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个滤波器$\mathbf W \in\Bbb{R}^{U\times V}$,,步长为$S$,对输入矩阵进行零填充,那么最终输出矩阵大小则为

        计算量为:

        

         一般常用的卷积有三种:

1. 窄卷积:步长S = 1,两端不补零P=0,卷积后输出尺寸为:

2. 宽卷积:步长$S=1$,两端补零$P=U-1=V-1$,卷积后输出尺寸为:

 3. 等宽卷积:步长$S=1$,两端补零$P=\frac{(U-1)}{2}=\frac{(V-1)}{2}$,卷积后输出尺寸为:

import torch
import numpy as np
import torch.nn as nnclass Conv2D(nn.Module):def __init__(self, kernel_size, stride=1, padding=0, ):super(Conv2D, self).__init__()w = torch.tensor(np.array([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype='float32').reshape([kernel_size, kernel_size]))self.weight = torch.nn.Parameter(w, requires_grad=True)# 步长self.stride = stride# 零填充self.padding = paddingdef forward(self, X):"""输入:- X:输入矩阵,shape=[B, M, N],B为样本数量输出:- output:输出矩阵"""new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding]) # 创建一个M'*N'的零矩阵new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X    # 将原数据放回u, v = self.weight.shapeoutput_w = (new_X.shape[1] - u) // self.stride + 1output_h = (new_X.shape[2] - v) // self.stride + 1output = torch.zeros([X.shape[0], output_w, output_h])for i in range(0, output.shape[1]):for j in range(0, output.shape[2]):output[:, i, j] = torch.sum(new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * self.weight,dim=[1, 2])return output# 随机构造一个二维输入矩阵inputs = torch.randn([2, 8, 8])
conv2d_padding = Conv2D(kernel_size=3, padding=1)
outputs = conv2d_padding(inputs)
print("When kernel_size=3, padding=1 stride=1, input's shape: {}, output's shape: {}".format(inputs.shape, outputs.shape))
conv2d_stride = Conv2D(kernel_size=3, stride=2, padding=1)
outputs = conv2d_stride(inputs)
print("When kernel_size=3, padding=1 stride=2, input's shape: {}, output's shape: {}".format(inputs.shape, outputs.shape))

        从输出结果看出,使用$3\times3$大小卷积,padding为1,当stride=1时,模型的输出特征图可以与输入特征图保持一致;当stride=2时,输出特征图的宽和高都缩小一倍。 

3 实现图像边缘检测

        在图像处理任务中,常用拉普拉斯算子对物体边缘进行提取,拉普拉斯算子为一个大小为$3 \times 3$的卷积核,中心元素值是$8$,其余元素值是$-1$

         考虑到边缘其实就是图像上像素值变化很大的点的集合,因此可以通过计算二阶微分得到,当二阶微分为0时,像素值的变化最大。此时,对$x$方向和$y$方向分别求取二阶导数:

         完整的二阶微分公式为:

        上述公式也被称为拉普拉斯算子,对应的二阶微分卷积核为: 

         对上述算子全部求反也可以起到相同的作用,此时,该算子可以表示为:

         也就是一个点的四邻域拉普拉斯的算子计算结果是自己像素值的四倍减去上下左右的像素的和,将这个算子旋转$45^{\circ}$后与原算子相加,就变成八邻域的拉普拉斯算子,也就是一个像素自己值的八倍减去周围一圈八个像素值的和,做为拉普拉斯计算结果,此时,该算子可以表示为:

         代码如下:

import torchimport matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import torch.nn as nnclass Conv2d(nn.Module):def __init__(self, kernel_size, stride=1, padding=0):super(Conv2d, self).__init__()# 设置卷积核参数w = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], dtype='float32').reshape((3, 3))w = torch.from_numpy(w)self.weight = torch.nn.Parameter(w, requires_grad=True)self.stride = strideself.padding = paddingdef forward(self, X):# 零填充new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = Xu, v = self.weight.shapeoutput_w = (new_X.shape[1] - u) // self.stride + 1output_h = (new_X.shape[2] - v) // self.stride + 1output = torch.zeros([X.shape[0], output_w, output_h])for i in range(0, output.shape[1]):for j in range(0, output.shape[2]):output[:, i, j] = torch.sum(new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * self.weight,dim=[1, 2])return output# 读取图片
img = Image.open('OIP-C.jpg').convert('L')
inputs = np.array(img, dtype='float32')
# 创建卷积算子,卷积核大小为3x3,并使用上面的设置好的数值作为卷积核权重的初始化参数
conv = Conv2d(kernel_size=3, stride=1, padding=0)
print("bf to_tensor, inputs:", inputs)
# 将图片转为Tensor
inputs = torch.tensor(inputs)
print("bf unsqueeze, inputs:", inputs)
inputs = torch.unsqueeze(inputs, dim=0)
print("af unsqueeze, inputs:", inputs)
outputs = conv(inputs)
print(outputs)
# 可视化结果
plt.figure(figsize=(8, 4))
f = plt.subplot(121)
f.set_title('input image', fontsize=15)
plt.imshow(img)
f = plt.subplot(122)
f.set_title('output feature map', fontsize=15)
plt.imshow(outputs.squeeze().detach().numpy(), cmap='gray')
plt.show()

 

4 自定义卷积层算子和汇聚层算子

         从上图可以看出,卷积网络是由多个基础的算子组合而成。下面我们先实现卷积网络的两个基础算子:卷积层算子和汇聚层算子。

        4.1 卷积算子

        卷积层是指用卷积操作来实现神经网络中一层。为了提取不同种类的特征,通常会使用多个卷积核一起进行特征提取。

        在前面介绍的二维卷积运算中,卷积的输入数据是二维矩阵。但实际应用中,一幅大小为$M\times N$的图片中的每个像素的特征表示不仅仅只有灰度值的标量,通常有多个特征,可以表示为$D$维的向量,比如RGB三个通道的特征向量。因此,图像上的卷积操作的输入数据通常是一个三维张量,分别对应了图片的高度$M$、宽度$N$和深度$D$,其中深度$D$通常也被称为输入通道数$D$。如果输入如果是灰度图像,则输入通道数为1;如果输入是彩色图像,分别有$RGB$三个通道,则输入通道数为3。

        此外,由于具有单个核的卷积每次只能提取一种类型的特征,即输出一张大小为$U\times V$特征图(Feature Map)。而在实际应用中,我们也希望每一个卷积层能够提取多种不同类型的特征,所以一个卷积层通常会组合多个不同的卷积核来提取特征,经过卷积运算后会输出多张特征图,不同的特征图对应不同类型的特征。输出特征图的个数通常将其称为输出通道数$P$

        PS:假设一个卷积层的输入特征图$\mathbf X\in \mathbb{R}^{D\times M\times N}$,其中$(M,N)$为特征图的尺寸,$D$代表通道数;卷积核为$\mathbf W\in \mathbb{R}^{P\times D\times U\times V}$,其中$(U,V)$为卷积核的尺寸,$D$代表输入通道数,$P$代表输出通道数。

         多张输出特征图的计算,如下图所示,具体的对这个不明确的可以翻翻我之前的博客,对这个解释的比较详细.

class Conv2D(nn.Module):def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):super(Conv2D, self).__init__()# 创建卷积核weight = torch.zeros([out_channels, in_channels, kernel_size, kernel_size], dtype=torch.float32)weight = nn.init.constant_(weight, val=1.0)self.weight = nn.Parameter(weight)# 创建偏置bias = torch.zeros([out_channels, 1], dtype=torch.float32)self.bias = nn.init.constant_(bias, val=0.0)  # 值可调整self.bias = nn.Parameter(bias)# 步长self.stride = stride# 零填充self.padding = padding# 输入通道数self.in_channels = in_channels# 输出通道数self.out_channels = out_channelsdef single_forward(self, X, weight):"""输入:- X:输入矩阵,shape=[B, M, N],B为样本数量输出:- output:输出矩阵"""new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])  # 创建一个M'*N'的零矩阵new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X  # 将原数据放回u, v = weight.shapeoutput_w = (new_X.shape[1] - u) // self.stride + 1output_h = (new_X.shape[2] - v) // self.stride + 1output = torch.zeros([X.shape[0], output_w, output_h])for i in range(0, output.shape[1]):for j in range(0, output.shape[2]):output[:, i, j] = torch.sum(new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * weight,dim=[1, 2])return outputdef forward(self, inputs):"""输入:- inputs:输入矩阵,shape=[B, D, M, N]- weights:P组二维卷积核,shape=[P, D, U, V]- bias:P个偏置,shape=[P, 1]"""feature_maps = []# 进行多次多输入通道卷积运算p = 0for w, b in zip(self.weight, self.bias):  # P个(w,b),每次计算一个特征图Zpmulti_outs = []# 循环计算每个输入特征图对应的卷积结果for i in range(self.in_channels):single = self.single_forward(inputs[:, i, :, :], w[i])multi_outs.append(single)# print("Conv2D in_channels:",self.in_channels,"i:",i,"single:",single.shape)# 将所有卷积结果相加feature_map = torch.sum(torch.stack(multi_outs), dim=0) + b  # Zpfeature_maps.append(feature_map)# print("Conv2D out_channels:",self.out_channels, "p:",p,"feature_map:",feature_map.shape)p += 1# 将所有Zp进行堆叠out = torch.stack(feature_maps, 1)return out

                       

        4.2 汇聚层算子

        汇聚层的作用是进行特征选择,降低特征数量,从而减少参数数量。由于汇聚之后特征图会变得更小,如果后面连接的是全连接层,可以有效地减小神经元的个数,节省存储空间并提高计算效率。

        常用的汇聚方法有两种,分别是:平均汇聚和最大汇聚。

  • 平均汇聚:将输入特征图划分为$2\times2$大小的区域,对每个区域内的神经元活性值取平均值作为这个区域的表示;
  • 最大汇聚:使用输入特征图的每个子区域内所有神经元的最大活性值作为这个区域的表示。

汇聚层输出的计算尺寸与卷积层一致,对于一个输入矩阵$\mathbf X\in\Bbb{R}^{M\times N}$和一个运算区域大小为$U\times V$的汇聚层,步长为$S$,对输入矩阵进行零填充,那么最终输出矩阵大小则为

        由于过大的采样区域会急剧减少神经元的数量,也会造成过多的信息丢失。目前,在卷积神经网络中比较典型的汇聚层是将每个输入特征图划分为$2\times2$大小的不重叠区域,然后使用最大汇聚的方式进行下采样。

        由于汇聚是使用某一位置的相邻输出的总体统计特征代替网络在该位置的输出,所以其好处是当输入数据做出少量平移时,经过汇聚运算后的大多数输出还能保持不变。比如:当识别一张图像是否是人脸时,我们需要知道人脸左边有一只眼睛,右边也有一只眼睛,而不需要知道眼睛的精确位置,这时候通过汇聚某一片区域的像素点来得到总体统计特征会显得很有用。这也就体现了汇聚层的平移不变特性。

        汇聚层的参数量和计算量

        由于汇聚层中没有参数,所以参数量为$0$;最大汇聚中,没有乘加运算,所以计算量为$0$,而平均汇聚中,输出特征图上每个点都对应了一次求平均运算.

class Pool2D(nn.Module):def __init__(self, size=(2, 2), mode='max', stride=1):super(Pool2D, self).__init__()# 汇聚方式self.mode = modeself.h, self.w = sizeself.stride = stridedef forward(self, x):output_w = (x.shape[2] - self.w) // self.stride + 1output_h = (x.shape[3] - self.h) // self.stride + 1output = torch.zeros([x.shape[0], x.shape[1], output_w, output_h])# 汇聚for i in range(output.shape[2]):for j in range(output.shape[3]):# 最大汇聚if self.mode == 'max':value_m = max(torch.max(x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h],dim=3).values[0][0])output[:, :, i, j] = torch.tensor(value_m)# 平均汇聚elif self.mode == 'avg':output[:, :, i, j] = torch.mean(x[:, :, self.stride * i:self.stride * i + self.w, self.stride * j:self.stride * j + self.h],dim=[2, 3])return outputinputs = torch.tensor([[[[1., 2., 3., 4.], [5., 6., 7., 8.], [9., 10., 11., 12.], [13., 14., 15., 16.]]]])
pool2d = Pool2D(stride=2)
outputs = pool2d(inputs)
print("input: {}, \noutput: {}".format(inputs.shape, outputs.shape))# 比较Maxpool2D与paddle API运算结果
maxpool2d_torch = nn.MaxPool2d(kernel_size=(2, 2), stride=2)
outputs_torch = maxpool2d_torch(inputs)
# 自定义算子运算结果
print('Maxpool2D outputs:', outputs)
# paddle API运算结果
print('nn.Maxpool2D outputs:', outputs_torch)# 比较Avgpool2D与torch API运算结果
avgpool2d_torch = nn.AvgPool2d(kernel_size=(2, 2), stride=2)
outputs_torch = avgpool2d_torch(inputs)
pool2d = Pool2D(mode='avg', stride=2)
outputs = pool2d(inputs)
# 自定义算子运算结果
print('Avgpool2D outputs:', outputs)
# paddle API运算结果
print('nn.Avgpool2D outputs:', outputs_torch)

5 学习torch.nn.Conv2d()、torch.nn.MaxPool2d();torch.nn.avg_pool2d(),简要介绍使用方法。

torch.nn.Conv2d()参数如下:

torch.nn.MaxPool2d()参数如下:

torch.nn.AvgPool2d()参数如下:

         具体的使用可以参照卷积层算子和池化层算子的代码中,有将调用库函数做比较~

6 分别用自定义卷积算子和torch.nn.Conv2d()编程实现下面的卷积运算

import torch.nn as nn
import torch
class Conv2D(nn.Module):def __init__(self, in_channels, Kernel, out_channels, kernel_size, stride=1, padding=0):super(Conv2D, self).__init__()self.weight = nn.Parameter(Kernel)# 创建偏置self.bias = nn.Parameter(torch.tensor([1, 0], dtype=torch.float32))# 步长self.stride = stride# 零填充self.padding = padding# 输入通道数self.in_channels = in_channels# 输出通道数self.out_channels = out_channelsdef single_forward(self, X, weight):"""输入:- X:输入矩阵,shape=[B, M, N],B为样本数量输出:- output:输出矩阵"""new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])  # 创建一个M'*N'的零矩阵new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X  # 将原数据放回u, v = weight.shapeoutput_w = (new_X.shape[1] - u) // self.stride + 1output_h = (new_X.shape[2] - v) // self.stride + 1output = torch.zeros([X.shape[0], output_w, output_h])for i in range(0, output.shape[1]):for j in range(0, output.shape[2]):output[:, i, j] = torch.sum(new_X[:, self.stride * i:self.stride * i + u, self.stride * j:self.stride * j + v] * weight,dim=[1, 2])return outputdef forward(self, inputs):"""输入:- inputs:输入矩阵,shape=[B, D, M, N]- weights:P组二维卷积核,shape=[P, D, U, V]- bias:P个偏置,shape=[P, 1]"""feature_maps = []# 进行多次多输入通道卷积运算p = 0for w, b in zip(self.weight, self.bias):  # P个(w,b),每次计算一个特征图Zpmulti_outs = []# 循环计算每个输入特征图对应的卷积结果for i in range(self.in_channels):single = self.single_forward(inputs[:, i, :, :], w[i])multi_outs.append(single)# print("Conv2D in_channels:",self.in_channels,"i:",i,"single:",single.shape)# 将所有卷积结果相加feature_map = torch.sum(torch.stack(multi_outs), dim=0) + b  # Zpfeature_maps.append(feature_map)# print("Conv2D out_channels:",self.out_channels, "p:",p,"feature_map:",feature_map.shape)p += 1# 将所有Zp进行堆叠out = torch.stack(feature_maps, 1)return outx = torch.tensor([[[0, 1, 1, 0, 2],[2, 2, 2, 2, 1],[1, 0, 0, 2, 0],[0, 1, 1, 0, 0],[1, 2, 0, 0, 2]],[[1, 0, 2, 2, 0],[0, 0, 0, 2, 0],[1, 2, 1, 2, 1],[1, 0, 0, 0, 0],[1, 2, 1, 1, 1]],[[2, 1, 2, 0, 0],[1, 0, 0, 1, 0],[0, 2, 1, 0, 1],[0, 1, 2, 2, 2],[2, 1, 0, 0, 1]]], dtype=torch.float32).reshape([1, 3, 5, 5])
Kernel = torch.tensor([[[[-1, 1, 0],[0, 1, 0],[0, 1, 1]],[[-1, -1, 0],[0, 0, 0],[0, -1, 0]],[[0, 0, -1],[0, 1, 0],[1, -1, -1]]],[[[1, 1, -1],[-1, -1, 1],[0, -1, 1]],[[0, 1, 0],[-1, 0, -1],[-1, 1, 0]],[[-1, 0, 0],[-1, 0, 1],[-1, 0, 0]]]], dtype=torch.float32).reshape([2, 3, 3, 3])
conv2d = Conv2D(in_channels=3, Kernel=Kernel, out_channels=2, kernel_size=3, padding=1, stride=2)
print("inputs shape:",x.shape)
outputs = conv2d(x)
print("Conv2D outputs shape:",outputs.shape)
print(outputs)conv2d_2 = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3, padding=1, stride=(2, 2), bias=True)
conv2d_2.weight = torch.nn.Parameter(Kernel)
conv2d_2.bias = torch.nn.Parameter(torch.tensor([1, 0], dtype=torch.float32))
out = conv2d_2(x)
print(out)

运行结果如下:

       

总结

        本次实验较为有难度,通过再次对池化层、卷积层,手推和调用库函数的代码的整理书写,对整个流程也更为明确了,就最后一个实践的时候有一点点困难,对于变量初始化、传入值的维度还是不明确这里总结一下。

对于卷积层的输入x:[batch_size(样本数), in_channel(图片的通道数), H, W(分别代表宽高)]

卷积核w:[out_channel(输出通道,输出需要有几个通道), in_channel(输入通道数,图片有几个通道, H, W(卷积核的宽高)]

偏置b:[1, out_channel(输出通道数)]

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构-堆和二叉树

目录 1.树的概念及结构 1.1 树的相关概念 1.2 树的概念 1.3 树的表示 1.4 树在实际中的应用(表示文件系统的目录树结构) 2.二叉树的概念及结构 2.1 概念 2.2 特殊的二叉树 2.3 二叉树的存储 3.堆的概念及结构 4.堆的实现 初始化堆 堆的插入…

计算机组成原理:大而快——层次化存储

原文链接www.xiaocr.fun/index.php/2023/11/14/计算机组成原理大而快-层次化存储/ 引言 关于两种局部性 时间局部性:如果某个数据被访问,那么在不久的将来它可能再次被访问空间局部性:如果某个数据项被访问,与它相邻的数据项可…

Qt文档阅读笔记-Fetch More Example解析

Fetch More Example这个例子说明了如何在视图模型上添加记录。 这个例子由一个对话框组成,在Directory的输入框中,可输入路径信息。应用程序会载入路径信息的文件信息等。不需要按回车键就能搜索。 当有大量数据时,需要对视图模型进行批量增…

剑指 Offer 07. 重建二叉树

title: 剑指 Offer 07. 重建二叉树 tags: 二叉树递归 categories:算法剑指 Offer 题目描述 输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,…

Spring 常见面试题

1、Spring概述 1.1、Spring是什么? Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。这些功能的底层都依赖于它的两个核心特性,也就是…

清华镜像源地址,适用于pip下载速度过慢从而导致下载失败的问题

清华地址 https://pypi.tuna.tsinghua.edu.cn/simple下载各种各样的包的指令模板 pip install XXX -i https://pypi.tuna.tsinghua.edu.cn/simple这样就行了,XXX代表的是你将要下载的包名称。 比如: pip install opencv-python -i https://pypi.tuna.…

nginx安装搭建

下载 免费开源版的官方网站:nginx news Nginx 有 Windows 版本和 Linux 版本,但更推荐在 Linux 下使用 Nginx; 下载nginx-1.14.2.tar.gz的源代码文件:wget http://nginx.org/download/nginx-1.14.2.tar.gz 我的习惯&#xff0…

NLP领域的突破催生大模型范式的形成与发展

当前的大模型领域的发展,只是范式转变的开始,基础大模型才刚刚开始改变人工智能系统在世界上的构建和部署方式。 1、大模型范式 1.1 传统思路(2019年以前) NLP领域历来专注于为具有挑战性的语言任务定义和设计系统&#xff0c…

僵尸进程问题如何处理

现象: 工作中遇到docker内有很多的僵尸进程,导致CPU过高,直接卡死。 原因: 每个进程都有一个唯一的标识,称为 pid,pid 是一个非负的整数值,使用 ps 命令可以查看其中 PID 是表示进程号。系统中…

[量化投资-学习笔记011]Python+TDengine从零开始搭建量化分析平台-MACD金死叉策略回测

在上一章节 MACD金死叉中结束了如何根据 MACD 金死叉计算交易信号。 目录 脚本说明文档(DevChat 生成)MACD 分析脚本安装依赖库参数配置查询与解析数据计算 MACD 指标判断金叉和死叉计算收益绘制图形运行脚本 本次将根据交易信号,模拟交易。更…

关灯游戏及扩展

7.8 图形界面应用案例——关灯游戏 题目: [案例]游戏初步——关灯游戏。 关灯游戏是很有意思的益智游戏,玩家通过单击关掉(或打开)一盏灯。如果关(掉(或打开)一个电灯,其周围(上下左右)的电灯也会触及开关,成…

Python实现扫雷游戏,代码示例,边玩边学+回忆童年!

文章目录 前言实现总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 前言 扫雷是一款益智类小游戏&#xff0…