Membangun RESTful API untuk Klasifikasi Berita Menggunakan Flask dan Scikit-Learn

Muhammad Arslan 21 Desember 2016

Membangun RESTful API untuk Klasifikasi Berita Menggunakan Flask dan Scikit-Learn

Bila di tutorial "Prediksi Bunga Iris" kita hanya menggunakan dataset yang terstruktur dan memiliki fitur yang jelas, sekarang kita akan mencoba pada data yang tak terstruktur. Data tersebut adalah sebuah teks yang akan diolah menjadi vektor dan memiliki fitur - fitur unik yang dapat digunakan untuk menebak teks baru yang akan diklasifikasikan. Dengan memanfaatkan teknik tersebut, kita dapat menentukan sebuah berita yang mengalir di sosial media atau diterbitkan sebuah portal untuk dikategorikan oleh mesin pengklasifikasi berdasarkan dataset yang telah diberikan kategori oleh sistem kita.

Sebagai bahan belajar, disini kita akan memanfaatkan dataset berita yang sudah dikurasi oleh tim Scikit-Learn melalui 20 News Group Dataset. Data teks tersebut terdiri dari berita yang dikoleksi dari 20 news group sebagai berikut:

['alt.atheism',
 'comp.graphics',
 'comp.os.ms-windows.misc',
 'comp.sys.ibm.pc.hardware',
 'comp.sys.mac.hardware',
 'comp.windows.x',
 'misc.forsale',
 'rec.autos',
 'rec.motorcycles',
 'rec.sport.baseball',
 'rec.sport.hockey',
 'sci.crypt',
 'sci.electronics',
 'sci.med',
 'sci.space',
 'soc.religion.christian',
 'talk.politics.guns',
 'talk.politics.mideast',
 'talk.politics.misc',
 'talk.religion.misc']

Kategori berita yang tersedia memang masih kurang, namun dapat kita manfaatkan untuk mulai mempelajari klasifikasi teks berbahasa inggris dengan menggunakan Scikit-Learn dan Flask.

1. Persiapan Alat dan Bahan

Disini kita akan mempelajari klasifikasi teks berupa berita berbahasa inggris yang akan ditebak menggunakan 20 News Group Dataset yang disediakan oleh Scikit-Learn. Pertama pastikan kamus udah memasang beberapa package Python berikut:
  • Scikit-Learn
  • Scipy
  • Numpy
  • Flask
Dan untuk mendapatkan dataset, kita hanya perlu melakukan pengambilan 20 News Group Dataset dengan menggunakan method fetch20_newsgroup() yang tersedia di modul sklearn.datasets:
In [1]: from sklearn import datasets

In [2]: x = datasets.fetch_20newsgroups()

In [3]: x.target_names Out[3]: ['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']

In [4]:

Bila sudah diambil, maka data berita akan disimpan di dalam folder ~/scikit_learn_data atau /home/nama_user/scikit_learn_data bila kamu menggunakan Linux untuk tutorial ini. Selanjutnya kita akan menggunakan Postman untuk menguji service dari RESTful API yang akan kita bangun. Dan kita akan melihat bagaimana melihat suatu performa model untuk proses klasifikasi dan melakukan klasifikasi terhadpa suatu teks yang belum diklasifikasi.

2. Membangun pondasi RESTful API dengan Flask

Karena kita akan membangun sebuah service yang berbasis RESTful API, maka kita akan membuat sebuah struktur sederhana menggunakan Flask yang dapat memberikan response berupa JSON terlebih dahulu. Kita akan membuat sebuah halaman indeks yang memperlihatkan pesan "Selamat Datang". Buat file dengan nama news-app.py. Kemudian buat kode berikut di dalam file tersebut:
from flask import Flask, Response, request

import json import datetime

app = Flask(name, static_url_path = "/static", static_folder = "static")

@app.route('/') def index(): msg = { "message": "Selamat Datang ke Klasifikator Berita ;()" }

resp = Response(response=json.dumps(msg),
                status=200, \
                mimetype="application/json")

return resp

if name=="main": app.run(debug=True)

Pada kode diatas, kita gunakan module Flask untuk membuat objek aplikasi Flask, menggunakan Response untuk mengemas response JSON yang akan kita buat, dan menggunakan request untuk menerima data yang dikirim oleh client. Kemudian kita import juga modul json dan datetime yang akan digunakan di bagian selanjutnya. Tak lupa kita atur URL untuk static file karena akan digunakan untuk menampilkan file yang dihasilkan setelah melakukan pengukuran model klasifikasi.

Kita kaitkan function index() dengan decorator app.route('/') untuk dapat diakses bila client mengakses root URL. Selanjutnya kita kemas response dengan menggunakan Response dan bila kita akses maka akan kita lihat hasilnya seperti pada gambar berikut:

Selection_002

Untuk mengujinya silahkan eksekusi file tersebut dengan perintah berikut:

$ python news-app.py

3. Memasang pengklasifikasi teks berita di dalam Flask

Berikutnya akan kita pasang terlebih dahulu pengklasifikasi yang menggunakan algoritma SGDClassifier. Disini kita ambil dataset 20 News Group. Kemudian kita gunakan beberapa komponen seperti CountVectorizer, TfidfTransformer, dan SGDClassfier yang dibungkus menggunakan Pipeline. Sehingga kita dapat dengan mudah hanya melewatkan data tanpa harus terlibat langsung di setiap fase pengklasfikasian.

SGDClassifier yang kita gunakan menggunakan parameter loss='hinge', alpha=1e-3, n_iter=5, dan random_state=42. Sedangkan untuk dataset latih, kita gunakan pengacak dan random_state=42. Setelah siap, kita latih dataset untuk pelatihan yang akan digunakan di bagian selanjutnya.

SGDClassifier merupakan singkatan dari stochastic gradient descent classifier. Parameter alpha adalah konstanta yang digunakan untuk pengali regularization term juga digunakan untuk perhitungan learning_rate. Parameter loss adalah penentu akan menggunakan metode apa, bila hinge maka akan menggunakan linear SVM. n_iter adalah banyaknya epoch yang dilakukan. Sedangkan random_state adalah pengacak untuk mengacak data.

from flask import Flask, Response, request
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline

import json
import datetime

app = Flask(__name__, static_url_path = "/static", static_folder = "static")

# menyiapkan dataset
dataset_train = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)

# mengatur classifier dengan menggunakan Pipeline
text_classifier = Pipeline([
        ('vect', CountVectorizer()),
        ('tfidf', TfidfTransformer()),
        ('clf', SGDClassifier(loss='hinge', alpha=1e-3, n_iter=5, random_state=42))
    ])

text_classifier = text_classifier.fit(dataset_train.data, dataset_train.target)

@app.route('/')
def index():
    msg = {
        "message": "Selamat Datang ke Klasifikator Berita ;()"
    }

    resp = Response(response=json.dumps(msg),
                    status=200, \
                    mimetype="application/json")

    return resp

if __name__=="__main__":
    app.run(debug=True)

4. Melakukan pengujian dan pengukuran sederhana untuk model klasifikasi

Pada bagian sebelumnya kita hanya baru menyiapkan pengklasifikasinya saja. Sekarang kita akan membuat sebuah endpoint "/info" yang memberitahukan seoptimal apa model pengklasifikasi yang kita siapkan. Kita membutuhkan modul metrics yang dimiliki oleh Scikit-Learn. Sebelum dilanjut, buatlah sebuah folder dengan nama static yang ditempatkan bersama file news-app.py.

Silahkan tambahkan sesuaikan kode sebelumnya dengan kode berikut:

from flask import Flask, Response, request
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
from sklearn import metrics

import json
import datetime

app = Flask(__name__, static_url_path = "/static", static_folder = "static")

# menyiapkan dataset
dataset_train = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)

# mengatur classifier dengan menggunakan Pipeline
text_classifier = Pipeline([
        ('vect', CountVectorizer()),
        ('tfidf', TfidfTransformer()),
        ('clf', SGDClassifier(loss='hinge', alpha=1e-3, n_iter=5, random_state=42))
    ])

text_classifier = text_classifier.fit(dataset_train.data, dataset_train.target)


.............................................................


@app.route('/info')
def info():
    # memilih dataset test
    dataset_test = fetch_20newsgroups(subset='test', shuffle=True, random_state=42)
    docs_test = dataset_test.data

    # menguji dataset test
    predicted = text_classifier.predict(docs_test)

    # mengukur hasil test
    accuracy_ratio = metrics.accuracy_score(predicted, dataset_test.target)
    report = metrics.classification_report(dataset_test.target, predicted, target_names=dataset_test.target_names)
    confusion_matrix = metrics.confusion_matrix(dataset_test.target, predicted)

    # menyimpan ke dalam file untuk report
    f = open('static/classification_report.txt', 'w')
    for line in report:
        f.write(line)
    f.close()

    f = open('static/confusion_matrix.txt', 'w')
    for line in confusion_matrix.tolist():
        f.write("\t".join([str(x) for x in line]))
        f.write("\n")
    f.close()

    # menghasilkan response hasil pengukuran
    msg = {
        "data":{
            "accuracy": accuracy_ratio,
            "classification_report":"http://localhost:5000/static/classification_report.txt",
            "confusion_matrix":"http://localhost:5000/static/confusion_matrix.txt",
        } 
    }

    resp = Response(response=json.dumps(msg),
                    status=200, \
                    mimetype="application/json")

    return resp


if __name__=="__main__":
    app.run(debug=True)

Pada bagian function info(), kita siapkan dataset pengujian yang diambil juga dari dataset latih. Kemudian kita prediksi hasilnya dengan pengklasifikasi yang telah dibangun. Lalu kita ukur akurasinya dengan membandingkan antara hasil prediksi dengan target yang telah ditentukan. Kita juga buat laporan klasifikasi dan laporan confusion matrix yang di tulis ke dalam file yang disimpan di folder static.

Lalu kita berikan hasil pengukuran model kita dengan memberikan URL hasil laporan yang telah dibuat kedalam file .txt. Berikut adalah contoh hasil eksekusinya:

Selection_003 Selection_004 Selection_005

5. Membuat service untuk mengklasifikasi berita

Terakhir kita tambahkan endpoint "/predict" yang akan menerima sebuah array teks yang akan diprediksi oleh mesin pengklasifikasi. Setiap teksnya akan diklasifikasi lalu dikemas ulang ke dalam list dan di-dump menjadi response JSON yang memperlihatkan teks tersebut berada di kategori mana. Silahkan tambahkan endpoint "/predict" ke dalam kode sebelumnya:
from flask import Flask, Response, request
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
from sklearn import metrics

import json import datetime

app = Flask(name, static_url_path = "/static", static_folder = "static")

menyiapkan dataset

dataset_train = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)

mengatur classifier dengan menggunakan Pipeline

text_classifier = Pipeline([ ('vect', CountVectorizer()), ('tfidf', TfidfTransformer()), ('clf', SGDClassifier(loss='hinge', alpha=1e-3, n_iter=5, random_state=42)) ])

text_classifier = text_classifier.fit(dataset_train.data, dataset_train.target)

.............................................................

@app.route('/predict', methods=["POST"]) def predict(): # mengambil post data berupa json req_body = request.get_json(silent=True)

# menguji teks dari post data
test_data = req_body['data']
predicted = text_classifier.predict(test_data)

# mengemas response
result = []
for doc, category in zip(test_data, predicted):
    result.append( '%r => %s' % (doc, dataset_train.target_names[category]) )

# mengirim response
msg = { 
    "data": result
}

resp = Response(response=json.dumps(msg),
                status=200, \
                mimetype="application/json")

return resp

if name=="main": app.run(debug=True)

Berikut adalah contoh akses terhadap endpoint "/predict" dengan sejumlah teks yang akan diprediksi:

Selection_007

6. Kode lengkap

Untuk menghindari kesalahpahaman pada penulisan kode, berikut adalah kode full dari tutorial ini:
from flask import Flask, Response, request
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
from sklearn import metrics

import json import datetime

app = Flask(name, static_url_path = "/static", static_folder = "static")

menyiapkan dataset

dataset_train = fetch_20newsgroups(subset='train', shuffle=True, random_state=42)

mengatur classifier dengan menggunakan Pipeline

text_classifier = Pipeline([ ('vect', CountVectorizer()), ('tfidf', TfidfTransformer()), ('clf', SGDClassifier(loss='hinge', alpha=1e-3, n_iter=5, random_state=42)) ])

text_classifier = text_classifier.fit(dataset_train.data, dataset_train.target)

@app.route('/') def index(): msg = { "message": "Selamat Datang ke Klasifikator Berita ;()" }

resp = Response(response=json.dumps(msg),
                status=200, \
                mimetype="application/json")

return resp

@app.route('/info') def info(): # memilih dataset test dataset_test = fetch_20newsgroups(subset='test', shuffle=True, random_state=42) docs_test = dataset_test.data

# menguji dataset test
predicted = text_classifier.predict(docs_test)

# mengukur hasil test
accuracy_ratio = metrics.accuracy_score(predicted, dataset_test.target)
report = metrics.classification_report(dataset_test.target, predicted, target_names=dataset_test.target_names)
confusion_matrix = metrics.confusion_matrix(dataset_test.target, predicted)

# menyimpan ke dalam file untuk report
f = open('static/classification_report.txt', 'w')
for line in report:
    f.write(line)
f.close()

f = open('static/confusion_matrix.txt', 'w')
for line in confusion_matrix.tolist():
    f.write("\t".join([str(x) for x in line]))
    f.write("\n")
f.close()

# menghasilkan response hasil pengukuran
msg = {
    "data":{
        "accuracy": accuracy_ratio,
        "classification_report":"http://localhost:5000/static/classification_report.txt",
        "confusion_matrix":"http://localhost:5000/static/confusion_matrix.txt",
    } 
}

resp = Response(response=json.dumps(msg),
                status=200, \
                mimetype="application/json")

return resp

@app.route('/predict', methods=["POST"]) def predict(): # mengambil post data berupa json req_body = request.get_json(silent=True)

# menguji teks dari post data
test_data = req_body['data']
predicted = text_classifier.predict(test_data)

# mengemas response
result = []
for doc, category in zip(test_data, predicted):
    result.append( '%r => %s' % (doc, dataset_train.target_names[category]) )

# mengirim response
msg = { 
    "data": result
}

resp = Response(response=json.dumps(msg),
                status=200, \
                mimetype="application/json")

return resp

if name=="main": app.run(debug=True)

7. Penutup

Menarik bukan? sistem prediksi kategori teks berita berbahasa inggris sederhana ini dapat kamu gunakan untuk memprediksi tweet yang ditangkap melalui stream, atau mengklasifikasi berita bahasa inggris yang ada di portal online. Namun sayangnya kategori yang dimiliki masih terbatas hanya 20 saja dan itupun dapat menghasilkan prediksi yang salah dimana, berita yang seharusnya masuk ke dalam kategori sepakbola, malah masuk ke dalam kategori hockey atau baseball.

Untuk menghasilkan prediksi yang bagus, ada berbagai banyak hal yang dilakukan diantaranya tuning parameter, dimensionality reduction, dan tentu saja menambah kategori baru yang tidak ada di dalam dataset latih. Kamu harus memodifikasi sendiri atau menyiapkan dataset latih yang dipersiapkan untuk menyelesaikan masalah yang sesuai dengan kebutuhan aplikasi yang kamu kembangkan.

Untuk lebih lanjut, silahkan pelajari lebih dalam Scikit-Learn melalui dokumentasi resminya ataupun melalui tulisan dan slide yang bisa kamu cari melalui Google.

8. Referensi

  • https://flask.pocoo.org
  • http://scikit-learn.org/stable/documentation.html
  • http://www.scipy-lectures.org/
(arslan/scikit-learn)