6 ms·
How to Implement a Cosine Similarity Function in TypeScript
- schappim 2y agoI attempted to implement this on the front end of my e-commerce site, which has approximately 20,000 products (see gist [1]). My goal was to enhance search speed by performing as many local operations as possible. Biggest impact in performance was by moving to dot products. Regrettably, the sheer size of the index of embeddings rendered it impractical to achieve the desired results. 1. https://gist.github.com/schappim/d4a6f0b29c1ef4279543f6b67408f005 https://gist.github.com/schappim/d4a6f0b29c1ef4279543f6b6740...
- alexop 2y agoThis looks nice. I also played on the weekend with Vue and Transformer.js to build the embeddings locally. See https://github.com/alexanderop/vue-vector-search https://github.com/alexanderop/vue-vector-search
- itishappy 2y agoThose are some janky diagrams. The labels are selectable, and therefore are repeatedly highlighted and un-highlighted while dragging the vector around. The "direction only" arrow prevents you from changing the magnitude, but it doesn't prevent said magnitude from changing and it does so often because the inputs are quantized but the magnitude isn't. Multiple conventions for decimals are used within the same diagram. The second diagram doesn't center the angle indicator on the actual angle. Also the "send me feedback on X" popup doesn't respond to the close button, but then disappeared when I scrolled again so maybe it did? I'm running Chrome 134.0.6998.36 for Windows 10 Enterprise 22H2 build 19045.5487. This whole thing looks like unreviewed AI. Stylish but fundamentally broken. I haven't had a chance to dig into the meat of the article yet, but unfortunately this is distracting enough that I'm not sure I will. Edit: I'm digging into the meat, and it's better! Fortunately, it appears accurate. Unfortunately, it's rather repetitive. There's two paragraphs discussing the meaning of -1, 0, and +1 interleaved with multiple paragraphs explaining how cosine similarity allows vectors to be compared regardless of magnitude. The motivation is spread throughout the whole thing and repetitive, and the real world examples seem similar though formatted just differently enough to make it hard to tell at a glance. To try to offer suggestions instead of just complaining... Here's my recommended edits: I'd move the simple English explanation to the top after the intro, then remove everything but the diagrams until you reach the example. I'd completely remove the explanation of vectors unless you're going to include an explanation of dot products. I really like the functional approach, but feel like you could combine it with the `Math.hypot` example (leave the full formula as a comment, the rest is identical), and with the full example (although it's missing the `Math.hypot` optimization). Finally, I feel like you could get away with just one real web programming example, though don't know which one I'd choose. The last section about using OpenAI for embedding and it's disclaimer is already great!
- alexop 2y agoThank you for the good feedback. I tried to improve that. I was writing the blog post for myself to understand Cosine Similarity, which is why it's maybe a bit repetitive, but this is the best way for me to learn something. I get your point. Next time I will write it better. Good feedback - I love that.
- itishappy 2y agoHa, when you put it that way, I can totally see why it read like that! It looks super great now. What you have here leaves an entirely different impression, and a stylish one! Two last suggestions: * Now I'm thinking the Why Cosine Similarity Matters for Modern Web Development section belongs at the top, right after your intro. * The angle indicator is still a bit wonky in the diagram. I might even take direction only mode out entirely, as you point out cosine similarity is invariant to changes in magnitude.
- ashvardanian 2y agoIt’s a nice post, but “using array methods” probably shouldn’t be placed in the “Efficient Implementation” section. As often happens with high-level languages, a single plain old loop is faster than three array methods. Similarly, if you plan to query those vectors in search, you should consider continuous `TypedArray` types and smaller scalars than the double precision `number`. I know very little about JS, but some of the amazing HackerNews community members have previously helped port SimSIMD to JavaScript (https://github.com/ashvardanian/SimSIMD https://github.com/ashvardanian/SimSIMD), and I wrote a blog post covering some of those JS/TS-specifics, NumJS, and MathJS in 2023 (https://ashvardanian.com/posts/javascript-ai-vector-search/ https://ashvardanian.com/posts/javascript-ai-vector-search/). Hopefully, it should help unlock another 10-100x in performance.
- mariusseufzer 2y agoOh this is awesome! We actually have a really good use case for this, it should improve performance in our case a lot and help us stay away from WASM a bit longer. Thanks for sharing!
- jgord 2y agoooh, fantastic.. thankyou ! Im doing a bunch of 3D rotation transforms on millions of points in javascript .. this looks like what I need.
- MrLeap 2y agoI'd probably use gpu.js for something like this.
- ZoomZoomZoom 2y agoI've just written a tag similarity calculation for a custom set of tagged documents, and if you're going to actually use the metrics, it's probably a good idea to fill a similarity table instead of recalculating it all on the spot. While doing that, the next logical step is precalculating the squared magnitudes for each item, and in my small test case of <1000 items that sped the calculation up almost 300 times. The gains are not exponential, but economy on that constant for each pair considered is not insignificant, especially on small data sets (of course, with large sets a table won't cut it due to memory limitations and requires more sophisticated techniques).
- deleted 2y ago[deleted]
- yesthisiswes 2y agoI liked your article. The chart with the vectors on it was cool though kinda hard to use on mobile. I went to the typescript tag and tried to read a few other articles and got 404 errors. Just wanted to let ya know. Nice blog and good work!
- dvt 2y agoGreat post, but what struck me (again, like every time I look at cos similiarity) is how unreasonably well it works. It's just one of those things that's so "weird" about our world: why would cosine similarity work in n-dimensional semantic spaces? It's so stupid simple, and it intuitively makes sense, and it works really well. Crazy cool. I'm reminded of that old Eugene Wigner quote: "The most incomprehensible thing about the universe is that it is comprehensible."
- hansvm 2y agoThat cosine distance works at all as a concept isn't terribly shocking, especially given our habit of norming everything. Cosine similarity in a unit vector space is monotonic with euclidean distance, and we're using this stuff in "select the K most relevant vectors" sorts of queries, so cosine similarity behaves identically to euclidean distance. Tack on the fact that every finite set of vectors, with your favorite metric, can be embedded in euclidean space with at most ~41% relative error (and errors that high require somewhat special circumstances, so you'd expect most real-world data to have lower errors -- plus, the error doesn't apply to every pair of points, and many will definitely have much lower error), and you're able to use normed cosine similarity somewhat reasonably on every finite set of stuff you care about, so long as you choose an appropriate embedding. All sets of things you care about in ML are finite, and the sub-metric induced by whichever infinite set you're considering works just fine for everything we've discussed, so cosine similarity is reasonable for all practical ML purposes. It's much more interesting that almost any set of ML-adjacent vectors can be somewhat reasonably compared via cosine distance _even without_ explicitly constructing an optimal embedding. It's not at all intuitive to me that an autoencoder's interior layer should behave well with respect to cosine similarity and not have any knots or anything warping that (associated) metric's usefulness.
- dvt 2y ago> behaves identically to euclidean distance Tbh, I would argue that's also pretty surprising, as Euclidean distance is notoriously unintuitive[1] (and noisy) in higher dimensions. (I guess norming does help, so that's likely a good point.) [1] https://bib.dbvis.de/uploadedFiles/155.pdf https://bib.dbvis.de/uploadedFiles/155.pdf
- frotaur 2y agoAs a physicist, I always found it funny that in ML people renamed 'the angle between vectors' into something as fancy-feeling as 'cosine similarity'.
- esafak 2y agohttps://insights.sei.cmu.edu/blog/translating-between-statistics-and-machine-learning/ https://insights.sei.cmu.edu/blog/translating-between-statis...
- jesse__ 2y agoI have a feeling it might be because the dot product of two unit length vectors is equal to the cosine of the angle between them.. but that's just a wild guess
- raverbashing 2y agoAs an engineer I find it funny when physicists (and general ML bros) don't know about linear algebra concepts
- frotaur 2y agohuh? This is just a funny joke on the way its named, what made you think I don't know about dot products ?
- raverbashing 2y agoIt could be a joke, but the way you wrote it makes it sound like you didn't know it > in ML people renamed 'the angle between vectors' into something as fancy-feeling as 'cosine similarity'. Since cosine similarity is a name derived from linear algebra
- Lerc 2y agoIt sounds pretentious, but I think it comes from having multiple ways to compare things for similarity. Using the word similarity says what you are using the angle for. Angle similarity might work just as well until someone comes up with another angle based calculation. Cosine Similarity becomes a specific meaning indicating how it's caclulated and why it's calculated. If you really wanted to be pretentious you could invent a Dimension Insensitive Euclidean Metric and call it DIEM to make it sound like you are putting some Latin into your papers.
- sunami-ai 2y agoI do all my work in Rust now via o3-mini-high and convert to WASM... JS just for DOM and event handling. What is the point of building these CPU-bound functions in TS. Why not Rust->WASM?
- tills13 2y agoBecause op knows TS and doesn't know Rust?
- sunami-ai 2y agoWhat I'm asking really is what is the benefit of TS if we could use decent and improving coding AIs to help us code in Rust, even if we are new to it, or never used it, and compile to WASM. Much faster execution per my experience. I mean not even comparable.
- tills13 2y agoHow sad is it to want a solution but none of the journey to get there. The future is bleak.
- sampullman 2y agoIt's nice for performance, but if that's not a bottleneck, Typescript is pretty convenient. It has better tooling for the web, and compiles almost instantly. Rust -> WASM can be frustratingly slow if you're exploring a new idea or prototyping.
- raverbashing 2y agoMeanwhile in Python this is just (a @ b.T)/(np.linalg.norm(a)*np.linalg.norm(b))
- forgotpwd16 2y agoBecause you use numpy. Could as well import cosine_similarity from sklearn.
- Etherlord87 2y agoyou could also normalize (divide all components by magnitude) both vectors and simply take the dot product?
- fergie 2y agoVery well explained (although not sure if the TypeScript is necessary) I wonder though- isn't classic TF-IDF scoring also a type of basic vector comparison? What is the actual difference between "vector database" and something like elastic search?
- deleted 2y ago[deleted]