Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
2 changes: 1 addition & 1 deletion .claude/commands/new-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ git checkout -b blog/<slug>

## Step 4: Scaffold the post

New posts go at the top level: `content/blog/<slug>/`. The subfolders (`tidyverse/`, `shiny/`, `quarto/`, `ai/`, etc.) are reserved for ported legacy content — never scaffold a new post into one, even if `source` is set. The `source` frontmatter field controls which project blog listing the post appears on; it does not affect folder placement.
New posts go at the top level: `content/blog/<slug>/`. The `content/blog/ported/` tree is reserved for posts ported from legacy blogs — never scaffold a new post in there, even if `source` is set. The `source` frontmatter field controls which project blog listing the post appears on; it does not affect folder placement.

Description: "Scaffolding the post folder and frontmatter from the blog archetype"

Expand Down
4 changes: 2 additions & 2 deletions content/blog/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ Each entry in the JSON array has:

```json
{
"md_path": "content/blog/tidyverse/my-post/index.md",
"source_path": "content/blog/tidyverse/my-post/index.qmd", // null if no source file
"md_path": "content/blog/ported/tidyverse/my-post/index.md",
"source_path": "content/blog/ported/tidyverse/my-post/index.qmd", // null if no source file
"frontmatter": {
"title": "My Post",
"date": "2025-01-15",
Expand Down
2 changes: 1 addition & 1 deletion content/blog/_authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you're using Claude Code, the `/new-post` skill will handle scaffolding, fron

New posts go at the top level: `content/blog/my-post-slug/`.

The subfolders (`quarto/`, `tidyverse/`, `shiny/`, `ai/`, etc.) contain ported legacy content — don't use them for new posts.
Ported posts from legacy blogs live under `content/blog/ported/<source>/` — never scaffold a new post in there. New posts always go at the top level of `content/blog/`.

Create a new post with:

Expand Down
24 changes: 12 additions & 12 deletions content/blog/_editing-ported-posts.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ For quick fixes, edit the `.md` file directly. If you plan to re-render, fix the
**Link guidelines:**

- Use **absolute URLs** for external sites: `https://shiny.posit.co/r/getstarted/`
- Use **site-root paths** for internal blog links: `/blog/shiny/foo/`
- Use **permalink-form paths** for internal blog links: `/blog/YYYY-MM-DD_<slug>/` (see `_authoring-guide.md`). Don't use content-directory paths like `/blog/ported/<source>/<post>/` — those break if posts are reorganized.
- Never use `https://posit.co/blog/...` for internal links — that's a different site
- In source files, avoid relative paths like `../` — they break when posts move

Expand All @@ -84,22 +84,22 @@ Some blogs have shared virtual environments for rendering posts with executable

| Blog | R (renv) | Python (uv) |
|------|----------|-------------|
| `content/blog/ai/` | Yes | — |
| `content/blog/shiny/` | Yes | Yes |
| `content/blog/great-tables/` | — | Yes |
| `content/blog/pointblank/` | — | Yes |
| `content/blog/plotnine/` | — | Yes |
| `content/blog/ported/ai/` | Yes | — |
| `content/blog/ported/shiny/` | Yes | Yes |
| `content/blog/ported/great-tables/` | — | Yes |
| `content/blog/ported/pointblank/` | — | Yes |
| `content/blog/ported/plotnine/` | — | Yes |

**To render with the environment:**

```bash
# Python (uv)
cd content/blog/<blog>
cd content/blog/ported/<blog>
uv sync
uv run quarto render <post>/index.qmd --to hugo-md

# R (renv) - start R from the blog directory
cd content/blog/<blog>
cd content/blog/ported/<blog>
R
# then in R: renv::restore()
```
Expand All @@ -115,7 +115,7 @@ R

## Blog-specific notes

### AI blog (`content/blog/ai/`)
### AI blog (`content/blog/ported/ai/`)

The AI blog has the biggest gap between source and rendered frontmatter. Source files typically have:
- `author:` with name/affiliation/url structure
Expand All @@ -125,15 +125,15 @@ The AI blog has the biggest gap between source and rendered frontmatter. Source

**If re-rendering:** Posts with bibliographies won't get a "References" heading without a full Quarto project setup.

### Tidyverse blog (`content/blog/tidyverse/`)
### Tidyverse blog (`content/blog/ported/tidyverse/`)

Source `.Rmd` files have `output: hugodown::hugo_document`. Remove this before rendering with Quarto.

### Education blog (`content/blog/education/`)
### Education blog (`content/blog/ported/education/`)

Uses `.Rmarkdown` (source) and `.markdown` (rendered). These are usually well-synced.

### Shiny blog (`content/blog/shiny/`)
### Shiny blog (`content/blog/ported/shiny/`)

`.qmd` and `.md` files are usually in sync. Watch for `imagealt` vs `image-alt`.

Expand Down
34 changes: 18 additions & 16 deletions content/blog/_porting-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ Shallow clones of all legacy blog repos are in `/_external-sources/` (ignored by

For posts with a `.qmd` source (Quarto, Shiny, Great Tables, etc.):

1. Copy the `.qmd` and any assets (images, data files) from `_external-sources/` into the new post folder under `content/blog/`.
1. Copy the `.qmd` and any assets (images, data files) from `_external-sources/` into the new post folder under `content/blog/ported/<source>/`.
2. **Commit the copied files before editing.** This makes it easy to audit changes and see exactly what was modified from the original source.
3. Edit the frontmatter **in the `.qmd`**: replace `author` with `people`, and add `source`, `ported_from`, `port_status`, `software`, `languages`, `topics`. `source` should always match `ported_from` — it powers the `blog/q/` collection pages for each legacy blog. Move any original `categories` values into `tags` (then remove `categories`). If the folder name contains a date prefix (e.g. `2026-04-06-april-newsletter`), add a `slug` without the date (e.g. `slug: april-newsletter`) to avoid the date appearing twice in the permalink.
4. Fix links in the `.qmd`:
- Internal blog cross-links: `/docs/blog/posts/<slug>/` → `/blog/quarto/<slug>/`
- Internal blog cross-links: use the permalink form `/blog/YYYY-MM-DD_<slug>/` (see `_authoring-guide.md`). Don't use content-directory paths like `/blog/ported/<source>/<post>/` — those break if posts are reorganized.
- Quarto docs links: `/docs/...something.qmd` → `https://quarto.org/docs/...something.html` (note: `.qmd` → `.html`, not just prefixing the domain — fragment URLs like `.qmd#anchor` need manual attention as regex replacements can miss them)
5. Run `quarto render index.qmd` from the post folder to generate `index.md`.
6. Commit both `index.qmd` and `index.md`.
Expand All @@ -47,21 +47,23 @@ Don't write the `.md` by hand — always render from the `.qmd` so the two stay

## Folder structure

Posts are organized by source blog:
Ported posts live under `content/blog/ported/`, organized by source blog:

```
content/blog/
├── tidyverse/ # tidyverse.org posts
│ ├── 2017/
│ ├── 2018/
├── ported/
│ ├── tidyverse/ # tidyverse.org posts
│ │ ├── 2017/
│ │ ├── 2018/
│ │ └── ...
│ ├── education/ # education.rstudio.com posts
│ │ ├── 2019-09-24-welcome/
│ │ └── ...
│ └── ...
├── education/ # education.rstudio.com posts
│ ├── 2019-09-24-welcome/
│ └── ...
└── ... # other posts (not ported)
└── ... # top-level: new posts only
```

This preserves original folder structures and makes it easy to track porting progress.
This preserves the original folder structures of each source blog and keeps new posts separate from ported content. URLs are unaffected — the blog permalink template uses `date` + `slug`/`basename`, not the section path.

## Cross-blog links

Expand Down Expand Up @@ -129,11 +131,11 @@ These exist but need implementation:

| Post | Issue |
|------|-------|
| quarto/2024-07-02-beautiful-tables-in-typst | Complex freeze structure, needs manual porting |
| education/2019-09-24-welcome | Very blog-specific ("Welcome to education.rstudio.com") |
| education/2019-08-26-learner-personas | Was draft in source |
| ai/2018-07-17-activity-detection | Blank on legacy blog, marked as draft |
| ai/2017-09-06-keras-for-r | Broken external image (keras.rstudio.com) |
| ported/quarto/2024-07-02-beautiful-tables-in-typst | Complex freeze structure, needs manual porting |
| ported/education/2019-09-24-welcome | Very blog-specific ("Welcome to education.rstudio.com") |
| ported/education/2019-08-26-learner-personas | Was draft in source |
| ported/ai/2018-07-17-activity-detection | Blank on legacy blog, marked as draft |
| ported/ai/2017-09-06-keras-for-r | Broken external image (keras.rstudio.com) |

## Archived porting scripts

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ knitr::opts_chunk$set(echo = TRUE, eval = FALSE)

In image captioning, an algorithm is given an image and tasked with producing a sensible caption. It is a challenging task for several reasons, not the least being that it involves a notion of _saliency_ or _relevance_. This is why recent deep learning approaches mostly include some "attention" mechanism (sometimes even more than one) to help focusing on relevant image features.

In this post, we demonstrate a formulation of image captioning as an encoder-decoder problem, enhanced by spatial attention over image grid cells. The idea comes from a recent paper on *Neural Image Caption Generation with Visual Attention* [@XuBKCCSZB15], and employs the same kind of attention algorithm as detailed in our post on [machine translation](/blog/ai/2018-07-30-attention-layer/).
In this post, we demonstrate a formulation of image captioning as an encoder-decoder problem, enhanced by spatial attention over image grid cells. The idea comes from a recent paper on *Neural Image Caption Generation with Visual Attention* [@XuBKCCSZB15], and employs the same kind of attention algorithm as detailed in our post on [machine translation](/blog/2018-07-30_attention-layer/).

We're porting Python code from a recent [Google Colaboratory notebook ](https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/generative_examples/image_captioning_with_attention.ipynb?linkId=54343050&pli=1#scrollTo=io7ws3ReRPGv), using Keras with TensorFlow eager execution to simplify our lives.

Expand Down Expand Up @@ -346,7 +346,7 @@ train_dataset <-
## Captioning model


The model is basically the same as that discussed in the [machine translation post](/blog/ai/2018-07-30-attention-layer/). Please refer to that article for an explanation of the concepts, as well as a detailed walk-through of the tensor shapes involved at every step. Here, we provide the tensor shapes as comments in the code snippets, for quick overview/comparison.
The model is basically the same as that discussed in the [machine translation post](/blog/2018-07-30_attention-layer/). Please refer to that article for an explanation of the concepts, as well as a detailed walk-through of the tensor shapes involved at every step. Here, we provide the tensor shapes as comments in the code snippets, for quick overview/comparison.

However, if you develop your own models, with eager execution you can simply insert debugging/logging statements at arbitrary places in the code - even in model definitions. So you can have a function

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ slug: eager-captioning

In image captioning, an algorithm is given an image and tasked with producing a sensible caption. It is a challenging task for several reasons, not the least being that it involves a notion of *saliency* or *relevance*. This is why recent deep learning approaches mostly include some "attention" mechanism (sometimes even more than one) to help focusing on relevant image features.

In this post, we demonstrate a formulation of image captioning as an encoder-decoder problem, enhanced by spatial attention over image grid cells. The idea comes from a recent paper on *Neural Image Caption Generation with Visual Attention* (Xu et al. 2015), and employs the same kind of attention algorithm as detailed in our post on [machine translation](/blog/ai/2018-07-30-attention-layer/).
In this post, we demonstrate a formulation of image captioning as an encoder-decoder problem, enhanced by spatial attention over image grid cells. The idea comes from a recent paper on *Neural Image Caption Generation with Visual Attention* (Xu et al. 2015), and employs the same kind of attention algorithm as detailed in our post on [machine translation](/blog/2018-07-30_attention-layer/).

We're porting Python code from a recent [Google Colaboratory notebook](https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/generative_examples/image_captioning_with_attention.ipynb?linkId=54343050&pli=1#scrollTo=io7ws3ReRPGv), using Keras with TensorFlow eager execution to simplify our lives.

Expand Down Expand Up @@ -334,7 +334,7 @@ train_dataset <-

## Captioning model

The model is basically the same as that discussed in the [machine translation post](/blog/ai/2018-07-30-attention-layer/). Please refer to that article for an explanation of the concepts, as well as a detailed walk-through of the tensor shapes involved at every step. Here, we provide the tensor shapes as comments in the code snippets, for quick overview/comparison.
The model is basically the same as that discussed in the [machine translation post](/blog/2018-07-30_attention-layer/). Please refer to that article for an explanation of the concepts, as well as a detailed walk-through of the tensor shapes involved at every step. Here, we provide the tensor shapes as comments in the code snippets, for quick overview/comparison.

However, if you develop your own models, with eager execution you can simply insert debugging/logging statements at arbitrary places in the code - even in model definitions. So you can have a function

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ As computer programs work with numbers, the cost function has to be pretty speci
In some cases it may be straightforward to map a task to a measure of error, in others, it may not. Consider the task of generating non-existing objects of a certain type (like a face, a scene, or a video clip). How do we quantify success?
The trick with _generative adversarial networks_ (GANs) is to let the network learn the cost function.

As shown in [Generating images with Keras and TensorFlow eager execution](/blog/ai/2018-08-26-eager-dcgan/), in a simple GAN the setup is this: One agent, the _generator_, keeps on producing fake objects. The other, the _discriminator_, is tasked to tell apart the real objects from the fake ones. For the generator, loss is augmented when its fraud gets discovered, meaning that the generator's cost function depends on what the discriminator does. For the discriminator, loss grows when it fails to correctly tell apart generated objects from authentic ones.
As shown in [Generating images with Keras and TensorFlow eager execution](/blog/2018-08-26_keydana2018eagerdcgan/), in a simple GAN the setup is this: One agent, the _generator_, keeps on producing fake objects. The other, the _discriminator_, is tasked to tell apart the real objects from the fake ones. For the generator, loss is augmented when its fraud gets discovered, meaning that the generator's cost function depends on what the discriminator does. For the discriminator, loss grows when it fails to correctly tell apart generated objects from authentic ones.

In a GAN of the type just described, creation starts from white noise. However in the real world, what is required may be a form of transformation, not creation. Take, for example, colorization of black-and-white images, or conversion of aerials to maps. For applications like those, we _condition_ on additional input: Hence the name, _conditional adversarial networks_.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ As computer programs work with numbers, the cost function has to be pretty speci
In some cases it may be straightforward to map a task to a measure of error, in others, it may not. Consider the task of generating non-existing objects of a certain type (like a face, a scene, or a video clip). How do we quantify success?
The trick with *generative adversarial networks* (GANs) is to let the network learn the cost function.

As shown in [Generating images with Keras and TensorFlow eager execution](/blog/ai/2018-08-26-eager-dcgan/), in a simple GAN the setup is this: One agent, the *generator*, keeps on producing fake objects. The other, the *discriminator*, is tasked to tell apart the real objects from the fake ones. For the generator, loss is augmented when its fraud gets discovered, meaning that the generator's cost function depends on what the discriminator does. For the discriminator, loss grows when it fails to correctly tell apart generated objects from authentic ones.
As shown in [Generating images with Keras and TensorFlow eager execution](/blog/2018-08-26_keydana2018eagerdcgan/), in a simple GAN the setup is this: One agent, the *generator*, keeps on producing fake objects. The other, the *discriminator*, is tasked to tell apart the real objects from the fake ones. For the generator, loss is augmented when its fraud gets discovered, meaning that the generator's cost function depends on what the discriminator does. For the discriminator, loss grows when it fails to correctly tell apart generated objects from authentic ones.

In a GAN of the type just described, creation starts from white noise. However in the real world, what is required may be a form of transformation, not creation. Take, for example, colorization of black-and-white images, or conversion of aerials to maps. For applications like those, we *condition* on additional input: Hence the name, *conditional adversarial networks*.

Expand Down
Loading
Loading