{"id":1964,"date":"2018-07-09T15:23:21","date_gmt":"2018-07-09T13:23:21","guid":{"rendered":"https:\/\/blogs.fu-berlin.de\/reseda\/?page_id=1964"},"modified":"2018-09-30T17:04:48","modified_gmt":"2018-09-30T15:04:48","slug":"svm-classification","status":"publish","type":"page","link":"https:\/\/blogs.fu-berlin.de\/reseda\/svm-classification\/","title":{"rendered":"SVM Classification"},"content":{"rendered":"<p>Just as with the Random Forest, there are quite a few R packages that provide SVM, e.g., <a href=\"https:\/\/cran.r-project.org\/web\/packages\/caret\/caret.pdf\" rel=\"noopener\" target=\"_blank\">caret<\/a>, <a href=\"https:\/\/cran.r-project.org\/web\/packages\/e1071\/e1071.pdf\" rel=\"noopener\" target=\"_blank\">e1071<\/a>, or <a href=\"https:\/\/cran.r-project.org\/web\/packages\/kernlab\/kernlab.pdf\" rel=\"noopener\" target=\"_blank\">kernLab<\/a>. We will use the e1071 package, as it offers <a href=\"https:\/\/cran.r-project.org\/web\/packages\/e1071\/vignettes\/svmdoc.pdf\" rel=\"noopener\" target=\"_blank\">an interface to the well-known libsvm <\/a>implementation.  <\/p>\n<p>Below you can see a complete code implementation. While this is already executable with your input data, you should read the comprehensive in-depth guide in the following to understand the code in detail.<\/p>\n<pre class=\"theme:amityreseda\">\r\n# import packages\r\nlibrary(raster)\r\nlibrary(e1071)\r\n \r\n# import image (img) and shapefile (shp)\r\nsetwd(\"\/media\/sf_exchange\/landsatdata\/\")\r\nimg &lt;- brick(&quot;LC081930232017060201T1-SC20180613160412_subset.tif&quot;)\r\nshp &lt;- shapefile(&quot;training_data.shp&quot;)\r\n \r\n# extract samples with class labels and put them all together in a dataframe\r\nnames(img) &lt;- c(&quot;b1&quot;, &quot;b2&quot;, &quot;b3&quot;, &quot;b4&quot;, &quot;b5&quot;, &quot;b6&quot;, &quot;b7&quot;)\r\nsmp &lt;- extract(img, shp, df = TRUE)\r\nsmp$cl &lt;- as.factor( shp$classes[ match(smp$ID, seq(nrow(shp)) ) ] )\r\nsmp &lt;- smp[-1]\r\n \r\n# optional: shuffle samples and undersample if necessary\r\nsmp &lt;- smp[sample(nrow(smp)),]\r\nsmp &lt;- smp[ave(1:(nrow(smp)), smp$cl, FUN = seq) &lt;= min(summary(smp$cl)), ]\r\n \r\n# tune and train model via gridsearch (gamma &amp; cost)\r\ngammas = 2^(-8:5)\r\ncosts = 2^(-5:8)\r\nsvmgs &lt;- tune(svm,\r\n              train.x = smp[-ncol(smp)],\r\n              train.y = smp$cl,\r\n              scale = TRUE,\r\n              kernel = &quot;radial&quot;, \r\n              type = &quot;C-classification&quot;,\r\n              ranges = list(gamma = gammas,\r\n                            cost = costs),\r\n              tunecontrol = tune.control(cross = 5)\r\n              )\r\n\r\n# save svmmodel\r\nsvmmodel &lt;- svmgs$best.model\r\nsave(svmmodel, file = &quot;svmmodel.RData&quot;)\r\n \r\n# predict image data with svm model\r\nresult &lt;- predict(img,\r\n                  svmmodel,\r\n                  filename = &quot;classification_svm.tif&quot;,\r\n                  overwrite = TRUE\r\n                  )\r\n<\/pre>\n<p><\/br><a name=\"1\"><\/a><\/p>\n<h1><font color=\"003366\">In-depth Guide<\/font><\/h1>\n<p>In order to be able to use the functions of the e1071 package, we must additionally load the library into the current session via <span class=\"crayon-inline theme:amityreseda\">library()<\/span>. If you do not use our VM, you must first download and install the packages with <span class=\"crayon-inline theme:amityreseda\">install.packages()<\/span>:<\/p>\n<pre class=\"theme:amityreseda\">\r\n#install.packages(\"raster\")\r\n#install.packages(\"e1071\")\r\nlibrary(raster)\r\nlibrary(e1071)\r\n<\/pre>\n<p>First, it is necessary to process the training samples in the form of a data frame. The necessary steps are described in detail in the <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/prepare-samples\/\">previous section<\/a>.<\/p>\n<pre class=\"theme:amityreseda\">\r\nnames(img) &lt;- c(&quot;b1&quot;, &quot;b2&quot;, &quot;b3&quot;, &quot;b4&quot;, &quot;b5&quot;, &quot;b6&quot;, &quot;b7&quot;)\r\nsmp &lt;- extract(img, shp, df = TRUE)\r\nsmp$cl &lt;- as.factor( shp$classes[ match(smp$ID, seq(nrow(shp)) ) ] )\r\nsmp &lt;- smp[-1]\r\n<\/pre>\n<p>After that, you can identify the number of available training samples per class with <span class=\"crayon-inline theme:amityreseda\">summary()<\/span>. There is often an imbalance in the number of those training pixels, i.e., one class is represented by a large number of pixels, while another class has very few samples:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsummary(smp$cl)\r\n##  baresoil    forest grassland  urban_hd  urban_ld     water \r\n##       719      2074      1226      1284       969       763\r\n<\/pre>\n<p>This often leads to the problem that classifiers favor and overclass strongly-represented classes in the classification. Unfortunately, a SVM can not handle this problem as elegantly as <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/random-forest\/\" rel=\"noopener\" target=\"_blank\">Random Forest<\/a> methods, why you might need to prepare your training data:<br \/>\n<strong>Only if your data is imbalanced<\/strong>, you should consider to do the following undersampling: We shuffle our entire training dataset and pick out the same number of samples for each class. The purpose of shuffle is to prevent sampling of all pixels that are next to each other (spatial autocorrelation). The training data is mixed using the <span class=\"crayon-inline theme:amityreseda\">sample()<\/span> method. We sample all rows of our dataset <span class=\"crayon-inline theme:amityreseda\">nrow(smp)<\/span> in a random manner in line 10:<\/p>\n<pre class=\"theme:amityreseda\">\r\nhead(smp)\r\n##    b1  b2  b3  b4  b5  b6 b7    cl\r\n## 1 192 229 321 204 161 100 72 water\r\n## 2 179 203 233 130 156  93 68 water\r\n## 3 189 221 272 164 173 109 81 water\r\n## 4 159 179 188  97 125  63 40 water\r\n## 5 171 194 203 116 146  82 56 water\r\n## 6 164 188 196 108 138  71 51 water\r\n\r\nsmp &lt;- smp[sample(nrow(smp)), ]\r\n\r\nhead(smp)\r\n##       b1  b2  b3  b4   b5   b6   b7       cl\r\n## 2024 176 195 324 234 2146 1022  479   forest\r\n## 5545 409 517 844 988 3038 3005 1818 baresoil\r\n## 2877 195 212 363 293 2500 1435  690   forest\r\n## 6210 303 322 482 411 2465 1410  872 urban_hd\r\n## 6613 367 418 672 600 2855 1876 1210 urban_ld\r\n## 321  163 178 201 136  124   76   56    water\r\n<\/pre>\n<p>Compare the first six entries of the <span class=\"crayon-inline theme:amityreseda\">smp<\/span> dataset before and after the command in line 10 using the <span class=\"crayon-inline theme:amityreseda\">head()<\/span> function. You will notice that the order of the rows is shuffled!<br \/>\nWith the <span class=\"crayon-inline theme:amityreseda\">min()<\/span> function, we can select the minimum value from the class column&#8217;s summary and use the <span class=\"crayon-inline theme:amityreseda\">ave()<\/span> function to make a selection of the same size for each class:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsummary(smp$cl)\r\n##  baresoil    forest grassland  urban_hd  urban_ld     water \r\n##       719      2074      1226      1284       969       763\r\n\r\nsmp.maxsamplesize &lt;- min(summary(smp$cl))\r\nsmp.maxsamplesize\r\n## [1] 719\r\n\r\nsmp &lt;- smp[ave(1:(nrow(smp)), smp$cl, FUN = seq) &lt;= smp.maxsamplesize, ]\r\n\r\nsummary(smp$cl)\r\n##  baresoil    forest grassland  urban_hd  urban_ld     water \r\n##       719       719       719       719       719       719\r\n<\/pre>\n<p>Let the training begin!<br \/>\nClassifications using an SVM require two parameters: one gamma \\(\\gamma\\) and one cost \\(C\\) value (for more details refer to the <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/support-vector-machine\/\" rel=\"noopener\" target=\"_blank\">SVM section<\/a>). These hyperparameters significantly determine the performance of the model. Finding the best hyparameters is not trivial and the best combination can not be determined in advance. Thus, we try to find the best combination by trial and error. Therefore, we create two vectors comprising all values that should be tried out:<\/p>\n<pre class=\"theme:amityreseda\">\r\ngammas = 2^(-8:5)\r\ngammas\r\n##  [1]  0.00390625  0.00781250  0.01562500  0.03125000  0.06250000\r\n##  [6]  0.12500000  0.25000000  0.50000000  1.00000000  2.00000000\r\n## [11]  4.00000000  8.00000000 16.00000000 32.00000000\r\n\r\ncosts = 2^(-5:8)\r\ncosts\r\n##  [1]   0.03125   0.06250   0.12500   0.25000   0.50000   1.00000   2.00000\r\n##  [8]   4.00000   8.00000  16.00000  32.00000  64.00000 128.00000 256.00000\r\n<\/pre>\n<p>So we have 14 different values for \\(\\gamma\\) and 14 different values for \\(C\\). Thus, the whole training process is done for 196 (14 * 14) models, often referred to as <strong>gridsearch<\/strong>. Conversely, this means that the more parameters we check, the longer the training process takes.<br \/>\nWe start the training with the <span class=\"crayon-inline theme:amityreseda\">tune()<\/span> function. We need to specify the training samples as <span class=\"crayon-inline theme:amityreseda\">train.x<\/span>, i.e., all columns of our <span class=\"crayon-inline theme:amityreseda\">smp<\/span> dataframe except the last one, and the corresponding class labels as <span class=\"crayon-inline theme:amityreseda\">train.y<\/span>, i.e. the last column of our <span class=\"crayon-inline theme:amityreseda\">smp<\/span> dataframe:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsvmgs &lt;- tune(svm,\r\n              train.x = smp[-ncol(smp)],\r\n              train.y = smp[ ncol(smp)],\r\n              type = &quot;C-classification&quot;,\r\n              kernel = &quot;radial&quot;, \r\n              scale = TRUE,\r\n              ranges = list(gamma = gammas, cost = costs),\r\n              tunecontrol = tune.control(cross = 5)\r\n              )\r\n<\/pre>\n<p>We have to set the <span class=\"crayon-inline theme:amityreseda\">type<\/span> of the SVM to <span class=\"crayon-inline theme:amityreseda\">&#8220;C-classification&#8221;<\/span> in line 4 in order to perform a classification task. Furthermore, we set the kernel used in training and predicting to a <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/support-vector-machine\/\" rel=\"noopener\" target=\"_blank\">RBF kernel<\/a> via <span class=\"crayon-inline theme:amityreseda\">&#8220;radial&#8221;<\/span>. We set the argument <span class=\"crayon-inline theme:amityreseda\">scale<\/span> to <span class=\"crayon-inline theme:amityreseda\">TRUE<\/span> in order to initiate the <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/support-vector-machine\/\" rel=\"noopener\" target=\"_blank\">z-transformation of our data<\/a>. The argument <span class=\"crayon-inline theme:amityreseda\">ranges<\/span> in line 7 takes a named list of parameter vectors spanning the sampling range. We put our <span class=\"crayon-inline theme:amityreseda\">gammas<\/span> and <span class=\"crayon-inline theme:amityreseda\">costs<\/span> vectors in this list. By using the <span class=\"crayon-inline theme:amityreseda\">tunecontrol<\/span> argument in line 8, you can set k for <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/support-vector-machine\/\" rel=\"noopener\" target=\"_blank\">the k-fold cross validation on the training data<\/a>, which is necessary to assess the model performance. <\/p>\n<p>Depending on the complexity of the data, this step may take some time. Once completed, you can check the output by calling the resultant object name:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsvmgs\r\n## \r\n## Parameter tuning of 'svm':\r\n## \r\n## - sampling method: 5-fold cross validation \r\n## \r\n## - best parameters:\r\n##  gamma cost\r\n##      2    8\r\n## \r\n## - best performance: 0.02132796\r\n<\/pre>\n<p>In the course of the cross-validation, the overall accuracies were compared and the best parameters were determined: In our example, those are 2 and 8 for \\(\\gamma\\) and \\(C\\), respectively. Furthermore, the error of the best model is displayed: 2.1% error rate (which is quite good!).<br \/>\nWe can plot the errors of all 196 different hyperparameter combinations in the so called gridsearch using the standard <span class=\"crayon-inline theme:amityreseda\">plot()<\/span> function:<\/p>\n<pre class=\"theme:amityreseda\">\r\nplot(svmgs)\r\n<\/pre>\n<p><a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022.png\" alt=\"\" width=\"1256\" height=\"853\" class=\"aligncenter size-full wp-image-2170\" srcset=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022.png 1256w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022-300x204.png 300w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022-768x522.png 768w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022-1024x695.png 1024w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_022-1200x815.png 1200w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><\/p>\n<p>The plot appears very homogeneous, as the performance varies only very slightly and is already at a very high level. It can happen that the highest accuracies are found on the edge of the grid search. Then the training with other ranges for \\(\\gamma\\) and \\(C\\) should be done again.<br \/>\nWe can extract the best model out of our <span class=\"crayon-inline theme:amityreseda\">svmgs<\/span> to use for image prediction:<\/p>\n<pre class=\"theme:amityreseda\">\r\nsvmmodel &lt;- svmgs$best.model\r\nsvmmodel\r\n## \r\n## Call:\r\n## best.tune(method = svm, train.x = smp[-ncol(smp)], train.y = smp$cl, \r\n##     ranges = list(gamma = gammas, cost = costs), tunecontrol = tune.control(cross = 5), \r\n##     scale = TRUE, kernel = &quot;radial&quot;, type = &quot;C-classification&quot;)\r\n## \r\n## \r\n## Parameters:\r\n##    SVM-Type:  C-classification \r\n##  SVM-Kernel:  radial \r\n##        cost:  8 \r\n##       gamma:  2 \r\n## \r\n## Number of Support Vectors:  602\r\n<\/pre>\n<p>Save the best model by using the <span class=\"crayon-inline theme:amityreseda\">save()<\/span> function. This function saves the model object <span class=\"crayon-inline theme:amityreseda\">svmmodel<\/span> to your working directory, so that you have it permanently stored on your hard drive. If needed, you can load it any time with <span class=\"crayon-inline theme:amityreseda\">load()<\/span>.<\/p>\n<pre class=\"theme:amityreseda\">\r\nsave(svmmodel, file = \"svmmodel.RData\")\r\n#load(\"svmmodel.RData\")\r\n<\/pre>\n<p>Since your SVM model is now completely trained, you can use it to predict all the pixels in your image. The command method <span class=\"crayon-inline theme:amityreseda\">predict()<\/span> takes a lot of work from you: It is recognized that there is an image which will be processed pixel by pixel. As with the training pixels, each image pixel is now individually classified and finally reassembled into your final classification image. Use the argument <span class=\"crayon-inline theme:amityreseda\">filename = <\/span> to specify the name of your output map:<\/p>\n<pre class=\"theme:amityreseda\">\r\nresult &lt;- predict(img,\r\n                  svmmodel,\r\n                  filename = &quot;classification_svm.tif&quot;,\r\n                  overwrite = TRUE\r\n                  )\r\n\r\nplot(result, \r\n     axes = FALSE, \r\n     box = FALSE,\r\n     col = c(\"#fbf793\", # baresoil\r\n             \"#006601\", # forest\r\n             \"#bfe578\", # grassland\r\n             \"#d00000\", # urban_hd\r\n             \"#fa6700\", # urban_ld\r\n             \"#6569ff\"  # water\r\n             )\r\n     )\r\n<\/pre>\n<p>Support Vector Machine classification completed!<br \/>\nIf you store the result of the <span class=\"crayon-inline theme:amityreseda\">predict()<\/span> function in an object, e.g., <span class=\"crayon-inline theme:amityreseda\">result<\/span>, you can plot the map using the standard plot command and passing this object:<\/p>\n<p><a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007.png\" alt=\"\" width=\"1096\" height=\"471\" class=\"aligncenter size-full wp-image-2126\" srcset=\"https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007.png 1096w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007-300x129.png 300w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007-768x330.png 768w, https:\/\/blogs.fu-berlin.de\/reseda\/files\/2018\/07\/cla_007-1024x440.png 1024w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/a><\/p>\n<p><\/br><\/br><\/p>\n<hr style=\"height:4px;background-color:#6b9e1f\">\n<a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/regression-in-r\/\"><br \/>\n<button style=\"width:100%;text-align:right;padding: 10 0;background-color:white;margin:-55px 0 0 0\"><\/p>\n<div style=\"font-family: 'Noto Sans',sans-serif;line-height: 1.2\">\n<span style=\"font-size: 12px;color:#bfbfbf\"><strong><em>NEXT<\/em><\/strong><\/span><br \/>\n<span style=\"font-size: 30px;color:#6b9e1f\"><strong><em>Regression in R<\/em><\/strong><\/span>\n<\/div>\n<p><\/button><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just as with the Random Forest, there are quite a few R packages that provide SVM, e.g., caret, e1071, or kernLab. We will use the e1071 package, as it offers an interface to the well-known libsvm implementation. Below you can see a complete code implementation. While this is already executable with your input data, you &hellip; <a href=\"https:\/\/blogs.fu-berlin.de\/reseda\/svm-classification\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;SVM Classification&#8221;<\/span><\/a><\/p>\n","protected":false},"author":3237,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1964","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/1964","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/users\/3237"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/comments?post=1964"}],"version-history":[{"count":24,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/1964\/revisions"}],"predecessor-version":[{"id":2689,"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/pages\/1964\/revisions\/2689"}],"wp:attachment":[{"href":"https:\/\/blogs.fu-berlin.de\/reseda\/wp-json\/wp\/v2\/media?parent=1964"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}