Home About Contact
TensorFlow , TensorFlow.js , MNIST

TensorFlow で MNIST その2【TensorFlow.js で推測する Node.js編】

前回のエントリーで Python の TensorFlow で MNIST の分類モデルを作成しました。 今回は、そのモデルを変換して TensorFlow.js で使います。

事前準備

環境は以下の通り。

$ python3 --version
Python 3.8.17

必要なモジュールを入れる。

$ pip install tensorflow==2.13.0
$ pip install pillow==10.0.0
$ pip install tensorflowjs==4.10.0

前回作成した model.keras をカレントディレクトリにコピーして、 Tensorflow.js で使用できる tfjs_graph_model 形式のモデルに変換します。

$ tensorflowjs_converter \
    --input_format=keras \
    --output_format=tfjs_graph_model \
    ./model.keras \
    ./js_model

現在のプロジェクトディレクトリの構成を確認します。

.
├── js_model
│   ├── group1-shard1of1.bin
│   └── model.json
└── model.keras

model.keras から変換されて js_model/ 以下に変換された TensorFlow.js 用のモデルができました。

Node.js から使う

Node.js 環境は以下の通りです。

$ node --version
v12.22.9
$ npm --version
8.5.1

カレントディレクトリで Node.js のプロジェクトを作成。

$ npm init -y
$ npm install @tensorflow/tfjs-node@4.10.0

モデルがロードできるか確かめます。

index.js

const path = require('path')
const tf = require('@tensorflow/tfjs-node')

const currentPath = process.cwd()
const modelPath = path.join(currentPath, 'js_model/model.json')

tf.ready().then(()=>{
    tf.loadGraphModel(`file://${modelPath}`).then((model)=>{
        console.log( model )
    })
})

実行します。

$ node index.js
GraphModel {
  modelUrl: 'file:///path_to_node_my_mninst_project_dir/js_model/model.json',
  loadOptions: {},
  version: '1482.undefined',
  io: [Object: null prototype] {
  ...

数字画像をロードしてモデルの入力形状にあわせる

それでは前回作成した 0 gray_0.jpg をこのプロジェクトにコピーして、 これを推測させてみます。

いつものことですが、この画像を推測させるには、このモデルが要求する入力形状をもつテンソルに変換する必要があります。

const image = fs.readFileSync('gray_0.jpg')
const tensor0 = tf.node.decodeImage(image)
console.log( tensor0.shape ) // [ 28, 28, 1 ]

const tensor1 = tensor0.div(255)
const inputTensor = tf.expandDims( tensor1 )
console.log( inputTensor.shape ) // [ 1, 28, 28, 1 ]

tensor0.div(255) で値を 0.0..1.0 の範囲にスケールしています。 tf.expandDims( tensor1 ) これで テンソルの形状を [ 28, 28, 1 ] から [ 1, 28, 28, 1 ] へ、 モデルが要求する形状に変換しています。

推測する

const outputTensors = model.predict( inputTensor )
console.log( outputTensors.shape ) // [ 1, 10 ]
const jsArray = outputTensors.arraySync()
jsArray[0].forEach((value, index)=>{
    const v = Number.parseFloat(value).toFixed(10)
    console.log( `- ${index} => ${v}` )
})

実行すると、この画像の 0..9 の数字のどれなのかの確率が表示されます。

- 0 => 0.9993641973
- 1 => 0.0000000001
- 2 => 0.0000067655
- 3 => 0.0000022582
- 4 => 0.0000016466
- 5 => 0.0000000084
- 6 => 0.0004208349
- 7 => 0.0000217544
- 8 => 0.0001810832
- 9 => 0.0000014795

0 が最も数値が大きく 0.9993641973 なっているため、この画像は 0 と推測されました。

ここでは、推測された 10 個から最も値が大きいものの index 値(およびその値)を得たいだけなので、 以下のように tf.argMax(), tf.max() 関数を使って結果を書き出すことができます。

const maxValueIndex = tf.argMax(outputTensors, 1).arraySync()[0]
const maxValue      = tf.max(outputTensors, 1).arraySync()[0]

ここまでで一旦コードの全体を確認します。

index.js

const path = require('path')
const fs = require('fs')
const tf = require('@tensorflow/tfjs-node')

const currentPath = process.cwd()
const modelPath = path.join(currentPath, 'js_model/model.json')

tf.ready().then(()=>{
    tf.loadGraphModel(`file://${modelPath}`).then((model)=>{
        tf.tidy(()=>{
            const imageFilename = 'gray_0.jpg'	

            const image = fs.readFileSync(imageFilename)
            const tensor0 = tf.node.decodeImage(image)
            //console.log( tensor0.shape ) // [ 28, 28, 1 ]
            const tensor1 = tensor0.div(255)
            const inputTensor = tf.expandDims( tensor1 )
            //console.log( inputTensor.shape ) // [ 1, 28, 28, 1 ]
    
            const outputTensors = model.predict( inputTensor )
            //console.log( outputTensors.shape ) // [ 1, 10 ]
    
            const maxValueIndex = tf.argMax(outputTensors, 1).arraySync()[0] 
            const maxValue      = tf.max(outputTensors, 1).arraySync()[0]
            console.log( `- ${imageFilename} => ${maxValueIndex} (${maxValue})` )
        })
    })
})

実行。

$ node index.js
- gray_0.jpg => 0 (0.9993641972541809)

それでは、0 から 9 までの画像全部をまとめて推測させてみます。

以下のように、forEach してやれば完全に機能するコードができるのですが・・・

const imageFilenames = 'gray_0.jpg gray_1.jpg gray_2.jpg gray_3.jpg gray_4.jpg gray_5.jpg gray_6.jpg gray_7.jpg gray_8.jpg gray_9.jpg'.split(' ')

tf.ready().then(()=>{
    tf.loadGraphModel(`file://${modelPath}`).then((model)=>{
        tf.tidy(()=>{
            imageFilenames.forEach((imageFilename)=> {
                const image = fs.readFileSync(imageFilename)
                const tensor0 = tf.node.decodeImage(image)
                const tensor1 = tensor0.div(255)
                const inputTensor = tf.expandDims( tensor1 )
                const outputTensors = model.predict( inputTensor )
        
                const maxValueIndex = tf.argMax(outputTensors, 1).arraySync()[0] 
                const maxValue      = tf.max(outputTensors, 1).arraySync()[0]
                console.log( `- ${imageFilename} => ${maxValueIndex} (${maxValue})` )
            })
        })
    })
})

model.predict() をループして10回実行しているのがなんとなく気持ち悪いので、まとめて一回の推測処理で済むようにします。 そのためには、次のようにして10枚の画像から [ 10, 28, 28, 1 ] の形状を持つ入力用テンソルを作り出します。

const range = (v)=>{ return [...Array(v).keys()] }
const imageFilenames = 'gray_0.jpg gray_1.jpg gray_2.jpg gray_3.jpg gray_4.jpg gray_5.jpg gray_6.jpg gray_7.jpg gray_8.jpg gray_9.jpg'.split(' ')

tf.ready().then(()=>{
    tf.loadGraphModel(`file://${modelPath}`).then((model)=>{
        tf.tidy(()=>{
            const inputTensorList = imageFilenames.map((imageFilename)=> {
                const image = fs.readFileSync(imageFilename)
                const tensor0 = tf.node.decodeImage(image)
                return tensor0.div(255)
            })

            const inputTensors =  tf.stack( inputTensorList )
            console.log( inputTensors.shape )

            const outputTensors = model.predict( inputTensors )
            console.log( outputTensors.shape ) // [ 10, 10 ]
        
            const maxValueIndexes = tf.argMax(outputTensors, 1).arraySync()
            const maxValues       = tf.max(outputTensors, 1).arraySync()

            range(10).forEach((i)=> {
                console.log( `- ${imageFilenames[i]} => ${maxValueIndexes[i]} (${maxValues[i]})` )
            })
        })
    })
})

実行。

$ node index.js
[ 10, 28, 28, 1 ]
[ 10, 10 ]
- gray_0.jpg => 0 (0.9993641376495361)
- gray_1.jpg => 1 (0.9981801509857178)
- gray_2.jpg => 2 (0.8476385474205017)
- gray_3.jpg => 3 (0.9981667995452881)
- gray_4.jpg => 4 (0.9987807869911194)
- gray_5.jpg => 5 (0.9999744296073914)
- gray_6.jpg => 6 (0.9671692848205566)
- gray_7.jpg => 7 (0.8808700442314148)
- gray_8.jpg => 8 (0.9915458559989929)
- gray_9.jpg => 4 (0.919191837310791)

モデルが同じなので当然ですが、やはり 9 は正しく推測できません。

まとめ

Python 版の TensorFlow でつくったモデルを TensorFlow.js 用のモデルに変換し、Node.js でそれをつかって推測できました。

最後に完成した推測用コードを掲載します。

index.js

const path = require('path')
const fs = require('fs')
const tf = require('@tensorflow/tfjs-node')

const currentPath = process.cwd()
const modelPath = path.join(currentPath, 'js_model/model.json')

const range = (v)=>{ return [...Array(v).keys()] }
const imageFilenames = 'gray_0.jpg gray_1.jpg gray_2.jpg gray_3.jpg gray_4.jpg gray_5.jpg gray_6.jpg gray_7.jpg gray_8.jpg gray_9.jpg'.split(' ')

tf.ready().then(()=>{
    tf.loadGraphModel(`file://${modelPath}`).then((model)=>{
        tf.tidy(()=>{
            const inputTensorList = imageFilenames.map((imageFilename)=> {
                const image = fs.readFileSync(imageFilename)
                const tensor0 = tf.node.decodeImage(image)
                return tensor0.div(255)
            })

            const inputTensors =  tf.stack( inputTensorList )
            console.log( inputTensors.shape )

            const outputTensors = model.predict( inputTensors )
            console.log( outputTensors.shape ) // [ 10, 10 ]
        
            const maxValueIndexes = tf.argMax(outputTensors, 1).arraySync()
            const maxValues       = tf.max(outputTensors, 1).arraySync()

            range(10).forEach((i)=> {
                console.log( `- ${imageFilenames[i]} => ${maxValueIndexes[i]} (${maxValues[i]})` )
            })
        })
    })
})

次回は、これをブラウザ上で使う方法を調べます。