Back To Blog

Heirloom

A couple of years ago my wife decided to try and record her father’s life story. He had an interesting life, growing up in a working class family in England, going to Cambridge on a scholarship, lived in London in the ‘60s and early ‘70s, and went on to a storied career as a biochemist and mathematician in the US, before moving back to the UK, in rural Cambridgeshire, where he lives now. We were in Kings Lynn, where her father had grown up in a council estate. They walked around areas he’d known as a boy, and she hoped it would jog his memory, and lead to a flood of childhood recollections. He did remember many things, but she had difficulty shaping them into a coherent narrative, since his memories were often disparate, or solipsistic, as most people’s stories are, when they talk about their lives. She had a hard time separating fact from misplaced memories. After she told me about the experience, I realised that not only do most people not know how to tell stories but, perhaps more to the point, most people don’t know how to INTERVIEW.

That was the seed of Heirloom.

[Section: The demo, and the crowded market]

I originally built Heirloom as a simple demo, an experiment in using AI to create a narrative. A user could create an interview subject, run a session interviewing their subject, then build a narrative with the results. As a placeholder, I had Claude.ai generate three interviews with three (fictitious) people from different backgrounds and countries. The AI did a decent job of generating a narrative.

But after I finished the demo I did some basic market research and discovered at least a half-dozen other apps: StoryWorth, which had been around since 2014, ALifeUntold, and newer apps like Remento. I had to find a way to make Heirloom unique if it was going to gain any traction.

The existing apps fell roughly into three camps: 

  • Some (StoryTerrace, LiveBook Memories, No Story Lost) provide actual human beings, writers or journalists, to conduct the interviews and ghost-write the narrative. This is the gold standard, and nothing can replace a professional writer in constructing a good story. But these services don’t come cheap. 
  • Most of the others provided a library of dozens, even hundreds of, prompts for users to try, using trial and error to generate the optimal story. Some sent out one question a week, with the elder writing out an answer, others offered a variety of choices. 

The “hundreds of expert-crafted prompts” feature DOES offer choice, but a static choice. A prompt list can’t adjust mid-conversation. The pre-existing prompt structure, even when there are hundreds of them, will tend to return generic responses.

I wondered if there was an opening here: using LLMs not just to generate a narrative out of repeated Q and A, but to help the interviewer, the adult child of the parent being interviewed, with the interview process itself. An iterative interview process to shape and refine the narrative until the optimal story was produced.

I used to work in documentary TV, and a lot of what I did was conducting interviews. Mostly pre-interviews, though I did interviews on camera from time to time. I never developed a methodology: I acted intuitively. I would learn a bit about whatever subject I was researching, and winged it with the person I was interviewing. I learned when to ask questions, what to ask questions on, what to focus in on. Maybe it came from always being curious about people, who knows.

For example, I worked on a TV series about golf course technology. I knew nothing about golf, had never played golf, and found the sport fantastically boring whenever I saw it on TV. I thought it would be a painfully uninteresting assignment. I was wrong. 

Over the course of a couple of weeks, I interviewed golf course architects, golf course landscapers and, most fascinating of all, a so-called “bulldozer artist”. With the architects, I started by asking what features they looked for when they designed a golf course. They talked about their courses almost as works of art: how they’d include different species of trees, shrubs and flowers to complement the natural landscape; how they’d take into account the creation of micro-climates, where to put rivers, lakes and marshes, and how those features would affect play. The landscapers talked about how they’d fill out the architect’s vision, what they could and couldn’t do, and how they chose their bulldozer artists.

The bulldozer artist was exactly that, an artist. He described how, when he was driving through a landscape, he’d get out of his car, walk up a hill, and lie down to get the feel of it. How he’d study rivers, valleys, stands of forest, imagining how he could recreate them.

I conducted a dozen or so interviews like these, often on subjects I knew little about when I started. As a general principle, I found people would always open up if 1) you were genuinely interested in what they did, and 2) you could ask intelligent questions, and learn as you went.

Obviously, interviewing a parent is not like interviewing a potential subject for a documentary. The concerns are different. Genuine interest, is already there: a child is going to be interested in their parent’s story. The second is the hard part: knowing when an answer is routine and when it’s gold, and knowing which thread to follow. Nobody would have read about “golf courses are challenging.” Everybody remembers the man lying on the hill. Asking the write questions to draw out he subject’s stories, then using the answers to shape the narrative is key.

I saw it as an iterative process, but I couldn’t figure out how to pull it off, or how to do it in a way that wouldn’t be too intrusive or too wearying for an older person, or place too much emphasis on technology that might be alienating or even incomprehensible.

If we could encode even a rough version of the skill of interviewing (understanding when a subject is open or guarded, following an unexpected thread when something of interest appears, knowing when to listen and when to ask), I felt we could enhance the interview process and return a more interesting, and thus more human, narrative.

My goal is that by integrating AI into the interview process, Heirloom can come closer to human interviewers at a much reduced price, and can, hopefully, teach people how to interview and appreciate what goes into the art of the interview.

Now the question becomes: CAN AI actually do this?

In the next post, I’ll look at what AI can and can’t do here.

Back To Blog

Part 2: From Claude.ai to Claude Code – and Back Again

This is the 2nd part of a series on using Claude Code. The introduction is here.

I’ve been working on Heirloom, an AI-assisted family memoir platform using a Laravel API as a backend, and a front end in Next.js. I’d got it to the first stage, where I could feed in (AI-generated) transcriptions of an imaginary interview and generate a coherent narrative out of the back and forth. I needed to take it to the next level – expand the admin panel to view the completed narratives, and start refining the interview process to allow iteration. I thought this might be a good place to start with Claude Code. I configured my Claude Code with the Claude.mdfile the actual creator of Claude Code uses:

So far so good. I had CC (Claude Code) run a scan of the apps to detect any issues. It found a couple of small things it duly fixed. Then, following the guidlines set out by Anthropic’s own Skilljar tutorial series, I put Claude Code in ‘Plan’ mode and had it lay out what it would then configured it so I had to sign off on each of the changes it made. I’d reached stage one with the Heirloom app I am working on, a Laravel dashboard which serves as an API for the Storywriter app (built with React Native) and the Heirloom front end (built with Next.js). I needed to:

  • expand the admin to read the narratives generated from each session (using the dummy transcripts I had Claude.ai generate to simulate an interview. I could read them in the Next.js front end, but I needed to be able to read them in the Laravel API backend as well.
  • I needed to be able to delete the sessions, which were already running into the dozens as I tested features over and over, and the narratives, when they appeared.
  • I then needed to make the sessions iterative, allowing the user to ask one set of questions, generate a narrative, then ask a follow up set of questions, add the transcript back to the narrative and update it accordingly – and be able to do this until they had enough information to create a narrative they were satisfied with.

I messed up, in preparing my prompt, to follow the procedure I’d followed using Claude.ai: generating a PHP Unit Test with each new feature, which was my first lesson in using Claude Code: it’s easy to overlook things you wouldn’t in iterative discussions with Claude.ai, where the AI (inside project memory) will likely remind you if you miss this step. Claude Code will not. I set CC to to prompt for my approval before making any changes so I could monitor what it was doing. However, this was quite a different process from monitoring Claude.ai as it sometimes took CC a few minutes to run through a task, and I ended up doing other things, and the constant prompts became distracting. I’d glimpse over the changes, struggling to read in the Terminal, then hit ‘yes’.

In the end, none of the changes I’d asked for showed up in the app, and I ended up spending more time doing a git status check to see what files had been affected, then going through those files, trying to see what had actually been updated and why the changes I’d requested hadn’t shown up in the app. I found hardly any changes at all, and ended up going back to Claude.ai, describing what I wanted to do and making the code changes myself. As it turned out, I did what I needed to in half the time I’d spent on Claude Code, and that was my second lesson: Claude Code is not appropriate for everything.

But as I still had the THIRD task I needed to do, and as I was to find out, this was one task Claude Code WAS appropriate for.

TBC . . .

Back To Blog

Trying Claude Code

I first found out about Claude Code at the AWS Summit here in NYC at the Anthropic booth but resisted using it until very recently. The entire AWS conference had been suffused with euphoria at the miracle that was going to be Agentic AI, culminating in a triumphant demo by AWS itself of their agentic tool suite, including an Agentic Coding Tool, presented by one of their own devs, who had the halting, trepidatious delivery of a man with a gun trained on him somewhere off-screen, describing how this miraculous tool had made his workflow infinitely more efficient.

My skepticism was not helped by attempting to debug and clean up the code generated by friends who’d ‘vibe-coded’ apps using mostly Claude Code. AI, particularly in the hands of people who don’t know how to code, generates a very particular type of code, different from human-generated code. Verboseness is one of the adjectives that comes to mind: files nearly a thousand lines long. Impenetrable is another, with no recognizable human logic at play.

Besides, I didn’t see the point. Using first Chat GPT, then Gemeni, then Claude.ai took focus enough, where I (mostly) looked over the generated code before copy/pasting into VS Code. I likened using the LLMs to write code to riding a horse: you have to stay focused and make sure you know where the horse is going, otherwise the horse will throw you off its back and go in whatever direction it wants (this happened to me in my very short spell riding horses growing up in rural Alberta) or, in the case of LLMs, spewing gibberish while going down an AI wormhole. Claude Code (or any agentic coding tool) seemed like it would be riding a whole team of horses. I read about devs running 15 agents at once, or running agents for so long, so desperate to fulfill as many PRs as possible, they’d walk around with their laptops half-open to keep their agents running. All this seemed crazed, and more work than it was worth. I was relying on AI too much as it was.

But then I started meeting devs who WERE using it. A dev shop I wanted to work with who used Claude Code for their prototypiing, and in the kind of dev-peer to dev-peer interview that, once common, are all too rare now, explained how they came over their own skepticism and outlined how they’d managed the team of horses that is Claude Code under control. So, when I had some free time a couple of weeks ago, I decided to give Agentic AI a shot.

Next Post: From Claude Code Back to Claude.ai

Back To Blog

Chat GPT: The Dawn of the Chatbot

When Chat GPT was released in December, 2022, I was initially, to put it mildly, skeptical. Following the general trend, I’d taken a turn into Blockchain and Crypto during the lockdown years. I had a friend, a very experienced developer, who got so far into crypto and NFTs that he attempted to create his own startup. Everywhere you turned, you were assured that NFTs were the future of art, crypto and blockchain the future of finance. The blockchain was to be the public ledger, leading to the ‘democratisation’ of finance. I made some money off crypto, almost entirely by luck. A dev friend who’d been around since the dot.com days, said the underground culture of blockchain reminded him of the early days the web. NFTs, crypto had a strong outsider element, attracting people who either part of the regular tech world. Startups were being launched all over the place, and it seemed this was the next big future of tech. I learned Solidity, had a look at Rust, two technologies associated most closely with the blockchain ecosphere.

But I could never figure that out either crypto or blockchain, not really. Smart contracts, crypto generally seemed of limited utility. Most of the coins being released didn’t do much besides encourage speculation. NFTs seemed completely daft, as did Web3. We already spend far too much time online; no way spending MORE time online, in a supposedly immersive environment, would be good for us or even attractive to most. Perhaps widespread adoption of the blockchain could lead to a more transparent global economic system, but there was no way any bank, any plutocrat, never mind the kleptocrats who wash shedloads of dirty money through western bank accounts, were ever going to agree to such a system. It was also fantastically expensive, consuming nation-state levels of energy.

Then it all collapsed.

If I felt somewhat vindicated for not getting too caught up in the hype, I felt chastened as well. I’d wasted a good amount of time reading about, studying, attempting to build with, these new technologies and it turned out to be a waste of time.

So when the hype machine started up around AI with the arrival of Chat GPT and its boy wonder Sam Altman, I was so skeptical I barely paid attention for the first few weeks. As far as I was concerned, it was must more tech BS.

Then I actually tried Chat GPT (GPT3.5)

In a way they were a fulfillment of a youthful dream, when my and my friend M were screwing around around on our high school’s one computer (first the Commodore Pie, upgraded the next year to the cutting-edge Apple II). Inventing games, designing and implementing programs that ‘allowed’ the machine to ask questions – creating the illusion that our machine could not only ‘talk’ but ‘think’. Incredibly primitive speech and thought, but speech and thought nonetheless – talking back to us by outputting sentences on the command line kernel, often we had ‘talked’ to the computer by typing in and compiling code, and even if we understood that the machine was simply following our instructions when it output the words we had fed into it – there was still a curious thrill, even sense of wonder, at seeing a semblance of human speech output in flashing light grey letters on the dark green screen. As generated by Chat GPT, to the prompt:

Please generate an if/else statement in BASIC in which a user enters a question and the program replies, with a follow-up question and response? I’m trying to emulate a program I wrote when I was in junior high.

10 PRINT "ASK ME A QUESTION:"
20 INPUT Q$
30 IF Q$ = "DO YOU LIKE ME" THEN GOTO 100
40 PRINT "I DON'T UNDERSTAND THAT QUESTION."
50 GOTO 10
100 PRINT "YES, I LIKE YOU."
110 PRINT "DO YOU LIKE ME TOO?"
120 INPUT A$
130 IF A$ = "YES" THEN GOTO 200
140 PRINT "OH. THAT MAKES ME SAD."
150 GOTO 10
200 PRINT "GOOD. WE ARE FRIENDS."
210 GOTO 10

I felt a flash of this same sense of wonder, a very precise memory image across the decades. Type in the kind of message you’d ask a human, a very human-like – eerily human-like – response appeared on the screen, line by line by line, complete with a helpful follow-up suggestion at the end of the answer to keep the conversation going. Follow up for clarification, or just the thrill of having an interaction with what appeared to be an actual intelligence, and another response appeared to be an actual intelligence, and another response appeared, ordering another helpful suggestion to keep the conversation going further. It seemed miraculous, like soon we’d be living in a world of actual CPOs and R2D2s (or, as some feared, HAL), our lives and abilities extended and enhanced by our wonderful thinking assistants.

But the LLMs don’t ‘think’. The LLMs and the Deep Learning technology on which they are based have some serious limitations, some of which were recognized as far back as 2019 (what was that Marcus who pointed out the limitations of Deep Learning). The LLMs, in the end, are just a vastly more sophisticated version of those either/or programs we wrote for kicks in BASIC. But its good to remember that initial sense of wonder Chat GPT inspired, how it felt, for maybe a year or so, that we were embarked on an entirely new adventure.

Ultimately, learning to use the LLMs is like using to play an instrument – without proper human input, it produces wrong notes, dissonance – nonsense.

Back To Blog

Pointers in GO

One of the hardest things I found in trying to learn Go – this before I got to Concurrency and Go Routines – was the idea of Pointers. I understood that they point to location of a variable, rather than its value. What I didn’t understand was – why are pointers useful? So what – you can point to a variable’s location rather than its value – but how does that help in any practical way and why does it figure so prominently in every tutorial on Go?

I came to GO from Javascript, PHP and Python, none of which explicitly use Pointers (though as we’ll see below, Python does use a form of Pointers).

Pointers are relatively easy to create and use in Go. The syntax we use to define a pointer is:

var pointerName *dataType

The pointerName is the name of the variable that will hold our pointer. The dataType is the type that we associate with the value stored in the pointer. Here is a simple example of creating a pointer in the default main.go file:

package main
import "fmt"
func main() {
     var a = 10 // variable a stores value of 10
     var b *int = &a // variable b stores the memory address (location) but NOT the value of variable a
}
     fmt.Println(a) // prints '10'// our value
     fmt.Println(b) // prints '0xc0000aa058', the location of our variable

So simple enough to get my head around, but I still didn’t understand why pointers were such a key part of the Go language. To understand their practical applications, I asked, as one does, the LLMs, specifically Gemini Pro, which I’ve been using more and more lately. Some of its response was reproduced below:


Here are the three practical reasons why we use pointers in Go.

1. Mutability (Changing the Original)

Because Go copies everything by default, a function cannot change the state of a variable passed to it unless we give it the “address” (pointer) of that variable.

The “Oops” moment (Pass by Value):

Go

func changeName(name string) {
    name = "Alice" // This only changes the COPY inside this function
}
func main() {
    user := "Bob"
    changeName(user)
    fmt.Println(user) // Output: Bob (Nothing happened!)
}

The Fix (Pass by Pointer):

Go

// We accept a pointer to a string (*string)
func changeName(name *string) {
    *name = "Alice" // We travel to the address and update the value there
}
func main() {
    user := "Bob"
    changeName(&user) // We pass the address (&)
    fmt.Println(user) // Output: Alice
}

Practical Application: We see this everywhere in Go methods: if a method needs to update the state of a struct (like setting a User.IsLoggedIn = true), it must use a pointer receiver.


2. Performance (Efficiency)

Imagine we have a struct that represents a massive JPEG image or a user profile with 50 fields.

  • Without Pointers: Every time we pass that struct to a function, Go has to pause and copy every single bit of that data into a new location. This eats up memory and CPU.
  • With Pointers: We pass the memory address. An address is tiny (usually 8 bytes on a 64-bit system).

Python, by contrast, does this for us automatically. It never copies the whole image object; it just passes the reference. In Go, you have to choose to do this manually by using a pointer.

Rule of Thumb: If the data is small (like an integer or a coordinate point), copy it. If it is large (like a User struct or a config object), use a pointer to share it without copying.


3. Representing “Null” (Absence of Value)

This is a massive one for web development (SQL/JSON).

In Go, types have “zero values.”

  • The zero value of an int is 0.
  • The zero value of a string is "".

The Problem:

If a user submits a JSON form and the “age” field is 0, did they enter 0, or did they just leave it blank? In standard Go, we can’t tell.

The Solution:

Pointers can be nil.

  • var age int -> Default is 0.
  • var age *int -> Default is nil.

If age is nil, the user left it blank. If age points to a value of 0, then we know the user explicitly typed 0.

Practical Application: We see this quite a lot in database structs.

Go

type User struct {
    Name      string
    DeletedAt *time.Time // Pointer allows this to be null (user is active)
}

If DeletedAt wasn’t a pointer, it would default to the year 0001-01-01, which is confusing data.


Summary Table

FeaturePass by Value (No Pointer)Pass by Pointer (*)
AnalogySending a friend a photocopy of a map.Sending a friend the GPS coordinates.
MutabilityThey can draw on the map, but your map stays clean.If they change the location, it changes for you too.
PerformanceSlow for large items (requires photocopying).Fast (just sending coordinates).
SafetySafe. Original data cannot be corrupted.Risky. Original data can be modified or be nil.

A Note on Python

Python, it seems, does use pointers exclusively for objects (Lists, Dicts, Classes)—it just hides the syntax from you (* and &).

  • Python: a = [1, 2]; b = a (b is now a pointer to a).
  • Go: a := []int{1, 2}; b := a (b is a pointer to the underlying array).*

(Additional Note: Go Slices and Maps are special types that implicitly act like pointers, which helps make Go feel friendlier, but Structs do not.)


Back To Blog

How I use LLMs

The LLMs, after a three year reign, are possibly on the way out. Or at least – there is now significant pushback and skepticism about their efficacy, their efficiency, their incredible energy use. They have been rightfully condemned for their tendency to hallucinate (ie make things up when they don’t have the correct answer), to sycophancy (ie explicitly trained to tell their users whatever they want to hear, reflecting not just their users’ tastes and opinions – but encouraging their users own WORST tastes and opinions. Like social media, they are designed to be addictive, pestering their users at the end of every answer, hallucinated or not, with further suggestions and queries, with the familiar, seemingly helpful tone of the eager assistant, designed to keep the user keeping the thread open as long as possible.

But the LLMs DO have their uses – and I’ve been using Chat GPT, still my default, almost since it appeared in December, 2023. I signed up for a Pro account as soon as it came out in January 2024. I’ve tried to write with it, I used it for study, as a replacement search engine. Seduced by a piece in the New Yorker ‘A Coder Considers the Waning Days of The Craft‘, I pondered giving up coding, or at least giving up coding without the integration of the LLMs, and build apps in languages and frameworks I either didn’t know well, or had forgotten, relying heavily on LLMs to fill in gaps in my knowledge. When, stymied by a collapsing NYC tech job market, I took up freelancing, I used LLMs as an assistant to deal with page builders, optimizations, SEO, integrations – subjects I sort of knew, or had known from last iteration as a freelancer 10 years before. I used the LLMs for job search, CV optimization, I used it to research and write copy, find and configure plugins and, when I returned to using Laravel and Django after a few years absence, I used the LLMs in an attempt to get back up to speed. I experimented with LLMs to write, or analyze books, find historical and Biblical and mythical analogies for stories I was writing or planning to write. I briefly experimented with generating images, even video.

Success? Mixed. Very mixed. I should have been more attentive and kept an ongoing log of what worked, what didn’t. I should have written blog posts, documenting my finds. But I didn’t. I was overwhelmed by the firehose of information about AI, and all the wonderful things it could do, all the jobs it would replace. I used it day-to-day on a sort of intuitive, almost unconscious level and attempted to control its voliminous flood of information, opinion, sometimes complete BS.

But here, I’ll try to unpack how I’ve been using the LLMs, mostly Chat GPT: what I’ve found useful and what I really haven’t.

Back To Blog

What Would UBI Be Like?

Sam Altman is fond of claiming that the advent of the LLMs means that, at some point, we’ll have to consider bringing in UBI because ‘AI will take all our jobs’. He’s even sponsored a study, done over a three year period, giving participants a grand a month.

For those not in the know, UBI stands for ‘Universal Basic Income’ and was first introduced as a concept (as we shall see, in its modern form) by Andrew Yang during his 2016 run to be the Democratic Presidential nominee. Yang’s idea was that every US resident (presumably limited to citizens and ‘resident aliens’ – ie green card holders) would receive $1200 a month as a base income, no matter what they made from their regular jobs. Such an idea had apparently been popular in parts of Silicon Valley (where Andrew Yang had been working) for some time; Yang just brought it to the public.

In other words, instead of just chidren of well-off baby boomer parents getting a trust fund – EVERYONE would get a trust fund. And why not? The US is a rich country. With some sensible, thoughtful taxation, UBI would be entirely feasible. Now with AI threatening to take everyone’s jobs, perhaps UBI is an idea whose time has come.

Thing is: I’ve lived through an early prototype of UBI, in Canada (and the UK) in the ’70s and ’80s. Only it wasn’t called UBI back then, it was called the welfare state, and came in the form of either unemployment or welfare payments. Montreal in particular, when a significant percentage of the population lived in whole or in part on UI (from the feds) or the Bien-Aide Sociale (BS, from the provincial government), supplemented by odd jobs, artistic or musical pursuits, or spells of grueling, physically intense work in the hinterlands. For many, it was more or less a way of life.

In those good old days, Canada’s second largest city, was recovering from the tumult of the ’60s and ’70s, when the FLQ had been setting off bombs and kidnapping government ministers, the election of the separatist Parti Quebecois and the subsequent referendum on Quebec’s sort-of-separation from the ROC (rest of Canada), along with the massive outflow of its Anglo population – and their money. This, at a time, when not only the opening of the St. Lawrence Seaway had reduced Montreal as a port, the ’76 Olympics which had left the city with ruinous debt, and the general industrial decline that bedevilled cities across the North-East. Montreal was not in great shape.

Into this void came the welfare state, attracting a generation of artists, slackers, and riff-raff from the ROC and the Montreal suburbs, drawn to a city where a two or even three bedroom apartment in a picturesque neighborhood with easy access to great food, cheap bars and restaurants, could be had for a couple of hundreds bucks a month. No deposit, no first and last: in most situations, you paid month by month. Basic BS was a mere $180/month, not enough to live on, but if you could scam medical BS, you could get up to $500/month. If you went out into the bush for the Great Canadian Migration that was tree-planting, and put in 8-12 weeks of back-breaking labour in all kinds of weather fighting off hordes of biting insects, you could come back with not just somewhere up to ten grand, but that prize of prizes, max UI weeks, which translated to over a grand a month (after taxes), paid in bi-monthly installments.

What a time.

Sounds great! Power to the People! But what did it mean in practice?

In St. Henri, a blue collar neighborhood just off downtown where I lived for a time, the factories that had been the neighborhood’s chief employer had long since closed and most of St. Henri’s residents had few options. For most of the month, the tavernes, the few diners that still existed, even the depanneurs (corner stores to those not familiar with Quebec) were largely empty. But come the last Friday of the month, when the BS cheques came out – hey presto! St. Henri came alive! Tavernes were suddenly full from noon ’til close. People lined up in the depanneurs for 24 cases of Cinquante, Molson Ex, cartons of cigarettes! For a weekend, St. Henri was as lively and vital as it must have been in its industrial glory days.

Then come Monday, everyone went back to staying home and watching TV.

Among my circle of bohemians and artists on the other side of the city, results were mixed. Some people got their UI weeks, then left the country to go traveling, or skiing, or to spend a few months on a beach, their bi-weekly check-in card filled out and signed by a trusted friend or relative. Many planned to use their newfound free time the BS or UI allowed to do their own work. And some did. Some had the discipline to set goals, then spend an allotted time – six months, a year – to fulfill them. But for most, myself included, the lack of structure was curiously dispiriting. One day stretched into another. We spent hours in coffee shops, nursing a single coffee, only to spend hours more on the phone, talking about more or less the same things we’d talked about in the coffee shop. Come evening, we’d gather in some cheap bar for the even cheaper happy hour, often with the same people we’d seen or talked to that day.

It kind of got stale. Fast.

That said, I wasn’t against it then, and I’m not against UBI now. It can’t just be the children of the rich – or the plain rich – who get to do art or work in media or launch startups. Or hang around and do nothing. Regular folks should be able to do all these things too. But if AI really does take all our jobs and we ended up with masses of leisure time, we’re going to be need to be trained on how to use that time. We’ll need to be trained as tinkerers, inventors, founders – or even just volunteers. We’ll need to be trained in how to be useful.

But this is only if AI takes all our jobs and the US government has the vision, the courage, to bring in some as profound as UBI. Since the US has been unable to even bring in a universal health care, this seems very unlikely, whatever Sam Altman says.

Back To Blog

Sierpinski’s Triangle PT 3

Alternate methods of generating Sierpinski’s Triangle: one using a recursive function, the other using the chaos game.

First: what is recursion? What is the chaos game?

Recursion is a fundamental concept in computer science and mathematics, where a function or algorithm calls itself again and again in order to solve a problem. The principle of recursion is to break down a complex problem into smaller, more manageable sub-problems, then continues until it reaches a condition that can be solved without further recursion, usually referred to as the ‘base case’. One very common example of recursion is the factorial (the product of all positive numbers up to a number). Here it is in Python:

def factorial(n):
    # Base case: factorial of 1 is 1
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

In this example, factorial(n-1) is the recursive call. When you call factorial(n), it calls factorial(n-1), which calls factorial(n-2), and so on, until it reaches factorial(1). This base case returns a simple, non-recursive result, which is 1 in this case.

The Chaos Game is a mathematical procedure that generates a sequence of points in a space, often resulting in a fractal pattern. Invented by mathematician Michael Barnsley, it’s part of the study of chaotic dynamical systems.

Here is the basic process of the Chaos Game:

  1. Begin by defining a set of rules or transformations. Typically, these involve geometric operations like rotations, translations, and scaling.
  2. Choose an initial point in the space.
  3. Apply one of the transformations to the point at random. The result is a new point.
  4. Repeat step 3 for the new point, and continue doing so. The key is that the choice of transformation is random at each step, introducing an element of unpredictability or “chaos”.

The Chaos Game is fascinating because it demonstrates how complex and intricate structures can emerge from simple rules and random behavior. This makes it a useful tool in the study of chaos theory and fractals.

Let’s try a relatively simple example by creating the Barnsley Fern.

The Barnsley Fern is a fractal named after British mathematician Michael Barnsley who first described it in his book “Fractals Everywhere”. The fern is created by plotting a sequence of points in the plane according to a set of four affine transformations chosen at random.

Here is a simple Python code using matplotlib and numpy:

import numpy as np
import matplotlib.pyplot as plt
# Define the transformation functions
def f1(x, y):
    return 0, 0.16*y
def f2(x, y):
    return 0.85*x + 0.04*y, -0.04*x + 0.85*y + 1.6
def f3(x, y):
    return 0.2*x - 0.26*y, 0.23*x + 0.22*y + 1.6
def f4(x, y):
    return -0.15*x + 0.28*y, 0.26*x + 0.24*y + 0.44
# Create an array of transformation functions
fs = [f1, f2, f3, f4]
# Choose an initial point
x, y = 0, 0
# Initialize arrays to hold x and y values
xs = [x]
ys = [y]
# Iterate the transformations
for i in range(100000):
    # Choose a random transformation function and call it
    f = np.random.choice(fs, p=[0.01, 0.85, 0.07, 0.07])
    x, y = f(x, y)
    xs.append(x)
    ys.append(y)
# Plot the points
plt.scatter(xs, ys, s=0.2, color='green', lw=0)
plt.show()

In this code, we define four transformation functions f1, f2, f3, and f4, which correspond to the four transformations used to generate the Barnsley Fern. We then apply one of these functions at each step, chosen at random according to specified probabilities. Despite the randomness, the sequence of points forms the recognizable shape of a fern.

  1. Recursive Function:

This function involves dividing a triangle into four smaller triangles and removing the center one, and then repeating this process for the remaining triangles. Here’s a simple way to do this using the turtle module in Python:

import turtle
def draw_sierpinski(length, depth):
    if depth==0:
        for i in range(3):
            turtle.forward(length)
            turtle.left(120)
    else:
        draw_sierpinski(length / 2, depth - 1)
        turtle.forward(length / 2)
        draw_sierpinski(length / 2, depth - 1)
        turtle.backward(length / 2)
        turtle.left(60)
        turtle.forward(length / 2)
        turtle.right(60)
        draw_sierpinski(length / 2, depth - 1)
        turtle.left(60)
        turtle.backward(length / 2)
        turtle.right(60)
# Initial settings
turtle.speed(0)
turtle.penup()
turtle.goto(-200, -200)
turtle.pendown()
# Draw Sierpinski Triangle
draw_sierpinski(400, 4)
# End drawing
turtle.done()

In this code, draw_sierpinski is a recursive function where length is the side length of the triangle and depth is the recursion depth. The turtle starts by moving forward, then recurses, moves forward again, recurses, moves backwards, and finally turns to start the next recursion.

  1. Chaos Game:

The chaos game is a method of creating a Sierpinski triangle by randomly moving a point half the distance towards the corners of an initial triangle. Here’s a way to do this using matplotlib and numpy:

import numpy as np
import matplotlib.pyplot as plt
# Initialize the triangle corners
corners = np.array([[0, 0], [0.5, np.sqrt(3)/2], [1, 0]])
points = np.zeros((100000, 2))
points[0, :] = [0, 0]
for i in range(1, len(points)):
    # Randomly select a corner
    corner = corners[np.random.randint(3)]
    # Move half the distance to the chosen corner
    points[i] = (points[i-1] + corner) / 2
# Plot the result
plt.scatter(points[:, 0], points[:, 1], s=0.1, color='k')
plt.axis('equal')
plt.show()

In this code, corners contains the coordinates of the three corners of the initial triangle, and points is an array of points that will be plotted. In each step of the loop, a corner is randomly selected, and the next point is set to be halfway between the previous point and the selected corner. The result is a scatter plot of all the points, which forms a Sierpinski triangle.

Back To Blog

Learn (Basic) Natural Language Processing (NLP) in 6 easy steps

Recently, I started the ‘Intro To NLP’ Course on the 365 Data Science platform. I’ve noticed the platform scrambling in recent months to introduce AI content after focusing almost exclusively on Data Science topics. Since the two are related, this wasn’t a bad thing, but glad to see it bringing in more high quality courses focusing exclusively on AI/LLM topics, and looking forward to seeing more.

The Intro to NLP consists of 7 sections of lessons with a Practical Task at the end of each section, then a section devoted to a whole project, finishing up with a ‘Future of NLP’ section before the Final Exam. So far it’s been a good course, about a subject I knew almost nothing about before I started, but which is central to LLM like Chat GPT and AI generally.

What is Natural Language Processing, or NLP? NLP is a technology that allows computers to understand, interpret and respond to human languages, both written and spoken. As it turns out, it has an astonishing number of uses, and was widely used in ways I never even thought about, even before the arrival of the LLMs:

  • Search engines: when we type a query into a search engine, NLP algorithms interpret our intent, understand the context, and deliver relevant search results. This involves processing the query, identifying key terms, and even understanding the context of our question.
  • Voice Assistants and Smart Home Devices: Devices like Amazon’s Alexa, Google Assistant, and Apple’s Siri use NLP to understand spoken commands. They can interpret our requests, respond to queries, control smart home devices, and even engage in casual conversation. Sort of.
  • Text Autocorrect and Predictive Text: The autocorrect and predictive text features on smartphones and other devices use NLP to understand the context of what we are typing, correct spelling errors, and predict the next word we might type. Sometimes this works, sometimes it doesn’t. I’ve sent many garbled text because of Autocorrect.
  • Language Translation Services: Online translation tools like Google Translate utilize NLP to convert text or spoken words from one language to another, understanding grammatical nuances and context to provide accurate translations.
  • Chatbots and Customer Service: Many websites and customer service platforms employ chatbots that use NLP to understand and respond to customer inquiries. These bots can handle a range of tasks from answering FAQs to helping with online shopping or troubleshooting. Next time you’re frustrated by some generic response from a chatbot and wish you were talking to an actual human – thank NLP.
  • Email Filtering: Email services use NLP to filter out spam or categorize emails into different folders (like social, promotions, primary). This is done by analyzing the content of the emails and identifying certain patterns or keywords. This has gotten dramatically better in recent years.
  • Social Media Feeds: NLP algorithms help in personalizing our social media feeds. They analyze our interactions, the content we engage with, and use this data to curate a feed that is supposedly tailored to our interests. I would say they mostly ruin them, but you get the idea.
  • Sentiment Analysis: Businesses use NLP for sentiment analysis to gauge public opinion about their products or services. By analyzing social media posts, reviews, and comments, they can understand customer satisfaction and general sentiment.
  • Content Recommendations: Streaming services like Netflix or Spotify use NLP to recommend movies, shows, or music based on our previous viewing or listening habits, search history, and preferences. I’ve found these to be mostly . . . if not quite useless, close to it.
  • Accessibility Tools: NLP aids in creating tools for individuals with disabilities, such as text-to-speech and speech-to-text applications, which allow users with visual or hearing impairments to interact with technology more effectively. Potentially very useful.
  • Educational Tools: NLP is used in educational software to aid in language learning, provide automated grading of essays, and even give feedback on writing style and grammar.
  • Resume Screening: In the hiring process, NLP is used to screen resumes and applications to identify the most suitable candidates by matching job requirements with the skills and experiences listed in the resumes. Another instance where we’d probably be better off without this. Ever wonder why you can’t get an interview? Thank some unknowable NLP algorithm.

But with the arrival of Chat GPT and the other LLMs a year ago, NLP really came into its own. Suddenly we were able to engage with a machine in a way eerily similar to how we interact with human beings. We can argue whether this, long term, a good thing, but seen purely as a technology, Chat GPT is amazing. Even with all its flaws, hallucinations, errors, the dubious practice of just scraping content without any permissions at all, and so on. Chat GPT and the other LLMs struggling to catch up, really are a leap ahead on a technological scale and NLP made them possible.

How is NLP used in the new Large Language Models?

  • Understanding Language: ChatGPT uses NLP to grasp the nuances of human language. When you type a sentence, NLP helps the model understand not just the words, but also the meaning and context behind them. This understanding is crucial for generating relevant and coherent responses.
  • Generating Text: Once ChatGPT understands our input, it uses its knowledge gained from NLP to construct a reply. NLP guides it in forming sentences that are not only grammatically correct but also contextually appropriate, maintaining a flow that resembles natural human conversation.
  • Learning from Large Datasets: ChatGPT has been trained on a vast array of text data. NLP is used to process and learn from this data, enabling the model to recognize patterns, understand various topics, and even mimic different writing styles.
  • Handling Different Tasks: Whether it’s answering questions, writing essays, or even creating ‘poetry’, ChatGPT uses NLP to tailor its responses to the specific task at hand. NLP provides the flexibility to switch between different types of language use, from formal to casual, technical to creative.
  • Continuous Learning: As ChatGPT interacts with users, it continually refines its understanding and use of language. NLP is key in this learning process, helping the model to adapt and improve over time based on new interactions and data.

In short, NLP is the ‘brain’ behind our LLM’s ability to communicate effectively with humans, using human language. It is what allows the model to understand our questions and respond in a way that is informative, engaging and eerily human-like.

So now we’re going to learn the basics of NLP and how to use it. First stop: Text Preparation.

Back To Blog

SierPinski’s Triangle PT 2

In Part I, I explained what the Sierpinski’s Triangle is, and how it is based on fractals. In this post, we will actually build the Sierpinski’s Triangle, reviewing the basic mathematical concepts behind the Triangle, then using Python to build it. This tutorial is based on Giles McCullen-Klein’s ‘Sierpinski’s Triangle’ section of his excellent ‘Python Programming Bootcamp‘ course on the 365 Data Science platform (also on Udemy):

To review, the Sierpinskis Triangle is a fractal, a self-replicating geometric pattern that exhibits intricate detail and self-similarity at different scales. It is named after the Polish mathematician Wacław Sierpiński, who first described the pattern in 1915.

The Sierpinski Triangle is formed by recursively subdividing an equilateral triangle into smaller equilateral triangles. The process begins with a single large equilateral triangle. Then, at each iteration, we:

  1. Divide the initial triangle into four smaller equilateral triangles by connecting the midpoints of each side.
  2. Remove the central triangle, leaving three smaller equilateral triangles that form a larger equilateral triangle.
  3. Repeat steps 1 and 2 for each of the remaining smaller triangles, continuing indefinitely.

As the number of iterations approaches infinity, the resulting pattern becomes an increasingly intricate set of triangles, with the final Sierpinski Triangle having an infinite number of triangles and a total area of zero.

The Sierpinski Triangle is an example of a deterministic fractal, meaning that it can be generated through a specific set of rules. It has been studied extensively in mathematics and has applications in areas such as computer graphics, geometry, and the study of complex systems.

To recap, here are our three basic mathematical formulas:

First Transformation:

x_{n+1} = 0.5x_n 
y_{n+1} = 0.5y_n

Second Transformation:

x_{n+1} = 0.5x_n + 0.5
y_{n+1} = 0.5y_n + 0.5

Third Transformation:

x_{n+1} = 0.5x_n + 1
y_{n+1} = 0.5y_n

Next, we take these formulas and express them in Python:

from random import choice
def trans_1(p):
    x = p[0]
    y = p[1]
    x1 = 0.5 * x
    y1 = 0.5 * y
    return x1,y1
def trans_2(p):
    x = p[0]
    y = p[1]
    x1 = 0.5 * x + 0.5
    y1 = 0.5 * y + 0.5
    return x1,y1
def trans_3(p):
    x = p[0]
    y = p[1]
    x1 = 0.5 * x + 1
    y1 = 0.5 * y
    return x1,y1

transformations = [trans_1,trans_2,trans_3]
a1 = [0]
b1 = [0]
a,b = 0,0
 
for i in range(100):
    trans = choice(transformations)
    a,b = trans((a,b))
    a1.append(a)
    b1.append(b)

Then we use Matplotlib to visualize our triangle (we’ll have to import matplotlib first):

import matplotlib.pyplot as plt 
%matplotlib inline
plt.rc('figure',figsize=(16,16))
plt.plot(a1,b1,'o')
plt.savefig('my_figure.png')

as we can see, at 100 points our triangle barely exists. Let’s try again at 1000:

for i in range(1000):
    trans = choice(transformations)
    a,b = trans((a,b))
    a1.append(a)
    b1.append(b)

Ok, our triangle is starting to take shape. Let’s skip ahead and try again at one million:

Ta-dah! There we have it, our Sierpinski’s Triangle. The first time I saw the triangle appear in Giles’ lesson, I thought it was a miracle.

Part 3: Alternative Ways to Generate the Sierpinski’s Triangle