Reverse Engineering Model Handwritten Digits Recognition Aplikasi Mobile SIREKAP

Di tulisan ini saya akan melakukan reverse engineering aplikasi SIREKAP 2024, mengekstrak model TensorFlow Lite untuk inference, lalu membuat aplikasi Android untuk mengetes model tersebut. Saya bandingkan juga dengan model sederhana yang jadi contoh tutorial tensorflow lite.

Handwritten Digits Recognition

Persoalan pengenalan digit tulisan tangan merupakan hal paling dasar (semacam Hello World) di pelajaran AI modern. Ini merupakan subset dari OCR (optical character recognition), di mana scopenya hanya digit saja.

Ada data contoh standard yang jadi acuan yang dari database MNIST (Modified National Institute of Standards and Technology database) yang disebut MNIST dataset. Data ini terdiri dari puluhan ribu gambar digit, tiap gambar dibeli label apa digit yang ada di gambar itu, ukuran gambarnya 28×28 piksel monokrom.

Meski jadi acuan, menurut saya MNIST ini tidak 100% mencerminkan cara penulisan digit di dunia nyata, ada banyak cara penulisan digit yang tidak tercantum di dalam contoh datanya. Contoh: digit bisa dituliskan seperti display digital. Jika ingin melihat sendiri datanya, silakan klik di sini.

Sebagian Dataset MNIST

Sebelum melangkah terlalu jauh, saya jelaskan dulu istilah dasar, karena banyak orang yang sok tahu tentang AI, tapi semua istilahnya tertukar-tukar:

  • MNIST adalah nama database dan dataset.
  • Definisi “Model AI” ada banyak dan beragam. Dalam konteks deep learning modern, ini adalah: arsitektur network (layernya apa saja, bagimana koneksi antar layernya), plus data bobot (weight) untuk tiap layer tersebut. Secara praktis, ini biasanya akan jadi 1 file model. File model AI dihasilkan dari training.

Meski memiliki dataset (contoh data) yang sama, dengan arsitektur yang sama, jika cara trainingnya berbeda, akurasi modelnya berbeda. Demikian juga jika datasetnya berbeda, maka hasil modelnya juga akan berbeda.

Berbagai model telah dibuat untuk menyelesaikan masalah pengenalan digit ini dengan tingkat akurasi yang berbeda-beda, tergantung kompleksitas modelnya.

Menurut website ini, Saat ini model SOTA (state of the art, alias yang paling modern/bagus) memiliki akurasi 99.87% pada dataset MNIST. Perhatikan bahwa di dunia nyata, ini bisa lebih rendah akurasinya.

Tensorflow Lite

TensorFlow adalah salah satu dari library AI. Library ini dikembangkan oleh salah satu tim dari Google. Ada banyak library AI lain, tapi yang menarik dari TensorFlow adalah: ada versi lite-nya yang bisa dipakai di mobile device, and sifatnya crossplatform (bisa di iOS dan Android).

Kita bisa melakukan training di komputer, lalu hasil trainingnya bisa diubah menjadi format .tflite dan kemudian dipakai di mobile. Yang akan saya bahas di sini adalah reverse engineering file .tflite ini agar bisa dipakai di aplikasi sendiri atau untuk dipelajari lebih lanjut.

Secara default TFLite akan memakai CPU, kecuali kita minta secara khusus untuk memakai GPU. Karena memakai CPU (yang memakai aritmatika IEEE 754 untuk float, dan biasanya versi quantized memakai integer) artinya walau devicenya berbeda, akurasinya akan tetap sama. Tapi faktor jenis kamera dan pencahayaan bisa mempengaruhi akurasi model.

Melihat model dengan Netron

File .tflite merupakan file dengan format FlatBuffers. File ini adalah file biner, dan biasanya jika dideploy tidak akan diberi metadata. Jika dibuka dengan hex editor, tidak banyak informasi yang bisa dipelajari dari sini.

Dalam aplikasi Android, model ini biasanya bisa ditemukan di direktori assets. File model .tflite bisa dicopy dan dipelajari dengan mudah.

Untuk contoh ini saya memakai file .tflite dari proyek Android ini.

File tflite

Program Netron dapat digunakan untuk membuka file .tflite ini agar kita bisa melihat arsitektur networknya.

File yang sama dilihat dengan Netron

Tentunya jika kita tidak mengerti neural network, gambar di atas tidak berarti apa-apa.

Input dan output layer

Sebuah model hanyalah sebuah fungsi yang memetakan input dan output. Ketika reverse engineering, kita perlu tahu apa format inputnya, berapa ukuran tensornya, dan untuk outputnya, kita perlu tahu bagaimana menginterpretasi datanya.

Shape (ukuran/dimensi) tensor input dan output bisa dilihat di Netron. Alternatif lain: kita load modelnya, lalu panggil method getInputTensors() dan getOutputTensors(). Pemahaman dasar mengenai neural network dan fungsi-fungsi umum seputar neural network sangat diperlukan, misalnya apa gunanya Softmax dan seperti apa hasil outputnya.

Pada contoh sebelumnya inputnya adalah ‘x16’ (ini cuma namanya saja, angka 16 tidak berarti apa-apa, hanya nomor lokasi), yang penting adalah: inputnya berukuran 1 x 28 x 28 x 1. Angka pertama adalah batch size, dalam kasus ini, kita memproses 1 gambar tiap waktu, lalu ukuran gambarnya adalah 28×28, dan channel-nya adalah 1, karena imagenya tidak berwarna (warna tidak penting ketika memproses digit).

Untuk model pengenalan digit, biasanya outputnya ada 10: digit 0 sampai 9. Seperti terlihat di akhir gambar (abaikan output 15, itu juga hanya nomor lokasi saja).

Karena ini adalah persoalan klasifikasi, jika tidak ada layer softmax, yang bisa kita lakukan adalah mencari dari semua output ini, mana nilai terbesar, di indeks itulah digit yang dikenali. Jika ada layer softmax, maka outputnya adalah probabilitas dengan total semuanya 1.0. Misalnya ada angka 1 yang agak mirip angka 7, maka kemungkinannya mungkin 0. 63 (63%) bahwa angkanya adalah 1, dan 0.35 (35%) angkanya adalah 7, dan yang 2% mungkin terdistribusi di angka-angka yang lain.

Kalau ingin tahu detail fungsi Softmax, silakan lihat Wikipedia

File model saja tidak cukup untuk mengetahui apa input dan outputnya, karena tidak ada penjelasan apa-apa di situ. Kita harus melakukan reverse engineering kode yang memanggil modelnya, atau mungkin kita tahu dari sebuah paper apa input/output dari arsitektur modelnya.

Reverse Engineering APK

File APK SIREKAP tidak diobfuscate sehingga lebih mudah direverse engineer. Andaikan diobfuscate juga tetap akan bisa, hanya butuh lebih banyak waktu. Di versi lama (sebelum masuk Play Store), SIREKAP tidak memproses data secara lokal di ponsel, foto form dikirimkan ke server untuk diproses apa adanya.

Dari versi yang ditemukan di internet, setelah akhir Januari 2024, ada penambahan file .tflite. Tidak diketahui kenapa penambahan digit recognition client side ini baru dilakukan di akhir. Tidak diketahui apakah sebelumnya rencananya akan dilakukan di server atau memang rencananya tidak memakai recognition sama sekali.

Tepatnya ada tiga file yang ditambahkan: blank_detection_model.tflite, accurate.tflite (tidak dipakai), dan satu folder ensemble-15 (berisi 15 file .tflite). Model ensemble adalah teknik machine learning untuk memakai banyak model, lalu dilakukan proses “voting” untuk mendapatkan jawaban mayoritas. “Voting” di sini bisa beragam prosesnya, cara sederhana tinggal menjumlahkan saja tiap prediksi, dan melihat total akhirnya.

accurate.tflite

Pertama kita lihat sekilas accurate.tflite yang tidak pernah diload oleh program (tidak dipakai). Karena ukuran input adalah 28×28, sudah bisa diduga bahwa ini merupakan model yang memakai dataset MNIST untuk trainingnya. Arsitekturnya berbeda dari file contoh sebelumnya (yang jadi tutorial TensorFlow), setelah saya cek, arsitekturnya sama dengan ini . Seperti di artikel itu, di layer terakhir tidak ada softmax.

Berikutnya adalah file blank_detection_model.tflite, ini untuk mendeteksi kosong, arsitekturnya seperti ini:

blank_detection_model.tflite

Model ini dijalankan sebelum menjalankan model ensemble. Dari hasil eksperimen, ternyata ini adalah bagian yang akan menghasilkan angka 0 jika karakternya adalah tanda silang (harus dari ujung diagonal ke diagonal).

Detektor blank (X)

Ini sesuai dengan petunjuk pengisian form:

Petunjuk pengisian form

Berikutnya yang bagian paling penting adalah model ensemblenya. Sebagai catatan, kebanyakan model SOTA memakai ensemble karena bisa memperbaiki akurasi.

Berikut ini salah satu file ensemble-nya ketika dibuka dengan Netron, semuanya arsitekturnya sama. Operasi Mult dan Add kadang disebut juga sebagai Batch Normalization. Pengetahuan semacam ini penting ketika kita ingin mencari atau membandingkan arsitektur model.

ensemble

Dengan membandingkan beberapa hasil pencarian, sepertinya arsitektur modelnya dari MNIST 15 CNN Ensemble, yang akurasinya cukup tinggi.

Dengan dekompilasi, kita bisa melihat pemrosesan apa yang dilakukan di akhir. Kode asli program ini dengan Kotlin, jadi ada kode-kode tambahan dari Kotlin seperti `checkNotNullExpressionValue`.

for (Interpreter interpreter2 : list) {
    Tensor inputTensor2 = interpreter2.getInputTensor(0);
    Tensor outputTensor2 = interpreter2.getOutputTensor(0);
    TensorBuffer createFixedSize2 = TensorBuffer.createFixedSize(inputTensor2.shape(), inputTensor2.dataType());
                Intrinsics.checkNotNullExpressionValue(createFixedSize2, "createFixedSize(inputDetā€¦ inputDetails.dataType())");
    createFixedSize2.loadBuffer(allocateDirect);
    TensorBuffer createFixedSize3 = TensorBuffer.createFixedSize(outputTensor2.shape(), outputTensor2.dataType());
                Intrinsics.checkNotNullExpressionValue(createFixedSize3, "createFixedSize(outputDeā€¦outputDetails.dataType())");
    interpreter2.run(createFixedSize2.getBuffer(), createFixedSize3.getBuffer());
    for (int i3 = 0; i3 < 10; i3++) {
        fArr[i3] = fArr[i3] + createFixedSize3.getFloatArray()[i3];
    }
}
return applyHeuristic(fArr);

Ini sebenarnya hanya memanggil “run” pada tiap “Interpreter” tensorflow dan menjumlahkan hasilnya. Bagian menarik berikutnya adalah applyHeuristic

    private final int applyHeuristic(final float[] probabilities) {
        Integer num;
        Iterator<Integer> it = ArraysKt.getIndices(probabilities).iterator();
        if (it.hasNext()) {
            Integer next = it.next();
            if (it.hasNext()) {
                float f = probabilities[next.intValue()];
                do {
                    Integer next2 = it.next();
                    float f2 = probabilities[next2.intValue()];
                    if (Float.compare(f, f2) < 0) {
                        next = next2;
                        f = f2;
                    }
                } while (it.hasNext());
            }
            num = next;
        } else {
            num = null;
        }
        Integer num2 = num;
        int intValue = num2 != null ? num2.intValue() : 0;
        int intValue2 = ((Number) CollectionsKt.sortedWith(ArraysKt.getIndices(probabilities), new Comparator() { // from class: org.informatika.sirekap.support.vision.Vision$applyHeuristic$$inlined$sortedByDescending$1
            @Override // java.util.Comparator
            public final int compare(T t, T t2) {
                return ComparisonsKt.compareValues(Float.valueOf(probabilities[((Number) t2).intValue()]), Float.valueOf(probabilities[((Number) t).intValue()]));
            }
        }).get(1)).intValue();
        if (intValue != 0 || probabilities[0] >= 10.0d) {
            if (intValue != 1 || probabilities[1] >= 14.5d) {
                if (intValue == 3 && probabilities[3] < 10.0d && probabilities[9] > 3.0d) {
                    return 9;
                }
            } else if (probabilities[intValue2] > 0.0d) {
                return intValue2;
            }
        } else if (intValue2 == 8) {
            return intValue2;
        }
        return intValue;
    }

Kodenya terlihat aneh karena ini hasil dekompilasi Kotlin yang memakai fitur filtering. Jika saya sederhanakan, hasilnya:

    private final int applyHeuristic(final float[] probabilities) {
        //find max
        int firstMax = 0;
        for (int i = 0; i < 10; i++) {
            if (probabilities[i] > probabilities[firstMax]) {
                firstMax = i;
            }
        }
        //find second max
        int secondMax = 0;
        for (int i = 0; i < 10; i++) {
            if (probabilities[i] > probabilities[secondMax] && i != firstMax) {
                secondMax = i;
            }
        }
        if (firstMax != 0 || probabilities[0] >= 10.0d) {
            if (firstMax != 1 || probabilities[1] >= 14.5d) {
                if (firstMax == 3 && probabilities[3] < 10.0d && probabilities[9] > 3.0d) {
                    return 9;
                }
            } else if (probabilities[secondMax] > 0.0d) {
                return secondMax;
            }
        } else if (secondMax == 8) {
            return secondMax;
        }
        return firstMax;
    }

Ternyata ini adalah tambahan heuristik untuk angka-angka tertentu yang terlihat mirip, misalnya 3 dan 9.

Loading dan testing

Saya ingin membandingkan hasil tutorial MNIST tensorflow vs SIREKAP, baik versi accurate maupun ensemble. Untuk ini saya membuat aplikasi Android sederhana (source codenya saya buka). Saya memakai contoh Finger Paint dari Android untuk komponen penggambaran digitnya, dan memanggil semua model, lalu menampilkan hasilnya.

Pemakaian TFLite (tensorflow lite) sangat sederhana, masalahnya adalah bagaimana mengkonversi data ke format yang dikehendaki. Tapi dengan melihat beberapa contoh yang ada, ini bisa mudah dilakukan.

Harap dimaklumi kalau tampilannya kurang bagus, saya bukan programmer mobile FE. Saya juga terbiasa memakai Java dibandingkan Kotlin. Salah satu alasannya adalah: saya lebih sering melakukan reverse engineering, dan hasil dekompilasi baik Kotlin maupun Java akan menjadi kode Java. Jadi lebih gampang untuk copy paste hasil reverse engineering dan testing hasilnya daripada harus konversi lagi menjadi Kotlin.

Program Test

Kodenya bisa dilihat di sini:

https://github.com/yohanes/mnist-sirekap

Dan jika ingin test APK-nya, bisa download dari sini:

https://github.com/yohanes/mnist-sirekap/releases

Kemungkinan ini tidak akan saya update lagi (ini cuma hasil keisengan hari ini). Ini demo videonya kalau tidak ingin menginstall APK (atau mungkin pemakai iOS).

Akurasi

Dari hasil percobaan saya, akurasi SIREKAP ini cukup tinggi. Saya hanya mengetes bagian pengenalan digitnya saja, masih ada pemrosesan sebelumnya (pengambilan foto, pelurusan gambar, filtering), yang mungkin bisa mengurangi akurasinya. Proses pengambilan foto memakai library AndroidDocumentScanner. Jika ingin melihat kode-kode Vision, ada di package: org.informatika.sirekap.support.vision.

Uhuy (fungsi ini tidak dipanggil)

Jika akurasi model adalah 99.9% (sementara model saat ini yang paling modem /state of the art akurasinya hanya 99.87%), dan semua orang taat mengisi sesuai petunjuk, tetap ada kemungkinan 0.1% digit salah terbaca. Karena ada 823236  TPS, dan tiap lembar form ada 9 digit yang perlu dibaca (3 digit untuk tiap calon), maka kemungkinan ribuan digit (sekitar 7409) yang salah terbaca (ini bisa terkonsentrasi di ratusan form, atau tersebar di ribuan form). Dengan kesalahan penulisan form, foto yang kurang jelas, noda di kertas, dsb, maka tingkat kesalahannya akan lebih tinggi.

Jadi 5 tahun lagi ketika ada pemilu lagi, jika metode ini dipakai, kemungkinannya tetap akan ada ratusan hingga ribuan form yang akan salah diproses digitnya. Untungnya hasil akhir pemilu tidak memakai SIREKAP tapi dengan rapat pleno berjenjang.

Disclaimer di halaman depan https://pemilu2024.kpu.go.id/

Penutup

Semoga artikel ini bisa membantu memberi informasi bagi yang ingin melakukan reverse engineering terhadap model AI, terutama aplikasi yang memakai TFLite. Contohnya: jika sebuah aplikasi finansial melakukan inferensi lokal dengan TFLite untuk KYC, maka ini bisa dipelajari dan mungkin dibypass.

Semoga bisa menjawab juga rasa penasaran orang-orang yang ingin tahu akurasi model SIREKAP.

Banyak yang bertanya ke saya soal pemilu, tapi hal-hal itu tidak bisa saya jawab karena saya tidak tahu seperti apa backend-nya. Saya tidak mau jadi orang yang sekedar menuduh tanpa bukti. Banyak “ahli” yang nebak-nebak memakai tool yang bahkan nggak tau cara kerjanya. Banyak “ahli” yang bahkan tidak mengerti soal CDN, tidak mengerti soal Loadbalancer, tidak mengerti bahwa 1 IP bisa ditangai banyak server, dan sebaliknya satu server bisa punya banyak IP.

Untuk masalah digits recognition ini, saya bisa melihat kodenya, bisa membongkar, dan juga menunjukkan cara kerjanya. Dan kalau ada bagian yang salah, orang-orang juga bisa mengoreksi tulisan dan kode saya.

6 thoughts on “Reverse Engineering Model Handwritten Digits Recognition Aplikasi Mobile SIREKAP”

Tinggalkan Balasan

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses.