{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"Di bagian ini, kami akan mengimport library-library yang kami perlukan untuk melakukan image recognition. Pertama, cv2 digunakan untuk membaca gambar dan melakukan feature extraction melalui SIFT. numpy digunakan untuk memudahkan perhitungan yang perlu dilakukan. matplotlib diperlukan untuk menampilkan gambar-gambar dengan format yang teratur. os digunakan untuk keperluan mendaftarkan file dalam sebuah directory. scipy.cluster.vq diperlukan untuk perhitungan BOV. sklearn.preprocessing diperlukan untuk melakukan normalization data.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport cv2 as cv\nimport matplotlib.pyplot as plt\nfrom scipy.cluster.vq import *\nfrom scipy.stats import mode\nimport sklearn.preprocessing\nimport os\nimport random","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-01-25T06:32:39.862658Z","iopub.execute_input":"2023-01-25T06:32:39.863217Z","iopub.status.idle":"2023-01-25T06:32:41.505819Z","shell.execute_reply.started":"2023-01-25T06:32:39.863104Z","shell.execute_reply":"2023-01-25T06:32:41.504173Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Pertama, karena data dalam dataset Dogs vs. Cats terdapat dalam file zip untuk train dan test data, kami menggunakan !unzip untuk mengextract semua files yang terdapat dalam zip files tersebut.","metadata":{}},{"cell_type":"code","source":"!unzip /kaggle/input/dogs-vs-cats/train.zip\n!unzip /kaggle/input/dogs-vs-cats/test1.zip","metadata":{"_kg_hide-input":false,"_kg_hide-output":true,"scrolled":true,"execution":{"iopub.status.busy":"2023-01-25T06:32:41.508611Z","iopub.execute_input":"2023-01-25T06:32:41.509225Z","iopub.status.idle":"2023-01-25T06:33:02.712730Z","shell.execute_reply.started":"2023-01-25T06:32:41.509179Z","shell.execute_reply":"2023-01-25T06:33:02.711309Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Dijelaskan dalam penjelasan dataset ini bahwa cat memiliki class id 0 dan dog memiliki class id 1, sehingga kami membuat variable labels yang menampung informasi tersebut. Setelah itu, menggunakan os.listdir, kami mendapatkan daftar semua file name dari train dataset. Oleh karena Kaggle memiliki batas memory yang dapat digunakan, kami menggunakan random.sample untuk mengambil sample sebanyak 10000 data dari 25000 data yang terdapat dalam train folder (kami telah mencoba menggunakan semua data, di mana kami mendapatkan error kehabisan memory di Kaggle). Dari ini, kami menggunakan 10000 data random yang akan dijadikan train set.","metadata":{}},{"cell_type":"code","source":"labels = ['cat', 'dog']\ntrain_path = '/kaggle/working/train'\ntrain_paths = os.listdir(train_path)\ntrain_image_list = []\ntrain_image_class = []\ntrain_samples = random.sample(range(0, 25000), 10000)\nfor index in train_samples:\n  train_image_list.append(cv.imread(train_path + '/' + train_paths[index]))\n  if 'cat' in train_paths[index]:\n    train_image_class.append(0)\n  else:\n    train_image_class.append(1)","metadata":{"execution":{"iopub.status.busy":"2023-01-25T06:33:02.715020Z","iopub.execute_input":"2023-01-25T06:33:02.715508Z","iopub.status.idle":"2023-01-25T06:33:33.627262Z","shell.execute_reply.started":"2023-01-25T06:33:02.715456Z","shell.execute_reply":"2023-01-25T06:33:33.625739Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Berikut adalah 100 gambar pertama dalam train set yang telah dibuat menggunakan random.sample.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize = (20, 70))\nfor i in range(100):\n    plt.subplot(20, 5, i + 1)\n    plt.imshow(cv.cvtColor(train_image_list[i], cv.COLOR_BGR2RGB))\n    plt.title(labels[train_image_class[i]])\nplt.suptitle('First 100 Images in Train Dataset')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-25T06:33:33.630868Z","iopub.execute_input":"2023-01-25T06:33:33.631446Z","iopub.status.idle":"2023-01-25T06:33:48.508753Z","shell.execute_reply.started":"2023-01-25T06:33:33.631389Z","shell.execute_reply":"2023-01-25T06:33:48.504798Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Pertama, kami akan mencari descriptor-descriptor dari semua gambar dalam dataset. Hal ini dilakukan menggunakan cv.SIFT_create() untuk membuat SIFT object, yang dapat memanggil fungsi detectAndCompute untuk mencari keypoints dan descriptors dari sebuah gambar. Array train_data akan diloop untuk mencari descriptor dari semua gambar (keypoint disimpan dalam variabel _, karena tidak diperlukan). Descriptor ini lalu akan ditambahkan ke dalam array bernama descriptors, yaitu 2D array yang berisi semua descriptor dari semua gambar.\n\nDi tahap berikutnya, kami perlu melakukan clustering untuk descriptors ini menggunakan K Means. Algoritma K Means ini akan menerima sebuah 1D array, sehingga array descriptors ini harus diubah bentuknya. Hal ini dilakukan dengan cara \"flattening\" arraynya, dari bentuknya yaitu sekumpulan array descriptor, menjadi 1 array yang berisi semua descriptor. Hal ini dilakukan menggunakan fungsi np.vstack, yang akan menumpukkan semua descriptors menjadi 1 array besar.\n\nUntuk demonstrasi, dimisalkan terdapat 3 gambar. Array descriptors ini akan menyimpan descriptor untuk ketiga gambar dalam bentuk [[3, 5, 2], [78, 3, 3], [2, 7, 9]]. Agar dapat digunakan dalam algoritma K Means, array ini harus diubah menjadi bentuk [3, 5, 2, 78, 3, 3, 2, 7, 9]. Hal inilah yang dilakukan dalam pembuatan stacked_descriptors.","metadata":{}},{"cell_type":"code","source":"sift = cv.SIFT_create()\ntrain_descriptors = []\nfor image in train_image_list:\n  _, descriptor = sift.detectAndCompute(image, None)\n  train_descriptors.append(descriptor)\n\nstacked_descriptors = train_descriptors[0]\nfor descriptor in train_descriptors[1:]:\n  stacked_descriptors = np.vstack((stacked_descriptors, descriptor))\nstacked_descriptors = np.float32(stacked_descriptors)","metadata":{"execution":{"iopub.status.busy":"2023-01-25T06:33:48.510267Z","iopub.execute_input":"2023-01-25T06:33:48.511156Z","iopub.status.idle":"2023-01-25T09:09:58.505027Z","shell.execute_reply.started":"2023-01-25T06:33:48.511114Z","shell.execute_reply":"2023-01-25T09:09:58.499488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Menggunakan stacked_descriptors yang diperolehkan dari langkah sebelumnya, kami melakukan clustering menggunakan algoritma K Means dalam scipy.cluster.vq. Hal ini akan mengelompokkan semua descriptors yang diperolehkan menjadi sejumlah cluster, di mana pusat dari setiap cluster ini disimpan dalam centroids. Dalam fungsi kmeans ini, kami menentukan bahwa akan ada 7 cluster, dan dilakukan 100 iterasi untuk menentukan centroidsnya. Maka, centroids ini akan berisikan 7 nilai, yaitu letak 7 centroids tersebut.","metadata":{}},{"cell_type":"code","source":"centroids, _ = kmeans(stacked_descriptors, 7, 100)","metadata":{"execution":{"iopub.status.busy":"2023-01-25T09:09:58.510817Z","iopub.execute_input":"2023-01-25T09:09:58.511474Z","iopub.status.idle":"2023-01-25T13:06:32.124664Z","shell.execute_reply.started":"2023-01-25T09:09:58.511425Z","shell.execute_reply":"2023-01-25T13:06:32.116658Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Selanjutnya, dibuat vector train_V, yang memiliki jumlah baris sebanyak jumlah dataset images, dan jumlah kolom sebanyak jumlah centroids. Vector ini akan diisikan dengan 0 terlebih dahulu. Lalu, untuk setiap gambar dalam train_image_list, kami menggunakan fungsi vq dalam scipy.cluster.vq untuk meng-assign setiap descriptor dari gambar tersebut ke dalam sebuah cluster. Hal ini dilakukan menggunakan euclidean distance untuk mencari jarak dari sebuah descriptor terhadap setiap centroid yang ada. Dengan ini, dapat ditemukan centroid manakah yang paling dekat dengan descriptor tersebut. Misalnya, jika ditemukan bahwa descriptor pertama dari gambar pertama memiliki jarak terdekat dengan centroid ketiga, maka descriptor pertama akan di-assign ke cluster ketiga. Assignment dari descriptor ini disimpan dalam variabel words, yang akan di-loop untuk melakukan increment kepada vector train_V, di mana train_V[i, w] merepresentasikan jumlah descriptor gambar i dalam cluster w.\n\nSetelah itu, menggunakan fungsi sklearn.preprocessing.normalize, kami akan melakukan normalisasi data untuk menghilangkan bias yang disebabkan jumlah descriptor yang berbeda dari setiap gambar. Beberapa gambar dapat saja memiliki jumlah descriptor yang besar, sedangkan beberapa gambar lainnya memiliki jumlah descriptor yang kecil. Hal ini dapat menyebabkan bias, sehingga nilai-nilainya sebaiknya di normalisasi agar memiliki ukuran yang mirip, yaitu dari 0 sampai 1. Cara kerja fungsi ini adalah membagi setiap elemen dalam satu baris dengan sum dari semua elemen dalam satu baris. Misalnya, jika satu baris berisikan 2, 3, 5, maka nilai normalizednya adalah 2/10, 3/10, dan 5/10.","metadata":{}},{"cell_type":"code","source":"train_V = np.zeros((len(train_image_list), len(centroids)), \"float32\")\n\nfor i in range(len(train_image_list)):\n  words, _ = vq(train_descriptors[i], centroids)\n  for w in words:\n    train_V[i, w] += 1\n\ntrain_V_normalized = sklearn.preprocessing.normalize(train_V, 'l1')\nprint(train_V_normalized[0:100])","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:06:32.135952Z","iopub.execute_input":"2023-01-25T13:06:32.138897Z","iopub.status.idle":"2023-01-25T13:08:36.343923Z","shell.execute_reply.started":"2023-01-25T13:06:32.138768Z","shell.execute_reply":"2023-01-25T13:08:36.341854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Berikut adalah fungsi dan class yang kami buatkan untuk algoritma K Nearest Neighbors (KNN) yang akan digunakan untuk memprediksikan apakah sebuah gambar itu termasuk dalam kategori cat atau dog. Untuk ini, dibuat satu helper function yaitu euclidean, yang akan menghitungkan euclidean distance dari dua poin, dan me-returnnya. Lalu, dibuat class bernama KNNClassification, yang akan berisikan semua fungsi dan variabel yang diperlukan untuk melakukan klasifikasi dengan KNN. Class ini akan menerima satu argumen pada saat object KNNClassification dibuat, yaitu k, yaitu nilai yang akan mempengaruhi proses predict. Lalu, fungsi pertama adalah fit, yang akan menerima dua argumen. Argumen pertama, X_train, adalah array yang berisi dengan features dari seluruh train dataset. Di sini, features tersebut adalah vector train_V, yang berisikan bag of visual words. Argumen kedua, y_train, adalah array yang berisi dengan class id dari train dataset. Fungsi kedua, predict, adalah fungsi yang dipanggil untuk melakukan klasifikasi. Fungsi ini akan me-return sebuah array y_pred, di mana isi dari y_pred ini akan diisikan menggunakan helper function _predict. Untuk semua test data, akan dipanggil fungsi _predict ini. Di dalam fungsi _predict, akan dihitung distance dari test data tersebut terhadap semua train data. Dari ini, dapat digunakan fungsi np.argsort untuk mengurutkan semua train data berdasarkan distancenya terhadap test data yang sedang diprediksikan, di mana urutan pertama memiliki distance terkecil. Setelah itu, menggunakan variabel k yang ditetapkan pada awalnya, fungsi ini akan mengambil hanya k train data dengan distance terkecil. Setelah itu, menggunakan fungsi mode, akan dicari class apa yang paling sering muncul dari k train data terdekat tersebut. Class yang paling sering muncul akan di-return, sehingga class tersebut yang menjadi predicted class dari test data tersebut.","metadata":{}},{"cell_type":"code","source":"def euclidean(x1, x2):\n  return np.sqrt(np.sum((x1 - x2) ** 2))\n\nclass KNNClassification:\n  def __init__(self, k):\n    self.k = k\n  \n  def fit(self, X_train, y_train):\n    self.X_train = X_train\n    self.y_train = y_train\n\n  def predict(self, X_test):\n    y_pred = [self._predict(x_test) for x_test in X_test]\n    return np.array(y_pred)\n\n  def _predict(self, x_test):\n    dist = [euclidean(x_test, x_train) for x_train in self.X_train]\n    k_nearest = np.argsort(dist)[:self.k]\n    k_nearest_class = [self.y_train[i] for i in k_nearest]\n    most_common = mode(k_nearest_class)\n    return most_common[0][0]","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:08:36.347095Z","iopub.execute_input":"2023-01-25T13:08:36.354810Z","iopub.status.idle":"2023-01-25T13:08:36.385659Z","shell.execute_reply.started":"2023-01-25T13:08:36.354722Z","shell.execute_reply":"2023-01-25T13:08:36.383647Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Hal yang sama yang dilakukan untuk mendapatkan train data dilakukan pula untuk test data, yang terdapat dalam folder test1. Untuk memudahkan evaluasi dan menampilkan data (oleh karena di dalam dataset ini, kami tidak diberikan data mengenai class dari test data, sehingga evaluasi harus dilakukan secara manual), kami akan mengambil sebanyak 100 data secara random dari seluruh data dalam test1, yaitu sebanyak 12500 data.","metadata":{}},{"cell_type":"code","source":"test_path = '/kaggle/working/test1'\ntest_paths = os.listdir(test_path)\ntest_samples = random.sample(range(0, 12500), 100)\ntest_image_list = []\nfor index in test_samples:\n  test_image_list.append(cv.imread(test_path + '/' + test_paths[index]))","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:08:36.388734Z","iopub.execute_input":"2023-01-25T13:08:36.389832Z","iopub.status.idle":"2023-01-25T13:08:36.795647Z","shell.execute_reply.started":"2023-01-25T13:08:36.389779Z","shell.execute_reply":"2023-01-25T13:08:36.794245Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sekali lagi, untuk setiap gambar dalam test_image_list, akan dicari descriptornya menggunakan detectAndCompute dari SIFT.","metadata":{}},{"cell_type":"code","source":"test_descriptors = []\nfor image in test_image_list:\n  _, descriptor = sift.detectAndCompute(image, None)\n  test_descriptors.append(descriptor)","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:08:36.802033Z","iopub.execute_input":"2023-01-25T13:08:36.802478Z","iopub.status.idle":"2023-01-25T13:08:42.303016Z","shell.execute_reply.started":"2023-01-25T13:08:36.802442Z","shell.execute_reply":"2023-01-25T13:08:42.301965Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sama seperti pada saat pembuatan bag of visual words untuk train data, kami akan melakukan proses yang sama untuk mendapatkan bag of visual words untuk test data yang disimpan dalam test_V. test_V ini lalu juga akan dinormalisasikan.","metadata":{}},{"cell_type":"code","source":"test_V = np.zeros((len(test_image_list), len(centroids)), \"float32\")\n\nfor i in range(len(test_image_list)):\n  words, _ = vq(test_descriptors[i], centroids)\n  for w in words:\n    test_V[i, w] += 1\n\ntest_V_normalized = sklearn.preprocessing.normalize(test_V, 'l1')\nprint(test_V_normalized[0:100])","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:08:42.326612Z","iopub.execute_input":"2023-01-25T13:08:42.327151Z","iopub.status.idle":"2023-01-25T13:08:43.277420Z","shell.execute_reply.started":"2023-01-25T13:08:42.327062Z","shell.execute_reply":"2023-01-25T13:08:43.276090Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Berikut adalah 100 random data yang akan digunakan dalam memprediksikan cat atau dog menggunakan bag of visual words sebagai features dan K Nearest Neighbors sebagai machine learning model.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize = (20, 70))\nfor i in range(100):\n    plt.subplot(20, 5, i + 1)\n    plt.imshow(cv.cvtColor(test_image_list[i], cv.COLOR_BGR2RGB))\nplt.suptitle('100 Random Sample Images in Test Dataset')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:08:43.284793Z","iopub.execute_input":"2023-01-25T13:08:43.285587Z","iopub.status.idle":"2023-01-25T13:08:58.706935Z","shell.execute_reply.started":"2023-01-25T13:08:43.285532Z","shell.execute_reply":"2023-01-25T13:08:58.705124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Kami menemukan bahwa k = 9 merupakan nilai k yang paling optimal untuk menghasilkan akurasi tertinggi menggunakan test sample tersebut, di mana didapatkan akurasi setinggi 73%. Dari 100 sample yang diambil untuk diprediksikan, kami menemukan bahwa menggunakan 7 centroids untuk membuat bag of visual words, dengan KNN model dengan k = 9, 73 gambar dengan benar diprediksikan classnya. Berdasarkan deskripsi yang diberikan di halaman dataset Dogs vs. Cats ini, di mana state of the art mencapai 80%, terlihat bahwa hanya menggunakan teknik machine learning traditional saja (bukan menggunakan hal-hal seperti CNN, deep learning, dan pre-trained models seperti ResNet, VGG, atau Inception), kami mampu mendapatkan akurasi yang sudah cukup mendekati 80% ini, yaitu sebesar 73%. Sebuah faktor yang dapat mempengaruhi akurasi ini, atau mempersulit mendapatkan akurasi yang sangat tinggi, berdasarkan gambar-gambar yang telah ditampilkan di notebook ini, adalah keragaman dari gambar-gambar dalam dataset. Terdapat banyak variasi gambar, seperti gambar close-up, di mana hewan peliharaan merupakan sebagian besar dari gambar, gambar di mana hewan peliharaan memiliki ukuran kecil di background, gambar dengan jumlah hewan peliharaan yang banyak (multiple objects), gambar dengan objek-objek lainnya selain hewan peliharaan seperti adanya manusia, aspect ratio yang berbeda, ataupun clarity dari hewan peliharaannya (seperti hewan peliharaan yang tertutupi oleh objek lain, seperti kandang).","metadata":{}},{"cell_type":"code","source":"model = KNNClassification(9)\nmodel.fit(train_V, train_image_class)\ny_pred = model.predict(test_V)\n\nplt.figure(figsize = (20, 70))\nfor i in range(100):\n    plt.subplot(20, 5, i + 1)\n    plt.imshow(cv.cvtColor(test_image_list[i], cv.COLOR_BGR2RGB))\n    plt.title(labels[y_pred[i]])\nplt.suptitle('Predictions of 100 Random Sample Test Images')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-25T13:10:33.253438Z","iopub.execute_input":"2023-01-25T13:10:33.253821Z","iopub.status.idle":"2023-01-25T13:10:56.805031Z","shell.execute_reply.started":"2023-01-25T13:10:33.253787Z","shell.execute_reply":"2023-01-25T13:10:56.801423Z"},"trusted":true},"execution_count":null,"outputs":[]}]}