{"id":2015,"date":"2026-04-20T13:34:33","date_gmt":"2026-04-20T11:34:33","guid":{"rendered":"https:\/\/lorentzen.ch\/?p=2015"},"modified":"2026-04-20T15:47:30","modified_gmt":"2026-04-20T13:47:30","slug":"gap-safe-screening-rules-for-the-lasso-landed-in-scikit-learn","status":"publish","type":"post","link":"https:\/\/lorentzen.ch\/index.php\/2026\/04\/20\/gap-safe-screening-rules-for-the-lasso-landed-in-scikit-learn\/","title":{"rendered":"Gap Safe Screening Rules for the Lasso landed in scikit-learn"},"content":{"rendered":"\n<p>In this post, we explore the topic of gap safe screening rules from convex optimisation and their impact on <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.linear_model.Lasso.html\">Lasso<\/a> and <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.linear_model.ElasticNet.html\">ElasticNet<\/a> in <a href=\"http:\/\/scikit-learn.org\">scikit-learn<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"0-the-lasso-in-convex-optimisation\">The Lasso in convex optimisation<\/h2>\n\n\n\n<p>As <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ordinary_least_squares\">ordinary least squares<\/a> the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lasso_(statistics)\">Lasso<\/a> model consists of predictions <code><span class=\"katex-eq\" data-katex-display=\"false\">X\\cdot\\beta<\/span><\/code> with feature matrix <code><span class=\"katex-eq\" data-katex-display=\"false\">X \\in \\mathbb{R}^{n, p}<\/span><\/code> and fitted coefficients <code><span class=\"katex-eq\" data-katex-display=\"false\">\\beta<\/span><\/code>. Given observations <code><span class=\"katex-eq\" data-katex-display=\"false\">y<\/span><\/code>, the coefficients are estimated by solving the convex optimisation problem<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\beta^\\star = \\argmin_\\beta \\left( P(\\beta)=\\frac{1}{2n} \\lVert y-X\\beta\\rVert_2^2 + \\alpha \\lVert\\beta\\rVert_1 \\right)<\/pre><\/div>\n\n\n\n<p>This is called the <em>primal formulation<\/em> of the Lasso. For simplicity, we ignore an intercept (bias) term and assume that <code><span class=\"katex-eq\" data-katex-display=\"false\">X<\/span><\/code> and <code><span class=\"katex-eq\" data-katex-display=\"false\">y<\/span><\/code> are mean centered.<\/p>\n\n\n\n<p>The major tool we need in this post is the <em>dual formulation<\/em>:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\nu^\\star = \\argmax_{\\{\\nu: \\lVert X'\\nu\\rVert_\\infty \\leq n\\alpha\\} } \\left( D(\\nu)=-\\frac{1}{2n} \\lVert \\nu\\rVert_2^2 + \\frac{1}{n} y\\cdot \\nu \\right)<\/pre><\/div>\n\n\n\n<p>This is an optimisation problem in terms of the dual variable <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu<\/span><\/code> which has to be in the dual feasible set <code><span class=\"katex-eq\" data-katex-display=\"false\">\\{\\nu: \\lVert X&#039;\\nu\\rVert_\\infty \\leq n\\alpha\\}<\/span><\/code>.<\/p>\n\n\n\n<p>Now the wonderful theory of convex optimisation states:<\/p>\n\n\n<div style=\"background-color: #d9edf7; color: #31708f; border-left-color: #31708f; \" class=\"ub-styled-box ub-notification-box wp-block-ub-styled-box\" id=\"ub-styled-box-df828bbd-94a7-4cfc-bd52-0b57c6b8dbfe\">\n<p id=\"ub-styled-box-notification-content-\"><strong>Weak Duality<\/strong><\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>D(\\nu) \\leq P(\\beta^\\star)<\/pre><\/div>\n\n\n\n<p>for all feasible <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu<\/span><\/code>, and with optimal <code><span class=\"katex-eq\" data-katex-display=\"false\">\\beta^\\star<\/span><\/code>.<\/p>\n\n\n\n<p><strong>Duality Gap<\/strong><\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>G(\\beta, \\nu) =P(\\beta) - D(\\nu) \\geq P(\\beta) - P(\\beta^\\star)\\geq 0<\/pre><\/div>\n\n\n\n<p>for feasible <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu<\/span><\/code>.<\/p>\n\n\n<\/div>\n\n<div style=\"background-color: #d9edf7; color: #31708f; border-left-color: #31708f; \" class=\"ub-styled-box ub-notification-box wp-block-ub-styled-box\" id=\"ub-styled-box-b3c60564-72b1-4ee4-852c-34e366f71f22\">\n<p id=\"ub-styled-box-notification-content-\"><strong>Strong Duality<\/strong><\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>D(\\nu^\\star) = P(\\beta^\\star)<\/pre><\/div>\n\n\n\n<p>This implies<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>y - X\\beta^\\star = \\nu^\\star<\/pre><\/div>\n\n\n<\/div>\n\n\n<p>Both weak and strong duality hold for the Lasso. Weak duality acts as a lower bound for the optimal primal value. The <strong>duality gap<\/strong> is therefore a conservative proxy for how close a current iteration is from the optimum. It is a very good stopping criterion for algorithms. Other optimisation problems would wish to have such a good guarantee when to stop iterating.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"1-screening-rules-in-coordinate-descent\">Screening rules in coordinate descent<\/h2>\n\n\n\n<p>The main algorithm to solver the Lasso is coordinate descent as implemented in many packages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>R: <a href=\"https:\/\/glmnet.stanford.edu\/\" data-type=\"link\" data-id=\"https:\/\/glmnet.stanford.edu\/\">glmnet<\/a><\/li>\n\n\n\n<li>Python: <a href=\"https:\/\/scikit-learn.org\">scikit-learn<\/a>, <a href=\"https:\/\/mathurinm.github.io\/celer\/\">Celer<\/a>, <a href=\"https:\/\/contrib.scikit-learn.org\/skglm\/\">skglm<\/a><\/li>\n<\/ul>\n\n\n\n<p>The core idea of coordinate descent is very simple:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Choose a coordinate j (a.k. feature or column) of X.<\/li>\n\n\n\n<li>Minimize <code><span class=\"katex-eq\" data-katex-display=\"false\">P<\/span><\/code> w.r.t. <code><span class=\"katex-eq\" data-katex-display=\"false\">\\beta_j<\/span><\/code> while keeping all the other coefficients constant.<\/li>\n\n\n\n<li>Repeat steps 1 and 2 until convergence (e.g. the dual gap is smaller than a user given tolerance).<\/li>\n<\/ol>\n\n\n\n<p>The second step even has a short closed form solution, called soft-thresholding, for the Lasso which makes it ideal for implementations.<\/p>\n\n\n\n<p>Whenever you have an iterate <code><span class=\"katex-eq\" data-katex-display=\"false\">\\beta<\/span><\/code>, you can calculate a feasible dual point <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu<\/span><\/code> as follows:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Set <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu = y - X\\beta<\/span><\/code>.<\/li>\n\n\n\n<li>If <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu<\/span><\/code> is not feasible, rescale it by its dual norm: <code><span class=\"katex-eq\" data-katex-display=\"false\">\\nu \\rightarrow \\nu \\frac{n\\alpha}{\\lVert X&#039;\\nu\\rVert_\\infty}<\/span><\/code>.<\/li>\n<\/ol>\n\n\n\n<p>The last ingredient are <strong>screening rules<\/strong>. The basic idea is the following: One important property of the Lasso is that it is able to set coefficients exactly to zero. The stronger the penalty <code><span class=\"katex-eq\" data-katex-display=\"false\">\\alpha<\/span><\/code> the more likely that some coefficients vanish. While cycling through the coordinates, <strong>safe<\/strong> screening rules allow to identify whether a coordinate can be set to zero. There are also non-safe screening rules which rely on heuristics. They later must check again.<\/p>\n\n\n\n<p>The practical most relevant gap safe screening rule is the one based on a sphere test:<\/p>\n\n\n<div style=\"background-color: #d9edf7; color: #31708f; border-left-color: #31708f; \" class=\"ub-styled-box ub-notification-box wp-block-ub-styled-box\" id=\"ub-styled-box-b76af7c8-20f9-4759-aa7a-1698e54f2299\">\n<p id=\"ub-styled-box-notification-content-\"><strong>Gap Safe Screening Rule<\/strong><\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\frac{n\\alpha-\\lvert x_j\\cdot \\nu \\rvert}{\\rVert x_j\\rVert_2}&gt;\\sqrt{2nG(\\beta,\\nu)} \\Rightarrow \\beta_j=0<\/pre><\/div>\n\n\n<\/div>\n\n\n<p>I implemented these gap safe screening rules for the scikit-learn estimators in pull requests #<a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/pull\/31882\">31882<\/a>, <a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/pull\/31986\">#31986<\/a>, <a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/pull\/31987\">#31987<\/a> and <a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/pull\/32014\">#32014<\/a>. Together with some further nice improvements they became available with scikit-learn version 1.8, see the <a href=\"https:\/\/scikit-learn.org\/stable\/auto_examples\/release_highlights\/plot_release_highlights_1_8_0.html#efficiency-improvements-in-linear-models\">release highlights<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"2-benchmark\">Benchmark<\/h2>\n\n\n\n<p>Enough  theory, time for some coding and benchmarks. Was is worth the implementation? In the following mini-benchmark, I intentionally called the estimators as a user would do. I could have just called the underlying algorithms to avoid input validation and transformation, I could have set <code>fit_intercept=False<\/code>. The improvements therefore show what a user could expect.<\/p>\n\n\n\n<p>The first benchmark is on the Leukemia dataset with only 72 observations but 7,129 features. Here, a whole path of <code>alpha<\/code> values is computed for three different values of the stopping tolerance.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"554\" height=\"453\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image.png\" alt=\"\" class=\"wp-image-2034\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image.png 554w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-300x245.png 300w\" sizes=\"auto, (max-width: 554px) 100vw, 554px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"567\" height=\"453\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-1.png\" alt=\"\" class=\"wp-image-2035\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-1.png 567w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-1-300x240.png 300w\" sizes=\"auto, (max-width: 567px) 100vw, 567px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p><strong>This shows a nice speedup of 2-4 times!<\/strong><\/p>\n\n\n\n<p>The second small benchmark is with the finance dataset, a.k.a. E2006-log1p with 16,087 observations and 4,272,227 features; this is quite large. The data is given as sparse matrix with a density of 0.14% (0.14% of entries have a value, the other entries are implicitly considered zero) which runs a different code path but with the same coordinate descent algorithm. This time, Celer and skglm are also compared.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"571\" height=\"453\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-2.png\" alt=\"\" class=\"wp-image-2036\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-2.png 571w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2026\/04\/image-2-300x238.png 300w\" sizes=\"auto, (max-width: 571px) 100vw, 571px\" \/><\/figure>\n\n\n\n<p>Two points seem worth noticing:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The gap safe screening rules of scikit-learn 1.8 show little to no improvement over version 1.7 without screening rules.<\/li>\n\n\n\n<li>Celer and skglm are significantly faster for larger penalties, skglm is always faster.<\/li>\n<\/ul>\n\n\n\n<p>Note that Celer and skglm use different refinements of the underlying coordinate descent algorithms.<\/p>\n\n\n\n<p>The full code is available as <a href=\"https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2026-04-20%20Gap%20Safe%20Screening%20Rules%20for%20Lasso.ipynb\">jupyter notebook<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"3-conclusion\">Conclusion<\/h2>\n\n\n\n<p>I would like to close this post with a few remarks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Results from theory like the gap safe screening rule can have a huge impact on algorithms.<\/li>\n\n\n\n<li>From the publication of &#8220;Mind the duality gap&#8221; until their release in scikit-learn was a gap of 10 years.<\/li>\n\n\n\n<li>Alexandre Gramfort is one of the very few people (to my knowledge the only one) who has contributed code to scikit-learn (he is a core contributor since the beginnings) and who published a paper that was implemented.<\/li>\n\n\n\n<li>There is ongoing work to bring these improvements of L1-solvers to logistic regression and other GLMs. Give your \ud83d\udc4d to pull request <a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/pull\/33160\">#33160<\/a>.<\/li>\n<\/ul>\n\n\n\n<p><strong>Further references:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Kim, S.-J., Koh, K., Lustig, M., Boyd, S., and Gorinevsky,D. &#8220;An Interior-Point Method for Large-Scale \u21131-Regularized Least Squares&#8221;. <em>IEEE J. Sel. Topics Signal Process., 1(4):606\u2013617, 2007<\/em>. <a href=\"https:\/\/doi.org\/10.1109\/JSTSP.2007.910971\">https:\/\/doi.org\/<\/a><a href=\"https:\/\/doi.org\/10.1109\/JSTSP.2007.910971\" target=\"_blank\" rel=\"noreferrer noopener\">10.1109\/JSTSP.2007.910971<\/a><\/li>\n\n\n\n<li>Friedman, J., Hastie, T. J., H\u00f6fling, H., and Tibshirani, R. &#8220;Pathwise coordinate optimization&#8221;. Ann. Appl. Stat., 1(2):302\u2013332, 2007. <a href=\"https:\/\/doi.org\/10.1214\/07-AOAS131\">https:\/\/doi.org\/10.1214\/07-AOAS131<\/a><\/li>\n\n\n\n<li>Fercoq, O., Gramfort, A., and Salmon, J. &#8220;Mind the duality gap: safer rules for the lasso&#8221;. In ICML, pp. 333\u2013342, 2015. <a href=\"https:\/\/doi.org\/10.48550\/arXiv.1505.03410\">https:\/\/doi.org\/10.48550\/arXiv.1505.03410<\/a><\/li>\n\n\n\n<li>Zou, H. and Hastie, T. &#8220;Regularization and variable selection via the elastic net&#8221;. J. Roy. Statist. Soc. Ser. B, 67(2): 301\u2013320, 2005. <a href=\"https:\/\/doi.org\/10.1111\/j.1467-9868.2005.00527.x\">https:\/\/doi.org\/10.1111\/j.1467-9868.2005.00527.x<\/a><\/li>\n\n\n\n<li>D\u00fcnner, C., Forte, S., Tak\u00e1c, M. and Jaggi, M. &#8220;Primal-Dual Rates and Certificates&#8221;. In ICML 2016 &#8211; Proceedings of the 33th International Conference on Machine Learning, pages 783\u2013792, 2016. <a href=\"https:\/\/doi.org\/10.48550\/arXiv.1602.05205\">https:\/\/doi.org\/10.48550\/arXiv.1602.05205<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"4-appendix-elasticnet\">Appendix: Elastic Net<\/h2>\n\n\n\n<p>The Elastic Net is an extension of the Lasso that adds an L2-penalty:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>P(\\beta)=\\frac{1}{2n} \\lVert y-X\\beta\\rVert_2^2 + \\alpha_1 \\lVert\\beta\\rVert_1 + \\frac{\\alpha_2}{2} \\lVert\\beta\\rVert_2^2<\/pre><\/div>\n\n\n\n<p>Very interestingly, there are two different dual formulations for it. There is the one that can be derived by extending <code><span class=\"katex-eq\" data-katex-display=\"false\">p<\/span><\/code> artificial observations to <code><span class=\"katex-eq\" data-katex-display=\"false\">X<\/span><\/code> and <code><span class=\"katex-eq\" data-katex-display=\"false\">y<\/span><\/code>, see Zou &amp; Hastie 2005:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\begin{align*}\nX \\rightarrow \\tilde{X}&amp;=\\begin{pmatrix}X\\\\ \\sqrt{n\\alpha_2}\\,\\mathbb{1}_p\\end{pmatrix}\\,,\\quad\ny \\rightarrow \\tilde{y}= \\begin{pmatrix}y\\\\ \\mathbb{0}_p\\end{pmatrix}\n\\\\\nP(\\beta)&amp;=-\\frac{1}{2n} \\lVert \\tilde{y}-\\tilde{X}\\beta\\rVert_2^2 + \\alpha_1\\lVert \\beta\\rVert_1\n\\\\\n\\nu \\rightarrow\\tilde{\\nu} &amp;= \\begin{pmatrix}\\nu\\\\ \\sqrt{n\\alpha_2}\\beta\\end{pmatrix}\\\n\\\\\nD(\\nu)&amp;=-\\frac{1}{2n} \\lVert \\nu\\rVert_2^2 + \\frac{1}{n} y\\cdot \\nu - \\frac{\\alpha_2}{2}\\lVert \\nu\\rVert_2^2\n\\\\\n\\mathrm{feasible\\, set}&amp;=\\{\\nu: \\lVert X'\\nu - n\\alpha_2\\beta\\rVert_\\infty \\leq n\\alpha_1\\}\n\\end{align*}<\/pre><\/div>\n\n\n\n<p>And dual can be derived by direct computation starting with the primal, see also D\u00fcnner et al 2016:<\/p>\n\n\n\n<div class=\"wp-block-katex-display-block katex-eq\" data-katex-display=\"true\"><pre>\\begin{align*}\nD(\\nu) =&amp; -\\frac{1}{2n} \\lVert \\nu\\rVert_2^2 + \\frac{1}{n} y\\cdot \\nu\n\\\\\n&amp;- \\frac{1}{2n^2\\alpha_2}\n-\\sum_j \\left( \\lvert x_j\\cdot\\nu\\rvert_2 - n\\alpha_1\\right)_+^2 \\,,\\quad \\alpha_2&gt;0\n\\\\\n&amp;\\mathrm{feasible\\, set}=\\mathbb{R}\\end{align*}<\/pre><\/div>\n\n\n\n<p>Here, <code><span class=\"katex-eq\" data-katex-display=\"false\">(x)_+=\\max(x,0)<\/span><\/code> denotes the positive part. Very surprisingly, the dual feasible set equals the real numbers, no restrictions! The disadvantage is that this formulation only holds for <code><span class=\"katex-eq\" data-katex-display=\"false\">\\alpha_2&gt;0<\/span><\/code>.<\/p>\n\n\n\n<p>The two different duals result in two different dual gaps for the Elastic Net. It actually makes quite a difference as investigated in scikit-learn issue <a href=\"https:\/\/github.com\/scikit-learn\/scikit-learn\/issues\/22836\">#22836<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, we explore the topic of gap safe screening rules from convex optimisation and their impact on Lasso and ElasticNet in scikit-learn.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16,41],"tags":[30,6],"class_list":["post-2015","post","type-post","status-publish","format-standard","hentry","category-machine-learning","category-optimisation","tag-data-science","tag-python"],"featured_image_src":null,"author_info":{"display_name":"Christian Lorentzen","author_link":"https:\/\/lorentzen.ch\/index.php\/author\/christian\/"},"_links":{"self":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/2015","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/comments?post=2015"}],"version-history":[{"count":49,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/2015\/revisions"}],"predecessor-version":[{"id":2073,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/2015\/revisions\/2073"}],"wp:attachment":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/media?parent=2015"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/categories?post=2015"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/tags?post=2015"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}