目录
经典的图像分类模型
AlexNet
学习目标
- 知道AlexNet网络结构
- 能够利用AlexNet完成图像分类
2012年,AlexNet横空出世,该模型的名字源于论文第一作者的姓名Alex Krizhevsky 。AlexNet使用了8层卷积神经网络,以很大的优势赢得了ImageNet 2012图像识别挑战赛。它首次证明了学习到的特征可以超越手工设计的特征,从而一举打破计算机视觉研究的方向。
; AlexNet的网络架构
AlexNet与LeNet的设计理念非常相似,但也有显著的区别,其网络架构如下图所示:
该网络的特点是:
- AlexNet包含8层变换,有5层卷积和2层全连接隐藏层,以及1个全连接输出层
- AlexNet第一层中的卷积核形状是11×1111×11。第二层中的卷积核形状减小到5×55×5,之后全采用3×33×3。所有的池化层窗口大小为3×33×3、步幅为2的最大池化。
- AlexNet将sigmoid激活函数改成了ReLU激活函数,使计算更简单,网络更容易训练
- AlexNet通过dropOut来控制全连接层的模型复杂度。
- AlexNet引入了大量的图像增强,如翻转、裁剪和颜色变化,从而进一步扩大数据集来缓解过拟合。
在tf.keras中实现AlexNet模型:
net = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(filters=96,kernel_size=11,strides=4,activation='relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2),
tf.keras.layers.Conv2D(filters=256,kernel_size=5,padding='same',activation='relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2),
tf.keras.layers.Conv2D(filters=384,kernel_size=3,padding='same',activation='relu'),
tf.keras.layers.Conv2D(filters=384,kernel_size=3,padding='same',activation='relu'),
tf.keras.layers.Conv2D(filters=256,kernel_size=3,padding='same',activation='relu'),
tf.keras.layers.MaxPool2D(pool_size=3, strides=2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(4096,activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4096,activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10,activation='softmax')
])
我们构造一个高和宽均为227的单通道数据样本来看一下模型的架构:
X = tf.random.uniform((1,227,227,1)
y = net(X)
net.summay()
网络架构如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param
=================================================================
conv2d (Conv2D) (1, 55, 55, 96) 11712
_________________________________________________________________
max_pooling2d (MaxPooling2D) (1, 27, 27, 96) 0
_________________________________________________________________
conv2d_1 (Conv2D) (1, 27, 27, 256) 614656
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (1, 13, 13, 256) 0
_________________________________________________________________
conv2d_2 (Conv2D) (1, 13, 13, 384) 885120
_________________________________________________________________
conv2d_3 (Conv2D) (1, 13, 13, 384) 1327488
_________________________________________________________________
conv2d_4 (Conv2D) (1, 13, 13, 256) 884992
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (1, 6, 6, 256) 0
_________________________________________________________________
flatten (Flatten) (1, 9216) 0
_________________________________________________________________
dense (Dense) (1, 4096) 37752832
_________________________________________________________________
dropout (Dropout) (1, 4096) 0
_________________________________________________________________
dense_1 (Dense) (1, 4096) 16781312
_________________________________________________________________
dropout_1 (Dropout) (1, 4096) 0
_________________________________________________________________
dense_2 (Dense) (1, 10) 40970
=================================================================
Total params: 58,299,082
Trainable params: 58,299,082
Non-trainable params: 0
_________________________________________________________________
手写数字势识别
AlexNet使用ImageNet数据集进行训练,但因为ImageNet数据集较大训练时间较长,我们仍用前面的MNIST数据集来演示AlexNet。读取数据的时将图像高和宽扩大到AlexNet使用的图像高和宽227。这个通过 tf.image.resize_with_pad
来实现。
数据读取
import numpy as np
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = np.reshape(train_images,(train_images.shape[0],train_images.shape[1],train_images.shape[2],1))
test_images = np.reshape(test_images,(test_images.shape[0],test_images.shape[1],test_images.shape[2],1))
由于使用全部数据训练时间较长,我们定义两个方法获取部分数据,并将图像调整为227*227大小,进行模型训练:
def get_train(size):
index = np.random.randint(0, np.shape(train_images)[0], size)
resized_images = tf.image.resize_with_pad(train_images[index],227,227,)
return resized_images.numpy(), train_labels[index]
def get_test(size):
index = np.random.randint(0, np.shape(test_images)[0], size)
resized_images = tf.image.resize_with_pad(test_images[index],227,227,)
return resized_images.numpy(), test_labels[index]
调用上述两个方法,获取参与模型训练和测试的数据集:
train_images,train_labels = get_train(256)
test_images,test_labels = get_test(128)
为了让大家更好的理解,我们将数据展示出来:
for i in range(9):
plt.subplot(3,3,i+1)
plt.imshow(train_images[i].astype(np.int8).squeeze(), cmap='gray', interpolation='none')
plt.title("数字{}".format(train_labels[i]))
结果为:
我们就使用上述创建的模型进行训练和评估。
模型编译
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0, nesterov=False)
net.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
模型训练
net.fit(train_images,train_labels,batch_size=128,epochs=3,verbose=1,validation_split=0.1)
训练输出为:
Epoch 1/3
2/2 [==============================] - 3s 2s/step - loss: 2.3003 - accuracy: 0.0913 - val_loss: 2.3026 - val_accuracy: 0.0000e+00
Epoch 2/3
2/2 [==============================] - 3s 2s/step - loss: 2.3069 - accuracy: 0.0957 - val_loss: 2.3026 - val_accuracy: 0.0000e+00
Epoch 3/3
2/2 [==============================] - 4s 2s/step - loss: 2.3117 - accuracy: 0.0826 - val_loss: 2.3026 - val_accuracy: 0.0000e+00
模型评估
net.evaluate(test_images,test_labels,verbose=1)
输出为:
4/4 [==============================] - 1s 168ms/step - loss: 2.3026 - accuracy: 0.0781
[2.3025851249694824, 0.078125]
如果我们使用整个数据集训练网络,并进行评估的结果:
[0.4866700246334076, 0.8395]
VGG
学习目标
- 知道VGG网络结构的特点
- 能够利用VGG完成图像分类
2014年,牛津大学计算机视觉组(Visual Geometry Group)和Google DeepMind公司的研究员一起研发出了新的深度卷积神经网络:VGGNet,并取得了ILSVRC2014比赛分类项目的第二名,主要贡献是使用很小的卷积核(3×3)构建卷积神经网络结构,能够取得较好的识别精度,常用来提取图像特征的VGG-16和VGG-19。
; VGG的网络架构
VGG可以看成是加深版的AlexNet,整个网络由卷积层和全连接层叠加而成,和AlexNet不同的是,VGG中使用的都是小尺寸的卷积核(3×3),其网络架构如下图所示:
VGGNet使用的全部都是3x3的小卷积核和2x2的池化核,通过不断加深网络来提升性能。VGG可以通过重复使用简单的基础块来构建深度模型。
在tf.keras中实现VGG模型,首先来实现VGG块,它的组成规律是:连续使用多个相同的填充为1、卷积核大小为3×33×3的卷积层后接上一个步幅为2、窗口形状为2×22×2的最大池化层。卷积层保持输入的高和宽不变,而池化层则对其减半。我们使用 vgg_block
函数来实现这个基础的VGG块,它可以指定卷积层的数量 num_convs
和每层的卷积核个数num_filters:
def vgg_block(num_convs, num_filters):
blk = tf.keras.models.Sequential()
for _ in range(num_convs):
blk.add(tf.keras.layers.Conv2D(num_filters,kernel_size=3,
padding='same',activation='relu'))
blk.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2))
return blk
VGG16网络有5个卷积块,前2块使用两个卷积层,而后3块使用三个卷积层。第一块的输出通道是64,之后每次对输出通道数翻倍,直到变为512。
conv_arch = ((2, 64), (2, 128), (3, 256), (3, 512), (3, 512))
因为这个网络使用了13个卷积层和3个全连接层,所以经常被称为VGG-16,通过制定conv_arch得到模型架构后构建VGG16:
def vgg(conv_arch):
net = tf.keras.models.Sequential()
for (num_convs, num_filters) in conv_arch:
net.add(vgg_block(num_convs, num_filters))
net.add(tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10, activation='softmax')]))
return net
net = vgg(conv_arch)
我们构造一个高和宽均为224的单通道数据样本来看一下模型的架构:
X = tf.random.uniform((1,224,224,1))
y = net(X)
net.summay()
网络架构如下:
Model: "sequential_15"
_________________________________________________________________
Layer (type) Output Shape Param
=================================================================
sequential_16 (Sequential) (1, 112, 112, 64) 37568
_________________________________________________________________
sequential_17 (Sequential) (1, 56, 56, 128) 221440
_________________________________________________________________
sequential_18 (Sequential) (1, 28, 28, 256) 1475328
_________________________________________________________________
sequential_19 (Sequential) (1, 14, 14, 512) 5899776
_________________________________________________________________
sequential_20 (Sequential) (1, 7, 7, 512) 7079424
_________________________________________________________________
sequential_21 (Sequential) (1, 10) 119586826
=================================================================
Total params: 134,300,362
Trainable params: 134,300,362
Non-trainable params: 0
__________________________________________________________________
手写数字势识别
因为ImageNet数据集较大训练时间较长,我们仍用前面的MNIST数据集来演示VGGNet。读取数据的时将图像高和宽扩大到VggNet使用的图像高和宽224。这个通过 tf.image.resize_with_pad
来实现。
数据读取
首先获取数据,并进行维度调整:
import numpy as np
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = np.reshape(train_images,(train_images.shape[0],train_images.shape[1],train_images.shape[2],1))
test_images = np.reshape(test_images,(test_images.shape[0],test_images.shape[1],test_images.shape[2],1))
由于使用全部数据训练时间较长,我们定义两个方法获取部分数据,并将图像调整为224*224大小,进行模型训练:
def get_train(size):
index = np.random.randint(0, np.shape(train_images)[0], size)
resized_images = tf.image.resize_with_pad(train_images[index],224,224,)
return resized_images.numpy(), train_labels[index]
def get_test(size):
index = np.random.randint(0, np.shape(test_images)[0], size)
resized_images = tf.image.resize_with_pad(test_images[index],224,224,)
return resized_images.numpy(), test_labels[index]
调用上述两个方法,获取参与模型训练和测试的数据集:
train_images,train_labels = get_train(256)
test_images,test_labels = get_test(128)
为了让大家更好的理解,我们将数据展示出来:
for i in range(9):
plt.subplot(3,3,i+1)
plt.imshow(train_images[i].astype(np.int8).squeeze(), cmap='gray', interpolation='none')
plt.title("数字{}".format(train_labels[i]))
结果为:
我们就使用上述创建的模型进行训练和评估。
模型编译
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0)
net.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
模型训练
net.fit(train_images,train_labels,batch_size=128,epochs=3,verbose=1,validation_split=0.1)
遇到问题: 进行模型训练时,一直遇到内核似乎挂掉了,它很快将自动重启。
我参考一篇博客,建议卸载重新安装jupyter,然后我照做了,结果我之前安装的库都没了,都要重新安装,一个多小时白白浪费了。。。。最后还是存在这个问题,我真的是服了啊,谁这么缺德,写这样的文章,这不是害人吗????。。。。。痛苦!!!!
那就只有先跳过这一步了,反正有经验了,遇到非解决不可的时候再说吧。。。。。心疼我之前安装的库啊,说没就没了。。。。
训练输出为:
Epoch 1/3
2/2 [==============================] - 34s 17s/step - loss: 2.6026 - accuracy: 0.0957 - val_loss: 2.2982 - val_accuracy: 0.0385
Epoch 2/3
2/2 [==============================] - 27s 14s/step - loss: 2.2604 - accuracy: 0.1087 - val_loss: 2.4905 - val_accuracy: 0.1923
Epoch 3/3
2/2 [==============================] - 29s 14s/step - loss: 2.3650 - accuracy: 0.1000 - val_loss: 2.2994 - val_accuracy: 0.1538
模型评估
net.evaluate(test_images,test_labels,verbose=1)
输出为:
4/4 [==============================] - 5s 1s/step - loss: 2.2955 - accuracy: 0.1016
[2.2955007553100586, 0.1015625]
如果我们使用整个数据集训练网络,并进行评估的结果:
[0.31822608125209806, 0.8855]
GoogLeNet
学习目标
- 知道GoogLeNet网络结构的特点
- 能够利用GoogLeNet完成图像分类
GoogLeNet的名字不是GoogleNet,而是GoogLeNet,这是为了致敬LeNet。GoogLeNet和AlexNet/VGGNet这类依靠加深网络结构的深度的思想不完全一样。GoogLeNet在加深度的同时做了结构上的创新,引入了一个叫做Inception的结构来代替之前的卷积加激活的经典组件。GoogLeNet在ImageNet分类比赛上的Top-5错误率降低到了6.7%。
; Inception 块
GoogLeNet中的基础卷积块叫作Inception块,得名于同名电影《盗梦空间》(Inception)。Inception块在结构比较复杂,如下图所示:
Inception块里有4条并行的线路。前3条线路使用窗口大小分别是1×11×1、3×33×3和5×55×5的卷积层来抽取不同空间尺寸下的信息,其中中间2个线路会对输入先做1×11×1卷积来减少输入通道数,以降低模型复杂度。第4条线路则使用3×33×3最大池化层,后接1×11×1卷积层来改变通道数。4条线路都使用了合适的填充来使输入与输出的高和宽一致。最后我们将每条线路的输出在通道维上连结,并向后进行传输。
1×1卷积:
它的计算方法和其他卷积核一样,唯一不同的是它的大小是1×11×1,没有考虑在特征图局部信息之间的关系。
它的作用主要是:
- 实现跨通道的交互和信息整合
- 卷积核通道数的降维和升维,减少网络参数
为什么1x1卷积可以减少网络参数?
以inception模块为例,来说明1x1的卷积如何来减少模型参数:
(a)是未加入1x1卷积的inception模块,(b)是加入了1x1 卷积的inception模块。
我们以3x3卷积线路为例,假设输入的特征图大小为(28x28x192),输出特征图的通道数是128:
(a)图中该线路的参数量为:3x3x192x128 = 221184
(b)图中加入1x1卷积后通道为96,再送入3x3卷积中的参数量为:(1x1x192x96)+(3x3x96x128)=129024.
对比可知,加入1x1卷积后参数量减少了。
在tf.keras中实现Inception模块,各个卷积层卷积核的个数通过输入参数来控制,如下所示:
class Inception(tf.keras.layers.Layer):
def __init__(self, c1, c2, c3, c4):
super().__init__()
self.p1_1 = tf.keras.layers.Conv2D(
c1, kernel_size=1, activation='relu', padding='same')
self.p2_1 = tf.keras.layers.Conv2D(
c2[0], kernel_size=1, padding='same', activation='relu')
self.p2_2 = tf.keras.layers.Conv2D(c2[1], kernel_size=3, padding='same',
activation='relu')
self.p3_1 = tf.keras.layers.Conv2D(
c3[0], kernel_size=1, padding='same', activation='relu')
self.p3_2 = tf.keras.layers.Conv2D(c3[1], kernel_size=5, padding='same',
activation='relu')
self.p4_1 = tf.keras.layers.MaxPool2D(
pool_size=3, padding='same', strides=1)
self.p4_2 = tf.keras.layers.Conv2D(
c4, kernel_size=1, padding='same', activation='relu')
def call(self, x):
p1 = self.p1_1(x)
p2 = self.p2_2(self.p2_1(x))
p3 = self.p3_2(self.p3_1(x))
p4 = self.p4_2(self.p4_1(x))
outputs = tf.concat([p1, p2, p3, p4], axis=-1)
return outputs
指定通道数,对Inception模块进行实例化:
Inception(64, (96, 128), (16, 32), 32)
GoogLeNet模型
GoogLeNet主要由Inception模块构成,如下图所示:
整个网络架构我们分为五个模块,每个模块之间使用步幅为2的3×3最大池化层来减小输出高宽。
googLeNet的网络设计
; B1模块
第一模块使用一个64通道的7×7卷积层。
inputs = tf.keras.Input(shape=(224,224,3),name = "input")
x = tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same', activation='relu')(inputs)
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
B2模块
第二模块使用2个卷积层:首先是64通道的1×1卷积层,然后是将通道增大3倍的3×3卷积层。
x = tf.keras.layers.Conv2D(64, kernel_size=1, padding='same', activation='relu')(x)
x = tf.keras.layers.Conv2D(192, kernel_size=3, padding='same', activation='relu')(x)
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
B3模块
第三模块串联2个完整的Inception块。第一个Inception块的输出通道数为64+128+32+32=256。第二个Inception块输出通道数增至128+192+96+64=480。
x = Inception(64, (96, 128), (16, 32), 32)(x)
x = Inception(128, (128, 192), (32, 96), 64)(x)
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
B4模块
第四模块更加复杂。它串联了5个Inception块,其输出通道数分别是192+208+48+64=512、160+224+64+64=512、128+256+64+64=512、112+288+64+64=528和256+320+128+128=832。并且增加了辅助分类器,根据实验发现网络的中间层具有很强的识别能力,为了利用中间层抽象的特征,在某些中间层中添加含有多层的分类器,如下图所示:
实现如下所示:
def aux_classifier(x, filter_size):
x = tf.keras.layers.AveragePooling2D(
pool_size=5, strides=3, padding='same')(x)
x = tf.keras.layers.Conv2D(filters=filter_size[0], kernel_size=1, strides=1,
padding='valid', activation='relu')(x)
x = tf.keras.layers.Flatten()(x)
x = tf.keras.layers.Dense(units=filter_size[1], activation='relu')(x)
x = tf.keras.layers.Dense(units=10, activation='softmax')(x)
return x
b4模块的实现:
x = Inception(192, (96, 208), (16, 48), 64)(x)
aux_output_1 = aux_classifier(x, [128, 1024])
x = Inception(160, (112, 224), (24, 64), 64)(x)
x = Inception(128, (128, 256), (24, 64), 64)(x)
x = Inception(112, (144, 288), (32, 64), 64)(x)
aux_output_2 = aux_classifier(x, [128, 1024])
x = Inception(256, (160, 320), (32, 128), 128)(x)
x = tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')(x)
B5模块
第五模块有输出通道数为256+320+128+128=832和384+384+128+128=1024的两个Inception块。后面紧跟输出层,该模块使用全局平均池化层(GAP)来将每个通道的高和宽变成1。最后输出变成二维数组后接输出个数为标签类别数的全连接层。
全局平均池化层(GAP)
用来替代全连接层前的Flatten,将特征图每一通道中所有像素值相加后求平均,得到就是GAP的结果,在将其送入后续网络中进行计算
实现过程是:
x = Inception(256, (160, 320), (32, 128), 128)(x)
x = Inception(384, (192, 384), (48, 128), 128)(x)
x = tf.keras.layers.GlobalAvgPool2D()(x)
main_outputs = tf.keras.layers.Dense(10,activation='softmax')(x)
构建GoogLeNet模型并通过summary来看下模型的结构:
model = tf.keras.Model(inputs=inputs, outputs=[main_outputs,aux_output_1,aux_output_2])
model.summary()
Model: "functional_3"
_________________________________________________________________
Layer (type) Output Shape Param
=================================================================
input (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
conv2d_122 (Conv2D) (None, 112, 112, 64) 9472
_________________________________________________________________
max_pooling2d_27 (MaxPooling (None, 56, 56, 64) 0
_________________________________________________________________
conv2d_123 (Conv2D) (None, 56, 56, 64) 4160
_________________________________________________________________
conv2d_124 (Conv2D) (None, 56, 56, 192) 110784
_________________________________________________________________
max_pooling2d_28 (MaxPooling (None, 28, 28, 192) 0
_________________________________________________________________
inception_19 (Inception) (None, 28, 28, 256) 163696
_________________________________________________________________
inception_20 (Inception) (None, 28, 28, 480) 388736
_________________________________________________________________
max_pooling2d_31 (MaxPooling (None, 14, 14, 480) 0
_________________________________________________________________
inception_21 (Inception) (None, 14, 14, 512) 376176
_________________________________________________________________
inception_22 (Inception) (None, 14, 14, 512) 449160
_________________________________________________________________
inception_23 (Inception) (None, 14, 14, 512) 510104
_________________________________________________________________
inception_24 (Inception) (None, 14, 14, 528) 605376
_________________________________________________________________
inception_25 (Inception) (None, 14, 14, 832) 868352
_________________________________________________________________
max_pooling2d_37 (MaxPooling (None, 7, 7, 832) 0
_________________________________________________________________
inception_26 (Inception) (None, 7, 7, 832) 1043456
_________________________________________________________________
inception_27 (Inception) (None, 7, 7, 1024) 1444080
_________________________________________________________________
global_average_pooling2d_2 ( (None, 1024) 0
_________________________________________________________________
dense_10 (Dense) (None, 10) 10250
=================================================================
Total params: 5,983,802
Trainable params: 5,983,802
Non-trainable params: 0
___________________________________________________________
手写数字识别
因为ImageNet数据集较大训练时间较长,我们仍用前面的MNIST数据集来演示GoogLeNet。读取数据的时将图像高和宽扩大到图像高和宽224。这个通过 tf.image.resize_with_pad
来实现。
数据读取
首先获取数据,并进行维度调整:
import numpy as np
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = np.reshape(train_images,(train_images.shape[0],train_images.shape[1],train_images.shape[2],1))
test_images = np.reshape(test_images,(test_images.shape[0],test_images.shape[1],test_images.shape[2],1))
由于使用全部数据训练时间较长,我们定义两个方法获取部分数据,并将图像调整为224*224大小,进行模型训练:(与VGG中是一样的)
def get_train(size):
index = np.random.randint(0, np.shape(train_images)[0], size)
resized_images = tf.image.resize_with_pad(train_images[index],224,224,)
return resized_images.numpy(), train_labels[index]
def get_test(size):
index = np.random.randint(0, np.shape(test_images)[0], size)
resized_images = tf.image.resize_with_pad(test_images[index],224,224,)
return resized_images.numpy(), test_labels[index]
调用上述两个方法,获取参与模型训练和测试的数据集:
train_images,train_labels = get_train(256)
test_images,test_labels = get_test(128)
模型编译
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0)
net.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'],loss_weights=[1,0.3,0.3])
模型训练
net.fit(train_images,train_labels,batch_size=128,epochs=3,verbose=1,validation_split=0.1)
训练过程:
Epoch 1/3
2/2 [==============================] - 8s 4s/step - loss: 2.9527 - accuracy: 0.1174 - val_loss: 3.3254 - val_accuracy: 0.1154
Epoch 2/3
2/2 [==============================] - 7s 4s/step - loss: 2.8111 - accuracy: 0.0957 - val_loss: 2.2718 - val_accuracy: 0.2308
Epoch 3/3
2/2 [==============================] - 7s 4s/step - loss: 2.3055 - accuracy: 0.0957 - val_loss: 2.2669 - val_accuracy: 0.2308
模型评估
net.evaluate(test_images,test_labels,verbose=1)
输出为:
4/4 [==============================] - 1s 338ms/step - loss: 2.3110 - accuracy: 0.0781
[2.310971260070801, 0.078125]
延伸版本
GoogLeNet是以InceptionV1为基础进行构建的,所以GoogLeNet也叫做InceptionNet,在随后的⼏年⾥,研究⼈员对GoogLeNet进⾏了数次改进, 就又产生了InceptionV2,V3,V4等版本。
InceptionV2
在InceptionV2中将大卷积核拆分为小卷积核,将V1中的5×5的卷积用两个3×3的卷积替代,从而增加网络的深度,减少了参数。
; InceptionV3
将n×n卷积分割为1×n和n×1两个卷积,例如,一个的3×3卷积首先执行一个1×3的卷积,然后执行一个3×1的卷积,这种方法的参数量和计算量都比原来降低。
ResNet
学习目标
- 知道ResNet网络结构的特点
- 能够利用ResNet完成图像分类
网络越深,获取的信息就越多,特征也越丰富。但是在实践中,随着网络的加深,优化效果反而越差,测试数据和训练数据的准确率反而降低了。
针对这一问题,何恺明等人提出了残差网络(ResNet)在2015年的ImageNet图像识别挑战赛夺魁,并深刻影响了后来的深度神经网络的设计。
; 残差块
假设 F(x) 代表某个只包含有两层的映射函数, x 是输入, F(x)是输出。假设他们具有相同的维度。在训练的过程中我们希望能够通过修改网络中的 w和b去拟合一个理想的 H(x)(从输入到输出的一个理想的映射函数)。也就是我们的目标是修改F(x) 中的 w和b逼近 H(x) 。如果我们改变思路,用F(x) 来逼近 H(x)-x ,那么我们最终得到的输出就变为 F(x)+x(这里的加指的是对应位置上的元素相加,也就是element-wise addition),这里将直接从输入连接到输出的结构也称为shortcut,那整个结构就是残差块,ResNet的基础模块。
ResNet沿用了VGG全3×33×3卷积层的设计。残差块里首先有2个有相同输出通道数的3×33×3卷积层。每个卷积层后接BN层和ReLU激活函数,然后将输入直接加在最后的ReLU激活函数前,这种结构用于层数较少的神经网络中,比如ResNet34。若输入通道数比较多,就需要引入1×11×1卷积层来调整输入的通道数,这种结构也叫作瓶颈模块,通常用于网络层数较多的结构中。如下图所示:
上图左中的残差块的实现如下,可以设定输出通道数,是否使用1*1的卷积及卷积层的步幅。
import tensorflow as tf
from tensorflow.keras import layers, activations
class Residual(tf.keras.Model):
def __init__(self, num_channels, use_1x1conv=False, strides=1):
super(Residual, self).__init__()
self.conv1 = layers.Conv2D(num_channels,
padding='same',
kernel_size=3,
strides=strides)
self.conv2 = layers.Conv2D(num_channels, kernel_size=3, padding='same')
if use_1x1conv:
self.conv3 = layers.Conv2D(num_channels,
kernel_size=1,
strides=strides)
else:
self.conv3 = None
self.bn1 = layers.BatchNormalization()
self.bn2 = layers.BatchNormalization()
def call(self, X):
Y = activations.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
return activations.relu(Y + X)
1*1卷积用来调整通道数。
ResNet模型
ResNet模型的构成如下图所示:
ResNet网络中按照残差块的通道数分为不同的模块。第一个模块前使用了步幅为2的最大池化层,所以无须减小高和宽。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半。
下面我们来实现这些模块。注意,这里对第一个模块做了特别处理。
class ResnetBlock(tf.keras.layers.Layer):
def __init__(self,num_channels, num_residuals, first_block=False):
super(ResnetBlock, self).__init__()
self.listLayers=[]
for i in range(num_residuals):
if i == 0 and not first_block:
self.listLayers.append(Residual(num_channels, use_1x1conv=True, strides=2))
else:
self.listLayers.append(Residual(num_channels))
def call(self, X):
for layer in self.listLayers.layers:
X = layer(X)
return X
ResNet的前两层跟之前介绍的GoogLeNet中的一样:在输出通道数为64、步幅为2的7×77×7卷积层后接步幅为2的3×33×3的最大池化层。不同之处在于ResNet每个卷积层后增加了BN层,接着是所有残差模块,最后,与GoogLeNet一样,加入全局平均池化层(GAP)后接上全连接层输出。
class ResNet(tf.keras.Model):
def __init__(self,num_blocks):
super(ResNet, self).__init__()
self.conv=layers.Conv2D(64, kernel_size=7, strides=2, padding='same')
self.bn=layers.BatchNormalization()
self.relu=layers.Activation('relu')
self.mp=layers.MaxPool2D(pool_size=3, strides=2, padding='same')
self.resnet_block1=ResnetBlock(64,num_blocks[0], first_block=True)
self.resnet_block2=ResnetBlock(128,num_blocks[1])
self.resnet_block3=ResnetBlock(256,num_blocks[2])
self.resnet_block4=ResnetBlock(512,num_blocks[3])
self.gap=layers.GlobalAvgPool2D()
self.fc=layers.Dense(units=10,activation=tf.keras.activations.softmax)
def call(self, x):
x=self.conv(x)
x=self.bn(x)
x=self.relu(x)
x=self.mp(x)
x=self.resnet_block1(x)
x=self.resnet_block2(x)
x=self.resnet_block3(x)
x=self.resnet_block4(x)
x=self.gap(x)
x=self.fc(x)
return x
mynet=ResNet([2,2,2,2])
这里每个模块里有4个卷积层(不计算 1×1卷积层),加上最开始的卷积层和最后的全连接层,共计18层。这个模型被称为ResNet-18。通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。虽然ResNet的主体架构跟GoogLeNet的类似,但ResNet结构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。 在训练ResNet之前,我们来观察一下输入形状在ResNe的架构:
X = tf.random.uniform(shape=(1, 224, 224 , 1))
y = mynet(X)
mynet.summary()
Model: "res_net"
_________________________________________________________________
Layer (type) Output Shape Param
=================================================================
conv2d_2 (Conv2D) multiple 3200
_________________________________________________________________
batch_normalization_2 (Batch multiple 256
_________________________________________________________________
activation (Activation) multiple 0
_________________________________________________________________
max_pooling2d (MaxPooling2D) multiple 0
_________________________________________________________________
resnet_block (ResnetBlock) multiple 148736
_________________________________________________________________
resnet_block_1 (ResnetBlock) multiple 526976
_________________________________________________________________
resnet_block_2 (ResnetBlock) multiple 2102528
_________________________________________________________________
resnet_block_3 (ResnetBlock) multiple 8399360
_________________________________________________________________
global_average_pooling2d (Gl multiple 0
_________________________________________________________________
dense (Dense) multiple 5130
=================================================================
Total params: 11,186,186
Trainable params: 11,178,378
Non-trainable params: 7,808
_________________________________________________________________
手写数字势识别
因为ImageNet数据集较大训练时间较长,我们仍用前面的MNIST数据集来演示resNet。读取数据的时将图像高和宽扩大到ResNet使用的图像高和宽224。这个通过 tf.image.resize_with_pad
来实现。
数据读取
首先获取数据,并进行维度调整:
import numpy as np
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = np.reshape(train_images,(train_images.shape[0],train_images.shape[1],train_images.shape[2],1))
test_images = np.reshape(test_images,(test_images.shape[0],test_images.shape[1],test_images.shape[2],1))
由于使用全部数据训练时间较长,我们定义两个方法获取部分数据,并将图像调整为224*224大小,进行模型训练:
def get_train(size):
index = np.random.randint(0, np.shape(train_images)[0], size)
resized_images = tf.image.resize_with_pad(train_images[index],224,224,)
return resized_images.numpy(), train_labels[index]
def get_test(size):
index = np.random.randint(0, np.shape(test_images)[0], size)
resized_images = tf.image.resize_with_pad(test_images[index],224,224,)
return resized_images.numpy(), test_labels[index]
调用上述两个方法,获取参与模型训练和测试的数据集:
train_images,train_labels = get_train(256)
test_images,test_labels = get_test(128)
模型编译
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0)
mynet.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
模型训练
mynet.fit(train_images,train_labels,batch_size=128,epochs=3,verbose=1,validation_split=0.1)
训练输出为:
Epoch 1/3
2/2 [==============================] - 10s 5s/step - loss: 2.7811 - accuracy: 0.1391 - val_loss: 4.7931 - val_accuracy: 0.1923
Epoch 2/3
2/2 [==============================] - 8s 4s/step - loss: 2.2579 - accuracy: 0.2478 - val_loss: 2.9262 - val_accuracy: 0.2692
Epoch 3/3
2/2 [==============================] - 15s 7s/step - loss: 2.0874 - accuracy: 0.2609 - val_loss: 2.5882 - val_accuracy: 0.2692
模型评估
mynet.evaluate(test_images,test_labels,verbose=1)
输出为:
4/4 [==============================] - 1s 370ms/step - loss: 3.4343 - accuracy: 0.1016
[3.4342570304870605, 0.1015625]
Original: https://blog.csdn.net/qq_43966129/article/details/123075180
Author: 最白の白菜
Title: 经典的图像分类模型
相关阅读1
Title: wukong-robot 语音识别&语音合成
本文为我的 源代码阅读专栏 第一个工程的代码阅读——wukong-robot 一个优秀的开源智能音箱项目。本文解析的源代码文件为robot文件夹中智能语音相关文件——ASR.py和TTS.py
wukong-robot相关语音识别引擎介绍
wukong-robot相关语音识别调用代码放在ASR.py中。ASR.py文件对百度、腾讯、讯飞和阿里的语音识别接口统一封装成语音识别引擎,以方便wukong-robot自有调用。这些引擎都封装成了类,并且继承自抽象类AbstractASR。下面我先对AbstractASR进行分析,然后分别对各语音识别引擎进行简单的解析
AbstractASR类提供了 SLUG
属性用以标识当前对象的具体插件功能,同时提供了以下抽象方法供子类进行个性化实现:
- transcribe。实现具体的语音识别功能
- get_instance。用于实例化不同的语音识别引擎
- get_config。由各子类实现,用于获取不同引擎的配置
百度的语音识别引擎可以使用百度开放的python包aip,也可以使用百度开放的http接口调用。作者将百度开放的http接口封装在robot/sdk/BaiduSpeech.py中
作者将腾讯开放的语音识别http接口封装在robot/sdk/TencentSpeech.py中,然后由TencentASR来调用
作者将讯飞开放的语音识别http接口封装在robot/sdk/XunfeiSpeech.py中,然后由XunfeiASR来调用
作者将阿里开放的语音识别http接口封装在robot/sdk/AliSpeech.py中,然后由AliASR来调用
ASR.py中额外提供了两个方法:get_engines和get_engine_by_slug。
get_engines可以递归搜索并返回所有继承于AbstractASR的类
get_engine_by_slug通过slug参数寻找类,实例化此类为对象并返回
wukong-robot相关语音合成引擎介绍
wukong-robot相关语音识别调用代码放在TTS.py中。TTS.py文件对百度、腾讯、讯飞和阿里的语音识别接口统一封装成语音识别引擎,以方便wukong-robot自有调用。这些引擎都封装成了类,并且继承自抽象类AbstractTTS。下面我先对AbstractTTS进行分析,然后分别对各语音识别引擎进行简单的解析。(tts的代码布局基本与asr一致,熟悉代码的童鞋可忽略)
AbstractTTS类提供了 SLUG
属性用以标识当前对象的具体插件功能,同时提供了以下抽象方法供子类进行个性化实现:
- get_speech。实现具体的语音合成功能
- get_instance。用于实例化不同的语音合成引擎
- get_config。由各子类实现,用于获取不同引擎的配置
作者将HanTTS语音合成引擎中的部分代码拷贝到wukong-robot项目中,主要有atc.py——实现数字转汉字功能;process.py——语音合成处理逻辑。主要处理逻辑为:
百度的语音合成引擎可以使用百度开放的python包aip来实现
作者将腾讯开放的语音合成http接口封装在robot/sdk/TencentSpeech.py中,然后由TencentTTS来调用
作者将讯飞开放的语音合成http接口封装在robot/sdk/XunfeiSpeech.py中,然后由XunfeiTTS来调用
作者将阿里开放的语音合成http接口封装在robot/sdk/AliSpeech.py中,然后由AliTTS来调用
TTS.py中额外提供了两个方法:get_engines和get_engine_by_slug。
get_engines可以递归搜索并返回所有继承于AbstractTTS的类
get_engine_by_slug通过slug参数寻找类,实例化此类为对象并返回
用到的技术点
- classmethod修饰符。classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等
- 递归搜索并返回所有继承于AbstractASR的类:
def get_engines():
def get_subclasses(cls):
subclasses = set()
for subclass in cls.__subclasses__():
subclasses.add(subclass)
subclasses.update(get_subclasses(subclass))
return subclasses
return [engine for engine in list(get_subclasses(AbstractASR)) if hasattr(engine, 'SLUG') and engine.SLUG]
- pypinyin。将汉字转换为拼音包
- AudioSegment。pydub的一个包,用于声音分割
考虑到项目源代码可能会被作者更新,致使代码与我的总结文档不一致,下面列表中会提供原始代码链接和冻结代码链接。冻结代码链到我自己fork的工程中,大家如果需要找原始源码,可以点击原始代码链接
Original: https://blog.csdn.net/chongtong/article/details/112334074
Author: gdyshi
Title: wukong-robot 语音识别&语音合成
相关阅读2
Title: Python 人脸识别系统
简介
人脸识别不同于人脸检测。在人脸检测中,我们只检测了人脸的位置,在人脸识别任务中,我们识别了人的身份。
本文重点介绍使用库 face_recognition 实现人脸识别,该库基于深度学习技术,并承诺使用单个训练图像的准确率超过 96%。
识别系统用例
- 寻找失踪者
- 识别社交媒体上的帐户
- 识别汽车中的驾驶员
- 考勤系统
; 了解人脸识别的工作原理
- 我们将人的照片和他们的名字传递给模型。
- 该模型拍摄每张照片,将它们转换为某种数字编码,并将它们存储在一个列表中,并将所有标签(人名)存储在另一个列表中。
- 在预测阶段,当我们传递未知人的图片时,识别模型会将该人的图像转换为编码。
- 在将未知人的图像转换为编码后,它会尝试根据距离参数找到最相似的编码。与未知人的编码距离最小的编码将是最接近的匹配。
- 在获得最接近的匹配编码后,我们从该列表中获取该编码的索引并使用索引。我们找到检测到的人的名字。
传统人脸识别算法
传统的人脸识别算法不符合现代人脸识别标准。它们旨在使用旧的传统算法识别面部。
OpenCV 提供了一些传统的面部识别算法。
- Eigenfaces:http://www.scholarpedia.org/article/Eigenfaces
- 尺度不变特征变换 (Scale Invariant Feature Transform,SIFT):https://en.wikipedia.org/wiki/Scale-invariant_feature_transform
- Fisher faces:http://www.scholarpedia.org/article/Fisherfaces
- 局部二进制模式直方图 (Local Binary Patterns Histograms,LBPH):https://en.wikipedia.org/wiki/Local_binary_patterns
这些方法在提取图像信息和匹配输入和输出图像的方式上有所不同。
LBPH 算法是一种简单但非常有效的方法,仍在使用中,但与现代算法相比速度较慢。
; 人脸识别深度学习
有多种基于深度学习的面部识别算法可供使用。
- DeepFace
- DeepID series of systems
- FaceNet
- VGGFace
一般来说,基于地标的人脸识别器对人脸图像进行拍摄,并试图找到眉毛、嘴角、眼睛、鼻子、嘴唇等基本特征点。有60多个地标。
人脸识别涉及的步骤
- 人脸检测:定位人脸,记下每个人脸定位的坐标,并在每个人脸周围绘制一个边界框。
- 面部对齐。标准化人脸以获得快速训练。
- 特征提取。从面部图片中提取局部特征进行训练,这一步由不同的算法执行不同的操作。
- 人脸识别。将输入人脸与我们数据集中的一个或多个已知人脸进行匹配。
; 识别流程
使用python实现人脸识别系统。使用 face_recognition 库实现基于深度学习的人脸识别系统。
1. 设置人脸识别库:
为了安装人脸识别库,我们需要先安装dlib
- dlib:它是一个现代 C++ 工具包,包含与机器学习相关的算法和工具。
pip install dlib
- 安装实际的人脸识别库face recognition。
pip install face recognition
- Opencv用于一些图像预处理
pip install opencv
Note: Sometimes installing dlib throws error in that case install install the C++ development toolkit using vs_code community .
导入库
import cv2
import numpy as np
import face_recognition
2. 加载图片:
我们完成了库的安装和导入。是时候将一些示例图像加载到face_recognition库中了。
该face_recognition库仅支持 BGR 格式的图像。在打印输出图像时,我们应该使用 OpenCV 将其转换为 RGB。
Face_recognition仅加载 BGR 格式的图像。
import cv2
import numpy as np
import face_recognition
img_bgr = face_recognition.load_image_file('student_images/modi.jpg')
img_rgb = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2RGB)
cv2.imshow('bgr', img_bgr)
cv2.imshow('rgb', img_rgb)
cv2.waitKey
输出→ BGR 与 RGB
3.检测和定位人脸:
face_recognition库可以自行快速定位人脸,我们不需要使用haar_cascade或其他技术。
img_modi=face_recognition.load_image_file('student_images/modi.jpg')
img_modi_rgb = cv2.cvtColor(img_modi,cv2.COLOR_BGR2RGB)
face = face_recognition.face_locations(img_modi_rgb)[0]
copy = img_modi_rgb.copy()
cv2.rectangle(copy, (face[3], face[0]),(face[1], face[2]), (255,0,255), 2)
cv2.imshow('copy', copy)
cv2.imshow('MODI',img_modi_rgb)
cv2.waitKey(0)
4. 样本图像识别:
该face_recognition库基于深度学习,它支持单次学习,这意味着它需要一张图片来训练自己检测一个人。
img_modi = face_recognition.load_image_file('student_images/modi.jpg')
img_modi = cv2.cvtColor(img_modi,cv2.COLOR_BGR2RGB)
face = face_recognition.face_locations(img_modi)[0]
train_encode = face_recognition.face_encodings(img_modi)[0]
test = face_recognition.load_image_file('student_images/modi2.jpg')
test = cv2.cvtColor(test, cv2.COLOR_BGR2RGB)
test_encode = face_recognition.face_encodings(test)[0]
print(face_recognition.compare_faces([train_encode],test_encode))
cv2.rectangle(img_modi, (face[3], face[0]),(face[1], face[2]), (255,0,255), 1)
cv2.imshow('img_modi', img_modi)
cv2.waitKey(0)
上面的代码拍了两张总理的照片,因为两张照片都是同一个人,所以它返回了true。
- face_recognition.face_encodings(imgelon)[0]→返回传递图像的编码。
- face_recognition.compare_faces([train_encode],test_encode) 获取经过训练的编码列表和未知图像的测试编码。如果两个测试编码在训练编码中匹配,则返回True ;否则,它会返回False.
挑战
- 姿势:识别系统容易受到人体姿势的影响。面部识别系统将无法预测该人的面部是否不可见。
- 照明:照明会极大地改变面部轮廓。用于人脸识别的照片应在适当的照明条件下拍摄。
- 面部表情:不同的面部表情会导致对同一个人的图像的不同预测。
- 低分辨率:低分辨率图片包含的信息较少,因此不适合人脸识别训练。
Original: https://blog.csdn.net/qq_45066628/article/details/125073985
Author: kuokay
Title: Python 人脸识别系统
相关阅读3
Title: 006-中断
利用中断消除数码管的显示抖动
中断
引例:烧水,电视,电话,在某个场景下同时发生,此刻人作为处理这些问题的唯一主体,就需要有一个轻重缓急,按照紧迫程度(优先级)去解决这些问题。
中断使能寄存器——IE的控制位(8位控制):
- 第7位 EA——总中断, 为1时打开(总开关)
- 第1位 ET0——定时器0中断使能,为1时打开(相当于闹钟开关)
注意:
- 中断函数不需要调用,达到中断条件自动进入。
- 刷新时间固定1ms,不会受到计算过程的影响。
- 中断函数定义时,格式为:
void InterruptTime0() interrupt 1
其中的1表示中断函数编号,其计算方式为:x*8+3=?,?为中断向量地址的十进制值,例如本例采用T0中断,对应地址为0x000B=11,所以x=1。
- 中断优先级分为固有优先级和抢占优先级:
固有优先级:几个中断同时发生时,先处理优先级默认高的程序,处理过程中对其他一切中断不予响应。
抢占优先级:同时发生中断,优先级高的西先响应,在处理中断过程中,出现更高级别优先级,则先执行更改级别优先级再回来处理当前优先级。
实验一:我们对【005-数码管的使用】中 方案三 的程序进行改动,使用中断函数以解决抖动问题。
#include<reg52.h> //头文件
sbit LS1 = P2^2; //74LS138控制端
sbit LS2 = P2^3;
sbit LS3 = P2^4;
unsigned char shu_ma_guan[16] = {
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, //0~F的对应值
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
//全局变量
unsigned char LedBuff[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //定义8个数码管初始状态均为00000000,即都不显示
unsigned char flag1s = 0; //一秒到达标志位,为1即表示到达一秒
unsigned int cnt = 0;
unsigned char i = 0;
void main() //主函数
{
unsigned long sec = 0;
TMOD = 0x01; //定时器定义,晶振12Mhz,每次1ms
TH0 = 0xFC;
TL0 = 0x18;
TR0 = 1; //打开定时器
EA = 1; //打开中断总开关
ET0 = 1; //打开定时器0中断开关
while(1)
{
if(flag1s == 1 ) //判断是否到一秒
{
flag1s = 0; //时间到达一秒,标志复位
sec++; //时间到达一秒自动加1
LedBuff[0] = shu_ma_guan[sec%10]; //确定数码管在某个时间时各个位的显示数字
LedBuff[1] = shu_ma_guan[sec/10%10];
LedBuff[2] = shu_ma_guan[sec/100%10];
LedBuff[3] = shu_ma_guan[sec/1000%10];
LedBuff[4] = shu_ma_guan[sec/10000%10];
LedBuff[5] = shu_ma_guan[sec/100000%10];
LedBuff[6] = shu_ma_guan[sec/1000000%10];
LedBuff[7] = shu_ma_guan[sec/10000000%10];
}
}
}
//中断函数
void InterruptTime0() interrupt 1 //函数名中在:Interrupt——中断函数,Time0——定时器0中断
//不要忘记interrupt关键字及1(1的计算看文章开头)
{
TH0 = 0xFC; /定义定时器
TL0 = 0x18; //注意:右移TF0溢出时,进入定时器中断硬件自动清零,不在需要软件清零,省略语句TF0=0;
cnt++; //TFO溢出一次,自动加1
if(cnt == 1000) /时间到达一秒
{
cnt = 0; //复位,从新计次
flag1s = 1; //1秒到达标志置1
}
P0 = 0x00; //消除鬼影,使得数码管的所有段全灭
switch(i) //数码管刷新
{
case 0: LS3=0;LS2=0;LS1=0;i++;P0=LedBuff[0];break;
case 1: LS3=0;LS2=0;LS1=1;i++;P0=LedBuff[1];break;
case 2: LS3=0;LS2=1;LS1=0;i++;P0=LedBuff[2];break;
case 3: LS3=0;LS2=1;LS1=1;i++;P0=LedBuff[3];break;
case 4: LS3=1;LS2=0;LS1=0;i++;P0=LedBuff[4];break;
case 5: LS3=1;LS2=0;LS1=1;i++;P0=LedBuff[5];break;
case 6: LS3=1;LS2=1;LS1=0;i++;P0=LedBuff[6];break;
case 7: LS3=1;LS2=1;LS1=1;i=0;P0=LedBuff[7];break;
default: break;
}
}
</reg52.h>
Original: https://www.cnblogs.com/Yang-shihao/p/14353178.html
Author: shihao_Yang
Title: 006-中断