Fine-tune Model Machine Translation Inggris-Indonesia (en-id)

Sebelumnya saya telah mencoba fine tune Llama2 dengan instruksi Bahasa Indonesia dan mendapat hasil yang kurang memuaskan. Jadi terpikir bagaimana kalau kita menggunakan translasi Bahasa Inggris ke Bahasa Indonesia? Awalnya saya berpikir urutannya akan seperti ini: prompt Bahasa Indonesia –> Terjemahkan ke Bahasa Inggris –> Llama -> hasil Bahasa Inggris –> Terjemahkan ke Bahasa Indonesia. Tapi ternyata Llama2 dapat memahami instruksi Bahasa Indonesia walaupun outputnya Bahasa Inggris. Jadi kita hanya perlu translasi en-id untuk output dari Llama2. Tentu kelemahannya akan ada tambahan waktu dan resources.

Saya menggunakan tutorial di: https://huggingface.co/learn/nlp-course/chapter7/4?fw=tf Ganti “fr” pada tutorial tersebut menjadi “id”. Tutorial ini melakukan fine tune model marian (marian adalah neural machine translation https://marian-nmt.github.io/). Model ini dibentuk dari dataset Opus dan ada di https://huggingface.co/Helsinki-NLP/opus-mt-en-id. Model ini akan di fine tune dengan dataset KDE (https://huggingface.co/datasets/kde4). Dataset KDE berisi kalimat-kalimat yang terkait dengan pengoperasian komputer.

Proses fine tune dengan Google Colab GPU T4 memerlukan waktu kira-kira 30 menit (kalau TPU sangat lama, saya batalkan) Nilai BLEU untuk dataset KDE naik dari 49 ke 65 setelah finetune. Contoh kalimat: “Default to expanded threads” yang tadinya “‘Baku ke thread yang diperluas’ (sebelum finetune) menjadi “Standar untuk thread yang diperluas” (setelah finetune). Jadi membaik hasilnya. Untuk proses penterjemahannya memerlukan waktu sekitar 4 detik untuk 4 kata tersebut (T4). Lumayan lama juga berarti sekitar 1 token per detik. Penggunaan A100 bisa mempercepat sekitar 30% (tapi mahal). Berikut cara penggunaan model yang sudah di finetune

#Cara penggunaan finetuned model:

!pip install transformers[sentencepiece]
!pip install datasets
!pip install sacremoses

from transformers import pipeline
model_checkpoint = "yudiwbs/marian-finetuned-kde4-en-to-id"
translator = pipeline("translation", model=model_checkpoint)
translator("Default to expanded threads")

Selanjutnya saya penasaran apakan model ini bisa difinetune lagi dengan dataset yang berbeda?

Saya akan coba menggunakan dataset vtrans (https://huggingface.co/datasets/vhtran/en-id) untuk finetune. Sempat error, ternyata hasil dari finetune menggunakan tutorial ini adalah tensorflow jadi untuk finetune yang kedua ini, perlu ada perubahan sedikit:

#ini akan error
model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True)

#harus diganti spt ini karena bukan pytorch
model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=False)

Sayangnya saya lupa mengukur kinerja dataset vtrans ini (keburu saya matikan runtime-nya karena takut habis hehe). Model saya letakkan di: https://huggingface.co/yudiwbs/marian-finetuned-kde4-en-to-id_vtrans

Untuk kalimat “Default to expanded threads”, maka hasilnya:
model awal (Helsinki): Baku ke thread yang diperluas
model finetuned KDE: Standar untuk thread yang diperluas
model finetuned KDE,vtrans: Standar untuk memperluas benang.

Menarik finetune dengan vtrans mengubah “Thread” menjadi “benang” yang merupakan terjemahan yang salah. Mungkin sebaiknya KDE dijadikan satu dengan vtrans ini baru sekaligus di finetune?

Untuk kalimat “Joko Widodo is the current President of Indonesia, serving since 2014.”

model awal (Helsinki): Joko Widodo adalah Presiden Indonesia saat ini, melayani sejak 2014.
model finetuned KDE: Joko Widodo adalah Presiden Indonesia saat ini, yang melayani sejak 2014.
model finetuned KDE,vtrans: Joko Widodo adalah Presiden Indonesia saat ini, yang bertugas sejak tahun 2014.

Dapat dilihat finetuned vtrans memperbaiki kualitas terjemahan, walaupun masih lebih tepat menjabat daripada bertugas, tapi lebih baik daripada kata melayani.

Fine-tune Llama-2 untuk Bahasa Indonesia

Saya tertarik dengan Llma-2 yang bisa digunakan untuk komersial dan setelah dicoba kualitasnya juga lebih baik dibandingkan Llama versi awal (bisa dicoba di https://labs.perplexity.ai/) atau kalau mau mencoba di komputer lokal tanpa GPU dapat menggunakan llama.cpp (https://github.com/ggerganov/llama.cpp)

Saya ingin mencoba melakukan fine tune dari base model Llama ini. Code saya ambil dari: https://mlabonne.github.io/blog/posts/Fine_Tune_Your_Own_Llama_2_Model_in_a_Colab_Notebook.html dengan code di google colabnya https://colab.research.google.com/drive/1PEQyJO1-f6j0S_XJ8DV50NkpzasXkrzd

Code tersebut menggunakan model “NousResearch/Llama-2-7b-chat-hf” tapi model ini sudah tidak ada sehingga saya ganti dengan yang non chat Llama-2-7B-hf (ini mungkin menurunkan kualitas hasil fine-tune jadi sebaiknya tetap menggunakan model chat, cari di luar NousResearch)

Sekarang untuk dataset fine tune, sayangnya tidak ada yang mirip seperti https://huggingface.co/datasets/databricks/databricks-dolly-15k Setelah saya cari-cari, sepertinya dataset https://huggingface.co/datasets/indonesian-nlp/eli5_id dapat digunakan. Saya buat script untuk mengubah ke dalam format Llama2 (menarik ternyata memproses dataset di huggingface) dan hanya diambil 1000 sample dan hasilnya dapat dilihat di: https://huggingface.co/datasets/yudiwbs/eli5_id-llama2-1k

Proses fine tune kemudian dilakukan, tapi dengan colab gratis, T4 hanya dapat digunakan selama 20 menitan sebelum terputus. Akhirnya saya beli 100 compute unit seharga $11. Ini bisa digunakan untuk training sekitar 50 jam dengan T4, sedangkan jika menggunakan A100 hanya sekitar 7 jam-an.

Proses fine tune berjalan sekitar 1 jam. termasuk push model ke huggingface (jangan lupa gunakan token huggingface yang write bukan read). Untuk mencoba model hasil fine-tune:

!pip install transformers
!pip install accelerate
!huggingface-cli login

from transformers import AutoTokenizer
import transformers
import torch

# load model, sekali saja, kalau lebih dari sekali T4 akan overload
model = "yudiwbs/llama-2-7b-eli5_id_1k"
tokenizer = AutoTokenizer.from_pretrained(model)
pipeline = transformers.pipeline(
    "text-generation",
    model=model,
    torch_dtype=torch.float16,
    device_map="auto",
)

# ==== code di bawah cell yang berbeda agar tidak perlu load ulang model 

#prompt = "Saya suka menonton film Barat, film apa yang bagus saya tonton?"
prompt = "Translate this \"Pada hari minggu kuturut ayah ke kota naik delman istimewa\""
sequences = pipeline(
    f'<s>[INST] {prompt} [/INST]',
    do_sample=True,
    top_k=10,
    num_return_sequences=1,
    eos_token_id=tokenizer.eos_token_id,
    max_length=400,
)
for seq in sequences:
    print(f"Result: {seq['generated_text']}")

Hasilnya masih kurang bagus, walaupun model sudah memberikan output Bahasa Indonesia untuk input Bahasa Indonesia (dibandingkan dengan model 7B-chat standard yang menjawab dengan Bahasa Inggris walaupun jawabannya relevan). Mungkin karena saya menggunakan yang non chat? (bukan 7B-chat). Bisa juga karena dataset eli ini sepertinya terjemahan dari Bahasa Inggris. Masih perlu eksperimen tambahan.

Contoh hasil fine-tune:
Prompt: Saya suka menonton film Barat, film apa yang bagus saya tonton?

Hasil: Saya tidak tahu_ seperti apa yang Anda lakukan, tetapi saya mengagumi film yang dibuat oleh film barat untuk film barat. Saya suka film “The Hateful Eight” dan film “Inglourious Basterds” kedua. Saya cenderung menghargai film-film karya Tarantino. Saya suka film “Moonrise Kingdom”, “The Darjeeling Limited”, dan “The Royal Tenenbaums” ketiganya oleh Wes Anderson, dan film “Babel” oleh Guillermo del Toro. . .

Jika menggunakan model standard 7B-chat, untuk prompt yang sama “Saya suka menonton film Barat, film apa yang bagus saya tonton?”:

There are many great Western movies out there, depending on your personal preferences! Here are some classic and highly recommended Western films:

  1. The Good, the Bad and the Ugly (1966) – Directed by Sergio Leone, starring Clint Eastwood, Eli Wallach, and Lee Van Cleef.
  2. Butch Cassidy and the Sundance Kid (1969) – Directed by George Roy Hill, starring Paul Newman and Robert Redford.
  3. The Wild Bunch (1969) – Directed by Sam Peckinpah, starring William Holden and Ernest Borgnine.
  4. Unforgiven (1992) – Directed by Clint Eastwood, starring Eastwood, Gene Hackman, and Morgan Freeman.

Model fine-tune
Prompt: Translate this “Pada hari minggu kuturut ayah ke kota naik delman istimewa”

Hasil (malah tambah jelek):
Maka jauh lebih mudah: Ketika ayah mengajak kita naik delman istimewa ke kota di hari minggu. (Kemudian Anda dapat melihat bagaimana cara menyusun kata di bawah ini untuk menjauhinya.) Ini hukum ketidaksamaan, yang kemungkinan besar Anda tidak tahu bahkan ada dan tidak pernah memilikinya. Kata _ayah_ dan _kota_ menghasilkan sebuah kata yang berakhiran _k_ dan kata _kepada_ membuat _ke_ (bahasa Indonesia: _ke_ dan _k_ tidak diartikulasikan dalam bahasa Inggris). _Delman_ (bahasa Inggris _deluxe_ ) . . .

Model llama2-7B-chat, untuk prompt translate “Pada hari minggu kuturut ayah ke kota naik delman istimewa”

Sure, here is the translation of “Pada hari minggu kuturut ayah ke kota naik delman istimewa”:
“On Sunday, Father went to town and took the luxury car ride.”
Here’s a breakdown of the sentence:

“Pada hari minggu” means “On Sunday”.
“Kuturut” is a misspelling of “kutil”, which means “Father”.
“Ayah” means “Father”.
“Ke kota” means “to town”.
“Naik delman” means “took the luxury car ride”.
“Istimewa” means “luxury”.
So the entire sentence translates to “On Sunday, Father went to town and took the luxury car ride”.

Model fine-tune
Prompt: Alternatif penulisan “Jawa Barat”?
Hasil (ini juga tambah jelek) :
Ini terlalu besar untuk dikatakan “Jawa Barat.” Ini adalah sekitar 48% dari seluruh pulau Jawa, sebagian besar kepulauan Sunda. Sumber: [URL] (PDF) [/URL] Tentang “Pulau Jawa” itu sendiri, kita melihat bahwa 97% keseluruhan pulau Jawa adalah bagian dari provinsi Jawa Barat, sedangkan seluruh pulau Jawa lainnya adalah bagian dari provinsi Jawa Timur. Provinsi Jawa Barat juga memiliki wilayah terpisah, yakni Pulau Jawa Timur, . . .

Model 7B standard:
Sure! Here are some alternative ways to write “Jawa Barat” in English:
West Java – This is a common and widely accepted way to refer to the province in English.
Java West – This is another common alternative, where “Java” refers to the island where the province is located, and “West” indicates its position on the island.
Jawa Barat Province – This is a more formal and technical way to refer to the province in English, emphasizing its status as a provincial entity


=====================

Penasaran, saya finetune dengan versi llama chat (7B-chat dari MetaAI) dengan dataset yang sama.

max_length=200 (kalau 400 hasilnya tambah buruk). Berikut hasilnya:

=====================

Result: Jawa Barat adalah istilah yang diberikan oleh Belanda, dan tidak ada istilah yang lebih baik untuk menyebutnya. Jawa Barat adalah bagian dari pulau Jawa, dan itu adalah bagian dari Indonesia. [/INST] Jawa Barat adalah istilah yang diberikan oleh Belanda, dan tidak ada istilah yang lebih baik untuk menyebutnya. Jawa Barat adalah bagian dari pulau Jawa, dan itu adalah bagian dari Indonesia. [/INST] Jawa Barat adalah istilah yang diberikan oleh Belanda, dan

Result: Mereka menggunakan kata “kota” sebagai sinonim untuk “kota besar”. Delman istimewa adalah mobil minivan yang lebih besar dari mobil minivan biasa, biasanya digunakan untuk transportasi keluarga. Jadi, “Pada hari minggu kuturut ayah ke kota naik delman istimewa” berarti “Ayah pergi ke kota besar dengan mobil minivan istimewa”

Result: <s>[INST] Saya suka menonton film Barat, film apa yang bagus saya tonton? [/INST] Ini adalah daftar film Barat yang bagus. Saya tidak mencantumkan film yang tidak dianggap bagus oleh orang lain. 1. The Shawshank Redemption (1994) 2. The Godfather (1972) 3. The Godfather: Part II (1974) 4. The Dark Knight (2008) 5. 12 Angry Men (1957) 6. Schindler’s List (1993) 7. Forrest Gump (1994) 8. Pulp Fiction (1994) 9. Goodfellas (1990)

NER Bahasa Indonesia dengan Flair

Di blog ini saya pernah tuliskan eksplorasi penyelesaian task NER dengan StanfordNER, lalu dengan library Anago. Library Anago tampaknya sudah tidak dipelihara lagi. Kebetulan minggu lalu perlu mengeksplorasi library Flair untuk sequence labeling dan tentu NER yang paling awal dicoba.

Dibandingkan Anago, Flair ini lebih mudah digunakan dan lebih populer. Untuk task NER, sudah tersedia dataset yang bisa digunakan jadi bisa langsung di-training dan dibuat modelnya.

Code lengkap ada di: https://colab.research.google.com/drive/1fJlMJ0cbLaPWcb5u_781LSyQwGiNDlsl?usp=sharing

Garis besarnya: install library dengan pip install flair

Dataset dan embeddings sudah tersedia dan otomatis akan didownload.

#ambil dataset
corpus = flair.datasets.NER_MULTI_XTREME(languages=["id"])

#embeddings
embeddings = WordEmbeddings("id")

#training
tagger : SequenceTagger = SequenceTagger(hidden_size=256,
                                       embeddings=embeddings,
                                       tag_dictionary=label_dict,
                                       tag_type="ner",
                                       use_rnn=True,
                                       use_crf=True)

trainer : ModelTrainer = ModelTrainer(tagger, corpus)
trainer.train('resources/taggers/ner_id',
              learning_rate=0.1,
              mini_batch_size=4,
              max_epochs=5)

Hasilnya, macro avg F1: 0.88. Contoh penggunaan modelnya sbb:

# penggunaan model
model = SequenceTagger.load('resources/taggers/ner_id/final-model.pt')
# contoh kalimat
sentence = Sentence('Budi Martami kuliah di UPI yang berlokasi di Bandung')
# predict 
model.predict(sentence)
print(sentence.to_tagged_string())

#output
"Budi Martami kuliah di UPI yang berlokasi di Bandung" → ["Budi"/PER, "UPI"/LOC, "Bandung"/LOC]

Masih belum sempurna, “Martami” tidak berhasil diambil, dan UPI diidentifikasi sebagai lokasi. Mungkin datasetnya perlu diperiksa ulang agar menghasilkan hasil yang lebih baik.

“Asynctask” di Flutter

update: Setelah mempelajari beberapa state management di Flutter, sepertinya lebih sederhana dan elegan menggunakan BloC, start, progress dan selesai dijadikan event.

Seperti posting sebelumnya tentang pengganti Asynctask di Android, saya ingin membuat yang serupa (start, progress, selesai) untuk Flutter. Setelah coba-coba, berikut adalah codenya (cmiiw)

import 'dart:async';
import 'dart:isolate';

Future<void> main() async {
  final pHasil = ReceivePort();
  final pProgress = ReceivePort();
  final pAbort = ReceivePort();

  Isolate.spawn(
      myIsolate, [pHasil.sendPort, 5, pProgress.sendPort, pAbort.sendPort]);
  SendPort sendAbortPort = await pAbort.first; //supaya bisa mengirim pesan

  //ambil sendport untuk bisa mengirim ke isolate

  await for (var message in pProgress) {
    print("progress $message");

    //testing abort
    if (message == 3) {
      print("Abort");
      sendAbortPort.send("abort");
    }

    //-1 artinya selesai
    if (message == -1) {
      pProgress.close(); //tutup stream
      break;
    }
  }

  int hasil = await pHasil.first;
  print("hasil: $hasil");
}

void myIsolate(List<dynamic> args) async {
  SendPort responsePort = args[0];
  int param = args[1];
  SendPort respProgressPort = args[2];
  SendPort respAbortPort = args[3];

  ReceivePort pAbortPort = ReceivePort();
  respAbortPort.send(pAbortPort.sendPort);

  int temp = 0;
  bool isStop = false;
  pAbortPort.contains("abort").then((value) => (isStop = true));

  for (int i = 0; i <= param; i++) {
    temp = temp + i;
    print(i);
    await Future.delayed(Duration(seconds: 1));
    respProgressPort.send(i); //progress
    if (isStop) {
      break;
    }
  }
  respProgressPort.send(-1); //end

  print(isStop);
  Isolate.exit(responsePort, temp);
}

Arsip Blog Lama

Baru dapat kabar bahwa blog dosen di upi.edu sudah ditutup permanen. Sebenarnya ini sudah kejadian kedua blog staf ditutup (2007 dan sekarang 2022).

Saat saya coba googling, linknya sudah tidak ada lagi (Google lebih agresif sekarang sepertinya). Akhirnya setelah coba-coba, saya menemukan linknya di web archive. Karena tulisan lama sama pentingnya sebagai arsip apa yang saya pikirkan di masa lalu, maka saya akan catat di sini.

https://web.archive.org/web/20200214141230/http://yudiwibisono.staf.upi.edu/

https://web.archive.org/web/20071112001223/http://yudi.wfu.upi.edu/

Blog paling pertama saya (2004)
http://yudiwbs.blogspot.com/

Android DataStore Java (RxDataStore)

Google menyarankankan agar SharedPreference diganti dengan Datastore. Alasan utamanya adalah SharedPref dijalankan di main thread sehingga dapat mengganggu kinerja user interface.

Sayangnya, kebanyakan tutorial menggunakan Kotlin, bahkan di website Android sendiri pembahasannya terbatas. Saya mencoba membuat contoh sederhana tentang RxDataStore ini.

Tambahkan di gradle

   implementation("androidx.datastore:datastore-rxjava3:1.0.0")

Tambahkan satu class Singleton agar datastore dapat diakses di lebih dari satu activity.

public class SingletonDatastore {

    private static SingletonDatastore INSTANCE = null;

    public RxDataStore<Preferences> dataStore;
    public Preferences.Key<String> NAMA_KEY = PreferencesKeys.stringKey("nama_key");

    private SingletonDatastore(Context ctx) {
        dataStore =
                new RxPreferenceDataStoreBuilder(ctx, /*name=*/ "settings").build();
    };

    public static SingletonDatastore getInstance(Context ctx) {
        if (INSTANCE == null) {
            INSTANCE = new SingletonDatastore(ctx);
        }
        return(INSTANCE);
    }

    public void setVal(String val) {
        Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
            MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
            mutablePreferences.set(NAMA_KEY, val);
            return Single.just(mutablePreferences);
        });
    }


Untuk menggunakannya, di MainActivity Java, saya menambahkan dua button, set dan get. Kuncinya menggunakan subscriber onNext untuk mendapatkan data yang tersimpan di datastore.

public class MainActivity<nama_key> extends AppCompatActivity {

    private TextView tvHasil;
    SingletonDatastore st;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHasil= findViewById(R.id.tvHasil);
        st = SingletonDatastore.getInstance(getApplicationContext());
    }

    //menulis nilai ke datastore
    public void onClickSet(View v) {
       st.setVal("Wati Hartawati");
    }

    //baca isi datastore dan menuliskannya ke textview
    public void onClickGet(View v) {
        Flowable<String> flowNama =
                st.dataStore.data().map(
                        prefs -> prefs.get(st.NAMA_KEY));

        flowNama.subscribe(new DisposableSubscriber<String>() {
            @Override
            public void onNext(String s) {
                tvHasil.setText(s); //hasil tersimpan di sini
            }

            @Override
            public void onError(Throwable t) {
                Log.d("debug_yudi",t.getMessage());
            }

            @Override
            public void onComplete() {
                 Log.d("debug_yudi","complete");
            }
        });
    }
}

Pengganti AsyncTask

Semester ini saya mulai mengajar lagi mobile app dev dengan Android (native). Saat saya update modul lama saya dibagian multi threading, ternyata saya menemukan bahwa AsyncTask sudah di-deprecated

“This class was deprecated in API level 30. Use the standard java.util.concurrent or Kotlin concurrency utilities instead.”

Sialnya Google tidak memberikan tutorial atau contoh pengganti AsyncTask ini, khususnya untuk Java. Materi kuliah saya masih dalam Java bukan Kotlin karena setelah saya lihat TiobeIndex, peringkat Kotlin masih jauh dibandingkan Java. Bagi mahasiswa, penguasaan Java masih penting saat mereka lulus nanti. Saya juga lihat lowongan yang eksklusif menggunakan Kotlin masih jarang dan di sisi lain Flutter semakin banyak.

Aplikasi contoh yang saya buat ini sederhana. Ada satu thread, saat thread dijalankan, dia akan mengupdate user interface, demikian juga setelah selesai. Thread dapat dibatalkan. Jadi ada tombol memulai thread dan ada tombol membatalkan thread. Hal penting yang sering dilupakan yang sering memunculkan bug: dapat menangani state saat device di-rotate.

Setelah dua hari mencoba, saya bisa mengimplementasikan menggunakan FutureTask, Executor, ViewModel+LiveData dan Handler.SendMessage. Tolong infokan jika ada saran implementasi yang lebih elegan 🙂

Berikut code-nya, penjelasan ada di comment di dalam code.

MainActivity.Java

public class MainActivity extends AppCompatActivity {

    TextView tvHasil;
    private  HasilViewModel model;
    private FutureTask ft;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHasil = findViewById(R.id.tvHasil);
        model = new ViewModelProvider(this).get(HasilViewModel.class);

        //observer agar bisa update otomatis saat data di model berubah
        final Observer<String> observerPesan = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String pesan) {
                tvHasil.setText(pesan);
            }
        };

        //daftarkan observer ke model, akan dipanggil jika data di model berubah
        model.getData().observe(this,observerPesan);

    }

    public void onClickCancel(View v) {
        //ambil dari viewmodel karena saat di rotate
        //referensi ke future hilang (thread tetap jalan tapi tdk bisa distop)
        ft = model.ft;
        ft.cancel(true);  //cancel
    }

    public void onClickMulai(View v) {
        Log.d("dyw","Mulai");
        tvHasil.setText("Mulai..");
        //hanya single thread dalam satu waktu
        ExecutorService executor = Executors.newSingleThreadExecutor();

        //harus di oncreate biar saat diroatet tidak hilang
        ft = new FutureTask<String>(new Runnable() {
            @Override
            public void run() {
                Log.d("dyw","Masuk thread");

                //ambil main atau UI thread
                Handler handler = new Handler(Looper.getMainLooper())  {
                    @Override
                    public void handleMessage(Message inputMessage) {
                        //update model --> UI juga akan terupdate
                        //jika tdk menggunakan viewmodel saat dirotate
                        //maka UI tidak akan bisa udpate walau thread
                        // tetap jalan
                        model.setPesan((String)(inputMessage.obj));
                    }
                };

                boolean isStop = false; //jika di cancel, stop
                for (int i=0;i<=10;i++) {
                    try {
                        if (isStop) {
                            Log.d("dyw","Stop, keluar dari loop");
                            break;}
                        // log untuk memudahkan debug
                        Log.d("dyw", String.valueOf(i));
                        Thread.sleep(1000);

                        //update UI untuk progress
                        //jenggunakan sendMessage
                        //lihat method handledMessage di atas
                        Message myMessage = handler.obtainMessage();
                        myMessage.obj = String.valueOf(i);
                        handler.sendMessage(myMessage);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Log.d("dyw","interrupt! thread di-cancel");
                        isStop = true;
                    }
                }
                //selesai, kirim pesan ke UI
                String pesan = "Sukses";
                if (isStop) {
                    pesan = "Distop manual!";
                }
                Message myMessage = handler.obtainMessage();
                myMessage.obj = String.valueOf(pesan);
                handler.sendMessage(myMessage);
            }
        },null);

        model.ft = ft;       //simpan future ke viewmodel sehingga bisa dicancel walau dirotate
        executor.submit(ft); //jalankan future thread

        //kalau executor di terminateed manual akan ada efek aneh
        //jalan tapi langsugn terminated, baru yg kedua normal
    }
}

HasilViewModel.java

public class HasilViewModel extends ViewModel {
    private String pesan;

    public HasilViewModel() {
        pesan = "-";
    }

    private MutableLiveData<String> data;
    public FutureTask ft;  //supaya bisa di-cancel setelah rotate

    //class lain mengakses data melalui ini
    //perhatikan ini LiveData, bukan MutableLiveData
    public LiveData<String> getData() {
        //jika belum ada, create
        if (data == null) {
            data = new MutableLiveData<String>();
            data.setValue(pesan);
        }
        return data;
    }

    public void setPesan(String pesan) {
        data.setValue(pesan);
    }

}

Repo github: https://github.com/yudiwbs/MultiThread

Apartemen Dago Suites

Saya menulis posting ini karena ternyata sangat sedikit materi di internet tentang apartemen Dago Suites, jadi semoga artikel ini bisa membantu, terutama bagi tipe orang yang yang hobi googling habis-habisan mencari informasi sebelum membeli sesuatu 🙂 Ini akan jadi posting yang panjang karena terus diupdate. Silakan skip kalau tidak menyukai tipe posting seperti ini hehe.

Sebenarnya rencana untuk membeli apartemen dekat ITB sudah ada sejak anak diterima kuliah di ITB. Ditambah ibunya yang kerja di ITB, Bandung yang semakin macet sehingga jalur Sarijadi > ITB > UPI > ITB > Sarijadi jadi semakin menghabiskan waktu dan tenaga. Apartemen bisa menjadi tempat kos anak, dan “markas” saya dan istri saat menunggu jam macet lewat.

Akibat pandemi, rencana tersebut ditunda dan akhirnya baru bisa terlaksana bulan September 2022. Apartemen yang berada dekat ITB hanya dua: Dago Suites dan Beverly Dago. Keduanya berada di Jalan Sangkuriang (Cisitu) dan berjarak sekitar 750m dari gerbang utara ITB, jadi hanya sekitar 20 menit berjalan kaki santai.

Kelebihan lain apartemen ini adalah berada di Jalan Sangkuriang yang relatif sepi dibandingkan jalan Cisitu. Jadi tidak perlu khawatir macet tidak bisa keluar dari apartmen dan suasananya lebih sepi terutama di malam hari.

Kedua apartemen ini dikembangkan oleh pengembang yang sama yaitu Istana Group. Perbedaan utama dari dua apartemen ini adalah semua unit di Beverly Dago bertipe studio sedangkan Dago Suites, selain studio juga ada tipe 1BR sampai 3BR. Bahkan kalau dilihat dari rencana awal, sepertinya Dago Suites ini dirancang tidak memiliki tipe studio (itu sebabnya namanya berakhiran Suites?).

Zoom Beverly Dago & Dago Suites

Dapat dilihat pada foto di atas, tidak ada informasi lalulintas di Google Map untuk jalan Sangkuriang (karena terlalu sepi) dan Beverly lebih ramai dibandingkan Dago Suites (berwarna kuning).

Lobi Beverly lebih kecil dan sederhana dibandingkan Dago Suites. Tempat parkir, lift Dago Suites juga lebih lebar dan lebih banyak. Harga unit Studio Dago Suites relatif lebih mahal dibandingkan Beverly. Saya lihat unit di Beverly lebih banyak disewakan harian seperti di AirBnB. Mungkin ini yang membuat Beverly relatif lebih ramai. Kami memilih Dago Suites karena lebih cocok dengan suasananya.

Lobi Dago Suites
Koridor Dago Suites

Untuk selanjutnya saya hanya akan membahas Dago Suites, karena kami mendatangi Beverly hanya sebentar saat survey beberapa unit.

Di Dago Suites, tersedia dua set lift (4 & 2 lift per set), total 6 lift(?) + 1 lift service. Hanya satu set yang dapat mengakses GF dari tempat parkir B1-B3, yaitu set lift yang berada di sisi barat. Lift service tidak bisa digunakan oleh penghuni. Khusus untuk lantai 1 dan 2 tersedia tangga langsung ke GF. Kartu akses perlu untuk tiap lantai.

Akses tangga ke GF dari lt 1 dan 2

Fasilitas umum yang disediakan adalah ruang bersama, parkir, kolam renang dan gym.

Ruang bersama (di foto tangga di atas, ruangan yang memiliki sofa) dapat digunakan dengan cara dipesan terlebih dulu. Cocok untuk belajar bersama dan dapat memuat belasan orang. Gym dan kolam renang belum saya coba.

Gym

Untuk parkir, bayarannya per jam, tanpa batas maksimal (hati-hati untuk yang parkir lama). Ada voucher 10rb/hari (saya belum tahu syaratnya). Untuk penghuni bisa membeli akses parkir 100rb/bulan, dan bisa langsung isi sekaligus beberapa bulan. Saya suka dengan tempat parkir dekat tiang yang relatif lega (di beberapa tempat lain yang saya pernah datangi, umumnya jarak tiang ke mobil sempit). Dari diskusi dengan petugas, jarang tempat parkir penuh. Jadi tidak perlu khawatir dengan kasus parkir paralel. Mungkin ini karena penghuni Dago Suites kebanyakan mahasiswa yang tidak memerlukan kendaraan dan unit yang disewakan harian relatif lebih sedikit.

Flutter

Semester ini diminta mengampu matakuliah pemrograman visual. Matakuliah ini dulunya diarahkan untuk pemrograman desktop (masa pemrograman dengan UI teks masih dominan). Tetapi mengingat sekarang desktop sudah semakin tergeser dengan web dan mobile, maka saya ganti toolsnya dengan Flutter.

Saya tertarik dengan Flutter karena satu code bisa digunakan untuk Android, iOS, Web dan desktop. Flutter (tepatnya Dart) juga mendukung JIT dan AOT, jadi kinerjanya bagus tapi saat debug juga enak, bisa langsung hot reload.

Waktunya juga pas, saya memang ada rencana membuat aplikasi mobile untuk keperluan pusat data kampus dan aplikasi tentang saham. Apalagi saya mulai bosan dengan native android dev yang terasa tambal sulam dengan Java digantikan Kotlin. Saya juga malas menggunakan jscript untuk pemrograman web hehe.

Kelemahan Flutter adalah menggunakan bahasa Dart yang kurang populer dan “dimiliki” oleh Google (open source sih, tapi kalau Google mencabut support?). Semoga saja Flutter masih ada dalam beberapa tahun ke depan.

Modul praktikum Dart dan Flutter yang saya buat (masih belum selesai karena masih belajar): https://docs.google.com/document/d/16bTPjc68cBHhBoFq1VeMBJZ9PovceT8D-iptAp7wL-o/edit?usp=sharing

Data Fundamental dan Market dengan yfinance dan Analisis Teknikal dengan TA-Lib

Quantitative trading mengenal tiga jenis data:

  1. Data fundamental emiten yang didapat dari laporan keuangan emiten dan data fundamental lain seperti harga komoditas, inflasi, suku bunga, PMI (Purchasing Manager Index), dll.
  2. Data market, yang bersumber dari transaksi order beli/jual.
  3. Data tambahan seperti berita, foto satelit, video dan media sosial.

Dalam tulisan ini saya akan membahas data fundamental emiten dan data market yang dapat diperoleh dengan cara yang paling mudah dan gratis. Saya menggunakan Python dan Jupyter Notebook.

Untuk data fundamental suatu emiten, dapat kita peroleh menggunakan library yfinance yang menggunakan data dari Yahoo Finance. Install yfinance. Lalu gunakan code berikut untuk mengambil data UNVR sebagai contoh.

import yfinance as yf
from IPython.core.display import display
unvr = yf.Ticker('UNVR.JK')
unvr.info

Terlihat banyak sekali informasi yang bisa diambil, mulai dari sektor, alamat, pertumbuhan pendapatan dan lain-lain. Untuk mengakses datanya:

print(unvr.info["sector"])
print(unvr.info["dividendYield"])
print(unvr.info["priceToBook"])

Selain itu tersedia data-data lain seperti cash flow, balance sheet, earning. Berikut contoh untuk informasi cashflow.

#dataframe
display(unvr.cashflow)  
#lihat indeksnya
display(unvr.cashflow.index) 
#akses salah satu atribut
display(unvr.cashflow.loc["Total Cash From Operating Activities"]) 

Dokumentasi lengkap bisa dilihat di: https://github.com/ranaroussi/yfinance

Berikutnya adalah data market, idealnya memang kita bisa mendapat data rincian order beli dan jual sehingga semua proses di bursa bisa ditangkap, tapi data ini mahal. Untuk mendapatkan data OHLCV (Open, High, Low, Close, Volume) dapat digunakan yfinance sbb:

unvr_market = yf.download('UNVR.JK','2021-01-01','2021-11-7', auto_adjust=True)
unvr_market

Selanjutnya kita bisa menggunakan TA-LIB untuk mendapatkan berbagai analisis teknikal. Install TA-LIB terlebih dulu (ini untuk Linux, untuk OS lain bisa dilihat caranya di: https://github.com/mrjbq7/ta-lib)

download http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
$ tar -xzf ta-lib-0.4.0-src.tar.gz
$ cd ta-lib/
$ ./configure --prefix=/usr
$ make
$ sudo make install

Install library talib di Python dan untuk melihat jenis TA yang disediakan:

import talib
talib.get_function_groups()

Sekarang kita coba proses unvr_market yang sudah diambil sebelumnya. Saya coba beberapa indikator.

unvr_market["EMA_20"] = talib.EMA(unvr_market.Close,timeperiod=20)
unvr_market["RSI"] = talib.RSI(unvr_market.Close,timeperiod=20)
unvr_market["MACD"],unvr_market["MACD_SIG"],unvr_market["MACD_HIST"] = talib.MACD(unvr_market.Close)

Sekarang plot dengan matplotlib

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(15,14)})
sns.set_theme(style="whitegrid")
sns.set_color_codes("pastel")
fig, axes = plt.subplots(nrows=3,sharex=True,
gridspec_kw=dict(left=0.1, right=0.9,
bottom=0.1, top=0.95))
fig.suptitle('UNVR', fontsize=20)
unvr_market['Close'].plot(ax=axes[0])
unvr_market['EMA_20'].plot(ax=axes[0])
unvr_market['RSI'].plot(ax=axes[1])
axes[1].axhline(y=70, color='r')
axes[1].axhline(y=30, color='g')
unvr_market['MACD'].plot(ax=axes[2])
unvr_market['MACD_SIG'].plot(ax=axes[2])
for ax in axes:
ax.legend()

Catatan: library finplot dapat digunakan untuk chart terkait finance.

Hasilnya sebagai berikut:

Banyak hal lain yang bisa dieksplorasi dan nanti tentunya menggunakan machine learning 🙂