ROS与机器学习(三)——手写数字识别

2023-05-16

ROS与机器学习(三)——手写数字识别

目录

    • 1、理论基础
    • 2、TensorFlow中的MNIST例程
      • 2.1 创建模型
      • 2.2 训练模型
      • 2.3 评估模型
    • 3、基于ROS实现MNIST
      • 3.1 初始化ROS节点
      • 3.2 设置ROS参数
      • 3.3 加入Subscriber和Publisher
      • 3.4 加入回调函数处理图像
      • 3.5 发布识别结果

1、理论基础

MNIST的下载链接:http://yann.lecun.com/exdb/mnist/。
MNIST是一个包含数字0~9的手写体图片数据集,图片已归一化为以手写数 字为中心的28*28规格的图片。
MNIST 由训练集与测试集两个部分组成,各部分的规模如下:

   训练集:60,000个手写体图片及对应标签 

   测试集:10,000个手写体图片及对应标签

在这里插入图片描述MNIST 是一个很有名的手写数字识别数据集,对于每张图片,存储的方式是一个 28 * 28 的矩阵,但是我们在导入数据进行使用的时候会自动展平成 1 * 784(28 * 28)的向量,这在TensorFlow导入很方便,在使用命令下载数据之后,可以看到有四个数据集:
在这里插入图片描述

2、TensorFlow中的MNIST例程

MNIST 是TensorFlow中的入门例程。先用原生MNIST例程的代码实现。

#!/usr/bin/env python3  
# -*- coding: utf-8 -*-  
  
import input_data  
import tensorflow as tf  
  
#MNIST数据输入  
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)  
  
x = tf.placeholder(tf.float32,[None, 784]) #图像输入向量  
W = tf.Variable(tf.zeros([784,10]))        #权重,初始化值为全零  
b = tf.Variable(tf.zeros([10]))            #偏置,初始化值为全零  
  
#进行模型计算,y是预测,y_ 是实际  
y = tf.nn.softmax(tf.matmul(x,W) + b)  
  
y_ = tf.placeholder("float", [None,10])  
  
#计算交叉熵  
cross_entropy = -tf.reduce_sum(y_*tf.log(y))  
#接下来使用BP算法来进行微调,以0.01的学习速率  
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)  
  
#上面设置好了模型,添加初始化创建变量的操作  
init = tf.global_variables_initializer()  
#启动创建的模型,并初始化变量  
sess = tf.Session()  
sess.run(init)  

#开始训练模型,循环训练1000次  
for i in range(1000):  
    #随机抓取训练数据中的100个批处理数据点  
    batch_xs, batch_ys = mnist.train.next_batch(100)  
    sess.run(train_step, feed_dict={x:batch_xs,y_:batch_ys})  
      
''''' 进行模型评估 '''  
#判断预测标签和实际标签是否匹配  
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1))   
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))  
#计算所学习到的模型在测试数据集上面的正确率  
print( sess.run(accuracy, feed_dict={x:mnist.test.images, y_:mnist.test.labels}) )  

2.1 创建模型

x = tf.placeholder(tf.float32,[None, 784])

其中x不是一个特定的值,而是一个占位符placeholder,在TensorFlow运行计算时再输入这个值。我们希望能够输入任意数量的MNIST图像,每张图都可以展开为784维的向量。用二维的浮点数张量来表示这些图,这个张量的形状是[None,784],其中None表示此张量的第一个维度可以是任何长度。

W = tf.Variable(tf.zeros([784,10]))  
b = tf.Variable(tf.zeros([10]))

模型也需要权重值和偏置量。我们赋予tf.Variable不同的初值来创建不同的Variable:这里用全为零的张量来初始化W和b。
现在,可以实现模型了,只需要一行代码:

y = tf.nn.softmax(tf.matmul(x,W) + b)  

用tf.matmul(x,W)表示x乘以W,对应模型中的,这里x是一个二维张量,拥有多个输入;然后再加上b,把两者的和输入tf.softmax 函数中。

2.2 训练模型

为训练模型,我们需要定义一个指标来评估这个模型,也就是代价函数。常见的代价函数是“交叉熵”(cross-entropy)。交叉熵产生于信息压缩编码技术,但是后来演变成为从博弈论到机器学习等其他领域里的重要技术手段。
为计算交叉熵,首先需要添加一个用于输入真实值的占位符:

y_ = tf.placeholder("float", [None,10])

然后计算交叉熵:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

这里的交叉熵不仅用来衡量一对预测和真实值,也是所有100幅图片交叉熵的总和。相比单一数据点预测,对于100个数据点的预测表现能更好地描述模型性能。
TensorFlow在后台为计算图增加了一系列新的计算操作单元,用于实现反向传播算法和梯度下降算法,然后返回一个单一操作。当运行这个操作时,将用梯度下降算法训练模型,微调变量,不断减少函数值。

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) 

这里我们要求TensorFlow用梯度下降算法以0.01的学习效率最小化交叉熵。当然TensorFlow也提供了许多其他种类的优化算法,只要简单调整这一行代码即可更换。
在运行计算之前,需要初始化创建的变量:

init = tf.global_variables_initializer()

现在可以通过Session启动模型,并且初始化变量:

sess = tf.Session()  
sess.run(init)

然后开始训练模型,这里让模型循环训练1000次。

for i in range(1000):    
    batch_xs, batch_ys = mnist.train.next_batch(100)  
    sess.run(train_step, feed_dict={x:batch_xs,y_:batch_ys})

该循环的每个步骤都会随机抓取训练数据中的100个批处理数据点,然后用这些数据点作为参数替换之前的占位符来运行train_step。
理想情况下,我们希望用所有数据进行每一步训练,从而实现更好的训练结果,但这显然需要很大的计算量。所以,每一次训练可以使用不同的数据子集,这样既可以减少计算量,又可以最大化地学习到数据集的总体特性。

2.3 评估模型

首先找出预测正确的标签。tf.argmax() 是一个非常有用的函数,它能给出某个tensor对象在某一维上数据最大值所在的索引值。由于标签向量由0、1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1)返回的是模型对于任意输入x预测到的标签值,而tf.argmax(y_,1)代表正确的标签,可以用tf.equal()来检测预测是否与真实标签匹配(索引位置一样表示匹配)。

correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(y_,1)) 

以上代码会得到一组布尔值。为了确定正确预测项的比例,可以把布尔值转换成浮点数,然后取平均值。例如[True,False,True,True]会变成[1.0,0.0,1.0,1.0],取平均值后得到0.75。

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

最后,计算所学习到的模型在测试数据集上的正确率。

print( sess.run(accuracy, feed_dict={x:mnist.test.images, y_:mnist.test.labels}) )

执行程序,从运行效果可以看出最终结果应该是在91%左右,这个结果并不算太好,因为我们仅使用了一个非常简单的模型。如果进一步优化模型,就可以得到97%以上的正确率,最好的模型甚至可以获得超过99.7%的正确率。

3、基于ROS实现MNIST

结合ROS,利用MNIST识别输入图像中的手写数字,并且将识别结果发布出去。

#!/usr/bin/env python 
# -*- coding: utf-8 -*-
 
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import Int16
from cv_bridge import CvBridge
import cv2
import numpy as np
import input_data  
import tensorflow as tf

class MNIST():
    def __init__(self):
        image_topic = rospy.get_param("~image_topic", "")

        self._cv_bridge = CvBridge()

        #MNIST数据输入  
        self.mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)  
          
        self.x = tf.placeholder(tf.float32,[None, 784]) #图像输入向量  
        self.W = tf.Variable(tf.zeros([784,10]))        #权重,初始化值为全零  
        self.b = tf.Variable(tf.zeros([10]))            #偏置,初始化值为全零  
          
        #进行模型计算,y是预测,y_ 是实际  
        self.y = tf.nn.softmax(tf.matmul(self.x, self.W) + self.b)  
          
        self.y_ = tf.placeholder("float", [None,10])  
          
        #计算交叉熵  
        self.cross_entropy = -tf.reduce_sum( self.y_*tf.log(self.y))  
        #接下来使用BP算法来进行微调,以0.01的学习速率  
        self.train_step = tf.train.GradientDescentOptimizer(0.01).minimize(self.cross_entropy)  
          
        #上面设置好了模型,添加初始化创建变量的操作  
        self.init = tf.global_variables_initializer()  
        #启动创建的模型,并初始化变量  
        self.sess = tf.Session()  
        self.sess.run(self.init)  

        #开始训练模型,循环训练1000次  
        for i in range(1000):  
            #随机抓取训练数据中的100个批处理数据点  
            batch_xs, batch_ys = self.mnist.train.next_batch(100)  
            self.sess.run(self.train_step, feed_dict={self.x:batch_xs, self.y_:batch_ys})  

        ''''' 进行模型评估 '''  
        #判断预测标签和实际标签是否匹配  
        correct_prediction = tf.equal(tf.argmax(self.y,1),tf.argmax(self.y_,1))   
        self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))  
       
        #计算所学习到的模型在测试数据集上面的正确率  
        print( "The predict accuracy with test data set: \n")
        print( self.sess.run(self.accuracy, feed_dict={self.x:self.mnist.test.images, self.y_:self.mnist.test.labels}) )  

        self._sub = rospy.Subscriber(image_topic, Image, self.callback, queue_size=1)
        self._pub = rospy.Publisher('result', Int16, queue_size=1)

    def callback(self, image_msg):
        #预处理接收到的图像数据
        cv_image = self._cv_bridge.imgmsg_to_cv2(image_msg, "bgr8")
        cv_image_gray = cv2.cvtColor(cv_image, cv2.COLOR_RGB2GRAY)
        ret,cv_image_binary = cv2.threshold(cv_image_gray,128,255,cv2.THRESH_BINARY_INV)
        cv_image_28 = cv2.resize(cv_image_binary,(28,28))
        
        #转换输入数据shape,以便于用于网络中
        np_image = np.reshape(cv_image_28, (1, 784))

        predict_num = self.sess.run(self.y, feed_dict={self.x:np_image, self.y_:self.mnist.test.labels})
        
        #找到概率最大值
        answer = np.argmax(predict_num, 1)
        
        #发布识别结果
        rospy.loginfo('%d' % answer)
        self._pub.publish(answer)
        rospy.sleep(1) 

    def main(self):
        rospy.spin()

if __name__ == '__main__':
    rospy.init_node('ros_tensorflow_mnist')
    tensor = MNIST()
    rospy.loginfo("ros_tensorflow_mnist has started.")
    tensor.main()

在MNIST的基础上进行一些简单修改,使之融入ROS中。

3.1 初始化ROS节点

封装ROS节点的第一步是加入ROS节点的初始化,代码如下:

rospy.init_node('ros_tensorflow_mnist')

3.2 设置ROS参数

将图像话题名作为参数传入节点中,便于灵活设置,代码如下:

image_topic = rospy.get_param("~image_topic", "")

3.3 加入Subscriber和Publisher

创建订阅图像消息的Subscriber和发布最终识别结果的Publisher,代码如下:

self._sub = rospy.Subscriber(image_topic, Image, self.callback, queue_size=1)
self._pub = rospy.Publisher('result', Int16, queue_size=1)

3.4 加入回调函数处理图像

接收到图像后进入回调函数,然后使用cv_bridge将ROS图像转换成OpenCV的图像格式,进行识别处理,代码如下:

def callback(self, image_msg):
        cv_image = self._cv_bridge.imgmsg_to_cv2(image_msg, "bgr8")
        ......

3.5 发布识别结果

图像处理完成后,发布识别结果,并且稍作延时,等待下一次识别,代码如下:

rospy.loginfo('%d' % answer)
self._pub.publish(answer)
rospy.sleep(1) 

使用ROS命令运行基于ROS的MNIST。启动后可以看到摄像头所拍摄的图像,通过命令查看识别结果。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ROS与机器学习(三)——手写数字识别 的相关文章

随机推荐