{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")\n\n### Libraries\nimport os\n\n## Data\nimport numpy as np\nimport pandas as pd\n\n## Visualization\nimport matplotlib.pyplot as plt\nimport seaborn as sns \n\n## DL\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\n\nimport tensorflow as tf\nfrom tensorflow.keras.applications import EfficientNetB3, InceptionV3, Xception","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2026-06-20T21:12:52.240910Z","iopub.execute_input":"2026-06-20T21:12:52.241267Z","iopub.status.idle":"2026-06-20T21:13:14.026508Z","shell.execute_reply.started":"2026-06-20T21:12:52.241237Z","shell.execute_reply":"2026-06-20T21:13:14.025324Z"},"trusted":true,"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Exercise 1: Understand the problem, inspect data</h1>","metadata":{}},{"cell_type":"markdown","source":"**Problem Research (Objective)**\n<p>Dog breed identification. This means training a model on pictures of dogs. Then letting it recognize test pictures. The rate of correctly identified pictures measures the success.</p>\n\n**Related Works**\n<p>CNN + SVM (Stanford Dogs, 2024)<br/>\nCui, Y. et al. (2024). Classification of Dog Breeds Using CNN Models and SVM. Bioengineering, 11(11), 1157. doi:10.3390/bioengineering11111157\n<a href=\"https://www.mdpi.com/2306-5354/11/11/1157\">MDPI</a></p>\n<p>Hybrid InceptionV3-Xception (2025)<br/>\nSai Ram, G. (2025). Next-Gen dog breed identification using hybrid and custom CNN models. Int J Comput Artif Intell, 6(1):99–110. doi:10.33545/27076571.2025.v6.i1b.138\n<a href=\"https://www.computersciencejournals.com/ijcai/archives/2025.v6.i1.B.138\">Computersciencejournals</a></p>\n<p><a href=\"https://www.kaggle.com/code/abhishekgautam711/dog-breed-identifier-92-accuracy#Model-Training-(Subset-of-Data)\">Similar kaggle project</a></p>\n\n**Tools**\n- This notebook here, written in Python\n- scikit-learn library\n- tensorflow library","metadata":{}},{"cell_type":"code","source":"## Load the directories\ntrain_dir = '../input/competitions/dog-breed-identification/train'\ntest_dir = '../input/competitions/dog-breed-identification/test'\n\nprint(f\"Train directory pictures: {len(os.listdir(train_dir))}\")\nprint(f\"Test directory pictures: {len(os.listdir(test_dir))}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:18.971320Z","iopub.execute_input":"2026-06-20T21:13:18.971914Z","iopub.status.idle":"2026-06-20T21:13:19.495437Z","shell.execute_reply.started":"2026-06-20T21:13:18.971882Z","shell.execute_reply":"2026-06-20T21:13:19.494351Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Load the labels\ndf = pd.read_csv('../input/competitions/dog-breed-identification/labels.csv')\n\ndf.info()\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:22.170588Z","iopub.execute_input":"2026-06-20T21:13:22.170926Z","iopub.status.idle":"2026-06-20T21:13:22.240230Z","shell.execute_reply.started":"2026-06-20T21:13:22.170896Z","shell.execute_reply":"2026-06-20T21:13:22.239403Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"All data is non-null and amount of entries matches train directory \n-> Data set is complete (no missing data)","metadata":{}},{"cell_type":"code","source":"## Amount of duplicate values\ndf.duplicated().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:27.491525Z","iopub.execute_input":"2026-06-20T21:13:27.491881Z","iopub.status.idle":"2026-06-20T21:13:27.501319Z","shell.execute_reply.started":"2026-06-20T21:13:27.491851Z","shell.execute_reply":"2026-06-20T21:13:27.500535Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"-> No duplicate values","metadata":{}},{"cell_type":"markdown","source":"<h1>Exercise 2: Data Exploration</h1>","metadata":{}},{"cell_type":"code","source":"## Unique dog breeds\ndog_breeds = sorted(df['breed'].unique())\ndog_breeds_amount = len(dog_breeds)\n\nprint(f\"Amount of different dog breeds: {dog_breeds_amount}\")\nprint(dog_breeds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:30.333638Z","iopub.execute_input":"2026-06-20T21:13:30.333986Z","iopub.status.idle":"2026-06-20T21:13:30.340675Z","shell.execute_reply.started":"2026-06-20T21:13:30.333934Z","shell.execute_reply":"2026-06-20T21:13:30.339815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## How often each type of breed exists\ndf_value_counts = df['breed'].value_counts()\n\nprint(f\"Lowest: {df_value_counts.min()}, Highest : {df_value_counts.max()}, Median: {df_value_counts.median()}, Mean: {df_value_counts.mean():.2f}\")\ndf_value_counts.plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:33.138935Z","iopub.execute_input":"2026-06-20T21:13:33.139292Z","iopub.status.idle":"2026-06-20T21:13:33.434468Z","shell.execute_reply.started":"2026-06-20T21:13:33.139265Z","shell.execute_reply":"2026-06-20T21:13:33.433751Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Could go with the lowest value of 66 pictures for all breeds? (if the uneven distribution causes issues)\n<br>That would mean using 120 * 66 = 7920 of the pictures (77.48%)","metadata":{}},{"cell_type":"code","source":"'''\nimg_names = df['id']\ndata_size = len(img_names)\n\n#img_size = np.zeros([data_size])\nimg_size = pd.DataFrame(columns=[\"x\", \"y\"])\n\nfor i in range(data_size):\n    img_path = os.path.join(train_dir, img_names[i] + '.jpg')\n    img = plt.imread(img_path)\n    a, b = img.shape[:2]\n    img_size.loc[i] = [a, b]\n\nimg_size.head()\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:07:30.599292Z","iopub.execute_input":"2026-06-09T20:07:30.599820Z","iopub.status.idle":"2026-06-09T20:07:30.605060Z","shell.execute_reply.started":"2026-06-09T20:07:30.599797Z","shell.execute_reply":"2026-06-09T20:07:30.604239Z"},"jupyter":{"source_hidden":true,"outputs_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Get the x and y image sizes\ndef get_image_sizes():\n    img_names = df['id']\n    data_size = len(img_names)\n    \n    img_size_x = np.zeros([data_size])\n    img_size_y = np.zeros([data_size])\n    \n    ## Get x and y for every image\n    for i in range(data_size):\n        img_path = os.path.join(train_dir, img_names[i] + '.jpg')\n        img = tf.keras.preprocessing.image.load_img(img_path)\n        y, x = img.size\n        img_size_x[i] = x\n        img_size_y[i] = y\n\n    return img_size_x, img_size_y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:12:19.228148Z","iopub.execute_input":"2026-06-20T21:12:19.228478Z","iopub.status.idle":"2026-06-20T21:12:19.235431Z","shell.execute_reply.started":"2026-06-20T21:12:19.228449Z","shell.execute_reply":"2026-06-20T21:12:19.233800Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Gets all image sizes and displays them\n## Commented out because it takes a while to complete\n'''\nimg_size_x, img_size_y = get_image_sizes()\n\n# Visualize the data\nprint(f\"[X] Lowest: {img_size_x.min()}, Highest : {img_size_x.max()}, Median: {np.median(img_size_x)}, Mean: {img_size_x.mean():.2f}\")\nprint(f\"[Y] Lowest: {img_size_y.min()}, Highest : {img_size_y.max()}, Median: {np.median(img_size_y)}, Mean: {img_size_y.mean():.2f}\")\n\nplt.scatter(img_size_x, img_size_y)\nplt.show()\n'''","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:13:38.562826Z","iopub.execute_input":"2026-06-20T21:13:38.563158Z","iopub.status.idle":"2026-06-20T21:15:13.760848Z","shell.execute_reply.started":"2026-06-20T21:13:38.563133Z","shell.execute_reply":"2026-06-20T21:15:13.760046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Random example image\nimport random\nimg_names = df['id']\ndata_size = len(img_names)\n\nplt.figure(figsize = (12, 5))\nrdm = random.randint(0, data_size)\nplt.imshow(plt.imread(os.path.join(train_dir, img_names[rdm] + '.jpg')))\nplt.title(str(rdm) + \" | id: \" + df.loc[rdm, \"id\"] + \" | breed: \" + df.loc[rdm, \"breed\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-20T21:21:14.581773Z","iopub.execute_input":"2026-06-20T21:21:14.582120Z","iopub.status.idle":"2026-06-20T21:21:14.827605Z","shell.execute_reply.started":"2026-06-20T21:21:14.582093Z","shell.execute_reply":"2026-06-20T21:21:14.826170Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Exercise 3: Prepare methods</h1>","metadata":{}},{"cell_type":"markdown","source":"**Identification of Input and Target attributes**\n- Input: Train images\n- Target (ground truth): Labels that identify the train images\n<br>After the model has been trained with the labelled input, it will then be asked to classify the test images. This is where its performance is measured [check if true]\n\n**Research of suitable methods**<br/>\nhttps://www.ibm.com/think/topics/classification-machine-learning<br/>\n- The dataset is labeled, meaning we'll use supervised learning (part of machine learning) for training.\n- Specifically creating a classification model -> It will be asked to classify the images<br><br>\nStep 1 - Learning: Model figures out what parts of tuple x (the training images) determines the label (dog breed)<br>\nStep 2 - Classification: It gets an input (test image) and gives an output/label (guess of the dog breed)<br><br>\n\n- Multiclass classification: Many labels that are mutually exclusive\n- Continuous predictions are expected: Giving a percentage for each label (e.g. this is a dachshund with 0.82 probability)\n- SVM (support vector machine): Works with multidimensional data (scikit-learn)\n- CNN (Convolutional Neural Networks): Well suited for image classification (Inception)\n- 80/20 train/validation split\n\n**Technical preparation**<br>\nRunning the models will take a while. This is normal for pictures, as they are rather complex.<br>\nFor example a picture with a size of 300x300 consists of 90,000 pixels, multiplied by 3 for the RGB channels makes 270,000 points to go through for 1 image\n\n**Model training can be sped up significantly by activating GPU acceleration**","metadata":{}},{"cell_type":"markdown","source":"<h2>Preprocessing</h2>\n= converting the data into something usable for the model","metadata":{}},{"cell_type":"code","source":"## Constants (TODO: Use them)\nIMAGE_DIMENSIONS = (299, 299, 3)\nOUTPUT_SIZE = 120","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:07:30.887254Z","iopub.execute_input":"2026-06-09T20:07:30.887546Z","iopub.status.idle":"2026-06-09T20:07:30.891215Z","shell.execute_reply.started":"2026-06-09T20:07:30.887514Z","shell.execute_reply":"2026-06-09T20:07:30.890612Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Encode the breeds (to identify them by number) as an additional column\n\nencoder = LabelEncoder()\ndf[\"encoded_breed\"] = encoder.fit_transform(df[\"breed\"])\ndf.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:07:30.892038Z","iopub.execute_input":"2026-06-09T20:07:30.892340Z","iopub.status.idle":"2026-06-09T20:07:30.911005Z","shell.execute_reply.started":"2026-06-09T20:07:30.892308Z","shell.execute_reply":"2026-06-09T20:07:30.910417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Preprocess the images\n## - Convert them to vectors\n## - Make them the same size\n\nfrom keras.preprocessing.image import load_img, img_to_array\nfrom keras.utils import to_categorical\n\ndef preprocess_images(img_size):\n    img_id = df[\"id\"]\n    img_label = df[\"breed\"]\n    \n    X = np.zeros([data_size, img_size[0], img_size[1], img_size[2]], dtype = np.float32)\n    y = np.zeros([data_size, 1], dtype = np.uint8)\n\n    # Create X and y for every image\n    for i in range(data_size):\n        img_dir = os.path.join(train_dir, img_id[i] + \".jpg\")\n        # Get PIL image scaled to the target size\n        train_img = load_img(img_dir, target_size=img_size)\n\n        # Scale the img vectors between 0 and 1 by dividing through 255, should make the process faster\n        # Disable this if it causes issues instead ^^'\n        train_img = img_to_array(train_img)\n        train_img = train_img / 255.0\n        \n        X[i] = train_img\n        y[i] = df[\"encoded_breed\"][i]\n\n    # Convert y to a matrix with binary values, with encoded_breed as columns\n    y = to_categorical(y)\n\n    # Shuffle dataset\n    ind = np.random.permutation(data_size)\n    X = X[ind]\n    y = y[ind]\n\n    print(f\"X shape: {X.shape}\")\n    print(f\"y shape: {y.shape}\")\n    \n    return X, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:07:30.911842Z","iopub.execute_input":"2026-06-09T20:07:30.912119Z","iopub.status.idle":"2026-06-09T20:07:30.922592Z","shell.execute_reply.started":"2026-06-09T20:07:30.912092Z","shell.execute_reply":"2026-06-09T20:07:30.921803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Run image preprocessing\n\nimg_size = [299, 299, 3] #Recommended minimum is 299, 299, 3 for Inception\nX_train, y_train = preprocess_images(img_size)\n#print(X.dtype)\n#print(X[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:07:30.923311Z","iopub.execute_input":"2026-06-09T20:07:30.923573Z","iopub.status.idle":"2026-06-09T20:09:21.946290Z","shell.execute_reply.started":"2026-06-09T20:07:30.923554Z","shell.execute_reply":"2026-06-09T20:09:21.945280Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Split the training dataset into train and validation parts\n## [?] This is using like 10 GiB of RAM\n\nX_train, X_val, y_train, y_val = train_test_split(\n    X_train,\n    y_train,\n    train_size = 0.8, # 80/20 split for train/validation parts\n    random_state = 123, # Determines how data is shuffled\n    stratify = y_train\n)\n\nprint(f\"X_train shape: {X_train.shape} | X_val shape: {X_val.shape}\")\nprint(f\"y_train shape: {y_train.shape} | y_val shape: {y_val.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:09:21.947369Z","iopub.execute_input":"2026-06-09T20:09:21.947734Z","iopub.status.idle":"2026-06-09T20:09:25.763200Z","shell.execute_reply.started":"2026-06-09T20:09:21.947711Z","shell.execute_reply":"2026-06-09T20:09:25.762403Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h1>Exercise 4: Apply methods</h1>","metadata":{}},{"cell_type":"markdown","source":"<h2>Transfer-learning</h2>\n<p>= Taking a model that has been trained on a similar dataset and adjusting it to fit our problem</p>\n<p><a href=\"https://keras.io/guides/transfer_learning/#transfer-learning-amp-finetuning\">https://keras.io/guides/transfer_learning/#transfer-learning-amp-finetuning</a></p>\n\n1. Instantiate a base model and load pre-trained weights into it.\n2. Freeze all layers in the base model by setting trainable = False.\n3. Create a new model on top of the output of one (or several) layers from the base model.\n4. Train your new model on your new dataset.\n\n<p>Optional: Fine-tuning the model. This is done by unfreezing layers of the base model and retraining it with a low learning rate</p>","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import Sequential, Model\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, GlobalAveragePooling2D, Flatten, Dense\n\ndef get_model(model_name):\n    if model_name == \"seq\":\n        # Create sequential model\n        # Each part is a layer: Input = Flatten -> Hidden = Dense -> Output = Dense\n        # No convolutional part\n        return Sequential([\n            Flatten(input_shape=(299, 299, 3)), # Flattened image input\n            Dense(128, activation=\"relu\"), # Hidden layer, 128 neurons with ReLU function\n            Dense(120) # 120 output classes\n        ])\n    elif model_name == \"xception\":\n        # Create Inception base model\n        base_model = tf.keras.applications.Xception(\n            #input_shape = (299, 299 , 3), # Defaults to (299, 299, 3) if include_top is true\n            include_top = False, # If false, last layer (output) is excluded\n            weights = \"imagenet\", # Pre-trained on imagenet\n            #classes = 120, # Output classes, works only if include_top = True and weights = None\n            #classifier_activation = \"softmax\", # Activation function in output layer\n        )\n    \n    # Freeze the base model layers -> They won't get trained\n    base_model.trainable = False\n    \n    # Define shape of the input\n    input_tensor = Input(shape = IMAGE_DIMENSIONS)\n    \n    # Build layers on top of base_model\n    x = base_model(input_tensor, training = False)\n    x = GlobalAveragePooling2D()(x)\n    x = Dense(1024, activation=\"relu\")(x)\n    outputs = Dense(OUTPUT_SIZE, activation=\"softmax\")(x) # Add output layer with 120 labels\n\n    # Create new model by combining input and output\n    model = Model(inputs = input_tensor, outputs = outputs)\n\n    return model\n\nmodel = get_model(\"xception\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:09:25.764246Z","iopub.execute_input":"2026-06-09T20:09:25.764557Z","iopub.status.idle":"2026-06-09T20:09:32.294880Z","shell.execute_reply.started":"2026-06-09T20:09:25.764534Z","shell.execute_reply":"2026-06-09T20:09:32.294034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Adjust model settings\nmodel.compile(\n    optimizer=\"adam\", # Using Adam algorithm\n    loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), # Loss function\n    metrics=[\"accuracy\"] # Metrics - Amount of correctly classified images\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:09:32.295829Z","iopub.execute_input":"2026-06-09T20:09:32.296148Z","iopub.status.idle":"2026-06-09T20:09:32.308496Z","shell.execute_reply.started":"2026-06-09T20:09:32.296126Z","shell.execute_reply":"2026-06-09T20:09:32.307818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Train the model for 10 iterations (fit model to the data)\nmodel.fit(\n    X_train, \n    y_train, \n    epochs=10\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:09:32.309379Z","iopub.execute_input":"2026-06-09T20:09:32.309630Z","iopub.status.idle":"2026-06-09T20:15:35.505812Z","shell.execute_reply.started":"2026-06-09T20:09:32.309611Z","shell.execute_reply":"2026-06-09T20:15:35.505188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Evaluate the accuracy\ntest_loss, test_acc = model.evaluate(X_val, y_val, verbose=2)\nprint(f\"Test accuracy: {test_acc}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:15:35.506800Z","iopub.execute_input":"2026-06-09T20:15:35.507313Z","iopub.status.idle":"2026-06-09T20:16:18.108760Z","shell.execute_reply.started":"2026-06-09T20:15:35.507289Z","shell.execute_reply":"2026-06-09T20:16:18.108098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Save model to working directory\nimport datetime\n\ndef save_model(model):\n    working_dir = \"/kaggle/working/\"\n    timestamp = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    filepath = f\"{working_dir}model-{timestamp}.keras\"\n    \n    model.save(filepath)\n    \n    print(f\"Saved model at {filepath}\")\n\nsave_model(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-09T20:25:22.289262Z","iopub.execute_input":"2026-06-09T20:25:22.289888Z","iopub.status.idle":"2026-06-09T20:25:22.987194Z","shell.execute_reply.started":"2026-06-09T20:25:22.289860Z","shell.execute_reply":"2026-06-09T20:25:22.986397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Load a saved model\ndef load_model(path):\n    model = tf.keras.models.load_model(path)\n    print(f\"Loaded model from {path}\")\n    return model","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"<h2>Model index</h2>\n- /kaggle/working/model-20260609-202522.keras = Custom Xception model, trained 10 epochs<br>\n64/64 - 38s - 588ms/step - accuracy: 0.8699 - loss: 0.6057\nTest accuracy: 0.8699266314506531\n\n- ","metadata":{}},{"cell_type":"markdown","source":"<h1>Additional stuff</h1>","metadata":{}},{"cell_type":"markdown","source":"**Future**\n- Evaluate based on accuracy, precision, recall, etc.\n- Visualization\n- True positives, false positives, false negatives, true negatives\n- Try other pretrained models\n- Create artificial data to supplement the existing training data by rotating, cropping , scaling pictures?\n- Make predictions on test data?? - Not sure if that's possible, there are no labels to compare the model's predictions with","metadata":{}},{"cell_type":"markdown","source":"**Helpful links**\n- https://www.geeksforgeeks.org/python/introduction-to-tensorflow/\n- https://www.datacamp.com/tutorial/introduction-to-convolutional-neural-networks-cnns\n- https://www.datacamp.com/tutorial/convolutional-neural-networks-python\n- https://www.datacamp.com/tutorial/cnn-tensorflow-python\n- https://keras.io/api/applications/\n- https://keras.io/api/applications/#usage-examples-for-image-classification-models\n- https://keras.io/guides/transfer_learning/#transfer-learning-amp-finetuning\n\n**Legacy links**\n\n- https://scikit-learn.org/stable/modules/svm.html\n- https://www.geeksforgeeks.org/machine-learning/classifying-data-using-support-vector-machinessvms-in-python/\n- http://vision.stanford.edu/aditya86/ImageNetDogs/","metadata":{}}]}