diff --git a/README.md b/README.md index 48de98d3dd4337861330ae523e28f3645d3d4cb5..2b73c7591218efc0738319ed210a3d695db8d6bb 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,23 @@ -# Image classification +# TD 1 : Image Classification +MOD 4.6 Deep Learning & Artificial Intelligence: an introduction -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.ec-lyon.fr/gbeauvy/image-classification.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.ec-lyon.fr/gbeauvy/image-classification/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - ## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +This project's goal is to implement image classification using two different models: k-nearest neighbors (KNN) and multilayer perceptron (MLP) neural network in Python. +We will used the CIFAR-10 dataset, consisting of 60,000 color images of size 32x32 divided into 10 classes. -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +This project is composed of three python files : "read_cifar.py", "knn.py" and "mlp.py". And two directories : "data" (that contains the batches with the data) and "results" (that contains two PNG files showing examples of th training of the two models. ## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +The project generates accuracy plots ("knn.png" and "mlp.png") in the "results" directory, showing the performance of the implemented models. ## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +In the file "read_cifar.py", the functions permit to read the dataset and shuffle it. The dataset is also split into training and testing sets. The split parameter determines the proportion of the data to be used for training. -## License -For open source projects, say how it is licensed. +In the file "knn.py", the functions evaluates the classification rate (accuracy) of the k-nearest neighbors algorithm using the provided training and testing data along with the number of neighbors (k). +Initially, the distance_matrix function computes the distance matrix between your training dataset and the dataset that holds images whose labels you aim to predict. Subsequently, by employing the knn_predict function, you have the flexibility to specify the hyperparameter k and utilize the precomputed distance matrix to forecast the labels for your test dataset. Lastly, the evaluate_knn function assesses the accuracy of the k-nearest neighbors algorithm implemented earlier on our dataset, considering a specific value for the hyperparameter k. +We notice that when k=1, it yields the highest accuracy, whereas k=2 results in the lowest. The accuracy hovers around 34% for the remaining values of k. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +In the files "mlp.py", the focus is on implementing and training a multilayer perceptron (MLP) for classification tasks. It delivers training accuracies across epochs and the final testing accuracy. Users can employ these functions to effectively train and evaluate MLP classifiers for their specific classification datasets. +The MLP undergoes learning steps at each epoch, where each step comprises a forward pass that generates predictions, a calculation of the loss, a backward pass to compute gradients for backpropagation, and the updating of the MLP's parameters. Both the learn_once_mse and learn_once_cross_entropy functions encapsulate an entire learning step, differing only in the loss calculation function they employ. \ No newline at end of file diff --git a/data/cifar-10-batches-py/batches.meta b/data/cifar-10-batches-py/batches.meta new file mode 100644 index 0000000000000000000000000000000000000000..4467a6ec2e886a9f14f25e31776fb0152d8ac64a Binary files /dev/null and b/data/cifar-10-batches-py/batches.meta differ diff --git a/data/cifar-10-batches-py/data_batch_1 b/data/cifar-10-batches-py/data_batch_1 new file mode 100644 index 0000000000000000000000000000000000000000..ab404a5ac32492b807a5c6cd02b83dc4dd5ff980 Binary files /dev/null and b/data/cifar-10-batches-py/data_batch_1 differ diff --git a/data/cifar-10-batches-py/data_batch_2 b/data/cifar-10-batches-py/data_batch_2 new file mode 100644 index 0000000000000000000000000000000000000000..6bf1369a6cacadfdbd2f8c61e354cc7d0c17bbae Binary files /dev/null and b/data/cifar-10-batches-py/data_batch_2 differ diff --git a/data/cifar-10-batches-py/data_batch_3 b/data/cifar-10-batches-py/data_batch_3 new file mode 100644 index 0000000000000000000000000000000000000000..66a0d630a7eb736563b1861ce716bdc489f2113b Binary files /dev/null and b/data/cifar-10-batches-py/data_batch_3 differ diff --git a/data/cifar-10-batches-py/data_batch_4 b/data/cifar-10-batches-py/data_batch_4 new file mode 100644 index 0000000000000000000000000000000000000000..cf8d03d1e80e6d9e440d1764faa85aedd1d6b960 Binary files /dev/null and b/data/cifar-10-batches-py/data_batch_4 differ diff --git a/data/cifar-10-batches-py/data_batch_5 b/data/cifar-10-batches-py/data_batch_5 new file mode 100644 index 0000000000000000000000000000000000000000..468b2aa538c551bc9f590f213b19d96915b85062 Binary files /dev/null and b/data/cifar-10-batches-py/data_batch_5 differ diff --git a/data/cifar-10-batches-py/readme.html b/data/cifar-10-batches-py/readme.html new file mode 100644 index 0000000000000000000000000000000000000000..e377adef45c85dc91051edf2dee72c1d4d57732c --- /dev/null +++ b/data/cifar-10-batches-py/readme.html @@ -0,0 +1 @@ +<meta HTTP-EQUIV="REFRESH" content="0; url=http://www.cs.toronto.edu/~kriz/cifar.html"> diff --git a/data/cifar-10-batches-py/test_batch b/data/cifar-10-batches-py/test_batch new file mode 100644 index 0000000000000000000000000000000000000000..3e03f1fc5261d102600fc1c130454f1f5cda567b Binary files /dev/null and b/data/cifar-10-batches-py/test_batch differ diff --git a/knn.py b/knn.py new file mode 100644 index 0000000000000000000000000000000000000000..2e7606aa99852268ebeb5ded2ce5f208e537dace --- /dev/null +++ b/knn.py @@ -0,0 +1,61 @@ +import numpy as np +import read_cifar as rc +import matplotlib.pyplot as plt + +def distance_matrix(M1, M2): + sum_of_M1 = np.sum(np.square(M1), axis=1, keepdims=True) + sum_of_M2 = np.sum(np.square(M2), axis=1, keepdims=True) + product = np.dot(M1, M2.T) + dists = np.sqrt(sum_of_M1 + sum_of_M2.T - 2 * product) + return dists + +def knn_predict(dists, labels_train, k): + predicted_labels = [] + + for i in range(dists.shape[1]): + nearest_neighbor = np.argpartition(dists[:, i], k) + predicted_label_int = np.zeros((max(labels_train)+1,)) + for j in range(k): + predicted_label_int[labels_train[nearest_neighbor[j]]] += 1 + predicted_labels.append(np.argmax(predicted_label_int)) + + + return np.array(predicted_labels) + +def evaluate_knn(data_train, labels_train, data_test, labels_test, k): + n_test = len(data_test) + correct_predictions = 0 + + dists = distance_matrix(data_train, data_test) + predicted_label = knn_predict(dists, labels_train, k) + + for i in range(n_test): + if predicted_label[i] == labels_test[i]: + correct_predictions += 1 + + accuracy = (correct_predictions / n_test) * 100 + return accuracy + + +if __name__ == "__main__": + directory="data\cifar-10-batches-py\\" + split_factor=0.9 + + + data, labels=rc.read_cifar(directory) + data_train, labels_train, data_test, labels_test=rc.split_dataset(data, labels, split_factor) + + + + L=[] + for k in range(1,21): + accuracy = evaluate_knn(data_train, labels_train, data_test, labels_test, k) + L.append(accuracy) + print(accuracy) + + plt.plot(L) + plt.title('Accuracy of KNN model depending on value of k') + plt.xlabel('k') + plt.ylabel('Accuracy') + plt.savefig('results/knn.png') + plt.show() \ No newline at end of file diff --git a/mlp.py b/mlp.py new file mode 100644 index 0000000000000000000000000000000000000000..fc321c7af9456f67a372a512244164b653e30166 --- /dev/null +++ b/mlp.py @@ -0,0 +1,138 @@ +import numpy as np +import read_cifar as rc +import matplotlib.pyplot as plt + + + +def learn_once_mse(w1, b1, w2, b2, data, targets, learning_rate): + # Forward pass + a0 = data # the data are the input of the first layer + z1 = np.matmul(a0, w1) + b1 # input of the hidden layer + a1 = 1 / (1 + np.exp(-z1)) # output of the hidden layer (sigmoid activation function) + z2 = np.matmul(a1, w2) + b2 # input of the output layer + a2 = 1 / (1 + np.exp(-z2)) # output of the output layer (sigmoid activation function) + predictions = a2 # the predicted values are the outputs of the output layer + + # Compute loss (MSE) + loss = np.mean(np.square(predictions - targets)) + print(loss) + + + delta2 = 2 * (predictions - targets) / data.shape[0] # Derivative of MSE loss + grad_w2 = np.matmul(a1.T, delta2) + grad_b2 = np.sum(delta2, axis=0, keepdims=True) + delta1 = np.matmul(delta2, w2.T) * a1*(1-a1) + grad_w1 = np.matmul(a0.T, delta1) + grad_b1 = np.sum(delta1, axis=0, keepdims=True) + + w1 -= learning_rate * grad_w1 + b1 -= learning_rate * grad_b1 + w2 -= learning_rate * grad_w2 + b2 -= learning_rate * grad_b2 + + return w1, b1, w2, b2, loss + +def one_hot(labels): + n=np.max(labels) + return np.eye(n+1)[labels] + +def softmax(x): + exps = np.exp(x) + return(exps/exps.sum()) + + +def learn_once_cross_entropy(w1, b1, w2, b2, data, labels_train, learning_rate): + # Forward pass + a0 = data # the data are the input of the first layer + z1 = np.matmul(a0, w1) + b1 # input of the hidden layer + a1 = 1 / (1 + np.exp(-z1)) # output of the hidden layer (sigmoid activation function) + z2 = np.matmul(a1, w2) + b2 # input of the output layer + a2 = softmax(z2) # output of the output layer (sigmoid activation function) + predictions = a2 # the predicted values are the outputs of the output layer + + labels_one_hot = one_hot(labels_train) + + # Compute loss (Binary Cross-Entropy) + loss = np.mean(np.square(predictions - labels_one_hot)) + + delta2 = a2 - labels_one_hot + grad_w2 = np.matmul(a1.T, delta2) + grad_b2 = np.sum(delta2, axis=0, keepdims=True) + delta1 = np.matmul(delta2, w2.T) * a1 * (1 - a1) + grad_w1 = np.matmul(a0.T, delta1) + grad_b1 = np.sum(delta1, axis=0, keepdims=True) + + w1 -= learning_rate * grad_w1 + b1 -= learning_rate * grad_b1 + w2 -= learning_rate * grad_w2 + b2 -= learning_rate * grad_b2 + + return w1, b1, w2, b2, loss + +def accuracy(w1, b1, w2, b2, data, labels): + # Forward pass + a0 = data + z1 = np.matmul(a0, w1) + b1 + a1 = 1 / (1 + np.exp(-z1)) + z2 = np.matmul(a1, w2) + b2 + a2 = softmax(z2) + predictions = a2 + return np.mean(np.argmax(predictions, axis=1) == labels) + + +def train_mlp(w1, b1, w2, b2, data_train, labels_train, learning_rate, num_epoch): + train_accuracy = [] + + for i in range(num_epoch): + w1, b1, w2, b2, loss = learn_once_cross_entropy(w1, b1, w2, b2, data_train, labels_train, learning_rate) + train_accuracy.append(accuracy(w1, b1, w2, b2, data_train, labels_train)) + + return w1, b1, w2, b2, train_accuracy + +def test_mlp(w1, b1, w2, b2, data_test, labels_test): + # Forward pass + a0 = data # the data are the input of the first layer + z1 = np.matmul(a0, w1) + b1 # input of the hidden layer + a1 = 1 / (1 + np.exp(-z1)) # output of the hidden layer (sigmoid activation function) + z2 = np.matmul(a1, w2) + b2 # input of the output layer + a2 = softmax(z2) # output of the output layer (sigmoid activation function) + predictions = a2 # the predicted values are the outputs of the output layer + + test_accuracy = np.mean(np.argmax(predictions, axis=1) == labels_test) + + return test_accuracy + +def run_mlp_training(data_train, labels_train, data_test, labels_test, d_h, learning_rate, num_epoch): + d_in = data_train.shape[1] + d_out = np.max(labels_train) + 1 + + w1 = 2 * np.random.rand(d_in, d_h) - 1 + b1 = np.zeros((1, d_h)) + w2 = 2 * np.random.rand(d_h, d_out) - 1 + b2 = np.zeros((1, d_out)) + + w1, b1, w2, b2, train_losses = train_mlp(w1, b1, w2, b2, data_train, labels_train, learning_rate, num_epoch) + test_accuracy = test_mlp(w1, b1, w2, b2, data_test, labels_test) + + return train_losses, test_accuracy + +if __name__ == "__main__": + directory="data\cifar-10-batches-py\\" + split_factor=0.9 + + d_h = 64 + learning_rate = 0.1 + num_epoch = 100 + + + data, labels=rc.read_cifar(directory) + data_train, labels_train, data_test, labels_test=rc.split_dataset(data, labels, split_factor) + + train_losses, test_accuracy = run_mlp_training(data_train, labels_train, data_test, labels_test, d_h, learning_rate, num_epoch) + + plt.plot(train_losses) + plt.xlabel('Epoch') + plt.ylabel('Training Loss') + plt.title('Training Loss Over Epochs') + plt.savefig('results/mlp.png') + plt.show() diff --git a/read_cifar.py b/read_cifar.py new file mode 100644 index 0000000000000000000000000000000000000000..2fdf145b7e148285cb12c3b03f274073578af5ac --- /dev/null +++ b/read_cifar.py @@ -0,0 +1,44 @@ +import pickle +import numpy as np + +def read_cifar_batch(file): + with open(file, 'rb') as fo: + dict = pickle.load(fo, encoding='bytes') + data=np.array(dict[b'data'], dtype=np.float32) + labels=np.array(dict[b'labels'], dtype=np.int64) + return data, labels + +def read_cifar(folder): + data, labels= [], [] + + batches=["data_batch_1","data_batch_2","data_batch_3","data_batch_4","data_batch_5","test_batch"] + for b in batches: + data_int, labels_int = read_cifar_batch(folder+b) + data.append(data_int) + labels.append(labels_int) + return np.concatenate(data), np.concatenate(labels) + +def split_dataset(data, labels, split): + permutation=np.random.permutation(len(data)) + data=data[permutation] + labels=labels[permutation] + int_split = int(split*len(data)) + data_train=data[:int_split] + labels_train=labels[:int_split] + data_test=data[int_split:] + labels_test=labels[int_split:] + return data_train, labels_train, data_test, labels_test + + + +if __name__ == "__main__": + # batch_1="data\cifar-10-batches-py\data_batch_1" + # batch_2="data\cifar-10-batches-py\data_batch_2" + # batch_3="data\cifar-10-batches-py\data_batch_3" + # batch_4="data\cifar-10-batches-py\data_batch_4" + # batch_5="data\cifar-10-batches-py\data_batch_5" + # test_batch="data\cifar-10-batches-py\test_batch" + directory="data\cifar-10-batches-py\\" + data, labels=read_cifar(directory) + test=split_dataset(data, labels, 0.6) + print(test) \ No newline at end of file diff --git a/results/knn.png b/results/knn.png new file mode 100644 index 0000000000000000000000000000000000000000..21fa7fdec75b7b6277fd955e22f6908e2fa4a059 Binary files /dev/null and b/results/knn.png differ diff --git a/results/mlp.png b/results/mlp.png new file mode 100644 index 0000000000000000000000000000000000000000..d53a1af7526fed2690f8d5d43b021797364688f9 Binary files /dev/null and b/results/mlp.png differ