1. 程式人生 > >利用Tensorflow實現手寫字符識別

利用Tensorflow實現手寫字符識別

status ade 模式 數學 malloc interact tutorials x模型 項目

模式識別領域應用機器學習的場景非常多,手寫識別就是其中一種,最簡單的數字識別是一個多類分類問題,我們借這個多類分類問題來介紹一下google最新開源的tensorflow框架,後面深度學習的內容都會基於tensorflow來介紹和演示

請尊重原創,轉載請註明來源網站www.shareditor.com以及原始鏈接地址

什麽是tensorflow

tensor意思是張量,flow是流。

張量原本是力學裏的術語,表示彈性介質中各點應力狀態。在數學中,張量表示的是一種廣義的“數量”,0階張量就是標量(比如:0、1、2……),1階張量就是向量(比如:(1,3,4)),2階張量就是矩陣,本來這幾種形式是不相關的,但是都歸為張量,是因為他們同時滿足一些特性:1)可以用坐標系表示;2)在坐標變換中遵守同樣的變換法則;3)有著相同的基本運算(如:加、減、乘、除、縮放、點積、對稱……)

那麽tensorflow可以理解為通過“流”的形式來處理張量的一種框架,是由google開發並開源,已經應用於google大腦項目開發

tensorflow安裝

sudo pip install https://storage.googleapis.com/tensorflow/mac/tensorflow-0.9.0-py2-none-any.whl

不同平臺找對應的whl包

可能遇到的問題:

發現無法import tensorflow,問題在於protobuf版本不對,必須先卸載掉,再安裝tensorflow,這樣會自動安裝3.0版本的protobuf

sudo pip uninstall protobuf
sudo brew remove protobuf260
sudo pip install --upgrade https://storage.googleapis.com/tensorflow/mac/tensorflow-0.9.0-py2-none-any.whl

手寫數字數據集獲取

在http://yann.lecun.com/exdb/mnist/可以下載手寫數據集,http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz和http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz,下載解壓後發現不是圖片格式,而是自己特定的格式,為了說明這是什麽樣的數據,我寫了一段程序來顯示這些數字:

/************************
 * author: SharEDITor
 * date:   2016-08-02
 * brief:  read MNIST data
 ************************/
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>

unsigned char *lables = NULL;

/**
 * All the integers in the files are stored in the MSB first (high endian) format
 */
void copy_int(uint32_t *target, unsigned char *src)
{
    *(((unsigned char*)target)+0) = src[3];
    *(((unsigned char*)target)+1) = src[2];
    *(((unsigned char*)target)+2) = src[1];
    *(((unsigned char*)target)+3) = src[0];
}

int read_lables()
{
    FILE *fp = fopen("./train-labels-idx1-ubyte", "r");
    if (NULL == fp)
    {
        return -1;
    }
    unsigned char head[8];
    fread(head, sizeof(unsigned char), 8, fp);
    uint32_t magic_number = 0;
    uint32_t item_num = 0;
    copy_int(&magic_number, &head[0]);
    // magic number check
    assert(magic_number == 2049);
    copy_int(&item_num, &head[4]);

    uint64_t values_size = sizeof(unsigned char) * item_num;
    lables = (unsigned char*)malloc(values_size);
    fread(lables, sizeof(unsigned char), values_size, fp);

    fclose(fp);
    return 0;
}

int read_images()
{
    FILE *fp = fopen("./train-images-idx3-ubyte", "r");
    if (NULL == fp)
    {
        return -1;
    }
    unsigned char head[16];
    fread(head, sizeof(unsigned char), 16, fp);
    uint32_t magic_number = 0;
    uint32_t images_num = 0;
    uint32_t rows = 0;
    uint32_t cols = 0;
    copy_int(&magic_number, &head[0]);
    // magic number check
    assert(magic_number == 2051);
    copy_int(&images_num, &head[4]);
    copy_int(&rows, &head[8]);
    copy_int(&cols, &head[12]);

    uint64_t image_size = rows * cols;
    uint64_t values_size = sizeof(unsigned char) * images_num * rows * cols;
    unsigned char *values = (unsigned char*)malloc(values_size);
    fread(values, sizeof(unsigned char), values_size, fp);

    for (int image_index = 0; image_index < images_num; image_index++)
    {
        // print the label
        printf("=========================================  %d  ======================================\n", lables[image_index]);
        for (int row_index = 0; row_index < rows; row_index++)
        {
            for (int col_index = 0; col_index < cols; col_index++)
            {
                // print the pixels of image
                printf("%3d", values[image_index*image_size+row_index*cols+col_index]);
            }
            printf("\n");
        }
        printf("\n");
    }

    free(values);
    fclose(fp);
    return 0;
}

int main(int argc, char *argv[])
{
    if (-1 == read_lables())
    {
        return -1;
    }
    if (-1 == read_images())
    {
        return -1;
    }
    return 0;
}

下載並解壓出數據集文件train-images-idx3-ubyte和train-labels-idx1-ubyte放到源代碼所在目錄後,編譯並執行:

gcc -o read_images read_images.c
./read_images

展示出來的效果如下:

技術分享

一共有60000個圖片,從代碼可以看出數據集裏存儲的實際就是圖片的像素

請尊重原創,轉載請註明來源網站www.shareditor.com以及原始鏈接地址

softmax模型

我們在《機器學習教程 十三-用scikit-learn做邏輯回歸》中介紹了邏輯回歸模型。邏輯回歸是用於解決二類分類問題(使用sigmoid函數),而softmax模型是邏輯回歸模型的擴展,用來解決多類分類問題。

softmax意為柔和的最大值,也就是如果某個zj大於其他z,那麽這個映射的分量就逼近於1,其他的分量就逼近於0,從而將其歸為此分類,多個分量對應的就是多分類,數學形式和sigmoid不同,如下:

技術分享

它的特點是,所有的softmax加和為1,其實它表示的是一種概率,即x屬於某個分類的概率。

在做樣本訓練時,這裏的xi計算方法是:

技術分享

其中W是樣本特征的權重,xj是樣本的特征值,bi是偏置量。

詳細來說就是:假設某個模型訓練中我們設計兩個特征,他們的值分別是f1和f2,他們對於第i類的權重分別是0.2和0.8,偏置量是1,那麽

xi=f1*0.2+f2*0.8+1

如果所有的類別都計算出x的值,如果是一個訓練好的模型,那麽應該是所屬的那個類別對應的softmax值最大

softmax回歸算法也正是基於這個原理,通過大量樣本來訓練這裏的W和b,從而用於分類的

tensorflow的優點

tensorflow會使用外部語言計算復雜運算來提高效率,但是不同語言之間的切換和不同計算資源之間的數據傳輸耗費很多資源,因此它使用圖來描述一系列計算操作,然後一起傳給外部計算,最後結果只傳回一次,這樣傳輸代價最低,計算效率最高

舉個例子:

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

這裏的x不是一個實際的x,而是一個占位符,也就是一個描述,描述成了二維浮點型,後面需要用實際的值來填充,這就類似於printf("%d", 10)中的占位符%d,其中第一維是None表示可無限擴張,第二維是784個浮點型變量

如果想定義可修改的張量,可以這樣定義:

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

其中W的維度是[784, 10],b的形狀是[10]

有了這三個變量,我們可以定義我們的softmax模型:

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

這雖然定義,但是沒有真正的進行計算,因為這只是先用圖來描述計算操作

其中matmul是矩陣乘法,因為x的維度是[None, 784],W的維度是[784, 10],所以矩陣乘法得出的是[None, 10],這樣可以和向量b相加

softmax函數會計算出10維分量的概率值,也就是y的形狀是[10]

數字識別模型實現

基於上面定義的x、W、b,和我們定義的模型:

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

我們需要定義我們的目標函數,我們以交叉熵(衡量預測用於描述真相的低效性)為目標函數,讓它達到最小:

技術分享

其中y‘是實際分布,y是預測的分布,即:

y_ = tf.placeholder("float", [None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

利用梯度下降法優化上面定義的Variable:

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

其中0.01是學習速率,也就是每次對變量做多大的修正

按照上面的思路,最終實現的代碼digital_recognition.py如下:

# coding:utf-8

import sys
reload(sys)
sys.setdefaultencoding( "utf-8" )

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

flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_string(‘data_dir‘, ‘./‘, ‘Directory for storing data‘)

mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)


x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
y = tf.nn.softmax(tf.matmul(x,W) + b)
y_ = tf.placeholder("float", [None,10])
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

init = tf.initialize_all_variables()
sess = tf.InteractiveSession()
sess.run(init)
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})

correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(accuracy.eval({x: mnist.test.images, y_: mnist.test.labels}))

運行效果如下:

[root@mymac $] python digital_recognition.py
Extracting ./train-images-idx3-ubyte.gz
Extracting ./train-labels-idx1-ubyte.gz
Extracting ./t10k-images-idx3-ubyte.gz
Extracting ./t10k-labels-idx1-ubyte.gz
0.9039

解釋一下

flags.DEFINE_string(‘data_dir‘, ‘./‘, ‘Directory for storing data‘)

表示我們用當前目錄作為訓練數據的存儲目錄,如果我們沒有提前下好訓練數據和測試數據,程序會自動幫我們下載到./

mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)

這句直接用庫裏幫我們實現好的讀取訓練數據的方法,無需自行解析

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})

這幾行表示我們循環1000次,每次從訓練樣本裏選取100個樣本來做訓練,這樣我們可以修改配置來觀察運行速度

最後幾行打印預測精度,當調整循環次數時可以發現總訓練的樣本數越多,精度就越高

利用Tensorflow實現手寫字符識別