A bike-sharing system can record many rentals during a busy hour and few during a quiet one. The source counts aggregate the system, not individual stations. If I have the last day's hourly counts, how much can a small model tell me about the next hour? This release lets a reader enter that history, compare the saved model with two simple guesses, and download the complete input and forecast. It is a historical forecasting experiment; it cannot tell a rider whether a bike is available now.
The first obstacle was the clock. The original hourly file has 17,379 rows, but 165 nominal hours are absent between its first and last timestamps. Taking the previous 24 rows would sometimes mean looking farther back than 24 hours. I kept the gaps visible and required a complete, consecutive 24-hour history. That leaves 4,258 of 4,376 test targets; the other 118 stay in the inventory with their exclusion reasons.
I trained one 1,185-parameter network for 40 CPU epochs and selected epoch 20 using validation data. On the eligible historical test hours, its average absolute error is 33.85 rentals, compared with 86.91 for repeating the last hour and 80.94 for repeating the same hour yesterday. An average can hide an expensive mistake: at 16:00 on 8 September 2012, the model predicts about 509 rentals against 114 recorded. That 395-rental error remains in the results.
These are rolling one-hour predictions. Each forecast can use earlier counts once those hours have happened, including earlier test counts; it is not a six-month forecast made from one starting point. The data comes from Washington D.C. in 2011–2012, and excluding incomplete histories can change which periods get scored. No current-city, station-availability, uncertainty or stronger-baseline claim follows from this comparison.
假设手里有过去一天逐小时的单车租借记录,能不能据此估计下一个小时会租出多少辆?这个小实验把保存的模型、两个简单猜法和完整下载放到同一个页面,读者可以输入24小时记录,查看差别。它用的是历史租借量,不负责判断眼前车站还有没有车。
我先遇到的麻烦是时间缺口。原文件有17,379条小时记录,但首尾之间缺了165个小时。直接取前24行,有时就不再是过去连续24小时。我要求每次预测都具备连续窗口,因此测试原有4,376个目标中只有4,258个可评,另外118条保留在库存里并注明排除原因,没有补零。
随后,一个1,185参数的小网络在CPU上实际训练40轮,只按验证集选第20轮。可评测试小时的平均绝对误差是33.85次租借,照搬上一小时为86.91,照搬前一天同小时为80.94。但平均数藏不住每次失败:2012年9月8日16点,模型估计约509次,实际114次,差了约395次。这条记录留在完整结果中。
每次预测只向前走一小时,允许使用当时已经发生的较早记录,包括较早测试小时;并非站在一个起点猜完整半年。数据来自华盛顿特区2011—2012年,缺窗口的时段也未进入评分,因此不能据此承诺今天另一座城市的效果、可用车辆、置信区间,或胜过更强的预测方法。
I trained a small residual network to forecast the next hour of recorded bike rentals from the preceding 24 hours and calendar features. The release includes the actual trained weights, chronological split inventory, all eligible test predictions, simple baselines and a browser app that runs the selected model locally.
The data is UCI Bike Sharing: Capital Bikeshare's 2011–2012 historical hourly records from Washington D.C. This experiment estimates recorded rentals. It does not estimate available bikes or unmet demand, and it has not been validated for current operations or another city.
| Method | Eligible hours | MAE (rentals) | RMSE (rentals) | RMSLE |
|---|---|---|---|---|
| Neural residual model | 4,258 | 33.8476 | 55.9980 | 0.263972 |
| Last observed hour | 4,258 | 86.9054 | 130.8970 | 0.627987 |
| Same hour 24 hours earlier | 4,258 | 80.9385 | 134.8722 | 0.696939 |
These are three methods on exactly the same eligible target hours: one trained neural model and two untrained baselines. The neural model has 1,185 parameters, trained for all 40 declared epochs. Validation count MAE selected epoch 20. The last epoch and every earlier epoch remain in run-001/trace.json; no test results selected the checkpoint.
The largest test absolute error is 395.01 rentals: 2012-09-08T16:00:00, truth 114, forecast 509.01. This example illustrates a failure; the complete test denominator and all errors remain in evaluation-001/predictions.csv. The neural model improves the aggregate metrics on this one split, without establishing significance, superiority to stronger forecasting methods, or performance under a new distribution.
The downloaded CSV contains 17,379 hourly rows. Its naive first-to-last calendar span contains 17,544 nominal hours: 165 timestamps are absent. The current UCI page displays 17,389 instances; the original README and actual hourly CSV agree on 17,379. This release uses the original CSV bytes, not the page count.
Every eligible target needs all 24 immediately preceding nominal hours. Missing hours are not filled with zero, interpolated, or compressed into a sequence of previous observed rows.
| Target split | Original rows | Eligible | Omitted |
|---|---|---|---|
| Before 2012-01-01 | 8,645 | 7,279 | 1,366 |
| 2012-01-01 through 2012-06-30 | 4,358 | 4,120 | 238 |
| 2012-07-01 through 2012-12-31 | 4,376 | 4,258 | 118 |
Of the 1,722 omitted targets, 24 are in the first 24 nominal hours and 1,698 have an incomplete lag window. All original rows remain in evaluation-001/inventory.csv with split, eligibility, omission reason and number of missing lag hours. Excluding incomplete contexts can select easier periods. Results do not cover the omitted targets.
The date and hour labels have no timezone or offset. The experiment uses nominal calendar arithmetic without reconstructing UTC or daylight-saving transitions. Browser UTC date methods are used solely for consistent calendar arithmetic; their use does not assert that the original labels were UTC.
The input has 35 features: 24 log1p rental counts ordered oldest to newest, sine/cosine of the target hour, seven Monday-first weekday indicators, and sine/cosine of the target month. The network is 35 → 32 tanh → 1, with a linear output predicting a standardized log-count residual relative to the last observed hour.
Feature means and population standard deviations, and the residual mean/standard deviation, are calculated from eligible training windows only. Training uses float64 NumPy on CPU, fixed seed 123, 128-row minibatches, Adam at 0.002, coupled weight decay 0.0001 and exactly 40 epochs. There were 2,280 parameter updates and 291,160 training-row visits. Full numerical choices are in CONFIG.json and the preimplementation PROTOCOL.md.
Target-time weather, temperature, humidity, wind, casual rentals, registered rentals and cnt never enter a target's input. In the source, casual + registered == cnt for all 17,379 rows. Their same-hour sum is a forbidden algebraic leakage check with zero error, not a forecast or trained-model result.
Evaluation is rolling one hour ahead. A later validation/test forecast uses actual earlier counts when those hours have already occurred. Contexts may cross split boundaries. This is not recursive forecasting across six months from one fixed origin, a station-level forecast, or a set of independent overlapping windows.
The predicted log count is clipped to [0, log1p(100000)] before conversion back to a continuous count. Neither validation nor test predictions hit that guard. The cap is a numeric rule, not a learned capacity or confidence bound. The app reports both the raw log count and whether the guard applied. No uncertainty interval is produced.
The browser app has no package dependency, external model API or backend inference. Serve the release directory over localhost, or deploy it later as a static app over HTTPS:
python3 -m http.server 8000 --bind 127.0.0.1
# Open http://127.0.0.1:8000.
Load the recorded example or enter exactly 24 consecutive hours as a JSON array:
{"timestamp":"2012-06-30T00:00:00","count":69}
Each array item needs exactly those two keys. Counts must have integer numeric value from 0 to 100,000; JSON 1 and 1.0 have the same accepted value. Booleans, fractional values, gaps, duplicate/out-of-order hours, offsets, impossible dates and extra fields reject. Supported input years are 1900–2100, with the derived next hour also in range. Input is limited to 64 KiB. These broad numeric/calendar checks are for interoperability; accepting a value does not validate the model for that situation. JSON.parse last-key semantics apply to duplicate object keys; this is not a duplicate-key detector.
Inference stays in browser memory. Static assets and a requested fixed example are fetched; input is not sent to an API or stored. Editing the history clears the previous result and download. The explicit JSON download includes all input hours/counts, target timestamp, model byte SHA, selected epoch, forecast, raw output, guard flag and both baselines. Downloaded reports retain the training dataset creator/DOI/license and code attribution. Arbitrary user histories retain their own terms; the page does not authenticate their origin. A SHA identifies bytes, not authenticity or model quality.
Python inference uses the same selected model and an adapter for integral JSON numbers:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python infer.py evaluation-001/example-context.json
Development used Python 3.14.7, NumPy 2.5.4 and Node 24.19.0. No GPU, pretrained weights, model API or paid compute was used. The saved host timing is descriptive, not a performance benchmark. The static browser needs ES modules, File/Blob support and HTTPS/localhost Web Crypto for the displayed model digest.
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python test_core.py
node test_inference.mjs
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python verify_results.py
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python verify_portable.py
verify_results.py is a separately written stdlib reference for the source inventory, all windows, calendar features, training-only normalizers, all 8,378 selected validation/test forecasts and all 25,134 method-specific error rows, including monthly/hourly summaries. verify_portable.py compares JavaScript's 35 features and forecasts with Python over the complete selected validation/test inventory. These checks do not fit another model or establish cloud deployment.
To reproduce the actual training, copy the release to a new working directory. Keep these paths, which are part of the byte-level freeze, and choose new output directories:
PYTHONDONTWRITEBYTECODE=1 OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 VECLIB_MAXIMUM_THREADS=1 \
.venv/bin/python train.py --out independent-run
The evaluator checks EVALUATION-FREEZE.json against a run's PRETEST digest. A fresh run has different elapsed metadata. In your independent copy, update only that freeze's pretest_sha256 to the newly created run's digest before evaluating it; retain the evaluator hash and original selected artifacts. Then run evaluate.py --run independent-run --out independent-evaluation. Do not rewrite a claimed original run. Exact arrays may depend on runtime/linear algebra; compare numerical outputs with stated tolerance rather than promising universal bit identity.
The protocol, numeric configuration and source digests were frozen before implementation and fit. PREFIT.json precedes the first update; PRETEST.json freezes the selected model, trace, normalizers, source and implementation before final model testing. The evaluator was frozen before its first test execution. A stray source-cardinality copyedit was corrected before any implementation; the original wording, freeze and edit note are preserved. Source inventory auditing had already read all original rows, including their counts; these local freeze files are not a historical blind-test attestation.
Optional figure regeneration needs requirements-plot.txt; plot_study.py uses a noninteractive Agg backend. The stored figure includes all 40 epochs and all test-month aggregates.
我用一份公开历史数据训练了小型逐小时回归模型,输入是过去连续24小时的租借量和目标小时的日历信息。模型真实训练40轮,只按验证集选择第20轮;完整测试集上的平均绝对误差为33.85,上一小时和前一天同小时基线分别为86.91、80.94。
原数据缺165个小时,所以并非每个目标都能构成完整历史窗口。测试原有4,376条,118条因窗口缺小时被排除;报告保留全部原记录和排除原因,没有补零或只报挑选过的成功样例。训练、验证、测试按目标时间切分,滚动预测允许使用当时已知的较早测试计数。
页面可直接在浏览器里运行保存的权重,导出含完整输入和模型摘要的报告。这里预测的是2011–2012年数据口径下的已记录租借量,不是当前城市需求或可用单车,也没有置信区间;遇到时段变化仍可能出现很大误差。
Dataset: Fanaee-T, H. (2013). Bike Sharing [Dataset]. UCI Machine Learning Repository. DOI 10.24432/C5W894, licensed CC BY 4.0 on the original dataset page. Original publication: Fanaee-T, Hadi, and Gama, Joao, Event labeling combining ensemble detectors and background knowledge, Progress in Artificial Intelligence (2013), DOI 10.1007/s13748-013-0040-3. The dataset README and unchanged source hashes are retained.
Code by Renchao Wu: Apache-2.0. Original data, derived selected weights, model artifacts and experimental outputs retain CC BY 4.0 attribution; see LICENSES.md, NOTICE and data/SOURCE.json. This is my separate experiment, work:bike-hour-forecast:v1; it does not claim to reproduce the original paper or imply endorsement by the dataset authors.