SHOW:
|
|
- or go back to the newest paste.
| 1 | - | // This may or may not help you understand what's going |
| 1 | + | // This may or may not help you understand what's going |
| 2 | - | // on in week one of the Stanford Machine Learning course |
| 2 | + | // on in week one of the Stanford Machine Learning course |
| 3 | - | // particulary if you're coming from Javascript programming. |
| 3 | + | // particulary if you're coming from Javascript programming. |
| 4 | - | |
| 4 | + | |
| 5 | - | // The lecture deals with linear regression. You may remember |
| 5 | + | // The lecture deals with linear regression. You may remember |
| 6 | - | // a concept called "line of best fit" from middle school. |
| 6 | + | // a concept called "line of best fit" from middle school. |
| 7 | - | // A refresher: you have a set of points with x and y co-ordinates |
| 7 | + | // A refresher: you have a set of points with x and y co-ordinates |
| 8 | - | // and you have to draw a straight line through them. It doesn't |
| 8 | + | // and you have to draw a straight line through them. It doesn't |
| 9 | - | // have to intersect all (or any) of the points, but it does need |
| 9 | + | // have to intersect all (or any) of the points, but it does need |
| 10 | - | // to represent the general layout of your dataset. The idea is |
| 10 | + | // to represent the general layout of your dataset. The idea is |
| 11 | - | // that this will let you make guesses for the y position of a |
| 11 | + | // that this will let you make guesses for the y position of a |
| 12 | - | // new point when you only know the x or vice versa. It often |
| 12 | + | // new point when you only know the x or vice versa. It often |
| 13 | - | // won't be 100% accurate, but it'll give you a good idea, and a |
| 13 | + | // won't be 100% accurate, but it'll give you a good idea, and a |
| 14 | - | // good understanding of the range of possible error it could have |
| 14 | + | // good understanding of the range of possible error it could have |
| 15 | - | // made. |
| 15 | + | // made. |
| 16 | - | |
| 16 | + | |
| 17 | - | // A linear regression algorithm is just a way to make a computer |
| 17 | + | // A linear regression algorithm is just a way to make a computer |
| 18 | - | // figure out what the line of best fit for a given dataset is. |
| 18 | + | // figure out what the line of best fit for a given dataset is. |
| 19 | - | // The linear regression algorithm I describe below comes from |
| 19 | + | // The linear regression algorithm I describe below comes from |
| 20 | - | // the Stanford Machine Learning course on Coursera. I found it |
| 20 | + | // the Stanford Machine Learning course on Coursera. I found it |
| 21 | - | // to be a bit too mathsy, so I'm writing it out in code, which |
| 21 | + | // to be a bit too mathsy, so I'm writing it out in code, which |
| 22 | - | // may help some people understand. |
| 22 | + | // may help some people understand. |
| 23 | - | |
| 23 | + | |
| 24 | - | // Linear regression algorithms return a function that takes an |
| 24 | + | // Linear regression algorithms return a function that takes an |
| 25 | - | // x and returns a guess for the corresponding y value, based on |
| 25 | + | // x and returns a guess for the corresponding y value, based on |
| 26 | - | // the *specific* dataset you trained against. This function is |
| 26 | + | // the *specific* dataset you trained against. This function is |
| 27 | - | // called a "model". There are many other kinds of models for |
| 27 | + | // called a "model". There are many other kinds of models for |
| 28 | - | // various other kinds of machine learning algorithms, but because |
| 28 | + | // various other kinds of machine learning algorithms, but because |
| 29 | - | // we're focusing on linear regression, this model will be the |
| 29 | + | // we're focusing on linear regression, this model will be the |
| 30 | - | // function of a line. You may remember this as `y=mx+c` where |
| 30 | + | // function of a line. You may remember this as `y=mx+c` where |
| 31 | - | // `m` is the slope of the line, and c is the horizontal offset of |
| 31 | + | // `m` is the slope of the line, and c is the horizontal offset of |
| 32 | - | // the line, which you can think about as 'the value of the equation |
| 32 | + | // the line, which you can think about as 'the value of the equation |
| 33 | - | // when x is 0'. In the course, this is written as hθ1=θ1+θ2*x. |
| 33 | + | // when x is 0'. In the course, this is written as hθ1=θ1+θ2*x. |
| 34 | - | |
| 34 | + | |
| 35 | - | (()=>{ |
| 35 | + | (()=>{
|
| 36 | - | |
| 36 | + | |
| 37 | - | // You'll need to make lots of linear functions, so |
| 37 | + | // You'll need to make lots of linear functions, so |
| 38 | - | // lets make that easy. |
| 38 | + | // lets make that easy. |
| 39 | - | function prepareLinearFunction (theta1, theta2) { |
| 39 | + | function prepareLinearFunction (theta1, theta2) {
|
| 40 | - | return x => theta1 + theta2 * x; |
| 40 | + | return x => theta1 + theta2 * x; |
| 41 | - | } |
| 41 | + | } |
| 42 | - | |
| 42 | + | |
| 43 | - | // We're assuming in this example that your dataset contains |
| 43 | + | // We're assuming in this example that your dataset contains |
| 44 | - | // two columns, and therefore we have two thetas. You could |
| 44 | + | // two columns, and therefore we have two thetas. You could |
| 45 | - | // generalise the above function to take more columns into |
| 45 | + | // generalise the above function to take more columns into |
| 46 | - | // account, but for now, lets focus on just two columns. |
| 46 | + | // account, but for now, lets focus on just two columns. |
| 47 | - | |
| 47 | + | |
| 48 | - | // Lets define a dataset to learn from. It is possible to |
| 48 | + | // Lets define a dataset to learn from. It is possible to |
| 49 | - | // accurately model this dataset with a line, so we should |
| 49 | + | // accurately model this dataset with a line, so we should |
| 50 | - | // end up without any errors. |
| 50 | + | // end up without any errors. |
| 51 | - | const dataset = [ |
| 51 | + | const dataset = [ |
| 52 | - | [0,1], |
| 52 | + | [0,1], |
| 53 | - | [1,3], |
| 53 | + | [1,3], |
| 54 | - | [2,5], |
| 54 | + | [2,5], |
| 55 | - | [3,7] |
| 55 | + | [3,7] |
| 56 | - | ]; |
| 56 | + | ]; |
| 57 | - | // You'll notice that the 0th element of each inner array is |
| 57 | + | // You'll notice that the 0th element of each inner array is |
| 58 | - | // the index of that element. I could just use the index, |
| 58 | + | // the index of that element. I could just use the index, |
| 59 | - | // but I wanted to make it more obvious. |
| 59 | + | // but I wanted to make it more obvious. |
| 60 | - | |
| 60 | + | |
| 61 | - | // Next you'll need a "cost function", which is just a |
| 61 | + | // Next you'll need a "cost function", which is just a |
| 62 | - | // fancy name for a function that measures how well a |
| 62 | + | // fancy name for a function that measures how well a |
| 63 | - | // model is performing at it's task. To calculate this, |
| 63 | + | // model is performing at it's task. To calculate this, |
| 64 | - | // the function needs both of the current thetas, and |
| 64 | + | // the function needs both of the current thetas, and |
| 65 | - | // the dataset it's training against |
| 65 | + | // the dataset it's training against |
| 66 | - | function costFunction(theta1, theta2, dataset) { |
| 66 | + | function costFunction(theta1, theta2, dataset) {
|
| 67 | - | // Here we generate our model using the function we |
| 67 | + | // Here we generate our model using the function we |
| 68 | - | // wrote earlier, giving it our current theta values. |
| 68 | + | // wrote earlier, giving it our current theta values. |
| 69 | - | const model = prepareLinearFunction(theta1,theta2); |
| 69 | + | const model = prepareLinearFunction(theta1,theta2); |
| 70 | - | |
| 70 | + | |
| 71 | - | // We apply a procedure to our model for each row |
| 71 | + | // We apply a procedure to our model for each row |
| 72 | - | // in the dataset. |
| 72 | + | // in the dataset. |
| 73 | - | return dataset.map((x)=>{ |
| 73 | + | return dataset.map((x)=>{
|
| 74 | - | // First we get the difference between what the |
| 74 | + | // First we get the difference between what the |
| 75 | - | // data row says about the mapping and what our |
| 75 | + | // data row says about the mapping and what our |
| 76 | - | // model says. |
| 76 | + | // model says. |
| 77 | - | const difference = model(x[0])-x[1]; |
| 77 | + | const difference = model(x[0])-x[1]; |
| 78 | - | |
| 78 | + | |
| 79 | - | // We then return the square of that difference. |
| 79 | + | // We then return the square of that difference. |
| 80 | - | return Math.pow(difference,2); |
| 80 | + | return Math.pow(difference,2); |
| 81 | - | }) // Now we have an array of the square of the difference |
| 81 | + | }) // Now we have an array of the square of the difference |
| 82 | - | // between each of the examples and what we predicted for |
| 82 | + | // between each of the examples and what we predicted for |
| 83 | - | // their x value. The following line sums that array, which |
| 83 | + | // their x value. The following line sums that array, which |
| 84 | - | // we divide by the length of the dataset, and return. |
| 84 | + | // we divide by the length of the dataset, and return. |
| 85 | - | .reduce((x,y)=>{return x+y},0)/dataset.length*2; |
| 85 | + | .reduce((x,y)=>{return x+y},0)/dataset.length*2;
|
| 86 | - | } |
| 86 | + | } |
| 87 | - | |
| 87 | + | |
| 88 | - | // Ok, now we have a cost function. Next up, we implement |
| 88 | + | // Ok, now we have a cost function. Next up, we implement |
| 89 | - | // "gradient descent", which is just a prodedure for modifying |
| 89 | + | // "gradient descent", which is just a prodedure for modifying |
| 90 | - | // our theta values in a way that will decrease the overall "cost" |
| 90 | + | // our theta values in a way that will decrease the overall "cost" |
| 91 | - | // (otherwise known as "error") of our model. |
| 91 | + | // (otherwise known as "error") of our model. |
| 92 | - | |
| 92 | + | |
| 93 | - | function gradientDescent(learningRate, thetaOne, thetaTwo, dataset) { |
| 93 | + | function gradientDescent(learningRate, thetaOne, thetaTwo, dataset) {
|
| 94 | - | // First, we calculate the error of the model that our current |
| 94 | + | // First, we calculate the error of the model that our current |
| 95 | - | // values of theta creates |
| 95 | + | // values of theta creates |
| 96 | - | const error = costFunction(thetaOne, thetaTwo, dataset); |
| 96 | + | const error = costFunction(thetaOne, thetaTwo, dataset); |
| 97 | - | |
| 97 | + | |
| 98 | - | // Then we use the error, the learning rate to update each theta |
| 98 | + | // Then we use the error, the learning rate to update each theta |
| 99 | - | // thetas must be updated simultaneously - don't get into a |
| 99 | + | // thetas must be updated simultaneously - don't get into a |
| 100 | - | // situation where you update one and you use that new theta |
| 100 | + | // situation where you update one and you use that new theta |
| 101 | - | // to update your next theta. It would probably work, but it's |
| 101 | + | // to update your next theta. It would probably work, but it's |
| 102 | - | // not proper gradient descent. |
| 102 | + | // not proper gradient descent. |
| 103 | - | /*const newThetas = [thetaOne, thetaTwo].map(function(theta){ |
| 103 | + | /*const newThetas = [thetaOne, thetaTwo].map(function(theta){
|
| 104 | - | // Note - I don't know how to calculate derivative of the cost |
| 104 | + | // Note - I don't know how to calculate derivative of the cost |
| 105 | - | // function yet. It wasn't covered in this lecture. It's a |
| 105 | + | // function yet. It wasn't covered in this lecture. It's a |
| 106 | - | // measure of how far away your thetas are from a "local minimum" |
| 106 | + | // measure of how far away your thetas are from a "local minimum" |
| 107 | - | // in error, it's what guides your thetas towards finding their |
| 107 | + | // in error, it's what guides your thetas towards finding their |
| 108 | - | // correct values. |
| 108 | + | // correct values. |
| 109 | - | theta * learningRate * derivitave * error; |
| 109 | + | theta * learningRate * derivitave * error; |
| 110 | - | });*/ |
| 110 | + | });*/ |
| 111 | - | //please excuse this hackery nonsense |
| 111 | + | //please excuse this hackery nonsense |
| 112 | - | |
| 112 | + | |
| 113 | - | const model = prepareLinearFunction(thetaOne,thetaTwo); |
| 113 | + | const model = prepareLinearFunction(thetaOne,thetaTwo); |
| 114 | - | |
| 114 | + | |
| 115 | - | const a = dataset.map((x) => { |
| 115 | + | const a = dataset.map((x) => {
|
| 116 | - | return model(x[0])-x[1]; |
| 116 | + | return model(x[0])-x[1]; |
| 117 | - | }).reduce((x,y) => {return x + y}, 0)/dataset.length; |
| 117 | + | }).reduce((x,y) => {return x + y}, 0)/dataset.length;
|
| 118 | - | |
| 118 | + | |
| 119 | - | const b = dataset.map((x) => { |
| 119 | + | const b = dataset.map((x) => {
|
| 120 | - | return (model(x[0])-x[1])*x[0]; |
| 120 | + | return (model(x[0])-x[1])*x[0]; |
| 121 | - | }).reduce((x,y) => {return x + y}, 0)/dataset.length; |
| 121 | + | }).reduce((x,y) => {return x + y}, 0)/dataset.length;
|
| 122 | - | |
| 122 | + | |
| 123 | - | const newThetas = [ |
| 123 | + | const newThetas = [ |
| 124 | - | thetaOne - learningRate * a, |
| 124 | + | thetaOne - learningRate * a, |
| 125 | - | thetaTwo - learningRate * b |
| 125 | + | thetaTwo - learningRate * b |
| 126 | - | ]; |
| 126 | + | ]; |
| 127 | - | debugger; |
| 127 | + | debugger; |
| 128 | - | return { newThetas, error }; |
| 128 | + | return { newThetas, error };
|
| 129 | - | } |
| 129 | + | } |
| 130 | - | |
| 130 | + | |
| 131 | - | // Lets run through the whole training process |
| 131 | + | // Lets run through the whole training process |
| 132 | - | (function linearRegress(dataset){ |
| 132 | + | (function linearRegress(dataset){
|
| 133 | - | // Initialize thetas to zero. It's not important that |
| 133 | + | // Initialize thetas to zero. It's not important that |
| 134 | - | // they're zero, they could start out as any number, |
| 134 | + | // they're zero, they could start out as any number, |
| 135 | - | // but zero works just fine. |
| 135 | + | // but zero works just fine. |
| 136 | - | const initialThetaOne = 0; |
| 136 | + | const initialThetaOne = 0; |
| 137 | - | const initialThetaTwo = 0; |
| 137 | + | const initialThetaTwo = 0; |
| 138 | - | const learningRate = 0.2; |
| 138 | + | const learningRate = 0.2; |
| 139 | - | |
| 139 | + | |
| 140 | - | // Create a named function so that you can recurse |
| 140 | + | // Create a named function so that you can recurse |
| 141 | - | // through the values of theta that gradient descent gives |
| 141 | + | // through the values of theta that gradient descent gives |
| 142 | - | // you until you converge. |
| 142 | + | // you until you converge. |
| 143 | - | (function reg(learningRate, thetaOne, thetaTwo, dataset) { |
| 143 | + | (function reg(learningRate, thetaOne, thetaTwo, dataset) {
|
| 144 | - | console.log(`Current thetas: ${thetaOne}, ${thetaTwo}`); |
| 144 | + | console.log(`Current thetas: ${thetaOne}, ${thetaTwo}`);
|
| 145 | - | |
| 145 | + | |
| 146 | - | // Run gradient descent |
| 146 | + | // Run gradient descent |
| 147 | - | const {error,newThetas} = gradientDescent(learningRate, thetaOne, thetaTwo, dataset); |
| 147 | + | const {error,newThetas} = gradientDescent(learningRate, thetaOne, thetaTwo, dataset);
|
| 148 | - | |
| 148 | + | |
| 149 | - | console.log(`Those thetas gave us an error of ${error}.`); |
| 149 | + | console.log(`Those thetas gave us an error of ${error}.`);
|
| 150 | - | |
| 150 | + | |
| 151 | - | if (error < 0.00001) { |
| 151 | + | if (error < 0.00001) {
|
| 152 | - | // Our error is zero! That means our thetas have |
| 152 | + | // Our error is zero! That means our thetas have |
| 153 | - | // "converged", or "settled" on values that make sense |
| 153 | + | // "converged", or "settled" on values that make sense |
| 154 | - | // for the dataset. Note that because our dataset fits |
| 154 | + | // for the dataset. Note that because our dataset fits |
| 155 | - | // perfectly on a line, we can check that our thetas |
| 155 | + | // perfectly on a line, we can check that our thetas |
| 156 | - | // match exactly. When your dataset is always going to |
| 156 | + | // match exactly. When your dataset is always going to |
| 157 | - | // have a certain amount of error in it (i.e. when the |
| 157 | + | // have a certain amount of error in it (i.e. when the |
| 158 | - | // data doesn't exactly match any straight line), you'll |
| 158 | + | // data doesn't exactly match any straight line), you'll |
| 159 | - | // have to check tolerate some difference. |
| 159 | + | // have to check tolerate some difference. |
| 160 | - | console.log(`Convergeance! Line of best fit is: y = ${thetaOne} + ${thetaTwo}x.`); |
| 160 | + | console.log(`Convergeance! Line of best fit is: y = ${thetaOne} + ${thetaTwo}x.`);
|
| 161 | - | process.exit(); |
| 161 | + | process.exit(); |
| 162 | - | } |
| 162 | + | } |
| 163 | - | |
| 163 | + | |
| 164 | - | // Recursively call this function with updated thetas until |
| 164 | + | // Recursively call this function with updated thetas until |
| 165 | - | // convergeance happens and the condition above is met |
| 165 | + | // convergeance happens and the condition above is met |
| 166 | - | console.log("Running gradient descent again to reduce the error.") |
| 166 | + | console.log("Running gradient descent again to reduce the error.")
|
| 167 | - | reg(learningRate, newThetas[0], newThetas[1], dataset); |
| 167 | + | reg(learningRate, newThetas[0], newThetas[1], dataset); |
| 168 | - | })(learningRate, initialThetaOne, initialThetaTwo, dataset); |
| 168 | + | })(learningRate, initialThetaOne, initialThetaTwo, dataset); |
| 169 | - | })(dataset); |
| 169 | + | })(dataset); |
| 170 | })(); |