2025-11-11 23:11:20
.NET 10 (LTS) and C# 14 dropped today — November 11, 2025. As an LTS release, .NET 10 is supported through November 14, 2028. This post is your concise, code‑first tour of what's new across the stack: runtime, C#, ASP.NET Core, and EF Core 10.
Because this release meaningfully changes how you start small (file‑based apps), how you compose APIs (Minimal API validation + OpenAPI 3.1), and how you model data (EF Core 10 complex types & JSON). And C# 14 is packed with quality‑of‑life and performance wins.
C# now behaves like a first‑class scripting language for CLIs and utilities. You can run a single *.cs file with dotnet run — no .sln or .csproj required.
dotnet run main.cs
File‑based apps support SDK and NuGet references via #: directives at the top of your file:
#:sdk Microsoft.NET.Sdk.Web
#:package Microsoft.EntityFrameworkCore.Sqlite@9.0.0
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder();
builder.Services.AddDbContext<OrderDbContext>(o => o.UseSqlite("Data Source=orders.db"));
var app = builder.Build();
app.MapGet("/orders", async (OrderDbContext db) => await db.Orders.ToListAsync());
app.Run();
return;
public record Order(string OrderNumber, decimal Amount);
public class OrderDbContext(DbContextOptions<OrderDbContext> options) : DbContext
{
public DbSet<Order> Orders => Set<Order>();
}
Reference existing projects too:
#:project ../ClassLib/ClassLib.csproj
#!/usr/bin/env dotnet
chmod +x app.cs
./app.cs
Convert your script to a full project at any time:
dotnet project convert app.cs
Multi‑file scripting is expected to expand in future versions, but this single‑file flow already unlocks fast prototypes and ops tools.
C# 14 focuses on ergonomics and performance. Highlights below with paste‑ready snippets.
Group instance & static extensions (methods and properties) for a receiver in one block.
public static class StringExtensions
{
extension(string value)
{
public bool IsNullOrEmpty() => string.IsNullOrEmpty(value);
public string Truncate(int max) => string.IsNullOrEmpty(value) || value.Length <= max
? value : value[..max];
// static extension on the receiver type
public static bool IsAscii(char c) => c <= 0x7F;
}
}
Make intent obvious and templates cleaner:
public static class EnumerableExtensions
{
extension<T>(IEnumerable<T> src)
{
public bool IsEmpty => !src.Any();
public int Count => src.Count();
}
}
public static class CacheExtensions
{
extension<T>(IEnumerable<T> src)
{
private List<T>? _list;
public List<T> Materialized => _list ??= src.ToList();
public bool IsEmpty => Materialized.Count == 0;
}
}
public static class ProductExtensions
{
extension(Product)
{
public static Product CreateDefault() => new() { Name = "Unnamed", Price = 0 };
public static bool IsValidPrice(decimal price) => price >= 0;
}
}
Assign with ?. without manual null checks:
user?.Profile = LoadProfile();
field keyword (backing field access)
Cleaner properties without manual fields:
public class ConfigReader
{
public string FilePath
{
get => field ??= "data/config.json";
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
}
delegate bool TryParse<T>(string text, out T result);
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
Perfect for source generators:
public partial class User
{
public partial User(string name);
public partial event Action<string> Saved;
}
Improve performance for in‑place ops:
public struct Money(string currency, decimal amount)
{
public decimal Amount { get; private set; } = amount;
public string Currency { get; } = currency;
public void operator +=(Money b)
{
if (Currency != b.Currency) throw new InvalidOperationException();
Amount += b.Amount;
}
}
Console.WriteLine(nameof(List<>)); // "List"
// Many calls now infer ReadOnlySpan<T> without type noise.
builder.Services.AddValidation();
app.MapPost("/products",
([Range(1, int.MaxValue)] int productId, [Required] string name) =>
TypedResults.Ok(new { productId, name })
);
Disable on a route if needed:
app.MapPost("/raw", (int id, string name) => TypedResults.Ok(id))
.DisableValidation();
Lightweight real‑time streams via TypedResults.ServerSentEvents.
public record StockPriceEvent(string Id, string Symbol, decimal Price, DateTime Timestamp);
public class StockService
{
public async IAsyncEnumerable<StockPriceEvent> Generate([EnumeratorCancellation] CancellationToken ct)
{
var symbols = new[] { "MSFT", "AAPL", "GOOG", "AMZN" };
while (!ct.IsCancellationRequested)
{
yield return new StockPriceEvent(DateTime.UtcNow:o, symbols[Random.Shared.Next(symbols.Length)],
Math.Round((decimal)(100 + Random.Shared.NextDouble()*50), 2),
DateTime.UtcNow);
await Task.Delay(TimeSpan.FromSeconds(2), ct);
}
}
}
builder.Services.AddSingleton<StockService>();
app.MapGet("/stocks", (StockService s, CancellationToken ct) =>
TypedResults.ServerSentEvents(s.Generate(ct), eventType: "stockUpdate"));
builder.Services.AddOpenApi(o => o.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1);
if (app.Environment.IsDevelopment())
{
app.MapOpenApi("/openapi/{documentName}.yaml");
}
dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease
modelBuilder.Entity<Customer>(b =>
{
b.ComplexProperty(c => c.ShippingAddress);
b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
});
public class Customer
{
public int Id { get; set; }
public Address ShippingAddress { get; set; } = default!;
public Address? BillingAddress { get; set; } // optional
}
public struct Address
{
public required string Street { get; set; }
public required string City { get; set; }
public required string ZipCode { get; set; }
}
var q = context.Students.LeftJoin(
context.Departments,
s => s.DepartmentID,
d => d.ID,
(s, d) => new { s.FirstName, s.LastName, Department = d.Name ?? "[NONE]" });
ExecuteUpdate for JSON columns
await context.Blogs.ExecuteUpdateAsync(s =>
s.SetProperty(b => b.Details.Views, b => b.Details.Views + 1));
modelBuilder.Entity<Blog>()
.HasQueryFilter("SoftDelete", b => !b.IsDeleted)
.HasQueryFilter("Tenant", b => b.TenantId == tenantId);
var all = await context.Blogs.IgnoreQueryFilters(["SoftDelete"]).ToListAsync();
await context.Blogs.ExecuteUpdateAsync(s =>
{
s.SetProperty(b => b.Views, 8);
if (nameChanged) s.SetProperty(b => b.Name, "foo");
});
dotnet CLI UX for script→project workflows.net10.0 and enable C# 14 (<LangVersion>preview</LangVersion> may not be needed once toolchain is updated).AddValidation(); standardize 400 responses via IProblemDetailsService.field, null‑conditional assignment, partial constructors/events — less boilerplate, more clarity.If you ship APIs, CLIs, or data‑heavy apps, this release will reduce ceremony and increase velocity.
✍️ Written by: Cristian Sifuentes — .NET/C# & architecture enthusiast. If you liked this, consider subscribing to the newsletter for more deep dives and production‑ready templates.
2025-11-11 23:08:49
I recently wanted to use Solid Cache for a new MVP I'm building out in Ruby on Rails. One thing I wanted was for this to all be deployed to a PaaS and in this case I was using Heroku. While I know Rails 8 pushes hard for Kamal and rolling your own deployment, for a lot of early projects it's nice to just have all the DevOps and CI/CD taken care of for you.
This creates a problem when it comes to Solid Cache. Rails recommends running these with SQLite and in fact I have a production application using SQLite for everything that works amazing. However, Heroku is an ephemeral application server and as such, wipes out your SQLite stores on every deployment.
Since this was an MVP I really just wanted to manage one database rather than introduce Redis or another instance of Postgres. After a lot of failed attempts and much googling, this was the solution I came up with.
After deploying the Rails 8 application to Heroku, I encountered this error when trying to use rate limiting:
PG::UndefinedTable (ERROR: relation "solid_cache_entries" does not exist)
This occurred because Rails 8's rate limiting feature depends on Rails.cache, which in production is configured to use Solid Cache by default. However, the solid_cache_entries table didn't exist in our database.
This worked locally for me because in development Rails uses an in memory store so no database was required. It wasn't until deployment that I was able to see the error.
Rails 8 introduces the "Solid" stack as default infrastructure:
By default, Rails 8 expects these to use separate databases. The solid_cache:install generator creates:
config/cache.yml - Cache configurationdb/cache_schema.rb - Schema file (NOT a migration)cache databaseFor our MVP, I chose to use a single PostgreSQL database for several reasons:
When you outgrow this setup, you have clear upgrade paths:
While Solid Cache supports SQLite, Heroku's filesystem is ephemeral:
SQLite-backed Solid Cache works great for:
But for Heroku and similar PaaS platforms, use PostgreSQL or Redis for caching.
bin/rails solid_cache:install
Result: Created cache_schema.rb but no migration file. Changed cache.yml to point to a separate cache database that doesn't exist on Heroku.
Following the official Rails guides, I configured database.yml with separate database entries:
production:
primary:
url: <%= ENV["DATABASE_URL"] %>
cache:
url: <%= ENV["DATABASE_URL"] %> # Same database, different connection
And cache.yml:
production:
database: cache
Result: The cache_schema.rb file wasn't loaded by db:migrate or db:prepare. Rails expected separate databases with separate schema files.
Ran bin/rails db:prepare hoping it would load all schema files.
Result: Only loaded db/schema.rb (main migrations), ignored db/cache_schema.rb.
After researching (including this Reddit thread), I found the working solution for single-database Heroku deployments.
Remove the database: configuration from production in config/cache.yml:
# config/cache.yml
default: &default
store_options:
max_size: <%= 256.megabytes %>
namespace: <%= Rails.env %>
development:
<<: *default
test:
<<: *default
production:
<<: *default # No database: specified - uses primary connection
Important: According to the Solid Cache README, when you omit database, databases, or connects_to settings, Solid Cache automatically uses the ActiveRecord::Base connection pool (your primary database).
Generate a migration to create the solid_cache_entries table:
bin/rails generate migration CreateSolidCacheEntries --database=cache
The --database=cache flag keeps the migration organized (though it still runs against the primary database in our single-DB setup).
Update the generated migration with the exact table structure from db/cache_schema.rb:
# db/migrate/YYYYMMDDHHMMSS_create_solid_cache_entries.rb
class CreateSolidCacheEntries < ActiveRecord::Migration[8.1]
def change
create_table :solid_cache_entries do |t|
t.binary :key, limit: 1024, null: false
t.binary :value, limit: 536870912, null: false
t.datetime :created_at, null: false
t.integer :key_hash, limit: 8, null: false
t.integer :byte_size, limit: 4, null: false
t.index :byte_size
t.index [:key_hash, :byte_size]
t.index :key_hash, unique: true
end
end
end
bin/rails db:migrate
Verify the table was created:
bin/rails runner "puts ActiveRecord::Base.connection.table_exists?('solid_cache_entries')"
# Should output: true
Leave development environment using :memory_store in config/environments/development.rb:
config.cache_store = :memory_store
This is the Rails convention and keeps development simple. Production uses Solid Cache, development uses in-memory caching.
Your existing Procfile with the release phase will handle the migration:
release: bundle exec rails db:migrate
web: bundle exec puma -C config/puma.rb
Deploy and the migration runs automatically during the release phase.
After deployment, verify Solid Cache is working:
# On Heroku
heroku run rails console
# Test cache
Rails.cache.write('test_key', 'test_value')
Rails.cache.read('test_key') # Should return 'test_value'
# Check table
SolidCache::Entry.count # Should be > 0 if cache is working
Solid Cache includes automatic cleanup to prevent indefinite growth. Our configuration uses both size and age-based expiration:
# config/cache.yml
default: &default
store_options:
max_age: <%= 30.days.to_i %> # Delete entries older than 30 days
max_size: <%= 256.megabytes %> # Delete oldest when exceeds 256MB
namespace: <%= Rails.env %>
Solid Cache uses a write-triggered background thread (not a separate job system):
max_size exceeded → Delete oldest entries (LRU eviction)max_age set and size OK → Delete entries older than max_ageexpiry_batch_size) DELETE FROM solid_cache_entries
WHERE created_at < NOW() - INTERVAL '30 days'
LIMIT 100
For rate limiting (which writes on every login attempt), this mechanism works perfectly and requires no additional infrastructure.
Rate limiting data is inherently short-lived:
30 days is generous for this use case and prevents cache bloat while maintaining safety margins.
db/cache_schema.rb won't be loaded by db:migrate
database: in cache.yml uses the primary connection - This is the key for single-database setupsWhen your app scales and you need better cache performance:
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
The migration is straightforward and won't require code changes beyond configuration.
2025-11-11 23:07:52
Hey everyone — over the last few months, I’ve been building SignumFlow, an API-first system for uploading documents and running programmatic workflows.
I built this because most workflow/e-signature platforms are UI-first. They require you to send users to their hosted UI and work inside their UX expectations — which is great for non-technical teams, but restrictive if you want everything embedded directly into your product.
I wanted something closer to Stripe-style developer ergonomics, but for docs + approval routing.
So I built SignumFlow.
Right now SignumFlow supports:
No UI is required, just call the API, handle responses, and keep your users inside your app.
Docs + quickstart:
👉 https://docs.signumflow.com
These are actively being built:
My goal is that approvals and lifecycle events can be driven either:
Webhooks will complement polling so apps can react immediately to workflow transitions.
Every product I’ve worked on that needs approvals/docs ends up reinventing the same pieces:
Eventually, the internal tool grows into a workflow engine.
But building + maintaining that layer is painful and especially around lifecycle state, routing, concurrency, and auditing.
So the idea with SignumFlow is:
Let devs own the UI + business logic, and outsource the workflow mechanics to an API.
Basic Example
# Upload a document
curl -X POST https://api.signumflow.com/api/v1/upload \
-H "Authorization: $API_KEY" \
-F "[email protected]"
# Initialize a workflow
curl -X POST https://api.signumflow.com/api/v1/workflow/init \
-H "Authorization: $API_KEY" \
-d '{
"documentId": "doc_123",
"steps": [
{ "assignee": "[email protected]" },
{ "assignee": "[email protected]" }
]
}'
# Check workflow state
curl https://api.signumflow.com/api/v1/workflow/workflow_123 \
-H "Authorization: $API_KEY"
Workflows are modeled as step graphs (seq/parallel).
State transitions are recorded + queryable.
This is not meant to replace full UI-heavy signature platforms.
It’s for developers who want control and flexibility.
If you try the API or skim the docs, I’d love feedback on:
Even one small insight would be super helpful this early.
Docs:
👉 https://docs.signumflow.com
If you’ve built workflow engines, approval systems, or signature integrations before, your feedback would mean a lot.
Thanks for reading!
Happy to answer any questions.
—
Junior
Founder, SignumFlow
2025-11-11 23:00:54
Trabalhar para empresas multinacionais ou internacionais pode ser desafiador, mas está longe de ser impossível.
O que acontece, na maioria das vezes, é que você perde oportunidades porque nem tenta, no fim, acaba se sabotando.
Trabalhar para uma empresa internacional parece um sonho distante até o dia em que você percebe que a maior barreira não está lá fora, mas dentro da sua cabeça.
Muitos profissionais brasileiros têm qualificação técnica, mas se bloqueiam por causa do inglês, da síndrome do impostor ou simplesmente pela falta de informação sobre o processo.
Muita gente nem se candidata porque pensa: “meu inglês não é bom o suficiente” ou “não vou dar conta”.
Mas o segredo é simples: ninguém começa pronto, você se prepara no caminho.
Se o seu objetivo é trabalhar como contractor (PJ) para fora, abra seu CNPJ e pesquise sobre emissão de invoice internacional, contratos e recebimento por plataformas como Deel, Payoneer ou Wise.
Isso mostra profissionalismo e evita surpresas quando a proposta chegar.
Nem toda vaga precisa ser internacional para ser global.
Multinacionais que operam no Brasil oferecem cargos CLT com salários competitivos e benefícios sólidos, como férias, plano de saúde e estabilidade.
Lembre-se: 30 dias de férias é um luxo até em empresas dos EUA.
Em muitos casos, vagas internacionais oferecem apenas 10 a 15 dias de PTOs, ou até o famoso “ilimitado”, que raramente é usado de verdade.
Você não precisa soar como um nativo, mas precisa se comunicar bem.
Foque em inglês técnico e inglês conversacional para reuniões.
Hoje há muito acesso a professores, plataformas e até comunidades gratuitas para praticar.
O inglês é uma ponte e você não precisa atravessá-la correndo. Apenas comece a caminhar.
Antes de se candidatar, revise seus projetos no GitHub.
Mostre código limpo, organização e boas práticas.
Inclua um README bem feito, isso demonstra profissionalismo e facilita a avaliação por recrutadores estrangeiros.
Evite criar projetos inteiros com IA se você não entende o que está implementando.
“Você pode ter toda a skill técnica do mundo, mas se não tiver consistência e iniciativa, vai continuar vendo os outros conquistarem o que você quer.”
A comparação é cruel e quase nunca reflete a realidade.
Você se compara com o fulano, o ciclano, o influenciador que já está lá fora e acaba parado no mesmo lugar.
O que realmente faz diferença é dar o primeiro passo.
Caminhe ao lado de pessoas que buscam o mesmo que você.
Converse com quem já conseguiu.
Afaste-se de quem joga baldes de água fria nos seus sonhos.
A cada processo, teste e entrevista, você ganha experiência.
Ninguém acerta de primeira, e tudo bem.
Você aprende com os “nãos”, com os feedbacks e com o tempo.
A consistência e o hábito de melhoria contínua fazem toda a diferença, especialmente nos soft skills.
Com cada entrevista, você se comunica melhor, se apresenta com mais clareza e ganha confiança.
Se tiver amigos mais experientes, simule entrevistas. Converse em inglês sobre o seu trabalho. Isso já é um treino valioso.
O processo não é um conto de fadas.
Cuidado com quem vende consultorias prometendo emprego internacional em 30 dias. Pode acontecer? Pode.
Mas na maioria dos casos, leva tempo e exige preparo.
A rotina internacional também tem suas diferenças.
Nos primeiros meses é normal sentir nervosismo, ansiedade e até travar no inglês.
Você vai esquecer palavras, errar verbos, ficar inseguro, e isso é parte do processo.
Com o tempo, a confiança vem.
E a recompensa é enorme: crescimento profissional, aprendizado contínuo e uma experiência global que transforma sua carreira.
O maior desafio é entrar na primeira empresa. Depois disso, tudo flui com muito mais naturalidade.
Você não precisa ser o melhor, só precisa estar preparado quando a oportunidade aparecer.
Trabalhar com empresas internacionais não é um prêmio, é a consequência natural de quem se prepara, se expõe e continua evoluindo.
2025-11-11 23:00:00
Every great product starts with people, not code.
This post kicks off my new series People Over Pixels, where I share the soft skills that I believe are more important than any framework, syntax, or architecture.
After years of working in different teams, I’ve realized that the success of a product depends less on how we code, and more on how we communicate.
In 1999, NASA lost the Mars Climate Orbiter, a $125 million spacecraft. Not because of bad hardware, or a complex bug, but because two brilliant teams weren’t aligned. One used imperial units. The other used metric.
A single misunderstanding destroyed years of work and one of the most advanced spacecraft ever built [1].
That story stuck with me because it shows something simple yet profound:
Even the best engineers fail when they stop talking the same language.
The best teams I’ve worked with didn’t avoid problems, they talked about them early. They didn’t guess what others meant, they asked. They didn’t hide mistakes, they shared them before they grew.
Communication isn’t about meetings or documentation. It’s about creating a shared understanding, so everyone moves in the same direction.
I used to stay quiet when I wasn’t 100% sure about something. I thought asking too many questions would make me look less experienced, but the truth is the opposite.
The people who ask questions make everyone smarter. They catch assumptions, expose risks, and help the team think more clearly.
The best developers aren’t the ones who always know the answer, they’re the ones who make it safe for everyone to ask the hard questions.
Toyota’s production system is famous for one rule: any worker on the line can pull the andon cord to stop production if they see a problem.
It doesn’t matter who you are, everyone is empowered to speak up. That culture of openness made Toyota one of the most consistent, high-quality manufacturers in the world.
In software, "pulling the andon cord" means saying:
That’s how teams build quality, not just in their code, but in their relationships.
Good communication turns individual effort into collective success. It’s how teams stay aligned, fix problems faster, and build trust over time.
When people talk, they discover solutions no one could find alone. When they stay silent, even the best code can’t save them.
Great teams aren’t made of the most talented individuals, they’re made of people who talk, listen, and learn together. So, if you want your next project to succeed, don’t just focus on writing perfect code.
Focus on creating conversations that lead to better code, because in the end:
Teams that talk early, fail less, and build more.
[1] Simscale - When NASA Lost a Spacecraft Due to a Metric Math Mistake
✅ That’s all, folks!
💬 Let’s Connect
Have any questions, suggestions for improvement, or just want to share your thoughts?
Feel free to leave a comment here, or get in touch with me directly on LinkedIn — I’d love to connect!
2025-11-11 22:57:00
TL;DR:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Here's the counterintuitive nightmare that kept researchers up at night:
Deeper networks should = better performance, right?
Wrong. Catastrophically wrong.
In 2015, teams were hitting a wall. Add more than 20 layers to your CNN? Watch your training accuracy decrease. Not overfit - just... fail.
# What researchers saw:
20-layer network: 85% accuracy ✅
56-layer network: 78% accuracy ❌
# This made ZERO sense
The cruel irony? A deeper network should theoretically match a shallow one by learning identity mappings in extra layers. But gradient descent couldn't figure this out.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Kaiming He and his team at Microsoft Research asked a brilliant question:
"What if we stop asking layers to learn the underlying mapping H(x), and instead learn the residual F(x) = H(x) - x?"
Instead of this:
output = layer(input) # Learn H(x) directly
Do this:
output = layer(input) + input # Learn F(x), add input back
Why this works:
Think of it like this: Instead of teaching someone a complex route, you teach them the detours from the highway they already know.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ResNet-50 has 50 layers organized in bottleneck blocks:
Input (224×224×3)
↓
7×7 conv, stride 2
↓
3×3 max pool
↓
[1×1 conv → 3×3 conv → 1×1 conv] × 3 # Stage 1
↓
[1×1 conv → 3×3 conv → 1×1 conv] × 4 # Stage 2
↓
[1×1 conv → 3×3 conv → 1×1 conv] × 6 # Stage 3
↓
[1×1 conv → 3×3 conv → 1×1 conv] × 3 # Stage 4
↓
Global Average Pooling
↓
Fully Connected (1000 classes)
class BottleneckBlock:
def forward(self, x):
identity = x
# 1×1 conv reduces dimensions
out = conv1x1(x, filters=64)
# 3×3 conv does the heavy lifting
out = conv3x3(out, filters=64)
# 1×1 conv restores dimensions
out = conv1x1(out, filters=256)
# THE MAGIC: Add skip connection
out += identity # 🎁
return relu(out)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Microsoft's v1.5 modification:
# v1 (original)
Bottleneck:
1×1 conv, stride=2 # Downsampling here
3×3 conv, stride=1
1×1 conv, stride=1
# v1.5 (improved)
Bottleneck:
1×1 conv, stride=1
3×3 conv, stride=2 # Downsampling moved here
1×1 conv, stride=1
Impact:
💡 When to use which:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
pip install transformers torch datasets
from transformers import AutoImageProcessor, ResNetForImageClassification
import torch
from PIL import Image
# Load pre-trained ResNet-50 v1.5
processor = AutoImageProcessor.from_pretrained("microsoft/resnet-50")
model = ResNetForImageClassification.from_pretrained("microsoft/resnet-50")
# Load your image
image = Image.open("your_image.jpg")
# Preprocess and predict
inputs = processor(image, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits
# Get prediction
predicted_class = logits.argmax(-1).item()
label = model.config.id2label[predicted_class]
print(f"Prediction: {label}")
print(f"Confidence: {torch.softmax(logits, dim=1).max().item():.2%}")
Prediction: golden_retriever
Confidence: 94.73%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ImageNet-1k Results (224×224):
| Metric | ResNet-50 v1.5 |
|---|---|
| Top-1 Accuracy | 76.13% |
| Top-5 Accuracy | 92.86% |
| Parameters | 25.6M |
| Inference (GPU) | ~5ms/image |
Why ResNet-50 is the go-to baseline:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Early layers learn general features (edges, textures)
# Freeze them, train only later layers
for param in model.resnet.embedder.parameters():
param.requires_grad = False
for param in model.resnet.encoder.stages[0].parameters():
param.requires_grad = False
# Use lower LR for pre-trained weights
optimizer = torch.optim.AdamW([
{'params': model.resnet.parameters(), 'lr': 1e-5},
{'params': model.classifier.parameters(), 'lr': 1e-3}
])
from transformers import AutoImageProcessor
processor = AutoImageProcessor.from_pretrained(
"microsoft/resnet-50",
do_resize=True,
do_center_crop=True,
do_normalize=True,
# Add augmentation
do_flip=True,
do_random_crop=True
)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ResNet-50 expects 224×224
image = processor(image, size={"height": 224, "width": 224})
# ResNet was trained with ImageNet normalization
# processor handles this automatically
# DON'T normalize manually unless you know what you're doing
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
inputs = {k: v.to(device) for k, v in inputs.items()}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use ResNet-50 when:
Consider alternatives when:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ResNet didn't just win ImageNet 2015. It changed how we think about deep learning:
"Residual learning is one of those ideas that seems obvious in retrospect but was revolutionary when introduced." - Andrej Karpathy
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Try this challenge:
Going deeper?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
What's been your experience with ResNet? Still using it in production, or have you moved to newer architectures? Drop your thoughts below! 👇
Found this useful? Follow for more deep learning breakdowns where I actually explain why things work, not just how.
═══════════════════════════════