Showing posts with label Software Development. Show all posts
Showing posts with label Software Development. Show all posts

Tuesday, 18 August 2026

Major Milestone: Skink Has Officially Bootstrapped!

Woohoo! It’s time for a long-overdue update on the language powering the future of DANI.

We just crossed a monumental threshold with Skink: it is now officially bootstrapped!

For anyone unfamiliar with compiler development, bootstrapping means that the Skink compiler can now compile its own source code. From here on out, all future versions of the Skink compiler will be written in Skink itself. It’s a huge rite of passage for any programming language project, and seeing it actually work after so much foundation work is insanely satisfying.

Where Go Fits In (For Now)

The handover
Now, does this mean I’m throwing Go out the window today? Not quite!

Go has a proven, rock-solid track record, and I’ll be keeping it around for prototyping work. Whenever I want to test out an idea quickly and I’m not sure if Skink is quite ready to handle the heavy lifting yet, Go gives me a reliable safety net.

However, the long-term vision hasn't changed: eventually, I plan to fully commit to Skink for almost all development across my projects, experiments, and research.

The Road to Skink 1.0

While bootstrapping is a massive step forward, we aren't quite at version 1.0 just yet. To earn that release tag, Skink needs to hit a few crucial targets:

  1. Cross-Platform Support: Clean cross-compilation across Windows, macOS, and Linux.
  2. Architecture Support: Full targeting for both x86 and ARM architectures.
  3. Embedded Target Transpilation: The ability to compile (or at least transpile down) to low-power platforms like the K210, ESP32, and standard Arduino C/C++.
  4. Direct Hardware I/O: First-class hardware abstraction libraries for lower-level protocols—specifically I2C, SPI, GPIO, and related interfaces.

Acceleration, Compute, and Graphics Backends

Beyond microcontrollers and standard CPUs, getting high-performance compute and neural inference support hooked up is high on the priority list.

  • CUDA Support: I still need to get CUDA fully enabled and tested. Because of my current local setup, this will likely require either acquiring new dedicated NVIDIA hardware or setting up a cloud-based VM for build and test pipelines.

  • Vulkan & AMD: My main local system currently relies on Vulkan to run LM Studio, so a Vulkan compute backend is high on the radar as a viable cross-vendor path. I'm also looking closely at AMD’s libraries (ROCm/HIP) to ensure broad hardware compatibility.

It's a lot of moving parts, but watching the architecture take shape piece by piece is incredibly rewarding.

Bringing It Back to DANI

So, why go through all the trouble of building a custom language from scratch?


It all comes down to DANI. My goal has always been to have a single, unified language capable of bridging every layer of DANI’s stack—from high-level logic and neural compute all the way down to real-time bare-metal sensor and actuator control. Skink is the key to making that happen without juggling three different language ecosystems.

Speaking of DANI, there have also been some fascinating developments regarding his neural network architecture recently... but I’ll leave you hanging on that for now and save the deep dive for the next post!

Stay tuned!


Tuesday, 21 July 2026

Skink Tutorials Now Available! (And a Quick Status Update)

I’m excited to announce that a set of tutorials for Skink is now live! If you’ve been following the progress of the project and want to dive in and learn how to use it, this is the perfect place to start.

You can find the tutorials here: https://www.daggertech.co.uk/p/skink-tutorials.html

In these tutorials, you'll find [briefly describe what the tutorials cover, e.g., step-by-step guides, basic concepts, advanced usage]. Whether you're just getting started or looking to deepen your understanding, I hope you find them useful.

Where is Skink at right now?

While I’m thrilled with how Skink is progressing and the tutorials are a big step forward, I want to be transparent about its current status.

Skink is coming along really well, but I wouldn't call it "production-ready" just yet. For me, a tool truly earns that label once it's been thoroughly battle-tested in a real-world, demanding environment.

That testing ground will be DANI.


Integrating Skink into DANI will be the ultimate proving ground. It’s where we’ll uncover the edge cases, stress-test the performance, and ensure it can handle the complexities of a real application. Until Skink has successfully powered DANI in the wild, I’m keeping the "beta" tag firmly in place.

I’m looking forward to getting to that milestone, and I’ll be sure to share updates along the way. In the meantime, please check out the tutorials, play around with Skink, and let me know your thoughts!

Happy coding!

Friday, 19 June 2026

Introducing Skink-lang: Elegant Systems Programming for AI & Robotics (or, How to Stop DANI from Eating the Skirting Boards)


Over the last thirty years, I’ve worked across a sprawling landscape of technologies—from writing Go, C#, Pascal, Swift, and C, to orchestrating massive cloud pipelines, down to micro-managing registers on bare-metal microcontrollers.

But lately, my time has been dominated by a very specific, highly opinionated, and occasionally cooperative localized AI agent: DANI.

If you've been following my previous posts, you know DANI has transitioned from an LSTM-based cognitive core into a physical, moving entity. And that is where the real-world engineering headaches began. Every time you start a new robotics or physical AI project, you are forced to make a compromise that feels like choosing between a kick in the shins or a poke in the eye:

  • The C/C++ Extremity: You get deterministic, blazing-fast performance on microcontrollers, but you sacrifice developer ergonomics, modern package safety, and swift prototyping. Writing raw $C++$ register manipulation code sometimes feels like trying to perform laparoscopic surgery on yourself with a rusty spoon.
  • The Python Extremity: You get instant access to rich AI primitives, neural networks, and expressiveness. But you inherit bloated virtual environments that take up more disk space than the library of Alexandria, and unpredictable garbage collection (GC) latency. When DANI’s control loop is running at $100\text{ Hz}$ and the Python garbage collector decides to take a mandatory three-millisecond tea break, DANI doesn't stop. She gracefully, but deterministically, plows straight into the skirting board.

I got tired of choosing between developer speed and hardware execution safety. I wanted a compiled language built specifically for low-level systems hardware, high-throughput concurrent event streams, and native AI execution.

So, I built Skink-lang.

What is Skink?

Skink is an LLVM-backed, statically-typed systems programming language designed from the ground up for the modern era of intelligent automation. It blends the tight, expressive syntax of modern languages like Swift with the lightweight concurrency model of Go, leaving behind the runtime bloat.

To save you from the nightmare of modern package managers—where doing a pip install on a single helper utility somehow downloads half the internet and a bootleg copy of Doom—Skink comes with a rich, "batteries-included" standard library right out of the box.

As a Skinker (yes, that is our official title now), you get native access to:

  1. Automatic Reference Counting (ARC): Predictable, deterministic memory management without "stop-the-world" garbage collection pauses. This is a non-negotiable requirement when you are driving physical motors or processing high-frequency sensor telemetry.
  2. Lightweight Concurrency: A native spawn keyword that allows you to spin up millions of concurrent tasks with near-zero overhead, communicating cleanly via typed channels.
  3. The Rules Engine: A compiler-optimized reactive rules engine that lets you define declarative behavioral overrides that monitor variables in the background. It is perfect for saying, "I don't care what the neural net is thinking; if we are $15\text{ cm}$ away from a wall, hit the brakes."
  4. Native Tensors & ML Cores: Multi-dimensional arrays built directly into the type system via std/tensor, complete with matrix operations, activation functions, neural layers, and hooks for CUDA acceleration and llama.cpp (std/llm). DANI can run local inferences natively, at maximum speed, without needing a dedicated power plant or $16\text{ GB}$ of RAM.
  5. Model Context Protocol (MCP) & MQTT: Built-in support for turning any hardware endpoint into an instantly discoverable AI tool with SQLite (std/db) and edge-to-cloud messaging (std/mqtt) ready to go.

Under the Hood: Preventing a DANI Catastrophe

To see what "skinking" actually looks like, let's write a syntactically valid program based on the current Skink manual.

The following script concurrently polls a physical distance sensor via GPIO, pipes the measurements to our main execution block using channels, evaluates safety overrides in a background ruleset, and processes the inputs through a linear neural network layer to calculate motor outputs:

module main

import "std/gpio"
import "std/time"
import "std/tensor"

// 1. Declare global state variables (accessible by the ruleset)
var current_distance: float = 100.0
var motor_speed: float = 0.5

// 2. Define a reactive ruleset for safety overrides
ruleset SafetyOverride {
    rule collision_warning when current_distance < 15.0 {
        action: trigger_emergency_stop()
        priority: 1
    }
}

// Helper function called when the rule fires
fn trigger_emergency_stop() {
    motor_speed = 0.0
    print("SAFETY OVERRIDE: Obstacle detected! Distance: {current_distance}cm. Braking.")
}

// 3. Concurrently poll the hardware sensor in a background task
fn sensor_loop(ch: chan<float>) {
    err := gpio.Setup()
    if err.message != "" {
        print("Failed to initialize GPIO: " + err.message)
        return
    }

    // Bind to BCM Pin 17 (e.g., our sensor input pin)
    sensor_pin := gpio.PinFactory(17)
    sensor_pin.SetInput()

    while true {
        // Mocking a physical sensor read for this demo loop
        // In physical deployment, you'd calculate raw voltage pulses here
        measured := 12.5 
        ch <- measured
        time.SleepMs(10) // Poll at 100Hz
    }
}

// 4. Main Entry Point
fn main() -> int {
    sensor_chan := make(chan<float>)

    // Spawn our lightweight background polling loop
    spawn sensor_loop(sensor_chan)

    // Activate our safety ruleset background thread
    safety := SafetyOverride{}
    safety.start()
    defer safety.stop()

    // Initialize a linear neural network layer [input_features: 3, output_features: 1]
    layer := tensor.NewLinear(3, 1)

    // Run our control loop for 100 iterations
    for i := 0; i < 100; i = i + 1 {
        // Block until next sensor reading arrives
        current_distance = <-sensor_chan

        // If we are in a safe zone, let the tensor neural layer drive
        if current_distance >= 15.0 {
            input := tensor.Zeros([1, 3])
            input.Set([0, 0], current_distance)
           
            output := layer.Forward(input)
            motor_speed = output.Get([0, 0])
           
            print("Processing... Current motor velocity: {motor_speed}")
        }
    }
    return 0
}

Notice how neatly this handles the classic embedded AI problem. The concurrency model lets you poll hardware safely on separate execution threads without blocking the main loop, while the native ruleset watches the critical state variables and takes action within milliseconds if a threshold is breached—guaranteeing deterministic safety constraints before DANI can do any structural damage to the house.

Where Skink-lang Goes From Here

Currently, the compiler is bootstrapped in Go, using an LLVM backend to output highly optimized native machine binaries targeting both Linux and Windows corporate environments. Because there is no heavy runtime or garbage collector, a compiled Skink binary running an active inference loop can comfortably squeeze inside less than $128\text{ KB}$ of RAM.

But this is just the beginning. The roadmap ahead includes:

  • Direct Single Board Computer HAL: Expanding the standard library to map hardware registers and pins natively (such as on the Raspberry Pi 5) with zero external C-bindings.
  • Self-Hosting: Rewriting the Skink compiler entirely in Skink itself, proving the language's capabilities to handle massive, complex systems-level software natively.

Cullen the Skink
Cullen Skink

I’m incredibly excited about what this language makes possible. DANI is already running much cooler, much faster, and with a significantly lower skirting-board collision rate.

It is time to stop fighting the plumbing.

Let's get skinking!

Let me know your thoughts on the syntax, and what features you'd like to see added to the compiler next!


Thursday, 18 September 2025

The Go-pher's Guide to Messaging Simplicity (Why I Wrote Nexus)

I've had to take a brief step back from working on DANI recently as I needed to tackle some challenges for work projects. So, instead of leaving you all hanging, I thought I would give you an update on something I've been working on in my "day job" that I thought was an interesting side project.

In today's fast-paced digital world, connecting different services and applications is a challenge. Many organizations rely on complex, costly messaging platforms that introduce significant operational overhead and vendor lock-in. What if there was a better way? A messaging solution that was secure, easy to deploy, and gave you full control?


Welcome to Nexus, a secure, observable, and extensible event distribution platform designed to connect publishers and subscribers with minimal operational overhead. Built with simplicity and control at its core, Nexus is a compelling alternative to traditional cloud-managed services and heavyweight message brokers.

Key Value Propositions

Nexus stands out by focusing on a few core principles that deliver immense value:

  • Operational Simplicity: Nexus is a single Go binary with zero external dependencies by default. This simplifies deployment, which can be done in hours rather than weeks or months.

  • Security-First Design: It uses RSA-based authentication, constant-time cryptographic comparisons, and replay attack protection. Metrics are also protected with an optional token.

  • Observability: With built-in health checks (/healthz, /readyz) and Prometheus-compatible metrics, Nexus provides the necessary tools for monitoring and debugging.

  • Cost Control and Portability: Nexus offers a predictable infrastructure cost, a significant advantage over the pay-per-use model of many cloud services that can become expensive at scale. It is also cross-platform, working identically on Linux, Windows, and macOS.

Nexus vs. the Alternatives

From the work I've done, I've had the chance to see how Nexus stacks up against other systems.

  • Compared to Cloud-Managed Services (AWS SNS/SQS, GCP Pub/Sub): While these services offer massive global scalability and immediate access, they come with high vendor lock-in and can get pretty pricey at scale. With Nexus, you get full control over your deployment and data, a predictable cost structure, and zero vendor lock-in as it is open-source.

  • Compared to Enterprise Message Brokers (Apache Kafka, RabbitMQ): These platforms are powerful but have a steep learning curve and high operational complexity. Deploying a complex cluster can take weeks to months. Nexus, with its single-binary deployment and low operational complexity, offers a gentle learning curve and a time-to-market measured in hours.

Behind the Scenes: Technical Highlights

Nexus is a scalable pub/sub messaging system with both HTTP and WebSocket interfaces. It uses a pluggable persistence layer (SQLite by default) to store client and message state.

For high availability and scale, Nexus nodes can be run in a multi-node cluster with a load balancer. Each node maintains its own local database and synchronizes client registry changes with peer nodes through a lightweight cluster sync endpoint. This architecture provides graceful degradation and error handling.

Effortless Management with Built-in Tooling

I'm a big believer in good tooling, and Nexus comes with a suite of command-line tools to simplify common tasks:

  • nexus-add-client: To provision a new client.

  • nexus-add-cluster: To register a new node with the cluster.

  • nexus-list-clients and nexus-list-clusters: To view existing clients and nodes.

  • nexus-serve: To start the Nexus service.

These tools, combined with comprehensive documentation, make managing your Nexus deployment straightforward.

Conclusion

Nexus offers a pragmatic approach to event distribution, balancing simplicity with enterprise-grade features. It is a solid foundation for reliable messaging systems without the complexity overhead of larger platforms. For organizations seeking a middle ground between custom solutions and heavyweight message brokers, Nexus provides a compelling combination of features, security, and operational simplicity.

It’s been an interesting journey, and now that I've gotten this out there, maybe I can get back to DANI's digital hormones and see what kind of wacky emotions he's developed.

As always, feel free to leave a comment.

Thursday, 17 July 2025

DANI's Grand Entrance: From Digital Dream to Physical Form (Mostly!)

Hello, fellow explorers of the digital frontier and anyone else who accidentally stumbled upon this blog while searching for "how to stop my toaster from plotting world domination!"

For what feels like eons (or at least, since my last post where I was still wrestling with the intricacies of a Nerf dart launcher – priorities, people!), I've been hinting, teasing, and occasionally outright dreaming about DANI. You know, D.A.N.I.: Dreaming AI Neural Integration. The project that, according to my wife, is either going to revolutionize AI or result in me building a very expensive, very purple paperweight.

Well, drumroll please... because the physical manifestation of those digital aspirations is finally complete! Yes, after countless hours of 3D printing, a few minor (okay, sometimes major) design tweaks, and enough superglue to build a small bridge, DANI's body is officially finished!

Behold! The Physical Form!

I'm absolutely thrilled to share the latest image of DANI. She's got her full body now, looking rather dashing in her signature purple and white. And yes, you eagle-eyed readers will notice a subtle but significant addition: ears! Because, let's be honest, how else is an AI supposed to convey deep thought or a sudden memory recall without a good ear twitch? It's all about those nuanced expressions, even for a robot.


D.A.N.I.

As you can see from the image, DANI is looking quite complete on the outside. The wheels are attached, the main chassis is assembled, and those newly added ears are poised for action (or at least, for looking thoughtfully into the middle distance)

The Inside Story (Still a Work in Progress, Like My Coffee Intake)

Now, before you ask, "But what about the brains?" – hold your horses! While the outer shell is a triumph of plastic and patience, the internal structure is still very much a work in progress. Think of it as a beautifully wrapped present with nothing but air inside. For now, anyway.

My main focus has now shifted squarely to the code. Because a pretty face is all well and good, but if DANI can't process information, learn from her mistakes (and mine!), and eventually, dream of electric sheep (or, you know, more efficient algorithms), then she's just a very elaborate desk ornament. And I have enough of those already.

So, expect more updates on the software side of things in the coming weeks. We're talking about getting her various "lobes" (single-board computers, for the less romantically inclined) communicating, refining those memory prioritization algorithms, and truly diving into the fascinating world of AI dreams. It's going to be a wild ride, probably involving more debugging than I care to admit, and almost certainly a few moments where I question my life choices at 3 AM.

But hey, that's the joy of independent AI development, right? No corporate overlords, just me, DANI, and the endless possibilities of a machine that might one day tell me what my dreams mean. Or at least, fetch me a biscuit without getting stuck on the rug.


Stay tuned, and wish me luck! And if you have any thoughts on how to make an AI's ears express existential angst, do drop a comment below. Every bit of neuro-spicy input helps!


Tuesday, 17 June 2025

Confessions of a Best Practice Hoarder


There are few tasks a developer enjoys more than writing documentation. It’s a thrilling journey into the exciting world of code formatting and variable naming conventions, right up there with untangling someone else’s regular expressions. So, you can imagine my sheer, unadulterated joy when I was asked to create a "Best Practice" document for my team's C# projects.

Okay, my sarcasm meter is now switched off. It was, in fact, a necessary and useful exercise. While C# isn't the language that sings me to sleep at night, it's a powerful tool (and a country mile better than Java). The goal was to create a shared map for our team, a guide to writing code that our future selves wouldn't want to travel back in time to prevent. The document covers the essentials: SOLID principles, consistent naming conventions, and key architectural patterns like Dependency Injection. It also provides guardrails for C#-specific features, like the right way to use async/await and how to query data with LINQ without accidentally DDOSing your own database.
C#
The result is a common language for quality. It makes our code reviews more productive and helps everyone, from senior to junior, stay on the same page.

Opening Pandora's Box
With the C# guide complete, a dangerous thought crept in over the weekend: "I wonder what this would look like for my other languages?" This was a classic case of a weekend project spiralling into a minor obsession. I fired up my editors and, in a fit of what I can only describe as productive procrastination, began creating similar guides for Lazarus/FreePascal, Go, Arduino C++, and Cerberus-X.
What started as a simple comparison turned into a fascinating exploration of programming language philosophy. The exercise proved that while principles like DRY (Don't Repeat Yourself) are universal, the "best" way to implement them is anything but.

A Tale of Five Philosophies
The way a language handles common problems tells you a lot about its personality. The differences are most stark in a few key areas.

Memory Management: From Butler Service to DIY Survival
How a language manages memory fundamentally changes how you write code.
  • C#: Has a garbage collector, which is like a butler who tidies up after you. It’s convenient, but you still need to know the rules. You have to explicitly tell the butler about any special (unmanaged) items using IDisposable, otherwise, they'll be left lying around.
  • Arduino/C++: This is the survivalist end of the spectrum. You have a tiny backpack with 2KB of RAM, which is less memory than a high-resolution emoji. Every byte is sacred. Heap allocation is a dangerous game of Jenga that leads to fragmentation and mysterious crashes. The Arduino String object is a notorious trap for new players, munching on your limited memory. Here, best practice isn't just a good idea; it's the only thing keeping your project from collapsing.
  • Go: Also has a garbage collector, but it’s more of a silent partner. The language and its idioms are designed in such a way that you rarely have to think about memory management. It just works.
  • Cerberus

    Cerberus-X:
    As another high-level language, Cerberus-X handles memory automatically. The developer's main responsibility isn't freeing memory, but ensuring its state is predictable. The most crucial best practice is to always use the Strict directive. This is the "no more mystery values" setting, as it enforces that all variables must be initialized before use , saving you from the bizarre bugs that come from variables defaulting to 0 or an empty string in non-strict mode.
  • Lazarus & FreePascal: The "Choose Your Own Adventure" Model
This is where things get really interesting. FreePascal offers a mixed model for memory management, letting you pick the right tool for the job.
    • The Classic Approach: This is pure manual control. Every object you create with .Create is your responsibility, and you must personally ensure it is destroyed with a corresponding .Free call. The try..finally block is your non-negotiable safety net to guarantee that cleanup happens, even when errors occur. It’s the ultimate "you made the mess, you clean it up" philosophy.
    • FreePascal cheetah

      The LCL Ownership Model: The Lazarus Component Library gives you a helping hand, especially for user interfaces. When you create a component, you can assign it an Owner (like the form it sits on). The Owner then acts like a responsible parent: when it gets destroyed, it automatically frees all the child components it owns. You should not manually .Free a component that has an owner.
    • The Modern Approach: To make life even easier, FreePascal supports Automatic Reference Counting (ARC) for interfaces. When an object is assigned to an interface variable, a counter is incremented. When that variable goes out of scope, the counter is decremented , and once it hits zero, the object is automatically freed. This brings the convenience of garbage collection to your business objects, drastically reducing the risk of memory leaks.
Concurrency: An Assembly Line vs. The Office Worker
  • C#: async/await feels like delegating a task. You ask a subordinate (Task) to do something, and you can either wait for the result (await) or carry on with other work. It's efficient and clean.
  • Go

    Go:
    Go's model is more like an automated assembly line. You have multiple workers (goroutines) and a system of pneumatic tubes (channels) connecting them. Workers perform their small task and send the result down a tube to the next worker, all happening simultaneously.
  • Arduino/C++: You're a solo act on a mission. There are no threads, so you can't do two things at once. The entire game is to never stop moving. You check a sensor, update a light, check a button, and repeat, all in a lightning-fast loop(). A delay() is your worst enemy because it brings everything to a grinding halt.
  • Lazarus/FreePascal: This is the classic office worker. To avoid freezing the UI during a long operation, you spawn a TThread to do the heavy lifting in the background. When the worker thread needs to update a label on the screen, it can't just barge in. It has to use TThread.Synchronize or TThread.Queue to politely tap the UI thread on the shoulder and ask it to make the change safely.
  • Cerberus-X: This is the resourceful indie developer. It doesn't have the fancy built-in machinery of async/await. To achieve non-blocking operations, it falls back on the fundamental tools, letting the developer build their own solution using threading or designing methods with callbacks.

Error Handling: The Town Crier vs. The Smoke Signal
  • C# & Friends: Languages like C#, Lazarus, and Cerberus-X prefer the "town crier" approach of exceptions. When something goes wrong, they shout about it loudly, and a try...catch block is expected to handle the commotion.
  • Go: Go has trust issues. It prefers you to look before you leap. Functions return an error value alongside their result, forcing you to confront the possibility of failure at every single step.
    Arduino C++

  • Arduino/C++: When your code is running on a chip in a field, how does it cry for help? It uses a smoke signal. There's no console, so robust error handling involves returning status codes or, in a critical failure, entering a safe state and blinking an LED in a specific pattern—a primitive but effective "blink of death" to signal an error code.

Up for Grabs
This dive into different programming paradigms was a blast. It’s a powerful reminder that there’s no single "best" language, only the right tool for the job, with its own unique set of best practices.

I’ve cleaned up all five documents and made them available for download. If you work in any of these languages, I hope they can be of some use to you. Now if you'll excuse me, I think I see a dusty corner of the internet where a language is just begging for a best practice guide. It's a sickness, really.


Grab them, use them, and happy coding!

Sunday, 20 April 2025

Demystifying Neural Networks: A Beginner's Friendly Guide

Hey there!

This week, I want to dive into something that might sound a bit intimidating at first: neural networks.

I know, I know. Just the phrase "neural networks" can bring to mind complex equations and head-scratching calculus. But trust me, it doesn't have to be that way! I want to share how I came to understand these fascinating systems, and hopefully, make it click for you too.

You see, I did my high school in England back in the 80s. And guess what wasn't on the curriculum? Calculus. While I might have been happy about it back then, it's definitely presented some interesting challenges when trying to get a grip on machine learning concepts today.  

So, I had to find a way to understand how neural networks work under the hood without getting bogged down in derivatives and the chain rule. And that's exactly what I want to share with you now.  

So, How Does a Neural Network Actually Work?

Think of a neural network like a team of interconnected nodes, or "neurons," organized in layers. At a minimum, you'll usually see three types of layers:  

  • Input Layer: This is where your raw data comes in. It's the starting point of the journey for your information.  
  • Hidden Layer(s): These are the layers in between the input and output. They're the workhorses, processing and transforming the data into a more useful format. You can have one or many hidden layers.  
  • Output Layer: This is where you get your final result or prediction.  

Data flows through this network, starting at the input layer, going through the hidden layers, and finally arriving at the output layer. This forward movement of data is what we call Feedforward.  

When we're training a neural network using something called supervised learning, we compare the network's output to the correct answers we already know (the "expected results"). The difference between what the network predicted and the correct answer is our "error".  

This error signal then travels backwards through the network. This is where the magic happens – the connections between those neurons are adjusted to help the network make better predictions next time. This backward movement is called Backpropagation.  

Sounds pretty simple when you break it down, right?

Let's Look Under the Hood: The Parts of a Neural Network

Okay, let's take a peek at the components. It might look a bit complex at first glance, but we'll break it down together.  



In our example, we have clearly defined the three layers. Each layer has its own neurons – 2 in the input, 3 in the hidden, and 2 in the output layer.  

You'll notice that each neuron in one layer is connected to every neuron in the next layer. So, a neuron in the input layer will have connections to all the neurons in the hidden layer, and the neurons in the hidden layer will connect to all the neurons in the output layer.  

Each of these connections has a weight associated with it. Think of the weight as the "strength" or importance of that connection.  

Also, each neuron (except for the input layer) has a bias. The weights and biases are just numbers, and they can be positive or negative. When you first create a neural network, these values are completely random.  

The values that come out of the output layer are our final result.  

Still with me? Great! Let's break it down even further. Yes, there will be a little bit of math, but I promise to keep it gentle.  

Feedforward: The Data's Journey

When we send data into the input layer, it gets passed along to the hidden layer. How? By calculating a "weighted sum" of the inputs and then adding a bias.  

What does that mean? Imagine each connection between neurons has a "strength" – that's the weight. For each neuron in the hidden layer, we take each input value, multiply it by the weight of the connection leading to that hidden neuron, and then add up all those results. Finally, we add the neuron's bias, which is like a little extra push to help the neuron activate.  

Let's look at the example from the original text:

Input values: i1=1 i2=2

Weights: From i1 to hidden layer: w1=0.1, w2=−0.02, w3=0.03 From i2 to hidden layer: w4=−0.4, w5=0.2, w6=0.6

Biases on the hidden layer: b1=1 b2=−1.5 b3=−0.25

So, for the first hidden neuron (h1), the value is calculated as: h1=(i1∗w1)+(i2∗w4)+b1 h1=(1∗0.1)+(2∗−0.4)+1 h1=0.1−0.8+1 h1=0.3  

We do this same calculation for every neuron in the hidden layer.  

So, our hidden layer values become: h1=0.3 h2=−1.12 h3=0.98  

Activation Functions: Squashing the Results

These values from the hidden layer then go through an activation function. Think of this as a way to normalize the results. There are different types of activation functions like Sigmoid, ReLU, and Linear, but for now, just know that a function is applied.  

A common one is the sigmoid function, which looks like this:  

f(x)=1+e−x1​  

Where:

  • f(x) is the output of the function.  
  • x is the input (that weighted sum we just calculated).  
  • e is Euler's number (about 2.71828).  

In simple terms, the sigmoid function takes any number and squashes it into a value between 0 and 1. This can be helpful if you want to interpret the output as probabilities.  

The original text provided a simple code example for this:


func sigmoid(x float64) float64 {

  return 1 / (1 + math.Exp(-x))

}


We don't need to worry too much about the inner workings of the code for now, just that it gives us a value between 0 and 1.  

After applying the sigmoid function, our hidden layer values might look like this: h1=0.574 h2=0.245 h3=0.728  

Now, we repeat the entire process: taking these new values from the hidden layer and feeding them forward to the output layer. It's worth noting that sometimes a different activation function is used for the final layer compared to the hidden layers.  

Calculating the Error: How Wrong Are We?

Once we have the values from the output layer, it's time to see how well the network did. We compare the network's output to the correct answers from our training data. The difference between what the network predicted and what it should have predicted is the error. This error tells us how poorly the network performed.  

We can also use these individual errors to calculate an overall average error for the network. Since these differences can be positive or negative, simply adding them up might make it look like the error is small when it's actually significant.  

A common way to get around this is using the Mean Squared Error (MSE). Here's the gist:  

  1. Calculate the difference between each predicted output and its corresponding correct value.  
  2. Square each of these differences (this makes them all positive).  
  3. Add up all the squared differences. 
  4. Divide the sum by the number of data points.  

The formula looks like this:

MSE=n1​∑i=1n​(yi​−y^​i​)2  

Where:

  • MSE is the Mean Squared Error.  
  • n is the number of data points.  
  • yi​ is the actual (correct) value.  
  • y^​i​ is the value the network predicted.  
  • ∑ just means "sum up".  

Let's use the example from the text:

Output values: o1=0.2, o2=0.9 Expected values: 1, 0

Individual errors: For o1: 1−0.2=0.8 For o2: 0−0.9=−0.9  

If we just added these, we'd get 0.8+(−0.9)=−0.1, which doesn't reflect the actual error.  

Using MSE: MSE=2(1−0.2)2+(0−0.9)2​ MSE=2(0.8)2+(−0.9)2​ MSE=20.64+0.81​ MSE=21.45​=0.725  

So, the network's error is 0.725. This gives us a clear picture of how far off the network's predictions were.

Backpropagation: Learning from Mistakes

Now that we know how wrong the network was (the error), we use that information to adjust the weights and biases. The goal is to make these adjustments so that the next time data flows through, the error will be smaller.  

The process of updating weights and biases does involve calculus in the real world, but as the original text points out, we can understand the concept without getting into the nitty-gritty of derivatives.  

Here's a simplified way to think about it:  

Adjusting Weights: For each weight connecting a hidden neuron to an output neuron, we calculate how much that weight needs to change. We do this by multiplying the error signal from the output neuron by the output of the hidden neuron. We also multiply this by a small number called the "learning rate," which controls how big of a step we take in adjusting the weight. Finally, we subtract this calculated change from the current weight.  

Weight Change = Error Signal * Hidden Neuron Output * Learning Rate New Weight = Old Weight - Weight Change  

Updating Biases: For each bias in the output layer, we multiply the error signal of that output neuron by the learning rate and subtract it from the current bias.  

Bias Change = Error Signal * Learning Rate New Bias = Old Bias - Bias Change  

Backpropagating Error to Hidden Layers: To update the weights and biases in the hidden layers, we first need to figure out the "error signal" for each hidden neuron. We do this by taking a weighted sum of the error signals from the layer above (the output layer). The original text mentions multiplying this by the derivative of the activation function, which is a detail related to calculus, but the core idea is that we're distributing the error back through the network.  

Once we have the error signal for the hidden neurons, we use that to update the weights and biases connecting to the hidden layer, just like we did for the output layer.  

If your neural network has many hidden layers ("deep network"), you repeat this backpropagation process layer by layer, moving backward from the output all the way to the input.  

And that's essentially it! As I mentioned, understanding this process doesn't necessarily require a deep understanding of calculus. Resources like the internet and Wikipedia can be incredibly helpful for finding the specific functions and details you might need.  

This is one way to approach the calculations within a neural network. If you have different approaches or see areas for correction, please feel free to share in the comments – learning is a journey we're on together!  


Friday, 11 April 2025

Diving Deep into Efficient Messaging Systems: My Journey with Polestar

I thought it was time to share some insights into a key part of a project I’ve been working on. It's not the whole codebase – I wouldn't want to bore you to tears! But I do want to talk about some of the core concepts I've implemented.   


One of the critical requirements of this project was building a messaging system capable of handling a potentially massive throughput of messages and ensuring they reach their intended destinations efficiently.   


Now, I could have gone with off-the-shelf solutions like ROS (Robot Operating System). However, I'm a bit of a control freak and enjoy crafting things from the ground up.   


That's how Polestar was born.   

Polestar


Polestar is a custom library designed to handle messages composed of maps (or dictionaries) containing primitive data types. Think strings, integers, floats, and booleans. These messages are published to Polestar with a specific topic, and any application subscribed to that topic receives a copy.   


My initial attempt at building this system was decent enough, achieving a throughput of about 800 messages per second. But I started thinking about how I could push the boundaries, enhance the throughput, and make the system even more robust.   


And guess what? I did it! I managed to crank up the throughput to an impressive 16,000 messages per second. That's more than sufficient for any scenario I can currently envision.   


To maintain efficiency, if the message queue is full when a new message arrives, the message is dropped to prevent blocking the processes.  Considering the queue's substantial capacity of 1,000,000 messages, this scenario should be quite rare.   


The Queue Conundrum


But here's where it gets interesting.  Recently, I started pondering: what if, instead of dropping the newest message when the queue is full, we dropped the oldest message?  How difficult would that be to implement?    


Go's channels, in their default state, don't offer this specific behavior. However, as is often the case in programming, there are multiple ways to achieve it.   


One approach involves creating a struct that encapsulates a queue (as a slice) and uses a single channel. But this felt like overkill for such a small feature. Plus, I'd lose the inherent speed advantages of Go's channels.   


So, I devised what I believe is a more elegant solution. It leverages the fundamental nature of channels and preserves the ability to iterate over them in the standard way.   


Go's flexibility allows you to create a new type based on an existing type, even a primitive one. In this case, I created a new type called ch based on a channel of strings:   


type ch chan string


This opens the door to using Go's method functionality to add a custom behavior to our new type.  I created a Send method with the following logic:   


// Send attempts to send a message to the channel.

// If the channel is full,

// it drops the oldest message and tries again.

// Returns a boolean indicating

// whether a message was dropped (true) and an error if the operation failed.

// The error is non-nil only if the channel remains full after attempting to

// drop the oldest message.

func (c ch) Send(msg string) (bool, error) {

  select {

  case c <- msg:

    return false, nil

  default:

    // Channel is full, drop the oldest and try again

    <-c // Discard oldest

    select {

    case c <- msg:

      // Message sent after dropping oldest

      return true, nil

    default:

      //This should rarely, if ever, happen.

      //Handle error/log message.

      return true, errors.New("Error: Channel still full after dropping oldest.")

    }

  }

}

This Send method replaces the typical channel send operation:


chVar <- “hello”


with:


chVar.Send(“hello”)


The beauty of this is that if you've created a buffered channel, the oldest item in the queue is dropped when the queue is full. This can be incredibly useful in scenarios like robotics, where outdated messages might lose their relevance, and prioritizing the latest information is crucial.   


I haven't integrated this into Polestar just yet. I'm still weighing the pros and cons of dropping the newest versus the oldest message.  Ideally, of course, no messages would be dropped at all.   


To give you a glimpse of Polestar's speed, here's a short video of one of the test runs:




My original plan involved using a hardware hub for this project. However, I don't believe I could have achieved this level of performance with a microcontroller (MCU), especially considering the queue size.  Polestar's heavy use of concurrency would also pose a challenge for microcontrollers.   


The trade-off is that all communication now relies on TCP instead of serial. Serial communication might have offered faster data transmission with less overhead, but the routing complexities would have been a significant hurdle.   


I hope this deep dive into my process provides some food for thought, especially for fellow developers. And for those who aren't knee-deep in code, I hope it offers a little peek into how my mind works.   


I welcome any comments or questions you might have. Please feel free to leave them in the comments section below!    

Thursday, 3 April 2025

Vibe Coding: The Future of Programming or Just a Fun Experiment?


Heard the latest buzzword in the tech world? It's "Vibe Coding". When I first encountered the term, my mind instantly pictured a programmer just winging it, letting the code flow wherever the digital current took them, maybe like a novelist surprised by their own characters. I'll admit, I've had moments like that – a vague goal in mind and just… coding.   

But, as it turns out, that initial guess was off the mark. So, what is vibe coding? According to the collective wisdom of Wikipedia:   

“Vibe coding (also vibecoding) is an AI-dependent programming technique where a person describes a problem in a few sentences as a prompt to a large language model (LLM) tuned for coding. The LLM generates software, shifting the programmer's role from manual coding to guiding, testing, and refining the AI-generated source code. Vibe coding is claimed by its advocates to allow even amateur programmers to produce software without the extensive training and skills required for software engineering”    

Essentially, you tell an AI what you want, and poof, it generates the code. The human becomes less of a manual coder and more of a guide, tester, and refiner. Sounds pretty cool, right? Maybe even revolutionary?   

The Allure and the Alarm Bells

I can definitely see the appeal. It sounds fun, potentially lowering the barrier to entry for software creation and offering a fascinating avenue for exploring AI capabilities. Imagine describing an app idea and having a functional starting point within minutes!   

However, based on my experience and reading, I'm not convinced it's a truly viable solution just yet. Why the hesitation?   

It Often Doesn't "Just Work": Getting AI-generated code that runs correctly the first time seems to be the exception, not the rule. It often takes several tries, tweaking prompts to get something functional.   

Functionality vs. Intent: Even if the code runs, does it actually do what you intended? That's another hurdle where luck plays a big role.   

The Amendment Nightmare: Here's the real kicker for me: trying to modify or fix AI-generated code. If you stick with vibe coding, you could end up in an endless loop of refining prompts for a single feature. Try to dive in manually? You might find code that, while functional, is baffling, overly rigid because it stuck too literally to your prompt, or just plain inefficient.   

So, Where Do We Stand?

Vibe coding is undeniably intriguing. As a tool for rapid prototyping, learning, or exploring AI's coding prowess, it has potential. But relying on it for serious development seems fraught with challenges, particularly when it comes to refinement and maintenance.   

Perhaps it's less about replacing traditional coding and more about augmenting it – a powerful assistant, but one whose work needs careful scrutiny and often, significant manual intervention.

What are your thoughts? Have you tried vibe coding? Is it the future, a fleeting trend, or something in between? Let me know in the comments!

Friday, 21 March 2025

Diving Deeper: My Journey to Create a Safer AI

In recent weeks, I've been somewhat vague about my AI and coding explorations. It's time to sharpen the focus and delve into the specifics of my AI assistant project and the research areas I'm most keen to explore.    

Let me be clear: I'm not trying to reinvent the wheel. Where it makes sense, I'll leverage existing open-source and readily available software. Why build a language model from scratch when there are perfectly good ones already out there?    

My core goal is to build an AI assistant, embodied in a robot head (and potentially a mobile platform), capable of experiencing the world and learning from those interactions.    

While that might sound like standard fare in today's AI landscape, I aim to integrate some less common and, I believe, crucial features:    

  • Self-reflection: The ability for the AI to revisit past experiences with the benefit of hindsight, analysing its previous choices to determine if it would make the same decision again.  Imagine the potential for growth if an AI could learn from its "mistakes" in a truly iterative way!    
  • Reinforced Memory Prioritization: A large-capacity memory system that prioritizes reinforced memories, similar to how our own memories function.  This would allow the AI to focus on and retain the most relevant and impactful information.    
  • Emotional Awareness: This is a significant challenge. I want the AI to learn from experiences that evoke "good" and "bad" responses.  Human feelings are complex, influenced by chemical reactions and endorphins.  My AI won't have these biological processes, so I'll need to simulate them and, crucially, understand why they are needed and what effect they would have on the AI's cognition and decision-making.    
  • A Conscience: I want the AI to be capable of second-guessing its choices based on a defined set of ethical considerations, perhaps even drawing inspiration from Asimov's Laws of Robotics.  As I've discussed previously, this is a complex but vital area of exploration.    
  • Dreams: Finally, I want to explore AI dreams. While there's existing research in this area, I believe I've identified a novel approach that could enable the AI to dream, with those dreams having a tangible impact on its cognition.    

This is undoubtedly a substantial undertaking for a single individual, and it might even exceed my current capabilities.  But I'm committed to pursuing it. This project will demand extensive research into AI, the human mind, and the ethical implications of creating such a system.    

Unlike some AI development approaches that rely on a single, powerful computer, I'm taking a distributed route.    

Another key requirement is to minimize costs.  To achieve this, each functional area (or "lobe") of the AI's "brain" will be housed on its own single-board computer.  These will be interconnected, exchanging information as needed.  This is similar in concept to the Robot Operating System (ROS), but I aim for greater speed and efficiency.  I plan to use different boards, each selected for its strengths in specific tasks.  For example, a board with a Kendryte K210 will handle vision processing, an Arduino Mega will manage motor control (yes, I know it's not an SBC, but it serves the purpose), and a Raspberry Pi will be used for memory management.    

The AI will also utilize Large Language Models (LLMs), likely at least three, for tasks such as understanding speech, processing input, and producing output.  However, unlike many systems that employ a single LLM, these will be distinct entities within the AI's architecture.    

Memory management will involve a "scoring" system to prioritize important information for short-term caching, while less critical memories will reside in long-term storage.  To prevent storage overload, I'll also implement a memory decay system that will gradually remove memories that become irrelevant to the AI's ongoing operation.    

I'm aiming to keep the total project cost under $2,000.  Whether that's achievable remains to be seen, but it's a target I'm striving for.    

Dreaming robot
Do androids dream of electric sheep?

Oh, and the dreams?  You'll have to wait a while before I reveal the details of their implementation.  Suffice it to say that, like humans, my AI will have to sleep, and this will be a non-negotiable requirement.    

So, what's my ultimate goal?  It's to create something new, something that hasn't been done before.  Not necessarily the individual components, as I've stated, I'll be using pre-existing software where possible (such as Gemma or Llama 2).  My ambition is to synthesize everything in a novel way, exploring how this approach could not only advance AI research but also contribute to making AI safer for the general public.   

And on that note, I can finally reveal the name I've given this project, and the meaning behind it: D.A.N.I. stands for Dreaming AI Neural Integration. This encapsulates the core of my research: to explore the potential of AI that learns and grows through a process akin to dreaming, deeply integrated within a neural network structure.

My wife jokes that I'm planning to build Skynet, but my intention is precisely the opposite.  I'm designing a system that would be inherently incapable of becoming Skynet – think more C3PO than Terminator.    

By incorporating the ability to learn from its mistakes (and successes), as well as the capacity for dreaming, I hope to enable the AI to accelerate its learning process.  We, as humans, frequently revisit our decisions, so why not equip an AI to do the same?    

I also aim to provide you with an engaging narrative of my development journey.  I anticipate making many mistakes. But that's part of the learning process – discovering not only how to do things, but also how not to do them.    

If you have any comments or questions, please leave a comment below.    👇



Saturday, 1 March 2025

Building My AI Fortress: The Digital Clean Room (and Taking it on the Road!)

In the exciting and sometimes unpredictable world of AI development, especially when you're diving into robotics and experimental projects, security and control are absolutely crucial. That's why I'm building a "digital clean room" for my AI project – a secure, isolated environment where it can grow and develop without external interference.


Imagine it as a "network within a network," a private lab within my digital space. To create this, I'm using two trusty ESP32 microcontrollers, those versatile little chips that are perfect for this kind of project.


The Front Door: Controlled Access

The first ESP32 acts as the "gatekeeper." It connects to my home router (or any available Wi-Fi), allowing me to access the clean room from the outside world. However, this access is strictly limited – think of it as a heavily guarded checkpoint. I’m using the WifiManager library for this ESP32, which is a lifesaver. It automatically scans for available networks, allowing me to easily enter credentials. No more hardcoding network details for every location!


The Inner Sanctum: AI's Safe Haven

Air lock
The 'Airlock'
The second ESP32 creates the "inner sanctum," acting as a dedicated access point for my AI and robotics projects. This internal network is completely isolated from the outside internet, a secure bubble where my AI can learn and grow without external influence.


The Airlock: Bridging the Gap

The two ESP32s communicate using UART, a simple serial communication protocol. This is where the magic happens – it's where I can implement strict filtering and control the flow of information, acting as an airlock between the AI and the outside world. I put a small display to show the external IP address, in case I ever need to access the clean room, and status messages.


Why This Elaborate Setup?

  • Security First: While I don't expect hordes of hackers, it's always better to be safe than sorry. This setup adds a robust layer of protection, significantly hindering unauthorized access.
  • AI Purity: Controlled Learning: I want to shield my AI from external influences, especially during its formative stages. It's vital to control the data it learns from, ensuring a solid foundation.
  • Protecting the "Child": Think of it as nurturing a child in a safe environment. I want to carefully curate the AI's initial experiences and training data before it explores the vast and sometimes chaotic internet.
  • Road-Ready AI, Cyberdeck Style!: This airlock setup has an added benefit. It makes my projects truly portable! I can take them on the road for demonstrations. As long as there's a power source and a network, I can connect an additional computer, like my trusty Raspberry Pi400, directly into the internal network. And here's the fun part: I'm planning to add a small TFT screen via a cyberdeck expansion to the Pi400. This will transform it into a self-contained, portable AI workstation, allowing me to interact with the AI's code, monitor its memory, and keep everything running smoothly, no matter where I am.

The Portable Command Centre: My Raspberry Pi400

At the heart of my portable AI lab is my trusty Raspberry Pi400. This isn't just a computer; it's the portable command centre for my AI project. It's the key to interacting with the AI within the secure confines of the internal network.


Here's why the Pi400 is so crucial:

  • Direct Access to the Inner Sanctum: The Pi400 connects directly to the internal network (IN), providing a secure platform for code development, debugging, and monitoring.
  • Raspberry Pi400 with Waveshare 3.5" screen
    and the Adafruit Cyberdeck
    Real-Time Monitoring: I can use the Pi400 to monitor the AI's memory usage, processing activity, and overall performance.
  • Cyberdeck Expansion: To make this truly portable, I'm adding a small TFT screen via a cyberdeck expansion. This transforms the Pi400 into a self-contained, mobile workstation.
  • On-the-Go Development: Whether I'm at a demonstration, a workshop, or simply working from a different location, the Pi400 allows me to access and manage my AI project with ease.
  • The gateway to the airlock: The pi400 will be the main device used to send and receive information through the airlock.
  • With the Pi400 as my portable command centre, I have the flexibility and control I need to develop and showcase my AI projects, no matter where I am.

At a later date, I may replace the Pi400 with a PiZero2 and a custom keyboard, to make things more compact, but for now, the Pi400 is perfect for the job. Plus, it gives me a full size keyboard for a more comfortable experience..

Controlled Growth, Controlled Access

Yes, this approach might temporarily limit the AI's exposure and potentially slow its initial learning curve. However, in this experimental space, I believe caution is key. It's better to guide its development carefully than to risk exposing it to unfiltered data too early.


By creating this digital clean room and airlock, I'm building a secure, portable, and controlled environment where my AI can thrive. And with the cyberdeck expansion, I'm adding a touch of classic hacker spirit to my mobile AI lab. It's a testament to the importance of security, careful nurturing, and adaptability in the exciting world of AI development.

Thursday, 27 February 2025

A Commentary: The Lost Art of Building from the Ground Up

If you've taken a peek at my bio, you'll know I've been programming and wrestling with code for a good long while. And when I say "programming," I'm not just talking about typing out lines of code. That's the easy part, honestly. But I digress.


What's been on my mind lately is the growing number of developers – and let's lump programmers, coders, and even those "script kiddies" into one big, friendly group – entering the industry with a heavy, sometimes too heavy, reliance on the current favourite framework or that magical library that makes life easier. Don't get me wrong, these tools are fantastic. I use libraries in almost all my projects. But here's the rub: many of today's rising talents seem unable to build anything without them.


Over the past decade, I've seen this reliance grow, while the fundamental skills of building without these crutches seem to be fading. I learned my craft before most of these libraries even existed. Heck, I recently realized I'm older than C++! I was a year old when Dennis Ritchie invented C at Bell Labs. So, I had no choice but to learn things from the ground up.


I had to master the basics. And I'm not talking about assembly or machine code, though I can still wrangle 6502 assembly. I mean understanding how to make a language do what you want with its core features, no external libraries or frameworks. I can already hear the C, Go, and Rust folks saying, "But <insert language here> provides standard libraries!" And you're right, but that's not what I'm talking about. I'm referring to those third-party libraries and frameworks you install separately. Some have become so ingrained in our workflow that they're almost mistaken for the language itself (looking at you, jQuery).


Take JavaScript, for example. I've written tons of it over the years. Sure, I could use React, and I probably will at some point. But for everything React does, and it does it well, I've likely built something similar in plain, native JavaScript. The same goes for Go, C++, or any other language I've worked with. I like to know how things work; I don't trust black boxes I can't peek inside.


I've worked with many talented university graduates who can create impressive projects. The UI is slick, the responsiveness is spot-on. But when something goes wrong, especially if it's within a library they're using, they hit a wall. And if you ask them to modify something built without their favourite framework, it takes far longer than it should. A solid grasp of the language's fundamentals is always essential.


So, how does this relate to my current projects? Well, I'm obviously going to be leaning on neural networks for a lot of the heavy lifting, likely recurrent neural networks. But before I even started this project, I knew I'd be using them. It's just the direction things are heading. To prepare for the onslaught of libraries and frameworks (like TensorFlow) in this area, I decided to build my own.


Let me be clear: my neural network is nowhere near as sophisticated as TensorFlow. For my limited dataset, I could probably have written a traditional program faster and more accurately. But that wasn't the point. The goal was to understand what's happening inside those infamous "black boxes."


I've since refined my network with concurrency and better activation functions, but that's just icing on the cake. By learning how these networks work, even at a basic level, I'm much more comfortable using them in my projects. I might even have to create my own, as I'm not sure there's one that perfectly fits my needs, but now I feel equipped to do so.


So, if I could offer one piece of advice (and I have many, but we'll stick to one for now), it would be this: Learn the basics. Get a book on the core language before diving into frameworks. If you need to learn a specific algorithm, try building it yourself first, without any external libraries. It doesn't have to be perfect, or even good. But by doing so, you'll gain a much deeper understanding of what those libraries are doing under the hood. Maybe not the exact details, but you'll have a solid conceptual grasp.


Don't let the convenience of libraries and frameworks replace the satisfaction of building something from the ground up. You might be surprised at what you can achieve.

Whether you agree, or disagree, I would love to hear your thoughts 👇

And yes, that is an AI generated image 😁

Aiming for Jarvis, Creating D.A.N.I.