From 88a9bded27bef3d2ab5f6e1d5196b8a9620144f1 Mon Sep 17 00:00:00 2001 From: mikoff Date: Fri, 22 Mar 2024 12:21:31 +0000 Subject: [PATCH] deploy: mikoff/blog@95b8eb4cec5fcf80f54f64c5ec7450a0cf08738a --- 404.html | 7 - about/index.html | 11 - categories/index.html | 7 - categories/page/1/index.html | 2 - ...c4f230507ad036da635d3621401d42ec4e2835.css | 1 - ...4470c58923aaaf385e2a73982c31dd7642decb.css | 1 - css/image.css | 22 - css/spoiler.css | 33 -- fontdata/css/academicons.css | 535 ----------------- fontdata/css/academicons.min.css | 1 - fontdata/fonts/academicons.woff | Bin 117932 -> 0 bytes images/zoom_in.png | Bin 680 -> 0 bytes images/zoom_out.png | Bin 657 -> 0 bytes index.html | 7 - index.xml | 4 +- .../academic-icons}/fonts/academicons.eot | Bin 61586 -> 68058 bytes .../academic-icons}/fonts/academicons.svg | 402 ++++++++++--- .../academic-icons}/fonts/academicons.ttf | Bin 61400 -> 67872 bytes plugins/academic-icons/fonts/academicons.woff | Bin 0 -> 131616 bytes posts/bypassing-censorship/index.html | 207 ------- posts/documenting-the-experience/index.html | 17 - posts/ekf-slam.md/index.html | 48 -- posts/index.html | 22 - posts/index.xml | 4 +- .../inverse-transform-sampling.md/index.html | 226 -------- posts/likelihood-and-log-sum-exp/index.html | 102 ---- .../index.html | 205 ------- posts/nn-training.md/index.html | 185 ------ .../index.html | 170 ------ .../forward-backpropagation.drawio-old.svg | 4 + posts/notes-on-backpropagation/index.html | 305 ---------- posts/optimization-on-manifold/index.html | 250 -------- posts/page/1/index.html | 2 - posts/particle-filter.md/index.html | 90 --- .../index.html | 220 ------- posts/point-cloud-alignment.md/index.html | 143 ----- .../probability-density-transform/index.html | 191 ------ posts/uncertainty-propagation.md/index.html | 543 ------------------ tags/automatic-differentiation/index.html | 8 - tags/automatic-differentiation/index.xml | 2 +- .../page/1/index.html | 2 - tags/backpropagation/index.html | 8 - tags/backpropagation/page/1/index.html | 2 - tags/bayesian-estimation/index.html | 8 - tags/bayesian-estimation/index.xml | 4 +- tags/bayesian-estimation/page/1/index.html | 2 - tags/censorship/index.html | 8 - tags/censorship/page/1/index.html | 2 - tags/deep-learning/index.html | 10 - tags/deep-learning/index.xml | 2 +- tags/deep-learning/page/1/index.html | 2 - tags/ekf/index.html | 8 - tags/ekf/page/1/index.html | 2 - tags/factor-graph/index.html | 8 - tags/factor-graph/index.xml | 2 +- tags/factor-graph/page/1/index.html | 2 - tags/index.html | 27 - tags/index.xml | 2 +- tags/iterative-closest-point/index.html | 8 - tags/iterative-closest-point/index.xml | 2 +- .../iterative-closest-point/page/1/index.html | 2 - tags/kalman-filter/index.html | 8 - tags/kalman-filter/page/1/index.html | 2 - tags/least-squares/index.html | 8 - tags/least-squares/index.xml | 2 +- tags/least-squares/page/1/index.html | 2 - tags/lie-algebra/index.html | 9 - tags/lie-algebra/index.xml | 2 +- tags/lie-algebra/page/1/index.html | 2 - tags/lie-group/index.html | 8 - tags/lie-group/index.xml | 2 +- tags/lie-group/page/1/index.html | 2 - tags/lie-groups/index.html | 8 - tags/lie-groups/index.xml | 2 +- tags/lie-groups/page/1/index.html | 2 - tags/linear-regression/index.html | 8 - tags/linear-regression/index.xml | 2 +- tags/linear-regression/page/1/index.html | 2 - tags/log-likelihood/index.html | 8 - tags/log-likelihood/index.xml | 2 +- tags/log-likelihood/page/1/index.html | 2 - tags/logistic-regression/index.html | 8 - tags/logistic-regression/index.xml | 2 +- tags/logistic-regression/page/1/index.html | 2 - tags/manifolds/index.html | 8 - tags/manifolds/page/1/index.html | 2 - tags/map/index.html | 8 - tags/map/index.xml | 2 +- tags/map/page/1/index.html | 2 - tags/me/index.html | 8 - tags/me/index.xml | 2 +- tags/me/page/1/index.html | 2 - tags/mle/index.html | 8 - tags/mle/index.xml | 2 +- tags/mle/page/1/index.html | 2 - tags/neural-networks/index.html | 8 - tags/neural-networks/index.xml | 2 +- tags/neural-networks/page/1/index.html | 2 - tags/optimization/index.html | 8 - tags/optimization/page/1/index.html | 2 - tags/page/1/index.html | 2 - tags/page/2/index.html | 26 - tags/particle-filter/index.html | 9 - tags/particle-filter/index.xml | 2 +- tags/particle-filter/page/1/index.html | 2 - tags/pcl/index.html | 8 - tags/pcl/page/1/index.html | 2 - tags/pdf/index.html | 8 - tags/pdf/index.xml | 2 +- tags/pdf/page/1/index.html | 2 - tags/personal/index.html | 8 - tags/personal/index.xml | 2 +- tags/personal/page/1/index.html | 2 - tags/point-cloud-alignment/index.html | 9 - tags/point-cloud-alignment/index.xml | 2 +- tags/point-cloud-alignment/page/1/index.html | 2 - tags/probability/index.html | 9 - tags/probability/page/1/index.html | 2 - tags/reading/index.html | 8 - tags/reading/index.xml | 2 +- tags/reading/page/1/index.html | 2 - tags/sampling/index.html | 9 - tags/sampling/index.xml | 4 +- tags/sampling/page/1/index.html | 2 - tags/sensor-fusion/index.html | 8 - tags/sensor-fusion/index.xml | 2 +- tags/sensor-fusion/page/1/index.html | 2 - tags/sigmoid/index.html | 8 - tags/sigmoid/page/1/index.html | 2 - tags/slam/index.html | 8 - tags/slam/page/1/index.html | 2 - tags/statistics/index.html | 9 - tags/statistics/index.xml | 4 +- tags/statistics/page/1/index.html | 2 - tags/stereo-vision/index.html | 8 - tags/stereo-vision/index.xml | 4 +- tags/stereo-vision/page/1/index.html | 2 - tags/svd/index.html | 8 - tags/svd/page/1/index.html | 2 - tags/transform/index.html | 8 - tags/transform/index.xml | 2 +- tags/transform/page/1/index.html | 2 - tags/uncertainty-propagation/index.html | 8 - tags/uncertainty-propagation/index.xml | 2 +- .../uncertainty-propagation/page/1/index.html | 2 - tags/vpn/index.html | 8 - tags/vpn/page/1/index.html | 2 - 147 files changed, 350 insertions(+), 4134 deletions(-) delete mode 100644 404.html delete mode 100644 about/index.html delete mode 100644 categories/index.html delete mode 100644 categories/page/1/index.html delete mode 100644 css/coder-dark.min.83a2010dac9f59f943b3004cd6c4f230507ad036da635d3621401d42ec4e2835.css delete mode 100644 css/coder.min.a4f332213a21ce8eb521670c614470c58923aaaf385e2a73982c31dd7642decb.css delete mode 100644 css/image.css delete mode 100644 css/spoiler.css delete mode 100755 fontdata/css/academicons.css delete mode 100755 fontdata/css/academicons.min.css delete mode 100644 fontdata/fonts/academicons.woff delete mode 100644 images/zoom_in.png delete mode 100644 images/zoom_out.png delete mode 100644 index.html rename {fontdata => plugins/academic-icons}/fonts/academicons.eot (81%) rename {fontdata => plugins/academic-icons}/fonts/academicons.svg (82%) rename {fontdata => plugins/academic-icons}/fonts/academicons.ttf (81%) create mode 100644 plugins/academic-icons/fonts/academicons.woff delete mode 100644 posts/bypassing-censorship/index.html delete mode 100644 posts/documenting-the-experience/index.html delete mode 100644 posts/ekf-slam.md/index.html delete mode 100644 posts/index.html delete mode 100644 posts/inverse-transform-sampling.md/index.html delete mode 100644 posts/likelihood-and-log-sum-exp/index.html delete mode 100644 posts/linear-and-logistic-regression.md/index.html delete mode 100644 posts/nn-training.md/index.html delete mode 100644 posts/nonlinear-estimation-mle-map.md/index.html create mode 100644 posts/notes-on-backpropagation/forward-backpropagation.drawio-old.svg delete mode 100644 posts/notes-on-backpropagation/index.html delete mode 100644 posts/optimization-on-manifold/index.html delete mode 100644 posts/page/1/index.html delete mode 100644 posts/particle-filter.md/index.html delete mode 100644 posts/point-cloud-alignment-and-lie-algebra.md/index.html delete mode 100644 posts/point-cloud-alignment.md/index.html delete mode 100644 posts/probability-density-transform/index.html delete mode 100644 posts/uncertainty-propagation.md/index.html delete mode 100644 tags/automatic-differentiation/index.html delete mode 100644 tags/automatic-differentiation/page/1/index.html delete mode 100644 tags/backpropagation/index.html delete mode 100644 tags/backpropagation/page/1/index.html delete mode 100644 tags/bayesian-estimation/index.html delete mode 100644 tags/bayesian-estimation/page/1/index.html delete mode 100644 tags/censorship/index.html delete mode 100644 tags/censorship/page/1/index.html delete mode 100644 tags/deep-learning/index.html delete mode 100644 tags/deep-learning/page/1/index.html delete mode 100644 tags/ekf/index.html delete mode 100644 tags/ekf/page/1/index.html delete mode 100644 tags/factor-graph/index.html delete mode 100644 tags/factor-graph/page/1/index.html delete mode 100644 tags/index.html delete mode 100644 tags/iterative-closest-point/index.html delete mode 100644 tags/iterative-closest-point/page/1/index.html delete mode 100644 tags/kalman-filter/index.html delete mode 100644 tags/kalman-filter/page/1/index.html delete mode 100644 tags/least-squares/index.html delete mode 100644 tags/least-squares/page/1/index.html delete mode 100644 tags/lie-algebra/index.html delete mode 100644 tags/lie-algebra/page/1/index.html delete mode 100644 tags/lie-group/index.html delete mode 100644 tags/lie-group/page/1/index.html delete mode 100644 tags/lie-groups/index.html delete mode 100644 tags/lie-groups/page/1/index.html delete mode 100644 tags/linear-regression/index.html delete mode 100644 tags/linear-regression/page/1/index.html delete mode 100644 tags/log-likelihood/index.html delete mode 100644 tags/log-likelihood/page/1/index.html delete mode 100644 tags/logistic-regression/index.html delete mode 100644 tags/logistic-regression/page/1/index.html delete mode 100644 tags/manifolds/index.html delete mode 100644 tags/manifolds/page/1/index.html delete mode 100644 tags/map/index.html delete mode 100644 tags/map/page/1/index.html delete mode 100644 tags/me/index.html delete mode 100644 tags/me/page/1/index.html delete mode 100644 tags/mle/index.html delete mode 100644 tags/mle/page/1/index.html delete mode 100644 tags/neural-networks/index.html delete mode 100644 tags/neural-networks/page/1/index.html delete mode 100644 tags/optimization/index.html delete mode 100644 tags/optimization/page/1/index.html delete mode 100644 tags/page/1/index.html delete mode 100644 tags/page/2/index.html delete mode 100644 tags/particle-filter/index.html delete mode 100644 tags/particle-filter/page/1/index.html delete mode 100644 tags/pcl/index.html delete mode 100644 tags/pcl/page/1/index.html delete mode 100644 tags/pdf/index.html delete mode 100644 tags/pdf/page/1/index.html delete mode 100644 tags/personal/index.html delete mode 100644 tags/personal/page/1/index.html delete mode 100644 tags/point-cloud-alignment/index.html delete mode 100644 tags/point-cloud-alignment/page/1/index.html delete mode 100644 tags/probability/index.html delete mode 100644 tags/probability/page/1/index.html delete mode 100644 tags/reading/index.html delete mode 100644 tags/reading/page/1/index.html delete mode 100644 tags/sampling/index.html delete mode 100644 tags/sampling/page/1/index.html delete mode 100644 tags/sensor-fusion/index.html delete mode 100644 tags/sensor-fusion/page/1/index.html delete mode 100644 tags/sigmoid/index.html delete mode 100644 tags/sigmoid/page/1/index.html delete mode 100644 tags/slam/index.html delete mode 100644 tags/slam/page/1/index.html delete mode 100644 tags/statistics/index.html delete mode 100644 tags/statistics/page/1/index.html delete mode 100644 tags/stereo-vision/index.html delete mode 100644 tags/stereo-vision/page/1/index.html delete mode 100644 tags/svd/index.html delete mode 100644 tags/svd/page/1/index.html delete mode 100644 tags/transform/index.html delete mode 100644 tags/transform/page/1/index.html delete mode 100644 tags/uncertainty-propagation/index.html delete mode 100644 tags/uncertainty-propagation/page/1/index.html delete mode 100644 tags/vpn/index.html delete mode 100644 tags/vpn/page/1/index.html diff --git a/404.html b/404.html deleted file mode 100644 index 88db845..0000000 --- a/404.html +++ /dev/null @@ -1,7 +0,0 @@ -Aleksandr Mikoff's blog -

404

Page Not Found

Sorry, this page does not exist.
You can head back to homepage.

© 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
\ No newline at end of file diff --git a/about/index.html b/about/index.html deleted file mode 100644 index 4a93844..0000000 --- a/about/index.html +++ /dev/null @@ -1,11 +0,0 @@ -About · Aleksandr Mikoff's blog -

About

I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. -This blog is about:

  • explorations with algorithms,
  • experiments with data,
  • understanding the math concepts and computer science algorithms in simple words.

You can find my CV here

© 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
\ No newline at end of file diff --git a/categories/index.html b/categories/index.html deleted file mode 100644 index a2cb080..0000000 --- a/categories/index.html +++ /dev/null @@ -1,7 +0,0 @@ -Category: Categories · Aleksandr Mikoff's blog -

Category: Categories

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/categories/page/1/index.html b/categories/page/1/index.html deleted file mode 100644 index 9fe1c04..0000000 --- a/categories/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/categories/ - \ No newline at end of file diff --git a/css/coder-dark.min.83a2010dac9f59f943b3004cd6c4f230507ad036da635d3621401d42ec4e2835.css b/css/coder-dark.min.83a2010dac9f59f943b3004cd6c4f230507ad036da635d3621401d42ec4e2835.css deleted file mode 100644 index 9072ad1..0000000 --- a/css/coder-dark.min.83a2010dac9f59f943b3004cd6c4f230507ad036da635d3621401d42ec4e2835.css +++ /dev/null @@ -1 +0,0 @@ -body.colorscheme-dark{color:#dadada;background-color:#212121}body.colorscheme-dark a{color:#36679f}body.colorscheme-dark h1,body.colorscheme-dark h2,body.colorscheme-dark h3,body.colorscheme-dark h4,body.colorscheme-dark h5,body.colorscheme-dark h6{color:#dadada}body.colorscheme-dark code{background-color:#424242;color:#dadada}body.colorscheme-dark pre code{background-color:inherit;color:inherit}body.colorscheme-dark blockquote{border-left:2px solid #424242}body.colorscheme-dark table td,body.colorscheme-dark table th{border:2px solid #dadada}@media(prefers-color-scheme:dark){body.colorscheme-auto{color:#dadada;background-color:#212121}body.colorscheme-auto a{color:#36679f}body.colorscheme-auto h1,body.colorscheme-auto h2,body.colorscheme-auto h3,body.colorscheme-auto h4,body.colorscheme-auto h5,body.colorscheme-auto h6{color:#dadada}body.colorscheme-auto code{background-color:#424242;color:#dadada}body.colorscheme-auto pre code{background-color:inherit;color:inherit}body.colorscheme-auto blockquote{border-left:2px solid #424242}body.colorscheme-auto table td,body.colorscheme-auto table th{border:2px solid #dadada}}body.colorscheme-dark .content .list ul li .title{color:#dadada}body.colorscheme-dark .content .list ul li .title:hover,body.colorscheme-dark .content .list ul li .title:focus{color:#36679f}body.colorscheme-dark .content .centered .about ul li a{color:#dadada}body.colorscheme-dark .content .centered .about ul li a:hover,body.colorscheme-dark .content .centered .about ul li a:focus{color:#36679f}@media(prefers-color-scheme:dark){body.colorscheme-auto .content .list ul li .title{color:#dadada}body.colorscheme-auto .content .list ul li .title:hover,body.colorscheme-auto .content .list ul li .title:focus{color:#36679f}body.colorscheme-auto .content .centered .about ul li a{color:#dadada}body.colorscheme-auto .content .centered .about ul li a:hover,body.colorscheme-auto .content .centered .about ul li a:focus{color:#36679f}}body.colorscheme-dark .navigation a,body.colorscheme-dark .navigation span{color:#dadada}body.colorscheme-dark .navigation a:hover,body.colorscheme-dark .navigation a:focus{color:#36679f}@media only screen and (max-width:768px){body.colorscheme-dark .navigation .navigation-list{background-color:#212121;border-top:solid 2px #424242;border-bottom:solid 2px #424242}}@media only screen and (max-width:768px){body.colorscheme-dark .navigation .navigation-list .menu-separator{border-top:2px solid #dadada}}@media only screen and (max-width:768px){body.colorscheme-dark .navigation #menu-toggle:checked+label{color:#424242}}@media only screen and (max-width:768px){body.colorscheme-dark .navigation .menu-button{color:#dadada}body.colorscheme-dark .navigation .menu-button:hover,body.colorscheme-dark .navigation .menu-button:focus{color:#36679f}}@media(prefers-color-scheme:dark){body.colorscheme-auto .navigation a,body.colorscheme-auto .navigation span{color:#dadada}body.colorscheme-auto .navigation a:hover,body.colorscheme-auto .navigation a:focus{color:#36679f}}@media only screen and (prefers-color-scheme:dark) and (max-width:768px){body.colorscheme-auto .navigation .navigation-list{background-color:#212121;border-top:solid 2px #424242;border-bottom:solid 2px #424242}}@media only screen and (prefers-color-scheme:dark) and (max-width:768px){body.colorscheme-auto .navigation .navigation-list .menu-separator{border-top:2px solid #dadada}}@media only screen and (prefers-color-scheme:dark) and (max-width:768px){body.colorscheme-auto .navigation #menu-toggle:checked+label{color:#424242}}@media only screen and (prefers-color-scheme:dark) and (max-width:768px){body.colorscheme-auto .navigation .menu-button{color:#dadada}body.colorscheme-auto .navigation .menu-button:hover,body.colorscheme-auto .navigation .menu-button:focus{color:#36679f}}body.colorscheme-dark .footer a{color:#36679f}@media(prefers-color-scheme:dark){body.colorscheme-auto .footer a{color:#36679f}} \ No newline at end of file diff --git a/css/coder.min.a4f332213a21ce8eb521670c614470c58923aaaf385e2a73982c31dd7642decb.css b/css/coder.min.a4f332213a21ce8eb521670c614470c58923aaaf385e2a73982c31dd7642decb.css deleted file mode 100644 index 727cd38..0000000 --- a/css/coder.min.a4f332213a21ce8eb521670c614470c58923aaaf385e2a73982c31dd7642decb.css +++ /dev/null @@ -1 +0,0 @@ -*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#212121;background-color:#fafafa;font-family:Merriweather,Georgia,serif;font-size:1.6em;font-weight:300;line-height:1.8em}@media only screen and (max-width:768px){body{font-size:1.6em;line-height:1.6em}}a{font-weight:300;color:#1565c0;text-decoration:none}a:focus,a:hover{text-decoration:underline}p{margin:2rem 0}h1,h2,h3,h4,h5,h6{font-family:Lato,Helvetica,sans-serif;font-weight:700;color:#000;margin:6.4rem 0 3.2rem}h1{font-size:3.2rem;line-height:3.6rem}@media only screen and (max-width:768px){h1{font-size:3rem;line-height:3.4rem}}h2{font-size:2.8rem;line-height:3.2rem}@media only screen and (max-width:768px){h2{font-size:2.6rem;line-height:3rem}}h3{font-size:2.4rem;line-height:2.8rem}@media only screen and (max-width:768px){h3{font-size:2.2rem;line-height:2.6rem}}h4{font-size:2.2rem;line-height:2.6rem}@media only screen and (max-width:768px){h4{font-size:2rem;line-height:2.4rem}}h5{font-size:2rem;line-height:2.4rem}@media only screen and (max-width:768px){h5{font-size:1.8rem;line-height:2.2rem}}h6{font-size:1.8rem;line-height:2.2rem}@media only screen and (max-width:768px){h6{font-size:1.6rem;line-height:2rem}}b,strong{font-weight:700}.highlight>div,.highlight>pre{margin:0 0 2rem;padding:1rem;border-radius:1rem}pre{display:block;font-family:source code pro,lucida console,monospace;font-size:1.6rem;font-weight:400;line-height:2.6rem;overflow-x:auto;margin:0}pre code{display:inline-block;background-color:inherit;color:inherit}code{font-family:source code pro,lucida console,monospace;font-size:1.6rem;font-weight:400;background-color:#e0e0e0;color:#212121}blockquote{border-left:2px solid #e0e0e0;padding-left:2rem;line-height:2.2rem;font-weight:400;font-style:italic}th,td{padding:1.6rem}table{border-collapse:collapse}table td,table th{border:2px solid #000}table tr:first-child th{border-top:0}table tr:last-child td{border-bottom:0}table tr td:first-child,table tr th:first-child{border-left:0}table tr td:last-child,table tr th:last-child{border-right:0}img{max-width:100%}figure{text-align:center}.wrapper{display:flex;flex-direction:column;min-height:100vh;width:100%}.container{margin:0 auto;max-width:90rem;width:100%;padding-left:2rem;padding-right:2rem}.fab{font-weight:400}.fas{font-weight:700}.float-right{float:right}.float-left{float:left}.fab{font-weight:400}.fas{font-weight:900}.content{flex:1;display:flex;margin-top:1.6rem;margin-bottom:3.2rem}.content article header{margin-top:6.4rem;margin-bottom:3.2rem}.content article header h1{font-size:4.2rem;line-height:4.6rem;margin:0}@media only screen and (max-width:768px){.content article header h1{font-size:4rem;line-height:4.4rem}}.content article footer{margin-top:4rem}.content article footer .see-also{margin:3.2rem 0}.content article footer .see-also h3{margin:3.2rem 0}.content article p{text-align:justify;text-justify:auto;hyphens:auto}.content .post .post-title{margin-bottom:.75em}.content .post .post-meta i{text-align:center;width:1.6rem;margin-left:0;margin-right:.5rem}.content .post .post-meta .date .posted-on{margin-left:0;margin-right:1.5rem}.content figure{margin:0;padding:0}.content figcaption p{text-align:center;font-style:italic;font-size:1.6rem;margin:0}.avatar img{width:20rem;height:auto;border-radius:50%}@media only screen and (max-width:768px){.avatar img{width:10rem}}.list ul{margin:3.2rem 0;list-style:none;padding:0}.list ul li{font-size:1.8rem}@media only screen and (max-width:768px){.list ul li{margin:1.6rem 0}}.list ul li .date{display:inline-block;width:20rem;text-align:right;margin-right:3rem}@media only screen and (max-width:768px){.list ul li .date{display:block;text-align:left}}.list ul li .title{font-size:1.8rem;color:#212121;font-family:Lato,Helvetica,sans-serif;font-weight:700}.list ul li .title:hover,.list ul li .title:focus{color:#1565c0}.centered{display:flex;align-items:center;justify-content:center}.centered .about{text-align:center}.centered .about h1{margin-top:2rem;margin-bottom:.5rem}.centered .about h2{margin-top:1rem;margin-bottom:.5rem;font-size:2.4rem}@media only screen and (max-width:768px){.centered .about h2{font-size:2rem}}.centered .about ul{list-style:none;margin:3rem 0 1rem;padding:0}.centered .about ul li{display:inline-block;position:relative}.centered .about ul li a{color:#212121;text-transform:uppercase;margin-left:1rem;margin-right:1rem;font-size:1.6rem}.centered .about ul li a:hover,.centered .about ul li a:focus{color:#1565c0}@media only screen and (max-width:768px){.centered .about ul li a{font-size:1.4rem}}.centered .about ul li a i{font-size:3.2rem}.centered .error{text-align:center}.centered .error h1{margin-top:2rem;margin-bottom:.5rem;font-size:4.6rem}@media only screen and (max-width:768px){.centered .error h1{font-size:3.2rem}}.centered .error h2{margin-top:2rem;margin-bottom:3.2rem;font-size:3.2rem}@media only screen and (max-width:768px){.centered .error h2{font-size:2.8rem}}.navigation{height:6rem;width:100%}.navigation a,.navigation span{display:inline;font-size:1.6rem;font-family:Lato,Helvetica,sans-serif;font-weight:700;line-height:6rem;color:#212121}.navigation a:hover,.navigation a:focus{color:#1565c0}.navigation .navigation-title{letter-spacing:.1rem;text-transform:uppercase}.navigation .navigation-list{float:right;list-style:none;margin-bottom:0;margin-top:0}@media only screen and (max-width:768px){.navigation .navigation-list{position:absolute;top:6rem;right:0;z-index:5;visibility:hidden;opacity:0;padding:0;max-height:0;width:100%;background-color:#fafafa;border-top:solid 2px #e0e0e0;border-bottom:solid 2px #e0e0e0;transition:opacity .25s,max-height .15s linear}}.navigation .navigation-list .navigation-item{float:left;margin:0;position:relative}@media only screen and (max-width:768px){.navigation .navigation-list .navigation-item{float:none!important;text-align:center}.navigation .navigation-list .navigation-item a,.navigation .navigation-list .navigation-item span{line-height:5rem}}.navigation .navigation-list .navigation-item a,.navigation .navigation-list .navigation-item span{margin-left:1rem;margin-right:1rem}@media only screen and (max-width:768px){.navigation .navigation-list .menu-separator{border-top:2px solid #212121;margin:0 8rem}.navigation .navigation-list .menu-separator span{display:none}}.navigation #menu-toggle{display:none}@media only screen and (max-width:768px){.navigation #menu-toggle:checked+label{color:#e0e0e0}.navigation #menu-toggle:checked+label+ul{visibility:visible;opacity:1;max-height:100rem}}.navigation .menu-button{display:none}@media only screen and (max-width:768px){.navigation .menu-button{display:block;font-size:2.4rem;font-weight:400;line-height:6rem;color:#212121;cursor:pointer}.navigation .menu-button:hover,.navigation .menu-button:focus{color:#1565c0}}.pagination{margin-top:6rem;text-align:center;font-family:Lato,Helvetica,sans-serif}.pagination li{display:inline;text-align:center;font-weight:700}.pagination li span{margin:0;text-align:center;width:3.2rem}.pagination li a{font-weight:300}.pagination li a span{margin:0;text-align:center;width:3.2rem}.footer{width:100%;text-align:center;line-height:2rem;margin-bottom:1rem}.footer a{color:#1565c0} \ No newline at end of file diff --git a/css/image.css b/css/image.css deleted file mode 100644 index 8fae5a8..0000000 --- a/css/image.css +++ /dev/null @@ -1,22 +0,0 @@ -img[src$='#center'] -{ - display: block; - margin: 0.7rem auto; /* you can replace the vertical '0.7rem' by - whatever floats your boat, but keep the - horizontal 'auto' for this to work */ - /* whatever else styles you fancy here */ -} - -img[src$='#floatleft'] -{ - float:left; - margin: 0.7rem; /* this margin is totally up to you */ - /* whatever else styles you fancy here */ -} - -img[src$='#floatright'] -{ - float:right; - margin: 0.7rem; /* this margin is totally up to you */ - /* whatever else styles you fancy here */ -} diff --git a/css/spoiler.css b/css/spoiler.css deleted file mode 100644 index b723bc4..0000000 --- a/css/spoiler.css +++ /dev/null @@ -1,33 +0,0 @@ -.spoiler { - display:inline; - } - .spoilerText{ - display:none; - } - .spoilerChecked{ - white-space: pre; - visibility:hidden; - } - .spoilerChecked:after{ - content:attr(showText) "\1F4CB\21D2"; - visibility:visible; - } - .spoilerChecked:checked:after{ - content:attr(showText) "\1F4CB\25BC"; - } - .spoilerContent{ - display:block; - height:auto; - max-height:0; - padding-left:0px; - margin-left:10px; - transform: scaleY(0); - transform-origin: top; - border-left: 0px solid #f0f3f3; - transition: max-height 0.2s, transform 0.2s; - } - .spoilerChecked:checked + .spoilerContent{ - max-height:4000px; - display:block; - transform: scaleY(1); - } \ No newline at end of file diff --git a/fontdata/css/academicons.css b/fontdata/css/academicons.css deleted file mode 100755 index f62dc19..0000000 --- a/fontdata/css/academicons.css +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Academicons 1.9.1 by James Walsh (https://github.com/jpswalsh) and Katja Bercic (https://github.com/katjabercic) - * Fonts generated using FontForge - https://fontforge.org - * Square icons designed to be used alongside Font Awesome square icons - https://fortawesome.github.io/Font-Awesome/ - * Licenses - Font: SIL OFL 1.1, CSS: MIT License - */ -@font-face { - font-family: 'Academicons'; - font-style: normal; - font-weight: 400; - font-display: block; - src:url('../fonts/academicons.eot'); - src:url('../fonts/academicons.eot') format('embedded-opentype'), - url('../fonts/academicons.ttf') format('truetype'), - url('../fonts/academicons.woff') format('woff'), - url('../fonts/academicons.svg') format('svg'); -} -.ai { - font-family: 'Academicons'; - font-weight: 400; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; -} -.ai-academia:before { - content: "\e9af"; -} -.ai-academia-square:before { - content: "\e93d"; -} -.ai-acclaim:before { - content: "\e92e"; -} -.ai-acclaim-square:before { - content: "\e93a"; -} -.ai-acm:before { - content: "\e93c"; -} -.ai-acm-square:before { - content: "\e95d"; -} -.ai-acmdl:before { - content: "\e96a"; -} -.ai-acmdl-square:before { - content: "\e9d3"; -} -.ai-ads:before { - content: "\e9cb"; -} -.ai-ads-square:before { - content: "\e94a"; -} -.ai-africarxiv:before { - content: "\e91b"; -} -.ai-africarxiv-square:before { - content: "\e90b"; -} -.ai-arxiv:before { - content: "\e974"; -} -.ai-arxiv-square:before { - content: "\e9a6"; -} -.ai-biorxiv:before { - content: "\e9a2"; -} -.ai-biorxiv-square:before { - content: "\e98b"; -} -.ai-ceur:before { - content: "\e96d"; -} -.ai-ceur-square:before { - content: "\e92f"; -} -.ai-ciencia-vitae:before { - content: "\e912"; -} -.ai-ciencia-vitae-square:before { - content: "\e913"; -} -.ai-closed-access:before { - content: "\e942"; -} -.ai-closed-access-square:before { - content: "\e943"; -} -.ai-conversation:before { - content: "\e94c"; -} -.ai-conversation-square:before { - content: "\e915"; -} -.ai-coursera:before { - content: "\e95f"; -} -.ai-coursera-square:before { - content: "\e97f"; -} -.ai-crossref:before { - content: "\e918"; -} -.ai-crossref-square:before { - content: "\e919"; -} -.ai-cv:before { - content: "\e9a5"; -} -.ai-cv-square:before { - content: "\e90a"; -} -.ai-datacite:before { - content: "\e91c"; -} -.ai-datacite-square:before { - content: "\e91d"; -} -.ai-dataverse:before { - content: "\e9f7"; -} -.ai-dataverse-square:before { - content: "\e9e4"; -} -.ai-dblp:before { - content: "\e94f"; -} -.ai-dblp-square:before { - content: "\e93f"; -} -.ai-depsy:before { - content: "\e97a"; -} -.ai-depsy-square:before { - content: "\e94b"; -} -.ai-doi:before { - content: "\e97e"; -} -.ai-doi-square:before { - content: "\e98f"; -} -.ai-dryad:before { - content: "\e97c"; -} -.ai-dryad-square:before { - content: "\e98c"; -} -.ai-elsevier:before { - content: "\e961"; -} -.ai-elsevier-square:before { - content: "\e910"; -} -.ai-figshare:before { - content: "\e981"; -} -.ai-figshare-square:before { - content: "\e9e7"; -} -.ai-google-scholar:before { - content: "\e9d4"; -} -.ai-google-scholar-square:before { - content: "\e9f9"; -} -.ai-hal:before { - content: "\e92c"; -} -.ai-hal-square:before { - content: "\e92d"; -} -.ai-hypothesis:before { - content: "\e95a"; -} -.ai-hypothesis-square:before { - content: "\e95b"; -} -.ai-ideas-repec:before { - content: "\e9ed"; -} -.ai-ideas-repec-square:before { - content: "\e9f8"; -} -.ai-ieee:before { - content: "\e929"; -} -.ai-ieee-square:before { - content: "\e9b9"; -} -.ai-impactstory:before { - content: "\e9cf"; -} -.ai-impactstory-square:before { - content: "\e9aa"; -} -.ai-inaturalist:before { - content: "\e900"; -} -.ai-inaturalist-square:before { - content: "\e901"; -} -.ai-inpn:before { - content: "\e902"; -} -.ai-inpn-square:before { - content: "\e903"; -} -.ai-inspire:before { - content: "\e9e9"; -} -.ai-inspire-square:before { - content: "\e9fe"; -} -.ai-isidore:before { - content: "\e936"; -} -.ai-isidore-square:before { - content: "\e954"; -} -.ai-jstor:before { - content: "\e938"; -} -.ai-jstor-square:before { - content: "\e944"; -} -.ai-lattes:before { - content: "\e9b3"; -} -.ai-lattes-square:before { - content: "\e99c"; -} -.ai-mathoverflow:before { - content: "\e9f6"; -} -.ai-mathoverflow-square:before { - content: "\e97b"; -} -.ai-mendeley:before { - content: "\e9f0"; -} -.ai-mendeley-square:before { - content: "\e9f3"; -} -.ai-moodle:before { - content: "\e907"; -} -.ai-moodle-square:before { - content: "\e908"; -} -.ai-nakala:before { - content: "\e940"; -} -.ai-nakala-square:before { - content: "\e941"; -} -.ai-obp:before { - content: "\e92a"; -} -.ai-obp-square:before { - content: "\e92b"; -} -.ai-open-access:before { - content: "\e939"; -} -.ai-open-access-square:before { - content: "\e9f4"; -} -.ai-open-data:before { - content: "\e966"; -} -.ai-open-data-square:before { - content: "\e967"; -} -.ai-open-materials:before { - content: "\e968"; -} -.ai-open-materials-square:before { - content: "\e969"; -} -.ai-openedition:before { - content: "\e946"; -} -.ai-openedition-square:before { - content: "\e947"; -} -.ai-orcid:before { - content: "\e9d9"; -} -.ai-orcid-square:before { - content: "\e9c3"; -} -.ai-osf:before { - content: "\e9ef"; -} -.ai-osf-square:before { - content: "\e931"; -} -.ai-overleaf:before { - content: "\e914"; -} -.ai-overleaf-square:before { - content: "\e98d"; -} -.ai-philpapers:before { - content: "\e98a"; -} -.ai-philpapers-square:before { - content: "\e96f"; -} -.ai-piazza:before { - content: "\e99a"; -} -.ai-piazza-square:before { - content: "\e90c"; -} -.ai-preregistered:before { - content: "\e906"; -} -.ai-preregistered-square:before { - content: "\e96b"; -} -.ai-protocols:before { - content: "\e952"; -} -.ai-protocols-square:before { - content: "\e953"; -} -.ai-psyarxiv:before { - content: "\e90e"; -} -.ai-psyarxiv-square:before { - content: "\e90f"; -} -.ai-publons:before { - content: "\e937"; -} -.ai-publons-square:before { - content: "\e94e"; -} -.ai-pubmed:before { - content: "\e99f"; -} -.ai-pubmed-square:before { - content: "\e97d"; -} -.ai-pubpeer:before { - content: "\e922"; -} -.ai-pubpeer-square:before { - content: "\e923"; -} -.ai-researcherid:before { - content: "\e91a"; -} -.ai-researcherid-square:before { - content: "\e95c"; -} -.ai-researchgate:before { - content: "\e95e"; -} -.ai-researchgate-square:before { - content: "\e99e"; -} -.ai-ror:before { - content: "\e948"; -} -.ai-ror-square:before { - content: "\e949"; -} -.ai-sci-hub:before { - content: "\e959"; -} -.ai-sci-hub-square:before { - content: "\e905"; -} -.ai-scirate:before { - content: "\e98e"; -} -.ai-scirate-square:before { - content: "\e99d"; -} -.ai-scopus:before { - content: "\e91e"; -} -.ai-scopus-square:before { - content: "\e91f"; -} -.ai-semantic-scholar:before { - content: "\e96e"; -} -.ai-semantic-scholar-square:before { - content: "\e96c"; -} -.ai-springer:before { - content: "\e928"; -} -.ai-springer-square:before { - content: "\e99b"; -} -.ai-ssrn:before { - content: "\e916"; -} -.ai-ssrn-square:before { - content: "\e917"; -} -.ai-stackoverflow:before { - content: "\e920"; -} -.ai-stackoverflow-square:before { - content: "\e921"; -} -.ai-zenodo:before { - content: "\e911"; -} -.ai-zotero:before { - content: "\e962"; -} -.ai-zotero-square:before { - content: "\e932"; -} -/* Duplication of the FontAwesome style classes using 'ai' in place of 'fa'. */ -.ai-lg { - font-size: 1.33333em; - line-height: 0.75em; - vertical-align: -.0667em; -} -.ai-xs { - font-size: .75em; -} -.ai-sm { - font-size: .875em; -} - -.ai-1x { - font-size: 1em; -} -.ai-2x { - font-size: 2em; -} -.ai-3x { - font-size: 3em; -} -.ai-4x { - font-size: 4em; -} -.ai-5x { - font-size: 5em; -} -.ai-6x { - font-size: 6em; -} -.ai-7x { - font-size: 7em; -} -.ai-8x {left - font-size: 8em; -} -.ai-9x { - font-size: 9em; -} -.ai-10x { - font-size: 10em; -} - -.ai-fw { - text-align: center; - width: 1.25em; -} - -.ai-ul { - list-style-type: none; - margin-left: 2.5em; - padding-left: 0; -} -.ai-ul > li { - position: relative; -} -.ai-li { - left: -2em; - position: absolute; - text-align: center; - width: 2em; - line-height: inherit; -} - -.ai-border { -border: solid 0.08em #eee; -border-radius: .1em; - padding: .2em .25em .15em; -} - -.ai-pull-left { - float: left; -} -.ai-pull-right { - float: right; -} -.ai.ai-pull-left { - margin-right: .3em; -} -.ai.ai-pull-right { - margin-right: .3em; -} - -.ai-stack { - display: inline-block; - height: 2em; - line-height: 2em; - position: relative; - vertical-align: middle; - width: 2.5em; -} -.ai-stack-1x, -.ai-stack-2x { - left: 0; - position: absolute; - text-align: center; - width: 100%; -} -.ai-stack-1x { - line-height: inherit; -} -.ai-stack-2x { - font-size: 2em; -} -.ai-inverse { - color: #fff; -} diff --git a/fontdata/css/academicons.min.css b/fontdata/css/academicons.min.css deleted file mode 100755 index b0122a2..0000000 --- a/fontdata/css/academicons.min.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:'Academicons';font-style:normal;font-weight:400;font-display:block;src:url(../fonts/academicons.eot);src:url(../fonts/academicons.eot) format('embedded-opentype'),url(../fonts/academicons.ttf) format('truetype'),url(../fonts/academicons.woff) format('woff'),url(../fonts/academicons.svg) format('svg')}.ai{font-family:'Academicons';font-weight:400;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.ai-academia:before{content:"\e9af"}.ai-academia-square:before{content:"\e93d"}.ai-acclaim:before{content:"\e92e"}.ai-acclaim-square:before{content:"\e93a"}.ai-acm:before{content:"\e93c"}.ai-acm-square:before{content:"\e95d"}.ai-acmdl:before{content:"\e96a"}.ai-acmdl-square:before{content:"\e9d3"}.ai-ads:before{content:"\e9cb"}.ai-ads-square:before{content:"\e94a"}.ai-africarxiv:before{content:"\e91b"}.ai-africarxiv-square:before{content:"\e90b"}.ai-arxiv:before{content:"\e974"}.ai-arxiv-square:before{content:"\e9a6"}.ai-biorxiv:before{content:"\e9a2"}.ai-biorxiv-square:before{content:"\e98b"}.ai-ceur:before{content:"\e96d"}.ai-ceur-square:before{content:"\e92f"}.ai-ciencia-vitae:before{content:"\e912"}.ai-ciencia-vitae-square:before{content:"\e913"}.ai-closed-access:before{content:"\e942"}.ai-closed-access-square:before{content:"\e943"}.ai-conversation:before{content:"\e94c"}.ai-conversation-square:before{content:"\e915"}.ai-coursera:before{content:"\e95f"}.ai-coursera-square:before{content:"\e97f"}.ai-crossref:before{content:"\e918"}.ai-crossref-square:before{content:"\e919"}.ai-cv:before{content:"\e9a5"}.ai-cv-square:before{content:"\e90a"}.ai-datacite:before{content:"\e91c"}.ai-datacite-square:before{content:"\e91d"}.ai-dataverse:before{content:"\e9f7"}.ai-dataverse-square:before{content:"\e9e4"}.ai-dblp:before{content:"\e94f"}.ai-dblp-square:before{content:"\e93f"}.ai-depsy:before{content:"\e97a"}.ai-depsy-square:before{content:"\e94b"}.ai-doi:before{content:"\e97e"}.ai-doi-square:before{content:"\e98f"}.ai-dryad:before{content:"\e97c"}.ai-dryad-square:before{content:"\e98c"}.ai-elsevier:before{content:"\e961"}.ai-elsevier-square:before{content:"\e910"}.ai-figshare:before{content:"\e981"}.ai-figshare-square:before{content:"\e9e7"}.ai-google-scholar:before{content:"\e9d4"}.ai-google-scholar-square:before{content:"\e9f9"}.ai-hal:before{content:"\e92c"}.ai-hal-square:before{content:"\e92d"}.ai-hypothesis:before{content:"\e95a"}.ai-hypothesis-square:before{content:"\e95b"}.ai-ideas-repec:before{content:"\e9ed"}.ai-ideas-repec-square:before{content:"\e9f8"}.ai-ieee:before{content:"\e929"}.ai-ieee-square:before{content:"\e9b9"}.ai-impactstory:before{content:"\e9cf"}.ai-impactstory-square:before{content:"\e9aa"}.ai-inaturalist:before{content:"\e900"}.ai-inaturalist-square:before{content:"\e901"}.ai-inpn:before{content:"\e902"}.ai-inpn-square:before{content:"\e903"}.ai-inspire:before{content:"\e9e9"}.ai-inspire-square:before{content:"\e9fe"}.ai-isidore:before{content:"\e936"}.ai-isidore-square:before{content:"\e954"}.ai-jstor:before{content:"\e938"}.ai-jstor-square:before{content:"\e944"}.ai-lattes:before{content:"\e9b3"}.ai-lattes-square:before{content:"\e99c"}.ai-mathoverflow:before{content:"\e9f6"}.ai-mathoverflow-square:before{content:"\e97b"}.ai-mendeley:before{content:"\e9f0"}.ai-mendeley-square:before{content:"\e9f3"}.ai-moodle:before{content:"\e907"}.ai-moodle-square:before{content:"\e908"}.ai-nakala:before{content:"\e940"}.ai-nakala-square:before{content:"\e941"}.ai-obp:before{content:"\e92a"}.ai-obp-square:before{content:"\e92b"}.ai-open-access:before{content:"\e939"}.ai-open-access-square:before{content:"\e9f4"}.ai-open-data:before{content:"\e966"}.ai-open-data-square:before{content:"\e967"}.ai-open-materials:before{content:"\e968"}.ai-open-materials-square:before{content:"\e969"}.ai-openedition:before{content:"\e946"}.ai-openedition-square:before{content:"\e947"}.ai-orcid:before{content:"\e9d9"}.ai-orcid-square:before{content:"\e9c3"}.ai-osf:before{content:"\e9ef"}.ai-osf-square:before{content:"\e931"}.ai-overleaf:before{content:"\e914"}.ai-overleaf-square:before{content:"\e98d"}.ai-philpapers:before{content:"\e98a"}.ai-philpapers-square:before{content:"\e96f"}.ai-piazza:before{content:"\e99a"}.ai-piazza-square:before{content:"\e90c"}.ai-preregistered:before{content:"\e906"}.ai-preregistered-square:before{content:"\e96b"}.ai-protocols:before{content:"\e952"}.ai-protocols-square:before{content:"\e953"}.ai-psyarxiv:before{content:"\e90e"}.ai-psyarxiv-square:before{content:"\e90f"}.ai-publons:before{content:"\e937"}.ai-publons-square:before{content:"\e94e"}.ai-pubmed:before{content:"\e99f"}.ai-pubmed-square:before{content:"\e97d"}.ai-pubpeer:before{content:"\e922"}.ai-pubpeer-square:before{content:"\e923"}.ai-researcherid:before{content:"\e91a"}.ai-researcherid-square:before{content:"\e95c"}.ai-researchgate:before{content:"\e95e"}.ai-researchgate-square:before{content:"\e99e"}.ai-ror:before{content:"\e948"}.ai-ror-square:before{content:"\e949"}.ai-sci-hub:before{content:"\e959"}.ai-sci-hub-square:before{content:"\e905"}.ai-scirate:before{content:"\e98e"}.ai-scirate-square:before{content:"\e99d"}.ai-scopus:before{content:"\e91e"}.ai-scopus-square:before{content:"\e91f"}.ai-semantic-scholar:before{content:"\e96e"}.ai-semantic-scholar-square:before{content:"\e96c"}.ai-springer:before{content:"\e928"}.ai-springer-square:before{content:"\e99b"}.ai-ssrn:before{content:"\e916"}.ai-ssrn-square:before{content:"\e917"}.ai-stackoverflow:before{content:"\e920"}.ai-stackoverflow-square:before{content:"\e921"}.ai-zenodo:before{content:"\e911"}.ai-zotero:before{content:"\e962"}.ai-zotero-square:before{content:"\e932"}.ai-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.ai-xs{font-size:.75em}.ai-sm{font-size:.875em}.ai-1x{font-size:1em}.ai-2x{font-size:2em}.ai-3x{font-size:3em}.ai-4x{font-size:4em}.ai-5x{font-size:5em}.ai-6x{font-size:6em}.ai-7x{font-size:7em}.ai-8x{left font-size:8em}.ai-9x{font-size:9em}.ai-10x{font-size:10em}.ai-fw{text-align:center;width:1.25em}.ai-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.ai-ul>li{position:relative}.ai-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.ai-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.ai-pull-left{float:left}.ai-pull-right{float:right}.ai.ai-pull-left{margin-right:.3em}.ai.ai-pull-right{margin-right:.3em}.ai-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.ai-stack-1x,.ai-stack-2x{left:0;position:absolute;text-align:center;width:100%}.ai-stack-1x{line-height:inherit}.ai-stack-2x{font-size:2em}.ai-inverse{color:#fff} diff --git a/fontdata/fonts/academicons.woff b/fontdata/fonts/academicons.woff deleted file mode 100644 index c8cf33edfc23ff1c814cc6731c28deeaaf0dceb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117932 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 z 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 a;>qm!G<@=HXa5zm_r}~y_x=LybVkar=WI;? diff --git a/images/zoom_in.png b/images/zoom_in.png deleted file mode 100644 index af4fe07477243b9b2099899d1ef47b8e3fd87b09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 680 zcmV;Z0$2TsP)FJNMp+(Bt!=q9U!ZZOlw$c zuAy5i+nTd|<_>NivLu&tYWf+obh7aHN%Hi45`pBR)x`tA#^U98gM4FFC6h~&)aWQw>e5Y84Gj%C?Fa5wL3#v12nvm3<6OafjJt}U((Qj zn8!nMmXr-qoCO7XcZRS8(x9RlIA>F^1(GoPldw}sc)rpQ>IL9yYf!7MN);5mno3dL zFr9-f3^@5I0h2d@QBNW#I`RB4IwvonO1T#W1?;?jrZNjp_!1ar;E|a)8g&BH^;Scq zt%uAgf}pb+yKn5ouFDnCJb}hGpY=s(m>77B`PIn4hUqw48S;@<+#YViwZYT4_>vEC z?=frJc<3Fn+HA3jXTwUklhgJ-dYkmNL^YBTW!uzZM O00008VI0S0!CoiyA}k0DS`b78y(oGU6a>AkUK9~n*^7|=kr7cwRFWv6%Z}9bXJxHq zv!bQ7t!|lawm;5>vpILy?iOC&HfG!Hc8BBJ-HKqfMb?WCJa0aq=l6SfssL2|4?ho1 zYF_hZS|)5^i5(2(3eSVtLjTwt+viCU4@P*+9|NQ!zmCYHkC8wnWH2cSOj5$smpmL3 zY1}4f$SympsTgoXWWvkj!KP${W<>az96Yfc0&EV^Sqav$1oSUxqGI6Xq{vddRFu7n z`2LdsHzi;=Dtr$y0$QI$*opyl-1%;k%_tFkSWDaJm;MHp-}I<6Z; z`=uSavTxAsh-+>P#z@O32VYD;@Uw_<1$Q)qo>w&5O)gAWuE2V33*Ucwqps{ny7nxp zX|aTf%a1~AQ*W?v_D-PC*yD@0=#7-ucnX}S5B~d&FdbXR-#fBe_gkP615I1CPtR;N z4-*FwcN*ZjSr5aZ75rZZR34vLukCGEW45>LLezQ{#QvpNP(bBYeXX{uZkgF|xEA`o r)y)eIsC4dIvZ!Ov;+nFL_^*5eZM*&99Aleksandr Mikoff's blog -

    avatar

    Aleksandr Mikoff

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/index.xml b/index.xml index 6a7960c..c109590 100644 --- a/index.xml +++ b/index.xml @@ -8,11 +8,11 @@ However, to use this method the gradient has to be computed. The first problem i Spoiler heights = np.array([120, 135, 145, 150, 151, 155, 165, 170, 172, 175, 180, 190]) labels = np.array([0, 0, 0, 0., 0, 0, 1, 1, 1, 1, 1, 1.]) females = labels == 0 males = labels == 1 fig, ax = plt.subplots(1, 1, figsize = (6, 2)) ax.scatter(heights[females], labels[females]) ax.scatter(heights[males], labels[males]) ax.set(xlabel = &#39;height, cm&#39;, ylabel = &#39;class&#39;) ax.Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What&rsquo;s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates.Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group.Point cloud alignment and SVDhttps://mikoff.github.io/posts/point-cloud-alignment.md/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/posts/point-cloud-alignment.md/Point cloud alignment and SVD Singular value decomposition Recently I studied the problem of finding the rotation and translation between two point sets and decided to write the post about it. The key here is singular value decomposition, or SVD. It is extremely popular technique in many types of linear problems. It should be not surprised, that the point cloud alignment problem can be solved with its help. My aim here is to show all accommpanying theory and provide the point cloud alignment algorithm that takes not more than 10 lines of code in Python.Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$EKF SLAMhttps://mikoff.github.io/posts/ekf-slam.md/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/posts/ekf-slam.md/Introduction One of the most fundamental problems in robotics is the simultaneous localization and mapping (SLAM) problem. +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.EKF SLAMhttps://mikoff.github.io/posts/ekf-slam.md/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/posts/ekf-slam.md/Introduction One of the most fundamental problems in robotics is the simultaneous localization and mapping (SLAM) problem. It is more difficult than localization in that the map is unknown and has to be estimated along the way. It is more difficult than mapping with known poses, since the poses are unknown and have to be estimated along the way. &ndash; S. Thrun In the following post I would like to discuss the EKF SLAM and highlight the important aspects of its implementation and convergence.Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: -Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: +Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ Cumulative distribution function shows us the probability (portion of data, frequence) to draw a number $X$ less or equal than $x$: $$ P(X \leq x) = F(x).Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: explorations with algorithms, experiments with data, understanding the math concepts and computer science algorithms in simple words. You can find my CV hereDocumenting the experiencehttps://mikoff.github.io/posts/documenting-the-experience/Sun, 09 Feb 2020 12:53:51 +0300https://mikoff.github.io/posts/documenting-the-experience/Hi there! For every engineer and developer the continious self-studying is a must. Everyday we read, try to solve problems and clarify the concepts, write the code and visualise the things, which help us to get an insights. I am interested in Navigation and Positioning, Statistics, Autonomous vehicles and Robotics. These topics are huge, and what is more important, they are about future of humanity. To summarize all the things, which I am doing and share the ideas I decided to post about it. \ No newline at end of file diff --git a/fontdata/fonts/academicons.eot b/plugins/academic-icons/fonts/academicons.eot similarity index 81% rename from fontdata/fonts/academicons.eot rename to plugins/academic-icons/fonts/academicons.eot index fe6a97df85fdfcc7c546e54b1a63d174f42b64fd..37ae5e371d02e87bb91150bfa4575e8e07c83ca2 100644 GIT binary patch delta 10272 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 a?3udR6pwa=zYFx-%vHZ`$Y-8(CjT3h`#917 delta 3951 zcmai14UiPY74Ght>6zKxnf;reyjiM8MRTD3S@)L&| zo)WMok?gvs@H%$I@zT)of<&tD%Y_8lh|R)&(u~UsuR!T7{EhTYufium+FqoxF9@Pj zUu~*iJGP3OiFw?P_`(@JIjtYHBLM}9Tv00eidM0?m?^dt=M+1Oi;I25!GbEx57rjb z#aywq*jDT=_7(?<%L^-o-omg*3r`77PcIro%h0{(5wss2M2FB5=vnj}dI6n6ufp=r z!h0TpB@h_+3%@_!(0UKXTqwk7TX+=NAnQKVj8d>J4fUe+g}t;rmZz-P!uq z`jZU<4X-u!HlA(;st z&pa{fYRiMO8)k2reWsPPE^d8m&iXdB{U7uC=bh}Ro1d6}x${Kl+nxXJ>g{@_>!a?@ z?nk=+vLL!(!-6Zf9PA18Jk@h$;fjUl7wukrp|`zvd+&>Vi~FwjZ|eVepmV@`Y3ZS* zmj}BC9~!(^7?hXi&1={2llUGq3(ZH!ZNYPSH&ds?^ zIGM4liD{V7Ux%SP*@EpRl2q}>#3M+8C=gy&s6L|eVM*pZqC%9)bflbLQp5R3$8^1E zWun%W4Ks6d2}UsN!MagZat;PI$bqFC?<@i-KiN<2|L9svXdc|0XRpkER|NRV(P zc0vdf;N|1_DG3w={BvwzprtaKF#cr*gJvLL1|`L>h|2*LDzAn@PGTNA$>^@@dn`;Uju5F9iq(%OxIJ1}so`V7YwVraGRj7j{W`PGSPSh6xd- zBzWZV>gqE5xd`dM0svMwiQqs&WMFK-NMtKpAec~uu3f+<@nPggKR`#(OXxJZTxefr zoGjocGf8A< z0ak!bE}NW708AMhh5|a6>j83|Y%(!9qe$~&A|@s*hDe|p8Gw(9Eg0m+sLn7AK+ZN{ z`^FX#0r+~xt;PUu!**g##Iaz(4Ei}Ig@?7uO6}@lDEmCr1J0)su&gZkKuZGVc#*5F z<|P3ORPf~`ndb;mIWUqJ00v0J+VsKc8rT4QJ>UgY-k$$|4k#H9@BQA}V`1Zy*2#I1mRjt18TU?4xOqOLZ7|ul)%-_yCe2`Rbyn z3yzy~aCYOytLHXu#0Tz;TlbE#qOg45bXO2{Q`B82>E38ZoO!RW51yN}vS_%xVwe@^ z*jHve9nW*9>e{RL6MP(^{Yo?p^D#V$7DH!(A*c!ihz&b48<#+qgz8-EWamKW)C`8S zqyq&Et-_N`)?94F{1{jn*`6AdN<&;ri>7E{v~EIlm9V171m|0;?^?L<8&wEE}aG4O0HPer!-Dr`F}S4?dl zGRGWjfzz|xVNK=-sKg-t&%vN3mP;nNm?CJ#Or_1FXuD=ghi>2oPb*~|C)~CLA$L&LMx7lRQ0AFWgC#U1cHi&%3a{WJ% z^8{(vE&` zX6)yIkRFEjP_V25vR7L$5)`TC*OW+*=V!OgYG1N)u547y=DA=*(F0*o3`Bx$VZ)df zx8v2n)cyX5t_I+oJZb$&<5!?Hlg2OMoY~NX@XD}nidmO!M6?r0lL6Y!xTXsMGuy;l zF)_)^uiK@F%hEFyt*islwd5dQ^v1RRfa+8I$`e(x;tSzH+$?eR93H|UpCVV$N;ur) z1ySPt@@>0GStwLd5ek)&T`Oe2q)NE*CTjTB@tqruZeKsurFwoCn_KTtIZ0?69koMKtWObQ#_2SP%X-W)z7WixHL}@4>6z> zj{F1;%m%6fVm(4(TXbzd0KFQHAP{~{D{1MLtFyBb{6Qhag3cP#5>}zzNNcDQ!NYl(2Yu^}Ax zhD@cZOz=tqgk8aOXDk`7U2>C7+u*nGNLN=6R~GhbJ|RlLRlp+Kpucec8`-uvdZ2V+ zf=eIr!exZrvk>S=g{#P9ogyzbr|L>@ruEoVT}9;%U-;yW#*SN18z>(0GRQE{(^GW; zRid}2>J<9fb&`!yV%xAaRaa3q3+Grg@!d!v^`=sI=yXHj0uA3uU|03Uyc;KlUu12S9--YxFB=Rd+;o%8?z diff --git a/fontdata/fonts/academicons.svg b/plugins/academic-icons/fonts/academicons.svg similarity index 82% rename from fontdata/fonts/academicons.svg rename to plugins/academic-icons/fonts/academicons.svg index 24cbbf4..d72201d 100644 --- a/fontdata/fonts/academicons.svg +++ b/plugins/academic-icons/fonts/academicons.svg @@ -5,7 +5,7 @@ --> -Created by FontForge 20190801 at Sun Nov 29 17:06:25 2020 +Created by FontForge 20190801 at Thu Jun 1 11:28:32 2023 By Nicolas @@ -26,12 +26,8 @@ Created by FontForge 20190801 at Sun Nov 29 17:06:25 2020 /> +d="M244 21c15 1 35 0 44 15c3 11 -1 23 -5 34c-6 16 -13 32 -19 47c-46 1 -93 0 -139 1c-8 -20 -16 -40 -23 -60c-4 -8 -7 -20 1 -27c10 -9 23 -8 35 -10v-21h-114v20c11 3 26 6 30 19c40 101 80 202 118 303c-6 14 -11 28 -17 42h99c46 -115 93 -230 139 -344 +c4 -14 19 -17 31 -19v-21h-180v21zM139 154h55h55c-19 47 -37 95 -56 142c-18 -48 -36 -94 -54 -142z" /> +d="M62.2578 439.994v0.000976563c8.7373 -0.208008 17.0049 -10.248 17.0049 -10.248l139.339 -133.672l101.071 91.6465l0.19043 0.172852l0.203125 0.166016c4.90625 4.81934 11.3877 7.70312 18.252 8.12207l0.00585938 0.0185547 +c1.05957 -0.00195312 2.11816 -0.0712891 3.16699 -0.211914c10.166 -2.4209 18.7891 -9.11816 23.6465 -18.3701c4.01758 -9.88574 0.291016 -17.6826 -7.50195 -27.8057l-0.102539 -0.139648l-0.115234 -0.132812l-87.1963 -102.987l25.2568 -24.2285 +c17.0928 -15.6211 17.002 -42.5723 -0.197266 -58.0762l-23.0615 -21.4434l128.783 -155.019c6.05273 -6.27637 8.49609 -15.2041 6.48145 -23.6885c-2.51465 -9.04297 -9.44238 -16.1914 -18.4004 -18.9902c-2.37793 -0.728516 -4.84863 -1.10254 -7.33496 -1.1084 +c-6.88184 0.109375 -13.4678 2.83105 -18.4189 7.61035l-146.593 139.609l-95.1221 -88.4375c-3.72852 -4.63086 -9.30664 -7.38672 -15.249 -7.53516c-9.27832 -0.0908203 -17.6777 5.47754 -21.209 14.0586c-3.46777 8.33105 0.375 19.9385 6.58398 26.5527 +l79.9766 98.2344l-28.6631 27.3008c-19.292 19.2861 -18.4004 47.1162 2.33984 67.8516l24.6191 23.0391s-111.243 133.583 -122.178 149.671c-7.46582 10.6934 -9.91016 16.4219 -6.50586 24.5918c3.57715 8.33398 11.8613 13.6562 20.9277 13.4473zM338.319 388.78 +v-0.0214844c-5.18945 -0.473633 -10.0342 -2.79199 -13.6592 -6.53516l-100.681 -91.3047l40.8809 -39.2197l86.8916 102.64c8.05957 10.4746 8.75195 14.9609 6.50488 20.4824c-3.83691 6.77344 -10.2119 11.7451 -17.7188 13.8105 +c-0.735352 0.0976562 -1.47754 0.145508 -2.21875 0.148438zM175.076 246.873l-24.623 -23.041c-16.8564 -16.8564 -19.4756 -39.8701 -2.15918 -57.1865l220.151 -209.691c3.5752 -3.45898 8.32031 -5.44336 13.293 -5.56152 +c1.75293 0.00292969 3.49512 0.266602 5.1709 0.78418c6.58887 2.0752 11.6631 7.36914 13.4629 14.0381c1.83984 6.0957 -0.860352 11.5049 -5.18066 16.9141z" /> +d="M48 416h352c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48zM119.65 351.996c-5.84961 0.134766 -11.1943 -3.29883 -13.502 -8.67578c-2.19629 -5.27051 -0.619141 -8.9668 4.19727 -15.8652 +c7.05469 -10.3799 78.8242 -96.5625 78.8242 -96.5625l-15.8828 -14.8633c-13.3809 -13.3779 -13.9561 -31.333 -1.50977 -43.7754l18.4922 -17.6133l-51.5977 -63.377c-4.00586 -4.26758 -6.48535 -11.7559 -4.24805 -17.1309 +c2.27832 -5.53613 7.69727 -9.12891 13.6836 -9.07031c3.83398 0.0957031 7.43262 1.87402 9.83789 4.86133l61.3691 57.0566l94.5762 -90.0703c3.19434 -3.08398 7.44336 -4.83984 11.8828 -4.91016c1.60449 0.00390625 3.19824 0.245117 4.73242 0.714844 +c5.7793 1.80566 10.249 6.41797 11.8711 12.252c1.2998 5.47363 -0.276367 11.2334 -4.18164 15.2832l-83.0859 100.012l14.8789 13.834c11.0957 10.0029 11.1543 27.3906 0.126953 37.4688l-16.2949 15.6309l56.2559 66.4434l0.0742188 0.0859375l0.0664062 0.0898438 +c5.02734 6.53125 7.43164 11.5615 4.83984 17.9395c-3.13379 5.96875 -8.69727 10.29 -15.2559 11.8516c-0.676758 0.0908203 -1.35938 0.135742 -2.04297 0.136719l-0.00390625 -0.0117188c-4.42871 -0.270508 -8.61035 -2.13086 -11.7754 -5.24023l-0.130859 -0.107422 +l-0.123047 -0.111328l-65.207 -59.127l-89.8965 86.2402s-5.33398 6.47754 -10.9707 6.61133zM297.754 318.955c0.478516 -0.00195312 0.957031 -0.0332031 1.43164 -0.0957031c4.84277 -1.33301 8.95605 -4.54004 11.4316 -8.91016 +c1.44922 -3.5625 1.00293 -6.45703 -4.19727 -13.2148l-56.0586 -66.2188l-26.375 25.3027l64.9551 58.9062c2.33887 2.41504 5.46484 3.91113 8.8125 4.2168v0.0136719zM192.436 227.402l142.01 -170.158c2.78711 -3.48926 4.5293 -6.97949 3.3418 -10.9121 +c-1.16113 -4.30273 -4.43457 -7.71777 -8.68555 -9.05664c-1.08105 -0.333984 -2.20508 -0.503906 -3.33594 -0.505859c-3.20801 0.0761719 -6.26953 1.35645 -8.57617 3.58789l-142.033 135.285c-11.1719 11.1719 -9.48242 26.0195 1.39258 36.8945z" /> +d="M310.362 408v-129.332l-93.1279 -30.6699l-192.066 65.9844zM9.6377 308.871l192.034 -65.9854l-192.034 -65.9844v131.97zM217.234 237.774l77.5703 -25.5635l-192.066 -65.958l-77.543 25.5361zM310.362 207.099v-131.97l-192.066 65.9854zM102.738 136.002 +l192.066 -65.9844l-285.167 -94.0176v129.332z" /> +d="M48 416h352c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48zM335.379 352l-211.254 -69.6426l142.271 -48.877l68.9824 22.7168v95.8027zM112.621 278.57v-97.7539l142.246 48.877zM266.396 225.906 +l-142.252 -48.877l57.4395 -18.916l142.271 48.8574zM335.379 203.184l-142.271 -48.877l142.271 -48.877v97.7539zM181.584 150.52l-68.9629 -22.7168v-95.8027l211.234 69.6426z" /> +d="M286.415 440c18.0693 -2.17578 35.5693 -6.43262 52.2197 -12.2979c0 -2.45996 -0.188477 -5.2041 -0.188477 -7.94727c-0.189453 -39.6387 15.3242 -77.0996 43.7051 -105.291c30.3672 -30.084 70.5752 -44.8418 110.781 -43.8965 +c5.58105 -16.6494 9.26953 -34.3408 11.2559 -52.4092c-4.8252 -0.378906 -9.74219 -0.567383 -14.5674 -0.567383c-52.5039 0 -104.914 19.7705 -144.741 59.4092c-38.4082 38.2188 -59.6006 88.9268 -59.4111 142.945c0 6.62207 0.189453 13.4326 0.946289 20.0547z +M219.814 439.621c21.1914 -66.7891 58.4648 -127.807 109.738 -178.893c50.1396 -49.9492 109.55 -85.5205 172.744 -106.712c-2.74414 -17.8799 -7.56836 -34.9072 -13.8115 -51.2734c-71.8984 23.6504 -139.443 63.668 -196.395 120.43 +c-58.0859 57.8965 -100.278 127.618 -123.74 203.395c16.4609 6.05469 33.584 10.5 51.4639 13.0537zM394.262 400.173c28.5693 -19.0146 53.0723 -44.0859 71.709 -73.0342c-9.17676 -7.37891 -20.2451 -11.2559 -32.0703 -11.2559h-0.19043 +c-13.8125 0 -26.582 5.39258 -36.3262 15.1357c-9.74414 9.74414 -15.2314 22.7051 -14.9473 36.5166c0 12.0146 4.25684 23.4609 11.8252 32.6377zM100.333 387.969l349.649 -352.299c-11.0684 -13.7178 -23.4609 -26.3936 -37.4619 -37.4619l-350.028 352.678 +c11.2578 13.6221 24.0293 26.0146 37.8408 37.083zM21.623 279.743c73.79 -23.5557 142.944 -64.1406 201.408 -121.942c56.9512 -56.5723 98.291 -124.401 122.226 -198.38c-16.2715 -6.43262 -33.3936 -11.0684 -51.2734 -13.8115 +c-21.5693 64.8975 -58.2744 124.497 -108.414 174.446c-51.2744 51.0859 -112.294 87.0332 -177.19 108.224c2.55371 17.7852 7 34.9092 13.2441 51.4639zM29.7139 163.097c51.1436 -0.947266 101.979 -20.833 140.719 -59.4072 +c38.4092 -38.2197 59.4102 -89.1152 59.3154 -142.943c0 -5.67676 -0.37793 -11.2588 -0.755859 -16.7461c-18.2578 1.8916 -35.665 5.77246 -52.4102 11.4482c0.189453 1.79785 0.188477 3.7832 0.188477 5.48633c0.189453 39.6377 -15.3242 77.1016 -43.7051 105.293 +c-31.0293 30.8398 -72.4668 45.4092 -113.24 43.8955c-5.77051 16.6494 -9.83789 34.1504 -12.0137 52.2197c7.28418 0.638672 14.5967 0.888672 21.9023 0.753906zM81.2227 64.8076h0.19043c13.8115 0 26.5811 -5.3916 36.3252 -15.1357 +c9.74414 -9.64941 14.9482 -22.7041 14.9482 -36.5166c0 -11.6357 -3.87793 -22.7988 -11.0674 -31.6914c-29.1377 18.4473 -54.209 42.7588 -73.4131 71.3291c9.27051 7.75684 20.8125 12.0146 33.0166 12.0146z" /> +d="M48 416h352c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48zM243.623 352c-0.488281 -4.27246 -0.611328 -8.66699 -0.611328 -12.9395c-0.12207 -34.8506 13.5508 -67.5654 38.3301 -92.2227 +c25.6953 -25.5732 59.5068 -38.3281 93.3809 -38.3281c3.1123 0 6.28613 0.123047 9.39844 0.367188c-1.28125 11.6572 -3.66113 23.0703 -7.26172 33.8125c-25.9395 -0.610352 -51.8789 8.91113 -71.4707 28.3203c-18.3105 18.1885 -28.3193 42.3564 -28.1973 67.9297 +c0 1.76953 0.121094 3.54004 0.121094 5.12695c-10.7422 3.78418 -22.0322 6.53027 -33.6895 7.93359zM200.654 351.756c-11.5352 -1.64746 -22.583 -4.51562 -33.2031 -8.42188c15.1367 -48.8877 42.3594 -93.8701 79.834 -131.223 +c36.7422 -36.6201 80.3193 -62.4385 126.705 -77.6973c4.02832 10.5586 7.14062 21.5449 8.91016 33.0801c-40.7705 13.6719 -79.0996 36.6221 -111.447 68.8477c-33.0801 32.958 -57.127 72.3242 -70.7988 115.414zM313.201 326.305 +c-4.88281 -5.91992 -7.62891 -13.3057 -7.62891 -21.0566c-0.182617 -8.91113 3.35645 -17.2725 9.64258 -23.5586s14.5264 -9.76562 23.4375 -9.76562h0.121094c7.62891 0 14.7715 2.50098 20.6914 7.26172c-12.0234 18.6768 -27.8311 34.8516 -46.2637 47.1191z +M123.57 318.432c-8.91113 -7.14062 -17.1514 -15.1367 -24.4141 -23.9258l225.824 -227.533c9.0332 7.14062 17.0293 15.3203 24.1699 24.1699zM72.7891 248.609c-4.02832 -10.6807 -6.89746 -21.7285 -8.54492 -33.2031 +c41.8691 -13.6719 81.2363 -36.8643 114.316 -69.8223c32.3477 -32.2256 56.0293 -70.6758 69.9453 -112.545c11.5352 1.76953 22.582 4.75977 33.0801 8.91016c-15.4414 47.7285 -42.1133 91.4883 -78.8555 127.986c-37.7188 37.292 -82.335 63.4766 -129.941 78.6738z +M78.0098 173.354c-4.71387 0.0869141 -9.43164 -0.0761719 -14.1309 -0.488281c1.40332 -11.6572 4.02734 -22.9473 7.75 -33.6895c26.3057 0.976562 53.04 -8.42383 73.0586 -28.3203c18.3105 -18.1885 28.3193 -42.3564 28.1973 -67.9297 +c0 -1.09863 0.000976563 -2.37988 -0.121094 -3.53906c10.8027 -3.66211 22.0332 -6.16602 33.8125 -7.38672c0.244141 3.54004 0.486328 7.14062 0.486328 10.8027c0.0605469 34.7285 -13.4883 67.5654 -38.2676 92.2227 +c-24.9932 24.8867 -57.7891 37.7178 -90.7852 38.3281zM111.24 109.941c-7.87305 0 -15.3193 -2.74707 -21.3008 -7.75195c12.3896 -18.4326 28.5645 -34.1182 47.3633 -46.0195c4.63867 5.7373 7.14062 12.9404 7.14062 20.4473c0 8.91113 -3.3584 17.333 -9.64453 23.5586 +c-6.28613 6.28613 -14.5244 9.76562 -23.4355 9.76562h-0.123047z" /> - + +d="M48 416h352c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48zM64 352v-320h320v320h-320zM119.48 295.088c13.4355 0 24.3398 -10.9043 24.3398 -24.3398v-0.00195312 +c0 -13.4355 -10.9043 -24.3398 -24.3398 -24.3398s-24.3398 10.9043 -24.3398 24.3398v0.00195312c0 13.4355 10.9043 24.3398 24.3398 24.3398zM173.889 288.645l45.8379 -0.115234l83.0215 -133.756v133.871h42.2363v-200.447h-46.5332l-80.1777 132.438v-132.438 +h-44.3848v200.447zM97.2891 229.227h44.3848v-141.029h-44.3848v141.029z" /> - + +d="M48 416h352c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48zM144.969 306.055l24.877 -38.0781l24.8594 38.0781h-49.7363zM248.992 306.055l24.8809 -38.0781l24.8555 38.0781h-49.7363z +M220.301 259.268c-9.75488 0 -18.7305 -1.53223 -26.9688 -4.58594c-8.21777 -3.05371 -15.3291 -7.43945 -21.334 -13.1367c-5.36914 -5.10254 -9.67383 -11.1904 -12.9121 -18.2402c-0.47168 5.06152 -1.65625 9.44824 -3.5625 13.1367 +c-2.52051 4.87695 -5.94336 8.74902 -10.2266 11.6387c-4.2832 2.86914 -9.28516 4.93848 -14.9824 6.16797s-11.7227 1.8457 -18.0547 1.8457h-48.2598v-124.766h27.4824v49.8809h13.5645l26.0879 -49.8809h32.9551l-31.7051 52.6914 +c8.8125 1.63965 15.5137 5.5498 20.084 11.7188c0.205078 0.287109 0.388672 0.597656 0.59375 0.884766c-0.0410156 -0.962891 -0.0605469 -1.92676 -0.0605469 -2.91016c0 -9.98047 1.67871 -19.0381 5.01953 -27.1328s8.0127 -15.0039 14.0176 -20.7012 +c5.98438 -5.69727 13.0938 -10.0791 21.332 -13.1328c8.21777 -3.05371 17.2158 -4.5918 26.9707 -4.5918s18.7305 1.53809 26.9688 4.5918c8.21777 3.05371 15.3291 7.43555 21.334 13.1328c5.98438 5.69727 10.6562 12.6064 14.0176 20.7012 +c0.451172 1.08594 0.858398 2.21094 1.24805 3.33789v-38.5898h27.4824v49.8809h13.5684l26.0859 -49.8809h32.9531l-31.7402 52.6914c8.8125 1.63965 15.5098 5.5498 20.0801 11.7188c4.59082 6.16895 6.86523 13.6709 6.86523 22.4629 +c0 7.2959 -1.27051 13.3633 -3.79102 18.2402s-5.94336 8.74902 -10.2266 11.6387c-4.2832 2.86914 -9.2793 4.93848 -14.9766 6.16797s-11.7246 1.8457 -18.0566 1.8457h-48.2832v-38.5898c-0.389648 1.12695 -0.796875 2.23145 -1.24805 3.33789 +c-3.34082 8.09473 -8.0127 15.0059 -14.0176 20.7031s-13.0957 10.083 -21.334 13.1367c-8.21777 3.05371 -17.2158 4.58594 -26.9707 4.58594zM220.322 233.898c5.88184 0 11.1865 -1.00391 15.9414 -2.99219s8.83301 -4.79688 12.2559 -8.38281 +c3.40234 -3.58594 6.02539 -7.84961 7.84961 -12.7676c1.82422 -4.93848 2.72656 -10.2842 2.72656 -16.043c0 -5.88184 -0.902344 -11.2539 -2.72656 -16.1309s-4.42676 -9.09766 -7.84961 -12.6836c-3.40234 -3.58594 -7.48047 -6.37207 -12.2559 -8.38086 +c-4.75488 -2.00879 -10.0801 -2.99414 -15.9414 -2.99414c-5.88184 0 -11.1885 1.00586 -15.9434 2.99414s-8.83301 4.79492 -12.2559 8.38086c-3.40234 3.58594 -6.02344 7.80664 -7.84766 12.6836s-2.72656 10.249 -2.72656 16.1309 +c0 5.75879 0.902344 11.1045 2.72656 16.043s4.4248 9.18164 7.84766 12.7676c3.40234 3.58594 7.48047 6.37402 12.2559 8.38281c4.75488 1.98828 10.0615 2.99219 15.9434 2.99219zM91.502 232.834h17.9727c2.45898 0 4.99805 -0.186523 7.58008 -0.535156 +s4.87695 -1.02148 6.86523 -2.02539s3.64844 -2.44238 4.93945 -4.32812s1.94922 -4.40332 1.94922 -7.58008c0 -3.38184 -0.739258 -6.02441 -2.21484 -7.91016s-3.32129 -3.26172 -5.55469 -4.14258s-4.73047 -1.41211 -7.47656 -1.57617 +c-2.7666 -0.18457 -5.37012 -0.263672 -7.84961 -0.263672h-16.2109v28.3613zM311.373 232.834h17.9727c2.45898 0 5.00195 -0.186523 7.58398 -0.535156s4.87695 -1.02148 6.86523 -2.02539s3.64844 -2.44238 4.93945 -4.32812s1.94531 -4.40332 1.94531 -7.58008 +c0 -3.38184 -0.739258 -6.02441 -2.21484 -7.91016s-3.31738 -3.26172 -5.55078 -4.14258s-4.73438 -1.41211 -7.48047 -1.57617c-2.7666 -0.18457 -5.37012 -0.263672 -7.84961 -0.263672h-16.2109v28.3613zM169.846 116.043l-24.877 -38.0977h49.7363zM273.873 116.043 +l-24.8809 -38.0977h49.7363z" /> + + + + + + + + + + + + diff --git a/fontdata/fonts/academicons.ttf b/plugins/academic-icons/fonts/academicons.ttf similarity index 81% rename from fontdata/fonts/academicons.ttf rename to plugins/academic-icons/fonts/academicons.ttf index 1b59b913a3cc544d4b2d7eee9ab469f0905fa648..ca7b48c880827108abd97a098090f5ab359dad50 100644 GIT binary patch delta 10363 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 diff --git a/plugins/academic-icons/fonts/academicons.woff b/plugins/academic-icons/fonts/academicons.woff new file mode 100644 index 0000000000000000000000000000000000000000..9d631dd444b3bec991420b9fce978cb3ce3d221c GIT binary patch literal 131616 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%0AlHn;_Qt_t#lojJKn~ddd&K#s+*A+8R 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 b2m0QToVDMr#WVc_foD}a>V74jOq~D#N2|C! literal 0 HcmV?d00001 diff --git a/posts/bypassing-censorship/index.html b/posts/bypassing-censorship/index.html deleted file mode 100644 index afaf55c..0000000 --- a/posts/bypassing-censorship/index.html +++ /dev/null @@ -1,207 +0,0 @@ -Bypassing censorship: tools and services · Aleksandr Mikoff's blog -

    Bypassing censorship: tools and services

    Bypassing the censorship in Russia

    The level of censorship in Russia has been increasing over last few decades and was pushed to the new heights after the war has started. In the following post I would like to discuss the options we have to bypass the restrictions (of course you can just buy VPN subscription and be done with that, but we are the engineers, right?).

    Suppose we are connected to our network provider and want to access the resource that is blocked by government firewall. From network perspective what we need is to route our traffic, or information flow, through some server in internet. This server should be reachable by us and at the same time this resource should be able to access the resource that is blocked in our home network.

    Suppose we have our virtual private server (VPS) with public IP address 55.55.55.55: we can directly access it and from this we can connect to our blocked resource. The preferable route of the traffic is shown in violet. If we do not have this route we will be never able to get to our blocked destination.

    svg

    Option 1: Outline

    One of the easiest solutions is to install Outline1 on your private: it allows to easily create and manage your own VPN server. The whole pipeline consists of three steps:

    1. Install Outline Manager on your VPS.
    2. Generate access keys and distribute them across the clients (friends, family, etc.).
    3. Clients connect to VPN using these keys and Outline Client app for their favourite platform (Windows, Linux, Android).

    The how-to for Outline is described here.

    Option 2: XRay or V2Ray

    XRay2 is the successor of V2Ray3 – the collection of tools that help users build their own basic communication networks. In China this tool is very popular for bypassing the Great Firewall and accessing the blocked content. XRay works as proxy tool: it starts the local proxy on client machine and routes all the traffic from this proxy to one or another destination.

    The configuration and route rules are very flexible: requests can be re-routed based on their target ip, domain or even protocol. New rules can be created very easily. The killer-feature of XRay for me is its ability to re-route only the requests to blocked websites through VPS and directly access all the others.

    Server setup

    Issue the following command4 on your server:

    bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install-geodata
    -

    Verify that the configuration file /usr/local/etc/xray/config.json was created. Create more clients if required:

    Spoiler -
    {
    -    "inbounds": [
    -        {
    -            "port": 11123, // Port you want to use from server side, select freely
    -            "protocol": "vmess",
    -            "settings": {
    -                "clients": [
    -                    {
    -                        "id": "37292d7c-ac1c-44ef-8c20-5864a6d62ac5" // Client 1, generate your own UUID
    -                    },
    -                    {
    -                        "id": "9d5ca145-070e-4c88-92d7-a32a167dde6e" // Client 2, generate your own UUID
    -                    }
    -                ]
    -            }
    -        }
    -    ],
    -    "outbounds": [
    -        {
    -            "protocol": "freedom"
    -        }
    -    ]
    -}
    -

    Client setup

    For client we have to create config.json file and set up the configuration, then we can use this same file for all our clients (Linux, Windows, Android) and change only UUIDs. I will leave mine config.json here for convenience. Do not forget to change the configuration parameters.

    Spoiler -
    {
    -    "inbounds": [
    -        {
    -            "port": 10808,          // Port for proxy on client side, you can use your own
    -            "listen": "127.0.0.1",
    -            "protocol": "socks",
    -            "settings": {
    -                "udp": true
    -            },
    -            "sniffing": {
    -                "enabled": true,
    -                "destOverride": [
    -                    "http",
    -                    "tls"
    -                ]
    -            }
    -        }
    -    ],
    -    "outbounds": [
    -        {
    -            "protocol": "freedom",
    -            "tag": "direct"
    -        },
    -        {
    -            "protocol": "vmess",
    -            "settings": {
    -                "vnext": [
    -                    {
    -                        "address": "55.55.55.55", // IP address of our VPS server
    -                        "port": 11123,            // Port on the server
    -                        "users": [
    -                            {
    -                                "id": "37292d7c-ac1c-44ef-8c20-5864a6d62ac5" // You have to change it for other users
    -                            }
    -                        ]
    -                    }
    -                ]
    -            },
    -            "tag": "vpn"
    -        },
    -        {
    -            "protocol": "blackhole",
    -            "settings": {},
    -            "tag": "block"
    -        }
    -    ],
    -    "routing": {
    -        "domainStrategy": "IPIfNonMatch",
    -        "rules": [
    -            {   // Rule 1: we want to route all ads to tag 'block'
    -                "type": "field",
    -                "outboundTag": "block",
    -                "domain": [
    -                    "geosite:category-ads-all"
    -                ]
    -            },
    -            {
    -                // Rule 2: we want to access all internal networks directly
    -                "type": "field",
    -                "outboundTag": "direct",
    -                "ip": [
    -                    "geoip:private"
    -                ]
    -            },
    -            {
    -                // Rule 3: we also want to acess some resources directly
    -                "type": "field",
    -                "outboundTag": "direct",
    -                "domain": [
    -                    "domain:leroymerlin.ru",
    -                    "geosite:yandex",
    -                    "domain:mirconnect.ru",
    -                    "domain:vk.com",
    -                    "domain:sberbank.ru"
    -                ]
    -            },
    -            {
    -                // Rule 4: the list of resources that have to be routed to our vpn tag.
    -                "type": "field",
    -                "outboundTag": "vpn",
    -                "domain": [
    -                    "domain:meduza.io",
    -                    "domain:istories.media",
    -                    "geosite:twitter",
    -                    "geosite:facebook",
    -                    "geosite:instagram",
    -                    "geosite:category-media",
    -                    "geosite:category-entertainment"
    -                ]
    -            }
    -        ]
    -    }
    -}
    -
    Depending on the target platform the following setup options are available:

    • Linux – install XRay in the same manner as for server, edit the /usr/local/etc/xray/config.json as described earlier, use systemctl restart xray or service xray restart if something goes wrong.
    • Windows – download the binaries from 5, add binary to autostart while providing the path to config.json file.
    • Android – download APK from 6, install, import config.json.

    Option 3: Wireguard

    As another option we can use Wireguard7 to organize the tunnels between our VPS and host to bypass the restrictions. Another benefit of Wireguard is the easiness of deployment site-to-site VPN. Connecting several distant local area networks (LAN - for example your home and villiage networks behind routers) using VPN has the following benefits:

    1. we can access the clients of all LANs directly by internal IPs;
    2. we can forward not all the traffic through VPN, but only the traffic between our LANs, while accessing all the other website directly, without loading our VPS server.

    Let’s assume we want to connect our home network (192.168.101.0/24 subnet), countryhouse network (192.168.102.0/24 subnet) and the laptop. The two LANs are managed by OpenWrt routers. As initial request we want our VPN to tunnel only LAN-to-LAN traffic. If in future we want to tunnel all the traffic through VPN we will have change only one line in our configuration, I will show which one.

    Our VPS server has the same IP address 55.55.55.55 and we allocate 10.3.2.0/24 subnet for wireguard network. The scheme of our network is shown here:

    svg

    Having this setup we can access 192.168.102.0/24 hosts while being connected to 192.168.101.0/24 network and vice-versa. For example, we can access CCTV camera from our PC, and access the printer from the laptop. If we are connected to our OpenWrt routers (192.168.101.1 and 192.168.102.1) we do not even need to set up a wireguard tunnel to our VPS, OpenWrt will handle all the connections. If we are outside our routers, we will have to connect to our VPN using wireguard client for PC or smartphone.

    Server setup

    Install wireguard on the server:allow traffic forwarding between interfaces:

    sudo apt install wireguard
    -

    allow traffic forwarding between interfaces and enable proxy ARP:

    sudo sysctl -w net.ipv4.ip_forward=1
    -sudo sysctl -w net.ipv4.conf.all.proxy_arp=1
    -

    Create and set up the config file for wireguard (/etc/wireguard/wg0.conf), where EXTERNAL_ETHERNET is the main internet interface on your server:

    Spoiler -
    [Interface]
    -Address = 10.3.2.1/24
    -ListenPort = 51820
    -MTU = 1420
    -PrivateKey = YOUR_PRIVATE_KEY
    -PostUp = sysctl --write net.ipv4.ip_forward=1; sysctl --write net.ipv6.conf.all.forwarding=1; nft add table inet wireguard-wg0; nft add chain inet wireguard-wg0 wireguard_chain {type nat hook postrouting priority srcnat\;}; nft add rule inet wireguard-wg0 wireguard_chain oifname EXTERNAL_ETHERNET masquerade
    -PostDown = sysctl --write net.ipv4.ip_forward=0; sysctl --write net.ipv6.conf.all.forwarding=0; nft delete table inet wireguard-wg0
    -SaveConfig = false
    -# OpenWrt 192.168.101.0/24 config
    -[Peer]
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_1
    -PresharedKey = YOUR_PRESHARED_KEY_1
    -AllowedIPs = 10.3.2.2/32,192.168.101.0/24
    -# OpenWrt 192.168.102.0/24 config
    -[Peer]
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_2
    -PresharedKey = YOUR_PRESHARED_KEY_2
    -AllowedIPs = 10.3.2.3/32,192.168.102.0/24
    -# Laptop config
    -[Peer]
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_3
    -PresharedKey = YOUR_PRESHARED_KEY_3
    -AllowedIPs = 10.3.2.4/32
    -# laptop end
    -
    Replace all the keys with your own, generated by wg genkey command. Note that every client has unique public and preshared keys.

    Client setup

    Create configs for your clients, note the AllowedIPs section, they differ depending on the client, OpenWrt 1 config:

    # Openwrt 192.168.101.1
    -[Interface]
    -Address = 10.3.2.2/32
    -DNS = 10.3.2.1
    -ListenPort = 29516
    -MTU = 1280
    -PrivateKey = YOUR_PRIVATE_KEY
    -[Peer]
    -AllowedIPs = 192.168.102.0/24,10.3.2.0/24
    -Endpoint = YOUR_SERVER_IP:51820
    -PersistentKeepalive = 25
    -PresharedKey = YOUR_PRESHARED_KEY_1
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_1
    -

    OpenWrt 2 config:

    # Openwrt 192.168.102.1
    -[Interface]
    -Address = 10.3.2.3/32
    -DNS = 10.3.2.1
    -ListenPort = 29341
    -MTU = 1280
    -PrivateKey = YOUR_PRIVATE_KEY
    -[Peer]
    -AllowedIPs = 192.168.101.0/24,10.3.2.0/24
    -Endpoint = YOUR_SERVER_IP:51820
    -PersistentKeepalive = 25
    -PresharedKey = YOUR_PRESHARED_KEY_2
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_2
    -

    Laptop config:

    # Laptop 10.3.2.4
    -[Interface]
    -Address = 10.3.2.4/32
    -DNS = 10.3.2.1
    -ListenPort = 54921
    -MTU = 1280
    -PrivateKey = YOUR_PRIVATE_KEY
    -[Peer]
    -AllowedIPs = 192.168.101.0/24,192.168.102.0/24,10.3.2.0/24
    -Endpoint = YOUR_SERVER_IP:51820
    -PersistentKeepalive = 25
    -PresharedKey = YOUR_PRESHARED_KEY_3
    -PublicKey = YOUR_CLIENT_PUBLIC_KEY_3
    -

    If you want to send all the traffic through VPN tunnel, just change AllowedIPs string on the client to 0.0.0.0/0.

    While rolling out this setup on OpenWrt I created the special VPN zone for firewall and assigned this zone to wireguard interface. The rules of the zone are the following:

    • VPN => LAN: accept input, accept output, accept forward, allow masquerading;
    • LAN => VPN: accept input, accept output, accept forward.

    The OpenWrt will start up the wireguard automatically. If you want to start the wireguard tunnel on you laptop copy the config to /etc/wireguard/wg0.conf and issue the command:

    sudo wg-quick up wg0
    -

    Now you should be able to ping and traceroute all the other LAN-clients succesfully (if everything works, of course):

    laptop$ traceroute 192.168.102.135
    -traceroute to 192.168.102.135 (192.168.102.135), 30 hops max, 60 byte packets
    - 1  10.3.2.1 (10.3.2.1)  144.531 ms  145.366 ms  145.247 ms
    - 2  10.3.2.3 (10.3.2.3)  233.145 ms  233.032 ms  233.023 ms
    - 3  192.168.102.135 (192.168.102.135)  246.700 ms  250.677 ms  250.604 ms
    -

    Wireguard obfuscation

    Since WireGuard is frequently blocked by traffic analysis systems, obfuscating its traffic is often necessary.

    This can be achieved by installing the Shadowsocks server and client components on all machines8, and then obfuscating WireGuard packets9.

    Links

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/documenting-the-experience/index.html b/posts/documenting-the-experience/index.html deleted file mode 100644 index 65ef020..0000000 --- a/posts/documenting-the-experience/index.html +++ /dev/null @@ -1,17 +0,0 @@ -Documenting the experience · Aleksandr Mikoff's blog -

    Documenting the experience

    Hi there! For every engineer and developer the continious self-studying is a must. -Everyday we read, try to solve problems and clarify the concepts, write the code and visualise the things, which help us to get an -insights.

    I am interested in Navigation and Positioning, Statistics, Autonomous vehicles and Robotics. These topics are huge, and what is more important, they are about future of humanity.

    To summarize all the things, which I am doing and share the ideas I decided to post about it. It will be the way I document the things I find helpful. -Every post will be devoted to the concept, algorithm or idea and include:

    • The code. It should be reproducible and easy to follow.
    • Typical usage scenarios. It should be clear where this concept can be applied.
    • Visualizations. I am strongly convinced that it is impossible to get an intuition behind the methodss without visualizations.

    Also I am planning to write about the books, which I found helpful or motivating.

    Thank you, and keep the track of your developments!

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/ekf-slam.md/index.html b/posts/ekf-slam.md/index.html deleted file mode 100644 index 5af4a9b..0000000 --- a/posts/ekf-slam.md/index.html +++ /dev/null @@ -1,48 +0,0 @@ -EKF SLAM · Aleksandr Mikoff's blog -

    EKF SLAM

    Introduction

    One of the most fundamental problems in robotics is the simultaneous localization and mapping (SLAM) problem.

    It is more difficult than localization -in that the map is unknown and has to be estimated along the way. It is more -difficult than mapping with known poses, since the poses are unknown and -have to be estimated along the way.

    S. Thrun

    In the following post I would like to discuss the EKF SLAM and highlight -the important aspects of its implementation and convergence. Particularly, I would like to demonstrate how the gyroscope bias causes the estimator divergence and how this can be fixed by filter state expansion. To illustrate the concepts I provide the code for visualization and EKF SLAM process.

    EKF SLAM allows to solve online SLAM problem: estimation of the posterior over the momentary pose along with the map:

    $$p(x_t, m | z_{1:t}, u_{1:t}).$$

    As usual, all the code is available here. The exemplary animation built with the code and random robot controller:

    Problem formulation

    Let’s pretend that we have a robot which state is represented by its position $(x, y)$ and orientation $\theta$. These values fully determine robot position and orientation in 2D and therefore named robot pose. The controller of the robot reports its forward speed and angular velocity. In addition to that, it can measure the distances and bearings to the landmarks. The robot can distinguish the measurements to one landmark from another, however, it does not know the positions $(m_x, m_y$) of the landmarks. Our task is to find the pose of the robot and the positions of the landmarks w.r.t. local coordinate frame.

    Fortunately, this task can be resolved using Kalman filter under the following assumptions:

    • The robot motion and perception noises are Gaissian distributed.
    • The knowledge of robot dynamic is known, and the relations between measurements $z$, pose $x_t$ and map $m$ are also known and all of them can be expressed in terms of each other. The last statement is very important – it makes the system observable and allows to tie all states together in covariance matrix.

    As usual, the Kalman filtering process consints of two repetitive steps:

    1. Prediction.

      The filter mean and covariance are being propogated according to the robot motion model: -$$\bar{\mu} = f(\mu_{t-1}, u_t)$$ -$$\bar{\Sigma} = F_t \Sigma_{t-1} F_t^T + G Q G^T,$$ -where $f$ is the the state transition function, $F$ – is the jacobian of the state transition function $f$ w.r.t. filter state, that, in our case consists of robot pose $(x, y, \theta, \omega_b)$ and $N$ landmark positions $(m_x^j, m_y^j)$, $G$ – is the jacobian of the state transition function w.r.t control $u$. For our robot, $u$ is equal to $(v, \omega)$, where $v$ is the speed, reported by robot odometry and $\omega$ is its angular velocity, reported by robot gyroscope. $Q$ is the diagonal matrix of the process noise,typically it is equal to $diag ( [\sigma_{v}^2, \sigma_{g}^2])$.

      The errors in velocity are assumed to be distributed according to $N(0, \sigma_v)$. The errors in angular velocity are distributed according to $N(\mu_g, \sigma_g)$ meaning that the gyroscope measurements are biased. This gyroscope bias leads to continously growing error in orientation and, as consequence, in position. To encount for this constant bias I propose to add new variable into the filter state, this variable represents gyroscope bias $\omega_b$. Now, state transition functions have the following forms:

      $$ f(\mu_{t-1}, u_t) = -\begin{bmatrix} -x_{t-1} + \Delta{t} v_t \cos{\left(\theta_{t-1} \right)} \newline -y_{t-1} + \Delta{t} v_t \sin{\left(\theta_{t-1} \right)} \newline -\theta_{t-1} + \Delta{t} (\omega_t - \omega_b)\newline -\omega_b \newline -m_x^1 \newline -m_y^1 \newline -\vdots \newline -m_x^N \newline -m_y^N -\end{bmatrix} -$$

    2. Correction.

      When new distance and bearing measurements are available we have to calculate the “expected” measurement given the current state and compare this measurements with the obtained one. For distance and bearing measurement to landmark $j$ this expected measurement can be calculated as: -$$h(\bar{\mathbf{x}}_k) = \hat{z}_t^j = -\begin{bmatrix} -\sqrt{(m_x^j - \bar{x})^2 + (m_y^j - \bar{y})^2} \newline -atan2(m_y^j - \bar{y}, m_x^j - \bar{x}) - \bar{\theta} -\end{bmatrix} -$$ -Then the observation matrix $H$ should be evaluated as Jacobian of $h(\bar{\mathbf{x}}_k)$ w.r.t. state and standard correction, or update equations for EKF can be employed.

    Now, as robot moves the uncertainty of our pose grows until we get some measurements. The measurements, however, improve not only the robot pose estimate, but also the uncertainty of landmarks previously seen by the robot. This dependence between poses and landmark positions is reflected in off-diagonal elements of the covariance matrix $\Sigma$.

    Implementation

    To see EKF SLAM in action I wrote its implementation in Python. The implementation consists of the following files:

    • robot.py – the robot simulator. The robot is able to move along the map and measure the distances and bearings to the landmarks which are within its sensing range (30 meters).
    • ekf_slam.py – the implementation of EKF SLAM algorithm, as presented in [1] with one extra state: gyroscope bias.
    • animation.py – the animation code to visualize the robot motion, its measurements and EKF SLAM state estimates.

    Illustration

    Let`s look at the EKF SLAM performance when robot sensors have the following error characteristics:

    • Velocity measurement noise $\sigma_v = 0.1 \frac{m}{s^2}$, meaning that 99.7% of velocity measurements, or controls, lie within $v_{true} \pm 0.3 m$ range.
    • Gyroscope measurement noise $\sigma_g = 0.5 \frac{deg}{s}$. The gyroscope bias is constant during the robot operations and randomly generated on a robot startup.
    • Distance measurement noise $\sigma_{d} = 1.0 m$.
    • Bearing measurement noise $\sigma_{b} = 5 deg$.

    The measurement accuracy is quite high: we know, that almost all distance measurements are accurate up to 3 meters, and bearing measurements up to 15 degrees (see 3 sigma rule).

    At first, let`s look at EKF SLAM performance when gyroscope bias is not included in its state and therefore not estimated. In the following animation the robot moves along the rectangular path while measuring the distances and bearings. -The true landmark positions are marked with black crosses, estimated positions - with magenta markers, the landmarks position uncertainty is shown by confidence ellipses, which diameters are proportional to its uncertainty. The measurements are depicted with dashed lines and the points on the map indicate the recent measurements to each landmark. The uncertainty in robot position is marked with magenta confidence ellipse around robot position estimate.

    When the robot observes the landmarks its own pose is being corrected. The interesting thing happens around 1100 frame: the robot detects the landmark that he saw in the beginning of mapping process. This knowledge, thanks to the correlation from the covariance matrix, allows to immidiately correct not only the pose of the robot, but also the position estimates of all other landmarks. Take a look at the covariance matrix on the right part of the plot: after some time all the states in the covariance matrix become correlated, meaning that all the states can be observed.

    Now, assume, that the robot equipped with much worse sensors, which noises levels 3 times higher than before, particularly $\sigma_{d} = 3.0 m$ and $\sigma_{b} = 15 deg$. The results are much worse: the robot is simply unable to correctly estimate its own position and the positions of the landmarks, because the uncertainty both of them is too high (Note: of course, we are able to “cheat” there and set the measurement noise of measurements lower than it tend to be in real life, but as long as it is only an example, I leave everything as iss).

    Now, let`s add the gyroscope bias into the filter state and allow the filter to estimate it, applying the equations, mentioned earlier. The results are even better than in the first case, when robot measurements had low noises. The filter was able to estimate gyroscope bias and, consequently, exclude this systematic error. The robot pose now drifts much slower than in two first cases, identifying the importance of correct system modelling and taking into account significant error sources. This result is especially important for SLAM with unknown landmark correspondences: the measurements clusters can be separated only if robot path drift is slow compared to the error growth rate.

    To run the animations and the EKF SLAM process locally simply execute run.py script.

    Afterword

    EKF SLAM is one of the earliest algorithm that allowed to solve SLAM problem quite efficiently. The simplicity of its implementation and intuitive transparency allows to grasp in the main concepts of SLAM. The limitations of EKF SLAM is well known and discussed in the literature. Therefore, in the next articles I plan to cover other SLAM algorithms that are intended for online and offline data processing.

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/index.html b/posts/index.html deleted file mode 100644 index 3d69877..0000000 --- a/posts/index.html +++ /dev/null @@ -1,22 +0,0 @@ -Posts · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/index.xml b/posts/index.xml index cf6b6f4..c89c2ce 100644 --- a/posts/index.xml +++ b/posts/index.xml @@ -8,10 +8,10 @@ However, to use this method the gradient has to be computed. The first problem i Spoiler heights = np.array([120, 135, 145, 150, 151, 155, 165, 170, 172, 175, 180, 190]) labels = np.array([0, 0, 0, 0., 0, 0, 1, 1, 1, 1, 1, 1.]) females = labels == 0 males = labels == 1 fig, ax = plt.subplots(1, 1, figsize = (6, 2)) ax.scatter(heights[females], labels[females]) ax.scatter(heights[males], labels[males]) ax.set(xlabel = &#39;height, cm&#39;, ylabel = &#39;class&#39;) ax.Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What&rsquo;s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates.Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group.Point cloud alignment and SVDhttps://mikoff.github.io/posts/point-cloud-alignment.md/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/posts/point-cloud-alignment.md/Point cloud alignment and SVD Singular value decomposition Recently I studied the problem of finding the rotation and translation between two point sets and decided to write the post about it. The key here is singular value decomposition, or SVD. It is extremely popular technique in many types of linear problems. It should be not surprised, that the point cloud alignment problem can be solved with its help. My aim here is to show all accommpanying theory and provide the point cloud alignment algorithm that takes not more than 10 lines of code in Python.Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$EKF SLAMhttps://mikoff.github.io/posts/ekf-slam.md/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/posts/ekf-slam.md/Introduction One of the most fundamental problems in robotics is the simultaneous localization and mapping (SLAM) problem. +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.EKF SLAMhttps://mikoff.github.io/posts/ekf-slam.md/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/posts/ekf-slam.md/Introduction One of the most fundamental problems in robotics is the simultaneous localization and mapping (SLAM) problem. It is more difficult than localization in that the map is unknown and has to be estimated along the way. It is more difficult than mapping with known poses, since the poses are unknown and have to be estimated along the way. &ndash; S. Thrun In the following post I would like to discuss the EKF SLAM and highlight the important aspects of its implementation and convergence.Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: -Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$Documenting the experiencehttps://mikoff.github.io/posts/documenting-the-experience/Sun, 09 Feb 2020 12:53:51 +0300https://mikoff.github.io/posts/documenting-the-experience/Hi there! For every engineer and developer the continious self-studying is a must. Everyday we read, try to solve problems and clarify the concepts, write the code and visualise the things, which help us to get an insights. +Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ Cumulative distribution function shows us the probability (portion of data, frequence) to draw a number $X$ less or equal than $x$: $$ P(X \leq x) = F(x).Documenting the experiencehttps://mikoff.github.io/posts/documenting-the-experience/Sun, 09 Feb 2020 12:53:51 +0300https://mikoff.github.io/posts/documenting-the-experience/Hi there! For every engineer and developer the continious self-studying is a must. Everyday we read, try to solve problems and clarify the concepts, write the code and visualise the things, which help us to get an insights. I am interested in Navigation and Positioning, Statistics, Autonomous vehicles and Robotics. These topics are huge, and what is more important, they are about future of humanity. To summarize all the things, which I am doing and share the ideas I decided to post about it. \ No newline at end of file diff --git a/posts/inverse-transform-sampling.md/index.html b/posts/inverse-transform-sampling.md/index.html deleted file mode 100644 index 72af9a0..0000000 --- a/posts/inverse-transform-sampling.md/index.html +++ /dev/null @@ -1,226 +0,0 @@ -Inverse transform sampling · Aleksandr Mikoff's blog -

    Inverse transform sampling

    Probability density and cumulative distribution functions

    Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. -Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: -$$ -P(a \leq X \leq b) = \int _a^b f(x) dx -$$

    Cumulative distribution function shows us the probability (portion of data, frequence) to draw a number $X$ less or equal than $x$: -$$ -P(X \leq x) = F(x). -$$

    It is obvious, that the probability that value lies in semi-closed interval $[a, b)$ can be represented as: -$$ -P(a \leq X \leq b) = F(b) - F(a) -$$

    Let’s experiment with normal distribution, its PDF has the following form:

    x, pi, sigma, mu = sp.symbols('x, pi, sigma, mu', positive = True)
    -pdf = ((2.0 * sp.pi) ** sp.Rational(-1, 2) / sigma) * sp.exp(-0.5 * ((x - mu)/sigma) ** 2)
    -display(pdf)
    -

    $\displaystyle \frac{0.707106781186548 e^{- \frac{0.5 \left(- \mu + x\right)^{2}}{\sigma^{2}}}}{\sqrt{\pi} \sigma}$

    Its CDF can be found by integration:

    cdf = sp.simplify(sp.integrate(pdf, x)) + 0.5
    -display(cdf)
    -

    $\displaystyle 0.5 - 0.5 \operatorname{erf}{\left(\frac{0.707106781186548 \left(\mu - x\right)}{\sigma} \right)}$

    To get 3-sigma 68-95-99.7 rule, which tells us that 68%-95%-99.7% of data lies within one-two-three standard deviation of the mean, we can just integrate the pdf on the following intervals: $[\mu - n\sigma, \mu + n\sigma]$, where $n = {1, 2, 3}$:

    Data portion within $\mu \pm \sigma$ interval:

    display(float(sp.integrate(pdf, (x, mu-sigma, mu+sigma))))
    -

    $\displaystyle 0.682689492137086$

    Data portion within $\mu \pm 2\sigma$ interval:

    display(cdf.subs({x: mu + 2 * sigma}) - cdf.subs({x: mu - 2 * sigma}))
    -

    $\displaystyle 0.954499736103642$

    Data portion within $\mu \pm 3\sigma$ interval:

    display(cdf.subs({x: mu + 3 * sigma}) - cdf.subs({x: mu - 3 * sigma}))
    -

    $\displaystyle 0.99730020393674$

    And now let’s visualize it as the area under gaussian PDF within given ranges:

    gaussian_pdf = sp.lambdify((x, mu, sigma), pdf)
    -gaussian_cdf = sp.lambdify((x, mu, sigma), cdf)
    -
    -Mu, Sigma = 4.0, 1.0
    -
    -fig, ax = plt.subplots(1, 1, figsize=(8, 4))
    -spanFrom, spanTo = Mu - 4 * Sigma, Mu + 4 * Sigma
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.plot(span, gaussian_pdf(span, Mu, Sigma), label='pdf')
    -ax.set(xlabel='$x$', ylabel='$p = f(x)$', title='$X \sim \mathcal{N} (' + str(Mu) + ", " + str(Sigma**2) + ")$")
    -for i in range(-3, 3):
    -    spanStart, spanEnd = Mu + i * Sigma, Mu + (i + 1) * Sigma
    -    span = np.linspace(spanStart, spanEnd, 100)
    -    ax.fill_between(span, gaussian_pdf(span, Mu, Sigma), interpolate=True, alpha=0.5)
    -    ax.annotate(str(np.round_(100 * (gaussian_cdf(spanEnd, Mu, Sigma) - gaussian_cdf(spanStart, Mu, Sigma)), 1)) + '%', 
    -                xy=((spanStart + spanEnd) * 0.5, 0.025), ha='center')
    -

    png

    Let’s find Gaussian distribution mean, or first moment, which, by definition equals to $\int_{-\infty}^\infty x f(x) dx$:

    mean = sp.integrate(pdf * x, (x, -sp.oo, sp.oo))
    -display(mean)
    -

    $\displaystyle 1.0 \mu$

    The variance can be found using second moment and mean, $Var(X) = \mathbb E[X^2] - \mathbb E [X]^2$

    variance = sp.integrate(pdf * x * x, (x, -sp.oo, sp.oo)) - mean**2
    -display(variance)
    -

    $\displaystyle 1.0 \sigma^{2}$

    And verify that the area under the curve, or total probability should be equal to 1.0:

    area = sp.integrate(pdf, (x, -sp.oo, sp.oo))
    -display(area)
    -

    $\displaystyle 1.0$

    Inverse CDF

    The inverse of CDF is called quantile function. If $F(x) = P(X \leq x) = p$ tells us the probability to draw a number less or equal than the value $x$ from distribution then $F^{-1}(p)$ tells us vise versa: what is the value of $x$, such that $F(X\leq x) = p$. -For example, $F^{-1}(0.5)$ is the median of the distribution, $F^{-1}(0.25)$ is the lower quartile. -In other words, it answer on the following question: what is the proportion of data, which is less or equal to the given value $x$.

    Inverse CDF for gaussian distribution has the following form:

    p = sp.symbols('p')
    -inverse_cdf = sp.solve(cdf - p, x)[0]
    -display(inverse_cdf)
    -

    $\displaystyle \mu - 1.41421356237309 \sigma \operatorname{erfinv}{\left(1.0 - 2.0 p \right)}$

    Now wish we would like to answer on the following questions: what is the value $x$ for which the half of the data is less than it?

    display(inverse_cdf.subs({p:0.5}))
    -

    $\displaystyle \mu$

    This makes sense: we know, that for gaussian its mean is equal to $mu$ and the half of the data should be less than this value.

    The following plot helps to understand an idea:

    • CDF maps $x$ value to the portion of data, which is less or equal than $x$
    • Inverse of CDF gives an answer on the following question: for given $p$ what is the value $x$, for which $P(X \leq x) = p$ satisfies?
    Mu, Sigma = 4.0, 1.0
    -
    -spanFrom, spanTo = Mu - 4 * Sigma, Mu + 4 * Sigma
    -span = np.linspace(spanFrom, spanTo, 100)
    -
    -fig, ax = plt.subplots(1, 1, figsize=(8, 4))
    -ax.plot(span, gaussian_cdf(span, Mu, Sigma), label='cdf')
    -ax.set(xlabel='$x = F^{-1}(p)$', ylabel='$p = F(x)$', title='Cumulative distribution function')
    -
    -cdf_value = float(sp.integrate(pdf, (x, -sp.oo, mu)))
    -X, Y = [Mu, Mu, spanFrom], [0.0, cdf_value, cdf_value]
    -ax.quiver(X[:-1], Y[:-1], np.diff(X), np.diff(Y), angles='xy', scale_units='xy', scale=1, width=0.005, color='C1', label='CDF')
    -
    -cdf_value = float(sp.integrate(pdf, (x, -sp.oo, mu-sigma)))
    -X, Y = [spanFrom, Mu - Sigma, Mu-Sigma], [cdf_value, cdf_value, 0.0]
    -ax.quiver(X[:-1], Y[:-1], np.diff(X), np.diff(Y), angles='xy', scale_units='xy', scale=1, width=0.005, color='C7', label='inverse CDF')
    -
    -ax.xaxis.set_major_locator(plticker.MultipleLocator(base=1.0))
    -ax.yaxis.set_major_locator(plticker.MultipleLocator(base=0.1))
    -ax.legend(loc='lower right')
    -
    -ax.set_xlim(spanFrom, spanTo)
    -ax.set_ylim(0.0, 1.0)
    -

    $\displaystyle \left( 0.0, \ 1.0\right)$

    png

    Inverse transform sampling

    Now, having this function, which maps $p$ to $x$ it should be obvious how to sample from our desired distribution having the samples from the uniform one.

    Let’s illustrate the idea with the set of uniformly distributed points along $p$ and map all of them to $x$ using inverse CDF. It’s clearly seen then after applying this transformation the points are not evenly spaced anymore.

    gaussian_inverse_cdf = sp.lambdify((p, mu, sigma), inverse_cdf)
    -
    -Mu, Sigma = 3.0, 2.0
    -
    -spanFrom, spanTo = Mu - 4 * Sigma, Mu + 4 * Sigma
    -span = np.linspace(spanFrom, spanTo, 100)
    -
    -fig, ax = plt.subplots(1, 1, figsize=(8, 4))
    -ax.plot(span, gaussian_cdf(span, Mu, Sigma), label='cdf')
    -ax.set(xlabel='$x = F^{-1}(p)$, sample space', ylabel='$p = F(x)$, uniform values')
    -uniform_samples = np.linspace(0.01, 0.99, 21)
    -for sample in uniform_samples:
    -    cdfValue = gaussian_inverse_cdf(sample, Mu, Sigma)
    -    X, Y = [spanFrom, cdfValue, cdfValue], [sample, sample, 0.0]
    -    ax.quiver(X[:-1], Y[:-1], np.diff(X), np.diff(Y), angles='xy', scale_units='xy', scale=1, width=0.001, color='C7', label='inverse CDF',
    -                headlength=30.0, headwidth=20.0)
    -
    -ax.scatter(gaussian_inverse_cdf(uniform_samples, Mu, Sigma), [0] * len(uniform_samples), alpha=0.3, c = 'C2')
    -ax.scatter([Mu - 3 * Sigma] * len(uniform_samples), uniform_samples, alpha=0.3, c = 'C1')
    -ax.set_xlim(Mu - 3 * Sigma, Mu + 3 * Sigma)
    -ax.set_ylim(0.0, 1.0)
    -ax.xaxis.set_major_locator(plticker.MultipleLocator(base=1.0))
    -

    png

    Now, thanks to CDF, we have one-to-one correspondance between uniformly sampled numbers $p$ (because for uniform distribution every sample is equally probable) and $x$, which maps $p$ to the value $x$ from our desired distribution. To have this one-to-one correspondance, CDF should be monotonically increasing.

    To experimentally verify that everythink works as expected we can randomly draw uniformly distributed numbers and map them to our desired distribution.

    Inverse transform sampling for Normal distribution

    As a first experiment, we will proceed with inverse of Gaussian CDF. To do so we sample from Uniform distribution and transform them to the Gaussian one.

    import seaborn as sns
    -
    -Mu, Sigma = 5, 1
    -
    -uniform_samples = np.random.uniform(size=5000)
    -gaussian_samples = gaussian_inverse_cdf(uniform_samples, Mu, Sigma)
    -
    -ax = sns.jointplot(np.array(gaussian_samples).astype(float), uniform_samples, marginal_kws=dict(bins=30, rug=True),
    -             joint_kws=dict(alpha=0.5, linewidth=None, marker='x'))
    -
    -spanFrom, spanTo = Mu - 4 * Sigma, Mu + 4 * Sigma
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.ax_joint.plot(span, gaussian_cdf(span, Mu, Sigma), color='red', label='CDF')
    -ax.fig.set_figwidth(6)
    -ax.fig.set_figheight(4)
    -ax.set_axis_labels('Mapped to Gaussian using inverse CDF', 'Uniformly sampled points', fontsize=10)
    -
    <seaborn.axisgrid.JointGrid at 0x7f2462c25b10>
    -

    png

    fig, ax = plt.subplots(1, 1, figsize=(6, 3))
    -ax.hist(gaussian_samples, bins=31, density=True, label='Sampled using inverse transform')
    -spanFrom, spanTo = Mu - 4 * Sigma, Mu + 4 * Sigma
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.plot(span, gaussian_pdf(span, Mu, Sigma), color='red', label='Analytical CDF')
    -ax.set(xlabel='$x$', ylabel='$p = f(x)$', title='$X \sim \mathcal{N} (' + str(Mu) + ", " + str(Sigma**2) + ")$")
    -ax.legend(loc='upper left')
    -
    <matplotlib.legend.Legend at 0x7f24509bdf90>
    -

    png

    Inverse transform sampling for Exponential distribution

    First, define exponential distribution PDF.

    x, lambd, p = sp.symbols('x, lambda, p', positive = True)
    -pdf_exponential = lambd * sp.exp(-lambd * x)
    -display(pdf_exponential)
    -

    $\displaystyle \lambda e^{- \lambda x}$

    Second, integrate PDF to obtain CDF.

    cdf_exponential = 1.0 + sp.integrate(pdf_exponential, x)
    -display(cdf_exponential)
    -

    $\displaystyle 1.0 - e^{- \lambda x}$

    Third, invert it.

    inverse_cdf_exponential = sp.solve(cdf_exponential - p, x)
    -display(inverse_cdf_exponential)
    -

    $\displaystyle \left[ \frac{\log{\left(- \frac{1}{p - 1.0} \right)}}{\lambda}\right]$

    Now we are ready to transform samples from uniform space to the exponential one.

    Lambda = 1.25
    -
    -exponential_pdf = sp.lambdify((x, lambd), pdf_exponential)
    -exponential_cdf = sp.lambdify((x, lambd), cdf_exponential)
    -exponential_inverse_cdf = sp.lambdify((p, lambd), inverse_cdf_exponential)
    -
    -uniform_samples = np.random.uniform(size=5000)
    -exponential_samples = exponential_inverse_cdf(uniform_samples, Lambda)[0]
    -
    -ax = sns.jointplot(exponential_samples, uniform_samples, marginal_kws=dict(bins=30, rug=True),
    -             joint_kws=dict(alpha=0.5, linewidth=None, marker='x'))
    -
    -spanFrom, spanTo = 0.0, exponential_inverse_cdf(0.9999, Lambda)
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.ax_joint.plot(span, exponential_cdf(span, Lambda), color='red', label='CDF')
    -ax.fig.set_figwidth(6)
    -ax.fig.set_figheight(4)
    -ax.set_axis_labels('Mapped to Exponential using inverse CDF', 'Uniformly sampled points', fontsize=10)
    -
    <seaborn.axisgrid.JointGrid at 0x7f246350dd50>
    -

    png

    fig, ax = plt.subplots(1, 1, figsize=(6, 3))
    -ax.hist(exponential_samples, bins=31, density=True, label='Sampled using inverse transform')
    -spanFrom, spanTo = 0.0, exponential_inverse_cdf(0.9999, Lambda)
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.plot(span, exponential_pdf(span, Lambda), color='red', label='Analytical CDF')
    -ax.set(xlabel='$x$', ylabel='$p = f(x)$', 
    -       title='$X \sim Exp(' + str(Lambda) + ")$")
    -ax.legend(loc='upper right')
    -
    <matplotlib.legend.Legend at 0x7f24507760d0>
    -

    png

    Sampling from experimental distribution

    Now assume that we have experimentally gathered data and we can visualize it using histogram. How we can sample from it? -One of the easiest approaches can be the following:

    • Approximate the histogram with a polynom.
    • Find polynom inverse.
    • Sample using inverse transform.

    However, finding polynom inverse analytically is not easy task. So, instead of finding it analytiall we can just solve for the root numerically each time.

    For example, let’s simulate experimental data:

    data = np.hstack([np.random.normal(6.0, 1.0, 10000), np.random.gumbel(1.5, 0.5, 10000)])
    -
    -fig, ax = plt.subplots(1, 1, figsize=(6, 3))
    -ax.hist(data, bins=100, density=True);
    -

    png

    Fit polynom to the histogram CDF and define inverse CDF as a function which finds the root:

    from scipy import interpolate
    -import scipy.optimize as scop
    -
    -heights, bins = np.histogram(data, bins=100, density=True)
    -histogram_cdf = np.hstack([0, np.cumsum(heights * np.diff(bins))])
    -
    -experimental_cdf = interpolate.interp1d(bins, histogram_cdf, fill_value="extrapolate")
    -experimental_inverse_cdf = lambda p: scop.fsolve(lambda x : experimental_cdf(x) - p, 0.0)[0]
    -
    uniform_samples = np.random.uniform(size=5000)
    -experimental_samples = [experimental_inverse_cdf(u) for u in uniform_samples];
    -
    -ax = sns.jointplot(experimental_samples, uniform_samples, marginal_kws=dict(bins=30, rug=True),
    -             joint_kws=dict(alpha=0.5, linewidth=None, marker='x'))
    -
    -spanFrom, spanTo = 0.0, experimental_inverse_cdf(0.9999)
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.ax_joint.plot(span, experimental_cdf(span), color='red', label='CDF')
    -ax.fig.set_figwidth(6)
    -ax.fig.set_figheight(4)
    -ax.set_axis_labels('Mapped to Experimental distribution using inverse CDF', 'Uniformly sampled points', fontsize=10)
    -
    <seaborn.axisgrid.JointGrid at 0x7f24508bb950>
    -

    png

    fig, ax = plt.subplots(1, 1, figsize=(6, 3))
    -ax.hist(data, bins=31, density=True, label='True data', alpha=0.5)
    -ax.hist(experimental_samples, bins=31, density=True, label='Sampled using inverse transform', alpha=0.5)
    -ax.set(xlabel='$x$', ylabel='$p = f(x)$', title='Experimental data vs Sampled from experimental distribution')
    -ax.legend(loc='upper right')
    -
    <matplotlib.legend.Legend at 0x7f244fb17a10>
    -

    png

    It works, however, its not efficient, because to find inverse CDF we have to numerically solve equation $F(x) - p = 0$. To overcome this computational overhead it is much better to just swap $x$ and $y$ on interpolation step. By doing so we directly obtain required inverse CDF for newly generated dataset:

    data = np.hstack([np.random.normal(4.0, 2.0, 10000), 
    -                  np.random.gumbel(0.5, 0.5, 10000), 
    -                  np.random.gumbel(7.5, 1.0, 10000),
    -                  np.random.normal(13.5, 1.0, 10000)])
    -
    -heights, bins = np.histogram(data, bins=100, density=True)
    -histogram_cdf = np.hstack([0, np.cumsum(heights * np.diff(bins))])
    -
    -experimental_cdf = interpolate.interp1d(bins, histogram_cdf, fill_value="extrapolate")
    -experimental_inverse_cdf = interpolate.interp1d(histogram_cdf, bins, fill_value="extrapolate")
    -

    And we can use this function directly:

    uniform_samples = np.random.uniform(size=5000)
    -experimental_samples = [experimental_inverse_cdf(u) for u in uniform_samples]
    -
    -ax = sns.jointplot(experimental_samples, uniform_samples, marginal_kws=dict(bins=30, rug=True),
    -             joint_kws=dict(alpha=0.5, linewidth=None, marker='x'))
    -
    -spanFrom, spanTo = 0.0, experimental_inverse_cdf(0.9999)
    -span = np.linspace(spanFrom, spanTo, 100)
    -ax.ax_joint.plot(span, experimental_cdf(span), color='red', label='CDF')
    -ax.fig.set_figwidth(6)
    -ax.fig.set_figheight(4)
    -ax.set_axis_labels('Mapped to Experimental distribution using inverse CDF', 'Uniformly sampled points', fontsize=10)
    -
    <seaborn.axisgrid.JointGrid at 0x7f244fa194d0>
    -

    png

    Compare with experimental distribution:

    fig, ax = plt.subplots(1, 1, figsize=(6, 3))
    -ax.hist(data, bins=31, density=True, label='True data', alpha=0.5)
    -ax.hist(experimental_samples, bins=31, density=True, label='Sampled using inverse transform', alpha=0.5)
    -ax.set(xlabel='$x$', ylabel='$p = f(x)$', title='Experimental data vs Sampled from experimental distribution')
    -ax.legend(loc='upper right')
    -
    <matplotlib.legend.Legend at 0x7f244f3d53d0>
    -

    png

    Conclusions

    After all, it should become clear what inverse transform sampling actually does and how to sample from any distribution, which inverse CDF can be derived. -In the next posts I am planning to show some examples on rejection, Metropolis Hastings and Gibbs sampling. These methods are usually used when direct sampling from probability distribution is difficult.

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/likelihood-and-log-sum-exp/index.html b/posts/likelihood-and-log-sum-exp/index.html deleted file mode 100644 index 0e992a0..0000000 --- a/posts/likelihood-and-log-sum-exp/index.html +++ /dev/null @@ -1,102 +0,0 @@ -Likelihood and probability normalization, log-sum-exp trick · Aleksandr Mikoff's blog -

    Likelihood and probability normalization, log-sum-exp trick

    Working with probabilities involves multiplication and normalization of their values. Since the numerical values sometimes are extremely low that can lead to underflow problems. -This problem is evident with particle filters - we have to multiply really low likelihood values that vanish in the end. Log-sum-exp allows to abbreviate this problem.

    Approach

    Log-likelihoods

    Since the likelihood values can be extremely low it is more convenient to work with loglikelihood instead of likelihood: -$$ -\log(\mathcal{L}). -$$ -Now, if we have independent measurements $i=1,\dots,n$ and associated likelihoods $\mathbf{L} = [\mathcal{L}_1, \dots, \mathcal{L}_n]$ we can calculate the joint likelihood as follows: -$$ -w = \prod\mathcal{L}_i -$$ -that is, obviously, numerically unstable, or joint log-likelihood: -$$ -\tau = \log\prod\mathcal{L}_{i} = \log(\mathcal{L}_1\cdot \mathcal{L}_2\cdot ~\dots~ \cdot\mathcal{L}_n) = \log{\mathcal{L}_1} + \log{\mathcal{L}_2}+~\dots~+\log{\mathcal{L}_n} = \sum_{i=1}^n\log\mathcal{L}_i, -$$ -by doing so we convert product to sum that doesn’t lead to numerical underflow issue.

    Quite often we are interested in relative likelihoods, or odds (since we normalize the probability afterwards) for our particles. Assuming that each particle has certain weight, all the weights, or log-weights are stacked into the vector: -$$ -\begin{gather} -\mathbf{w} = [w_1,\dots, w_m],\\ -\boldsymbol{\tau} = [\tau_1, \dots, \tau_m] -\end{gather} -$$ -where $m$ is the number of particles.

    To calculate the relative weight for each particle $j$ we can rewrite the equation: -$$ -\begin{aligned} -\hat{w}_j &= \frac{w_j}{\max(\mathbf{w})}\\ &= \exp\log{\frac{w_j}{\max(\mathbf{w})}}\\ &= \exp\left( \log w_j - \max(\log\mathbf{w}) \right)\\ &= \exp(\tau_j - \max\boldsymbol{\tau})\\ &= \exp\left(\sum_{i=1}^n\log\mathcal{L}_i - \max\boldsymbol{\tau}\right). -\end{aligned} -$$

    Probability normalization

    Now, having these relative weights, we can normalize them more conveniently: -$$ -p_j = \frac{\hat{w}_j}{\sum_{j=1}^m \hat{w}_j},~\sum_{j=1}^m{p_j} = 1. -$$ -Taking the logarithms on both side: -$$ -\log p_i = \log\hat{w}_j - \log\sum_{j=1}^m\hat{w}_j, -$$ -and making the aforementioned substitution $\hat{w}_j = \exp(\tau_j - \max\boldsymbol{\tau})$, after some rewriting we end up with: -$$ -\begin{align} -\log p_i &= \log\exp(\tau_j - \max\boldsymbol{\tau}) - \log\sum\exp(\tau_j - \max\boldsymbol{\tau})\\ -\log p_i &= \tau_j \underbrace{ - \max\boldsymbol{\tau} -\log\sum\exp(\tau_j - \max\boldsymbol{\tau})}_{\log\sum\exp(\boldsymbol{\tau})~-~\text{log-sum-exp trick}} -\\ -p_i &= \exp\left( \tau_j - \max\boldsymbol{\tau} -\log\sum\exp(\tau_j - \max\boldsymbol{\tau}) \right) -\end{align} -$$ -We ended up with a famous log-sum-exp trick1, the second line from the end is the normalized log-weights, and the last one is the normalized weights. The underlying meaning of this trick is apparent now: it scales the weights before normalization, avoiding the numerical under- or overflow.

    Effective sample size

    Quite often the following equation is used to estimate effective sample size: -$$ -N_{eff} = \frac{1}{\sum_{j=1}^{m} p_j^2}. -$$ -Since we work with log-likelihoods, substitute $p_j$ with $\exp\log p_j$: -$$ -\begin{gather} -\log N_{eff} &= -\log\sum_{j=1}^m \exp\log p_j^2 \\ -\log N_{eff} &= -\log\sum_{j=1}^m \exp(2\cdot\log p_j). -\end{gather} -$$ -Resulting in the following equation for $N_{eff}$: -$$ -N_{eff} = \exp\left(-\log\sum_{j=1}^m \exp(2\cdot\log p_i) \right). -$$

    Code examples

    Assume that we have two likelihoods. By calculating the joint likelihood using multiplication, we, obviously, experience numerical underflow and end up with zeros:

    >>> import numpy as np
    -
    ->>> likelihoods1 = np.array([1e-249, 1e-244, 1e-244, 1e-247])
    ->>> likelihoods2 = np.array([1e-249, 1e-244, 1e-244, 1e-247])
    -
    ->>> likelihoods1 * likelihoods2
    -array([0., 0., 0., 0.])
    -

    Let’s calculate log-likelihoods and normalize probabilities using naive method:

    >>> log_likelihood1 = np.log(likelihoods1)
    ->>> log_likelihood2 = np.log(likelihoods2)
    -
    ->>> tau = log_likelihood1 + log_likelihood2
    ->>> tau
    -array([-1132.87186575, -1123.66152538, -1123.66152538, -1137.47703594])
    -
    ->>> np.exp(tau)/np.sum(np.exp(tau))
    -array([nan, nan, nan, nan])
    -

    And now let’s normalize using the derived equation:

    >>> max_tau = tau.max()
    ->>> tau_normed = tau - max_tau - np.log(np.sum(np.exp(tau - max_tau)))
    ->>> w_normed = np.exp(tau_normed)
    ->>> w_normed
    -array([0.00005   , 0.49997475, 0.49997475, 0.0000005 ])
    -

    If we are interested in effective number of particles:

    def logSumExp(tau):
    -    max_tau = np.max(tau)
    -    return max_tau + np.log(np.sum(np.exp(tau - max_tau)))
    -
    ->>> tau_normed = tau - logSumExp(tau)
    ->>> effective_sample_size = np.exp(-logSumExp(2 * tau_normed))
    ->>> effective_sample_size
    -2.00020199509849
    -

    Success! Works perfectly!

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/linear-and-logistic-regression.md/index.html b/posts/linear-and-logistic-regression.md/index.html deleted file mode 100644 index 5db9898..0000000 --- a/posts/linear-and-logistic-regression.md/index.html +++ /dev/null @@ -1,205 +0,0 @@ -Linear and logistic regressions · Aleksandr Mikoff's blog -

    Linear and logistic regressions

    Let’s assume we want to predict whether the person is male or female judging by its height.

    Spoiler -
    heights = np.array([120, 135, 145, 150, 151, 155, 165, 170, 172, 175, 180, 190])
    -labels = np.array([0, 0, 0, 0., 0, 0, 1, 1, 1, 1, 1, 1.])
    -females = labels == 0
    -males = labels == 1
    -
    -fig, ax = plt.subplots(1, 1, figsize = (6, 2))
    -ax.scatter(heights[females], labels[females])
    -ax.scatter(heights[males], labels[males])
    -ax.set(xlabel = 'height, cm', ylabel = 'class')
    -ax.legend(['female', 'male'])
    -ax.grid()
    -

    png

    What we want to do is to find a function that approximates $p(\mathbf{Y}|\mathbf{X})$.

    Linear regression

    Let’s say we are given $N$ inputs and outputs. We want to find the linear function that maps our input $x$ to our output $y$: -$$ -y = f(x) = ax + b, -$$ -or, in general case -$$ -y = f(\boldsymbol{x}) = \boldsymbol{\theta}^T \boldsymbol{x}, -$$ -if our our prediction depends not on one parameter, but on $n$ of them: $\boldsymbol{x} \in \mathcal{R}^n$.

    What we want to minimize is our loss function, that somehow describes the discrepancy between our predictions $\hat{y}$ and actual labels $y$ for all samples $x_i$: -$$ -L(\boldsymbol{y}, \hat{\boldsymbol{y}}) = \sum_{i=0}^{N-1}L(y_i, \hat{y}_i) = \sum_{i=0}^{N-1}L(y_i, \hat{\boldsymbol{\theta}}^T \boldsymbol{x}_i). -$$

    Least squares

    If we want to minimize the sum of squared residuals we end up with: -$$ -L(\boldsymbol{y}, \hat{\boldsymbol{y}}) = \sum_{i=0}^{N-1}(y_i - \hat{\boldsymbol{\theta}}^T \boldsymbol{x}_i)^2. -$$

    If we stack all inputs (and add extra column of ones, that models the constant, or shift) and outputs, we will have two matrices $\mathbf{X}$ and $\mathbf{Y}$: -$$ -\begin{align*} -\mathbf{X} = \begin{bmatrix} \boldsymbol{x}_0^T \\ \vdots \\ \boldsymbol{x}_{N-1}^T \end{bmatrix} & & \mathbf{Y} = \begin{bmatrix} y_0 \\ \vdots \\ y_{N-1} \end{bmatrix} -\end{align*}, -$$ -we can write our loss function as: -$$ -\begin{align*} -L(\boldsymbol{y}, \hat{\boldsymbol{y}}) &= ||\mathbf{Y} - \mathbf{X} \boldsymbol{\theta}||^2 \\ -&= (\mathbf{Y} - \mathbf{X} \boldsymbol{\theta})^T(\mathbf{Y} - \mathbf{X} \boldsymbol{\theta})\\ -&= \mathbf{Y}^T\mathbf{Y} - \mathbf{Y}^T\mathbf{X}\boldsymbol{\theta} - \boldsymbol{\theta}^T\mathbf{X}^T\mathbf{Y} + \boldsymbol{\theta}^T\mathbf{X}^T\mathbf{X}\boldsymbol{\theta}\\ -&= \mathbf{Y}^T\mathbf{Y} - 2\mathbf{Y}^T\mathbf{X}\boldsymbol{\theta} + \boldsymbol{\theta}^T\mathbf{X}^T\mathbf{X}\boldsymbol{\theta}. -\end{align*} -$$ -During the derivation we note that since $\boldsymbol{\theta}$ is a vector we can rewrite $\mathbf{Y}^T\mathbf{X}\boldsymbol{\theta}$ as $\boldsymbol{\theta}^T\mathbf{X}^T\mathbf{Y}$.

    Finding the derivative w.r.t. parameters: -$$ -\begin{align*} -\frac{\partial L}{\partial {\boldsymbol {\theta }}} &= \frac{\partial(\mathbf{Y}^T\mathbf{Y} - 2\mathbf{Y}^T\mathbf{X}\boldsymbol{\theta} + \boldsymbol{\theta}^T\mathbf{X}^T\mathbf{X}\boldsymbol{\theta})}{\partial{\boldsymbol {\theta }}} \\ -&= -2\mathbf{Y}^T\mathbf{X} + 2\boldsymbol{\theta}^T\mathbf{X}^T\mathbf{X} \\ -&= \left(2\boldsymbol{\theta}^T\mathbf{X}^T - 2\mathbf{Y}^T\right)\mathbf{X} -\end{align*} -$$ -Then set the derivative to zero and solve for $\boldsymbol{\theta}$: -$$ -\begin{align*} --2\mathbf{Y}^T\mathbf{X} + 2\boldsymbol{\theta}^T\mathbf{X}^T\mathbf{X} &= \boldsymbol{0} \\ -\mathbf{X}^T\mathbf{X}\boldsymbol{\theta} &= \mathbf{X}^T\mathbf{Y}\\ -\boldsymbol{\theta} &= (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y}. -\end{align*} -$$ -By doing so and plugging the available data to $\mathbf{X}$ and $\mathbf{Y}$ we find the best fit line and perform linear regression.

    Maximum Likelihood

    The probability of multiple independent events is known as joint probability. The joint probability can be found by multiplication of the probabilities of individual events: -$$ -p(\mathbf{Y}| \boldsymbol{\theta}, \mathbf{X}) = \prod_{i=0}^{N-1}p(y_i|\boldsymbol{\theta}, x_i). -$$ -If we assume that our noises are gaussian, we can write: -$$ -\mathcal{L} = p(\mathbf{Y}| \boldsymbol{\theta}, \mathbf{X}) = \prod_{i=0}^{N-1}\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\left( \frac{y - \hat{y}}{\sigma} \right)^2} -$$

    This probability $\mathcal{L}$ is called likelihood, applying $\log$ we get log-likelihood, the sum instead of product

    $$ -\begin{align*} -\mathcal{LL} &= \sum_{i=0}^{N-1}\log\left(\frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{1}{2}\left( \frac{y - \hat{y}}{\sigma} \right)^2}\right) \\ -&= \sum_{i=0}^{N-1}\log\frac{1}{\sqrt{2\pi\sigma^2}} + \sum_{i=0}^{N-1}\log\left(e^{-\frac{1}{2}\left( \frac{y - \hat{y}}{\sigma} \right)^2}\right) \\ -&= \log\frac{1}{\sqrt{2\pi\sigma^2}} \sum_{i=0}^{N-1}1 + \sum_{i=0}^{N-1}{-\frac{1}{2}\left( \frac{y - \hat{y}}{\sigma} \right)^2} \\ -&= -\frac{\log{\pi\sigma^2}}{2}{N} - \frac{\log{2}}{2}N - \frac{1}{2\sigma^2}\sum_{i=0}^{N-1}(y - \hat{y})^2. -\end{align*} -$$ -Now if we look at $\mathcal{LL}$ as a function of only $\boldsymbol{\theta}$ and differentiate $\mathcal{LL}$ with respect to $\boldsymbol{\theta}$ we end up with exact same expression as for least-squares case. That is the reason why least squares estimate can be viewed as MLE under Gaussian noise model: -$$ -y = y_{true} + \epsilon, \quad\text{ where }\epsilon\thicksim N(0,\sigma^2). -$$

    Fitting line to our data

    Spoiler -
    # linear regression
    -X = np.hstack((heights.reshape((-1, 1)), np.ones((len(heights), 1))))
    -Y = labels.reshape((-1, 1))
    -theta = np.linalg.inv(X.T @ X) @ X.T @ Y
    -
    -fig, ax = plt.subplots(1, 1, figsize = (6, 2))
    -ax.scatter(heights[females], labels[females])
    -ax.scatter(heights[males], labels[males])
    -xrange = np.arange(np.min(heights), np.max(heights))
    -ax.plot(xrange, theta[0][0] * xrange + theta[1][0])
    -ax.set(xlabel = 'height, cm', ylabel = 'class')
    -ax.legend(['female', 'male'])
    -ax.grid()
    -
    -print(f'theta: {*theta.flatten(),}')
    -
    theta: (0.022082018927444665, -3.011041009463699)
    -

    png

    Now if we want to predict the gender of a person by its height we just multiply our feature vector with $\boldsymbol{\theta}$ and obtain the result: if it is greater than 0.5 then it is a male, if lower - female. Done! But if look at this problem more closely we fill figure that sometimes the “probability” can be negative, or bigger than one. It is counter-intuitive, since we want to stick to probability space. -Another problem is that linear regression is sensitive to imbalances in data, for example:

    Spoiler -
    heights = np.array([120, 121, 125, 135, 160, 161, 162, 163, 164, 165, 166, 167, 168, 170, 172, 175, 180])
    -labels = np.array([0, 0, 0, 0, 0, 0., 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
    -females = labels == 0
    -males = labels == 1
    -
    -X = np.hstack((heights.reshape((-1, 1)), np.ones((len(heights), 1))))
    -Y = labels.reshape((-1, 1))
    -theta = np.linalg.inv(X.T @ X) @ X.T @ Y
    -
    -fig, ax = plt.subplots(1, 1, figsize = (6, 2))
    -ax.scatter(heights[females], labels[females])
    -ax.scatter(heights[males], labels[males])
    -xrange = np.arange(np.min(heights), np.max(heights))
    -ax.plot(xrange, theta[0][0] * xrange + theta[1][0])
    -ax.set(xlabel = 'height, cm', ylabel = 'class')
    -ax.legend(['female', 'male'], loc = 'upper left')
    -ax.grid()
    -
    -print(f'theta: {*theta.flatten(),}')
    -
    theta: (0.013266157882184739, -1.7925709515859998)
    -

    png

    Now we can see that the classification is clearly wrong for males under 175cm.

    Logistic regression

    Can we find a better function that approximates our data? Let’s have a look at the sigmoid function and plot its graph. -$$ -\sigma(z) = \frac{1}{1 + e^{-z}} \in [0, 1]. -$$

    Spoiler -
    def sigmoid(z):
    -    return 1 / (1. + np.exp(-z))
    -
    -fig, ax = plt.subplots(figsize = (6, 2))
    -inputs = np.linspace(-10, 10, 100)
    -ax.plot(inputs, sigmoid(inputs))
    -ax.grid()
    -ax.set(xlabel='x', ylabel='$\sigma(x)$')
    -

    png

    The sigmoid takes an input $z \in \mathcal{R}$ and maps it into the range $(0, 1)$. Since we have only two classes we can use Bernoulli distribution. -The likelihood of the data can be written as: -$$ -\mathcal{L} = p(\mathbf{Y}| \boldsymbol{\theta}, \mathbf{X}) = \prod_{i=0}^{N-1} \sigma\left(\hat{y}\right)^y \left(1 - \sigma\left(\hat{y}\right)\right)^{1-y},~~~\hat{y}=\boldsymbol{\theta}^T\boldsymbol{x}. -$$

    Taking the logarithm, we obtain: -$$ -\mathcal{LL} = -\sum_{i=0}^{N-1}\left(y\log\sigma\left(\hat{y}\right) + \left(1 - y\right)\log(1 - \sigma\left(\hat{y}\right) \right) -$$

    That is the function we want to minimize. Let’s look at the penalties that generates the aforementioned log-likelihood. To do so we plot two graphs: one for the case when the true label is $0$ and the other for the case when the true label is $1$. If our prediction is close to the correct label the penalization term approaches zero, if the prediction is completely wrong the penalization term has very high value. That is desirable behaviour for the classification problem.

    Spoiler -
    def ll(label, prediction):
    -    return - label * np.log(prediction) - (1 - label) * np.log(1 - prediction)
    -
    -fig, ax = plt.subplots(figsize = (6, 2))
    -ax.plot(sigmoid(inputs), ll(1, sigmoid(inputs)), label = '$y = 1$')
    -ax.plot(sigmoid(inputs), ll(0, sigmoid(inputs)), label = '$y = 0$')
    -ax.grid()
    -ax.legend()
    -ax.set(xlabel = 'Probability', ylabel = 'Penalization term')
    -

    png

    Writing the log likelihood $\mathcal{LL}$ in matrix notation: -$$ -\begin{align*} -\mathcal{LL} = -\mathbf{Y}^T\log\sigma\left( \mathbf{X}\boldsymbol{\theta} \right) - (\mathbf{1}^T - \mathbf{Y}^T)\log\left(\mathbf{1} - \sigma\left( \mathbf{X}\boldsymbol{\theta} \right) \right) \\ -\end{align*} -$$ -The derivative w.r.t. $\boldsymbol{\theta}$ can be found to be 1: -$$ -\frac{\partial\mathcal{LL}}{\partial\boldsymbol{\theta}} = \frac{1}{N}\mathbf{X}^T\left( \sigma(\mathbf{x}\boldsymbol{\theta}) - \mathbf{Y} \right) -$$

    Since there is no closed form solution we have to use one of the numerical optimization techniques. Let’s use the gradient descent. I also tried the Newton method, but wasn’t able to produce robust results for all tested cases.

    def logistic_regression_gradient_descent(
    -    X, Y, step_size = 1e-4, iters = 1000000
    -):
    -    theta = np.zeros((X.shape[1], 1))
    -    for i in range(iters):
    -        theta = theta -  step * X.T @ (sigmoid(X @ theta) - Y)
    -    return theta
    -
    -Y = labels.reshape((-1, 1))
    -X = np.hstack((heights.reshape((-1, 1)), np.ones((len(Y), 1))))
    -theta = logistic_regression_gradient_descent(X, Y, step_size = 1e-2)
    -print(f'theta: {*theta.flatten(),}')
    -
    theta: (17.057175819704746, -2857.0739642526546)
    -

    And let’s look how our function approximates the probability of a point to be related to one or another class:

    Spoiler -
    inputs = np.arange(100, 200)
    -outputs = sigmoid(np.hstack((inputs.reshape((-1, 1)), np.ones((len(inputs), 1)))) @ theta)
    -
    -fig, ax = plt.subplots(1, 1, figsize = (6, 2))
    -ax.scatter(heights[females], labels[females])
    -ax.scatter(heights[males], labels[males])
    -xrange = np.arange(np.min(heights), np.max(heights))
    -ax.plot(inputs, outputs)
    -ax.set(xlabel = 'height, cm', ylabel = 'class')
    -ax.legend(['female', 'male'])
    -ax.grid()
    -

    png

    Much better now!

    Logarithm roots

    The key thing in logistic regression is the sequence of operation:

    1. converting the probability to odds ratio (the range $[0, 1]$ of the distribution function of a PDF is being mapped to range $(0, \infty)$;
    2. converting odds to log-odds (maps $(0, \infty)$ range to $(-\infty, \infty)$;
    3. the last step allows us to use regression on unconstrained space: -$$ -\log \frac{p_i}{1-p_i} = \boldsymbol{\theta}^T\mathbf{x_i} -$$

    After these transforms we have a linear model for the logistic regression.

    Of course, we can go vice versa now and get the sigmoid function that we use during the optimization and mapping between domains: -$$ -\begin{align*} -\log \frac{p}{1-p} &= \boldsymbol{\theta}^T\mathbf{x} \\ -\frac{p_i}{1-p} &= e^{~\boldsymbol{\theta}^T\mathbf{x}} \\ -p &= \frac{e^{~\boldsymbol{\theta}^T\mathbf{x}}}{1 + e^{~\boldsymbol{\theta}^T\mathbf{x}}} \\ -p &= \frac{1}{1 + e^{-\boldsymbol{\theta}^T\mathbf{x}}} -\end{align*} -$$

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/nn-training.md/index.html b/posts/nn-training.md/index.html deleted file mode 100644 index bd5904a..0000000 --- a/posts/nn-training.md/index.html +++ /dev/null @@ -1,185 +0,0 @@ -Understanding deep learning: training, SGD, code samples · Aleksandr Mikoff's blog -

    Understanding deep learning: training, SGD, code samples

    Recently, I have been reading a new [book]1 by S. Prince titled “Understanding Deep Learning.” While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms.

    This post is:

    1. a collection of keynotes from the first seven chapters of the book, that I have found useful for myself;
    2. the numpy-only implementation of deep neural network with variable layers size and training using SGD.

    General terms

    The Universal Approximation Theorem proves that for any continuous function, there exists a network that can approximate this function to any specified precision.

    Shallow Neural Networks (SNN) approximate the required function using piecewise linear functions of the input.

    The Dimensionality Curse is a well-known problem for Particle Filters. In neural networks, this problem is also known: as the input dimensions grow, the number of linear regions increases rapidly, leading to the need for a larger number of parameters in the neural network to obtain a good approximation of the underlying function.

    Layers

    Neural networks are often described in terms of layers:

    1. input layer;
    2. hidden layer;
    3. output layer.

    When data is passed through the neural network, the values that are fed to the hidden layer are termed pre-activations (e.g., before ReLU is applied). The values after the hidden layer, or after ReLU is applied, are called activations.

    Here is what the [ReLU]2 layer does for 1D-function using SNN:

    1. Generates $D$ linear functions, that are pre-activations.
    2. For each pre-activation, ReLU clamps the input, so its range becomes $[0, +\inf)$; these are the activations.
    3. Then each activation is multiplied by a number, and all of them are summed up with the bias, which controls the height.

    Deep Neural networks

    Since the approximation power of SNN depends on the number of hidden units, for some high-dimensional or complex functions, the required number of hidden units is impractically large. Deep networks produce many more linear regions for the same number of parameters and, as a result, have better descriptive power. For example, the number of linear regions for an SNN with 6 hidden units is equal to 7, but a Deep Neural Network (DNN) consisting of two layers with 3 hidden units each creates 9 linear regions.

    Note: Deep networks can create an extremely large number of linear regions, but these contain complex dependencies and symmetries.

    Hyperparameters

    • width - the number of hidden units in each layer ($D$);
    • depth - the number of hidden layers ($K$);
    • capacity - the total number of hidden units.

    Matrix notation

    $$ -\begin{split} -\mathbf{h}_1 &= \mathbf{a}[\boldsymbol{\beta}_0 + \boldsymbol{\Omega}_0 \mathbf{x}] \\ -\mathbf{h}_2 &= \mathbf{a}[\boldsymbol{\beta}_1 + \boldsymbol{\Omega}_1 \mathbf{h}_1] \\ -&~~\vdots \\ -\mathbf{h}_{K} &= \mathbf{a}[\boldsymbol{\beta}_{K-1} + \boldsymbol{\Omega}_{K-1} \mathbf{h}_{K-1}] \\ -\mathbf{y} &= \boldsymbol{\beta}_K + \boldsymbol{\Omega}_K \mathbf{h}_{K} -\end{split} -$$ -where $\mathbf{x}$ is the input vector, $\boldsymbol{\beta}$ - bias vector, which size is equal to $D_k$, $\boldsymbol{\Omega}$ - weight matrix.

    One general equation for the whole neural network is: -$$ -\mathbf{y} = f[\mathbf{x}, \boldsymbol{\phi}] , -$$ -where $\boldsymbol{\phi}$ are the parameters of the model, comprising all the weight matrices and bias vectors $\boldsymbol{\phi} = {\boldsymbol{\beta}_k, \boldsymbol{\Omega}_k}_{k=0}^K$.

    Fitting

    There are two most popular options for NN training:

    • Stochastic Gradient Descent (SGD) adds randomness at any given iteration. The mechanism is simple: the algorithm chooses a random subset of data, known as a minibatch, and computes the gradient from this training subset. The batches are usually drawn without replacement. An alternative interpretation of SGD is that it computes the gradient of a different loss function at each iteration; the loss function depends on both the model and the training data and hence will differ for each randomly selected batch. In this view, SGD performs deterministic gradient descent on a constantly changing loss function.

    • ADAM, adaptive moment estimation, applies moments, or smoothing, both to the estimated gradient and normalizing term (that is the squared gradient).

    Initialization

    Usually the biases are initialized to zero, and the weights as normally distributed with zero mean and variance $\sigma_\Omega^2$. -He initialization allows to have the variance of the subsequent pre-activations to be the same as variance of the original pre-activations. To achieve this the variance should be defined as: -$$ -\sigma_\Omega^2 = \frac{2}{D_h}, -$$ -$D_h$ is the dimension of the original layer. The details can be found [there]3.

    Code example

    Let’s consider the following neural network $\mathbf{f}[\mathbf{x}, \boldsymbol{\phi}]$ with input $\mathbf{x}$, parameters $\boldsymbol{\phi}$ and two hidden layers $\mathbf{h}_1, \mathbf{h}_2$:

    nn-1

    The model parameters $\boldsymbol{\phi} = {\boldsymbol{\beta}_0, \boldsymbol{\Omega}_0, \boldsymbol{\beta}_1, \boldsymbol{\Omega}_1 , \boldsymbol{\beta}_2, \boldsymbol{\Omega}_2 }$.

    Computing derivatives

    The derivative tells us how the loss changes if we make a small change to the parameters. Optimization algorithms use this information to update the parameters so that the loss becomes smaller.

    Each layer involves the multiplication of the weights and activations from the previous layer. So, any change to the weight affects the activation at the hidden unit. During the forward pass, the activations are stored to compute the gradients later.

    Once all the data have moved forward, we can calculate the loss and roll this loss back to each and every parameter in our network. This step is known as the backward pass.

    In the following chart, you can see the forward and backward passes for our example neural network. First, we sequentially calculate the actual values of $\mathbf{f}_k$ and $\mathbf{h}_k$, obtaining the loss value at the end.

    nn-2

    In our example let’s use the least squares loss: $\mathbf{L}(\mathbf{f}_o, \mathbf{y}_i) = (\mathbf{f}_o - \mathbf{y}_i)^2$, whose derivative is well known. -Having the numerical value of the derivative of the loss, we can roll it back to all the parameters using the chain rule. For example:

    $$ -\frac{\partial \mathcal{L}}{\partial\mathbf{h}_1} = \frac{\partial \mathbf{f}_1}{\partial\mathbf{h}_1} \left( \frac{\partial \mathbf{h}_2}{\partial\mathbf{f}_1} \frac{\partial \mathbf{f}_2}{\partial\mathbf{h}_2} \frac{\partial \mathcal{L}}{\partial\mathbf{f}_2} \right). -$$ -Please note that since matrix multiplication is not commutative, this is the only correct order. The term in brackets is computed in the previous step and can be directly reused.

    Actual code

    Now, let’s define our neural network layers and the general neural network class:

    Spoiler -
    import numpy as np
    -
    -class HiddenLayer:
    -    def __init__(self, n_units, input_size, learning_rate):
    -        self.omega = np.random.normal(0, np.sqrt(2/(n_units)), size=(n_units, input_size))
    -        self.beta = np.zeros((n_units, 1))
    -        self.alpha = learning_rate
    -
    -    def forward(self, input):
    -        self.h = np.clip(input, 0, None)
    -        self.f = self.beta + self.omega @ self.h
    -        return self.f
    -
    -    def backward(self, dloss):
    -        self.df_dbeta = np.eye(len(self.beta))
    -        self.df_domega = np.copy(self.h.T)
    -        self.df_dh = np.copy(self.omega.T)
    -
    -        self.dloss_dbeta = self.df_dbeta @ dloss
    -        self.dloss_domega = dloss @ self.df_domega
    -        self.dloss_dh = self.df_dh @ dloss
    -
    -        self.omega -= self.alpha * self.dloss_domega
    -        self.beta -= self.alpha * self.dloss_dbeta
    -
    -class InputLayer:
    -    def __init__(self, n_units, input_size, learning_rate):
    -        self.omega = np.random.normal(0, np.sqrt(2/(n_units)), size=(n_units, input_size))
    -        self.beta = np.zeros((n_units, 1))
    -        self.alpha = learning_rate
    -
    -    def forward(self, input):
    -        self.input = np.asarray(input).reshape((-1, 1))
    -        self.f = self.beta + self.omega @ self.input
    -        return self.f
    -
    -    def backward(self, dloss):
    -        self.df_dbeta = np.eye(len(self.beta))
    -        self.df_domega = np.copy(self.input.T)
    -
    -        self.dloss_dbeta = self.df_dbeta @ dloss
    -        self.dloss_domega = dloss @ self.df_domega
    -
    -        self.omega -= self.alpha * self.dloss_domega
    -        self.beta -= self.alpha * self.dloss_dbeta
    -
    -class NN:
    -    def __init__(self, input_size, layers_sizes, output_size, learning_rate):
    -        self.input_layer = InputLayer(layers_sizes[0], input_size, learning_rate)
    -        self.hidden_layers = []
    -        for l_input, l_output in zip(layers_sizes[0:], layers_sizes[1:]):
    -            self.hidden_layers.append(HiddenLayer(l_output, l_input, learning_rate))
    -        self.output_layer = HiddenLayer(output_size, layers_sizes[-1], learning_rate)
    -
    -    def forward(self, input):
    -        o = self.input_layer.forward(input)
    -        for l in self.hidden_layers:
    -            o = l.forward(o)
    -        o = self.output_layer.forward(o)
    -        return o
    -
    -    def backward(self, predicted_value, true_value):
    -        dloss = 2 * (predicted_value - true_value)
    -        self.output_layer.backward(dloss)
    -        dfprev_dhprev = self.output_layer.df_dh
    -        
    -        for l in self.hidden_layers[::-1]:
    -            dhprev_df = np.diag((l.f > 0).flatten()).astype(int)
    -            dloss = dhprev_df @ dfprev_dhprev @ dloss
    -            l.backward(dloss)
    -            dfprev_dhprev = l.df_dh
    -
    -        dhprev_df = np.diag((self.input_layer.f > 0).flatten()).astype(int)
    -        dloss = dhprev_df @ dfprev_dhprev @ dloss
    -        self.input_layer.backward(dloss)
    -
    -    def train(self, input, true_value):
    -        p = self.forward(input)
    -        self.backward(p, true_value)
    -

    It’s the time to define our first function $f(x) = \sin(10 x) + 0.75$ and train our NN to approximate it:

    Spoiler -
    import numpy as np
    -import matplotlib.pylab as plt
    -%matplotlib inline
    -%config InlineBackend.figure_format='retina'
    -
    -# our function that will be approximated by our NN
    -f = lambda x: np.sin(x * 10) + 0.75
    -
    -# NN structure - 3 layers, each having 12 units, input and output are 1d
    -learning_rate = 0.01
    -nn = NN(1, (24, 24, 24), 1, learning_rate)
    -
    -# generate 10000 samples and train NN
    -for b in range(0, 10000):
    -    x = np.random.uniform(-1, 1, size = (1, 1))
    -    y = f(*x)
    -    nn.train(x, y)
    -
    -# now let's get true values and predictions for some range
    -x = np.linspace(-1, 1, 100)
    -y_predict = np.zeros_like(x)
    -for i, xx in enumerate(x):
    -    y_predict[i] = nn.forward(xx)
    -
    -# visualize 
    -fig, ax = plt.subplots(1, 1, figsize = (6, 3))
    -ax.plot(x, f(x), label = "True function")
    -ax.plot(x, y_predict, label = "NN approximation")
    -ax.set(xlabel='$x$', ylabel='$y$')
    -ax.grid()
    -ax.legend(loc='upper left')
    -

    png

    Let’s do the same for the second function, which is 2d:

    Spoiler -
    # our function that will be approximated by our NN
    -f = lambda x, y: 25 * x + 17.8 * y + 0.25 * x * y + x * x * 15.0 - y * y * 5.0 - 25 * (x < 0.0 and y < 0.0)
    -
    -# NN structure - 2 layers, each having 12 units, input is 2d, output is 1d
    -learning_rate = 0.001
    -nn = NN(2, (12, 24), 1, learning_rate)
    -
    -# generate 10000 samples and train NN
    -for b in range(0, 10000):
    -    x = np.random.uniform(-1, 1, size = (2, 1))
    -    y = f(*x)
    -    nn.train(x, y)
    -
    -# now let's get true values and predictions for some range
    -x, y = np.arange(-1, 1, 0.05), np.arange(-1, 1, 0.05)
    -y_predict = np.zeros((x.shape[0], y.shape[0]))
    -y_true = np.zeros((x.shape[0], y.shape[0]))
    -for i, xx in enumerate(x):
    -    for j, yy in enumerate(y):
    -        y_predict[i, j] = nn.forward([xx, yy])
    -        y_true[i, j] = f(xx, yy)
    -
    -# visualize 
    -fig, ax = plt.subplots(1, 2, figsize = (6, 3))
    -ax[0].imshow(y_true, extent=[x.min(), x.max(), y.min(), y.max()], origin="lower", cmap='cool', vmin=np.min(y_true), vmax=np.max(y_true))
    -ax[0].set(xlabel='$x$', ylabel='$y$')
    -ax[0].set_title("True function")
    -ax[1].imshow(y_predict, extent=[x.min(), x.max(), y.min(), y.max()], origin="lower", cmap='cool', vmin=np.min(y_true), vmax=np.max(y_true))
    -ax[1].set(xlabel='$x$', ylabel='$y$')
    -ax[1].set_title("NN approximation")
    -fig.tight_layout()
    -

    png

    Bingo!

    The great video on how neural networks learn to approximate any function is 4.

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/nonlinear-estimation-mle-map.md/index.html b/posts/nonlinear-estimation-mle-map.md/index.html deleted file mode 100644 index 10c806a..0000000 --- a/posts/nonlinear-estimation-mle-map.md/index.html +++ /dev/null @@ -1,170 +0,0 @@ -Nonlinear estimation: Full Bayesian, MLE and MAP · Aleksandr Mikoff's blog -

    Nonlinear estimation: Full Bayesian, MLE and MAP

    Intro

    Recently I have read “State Estimation for Robotics” book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data.

    Distance from stereo-images

    The camera image is a projection of the world on the image plane. -The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. -$$disparity = x_{left} - x_{right}$$

    svg

    Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.

    To do so, let’s draw a simple scheme of two pinholes cameras’ setup and derive the equation for depth, or forward distance, between the camera and the landmark.

    svg

    On the preceding image $l$ is the landmark, $(x, z)$ is the landmark position w.r.t. the left camera $c_{left}$ (meters). We want to find $z$, depth of the landmark. By design, the focal length $f$ (in pixels) and baseline $b$, which is the horizontal distance between left and right cameras (in meters) are known.

    From the similar triangles (marked with arcs) and under assumption that the optical axes of the cameras are parallel we have (for simplicty $x_{left} = x_{l}, x_{right} = x_r$): -$$\frac{z}{f} = \frac{x}{x_{l}} = \frac{x-b}{x_{r}}.$$ -Now, expressing $x$ in terms of $x_{l}$ and $x_{r}$ gives: -$$x x_{r} = (x - b) x_{l}$$ -$$x (x_{r} - x_{l}) = -b x_{l}$$ -$$x = \frac{b x_{l}}{x_{l} - x_{r}}$$

    Substituting it into previous equation: -$$\frac{z}{f} = \frac{b}{x_r - x_l}$$ -we can derive the equation for depth, or $z$ as: -$$z = \frac{f b}{x_l - x_r}.$$

    Bayesian model

    From the depth equation the measurement model for disparity $disparity = x_l - x_r$ can be written as: -$$disparity = x_l - x_r = y = \frac{f b}{z} + n,$$ -where the state $z$ is the depth of the landmark (meters) and $n$ is the measurement noise (in pixels), all other terms were defined earlier. Despite of being simple it is a nonlinear model: $f(x_1 + x_2) \neq f(x_1) + f(x_2)$.

    This fact can be visualized by passing the state through our measurement function. After applying this transform evenly spaced samples from our state space become unevenly spaced:

    fig, ax = plt.subplots(1, 1, figsize = (10, 3))
    -
    -# depth
    -z_space = np.linspace(1e-8, 40, 100)
    -# corresponding disparity measurements
    -
    -measurement_space = 400 * 0.1 / z_space
    -ax.vlines(z_space, 0, 0.2, colors='k', label = "state samples, $z$", lw = 0.5)
    -ax.vlines(measurement_space, 0, 0.2, colors='green', label = "measurement samples, $y$", lw = 0.5)
    -ax.set_xlim(0, 40)
    -ax.set_ylim(-0.2, 0.4)
    -ax.legend()
    -

    png

    The Bayesian update is defined as: -$$p(z|y) = \eta~p(y|z)~p(z),$$ -where $\eta = \frac{1}{p(y)} = \frac{1}{\int p(y|z)~p(z)~dz}$ is the normalizer.

    To perform such update, the probabilities of $p(y|z)$ and $p(z)$ must be known. Our choice is to define the measurement noise as zero-mean Gaussian $n \sim N(0, R)$. Thus: -$$p(y|z) = N\left(\frac{f b}{z}, R\right) = \frac{1}{\sqrt{2 \pi R}} exp\left(-\frac{1}{2 R} \left(y - \frac{f b}{z}\right)^2\right).$$ -We also asssume that the prior is Gaussian: -$$p(z) = N(z^{-}, P^{-}) = \frac{1}{\sqrt{2 \pi P^{-}}} exp\left(-\frac{1}{2 P^{-}} (z - z^{-})^2\right).$$

    The Bayesian framework consists of the following operations:

    1. Get the prior $p(z)$ over the state space $z$, given the current estimate $z^{-}$.
    2. Obtain the measurement $y$.
    3. Calculate the measurement likelihood using $p(y|z)$.
    4. Calculate the posterior $p(z|y)$ using Bayes rule.

    Visualization

    To illustrate how this nonlinearity affects the pdf of the posterior it is possible to perform numerical experiments. For such an experiment, the selected parameters are the following: -$$z^{-} = 20~[m],~P^{-} = 9~[m^2],~f = 400~[pixel],~b = 0.1~[m],~R = 0.09~[pixel^2].$$

    The parameters for posterior calculation: -$$z_{true} = 22~[m],~y_{meas} = \frac{f b}{z_{true}} + 1~[pixel].$$

    # prior parameters
    -z_prior = 20
    -P_prior = 9
    -f = 400
    -b = 0.1
    -R = 0.09
    -
    -# measurement parameters
    -z_true = 22
    -noise = 1.0
    -y_meas = f * b / z_true + noise
    -
    -fig, ax = plt.subplots(1, 1, figsize = (15, 4))
    -
    -# prior
    -z_space = np.linspace(1e-8, 40, 10000)
    -priors = stats.norm.pdf(z_space, loc = z_prior, scale = np.sqrt(P_prior))
    -priors = priors / priors.sum()
    -ax.plot(z_space, priors, 'r-', label = "prior")
    -
    -# likelihood
    -measurement_space = 400 * 0.1 / z_space
    -likelihoods = stats.norm.pdf(measurement_space, loc = y_meas, scale = np.sqrt(R))
    -likelihoods = likelihoods / likelihoods.sum()
    -ax.plot(z_space, likelihoods, 'g--', label = "likelihood")
    -
    -# posterior
    -posteriors = priors * likelihoods
    -posteriors = posteriors / np.sum(posteriors)
    -ax.plot(z_space, posteriors, 'b-.', label = "posterior")
    -
    -ymin, ymax = ax.get_ylim()
    -ax.vlines([z_true], ymin, ymax, colors = 'magenta', linestyles = 'dotted', label = '$z_{true}$')
    -
    -zFromMeas = f * b / y_meas
    -ax.vlines([zFromMeas], ymin, ymax, colors = 'brown', linestyles = 'dotted', label = '$g^{-1}(y_{meas})$')
    -
    -maxPosterior = np.argmax(posteriors)
    -ax.vlines([z_space[maxPosterior]], ymin, ymax, colors = 'blue', linestyles = 'dotted', label = '$z^{+}$')
    -
    -ax.legend()
    -

    png

    The maximum likelihood estimation (MLE) can be utilized when we want to use only the measurements and have no prior information about possible system state. It helps to find the answer to the following question: which value of $z$ is most probable given the measurement $y$?

    However, if we know the prior distribution of our parameter of interest, it is generally a good idea to take it into account. The incorporation of the measurement results in more concentrated (less uncertainty) posterior about the state than the prior.

    We can find the most probable posterior value for this concrete example by visual inspection of the posterior PDF. However, it is not the case for most real-life problems. To overcome this problem the maximum a posteriori (MAP) approach can be utilized. It is used to find the peak of the posterior.

    Maximum Likelihood estimation

    Talking about the maximum likelihood estimation we seek for a value that maximizes the likelihood of our measurement, or data, given the state $z$: -$$\hat{x}_{mle} = \underset{x}{arg~max}~p(y|x),$$ -going to the $log$ space: -$$\hat{x}_{mle} = \underset{x}{arg~min}~(-ln(p(y|x))).$$ -Note: if we want to find MLE for a number of measurements, or samples under assumption that they are undepended, we replace $p(y|x)$ by the product of individual samples probabilities: $\underset{i}{\prod} p(y_i|x)$. This leads to the following $\hat{x}_{mle}$: -$$\hat{x}_{mle} = \underset{x}{arg~min}~(-\underset{i}{\sum}ln(p(y_i|x))).$$

    For stereo-camera depth estimate: -$$\hat{z}_{mle} = \underset{z}{arg~min}~J_{mle}(z),$$ -where $J_{mle}(z)$ can be found as: -$$J_{mle}(z) = \frac{1}{2 R} \left(y - \frac{f b}{z}\right)^2,$$ -after dropping the constants that do not depend on $x$.

    Maximum a Posteriori estimation

    When we are talking about maximum a posteriori estimation we assume that we want to find the single state value $x$ that maximizes the true posterior: -$$\hat{x}_{map} = \underset{x}{arg~max}~p(x|y),$$ -or, alternatively, because $log$ is the monotolically increasing function: -$$\hat{x}_{map} = \underset{x}{arg~min}~(-ln(p(x|y))).$$ -Applying the Bayes rule we have: -$$\hat{x}_{map} = \underset{x}{arg~min}~(-ln(p(y|x)) - ln(p(x))),$$ -where $p(y)$ is being dropped because it does not depend on $x$. Again, if we have a number of measurements $y_i$, we have to add their product that leads to the sum after switching to the $log$ space.

    In the stereo-camera depth estimate we can write: -$$\hat{z}_{map} = \underset{z}{arg~min}~J_{map}(z),$$ -where $J(z)$ is obtained by dropping the constants that do not depend on $x$: -$$J_{map}(z) = \frac{1}{2 R} \left(y - \frac{f b}{z}\right)^2 + \frac{1}{2 P^{-}} \left(z^{-} - z\right)^2$$

    Both MLE and MAP estimates can be found using a number of numerical optimization techniques by minimizing our target cost functions $J_{mle}$ or $J_{map}$.

    Let’s go back and find the MLE and MAP estimates of $z$:

    from scipy.optimize import minimize
    -
    -Jz_mle = lambda z: 1.0 / (2.0 * R) * (y_meas - f * b / z) ** 2
    -
    -ml_estimate = minimize(Jz_mle, 1e-8).x[0]
    -
    -print("ML estimate:", ml_estimate)
    -
    -Jz_map = lambda z: 1.0 / (2.0 * R) * (y_meas - f * b / z) ** 2 + \
    -        1.0 / (2.0 * P_prior) * (z_prior - z) ** 2
    -
    -map_estimate = minimize(Jz_map, 1e-8).x[0]
    -print("MAP estimate: ", map_estimate)
    -
    -ax.vlines([ml_estimate], ymin, ymax, colors = 'green', linestyles = '-', label = '$z_{ml}$')
    -ax.legend()
    -
    -ax.vlines([map_estimate], ymin, ymax, colors = 'blue', linestyles = '-', label = '$z_{map}$')
    -ax.legend()
    -
    -IPython.display.display(fig)
    -
    ML estimate: 14.19354712952584
    -MAP estimate:  15.671431302257346
    -

    png

    To test whether or not the estimators find the true value of $z$ the simulations can be performed, estimating the residual between true and estimated states.

    results = {'true_state' : [], 'measurement' : [], "map_estimate" : [], "mle_estimate" : []}
    -
    -for i in range(0, 10000):
    -    z_true = np.random.normal(z_prior, np.sqrt(P_prior))
    -    y_meas = np.random.normal(f * b / z_true, np.sqrt(R))
    -    
    -    Jz_mle = lambda z: 1.0 / (2.0 * R) * (y_meas - f * b / z) ** 2
    -    mle_estimate = minimize(Jz_mle, 1e-8).x[0]
    -    
    -    Jz_map = lambda z: 1.0 / (2.0 * R) * (y_meas - f * b / z) ** 2 + \
    -        1.0 / (2.0 * P_prior) * (z_prior - z) ** 2
    -    map_estimate = minimize(Jz_map, 1e-8).x[0]
    -
    -    results['true_state'].append(z_true)
    -    results['mle_estimate'].append(mle_estimate)
    -    results['map_estimate'].append(map_estimate)
    -    
    -true_results = np.array(results['true_state'])
    -mle_results = np.array(results['mle_estimate'])
    -map_results = np.array(results['map_estimate'])
    -
    -fig, ax = plt.subplots(1, 1, figsize = (15, 4))
    -ax.hist(mle_results, bins=50, alpha = 0.5, density = True, label = "MLE estimate");
    -ax.hist(map_results, bins=50, alpha = 0.5, density = True, label = "MAP estimate");
    -ax.legend()
    -
    -print("Mean error MAP and true state:", np.mean(map_results - true_results))
    -print("Variance of the error: ", np.mean((map_results  - true_results) ** 2))
    -
    -print("Mean error MLE and true state:", np.mean(mle_results - true_results))
    -print("Variance of the error: ", np.mean((mle_results  - true_results) ** 2))
    -
    Mean error MAP and true state: -0.3708958146195719
    -Variance of the error:  4.335754086406355
    -Mean error MLE and true state: 0.475211450081065
    -Variance of the error:  13.431400451884032
    -

    png

    When selecting between MLE and MAP estimators it is a good practice to use MAP when the prior is given or can be inferred from experiments or researcher’s intuition. Also, it is important to note that if the prior is a uniform distribution, MAP becomes an equivalent to MLE.

    Both MLE and MAP estimators are biased even for such vanilla example. Why it happens and what to do with that? -There is no straightforward answer: it depends on the problem. One of approaches that tackle this problem is the Bias-Variance decomposition.

    Jupyter notebook is available here.

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/notes-on-backpropagation/forward-backpropagation.drawio-old.svg b/posts/notes-on-backpropagation/forward-backpropagation.drawio-old.svg new file mode 100644 index 0000000..b375de7 --- /dev/null +++ b/posts/notes-on-backpropagation/forward-backpropagation.drawio-old.svg @@ -0,0 +1,4 @@ + + + +
    x
    w_1
    \time...
    w_0
    +
    y
    -
    (\cdo...
    7
    7
    3
    3
    2
    2
    -2
    -2
    6
    6
    4
    4
    -3
    -3
    9
    9
    1
    1
    -6
    -6
    6
    6
    -6
    -6
    -6
    -6
    -6
    -6
    -12
    -12
    -18
    -18
    d
    d
    c
    c
    b
    b
    a
    a
    \begin{a...
    \begin{al...
    \begin{a...
    \begin{a...
    \begin{a...
    Viewer does not support full SVG 1.1
    \ No newline at end of file diff --git a/posts/notes-on-backpropagation/index.html b/posts/notes-on-backpropagation/index.html deleted file mode 100644 index 177aa2e..0000000 --- a/posts/notes-on-backpropagation/index.html +++ /dev/null @@ -1,305 +0,0 @@ -Notes on backpropagation · Aleksandr Mikoff's blog -

    Notes on backpropagation

    Notes on backpropagation

    In optimization and machine learning applications the widely used tool for finding the model parameters is the gradient descent. It allows to find the maximum or minimum of the target function w.r.t. the parameters, in other words, to minimize the discrepancy between the model and the data.

    However, to use this method the gradient has to be computed. The first problem is that if our function has a complex form the process of differentiating is quite tricky. The second - gradient calculations might be computationally demanding, especially if we have a large number of parameters: we have to find the gradient w.r.t. each of them. To ease the calculations and improve its efficiency the backpropagation algorithm can be used. From algorithmic point of view it allows us to:

    • split the differentiation into the simple computational steps;
    • reuse the intermediate calculation results.

    Chain rule: example

    Let’s start from an example. We have the following function: -$$ -f(x, \boldsymbol{w}, y) = \left(w_0 + w_1 x - y\right)^2 -$$ -to minimize it, we need to follow the negative of the gradient (since the gradient shows us the direction of growth).

    To do so we have find its derivatives with respect to vector $\boldsymbol{w}$. For such a case it is possible to differentiate directly, but for educational purposes let’s utilize the chain rule to compute the gradient of $f$ w.r.t. $\boldsymbol{w}$. -To do so we first split the original expression $f$ to the elementary operations: -$$ -\begin{align} -&a(w_1, x) &&= w_1 x ,&& D_{w_1}a = \frac{\partial a}{\partial w_1} = x & D_{x}a=\frac{\partial a}{\partial x} = w_1\\ -&b(a, w_0) &&= a + w_0 ,&& D_{a}b = \frac{\partial b}{\partial a} = 1 & D_{w_0}b=\frac{\partial b}{\partial w_0} = 1\\ -&c(b, y) &&= b - y ,&& D_{b}c = \frac{\partial c}{\partial b} = 1 & D_{y}c=\frac{\partial c}{\partial y} = -1\\ -&d(c) &&= c^2 ,&& D_{c}d = \frac{\partial d}{\partial c} = 2c -\end{align} -$$ -and our target function can be written as the composition of elementary functions: -$$ -f(x, \boldsymbol{w}, y) = d\left(c\left(b\left(a\left(w_1, x\right), w_0\right), y\right)\right). -$$ -Their derivatives are known, and the chaining of the gradient expressions can be done using multiplication: -$$ -\frac{\partial f}{\partial w_1} = \frac{\partial f}{\partial d} \frac{\partial d}{\partial c} \frac{\partial c}{\partial b} \frac{\partial b}{\partial a} \frac{\partial a}{\partial w_1}, -$$ -noting that $d$ and $f$ are the same functions the $\frac{\partial f}{\partial d} = 1$ and expanding: -$$ -\frac{\partial f}{\partial w_1} = \frac{\partial d}{\partial c} \frac{\partial c}{\partial b} \frac{\partial b}{\partial a} \frac{\partial a}{\partial w_1} = 2 c x = 2 (b - y) x = 2 (w_0 + w_1x - y) x -$$

    Computational graph

    The visual representation of the computation sequence is shown on the following graph, where:

    • ➡️ the forward pass (🟢 - green color) computes the output values from the inputs;
    • ⬅️ the backward pass (🔴 - red color) uses the chain rule to compute the gradients, on the diagram we show the numeric values of the negative of the gradient.

    cg

    After completion of the forward pass we have the inputs and outputs for all the nodes (shown in green). Now what we want to compute is the gradient that shows the sensitivity of parameters ${w_0, w_1}$ on target function $f$. -Since the local gradients for every node are also known we can compute the gradient w.r.t. the target function by simple multiplication. -For example, if we want to compute $\frac{\partial f}{\partial c}$ we note that we know the input $c = -3$, the derivative of $c^2$, and $\frac{\partial f}{\partial d} = 1$. Chaining the results we obtain: -$$ -\frac{\partial f}{\partial c} = \frac{\partial f}{\partial d}\frac{\partial d}{\partial c} = 1 \cdot 2 \cdot c = 1 \cdot 2 \cdot 3 = -6. -$$ -After performing this step we know how much the change in node $c$ affects the node $d$: that is the information, given by the derivative. The gradient shows the direction in which a function grows faster, but during the optimization we want to step towards its minimum. To do so we move in the direction of negative gradient. Let’s look at numbers: what $d$ wants is to increase $c$ with a force of $6$ (we negate the numbers, because we are looking for minimum, right?). Does it make sense? Yes, our current value of $c$ is $-3$ and if it becomes higher, $d$ decreases. -If we continue this logic we note that $y$, for example, should decrease (since $-\frac{\partial f}{\partial y} = -6$) when other parameters are fixed: it also makes sense, because if $y$ increases it makes our target $f = (b - y)^2~|~b = 4$ higher and what we want to achieve is the opposite: we want to minimize $f$.

    The nice and short explanation of the gradient flow process is given in 1:

    To compute the gradient of some scalar $z$ with respect to one of its ancestors $x$ in the graph, we begin by observing that the gradient with respect to z is equal to 1. -We can compute the gradient with respect to each parent of $z$ in the graph by multiplying the current gradient by the Jacobian of the operation that produced $z$. We continue multiplying by Jacobians, traveling backward through the graph in this way until we reach $x$.

    The forward and backward passes for this case can be written in a few lines of code:

    x, y = 3, 7
    -w0, w1 = -2, 2
    -
    -# forward pass
    -a = x * w1
    -b = a + w0
    -c = b - y
    -d = c ** 2
    -
    -# backward pass
    -df_dc = 2 * c
    -df_db = df_dc
    -df_da = df_db
    -df_dw1 = df_da * x
    -df_dw0 = df_db
    -
    -print(f'df/dw0 = {df_dw0}, df/dw1 = {df_dw1}')
    -
    df/dw0 = -6, df/dw1 = -18
    -

    Note that even in such a simple case some intermediate results are reusable: we do not compute the results twice (for example, for $\frac{\partial f}{\partial c}$), we use the same cached value.

    Patterns

    According to 2, it is useful to keep in mind the interpretation of some operations on intuitive level:

    • add - takes the gradient and distributes it equally to all the inputs;
    • max - routes the gradient to the largest value of inputs (only one);
    • multiply - if we think about the multiplication as a constant term, then the gradients of the output are being split to the inputs inversely proportional to their values.

    Implementation

    Thinking about the actual implementation we note that to calculate the final gradients we need to:

    • correctly define the forward and backward walking order, it can be done using the graphs;
    • store the outputs of the nodes, because the local gradient depends on their values;
    • know the derivative of each node w.r.t. its inputs.

    First, let’s define the nodes that perform the calculations, store the results and can calculate the gradient of the output w.r.t. the input:

    Spoiler -
    from abc import ABC, abstractmethod
    -
    -class Operation(ABC):
    -    """Base class for node operations.
    -    
    -    On forward pass the inherited class calculates the output of the 
    -    node and nulls the local gradient for future calculations.
    -    
    -    """
    -    
    -    def __init__(self, inputs):
    -        self.inputs = inputs
    -        self.output = None
    -        self.local_grad = 0.
    -        
    -    def forward(self, *args):
    -        """Calculates the node output given inputs"""
    -        self.local_grad = 0.
    -        self._forward(*args)
    -        
    -    @abstractmethod
    -    def _forward(self, *inputs):
    -        pass
    -    
    -    @abstractmethod
    -    def backward(self, dz):
    -        """Returns the local gradient."""
    -        pass
    -
    -
    -class Multiply(Operation):
    -    def _forward(self, x, y):
    -        self.x = x
    -        self.y = y
    -        self.output = x * y
    -        return self.output
    -    
    -    def backward(self, dz):
    -        dx = self.y * dz
    -        dy = self.x * dz
    -        return [dx, dy]
    -
    -
    -class Add(Operation):
    -    def _forward(self, x, y):
    -        self.output = x + y
    -        return self.output
    -    
    -    def backward(self, dz):
    -        return [dz, dz]
    -
    -
    -class Sub(Operation):
    -    def _forward(self, x, y):
    -        self.output = x - y
    -        return self.output
    -    
    -    def backward(self, dz):
    -        return [dz, -dz]
    -
    -
    -class Square(Operation):
    -    def _forward(self, x):
    -        self.x = x
    -        self.output = x * x
    -        return self.output
    -    
    -    def backward(self, dz):
    -        return [2 * self.x * dz]
    -
    -
    -class Sigmoid(Operation):
    -    def _forward(self, x):
    -        from numpy import exp
    -        self.output = 1. / (1. + exp(-x))
    -        return self.output
    -    
    -    def backward(self, dz):
    -        return [(1 - self.output) * self.output * dz]
    -
    -
    -class Div(Operation):
    -    def _forward(self, a, b):
    -        self.a = a
    -        self.b = b
    -        self.output = a / b
    -        return self.output
    -    
    -    def backward(self, dz):
    -        return [1 / self.b * dz, - self.a / self.b**2 * dz]
    -
    -
    -class Inv(Operation):
    -    def _forward(self, a):
    -        self.a = a
    -        self.output = 1 / a
    -        return self.output
    -    def backward(self, dz):
    -        return [-1. / self.a**2 * dz]
    -    
    -class Input(object):
    -    """Class for input values"""
    -    def __init__(self, value):
    -        self.output = value
    -        self.local_grad = 0.0
    -

    Now let’s define the ComputationalGraph class, that is responsible for chaining and calculating the forward and backward pass for the graph:

    Spoiler -
    class ComputationalGraph:
    -    """Computational graph: stores computing nodes, gradients and inputs.
    -    
    -    The computational graph is used to calculate the output of each node
    -    on forward pass and to calculate the local gradients of each node with
    -    respect to the node inputs.
    -    """
    -    
    -    def __init__(self):
    -        self.operations = {}
    -        self.inputs = {}
    -        self.nodes = {}
    -        
    -    def add_inputs(self, **kwargs):
    -        for name, value in kwargs.items():
    -            self.inputs[name] = Input(value)
    -            self.nodes[name] = self.inputs[name]
    -            
    -    def add_computation_node(self, name, operation, inputs):
    -        self.operations[name] = operation(inputs)
    -        self.nodes[name] = self.operations[name]
    -        
    -    def _get_traverse_order(self, root):
    -        route = []
    -        queue = [root]
    -        while len(queue):
    -            prev = queue.pop(0)
    -            if prev not in self.inputs:
    -                route.append(prev)
    -            if prev in self.operations:
    -                queue.extend(self.operations[prev].inputs)
    -        return route
    -    
    -    def forward(self, root):
    -        for name in reversed(self._get_traverse_order(root)):
    -            node = self.nodes[name]
    -            node.forward(*[self.nodes[i].output for i in node.inputs])
    -            
    -        for inp in self.inputs:
    -            self.inputs[inp].local_grad = 0.0
    -            
    -    def backward(self, root):
    -        self.operations[root].local_grad = 1.
    -        for name in self._get_traverse_order(root):
    -            local_grad = self.nodes[name].local_grad
    -            inputs = self.nodes[name].inputs
    -            gradients_wrt_input = self.nodes[name].backward(local_grad)
    -            # roll back to inputs
    -            for parent, g in zip(inputs, gradients_wrt_input):
    -                self.nodes[parent].local_grad += g
    -                
    -    def get_gradient(self, node):
    -        if node in self.nodes:
    -            return self.nodes[node].local_grad
    -

    Now we can make the real job, let’s calculate the gradient of the following function w.r.t. its inputs, $x$ and $y$: -$$ -f(x, y) = \frac{x + \sigma(y)}{\sigma(x) + (x + y)^2}. -$$

    cg = ComputationalGraph()
    -cg.add_inputs(x = 2., y = -4.)
    -cg.add_computation_node('sigy', Sigmoid, ('y',))
    -cg.add_computation_node('num', Add, ('x', 'sigy'))
    -cg.add_computation_node('sigx', Sigmoid, ('x',))
    -cg.add_computation_node('xpy', Add, ('x', 'y'))
    -cg.add_computation_node('sq', Square, ('xpy',))
    -cg.add_computation_node('denom', Add, ('sigx', 'sq'))
    -cg.add_computation_node('f', Div, ('num', 'denom'))
    -
    -cg.forward('f')
    -cg.backward('f')
    -
    -dx = cg.get_gradient('x')
    -dy = cg.get_gradient('y')
    -print(f'df/dx = {dx}, df/dy = {dy}')
    -
    df/dx = 0.5348320870757595, df/dy = 0.342460382923052
    -

    And verify the result on an additional example that we studied in the beginning: -$$ -f(x, \boldsymbol{w}, y) = \left(w_0 + w_1 x - y\right)^2. -$$

    cg = ComputationalGraph()
    -cg.add_inputs(x = 3., y = 7., w0 = -2, w1 = 2)
    -cg.add_computation_node('w1_times_x', Multiply, ('w1', 'x'))
    -cg.add_computation_node('prediction', Add, ('w0', 'w1_times_x'))
    -cg.add_computation_node('l', Sub, ('prediction', 'y'))
    -cg.add_computation_node('f', Square, ('l',))
    -
    -cg.forward('f')
    -cg.backward('f')
    -
    -dw0 = cg.get_gradient('w0')
    -dw1 = cg.get_gradient('w1')
    -print(f'df/dw0 = {dw0}, df/dw1 = {dw1}')
    -
    df/dw0 = -6.0, df/dw1 = -18.0
    -

    Result is exactly the same. -As a final step let’s use this scheme to find the minimum of the following function: -$$ -f(x, y) = x^2 + y^2 + 400x + 8y -$$

    cg = ComputationalGraph()
    -cg.add_inputs(x = 0., y = 0., a = 400., b = 8.)
    -cg.add_computation_node('sqx', Square, ('x',))
    -cg.add_computation_node('sqy', Square, ('y',))
    -cg.add_computation_node('ax', Multiply, ('a', 'x'))
    -cg.add_computation_node('by', Multiply, ('b', 'y'))
    -cg.add_computation_node('sqxpsqy', Add, ('sqx', 'sqy'))
    -cg.add_computation_node('axpby', Add, ('ax', 'by'))
    -cg.add_computation_node('f', Add, ('sqxpsqy', 'axpby'))
    -
    -for i in range(0, 50):
    -    cg.forward('f')
    -    cg.backward('f')
    -
    -    dx = cg.get_gradient('x')
    -    dy = cg.get_gradient('y')
    -    cg.inputs['x'].output -= dx * 0.1
    -    cg.inputs['y'].output -= dy * 0.1
    -    
    -print('Minimum:', cg.inputs['x'].output, cg.inputs['y'].output)
    -
    Minimum: -199.9971455046146 -3.9999429100922916
    -

    All done! Minimum was correctly found, it is time to visual assess it.

    Spoiler -
    import matplotlib.pylab as plt
    -import numpy as np
    -%matplotlib inline
    -%config InlineBackend.figure_format='retina'
    -
    -x, y = np.meshgrid(np.arange(-800, 800), np.arange(-400, 400))
    -z = x**2 + y**2 + 400*x + 8*y 
    -fig, ax = plt.subplots(1, 1, figsize = (6, 3))
    -ax.imshow(z, extent=[x.min(), x.max(), y.min(), y.max()], origin="lower", cmap='cool')
    -ax.scatter([cg.inputs['x'].output], [cg.inputs['y'].output], c = 'red', label='Min')
    -ax.set(xlabel='$x$', ylabel='$y$')
    -ax.legend()
    -

    png

    The code is available here: -https://github.com/mikoff/blog_projects/tree/master/auto_differentation

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/optimization-on-manifold/index.html b/posts/optimization-on-manifold/index.html deleted file mode 100644 index 65d3e27..0000000 --- a/posts/optimization-on-manifold/index.html +++ /dev/null @@ -1,250 +0,0 @@ -Optimization on manifold · Aleksandr Mikoff's blog -

    Optimization on manifold

    Optimization on manifold

    In the following post I would like to summarize my perception of poses’ optimization problem. Such a problem often occurs in robotics and other related fields. Usually we want jointly optimize the poses, their increments and various measurements. What we want to find is such set of parameters, that minimize the sum of residuals, or differences, between the real measurements and measurements, that we derive from our state.

    Assume that we have the prior on pose of our object origin $b$ w.r.t. the world origin $w$ at time $i$ - $\mathbf{T}_{wb_i}$, we also know the prior on pose at time ${i+1}$ - $\mathbf{T}_{wb_{i+1}}$, and the relative pose between them $\mathbf{T}_{b_i b_{i+1}}$: -$$ -\mathbf{T}_{w b_{i+1}} = \mathbf{T}_{w b_{i}}\mathbf{T}_{b_{i}b_{i+1}}. -$$ -In such a case we have only two poses and one pose difference between them. Real-life examples are usually much more harder, and include hundreds of parameters. Nevertheless, even in such a simple case, how can we formulate it in a way that we can optimize?

    If we have two poses $\mathbf{T}_{wb_i}$ and $\mathbf{T}_{wb_{i+1}}$, we can define the measurement function $\mathbf{h}$ as their composition: -$$ -\mathbf{h}\left(\mathbf{T}_{wb_i}, \mathbf{T}_{wb_{i+1}}\right) = \hat{\mathbf{T}}_{b_{i}b_{i+1}} = \mathbf{T}_{wb_{i}}^{-1}\mathbf{T}_{wb_{i+1}} -$$

    Now, having an estimated and real measurements we can find their difference as: -$$ -\hat{\mathbf{T}}_{b_{i}b_{i+1}}^{-1}\mathbf{T}_{b_{i}b_{i+1}}. -$$

    To find the minimum we should find the derivative w.r.t. the minimization parameters (our matrices!), set it to zero and solve for the parameters. Of course, we could try to directly solve such a problem, but imagine a beast that comes out as a result such attempt. There should be a better way of doing this. -This way utilizes the following math concepts:

    1. Define residuals on the tangent space of the manifold.
    2. Use first-order approximation - it allows us to describe how the non-linear function behaves near the linearization point, and allows to deduce standard Jacobian blocks.
    3. Use the first-order approximation to rewrite the problem in standard linear form that allows to use standard Jacobians that depend only on the measurement function.

    Residuals minimization on Manifold

    Step 1

    We want to minimize the following cost function: -$$ -\left|\left|\log\left(\hat{\mathbf{T}}_{b_{i}b_{i+1}}^{-1}\mathbf{T}_{b_{i}b_{i+1}}\right)\right|\right|^2. -$$ -We can interpret this function intuitively:

    • by group definition if the elements $\hat{\mathbf{T}}$ and $\mathbf{T}$ are equal, then their composition $\hat{\mathbf{T}}^{-1}\mathbf{T}$ is the identity, if they differ, then the composition allows us to find this difference;
    • $\log$ operations maps the difference from the manifold to the tangent space, $\log: \mathcal{M} \to \mathcal{R}^3$, that is the vector space;
    • norm operator measures the size of our difference, since we use $\mathcal{l}^2$ norm, we can easily find the derivative.

    Step 2

    The solution of the problem $\mathbf{L} = \arg\underset{x}{\min}||\mathbf{A}\mathbf{x} - \mathbf{b}||^2$ is well-known and can be derived quite easily: -$$ -\mathbf{x} = (\mathbf{A}^T\mathbf{A})^{-1}\mathbf{A}^T\mathbf{b}. -$$

    The aforementioned cost function expression, however, has the different form. But let’s use first-order approximation for the measurement function $\mathbf{h}$, where $\delta\mathbf{x}$ is state perturbation around our current estimate $\mathbf{\hat{x}}$:

    $$\mathbf{h}(\mathbf{\hat{x}} + \delta\mathbf{x}) \approx \mathbf{h}(\mathbf{\hat{x}}) + \mathbf{J}^{\mathbf{h}}_{\mathbf{x}}(\mathbf{\hat{x}}) \delta \mathbf{x}$$

    After trivial rewriting we can identify the standard form: -$$ -\mathbf{L} = \arg\underset{\delta\mathbf{x}}{\min}\left|\left| \mathbf{J}^{\mathbf{h}}_{\mathbf{x}}(\mathbf{\hat{x}}) \delta \mathbf{x} - \left(\mathbf{h}(\mathbf{\hat{x}} + \delta\mathbf{x}) - \mathbf{h}(\mathbf{\hat{x}}) \right)\right|\right|^2 , -$$ -noticing that $\mathbf{\hat{x}} + \delta\mathbf{x} = \mathbf{x}$, where $\mathbf{x}$ is the true state, we substitute $\mathbf{h}(\mathbf{x})$ with our measurement $\mathbf{z}$, we simplifying the expression one step further: -$$ -\mathbf{L} = \arg\underset{\delta\mathbf{x}}{\min}\left|\left| \underbrace{\mathbf{J}^{\mathbf{h}}_{\mathbf{x}}(\mathbf{\hat{x}})}_{\vphantom{f}\mathbf{A}} \delta \mathbf{x} - \underbrace{\left(\mathbf{z} - \mathbf{h}(\mathbf{\hat{x}}) \right)}_{\vphantom{f}\mathbf{b}}\right|\right|^2. -$$ -In this form we clearly identify the linear residuals form! Since $\mathbf{b} = \log\left(\hat{\mathbf{T}}_{b_{i}b_{i+1}}^{-1}\mathbf{T}_{b_{i}b_{i+1}}\right)$ and has vector form only one thing that is needed now is the jacobian $\mathbf{J}^{\mathbf{h}}$.

    Step 3

    To find the Jacobian we can use the elementary blocks and chain rule, as stated in 1. Note, that after our former reformulation we need to find the Jacobian of the measurement function $\mathbf{h}$ w.r.t. the state, not w.r.t. the squared residual: -$$ -\mathbf{J}^{\mathbf{h}}_{\mathbf{x}} = \frac{D \mathbf{h}(\mathbf{x})}{D \mathbf{x}}. -$$ -Having the measurement function -$$\mathbf{h}\left(\mathbf{T}_{wb_i}, \mathbf{T}_{wb_{i+1}}\right) = \hat{\mathbf{T}}_{b_{i}b_{i+1}} = \mathbf{T}_{wb_{i}}^{-1}\mathbf{T}_{wb_{i+1}}$$ -we can use the jacobians that already derived for us in the literature, for example the jacobians w.r.t. $\mathbf{T}_{wb_{i}}$ and $\mathbf{T}_{wb_{i+1}}$ can be found to be (eq. (82) and (83) in 1): -$$ -\begin{gather} -\mathbf{J}^\mathbf{h}_{\mathbf{x}_{\mathbf{T}_{wb_{i}}}} = -\mathbf{J}_l^{-1}\left(\mathbf{e}\right), \\ -\mathbf{J}^\mathbf{h}_{\mathbf{x}_{\mathbf{T}_{wb_{i+1}}}} =\mathbf{J}_r^{-1}\left(\mathbf{e}\right), -\end{gather} -$$ -and $\mathbf{e}=\log\left(\mathbf{T}_{wb_{i}}^{-1}\mathbf{T}_{wb_{i+1}}\right) \in \mathcal{T}_{b_i}\mathcal{M}$.

    Equipped with Jacobians we can write our toy problem. Assume that we know prior robot poses ${\mathbf{T}_{wb_{1}}, \mathbf{T}_{wb_{2}}, \mathbf{T}_{wb_{3}}} \in SE(2)$ and the measured pose increments between them ${\mathbf{\tau}_{b_{1}b_{2}}, \mathbf{\tau}_{b_{2}b_{3}} } \in \mathcal{T}\mathcal{M}$. Our state consist of three poses, and we have two increments. Then we can define our problem using matrices: -$$ -\underbrace{ -\begin{bmatrix} --\mathbf{J}_l^{-1}\left(\log\left(\mathbf{T}_{wb_{1}}^{-1}\mathbf{T}_{wb_{2}}\right)\right) & \mathbf{J}_r^{-1}\left(\log\left(\mathbf{T}_{wb_{1}}^{-1}\mathbf{T}_{wb_{2}}\right)\right) & \mathbf{0}_{3\times3}\\ -\mathbf{0}_{3\times3} & -\mathbf{J}_l^{-1}\left(\log\left(\mathbf{T}_{wb_{2}}^{-1}\mathbf{T}_{wb_{3}}\right)\right) & \mathbf{J}_r^{-1}\left(\log\left(\mathbf{T}_{wb_{2}}^{-1}\mathbf{T}_{wb_{3}}\right)\right) -\end{bmatrix}}_{\mathbf{J}_{6\times 9}} \delta\mathbf{x} = -\underbrace{ -\begin{bmatrix} -\mathbf{\tau}_{b_{1}b_{2}} - \log\left(\mathbf{T}_{wb_{1}}^{-1}\mathbf{T}_{wb_{2}}\right) \\ -\mathbf{\tau}_{b_{2}b_{3}} - \log\left(\mathbf{T}_{wb_{2}}^{-1}\mathbf{T}_{wb_{3}}\right) -\end{bmatrix}}_{\mathbf{r}_{6\times 1}} -$$

    This can be solved using least-squares, the errors, or perturbation can be found to be: -$$ -\delta\mathbf{x} = \left(\mathbf{J}^T\mathbf{J}\right)^{-1}\mathbf{J}^T\mathbf{r} -$$ -then these errors are injected into the current, or nominal state: $\mathbf{x} \leftarrow \mathbf{x} + \delta\mathbf{x}$. The procedure is repeated until convergence. -Doing so we evenly distribute the total error vector between the whole elements of the state vector. To overcome this issue we can use weighted least squares and assign weight to each residual based on our confidence in it.

    Note that this problem can be described using factor graph 2: this uncovers the close relationship between graphs and linear algebra.

    Arbitrary measurement functions

    Last thing I would like to tackle in this post is the arbitrary measurement functions. -Assume that we have a sensor that gives us the range measurements to the landmark, and the landmark position $\mathbf{p}_{wl}$ is known. How can we add such a sensor in our setup? -As usual, we have to define the measurement function $\mathbf{h}$: -$$ -\mathbf{h}_{r}\left(\mathbf{T}_{wb}, \mathbf{p}_{wl}\right) = \sqrt{\mathbf{t}^T \mathbf{t}}, -$$ -where $\mathbf{t} = \begin{bmatrix}\mathbf{T}_{wb}^x - \mathbf{p}_{wl}^x \ \mathbf{T}_{wb}^y - \mathbf{p}_{wl}^y\end{bmatrix}$ is the translation between origin of the body frame and the landmark.

    Only one thing we need is to find the derivative of $\mathbf{h}_r$ w.r.t. our state. First, let’s resolve the landmark position in body frame (doing so we don’t need to calculate the difference before dot product): -$$ -\mathbf{p}_{bl} = \mathbf{T}_{bw}\mathbf{p}_{wl} = \mathbf{T}_{wb}^{-1}\mathbf{p}_{wl} -$$ -substituting into the $\mathbf{h}_{r}$ and extracting only pose component of state vector we get: -$$ -\mathbf{h}_{r}\left(\mathbf{T}_{wb}, \mathbf{p}_{wl}\right) = \sqrt{\left(\mathbf{T}_{wb}^{-1}\mathbf{p}_{wl}\right)^T \left(\mathbf{T}_{wb}^{-1}\mathbf{p}_{wl}\right)}, -$$

    We can think write our measurement function $\mathbf{h}$ as composition of $\mathbf{f} \circ \mathbf{g} \circ \mathbf{n}$, -where

    • $\mathbf{n}\left(\mathbf{T}\right) = \mathbf{T}^{-1}$;
    • $\mathbf{g}\left(\mathbf{T}, \mathbf{p}\right) = \mathbf{T} \mathbf{p}$;
    • $\mathbf{f}\left(\mathbf{p}’\right) = \sqrt{\mathbf{p’}_x^2 + \mathbf{p’}_y^2}$.

    The derivative w.r.t. to state can be found using chain rule: -$$ -\mathbf{J}^{\mathbf{f}}_{\mathbf{T}} = \mathbf{J}^{\mathbf{f}}_{\mathbf{p}’} \mathbf{J}^{\mathbf{T}^{-1}\mathbf{p}}_{\mathbf{T}^{-1}} \mathbf{J}^{\mathbf{T}^{-1}}_{\mathbf{T}}. -$$ -For $SE(2)$ the latter two are derived in 1 by equations (166) and (62). -The derivation of $\mathbf{J}^{\mathbf{f}}_{\mathbf{p}’}$ is trivial: -$$ -\mathbf{J}^{\mathbf{f}}_{\mathbf{p}’} = \begin{bmatrix}\frac{\mathbf{p’}_x}{r} & \frac{\mathbf{p’}_y}{r}\end{bmatrix}, ~r = \sqrt{\mathbf{p’}_x^2 + \mathbf{p’}_y^2}. -$$

    Now, if we have two landmarks $\mathbf{l}_1$ and $\mathbf{l}_2$, and our robot sees first landmark only at $\mathbf{T}_{wb_1}$ and second landmark only at $\mathbf{T}_{wb_3}$, we have to add two extra rows at our matrix $\mathbf{J}$ to account for this data: -$$ -\underbrace{ -\begin{bmatrix} -&\mathbf{J}_{6\times 9}& \\ -\mathbf{J}^{\mathbf{f}}_{\mathbf{T}_{wb_1}} & \mathbf{0}_{1\times 3} & \mathbf{0}_{1\times 3} \\ -\mathbf{0}_{1\times 3} & \mathbf{0}_{1\times 3} & \mathbf{J}^{\mathbf{f}}_{\mathbf{T}_{wb_3}} -\end{bmatrix}}_{\mathbf{J}_{8\times 9}} -\delta\mathbf{x} = -\underbrace{ -\begin{bmatrix} -\mathbf{r}_{6\times 1} \\ -r_1 - \mathbf{h}_r\left( \mathbf{T}_{wb_1}, \mathbf{p}_{wl_1} \right) \\ -r_2 - \mathbf{h}_r\left( \mathbf{T}_{wb_3}, \mathbf{p}_{wl_2} \right) -\end{bmatrix}}_{\mathbf{r}_{8\times 1}}, -$$ -where $r_1$ and $r_2$ - range measurements.

    Implementation

    Now let’s give the numerical example to all aforementioned concepts and functions.

    First, define all needed operations for Lie groups and associated algebras.

    Spoiler -
    import numpy as np
    -import scipy.linalg
    -
    -def Jr(tau):
    -    '''Right jacobian for SE2, eq. (163) from [1].'''
    -    x, y, theta = tau.flatten()
    -    s, c = np.sin(theta), np.cos(theta)
    -    return np.array([[s / theta, (1 - c) / theta, (theta * x - y + y * c - x * s) / theta**2],
    -                     [(c - 1) / theta, s / theta, (x + theta * y - x * c - y * s) / theta**2],
    -                     [0., 0., 1.]])
    -
    -def Jl(tau):
    -    '''Left jacobian for SE2, eq. (163) from [1].'''
    -    x, y, theta = tau.flatten()
    -    s, c = np.sin(theta), np.cos(theta)
    -    return np.array([[s / theta, (c - 1) / theta, (theta * x + y - y * c - x * s) / theta**2],
    -                     [(1 - c) / theta, s / theta, (-x + theta * y + x * c - y * s) / theta**2],
    -                     [0., 0., 1.]])
    -
    -def T(x, y, theta):
    -    '''Returns transformation matrix for given x, y and theta parameters.'''
    -    return np.array([[np.cos(theta), -np.sin(theta), x], [np.sin(theta), np.cos(theta), y], [0., 0., 1.]])
    -
    -def Exp(tau):
    -    '''Exponent, transfers Lie algebra to the group, known as retraction.'''
    -    tau = tau.flatten()
    -    x, y, theta = tau[0], tau[1], tau[2]
    -    T = np.zeros((3, 3))
    -    T[0:2, 0:2] = scipy.linalg.expm(np.array([[0, -theta], [theta, 0]]))
    -    V_theta = np.sin(theta) / theta * np.eye(2) + (1. - np.cos(theta)) / theta * np.array([[0, -1], [1, 0]])
    -    p = V_theta @ np.array([[x], [y]])
    -    T[0:2, 2] = p.flatten()
    -    T[2, 2] = 1.0
    -    return T
    -
    -def Log(T):
    -    '''Inverse of Exp, unwraps group to Lie algebra.'''
    -    t = T[1, 0] / T[0, 0]
    -    x, y, theta = T[0, 2], T[1, 2], np.arctan2(T[1, 0], T[0, 0])
    -    V_theta = np.sin(theta) / theta * np.eye(2) + (1. - np.cos(theta)) / theta * np.array([[0, -1], [1, 0]])
    -    v = np.linalg.inv(V_theta) @ np.array([[x], [y]])
    -    return np.array([[v[0, 0]], [v[1, 0]], [theta]])
    -
    -def Ad(T):
    -    '''Adjoint, linear transform that transforms tangent space at X to tangent space at identity.'''
    -    Ad = np.copy(T[:, :])
    -    Ad[0:2, 2] = -np.array([[0., -1.], [1., 0]]) @ T[0:2, 2]
    -    return Ad[:, :]
    -
    -def J_df_dp(p):
    -    px, py = p[0], p[1]
    -    r = np.sqrt(px**2 + py**2)
    -    return np.array([px / r, py / r, 0.])
    -
    -def J_dTp_dT(T, p):
    -    J = np.eye(3)
    -    J[0:2, 0:2] = T[0:2, 0:2]
    -    J[0:2, 2] = T[0:2, 0:2] @ np.array([[0, -1.], [1., 0]]) @ p
    -    
    -    return J
    -

    Now select true values, measurements and add noise to them.

    Spoiler -
    # true values
    -trueT_1 = T(2., 4., np.deg2rad(35))
    -trueT_2 = T(2., 3., np.deg2rad(50))
    -trueT_3 = T(4., 3., np.deg2rad(70))
    -
    -# landmark positions
    -L1 = np.array([1.5, 3.75, 1.])
    -L2 = np.array([4.5, 3., 1.])
    -
    -# pose increments measured + noise
    -dT_noise = np.array([0.01, 0.01, 0.01])
    -T_12 = Log(np.linalg.inv(trueT_1) @ trueT_2) + np.random.normal(scale = dT_noise).reshape((3, 1))
    -T_23 = Log(np.linalg.inv(trueT_2) @ trueT_3) + np.random.normal(scale = dT_noise).reshape((3, 1))
    -
    -# ranges measured + noise
    -dR_noise = 0.01
    -R1 = np.sqrt(np.sum((trueT_1[:, 2] - L1) ** 2)) + np.random.normal(scale = dR_noise)
    -R2 = np.sqrt(np.sum((trueT_3[:, 2] - L2) ** 2)) + np.random.normal(scale = dR_noise)
    -
    -# noise prior estimates
    -T_1 = trueT_1 @ Exp(np.random.normal(scale = 20 * dT_noise))
    -T_2 = trueT_2 @ Exp(np.random.normal(scale = 20 * dT_noise))
    -T_3 = trueT_3 @ Exp(np.random.normal(scale = 20 * dT_noise))
    -pT_1, pT_2, pT_3 = np.copy(T_1), np.copy(T_2), np.copy(T_3)
    -

    Fill-in jacobians and solve the problem iteratively:

    u_sigmas = np.array([0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.00001, 0.00001])
    -W = np.diagflat(1./u_sigmas)  # this is Q^(-T/2)
    -
    -for i in range(0, 1000):
    -    H = np.zeros((8, 9))
    -    est_T_12 = Log(np.linalg.inv(T_1) @ T_2)
    -    H[0:3, 0:3] = -np.linalg.inv(Jl(est_T_12))
    -    H[0:3, 3:6] =  np.linalg.inv(Jr(est_T_12))
    -    
    -    est_T_23 = Log(np.linalg.inv(T_2) @ T_3)
    -    H[3:6, 3:6] = -np.linalg.inv(Jl(est_T_23))
    -    H[3:6, 6:9] =  np.linalg.inv(Jr(est_T_23))
    -    
    -    est_range_R1 = np.sqrt(np.sum((T_1[:, 2] - L1) ** 2))
    -    # chain rule
    -    T1_inv = np.linalg.inv(T_1)
    -    L1_transformed = (T1_inv @ L1)[0:2]
    -    H[6, 0:3] = J_df_dp(L1_transformed) @ J_dTp_dT(T1_inv, L1[0:2]) @ -Ad(T_1)
    -    
    -    est_range_R2 = np.sqrt(np.sum((T_3[:, 2] - L2) ** 2))
    -    # chain rule
    -    T3_inv = np.linalg.inv(T_3)
    -    L2_transformed = (T3_inv @ L2)[0:2]
    -    H[7, 6:9] = J_df_dp(L2_transformed) @ J_dTp_dT(T3_inv, L2[0:2]) @ -Ad(T_3)
    -    
    -    residual = np.vstack((T_12 - est_T_12, 
    -                          T_23 - est_T_23, 
    -                          R1 - est_range_R1,
    -                          R2 - est_range_R2))
    -    
    -    delta = np.linalg.pinv(H.T @ W @ H) @ H.T @ W @ residual
    -    
    -    T_1 = T_1 @ Exp(delta[0:3, :])
    -    T_2 = T_2 @ Exp(delta[3:6, :])
    -    T_3 = T_3 @ Exp(delta[6:9, :])
    -
    Spoiler -
    import matplotlib.pylab as plt
    -from matplotlib.patches import Circle
    -%config InlineBackend.figure_format='retina'
    -
    -fig, ax = plt.subplots(1, 1, figsize = (7, 5))
    -ax.plot([trueT_1[0, 2], trueT_2[0, 2], trueT_3[0, 2]], [trueT_1[1, 2], trueT_2[1, 2], trueT_3[1, 2]], 'o-', label = 'True')
    -ax.plot([T_1[0, 2], T_2[0, 2], T_3[0, 2]], [T_1[1, 2], T_2[1, 2], T_3[1, 2]], 'o-', label = 'Optimized')
    -ax.plot([pT_1[0, 2], pT_2[0, 2], pT_3[0, 2]], [pT_1[1, 2], pT_2[1, 2], pT_3[1, 2]], 'o-', label = 'Initial')
    -ax.scatter([L1[0], L2[0]], [L1[1], L2[1]], marker='x', s = 120, label = 'Landmarks')
    -for l, r in zip((L1, L2), (R1, R2)):
    -    circle = Circle((l[0], l[1]), r, facecolor='none', edgecolor='tab:blue', linewidth=3, alpha=0.5)
    -    ax.add_patch(circle)
    -ax.axis("equal")
    -ax.legend()
    -ax.grid()
    -ax.set_title("True, initial and estimated trajectories")
    -ax.text(1, 2.25, "Circles - measured distances to the landmarks", c = "tab:blue", alpha = 0.8);
    -

    png

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/page/1/index.html b/posts/page/1/index.html deleted file mode 100644 index 7b17c12..0000000 --- a/posts/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/posts/ - \ No newline at end of file diff --git a/posts/particle-filter.md/index.html b/posts/particle-filter.md/index.html deleted file mode 100644 index 60f8be8..0000000 --- a/posts/particle-filter.md/index.html +++ /dev/null @@ -1,90 +0,0 @@ -Particle Filter: localizing the robot · Aleksandr Mikoff's blog -

    Particle Filter: localizing the robot

    Particle filter

    png

    In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It’s widespread application lies in its versatile nature and universalism. The filter is able to:

    • Work with nonlinearities.
    • Handle non-gaussian distributions.
    • Easily fuse various information sources.
    • Simulate the processes.

    My sample implementation takes less then 100 lines of Python code and can be found here.

    Idea

    The idea of the particle filter is to represent the probability distribution by a set $X$ of random samples drawn from the original probability distribution. These samples usually called particles. Every particle $x^{(i)}_t$ can be seen as the hypothesis about what true state can be at time $t$.

    In ideal world, the likelihood of getting a hypothesis $x^{(i)}_t$ shall be proportional to its Bayes posterior: -$$x^{(i)}_t \sim p(x_t | z_{1:t}, u_{1:t}).$$

    The particle filter is the recursive one. That means that belief at time $t$ is being computed from the belief one time step earlier, $t-1$. Since the belief is represented by the particle set $X$, then the particles from $X_{t-1}$ can be transformed into the set $X_t$ based on system model.

    Steps

    Each particle filter iteration cycle can be broken into three steps:

    1. Prediction. -Generate the particle set $X_t$ based on the particle set $X_{t-1}$ and the control $u_t$. If it is the initialization, then the particle set have to be generated across all possible states. Usually on this step some noise is injected into the model to accomodate for noise in control and to make particle set more distributed.
    2. Correction. -Calculate the importance factor, or weight of each particle. The importance factor is the probability to get the measurement $z_t$ given the hypothesis, or particle $x_t$: $w^{(i)}_t = p(z_t | x^{(i)}_t)$. If more than one measurement is available, say $M$, then all these measurements should be multiplied under the assumption that they are independent: $w^{(i)}_t = \prod_{j=1}^M p(z_{t,j} | x^{(i)}_t)$. Normalize the particle weights: $w_{t}^{(i)}={\frac {{ {w}}_{t}^{(i)}}{\sum _{i=1}^{N}{{w}}_{t}^{(i)}}}$. The set of weighted particles represents the filter belief.
    3. Resampling. -It is done through replacing when the more unlikely hypothesis with more likely ones. The probability of drawing each particle is given by its relative importance weight (w.r.t the whole set). The idea of resampling is similar to the idea in volutionary algorithms: only the fittest ones should survive.

    Notes:

    1. It is not always the best idea to sample the particles across all possible state space on the initialization. Sometimes it is better to wait for few available measurements and sample from $p(z_t|m)$, where $m$ – is the map.
    2. The resampling process is computational extensive. Therefore it is recommended to resample only when the effective number of particles is less than the given threshold $N_X$. It is shown, that the effective number of particles can be approximated as: $\hat {N}_{\mathit {eff}}={\frac {1}{\sum _{i=1}^{N}\left(w_{k}^{(i)}\right)^{2}}}$. This formula reflects the variance of particle weights.

    Importance Sampling

    To understand the idea behind importance sampling more clearly let’s reformulate the problem: say we would like to get a sample from target distribution $f$, from which direct sampling is very hard or even impossible. -However, we are able to generate the samples from some probability density function $g$, called proposal distribution.

    What resampling step is able to do is to generate a new set of particles distributed according to the density function $f$ given the set of particles distributed according to density $g$.

    To illustrate this let’s generate samples from density $g$. The samples, drawn from $g$ are shown on the bottom of the plot.

    N_samples = 1000
    -proposal_mean = 25
    -proposal_std = 10.0
    -xfrom, xto = -10, 60
    -
    -def target_pdf(x):
    -    return 0.5 * norm.pdf(x, 1.0, 3.0) + 0.5 * norm.pdf(x, 12.0, 4.0)
    -
    -proposal_samples = np.random.normal(proposal_mean, proposal_std, size=N_samples)
    -
    -fig, ax = plt.subplots(1, 1, figsize=(10, 4), sharex = True)
    -x = np.linspace(xfrom, xto, 1000)
    -ax.plot(x, norm.pdf(x, proposal_mean, proposal_std), label='proposal pdf, $g(x)$', c='red')
    -ax.plot(x, target_pdf(x), label='target pdf, $f(x)$', c='green')
    -# plot rug plot
    -rel_rug_height = ax.get_ylim()[1] / 20.0
    -ax.vlines(proposal_samples, [0] * N_samples, [rel_rug_height] * N_samples, 
    -          alpha=0.05, colors='red', label='proposal samples', linewidths=5.0)
    -ax.set_xlim([xfrom, xto])
    -ax.legend()
    -

    png

    Samples from $f$ are obtained by attaching weights $w = f(x) / g(x)$ to each sample, drawn from $g$. The following plot shows the proposal samples with their associated weights (the height of the vertical lines). Some weights became equal to zero while the others are significantly increased. For example, in spite of the fact that the proposal density in range $(20, 30)$ is high, due to the weighing these samples became insignificant.

    The combination of color intensity and the height of the vertical bars can be considered as the target probability density function. The more intensive and higher the vertical bars - the more probable the neighbourhood samples $x$ around it. -As an alternative, weights are shown as circles where the radius of the circle reflects the particle weight and the circle color intensity due to the overlapping - is their density in the given region.

    target_weights = target_pdf(proposal_samples) / norm.pdf(proposal_samples, proposal_mean, proposal_std)
    -
    -fig, ax = plt.subplots(1, 1, figsize=(10, 4))
    -ax.set_xlim([xfrom, xto])
    -ax.vlines(proposal_samples, [0] * N_samples, target_weights / np.sum(target_weights), 
    -          alpha=0.05, colors='green', label='proposal samples with $ w = f(x) / g(x)$', linewidths=5.0)
    -x = np.linspace(xfrom, xto, 1000)
    -ax.plot(x, target_pdf(x), label='target pdf, $f(x)$', c='green')
    -ax.scatter(proposal_samples, [ax.get_ylim()[0]] * N_samples, 
    -           sizes=target_weights / np.sum(target_weights) * 10000, alpha=0.05,
    -           label="proposal sample circles with $r \sim w$")
    -    
    -ax.legend()
    -

    png

    Now, if we resample from the weighted samples distribution we get the samples from our target distribution (as $N \xrightarrow{}\infty$):

    def systematic_resample(weights, samples):
    -    weights, samples, n_samples = np.array(weights), np.array(samples), len(samples)
    -    weights /= np.sum(weights)
    -    r, c, i = np.random.uniform(0.0, 1.0 / N_samples), weights[0], 0
    -    resampled = []
    -    for m in range(0, n_samples):
    -        U = r + m / n_samples
    -        while c < U:
    -            i += 1
    -            c += weights[i]
    -        resampled.append(deepcopy(samples[i]))
    -    return resampled
    -
    -fig, ax = plt.subplots(1, 1, figsize=(10, 4))  
    -x = np.linspace(xfrom, xto, 200)
    -ax.plot(x, target_pdf(x), label='target pdf, $f(x)$', c='green')
    -target_samples = systematic_resample(target_weights, proposal_samples)
    -rel_rug_height = ax.get_ylim()[1] / 20.0
    -ax.vlines(target_samples, [0] * N_samples, [rel_rug_height] * N_samples, 
    -          alpha=0.05, colors='violet', label='resampled', linewidths=5.0)
    -ax.scatter(target_samples, [ax.get_ylim()[0]] * N_samples, 
    -           s=100, alpha=0.05,
    -           label="resampled samples with $r \sim w$")
    -
    -ax.legend()
    -ax.set_xlim([xfrom, xto])
    -plt.show()
    -

    png

    Particle filter implementation

    Now, having all key concepts explained, we can write the particle filter implementation for robot localization. -The filter state consists of:

    • robot position - $(x, y)$.
    • robot heading angle - $\theta$.

    The robot moves along a map and measures the distances to the known landmarks, or anchors, when they are within its sense range.

    The robot distance measurements to the landmarks and its motion controls are not perfect. To take that into account I inject the noises in particle filter process in the following forms:

    • For predict step - the control $u_t$ that consists of $(\delta d, \delta \theta)$ is additionally pertrubated with random noise, $N(0, \sigma_{motion})$ and $N(0, \sigma_{\theta})$.
    • For correction step. Whenever the robot measures the distances to the known landmark $n$, we can asses the measurement likelihood given our state $(x, y, \theta)$. Having the predicted $\hat{d} = \sqrt{(x - x_{landmark})^2 + (y - y_{landmark})^2}$ and measured $d_n$ distances, and the measurement noise $\sigma_d$ the likelihood can be calculated as: -$$p(z_n|x_t) = {\frac {1}{\sigma_d {\sqrt {2\pi }}}}e^{-{\frac {1}{2}}\left({\frac {\hat{d}-d_{n} }{\sigma_d }}\right)^{2}}$$ -The current particle weight have to be multiplied with $p(z_n|x_t)$ for each available measurement.

    Then the weights have to be renormalized, particles - resampled, if it is neccessary. -This process continues to evolve in iterative manner.

    Animation

    To test the code and view the particle filtering process in action you can simply execute run.py script:

    python3 run.py
    -

    In the following animation the robot moves along a circle and measures the distances to anchors that are marked with red crosses. -The true robot position is shown with black plus sign. The blue lines, connecting the robot and the anchors - true distance measurements between the robot and the anchors, the grey circles - the measured distances between robot and anchor. -Feel free to experiment with configuration and add new types of measurements (for example the heading w.r.t to the anchors, or absolute motion heading).

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/point-cloud-alignment-and-lie-algebra.md/index.html b/posts/point-cloud-alignment-and-lie-algebra.md/index.html deleted file mode 100644 index c33ce56..0000000 --- a/posts/point-cloud-alignment-and-lie-algebra.md/index.html +++ /dev/null @@ -1,220 +0,0 @@ -Point cloud alignment using Lie algebra machinery · Aleksandr Mikoff's blog -

    Point cloud alignment using Lie algebra machinery

    Point cloud alignment using Lie algebra machinery

    Special Orthogonal group and vectorspaces

    Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. -The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group.

    Why it stands up a problem? Quite often we need to use the optimization algorithms to find some optimal element. Most of these algorithms start from some initial guess and update it in some direction $y$: -$$ -x = x + \epsilon y -$$ -This equation does not make any sense if we operate not in the vectorspace!

    While SO(3) is not a vector space, it can be shown to be matrix Lie group:

    • It is a set of all valid rotation matrices ${\mathbf{C} \in \mathbb{R}^{3\times3}~|~\mathbf{C}\mathbf{C}^T = \mathbf{1},~\det\mathbf{C} = 1}$
    • satisfying the the four properties: closure, associativity, identity and invertability.

    The Lie group theory allows us associate a Lie algebra with every Lie group. This Lie algebra consists of a vectorspace, that provides a standard way to parameterize the rotation matrices and their errors.

    Lie Algebra

    The vectorspace of a Lie algebra $\mathfrak{so}(3)$ is the tangent space of the associated Lie group, and it completely describes the local structure of the group.

    The lie algebra, associated with $SO(3)$ is given by

    • vectorspace $\mathfrak{so}(3) = {\mathbf{\Phi} = \mathbf{\phi}^{\wedge}~\in~\mathbb{R}^{3\times3}~|~\mathbf{\phi} \in \mathbb{R}^3},$
    • field $\mathbb{R}$,
    • Lie bracket: $[\mathbf{\Phi}_1, \mathbf{\Phi}_2] = \mathbf{\Phi}_1 \mathbf{\Phi}_2 - \mathbf{\Phi}_2, \mathbf{\Phi}_1,$

    where -$$ -\mathbf{\phi}^{\wedge} = {\begin{bmatrix} \phi_1 \newline \phi_2 \newline \phi_3 \end{bmatrix}}^{\wedge} = -\begin{bmatrix} 0 & -\phi_3 & \phi_2 \newline \phi_3 & 0 & -\phi_1 \newline -\phi_2 & \phi_1 & 0 \end{bmatrix} -$$

    The exponential map is the key that helps us to switch from our Lie algebra to the associated Lie group. -This relationship between a vector space $\mathfrak{so}(3)$ and Lie group is what allows us to convert the rotation matrices optimization problems to linear algebra problems and vice versa (the intuition behind is greatly discussed in this video).

    To verify the Lie algebra element structure, let’s assume that we are not aware of its form and only know that it is some arbitrary matrix $\mathbf{M} \in \mathbb{R}^{3\times3}$. By the definition of SO(3) it should fullfill the following properties:

    • $\exp(\mathbf{M}) \exp(\mathbf{M})^T = \mathbf{1}$
    • $\det(\exp(\mathbf{M})) = 1.$

    For the first property, it is easy to show through Taylor series expansion that $\exp(\mathbf{M})^T = \exp(\mathbf{M^T})$ is valid for any matrix. Also we know that $\exp(\mathbf{0}) = \mathbf{1}$. Therefore: -$$\exp(\mathbf{M}) \exp(\mathbf{M})^T = \mathbf{1}$$ -$$\exp(\mathbf{M} + \mathbf{M}^T) = \exp(\mathbf{0})$$ -$$\mathbf{M} + \mathbf{M}^T = \mathbf{0} $$ -$$\mathbf{M} = - \mathbf{M}^T$$ -We see, that the first property leads us the the neccessitiy for our Lie algebra to be skew-symmetric.

    To check the second property let’s recall the following identity: -$$ -\det(\exp(\mathbf{M})) = \exp(\mathrm{tr}(\mathbf{M})) -$$ -It can only happen when the trace of our matrix is equal to 0. Any skew-symmetric matrix fullfills this property.

    Point cloud alignment with known correspondences

    The implemented approach is greatly discussed here. I will just point out some key ideas:

    • Since the optimization is unconstrained, the SVD or eigen decomposition is not required.
    • The result is not optimal globally, but locally. However, if there is a unique global minimum, there is no any local minima.

    Let’s pretend that we have:

    • $M$ measurements $\mathbf{y}_j = \mathbf{r}_{v_k}^{p_j v_k}$, where $j = 1\dots M$, of points from the vehicle (expressed in vehicle frame).
    • The coordinates of these points in non-moving frame $i$: $\mathbf{p}_j = \mathbf{r}_{i}^{p_j i}$.

    Our goal is to align a collection of points, expressed in two reference frames: find the rotation and translation between them.

    To solve the problem we require the following:

    • $\mathbf{W} = \frac{1}{w} \sum_{j=1}^M w_j (\mathbf{y}_j - \mathbf{y})(\mathbf{p}_j - \mathbf{p})^T,$

      that captures the spread of the points and where $w_j$ is the scalar weight for point pair $j$.

    • $\mathbf{I} = -\frac{1}{w} \sum_{j=1}^M w_j (\mathbf{p}_j - \mathbf{p})^\wedge (\mathbf{p}_j - \mathbf{p})^\wedge.$

    • $\mathbf{b} = [\mathrm{tr}(\mathbf{1}_i^\wedge \mathbf{C}_{op}) \mathbf{W}^T)]_i,$

      where $i$ is the $i$-th column of the identity matrix and the corresponding $i$-th row of column vector $\mathbf{b}$.

    The update for our goal rotation matrix $\mathbf{C}_{op}$ can be written as: -$$ -\mathbf{C}_{op} \leftarrow \exp \left(\mathbf{\psi}^{{*}^{\wedge}} \right)\mathbf{C}_{op}, -$$ -where the optimal update for $\mathbf{\psi}^{*}$ is defined as: -$$ -\mathbf{\psi}^{*} = \mathbf{C}_{op} \mathbf{I}^{-1} \mathbf{C}_{op}^T \mathbf{b}. -$$

    The following sequence is itereated until convergence, where $\hat{\mathbf{C}}_{v_k i} = \mathbf{C}_{op}$. Then the translation can be found as: -$\hat{\mathbf{r}}_i^{v_k i} = \mathbf{p} - \hat{\mathbf{C}}_{v_k i} \mathbf{y}$.

    Now let’s move the example:

    import numpy as np
    -from scipy.linalg import fractional_matrix_power
    -np.set_printoptions(precision=3)
    -np.set_printoptions(suppress=True)
    -
    -def skew(x): 
    -      if (isinstance(x, np.ndarray) and len(x.shape)>=2): 
    -          return np.array([[0, -x[2][0], x[1][0]], 
    -                           [x[2][0], 0, -x[0][0]], 
    -                           [-x[1][0], x[0][0], 0]]) 
    -      else: 
    -          return np.array([[0, -x[2], x[1]], 
    -                           [x[2], 0, -x[0]], 
    -                           [-x[1], x[0], 0]])
    -    
    -def skewExponent(psi):
    -    psiNorm = np.linalg.norm(psi)
    -    a = np.array(psi) / psiNorm
    -    return np.cos(psiNorm) * np.eye(3) + (1 - np.cos(psiNorm)) * a @ a.T + np.sin(psiNorm) * skew(a)
    -
    # define point set P: the point coordinates in non-moving frame
    -P = np.array(
    -      [[ 3.,  0.,  0., -3., -0., -0.],
    -       [ 0.,  2.,  0., -0., -2., -0.],
    -       [ 0.,  0.,  1., -0., -0., -1.]])\
    -    + np.array([[100], [200], [300]])
    -
    -# define point set Y: the point coordinates in vehicle frame
    -Y = np.array(
    -      [[-3., -0., -0.,  3.,  0.,  0.],
    -       [-0., -2., -0.,  0.,  2.,  0.],
    -       [-0., -0., -1.,  0.,  0.,  1.]])
    -
    -# calculate the centers of the point clouds
    -meanP = np.mean(P, axis=1).reshape((3, 1))
    -meanY = np.mean(Y, axis=1).reshape((3, 1))
    -
    -# pre-calcaulate W matrix
    -W = np.zeros((3, 3))
    -for j in range(P.shape[1]):
    -    W += (Y[:, [j]] - meanY) @ (P[:, [j]] - meanP).T
    -    
    -# pre-calculate I^-1 matrix
    -I = np.zeros((3, 3))
    -for j in range(P.shape[1]):
    -    I -= skew(P[:, [j]] - meanP) @ skew(P[:, [j]] - meanP)
    -    
    -invI = np.linalg.inv(I)
    -
    -# build initial estimate of our rotation matrix
    -C_op = skewExponent([0.2, 0.1, -0.2]) @ np.eye(3)
    -C_op = fractional_matrix_power(C_op @ C_op.T, -0.5) @ C_op
    -
    -C_op_prev = np.eye(3)
    -I3 = np.eye(3)
    -while np.linalg.norm(C_op - C_op_prev) > 0.001:
    -    # build b vector
    -    b = np.zeros(3)
    -    for i in range(3):
    -        b[i] = np.trace(skew(I3[i]) @ C_op @ W.T)
    -    b = b.reshape((3, 1))
    -
    -    psi_star = C_op @ invI @ C_op.T @ b
    -
    -    C_op_prev = C_op
    -    C_op = skewExponent(psi_star) @ C_op
    -    
    -# find the translation
    -t = meanP - C_op.T @ meanY
    -
    -print(C_op)
    -print(t)
    -
    [[-1.  0. -0.]
    - [-0. -1.  0.]
    - [-0.  0.  1.]]
    -[[100.]
    - [200.]
    - [300.]]
    -

    Point cloud alignment without correspondences

    If we the correspondences between points are not known, then we can use, for example, the Iterative Closest Point algorithm where the correspondences are determined by finding the closest point from the model to the observed point. -I would like to present a piece of code for this scenario too.

    def nearest_point(P, Y):
    -    P = np.array(P)
    -    Y = np.array(Y)
    -    distances = np.zeros(P.shape[1])
    -    index = np.zeros(Y.shape[1], dtype = np.int)
    -
    -    for i in range(P.shape[1]):
    -        minDist = np.inf
    -        for j in range(Y.shape[1]):
    -            curDist = np.linalg.norm(P[:, i] - Y[:, j])
    -            if curDist < minDist:
    -                minDist = curDist
    -                index[i] = j
    -        distances[i] = minDist
    -    return distances, index
    -

    In this simplest ICP form we assume that the number of points is the same and we use all of them for fitting. It is always a good thing to remember that it is not always the case, to solve the problem with outliers and large number of points the following things could be added to the algorithm implementation:

    • Randomly select the subset from each point set.
    • Split the aligned points to inliers and outliers based on some statistic criteria.
    • Throw away the outliers from cloud alignment procedure.

    However, for our illustration purposes this vanilla ICP algorithm should work fine. Let’s verify it.

    # read the rabbit data and make it column-wise
    -P = np.loadtxt("data.xyz").T * 200
    -
    -# generate random rotation and translation
    -C = skewExponent([0.8, 0.6, -0.4]) @ np.eye(3)
    -C = fractional_matrix_power(C @ C.T, -0.5) @ C
    -translation = np.array([[10], [20], [30]])
    -
    -# rotate and translate original points, add noise
    -Y = C @ (P - translation) + np.random.normal(0.0, 0.1, size=P.shape)
    -# randomly shuffle the columns to find the correspondences using ICP
    -np.random.shuffle(Y.T)
    -Y_original = np.array(Y)
    -
    -# build initial estimate of our rotation matrix
    -C_op = np.eye(3)
    -C_op_prev = np.diag([np.inf, np.inf, np.inf])
    -I3 = np.eye(3)
    -distances = np.inf
    -
    -MAX_ITER = 20
    -iteration = 0
    -
    -while (np.linalg.norm(C_op - C_op_prev) > 0.001 or np.sum(distances) > 1.0) and iteration < MAX_ITER:
    -    # calculate the centers of the point clouds
    -    meanP = np.mean(P, axis=1).reshape((3, 1))
    -    meanY = np.mean(Y, axis=1).reshape((3, 1))
    -    
    -    # calculate W matrix
    -    W = np.zeros((3, 3))
    -    W = (Y - meanY) @ (P - meanP).T
    -      
    -    # build b vector
    -    b = np.zeros(3)
    -    for i in range(3):
    -        b[i] = np.trace(skew(I3[i]) @ C_op @ W.T)
    -    b = b.reshape((3, 1))
    -
    -    # calculate I matrix
    -    I = np.zeros((3, 3))
    -    for p in P.T:
    -        skewP = skew(p - meanP.ravel())
    -        I -= skewP @ skewP
    -    psi_star = C_op @ np.linalg.inv(I) @ C_op.T @ b
    -
    -    C_op_prev = C_op
    -    C_op = skewExponent(psi_star) @ C_op
    -    t = meanP - C_op.T @ meanY
    -    
    -    # find correspondences using ICP
    -    distances, index = nearest_point(P, t + C_op.T @ Y_original)
    -    # replace the point set with correspondences
    -    Y = Y_original[:, index]
    -    
    -    iteration += 1
    -    
    -print(C_op)
    -print(t)
    -
    [[ 0.865  0.16   0.475]
    - [ 0.163  0.806 -0.569]
    - [-0.474  0.57   0.672]]
    -[[ 9.995]
    - [20.001]
    - [30.   ]]
    -

    If we are talking about the vehicle positioning, then we have found the rotation matrix $\hat{\mathbf{C}}_{v_k i}$ and the translation $\hat{\mathbf{r}}_i^{v_k i}$. -Now we can apply the found rotation and translation to the transformed dataset to visually check the algorithm performance.

    from mpl_toolkits.mplot3d.axes3d import Axes3D
    -import matplotlib.pyplot as plt
    -
    -fig, ax = plt.subplots(1, 2, subplot_kw={'projection': '3d'}, figsize=(8, 4))
    -ax[0].scatter(P[0,:], P[1,:], P[2, :], ',')
    -ax[0].scatter(Y_original[0,:], Y_original[1,:], Y_original[2, :], 'X')
    -scaling = np.array([getattr(ax[0], 'get_{}lim'.format(dim))() for dim in 'xyz'])
    -ax[0].auto_scale_xyz(*[[np.min(scaling), np.max(scaling)]]*3)
    -
    -ax[1].scatter(P[0,:], P[1,:], P[2, :], ',')
    -Y = C_op.T @ Y_original + t
    -ax[1].scatter(Y[0,:], Y[1,:], Y[2, :], 'X')
    -scaling = np.array([getattr(ax[1], 'get_{}lim'.format(dim))() for dim in 'xyz'])
    -ax[1].auto_scale_xyz(*[[np.min(scaling), np.max(scaling)]]*3)
    -
    -ax[0].view_init(elev=10., azim=-215)
    -ax[1].view_init(elev=10., azim=215)
    -

    png

    They are quite close! -Jupyter notebook is available here.

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/point-cloud-alignment.md/index.html b/posts/point-cloud-alignment.md/index.html deleted file mode 100644 index d03d2b3..0000000 --- a/posts/point-cloud-alignment.md/index.html +++ /dev/null @@ -1,143 +0,0 @@ -Point cloud alignment and SVD · Aleksandr Mikoff's blog -

    Point cloud alignment and SVD

    Point cloud alignment and SVD

    Singular value decomposition

    Recently I studied the problem of finding the rotation and translation between two point sets and decided to write the post about it. The key here is singular value decomposition, or SVD.

    It is extremely popular technique in many types of linear problems. It should be not surprised, that the point cloud alignment problem can be solved with its help. -My aim here is to show all accommpanying theory and provide the point cloud alignment algorithm that takes not more than 10 lines of code in Python.

    I would like to start from SVD. -It is convinient to think about any matrix as a function, action, or transformation $A$, that maps points $\vec{x} \in R^n$ to points $\vec{y} = A\vec{x} \in R^m$. Intuitively, we can raise the question: what happens to the geometry of $R^n$ after action $A$. If our initial column space $R^n$ is represented by orthonormal basis, that can be visualzed as a grid, then we ask the following questions: what happens with our grid’s squares after transformation $A$.

    Let’s illustrate this problem with simple graphics.

    def genRMatrix(theta):
    -    return np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
    -
    -def transform(A, X, Y):
    -    mult = A @ np.array([X.ravel(), Y.ravel()])
    -    return mult[0,:].reshape(X.shape), mult[1,:].reshape(Y.shape)
    -
    -def visualise(ax, X, Y, title):
    -    ax.scatter(X, Y)
    -    ax.axis("equal")
    -    ax.set_xlim(-7, 7)
    -    ax.set_ylim(-7, 7)
    -    ax.set(adjustable='box', aspect='equal')
    -    ax.set_title(title)
    -
    -x = np.linspace(-5, 5, 11)
    -y = np.linspace(-5, 5, 11)
    -X, Y = np.meshgrid(x, y, sparse=False)
    -fig, ax = plt.subplots(1, 4, figsize = (24, 5), subplot_kw=dict(aspect='equal'), 
    -                       gridspec_kw=dict(width_ratios=[1,1,1,1]))
    -visualise(ax[0], X, Y, "Initial grid $G$")
    -
    -alphaV = np.deg2rad(30)
    -VT = genRMatrix(alphaV).T
    -XVT, YVT = transform(VT, X, Y)
    -visualise(ax[1], XVT, YVT, "$G$ transformed by $V^T$")
    -
    -S = np.array([[0.5, 0], [0, 0.75]])
    -XSVT, YSVT = transform(S @ VT, XVT, YVT)
    -visualise(ax[2], XSVT, YSVT, "$G$ transformed by $\Sigma V^T$")
    -
    -alphaU = np.deg2rad(45)
    -U = genRMatrix(alphaU)
    -A = USVT = U @ S @ VT
    -XUSVT, YUSVT = transform(A, X, Y)
    -visualise(ax[3], XUSVT, YUSVT, "$G$ transformed by $U \Sigma V^T$")
    -

    png

    The SVD is very helpful because it is always exists for any matrix $A \in R^{m \times n}$. From geometric standpoint the SVD can be seen as a composition of isometry, followed by a scale in each coordinate axis and a second isometry. The isometry means that $U$ and $V$ are orthogonal: their action preserves lengths and the angles.

    Let’s solve the reverse problem: find the SVD factorization for our matrix $A$:

    u, s, v = np.linalg.svd(A)
    -print("U:\n", u)
    -print("S:\n", s)
    -print("V:\n", v)
    -
    U:
    - [[-0.70710678  0.70710678]
    - [ 0.70710678  0.70710678]]
    -S:
    - [0.75 0.5 ]
    -V:
    - [[-0.5        0.8660254]
    - [ 0.8660254  0.5      ]]
    -

    We can note that the order is different. It happens because usually the math libraries return left and singular vectors $U$ and $V$, and the corresponding singular values in sorted order, such as $\sigma_1 \geq \sigma_2 \geq \dots \sigma_n \geq 0$.

    SVD computation

    It is valuable to note why SVD computation is costly:

    • The columns of $V$ are the eigenvectors of $A^T A$ (that can be computed, by example, using QR iteration).
    • Then, rewriting $A = U \Sigma V^T$ as $AV = U \Sigma$ we can make the observation that the columns of $U$ corresponding to $\sigma_i \neq 0$ are normalized columns of $AV$.
    • The remaining columns satisfy the following property: $AA^T\vec{u}_i = 0$. They can be computed through LU factorization.

    Point cloud alignment, the Procrustes problem

    The problem of the shape alignment arises quite often in computer vision problems. For example, we can have the lidar scan results that consist of two point clouds of the same object from different views. A task here is to align these points, or to find the rotation matrix $R$ and translation $\vec{t}$ that align one point set to another. It is assumed that the points correspondences are known (in our case - the corresponding points have the same indices).

    Mathematically, this problem can be formulated in the following way:

    • Having $n$ overlapping points from two point clouds we can store them in two matrices $X_1, X_2 \in R^{m \times n}$, where $m$ is the number of degrees of freedom of the point.
    • Then, for each point $x_{1i}$ of $X_1$ and $x_{2i}$ of $X_2$ the following equality should hold: -$$R \vec{x}_{1i} + \vec{t} = \vec{x}_{2i}$$
    • Because of the errors, the following energy function should be minimized: -$$E(R, \vec{t}) = \sum_{i} || R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i}||^2.$$
    • The minimum of the objective function can be found by taking the derivative w.r.t. $R$ and $\vec{t}$.

    Translational component

    To find the optimal $\vec{t}$ value we have to fix $R$ and find the derivative w.r.t. $\vec{t}$: -$$ -\begin{array}{c} -\sum_{i} || R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i}||^2 = \\ -= \sum_{i} (R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i}) \cdot (R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i}) \\ -= \sum_{i} (R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i})^T (R \vec{x}_{1i} + \vec{t} - \vec{x}_{2i}) = \\ -= \sum_{i} (\vec{x}_{2i}^T R^T R \vec{x}_{1i} + \vec{x}_{1i}^T R^T \vec{t} - \vec{x}_{1i}^T R^T \vec{x}_{2i} + \vec{t} R \vec{x}_{1i} + \vec{t}^T \vec{t} - \vec{t} \vec{x}_{2i} - \vec{2}_{1i}^T R \vec{x}_{1i} - \vec{x}_{2i}^T \vec{t} + \vec{x}_{2i}^T \vec{x}_{2i}) -\end{array} -$$ -The gradient of this expression w.r.t. $\vec{t}$ must be zero at the minimum, therefore, dropping out the terms, do not depending on $\vec{t}$ we have: -$$ -\begin{array}{c} -\frac{\delta E}{\delta \vec{t}} = \frac{\delta}{\delta \vec{t}}\sum_{i} (2 \vec{t}^T R \vec{x}_{1i} - 2 \vec{t}^T \vec{x}_{2i} + \vec{t}^T \vec{t}) = \\ -= \sum_{i} (2 R \vec{x}_{1i} - 2 \vec{x}_{2i} + 2 \vec{t}) = \\ -= 2 n\vec{t} + \sum_{i}(2 R \vec{x}_{1i} - 2\vec{x}_{2i}) = \\ -= n\vec{t} + \sum_{i}(R \vec{x}_{1i} - \vec{x}_{2i}) -\end{array} -$$ -The critical point can be found by setting the gradient to zero: -$$ -\begin{array}{c} -n\vec{t} + \sum_{i}(R \vec{x}_{1i} - \vec{x}_{2i}) = 0 \\ -n\vec{t} = \sum_{i}(\vec{x}_{2i} - R \vec{x}_{1i}) \\ -n\vec{t} = \sum_{i}(\vec{x}_{2i} - R \vec{x}_{1i}) \\ -\vec{t} = \frac{\sum_{i}(\vec{x}_{2i} - R \vec{x}_{1i})}{n} -\end{array} -$$

    Rotational component

    To find the optimal $R$ we have to fix $\vec{t}$ and find the $R$ subject to constraint that $R$ is an orthogonal matrix: $R^T R = I_{3}$. See the detailed description and the derivation of the problem solution here.

    The orthogonal matrix $R$ minimizing the $||RX - Y||^2$ is given by $V U^T$, where SVD is applied to find out $XY^T = U\Sigma V^T$.

    Alternation

    Now, knowing how both the components can be found, one of the most typical strategies is the alternation (that is used in EM algorithm, clustering method and other iterative algorithms):

    1. Fix $R$ and minimize $E$ w.r.t. $\vec{t}$.
    2. Fix the newly found $\vec{t}$ and minimize $E$ w.r.t. $R$ under $R^T R = I_3$ constraint.
    3. Go to step 1 until convergence.

    Since in this procedure the $R$ and $\vec{t}$ are not optimized simultaneously, there is no guarantee that we reach the globally optimal solution, but in practice it works well.

    Example

    Suppose we have a set of 2d points $X_1$, and let’s rotate and translate them randomly, generating point set $X_2$:

    # generate points for X1 set
    -x1part1 = np.array([[0, 0], [4, 4], [4, 0], [0, 4], [2., 6.], [1, 1], [1, 3], [3, 3], [3, 1]]).T
    -thetas = np.linspace(0, 2 * np.pi, 24)
    -x1part2 = np.vstack([np.sin(thetas) * 3 + 8, np.cos(thetas) * 3 + 2])
    -x1part3 = np.vstack([np.sin(thetas) * 4 + 12, np.cos(thetas) * 3 + 4])
    -X1 = np.hstack([x1part1, x1part2, x1part3])
    -
    -# randomly pick angle and translation
    -rotationAngle = np.deg2rad(np.random.randint(-180, 180, 1)[0])
    -rotation = genRMatrix(rotationAngle)
    -translation = np.random.randint(-10, 10, 2).reshape((2, 1))
    -
    -# rotate our original point set X1
    -X2 = rotation @ X1 + translation
    -

    Our alternation step for $R$ and $t$ estimation is defined in pointCloudAlignmentStep function, the residual calculation between two point clouds, that is used as termination condition - in pointCloudResidual function.

    def pointCloudAlignmentStep(R, t, X1, X2):
    -    foundTransl = np.mean((X2 - R @ X1), axis=1).reshape((2, 1))
    -    u, s, v = np.linalg.svd(X1 @ (X2 - foundTransl).T)
    -    foundRot = v @ u.T
    -    return foundRot, foundTransl
    -
    -def pointCloudResidual(R, t, X1, X2):
    -    X2f = R @ X1 + t
    -    return np.linalg.norm(X2 - X2f)
    -
    # initial values
    -R = np.eye(2)
    -t = np.zeros((2, 1))
    -MIN_RESIDUAL_TO_TERMINATE = 1e-8
    -
    -# find the translation and rotation between two point clouds iteratively
    -while pointCloudResidual(R, t, X1, X2) > MIN_RESIDUAL_TO_TERMINATE:
    -    R, t = pointCloudAlignmentStep(R, t, X1, X2)
    -    
    -print("True rotation and translation between point sets.\nR:\n", rotation, "\nt:\n", translation)
    -print("Estimated rotation and translation between point sets.\nR:\n", R, "\nt:\n", t)
    -
    True rotation and translation between point sets.
    -R:
    - [[ 0.98162718  0.190809  ]
    - [-0.190809    0.98162718]] 
    -t:
    - [[9]
    - [7]]
    -Estimated rotation and translation between point sets.
    -R:
    - [[ 0.98162718  0.190809  ]
    - [-0.190809    0.98162718]] 
    -t:
    - [[9.]
    - [7.]]
    -

    In the end let’s visialize the algorithm actions:

    If you are interested in more complex topic or want to find the ready-to-use solution for your problem do not forget to check Point Cloud Library.

    The jupyter notebook is available here.

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/probability-density-transform/index.html b/posts/probability-density-transform/index.html deleted file mode 100644 index 7cd71bf..0000000 --- a/posts/probability-density-transform/index.html +++ /dev/null @@ -1,191 +0,0 @@ -Probability density transform · Aleksandr Mikoff's blog -

    Probability density transform

    PDF transformations

    While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5.

    Introduction

    Assume that we have random variable $X$ that has a continuous distribution on an interval $S \in \mathcal{R}$, its probability density function $f(x)$ is known. Suppose that we have a differentiable function $y$ from $S$ to $T \in \mathcal{R}$ and we apply this function to random variable $X$. We end up having a new random variable $Y = y(X)$. The question is how can we find the distribution of our random variable $Y$?

    The main theorem

    We know, by definition of probability that: -$$ -P(a \leq X < b) = \int_a^b f(x) dx. -$$

    Now, if $X = a$ the derived random variable $Y = y(a)$ and if $X = b$, $Y = y(b)$. Hence: -$$ -P(y(a) \leq Y < y(b)) = P(a \leq X < b) = \int_a^b f(x)dx = \int_{y(a)}^{y(b)}f(x(y))\frac{dx}{dy} dy. -$$ -In the last step we used the integration by substitution method, $x(y)$ is $x$ expressed in terms of $y$.

    The right-hand integrand $f(x(y))\frac{dx}{dy}$ is expressed wholly in terms of y, so can be replaced by function $g(y)$: -$$ -P(y(a) \leq Y < y(b)) = \int_{y(a)}^{y(b)} g(y) dy, -$$ -making it obvious that $g(y)$ is the pdf associated with $Y$.

    Now, taking the derivative, we have -$$ -g(y) = f(x(y)) \left|\frac{dx}{dy}\right|. -$$ -Here we have an absolute value because the formula is the same for increasing or decreasing $g$.

    While being compact, this formula is less clear. We must remember that $x$ on the ride side should be written in terms of $y$, but since we originally express $y$ in terms of $x$: $Y = y(X)$, the inverse function should be used there.

    To be a little bit more explicit let’s rewrite this equation using arbitrary function $r$: $y = r(x)$ applied to variable $x$, and giving back variable $y$, the aforementioned formula can be rendered as follows: -$$ -g(y) = f(r^{-1}(y))\left|\frac{d}{dy}r^{-1}(y)\right|. -$$

    What this formula states is that any density $g(y)$ can be obtained from a fixed density $f(x)$.

    Example 1

    Suppose that $Y = a + bX$, $a \in \mathcal{R}$ and $b \in \mathcal{R} \backslash {0}$, the distribution of $X$ is known: $f(x)$. What is the pdf of $Y$?

    Since $r(x) = y = a + bx$, we can find inverse function to be $r^{-1}(y) = x = \frac{y - a}{b}$ and the derivative $\frac{dx}{dy} = \frac{1}{b}$.

    Then $g(y) = f(\frac{y - a}{b})\frac{1}{|b|}$.

    Example 2

    Suppose that $f(x) = 2 x, x \in [0, 1)$ and $y(x) = x^2$.

    First, derive the inverse of the function $x(y) = \sqrt{y}$.

    Now, having:

    $$ -\begin{align} -f(x(y)) = 2 \sqrt{y}~\text{and}~ \frac{dx}{dy} = \frac{1}{2\sqrt{y}} -\end{align} -$$ -we can find $g(y)$ to be:

    $$ -g(y) = f(x(y)) \left|\frac{dx}{dy}\right| = 2 \sqrt{y} \frac{1}{2\sqrt{y}} = 1. -$$ -So after applying transformation $y(x) = x^2$ to random variable $X$ the function $y(x)$ transforms the triangular distribution into the uniform one.

    Example 3

    Given random variable $X \sim \mathcal{N}(6.5, 1.)$ we apply to it transformation function $y(x)$ specified as: -$$ -y(x) = \frac{1}{1 + \exp(5 - x)}. -$$ -Let $Y = y(X)$, and $p(y)$ be the pdf associated with $Y$. What is the pdf of $p(y)$?

    Let’s solve the problem using sympy:

    Spoiler -
    import sympy as sp
    -x = sp.Symbol('x', positive=True)
    -y = sp.Symbol('y')
    -
    -y_x = 1. / (1. + sp.exp(5 - x))
    -x_y = sp.solve(y_x - y, x)[0]
    -
    -dx_dy = x_y.diff(y).simplify()
    -
    -# print(sp.latex(x_y), sp.latex(dx_dy))
    -

    After solving for $y$ and finding the derivative we got: -$$ -\begin{align} -x(y) = \log{\left(- \frac{y}{y - 1.0} \right)} + 5.0 ~\text{and}~ \frac{dx}{dy} = -\frac{1.0}{y \left(y - 1\right)} -\end{align} -$$

    Spoiler -
    import sympy.stats
    -
    -X = sp.stats.Normal('x', 6.5, 1)
    -pdf_x = sp.stats.density(X)(x)
    -
    -pdf_y_simple_transform = pdf_x.subs({x:x_y})
    -pdf_y = (pdf_x * dx_dy).subs({x:x_y})
    -# print(sp.latex(pdf_y))
    -

    Giving the following $p(y)$: -$$ -p(y) = - \frac{0.5 \sqrt{2} e^{- 1.125 \left(0.666666666666667 \log{\left(- \frac{y}{y - 1.0} \right)} - 1\right)^{2}}}{\sqrt{\pi} y \left(y - 1\right)} -$$

    Now let’s plot everything. First let’s plot $p(x)$ and draw samples from $X$ distribution, plotting the histogram of their values (blue).

    Next, let’s apply the function $y(x)$ to each sample from the drawn set and plot its histogram too (orange).

    If we substitute $x$ by $y$ in $p(x)$, we will obtain a purple curve. However, this curve does not agree with the histogram!

    The correct curve for $p(y)$ density is shown in orange. In this case, in addition to substitution, we multiplied the result by $\frac{dx}{dy}$ to obtain the correct result.

    Spoiler -
    import numpy as np
    -from scipy.stats import norm
    -
    -# lambdify pdfs
    -p_x = sp.lambdify(x, pdf_x)
    -p_y_simple_transform = sp.lambdify(y, pdf_y_simple_transform)
    -p_y = sp.lambdify(y, pdf_y)
    -
    -# lambdify functions
    -y_x_func = sp.lambdify(x, y_x, modules = ["numpy"])
    -x_y_func = sp.lambdify(y, x_y)
    -dx_dy_func = sp.lambdify(y, dx_dy)
    -
    -x_interval = np.arange(0, 10, 0.1)
    -y_interval = np.linspace(0.0, 1.0, 1000, endpoint=False)
    -
    -# sample X from our distribution
    -X_sampled = norm(6.5, 1.).rvs(size = 100000)
    -# transform the values
    -Y = y_x_func(X_sampled)
    -
    -import matplotlib.pyplot as plt
    -%matplotlib inline
    -%config InlineBackend.figure_format='retina'
    -
    -fig, ax = plt.subplots(1, 1, figsize=(6, 6))
    -ax.plot(x_interval, p_x(x_interval), c='tab:blue')
    -ax.hist(X_sampled, density=True, bins = 50, color='tab:blue', alpha = 0.5)
    -ax.plot(x_interval, y_x_func(x_interval), c='tab:green')
    -ax.set(ylim=[0, 1], xlim=[0, 10])
    -ax.text(8, 0.25, '$p_x(x)$', color='tab:blue')
    -ax.text(8, 0.9, '$y(x)$', color='tab:green')
    -ax.grid(axis='both')
    -ax.set(xlabel='$x$')
    -ax.set(ylabel='$y$')
    -
    -ax2 = ax.twinx()
    -ax2.hist(Y, density=True, bins = 50, orientation="horizontal", color='tab:orange', alpha = 0.5)
    -ax2.plot(p_y(y_interval), y_interval, c='tab:orange')
    -ax2.plot(p_y_simple_transform(y_interval) / np.trapz(p_y_simple_transform(y_interval), y_interval), y_interval, c = 'tab:purple')
    -ax2.set(ylim=[0, 1])
    -ax2.text(3.8, 0.9, '$p_y(y)$', color='tab:orange')
    -ax2.grid(axis='both')
    -

    png

    Example 4

    For the next example, let’s consider coordinate transform.

    The polar coordinate transforms two numbers $(r, \theta)$, where radial distance $r \in [0, \inf)$ and polar angle $\theta \in [0, 2\pi)$ to a point on a plane. -$$ -\begin{align} -x = r \cos{\left(\theta \right)} \\ -y = r \sin{\left(\theta \right)} -\end{align} -$$

    Evaluating the derivatives we get: -$$ -\begin{align} -\frac{\partial x}{\partial r} &= \cos(\phi) \\ -\frac{\partial x}{\partial \phi} &= - r \sin(\phi) \\ -\frac{\partial y}{\partial r} &= \sin(\phi) \\ -\frac{\partial x}{\partial \phi} &= r \cos(\phi) -\end{align} -$$

    We can view this transformation as a multivariate change of variables. In such a case instead of $\frac{dx}{dy}$ we use the determinant of Jacobian ($\mathbf{J}$) that describes how the volume changes under the transformation.

    Now, if we sample a polar coordinate $(r, \theta)$ with probability $f(r, \theta)$, then the distribution $g(x, y)$ is given by: -$$ -g(x, y) = \frac{f(r, \theta)}{\det\mathbf{J}} = \frac{f(r, \theta)}{r}. -$$

    Now let’s repeat an experiments. First let’s take a look how the uniform grid changes after applying $x(r, \theta)$ and $y(r, \theta)$.

    Spoiler -
    import matplotlib.pyplot as plt
    -import numpy as np
    -from scipy.stats import uniform, multivariate_normal
    -
    -# sample points uniformly in polar space
    -r_interval = np.linspace(1e-1, 2, 40)
    -theta_interval = np.linspace(0.0, 2 * np.pi, 40)
    -r, theta = np.meshgrid(r_interval, theta_interval)
    -
    -# transform
    -x, y = r * np.cos(theta), r * np.sin(theta)
    -
    -fig = plt.figure(figsize=(9, 4))
    -ax1 = fig.add_subplot(131)
    -ax2 = fig.add_subplot(132, polar=True)
    -ax3 = fig.add_subplot(133)
    -
    -
    -ax1.scatter(r, theta, s = 10, edgecolor='none', alpha = 0.75, marker='.', c = 'tab:red')
    -ax1.set(xlim = [0, 2], ylim=[0, 2 * np.pi], xlabel='$r$, m', ylabel='$\\theta$, rad')
    -ax2.scatter(theta, r, s = 10, edgecolor='none', alpha = 0.75, marker='.', c = 'tab:red')
    -ax2.set_rmax(2)
    -ax2.set_rticks([0.5, 1, 1.5, 2])
    -ax2.set(xlabel='$r$', ylabel='$\\theta$')
    -
    -ax3.scatter(x, y, s = 10, edgecolor='none', alpha=0.75, marker='.', c = 'tab:red')
    -ax3.set(xlim =[-2, 2], ylim=[-2, 2], aspect='equal', xlabel='$x$, m', ylabel='$y$, m')
    -
    -fig.tight_layout()
    -

    png

    And finally, let’s draw samples from our $f(r, \theta)$ distribution, apply our transform, and plot the transformed samples. The top row of the plot displays the transformed samples, while the bottom row shows the $f(r, \theta)$ and $g(x, y)$ pdfs.

    Spoiler -
    # pdf for r, theta multivariate distribution
    -f_rtheta = uniform(0, 2).pdf(r) * uniform(0, 2 * np.pi).pdf(theta)
    -g_xy = f_rtheta / r
    -
    -fig, ax = plt.subplots(2, 2, figsize = (6, 7))
    -ax1 = plt.subplot(221)
    -ax2 = plt.subplot(222)
    -ax3 = plt.subplot(223)
    -ax4 = plt.subplot(224)
    -
    -r_sampled, theta_sampled = uniform(0, 2).rvs(size=10000), uniform(0, 2 * np.pi).rvs(size=10000)
    -x_sampled, y_sampled = r_sampled * np.cos(theta_sampled), r_sampled * np.sin(theta_sampled)
    -
    -ax1.scatter(r_sampled, theta_sampled, s = 10, edgecolor='none', alpha = 0.1)
    -ax1.set(xlim = [0, 2], ylim=[0, 2 * np.pi], aspect='equal', xlabel='$r$, m', ylabel='$\\theta$, rad', title='Samples')
    -ax2.scatter(x_sampled, y_sampled, s = 10, edgecolor='none', alpha=0.1)
    -ax2.set(xlim = [-2, 2], ylim=[-2, 2], aspect='equal', xlabel='$x$, m', ylabel='$y$, m', title='Samples')
    -ax3.contourf(r, theta, f_rtheta, vmin=0, vmax = 0.2)
    -ax3.set(xlim = [0, 2], ylim=[0, 2 * np.pi], aspect='equal', xlabel='$r$, m', ylabel='$\\theta$, rad', title='pdf')
    -cf = ax4.contourf(x, y, g_xy, vmin=0, vmax = 0.2, extend='max', cmap='viridis')
    -ax4.set(xlim = [-2, 2], ylim=[-2, 2], aspect='equal', xlabel='$x$, m', ylabel='$y$, m', title='pdf')
    -fig.colorbar(cf, ax=(ax1, ax2, ax3, ax4), orientation='horizontal')
    -

    png

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/posts/uncertainty-propagation.md/index.html b/posts/uncertainty-propagation.md/index.html deleted file mode 100644 index 1426d55..0000000 --- a/posts/uncertainty-propagation.md/index.html +++ /dev/null @@ -1,543 +0,0 @@ -Uncertainty propagation with and without Lie groups · Aleksandr Mikoff's blog -

    Uncertainty propagation with and without Lie groups

    Uncertainty propagation with and without Lie groups and algebras

    The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse.

    In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What’s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates.

    Small Lie notes

    These notes are made for myself and summarize the info from Joan Sola paper “A Micro Lie Theory”1 and Barfoot’s “State estimation for robotics”2.

    Notation

    • Lie group $\mathcal{M}$ - a smooth manifold whose elements satisfy four group axioms: closure, identity, inverse and associativity. The manifold looks the same at its every point (no spikes, edges, etc.), thus all tangent spaces at any point are similar and have the same structure.
    • Group compositions and actions.
      • Given group elements $\mathcal{X}, \mathcal{Y} \in \mathcal{M}$ there is a composition $\circ$, that gives the compositions of group elements $\mathcal{X}, \mathcal{Y}$ that also lies on the manifold. There is one special element of the group, that is called identity element $\mathcal{E}$. The composition of any other group element $\mathcal{Z}$ with $\mathcal{E}$ produces $\mathcal{Z}$: $\forall \mathcal{Z} \in \mathcal{M} : \mathcal{E} \circ \mathcal{Z} = \mathcal{Z} \circ \mathcal{E} = \mathcal{Z}$
      • Given group element $\mathcal{X}$ and some other set $\mathcal{v}$, $\mathcal{X}\cdot \mathcal{v}$ is the action of $\mathcal{X}$ on $\mathcal{v}$. For example, for $SO(3)$ group its action on vector is rotation: $\mathbf{R}\cdot\mathbf{x} = \mathbf{R}\mathbf{x}$, for $SE(3)$ the action is the rotation plus translation: $\mathbf{H}\cdot\mathbf{x} = \mathbf{R}\mathbf{x} + \mathbf{t}$.
    • Lie algebra $\mathcal{m} = \mathcal{T}_{\mathcal{E}} \mathcal{M}$. It is the tangent space to $\mathcal{M}$ at the identity element. The beaty of $\mathcal{m}$ is that it is a vector space and we can use the standard machinery for it. Lie algebras can be defined to any group element $\mathcal{X}$, they can be viewed as local tangent spaces $\mathcal{T}_{\mathcal{X}}\mathcal{M}$, having vectors in its local coordinates.
    • Lie algebra elements $\tau$:
      • Can be identified as vectors in $\mathcal{R}^n$, where $n$ is the number degrees of freedom of group $\mathcal{M}$.
      • Explicitly denoted with a hat decorator: $\wedge: \mathbf{R}^n \to \mathcal{m}$.
      • Represented as $n$-length vectors, such operation is usally encoded with $\vee: \mathcal{m} \to \mathbf{R}^n $ symbol.
    • The exponential map, generally known as retraction, $\exp$ allows to convert elements of Lie algebra $\mathcal{m}$ into elements of Lie group $\mathcal{M}$: $\exp: \mathcal{m} \to \mathcal{M}$. It is an exact conversion.
      The capitalized $Exp$ is used to denote $\mathbf{R}^n \to \mathcal{M}$ conversion.
    • The logarithm map $\log: \mathcal{M} \to \mathcal{m}$ allows to go vice versa.
    • Adjoint of $\mathcal{m}$. Vectors of the tangent space at element $\mathcal{X}$ can be transformed to the tangent space at $\mathcal{E}$ through a adjoint transform, that is linear: $ad: \mathcal{T}_{\mathcal{X}}\mathcal{M} \to \mathcal{T}_{\mathcal{E}}\mathcal{M}$.
    • Adjoint of $\mathcal{M}$. The representations of a given group element $\mathcal{X}$ at the $\mathcal{T}_{\mathcal{E}}\mathcal{M}$ vector space: $Ad$:
      $Ad_{\mathcal{X}} = {}^\mathcal{E}\tau^{\wedge} = \mathcal{X}~{}^\mathcal{X}\tau^{\wedge}\mathcal{X}^{-1}$.
      The adjoint is linear and homomorphic. -There is also adjoint matrix:
      $\mathbf{Ad}_\mathcal{X}: \mathbf{R}^n \to \mathbf{R}^n;~{}^\mathcal{X}\tau \mapsto {}^\mathcal{E}\tau = \mathbf{Ad}_\mathcal{X} {}^\mathcal{X}\tau$.
    • Plus $\oplus$ and minus $\ominus$ operators: the combination of $Exp$ or $Log$ operator with one composition.
      • There is a right operator, where $^\mathcal{X}\tau$ belongs to the tangent space $\mathcal{T}_{\mathcal{X}}\mathcal{M}$. It is said that ${}^\mathcal{X}\tau$ is expressed in local frame at $\mathcal{X}$:
        $\mathcal{Y} = \mathcal{X} \oplus {}^\mathcal{X}\tau = \mathcal{X} \circ Exp({}^\mathcal{X}\mathcal{\tau}) \in \mathcal{M}$
        ${}^\mathcal{X}\tau = \mathcal{Y} \ominus \mathcal{X} = Log(\mathcal{X}^{-1}\circ\mathcal{Y}) \in \mathcal{T}_{\mathcal{X}}\mathcal{M}$
      • In the left operator $Exp({}^\mathcal{E}\mathcal{\tau})$ is on the left meaning that ${}^\mathcal{E}\mathcal{\tau}$ expressed in global frame: -$\mathcal{Y} = {}^\mathcal{E}\tau \oplus \mathcal{X} = Exp({}^\mathcal{E}\mathcal{\tau}) \circ \mathcal{X} \in \mathcal{M}$
        ${}^\mathcal{E}\tau = \mathcal{Y} \ominus \mathcal{X} = Log(\mathcal{Y}\circ\mathcal{X}^{-1}) \in \mathcal{T}_{\mathcal{E}}\mathcal{M}$
        The choice of left or right operator specifies the perturbation frame: they can be defined globally or locally.

    Uncertainty on manifolds

    If there is a local perturbation $\mathcal{\tau}$ near the operation point $\bar{\mathcal{X}} \in \mathcal{M}$ in the tangent vector space $\mathcal{T}_{\bar{\mathcal{X}}}\mathcal{M}$, that can be defined as: -$$ -\mathcal{X} = \bar{\mathcal{X}}\oplus\tau,~~~ \tau = \mathcal{X}\ominus\bar{\mathcal{X}} \in \mathcal{T}_{\bar{\mathcal{X}}}\mathcal{M}, -$$ -the covariances matrices can be properly defined on tangent space through standard expectation: -$$ -\Sigma_{\mathcal{X}} = \mathbf{E}\left[\tau \tau ^T \right] \in \mathbf{R}^{n\times n} -$$

    If we work with perturbations, expressed in global reference frame, then: -$$ -\mathcal{X} = \tau\oplus\bar{\mathcal{X}},~~~ \tau = \mathcal{X}\ominus\bar{\mathcal{X}} \in \mathcal{T}_{\mathcal{E}}\mathcal{M} -$$

    If we want to propagate the covariance through $f : \mathcal{M} \to \mathcal{N}; \mathcal{X}\mapsto\mathcal{Y} = f(\mathcal{X})$, we have to linearize: -$$ -\Sigma_{\mathcal{Y}} \approx \frac{Df}{D\mathcal{X}}\Sigma_{\mathcal{X}}\frac{Df}{D\mathcal{X}}^T. -$$

    SO(3) and SE(3)

    Lie group composition operator for SO(3) and SE(3) is just a matrix multiplication.

    Plotting utilities

    Spoiler -
    
    -import numpy as np
    -import matplotlib.pylab as plt
    -np.set_printoptions(precision=3, suppress=True, linewidth=120)
    -
    -%matplotlib inline
    -%config InlineBackend.figure_format='retina'
    -
    -def density_scatter(x , y, ax = None, fig = None, sort = True, bins = 20, cbar=True, **kwargs ):
    -    from matplotlib import cm
    -    from matplotlib.colors import Normalize 
    -    from scipy.interpolate import interpn
    -    """
    -    Scatter plot colored by 2d histogram
    -    """
    -    if ax is None :
    -        fig , ax = plt.subplots()
    -    data, x_e, y_e = np.histogram2d(x, y, bins = bins, density = False )
    -    z = interpn((0.5*(x_e[1:] + x_e[:-1]), 0.5*(y_e[1:]+y_e[:-1]) ), data ,np.vstack([x,y]).T , method = "splinef2d", bounds_error = False)
    -
    -    #To be sure to plot all data
    -    z[np.where(np.isnan(z))] = 0.0
    -    z = z / np.sum(z)
    -    
    -    # Sort the points by density, so that the densest points are plotted last
    -    if sort :
    -        idx = z.argsort()
    -        x, y, z = x[idx], y[idx], z[idx]
    -        
    -    sc = ax.scatter( x, y, c=z, marker='.', s=1.0, **kwargs )
    -
    -    if cbar:
    -        norm = Normalize(vmin = np.min(z), vmax = np.max(z))
    -        cbar = fig.colorbar(cm.ScalarMappable(norm = norm, **kwargs), ax=ax)
    -        cbar.ax.set_ylabel('Density')
    -    
    -    levels = [0.5, 0.75, 0.9]
    -    H, X, Y = data, x_e, y_e
    -    if True:
    -        # Compute the density levels.
    -        Hflat = H.flatten()
    -        inds = np.argsort(Hflat)[::-1]
    -        Hflat = Hflat[inds]
    -        sm = np.cumsum(Hflat)
    -        sm /= sm[-1]
    -        V = np.empty(len(levels))
    -        for i, v0 in enumerate(levels):
    -            try:
    -                V[i] = Hflat[sm <= v0][-1]
    -            except IndexError:
    -                V[i] = Hflat[0]
    -        V.sort()
    -        m = np.diff(V) == 0
    -        if np.any(m) and not quiet:
    -            logging.warning("Too few points to create valid contours")
    -        while np.any(m):
    -            V[np.where(m)[0][0]] *= 1.0 - 1e-4
    -            m = np.diff(V) == 0
    -        V.sort()
    -
    -        # Compute the bin centers.
    -        X1, Y1 = 0.5 * (X[1:] + X[:-1]), 0.5 * (Y[1:] + Y[:-1])
    -
    -        # Extend the array for the sake of the contours at the plot edges.
    -        H2 = H.min() + np.zeros((H.shape[0] + 4, H.shape[1] + 4))
    -        H2[2:-2, 2:-2] = H
    -        H2[2:-2, 1] = H[:, 0]
    -        H2[2:-2, -2] = H[:, -1]
    -        H2[1, 2:-2] = H[0]
    -        H2[-2, 2:-2] = H[-1]
    -        H2[1, 1] = H[0, 0]
    -        H2[1, -2] = H[0, -1]
    -        H2[-2, 1] = H[-1, 0]
    -        H2[-2, -2] = H[-1, -1]
    -        X2 = np.concatenate(
    -            [
    -                X1[0] + np.array([-2, -1]) * np.diff(X1[:2]),
    -                X1,
    -                X1[-1] + np.array([1, 2]) * np.diff(X1[-2:]),
    -            ]
    -        )
    -        Y2 = np.concatenate(
    -            [
    -                Y1[0] + np.array([-2, -1]) * np.diff(Y1[:2]),
    -                Y1,
    -                Y1[-1] + np.array([1, 2]) * np.diff(Y1[-2:]),
    -            ]
    -        )
    -        
    -        ax.contour(X2, Y2, H2.T, V, alpha = 0.5)
    -
    -    return ax
    -
    -def vis_distribution(fig, ax, mean, cov, transform, N = 10000):
    -    from collections import Iterable
    -
    -    samples = np.random.multivariate_normal(mean=np.zeros(6), cov=Sigma_next, size=N).T
    -    samples_se3 = np.zeros((N, 2))
    -    for i, s in enumerate(samples.T):
    -        sample_se3 = transform @ SE3.exp(s)
    -        samples_se3[i, 0] = sample_se3[0, 3]
    -        samples_se3[i, 1] = sample_se3[1, 3]
    -    
    -    if isinstance(ax, Iterable):
    -        ax1, ax2 = ax
    -        density_scatter(samples[0, :], samples[1, :], ax1, fig, cmap=plt.cm.cool, cbar = False)
    -        ax1.set(xlabel = 'x, meters', ylabel = 'y, meters')
    -        ax1.axis('equal')
    -        ax2.set(xlabel = 'x, meters', ylabel = 'y, meters')
    -        ax1.set_title('Uncertainty at tangent space, $\mathfrak {g}$')
    -    else:
    -        ax2 = ax
    -    density_scatter(samples_se3[:,0], samples_se3[:,1], ax2, fig, cmap=plt.cm.cool, cbar = False)
    -    meanse3 = np.mean(samples_se3, axis = 0)
    -    ax2.scatter([meanse3[0]], [meanse3[1]], marker = 'x', s = 150, c = 'black')
    -    ax2.set(xlabel = 'x, meters', ylabel = 'y, meters')
    -    ax2.axis('equal')
    -    ax2.set_title('Uncertainty at manifold, $\mathcal{G}$')
    -

    Lie algebras and groups operations

    First, let’s define all the needed functions to operate on SO(3) and SE(3) groups using Lie algebras. In the code I explicitly specify the implemented equation’s numbers from Barfoot’s book on robotics. Here we will use the left-hand convention, that means that the state is expressed with respect to the moving, or robot perspective. Therefore the increments are represented in global coordinate frame and applied from the left side. Our $\mathbf{T}_{next} = \mathbf{T}_{bw}$, this matrix represents the transformation of the world w.r.t. the body: to plot the robot pose in global coordinate frame we have to invert it, to do so we find the transformation of the body w.r.t. the world: $\mathbf{T}_{wb}$

    Written using the matrices operations we have: -$$ -\mathbf{T}_{b_{i+1}w} = \mathbf{T}_{b_{i+1}b_{i}} \mathbf{T}_{b_{i}w}. -$$ -It means that to we want to obtain the transformation of the world w.r.t. the body at time step $i+1$ we have to multiply:

    • the transformation of the world w.r.t. the body at time step $i$ $-$ $\mathbf{T}_{b_{i}w}$;
    • with transformation increment of the body from time step $i$ to time step $i+1$ $-$ $\mathbf{T}_{b_{i+1}b_{i}}$.

    In this case our $\mathbf{T}_{b_{i+1}b_{i}}$ has trivial form, but the important note here is that its $(0, 0)$ elements has value $-1$, describing the pose of at time $i$ w.r.t. to the pose at time $i+1$, and equal to $[-1, 0, 0]^T$ if we move to the right (initially I found this $-1$ counter-intuitive).

    Of course, this notation will be much more intuitive if we work with transform matrix $T_{wb}$, that represents the transform of the body w.r.t. world. In such a case we have: -$$ -\mathbf{T}_{w b_{i+1}} = \mathbf{T}_{w b_{i}}\mathbf{T}_{b_{i}b_{i+1}}, -$$ -and our increment is equal to $1$, instead of $-1$. These differences are very important when we work with transformations and can lead to dramatic results when mixed order is used. The latter approach typically used in robotics and when we work with IMU, the former one (left) - in computer vision 3, 4.

    Spoiler -
    from abc import ABC, abstractmethod
    -import numpy as np
    -
    -class LieGroup(ABC):
    -    @staticmethod
    -    @abstractmethod
    -    def wedge(tau):
    -        pass
    -    
    -    @staticmethod
    -    @abstractmethod
    -    def exp(tau):
    -        pass
    -    
    -    @staticmethod
    -    @abstractmethod
    -    def log(T):
    -        pass
    -    
    -    @staticmethod
    -    @abstractmethod
    -    def Ad(tau):
    -        pass
    -    
    -class SO3(LieGroup):
    -    @staticmethod
    -    def wedge(phi):
    -        '''B. eq. (7.10)'''
    -        e1, e2, e3 = np.squeeze(phi)
    -        return np.array([[0., -e3, e2],
    -                         [e3, 0., -e1],
    -                         [-e2, e1, 0.]])
    -    
    -    @staticmethod
    -    def exp(phi):
    -        '''B. eq. (7.23)'''
    -        if np.linalg.norm(phi) < 1e-8:
    -            return np.eye(3) + SO3.wedge(phi)
    -        else:
    -            norm = np.linalg.norm(phi)
    -            a = phi / norm
    -            return np.cos(norm) * np.eye(3) + (1.0 - np.cos(norm)) * np.outer(a, a.T) + np.sin(norm) * SO3.wedge(a)
    -        
    -    @staticmethod
    -    def Ad(phi):
    -        return SO3.exp(phi)
    -    
    -    @staticmethod
    -    def log(R):
    -        '''B. eq. (7.28)'''
    -        phi = np.arccos((np.trace(R) - 1.0) / 2.0)
    -        if phi ** 2 < 1e-8:
    -            return np.zeros(3)
    -        else:
    -            w = (phi / (2. * np.sin(phi))) * (R - R.T)
    -            return np.array([[w[2, 1]], [w[0, 2]], [w[1, 0]]])
    -        
    -    @staticmethod
    -    def inv(R):
    -        return R.T[:,:]
    -        
    -    @staticmethod
    -    def J(phi):
    -        '''B. eq. (7.37a), left Jacobian of SO3'''
    -        if np.linalg.norm(phi) < 1e-8:
    -            return np.eye(3) + 0.5 * SO3.wedge(phi)
    -        else:
    -            norm = np.linalg.norm(phi)
    -            a = phi / norm
    -            return np.sin(norm) / norm * np.eye(3) + (1.0 - np.sin(norm)/norm) * np.outer(a, a.T)\
    -                + (1.0 - np.cos(norm)) / norm * SO3.wedge(a)
    -      
    -    @staticmethod
    -    def J_inv(phi):
    -        '''B. eq. (7.37b), J^{-1}'''
    -        if np.linalg.norm(phi) < 1e-8:
    -            return np.eye(3) - 0.5 * SO3.wedge(phi)
    -        else:
    -            norm = np.linalg.norm(phi)
    -            a = phi / norm
    -            cotHalfP = 1. / np.tan(norm/2)
    -            return norm / 2 * cotHalfP * np.eye(3) + (1.0 - norm/2 * cotHalfP) * np.outer(a, a.T)\
    -                - norm/2 * SO3.wedge(a)
    -        
    -    
    -class SE3(LieGroup):
    -    @staticmethod
    -    def split_xi(xi):
    -        '''return translational and rotational component, B. eq. (7.14)'''
    -        xi = np.squeeze(xi)
    -        return xi[0:3], xi[3:6]
    -    
    -    @staticmethod
    -    def wedge(xi):
    -        '''B. eq. (7.14)'''
    -        t, phi = SE3.split_xi(xi)
    -        wedge = np.zeros((4,4))
    -        wedge[0:3,0:3] = SO3.wedge(phi)
    -        wedge[0:3, 3] = t
    -        return wedge
    -    
    -    @staticmethod
    -    def exp(xi):
    -        '''B. eq. (7.44)'''
    -        t, phi = SE3.split_xi(xi)
    -        if np.linalg.norm(phi) < 1e-8:
    -            return np.eye(4) + SE3.wedge(xi)
    -        else:
    -            norm = np.linalg.norm(phi)
    -            se3_wedge = SE3.wedge(xi)
    -            return np.eye(4) + se3_wedge + (1.0 - np.cos(norm)) / norm ** 2 * se3_wedge @ se3_wedge\
    -                + (norm - np.sin(norm)) / norm ** 3 * se3_wedge @ se3_wedge @ se3_wedge
    -    
    -    @staticmethod
    -    def Ad(xi):
    -        '''B. eq. (7.45)'''
    -        if xi.shape == (4, 4):
    -            xi = SE3.log(xi)
    -        
    -        t, phi = SE3.split_xi(xi)
    -
    -        C = SO3.exp(phi)
    -
    -        Tau = np.zeros((6, 6))
    -        Tau[0:3,0:3] = Tau[3:6,3:6] = C
    -        Tau[0:3,3:6] = SO3.wedge(SO3.J(phi) @ t) @ C
    -
    -        return Tau
    -    
    -    @staticmethod
    -    def log(T):
    -        '''B. eq. (7.35)'''
    -        t = T[0:3, 3]
    -        w = SO3.log(T[0:3, 0:3])
    -
    -        # 7.35 p = invJ @ t
    -        p = SO3.J_inv(w) @ t
    -
    -        return np.hstack((p.flatten(), w.flatten()))
    -    
    -    @staticmethod
    -    def inv(T):
    -        T_inv = np.eye(4)
    -        T_inv[0:3, 0:3] = T[0:3, 0:3].T
    -        T_inv[0:3, 3] = - T_inv[0:3, 0:3] @ T[0:3, 3]
    -        return T_inv
    -

    Pose compound experiments

    Now assume that we have some initial pose and we are absolutely confident about it. Then we make 500 moves forward (right direction) and on each move we are absolutely confident about travelled distance (it is equal to 1 meter), but not so certain about direction, it’s associated uncertainty is equal to $\sigma = 2 ^\circ$.

    Monte-carlo

    At first, let’s directly simulate it. To do so we would like to generate 1000 sequences of 100 moves. On each move we are uncertain about our direction and therefore randomly draw the angle from the normal distribution.

    import matplotlib.pylab as plt
    -
    -N_steps = 500
    -step_directed, sigma_direction_rad = -1.0, np.deg2rad(2.0)
    -
    -P = 1000
    -
    -coords = np.zeros((P, 2, N_steps))
    -
    -for p in range(0, P):
    -    T_next = np.eye(4)
    -    for i in range(0, N_steps):
    -        # perturb our motion
    -        T_move = SE3.exp([step_directed, 0., 0., 0., 0., np.random.normal(0.0, sigma_direction_rad)])
    -        # move the particle
    -        T_next = T_move @ T_next
    -        # save the state
    -        coords[p, :,i] = np.linalg.inv(T_next)[0:2, 3]
    -
    -fig, ax = plt.subplots(1, 1, figsize = (5, 5))
    -for p in range(0, P):
    -    ax.plot(coords[p, 0,:], coords[p, 1,:], c='b', alpha=0.1)
    -ax.scatter(coords[:, 0, -1], coords[:, 1, -1], c='r', s = 2.0)
    -ax.axis("equal")
    -ax.set(xlabel = 'x, meters', ylabel = 'y, meters')
    -ax.set_title("Monte-carlo simulation")
    -
    -print("Mean:\n", np.mean(coords[:, :, -1], axis = 0))
    -print("Covariance:\n", np.cov(coords[:,:,-1].T))
    -
    Mean:
    - [430.06    6.476]
    -Covariance:
    - [[ 4857.184 -1585.499]
    - [-1585.499 36559.492]]
    -

    png

    After 100 steps we see famously known banana-shaped distribution, that can not be represented using gaussian distribution! But wait, in next subsection we will note that this “banana” can be described by Gaussian in our tangent space $\mathcal{T}_{\mathcal{X}}\mathcal{M}$ and mapped from vector tangent space to Lie group conveniently.

    First-order approximation

    Now let’s first show the results of the covariance estimation using first-order method, where we throw away all Taylor expansion terms except the first one.

    # True pose on the first iteration
    -T_0_true = np.eye(4)
    -
    -T_move = np.array([[1., 0., 0.,  step_directed],
    -                    [0., 1., 0., 0.],
    -                    [0., 0., 1., 0.],
    -                    [0., 0., 0., 1.]])
    -Sigma_move = np.diag([0., 0., 0., 0., 0., sigma_direction_rad**2])
    -
    -T_next = T_0_true
    -Sigma_next = np.zeros((6, 6))
    -for _ in range(0, N_steps):
    -    T_next = T_move @ T_next 
    -    
    -    # second-order term
    -    Ad_T1 = SE3.Ad(T_next)
    -    Sigma_move_dash = Ad_T1 @ Sigma_move @ Ad_T1.T
    -    
    -    Sigma_next = Sigma_next + Sigma_move_dash
    -    
    -fig, ax = plt.subplots(1, 2, figsize = (10, 5))
    -vis_distribution(fig, ax, np.zeros(6), Sigma_next, np.linalg.inv(T_next), N = 10000)
    -print("Covariance:\n", Sigma_next[0:2, 0:2])
    -
    <ipython-input-1-14b40a8f213d>:94: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
    -  from collections import Iterable
    -
    -
    -Covariance:
    - [[    0.       0.  ]
    - [    0.   50921.98]]
    -

    png

    When we closely look at these samples we immediately understand why it is beneficial to have the Gaussian in tangent space (Lie algebra elements) and then retract it to our manifold (Lie group elements - rotation matrices!). Doing so we note that the banana-shaped distribution is actually a Gaussian 5 and we can correctly represent the uncertainties and manipulate them!

    However, we still observe the inaccuracies in our representation and what is more important: our uncertainty is underestimated and equal to zero along Y-coordinate axis. To improve the representation let’s leverage the Taylor series even more and take into account higher order terms.

    Unscented transform

    Now let’s compound the poses, or move poses from first to last using unscented transform. This technique is often used when the nonlinear transformation of a probability distribution is required.

    Its underlying idea is the following:

    • Take a set of particles, or sigma points, from our prior distribution.
    • Apply the transform function $f$ to each point.
    • Calculate the mean and covariance of the transformed points statistically.

    Do we have any improvement? Let’s check!

    T_next = T_0_true
    -Sigma_next = np.zeros((6, 6))
    -
    -L = 12
    -kappa = 1.0
    -
    -sigma_weights = np.zeros(2 * L + 1)
    -sigma_weights[0] = kappa / (kappa + L)
    -for i in range(1, len(sigma_weights)):
    -    sigma_weights[i] = 1.0 / (2 * kappa + 2 * L)
    -
    -for _ in range(0, 500):
    -    Sigma_total = np.zeros((12, 12)) + np.eye(12) * 1e-9
    -    Sigma_total[0:6, 0:6] += Sigma_next
    -    Sigma_total[6:12, 6:12] += Sigma_move
    -    
    -    S = np.linalg.cholesky(Sigma_total)
    -
    -    sigma_points = np.zeros((L, 2 * L + 1))
    -    sigma_points[:, [0]] = np.zeros((12, 1))
    -    for i in range(0, (sigma_points.shape[1] - 1) // 2):
    -        sigma_points[:, [1 + i]] = np.sqrt(L + kappa) * S[:,[i]]
    -        sigma_points[:, [1 + i + L]] = -np.sqrt(L + kappa) * S[:,[i]]
    -        
    -    T_new = T_move @ T_next
    -        
    -    xis = np.zeros((6, 2 * L + 1))
    -    for i in range(0, 2 * L + 1):
    -        xi_next = sigma_points[0:6, [i]]
    -        xi_move = sigma_points[6:12, [i]]
    -        
    -        xis[:, i] = SE3.log(SE3.exp(xi_next) @ T_next @ SE3.exp(xi_move) @ T_move @ np.linalg.inv(T_new))
    -        
    -    xis_mean = (sigma_weights @ xis.T).reshape((6, 1))
    -    xis_sigma = np.zeros((6, 6))
    -    for i in range(0, 2 * L + 1):
    -        cov = (xis[:,[i]] - xis_mean) @ (xis[:,[i]] - xis_mean).T
    -        xis_sigma += sigma_weights[i] * cov
    -        
    -    T_next = T_new
    -    Sigma_next = xis_sigma
    -    
    -fig, ax = plt.subplots(1, 2, figsize = (10, 5))
    -vis_distribution(fig, ax, np.zeros(6), Sigma_next, np.linalg.inv(T_next), N = 10000)
    -print("Covariance:\n", Sigma_next[0:2, 0:2])
    -
    Covariance:
    - [[    0.        0.   ]
    - [    0.    50617.404]]
    -

    png

    Unfortunately, no, the covariance we end up with is the same as for previous method. As noted in 6, 7, such representation of the mean is valid up to the second order, so there is no need to apply unscented transform - we won’t be able to get better results with it.

    Considering 4-th order terms

    The derivation of higher terms of covariance update is presented in paper 8. Here I will just propagate the covariance while using their derivations.

    def linOp(A):
    -    return -np.trace(A) * np.eye(3) + A
    -
    -def linOpB(A, B):
    -    return linOp(A) @ linOp(B) + linOp(B @ A)
    -
    -T_next = T_0_true
    -Sigma_next = np.zeros((6, 6))
    -for _ in range(0, 500):
    -    T_next = T_next @ T_move
    -    
    -    # second-order term
    -    Ad_T1 = SE3.Ad(T_next)
    -    Sigma_move_dash = Ad_T1 @ Sigma_move @ Ad_T1.T
    -    
    -    # Temp variables
    -    Sigma1_PosPos = Sigma_next[0:3,0:3]
    -    Sigma1_PosPhi = Sigma_next[0:3,3:6]
    -    Sigma1_PhiPhi = Sigma_next[3:6,3:6]
    -    Sigma2_PosPos = Sigma_move_dash[0:3,0:3]
    -    Sigma2_PosPhi = Sigma_move_dash[0:3,3:6]
    -    Sigma2_PhiPhi = Sigma_move_dash[3:6,3:6]
    -    
    -    # third and fourth terms
    -    A1 = np.zeros((6,6))
    -    A1[0:3,0:3] = A1[3:6,3:6] = linOp(Sigma1_PhiPhi)
    -    A1[0:3,3:6] = linOp(Sigma1_PosPhi + Sigma1_PosPhi.T)
    -    A2_dashed = np.zeros((6,6))
    -    A2_dashed[0:3,0:3] = A2_dashed[3:6,3:6] = linOp(Sigma2_PhiPhi)
    -    A2_dashed[0:3,3:6] = linOp(Sigma2_PosPhi + Sigma2_PosPhi.T)
    -    
    -    B_PosPos = linOpB(Sigma1_PhiPhi, Sigma2_PosPos) + linOpB(Sigma1_PosPhi.T, Sigma2_PosPhi)\
    -        + linOpB(Sigma1_PosPhi, Sigma2_PosPhi.T) + linOpB(Sigma1_PosPos, Sigma2_PhiPhi)
    -    B_PosPhi = linOpB(Sigma1_PhiPhi, Sigma2_PosPhi.T) + linOpB(Sigma1_PosPhi.T, Sigma2_PhiPhi)
    -    B_PhiPhi = linOpB(Sigma1_PhiPhi, Sigma2_PhiPhi)
    -    B = np.zeros((6, 6))
    -    B[0:3,0:3] = B_PosPos
    -    B[0:3,3:6] = B_PosPhi
    -    B[3:6,0:3] = B_PosPhi.T
    -    B[3:6,3:6] = B_PhiPhi
    -    
    -
    -    Sigma_next = Sigma_next + Sigma_move_dash\
    -        + 1/12.0 * (A1 @ Sigma_move_dash + Sigma_move_dash @ A1.T 
    -                    + A2_dashed @ Sigma_next + Sigma_next @ A2_dashed.T)\
    -        + 1/4.0 * B
    -    
    -fig, ax = plt.subplots(1, 2, figsize = (10, 5))
    -vis_distribution(fig, ax, np.zeros(6), Sigma_next, np.linalg.inv(T_next), N = 10000)
    -print("Covariance:\n", Sigma_next[0:2, 0:2])
    -
    Covariance:
    - [[ 1858.357     0.   ]
    - [    0.    49714.573]]
    -

    png

    Bingo, now our uncertainty has much better shape, and we see that the higher order terms definitely helped us to end up with decent covariance that represents our true uncertainty more accurately and precise.

    The usage of higher order terms is not always an option: first, the derivations are usually quite tricky and second, the computation complexity also increases.

    Bonus

    In the last section I would like to compare the performance of vanilla-old Extended Kalman Filter for our toy-problem. Let’s reformulate the problem: we have a robot that moves in one direction with perfectly known speed (1 m/s) and with slightly uncertain steering direction (as before, $2^\circ$). If our state $\mathbf{x}$ includes position and orientation, and our controls $\mathbf{u}$ are robot linear and angular speeds, we have the following state and control vectors: -$$ -\mathbf{x} = \displaystyle \left[\begin{matrix}x & y & z & \phi & \theta & \psi\end{matrix}\right], -$$ -$$ -\mathbf{u} = \displaystyle \left[\begin{matrix}v_{x} & v_{y} & v_{z} & w_{x} & w_{y} & w_{z}\end{matrix}\right]. -$$

    and their dynamics have the following forms: -$$ -\displaystyle \begin{bmatrix}\Delta{t} \left(v_{x} \cos{\left(\psi \right)} \cos{\left(\theta \right)} + v_{y} \left(\sin{\left(\phi \right)} \sin{\left(\theta \right)} \cos{\left(\psi \right)} - \sin{\left(\psi \right)} \cos{\left(\phi \right)}\right) + v_{z} \left(\sin{\left(\phi \right)} \sin{\left(\psi \right)} + \sin{\left(\theta \right)} \cos{\left(\phi \right)} \cos{\left(\psi \right)}\right)\right) + x \\ \Delta{t} \left(v_{x} \sin{\left(\psi \right)} \cos{\left(\theta \right)} + v_{y} \left(\sin{\left(\phi \right)} \sin{\left(\psi \right)} \sin{\left(\theta \right)} + \cos{\left(\phi \right)} \cos{\left(\psi \right)}\right) - v_{z} \left(\sin{\left(\phi \right)} \cos{\left(\psi \right)} - \sin{\left(\psi \right)} \sin{\left(\theta \right)} \cos{\left(\phi \right)}\right)\right) + y\\ \Delta{t} \left(- v_{x} \sin{\left(\theta \right)} + v_{y} \sin{\left(\phi \right)} \cos{\left(\theta \right)} + v_{z} \cos{\left(\phi \right)} \cos{\left(\theta \right)}\right) + z\\ \Delta{t} \left(w_{x} + w_{y} \sin{\left(\phi \right)} \tan{\left(\theta \right)} + w_{z} \cos{\left(\phi \right)} \tan{\left(\theta \right)}\right) + \phi\\ \Delta{t} \left(w_{y} \cos{\left(\phi \right)} - w_{z} \sin{\left(\phi \right)}\right) + \theta\\ \frac{\Delta{t} \left(w_{y} \sin{\left(\phi \right)} + w_{z} \cos{\left(\phi \right)}\right) + \psi \cos{\left(\theta \right)}}{\cos{\left(\theta \right)}}\end{bmatrix} -$$

    Then we can find the Jacobians of state equations w.r.t. the state vector and controls and use the standard EKF equations for covariance propagation: -$$ -\mathbf{P}_{i+1} = \mathbf{F}_i \mathbf{P}_i \mathbf{F}_{i}^T + \mathbf{G}_i \mathbf{U}_i \mathbf{G}_i^T. -$$

    Now let’s write the code and assess the covariance we end up with.

    Spoiler -
    dt = 1.0
    -sin, cos, tan  = np.sin, np.cos, np.tan
    -v_x, v_y, v_z = 1., 0., 0.
    -w_x, w_y, w_z = 0.0, 0., 0.0
    -
    -P = np.zeros((6, 6))
    -x, y, z = 0., 0., 0.
    -phi, theta, psi = 0., 0., 0.
    -for _ in range(0, N_steps):
    -    x = dt*(v_x*cos(psi)*cos(theta) + v_y*(sin(phi)*sin(theta)*cos(psi) - sin(psi)*cos(phi)) + v_z*(sin(phi)*sin(psi) + sin(theta)*cos(phi)*cos(psi))) + x
    -    y = dt*(v_x*sin(psi)*cos(theta) + v_y*(sin(phi)*sin(psi)*sin(theta) + cos(phi)*cos(psi)) - v_z*(sin(phi)*cos(psi) - sin(psi)*sin(theta)*cos(phi))) + y
    -    z = dt*(-v_x*sin(theta) + v_y*sin(phi)*cos(theta) + v_z*cos(phi)*cos(theta)) + z
    -    phi = dt*(w_x + w_y*sin(phi)*tan(theta) + w_z*cos(phi)*tan(theta)) + phi
    -    theta = dt*(w_y*cos(phi) - w_z*sin(phi)) + theta
    -    psi = (dt*(w_y*sin(phi) + w_z*cos(phi)) + psi*cos(theta))/cos(theta)
    -    
    -    # Jacobian of state dynamics w.r.t. the state
    -    F_x = np.eye(6)
    -    F_x[0, 3] = dt*(v_y*(sin(phi)*sin(psi) + sin(theta)*cos(phi)*cos(psi)) - v_z*(sin(phi)*sin(theta)*cos(psi) - sin(psi)*cos(phi)))
    -    F_x[0, 4] = dt*(-v_x*sin(theta) + v_y*sin(phi)*cos(theta) + v_z*cos(phi)*cos(theta))*cos(psi)
    -    F_x[0, 5] = -dt*(v_x*sin(psi)*cos(theta) + v_y*(sin(phi)*sin(psi)*sin(theta) + cos(phi)*cos(psi)) - v_z*(sin(phi)*cos(psi) - sin(psi)*sin(theta)*cos(phi)))
    -    F_x[1, 3] = -dt*(v_y*(sin(phi)*cos(psi) - sin(psi)*sin(theta)*cos(phi)) + v_z*(sin(phi)*sin(psi)*sin(theta) + cos(phi)*cos(psi)))
    -    F_x[1, 4] = dt*(-v_x*sin(theta) + v_y*sin(phi)*cos(theta) + v_z*cos(phi)*cos(theta))*sin(psi)
    -    F_x[1, 5] = dt*(v_x*cos(psi)*cos(theta) + v_y*(sin(phi)*sin(theta)*cos(psi) - sin(psi)*cos(phi)) + v_z*(sin(phi)*sin(psi) + sin(theta)*cos(phi)*cos(psi)))
    -    F_x[2, 3] = dt*(v_y*cos(phi) - v_z*sin(phi))*cos(theta)
    -    F_x[2, 4] = -dt*(v_x*cos(theta) + v_y*sin(phi)*sin(theta) + v_z*sin(theta)*cos(phi))
    -    F_x[3, 3] = dt*(w_y*cos(phi) - w_z*sin(phi))*tan(theta) + 1
    -    F_x[3, 4] = dt*(w_y*sin(phi) + w_z*cos(phi))/cos(theta)**2
    -    F_x[4, 3] = -dt*(w_y*sin(phi) + w_z*cos(phi))
    -    F_x[5, 3] = dt*(w_y*cos(phi) - w_z*sin(phi))/cos(theta)
    -    F_x[5, 4] = dt*(w_y*sin(phi) + w_z*cos(phi))*sin(theta)/cos(theta)**2
    -
    -    # Jacobian of state w.r.t. the controls
    -    G_i = np.zeros((6, 6))
    -    G_i[0, 0] = dt*cos(psi)*cos(theta)
    -    G_i[0, 1] = dt*(sin(phi)*sin(theta)*cos(psi) - sin(psi)*cos(phi))
    -    G_i[0, 2] = dt*(sin(phi)*sin(psi) + sin(theta)*cos(phi)*cos(psi))
    -    G_i[1, 0] = dt*sin(psi)*cos(theta)
    -    G_i[1, 1] = dt*(sin(phi)*sin(psi)*sin(theta) + cos(phi)*cos(psi))
    -    G_i[1, 2] = dt*(-sin(phi)*cos(psi) + sin(psi)*sin(theta)*cos(phi))
    -    G_i[2, 0] = -dt*sin(theta)
    -    G_i[2, 1] = dt*sin(phi)*cos(theta)
    -    G_i[2, 2] = dt*cos(phi)*cos(theta)
    -    G_i[3, 3] = dt
    -    G_i[3, 4] = dt*sin(phi)*tan(theta)
    -    G_i[3, 5] = dt*cos(phi)*tan(theta)
    -    G_i[4, 4] = dt*cos(phi)
    -    G_i[4, 5] = -dt*sin(phi)
    -    G_i[5, 4] = dt*sin(phi)/cos(theta)
    -    G_i[5, 5] = dt*cos(phi)/cos(theta)
    -
    -    Uc = np.diag([0., 0., 0.0,
    -                  0., 0., sigma_direction_rad**2])
    -
    -    P = F_x @ P @ F_x.T + G_i @ Uc @ G_i.T
    -
    print("Covariance:\n", Sigma_next[0:2, 0:2])
    -
    Covariance:
    - [[ 1858.357     0.   ]
    - [    0.    49714.573]]
    -

    We see that the result is the same. We got the same covariance using various methods. Do we have to bother to study and use Lie algebra if result is the same? It is interesting question and I try to leverage the answer in my next posts.

    References

    comments powered by Disqus
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/automatic-differentiation/index.html b/tags/automatic-differentiation/index.html deleted file mode 100644 index 3723df7..0000000 --- a/tags/automatic-differentiation/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Automatic differentiation · Aleksandr Mikoff's blog -

    Automatic differentiation

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/automatic-differentiation/index.xml b/tags/automatic-differentiation/index.xml index 0ab3960..bebc486 100644 --- a/tags/automatic-differentiation/index.xml +++ b/tags/automatic-differentiation/index.xml @@ -1,2 +1,2 @@ -Automatic differentiation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/automatic-differentiation/Recent content in Automatic differentiation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 19 Feb 2022 23:00:00 +0300Notes on backpropagationhttps://mikoff.github.io/posts/notes-on-backpropagation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/posts/notes-on-backpropagation/Notes on backpropagation In optimization and machine learning applications the widely used tool for finding the model parameters is the gradient descent. It allows to find the maximum or minimum of the target function w.r.t. the parameters, in other words, to minimize the discrepancy between the model and the data. +Automatic Differentiation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/automatic-differentiation/Recent content in Automatic Differentiation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 19 Feb 2022 23:00:00 +0300Notes on backpropagationhttps://mikoff.github.io/posts/notes-on-backpropagation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/posts/notes-on-backpropagation/Notes on backpropagation In optimization and machine learning applications the widely used tool for finding the model parameters is the gradient descent. It allows to find the maximum or minimum of the target function w.r.t. the parameters, in other words, to minimize the discrepancy between the model and the data. However, to use this method the gradient has to be computed. The first problem is that if our function has a complex form the process of differentiating is quite tricky. \ No newline at end of file diff --git a/tags/automatic-differentiation/page/1/index.html b/tags/automatic-differentiation/page/1/index.html deleted file mode 100644 index 40a75d9..0000000 --- a/tags/automatic-differentiation/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/automatic-differentiation/ - \ No newline at end of file diff --git a/tags/backpropagation/index.html b/tags/backpropagation/index.html deleted file mode 100644 index 8fce5e3..0000000 --- a/tags/backpropagation/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Backpropagation · Aleksandr Mikoff's blog -

    Backpropagation

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/backpropagation/page/1/index.html b/tags/backpropagation/page/1/index.html deleted file mode 100644 index 42ea2b9..0000000 --- a/tags/backpropagation/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/backpropagation/ - \ No newline at end of file diff --git a/tags/bayesian-estimation/index.html b/tags/bayesian-estimation/index.html deleted file mode 100644 index 0b67c3b..0000000 --- a/tags/bayesian-estimation/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Bayesian estimation · Aleksandr Mikoff's blog -

    Bayesian estimation

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/bayesian-estimation/index.xml b/tags/bayesian-estimation/index.xml index d523cba..2e6056d 100644 --- a/tags/bayesian-estimation/index.xml +++ b/tags/bayesian-estimation/index.xml @@ -1,2 +1,2 @@ -Bayesian estimation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/bayesian-estimation/Recent content in Bayesian estimation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ \ No newline at end of file +Bayesian Estimation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/bayesian-estimation/Recent content in Bayesian Estimation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark. \ No newline at end of file diff --git a/tags/bayesian-estimation/page/1/index.html b/tags/bayesian-estimation/page/1/index.html deleted file mode 100644 index e0a2557..0000000 --- a/tags/bayesian-estimation/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/bayesian-estimation/ - \ No newline at end of file diff --git a/tags/censorship/index.html b/tags/censorship/index.html deleted file mode 100644 index de49e64..0000000 --- a/tags/censorship/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Censorship · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/censorship/page/1/index.html b/tags/censorship/page/1/index.html deleted file mode 100644 index 0e1a61c..0000000 --- a/tags/censorship/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/censorship/ - \ No newline at end of file diff --git a/tags/deep-learning/index.html b/tags/deep-learning/index.html deleted file mode 100644 index 6b7cc19..0000000 --- a/tags/deep-learning/index.html +++ /dev/null @@ -1,10 +0,0 @@ -deep learning · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/deep-learning/index.xml b/tags/deep-learning/index.xml index 8a46309..e0597aa 100644 --- a/tags/deep-learning/index.xml +++ b/tags/deep-learning/index.xml @@ -1,4 +1,4 @@ -deep learning on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/deep-learning/Recent content in deep learning on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. +Deep Learning on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/deep-learning/Recent content in Deep Learning on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. Introduction Assume that we have random variable $X$ that has a continuous distribution on an interval $S \in \mathcal{R}$, its probability density function $f(x)$ is known.Understanding deep learning: training, SGD, code sampleshttps://mikoff.github.io/posts/nn-training.md/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/posts/nn-training.md/Recently, I have been reading a new [book]1 by S. Prince titled &ldquo;Understanding Deep Learning.&rdquo; While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms. This post is: a collection of keynotes from the first seven chapters of the book, that I have found useful for myself; the numpy-only implementation of deep neural network with variable layers size and training using SGD.Notes on backpropagationhttps://mikoff.github.io/posts/notes-on-backpropagation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/posts/notes-on-backpropagation/Notes on backpropagation In optimization and machine learning applications the widely used tool for finding the model parameters is the gradient descent. It allows to find the maximum or minimum of the target function w.r.t. the parameters, in other words, to minimize the discrepancy between the model and the data. diff --git a/tags/deep-learning/page/1/index.html b/tags/deep-learning/page/1/index.html deleted file mode 100644 index 389586d..0000000 --- a/tags/deep-learning/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/deep-learning/ - \ No newline at end of file diff --git a/tags/ekf/index.html b/tags/ekf/index.html deleted file mode 100644 index d5f8412..0000000 --- a/tags/ekf/index.html +++ /dev/null @@ -1,8 +0,0 @@ -EKF · Aleksandr Mikoff's blog -

    EKF

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/ekf/page/1/index.html b/tags/ekf/page/1/index.html deleted file mode 100644 index f63578c..0000000 --- a/tags/ekf/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/ekf/ - \ No newline at end of file diff --git a/tags/factor-graph/index.html b/tags/factor-graph/index.html deleted file mode 100644 index db4a00a..0000000 --- a/tags/factor-graph/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Factor graph · Aleksandr Mikoff's blog -

    Factor graph

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/factor-graph/index.xml b/tags/factor-graph/index.xml index 25988b8..50f48c9 100644 --- a/tags/factor-graph/index.xml +++ b/tags/factor-graph/index.xml @@ -1 +1 @@ -Factor graph on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/factor-graph/Recent content in Factor graph on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 20 Nov 2022 21:00:00 +0300Optimization on manifoldhttps://mikoff.github.io/posts/optimization-on-manifold/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/posts/optimization-on-manifold/Optimization on manifold In the following post I would like to summarize my perception of poses&rsquo; optimization problem. Such a problem often occurs in robotics and other related fields. Usually we want jointly optimize the poses, their increments and various measurements. What we want to find is such set of parameters, that minimize the sum of residuals, or differences, between the real measurements and measurements, that we derive from our state. \ No newline at end of file +Factor Graph on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/factor-graph/Recent content in Factor Graph on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 20 Nov 2022 21:00:00 +0300Optimization on manifoldhttps://mikoff.github.io/posts/optimization-on-manifold/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/posts/optimization-on-manifold/Optimization on manifold In the following post I would like to summarize my perception of poses&rsquo; optimization problem. Such a problem often occurs in robotics and other related fields. Usually we want jointly optimize the poses, their increments and various measurements. What we want to find is such set of parameters, that minimize the sum of residuals, or differences, between the real measurements and measurements, that we derive from our state. \ No newline at end of file diff --git a/tags/factor-graph/page/1/index.html b/tags/factor-graph/page/1/index.html deleted file mode 100644 index 064c859..0000000 --- a/tags/factor-graph/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/factor-graph/ - \ No newline at end of file diff --git a/tags/index.html b/tags/index.html deleted file mode 100644 index 3252db4..0000000 --- a/tags/index.html +++ /dev/null @@ -1,27 +0,0 @@ -Tag: Tags · Aleksandr Mikoff's blog -

    Tag: Tags

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/index.xml b/tags/index.xml index c87c2fe..e932dbf 100644 --- a/tags/index.xml +++ b/tags/index.xml @@ -1 +1 @@ -Tags on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/Recent content in Tags on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300deep learninghttps://mikoff.github.io/tags/deep-learning/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/deep-learning/pdfhttps://mikoff.github.io/tags/pdf/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/pdf/Probabilityhttps://mikoff.github.io/tags/probability/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/probability/transformhttps://mikoff.github.io/tags/transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/transform/neural networkshttps://mikoff.github.io/tags/neural-networks/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/tags/neural-networks/readinghttps://mikoff.github.io/tags/reading/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/tags/reading/Log-likelihoodhttps://mikoff.github.io/tags/log-likelihood/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/tags/log-likelihood/Particle filterhttps://mikoff.github.io/tags/particle-filter/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/tags/particle-filter/Factor graphhttps://mikoff.github.io/tags/factor-graph/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/factor-graph/Least squareshttps://mikoff.github.io/tags/least-squares/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/least-squares/Manifoldshttps://mikoff.github.io/tags/manifolds/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/manifolds/Optimizationhttps://mikoff.github.io/tags/optimization/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/optimization/Censorshiphttps://mikoff.github.io/tags/censorship/Tue, 14 Jun 2022 21:00:00 +0300https://mikoff.github.io/tags/censorship/VPNhttps://mikoff.github.io/tags/vpn/Tue, 14 Jun 2022 21:00:00 +0300https://mikoff.github.io/tags/vpn/Automatic differentiationhttps://mikoff.github.io/tags/automatic-differentiation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/tags/automatic-differentiation/Backpropagationhttps://mikoff.github.io/tags/backpropagation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/tags/backpropagation/Linear regressionhttps://mikoff.github.io/tags/linear-regression/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/linear-regression/Logistic regressionhttps://mikoff.github.io/tags/logistic-regression/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/logistic-regression/Sigmoidhttps://mikoff.github.io/tags/sigmoid/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/sigmoid/Lie algebrahttps://mikoff.github.io/tags/lie-algebra/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/lie-algebra/Lie grouphttps://mikoff.github.io/tags/lie-group/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/lie-group/Uncertainty propagationhttps://mikoff.github.io/tags/uncertainty-propagation/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/uncertainty-propagation/Iterative closest pointhttps://mikoff.github.io/tags/iterative-closest-point/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/iterative-closest-point/Lie groupshttps://mikoff.github.io/tags/lie-groups/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/lie-groups/Point cloud alignmenthttps://mikoff.github.io/tags/point-cloud-alignment/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/point-cloud-alignment/PCLhttps://mikoff.github.io/tags/pcl/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/tags/pcl/SVDhttps://mikoff.github.io/tags/svd/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/tags/svd/Bayesian estimationhttps://mikoff.github.io/tags/bayesian-estimation/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/bayesian-estimation/MAPhttps://mikoff.github.io/tags/map/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/map/MLEhttps://mikoff.github.io/tags/mle/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/mle/statisticshttps://mikoff.github.io/tags/statistics/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/statistics/Stereo visionhttps://mikoff.github.io/tags/stereo-vision/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/stereo-vision/EKFhttps://mikoff.github.io/tags/ekf/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/ekf/Kalman Filterhttps://mikoff.github.io/tags/kalman-filter/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/kalman-filter/SLAMhttps://mikoff.github.io/tags/slam/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/slam/samplinghttps://mikoff.github.io/tags/sampling/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/tags/sampling/sensor fusionhttps://mikoff.github.io/tags/sensor-fusion/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/tags/sensor-fusion/mehttps://mikoff.github.io/tags/me/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/tags/me/personalhttps://mikoff.github.io/tags/personal/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/tags/personal/ \ No newline at end of file +Tags on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/Recent content in Tags on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Deep Learninghttps://mikoff.github.io/tags/deep-learning/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/deep-learning/Pdfhttps://mikoff.github.io/tags/pdf/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/pdf/Probabilityhttps://mikoff.github.io/tags/probability/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/probability/Transformhttps://mikoff.github.io/tags/transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/tags/transform/Neural Networkshttps://mikoff.github.io/tags/neural-networks/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/tags/neural-networks/Readinghttps://mikoff.github.io/tags/reading/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/tags/reading/Log-Likelihoodhttps://mikoff.github.io/tags/log-likelihood/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/tags/log-likelihood/Particle Filterhttps://mikoff.github.io/tags/particle-filter/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/tags/particle-filter/Factor Graphhttps://mikoff.github.io/tags/factor-graph/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/factor-graph/Least Squareshttps://mikoff.github.io/tags/least-squares/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/least-squares/Manifoldshttps://mikoff.github.io/tags/manifolds/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/manifolds/Optimizationhttps://mikoff.github.io/tags/optimization/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/tags/optimization/Censorshiphttps://mikoff.github.io/tags/censorship/Tue, 14 Jun 2022 21:00:00 +0300https://mikoff.github.io/tags/censorship/VPNhttps://mikoff.github.io/tags/vpn/Tue, 14 Jun 2022 21:00:00 +0300https://mikoff.github.io/tags/vpn/Automatic Differentiationhttps://mikoff.github.io/tags/automatic-differentiation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/tags/automatic-differentiation/Backpropagationhttps://mikoff.github.io/tags/backpropagation/Sat, 19 Feb 2022 23:00:00 +0300https://mikoff.github.io/tags/backpropagation/Linear Regressionhttps://mikoff.github.io/tags/linear-regression/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/linear-regression/Logistic Regressionhttps://mikoff.github.io/tags/logistic-regression/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/logistic-regression/Sigmoidhttps://mikoff.github.io/tags/sigmoid/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/tags/sigmoid/Lie Algebrahttps://mikoff.github.io/tags/lie-algebra/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/lie-algebra/Lie Grouphttps://mikoff.github.io/tags/lie-group/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/lie-group/Uncertainty Propagationhttps://mikoff.github.io/tags/uncertainty-propagation/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/tags/uncertainty-propagation/Iterative Closest Pointhttps://mikoff.github.io/tags/iterative-closest-point/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/iterative-closest-point/Lie Groupshttps://mikoff.github.io/tags/lie-groups/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/lie-groups/Point Cloud Alignmenthttps://mikoff.github.io/tags/point-cloud-alignment/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/tags/point-cloud-alignment/PCLhttps://mikoff.github.io/tags/pcl/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/tags/pcl/SVDhttps://mikoff.github.io/tags/svd/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/tags/svd/Bayesian Estimationhttps://mikoff.github.io/tags/bayesian-estimation/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/bayesian-estimation/MAPhttps://mikoff.github.io/tags/map/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/map/MLEhttps://mikoff.github.io/tags/mle/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/mle/Statisticshttps://mikoff.github.io/tags/statistics/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/statistics/Stereo Visionhttps://mikoff.github.io/tags/stereo-vision/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/tags/stereo-vision/EKFhttps://mikoff.github.io/tags/ekf/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/ekf/Kalman Filterhttps://mikoff.github.io/tags/kalman-filter/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/kalman-filter/SLAMhttps://mikoff.github.io/tags/slam/Thu, 09 Apr 2020 21:00:00 +0300https://mikoff.github.io/tags/slam/Samplinghttps://mikoff.github.io/tags/sampling/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/tags/sampling/Sensor Fusionhttps://mikoff.github.io/tags/sensor-fusion/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/tags/sensor-fusion/Mehttps://mikoff.github.io/tags/me/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/tags/me/Personalhttps://mikoff.github.io/tags/personal/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/tags/personal/ \ No newline at end of file diff --git a/tags/iterative-closest-point/index.html b/tags/iterative-closest-point/index.html deleted file mode 100644 index c1f0683..0000000 --- a/tags/iterative-closest-point/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Iterative closest point · Aleksandr Mikoff's blog -

    Iterative closest point

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/iterative-closest-point/index.xml b/tags/iterative-closest-point/index.xml index f51c330..badaf9b 100644 --- a/tags/iterative-closest-point/index.xml +++ b/tags/iterative-closest-point/index.xml @@ -1 +1 @@ -Iterative closest point on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/iterative-closest-point/Recent content in Iterative closest point on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group. \ No newline at end of file +Iterative Closest Point on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/iterative-closest-point/Recent content in Iterative Closest Point on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group. \ No newline at end of file diff --git a/tags/iterative-closest-point/page/1/index.html b/tags/iterative-closest-point/page/1/index.html deleted file mode 100644 index 14df377..0000000 --- a/tags/iterative-closest-point/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/iterative-closest-point/ - \ No newline at end of file diff --git a/tags/kalman-filter/index.html b/tags/kalman-filter/index.html deleted file mode 100644 index a4baf33..0000000 --- a/tags/kalman-filter/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Kalman Filter · Aleksandr Mikoff's blog -

    Kalman Filter

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/kalman-filter/page/1/index.html b/tags/kalman-filter/page/1/index.html deleted file mode 100644 index e3e37c2..0000000 --- a/tags/kalman-filter/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/kalman-filter/ - \ No newline at end of file diff --git a/tags/least-squares/index.html b/tags/least-squares/index.html deleted file mode 100644 index 17b5df9..0000000 --- a/tags/least-squares/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Least squares · Aleksandr Mikoff's blog -

    Least squares

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/least-squares/index.xml b/tags/least-squares/index.xml index 8feca52..4ed8216 100644 --- a/tags/least-squares/index.xml +++ b/tags/least-squares/index.xml @@ -1 +1 @@ -Least squares on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/least-squares/Recent content in Least squares on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 20 Nov 2022 21:00:00 +0300Optimization on manifoldhttps://mikoff.github.io/posts/optimization-on-manifold/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/posts/optimization-on-manifold/Optimization on manifold In the following post I would like to summarize my perception of poses&rsquo; optimization problem. Such a problem often occurs in robotics and other related fields. Usually we want jointly optimize the poses, their increments and various measurements. What we want to find is such set of parameters, that minimize the sum of residuals, or differences, between the real measurements and measurements, that we derive from our state. \ No newline at end of file +Least Squares on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/least-squares/Recent content in Least Squares on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 20 Nov 2022 21:00:00 +0300Optimization on manifoldhttps://mikoff.github.io/posts/optimization-on-manifold/Sun, 20 Nov 2022 21:00:00 +0300https://mikoff.github.io/posts/optimization-on-manifold/Optimization on manifold In the following post I would like to summarize my perception of poses&rsquo; optimization problem. Such a problem often occurs in robotics and other related fields. Usually we want jointly optimize the poses, their increments and various measurements. What we want to find is such set of parameters, that minimize the sum of residuals, or differences, between the real measurements and measurements, that we derive from our state. \ No newline at end of file diff --git a/tags/least-squares/page/1/index.html b/tags/least-squares/page/1/index.html deleted file mode 100644 index d5c693c..0000000 --- a/tags/least-squares/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/least-squares/ - \ No newline at end of file diff --git a/tags/lie-algebra/index.html b/tags/lie-algebra/index.html deleted file mode 100644 index 79bc628..0000000 --- a/tags/lie-algebra/index.html +++ /dev/null @@ -1,9 +0,0 @@ -Lie algebra · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/lie-algebra/index.xml b/tags/lie-algebra/index.xml index d7c9204..494fd0f 100644 --- a/tags/lie-algebra/index.xml +++ b/tags/lie-algebra/index.xml @@ -1,2 +1,2 @@ -Lie algebra on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-algebra/Recent content in Lie algebra on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. +Lie Algebra on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-algebra/Recent content in Lie Algebra on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What&rsquo;s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates.Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group. \ No newline at end of file diff --git a/tags/lie-algebra/page/1/index.html b/tags/lie-algebra/page/1/index.html deleted file mode 100644 index 47872d3..0000000 --- a/tags/lie-algebra/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/lie-algebra/ - \ No newline at end of file diff --git a/tags/lie-group/index.html b/tags/lie-group/index.html deleted file mode 100644 index 3969ec4..0000000 --- a/tags/lie-group/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Lie group · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/lie-group/index.xml b/tags/lie-group/index.xml index 6e9d012..eaaeb1e 100644 --- a/tags/lie-group/index.xml +++ b/tags/lie-group/index.xml @@ -1,2 +1,2 @@ -Lie group on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-group/Recent content in Lie group on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. +Lie Group on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-group/Recent content in Lie Group on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What&rsquo;s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates. \ No newline at end of file diff --git a/tags/lie-group/page/1/index.html b/tags/lie-group/page/1/index.html deleted file mode 100644 index f228298..0000000 --- a/tags/lie-group/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/lie-group/ - \ No newline at end of file diff --git a/tags/lie-groups/index.html b/tags/lie-groups/index.html deleted file mode 100644 index a4ea985..0000000 --- a/tags/lie-groups/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Lie groups · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/lie-groups/index.xml b/tags/lie-groups/index.xml index 489b059..d0866dc 100644 --- a/tags/lie-groups/index.xml +++ b/tags/lie-groups/index.xml @@ -1 +1 @@ -Lie groups on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-groups/Recent content in Lie groups on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group. \ No newline at end of file +Lie Groups on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/lie-groups/Recent content in Lie Groups on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group. \ No newline at end of file diff --git a/tags/lie-groups/page/1/index.html b/tags/lie-groups/page/1/index.html deleted file mode 100644 index 4185c80..0000000 --- a/tags/lie-groups/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/lie-groups/ - \ No newline at end of file diff --git a/tags/linear-regression/index.html b/tags/linear-regression/index.html deleted file mode 100644 index 5508b56..0000000 --- a/tags/linear-regression/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Linear regression · Aleksandr Mikoff's blog -

    Linear regression

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/linear-regression/index.xml b/tags/linear-regression/index.xml index d9f83d7..c59d72d 100644 --- a/tags/linear-regression/index.xml +++ b/tags/linear-regression/index.xml @@ -1,2 +1,2 @@ -Linear regression on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/linear-regression/Recent content in Linear regression on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 10 Feb 2022 19:00:00 +0300Linear and logistic regressionshttps://mikoff.github.io/posts/linear-and-logistic-regression.md/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/posts/linear-and-logistic-regression.md/Let&rsquo;s assume we want to predict whether the person is male or female judging by its height. +Linear Regression on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/linear-regression/Recent content in Linear Regression on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 10 Feb 2022 19:00:00 +0300Linear and logistic regressionshttps://mikoff.github.io/posts/linear-and-logistic-regression.md/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/posts/linear-and-logistic-regression.md/Let&rsquo;s assume we want to predict whether the person is male or female judging by its height. Spoiler heights = np.array([120, 135, 145, 150, 151, 155, 165, 170, 172, 175, 180, 190]) labels = np.array([0, 0, 0, 0., 0, 0, 1, 1, 1, 1, 1, 1.]) females = labels == 0 males = labels == 1 fig, ax = plt.subplots(1, 1, figsize = (6, 2)) ax.scatter(heights[females], labels[females]) ax.scatter(heights[males], labels[males]) ax.set(xlabel = &#39;height, cm&#39;, ylabel = &#39;class&#39;) ax. \ No newline at end of file diff --git a/tags/linear-regression/page/1/index.html b/tags/linear-regression/page/1/index.html deleted file mode 100644 index 528e4cf..0000000 --- a/tags/linear-regression/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/linear-regression/ - \ No newline at end of file diff --git a/tags/log-likelihood/index.html b/tags/log-likelihood/index.html deleted file mode 100644 index b54395f..0000000 --- a/tags/log-likelihood/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Log-likelihood · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/log-likelihood/index.xml b/tags/log-likelihood/index.xml index 344e50f..5ca1a52 100644 --- a/tags/log-likelihood/index.xml +++ b/tags/log-likelihood/index.xml @@ -1,2 +1,2 @@ -Log-likelihood on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/log-likelihood/Recent content in Log-likelihood on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usFri, 11 Aug 2023 23:00:00 +0300Likelihood and probability normalization, log-sum-exp trickhttps://mikoff.github.io/posts/likelihood-and-log-sum-exp/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/posts/likelihood-and-log-sum-exp/Working with probabilities involves multiplication and normalization of their values. Since the numerical values sometimes are extremely low that can lead to underflow problems. This problem is evident with particle filters - we have to multiply really low likelihood values that vanish in the end. Log-sum-exp allows to abbreviate this problem. +Log-Likelihood on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/log-likelihood/Recent content in Log-Likelihood on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usFri, 11 Aug 2023 23:00:00 +0300Likelihood and probability normalization, log-sum-exp trickhttps://mikoff.github.io/posts/likelihood-and-log-sum-exp/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/posts/likelihood-and-log-sum-exp/Working with probabilities involves multiplication and normalization of their values. Since the numerical values sometimes are extremely low that can lead to underflow problems. This problem is evident with particle filters - we have to multiply really low likelihood values that vanish in the end. Log-sum-exp allows to abbreviate this problem. Approach Log-likelihoods Since the likelihood values can be extremely low it is more convenient to work with loglikelihood instead of likelihood: $$ \log(\mathcal{L}). \ No newline at end of file diff --git a/tags/log-likelihood/page/1/index.html b/tags/log-likelihood/page/1/index.html deleted file mode 100644 index 865014f..0000000 --- a/tags/log-likelihood/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/log-likelihood/ - \ No newline at end of file diff --git a/tags/logistic-regression/index.html b/tags/logistic-regression/index.html deleted file mode 100644 index b299643..0000000 --- a/tags/logistic-regression/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Logistic regression · Aleksandr Mikoff's blog -

    Logistic regression

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/logistic-regression/index.xml b/tags/logistic-regression/index.xml index 59e81d6..f504362 100644 --- a/tags/logistic-regression/index.xml +++ b/tags/logistic-regression/index.xml @@ -1,2 +1,2 @@ -Logistic regression on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/logistic-regression/Recent content in Logistic regression on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 10 Feb 2022 19:00:00 +0300Linear and logistic regressionshttps://mikoff.github.io/posts/linear-and-logistic-regression.md/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/posts/linear-and-logistic-regression.md/Let&rsquo;s assume we want to predict whether the person is male or female judging by its height. +Logistic Regression on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/logistic-regression/Recent content in Logistic Regression on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 10 Feb 2022 19:00:00 +0300Linear and logistic regressionshttps://mikoff.github.io/posts/linear-and-logistic-regression.md/Thu, 10 Feb 2022 19:00:00 +0300https://mikoff.github.io/posts/linear-and-logistic-regression.md/Let&rsquo;s assume we want to predict whether the person is male or female judging by its height. Spoiler heights = np.array([120, 135, 145, 150, 151, 155, 165, 170, 172, 175, 180, 190]) labels = np.array([0, 0, 0, 0., 0, 0, 1, 1, 1, 1, 1, 1.]) females = labels == 0 males = labels == 1 fig, ax = plt.subplots(1, 1, figsize = (6, 2)) ax.scatter(heights[females], labels[females]) ax.scatter(heights[males], labels[males]) ax.set(xlabel = &#39;height, cm&#39;, ylabel = &#39;class&#39;) ax. \ No newline at end of file diff --git a/tags/logistic-regression/page/1/index.html b/tags/logistic-regression/page/1/index.html deleted file mode 100644 index 91b4858..0000000 --- a/tags/logistic-regression/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/logistic-regression/ - \ No newline at end of file diff --git a/tags/manifolds/index.html b/tags/manifolds/index.html deleted file mode 100644 index 1e0136f..0000000 --- a/tags/manifolds/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Manifolds · Aleksandr Mikoff's blog -

    Manifolds

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/manifolds/page/1/index.html b/tags/manifolds/page/1/index.html deleted file mode 100644 index b39fdb7..0000000 --- a/tags/manifolds/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/manifolds/ - \ No newline at end of file diff --git a/tags/map/index.html b/tags/map/index.html deleted file mode 100644 index 94e02e5..0000000 --- a/tags/map/index.html +++ /dev/null @@ -1,8 +0,0 @@ -MAP · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/map/index.xml b/tags/map/index.xml index bf5dc12..be5c40e 100644 --- a/tags/map/index.xml +++ b/tags/map/index.xml @@ -1,2 +1,2 @@ MAP on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/map/Recent content in MAP on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ \ No newline at end of file +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.
    \ No newline at end of file diff --git a/tags/map/page/1/index.html b/tags/map/page/1/index.html deleted file mode 100644 index ce29324..0000000 --- a/tags/map/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/map/ - \ No newline at end of file diff --git a/tags/me/index.html b/tags/me/index.html deleted file mode 100644 index 2d8b8af..0000000 --- a/tags/me/index.html +++ /dev/null @@ -1,8 +0,0 @@ -me · Aleksandr Mikoff's blog -

    me

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/me/index.xml b/tags/me/index.xml index 77a0ce1..60f86af 100644 --- a/tags/me/index.xml +++ b/tags/me/index.xml @@ -1,2 +1,2 @@ -me on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/me/Recent content in me on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 09 Feb 2020 13:40:59 +0300Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: +Me on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/me/Recent content in Me on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 09 Feb 2020 13:40:59 +0300Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: explorations with algorithms, experiments with data, understanding the math concepts and computer science algorithms in simple words. You can find my CV here \ No newline at end of file diff --git a/tags/me/page/1/index.html b/tags/me/page/1/index.html deleted file mode 100644 index f2f52e5..0000000 --- a/tags/me/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/me/ - \ No newline at end of file diff --git a/tags/mle/index.html b/tags/mle/index.html deleted file mode 100644 index dc8c712..0000000 --- a/tags/mle/index.html +++ /dev/null @@ -1,8 +0,0 @@ -MLE · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/mle/index.xml b/tags/mle/index.xml index 7f7abdf..181a127 100644 --- a/tags/mle/index.xml +++ b/tags/mle/index.xml @@ -1,2 +1,2 @@ MLE on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/mle/Recent content in MLE on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ \ No newline at end of file +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.
    \ No newline at end of file diff --git a/tags/mle/page/1/index.html b/tags/mle/page/1/index.html deleted file mode 100644 index c0775af..0000000 --- a/tags/mle/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/mle/ - \ No newline at end of file diff --git a/tags/neural-networks/index.html b/tags/neural-networks/index.html deleted file mode 100644 index cac2e22..0000000 --- a/tags/neural-networks/index.html +++ /dev/null @@ -1,8 +0,0 @@ -neural networks · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/neural-networks/index.xml b/tags/neural-networks/index.xml index 4802fd4..134340f 100644 --- a/tags/neural-networks/index.xml +++ b/tags/neural-networks/index.xml @@ -1,3 +1,3 @@ -neural networks on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/neural-networks/Recent content in neural networks on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 15 Oct 2023 12:00:00 +0300Understanding deep learning: training, SGD, code sampleshttps://mikoff.github.io/posts/nn-training.md/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/posts/nn-training.md/Recently, I have been reading a new [book]1 by S. Prince titled &ldquo;Understanding Deep Learning.&rdquo; While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms. +Neural Networks on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/neural-networks/Recent content in Neural Networks on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 15 Oct 2023 12:00:00 +0300Understanding deep learning: training, SGD, code sampleshttps://mikoff.github.io/posts/nn-training.md/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/posts/nn-training.md/Recently, I have been reading a new [book]1 by S. Prince titled &ldquo;Understanding Deep Learning.&rdquo; While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms. This post is: a collection of keynotes from the first seven chapters of the book, that I have found useful for myself; the numpy-only implementation of deep neural network with variable layers size and training using SGD. \ No newline at end of file diff --git a/tags/neural-networks/page/1/index.html b/tags/neural-networks/page/1/index.html deleted file mode 100644 index 52f533e..0000000 --- a/tags/neural-networks/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/neural-networks/ - \ No newline at end of file diff --git a/tags/optimization/index.html b/tags/optimization/index.html deleted file mode 100644 index f0ef8f4..0000000 --- a/tags/optimization/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Optimization · Aleksandr Mikoff's blog -

    Optimization

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/optimization/page/1/index.html b/tags/optimization/page/1/index.html deleted file mode 100644 index eaf7949..0000000 --- a/tags/optimization/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/optimization/ - \ No newline at end of file diff --git a/tags/page/1/index.html b/tags/page/1/index.html deleted file mode 100644 index d37c763..0000000 --- a/tags/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/ - \ No newline at end of file diff --git a/tags/page/2/index.html b/tags/page/2/index.html deleted file mode 100644 index 36dfa65..0000000 --- a/tags/page/2/index.html +++ /dev/null @@ -1,26 +0,0 @@ -Tag: Tags · Aleksandr Mikoff's blog -

    Tag: Tags

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/particle-filter/index.html b/tags/particle-filter/index.html deleted file mode 100644 index 80cfb74..0000000 --- a/tags/particle-filter/index.html +++ /dev/null @@ -1,9 +0,0 @@ -Particle filter · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/particle-filter/index.xml b/tags/particle-filter/index.xml index fa477fd..796c9cf 100644 --- a/tags/particle-filter/index.xml +++ b/tags/particle-filter/index.xml @@ -1,3 +1,3 @@ -Particle filter on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/particle-filter/Recent content in Particle filter on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usFri, 11 Aug 2023 23:00:00 +0300Likelihood and probability normalization, log-sum-exp trickhttps://mikoff.github.io/posts/likelihood-and-log-sum-exp/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/posts/likelihood-and-log-sum-exp/Working with probabilities involves multiplication and normalization of their values. Since the numerical values sometimes are extremely low that can lead to underflow problems. This problem is evident with particle filters - we have to multiply really low likelihood values that vanish in the end. Log-sum-exp allows to abbreviate this problem. +Particle Filter on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/particle-filter/Recent content in Particle Filter on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usFri, 11 Aug 2023 23:00:00 +0300Likelihood and probability normalization, log-sum-exp trickhttps://mikoff.github.io/posts/likelihood-and-log-sum-exp/Fri, 11 Aug 2023 23:00:00 +0300https://mikoff.github.io/posts/likelihood-and-log-sum-exp/Working with probabilities involves multiplication and normalization of their values. Since the numerical values sometimes are extremely low that can lead to underflow problems. This problem is evident with particle filters - we have to multiply really low likelihood values that vanish in the end. Log-sum-exp allows to abbreviate this problem. Approach Log-likelihoods Since the likelihood values can be extremely low it is more convenient to work with loglikelihood instead of likelihood: $$ \log(\mathcal{L}).Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here. \ No newline at end of file diff --git a/tags/particle-filter/page/1/index.html b/tags/particle-filter/page/1/index.html deleted file mode 100644 index 7d5be80..0000000 --- a/tags/particle-filter/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/particle-filter/ - \ No newline at end of file diff --git a/tags/pcl/index.html b/tags/pcl/index.html deleted file mode 100644 index 35f74a4..0000000 --- a/tags/pcl/index.html +++ /dev/null @@ -1,8 +0,0 @@ -PCL · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/pcl/page/1/index.html b/tags/pcl/page/1/index.html deleted file mode 100644 index f9dd860..0000000 --- a/tags/pcl/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/pcl/ - \ No newline at end of file diff --git a/tags/pdf/index.html b/tags/pdf/index.html deleted file mode 100644 index da8f79b..0000000 --- a/tags/pdf/index.html +++ /dev/null @@ -1,8 +0,0 @@ -pdf · Aleksandr Mikoff's blog -

    pdf

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/pdf/index.xml b/tags/pdf/index.xml index b289cb1..e43b551 100644 --- a/tags/pdf/index.xml +++ b/tags/pdf/index.xml @@ -1,2 +1,2 @@ -pdf on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/pdf/Recent content in pdf on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. +Pdf on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/pdf/Recent content in Pdf on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. Introduction Assume that we have random variable $X$ that has a continuous distribution on an interval $S \in \mathcal{R}$, its probability density function $f(x)$ is known. \ No newline at end of file diff --git a/tags/pdf/page/1/index.html b/tags/pdf/page/1/index.html deleted file mode 100644 index 8d25fbe..0000000 --- a/tags/pdf/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/pdf/ - \ No newline at end of file diff --git a/tags/personal/index.html b/tags/personal/index.html deleted file mode 100644 index 75e25c2..0000000 --- a/tags/personal/index.html +++ /dev/null @@ -1,8 +0,0 @@ -personal · Aleksandr Mikoff's blog -

    personal

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/personal/index.xml b/tags/personal/index.xml index d354a71..72caf29 100644 --- a/tags/personal/index.xml +++ b/tags/personal/index.xml @@ -1,2 +1,2 @@ -personal on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/personal/Recent content in personal on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 09 Feb 2020 13:40:59 +0300Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: +Personal on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/personal/Recent content in Personal on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 09 Feb 2020 13:40:59 +0300Abouthttps://mikoff.github.io/about/Sun, 09 Feb 2020 13:40:59 +0300https://mikoff.github.io/about/I am researcher and algorithm developer in the indoor and outdoor navigation field. I am interested in Statistics, Robotics, Positioning and Automotive fields. This blog is about: explorations with algorithms, experiments with data, understanding the math concepts and computer science algorithms in simple words. You can find my CV here \ No newline at end of file diff --git a/tags/personal/page/1/index.html b/tags/personal/page/1/index.html deleted file mode 100644 index 4cc93f0..0000000 --- a/tags/personal/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/personal/ - \ No newline at end of file diff --git a/tags/point-cloud-alignment/index.html b/tags/point-cloud-alignment/index.html deleted file mode 100644 index 50949eb..0000000 --- a/tags/point-cloud-alignment/index.html +++ /dev/null @@ -1,9 +0,0 @@ -Point cloud alignment · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/point-cloud-alignment/index.xml b/tags/point-cloud-alignment/index.xml index 8c1d27d..2173f14 100644 --- a/tags/point-cloud-alignment/index.xml +++ b/tags/point-cloud-alignment/index.xml @@ -1,2 +1,2 @@ -Point cloud alignment on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/point-cloud-alignment/Recent content in Point cloud alignment on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group.Point cloud alignment and SVDhttps://mikoff.github.io/posts/point-cloud-alignment.md/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/posts/point-cloud-alignment.md/Point cloud alignment and SVD Singular value decomposition Recently I studied the problem of finding the rotation and translation between two point sets and decided to write the post about it. The key here is singular value decomposition, or SVD. +Point Cloud Alignment on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/point-cloud-alignment/Recent content in Point Cloud Alignment on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usMon, 27 Jul 2020 19:30:00 +0300Point cloud alignment using Lie algebra machineryhttps://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Mon, 27 Jul 2020 19:30:00 +0300https://mikoff.github.io/posts/point-cloud-alignment-and-lie-algebra.md/Point cloud alignment using Lie algebra machinery Special Orthogonal group and vectorspaces Today I would like to cover the importance of Lie groups to the problems, that often arises in robotics field. The pose of the robot can be described through rotation and translation. Rotations, however, do not belong to the vector space: we are not allowed to sum the rotations or multiply them by a scalar, because the resulting element will not belong to SO(3) group.Point cloud alignment and SVDhttps://mikoff.github.io/posts/point-cloud-alignment.md/Wed, 24 Jun 2020 18:00:00 +0300https://mikoff.github.io/posts/point-cloud-alignment.md/Point cloud alignment and SVD Singular value decomposition Recently I studied the problem of finding the rotation and translation between two point sets and decided to write the post about it. The key here is singular value decomposition, or SVD. It is extremely popular technique in many types of linear problems. It should be not surprised, that the point cloud alignment problem can be solved with its help. My aim here is to show all accommpanying theory and provide the point cloud alignment algorithm that takes not more than 10 lines of code in Python. \ No newline at end of file diff --git a/tags/point-cloud-alignment/page/1/index.html b/tags/point-cloud-alignment/page/1/index.html deleted file mode 100644 index 19960d0..0000000 --- a/tags/point-cloud-alignment/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/point-cloud-alignment/ - \ No newline at end of file diff --git a/tags/probability/index.html b/tags/probability/index.html deleted file mode 100644 index ce502a6..0000000 --- a/tags/probability/index.html +++ /dev/null @@ -1,9 +0,0 @@ -Probability · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/probability/page/1/index.html b/tags/probability/page/1/index.html deleted file mode 100644 index 9e4f66e..0000000 --- a/tags/probability/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/probability/ - \ No newline at end of file diff --git a/tags/reading/index.html b/tags/reading/index.html deleted file mode 100644 index 20291f6..0000000 --- a/tags/reading/index.html +++ /dev/null @@ -1,8 +0,0 @@ -reading · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/reading/index.xml b/tags/reading/index.xml index f28f660..750a6e4 100644 --- a/tags/reading/index.xml +++ b/tags/reading/index.xml @@ -1,3 +1,3 @@ -reading on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/reading/Recent content in reading on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 15 Oct 2023 12:00:00 +0300Understanding deep learning: training, SGD, code sampleshttps://mikoff.github.io/posts/nn-training.md/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/posts/nn-training.md/Recently, I have been reading a new [book]1 by S. Prince titled &ldquo;Understanding Deep Learning.&rdquo; While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms. +Reading on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/reading/Recent content in Reading on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSun, 15 Oct 2023 12:00:00 +0300Understanding deep learning: training, SGD, code sampleshttps://mikoff.github.io/posts/nn-training.md/Sun, 15 Oct 2023 12:00:00 +0300https://mikoff.github.io/posts/nn-training.md/Recently, I have been reading a new [book]1 by S. Prince titled &ldquo;Understanding Deep Learning.&rdquo; While reading it, I made some notes and practiced with concepts that were described in great detail by the author. Having no prior experience in deep learning, I was fascinated by how clearly the author explains the concepts and main terms. This post is: a collection of keynotes from the first seven chapters of the book, that I have found useful for myself; the numpy-only implementation of deep neural network with variable layers size and training using SGD. \ No newline at end of file diff --git a/tags/reading/page/1/index.html b/tags/reading/page/1/index.html deleted file mode 100644 index e8c2f85..0000000 --- a/tags/reading/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/reading/ - \ No newline at end of file diff --git a/tags/sampling/index.html b/tags/sampling/index.html deleted file mode 100644 index 2c72c37..0000000 --- a/tags/sampling/index.html +++ /dev/null @@ -1,9 +0,0 @@ -sampling · Aleksandr Mikoff's blog -

    sampling

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/sampling/index.xml b/tags/sampling/index.xml index c196355..8b91107 100644 --- a/tags/sampling/index.xml +++ b/tags/sampling/index.xml @@ -1,2 +1,2 @@ -sampling on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/sampling/Recent content in sampling on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usTue, 31 Mar 2020 19:59:26 +0300Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: -Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ \ No newline at end of file +Sampling on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/sampling/Recent content in Sampling on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usTue, 31 Mar 2020 19:59:26 +0300Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: +Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ Cumulative distribution function shows us the probability (portion of data, frequence) to draw a number $X$ less or equal than $x$: $$ P(X \leq x) = F(x). \ No newline at end of file diff --git a/tags/sampling/page/1/index.html b/tags/sampling/page/1/index.html deleted file mode 100644 index 391993c..0000000 --- a/tags/sampling/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/sampling/ - \ No newline at end of file diff --git a/tags/sensor-fusion/index.html b/tags/sensor-fusion/index.html deleted file mode 100644 index dd61882..0000000 --- a/tags/sensor-fusion/index.html +++ /dev/null @@ -1,8 +0,0 @@ -sensor fusion · Aleksandr Mikoff's blog -

    sensor fusion

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/sensor-fusion/index.xml b/tags/sensor-fusion/index.xml index c4155da..17d91c1 100644 --- a/tags/sensor-fusion/index.xml +++ b/tags/sensor-fusion/index.xml @@ -1,2 +1,2 @@ -sensor fusion on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/sensor-fusion/Recent content in sensor fusion on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usTue, 31 Mar 2020 19:59:26 +0300Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: +Sensor Fusion on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/sensor-fusion/Recent content in Sensor Fusion on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usTue, 31 Mar 2020 19:59:26 +0300Particle Filter: localizing the robothttps://mikoff.github.io/posts/particle-filter.md/Tue, 31 Mar 2020 19:59:26 +0300https://mikoff.github.io/posts/particle-filter.md/Particle filter In this post I would like to show the basic implementation of the Particle filter for robot localization using distance measurements to the known anchors, or landmarks. So why particle filter is so widely used? It&rsquo;s widespread application lies in its versatile nature and universalism. The filter is able to: Work with nonlinearities. Handle non-gaussian distributions. Easily fuse various information sources. Simulate the processes. My sample implementation takes less then 100 lines of Python code and can be found here. \ No newline at end of file diff --git a/tags/sensor-fusion/page/1/index.html b/tags/sensor-fusion/page/1/index.html deleted file mode 100644 index 8bf52f6..0000000 --- a/tags/sensor-fusion/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/sensor-fusion/ - \ No newline at end of file diff --git a/tags/sigmoid/index.html b/tags/sigmoid/index.html deleted file mode 100644 index 3fa02ad..0000000 --- a/tags/sigmoid/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Sigmoid · Aleksandr Mikoff's blog -

    Sigmoid

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/sigmoid/page/1/index.html b/tags/sigmoid/page/1/index.html deleted file mode 100644 index db0e656..0000000 --- a/tags/sigmoid/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/sigmoid/ - \ No newline at end of file diff --git a/tags/slam/index.html b/tags/slam/index.html deleted file mode 100644 index db4d0be..0000000 --- a/tags/slam/index.html +++ /dev/null @@ -1,8 +0,0 @@ -SLAM · Aleksandr Mikoff's blog -

    SLAM

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/slam/page/1/index.html b/tags/slam/page/1/index.html deleted file mode 100644 index 0a7d0c2..0000000 --- a/tags/slam/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/slam/ - \ No newline at end of file diff --git a/tags/statistics/index.html b/tags/statistics/index.html deleted file mode 100644 index 83253e2..0000000 --- a/tags/statistics/index.html +++ /dev/null @@ -1,9 +0,0 @@ -statistics · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/statistics/index.xml b/tags/statistics/index.xml index 4509f07..18a084d 100644 --- a/tags/statistics/index.xml +++ b/tags/statistics/index.xml @@ -1,2 +1,2 @@ -statistics on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/statistics/Recent content in statistics on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ \ No newline at end of file +Statistics on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/statistics/Recent content in Statistics on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark.Inverse transform samplinghttps://mikoff.github.io/posts/inverse-transform-sampling.md/Sun, 09 Feb 2020 14:56:13 +0300https://mikoff.github.io/posts/inverse-transform-sampling.md/Probability density and cumulative distribution functions Probability density function $f(x)$ is a function, which allows us to evaluate the probability that the sample, drawn from the distribution, will be equal to the value $X$. Also we can use PDF to calculate the probability that the randomly drawn sample from distribution will be in certain range, for example, $a \leq X \leq b$. This probability equals to the area under the PDF curve on the given interval and can be calculated by integration: $$ P(a \leq X \leq b) = \int _a^b f(x) dx $$ Cumulative distribution function shows us the probability (portion of data, frequence) to draw a number $X$ less or equal than $x$: $$ P(X \leq x) = F(x). \ No newline at end of file diff --git a/tags/statistics/page/1/index.html b/tags/statistics/page/1/index.html deleted file mode 100644 index d85c578..0000000 --- a/tags/statistics/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/statistics/ - \ No newline at end of file diff --git a/tags/stereo-vision/index.html b/tags/stereo-vision/index.html deleted file mode 100644 index 4213755..0000000 --- a/tags/stereo-vision/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Stereo vision · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/stereo-vision/index.xml b/tags/stereo-vision/index.xml index e0b1c9d..4df0ba9 100644 --- a/tags/stereo-vision/index.xml +++ b/tags/stereo-vision/index.xml @@ -1,2 +1,2 @@ -Stereo vision on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/stereo-vision/Recent content in Stereo vision on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. -Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ \ No newline at end of file +Stereo Vision on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/stereo-vision/Recent content in Stereo Vision on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 18 Apr 2020 10:51:21 +0300Nonlinear estimation: Full Bayesian, MLE and MAPhttps://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Sat, 18 Apr 2020 10:51:21 +0300https://mikoff.github.io/posts/nonlinear-estimation-mle-map.md/Intro Recently I have read &ldquo;State Estimation for Robotics&rdquo; book and came across a good example on one-dimensional nonlinear estimation problem: the estimation of the position of a landmark from stereo-camera data. +Distance from stereo-images The camera image is a projection of the world on the image plane. The depth perceptions arises from disparity of 3d point (landmark) on two images, obtained from left and right cameras. $$disparity = x_{left} - x_{right}$$ Having two images with known landmark positions in image coordinates (pixels), it turns out that we can calculate the depth between camera and the landmark. \ No newline at end of file diff --git a/tags/stereo-vision/page/1/index.html b/tags/stereo-vision/page/1/index.html deleted file mode 100644 index 55b4d53..0000000 --- a/tags/stereo-vision/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/stereo-vision/ - \ No newline at end of file diff --git a/tags/svd/index.html b/tags/svd/index.html deleted file mode 100644 index 71480cf..0000000 --- a/tags/svd/index.html +++ /dev/null @@ -1,8 +0,0 @@ -SVD · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/svd/page/1/index.html b/tags/svd/page/1/index.html deleted file mode 100644 index fb1b1e7..0000000 --- a/tags/svd/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/svd/ - \ No newline at end of file diff --git a/tags/transform/index.html b/tags/transform/index.html deleted file mode 100644 index fb0c901..0000000 --- a/tags/transform/index.html +++ /dev/null @@ -1,8 +0,0 @@ -transform · Aleksandr Mikoff's blog -

    transform

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/transform/index.xml b/tags/transform/index.xml index 16441c7..1cc801f 100644 --- a/tags/transform/index.xml +++ b/tags/transform/index.xml @@ -1,2 +1,2 @@ -transform on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/transform/Recent content in transform on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. +Transform on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/transform/Recent content in Transform on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usSat, 27 Jan 2024 12:00:00 +0300Probability density transformhttps://mikoff.github.io/posts/probability-density-transform/Sat, 27 Jan 2024 12:00:00 +0300https://mikoff.github.io/posts/probability-density-transform/PDF transformations While reading new [book]1 by Bishop I came across pdf transformation topic. It turned out to be counter-intuitive that we need not only transform the pdf by the selected function, but also multiply it by the derivative of the inverse function w.r.t. substituted variable. While delving into the details of this topic, I found the following sources to be quite useful: [2]2, [3]3, [4]4, [5]5. Introduction Assume that we have random variable $X$ that has a continuous distribution on an interval $S \in \mathcal{R}$, its probability density function $f(x)$ is known. \ No newline at end of file diff --git a/tags/transform/page/1/index.html b/tags/transform/page/1/index.html deleted file mode 100644 index a0525ce..0000000 --- a/tags/transform/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/transform/ - \ No newline at end of file diff --git a/tags/uncertainty-propagation/index.html b/tags/uncertainty-propagation/index.html deleted file mode 100644 index e4b6b2a..0000000 --- a/tags/uncertainty-propagation/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Uncertainty propagation · Aleksandr Mikoff's blog -

    Uncertainty propagation

    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/uncertainty-propagation/index.xml b/tags/uncertainty-propagation/index.xml index 0e921e8..3176372 100644 --- a/tags/uncertainty-propagation/index.xml +++ b/tags/uncertainty-propagation/index.xml @@ -1,2 +1,2 @@ -Uncertainty propagation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/uncertainty-propagation/Recent content in Uncertainty propagation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. +Uncertainty Propagation on Aleksandr Mikoff's bloghttps://mikoff.github.io/tags/uncertainty-propagation/Recent content in Uncertainty Propagation on Aleksandr Mikoff's blogHugo -- gohugo.ioen-usThu, 04 Nov 2021 23:00:00 +0300Uncertainty propagation with and without Lie groupshttps://mikoff.github.io/posts/uncertainty-propagation.md/Thu, 04 Nov 2021 23:00:00 +0300https://mikoff.github.io/posts/uncertainty-propagation.md/Uncertainty propagation with and without Lie groups and algebras The correct uncertainty estimation of the pose is crucial for any navigation or positioning algorithm performance. One of the most natural way of representing the uncertainty for me is the confidence ellipse. In the following post I would like to show effect of the uncertainty propagation using the various algorithms. What&rsquo;s more important, I would like to show the Gaussians representations both in cartesian and exponential coordinates. \ No newline at end of file diff --git a/tags/uncertainty-propagation/page/1/index.html b/tags/uncertainty-propagation/page/1/index.html deleted file mode 100644 index 2659b7a..0000000 --- a/tags/uncertainty-propagation/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/uncertainty-propagation/ - \ No newline at end of file diff --git a/tags/vpn/index.html b/tags/vpn/index.html deleted file mode 100644 index 555eaf1..0000000 --- a/tags/vpn/index.html +++ /dev/null @@ -1,8 +0,0 @@ -VPN · Aleksandr Mikoff's blog -
    © 2024 -Aleksandr Mikoff -· -Powered by Hugo & Coder.
    \ No newline at end of file diff --git a/tags/vpn/page/1/index.html b/tags/vpn/page/1/index.html deleted file mode 100644 index e170a62..0000000 --- a/tags/vpn/page/1/index.html +++ /dev/null @@ -1,2 +0,0 @@ -https://mikoff.github.io/tags/vpn/ - \ No newline at end of file