Dataset Klasifikasi Bahasa Indonesia (SMS Spam) & Klasifikasi Teks dengan Scikit-Learn

Setelah saya cari-cari, sepertinya  belum ada dataset klasifikasi Bahasa Indonesia yang bisa didownload dengan gampang dan berlisensi bebas (mirip seperti 20NewsGroup untuk Bahasa Inggris). Aneh juga kan kalau untuk kuliah atau pelatihan NLP Bahasa Indonesia malah menggunakan dataset Bahasa Inggris. Oleh karena itu berdasarkan dataset yang dibuat mahasiswa saya (dan dengan ijin dia), saya publish dataset untuk domain SMS spam dengan lisensi creative commons. Ada tiga kelas: SMS  normal, SMS penipuan, SMS promosi. Dua yang terakhir ini dapat dianggap spam. Jumlah instances ada 1143. Download di: nlp.yuliadi.pro/dataset 

Sekalian saya buat tutorial singkat untuk membuat classifier berdasarkan dataset tersebut dengan lib scikit-learn. Caranya: Install lib scikit-learn, download dataset, sesuaikan namaFile dengan lokasi data. (Catatan: ada spasi di nama file, nanti saya perbaiki). Akurasinya 0.90 dengan MultinomialNB seperti code di bawah dan 0.92 jika menggunakan linear SVM.


#%%
#load data
from collections import Counter
import csv
namaFile = "/home/yudiwbs/dataset_sms_spam _v1.zip"
data = []
label = []
with open(namaFile, 'r', encoding='utf-8') as csvfile:
    reader = csv.reader(csvfile, delimiter=',', quotechar='"')
    next(reader) #skip header
    for row in reader:
        data.append(row[0])
        label.append(row[1])

print("jumlah data:{}".format(len(data)))
print(Counter(label))

#%%
#random urutan dan split ke data training dan test
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split( data, label, test_size=0.2, random_state=123)

print("Data training:")
print(len(X_train))
print(Counter(y_train))

print("Data testing:")
print(len(X_test))
print(Counter(y_test))

#%%
#transform ke tfidf dan train dengan naive bayes
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
text_clf = Pipeline([('vect', CountVectorizer()),
                     ('tfidf', TfidfTransformer()),
                      ('clf', MultinomialNB())])
text_clf.fit(X_train, y_train)
#%%
# coba prediksi data baru
sms_baru = ['Anda mendapatkan hadiah mobil','nanti ketemu dimana?']
pred = text_clf.predict(sms_baru)
print("Hasil prediksi {}".format(pred))
#%%
#hitung akurasi data test
import numpy as np
pred = text_clf.predict(X_test)
akurasi = np.mean(pred==y_test)
print("Akurasi: {}".format(akurasi))

Iklan

Mempercepat Tensorflow tanpa GPU

Kebanyakan orang sudah tahu bahwa GPU akan mempercepat proses pembuatan model dengan Tensorflow, masalahnya tidak semua mempunyai akses komputer dengan GPU. Laptop saya misalnya,  tidak memiliki GPU yang dapat digunakan oleh Tensorflow.

Saat menjalankan Tensorflow  mungkin ada yang terkena  warning seperti ini “The TensorFlow library wasn’t compiled to use SSE4.1 instructionn … SSE4.2 instructions .. AVX instructions “.  Itu adalah fitur-fitur CPU yang dapat mempercepat proses komputasi.  Default installer binary Tensorflow tidak menggunakan ini agar dapat men-support  CPU lama.

Solusi untuk meng-enable fitur ini adalah dengan mengcompile sendiri source code TensorFlow.  Saya pernah bahas tentang ini di https://yudiwbs.wordpress.com/2017/02/26/mencoba-tensorflow/    Tapi compile perlu waktu lama dan ribet.  Saya sekarang biasanya langsung install saja, praktis,  asal ada koneksi internet.

Saat ini ada alternatif lain, saya menemukan repo yang sudah meng-compile-kan Tensorflow dengan opsi AVX, FMA, SSE. https://github.com/lakshayg/tensorflow-build  Jadi tidak perlu compile sendiri dari source code 🙂

Saya menggunakan Pycharm, sayangnya tidak ada opsi install whl langsung dari IDE-nya, jadi perlu via terminal. Langkah-langkahnya (dengan virt-env) sbb:

Masuk ke directory project atau di mana virtenv berada dan aktifkan.

 source venv/bin/activate 

Pastikan (venv) muncul, soalnya saat percobaan pertama saya malah install di global. Cek versi python di virt-env ini, karena bahkan versi 3.5 dan 3.6 tidak compatible.  Cek juga versi gcc.

python --version
gcc -v

Download binary yang cocok di https://github.com/lakshayg/tensorflow-build, pastikan versinya sudah benar (ubuntu, python, gcc). Lalu install dengan pip biasa

pip install --ignore-installed --upgrade [lokasi .whl yang didownload]

Kembali ke Pycharm dan otomatis akan diindeks.

Penasaran tentang berapa peningkatan kecepatannya, saya coba dengan Keras untuk klasifikasi teks dataset review IMDB. Hasilnya ada peningkatan  27%  untuk  proses training (26 detik vs 36 detik).  Sangat lumayan  🙂

Web App (frontend) dengan Python

Saya membantu penelitian dosen prodi lain dan karena programmernya menghilang, saya diminta untuk jadi programmer pengganti. Aplikasi yang dibuat adalah aplikasi web tetapi banyak menggunakan javascript untuk user interfacenya.

Posting ini untuk catatan pribadi  sebagai template pengembangan web app (karena saya gampang lupa). Setiap orang punya preferensi masing-masing dan perlu disesuaikan dengan kondisi yang ada. Saya pilih Python+Flask karena hanya saya programmernya dan bisa memilih tools dengan bebas 🙂  Sempat lihat-lihat framework  seperti react, vue, angular tapi saya sendiri tidak terlalu suka dengan bahasa Javascript. Saya tetap gunakan Javascript, tetapi hanya untuk user interface saja.  Ada juga framework seperti Django, tetapi menurut saya terlalu berlebihan untuk proyek kecil yang  dikerjakan satu orang.   Sebelumnya saya sudah membahas tentang Flask, tapi dengan docker bisa dipermudah.

Saya menggunakan PyCharm Profesional karena Pycharm Community tidak dapat mengedit css dan javascript (dianggap sebagai teks biasa). Untungnya ada versi pro gratis untuk dosen yang punya email .edu.

Pertama yang perlu dilakukan adalah install Flask.  Siapkan struktur direktori: /app, /app/static/img, app/static/node_modules, /app/templates. Buat program utama di: /app/main.py

Template dasarnya seperti ini:

import requests
from flask import Flask, session
from flask import render_template
from flask import request

app = Flask(__name__)
app.secret_key = "x"

@app.route("/", methods=['GET', 'POST'])
def beranda():
    return render_template('index.html')

if __name__ == "__main__":
    # Only for debugging while developing
    # operasional
    app.run(host='0.0.0.0', debug=False, port=80)

    # debug
    #app.run(host='0.0.0.0', debug=True, port=8080)

Untuk mengelola lib javascript, gunakan NPM.  Masuk ke directory app/static, gunakan npm init  lalu npm install lib yang diperlukan. Tadinya saya kira NPM itu seperti pip-nya Python, defaultnya global, ternyata lokal.

Di html, untuk memanggil javascript gunakan url_for seperti ini (ganti img dengan script, karena WordPress ternyata memblok tag script):

//ganti dengan img dengan script karena wordpress tdk mengijinkan
<img src="{{ url_for('static', filename='node_modules/file-js-nya')}}" /> 

Demikian juga dengan image dan css :

<img src="{{ url_for('static', filename='img/gambar.png')}}" />

Untuk deploy, buat Dockerfile yang sejajar dengan /app (bukan di dalam).

FROM tiangolo/uwsgi-nginx-flask:python3.6
RUN pip install requests
COPY ./app /app
EXPOSE 80

Coba dulu di lokal (jangan lupa titik saat docker build):

docker build -t nama-app .
docker run -p 80:80 nama-app

Untuk stop bisa dengan ctrl-c, atau:

docker ps
docker stop container [id-container]

Untuk deploy, push ke gitlab (di Pycharm gunakan plugin gitlab project, posting saya tentang ini).

Di server, jika pertama kali, gunakan git clone  (siapkan sebelumnya ssh key untuk gitlab). Selanjutnya untuk update gunakan git pull. Lakukan docker build dan docker run.

Untuk koneksi ke database, saya belum memutuskan apakah digabung saja dengan frontend ini, atau dipisahkan jadi web service. Tapi mungkin akan saya pisahkan untuk jaga-jaga jika mau dibuat versi mobile app-nya, sekalian lebih mendalami model web API.

 

Chrome Remote Desktop di Ubuntu 16

Ini berawal dari account TeamViewer saya yang diblokir karena dianggap untuk komersial. Sebenarnya untuk riset, tapi memang saya gunakan untuk mengendalikan sampai 4 PC, jadi ya wajar juga jika dicurigai 🙂

Beberapa alternatif opensource sulit untuk digunakan karena PC harus bisa diakses langsung atau harus memodifikasi router, padahal PC di kampus ditutup akses portnya.  Saya lihat Chrome Remote Dekstop (CRD) jadi alternatif yang menarik. Untuk me-remote Windows dengan CRD  lancar.  Masalah muncul saat saya gunakan pada Ubuntu 16. Layar memang muncul tapi hanya wallpaper saja.  Terminal masih bisa diakses melalui menu klik kanan. Tapi semua window tidak bisa digeser dan diresize! Ternyata berbeda dengan TeamViewer yang prinsipnya screen mirroring, di Ubuntu, CRD menggunakan virtual session. Dari yang saya baca, ada bug untuk session Unity yang diakses secara remote karena masalah 3D accelerator(?).

Googling, dapat solusi dari https://productforums.google.com/forum/#!msg/chrome/LJgIh-IJ9Lk/tj-SBgEd85wJ   Prinsipnya CRD dimodifikasi agar tidak membuat session baru, tetapi menggunakan session yang sudah ada. Solusi ini berhasil, dan saat dijalankan, maka layar host jadi gelap saat PC diakses remote.   Masalahnya, saat koneksi terputus, maka layar host akan tetap gelap dan reconnect pun gagal 😦  Googling tapi tidak menemukan solusi lain. Aneh ya, itu sebabnya saya buat posting blog ini.

Setelah saya lihat-lihat code di /opt/google/chrome-remote-desktop/chrome-remote-desktop (jangan lupa backup), di Ubuntu, desktop display manager yang digunakan adalah  /usr/sbin/lightdm-session. Secara default Ubuntu 16 menjalankan gnome-session –session=ubuntu dan ini yang menjadi sumber masalah. Sebenarnya ada code yang akan memilih gnome-session –session=ubuntu-2d jika tersedia di OS untuk menghindari bug akibat 3D accelerator. Masalahnya di Ubuntu 16, sudah tidak ada lagi gnome ubuntu-2d (hanya ada di Ubuntu 12). Saat saya coba install, sudah tidak ada dan tidak disupport.  Solusinya saya install gnome-session-flashback:

sudo apt-get update
sudo apt-get install gnome-session-flashback

Stop dulu service chrome desktop:

/opt/google/chrome-remote-desktop/chrome-remote-desktop --stop

Lalu  sudo edit (jangan lupa  backup): /opt/google/chrome-remote-desktop/chrome-remote-desktop . Saya akan update bagian yang tadinya menggunakan ubuntu-2D. Edit  bagian ini  (cari dengan kata kunci “2d”):

  SESSION_WRAPPERS = [
    "/usr/sbin/lightdm-session",
    "/etc/gdm/Xsession",
    "/etc/X11/Xsession" ]
  for session_wrapper in SESSION_WRAPPERS:
    if os.path.exists(session_wrapper):
      #yw edit
      #if os.path.exists("/usr/bin/unity-2d-panel"):
        # On Ubuntu 12.04, the default session relies on 3D-accelerated
        # hardware. Trying to run this with a virtual X display produces
        # weird results on some systems (for example, upside-down and
        # corrupt displays).  So if the ubuntu-2d session is available,
        # choose it explicitly.
        return [session_wrapper, "/usr/lib/gnome-flashback/gnome-flashback-metacity"]
      #else:
        # Use the session wrapper by itself, and let the system choose a
        # session.
        #return [session_wrapper]
  return None

Intinya code akan menjalankan /usr/lib/gnome-flashback/gnome-flashback-metacity bukan gnome-session –session=ubuntu . Gunakan gnome metacity, bukan compiz karena metacity tidak menggunakan 3D accelerator.

Lalu dibagian lain edit juga:

x_session = choose_x_session()
#  yw ganti
if (len(x_session) == 2 and
   x_session[1] == "/usr/lib/gnome-flashback/gnome-flashback-metacity"):
       extra_x_args.extend(["-extension", "Composite"])

Katanya ini untuk mengatasi bug di Ubuntu 2D metacity, jadi saya juga tambahkan untuk gnome-flashback-metacity ini (entah valid atau tidak hehe).

restart ulang:

 /opt/google/chrome-remote-desktop/chrome-remote-desktop --start

Coba jalankan secara remote dan semua bisa berjalan dengan baik, walaupun user interfacenya agak berbeda dengan Ubuntu standard 🙂 CRD bahkan lebih cepat daripada TeamViewer.

Catatan: jangan lupa catat pin, Google Remote Desktop tetap membutuhkan pin walaupun kita sudah login.

Analis Sentimen Berbasis Aspek

Update:
nlp.yuliadi.pro/sentimen sudah ditambahkan aspect detection dan ekspresi opini (selain polaritas). Kinerja juga sudah membaik (F1 polaritas 0.52) walaupun masih dibawah harapan.   F1 ekpresi 0.61, F1 aspek 0.34.  Untuk aspek, di situs saya gabungkan antara makanan dan minuman.  Jumlah dataset yang diperlukan sepertinya harus jauh lebih banyak daripada NER.

Sebelumnya saya sudah menulis tentang analisis sentimen sekitar 7 tahun yang lalu (https://yudiwbs.wordpress.com/2011/12/26/analisis-twee-analisis-opini-sentimen/). Sekarang saya tertarik lagi dengan bidang ini karena sering melakukan review lewat Google Map dan ternyata  aspect based sentiment analysis (ABSA) masih menjadi task sampai  SemEval 2015.  Lagipula task ini bisa dilihat sebagai kasus sequence labeling yang sekarang saya sedang saya coba-coba.

Selain Google Map yang mulai serius menggarap review,  situs seperti Tokopedia, BukaLapak, Agoda, AiryRoom, Gojek  dsb juga memproses data review dalam jumlah besar.  Aspect Based Sentiment Analysis harusnya akan bermanfaat, karena satu review dapat diproses <1 detik dan jika diparalelkan, ratusan bahkan ribuan review dapat diproses dalam beberapa detik saja.  Perusahaan bisa mendapatkan insight dengan cepat.

Jika  task pada Semeval 2015 jadi patokan,  maka ada tiga subtask di ABSA. Pertama menemukan polaritas, kedua menentukan aspek dan ketiga menentukan ekspresi opini.  Polaritas terdiri atas netral, positif dan negatif. Aspek terdiri atas kombinasi entitas:atribut.   Untuk domain restoran ada enam entitas: Restaurant, Food, Drink, Ambience, Location dan Service sedangkan  atributnya: Price, Quality,  Style, General dan Misc. Kombinasi entitas:atribut yang mungkin misalnya: Food:Price, Food:Style (porsi, penyajian), Food:Quality dst.  Tentu ada kombinasi Entitas:Atribut yang tidak bisa digunakan seperti Location:Quality (Location dan Service hanya bisa dipasangkan dengan atribut General).   Terakhir ekspresi opini berisi kata atau frasa yang terkait entitas.

Sebagai contoh,  berikut anotasi  untuk kalimat: ” Tempatnya bagus banget terlebih ada view kota bandungnya. Cuma sayang banget kemaren pesen makanan di restauran cave nya lama banget datengnya ampe setengah jam baru dateng, rasanya lumayan, penyajiannya lumayan. ”

Polaritas

  • Positif: Tempatnya bagus banget terlebih ada view kota bandungnya
  • Negatif: Cuma sayang banget kemaren pesen makanan di restauran cave nya lama banget datengnya ampe setengah jam baru dateng.
  • Netral: rasanya lumayan, penyajiannya lumayan

Aspek

  • Ambience:General : Tempatnya bagus banget terlebih ada view kota bandungnya
  • Service:General: Cuma sayang banget kemaren pesen makanan di restauran cave nya lama banget datengnya ampe setengah jam baru dateng.
  • Food: Quality:  rasanya lumayan,
  • Food: Style: penyajiannya lumayan

Ekspresi:

  • tempatnya” : Tempatnya bagus banget terlebih ada view kota bandungnya
  • pesen makanan“: Cuma sayang banget kemaren pesen makanan di restauran cave nya lama banget datengnya ampe setengah jam baru dateng.

Ada beberapa kasus yang lain yang mengandung kata positif, tetapi secara kesuluruhan sebenarnya kalimat negatif, sebagai contoh:

  • Biasanya nasinya masih panas dan empuk.
  • mestinya kualitas bisa lbh baik krn bnyak resoran serupa di bandung skr sdh menjamur.
  • saya lebih suka sup iga bakar  dari restoran lain di Bandung

Kasus-kasus lain yang sulit:

  • Sarkasme: “Dan saat disodorkan buku menu , saya kembali terpukau . Menu makanannya sedikit dan harganya sangat fantastis !”
  • Opini orang lain: “Teman yang tinggal di Bandung juga kebetulan hobi sekali bersantai di sini”
  • Positif walaupun awalnya negatif:  “Ketika awal-awal baru dibuka sih saya kurang suka dengan rasanya . Tidak sesuai dengan di lidah. Tapi sepertinya manajemennyaterus memperbaiki diri sehingga dalam jangka waktu 1 tahun saja , makanannya sudah berubah menjadi enak.”

Berdasarkan data tripadvisor, saya mencoba ketiga task tersebut. Saat ini baru sampai polaritas, bisa dicek di: nlp.yuliadi.pro/sentimen   Datasetnya saya anotasi sendiri dan saat ini masih belum bisa di-share.

 

Eksplorasi Front-End

Setelah membuat model untuk task named entity, membuat model server, membuat web service maka selanjutnya adalah membuat web untuk mendemokan API yang dibuat.

Saya terakhir mengurusi front-end hampir 10 thn yang lalu untuk penelitian bersama pembimbing S2 saya. Waktu itu saya jadi belajar CSS dan Javascript. Saat lihat-lihat kondisi sekarang, wah sudah jauh berbeda. Ada berbagai macam framework: React, Angular, Vue.

Setelah saya eksplorasi, ternyata jauh berbeda dengan pendekatan lama.  Perlu belajar banyak. Masalahnya, framework juga bergeser dengan cepat. Bahkan untuk framework yang sama bisa mengalami perubahan besar (seperti Angular).  Kombinasi yang buruk terutama bagi saya yang punya waktu terbatas: perlu sumberdaya besar untuk belajar + gampang kadaluarsa ilmunya.

Akhirnya saya buat dengan cara lama saja, Python+Flask+Jinja2 +JQueryUI dan Bootstrap. Mungkin kalau benar-benar perlu serius, kami akan sewa orang lain saja 🙂

 

Dockerized Model Server

Posting saya sebelumnya tentang model server

Hal yang harus dilakukan berikutnya adalah deploy model server ini. Pengalaman saya sebelumnya,  deployment bisa jadi hal yang merepotkan karena harus install aplikasi, install library, setting parameter dan sebagainya.  Banyak app lama yang malas saya sentuh karena ini. Saya langsung tertarik setelah membaca Docker, karena akan sangat memudahkan bagi saya yang males ini hehe.

Rencananya, setiap task akan menjadi container yang terpisah. Jadi akan ada container untuk NER (named entity recognition), deteksi 5W1h (what, where, dsb),  paraphrase, similarity, aspect based sentiment analysis dsb. Lalu ada container untuk web service sebagai penghubung model server dengan dunia luar.  Saya menggunakan image dari https://github.com/tiangolo/uwsgi-nginx-flask-docker untuk webservice (flask, uwsgi dan nginx).

Saat saya coba menjalankan dua container (model server NER dan web service), sempat terbentur masalah. Pertama, di model server yang menggunakan socketserver,  tidak bisa menggunakan “localhost” tetapi 0.0.0.0 (masalah binding?).  Kedua,  container web service ternyata tidak dapat menghubungi model server (masalah komunikasi antar dua container). Solusinya saat container web service dijalankan, tambahkan parameter run –add-host=parent-host:`ip route show | grep docker0 | awk ‘{print \$9}’`

Solusi yang lebih elegan adalah menggunakan docker compose. Jadi di docker-compose.yml isinya seperti ini untuk mendefinisikan web service dan model server:

version: "3"
services:
  web:
    build: .
    ports:
      - "5000:80"
  ner5w1h:
    image: modelserverner5w1h
    ports:
      - "6200:6200"

Setelah itu di web service, nama host bisa langsung menggunakan nama container yang dituju.

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect(("ner5w1h", 6200))  # gunakan nama sesuai docker-compose.yml
        s.sendall(words.encode('utf-8'))
        data = s.recv(1024)
        s.close
    return data.decode('utf-8')

Catatan lain tentang docker yang saya temui:

  1. Untuk melihat isi image, gunakan “docker run -it namaimage sh”.  Ini gara-gara saya kira opsi “ADD model” akan otomatis membuat isi direktori /model (ternyata cuma copy dalamnya saja). Harusnya “ADD model /model”.
  2. Untuk melihat isi log gunakan  “docker logs -f namacontainer”. Sedangkan code untuk loggingnya adalah sebagai berikut (cmiiw):
def get_module_logger(mod_name):
    """
    penggunaan: get_module_logger(__name__).info("mulai...")
    """
    logger = logging.getLogger(mod_name)
    handler = logging.StreamHandler()
    formatter = logging.Formatter(
        '%(asctime)s [%(name)-12s] %(levelname)-8s %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)
    return logger

docker-compose cocok untuk multi docker pada satu host. Jika sudah melibatkan banyak host, katanya dianjurkan menggunakan kubernetes. Tapi untuk sekarang cukup dulu 🙂

Contoh hasilnya: (silakan ganti isi parameter kalimat)

http://nlp.yuliadi.pro:5000/ner?kalimat=Jokowi%20dan%20Jusuf%20Kalla%20pergi%20ke%20Jakarta

Langkah berikutnya adalah membuat frontend untuk demo API.