2024年11月12日火曜日

Oracle APEXのページにTensorFlow.jsを組み込みInception_v3による分類を実行する

O'Reillyから出版されている亀が表紙の本「Learning TensorFlow.js」のChapter 5、Introducing Modelsで紹介されている、Inception_v3による分類をOracle APEXのアプリケーションに組み込んでみました。Chapter 5は、Inception_v3を使った分類とペットの顔を検出する2つのサンプルの実装を含んでいますが、分類の方を実装しています。

TensorFlow.jsをOracle APEXに組み込む、といっても特別な作業ではなく、HTMLのページに実装できるものであれば、大抵はAPEXのアプリケーションに組み込めます。TensorFlow.jsも同様です。

今回作成したAPEXアプリケーションは以下のように動作します。

リスはsquirrel、レッサーパンダはlesser panda、シマウマはzebraと分類されました。


ホーム・ページにすべての機能を実装しています。


TensorFlow.jsの状態を表示するために、ページ・アイテムP1_STATUSを作成しています。タイプ表示のみで、JavaScriptのコード中よりページ・アイテムの値を設定します。


分類の対象となる画像は、静的コンテンツのリージョンに表示します。ソースHTMLコードに以下を記述します。

<img id="mystery" src="" class="w80p" />

属性srcの値は、ページ・アイテムP1_IMAGEの動的アクション中で設定します。


分類の結果はページ・アイテムP1_OBJECTSに出力します。タイプテキスト領域とし、複数行を表示できるようにしています。


分類する画像を選択するページ・アイテムとしてP1_IMAGEを作成します。タイプイメージ・アップロードです。このアプリケーションはサーバーにページを送信することはありません。画像ファイルをブラウザ上で選択するだけで、イメージのアップロードは行いません。


ページ・アイテムP1_IMAGEで画像を選択すると、変更イベントが発生します。変更イベントにより呼び出される動的アクション(のTRUEアクション)として、JavaScriptコードの実行を選び、設定コードに以下を記述します。
// get image file and its URL for img src.
const imageFile = this.triggeringElement.files[0];
const imageUrl = URL.createObjectURL(imageFile);
document.getElementById("mystery").src = imageUrl;
// Reset Status
apex.items.P1_OBJECTS.setValue("");
apex.items.P1_STATUS.setValue("Ready for Detection");
ページ・アイテムに選択された画像を、静的コンテンツに含まれるIMG要素の属性SRCに設定しています。そうすることにより、選択した画像がIMG要素として画面に表示されます。


ページ・ロード時にTensorFlow.jsを初期化します。

ページ・プロパティJavaScriptファイルURLとして、以下を記述します。

https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.22.0/dist/tf.min.js

ファンクションおよびグローバル変数の宣言に以下を記述します。
var model;
var INCEPTION_CLASSES = [];

ページ・ロード時に実行に、以下を記述します。TensorFlow.jsの初期化が完了したのち、Inception_v3のモデルを読み込んでいます。また、分類のラベルをテキスト・ファイルからJSON配列INCEPTION_CLASSESに読み込んでいます。
/*
 * ページ・ロード時にTensorFlow.jsが読み込まれる。TensorFlow.jsがReadyになったら、続けてモデルを読み込む。
 * ページを離れたらロードしたモデルは破棄されると思うので、モデルにたいしてはdispose()は呼び出していない。
 */
tf.ready().then(() => {
    // const modelPath = "https://tfhub.dev/google/tfjs-model/imagenet/inception_v3/classification/3/default/1";
    const modelPath = "https://www.kaggle.com/models/google/inception-v3/TfJs/classification/2";
    apex.items.P1_STATUS.setValue("TensorFlow.js is Ready, Loading Model...");
    tf.loadGraphModel(modelPath, { fromTFHub: true }).then((m) => {
        model = m; // modelはページ・グローバルな変数
        apex.items.P1_STATUS.setValue("Model is Loaded");
    });
});

/*
 * ImageNetのラベルを配列INCEPTION_CLASSESに読み込む。
 */
fetch('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
    .then(response => {
        if (!response.ok) {
            throw new Error('Failed to get ImageNetLabels.txt');
        }
        return response.text();
    })
    .then( (text) => {
        // テキストを行ごとに分割し、配列に変換
        INCEPTION_CLASSES = text.split('\n').filter(label => label.trim() !== '');
        console.log(INCEPTION_CLASSES);
    })
    .catch(error => {
        console.error('Failed to read ImageNetLabels.txt:', error);
    });
モデルのロードが完了してから(P1_STATUSModel is Loadedと表示されたのち)、画像の分類を実行できるようになりますが、それまでの間、ボタンを無効化するなどの処理は行っていないため、注意が必要です。


kaggle.comでホストされているモデルのブラウザへの読み込みを許可するため、アプリケーション定義セキュリティブラウザ・セキュリティのセクションにあるHTTPレスポンス・ヘッダーに以下を記述します。

Access-Control-Allow-Origin: https://www.kaggle.com


最後に分類を実行するボタンDETECTを作成し、ボタンをクリックしたときに呼び出される動的アクションを定義します。

実行するJavaScriptのコードは以下になります。
tf.tidy(() => {
    const mysteryImage = document.getElementById("mystery");
    const myTensor = tf.browser.fromPixels(mysteryImage);
    // Inception v3 expects an image resized to 299x299
    const readyfied = tf.image
        .resizeBilinear(myTensor, [299,299], true)
        .div(255)
        .reshape([1,299,299,3]);
    apex.items.P1_STATUS.setValue("Start prediction !");
    // predict.
    const result = model.predict(readyfied);
    result.print();
    apex.items.P1_STATUS.setValue("Prediction Done !");

    const { values, indices } = tf.topk(result, 3);
    indices.print();

    // Let's hear those winners
    const winners = indices.dataSync();
    
    apex.items.P1_OBJECTS.setValue(
`First place ${INCEPTION_CLASSES[winners[0]]},
Second place ${INCEPTION_CLASSES[winners[1]]},
Third place ${INCEPTION_CLASSES[winners[2]]}`);

});

以上でアプリケーションは完成です。実行すると記事の先頭にあるGIF動画のように動作します。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/tensorflow-js-classification.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。