<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>S. Keshav</title>
<link>https://svr-sk818-web.cl.cam.ac.uk/keshav/blog/</link>
<atom:link href="https://svr-sk818-web.cl.cam.ac.uk/keshav/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Using computer science to address the climate crisis</description>
<generator>quarto-1.9.37</generator>
<lastBuildDate>Tue, 08 Sep 2026 23:00:00 GMT</lastBuildDate>
<item>
  <title>Evaluating your downstream task with Tessera embeddings</title>
  <link>https://svr-sk818-web.cl.cam.ac.uk/keshav/blog/posts/2026-09-09-evaluating-downstream-tasks-with-tessera-embeddings.html</link>
  <description><![CDATA[ 




<p><a href="https://geotessera.org">Tessera</a> compresses a year of Sentinel-1 and Sentinel-2 imagery into a single 128-dimensional embedding for every 10 m pixel, which serves as a general-purpose summary of what that patch of ground looked like and how it changed. For an ecologist the practical question is: are these embeddings good enough for my task, and which ML model should I use? The <code>tessera-eval</code> Python package helps to answer that.</p>
<p>Assuming that your ground truth is in a shapefile, the basic idea is that each labelled pixel is matched with a corresponding embedding vector; your labels plus those vectors then beocome a standard supervised-learning problem. <code>tessera-eval</code> handles the details, fetching and mosaicking the Tessera tiles, sampling pixels from your polygons, and scoring models. It supports both classification (habitat class, land cover) and regression (canopy height, biomass, percent cover).</p>
<p>It supports:</p>
<ul>
<li><strong>k-fold cross-validation</strong> — a robust accuracy estimate (macro-F1, or R²/RMSE/MAE for regression) with the fold-to-fold spread.</li>
<li><strong>Learning curves</strong> — accuracy versus training-set size, so you can see whether collecting more labels would still help.</li>
<li><strong>Confusion matrices</strong>, and predicted-vs-actual scatters for regression.</li>
<li><strong>Spatial train/test splits</strong> — hold out a whole region instead of random pixels, so nearby look-alike pixels can’t inflate the score.</li>
<li><strong>Spatial-context models</strong> — MLPs over a 3×3 or 5×5 neighbourhood, alongside per-pixel k-NN, random forest, XGBoost and MLP.</li>
</ul>
<p>Here is a minimal run. <a href="https://raw.githubusercontent.com/ucam-eo/tessera-eval/main/examples/austria_crops.geojson"><code>austria_crops.geojson</code></a> is 349 field parcels near Vienna labelled by crop type:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> geopandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> gpd</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> geotessera <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> GeoTessera</span>
<span id="cb1-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tessera_eval <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_embeddings_for_shapefile, run_kfold_cv</span>
<span id="cb1-4"></span>
<span id="cb1-5">gdf <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gpd.read_file(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"austria_crops.geojson"</span>).to_crs(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># polygons with a "crop" column</span></span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># One embedding per 10 m pixel inside your polygons (downloads tiles on first run)</span></span>
<span id="cb1-8">vectors, labels, class_names, stats <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> load_embeddings_for_shapefile(</span>
<span id="cb1-9">    gdf, field<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"crop"</span>, year<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2024</span>, gt_instance<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>GeoTessera()</span>
<span id="cb1-10">)</span>
<span id="cb1-11"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(labels)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:,}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> labelled pixels across </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(class_names)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> classes"</span>)</span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5-fold cross-validation, random forest on the raw embeddings</span></span>
<span id="cb1-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> event <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> run_kfold_cv(vectors, labels, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rf"</span>], k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, task<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classification"</span>):</span>
<span id="cb1-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate"</span>:</span>
<span id="cb1-16">        m <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> event[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"models"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rf"</span>]</span>
<span id="cb1-17">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"macro-F1: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'mean_f1'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ± </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'std_f1'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.3f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div></div>
<p>That prints macro-F1: 0.796 ± 0.004, a reasonable result for 10-way crop classification from embeddings alone. Replace [“rf”] with [“nn”, “rf”, “xgboost”, “mlp”] to compare all four per-pixel models at once. For a continuous target (canopy height, biomass, percent cover), pass task=“regression” with the regressor names — [“nn_reg”, “rf_reg”, “xgboost_reg”, “mlp_reg”] to get R²/RMSE/MAE instead of F1. One caveat: random k-fold on satellite data is optimistic because neighbouring pixels are correlated. For a realistic comparison, use a spatial split.</p>
<p>Install with <code>pip install "tessera-eval[geotessera,xgboost]"</code>. A follow-up post will cover the command-line interface, which runs the same evaluations without writing any Python.</p>
<p>To find out more, here is the link to the <a href="https://github.com/ucam-eo/tessera-eval">git repo</a>.</p>



 ]]></description>
  <category>tessera</category>
  <category>machine learning</category>
  <guid>https://svr-sk818-web.cl.cam.ac.uk/keshav/blog/posts/2026-09-09-evaluating-downstream-tasks-with-tessera-embeddings.html</guid>
  <pubDate>Tue, 08 Sep 2026 23:00:00 GMT</pubDate>
</item>
</channel>
</rss>
