Kelp.Net是一个用c#编写的深度学习库

基于C#的机器学习--c# .NET中直观的深度学习

 

在本章中,将会学到:

l  如何使用Kelp.Net来执行自己的测试

l  如何编写测试

l  如何对函数进行基准测试

Kelp.Net是一个用c#编写的深度学习库。由于能够将函数链到函数堆栈中,它在一个非常灵活和直观的平台中提供了惊人的功能。它还充分利用OpenCL语言平台,在支持cpu和gpu的设备上实现无缝操作。深度学习是一个非常强大的工具,对Caffe和Chainer模型加载的本机支持使这个平台更加强大。您将看到,只需几行代码就可以创建一个100万个隐藏层的深度学习网络。

Kelp.Net还使得从磁盘存储中保存和加载模型变得非常容易。这是一个非常强大的特性,允许您执行训练、保存模型,然后根据需要加载和测试。它还使代码的产品化变得更加容易,并且真正地将训练和测试阶段分离开来。

其中,Kelp.Net是一个非常强大的工具,可以帮助你更好地学习和理解各种类型的函数、它们的交互和性能。例如,你可以使用不同的优化器在相同的网络上运行测试,并通过更改一行代码来查看结果。此外,可以轻松地设计你的测试,以查看使用不同批处理大小、隐藏层数、纪元、和更多内容。

什么是深度学习?

深度学习是机器学习和人工智能的一个分支,它使用许多层次的神经网络层(如果你愿意,可以称之为层次结构)来完成它的工作。在很多情况下,这些网络的建立是为了反映我们对人类大脑的认知,神经元像错综复杂的网状结构一样将不同的层连接在一起。这允许以非线性的方式进行数据处理。每一层都处理来自上一层的数据(当然,第一层除外),并将其信息传递到下一层。幸运的话,每一层都改进了模型,最终,我们实现了目标并解决了问题。

OpenCL

Kelp.Net 大量使用了开源计算语言(OpenCL).

OpenCL认为计算系统是由许多计算设备组成的,这些计算设备可以是中央处理器(CPU),也可以是附加在主机处理器(CPU)上的图形处理单元(GPU)等加速器。在OpenCL设备上执行的函数称为内核。单个计算设备通常由几个计算单元组成,这些计算单元又由多个处理元素(PS)组成。一个内核执行可以在所有或多个PEs上并行运行。

在OpenCL中,任务是在命令队列中调度的。每个设备至少有一个命令队列。OpenCL运行时将调度数据的并行任务分成几部分,并将这些任务发送给设备处理元素。

OpenCL定义了一个内存层次结构:

Global:由所有处理元素共享,并且具有高延迟。

Read-only:更小,更低的延迟,可由主机CPU写入,但不包括计算设备。

Local:由流程元素组共享。

Per-elemen:私有内存。

OpenCL还提供了一个更接近数学的API。这可以在固定长度向量类型的公开中看到,比如float4(单精度浮点数的四个向量),它的长度为2、3、4、8和16。如果你接触了更多的Kelp.Net并开始创建自己的函数,你将会遇到OpenCL编程。现在,只要知道它的存在就足够了,而且它正在被广泛地使用。

OpenCL 层次结构

在Kelp.Net各种OpenCL资源的层次结构如下图所示:

让我们更详细地描述这些。

Compute kernel

内核对象封装在程序中声明的特定内核函数,以及执行此内核函数时使用的参数值。

Compute program

由一组内核组成的OpenCL程序。程序还可以包含内核函数和常量数据调用的辅助函数。

Compute sampler

描述如何在内核中读取图像时对图像进行采样的对象。图像读取函数以采样器作为参数。采样器指定图像寻址模式(表示如何处理范围外的坐标)、过滤模式以及输入图像坐标是规范化还是非规范化值。

Compute device

计算设备是计算单元的集合。命令队列用于将命令排队到设备。命令示例包括执行内核或读写内存对象。OpenCL设备通常对应于GPU、多核CPU和其他处理器,如数字信号处理器(DSP)和cell/B.E.处理器。

Compute resource

可以由应用程序创建和删除的OpenCL资源。

Compute object

在OpenCL环境中由句柄标识的对象。

Compute context

计算上下文是内核执行的实际环境和定义同步和内存管理的域。

Compute command queue

命令队列是一个对象,它包含将在特定设备上执行的命令。命令队列是在上下文中的特定设备上创建的。对队列的命令按顺序排队,但可以按顺序执行,也可以不按顺序执行。

Compute buffer

存储线性字节集合的内存对象。可以使用在设备上执行的内核中的指针来访问缓冲区对象。

Compute event

事件封装了操作(如命令)的状态。它可用于同步上下文中的操作。

Compute image

存储2D或3D结构数组的内存对象。图像数据只能通过读写函数访问。读取函数使用采样器。

Compute platform

主机加上OpenCL框架管理的设备集合,允许应用程序共享资源并在平台上的设备上执行内核。

Compute user event

这表示用户创建的事件。

Kelp.Net Framework

函数

函数是Kelp.Net神经网络的基本组成部分。单个函数在函数堆栈中链接在一起,以创建功能强大且可能复杂的网络链。

我们需要了解四种主要的函数类型:

Single-input functions 单输入函数
Dual-input functions 双输入函数
Multi-input functions 多输入函数
Multi-output functions 多输出函数

当从磁盘加载网络时,函数也被链接在一起。

每个函数都有一个向前和向后的方法。

  public abstract NdArray[] Forward(params NdArray[] xs);

  public virtual void Backward([CanBeNull] params NdArray[] ys){}    

函数栈

函数堆栈是在向前、向后或更新传递中同时执行的函数层。当我们创建一个测试或从磁盘加载一个模型时,将创建函数堆栈。下面是一些函数堆栈的例子。

它们可以小而简单:

 FunctionStack nn = new FunctionStack(

                 new Linear(2, 2, name: "l1 Linear"),

                 new Sigmoid(name: "l1 Sigmoid"),

                 new Linear(2, 2, name: "l2 Linear"));

它们也可以在大一点:

FunctionStack nn = new FunctionStack(

                  // Do not forget the GPU flag if necessary

                 new Convolution2D(1, 2, 3, name: "conv1", gpuEnable: true),

                 new ReLU(),

                 new MaxPooling(2, 2),

                 new Convolution2D(2, 2, 2, name: "conv2", gpuEnable: true),

                 new ReLU(),

                 new MaxPooling(2, 2),

                 new Linear(8, 2, name: "fl3"),

                 new ReLU(),

                 new Linear(2, 2, name: "fl4")

             );

它们也可以非常大:

 FunctionStack nn = new FunctionStack(

                 new Linear(neuronCount * neuronCount, N, name: "l1 Linear"),//L1

                 new BatchNormalization(N, name: "l1 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l1 LeakyReLU"),

                 new Linear(N, N, name: "l2 Linear"), // L2

                 new BatchNormalization(N, name: "l2 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l2 LeakyReLU"),

                 new Linear(N, N, name: "l3 Linear"), // L3

                 new BatchNormalization(N, name: "l3 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l3 LeakyReLU"),

                 new Linear(N, N, name: "l4 Linear"), // L4

                 new BatchNormalization(N, name: "l4 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l4 LeakyReLU"),

                 new Linear(N, N, name: "l5 Linear"), // L5

                 new BatchNormalization(N, name: "l5 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l5 LeakyReLU"),

                 new Linear(N, N, name: "l6 Linear"), // L6

                 new BatchNormalization(N, name: "l6 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l6 LeakyReLU"),

                 new Linear(N, N, name: "l7 Linear"), // L7

                 new BatchNormalization(N, name: "l7 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l7 ReLU"),

                 new Linear(N, N, name: "l8 Linear"), // L8

                 new BatchNormalization(N, name: "l8 BatchNorm"),

                 new LeakyReLU(slope: 0.000001, name: "l8 LeakyReLU"),

                 new Linear(N, N, name: "l9 Linear"), // L9

                 new BatchNormalization(N, name: "l9 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l9 PolynomialApproximantSteep"),

                 new Linear(N, N, name: "l10 Linear"), // L10

                 new BatchNormalization(N, name: "l10 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l10 PolynomialApproximantSteep"),

                 new Linear(N, N, name: "l11 Linear"), // L11

                 new BatchNormalization(N, name: "l11 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l11 PolynomialApproximantSteep"),

                 new Linear(N, N, name: "l12 Linear"), // L12

                 new BatchNormalization(N, name: "l12 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l12 PolynomialApproximantSteep"),

                 new Linear(N, N, name: "l13 Linear"), // L13

                 new BatchNormalization(N, name: "l13 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l13 PolynomialApproximantSteep"),

                 new Linear(N, N, name: "l14 Linear"), // L14

                 new BatchNormalization(N, name: "l14 BatchNorm"),

                 new PolynomialApproximantSteep(slope: 0.000001, name: "l14 PolynomialApproximantSteep"),

                 new Linear(N, 10, name: "l15 Linear") // L15

             );

函数字典

函数字典是一个可序列化的函数字典(如前所述)。当从磁盘加载网络模型时,将返回一个函数字典,并且可以像在代码中创建函数堆栈一样对其进行操作。函数字典主要用于Caffe数据模型加载器。

Caffe1

Kelp.Net是围绕Caffe风格开发的,它支持许多特性。

Caffe为多媒体科学家和实践者提供了一个简洁和可修改的框架,用于最先进的深度学习算法和一组参考模型。该框架是一个bsd许可的c++库,带有Python和MATLAB绑定,用于在普通架构上高效地培训和部署通用卷积神经网络和其他深度模型。Caffe通过CUDA GPU计算满足了行业和互联网规模的媒体需求,在一个K40或Titan GPU上每天处理超过4000万张图像(大约每张图像2毫秒)。通过分离模型表示和实际实现,Caffe允许在平台之间进行试验和无缝切换,以简化开发和部署,从原型机到云环境。

“Chainer是一个灵活的神经网络框架。一个主要的目标是灵活性,因此它必须使我们能够简单而直观地编写复杂的体系结构。”

Chainer采用了按运行定义的方案,即通过实际的正向计算动态地定义网络。更准确地说,Chainer存储的是计算历史,而不是编程逻辑。例如,Chainer不需要任何东西就可以将条件和循环引入到网络定义中。按运行定义方案是Chainer的核心概念。这种策略也使得编写多gpu并行化变得容易,因为逻辑更接近于网络操作。

Kelp.Net可以直接从磁盘加载Chainer模型。

Loss

Kelp.Net由一个抽象的LossFunction类组成,设计用于确定如何评估损失的特定实例。

在机器学习中,损失函数或成本函数是将一个事件或一个或多个变量的值直观地映射到一个实数上的函数,表示与该事件相关的一些成本。Kelp.Net提供了两个开箱即用的损失函数:均方误差和软最大交叉熵。我们可以很容易地扩展它们以满足我们的需求。

模型保存和加载

Kelp.Net使得通过调用一个简单的类来保存和加载模型变得非常容易。ModelIO类同时提供了保存和加载方法,以便轻松地保存和加载到磁盘。下面是一个非常简单的例子,在训练、重新加载并对模型执行测试之后保存模型:

优化程序

优化算法根据模型的参数最小化或最大化误差函数。参数的例子有权重和偏差。它们通过最小化损失来帮助计算输出值并将模型更新到最优解的位置。扩展Kelp.Net以添加我们自己的优化算法是一个简单的过程,尽管添加OpenCL和资源方面的东西是一个协调的工作。

Kelp.Net提供了许多预定义的优化器,比如:

AdaDelta

    AdaGrad

    Adam

    GradientClipping

    MomentumSGD

    RMSprop

    SGD

这些都是基于抽象的优化器类。

数据集

Kelp.Net本身支持以下数据集:

CIFAR

    MNIST

CIFAR

CIFAR数据集有两种形式,CIFAR-10和CIFAR 100,它们之间的区别是类的数量。让我们简要地讨论一下两者。

CIFAR-10

CIFAR-10数据集包含10个类中的60000张32×32张彩色图像,每个类包含6000张图像。有50,000张训练图像和10,000张测试图像。数据集分为五个训练批次和一个测试批次,每个测试批次有10,000张图像。测试批次包含从每个类中随机选择的1000个图像。训练批次包含随机顺序的剩余图像,但是一些训练批次可能包含一个类的图像多于另一个类的图像。在他们之间,每批训练包含了5000张图片。

CIFAR-100

CIFAR-100数据集与CIFAR-10一样,只是它有100个类,每个类包含600个图像。每班有500张训练图片和100张测试图片。CIFAR-100中的100个类被分为20个超类。每个图像都有一个细标签(它所属的类)和一个粗标签(它所属的超类)。以下是CIFAR-100的类型列表:

Superclass

Classes

水生哺乳动物

海狸、海豚、水獭、海豹和鲸鱼

水族鱼,比目鱼,鳐鱼,鲨鱼和鱼

兰花、罂粟、玫瑰、向日葵和郁金香

食品容器

瓶子、碗、罐子、杯子和盘子

水果和蔬菜

苹果、蘑菇、桔子、梨和甜椒

家用电器设备

时钟、电脑键盘、灯、电话和电视

家用家具

床、椅子、沙发、桌子和衣柜

昆虫

蜜蜂、甲虫、蝴蝶、毛虫和蟑螂

大型食肉动物

熊、豹、狮子、老虎和狼

大型人造户外用品

桥、城堡、房子、道路和摩天大楼

大型自然户外景观

云、林、山、平原、海

大型杂食动物和食草动物

骆驼、牛、黑猩猩、大象和袋鼠

中等大小的哺乳动物

狐狸,豪猪,负鼠,浣熊和臭鼬

无脊椎动物

螃蟹、龙虾、蜗牛、蜘蛛和蠕虫

宝贝,男孩,女孩,男人,女人

爬行动物

鳄鱼、恐龙、蜥蜴、蛇和乌龟

小型哺乳动物

仓鼠,老鼠,兔子,鼩鼱和松鼠

枫树、橡树、棕榈树、松树和柳树

车辆1

自行车、公共汽车、摩托车、小货车和火车

车辆2

割草机、火箭、有轨电车、坦克和拖拉机

MNIST

MNIST数据库是一个手写数字的大型数据库,通常用于训练各种图像处理系统。该数据库还广泛用于机器学习领域的培训和测试。它有一个包含6万个例子的训练集和一个包含1万个例子的测试集。数字的大小已经标准化,并集中在一个固定大小的图像中,这使它成为人们想要尝试各种学习技术而不需要进行预处理和格式化的标准选择:

测试

测试是实际的执行事件,也可以说是小程序。由于OpenCL的使用,这些程序是在运行时编译的。要创建一个测试,您只需要提供一个封装代码的静态运行函数。Kelp.Net提供了一个预配置的测试器,这使得添加我们自己的测试变得非常简单。

现在,这里有一个简单的XOR测试程序的例子:

  public static void Run()

    {

         const int learningCount = 10000;

      Real[][] trainData =
{ new Real[] { 0, 0 },
new Real[] { 1, 0 },
new Real[] { 0, 1 },
new Real[] { 1, 1 }
};       Real[][] trainLabel =
{
new Real[] { 0 },
new Real[] { 1 },
new Real[] { 1 },
new Real[] { 0 }
}; FunctionStack nn = new FunctionStack( new Linear(2, 2, name: "l1 Linear"), new ReLU(name: "l1 ReLU"), new Linear(2, 1, name: "l2 Linear")); nn.SetOptimizer(new AdaGrad());    RILogManager.Default?.SendDebug("Training...");    for (int i = 0; i < learningCount; i++)
   {     //use MeanSquaredError for loss function     Trainer.Train(nn,trainData[0],trainLabel[0],newMeanSquaredError(), false);     Trainer.Train(nn, trainData[1], trainLabel[1], new MeanSquaredError(), false);     Trainer.Train(nn, trainData[2], trainLabel[2], new MeanSquaredError(), false);       Trainer.Train(nn, trainData[3], trainLabel[3], new MeanSquaredError(), false);       //If you do not update every time after training, you can update it as a mini batch nn.Update();
  }   RILogManager.Default?.SendDebug("Test Start...");   foreach (Real[] val in trainData)
  {   NdArray result = nn.Predict(val)[0];   RILogManager.Default?.SendDebug($"{val[0]} xor {val[1]} = {(result.Data[0] > 0.5 ? 1 : 0)} {result}");   }
}

Weaver

Weaver是Kelp.Net的重要组成部分。是运行测试时要执行的第一个对象调用。这个对象包含各种OpenCL对象,比如:

l  计算上下文

l  一组计算设备

l  计算命令队列

l  一个布尔标志,表明GPU是否为启用状态

l  可核心计算资源的字典

Weaver是用来告诉我们的程序我们将使用CPU还是GPU,以及我们将使用哪个设备(如果我们的系统能够支持多个设备)的地方。我们只需要在我们的程序开始时对Weaver做一个简单的调用,就像在这里看到的:

Weaver.Initialize(ComputeDeviceTypes.Gpu);

我们还可以避免使用weaver的初始化调用,并允许它确定需要自动发生什么。

以下是Weaver的基本内容。它的目的是构建(在运行时动态编译)将执行的程序:

      ///<summary>上下文</summary>
internal static ComputeContext Context; ///<summary>设备</summary>
private static ComputeDevice[] Devices; ///<summary>命令队列</summary>
internal static ComputeCommandQueue CommandQueue; ///<summary>设备的从零开始索引</summary>
private static int DeviceIndex; ///<summary>True启用,false禁用</summary>
internal static bool Enable; ///<summary>平台</summary>
private static ComputePlatform Platform; ///<summary>核心资源</summary>
private static readonly Dictionary<string, string> KernelSources = new Dictionary<string, string>();

编写测试

为Kelp.Net创建测试非常简单。我们编写的每个测试只需要公开一个运行函数。剩下的就是我们希望网络如何运作的逻辑了。

运行函数的一般准则是:

  1. 负载数据(真实或模拟):
  Real[][] trainData = new Real[N][];

  Real[][] trainLabel = new Real[N][];

  for (int i = 0; i < N; i++)

  {

      //Prepare Sin wave for one cycle

      Real radian = -Math.PI + Math.PI * 2.0 * i / (N - 1);

      trainData[i] = new[] { radian };

      trainLabel[i] = new Real[] { Math.Sin(radian)

  };

  2.创建函数堆栈:

  FunctionStack nn = new FunctionStack(

                 new Linear(1, 4, name: "l1 Linear"),

                 new Tanh(name: "l1 Tanh"),

                 new Linear(4, 1, name: "l2 Linear")

             );

  3.选择优化器:

  nn.SetOptimizer(new SGD());

  4.训练数据:

for (int i = 0; i < EPOCH; i++)
{ Real loss = 0; for (int j = 0; j < N; j++)
{
//When training is executed in the network, an error is returned to the return value
loss += Trainer.Train(nn, trainData[j], trainLabel[j], new MeanSquaredError());
} if (i % (EPOCH / 10) == 0)
{
RILogManager.Default?.SendDebug("loss:" + loss / N);
RILogManager.Default?.SendDebug("");
}
}

  5.测试数据:

RILogManager.Default?.SendDebug("Test Start...");

foreach (Real[] val in trainData)
{
RILogManager.Default?.SendDebug(val[0]+":"+nn.Predict(val)[0].Data[0]);

总结

在这一章中,我们进入了深度学习的世界。我们学习了如何使用Kelp.Net作为我们的研究平台,它几乎可以测试任何假设。我们还看到了Kelp.Net的强大功能和灵活性。

  下一章开始,我们进入实际应用阶段。

Kelp.Net是一个用c#编写的深度学习库的更多相关文章

  1. [AI开发]一个例子说明机器学习和深度学习的关系

    深度学习现在这么火热,大部分人都会有‘那么它与机器学习有什么关系?’这样的疑问,网上比较它们的文章也比较多,如果有机器学习相关经验,或者做过类似数据分析.挖掘之类的人看完那些文章可能很容易理解,无非就 ...

  2. 从零开始编写深度学习库(五)PoolingLayer 网络层CPU编写

    记录:编写卷积层和池化层,比较需要注意的细节就是边界问题,还有另外一个就是重叠池化的情况,这两个小细节比较重要,边界问题pad在反向求导的时候,由于tensorflow是没有计算的,另外一个比较烦人的 ...

  3. 从零开始编写深度学习库(五)Eigen Tensor学习笔记2.0

    1.extract_image_patches函数的使用: 假设Eigen::Tensor形状为(3,8,8,9),现在要对第二维.第三维根据size大小为(2,2),stride=(2,2),那么如 ...

  4. CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL

    CSharpGL(34)以从零编写一个KleinBottle渲染器为例学习如何使用CSharpGL +BIT祝威+悄悄在此留下版了个权的信息说: 开始 本文用step by step的方式,讲述如何使 ...

  5. 【转】发布一个基于NGUI编写的UI框架

    发布一个基于NGUI编写的UI框架 1.加载,显示,隐藏,关闭页面,根据标示获得相应界面实例 2.提供界面显示隐藏动画接口 3.单独界面层级,Collider,背景管理 4.根据存储的导航信息完成界面 ...

  6. artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口

    artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应 ...

  7. Fastjson是一个Java语言编写的高性能功能完善的JSON库。

    简介 Fastjson是一个Java语言编写的高性能功能完善的JSON库. 高性能 fastjson采用独创的算法,将parse的速度提升到极致,超过所有json库,包括曾经号称最快的jackson. ...

  8. 让你编写的控件库在 XAML 中有一个统一的漂亮的命名空间(xmlns)和命名空间前缀

    原文 让你编写的控件库在 XAML 中有一个统一的漂亮的命名空间(xmlns)和命名空间前缀 在 WPF XAML 中使用自己定义的控件时,想必大家都能在 XAML 中编写出这个控件的命名空间了.然而 ...

  9. 公布一个基于 Reactor 模式的 C++ 网络库

    公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...

随机推荐

  1. C# NuGet常用命令

    命令执行位置:工具=〉Nuget包管理器=〉程序包管理器控制台 一.安装 1.安装指定版本类库install-package <程序包名> -version <版本号> 2.安 ...

  2. MVC 创建Controllers 发生 EntityType has no key defined error

    发生如图错误 只需要在对应的类中指定Key即可 添加引用 : System.ComponentModel.DataAnnotations 参考:https://stackoverflow.com/qu ...

  3. JS运算符类型

    一.运算符类型 1.算术运算符: 用于各类数值运算,包括加(+).减(-).乘(*).除(/).求余(或称模运算,%).自增(++).自减(--)共七种. 2.关系运算符: 用于比较运算.包括大于(& ...

  4. redis笔记2

    分布式锁的实现 锁是用来解决什么问题的; 一个进程中的多个线程,多个线程并发访问同一个资源的时候,如何解决线程安全问题. 一个分布式架构系统中的两个模块同时去访问一个文件对文件进行读写操作 多个应用对 ...

  5. Linux出现You have new mail in /var/spool/mail/root提示,关闭邮件提示清理内容的解决方案

    Linux出现You have new mail in /var/spool/mail/root提示,关闭邮件提示的解决方案 有的时候敲一下回车,就出来You have new mail in /va ...

  6. js javascript 如何获取某个值在数组中的下标

    js 某个值在数组中的下标javascript中知道一个数组中的一个元素的值,如何获取数组下标JS 获取数组某个元素下标 函数方法 采用prototype原型实现方式,查找元素在数组中的索引值js查找 ...

  7. Java中的参数验证(非Spring版)

    1. Java中的参数验证(非Spring版) 1.1. 前言 为什么我总遇到这种非正常问题,我们知道很多时候我们的参数校验都是放在controller层的传入参数进行校验,我们常用的校验方式就是引入 ...

  8. python 使用turtule绘制递归图形(螺旋、二叉树、谢尔宾斯基三角形)

    插图工具使用Python内置的turtle模块,为什么叫这个turtle乌龟这个名字呢,可以这样理解,创建一个乌龟,乌龟能前进.后退.左转.右转,乌龟的尾巴朝下,它移动时就会画一条线.并且为了增加乌龟 ...

  9. i春秋四周年福利趴丨一纸证书教你赢在起跑线

    i春秋四周年庆典狂欢已接近尾声 作为压轴福利 CISP-PTE认证和 CISAW-Web安全认证 迎来了史无前例的超低折扣 每个行业都有特定的精英证书,例如会计行业考取的是注册会计师证,建筑行业是一级 ...

  10. CnetOS6.7编译安装MariaDB

    --安装所需软件包 [root@localhost mariadb-10.1.14]# yum install bison bison-devel ncurses libxml2 libxml2-de ...