mantel/documents/kohonen.ipynb
2024-06-11 21:08:08 +10:00

275 lines
44 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Self Organising Map Challenge\n",
"\n",
"## The Kohonen Network\n",
"\n",
"The Kohonen Self Organising Map (SOM) provides a data visualization technique which helps to understand high dimensional data by reducing the dimensions of data to a map. SOM also represents clustering concept by grouping similar data together.\n",
"\n",
"Unlike other learning technique in neural networks, training a SOM requires no target vector. A SOM learns to classify the training data without any external supervision.\n",
"\n",
"![Network](http://www.pitt.edu/~is2470pb/Spring05/FinalProjects/Group1a/tutorial/kohonen1.gif)\n",
"\n",
"### Structure\n",
"A network has a width and a height that descibes the grid of nodes. For example, the grid may be 4x4, and so there would be 16 nodes.\n",
"\n",
"Each node has a weight for each value in the input vector. A weight is simply a float value that the node multiplies the input value by to determine how influential it is (see below)\n",
"\n",
"Each node has a set of weights that match the size of the input vector. For example, if the input vector has 10 elements, each node would have 10 weights.\n",
"\n",
"### Training \n",
"To train the network\n",
"\n",
"1. Each node's weights are initialized.\n",
"2. We enumerate through the training data for some number of iterations (repeating if necessary). The current value we are training against will be referred to as the `current input vector`\n",
"3. Every node is examined to calculate which one's weights are most like the input vector. The winning node is commonly known as the Best Matching Unit (BMU).\n",
"4. The radius of the neighbourhood of the BMU is now calculated. This is a value that starts large, typically set to the 'radius' of the lattice, but diminishes each time-step. Any nodes found within this radius are deemed to be inside the BMU's neighbourhood.\n",
"5. Each neighbouring node's (the nodes found in step 4) weights are adjusted to make them more like the input vector. The closer a node is to the BMU, the more its weights get altered.\n",
"6. Go to step 2 until we've completed N iterations.\n",
" \n",
"\n",
"### Calculating the Best Matching Unit (BMU)\n",
"\n",
"To determine the best matching unit, one method is to iterate through all the nodes and calculate the Euclidean distance between each node's weight vector and the current input vector. The node with a weight vector closest to the input vector is tagged as the BMU.\n",
"\n",
"The Euclidean distance $\\mathsf{distance}_{i}$ (from the input vector $V$ to the $i$th node's weights $W_i$)is given as (using Pythagoras):\n",
"\n",
"$$ \\mathsf{distance}_{i}=\\sqrt{\\sum_{k=0}^{k=n}(V_k - W_{i_k})^2}$$\n",
"\n",
"where V is the current input vector and $W_i$ is the node's weight vector. $n$ is the size of the input & weight vector.\n",
"\n",
"*Note*: $V$ and $W$ are vectors. $V$ is the input vector, and $W_i$ is the weight vector of the $i$th node. $V_k$ and $W_{i_k}$ represent the $k$'th value within those vectors. \n",
"\n",
"The BMU is the node with the minimal distance for the current input vector\n",
"\n",
"### Calculating the Neighbourhood Radius\n",
"\n",
"The next step is to calculate which of the other nodes are within the BMU's neighbourhood. All these nodes will have their weight vectors altered.\n",
"\n",
"First we calculate what the radius of the neighbourhood should be and then use Pythagoras to determine if each node is within the radial distance or not.\n",
"\n",
"A unique feature of the Kohonen learning algorithm is that the area of the neighbourhood shrinks over time. To do this we use the exponential decay function:\n",
"\n",
"Given a desired number of training iterations $n$:\n",
"$$n_{\\mathsf{max iterations}} = 100$$\n",
"\n",
"Calculate the radius $\\sigma_t$ at iteration number $t$:\n",
"\n",
"$$\\sigma_t = \\sigma_0 \\exp\\left(- \\frac{t}{\\lambda} \\right) \\qquad t = 1,2,3,4... $$\n",
"\n",
"Where $\\sigma_0$ denotes the neighbourhood radius at iteration $t=0$, $t$ is the current iteration. We define $\\sigma_0$ (the initial radius) and $\\lambda$ (the time constant) as below:\n",
"\n",
"$$\\sigma_0 = \\frac{\\max(width,height)}{2} \\qquad \\lambda = \\frac{n_{\\mathsf{max iterations}}}{\\log(\\sigma_0)} $$\n",
"\n",
"Where $width$ & $height$ are the width and height of the grid.\n",
"\n",
"### Calculating the Learning Rate\n",
"\n",
"We define the initial leanring rate $\\alpha_0$ at iteration $t = 0$ as:\n",
"$$\\alpha_0 = 0.1$$\n",
"\n",
"So, we can calculate the learning rate at a given iteration t as:\n",
"\n",
"$$\\alpha_t = \\alpha_0 \\exp \\left(- \\frac{t}{\\lambda} \\right) $$\n",
"\n",
"where $t$ is the iteration number, $\\lambda$ is the time constant (calculated above)\n",
" \n",
"### Calculating the Influence\n",
"\n",
"As well as the learning rate, we need to calculate the influence $\\theta_t$ of the learning/training at a given iteration $t$. \n",
"\n",
"So for each node, we need to caclulate the euclidean distance $d_i$ from the BMU to that node. Similar to when we calculate the distance to find the BMU, we use Pythagoras. The current ($i$th) node's x position is given by $x(W_i)$, and the BMU's x position is, likewise, given by $x(Z)$. Similarly, $y()$ returns the y position of a node.\n",
"\n",
"$$ d_{i}=\\sqrt{(x(W_i) - x(Z))^2 + (y(W_i) - y(Z))^2} $$\n",
"\n",
"Then, the influence decays over time according to:\n",
"\n",
"$$\\theta_t = \\exp \\left( - \\frac{d_{i}^2}{2\\sigma_t^2} \\right) $$\n",
"\n",
"Where $\\sigma_t$ is the neighbourhood radius at iteration $t$ as calculated above. \n",
"\n",
"Note: You will need to come up with an approach to x() and y().\n",
"\n",
"\n",
"### Updating the Weights\n",
"\n",
"To update the weights of a given node, we use:\n",
"\n",
"$$W_{i_{t+1}} = W_{i_t} + \\alpha_t \\theta_t (V_t - W_{i_t})$$\n",
" \n",
"So $W_{i_{t+1}}$ is the new value of the weight for the $i$th node, $V_t$ is the current value of the training data, $W_{i_t}$ is the current weight and $\\alpha_t$ and $\\theta_t$ are the learning rate and influence calculated above.\n",
"\n",
"*Note*: the $W$ and $V$ are vectors "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Example 10x10 network after 100 iterations"
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.image.AxesImage at 0x7fce01327978>"
]
},
"execution_count": 147,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"plt.imshow(image_data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Example 100x100 network after 1000 iterations"
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.image.AxesImage at 0x7fce012f8208>"
]
},
"execution_count": 141,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"plt.imshow(image_data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Challenge\n",
"\n",
"Sam has written an implementation of a Self Organising Map. Consider the following criteria when assessing Sam's code:\n",
"\n",
"- Could the code be made more efficient? A literal interpretation of the instructions above is not necessary.\n",
"- Is the code best structured for later use by other developers and in anticipation of productionisation?\n",
"- How would you approach productionising this application?\n",
"- Anything else you think is relevant."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# kohonen.py\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"def train(input_data, n_max_iterations, width, height):\n",
" σ0 = max(width, height) / 2\n",
" α0 = 0.1\n",
" weights = np.random.random((width, height, 3))\n",
" λ = n_max_iterations / np.log(σ0)\n",
" for t in range(n_max_iterations):\n",
" σt = σ0 * np.exp(-t/λ)\n",
" αt = α0 * np.exp(-t/λ)\n",
" for vt in input_data:\n",
" bmu = np.argmin(np.sum((weights - vt) ** 2, axis=2))\n",
" bmu_x, bmu_y = np.unravel_index(bmu, (width, height))\n",
" for x in range(width):\n",
" for y in range(height):\n",
" di = np.sqrt(((x - bmu_x) ** 2) + ((y - bmu_y) ** 2))\n",
" θt = np.exp(-(di ** 2) / (2*(σt ** 2)))\n",
" weights[x, y] += αt * θt * (vt - weights[x, y])\n",
" return weights\n",
"\n",
"if __name__ == '__main__':\n",
" # Generate data\n",
" input_data = np.random.random((10,3))\n",
" image_data = train(input_data, 100, 10, 10)\n",
"\n",
" plt.imsave('100.png', image_data)\n",
"\n",
" # Generate data\n",
" input_data = np.random.random((10,3))\n",
" image_data = train(input_data, 1000, 100, 100)\n",
"\n",
" plt.imsave('1000.png', image_data)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}