Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
thomas_mller13
Participant
0 Kudos

Introduction

The MNIST dataset consists of 70.000 records, each record representing a little greyscale image of size 28x28 pixels of a handwritten digit and the respective number as integer value ,  i.e. 28x28 =  784 integer values in the range interval [0,255] and one integer value for the number value. Here some examples:

thomas_mller13_0-1714503870119.png

from the website https://huggingface.co/datasets/mnist where the dataset can also be downloaded. This dataset is a very good example in order to demonstrate the power of neural networks since this dataset has a trivial vector space embedding. The greyscale values of the 784 pixels of each image build a 784-dimensional vector. These vector components can directly be used as input values for the input neurons of a neural network. The purpose of the network is to classify the image, i.e. recognize the digit on the image. In this article we demonstrate how the HANA predictive analysis library can be used in order to build such a neural network. 

 The Dataset

The dateset consists of two .csv files. The first file contains 60000 records the second one contains 10000 records. Both datasets are labeled. The first dataset will be used to train the neural network. The second dataset is used to test the neural network. For easy handling we will import the whole datasets into 2 DB tables by a simple SAPGUI upload. We have to create large DDIC structures for the DB tables and also for internal handling in the ABAP programs. These structures can be created as described in this article: Creating ABAP Dataframes for SAP PAL - SAP Community

Data Structures in ABAP

We create one DDIC structure for training and one DDIC structure for prediction. The structure for training consists of 784 float variables which represent the independent variables. Secondly we have a dependent variable Y which represents the dependent variable (note that the training data records contain the dependent variable, also called the label; otherwise training would be impossible) . This variable is categorical and contains the prediction result. In our case it has the values '0', ''1, ..., '9'. Finally each data record gets an ID. ID has type integer (actually unsigned integer, >0). The structure for prediciton looks the same but does not have the Y-variable, since this value has to be predicted by the algorithm and thus cannot imported into to ML model.

Data structure for training data:

thomas_mller13_0-1714655937180.png

...

thomas_mller13_1-1714655980558.png

Data structure for prediction:

thomas_mller13_2-1714656040107.png

...

thomas_mller13_3-1714656083677.png.

Database table for training data:

thomas_mller13_4-1714656140685.png

...

thomas_mller13_5-1714656173385.png

Database table for test data records:

thomas_mller13_6-1714656218168.png

...

thomas_mller13_7-1714656249555.png.

Note that the DB table for test data records contains (in contrast to the corresponding DDIC structure) the dependent variable Y, because  the purpose of the test dataset is to verify if the prediction works correctly. 

Finally we need one database table for the model:

thomas_mller13_0-1714658239901.png

This table will contain only one record. The model are stored in the "DATA" field.

Uploading the Datasets

The .csv-files of datasets can be uploaded to the DB tables by using the follwoing program. This program uploads the training data.

*&---------------------------------------------------------------------*
*& Report ZZZ_MNIST_UPLOAD
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zzz_mnist_upload.

INCLUDE /cow/base_macros.

DATA: lv_data_out  TYPE xstring,
      lv_data_in   TYPE xstring,
      lo_ocr       TYPE REF TO /cow/cl_ocr,
      lo_exc       TYPE REF TO /cow/cx_root,
      lt_file      TYPE filetable,
      lv_filename  TYPE string,
      lv_rc        TYPE i,
      lv_size      TYPE i,
      lt_data      TYPE TABLE OF string,
      lo_file      TYPE REF TO /cow/cl_fs_file,
      lv_file_ext  TYPE /cow/file_ext,
      ls_message   TYPE bapiret2,
      lt_string    TYPE TABLE OF string,
      lv_index_var TYPE sy-index.

FIELD-SYMBOLS: <fs_filename> TYPE any.

TRY.
*   Choose file
    CLEAR: lt_file, lv_rc.
    cl_gui_frontend_services=>file_open_dialog(
      EXPORTING
        window_title            = 'Upload data'
        default_extension       = 'CSV'
      CHANGING
        file_table              = lt_file
        rc                      = lv_rc
      EXCEPTIONS
        file_open_dialog_failed = 1
        cntl_error              = 2
        error_no_gui            = 3
        not_supported_by_gui    = 4
        OTHERS                  = 5 ).
    IF sy-subrc <> 0.
      fill_mess ls_message 'I' '/COW/DCO_MESSAGES' '001'.
      raise_exc /cow/cx_root ls_message.
    ENDIF.
*   Get filename
    READ TABLE lt_file INDEX 1 ASSIGNING <fs_filename>.
    IF sy-subrc NE 0.
      fill_mess ls_message 'I' '/COW/DCO_MESSAGES' '006'.
      raise_exc /cow/cx_root ls_message.
    ENDIF.
    lv_filename = <fs_filename>.
*   Upload file
    CLEAR lt_data.
    cl_gui_frontend_services=>gui_upload(
    EXPORTING
      filename                = lv_filename
      filetype                = 'ASC'
    IMPORTING
      filelength              = lv_size
    CHANGING
      data_tab                = lt_data
    EXCEPTIONS
      OTHERS                  = 19 ).
    IF sy-subrc <> 0.
      fill_mess ls_message 'I' '/COW/DCO_MESSAGES' '002'.
      raise_exc /cow/cx_root ls_message.
    ENDIF.

    DATA: lv_tabix    TYPE string,
          lv_index    TYPE sy-tabix,
          ls_input    TYPE ZZZ_S_MNIST_INPUt,
          lt_input    TYPE ZZZ_t_MNIST_INPUt,
          lv_name     TYPE string,
          lt_train_db TYPE TABLE OF zmnist_train.


    LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<ls_data>).
      lv_index = sy-tabix.

      CLEAR: lt_string.
      SPLIT <ls_data> AT ',' INTO TABLE lt_string.

      LOOP AT lt_string ASSIGNING FIELD-SYMBOL(<lv_string>).
        lv_tabix = sy-tabix.
        IF lv_tabix GT 1.
          lv_index_var = lv_tabix - 1.
          lv_name = 'X_' && |{ lv_index_var }|.
          ASSIGN COMPONENT lv_name OF STRUCTURE ls_input TO FIELD-SYMBOL(<lv_any>).
          <lv_any> = <lv_string>.
          IF <lv_any> = 0.
            <lv_any> = '0.00001'.
          ELSE.
            <lv_any> = <lv_any> / 256.
          ENDIF.
        ENDIF.
        IF  lv_tabix = 1.
          ASSIGN COMPONENT 'Y' OF STRUCTURE ls_input TO <lv_any>.
          <lv_any> = <lv_string>.
        ENDIF.
      ENDLOOP.
      ls_input-id = lv_index.
      APPEND ls_input TO lt_input.
    ENDLOOP.

    MOVE-CORRESPONDING lt_input TO lt_train_db.

    MODIFY zmnist_train FROM TABLE lt_train_db.

  CATCH /cow/cx_root INTO lo_exc.
    IF lo_ocr IS NOT INITIAL.
*     /Dequeue
      lo_ocr->/cow/if_enqueue~dequeue( 1 ).
    ENDIF.
*   Send message
    lo_exc->to_message( 'E' ).
ENDTRY.

BREAK-POINT.

Since the two tables have the same structure, the same program can be used in order to upload the test data. Only the line 109 has to be changed to  'MODIFY zmnist_test FROM TABLE lt_train_db'.

ML Model

As already mentioned we want to use a neural network with one or more hidden layers to predict the digits on the images. The corresponding model in HANA PAL is the "Multilayer Perceptron": https://help.sap.com/docs/SAP_HANA_PLATFORM/2cfbc5cf2bc14f028cfbe2a2bba60a50/ddd236d66f394dea885f61c....

We want to use this model from within ABAP. Therefore we need two classes with AMDP-methods in order to execute the model training and model inference. (One can also implement both methods in a single class). The class for training:

 

CLASS z_cl_pal_nn_mnist DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.

    CLASS-METHODS:
      do_nn_train
        IMPORTING
          VALUE(it_data)      TYPE zzz_t_mnist_input
          VALUE(it_param)     TYPE zzz_t_pal_param
        EXPORTING
          VALUE(et_model)     TYPE zzz_t_pal_model
          VALUE(et_stat)      TYPE zzz_t_pal_stat
          VALUE(et_train_log) TYPE zzz_t_pal_train_log
          VALUE(et_opt_param) TYPE zzz_t_pal_opt_param.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS Z_CL_PAL_NN_MNIST IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method Z_CL_PAL_NN_MNIST=>DO_NN_TRAIN
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_DATA                        TYPE        ZZZ_T_MNIST_INPUT
* | [--->] IT_PARAM                       TYPE        ZZZ_T_PAL_PARAM
* | [<---] ET_MODEL                       TYPE        ZZZ_T_PAL_MODEL
* | [<---] ET_STAT                        TYPE        ZZZ_T_PAL_STAT
* | [<---] ET_TRAIN_LOG                   TYPE        ZZZ_T_PAL_TRAIN_LOG
* | [<---] ET_OPT_PARAM                   TYPE        ZZZ_T_PAL_OPT_PARAM
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD do_nn_train BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT.
   CALL _SYS_AFL.PAL_MULTILAYER_PERCEPTRON(:it_data, :it_param, et_model, et_train_log, et_stat,  et_opt_param );
  ENDMETHOD.
ENDCLASS.

 

The class for testing (prediction):

 

CLASS z_cl_pal_nn_predict_mnist DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb.

    CLASS-METHODS:
      do_nn_predict
        IMPORTING
          VALUE(it_data)    TYPE ZZZ_T_MNIST_PREDICT
          VALUE(it_model)   TYPE zzz_t_pal_model
          VALUE(it_param)   TYPE zzz_t_pal_param
        EXPORTING
          VALUE(et_result)  TYPE zzz_t_pal_pr_result
          VALUE(et_softmax) TYPE zzz_t_pal_pr_softmax.


  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS Z_CL_PAL_NN_PREDICT_MNIST IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method Z_CL_PAL_NN_PREDICT_MNIST=>DO_NN_PREDICT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_DATA                        TYPE        ZZZ_T_MNIST_PREDICT
* | [--->] IT_MODEL                       TYPE        ZZZ_T_PAL_MODEL
* | [--->] IT_PARAM                       TYPE        ZZZ_T_PAL_PARAM
* | [<---] ET_RESULT                      TYPE        ZZZ_T_PAL_PR_RESULT
* | [<---] ET_SOFTMAX                     TYPE        ZZZ_T_PAL_PR_SOFTMAX
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD do_nn_predict BY DATABASE PROCEDURE FOR HDB LANGUAGE SQLSCRIPT.
    CALL _SYS_AFL.PAL_MULTILAYER_PERCEPTRON_PREDICT(:it_data, :it_model, :IT_PARAM, et_result, et_softmax );
  ENDMETHOD.
ENDCLASS.

 

Training of the Model

The model will be trained by the following program:

 

*&---------------------------------------------------------------------*
*& Report ZZZ_MNIST_TRAIN
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zzz_mnist_train.

DATA: lt_input     TYPE zzz_t_mnist_input,
      ls_input     TYPE zzz_s_mnist_input,
      lt_param     TYPE zzz_t_pal_param,
      ls_param     TYPE zzz_s_pal_param,
      lt_model     TYPE zzz_t_pal_model,
      lt_stat      TYPE zzz_t_pal_stat,
      lt_train_log TYPE zzz_t_pal_train_log,
      lt_opt_param TYPE zzz_t_pal_opt_param.


SELECT *  FROM ZMNIST_TRAin INTO TABLE (lt_input_db) ORDER BY id ASCENDING.


LOOP AT lt_input_db ASSIGNING FIELD-SYMBOL(<ls_input_db>).
  MOVE-CORRESPONDING <ls_input_db> TO ls_input.
  APPEND ls_input TO lt_input.
ENDLOOP.

CLEAR lt_input_db.

ls_param-param_name = 'HIDDEN_LAYER_ACTIVE_FUNC'.
ls_param-int_value = 4.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'OUTPUT_LAYER_ACTIVE_FUNC'.
ls_param-int_value = 4.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'HIDDEN_LAYER_SIZE'.
ls_param-string_value = '100'.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'HAS_ID'.
ls_param-int_value = 1.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'MAX_ITERATION'.
ls_param-int_value = 50.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'DEPENDENT_VARIABLE'.
ls_param-string_value = 'Y'.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'WEIGHT_INIT'.
ls_param-int_value = 1.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'CATEGORICAL_VARIABLE'.
ls_param-string_value = 'Y'.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'LEARNING_RATE'.
ls_param-double_value = '0.001'.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'MOMENTUM_FACTOR'.
ls_param-double_value = '0.0'.
APPEND ls_param TO lt_param.
CLEAR ls_param.
ls_param-param_name = 'THREAD_RATIO'.
ls_param-double_value = '0.5'.
APPEND ls_param TO lt_param.

z_cl_pal_nn_mnist=>do_nn_train(
  EXPORTING
    it_data      = lt_input
    it_param     = lt_param
  IMPORTING
    et_model     = lt_model
    et_stat      = lt_stat
    et_train_log = lt_train_log
    et_opt_param = lt_opt_param ).


DATA: ls_db_model TYPE zmnist_model.

LOOP AT lt_model ASSIGNING FIELD-SYMBOL(<ls_model>).
  IF sy-tabix = 1.
    ls_db_model-data = <ls_model>-model_content.
  ELSE.
    ls_db_model-data = ls_db_model-data && |{ <ls_model>-model_content }|.
  ENDIF.
ENDLOOP.

ls_db_model-id = 1.

DELETE FROM zmnist_model.

MODIFY zmnist_model FROM ls_db_model.

 

Training is a very resource consuming task and depends also strongly on the parameters of the model. Therefore this program should run in background task. In this setup the training run roughly 1,5 hours. More layers, more neurons per layer, smaller learning rate or small momentum > 0 factor can result in even longer runtimes. We are using one hidden layer with 100 neurons. We do not use a momentum.

Testing of the Model

The model can be tested by the following program:

 

*&---------------------------------------------------------------------*
*& Report ZZZ_MNIST_PREDICT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zzz_mnist_predict.


DATA: ls_model_db TYPE zmnist_model,
      lt_model    TYPE zzz_t_pal_model,
      ls_model    TYPE zzz_s_pal_model,
      lt_data     TYPE zzz_t_mnist_predict,
      ls_data     TYPE zzz_s_mnist_predict,
      lt_param    TYPE zzz_t_pal_param,
      lt_result   TYPE zzz_t_pal_pr_result,
      lt_softmax  TYPE zzz_t_pal_pr_softmax.

SELECT SINGLE * FROM zmnist_model INTO ls_model_db.

WHILE ls_model_db-data IS NOT INITIAL.
  DATA(lv_index) = sy-index.
  ls_model-row_index = lv_index.
  ls_model-model_content = ls_model_db-data.
  APPEND ls_model TO lt_model.
  SHIFT ls_model_db-data LEFT BY 5000 PLACES.
ENDWHILE.


SELECT * FROM zmnist_test INTO CORRESPONDING FIELDS OF TABLE lt_data.

*SELECT SINGLE * FROM zmnist_test INTO (ls_pred)
*  WHERE id = 100.
*
*MOVE-CORRESPONDING ls_pred TO ls_data.
*APPEND ls_data TO lt_data.

z_cl_pal_nn_predict_mnist=>do_nn_predict(
  EXPORTING
    it_data    = lt_data
    it_model   = lt_model
    it_param   = lt_param
  IMPORTING
    et_result  = lt_result
    et_softmax = lt_softmax ).


BREAK-POINT.

 

 

If the training was succesful the table lt_result contains for all 10000 test data records the predicted result. Ideally this should be the same value as the value of the dependend variable Y in table  ZMNIST_TEST.

thomas_mller13_0-1714658965127.png

Note: The webeditor does not allow the \address symbol in the select statements. The correct select statement is:

SELECT *  FROM ZMNIST_TRAin INTO TABLE \addressDATA(lt_input_dbORDER BY id ASCENDING.

 

Labels in this area