Mercurial > hg > machine-learning-hw7
changeset 0:ded78d0b4987
init
author | Jordi Gutiérrez Hermoso <jordigh@octave.org> |
---|---|
date | Mon, 05 Dec 2011 00:19:20 -0500 |
parents | |
children | 90d2a292663c |
files | bird_small.png computeCentroids.m displayData.m drawLine.m ex7.m ex7_pca.m ex7data1.mat ex7data2.mat featureNormalize.m findClosestCentroids.m kMeansInitCentroids.m pca.m plotDataPoints.m plotProgresskMeans.m projectData.m recoverData.m runkMeans.m submit.m submitWeb.m |
diffstat | 19 files changed, 1462 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 index 0000000000000000000000000000000000000000..a3cd00c107b1b8740280b9c28dcdbec299325d3e GIT binary patch literal 33031 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
new file mode 100644 --- /dev/null +++ b/computeCentroids.m @@ -0,0 +1,40 @@ +function centroids = computeCentroids(X, idx, K) +%COMPUTECENTROIDS returs the new centroids by computing the means of the +%data points assigned to each centroid. +% centroids = COMPUTECENTROIDS(X, idx, K) returns the new centroids by +% computing the means of the data points assigned to each centroid. It is +% given a dataset X where each row is a single data point, a vector +% idx of centroid assignments (i.e. each entry in range [1..K]) for each +% example, and K, the number of centroids. You should return a matrix +% centroids, where each row of centroids is the mean of the data points +% assigned to it. +% + +% Useful variables +[m n] = size(X); + +% You need to return the following variables correctly. +centroids = zeros(K, n); + + +% ====================== YOUR CODE HERE ====================== +% Instructions: Go over every centroid and compute mean of all points that +% belong to it. Concretely, the row vector centroids(i, :) +% should contain the mean of the data points assigned to +% centroid i. +% +% Note: You can use a for-loop over the centroids to compute this. +% + + + + + + + + +% ============================================================= + + +end +
new file mode 100644 --- /dev/null +++ b/displayData.m @@ -0,0 +1,59 @@ +function [h, display_array] = displayData(X, example_width) +%DISPLAYDATA Display 2D data in a nice grid +% [h, display_array] = DISPLAYDATA(X, example_width) displays 2D data +% stored in X in a nice grid. It returns the figure handle h and the +% displayed array if requested. + +% Set example_width automatically if not passed in +if ~exist('example_width', 'var') || isempty(example_width) + example_width = round(sqrt(size(X, 2))); +end + +% Gray Image +colormap(gray); + +% Compute rows, cols +[m n] = size(X); +example_height = (n / example_width); + +% Compute number of items to display +display_rows = floor(sqrt(m)); +display_cols = ceil(m / display_rows); + +% Between images padding +pad = 1; + +% Setup blank display +display_array = - ones(pad + display_rows * (example_height + pad), ... + pad + display_cols * (example_width + pad)); + +% Copy each example into a patch on the display array +curr_ex = 1; +for j = 1:display_rows + for i = 1:display_cols + if curr_ex > m, + break; + end + % Copy the patch + + % Get the max value of the patch + max_val = max(abs(X(curr_ex, :))); + display_array(pad + (j - 1) * (example_height + pad) + (1:example_height), ... + pad + (i - 1) * (example_width + pad) + (1:example_width)) = ... + reshape(X(curr_ex, :), example_height, example_width) / max_val; + curr_ex = curr_ex + 1; + end + if curr_ex > m, + break; + end +end + +% Display Image +h = imagesc(display_array, [-1 1]); + +% Do not show axis +axis image off + +drawnow; + +end
new file mode 100644 --- /dev/null +++ b/drawLine.m @@ -0,0 +1,8 @@ +function drawLine(p1, p2, varargin) +%DRAWLINE Draws a line from point p1 to point p2 +% DRAWLINE(p1, p2) Draws a line from point p1 to point p2 and holds the +% current figure + +plot([p1(1) p2(1)], [p1(2) p2(2)], varargin{:}); + +end \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/ex7.m @@ -0,0 +1,170 @@ +%% Machine Learning Online Class +% Exercise 7 | Principle Component Analysis and K-Means Clustering +% +% Instructions +% ------------ +% +% This file contains code that helps you get started on the +% exercise. You will need to complete the following functions: +% +% pca.m +% projectData.m +% recoverData.m +% computeCentroids.m +% findClosestCentroids.m +% kMeansInitCentroids.m +% +% For this exercise, you will not need to change any code in this file, +% or any other files other than those mentioned above. +% + +%% Initialization +clear ; close all; clc + +%% ================= Part 1: Find Closest Centroids ==================== +% To help you implement K-Means, we have divided the learning algorithm +% into two functions -- findClosestCentroids and computeCentroids. In this +% part, you shoudl complete the code in the findClosestCentroids function. +% +fprintf('Finding closest centroids.\n\n'); + +% Load an example dataset that we will be using +load('ex7data2.mat'); + +% Select an initial set of centroids +K = 3; % 3 Centroids +initial_centroids = [3 3; 6 2; 8 5]; + +% Find the closest centroids for the examples using the +% initial_centroids +idx = findClosestCentroids(X, initial_centroids); + +fprintf('Closest centroids for the first 3 examples: \n') +fprintf(' %d', idx(1:3)); +fprintf('\n(the closest centroids should be 1, 3, 2 respectively)\n'); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + +%% ===================== Part 2: Compute Means ========================= +% After implementing the closest centroids function, you should now +% complete the computeCentroids function. +% +fprintf('\nComputing centroids means.\n\n'); + +% Compute means based on the closest centroids found in the previous part. +centroids = computeCentroids(X, idx, K); + +fprintf('Centroids computed after initial finding of closest centroids: \n') +fprintf(' %f %f \n' , centroids'); +fprintf('\n(the centroids should be\n'); +fprintf(' [ 2.428301 3.157924 ]\n'); +fprintf(' [ 5.813503 2.633656 ]\n'); +fprintf(' [ 7.119387 3.616684 ]\n\n'); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% =================== Part 3: K-Means Clustering ====================== +% After you have completed the two functions computeCentroids and +% findClosestCentroids, you have all the necessary pieces to run the +% kMeans algorithm. In this part, you will run the K-Means algorithm on +% the example dataset we have provided. +% +fprintf('\nRunning K-Means clustering on example dataset.\n\n'); + +% Load an example dataset +load('ex7data2.mat'); + +% Settings for running K-Means +K = 3; +max_iters = 10; + +% For consistency, here we set centroids to specific values +% but in practice you want to generate them automatically, such as by +% settings them to be random examples (as can be seen in +% kMeansInitCentroids). +initial_centroids = [3 3; 6 2; 8 5]; + +% Run K-Means algorithm. The 'true' at the end tells our function to plot +% the progress of K-Means +[centroids, idx] = runkMeans(X, initial_centroids, max_iters, true); +fprintf('\nK-Means Done.\n\n'); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + +%% ============= Part 4: K-Means Clustering on Pixels =============== +% In this exercise, you will use K-Means to compress an image. To do this, +% you will first run K-Means on the colors of the pixels in the image and +% then you will map each pixel on to it's closest centroid. +% +% You should now complete the code in kMeansInitCentroids.m +% + +fprintf('\nRunning K-Means clustering on pixels from an image.\n\n'); + +% Load an image of a bird +A = double(imread('bird_small.png')); +A = A / 255; % Divide by 255 so that all values are in the range 0 - 1 + +% Size of the image +img_size = size(A); + +% Reshape the image into an Nx3 matrix where N = number of pixels. +% Each row will contain the Red, Green and Blue pixel values +% This gives us our dataset matrix X that we will use K-Means on. +X = reshape(A, img_size(1) * img_size(2), 3); + +% Run your K-Means algorithm on this data +% You should try different values of K and max_iters here +K = 16; +max_iters = 10; + +% When using K-Means, it is important the initialize the centroids +% randomly. +% You should complete the code in kMeansInitCentroids.m before proceeding +initial_centroids = kMeansInitCentroids(X, K); + +% Run K-Means +[centroids, idx] = runkMeans(X, initial_centroids, max_iters); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% ================= Part 5: Image Compression ====================== +% In this part of the exercise, you will use the clusters of K-Means to +% compress an image. To do this, we first find the closest clusters for +% each example. After that, we + +fprintf('\nApplying K-Means to compress an image.\n\n'); + +% Find closest cluster members +idx = findClosestCentroids(X, centroids); + +% Essentially, now we have represented the image X as in terms of the +% indices in idx. + +% We can now recover the image from the indices (idx) by mapping each pixel +% (specified by it's index in idx) to the centroid value +X_recovered = centroids(idx,:); + +% Reshape the recovered image into proper dimensions +X_recovered = reshape(X_recovered, img_size(1), img_size(2), 3); + +% Display the original image +subplot(1, 2, 1); +imagesc(A); +title('Original'); + +% Display compressed image side by side +subplot(1, 2, 2); +imagesc(X_recovered) +title(sprintf('Compressed, with %d colors.', K)); + + +fprintf('Program paused. Press enter to continue.\n'); +pause; +
new file mode 100644 --- /dev/null +++ b/ex7_pca.m @@ -0,0 +1,231 @@ +%% Machine Learning Online Class +% Exercise 7 | Principle Component Analysis and K-Means Clustering +% +% Instructions +% ------------ +% +% This file contains code that helps you get started on the +% exercise. You will need to complete the following functions: +% +% pca.m +% projectData.m +% recoverData.m +% computeCentroids.m +% findClosestCentroids.m +% kMeansInitCentroids.m +% +% For this exercise, you will not need to change any code in this file, +% or any other files other than those mentioned above. +% + +%% Initialization +clear ; close all; clc + +%% ================== Part 1: Load Example Dataset =================== +% We start this exercise by using a small dataset that is easily to +% visualize +% +fprintf('Visualizing example dataset for PCA.\n\n'); + +% The following command loads the dataset. You should now have the +% variable X in your environment +load ('ex7data1.mat'); + +% Visualize the example dataset +plot(X(:, 1), X(:, 2), 'bo'); +axis([0.5 6.5 2 8]); axis square; + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% =============== Part 2: Principal Component Analysis =============== +% You should now implement PCA, a dimension reduction technique. You +% should complete the code in pca.m +% +fprintf('\nRunning PCA on example dataset.\n\n'); + +% Before running PCA, it is important to first normalize X +[X_norm, mu, sigma] = featureNormalize(X); + +% Run PCA +[U, S] = pca(X_norm); + +% Compute mu, the mean of the each feature + +% Draw the eigenvectors centered at mean of data. These lines show the +% directions of maximum variations in the dataset. +hold on; +drawLine(mu, mu + 1.5 * S(1,1) * U(:,1)', '-k', 'LineWidth', 2); +drawLine(mu, mu + 1.5 * S(2,2) * U(:,2)', '-k', 'LineWidth', 2); +hold off; + +fprintf('Top eigenvector: \n'); +fprintf(' U(:,1) = %f %f \n', U(1,1), U(2,1)); +fprintf('\n(you should expect to see -0.707107 -0.707107)\n'); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% =================== Part 3: Dimension Reduction =================== +% You should now implement the projection step to map the data onto the +% first k eigenvectors. The code will then plot the data in this reduced +% dimensional space. This will show you what the data looks like when +% using only the corresponding eigenvectors to reconstruct it. +% +% You should complete the code in projectData.m +% +fprintf('\nDimension reduction on example dataset.\n\n'); + +% Plot the normalized dataset (returned from pca) +plot(X_norm(:, 1), X_norm(:, 2), 'bo'); +axis([-4 3 -4 3]); axis square + +% Project the data onto K = 1 dimension +K = 1; +Z = projectData(X_norm, U, K); +fprintf('Projection of the first example: %f\n', Z(1)); +fprintf('\n(this value should be about 1.481274)\n\n'); + +X_rec = recoverData(Z, U, K); +fprintf('Approximation of the first example: %f %f\n', X_rec(1, 1), X_rec(1, 2)); +fprintf('\n(this value should be about -1.047419 -1.047419)\n\n'); + +% Draw lines connecting the projected points to the original points +hold on; +plot(X_rec(:, 1), X_rec(:, 2), 'ro'); +for i = 1:size(X_norm, 1) + drawLine(X_norm(i,:), X_rec(i,:), '--k', 'LineWidth', 1); +end +hold off + +fprintf('Program paused. Press enter to continue.\n'); +pause; + +%% =============== Part 4: Loading and Visualizing Face Data ============= +% We start the exercise by first loading and visualizing the dataset. +% The following code will load the dataset into your environment +% +fprintf('\nLoading face dataset.\n\n'); + +% Load Face dataset +load ('ex7faces.mat') + +% Display the first 100 faces in the dataset +displayData(X(1:100, :)); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + +%% =========== Part 5: PCA on Face Data: Eigenfaces =================== +% Run PCA and visualize the eigenvectors which are in this case eigenfaces +% We display the first 36 eigenfaces. +% +fprintf(['\nRunning PCA on face dataset.\n' ... + '(this mght take a minute or two ...)\n\n']); + +% Before running PCA, it is important to first normalize X by subtracting +% the mean value from each feature +[X_norm, mu, sigma] = featureNormalize(X); + +% Run PCA +[U, S] = pca(X_norm); + +% Visualize the top 36 eigenvectors found +displayData(U(:, 1:36)'); + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% ============= Part 6: Dimension Reduction for Faces ================= +% Project images to the eigen space using the top k eigenvectors +% If you are applying a machine learning algorithm +fprintf('\nDimension reduction for face dataset.\n\n'); + +K = 100; +Z = projectData(X_norm, U, K); + +fprintf('The projected data Z has a size of: ') +fprintf('%d ', size(Z)); + +fprintf('\n\nProgram paused. Press enter to continue.\n'); +pause; + +%% ==== Part 7: Visualization of Faces after PCA Dimension Reduction ==== +% Project images to the eigen space using the top K eigen vectors and +% visualize only using those K dimensions +% Compare to the original input, which is also displayed + +fprintf('\nVisualizing the projected (reduced dimension) faces.\n\n'); + +K = 100; +X_rec = recoverData(Z, U, K); + +% Display normalized data +subplot(1, 2, 1); +displayData(X_norm(1:100,:)); +title('Original faces'); +axis square; + +% Display reconstructed data from only k eigenfaces +subplot(1, 2, 2); +displayData(X_rec(1:100,:)); +title('Recovered faces'); +axis square; + +fprintf('Program paused. Press enter to continue.\n'); +pause; + + +%% === Part 8(a): Optional (ungraded) Exercise: PCA for Visualization === +% One useful application of PCA is to use it to visualize high-dimensional +% data. In the last K-Means exercise you ran K-Means on 3-dimensional +% pixel colors of an image. We first visualize this output in 3D, and then +% apply PCA to obtain a visualization in 2D. + +close all; close all; clc + +% Re-load the image from the previous exercise and run K-Means on it +% For this to work, you need to complete the K-Means assignment first +A = double(imread('bird_small.png')); +A = A / 255; +img_size = size(A); +X = reshape(A, img_size(1) * img_size(2), 3); +K = 16; +max_iters = 10; +initial_centroids = kMeansInitCentroids(X, K); +[centroids, idx] = runkMeans(X, initial_centroids, max_iters); + +% Sample 1000 random indexes (since working with all the data is +% too expensive. If you have a fast computer, you may increase this. +sel = floor(rand(1000, 1) * size(X, 1)) + 1; + +% Setup Color Palette +palette = hsv(K); +colors = palette(idx(sel), :); + +% Visualize the data and centroid memberships in 3D +figure; +scatter3(X(sel, 1), X(sel, 2), X(sel, 3), 10, colors); +title('Pixel dataset plotted in 3D. Color shows centroid memberships'); +fprintf('Program paused. Press enter to continue.\n'); +pause; + +%% === Part 8(b): Optional (ungraded) Exercise: PCA for Visualization === +% Use PCA to project this cloud to 2D for visualization + +% Subtract the mean to use PCA +[X_norm, mu, sigma] = featureNormalize(X); + +% PCA and project the data to 2D +[U, S] = pca(X_norm); +Z = projectData(X_norm, U, 2); + +% Plot in 2D +figure; +plotDataPoints(Z(sel, :), idx(sel), K); +title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction'); +fprintf('Program paused. Press enter to continue.\n'); +pause;
new file mode 100644 index 0000000000000000000000000000000000000000..f9c396160204acdf128b42882e1824ecd75bda6d GIT binary patch literal 995 zc$@*_104KKK~zjZLLfCRFd$7qR4ry{Y-KDUP;6mzW^ZzBIv`L(S4mDbG%O%Pa%Ew3 zWn>_4ZaN@MZ*Cw?Z+0LtG$1lEIy5mlG&CSGFflP8ARr(hARr(hARr(hARr(hARr(h zARr(hARr(hARr(hARr(hARr(hARr(hARr(B0ZB~{000160{{ScoGp=UOj8FGhVOu0 zT4=fS-rEnLoopI4fUbqwEg8}mqsBoTAWK$L+AU(LKn7B}O6p)ZVGb||i>p|PX4A4w z;))p*i%ADbOQd`VS)<|>jdSC}O{ESEFk!pPc21r=Imt=hA8#B%5c!b=L2~93|M}nH z{eP!(L~y*st)8=hBbob8ZrAi562*eNt}r$$Xw)9}ym~W>htb+1L&h2s{AT*?!2pTz zk4@*~$rKFH_GQa>EXp$~%Tir3oEAKJ_Fg%KvZAy=fsTe{W2AK|mqt^T@9NhdiP3A^ zVb1l(qiN5)Exg2H`;(Oyqiz!GG7qe+Z<3<G{>qjyAB~a0@r9`|5_gq3%-A6oiQgps z9qwoG+XeT;?vNDkYtDD6F9=aPutnf)kwR5=tUD+Y<5a|n;^CYq2wPNl%|QX~AF1gX z>0_||j8eIhDnnT4?w|RLM2WO9l&7HZfT?T?`(kiyy@#Btm!VX2=ERR7J`Qfo$+Q(p zP|V*yc8`dK!}(!vK@x+MroERR9pGVa-DtxuLJE_n`SI+)3ti!@g52yJG5X)g3MRS5 zQ2Um*YAOV<Z2Eo2mF;{u9mkgzD`?oLw2_;=(O4E7KA1X0!QK<lX8!V2#}{dfc|EJT zMX$eFf02SOzDqq^&BA}OPBEj1#i>WUOP5P!*!sX0zV8y_9i}sw;uT@lS@pc{`*_ex zpC}U-60o5vB9e(0q0VaGm*h=En?s<wv7g0Cc3$eXS}CYcqkQVq5`^B8``^4RL7IP0 z`q@baF?E0Oj4m4U!mFF7*To^6-aY5kC7?aS^IY?-6ssNM12so!5akmSb5%;*GFa@S zgGQ^?)VPn!q1@>kgjxpH$;y+3N90&wJ1;E_(lE{#rYjf<9iH~0O#ud`#fR?sNf`vD zLC0!2i|PjL?T(!c23+@aLuM8_Yt74+&t({D&CPl&k)r#L?~dtzii54RrGfQw`T9S< zc)E?o!a2_e)~E!yuFPE{r$q3q#LNu$NfFh3wDZ}r0A@p1MWB+!&Y$!1vlHb|-D%F# z{~(4bT$0xoqVN@UM?K5+-Prot*;#W8s$cJ%Zun9HqWD*TYjiZq>ib5n?_v>5DLHKA R(RipbI+gFr@edSNPof^A*x~>H
new file mode 100644 index 0000000000000000000000000000000000000000..de3f5b9b487beebcc79b7c28386d2c1d84f8bd3b GIT binary patch literal 4784 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 K7W@xVK`_vz&>xTh
new file mode 100644 --- /dev/null +++ b/featureNormalize.m @@ -0,0 +1,17 @@ +function [X_norm, mu, sigma] = featureNormalize(X) +%FEATURENORMALIZE Normalizes the features in X +% FEATURENORMALIZE(X) returns a normalized version of X where +% the mean value of each feature is 0 and the standard deviation +% is 1. This is often a good preprocessing step to do when +% working with learning algorithms. + +mu = mean(X); +X_norm = bsxfun(@minus, X, mu); + +sigma = std(X_norm); +X_norm = bsxfun(@rdivide, X_norm, sigma); + + +% ============================================================ + +end
new file mode 100644 --- /dev/null +++ b/findClosestCentroids.m @@ -0,0 +1,33 @@ +function idx = findClosestCentroids(X, centroids) +%FINDCLOSESTCENTROIDS computes the centroid memberships for every example +% idx = FINDCLOSESTCENTROIDS (X, centroids) returns the closest centroids +% in idx for a dataset X where each row is a single example. idx = m x 1 +% vector of centroid assignments (i.e. each entry in range [1..K]) +% + +% Set K +K = size(centroids, 1); + +% You need to return the following variables correctly. +idx = zeros(size(X,1), 1); + +% ====================== YOUR CODE HERE ====================== +% Instructions: Go over every example, find its closest centroid, and store +% the index inside idx at the appropriate location. +% Concretely, idx(i) should contain the index of the centroid +% closest to example i. Hence, it should be a value in the +% range 1..K +% +% Note: You can use a for-loop over the examples to compute this. +% + + + + + + + +% ============================================================= + +end +
new file mode 100644 --- /dev/null +++ b/kMeansInitCentroids.m @@ -0,0 +1,26 @@ +function centroids = kMeansInitCentroids(X, K) +%KMEANSINITCENTROIDS This function initializes K centroids that are to be +%used in K-Means on the dataset X +% centroids = KMEANSINITCENTROIDS(X, K) returns K initial centroids to be +% used with the K-Means on the dataset X +% + +% You should return this values correctly +centroids = zeros(K, size(X, 2)); + +% ====================== YOUR CODE HERE ====================== +% Instructions: You should set centroids to randomly chosen examples from +% the dataset X +% + + + + + + + + +% ============================================================= + +end +
new file mode 100644 --- /dev/null +++ b/pca.m @@ -0,0 +1,31 @@ +function [U, S] = pca(X) +%PCA Run principal component analysis on the dataset X +% [U, S, X] = pca(X) computes eigenvectors of the covariance matrix of X +% Returns the eigenvectors U, the eigenvalues (on diagonal) in S +% + +% Useful values +[m, n] = size(X); + +% You need to return the following variables correctly. +U = zeros(n); +S = zeros(n); + +% ====================== YOUR CODE HERE ====================== +% Instructions: You should first compute the covariance matrix. Then, you +% should use the "svd" function to compute the eigenvectors +% and eigenvalues of the covariance matrix. +% +% Note: When computing the covariance matrix, remember to divide by m (the +% number of examples). +% + + + + + + + +% ========================================================================= + +end
new file mode 100644 --- /dev/null +++ b/plotDataPoints.m @@ -0,0 +1,14 @@ +function plotDataPoints(X, idx, K) +%PLOTDATAPOINTS plots data points in X, coloring them so that those with the same +%index assignments in idx have the same color +% PLOTDATAPOINTS(X, idx, K) plots data points in X, coloring them so that those +% with the same index assignments in idx have the same color + +% Create palette +palette = hsv(K + 1); +colors = palette(idx, :); + +% Plot the data +scatter(X(:,1), X(:,2), 15, colors); + +end
new file mode 100644 --- /dev/null +++ b/plotProgresskMeans.m @@ -0,0 +1,27 @@ +function plotProgresskMeans(X, centroids, previous, idx, K, i) +%PLOTPROGRESSKMEANS is a helper function that displays the progress of +%k-Means as it is running. It is intended for use only with 2D data. +% PLOTPROGRESSKMEANS(X, centroids, previous, idx, K, i) plots the data +% points with colors assigned to each centroid. With the previous +% centroids, it also plots a line between the previous locations and +% current locations of the centroids. +% + +% Plot the examples +plotDataPoints(X, idx, K); + +% Plot the centroids as black x's +plot(centroids(:,1), centroids(:,2), 'x', ... + 'MarkerEdgeColor','k', ... + 'MarkerSize', 10, 'LineWidth', 3); + +% Plot the history of the centroids with lines +for j=1:size(centroids,1) + drawLine(centroids(j, :), previous(j, :)); +end + +% Title +title(sprintf('Iteration number %d', i)) + +end +
new file mode 100644 --- /dev/null +++ b/projectData.m @@ -0,0 +1,26 @@ +function Z = projectData(X, U, K) +%PROJECTDATA Computes the reduced data representation when projecting only +%on to the top k eigenvectors +% Z = projectData(X, U, K) computes the projection of +% the normalized inputs X into the reduced dimensional space spanned by +% the first K columns of U. It returns the projected examples in Z. +% + +% You need to return the following variables correctly. +Z = zeros(size(X, 1), K); + +% ====================== YOUR CODE HERE ====================== +% Instructions: Compute the projection of the data using only the top K +% eigenvectors in U (first K columns). +% For the i-th example X(i,:), the projection on to the k-th +% eigenvector is given as follows: +% x = X(i, :)'; +% projection_k = x' * U(:, k); +% + + + + +% ============================================================= + +end
new file mode 100644 --- /dev/null +++ b/recoverData.m @@ -0,0 +1,28 @@ +function X_rec = recoverData(Z, U, K) +%RECOVERDATA Recovers an approximation of the original data when using the +%projected data +% X_rec = RECOVERDATA(Z, U, K) recovers an approximation the +% original data that has been reduced to K dimensions. It returns the +% approximate reconstruction in X_rec. +% + +% You need to return the following variables correctly. +X_rec = zeros(size(Z, 1), size(U, 1)); + +% ====================== YOUR CODE HERE ====================== +% Instructions: Compute the approximation of the data by projecting back +% onto the original space using the top K eigenvectors in U. +% +% For the i-th example Z(i,:), the (approximate) +% recovered data for dimension j is given as follows: +% v = Z(i, :)'; +% recovered_j = v' * U(j, 1:K)'; +% +% Notice that U(j, 1:K) is a row vector. +% + + + +% ============================================================= + +end
new file mode 100644 --- /dev/null +++ b/runkMeans.m @@ -0,0 +1,64 @@ +function [centroids, idx] = runkMeans(X, initial_centroids, ... + max_iters, plot_progress) +%RUNKMEANS runs the K-Means algorithm on data matrix X, where each row of X +%is a single example +% [centroids, idx] = RUNKMEANS(X, initial_centroids, max_iters, ... +% plot_progress) runs the K-Means algorithm on data matrix X, where each +% row of X is a single example. It uses initial_centroids used as the +% initial centroids. max_iters specifies the total number of interactions +% of K-Means to execute. plot_progress is a true/false flag that +% indicates if the function should also plot its progress as the +% learning happens. This is set to false by default. runkMeans returns +% centroids, a Kxn matrix of the computed centroids and idx, a m x 1 +% vector of centroid assignments (i.e. each entry in range [1..K]) +% + +% Set default value for plot progress +if ~exist('plot_progress', 'var') || isempty(plot_progress) + plot_progress = false; +end + +% Plot the data if we are plotting progress +if plot_progress + figure; + hold on; +end + +% Initialize values +[m n] = size(X); +K = size(initial_centroids, 1); +centroids = initial_centroids; +previous_centroids = centroids; +idx = zeros(m, 1); + +% Run K-Means +for i=1:max_iters + + % Output progress + fprintf('K-Means iteration %d/%d...\n', i, max_iters); + if exist('OCTAVE_VERSION') + fflush(stdout); + end + + % For each example in X, assign it to the closest centroid + idx = findClosestCentroids(X, centroids); + + % Optionally, plot progress here + if plot_progress + plotProgresskMeans(X, centroids, previous_centroids, idx, K, i); + previous_centroids = centroids; + fprintf('Press enter to continue.\n'); + pause; + end + + % Given the memberships, compute new centroids + centroids = computeCentroids(X, idx, K); +end + +% Hold off if we are plotting progress +if plot_progress + hold off; +end + +end +
new file mode 100644 --- /dev/null +++ b/submit.m @@ -0,0 +1,336 @@ +function submit(partId) +%SUBMIT Submit your code and output to the ml-class servers +% SUBMIT() will connect to the ml-class server and submit your solution + + fprintf('==\n== [ml-class] Submitting Solutions | Programming Exercise %s\n==\n', ... + homework_id()); + if ~exist('partId', 'var') || isempty(partId) + partId = promptPart(); + end + + % Check valid partId + partNames = validParts(); + if ~isValidPartId(partId) + fprintf('!! Invalid homework part selected.\n'); + fprintf('!! Expected an integer from 1 to %d.\n', numel(partNames) + 1); + fprintf('!! Submission Cancelled\n'); + return + end + + [login password] = loginPrompt(); + if isempty(login) + fprintf('!! Submission Cancelled\n'); + return + end + + fprintf('\n== Connecting to ml-class ... '); + if exist('OCTAVE_VERSION') + fflush(stdout); + end + + % Setup submit list + if partId == numel(partNames) + 1 + submitParts = 1:numel(partNames); + else + submitParts = [partId]; + end + + for s = 1:numel(submitParts) + % Submit this part + partId = submitParts(s); + + % Get Challenge + [login, ch, signature] = getChallenge(login); + if isempty(login) || isempty(ch) || isempty(signature) + % Some error occured, error string in first return element. + fprintf('\n!! Error: %s\n\n', login); + return + end + + % Attempt Submission with Challenge + ch_resp = challengeResponse(login, password, ch); + [result, str] = submitSolution(login, ch_resp, partId, output(partId), ... + source(partId), signature); + + fprintf('\n== [ml-class] Submitted Homework %s - Part %d - %s\n', ... + homework_id(), partId, partNames{partId}); + fprintf('== %s\n', strtrim(str)); + if exist('OCTAVE_VERSION') + fflush(stdout); + end + end + +end + +% ================== CONFIGURABLES FOR EACH HOMEWORK ================== + +function id = homework_id() + id = '7'; +end + +function [partNames] = validParts() + partNames = { + 'Find Closest Centroids (k-Means)', ... + 'Compute Centroid Means (k-Means)' ... + 'PCA', ... + 'Project Data (PCA)', ... + 'Recover Data (PCA)' ... + }; +end + +function srcs = sources() + % Separated by part + srcs = { { 'findClosestCentroids.m' }, ... + { 'computeCentroids.m' }, ... + { 'pca.m' }, ... + { 'projectData.m' }, ... + { 'recoverData.m' } ... + }; +end + +function out = output(partId) + % Random Test Cases + X = reshape(sin(1:165), 15, 11); + Z = reshape(cos(1:121), 11, 11); + C = Z(1:5, :); + idx = (1 + mod(1:15, 3))'; + if partId == 1 + idx = findClosestCentroids(X, C); + out = sprintf('%0.5f ', idx(:)); + elseif partId == 2 + centroids = computeCentroids(X, idx, 3); + out = sprintf('%0.5f ', centroids(:)); + elseif partId == 3 + [U, S] = pca(X); + out = sprintf('%0.5f ', abs([U(:); S(:)])); + elseif partId == 4 + X_proj = projectData(X, Z, 5); + out = sprintf('%0.5f ', X_proj(:)); + elseif partId == 5 + X_rec = recoverData(X(:,1:5), Z, 5); + out = sprintf('%0.5f ', X_rec(:)); + end +end + +function url = challenge_url() + url = 'http://www.ml-class.org/course/homework/challenge'; +end + +function url = submit_url() + url = 'http://www.ml-class.org/course/homework/submit'; +end + +% ========================= CHALLENGE HELPERS ========================= + +function src = source(partId) + src = ''; + src_files = sources(); + if partId <= numel(src_files) + flist = src_files{partId}; + for i = 1:numel(flist) + fid = fopen(flist{i}); + while ~feof(fid) + line = fgets(fid); + src = [src line]; + end + fclose(fid); + src = [src '||||||||']; + end + end +end + +function ret = isValidPartId(partId) + partNames = validParts(); + ret = (~isempty(partId)) && (partId >= 1) && (partId <= numel(partNames) + 1); +end + +function partId = promptPart() + fprintf('== Select which part(s) to submit:\n', ... + homework_id()); + partNames = validParts(); + srcFiles = sources(); + for i = 1:numel(partNames) + fprintf('== %d) %s [', i, partNames{i}); + fprintf(' %s ', srcFiles{i}{:}); + fprintf(']\n'); + end + fprintf('== %d) All of the above \n==\nEnter your choice [1-%d]: ', ... + numel(partNames) + 1, numel(partNames) + 1); + selPart = input('', 's'); + partId = str2num(selPart); + if ~isValidPartId(partId) + partId = -1; + end +end + +function [email,ch,signature] = getChallenge(email) + str = urlread(challenge_url(), 'post', {'email_address', email}); + + str = strtrim(str); + [email, str] = strtok (str, '|'); + [ch, str] = strtok (str, '|'); + [signature, str] = strtok (str, '|'); +end + + +function [result, str] = submitSolution(email, ch_resp, part, output, ... + source, signature) + + params = {'homework', homework_id(), ... + 'part', num2str(part), ... + 'email', email, ... + 'output', output, ... + 'source', source, ... + 'challenge_response', ch_resp, ... + 'signature', signature}; + + str = urlread(submit_url(), 'post', params); + + % Parse str to read for success / failure + result = 0; + +end + +% =========================== LOGIN HELPERS =========================== + +function [login password] = loginPrompt() + % Prompt for password + [login password] = basicPrompt(); + + if isempty(login) || isempty(password) + login = []; password = []; + end +end + + +function [login password] = basicPrompt() + login = input('Login (Email address): ', 's'); + password = input('Password: ', 's'); +end + + +function [str] = challengeResponse(email, passwd, challenge) + salt = ')~/|]QMB3[!W`?OVt7qC"@+}'; + str = sha1([challenge sha1([salt email passwd])]); + sel = randperm(numel(str)); + sel = sort(sel(1:16)); + str = str(sel); +end + + +% =============================== SHA-1 ================================ + +function hash = sha1(str) + + % Initialize variables + h0 = uint32(1732584193); + h1 = uint32(4023233417); + h2 = uint32(2562383102); + h3 = uint32(271733878); + h4 = uint32(3285377520); + + % Convert to word array + strlen = numel(str); + + % Break string into chars and append the bit 1 to the message + mC = [double(str) 128]; + mC = [mC zeros(1, 4-mod(numel(mC), 4), 'uint8')]; + + numB = strlen * 8; + if exist('idivide') + numC = idivide(uint32(numB + 65), 512, 'ceil'); + else + numC = ceil(double(numB + 65)/512); + end + numW = numC * 16; + mW = zeros(numW, 1, 'uint32'); + + idx = 1; + for i = 1:4:strlen + 1 + mW(idx) = bitor(bitor(bitor( ... + bitshift(uint32(mC(i)), 24), ... + bitshift(uint32(mC(i+1)), 16)), ... + bitshift(uint32(mC(i+2)), 8)), ... + uint32(mC(i+3))); + idx = idx + 1; + end + + % Append length of message + mW(numW - 1) = uint32(bitshift(uint64(numB), -32)); + mW(numW) = uint32(bitshift(bitshift(uint64(numB), 32), -32)); + + % Process the message in successive 512-bit chs + for cId = 1 : double(numC) + cSt = (cId - 1) * 16 + 1; + cEnd = cId * 16; + ch = mW(cSt : cEnd); + + % Extend the sixteen 32-bit words into eighty 32-bit words + for j = 17 : 80 + ch(j) = ch(j - 3); + ch(j) = bitxor(ch(j), ch(j - 8)); + ch(j) = bitxor(ch(j), ch(j - 14)); + ch(j) = bitxor(ch(j), ch(j - 16)); + ch(j) = bitrotate(ch(j), 1); + end + + % Initialize hash value for this ch + a = h0; + b = h1; + c = h2; + d = h3; + e = h4; + + % Main loop + for i = 1 : 80 + if(i >= 1 && i <= 20) + f = bitor(bitand(b, c), bitand(bitcmp(b), d)); + k = uint32(1518500249); + elseif(i >= 21 && i <= 40) + f = bitxor(bitxor(b, c), d); + k = uint32(1859775393); + elseif(i >= 41 && i <= 60) + f = bitor(bitor(bitand(b, c), bitand(b, d)), bitand(c, d)); + k = uint32(2400959708); + elseif(i >= 61 && i <= 80) + f = bitxor(bitxor(b, c), d); + k = uint32(3395469782); + end + + t = bitrotate(a, 5); + t = bitadd(t, f); + t = bitadd(t, e); + t = bitadd(t, k); + t = bitadd(t, ch(i)); + e = d; + d = c; + c = bitrotate(b, 30); + b = a; + a = t; + + end + h0 = bitadd(h0, a); + h1 = bitadd(h1, b); + h2 = bitadd(h2, c); + h3 = bitadd(h3, d); + h4 = bitadd(h4, e); + + end + + hash = reshape(dec2hex(double([h0 h1 h2 h3 h4]), 8)', [1 40]); + + hash = lower(hash); + +end + +function ret = bitadd(iA, iB) + ret = double(iA) + double(iB); + ret = bitset(ret, 33, 0); + ret = uint32(ret); +end + +function ret = bitrotate(iA, places) + t = bitshift(iA, places - 32); + ret = bitshift(iA, places); + ret = bitor(ret, t); +end
new file mode 100644 --- /dev/null +++ b/submitWeb.m @@ -0,0 +1,352 @@ +function submitWeb(partId) +%SUBMITWEB Generates a base64 encoded string for web-based submissions +% SUBMITWEB() will generate a base64 encoded string so that you can submit your +% solutions via a web form + + fprintf('==\n== [ml-class] Submitting Solutions | Programming Exercise %s\n==\n', ... + homework_id()); + if ~exist('partId', 'var') || isempty(partId) + partId = promptPart(); + end + + % Check valid partId + partNames = validParts(); + if ~isValidPartId(partId) + fprintf('!! Invalid homework part selected.\n'); + fprintf('!! Expected an integer from 1 to %d.\n', numel(partNames)); + fprintf('!! Submission Cancelled\n'); + return + end + + [login] = loginPrompt(); + if isempty(login) + fprintf('!! Submission Cancelled\n'); + return + end + + [result] = submitSolution(login, partId, output(partId), ... + source(partId)); + result = base64encode(result); + + fprintf('\nSave as submission file [submit_ex%s_part%d.txt]: ', ... + homework_id(), partId); + saveAsFile = input('', 's'); + if (isempty(saveAsFile)) + saveAsFile = sprintf('submit_ex%s_part%d.txt', homework_id(), partId); + end + + fid = fopen(saveAsFile, 'w'); + if (fid) + fwrite(fid, result); + fclose(fid); + fprintf('\nSaved your solutions to %s.\n\n', saveAsFile); + fprintf(['You can now submit your solutions through the web \n' ... + 'form in the programming exercises. Select the corresponding \n' ... + 'programming exercise to access the form.\n']); + + else + fprintf('Unable to save to %s\n\n', saveAsFile); + fprintf(['You can create a submission file by saving the \n' ... + 'following text in a file: (press enter to continue)\n\n']); + pause; + fprintf(result); + end + +end + +% ================== CONFIGURABLES FOR EACH HOMEWORK ================== + +function id = homework_id() + id = '7'; +end + +function [partNames] = validParts() + partNames = { + 'Find Closest Centroids (k-Means)', ... + 'Compute Centroid Means (k-Means)' ... + 'PCA', ... + 'Project Data (PCA)', ... + 'Recover Data (PCA)' ... + }; +end + +function srcs = sources() + % Separated by part + srcs = { { 'findClosestCentroids.m' }, ... + { 'computeCentroids.m' }, ... + { 'pca.m' }, ... + { 'projectData.m' }, ... + { 'recoverData.m' } ... + }; +end + +function out = output(partId) + % Random Test Cases + X = reshape(sin(1:165), 15, 11); + Z = reshape(cos(1:121), 11, 11); + C = Z(1:5, :); + idx = (1 + mod(1:15, 3))'; + if partId == 1 + idx = findClosestCentroids(X, C); + out = sprintf('%0.5f ', idx(:)); + elseif partId == 2 + centroids = computeCentroids(X, idx, 3); + out = sprintf('%0.5f ', centroids(:)); + elseif partId == 3 + [U, S] = pca(X); + out = sprintf('%0.5f ', abs([U(:); S(:)])); + elseif partId == 4 + X_proj = projectData(X, Z, 5); + out = sprintf('%0.5f ', X_proj(:)); + elseif partId == 5 + X_rec = recoverData(X(:,1:5), Z, 5); + out = sprintf('%0.5f ', X_rec(:)); + end +end + + +% ========================= SUBMIT HELPERS ========================= + +function src = source(partId) + src = ''; + src_files = sources(); + if partId <= numel(src_files) + flist = src_files{partId}; + for i = 1:numel(flist) + fid = fopen(flist{i}); + while ~feof(fid) + line = fgets(fid); + src = [src line]; + end + fclose(fid); + src = [src '||||||||']; + end + end +end + +function ret = isValidPartId(partId) + partNames = validParts(); + ret = (~isempty(partId)) && (partId >= 1) && (partId <= numel(partNames)); +end + +function partId = promptPart() + fprintf('== Select which part(s) to submit:\n', ... + homework_id()); + partNames = validParts(); + srcFiles = sources(); + for i = 1:numel(partNames) + fprintf('== %d) %s [', i, partNames{i}); + fprintf(' %s ', srcFiles{i}{:}); + fprintf(']\n'); + end + fprintf('\nEnter your choice [1-%d]: ', ... + numel(partNames)); + selPart = input('', 's'); + partId = str2num(selPart); + if ~isValidPartId(partId) + partId = -1; + end +end + + +function [result, str] = submitSolution(email, part, output, source) + + result = ['a:5:{' ... + p_s('homework') p_s64(homework_id()) ... + p_s('part') p_s64(part) ... + p_s('email') p_s64(email) ... + p_s('output') p_s64(output) ... + p_s('source') p_s64(source) ... + '}']; + +end + +function s = p_s(str) + s = ['s:' num2str(numel(str)) ':"' str '";']; +end + +function s = p_s64(str) + str = base64encode(str, ''); + s = ['s:' num2str(numel(str)) ':"' str '";']; +end + +% =========================== LOGIN HELPERS =========================== + +function [login] = loginPrompt() + % Prompt for password + [login] = basicPrompt(); +end + + +function [login] = basicPrompt() + login = input('Login (Email address): ', 's'); +end + + +% =========================== Base64 Encoder ============================ +% Thanks to Peter John Acklam +% + +function y = base64encode(x, eol) +%BASE64ENCODE Perform base64 encoding on a string. +% +% BASE64ENCODE(STR, EOL) encode the given string STR. EOL is the line ending +% sequence to use; it is optional and defaults to '\n' (ASCII decimal 10). +% The returned encoded string is broken into lines of no more than 76 +% characters each, and each line will end with EOL unless it is empty. Let +% EOL be empty if you do not want the encoded string broken into lines. +% +% STR and EOL don't have to be strings (i.e., char arrays). The only +% requirement is that they are vectors containing values in the range 0-255. +% +% This function may be used to encode strings into the Base64 encoding +% specified in RFC 2045 - MIME (Multipurpose Internet Mail Extensions). The +% Base64 encoding is designed to represent arbitrary sequences of octets in a +% form that need not be humanly readable. A 65-character subset +% ([A-Za-z0-9+/=]) of US-ASCII is used, enabling 6 bits to be represented per +% printable character. +% +% Examples +% -------- +% +% If you want to encode a large file, you should encode it in chunks that are +% a multiple of 57 bytes. This ensures that the base64 lines line up and +% that you do not end up with padding in the middle. 57 bytes of data fills +% one complete base64 line (76 == 57*4/3): +% +% If ifid and ofid are two file identifiers opened for reading and writing, +% respectively, then you can base64 encode the data with +% +% while ~feof(ifid) +% fwrite(ofid, base64encode(fread(ifid, 60*57))); +% end +% +% or, if you have enough memory, +% +% fwrite(ofid, base64encode(fread(ifid))); +% +% See also BASE64DECODE. + +% Author: Peter John Acklam +% Time-stamp: 2004-02-03 21:36:56 +0100 +% E-mail: pjacklam@online.no +% URL: http://home.online.no/~pjacklam + + if isnumeric(x) + x = num2str(x); + end + + % make sure we have the EOL value + if nargin < 2 + eol = sprintf('\n'); + else + if sum(size(eol) > 1) > 1 + error('EOL must be a vector.'); + end + if any(eol(:) > 255) + error('EOL can not contain values larger than 255.'); + end + end + + if sum(size(x) > 1) > 1 + error('STR must be a vector.'); + end + + x = uint8(x); + eol = uint8(eol); + + ndbytes = length(x); % number of decoded bytes + nchunks = ceil(ndbytes / 3); % number of chunks/groups + nebytes = 4 * nchunks; % number of encoded bytes + + % add padding if necessary, to make the length of x a multiple of 3 + if rem(ndbytes, 3) + x(end+1 : 3*nchunks) = 0; + end + + x = reshape(x, [3, nchunks]); % reshape the data + y = repmat(uint8(0), 4, nchunks); % for the encoded data + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Split up every 3 bytes into 4 pieces + % + % aaaaaabb bbbbcccc ccdddddd + % + % to form + % + % 00aaaaaa 00bbbbbb 00cccccc 00dddddd + % + y(1,:) = bitshift(x(1,:), -2); % 6 highest bits of x(1,:) + + y(2,:) = bitshift(bitand(x(1,:), 3), 4); % 2 lowest bits of x(1,:) + y(2,:) = bitor(y(2,:), bitshift(x(2,:), -4)); % 4 highest bits of x(2,:) + + y(3,:) = bitshift(bitand(x(2,:), 15), 2); % 4 lowest bits of x(2,:) + y(3,:) = bitor(y(3,:), bitshift(x(3,:), -6)); % 2 highest bits of x(3,:) + + y(4,:) = bitand(x(3,:), 63); % 6 lowest bits of x(3,:) + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Now perform the following mapping + % + % 0 - 25 -> A-Z + % 26 - 51 -> a-z + % 52 - 61 -> 0-9 + % 62 -> + + % 63 -> / + % + % We could use a mapping vector like + % + % ['A':'Z', 'a':'z', '0':'9', '+/'] + % + % but that would require an index vector of class double. + % + z = repmat(uint8(0), size(y)); + i = y <= 25; z(i) = 'A' + double(y(i)); + i = 26 <= y & y <= 51; z(i) = 'a' - 26 + double(y(i)); + i = 52 <= y & y <= 61; z(i) = '0' - 52 + double(y(i)); + i = y == 62; z(i) = '+'; + i = y == 63; z(i) = '/'; + y = z; + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Add padding if necessary. + % + npbytes = 3 * nchunks - ndbytes; % number of padding bytes + if npbytes + y(end-npbytes+1 : end) = '='; % '=' is used for padding + end + + if isempty(eol) + + % reshape to a row vector + y = reshape(y, [1, nebytes]); + + else + + nlines = ceil(nebytes / 76); % number of lines + neolbytes = length(eol); % number of bytes in eol string + + % pad data so it becomes a multiple of 76 elements + y = [y(:) ; zeros(76 * nlines - numel(y), 1)]; + y(nebytes + 1 : 76 * nlines) = 0; + y = reshape(y, 76, nlines); + + % insert eol strings + eol = eol(:); + y(end + 1 : end + neolbytes, :) = eol(:, ones(1, nlines)); + + % remove padding, but keep the last eol string + m = nebytes + neolbytes * (nlines - 1); + n = (76+neolbytes)*nlines - neolbytes; + y(m+1 : n) = ''; + + % extract and reshape to row vector + y = reshape(y, 1, m+neolbytes); + + end + + % output is a character array + y = char(y); + +end