CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]一、机器如何识图先给大家出个脑筋急转弯:在白纸上画出一个大熊猫,一共需要几种颜色的画笔?——大家应该都知道,只需要一种黑色的画笔,只需要将大熊猫黑色的地方涂上黑色,一个大熊猫的图像就可以展现出来。我们画大熊猫的方式,其实和妈妈们的十字绣很接近——在给定的格子里,绣上不同的颜色,最后就可以展现出一幅特定的“图片”。而机器识图的方式正好和绣十字绣的方式相反,现在有了一幅图片,机器通过识别图片中…

大家好,又见面了,我是你们的朋友全栈君。

一、机器如何识图

先给大家出个脑筋急转弯:在白纸上画出一个大熊猫,一共需要几种颜色的画笔?——大家应该都知道,只需要一种黑色的画笔,只需要将大熊猫黑色的地方涂上黑色,一个大熊猫的图像就可以展现出来。

我们画大熊猫的方式,其实和妈妈们的十字绣很接近——在给定的格子里,绣上不同的颜色,最后就可以展现出一幅特定的“图片”。而机器识图的方式正好和绣十字绣的方式相反,现在有了一幅图片,机器通过识别图片中每个格子(像素点)上的颜色,将每个格子里的颜色都用数字类型存储,得到一张很大的数字矩阵,图片信息也就存储在这张数字矩阵中。 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]
上图中每一个格子代表一个像素点,像素点里的数字代表颜色码,颜色码范围是[0,255],(各式各样的颜色都是由红、绿、蓝三色组成,每个颜色都是0~255之间数字) 

我们在得到的一张大数字矩阵的基础上开展卷积神经网络识别工作: 
机器识图的过程:机器识别图像并不是一下子将一个复杂的图片完整识别出来,而是将一个完整的图片分割成许多个小部分,把每个小部分里具有的特征提取出来(也就是识别每个小部分),再将这些小部分具有的特征汇总到一起,就可以完成机器识别图像的过程了 

二、卷积神经网络原理介绍

用CNN卷积神经网络识别图片,一般需要的步骤有:

  1. 卷积层初步提取特征

  2. 池化层提取主要特征

  3. 全连接层将各部分特征汇总

  4. 产生分类器,进行预测识别

1、卷积层工作原理

卷积层的作用:就是提取图片每个小部分里具有的特征

假定我们有一个尺寸为6*6 的图像,每一个像素点里都存储着图像的信息。我们再定义一个卷积核(相当于权重),用来从图像中提取一定的特征。卷积核与数字矩阵对应位相乘再相加,得到卷积层输出结果。 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

(429 = 18*1+54*0+51*1+55*0+121*1+75*0+35*1+24*0+204*1) 
卷积核的取值在没有以往学习的经验下,可由函数随机生成,再逐步训练调整

当所有的像素点都至少被覆盖一次后,就可以产生一个卷积层的输出(下图的步长为1)

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

机器一开始并不知道要识别的部分具有哪些特征,是通过与不同的卷积核相作用得到的输出值,相互比较来判断哪一个卷积核最能表现该图片的特征——比如我们要识别图像中的某种特征(比如曲线),也就是说,这个卷积核要对这种曲线有很高的输出值,对其他形状(比如三角形)则输出较低。卷积层输出值越高,就说明匹配程度越高,越能表现该图片的特征

卷积层具体工作过程: 
比如我们设计的一个卷积核如下左,想要识别出来的曲线如下右:

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

现在我们用上面的卷积核,来识别这个简化版的图片——一只漫画老鼠 
CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

 

当机器识别到老鼠的屁股的时候,卷积核与真实区域数字矩阵作用后,输出较大:6600 

                                                                            CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

而用同一个卷积核,来识别老鼠的耳朵的时候,输出则很小:0 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

我们就可以认为:现有的这个卷积核保存着曲线的特征,匹配识别出来了老鼠的屁股是曲线的。我们则还需要其他特征的卷积核,来匹配识别出来老鼠的其他部分。卷积层的作用其实就是通过不断的改变卷积核,来确定能初步表征图片特征的有用的卷积核是哪些,再得到与相应的卷积核相乘后的输出矩阵

2、池化层工作原理

池化层的输入就是卷积层输出的原数据与相应的卷积核相乘后的输出矩阵 
池化层的目的:

  • 为了减少训练参数的数量,降低卷积层输出的特征向量的维度

  • 减小过拟合现象,只保留最有用的图片信息,减少噪声的传递

最常见的两种池化层的形式:

  • 最大池化:max-pooling——选取指定区域内最大的一个数来代表整片区域

  • 均值池化:mean-pooling——选取指定区域内数值的平均值来代表整片区域

举例说明两种池化方式:(池化步长为2,选取过的区域,下一次就不再选取)

  • CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

在4*4的数字矩阵里,以步长2*2选取区域,比如上左将区域[1,2,3,4]中最大的值4池化输出;上右将区域[1,2,3,4]中平均值5/2池化输出

3、全连接层工作原理

卷积层和池化层的工作就是提取特征,并减少原始图像带来的参数。然而,为了生成最终的输出,我们需要应用全连接层来生成一个等于我们需要的类的数量的分类器。

全连接层的工作原理和之前的神经网络学习很类似,我们需要把池化层输出的张量重新切割成一些向量,乘上权重矩阵,加上偏置值,然后对其使用ReLU激活函数,用梯度下降法优化参数既可。

三、卷积神经网络代码解析

1、数据集的读取,以及数据预定义

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf

#读取MNIST数据集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

sess = tf.InteractiveSession()

#预定义输入值X、输出真实值Y    placeholder为占位符
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
keep_prob = tf.placeholder(tf.float32)
x_image = tf.reshape(x, [-1,28,28,1])
  • MNIST是Google的很经典的一个做图像识别的数据集,图片大小是28*28的,需要先下载才能使用。

  • x、y_现在都是用占位符表示,当程序运行到一定指令,向x、y_传入具体的值后,就可以代入进行计算了

  • shape=[None, 784]是数据维度大小——因为MNIST数据集中每一张图片大小都是28*28的,计算时候是将28*28的二维数据转换成一个一维的、长度为784的新向量。None表示其值大小不定,意即选中的x、y_的数量暂时不定

  • keep_prob 是改变参与计算的神经元个数的值。(下有详细说明)

2、权重、偏置值函数

def weight_variable(shape):
    # 产生随机变量
    # truncated_normal:选取位于正态分布均值=0.1附近的随机值
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

truncated_normal()函数:选取位于正态分布均值=0.1附近的随机值

3、卷积函数、池化函数定义

def conv2d(x, W):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
  • 输入x是图片信息矩阵,W是卷积核的值

  • 卷积层conv2d()函数里strides参数要求第一个、最后一个参数必须是1;

  • 第二个参数表示:卷积核每次向右移动的步长

  • 第三个参数表示:卷积核每次向下移动的步长

  • 在上面卷积层的工作原理中,有展示strides=[1, 1, 1, 1]的动态图, 下面展示strides=[1, 2, 2, 1]时的情况:可以看到高亮的区域每次向右移动两格,向下移动两格 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

可以得到:当我们的卷积层步长值越大,得到的输出图像的规格就会越小。为了使得到的图像的规格和原图像保持一样的大,在输入图像四周填充足够多的 0 边界就可以解决这个问题,这时padding的参数就为“SAME”(利用边界保留了更多信息,并且也保留了图像的原大小)下图: 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

padding的另一个可选参数为“VALID”,和“SAME”不同的是:不用0来填充边界,这时得到的图像的规格就会小于原图像。新图像尺寸大小 = 原数据尺寸大小-卷积核尺寸大小+1(一般我们选用的padding都为“SAME”)

池化函数用简单传统的2×2大小的模板做max pooling,池化步长为2,选过的区域下次不再选取

4、第一次卷积+池化

#卷积层1网络结构定义
#卷积核1:patch=5×5;in size 1;out size 32;激活函数reLU非线性处理
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)         #output size 28*28*32
h_pool1 = max_pool_2x2(h_conv1)                                  #output size 14*14*32#卷积层2网络结构定义

 

  • 图片集是黑白单色,x_image 中的图片尺寸参数最后一个 = 1,彩色 = 3

  • 这里的卷积核大小是5*5的,输入的通道数是1,输出的通道数是32

  • 卷积核的值这里就相当于权重值,用随机数列生成的方式得到

  • 由于MNIST数据集图片大小都是28*28,且是黑白单色,所以准确的图片尺寸大小是28*28*1(1表示图片只有一个色层,彩色图片都是3个色层——RGB),所以经过第一次卷积后,输出的通道数由1变成32,图片尺寸变为:28*28*32(相当于拉伸了高)

  • 再经过第一次池化(池化步长是2),图片尺寸为14*14*32

5、第二次卷积+池化

#卷积核2:patch=5×5;in size 32;out size 64;激活函数reLU非线性处理
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)         #output size 14*14*64
h_pool2 = max_pool_2x2(h_conv2)                                  #output size 7 *7 *64

 

  • 这里的卷积核大小也是5*5的,第二次输入的通道数是32,输出的通道数是64

  • 第一次卷积+池化输出的图片大小是14*14*32,经过第二次卷积后图片尺寸变为:14*14*64

  • 再经过第二次池化(池化步长是2),最后输出的图片尺寸为7*7*64

6、全连接层1、全连接层2

# 全连接层1
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])   #[n_samples,7,7,64]->>[n_samples,7*7*64]
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 减少计算量dropout

# 全连接层2
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
  • 全连接层的输入就是第二次池化后的输出,尺寸是7*7*64,全连接层1有1024个神经元

  • tf.reshape(a,newshape)函数,当newshape = -1时,函数会根据已有的维度计算出数组的另外shape属性值

  • keep_prob 是为了减小过拟合现象。每次只让部分神经元参与工作使权重得到调整。只有当keep_prob = 1时,才是所有的神经元都参与工作

  • 全连接层2有10个神经元,相当于生成的分类器

  • 经过全连接层1、2,得到的预测值存入prediction 中

7、梯度下降法优化、求准确率

#二次代价函数:预测值与真实值的误差
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction))

#梯度下降法:数据太庞大,选用AdamOptimizer优化器
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)

#结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
  • 由于数据集太庞大,这里采用的优化器是AdamOptimizer,学习率是1e-4

  • tf.argmax(prediction,1)返回的是对于任一输入x预测到的标签值,tf.argmax(y_,1)代表正确的标签值

  • correct_prediction 这里是返回一个布尔数组。为了计算我们分类的准确率,我们将布尔值转换为浮点数来代表对与错,然后取平均值。例如:[True, False, True, True]变为[1,0,1,1],计算出准确率就为0.75

8、其他说明、保存参数

for i in range(1000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
        print("step", i, "training accuracy", train_accuracy)

    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

#保存模型参数
saver.save(sess, './model.ckpt')
print("test accuracy %g"%accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
  • batch 是来源于MNIST数据集,一个批次包含50条数据

  • feed_dict=({x: batch[0], y_: batch[1], keep_prob: 0.5}语句:是将batch[0],batch[1]代表的值传入x,y_;

  • keep_prob = 0.5  只有一半的神经元参与工作

  • 当完成训练时,程序会保存学习到的参数,不用下次再训练

  • 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑

  • 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑

  • 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑

 

四、源码及效果展示

# -*- coding:utf-8 -*-
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def weight_variable(shape):
    # 产生随机变量
    # truncated_normal:选取位于正态分布均值=0.1附近的随机值
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

def conv2d(x, W):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')


#读取MNIST数据集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

sess = tf.InteractiveSession()

#预定义输入值X、输出真实值Y    placeholder为占位符
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
keep_prob = tf.placeholder(tf.float32)
x_image = tf.reshape(x, [-1,28,28,1])
#print(x_image.shape)  #[n_samples,28,28,1]

#卷积层1网络结构定义
#卷积核1:patch=5×5;in size 1;out size 32;激活函数reLU非线性处理
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)         #output size 28*28*32
h_pool1 = max_pool_2x2(h_conv1)                                  #output size 14*14*32#卷积层2网络结构定义

#卷积核2:patch=5×5;in size 32;out size 64;激活函数reLU非线性处理
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)         #output size 14*14*64
h_pool2 = max_pool_2x2(h_conv2)                                  #output size 7 *7 *64

# 全连接层1
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])   #[n_samples,7,7,64]->>[n_samples,7*7*64]
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 减少计算量dropout

# 全连接层2
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
# prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

#二次代价函数:预测值与真实值的误差
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction))

#梯度下降法:数据太庞大,选用AdamOptimizer优化器
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)

#结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

saver = tf.train.Saver()  # defaults to saving all variables
sess.run(tf.global_variables_initializer())

for i in range(1000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
        print("step", i, "training accuracy", train_accuracy)

    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

#保存模型参数
saver.save(sess, './model.ckpt')

 

效果展示如下: 

CNN卷积神经网络原理讲解+图片识别应用(附源码)[通俗易懂]

 训练700次时候,成功率已经到达98%,越往后学习,准确率越高,但是 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑 特别提醒:运行非常占内存,而且运行到最后保存参数时,有可能卡死电脑

结束语:自己也是通过学习前辈们的讲解,自己慢慢摸索的,学习就是自我填坑的过程,希望我们都能坚持下来,也希望这篇能帮到你,朋友。

经过博友的反应,现已增加测试代码

# -*- coding:utf-8 -*-
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def weight_variable(shape):
    # 产生随机变量
    # truncated_normal:选取位于正态分布均值=0.1附近的随机值
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

def conv2d(x, W):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
    # stride = [1,水平移动步长,竖直移动步长,1]
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')


#读取MNIST数据集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

sess = tf.InteractiveSession()

#预定义输入值X、输出真实值Y    placeholder为占位符
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
keep_prob = tf.placeholder(tf.float32)
x_image = tf.reshape(x, [-1,28,28,1])
#print(x_image.shape)  #[n_samples,28,28,1]

#卷积层1网络结构定义
#卷积核1:patch=5×5;in size 1;out size 32;激活函数reLU非线性处理
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)         #output size 28*28*32
h_pool1 = max_pool_2x2(h_conv1)                                  #output size 14*14*32#卷积层2网络结构定义

#卷积核2:patch=5×5;in size 32;out size 64;激活函数reLU非线性处理
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)         #output size 14*14*64
h_pool2 = max_pool_2x2(h_conv2)                                  #output size 7 *7 *64

# 全连接层1
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])   #[n_samples,7,7,64]->>[n_samples,7*7*64]
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 减少计算量dropout

# 全连接层2
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

#结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

saver = tf.train.Saver()  # defaults to saving all variables
sess.run(tf.global_variables_initializer())

saver.restore(sess, './model.ckpt')           # 和之前保存的文件名一致
print("test accuracy %g"% sess.run(accuracy,feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
print("test accuracy %g"% accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

测试准确率高达96.6%

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/130481.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • C++ vector的用法(整理)

    C++vector的用法(整理)vector是向量类型,它可以容纳许多类型的数据,如若干个整数,所以称其为容器。vector是C++STL的一个重要成员,使用它时需要包含头文件:#include<vector>;一、vector的初始化:可以有五种方式,举例说明如下:(1)vector<int>a(10);//定义了10个整型元素…

  • apache工作模式与配置

    apache工作模式与配置

  • 多线程中线程锁的使用

    多线程中线程锁的使用在多线程的程序编写中,常常遇到共享资源使用冲突解决的苦恼。终于看到并测试了一种简单方法。线程锁的5个要素:CRITICAL_SECTIONg_cs; //定义线程锁InitializeCriticalSection(&g_cs);  //初始化DeleteCriticalSection(&g_cs);  //删除EnterCriticalSection(&g_c…

  • 很黄很暴力 地球不同版本

    很黄很暴力 地球不同版本国际英语:toopornographic,tooviolent.江氏英语:toosimple,toonaive阿拉伯语:الاباحيهجدا،عنيفةجدا纽约:Veryyellow,veryboompower德语:sehrpornografisch,sehrgewalttaetig挪威语:veldigporno,v

  • hibernate与mybatis的区别比较_mybatis中

    hibernate与mybatis的区别比较_mybatis中为方便以后准备面试,把一些常用的技术整理出来,会不定期更新。首先简单介绍下两者的概念:Hibernate :Hibernate 是当前最流行的ORM框架,对数据库结构提供了较为完整的封装。Mybatis:Mybatis同样也是非常流行的ORM框架,主要着力点在于POJO 与SQL之间的映射关系。下面具体从几个方面说一下两者的区别:1.两者最大的区

  • Linux发邮件shell脚本与群发邮件shell脚本

    Linux发邮件shell脚本与群发邮件shell脚本Linux发邮件shell脚本与群发邮件shell脚本说明:因为明天统计疫情健康打卡,需要通知同学完成打卡,最开始是一个人一个人的进行QQ通知,为了方便通知,我利用Linux写了一个shell定时群发邮件提醒脚本,如果大家有需要的可以参考我的方式方法下面我将我进行配置的方法分享给大家1.Linux安装邮件服务因为Linux默认没有安装mail邮件服务,我们将进行安装,输入安装命令等待几秒即可安装成功yuminstallmailx2.配置发送邮件服务即你的邮箱2.1在命令行中输入

    2022年10月20日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号