From 3c62326506a560e3b0fbf4ac613d75d759118108 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:36:47 -0400 Subject: [PATCH 01/26] New practice exercise - Perceptron --- config.json | 15 +++++++ .../practice/perceptron/.docs/instructions.md | 34 +++++++++++++++ .../practice/perceptron/.meta/config.json | 20 +++++++++ .../practice/perceptron/.meta/example.jl | 8 ++++ .../practice/perceptron/.meta/tests.toml | 25 +++++++++++ exercises/practice/perceptron/perceptron.jl | 3 ++ exercises/practice/perceptron/runtests.jl | 43 +++++++++++++++++++ exercises/practice/perceptron/testtools.jl | 43 +++++++++++++++++++ 8 files changed, 191 insertions(+) create mode 100644 exercises/practice/perceptron/.docs/instructions.md create mode 100644 exercises/practice/perceptron/.meta/config.json create mode 100644 exercises/practice/perceptron/.meta/example.jl create mode 100644 exercises/practice/perceptron/.meta/tests.toml create mode 100644 exercises/practice/perceptron/perceptron.jl create mode 100644 exercises/practice/perceptron/runtests.jl create mode 100644 exercises/practice/perceptron/testtools.jl diff --git a/config.json b/config.json index fdf85667..e523bff8 100644 --- a/config.json +++ b/config.json @@ -883,6 +883,21 @@ "conditionals", "variables" ] + }, + { + "uuid": "b43a938a-7bd2-4fe4-b16c-731e2e25e747", + "practices": [], + "prerequisites": [], + "slug": "perceptron", + "name": "Perceptron", + "difficulty": 3, + "topics": [ + "machine learning", + "loops", + "arrays", + "logic", + "math" + ] } ] }, diff --git a/exercises/practice/perceptron/.docs/instructions.md b/exercises/practice/perceptron/.docs/instructions.md new file mode 100644 index 00000000..10a68d57 --- /dev/null +++ b/exercises/practice/perceptron/.docs/instructions.md @@ -0,0 +1,34 @@ +# Instructions + +### Introduction +[Perceptron](https://en.wikipedia.org/wiki/Perceptron) is one of the oldest and bestestly named machine learning algorithms out there. Since perceptron is also quite simple to implement, it's a favorite place to start a machine learning journey. As a linear classifier, if a linear decision boundary (e.g. a line in 2D or hyperplane in general) can be drawn to separate two labled classes of objects, perceptron is guaranteed to do so. This can help in predicting what an unlabeled object would likely be classified as by seeing which side of the decision boundary it is on. + +### Details +The basic idea is fairly straightforward. We cycle through the objects and check if they are on the correct side of our hyperplane. If one is not, we make a correction to the hyperplane and continue checking the objects against the new hyperplane. Eventually the hyperplane is adjusted to correctly separate all the objects and we have our decision boundary! + +#### A Brief Word on Hyperplanes +How do you pick your starting hyperplane? It's up to you! Be creative! Or not... Actually perceptron's convergence times are sensitive to conditions such as the initial hyperplane and even the order the objects are looped through, so you might not want to go too wild. + +We will be dealing with a two dimensional space, so our divider will be a line. The standard equation for a line is usually written as $y = ax+b$, where $a,b \in \Re$, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as $w_0 + w_1x + w_2y = 0$. This is the form of the [hyperplane](https://en.wikipedia.org/wiki/Hyperplane) we will be using, so your output should be $[w_0, w_1, w_2]$. In machine learning, ${w_0,w_1,w_2}$ are usually referred to as weights. + +While hyperplanes are equivalent under scalar multiplication, there is a difference between $[w_0, w_1, w_2]$ and $[-w_0, -w_1, -w_2]$ in that the normal to the hyperplane points in opposite directions. By convention, the perceptron normal points towards the class defined as positive, so this property will be checked but not result in a test failure. + +#### Updating +Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects $v_1,v_2$ in relation to the hyperplane $[w_0, w_1, w_2] = [1, 1, 1]$: + +$$v_1$$ $$[x_1, y_1] = [2, 2]$$ $$w_0 + w_1*x_1 + w_2*y_1 = 1 + 1*2 + 1*2 = 5 > 0$$ + + +$$v_2$$ $$[x_2,y_2]=[-2,-2]$$ $$w_0 + w_1*x_2 + w_2*y_2 = 1 + 1*(-2) + 1*(-2) = -3 < 0$$ + +If $v_1$ and $v_2$ have different labels, such as $1$ and $-1$ (like we will be using), then the hyperplane $[1, 1, 1]$ is a valid decision boundary for them. + +Now that we know how to tell which side of the hyperplane an object lies on, we can look at how perceptron updates a hyperplane. If an object is on the correct side of the hyperplane, no update is performed on the weights. However, if we find an object on the wrong side, the update rule for the weights is: + +$$[w_0', w_1', w_2'] = [w_0 \pm l_{class}, w_1 \pm x*l_{class}, w_2 \pm y*l_{class}]$$ + +Where $l_{class}=\pm 1$, according to the class of the object (i.e. its label), $x,y$ are the coordinates of the object, the $w_i$ are the weights of the hyperplane and the $w_i'$ are the weights of the updated hyperplane. The plus or minus signs are homogenous, so either all plus or all minus, and are determined by the choice of which class you define to be on the positive side of the hyperplane. Beware that only two out of the four possible combinations of class on positive side of the hyperplane and the plus/minus in the update are valid ($\pm \pm, \mp \mp$), with the other two ($\pm \mp, \mp \pm$) leading to infinite loops. + +This update is repeated for each object in turn, and then the whole process repeated until there are no updates made to the hyperplane. All objects passing without an update means they have been successfully separated and you can return your decision boundary! + +Note: Although the perceptron algorithm is deterministic, a decision boundary depends on initialization and is not unique in general, so the tests accept any hyperplane which fully separates the objects. \ No newline at end of file diff --git a/exercises/practice/perceptron/.meta/config.json b/exercises/practice/perceptron/.meta/config.json new file mode 100644 index 00000000..278526a3 --- /dev/null +++ b/exercises/practice/perceptron/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "depial" + ], + "contributors": [ + "cmcaine" + ], + "files": { + "solution": [ + "perceptron.jl" + ], + "test": [ + "runtests.jl" + ], + "example": [ + ".meta/example.jl" + ] + }, + "blurb": "Given points and their labels, provide a hyperplane which separates them" +} diff --git a/exercises/practice/perceptron/.meta/example.jl b/exercises/practice/perceptron/.meta/example.jl new file mode 100644 index 00000000..47a48073 --- /dev/null +++ b/exercises/practice/perceptron/.meta/example.jl @@ -0,0 +1,8 @@ +function perceptron(points, labels) + θ, pnts = [0, 0, 0], vcat.(1, points) + while true + θ_0 = θ + foreach(i -> labels[i]*θ'*pnts[i] ≤ 0 && (θ += labels[i]*pnts[i]), eachindex(pnts)) + θ_0 == θ && return θ + end +end \ No newline at end of file diff --git a/exercises/practice/perceptron/.meta/tests.toml b/exercises/practice/perceptron/.meta/tests.toml new file mode 100644 index 00000000..5c49832e --- /dev/null +++ b/exercises/practice/perceptron/.meta/tests.toml @@ -0,0 +1,25 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[728853d3-24de-4855-a452-6520b67dec23] +description = "Initial set" + +[ed5bf871-3923-47ca-8346-5d640f9069a0] +description = "Initial set w/ opposite labels" + +[15a9860e-f9be-46b1-86b2-989bd878c8a5] +description = "Hyperplane cannot pass through origin" + +[52ba77fc-8983-4429-91dc-e64b2f625484] +description = "Hyperplane nearly parallel with y-axis" + +[3e758bbd-5f72-447d-999f-cfa60b27bc26] +description = "Increasing Populations" \ No newline at end of file diff --git a/exercises/practice/perceptron/perceptron.jl b/exercises/practice/perceptron/perceptron.jl new file mode 100644 index 00000000..0945cdea --- /dev/null +++ b/exercises/practice/perceptron/perceptron.jl @@ -0,0 +1,3 @@ +function perceptron(points, labels) + # Perceptronize! +end \ No newline at end of file diff --git a/exercises/practice/perceptron/runtests.jl b/exercises/practice/perceptron/runtests.jl new file mode 100644 index 00000000..2a38df3d --- /dev/null +++ b/exercises/practice/perceptron/runtests.jl @@ -0,0 +1,43 @@ +using Test + +include("perceptron.jl") +include("testtools.jl") + +@testset "Low population" begin + @testset "Initial set" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [1, 2, 1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Initial set w/ opposite labels" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [-1, -1, 1, 1, -1, -1] + reference = [-1, -2, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane cannot pass through origin" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [-1, -1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [-1, 3, 3] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane nearly parallel with y-axis" begin + points = [[0, 50], [0, -50], [-2, 0], [1, 50], [1, -50], [2, 0]] + labels = [-1, -1, -1, 1, 1, 1] + reference = [2, 0, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end +end + +@testset "Increasing Populations" begin + for n in 10:50 + points, labels, reference = population(n, 25) + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end +end \ No newline at end of file diff --git a/exercises/practice/perceptron/testtools.jl b/exercises/practice/perceptron/testtools.jl new file mode 100644 index 00000000..edb21e15 --- /dev/null +++ b/exercises/practice/perceptron/testtools.jl @@ -0,0 +1,43 @@ +using Random + +function dotest(points, labels, hyperplane, reference) + # Tests if a hyperplane linearly separates labeled points + # Returns true or false + + points = vcat.(1, points) + test = reduce(hcat, points)' * hyperplane .* labels + if all(>(0), test) + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! And the normal points towards the positively labeled side\n") + return true + elseif all(<(0), test) + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! But the normal points towards the negatively labeled side\n") + return true + else + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nThe sides are not properly separated...\n") + return false + end +end + +Random.seed!(42) # set seed for deterministic test set + +function population(n, bound) + # Builds a population of n points with labels {1, -1} in area bound x bound around a reference hyperplane + # Returns linearly separable points, labels and reference hyperplane + + vertical = !iszero(n % 10) #every tenth test has vertical reference hyperplane + x, y, b = rand(-bound:bound), rand(-bound:bound)*vertical, rand(-bound÷2:bound÷2) + y_intercept = -b ÷ (iszero(y) ? 1 : y) + points, labels, hyperplane = [], [], [b, x, y] + while n > 0 + # points are centered on y-intercept, but not x-intercept so distributions can be lopsided + point = [rand(-bound:bound), y_intercept + rand(-bound:bound)] + label = point' * [x, y] + b + if !iszero(label) + push!(points, point) + push!(labels, sign(label)) + n -= 1 + end + end + + points, labels, hyperplane +end \ No newline at end of file From 053cb6377e4741c1ed3400f171f3781e54d3808e Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:05:23 -0500 Subject: [PATCH 02/26] Update runtests.jl Added helper functions from `testtools.jl` to bottom of file so it doesn't have to be included. Since the `dotest` function does not show a working solution (only how to check for one), I think a spoiler warning is unnecessary, and could just end up attracting more attention than without. --- exercises/practice/perceptron/runtests.jl | 46 +++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/exercises/practice/perceptron/runtests.jl b/exercises/practice/perceptron/runtests.jl index 2a38df3d..be5de247 100644 --- a/exercises/practice/perceptron/runtests.jl +++ b/exercises/practice/perceptron/runtests.jl @@ -1,7 +1,6 @@ -using Test +using Test, Random include("perceptron.jl") -include("testtools.jl") @testset "Low population" begin @testset "Initial set" begin @@ -40,4 +39,45 @@ end hyperplane = perceptron(points, labels) @test dotest(points, labels, hyperplane, reference) end -end \ No newline at end of file +end + + + +Random.seed!(42) # set seed for deterministic test set + +function population(n, bound) + # Builds a population of n points with labels {1, -1} in area bound x bound around a reference hyperplane + # Returns linearly separable points, labels and reference hyperplane + + vertical = !iszero(n % 10) #every tenth test has vertical reference hyperplane + x, y, b = rand(-bound:bound), rand(-bound:bound)*vertical, rand(-bound÷2:bound÷2) + y_intercept = -b ÷ (iszero(y) ? 1 : y) + points, labels, hyperplane = [], [], [b, x, y] + while n > 0 + # points are centered on y-intercept, but not x-intercept so distributions can be lopsided + point = [rand(-bound:bound), y_intercept + rand(-bound:bound)] + label = point' * [x, y] + b + if !iszero(label) + push!(points, point) + push!(labels, sign(label)) + n -= 1 + end + end + + points, labels, hyperplane +end + +function dotest(points, labels, hyperplane, reference) + points = vcat.(1, points) + test = reduce(hcat, points)' * hyperplane .* labels + if all(>(0), test) + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! And the normal points towards the positively labeled side\n") + return true + elseif all(<(0), test) + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! But the normal points towards the negatively labeled side\n") + return true + else + println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nThe sides are not properly separated...\n") + return false + end +end From c821340dede9dec9f93469e9ec3ef1314f696924 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:28:38 -0500 Subject: [PATCH 03/26] Update runtests.jl Tests still failed with helper functions at the bottom of the file, so I'm moving them to the top to see if that changes things. --- exercises/practice/perceptron/runtests.jl | 80 +++++++++++------------ 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/exercises/practice/perceptron/runtests.jl b/exercises/practice/perceptron/runtests.jl index be5de247..bf7f7f6e 100644 --- a/exercises/practice/perceptron/runtests.jl +++ b/exercises/practice/perceptron/runtests.jl @@ -2,47 +2,6 @@ using Test, Random include("perceptron.jl") -@testset "Low population" begin - @testset "Initial set" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] - labels = [1, 1, -1, -1, 1, 1] - reference = [1, 2, 1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Initial set w/ opposite labels" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] - labels = [-1, -1, 1, 1, -1, -1] - reference = [-1, -2, -1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Hyperplane cannot pass through origin" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [-1, -1]] - labels = [1, 1, -1, -1, 1, 1] - reference = [-1, 3, 3] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Hyperplane nearly parallel with y-axis" begin - points = [[0, 50], [0, -50], [-2, 0], [1, 50], [1, -50], [2, 0]] - labels = [-1, -1, -1, 1, 1, 1] - reference = [2, 0, -1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end -end - -@testset "Increasing Populations" begin - for n in 10:50 - points, labels, reference = population(n, 25) - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end -end - - - Random.seed!(42) # set seed for deterministic test set function population(n, bound) @@ -81,3 +40,42 @@ function dotest(points, labels, hyperplane, reference) return false end end + +@testset "Low population" begin + @testset "Initial set" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [1, 2, 1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Initial set w/ opposite labels" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [-1, -1, 1, 1, -1, -1] + reference = [-1, -2, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane cannot pass through origin" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [-1, -1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [-1, 3, 3] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane nearly parallel with y-axis" begin + points = [[0, 50], [0, -50], [-2, 0], [1, 50], [1, -50], [2, 0]] + labels = [-1, -1, -1, 1, 1, 1] + reference = [2, 0, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end +end + +@testset "Increasing Populations" begin + for n in 10:50 + points, labels, reference = population(n, 25) + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end +end From a4bc759940c1c92390b6428b370db595c9baac28 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:22:16 -0500 Subject: [PATCH 04/26] Update runtests.jl Wrapped the test set in a function to be called at the end of the file so the helper functions can be put at the bottom of the file. --- exercises/practice/perceptron/runtests.jl | 85 ++++++++++++----------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/exercises/practice/perceptron/runtests.jl b/exercises/practice/perceptron/runtests.jl index bf7f7f6e..56363cc1 100644 --- a/exercises/practice/perceptron/runtests.jl +++ b/exercises/practice/perceptron/runtests.jl @@ -1,8 +1,49 @@ using Test, Random - include("perceptron.jl") -Random.seed!(42) # set seed for deterministic test set +function runtestset() + + @testset "Low population" begin + @testset "Initial set" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [1, 2, 1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Initial set w/ opposite labels" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] + labels = [-1, -1, 1, 1, -1, -1] + reference = [-1, -2, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane cannot pass through origin" begin + points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [-1, -1]] + labels = [1, 1, -1, -1, 1, 1] + reference = [-1, 3, 3] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + @testset "Hyperplane nearly parallel with y-axis" begin + points = [[0, 50], [0, -50], [-2, 0], [1, 50], [1, -50], [2, 0]] + labels = [-1, -1, -1, 1, 1, 1] + reference = [2, 0, -1] + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + end + + @testset "Increasing Populations" begin + for n in 10:50 + points, labels, reference = population(n, 25) + hyperplane = perceptron(points, labels) + @test dotest(points, labels, hyperplane, reference) + end + end + +end + function population(n, bound) # Builds a population of n points with labels {1, -1} in area bound x bound around a reference hyperplane @@ -41,41 +82,5 @@ function dotest(points, labels, hyperplane, reference) end end -@testset "Low population" begin - @testset "Initial set" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] - labels = [1, 1, -1, -1, 1, 1] - reference = [1, 2, 1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Initial set w/ opposite labels" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [1, 1]] - labels = [-1, -1, 1, 1, -1, -1] - reference = [-1, -2, -1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Hyperplane cannot pass through origin" begin - points = [[1, 2], [3, 4], [-1, -2], [-3, -4], [2, 1], [-1, -1]] - labels = [1, 1, -1, -1, 1, 1] - reference = [-1, 3, 3] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end - @testset "Hyperplane nearly parallel with y-axis" begin - points = [[0, 50], [0, -50], [-2, 0], [1, 50], [1, -50], [2, 0]] - labels = [-1, -1, -1, 1, 1, 1] - reference = [2, 0, -1] - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end -end - -@testset "Increasing Populations" begin - for n in 10:50 - points, labels, reference = population(n, 25) - hyperplane = perceptron(points, labels) - @test dotest(points, labels, hyperplane, reference) - end -end +Random.seed!(42) # set seed for deterministic test set +runtestset() From 185e88c25c2297cf807d1ebd27a03d1b1502a872 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:23:37 -0500 Subject: [PATCH 05/26] Delete exercises/practice/perceptron/testtools.jl Deleting since test helper functions have been moved to `runtests.jl` --- exercises/practice/perceptron/testtools.jl | 43 ---------------------- 1 file changed, 43 deletions(-) delete mode 100644 exercises/practice/perceptron/testtools.jl diff --git a/exercises/practice/perceptron/testtools.jl b/exercises/practice/perceptron/testtools.jl deleted file mode 100644 index edb21e15..00000000 --- a/exercises/practice/perceptron/testtools.jl +++ /dev/null @@ -1,43 +0,0 @@ -using Random - -function dotest(points, labels, hyperplane, reference) - # Tests if a hyperplane linearly separates labeled points - # Returns true or false - - points = vcat.(1, points) - test = reduce(hcat, points)' * hyperplane .* labels - if all(>(0), test) - println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! And the normal points towards the positively labeled side\n") - return true - elseif all(<(0), test) - println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nSeparated! But the normal points towards the negatively labeled side\n") - return true - else - println("Reference hyperplane = $reference\nYour hyperplane = $hyperplane\nThe sides are not properly separated...\n") - return false - end -end - -Random.seed!(42) # set seed for deterministic test set - -function population(n, bound) - # Builds a population of n points with labels {1, -1} in area bound x bound around a reference hyperplane - # Returns linearly separable points, labels and reference hyperplane - - vertical = !iszero(n % 10) #every tenth test has vertical reference hyperplane - x, y, b = rand(-bound:bound), rand(-bound:bound)*vertical, rand(-bound÷2:bound÷2) - y_intercept = -b ÷ (iszero(y) ? 1 : y) - points, labels, hyperplane = [], [], [b, x, y] - while n > 0 - # points are centered on y-intercept, but not x-intercept so distributions can be lopsided - point = [rand(-bound:bound), y_intercept + rand(-bound:bound)] - label = point' * [x, y] + b - if !iszero(label) - push!(points, point) - push!(labels, sign(label)) - n -= 1 - end - end - - points, labels, hyperplane -end \ No newline at end of file From 475efe316af502b0a7ac6f92a14f0a6a1c87aa5e Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Sun, 4 Feb 2024 08:09:56 -0500 Subject: [PATCH 06/26] Update config.json Better blurb --- exercises/practice/perceptron/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/perceptron/.meta/config.json b/exercises/practice/perceptron/.meta/config.json index 278526a3..713bd931 100644 --- a/exercises/practice/perceptron/.meta/config.json +++ b/exercises/practice/perceptron/.meta/config.json @@ -16,5 +16,5 @@ ".meta/example.jl" ] }, - "blurb": "Given points and their labels, provide a hyperplane which separates them" + "blurb": "Write your own machine learning classifier" } From 4a24b2c207cd168ac81bbbb5208c2060bcc1651a Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Sun, 4 Feb 2024 08:21:35 -0500 Subject: [PATCH 07/26] Update tests.toml Delete auto-generated comment as these were manually created --- exercises/practice/perceptron/.meta/tests.toml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/exercises/practice/perceptron/.meta/tests.toml b/exercises/practice/perceptron/.meta/tests.toml index 5c49832e..657cb537 100644 --- a/exercises/practice/perceptron/.meta/tests.toml +++ b/exercises/practice/perceptron/.meta/tests.toml @@ -1,14 +1,3 @@ -# This is an auto-generated file. -# -# Regenerating this file via `configlet sync` will: -# - Recreate every `description` key/value pair -# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications -# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) -# - Preserve any other key/value pair -# -# As user-added comments (using the # character) will be removed when this file -# is regenerated, comments can be added via a `comment` key. - [728853d3-24de-4855-a452-6520b67dec23] description = "Initial set" @@ -22,4 +11,4 @@ description = "Hyperplane cannot pass through origin" description = "Hyperplane nearly parallel with y-axis" [3e758bbd-5f72-447d-999f-cfa60b27bc26] -description = "Increasing Populations" \ No newline at end of file +description = "Increasing Populations" From e4760e2416b95ae0e115bb0e7dfd0d429208fce8 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Sun, 4 Feb 2024 10:52:02 -0500 Subject: [PATCH 08/26] Update instructions.md Updates to instructions including clarifications and an added gif from https://commons.wikimedia.org/wiki/File:Perceptron_training_without_bias.gif --- .../practice/perceptron/.docs/instructions.md | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/exercises/practice/perceptron/.docs/instructions.md b/exercises/practice/perceptron/.docs/instructions.md index 10a68d57..24f6ad6a 100644 --- a/exercises/practice/perceptron/.docs/instructions.md +++ b/exercises/practice/perceptron/.docs/instructions.md @@ -1,34 +1,50 @@ # Instructions ### Introduction -[Perceptron](https://en.wikipedia.org/wiki/Perceptron) is one of the oldest and bestestly named machine learning algorithms out there. Since perceptron is also quite simple to implement, it's a favorite place to start a machine learning journey. As a linear classifier, if a linear decision boundary (e.g. a line in 2D or hyperplane in general) can be drawn to separate two labled classes of objects, perceptron is guaranteed to do so. This can help in predicting what an unlabeled object would likely be classified as by seeing which side of the decision boundary it is on. +[Perceptron](https://en.wikipedia.org/wiki/Perceptron) is one of the oldest and bestestly named machine learning algorithms out there. Since it is also quite simple to implement, it's a favorite place to start a machine learning journey. Perceptron is what is known as a linear classifier, which means that, if we have two labled classes of objects, for example in 2D space, it will search for a line that can be drawn to separate them. If such a line exists, Perceptron is guaranteed to find one. See Perceptron in action separating black and white dots below! + +
### Details -The basic idea is fairly straightforward. We cycle through the objects and check if they are on the correct side of our hyperplane. If one is not, we make a correction to the hyperplane and continue checking the objects against the new hyperplane. Eventually the hyperplane is adjusted to correctly separate all the objects and we have our decision boundary! +The basic idea is fairly straightforward. As illustrated above, we cycle through the objects and check if they are on the correct side of our guess at a line. If one is not, we make a correction and continue checking the objects against the corrected line. Eventually the line is adjusted to correctly separate all the objects and we have what is called a decision boundary! + +Why is this of any use? The decision boundary found can then help us in predicting what a new, unlabeled, object would likely be classified as by seeing which side of the boundary it is on. #### A Brief Word on Hyperplanes -How do you pick your starting hyperplane? It's up to you! Be creative! Or not... Actually perceptron's convergence times are sensitive to conditions such as the initial hyperplane and even the order the objects are looped through, so you might not want to go too wild. +What we have been calling a line in 2D can be generalized to something called a [hyperplane](https://en.wikipedia.org/wiki/Hyperplane), which is a convenient representation, and, if you follow the classic Perceptron algorithm, you will have to pick an initial hyperplane to start with. How do you pick your starting hyperplane? It's up to you! Be creative! Or not... Actually perceptron's convergence times are sensitive to conditions such as the initial hyperplane and even the order the objects are looped through, so you might not want to go too wild. -We will be dealing with a two dimensional space, so our divider will be a line. The standard equation for a line is usually written as $y = ax+b$, where $a,b \in \Re$, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as $w_0 + w_1x + w_2y = 0$. This is the form of the [hyperplane](https://en.wikipedia.org/wiki/Hyperplane) we will be using, so your output should be $[w_0, w_1, w_2]$. In machine learning, ${w_0,w_1,w_2}$ are usually referred to as weights. +We will be playing in a two dimensional space, so our separating hyperplane will simply be a 1D line. You might remember the standard equation for a line as $y = ax+b$, where $a,b \in \Re$, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as $w_0 + w_1x + w_2y = 0$. This is the form of the hyperplane we will be using, so your output should be $[w_0, w_1, w_2]$. In machine learning, ${w_0,w_1,w_2}$ are usually referred to as weights. -While hyperplanes are equivalent under scalar multiplication, there is a difference between $[w_0, w_1, w_2]$ and $[-w_0, -w_1, -w_2]$ in that the normal to the hyperplane points in opposite directions. By convention, the perceptron normal points towards the class defined as positive, so this property will be checked but not result in a test failure. +Scaling a hyperplane by a positive value gives an equivalent hyperplane (e.g. $[w_0, w_1, w_2] = [\alpha \cdot w_0, \alpha \cdot w_1, \alpha \cdot w_2]$ with $\alpha > 0$). However, it should be noted that there is a difference between the normal vectors (the green arrow in illustration above) of hyperplanes scaled by a negative value (e.g. $[w_0, w_1, w_2]$ vs $[-w_0, -w_1, -w_2]$) in that the normal to the negative hyperplane points in opposite direction of that of the positive one. By convention, the perceptron normal points towards the class defined as positive, so this property will be checked but not result in a test failure. #### Updating Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects $v_1,v_2$ in relation to the hyperplane $[w_0, w_1, w_2] = [1, 1, 1]$: -$$v_1$$ $$[x_1, y_1] = [2, 2]$$ $$w_0 + w_1*x_1 + w_2*y_1 = 1 + 1*2 + 1*2 = 5 > 0$$ +$$\large v_1$$ + +$$[x_1, y_1] = [2, 2]$$ + +$$w_0 + w_1 \cdot x_1 + w_2 \cdot y_1 = 1 + 1 \cdot 2 + 1 \cdot 2 = 5 > 0$$ + + +$$\large v_2$$ +$$[x_2,y_2]=[-2,-2]$$ -$$v_2$$ $$[x_2,y_2]=[-2,-2]$$ $$w_0 + w_1*x_2 + w_2*y_2 = 1 + 1*(-2) + 1*(-2) = -3 < 0$$ +$$w_0 + w_1 \cdot x_2 + w_2 \cdot y_2 = 1 + 1 \cdot (-2) + 1 \cdot (-2) = -3 < 0$$ -If $v_1$ and $v_2$ have different labels, such as $1$ and $-1$ (like we will be using), then the hyperplane $[1, 1, 1]$ is a valid decision boundary for them. +If $v_1$ and $v_2$ have the labels $1$ and $-1$ (like we will be using), then the hyperplane $[1, 1, 1]$ is a valid decision boundary for them since the signs match. Now that we know how to tell which side of the hyperplane an object lies on, we can look at how perceptron updates a hyperplane. If an object is on the correct side of the hyperplane, no update is performed on the weights. However, if we find an object on the wrong side, the update rule for the weights is: -$$[w_0', w_1', w_2'] = [w_0 \pm l_{class}, w_1 \pm x*l_{class}, w_2 \pm y*l_{class}]$$ +$$[w_0', w_1', w_2'] = [w_0 + l_{class}, w_1 + x \cdot l_{class}, w_2 + y \cdot l_{class}]$$ -Where $l_{class}=\pm 1$, according to the class of the object (i.e. its label), $x,y$ are the coordinates of the object, the $w_i$ are the weights of the hyperplane and the $w_i'$ are the weights of the updated hyperplane. The plus or minus signs are homogenous, so either all plus or all minus, and are determined by the choice of which class you define to be on the positive side of the hyperplane. Beware that only two out of the four possible combinations of class on positive side of the hyperplane and the plus/minus in the update are valid ($\pm \pm, \mp \mp$), with the other two ($\pm \mp, \mp \pm$) leading to infinite loops. +Where $l_{class}=\pm 1$, according to the class of the object (i.e. its label), $x,y$ are the coordinates of the object, the $w_i$ are the weights of the hyperplane and the $w_i'$ are the weights of the updated hyperplane. This update is repeated for each object in turn, and then the whole process repeated until there are no updates made to the hyperplane. All objects passing without an update means they have been successfully separated and you can return your decision boundary! -Note: Although the perceptron algorithm is deterministic, a decision boundary depends on initialization and is not unique in general, so the tests accept any hyperplane which fully separates the objects. \ No newline at end of file +Notes: +- Although the perceptron algorithm is deterministic, a decision boundary depends on initialization and is not unique in general, so the tests accept any hyperplane which fully separates the objects. +- The tests here will only include linearly separable classes, so a decision boundary will always be possible (i.e. no need to worry about non-separable classes). From 39ba0fa5bbbdea9e9b31dea83a65cb855bd30c04 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Fri, 16 Feb 2024 08:48:31 -0500 Subject: [PATCH 09/26] Update instructions.md --- .../practice/perceptron/.docs/instructions.md | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/exercises/practice/perceptron/.docs/instructions.md b/exercises/practice/perceptron/.docs/instructions.md index 24f6ad6a..244d49bf 100644 --- a/exercises/practice/perceptron/.docs/instructions.md +++ b/exercises/practice/perceptron/.docs/instructions.md @@ -15,33 +15,29 @@ Why is this of any use? The decision boundary found can then help us in predicti #### A Brief Word on Hyperplanes What we have been calling a line in 2D can be generalized to something called a [hyperplane](https://en.wikipedia.org/wiki/Hyperplane), which is a convenient representation, and, if you follow the classic Perceptron algorithm, you will have to pick an initial hyperplane to start with. How do you pick your starting hyperplane? It's up to you! Be creative! Or not... Actually perceptron's convergence times are sensitive to conditions such as the initial hyperplane and even the order the objects are looped through, so you might not want to go too wild. -We will be playing in a two dimensional space, so our separating hyperplane will simply be a 1D line. You might remember the standard equation for a line as $y = ax+b$, where $a,b \in \Re$, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as $w_0 + w_1x + w_2y = 0$. This is the form of the hyperplane we will be using, so your output should be $[w_0, w_1, w_2]$. In machine learning, ${w_0,w_1,w_2}$ are usually referred to as weights. +We will be playing in a two dimensional space, so our separating hyperplane will simply be a 1D line. You might remember the standard equation for a line as `y = ax+b`, where `a,b ∈ R`, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as `w_0 + w_1x + w_2y = 0`. This is the form of the hyperplane we will be using, so your output should be `[w_0, w_1, w_2]`. In machine learning, the `{w_0, w_1, w_2}` are usually referred to as weights. -Scaling a hyperplane by a positive value gives an equivalent hyperplane (e.g. $[w_0, w_1, w_2] = [\alpha \cdot w_0, \alpha \cdot w_1, \alpha \cdot w_2]$ with $\alpha > 0$). However, it should be noted that there is a difference between the normal vectors (the green arrow in illustration above) of hyperplanes scaled by a negative value (e.g. $[w_0, w_1, w_2]$ vs $[-w_0, -w_1, -w_2]$) in that the normal to the negative hyperplane points in opposite direction of that of the positive one. By convention, the perceptron normal points towards the class defined as positive, so this property will be checked but not result in a test failure. +Scaling a hyperplane by a positive value gives an equivalent hyperplane (e.g. `[w_0, w_1, w_2] ≈ [c * w_0, c * w_1, c * w_2]` with `c > 0`), since an infinite line scaled by a value is just the same infinite line. However, it should be noted that there is a difference between the normal vectors (the green arrow in illustration above) associated with hyperplanes scaled by a negative value (e.g. `[w_0, w_1, w_2]` vs `[-w_0, -w_1, -w_2]`) in that the normal to the negative hyperplane points in opposite direction of that of the positive one. By convention, the perceptron normal points towards the class defined as positive. #### Updating -Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects $v_1,v_2$ in relation to the hyperplane $[w_0, w_1, w_2] = [1, 1, 1]$: +Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects `v_1, v_2` in relation to the hyperplane `[w_0, w_1, w_2] = [1, 1, 1]`: -$$\large v_1$$ +`v_1 = [x_1, y_1] = [2, 2]` -$$[x_1, y_1] = [2, 2]$$ +`w_0 + w_1 * x_1 + w_2 * y_1 = 1 + 1 * 2 + 1 * 2 = 5 > 0` -$$w_0 + w_1 \cdot x_1 + w_2 \cdot y_1 = 1 + 1 \cdot 2 + 1 \cdot 2 = 5 > 0$$ +`v_2 = [x_2, y_2] = [-2, -2]` -$$\large v_2$$ +`w_0 + w_1 * x_2 + w_2 * y_2 = 1 + 1 * (-2) + 1 * (-2) = -3 < 0` -$$[x_2,y_2]=[-2,-2]$$ - -$$w_0 + w_1 \cdot x_2 + w_2 \cdot y_2 = 1 + 1 \cdot (-2) + 1 \cdot (-2) = -3 < 0$$ - -If $v_1$ and $v_2$ have the labels $1$ and $-1$ (like we will be using), then the hyperplane $[1, 1, 1]$ is a valid decision boundary for them since the signs match. +If `v_1` and `v_2` have the labels `1` and `-1` (like we will be using), then the hyperplane `[1, 1, 1]` is a valid decision boundary for them since the signs match. Now that we know how to tell which side of the hyperplane an object lies on, we can look at how perceptron updates a hyperplane. If an object is on the correct side of the hyperplane, no update is performed on the weights. However, if we find an object on the wrong side, the update rule for the weights is: -$$[w_0', w_1', w_2'] = [w_0 + l_{class}, w_1 + x \cdot l_{class}, w_2 + y \cdot l_{class}]$$ +`[w_0', w_1', w_2'] = [w_0 + class, w_1 + x * class, w_2 + y * class]` -Where $l_{class}=\pm 1$, according to the class of the object (i.e. its label), $x,y$ are the coordinates of the object, the $w_i$ are the weights of the hyperplane and the $w_i'$ are the weights of the updated hyperplane. +Where `class ∈ {1,-1}`, according to the class of the object (i.e. its label), `x, y` are the coordinates of the object, the `w_i` are the weights of the hyperplane and the `w_i'` are the weights of the updated hyperplane. This update is repeated for each object in turn, and then the whole process repeated until there are no updates made to the hyperplane. All objects passing without an update means they have been successfully separated and you can return your decision boundary! From 735743f4be41e86de9c8202d9b7836a61d52cd6b Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Fri, 16 Feb 2024 12:46:11 -0500 Subject: [PATCH 10/26] Update instructions.md --- .../practice/perceptron/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/perceptron/.docs/instructions.md b/exercises/practice/perceptron/.docs/instructions.md index 244d49bf..f59d9ca6 100644 --- a/exercises/practice/perceptron/.docs/instructions.md +++ b/exercises/practice/perceptron/.docs/instructions.md @@ -15,29 +15,29 @@ Why is this of any use? The decision boundary found can then help us in predicti #### A Brief Word on Hyperplanes What we have been calling a line in 2D can be generalized to something called a [hyperplane](https://en.wikipedia.org/wiki/Hyperplane), which is a convenient representation, and, if you follow the classic Perceptron algorithm, you will have to pick an initial hyperplane to start with. How do you pick your starting hyperplane? It's up to you! Be creative! Or not... Actually perceptron's convergence times are sensitive to conditions such as the initial hyperplane and even the order the objects are looped through, so you might not want to go too wild. -We will be playing in a two dimensional space, so our separating hyperplane will simply be a 1D line. You might remember the standard equation for a line as `y = ax+b`, where `a,b ∈ R`, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as `w_0 + w_1x + w_2y = 0`. This is the form of the hyperplane we will be using, so your output should be `[w_0, w_1, w_2]`. In machine learning, the `{w_0, w_1, w_2}` are usually referred to as weights. +We will be playing in a two dimensional space, so our separating hyperplane will simply be a 1D line. You might remember the standard equation for a line as `y = ax+b`, where `a,b ∈ ℝ`, however, in order to help generalize the idea to higher dimensions, it's convenient to reformulate this equation as `w₀ + w₁x + w₂y = 0`. This is the form of the hyperplane we will be using, so your output should be `[w₀, w₁, w₂]`. In machine learning, the `{w₀, w₁, w₂}` are usually referred to as weights. -Scaling a hyperplane by a positive value gives an equivalent hyperplane (e.g. `[w_0, w_1, w_2] ≈ [c * w_0, c * w_1, c * w_2]` with `c > 0`), since an infinite line scaled by a value is just the same infinite line. However, it should be noted that there is a difference between the normal vectors (the green arrow in illustration above) associated with hyperplanes scaled by a negative value (e.g. `[w_0, w_1, w_2]` vs `[-w_0, -w_1, -w_2]`) in that the normal to the negative hyperplane points in opposite direction of that of the positive one. By convention, the perceptron normal points towards the class defined as positive. +Scaling a hyperplane by a positive value gives an equivalent hyperplane (e.g. `[w₀, w₁, w₂] ≈ [c⋅w₀, c⋅w₁, c⋅w₂]` with `c > 0`), since an infinite line scaled by a value is just the same infinite line. However, it should be noted that there is a difference between the normal vectors (the green arrow in illustration above) associated with hyperplanes scaled by a negative value (e.g. `[w₀, w₁, w₂]` vs `[-w₀, -w₁, -w₂]`) in that the normal to the negative hyperplane points in opposite direction of that of the positive one. By convention, the perceptron normal points towards the class defined as positive. #### Updating -Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects `v_1, v_2` in relation to the hyperplane `[w_0, w_1, w_2] = [1, 1, 1]`: +Checking if an object is on one side of a hyperplane or another can be done by checking the normal vector which points to the object. The value will be positive, negative or zero, so all of the objects from a class should have normal vectors with the same sign. A zero value means the object is on the hyperplane, which we don't want to allow since its ambiguous. Checking the sign of a normal to a hyperplane might sound like it could be complicated, but it's actually quite easy. Simply plug in the coordinates for the object into the equation for the hyperplane and check the sign of the result. For example, we can look at two objects `v₁, v₂` in relation to the hyperplane `[w₀, w₁, w₂] = [1, 1, 1]`: -`v_1 = [x_1, y_1] = [2, 2]` +`v₁ = [x₁, y₁] = [2, 2]` -`w_0 + w_1 * x_1 + w_2 * y_1 = 1 + 1 * 2 + 1 * 2 = 5 > 0` +`w₀ + w₁⋅x₁ + w₂⋅y₁ = 1 + 1⋅2 + 1⋅2 = 5 > 0` -`v_2 = [x_2, y_2] = [-2, -2]` +`v₂ = [x₂, y₂] = [-2, -2]` -`w_0 + w_1 * x_2 + w_2 * y_2 = 1 + 1 * (-2) + 1 * (-2) = -3 < 0` +`w₀ + w₁⋅x₂ + w₂⋅y₂ = 1 + 1⋅(-2) + 1⋅(-2) = -3 < 0` -If `v_1` and `v_2` have the labels `1` and `-1` (like we will be using), then the hyperplane `[1, 1, 1]` is a valid decision boundary for them since the signs match. +If `v₁` and `v₂` have the labels `1` and `-1` (like we will be using), then the hyperplane `[1, 1, 1]` is a valid decision boundary for them since the signs match. Now that we know how to tell which side of the hyperplane an object lies on, we can look at how perceptron updates a hyperplane. If an object is on the correct side of the hyperplane, no update is performed on the weights. However, if we find an object on the wrong side, the update rule for the weights is: -`[w_0', w_1', w_2'] = [w_0 + class, w_1 + x * class, w_2 + y * class]` +`[w₀', w₁', w₂'] = [w₀ + 1ₗ, w₁ + x⋅1ₗ, w₂ + y⋅1ₗ]` -Where `class ∈ {1,-1}`, according to the class of the object (i.e. its label), `x, y` are the coordinates of the object, the `w_i` are the weights of the hyperplane and the `w_i'` are the weights of the updated hyperplane. +Where `1ₗ ∈ {1, -1}`, according to the class of the object (i.e. its label), `x, y` are the coordinates of the object, the `wᵢ` are the weights of the hyperplane and the `wᵢ'` are the weights of the updated hyperplane. This update is repeated for each object in turn, and then the whole process repeated until there are no updates made to the hyperplane. All objects passing without an update means they have been successfully separated and you can return your decision boundary! From 121e5fa5c3fdd71cb895eb9cd214760414163ff2 Mon Sep 17 00:00:00 2001 From: depial <91621102+depial@users.noreply.github.com> Date: Fri, 16 Feb 2024 13:21:42 -0500 Subject: [PATCH 11/26] Update exercises/practice/perceptron/perceptron.jl Co-authored-by: Victor Goff