Deteksi Kesamaan (plagiarisme) Dokumen MS-Word dengan Python

Saya ditugasi prodi untuk mengecek kemiripan borang akreditasi prodi ilmu komputer dibandingkan dengan borang prodi pendidikan ilmu komputer yang sudah disubmit sebelumnya. Prodi ilmu komputer dan pendidikan ilmu komputer memang berbagi dosen, ruangan dan resource lainnya, jadi wajar kalau borangnya mirip. Tapi tentu jika dikti menggunakan software deteksi plagiarisme bisa saja nanti borang ditolak bahkan prodi kena penalti.

Saya sudah pernah membuat program yang mirip dengan Java, tetapi dengan library python, harusnya bisa dibuat lebih cepat, dan memang bisa dibuat hanya dalam beberapa jam saja ūüôā

Bagian yang terlama adalah mencari dan mencoba-coba library,  tetapi akhirnya saya menggunakan dua: python-docx dan strsim.     Python-docx untuk membaca file MS Word dan strmsim untuk kemiripan string-nya. Setelah dicoba, ternyata pemrosesan teks di dalam tabel harus ditangani terpisah. Teks yang di dalam texbox juga tidak bisa dibaca oleh python-docx. Untuk kasus ini, terpaksa semua teks yang berada di dalam textbox dikeluarkan secara manual.

Kodenya adalah sebagai berikut:

from docx import Document
from similarity.ngram import NGram

jum_huruf_min = 20 #kalimat terlalu pendek diabaikan

#load file dan pindahkan ke memory

def doc_to_text(nama_file):
    doc = Document(nama_file)
    fullText = []
    for para in doc.paragraphs:
        par = para.text.lower().strip()
        if par != "" and len(par) > jum_huruf_min:
            fullText.append(par)

    #proses isi tabel
    print("=======================")
    tables = doc.tables
    for table in tables:
        for row in table.rows:
            for cell in row.cells:
                for paragraph in cell.paragraphs:
                    par = paragraph.text.lower().strip()
                    if par != "" and len(par) > jum_huruf_min:
                        fullText.append(par)

    fullTextNoDup = list(set(fullText)) #buang duplikasi
    fullTextNoDup.sort()  # sort supaya lebih mudah dicek
    return fullTextNoDup 

jum_gram = 4
batas = 0.4 #threshold
gram = NGram(jum_gram)

dir = "C:\\yudiwbs\\dataset\\deteksi_plagiat\\"
file1 = 'borang_nondik_standard_4.docx'
file2 = 'borang_dik_standard_4.docx'
file_out = "kesamaan_standard4.txt"

list_par_doc1 = doc_to_text(dir+file1)
list_par_doc2 = doc_to_text(dir+file2)

file = open(dir+file_out, "w")

for par1 in list_par_doc1:
     for par2 in list_par_doc2:
         jarak = gram.distance(par1, par2)
         if ( jarak <=batas):
             print(par1)
             file.write(par1+"\n")
             print(par2)
             file.write(par2+"\n")
             print(jarak)
             file.write(str(jarak)+"\n")
             print("--")
             file.write("--\n")
             file.flush()

file.close()
print("Selesai===") 

Contoh hasilnya (semakin kecil nilainya maka semakin mirip):

--
sedangkan strategi pencapaian yang akan dilakukan oleh program studi ilmu komputer adalah:
strategi pencapaian yang dilakukan oleh program studi pendidikan ilmu komputer adalah:
0.30833333333333335
--
verifikasi dan validasi draft rumusan visi, misi, tujuan dan sasaran dengan cara melakukan rapat yang diikuti oleh tim penyusun internal, para perwakilan dosen dan pihak eksternal (alumni atau pengguna lulusan). dari hasil rapat tersebut terdapat beberapa saran dan masukan diantaranya:
validasi draft rumusan visi, misi, tujuan dan sasaran dengan cara melakukan rapat yang diikuti oleh tim internal, para dosen dan pihak eksternal. dari hasil rapat tersebut terdapat beberapa saran dan masukan diantaranya
0.24388111888111888
--

 

Iklan

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))

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 ūüôā