{"id":17025,"date":"2018-11-16T15:34:36","date_gmt":"2018-11-16T14:34:36","guid":{"rendered":"https:\/\/thingsolver.com\/blog\/time-series-anomaly-detection-using-a-variational-autoencoder-vae\/"},"modified":"2024-12-02T13:45:15","modified_gmt":"2024-12-02T12:45:15","slug":"detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae","status":"publish","type":"post","link":"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/","title":{"rendered":"Detekcija anomalija vremenske serije pomoc\u0301u varijacionog autoenkodera (VAE)"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_72 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Sadr\u017eaj<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/#Zasto_detekcija_anomalija_vremenske_serije\" title=\"Za\u0161to detekcija anomalija vremenske serije?\">Za\u0161to detekcija anomalija vremenske serije?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/#Kontekst\" title=\"Kontekst\">Kontekst<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/#Koji_model_Neuronske_mreze_stupaju_na_scenu%E2%80%A6\" title=\"Koji model? Neuronske mre\u017ee stupaju na scenu\u2026\">Koji model? Neuronske mre\u017ee stupaju na scenu\u2026<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/#Kopanje_dublje_u_varijacione_autoenkodere%E2%80%A6\" title=\"Kopanje dublje u varijacione autoenkodere\u2026\">Kopanje dublje u varijacione autoenkodere\u2026<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/#Teorija_je_sjajna%E2%80%A6_Ali_kako_stoje_stvari_u_stvarnom_svetu\" title=\"Teorija je sjajna\u2026 Ali kako stoje stvari u stvarnom svetu?\">Teorija je sjajna\u2026 Ali kako stoje stvari u stvarnom svetu?<\/a><\/li><\/ul><\/nav><\/div>\n<h2 style=\"text-align: center;\"><span class=\"ez-toc-section\" id=\"Zasto_detekcija_anomalija_vremenske_serije\"><\/span><strong>Za\u0161to detekcija anomalija vremenske serije?<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>&nbsp;<\/p>\n<p>Recimo da pratite veliki broj poslovnih ili tehni\u010dkih KPI-ja (koji mogu imati sezonski karakter i \u0161umove). U va\u0161em je interesu da automatski izolujete vremenski okvir za jedan KPI \u010dije pona\u0161anje odstupa od normalnog pona\u0161anja (kontekstualna anomalija \u2013 za definiciju pogledajte ovaj <a href=\"https:\/\/thingsolver.com\/anomaly-detection\/\">post<\/a>). Kada imate problemati\u010dan vremenski okvir pri ruci, mo\u017eete dalje istra\u017eiti vrednosti tog KPI-ja. Zatim mo\u017eete povezati anomaliju sa doga\u0111ajem koji je izazvao neo\u010dekivano pona\u0161anje. Ono \u0161to je najva\u017enije, tada mo\u017eete delovati na osnovu informacija.<\/p>\n<p>Kako bismo izvr\u0161ili automatsku izolaciju vremenskog prozora, potreban nam je model ma\u0161inskog u\u010denja za detekciju Cilj ovog posta je da predstavi <a href=\"https:\/\/en.wikipedia.org\/wiki\/Probabilistic_neural_network\" target=\"_blank\" rel=\"noopener\">probabilisti\u010dku neuronsku mre\u017eu<\/a>\u00a0 (VAE) kao model ma\u0161inskog u\u010denja vremenske serije i istra\u017ei njenu upotrebu u oblasti detekcije anomalija. Kako ovaj post poku\u0161ava da smanji matematiku \u0161to je vi\u0161e moguc\u0301e, ipak je potrebno odre\u0111eno poznavanje <a href=\"https:\/\/www.deeplearningbook.org\/\" target=\"_blank\" rel=\"noopener\">neuronske mre\u017ee<\/a> i verovatno\u0107e.<\/p>\n<h2><\/h2>\n<h2 style=\"text-align: center;\"><span class=\"ez-toc-section\" id=\"Kontekst\"><\/span><strong>Kontekst<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>&nbsp;<\/p>\n<p>Kako je Valentina pomenula u njenom <a href=\"https:\/\/thingsolver.com\/anomaly-detection\/\">postu<\/a> postoje tri razli\u010dita pristupa detekciji anomalija kori\u0161\u0107enjem ma\u0161inskog u\u010denja zasnovanog na dostupnosti oznaka:<\/p>\n<ol>\n<li>nenadzirana detekcija anomalija<\/li>\n<li>polunadzirana detekcija anomalija<\/li>\n<li>nadzirana detekcija anomalija<\/li>\n<\/ol>\n<p>Potrebno je da neko ko poznaje domen manuelno dodeli oznake. Stoga je sticanje preciznih i opse\u017enih oznaka dugotrajan i skup proces. Namerno sam stavio nenadziranu detekciju kao prvi pristup, jer ne zahteva oznake. Me\u0111utim, to zahteva da normalni slu\u010dajevi budu brojniji od abnormalnih. Me\u0111utim, to zahteva da normalni slu\u010dajevi budu brojniji od abnormalnih.<\/p>\n<h2><\/h2>\n<h2 style=\"text-align: center;\"><span class=\"ez-toc-section\" id=\"Koji_model_Neuronske_mreze_stupaju_na_scenu%E2%80%A6\"><\/span><strong>Koji model? Neuronske mre\u017ee stupaju na scenu\u2026<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\"><img decoding=\"async\" class=\"alignnone size-full wp-image-8690\" src=\"https:\/\/thingsolver.com\/wp-content\/uploads\/Autoencoder-768x590-1.png\" alt=\"\" width=\"768\" height=\"590\" title=\"\"><\/span><\/p>\n<p>Istorijski gledano, razli\u010dite vrste neuronskih mre\u017ea su imale uspeha u modeliranju slo\u017eenih nelinearnih podataka (npr. slike, zvuk i tekstualni podaci). Me\u0111utim, budu\u0107i univerzalni aproksimatori funkcija kakvi jesu, oni su neizbe\u017eno na\u0161li svoj put u modeliranju tabelarnih podataka. Jedna zanimljiva vrsta tabelarnog modeliranja podataka je modeliranje vremenskih serija.<\/p>\n<p>Model koji je izvr\u0161io prelazak sa slo\u017eenih podataka na tabelarne podatke je Autoenkoder (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Autoencoder\" target=\"_blank\" rel=\"noopener\">AE<\/a>). Autoenkoder se sastoji iz dva dela \u2013 <em>enkodera i dekodera<\/em>. Poku\u0161ava da nau\u010di manji prikaz svog inputa (enkoder) i zatim rekonstrui\u0161e svoj input iz tog manjeg prikaza (dekoder). Ocena anomalije je dizajnirana da odgovara gre\u0161ci rekonstrukcije.<\/p>\n<p>Autoenkoder ima probabilisti\u010dkog brata Varijacionog autoenkodera (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Autoencoder%23Variational_autoencoder_(VAE)\" target=\"_blank\" rel=\"noopener\">VAE<\/a>), bajesovsku neuronsku mre\u017eu. Poku\u0161ava da ne rekonstrui\u0161e originalni input, vec\u0301 parametre (izabrane) distribucije izlaznih vrednosti. Ocena anomalije je dizajnirana da odgovara verovatnoc\u0301i anomalije. Izbor distribucije je zadatak koji zavisi od problema, a mo\u017ee biti i putanja istra\u017eivanja. Sada ulazimo u malo vi\u0161e tehni\u010dkih detalja.<\/p>\n<p>&nbsp;<\/p>\n<p>I AE i VAE koriste klizni prozor vrednosti KPI kao input. Performanse modela su uglavnom odre\u0111ene veli\u010dinom kliznog prozora.<\/p>\n<h2 style=\"text-align: center;\"><span class=\"ez-toc-section\" id=\"Kopanje_dublje_u_varijacione_autoenkodere%E2%80%A6\"><\/span><strong>Kopanje dublje u varijacione autoenkodere\u2026<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>&nbsp;<\/p>\n<h2><img decoding=\"async\" class=\"alignnone size-full wp-image-8691\" src=\"https:\/\/thingsolver.com\/wp-content\/uploads\/VAE-Time-series-anomaly-detection-detailed-1-1024x361-1.png\" alt=\"\" width=\"1024\" height=\"361\" title=\"\" srcset=\"https:\/\/thingsolver.com\/wp-content\/uploads\/VAE-Time-series-anomaly-detection-detailed-1-1024x361-1.png 1024w, https:\/\/thingsolver.com\/wp-content\/uploads\/VAE-Time-series-anomaly-detection-detailed-1-1024x361-1-768x271.png 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/h2>\n<p>&nbsp;<\/p>\n<p>Manje zastupljenost u kontekstu VAE naziva se latentna varijabla i ima prethodnu distribuciju (odabranu da bude <a href=\"https:\/\/en.wikipedia.org\/wiki\/Normal_distribution\" target=\"_blank\" rel=\"noopener\">Normalna distribucija<\/a>). Enkoder je njegova zadnja distribucija, a dekoder je njegova distribucija verovatnoc\u0301e. Oba predstavljaju normalnu distribuciju u na\u0161em zadatku. Dodavanje unapred bi bilo slede\u0107e:<\/p>\n<ol>\n<li>Kodirati instancu u srednju vrednost i standardnu devijaciju latentne varijable<\/li>\n<li>Uzorak iz distribucije latentne varijable<\/li>\n<li>Dekodirati uzorak u srednju vrednost i standardnu devijaciju izlazne varijable<\/li>\n<li>Uzorak iz distribucije izlazne varijable<\/li>\n<\/ol>\n<p>Varijacijski autoenkoder kao probabilisti\u010dka neuronska mre\u017ea (tako\u0111e nazvana Bajesova neuronska mre\u017ea). To je tako\u0111e vrsta grafi\u010dkog modela. Dubinski opis grafi\u010dkih modela mo\u017ee se na\u0107i u Poglavlju 8 <a href=\"http:\/\/users.isr.ist.utl.pt\/~wurmd\/Livros\/school\/Bishop%20-%20Pattern%20Recognition%20And%20Machine%20Learning%20-%20Springer%20%202006.pdf\" target=\"_blank\" rel=\"noopener\">Ma\u0161insko u\u010denje i prepoznavanje obrazaca<\/a> <a href=\"https:\/\/www.microsoft.com\/en-us\/research\/people\/cmbishop\/\" target=\"_blank\" rel=\"noopener\">Kristofera Bi\u0161opa<\/a>.<\/p>\n<p>Definicija modela TensorFlow:<\/p>\n<pre>class VAE(object):\r\n    def __init__(self, kpi, z_dim=None, n_dim=None, hidden_layer_sz=None):\r\n        \"\"\"\r\n        Args:\r\n          z_dim : dimension of latent space.\r\n          n_dim : dimension of input data.\r\n        \"\"\"\r\n        if not z_dim or not n_dim:\r\n            raise ValueError(\"You should set z_dim\"\r\n                             \"(latent space) dimension and your input n_dim.\"\r\n                             \" \\n            \")\r\n\r\n        tf.reset_default_graph()\r\n        \r\n        def make_prior(code_size):\r\n            loc = tf.zeros(code_size)\r\n            scale = tf.ones(code_size)\r\n            return tfd.MultivariateNormalDiag(loc, scale)\r\n\r\n        self.z_dim = z_dim\r\n        self.n_dim = n_dim\r\n        self.kpi = kpi\r\n        self.dense_size = hidden_layer_sz\r\n        \r\n        self.input = tf.placeholder(dtype=tf.float32,shape=[None, n_dim], name='KPI_data')\r\n        self.batch_size = tf.placeholder(tf.int64, name=\"init_batch_size\")\r\n\r\n        # tf.data api\r\n        dataset = tf.data.Dataset.from_tensor_slices(self.input).repeat() \\\r\n            .batch(self.batch_size)\r\n        self.ite = dataset.make_initializable_iterator()\r\n        self.x = self.ite.get_next()\r\n        \r\n        # Define the model.\r\n        self.prior = make_prior(code_size=self.z_dim)\r\n        x = tf.contrib.layers.flatten(self.x)\r\n        x = tf.layers.dense(x, self.dense_size, tf.nn.relu)\r\n        x = tf.layers.dense(x, self.dense_size, tf.nn.relu)\r\n        loc = tf.layers.dense(x, self.z_dim)\r\n        scale = tf.layers.dense(x, self.z_dim , tf.nn.softplus)\r\n        self.posterior = tfd.MultivariateNormalDiag(loc, scale)\r\n        self.code = self.posterior.sample()\r\n\r\n        # Define the loss.\r\n        x = self.code\r\n        x = tf.layers.dense(x, self.dense_size, tf.nn.relu)\r\n        x = tf.layers.dense(x, self.dense_size, tf.nn.relu)\r\n        loc = tf.layers.dense(x, self.n_dim)\r\n        scale = tf.layers.dense(x, self.n_dim , tf.nn.softplus)\r\n        self.decoder = tfd.MultivariateNormalDiag(loc, scale)\r\n        self.likelihood = self.decoder.log_prob(self.x)\r\n        self.divergence = tf.contrib.distributions.kl_divergence(self.posterior, self.prior)\r\n        self.elbo = tf.reduce_mean(self.likelihood - self.divergence)\r\n        self._cost = -self.elbo\r\n        \r\n        self.saver = tf.train.Saver()\r\n        self.sess = tf.Session()\r\n\r\n<\/pre>\n<pre>def fit(self, Xs, learning_rate=0.001, num_epochs=10, batch_sz=200, verbose=True):\r\n        \r\n        self.optimize = tf.train.AdamOptimizer(learning_rate).minimize(self._cost)\r\n\r\n        batches_per_epoch = int(np.ceil(len(Xs[0]) \/ batch_sz))\r\n        print(\"\\n\")\r\n        print(\"Training anomaly detector\/dimensionalty reduction VAE for KPI\",self.kpi)\r\n        print(\"\\n\")\r\n        print(\"There are\",batches_per_epoch, \"batches per epoch\")\r\n        start = timer()\r\n        \r\n        self.sess.run(tf.global_variables_initializer())\r\n        \r\n        for epoch in range(num_epochs):\r\n            train_error = 0\r\n\r\n            \r\n            self.sess.run(\r\n                self.ite.initializer,\r\n                feed_dict={\r\n                    self.input: Xs,\r\n                    self.batch_size: batch_sz})\r\n\r\n            for step in range(batches_per_epoch):\r\n                _, loss = self.sess.run([self.optimize, self._cost])\r\n                train_error += loss\r\n                if step == (batches_per_epoch - 1):\r\n                        mean_loss = train_error \/ batches_per_epoch   \r\n            if verbose:\r\n                print(\r\n                    \"Epoch {:^6} Loss {:0.5f}\"  .format(\r\n                        epoch + 1, mean_loss))\r\n                \r\n            if train_error == np.nan:\r\n                return False\r\n        end = timer()\r\n        print(\"\\n\")\r\n        print(\"Training time {:0.2f} minutes\".format((end - start) \/ (60)))\r\n        return True<\/pre>\n<h2 style=\"text-align: center;\"><span class=\"ez-toc-section\" id=\"Teorija_je_sjajna%E2%80%A6_Ali_kako_stoje_stvari_u_stvarnom_svetu\"><\/span><strong>Teorija je sjajna\u2026 Ali kako stoje stvari u stvarnom svetu?<\/strong><span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>&nbsp;<\/p>\n<p>Kori\u0161\u0107enje modela na jednom o setova podataka iz Numenta repera anomalija (<a href=\"https:\/\/github.com\/numenta\/NAB\" target=\"_blank\" rel=\"noopener\">NAB<\/a>):<\/p>\n<p>&nbsp;<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-8693\" src=\"https:\/\/thingsolver.com\/wp-content\/uploads\/test_set-1024x277-1.png\" alt=\"\" width=\"1024\" height=\"277\" title=\"\" srcset=\"https:\/\/thingsolver.com\/wp-content\/uploads\/test_set-1024x277-1.png 1024w, https:\/\/thingsolver.com\/wp-content\/uploads\/test_set-1024x277-1-768x208.png 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>U ovom slu\u010daju model je uspeo da postigne pravu pozitivnu stopu (TPR = 1,0) i la\u017enu pozitivnu stopu (FPR = 0,07). Za razli\u010dte pragove verovatno\u0107e anomalija dobijamo <a href=\"https:\/\/en.wikipedia.org\/wiki\/Receiver_operating_characteristic\" target=\"_blank\" rel=\"noopener\">ROC krivu<\/a>:<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-8694\" src=\"https:\/\/thingsolver.com\/wp-content\/uploads\/roc_curve.png\" alt=\"\" width=\"621\" height=\"296\" title=\"\"><\/p>\n<p>Odabirom o\u010ditanog praga sa ROC krive, dobijamo sledec\u0301e iz testnog skupa:<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-8695\" src=\"https:\/\/thingsolver.com\/wp-content\/uploads\/model_output.png\" alt=\"\" width=\"531\" height=\"343\" title=\"\"><\/p>\n<p>Ba\u0161 kao \u0161to je ROC kriva sugerisala, model je bio u stanju da u potpunosti uhvati abnormalno pona\u0161anje. Na\u017ealost, po\u0161to je svim modelima neuronskih mre\u017ea potrebno pode\u0161avanje hiperparametara, ova zver nije izuzetak. Me\u0111utim, jedini hiperparametar koji mo\u017ee u velikoj meri uticati na performanse je veli\u010dina kliznog prozora.<\/p>\n<p>Nadam se da sam uspeo da predstavim ovaj prili\u010dno slo\u017een model jednostavnim re\u010dima. Ohrabrujem vas da isprobate model na drugim skupovima podataka dostupnim <a href=\"http:\/\/odds.cs.stonybrook.edu\/\" target=\"_blank\" rel=\"noopener\">ovde<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<p>Nastavite sa u\u010denjem i re\u0161avanjem problema!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Za\u0161to detekcija anomalija vremenske serije? &nbsp; Recimo da pratite veliki broj poslovnih ili tehni\u010dkih KPI-ja (koji mogu imati sezonski karakter i \u0161umove). U va\u0161em je interesu da automatski izolujete vremenski okvir za jedan KPI \u010dije pona\u0161anje odstupa od normalnog pona\u0161anja &#8230; <\/p>\n<p class=\"read-more\"><a class=\"btn small\" href=\"https:\/\/thingsolver.com\/sr\/blog\/detekcija-anomalija-vremenske-serije-pomocu-varijacionog-autoenkodera-vae\/\">Pro\u010ditaj vi\u0161e<\/a><\/p>\n","protected":false},"author":15,"featured_media":16936,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[189],"tags":[251,287,288,196,278,289,290,291,292,293,294,295,296],"acf":[],"_links":{"self":[{"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/posts\/17025"}],"collection":[{"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/comments?post=17025"}],"version-history":[{"count":3,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/posts\/17025\/revisions"}],"predecessor-version":[{"id":17033,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/posts\/17025\/revisions\/17033"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/media\/16936"}],"wp:attachment":[{"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/media?parent=17025"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/categories?post=17025"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thingsolver.com\/sr\/wp-json\/wp\/v2\/tags?post=17025"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}