A small C11 program that fits a linear model with batch gradient descent. It reads a CSV of input features plus a target, then prints mean squared error and the current weights after every epoch.
The binary is a trainer, not a library. There is no dependency beyond a C compiler and the math library. Sample sets for one and two features ship in the repo.
- One to several input features (compile-time cap is
MAX_FEATURESinregression_utils.h) - Batch gradient descent on mean squared error, with a caller-supplied learning rate
- CSV loader that stores data feature-major (
samples[feature][index]) - Per-epoch MSE and weight dump on stdout
- Bounds checks on epochs and learning rate via
strtoul/strtof;DIEon allocation failure
Needs gcc and make.
git clone https://github.com/mdelriolanse/c-linear-regression.git
cd c-linear-regression
makeThat builds ./regression from main.c, regression_core.c, and regression_io.c (-std=c11 -O2 -Wall -Wextra -lm).
Equivalent without Make:
gcc -Wall -Wextra -std=c11 -O2 -o regression main.c regression_core.c regression_io.c -lmmake cleanThere is a minimal make test target: it runs the two example commands below and checks for a clean exit plus at least one epoch: line, catching a broken build without asserting exact numbers.
./regression <num_epochs> <num_features> <num_samples> <learning_rate> <data_file_path>| Argument | Meaning |
|---|---|
num_epochs |
Training iterations. Parsed with strtoul. Accepted range is MIN_EPOCHS–MAX_EPOCHS (1–2500). |
num_features |
Input columns in the CSV, not counting the target. Must be between 1 and MAX_FEATURES (10). |
num_samples |
Rows to read. Must be between 1 and MAX_SAMPLES (1000). |
learning_rate |
Step size α. Parsed with strtof. Accepted range is MIN_LR–MAX_LR (1e-12–1). |
data_file_path |
Path to a CSV with no header row. |
The sample CSVs each have 100 rows. A run that actually completed on this tree:
./regression 20 1 100 0.0001 singledim_data.csv
./regression 20 2 100 0.0001 multidim_data.csvinitialize_training_config and initialize_dataloader now allow values up to and including the documented maxima (2500 epochs, 10 features, 1000 samples); the 20-epoch commands above are the ones that were run for the output in this README.
Each line is feature1,feature2,...,featureN,output. No header. The last field is the label. The parser uses strtok and atof.
1,2.14
2,3.88
3,6.11
1,2,3.14
2,3,5.88
3,4,8.11
singledim_data.csv is 100 pairs. multidim_data.csv is 100 rows of two inputs plus a label. In the two-feature file, the second input is always the first plus one.
./regression 20 1 100 0.0001 singledim_data.csv (trimmed):
Initializing 20 epochs
Initializing 0.000100 learning rate in training configuration
singledim_data.csv
Using 1 features
Using 100 samples
epoch: 1 - mse: 1.354464e+04
weights: w0=1.353932e+00 - bias: 2.020916e-02
epoch: 8 - mse: 5.628254e-03
weights: w0=2.000103e+00 - bias: 2.985244e-02
epoch: 20 - mse: 3.794078e-03
weights: w0=2.000341e+00 - bias: 2.985298e-02
./regression 20 2 100 0.0001 multidim_data.csv (trimmed):
Initializing 20 epochs
Initializing 0.000100 learning rate in training configuration
multidim_data.csv
Using 2 features
Using 100 samples
epoch: 1 - mse: 1.604226e+04
weights: w0=1.472700e+00 w1=1.495039e+00 - bias: 2.233916e-02
epoch: 8 - mse: 1.344831e+01
weights: w0=1.070359e+00 w1=1.088807e+00 - bias: 1.844792e-02
epoch: 20 - mse: 1.341520e+01
weights: w0=1.068919e+00 w1=1.090992e+00 - bias: 2.207305e-02
The one-feature set is close to y = 2x. The two-feature set is collinear, so the split between w0 and w1 is not unique and MSE levels off near 13.4.
c-linear-regression/
├── Makefile
├── main.c CLI, argument order, trainer entry
├── regression_core.c Parameters, forward/backward pass, train loop
├── regression_io.c CSV parser and epoch / learning-rate / path checks
├── regression_utils.h Caps, structs, prototypes, DIE macro
├── singledim_data.csv 100 rows, 1 feature
├── multidim_data.csv 100 rows, 2 features
├── test_smoke.sh Smoke test for `make test`
└── LICENSE Apache License 2.0
Compile-time caps live in regression_utils.h. Learning rate is not taken from the leftover LEARNING_RATE define; main reads it from argv. All caps below are inclusive: a value equal to the maximum is accepted.
| Macro | Value | Role |
|---|---|---|
MIN_EPOCHS / MAX_EPOCHS |
1 / 2500 | Epoch validator |
MIN_LR / MAX_LR |
1e-12 / 1 | Learning-rate validator |
MAX_SAMPLES |
1000 | Sample-count check in main |
MAX_FEATURES |
10 | Feature-count check in main |
PATH_BUF_LEN |
256 | Path copy buffer |
LEARNING_RATE |
0.0001 | Defined, unused by main |
The model is
y_hat = w1*x1 + ... + wn*xn + b
Weights and bias start at zero. Each epoch:
- Forward: predict every sample, accumulate MSE, the sum of residuals, and per-feature
Σ (y_hat - y) * xj. - Backward:
wj -= α * (2 / n) * Σ (y_hat - y) * xj, and the same form for the bias withoutxj.
Time per run is O(epochs × features × samples). Storage is one float column per feature plus the label vector.
Open an issue or a pull request against this repository. There is no separate contributor guide; make test runs the available smoke test.
Apache License 2.0. See LICENSE.