Source: https://oliviercouthaud.com/priceestimator · Status: Prototype · 2026 · Tags: Python, CLIP, LightGBM, Conformal prediction # PriceEstimator Guesses what an item costs from a photo and a title, and gives a range instead of a single number. ## What I wanted I wanted to point a phone at an object and get a price. Marketplace listings are the obvious training data, but a photo leaves out brand, size and condition, so I settled on something narrower: a ballpark price plus a range that says how far to trust it. I also decided up front to use no generative model. Everything here is an encoder, which turns an image or a sentence into a list of numbers, feeding gradient-boosted trees. ## How it works The text input is the brand, title and leaf category joined into one string. Both encoders are frozen, so every product's image and text are embedded once and cached, and everything after that runs on a CPU. The target is log1p(price), which makes a 20% miss cost the same at $5 and at $500. - The product photo. For the Amazon sets it is the catalog image, and I also tested on photos customers uploaded in reviews. - CLIP ViT-B/32, frozen. It turns the photo into 512 numbers. It stays frozen, and each image is embedded once. - Brand, title and leaf category joined into one string. - all-MiniLM-L6-v2, also frozen. It turns that string into 384 numbers. - Optional. One slot per category, 8 or 18 of them, set to 1 for the item's own category. It barely helped: RMSLE went from 0.791 to 0.788 on the 12,000-product set. - The image numbers, text numbers and category slots are stuck end to end into one long vector. - Three LightGBM models, one each for the 10th, 50th and 90th percentile of log1p(price). The middle one is the guess, the outer two are the raw low and high. - A conformal step checks the low and high against a held-out fifth of the data and widens them by exactly as much as needed. On the 72,000 run, 79% of test prices fell inside the range. Figure: Diagram. The pipeline for the Amazon models. Both encoders are frozen and only the LightGBM heads are trained. The CS2 models differ: the text model uses name n-grams plus float and wear features, and the image model uses the CLIP vector plus float, StatTrak and wear features, with no text encoder. Swipe sideways to see all of it. **What is CLIP?** CLIP is a model trained on pairs of images and captions, so that a photo and a description of it end up close together as lists of numbers. I use it frozen: ViT-B/32 turns each photo into 512 numbers and I do not retrain it. It describes images, it doesn't generate them. Three LightGBM models predict the 10th, 50th and 90th percentile of that target. A conformal step then widens or tightens the band using a held-out calibration split, so the stated 80% range holds about 80% of the time. I wrote that step by hand instead of pulling in a library. The split is 60% train, 20% calibration, 20% test. **What is a quantile model?** An ordinary regression predicts one number, the average. A quantile model predicts a chosen percentile instead: the 10th percentile is a price that 10% of similar items fall below. With the 10th, 50th and 90th together you get a low, a middle and a high, and the width tells you how spread out the prices are for items that look like this one. **What is conformal calibration?** The raw 10th-to-90th band is only as honest as the model, and a model can be too confident. So I score it on data it was not trained on and measure by how much the true prices miss the band. The conformal step then picks the width that brings the miss rate to 20%, which is what makes the 80% mean something. This version is called CQR. I built it in four stages, each a bigger version of the last. CS2 skins from CSFloat listings came first, because it was a cheap way to check the pipeline. Then 5,000 Amazon grocery products, to ask whether a photo adds anything a title doesn't. Then 12,000 products across 8 categories, and finally 72,000 across 18. For the last one I rented an RTX 3090 on Vast.ai to do the embedding, which cost $2.09 in all. ## Results The photo does carry price signal on general products, and fusing it with the title works best. Switch between the stages to see the numbers. **What is RMSLE?** It is the typical size of the error measured in log-price, so being off by a factor of two counts the same whether the item costs $5 or $500. Lower is better. Where a table has a first row that guesses the median price for everything, that row shows what no skill looks like. ### The same question at four scales Coverage is how often the real price fell inside the stated 80% range. 8,821 listings, 3,658 distinct skins. The target is CSFloat's reference market value, not the seller's ask, which was too noisy. | Model | RMSLE | Median error | Coverage | |---|---|---|---| | Guess the median | 3.04 | n/a | n/a | | Name text + tabular features | 0.788 | 33.1% | 83% | | Image only | 0.913 | 29.9% | 81% | | Image + float, StatTrak | 0.828 | 19.0% | 83% | The photo model has the lower typical error and the worse RMSLE. I put that down to a few expensive skins. 5,000 Amazon grocery products, 4,296 of them usable. Price runs from $0.39 to $495, median $18.75. | Model | RMSLE | Median error | Coverage | |---|---|---|---| | Guess the median | 0.726 | n/a | n/a | | Text only | 0.671 | 42.1% | 78.8% | | Image only | 0.677 | 43.8% | 80.5% | | Image + text | 0.659 | 41.6% | 81.9% | Fusing gave 1.8% better RMSLE than text alone. Small, but the photo on its own nearly matches the title. 12,000 products, 1,500 in each of 8 categories. Price runs from $0.01 to $2,000, median $19.99. | Model | RMSLE | Median error | Coverage | |---|---|---|---| | Guess the median | 1.023 | n/a | n/a | | Text only | 0.855 | 48.5% | 79.2% | | Image only | 0.823 | 47.9% | 80.7% | | Image + text | 0.791 | 46.7% | 80.2% | | Image + text + category | 0.788 | 47.1% | 80.7% | Image beats text here. One model for all eight categories did as well as eight separate ones: average RMSLE 0.784 against 0.782. 72,000 products, 4,000 in each of 18 categories, tested on held-out catalog rows. | Model | RMSLE | Median error | Coverage | |---|---|---|---| | Image only | 0.774 | 46% | 79% | | Image + text | 0.726 | 42% | 80% | | Image + text + category | 0.720 | 42% | 79% | Six times the data moved the typical error from 47% to 42%. More on that under what I ran into. CS2 is a narrow test of the photo idea, because a skin image is just a render of its name and wear, so the model mostly recognises which skin it is. Even so, the image-only model priced correctly a skin the text model missed by a wide margin. The text model priced AK-47 Crane Flight (Field-Tested) at $1.20, because its name pieces are shared with thousands of cheap skins. The image-only model said $13.93 against about $15.47. That is one skin I picked, an example and not a measurement. Figure: Diagram. AK-47 Crane Flight (Field-Tested), drawn from the three prices above on a log axis. Swipe sideways to see all of it. Figure: The same table as a picture: out of every 100 products in the held-out test set (a fifth of the 72,000), how many the middle guess landed within 10%, 25% and 50% of the real price. **72,000 Amazon products, 18 categories. Held-out catalog test, and 2,979 real user review photos. The first three columns (≤10%, ≤25%, ≤50%) are the share of products whose middle guess was within that distance of the listed price. Median error is the median absolute percentage error. Coverage is the share of true prices inside the stated 80% range. Fused means image + text + category.** | Model, test set | ≤10% | ≤25% | ≤50% | Median error | Coverage | |---|---|---|---|---|---| | Fused, catalog images | 13% | 31% | 57% | 42% | 79% | | Fused, user photos | 13% | 31% | 57% | 43% | 82% | | Image only, user photos | 11% | 27% | 52% | 47% | 82% | The user photos come from 8 of the 18 categories, and the fused rows also read each product's title. Those products may also appear in the training catalog, so this shows how well it copes with phone photos, not a clean exam. The cost of the switch from catalog to real photos is small: RMSLE 0.720 to 0.770. Figure: Median error by category for the 72,000-product model. Only the four best and four worst categories are shown. Swipe sideways to see all of it. All eight categories, from the 12,000-product run. Pick what to rank them by. ### Eight categories, one model **12,000-product model, image + text + category, held-out test. About 300 test products per category. Coverage is the share of true prices inside the stated 80% range.** | Category | Test n | RMSLE | Median error | Coverage | |---|---|---|---|---| | Grocery | 302 | 0.643 | 39.0% | 84.1% | | Toys & Games | 307 | 0.699 | 40.6% | 86.6% | | Amazon Fashion | 270 | 0.727 | 41.0% | 83.3% | | Home & Kitchen | 304 | 0.780 | 42.9% | 79.9% | | Sports & Outdoors | 297 | 0.769 | 47.9% | 78.5% | | Office Products | 306 | 0.787 | 54.0% | 79.7% | | Electronics | 317 | 0.855 | 59.6% | 78.2% | | Musical Instruments | 297 | 0.991 | 61.5% | 75.4% | Sorted by median error, best first. Grocery, toys and fashion are the easy ones. Electronics and instruments are the hard ones. A photo gets a ballpark rather than a price: 13% of items land within 10%, and the median error is about 42%. ## What I ran into Scaling up helped a little. Going from 12,000 to 72,000 products moved the median error from 47% to 42% (the category sets differ, 8 then 18, and so do the test rows, so the two are only roughly comparable), and the share within 10% stayed at 13%. My read is that this is the level frozen CLIP plus trees reaches, rather than a lack of data, since brand, size, specs and condition mostly aren't in the pixels. Nothing I ran tests that directly. Smaller points. On the grocery set, fusing image and title improved RMSLE by 1.8%, and image-only was slightly worse than text-only. I suspect pack size and weight, which a photo rarely shows, and I have not tested that. CSFloat's rate limit capped how much CS2 data I could pull (8,821 listings across 3,658 skins, about 2.4 per skin), so rare skins are priced less accurately, though the model flags them as low confidence. Embedding at scale was limited by image downloads, about 13 images per second, not by the GPU. ## Where it stands The demo is a local web page for the CS2 skin model: upload a skin image and it shows the estimate, the 80% range and a confidence meter. Next on the list are an inference API, an abstain gate for photos that can't be priced, structured attributes such as specs and brand, retrieval of comparable listings for recognisable products, and end-to-end fine-tuning. None of these is built or tried yet. I also began applying the same stack to road condition. On 4,805 US dashcam-style images from RDD2022 (CC-BY-SA), CLIP embeddings plus LightGBM pick up a weak to moderate signal for each of the four damage types, with ROC-AUC between 0.65 and 0.76 (0.5 is chance). **RDD2022, United States split, held-out 20%. Average precision is the area under the precision-recall curve.** | Damage type | Share of images | ROC-AUC | Average precision | |---|---|---|---| | Alligator crack (D20) | 15% | 0.764 | 0.43 | | Transverse crack (D10) | 49% | 0.679 | 0.68 | | Longitudinal crack (D00) | 81% | 0.655 | 0.88 | | Pothole (D40) | 2.4% | 0.654 | 0.06 | Potholes score lowest, since they are small and rare and a whole-image embedding mostly misses them. Every image in that set contains damage, so a damaged-or-not test isn't possible on it. The second step matched Mapillary street imagery to San Francisco's open pavement-condition scores by GPS, with no manual labelling. 656,951 image points against 19,425 road segments gave 30,479 matches within 25 m, and I sampled 10,479 labelled images from those. A condition model trained on them is a little better than guessing the median: mean error 16.5 points against 18.0, correlation 0.34. That is a first result. A street photo is mostly sky, buildings and cars, so the road is a small part of the pixels, and cropping to the pavement is the first thing I would try. The training data is Amazon Reviews 2023 (McAuley Lab) and CSFloat's listings API, and the road work uses RDD2022, Mapillary imagery and San Francisco's open pavement data. The data and the trained models are not published, and I have not checked whether those terms allow commercial use, so this stays a research project. - **Stack**: Python, CLIP ViT-B/32 (via sentence-transformers clip-ViT-B-32), all-MiniLM-L6-v2, LightGBM, hand-written conformal prediction (CQR), Flask demo - **Data**: Amazon Reviews 2023 (up to 72,000 products), CSFloat listings (8,821), RDD2022 (4,805 US images), Mapillary x San Francisco pavement data (10,479). Not redistributed. - **Status**: Prototype. Evaluated offline; local demo for CS2 only - **Code**: Not public