<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Olivier Cruchant on Medium]]></title>
        <description><![CDATA[Stories by Olivier Cruchant on Medium]]></description>
        <link>https://medium.com/@oliviercruchant?source=rss-a2d4c58edd86------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*k-NyieIRn25Rb8CIV2nWtw.jpeg</url>
            <title>Stories by Olivier Cruchant on Medium</title>
            <link>https://medium.com/@oliviercruchant?source=rss-a2d4c58edd86------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 00:24:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@oliviercruchant/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Using MXNet NDArray for fast GPU algebra on images]]></title>
            <link>https://medium.com/apache-mxnet/using-mxnet-ndarray-for-fast-gpu-algebra-on-images-a3cd63c94c62?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/a3cd63c94c62</guid>
            <category><![CDATA[gpu]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[apache-mxnet]]></category>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[numpy]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Mon, 15 Feb 2021 18:55:04 GMT</pubDate>
            <atom:updated>2021-02-15T18:55:04.030Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using MXNet NDArray for Fast GPU Algebra on Images</h3><p><em>By Philippe Saadé and Olivier Cruchant</em></p><h3>Introduction</h3><p><a href="https://mxnet.apache.org/versions/1.7.0/">Apache MXNet</a> is a modern deep learning framework featuring both a pleasant developer experience and high-performance training and inference. In order to get the best possible performance while exposing a friendly interface, the MXNet community re-developed from scratch a suite of optimized tensor algebra kernels, historically accessible via the Python MXNet <a href="https://mxnet.incubator.apache.org/versions/1.7.0/api/python/docs/api/ndarray/index.html">NDArray library</a>. The initial MXNet <a href="https://mxnet.apache.org/versions/1.0.0/api/python/ndarray/ndarray.html">NDArray API</a> is very similar to <a href="https://numpy.org/doc/stable/reference/">NumPy</a>, yet it features major internal differences enabling high-performance deep learning: (1) it can run on GPU, (2) it supports auto-differentiation, (3) it runs asynchronously, executing only the code that need to run and in an optimized order. In order to make the powerful MXNet NDArray API fully accessible to NumPy developers, the MXNet community released in 2019 the mxnet.numpy library, which implements the NumPy API (<a href="https://medium.com/apache-mxnet/a-new-numpy-interface-for-apache-mxnet-incubating-dbb4a4096f9f">Announcement on MXNet Medium</a>). In this blog post, we illustrate the strength of MXNet NDArray library for fast and compact algebra over images. We first highlight couple NDArray concepts and then assemble them into a function, that we provide both as an mxnet.ndarray implementation and an mxnet.numpy implementation. We use as an illustration a fictional use-case of anomalous image detection via simple pixel difference. This can be a reasonable baseline when looking for anomalous areas over a batch of near-identical images with same viewpoint and luminosity, for example the images below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iAa3VR0-Bk97SYoMFrNv4w.png" /><figcaption><em>From left to right: (1) correct sample (2) synthetic anomalous sample with a stain (3) anomalous sample after difference with the cross-batch mean.</em></figcaption></figure><h4><strong>Notable concepts</strong></h4><p>We will use the following libraries:</p><pre>import itertools as itr<br>import os<br>import mxnet as mx<br>from mxnet import image as mxim<br>from mxnet import init, gluon, nd<br>import matplotlib.cm as cm<br>from matplotlib import pyplot as plt<br>from matplotlib import image as mpim</pre><p>MXNet features the image library that we can use to read images:</p><pre>picture = mxim.imread(&#39;orig1.JPG&#39;)</pre><pre><em># .asnumpy() converts MXNet NDArrays to Numpy array</em><br>plt.imshow(picture.asnumpy())</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/1*jtp5ghCvqkseI42IniGyHQ.jpeg" /><figcaption><em>This is a non-anomalous image. In our synthetic dataset, we have 10 such images, and two images with visual anomalies</em></figcaption></figure><p>MXNet NDArray leverages the mxnet <a href="https://mxnet.apache.org/versions/1.7.0/api/python/docs/api/mxnet/context/index.html">context</a> concept, that enables to control the hardware context - CPU or GPU of objects. For example, we can send our previously loaded image to the GPU (if on a GPU-equipped instance) with the following snippet:</p><pre>pic_on_gpu = picture.copyto(mx.gpu())</pre><p>Note that we can also use picture.as_in_context(mx.gpu()) to copy the picture to the GPU ; the advantage of the latter being that it does the actual copy only if the image is not in the GPU already. <br> <br> Similarly to Numpy, MXNet NDArray is a great platform to apply algebraic transformations to batches of records. Writing transformations applied to batch of images is furthermore relevant on GPUs which excel at batch processing. Very similar to their Numpy counterparts, we use in this demo <a href="https://mxnet.apache.org/versions/1.0.0/api/python/ndarray/ndarray.html#mxnet.ndarray.Concat">ndarray.concat()</a>, <a href="https://mxnet.apache.org/versions/1.0.0/api/python/ndarray/ndarray.html#mxnet.ndarray.mean">ndarray.mean()</a>, <a href="https://mxnet.apache.org/versions/1.0.0/api/python/ndarray/ndarray.html#mxnet.ndarray.abs">ndarray.abs()</a>, <a href="https://mxnet.apache.org/versions/1.0.0/api/python/ndarray/ndarray.html#mxnet.ndarray.max">ndarray.max()</a> to manipulate and analyze a batch of images.<br> <br> MXNet being primarily designed for deep learning, it features abundant neural network primitives. Its imperative Python front-end <a href="https://mxnet.apache.org/versions/1.4.1/gluon/index.html">Gluon</a> provides numerous model layers, and in this blog - even though we do not use deep learning nor even machine learning - we borrow from Gluon its 2D-convolution (<a href="https://mxnet.apache.org/versions/1.6/api/python/docs/api/gluon/nn/index.html#mxnet.gluon.nn.Conv2D">mxnet.gluon.nn.Conv2D</a>) with all coefficients initialized at 1 to apply a smoothing effect and isolate contiguous areas of anomalous pixels.</p><h3>Wrapping it all in a compact function</h3><p>The inline code block below proposes an example function to run the above-mentioned analysis. It first reads a batch of images, then subtracts from all of them the cross-batch mean image, applies a hard-coded convolution to each of them and return images that have high-value pixels after the convolution. Note that this code features couple hard-coded constants that would be worth tuning over representative data. This sample is provided as an illustration of the NDArray capacities and may deserve further refinement and testing before being used in the real-world.</p><h4>Implementation with mxnet.ndarray</h4><pre>def mxnd_find_anomalies(folder, gpu=False, save_viz=True, threshold=300):<br><em>    &quot;&quot;&quot;<br>    Parameters<br>        ----------<br>        folder : str<br>            local directory with the batch of images to score<br>        gpu : bool, optional<br>            whether to use GPU or CPU<br>        save_viz : bool, optional<br>            saves the pictures of deltas and convolutions<br>        threshold : int, optional<br>            conv threshold to be anomalous. Should be tuned!           <br>    &quot;&quot;&quot;</em><br>    <br>    ctx = mx.gpu() if gpu else mx.cpu()  <em># Set context</em><br>    <br>    <em># read images in a 4D-NDArray</em><br>    pics = os.listdir(folder)<br>    ims = [mxim.imread(folder+&#39;/&#39;+pic).expand_dims(0).as_in_context(ctx) for pic in pics]<br>    ims = nd.concat(*ims, dim=0)<br>        <br>    <em># Compute the average image</em><br>    avg = nd.mean(ims.astype(&#39;float32&#39;), axis=0)<br>    <br>    <em># Remove the mean from every image, then average-pool on color</em><br>    deltas = nd.mean(ims.astype(&#39;float32&#39;)-avg, axis=3)<br>    <br>    <em># Apply a batched convolution to all the deltas, then apply abs</em><br>    conv = gluon.nn.Conv2D(1, kernel_size=5, use_bias=False)<br>    conv.initialize(init.Constant(1))<br>    conv.collect_params().reset_ctx(ctx)<br>    <br>    <em># we expand dims for conv, which expect 3D pics </em>(with channels)<br>    conv_delta = nd.abs(conv(deltas.expand_dims(1)))<br>    <br>    <em># Max-pool over the image</em><br>    top_deltas = nd.max(conv_delta, axis=(1,2,3))<br>    <br>    <em># Return pics that have a convolution &gt; threshold</em><br>    anomalies = list(itr.compress(pics, top_deltas &gt; threshold))<br>    an_indexes = list(itr.compress(range(len(pics)), top_deltas &gt; threshold))<br>    <br>    if save_viz:  <em># optionally, save images for interpretability</em><br>        for a, i in zip(anomalies, an_indexes):<br>            mpim.imsave(&#39;pixmap-&#39;+a, deltas[i].asnumpy(), format=&#39;png&#39;)<br>            mpim.imsave(&#39;convmap-&#39;+a, conv_delta[i][0].asnumpy(), format=&#39;png&#39;)<br>                <br>    return list(anomalies)</pre><h3>Implementation with mxnet.numpy</h3><p>In the below code block, we implement the same logic as above, while replacing the mxnet.ndarray API by the mxnet.numpy API. Since mxnet.numpy implements the NumPy API, we need to replace couple methods by their NumPy counterpart, notably:</p><ul><li>Expanding array dimensions: array.expand_dims(0) above becomes np.expand_dims(array, 0)</li><li>Concatenation: nd.concat(*ims, dim=0) becomes np.concatenate(ims, axis=0)</li></ul><p>We further need to import mxnet.numpy and notify MXNet backend of our use of NumPy semantics, with the extra import and configuration below:</p><pre>from mxnet import np, npx<br>npx.set_np()</pre><p>And here is the final implementation of our baseline anomaly detection function using the MXNet NumPy API:</p><pre>def mxnp_find_anomalies(folder, gpu=False, save_viz=True, threshold=300):<br>    <em>&quot;&quot;&quot;<br>    Parameters<br>        ----------<br>        folder : str<br>            local directory with the batch of images to score<br>        gpu : bool, optional<br>            whether to use GPU or CPU<br>        save_viz : bool, optional<br>            saves the pictures of deltas and convolutions<br>        threshold : int, optional<br>            conv threshold to be anomalous. Should be tuned!<br>    &quot;&quot;&quot;</em><br>    <br>    ctx = mx.gpu() if gpu else mx.cpu()<em>  # Set context</em><br>    <br>    <em># read images in a 4D-NDArray</em><br>    pics = os.listdir(folder)<br>    ims = [np.expand_dims(mxim.imread(folder+&#39;/&#39;+pic), 0).as_in_context(ctx) for pic in pics]<br>    ims = np.concatenate(ims, axis=0)<br>        <br>    <em># Compute the average image</em><br>    avg = np.mean(ims.astype(&#39;float32&#39;), axis=0)<br>    <br>    <em># Remove the mean from every image, then average-pool on color</em><br>    deltas = np.mean(ims.astype(&#39;float32&#39;)-avg, axis=3)<br>    <br>    <em># Apply a batched convolution to all the deltas, then apply abs</em><br>    conv = gluon.nn.Conv2D(1, kernel_size=5, use_bias=False)<br>    conv.initialize(init.Constant(1))<br>    conv.collect_params().reset_ctx(ctx)<br>    <br><em>    # we expand dims for conv, which expect 3D pics (with channels)</em><br>    conv_delta = np.abs(conv(np.expand_dims(deltas, 1)))<br>    <br>    <em># Max-pool over the image</em><br>    top_deltas = np.max(conv_delta, axis=(1,2,3))<br>    <br>    <em># Return pics that have a convolution &gt; threshold</em><br>    anomalies = list(itr.compress(pics, top_deltas &gt; threshold))<br>    an_indexes = list(itr.compress(range(len(pics)), top_deltas &gt; threshold))<br>    <br>    if save_viz:  <em># optionally, save images for interpretability</em><br>        for a, i in zip(anomalies, an_indexes):<br>            mpim.imsave(&#39;pixmap-&#39;+a, deltas[i].asnumpy(), format=&#39;png&#39;)<br>            mpim.imsave(&#39;convmap-&#39;+a, conv_delta[i][0].asnumpy(), format=&#39;png&#39;)<br>                <br>    return list(anomalies)</pre><p>Here are 2 anomalies the function found over our synthetic data:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sqVO0tXGVdHCh3wxnfIcQA.png" /></figure><p>Both functions have comparable latencies, which makes sense since they use the same back-end kernels. Here are execution latencies excluding visualizations creation, over a batch of 12 1000px*750px images with MXNet 1.7, on an Amazon SageMaker GPU-equipped ml.p3.2xlarge Notebook (average of 10 runs)</p><ul><li>The mxnet.ndarray code runs in <strong>780ms</strong> and the mxnet.numpy code in <strong>750ms</strong> on the 8 vCPU (Intel Xeon E5)</li><li>The mxnet.ndarray code runs in <strong>100ms</strong> and the mxnet.numpy code in <strong>75ms</strong> on the GPU (NVIDIA Tesla V100 GPU), close to 90% faster vs CPU</li></ul><h3>Conclusion</h3><p>Even though MXNet is a deep learning framework, its primitives can be creatively used beyond neural network development. In this post, we showed how to perform simple, GPU-accelerated algebra over images using its NDArray library, that can be used by NumPy developers without extra learning curve via the mxnet.numpy library. Do not hesitate to take a look at it, contribute and engage with the community!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3cd63c94c62" width="1" height="1" alt=""><hr><p><a href="https://medium.com/apache-mxnet/using-mxnet-ndarray-for-fast-gpu-algebra-on-images-a3cd63c94c62">Using MXNet NDArray for fast GPU algebra on images</a> was originally published in <a href="https://medium.com/apache-mxnet">Apache MXNet</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Faster, Cheaper, Leaner: improving real-time ML inference using Apache MXNet]]></title>
            <link>https://medium.com/apache-mxnet/faster-cheaper-leaner-improving-real-time-ml-inference-using-apache-mxnet-2ee245668b55?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/2ee245668b55</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[mxnet]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Tue, 07 Jan 2020 23:55:50 GMT</pubDate>
            <atom:updated>2020-01-07T23:55:50.509Z</atom:updated>
            <content:encoded><![CDATA[<p>Machine learning models are data transformations learned over empirical examples. While a lot of research and tooling are now available regarding how to properly train models, deployment of models for inference — the phase during which the trained model artifact is exposed and put to use — is a lesser-known domain and is developing fast as more and more use-cases are shipped to production. Also, from a more holistic standpoint, it can account for up to 90% of the compute costs of a deployed machine learning application since it consists of the phase during which the model is actually used by end users or downstream systems. In that regard, it deserves specific attention.<br> <br>While reducing inference footprint and its cost is a paramount concern for model owners, inference speed is also a common priority in use-cases requiring real-time model answers over dynamic streams of data, such as fraud detection, advertising, transit time prediction or production line monitoring. In this post, we go over the possible optimization techniques that contribute to reducing model inference latency, hardware footprint and costs.</p><p>It is exciting to see the fast development of commercial services and open-source tooling supporting inference optimization. While the sheer volume of innovation in the space can sometimes feel overwhelming, a simple mental model to understand those optimizations techniques and tricks is to cluster them along three orthogonal dimensions: (1) hardware, (2) software and (3) scientific logic of the deployed algorithm. Eventually, we will also discuss architectural consideration that can contribute to further speed and cost optimizations such as caching, serverless deployment and model locality.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QZM3z7wC3Rva2M0frFLk6g.png" /><figcaption><strong><em>The three dimensions of inference optimization.</em></strong><em> Hardware and software optimizations usually do not disturb model accuracy. On the other hand, changes to the model algorithm usually alter its accuracy, hence the dashed line in the figure above</em></figcaption></figure><ol><li><strong>Hardware optimizations</strong></li></ol><p>A usually straightforward technique to reduce model latency is to use an appropriate hardware back-end. You can turn those bolts in two directions:<br> <br><em>Horizontal scaling</em> — There is usually a bidirectional causal relationship between server load and response latency, where latency gets degraded as server load increases, and server load increases if the model takes longer to run. In that regard, to keep latency predictably low, increasing the number of server nodes is a first technique that can be done manually but also programmatically. For example, the <a href="https://docs.aws.amazon.com/en_us/sagemaker/latest/dg/endpoint-auto-scaling.html">Autoscaling feature of Amazon SageMaker</a> endpoints enables you to configure scale-out and scale-in policies based on a metric of your choice, typically using the pre-defined Amazon Cloudwatch metric InvocationsPerInstance. Determining appropriate thresholds for the scaling policy can be done via load testing, a best practice that can be part of the model deployment cycle. Note that some forms of load testing qualify for the <a href="https://aws.amazon.com/ec2/testing/">Amazon EC2 Testing Policy.</a> An<a href="https://aws.amazon.com/blogs/machine-learning/load-test-and-optimize-an-amazon-sagemaker-endpoint-using-automatic-scaling/"> AWS ML Blog post</a> presents the details of SageMaker Autoscaling and end point load testing. <br> <br><em>Vertical scaling &amp; hardware specialization - </em>Another axis of hardware back-end improvement is to right-size the endpoint instances and choose appropriate compute host type and <em>AI accelerator</em>, if relevant to the model. The concept of AI accelerator usually refers to the class of microprocessors designed for AI applications. NVIDIA GPUs are a common option for AI acceleration, most notably the recent Volta and Turing architectures that feature the <em>Tensor Cores</em>, which are specialized cells for mixed-precision matrix multiply-accumulate are found in the AWS P3 and G4 families of Amazon EC2 instances respectively. Notably, other AI accelerators are being made available, such as <a href="https://aws.amazon.com/machine-learning/elastic-inference/">Amazon Elastic Inference</a> (EIA) and <a href="https://aws.amazon.com/machine-learning/inferentia/">AWS Inferentia</a>. Every model, ML framework and serving stack has its sweet spot when it comes to choosing the right mix of RAM, CPU, accelerator memory and accelerator compute capacity. Appropriate load testing will reveal which instance is more appropriate for your use-case. The use of AI accelerators, such as GPU or Amazon Elastic Inference, usually accelerates the inference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1009/1*BmZfPZNNqZJ6w9wb1qNHbg.png" /><figcaption>Model latency of identical Apache MXNet ResNet18 artifacts deployed in Amazon SageMaker in 3 different endpoints in region eu-west-1: in green a single-instance ml.c5.2xlarge endpoint ($0.538/h); in orange a single-instance ml.p3.2xlarge ($4.627/h) endpoint ; in blue a single-instance ml.t2.medium + ml.eia1.medium Elastic Inference Accelerator device ($0.07/h + $0.196/h = $0.266/h). All endpoints receive an identical traffic of identical pictures sent at a rate of 26 pictures/minutes. Average latency in the EIA-accelerated endpoint is ~55ms, about 28% faster than the full CPU endpoint, while 51% cheaper! A full GPU endpoint gives the best latency in that situation (~33ms), but is 17 times the price per hour (+1,639%) of the EIA-accelerated endpoint.</figcaption></figure><p>An i<a href="https://aws.amazon.com/blogs/machine-learning/serving-deep-learning-at-curalate-with-apache-mxnet-aws-lambda-and-amazon-elastic-inference/">nteresting comparison was recently made by a customer of AWS, Curalate, in the AWS ML Blog</a>, comparing inference of various vision models in AWS Lambda back-end (serverless, CPU) vs Amazon EC2 with Amazon Elastic Inference acceleration. Researchers from Curalate were successful in running Apache MXNet vision classifiers inference in Amazon Lambda, and found for various models thresholds of traffic below which it AWS Lambda deployment would be cheaper than when using always-on EC2. For example, they measured that in their setup, a ResNet 152 deployment would be cheaper than an EC2 + Amazon Elastic Inference-based deployment for traffics up to 7.5MM calls/month. They also mention that leveraging Amazon Elastic Inference led to cost savings on hosting between 35% and 65% compared to regular Amazon EC2 GPU instances.</p><p><strong>2. Software optimizations</strong></p><p>Another axis of improvement, separated from the hardware and algorithm considerations, is to work on the software stack, itself composed of two sub-parts — server and compilation.<br> <br>The <strong>model server</strong> is tasked with loading one or several models into memory and exposing them through a web service. Different servers will handle load at different levels of performance and provide various levels of convenience. A common trick to maximize hosting hardware utilization is to leverage batching. For example, in some situations instead of calling your server 100 times, with 100 individual requests, you can call your server 1 time with a batch of 100 requests. Some ML problems such as ad scoring or recommendation naturally lends themselves to batching, as you can for example score relevancy of all products in a catalog in one model call. Specialized deep learning servers such as TensorFlow Serving, TensorRT Inference Server or <a href="https://github.com/awslabs/multi-model-server">Multi Model Server</a> (MMS) are actually able to accumulate incoming requests into batches to improve GPU parallelism, a practice usually called as <em>micro-batching. </em>While micro-batching can improve inference economics, it usually comes at the expense of latency, since incoming requests will wait a varying amount of time while the micro-batch is assembled. This logic is handled by the server, and servers with micro-batch enabled usually come with controls to tune maximum acceptable latency and micro-batch size. While MMS is not specific to Apache MXNet, it was built with strong native integration with Apache MXNet and features a number of functionalities specific to ML model serving, such as logging, model pre-loading, micro-batching and multi-model serving.<br> <br>Another axis of software-based improvement is <strong>model compilation</strong>, a post-training step that consists of creating a model artifact and a model execution runtime finely adapted to the underlying supporting hardware. This concept leverages the fact that when you know where you’re going to deploy (e.g., NVIDIA, Intel, ARM), you have an insider edge and you can refine the model graph and inference runtime to better leverage available low-level specifics. This can reduce memory consumption and latency by double-digit percentage, and is an active area of ML research as illustrated by the papers mentioned in the rest of this paragraph. A popular, flexible and high-performance option for compilation is <em>Tensor Virtual Machine</em> (TVM, Chen et al, <a href="https://arxiv.org/abs/1802.04799">paper</a>, <a href="https://docs.tvm.ai/tutorials/">documentation</a>), which demonstrated compilation from a large number of frameworks (MXNet, Keras, Caffe2, CoreML, TensorFlow,…). Other notable compilers are for example treelite (<a href="https://www.sysml.cc/doc/2018/196.pdf">paper</a>, <a href="https://treelite.readthedocs.io/en/latest/">doc</a>) for decision tree compilation, and the <a href="https://github.com/pytorch/glow">Glow compilation engine</a> for PyTorch. Interestingly, both treelite and TVM are dependencies of the AWS compiler SageMaker Neo, which provides a managed experience of compilation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e6jSjlpcvgHZHbDC32HolQ.png" /><figcaption><em>CPU and GPU benchmarks of TVM. Source: “TVM: An Automated End-to-End Optimizing Compiler for Deep Learning”, Chen et al. </em><a href="https://arxiv.org/abs/1802.04799"><em>https://arxiv.org/abs/1802.04799 </em></a><em>Last revised 5 Oct 2018.</em></figcaption></figure><p>One benefit of using Amazon SageMaker Neo is that it is compatible with the most popular frameworks (MXNet, TensorFlow, XGBoost, PyTorch) and hardware back-ends (Intel, NVIDIA, ARM and others), and that it chooses on your behalf the underlying compiler tricks most appropriate for your combination of hardware target, framework and model.<br>Model quantization refers to reducing the number of bits representing model weights. It is usually available as part of a compilation stack or as a model framework component (examples of quantization functions in <a href="https://tvm.ai/2019/04/29/opt-cuda-quantized">TVM</a>, <a href="https://www.tensorflow.org/lite/performance/post_training_quantization">TensorFlow Lite</a>, <a href="https://medium.com/apache-mxnet/model-quantization-for-production-level-neural-network-inference-f54462ebba05">Apache MXNet</a> or <a href="https://docs.nvidia.com/deeplearning/frameworks/tf-trt-user-guide/index.html#custom-quantization">NVIDIA TensorRT</a>). Quantization aims to reduce model storage footprint and computation time, and is most commonly a post-training step. Another benefit of quantization is that it can leverage specific low-precision optimizations, such as the Vector Neural Network Instruction (VNNI) on modern Intel Xeon Scalable CPUs. Quantization is most commonly ran post-training, with an optional calibration step during which the model sees sample data to properly quantify the ranges to be quantized. Quantization may also be run in-training, a concept named <em>quantization-aware training </em>that involves directly training a quantized representation. At the time of this writing, this seems to be still at active research stage and less a popular option than post-training quantization. Notably, in summer 2019 the TensorFlow group encouraged <a href="https://medium.com/tensorflow/tensorflow-model-optimization-toolkit-post-training-integer-quantization-b4964a1ea9ba">in a blog post</a> the use of post-training quantization over quantization-aware training.<br> <br> <strong>3. Algorithm optimization</strong><br>On a given hardware and software platform, model inference latency and hardware footprint will significantly vary with the algorithmic logic of the model. If speed, throughput or costs are your priorities over accuracy, and if you already exploited all possible tricks along hardware and software axis above, consider using models adapted to those goals. For example:</p><ul><li>Models with very little computation, such as logistic regression for classification over structured data</li><li>In the computer vision classification space, the popular MobileNet architecture (Howard et al. <a href="https://arxiv.org/abs/1704.04861">https://arxiv.org/abs/1704.04861)</a> enables significant computation and memory footprint savings via the use of depth-wise separated convolutions.</li><li>In the object detection space, YoloV3 (<a href="https://pjreddie.com/media/files/papers/YOLOv3.pdf">Redmon et Farhadi</a>) and Single-Shot Multibox Detector (SSD, <a href="https://arxiv.org/abs/1512.02325">Liu et al</a>) are known to be usually faster and less memory-intensive than FasterRCNN (<a href="https://papers.nips.cc/paper/5638-faster-r-cnn-towards-real-time-object-detection-with-region-proposal-networks.pdf">Ren et al</a>), yet also generally less accurate.</li><li>For k-nearest neighbor (k-NN) search problems, a number of approximate k-NN algorithm have been developed and enable fast approximate search: <a href="https://github.com/facebookresearch/faiss">FAISS</a> (related paper: <a href="https://arxiv.org/abs/1702.08734"><em>Billion-Scale Similarity Search with GPU</em>, Johnson et al</a>), <a href="https://github.com/nmslib/hnswlib">HNSW</a> (related paper: <a href="https://arxiv.org/abs/1603.09320"><em>Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs</em>, Malkov et Yashunin</a>), or <a href="https://github.com/spotify/annoy">Annoy</a></li></ul><p>Be aware that unlike hardware and software optimization axis, changing model algorithm can significantly alter accuracy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gRmVdG0sL5Fl1Hyk6PU19w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8gNX2rSN_sSoJqotEfNtSA.png" /><figcaption><em>GluonCV </em><a href="https://gluon-cv.mxnet.io/model_zoo/classification.html"><em>Classification Zoo</em></a><em> (top) and </em><a href="https://gluon-cv.mxnet.io/model_zoo/detection.html"><em>Detection Zoo</em></a><em> (bottom) feature a great display of the accuracy vs throughput trade-off. GluonCV (</em><a href="https://gluon-cv.mxnet.io/">https://gluon-cv.mxnet.io/</a>) <em>is a compact python toolkit providing access to state-of-the-art vision models and tools for a number of computer vision paradigm. (</em><a href="https://arxiv.org/pdf/1907.04433.pdf"><em>Guo et al</em></a><em>)</em></figcaption></figure><p><strong>4. System Architecture Considerations</strong><br>Beyond improving the model inference functionality, other architectural concepts can contribute to reducing latency and costs of an end-to-end real-time machine learning prediction pipeline:</p><ul><li><strong>Caching</strong>: if you expect a high level of prediction requests with the same input, it is worth considering creating a cache layer in front of the model. A cache can store model input-prediction pairs during an appropriate time-to-live, so that a prediction request over an input that was seen already can be fetched from the cache at a lower latency and cost than re-running the model prediction. Typical relevant options for this layer are fast key-value stores such as Amazon DynamoDB and Redis.</li><li><strong>Locality:</strong> localizing the model as close as possible to the consumption point of the prediction may contribute to significant latency improvement. For example, running one model.predict() prediction call on a vector of 127 records with the Scikit-Learn random forest regressor of the public <a href="https://github.com/awslabs/amazon-sagemaker-examples/blob/master/sagemaker-python-sdk/scikit_learn_randomforest/Sklearn_on_SageMaker_end2end.ipynb">SageMaker Scikit-Learn integration demo notebook</a> takes about 9ms of local execution time on a single-machine ml.c5.large Amazon SageMaker endpoint, but takes 25ms when model serving, serialization/de-serialization and network communication time and are taken into account (from the endpoint to a client instance making the call from within the same region and the same account - both numbers are averages over 10 invocations spaced by multiple seconds). Footprint optimizations mentioned in 1, 2 and 3 above are especially relevant when models are deployed on edge hardware with limited power supply and computing capacity.</li><li><strong>Serverless deployment:</strong> For models facing an unpredictable traffic and not requiring GPU a serverless compute engine - for example <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> - can be a relevant inferencing platform. The pay-per-inference format makes it economical for reasonable amounts of traffic. It also comes with the benefit of simplifying the deployment stack by removing the model server layer. Numerous blog posts show its successful use for inference of deep MXNet models:</li><li><a href="https://aws.amazon.com/blogs/machine-learning/serving-deep-learning-at-curalate-with-apache-mxnet-aws-lambda-and-amazon-elastic-inference/"><em>Serving deep learning at Curalate with Apache MXNet, AWS Lambda, and Amazon Elastic Inference</em></a></li><li><a href="https://aws.amazon.com/blogs/machine-learning/build-test-and-deploy-your-amazon-sagemaker-inference-models-to-aws-lambda/"><em>Build, test, and deploy your Amazon Sagemaker inference models to AWS Lambda</em></a></li></ul><p>In summary, there are multiple axis to explore to reduce the latency, cost and hardware footprint of your real-time model deployments, and many of those ideas are orthogonal and can be combined. Apache MXNet provides a growing list of tools supporting all flavors of software-based inference improvement, from model architectures to web serving, including compilation and quantization. Please do not hesitate to contribute to those projects and reach out to the community on the forum <a href="https://discuss.mxnet.io/">discuss.mxnet.io/</a>!</p><p><em>Many thanks to Jonathan Taws, Chris Fregly, Will Badr, Tanya Roostaeyan, Madhuri Peri, Frank Liu, Jonathan Chung and Nikolay Ulmasov who provided helpful reviews on the topic</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2ee245668b55" width="1" height="1" alt=""><hr><p><a href="https://medium.com/apache-mxnet/faster-cheaper-leaner-improving-real-time-ml-inference-using-apache-mxnet-2ee245668b55">Faster, Cheaper, Leaner: improving real-time ML inference using Apache MXNet</a> was originally published in <a href="https://medium.com/apache-mxnet">Apache MXNet</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/simplest word count]]></title>
            <link>https://medium.com/@oliviercruchant/python-simplest-word-count-76237c3bed97?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/76237c3bed97</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:40:41 GMT</pubDate>
            <atom:updated>2018-05-08T20:58:55.330Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>text </strong>is a string.</p><pre><strong>import</strong> collections <strong>as</strong> col</pre><pre><em># text stores a piece of text</em><br>words = col.Counter(text.split(sep=’ ‘))</pre><p>Store result in a dictionary</p><pre>result = dict(col.Counter(text.split(sep=’ ‘))</pre><p>Store in a pandas data frame</p><pre>result = pd.DataFrame(col.Counter(text.split(sep=’ ‘)),<br>                      columns=[&#39;word&#39;,&#39;count&#39;])</pre><p>The <strong>most_common</strong> method allows to restrict the counter to top N occurrences</p><pre>topten = col.Counter(text.split(sep=’ ‘)).most_common(10)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=76237c3bed97" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/numpy analytic magic]]></title>
            <link>https://medium.com/@oliviercruchant/python-numpy-analytic-magic-33b34f6e7173?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/33b34f6e7173</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:37:18 GMT</pubDate>
            <atom:updated>2018-05-08T21:00:43.267Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>data</strong> is a pandas dataframe, <strong>measure</strong> a column name</p><p><em>percentiles (</em><strong><em>p</em></strong><em> in [0;100])</em></p><pre>np.nanpercentile(data[measure], p)</pre><p><em>log10</em></p><pre>np.log10(data[measure])</pre><p>power of 10</p><pre>np.power(10, data[measure])</pre><p><em>Finding columns with infinite values (-</em><strong><em>inf</em></strong><em> or </em><strong><em>inf</em></strong><em>)</em></p><pre><strong>def</strong> list_inf(df):<br>    <strong>return</strong> [f <strong>for</strong> f <strong>in</strong> df.columns <strong>if</strong> np.isinf(df[f]).any()]</pre><p><em>Creating a binary from threshold </em><strong><em>T</em></strong><em> of metric </em><strong><em>m</em></strong></p><pre>np.sign(data[m] + T)</pre><p><em>Weighted average</em></p><pre>np.average(df[measure], weights=df[weights])</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33b34f6e7173" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/compound growth]]></title>
            <link>https://medium.com/@oliviercruchant/python-compound-growth-5e0123956523?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e0123956523</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:36:21 GMT</pubDate>
            <atom:updated>2018-05-08T21:01:07.911Z</atom:updated>
            <content:encoded><![CDATA[<pre><strong>import</strong> scipy.stats <strong>as</strong> spst<br><strong>import</strong> numpy <strong>as</strong> np</pre><pre><strong>def</strong> compound_growth(L):<br>    <strong>return</strong> spst.gmean(1 + np.array(L)) — 1</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e0123956523" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/Pandas listagg UDF]]></title>
            <link>https://medium.com/@oliviercruchant/python-pandas-listagg-udf-e1429a972995?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1429a972995</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:36:04 GMT</pubDate>
            <atom:updated>2020-09-04T14:44:57.125Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>df</strong> is a pandas dataframe, <strong>dim</strong> a dimension to group by, <strong>textcolumn</strong> the text field to concatenate</p><pre><strong>import</strong> operator <strong>as</strong> op<br><strong>import</strong> pandas <strong>as</strong> pd<br><strong>import</strong> functools <strong>as</strong> fct  <em># reduce is in functools in python 3+</em></pre><pre><strong>def</strong> listagg(L):<br>    <strong>return</strong> fct.reduce(op.concat, L)</pre><pre>df.groupby(dim)[textcolumn].apply(listagg)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1429a972995" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/networkx graph magic]]></title>
            <link>https://medium.com/@oliviercruchant/python-networkx-graph-magic-260309cce484?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/260309cce484</guid>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:35:38 GMT</pubDate>
            <atom:updated>2018-05-08T21:03:34.337Z</atom:updated>
            <content:encoded><![CDATA[<p>Basic graph representation function on top of <a href="https://networkx.github.io/documentation/networkx-1.10/tutorial/tutorial.html/">networkx</a> graph library.</p><pre><strong>import</strong> networkx <strong>as</strong> nx</pre><pre><strong>def</strong> plot_graph(nodes, edges, labels=False, node_size=False,<br>               node_color=&#39;r&#39;, arrows=False, alpha=0.1):</pre><pre><em>    &quot;&quot;&quot;nodes: list of nodes. Used as labels if labels=True<br>       edges: list of edges. Edges are tuples.<br>       node_size: size or list of sizes<br>       node_color: color or list of colors<br>    &quot;&quot;&quot;</em></pre><pre>    G = nx.DiGraph()<br>    G.add_nodes_from(nodes)<br>    G.add_edges_from(edges)<br>    nx.draw(G, with_labels=labels, node_color=node_color,<br>            node_size=node_size, arrows=arrows, alpha=alpha)</pre><p>Example with a simple fictional dataset:</p><pre><strong>import</strong> pandas <strong>as</strong> pd</pre><pre><em># Node data</em><br>Stations = pd.DataFrame([[&#39;Station1&#39;, 2500],[&#39;Station2&#39;,1210],<br>                         [&#39;Station3&#39;, 90],[&#39;Station4&#39;, 312],<br>                         [&#39;Station5&#39;, 11],[&#39;Station6&#39;, 190]],<br>                        columns=[&#39;Station&#39;, &#39;Traffic&#39;])</pre><pre><em># Edge data<br></em>routes = pd.DataFrame([[&#39;Station1&#39;, &#39;Station2&#39;],<br>                       [&#39;Station1&#39;, &#39;Station4&#39;],<br>                       [&#39;Station1&#39;, &#39;Station5&#39;],<br>                       [&#39;Station2&#39;, &#39;Station3&#39;],<br>                       [&#39;Station2&#39;, &#39;Station5&#39;],<br>                       [&#39;Station2&#39;, &#39;Station6&#39;],<br>                       [&#39;Station3&#39;, &#39;Station5&#39;],<br>                       [&#39;Station4&#39;, &#39;Station1&#39;],<br>                       [&#39;Station4&#39;, &#39;Station2&#39;],<br>                       [&#39;Station4&#39;, &#39;Station6&#39;],<br>                       [&#39;Station5&#39;, &#39;Station6&#39;]],<br>                      columns=[&#39;start&#39;, &#39;stop&#39;])</pre><pre>plot_graph(nodes=Stations[&#39;Station&#39;],<br>           edges=[tuple(row) <strong>for</strong> row <strong>in</strong> routes.values],<br>           labels=<strong>True</strong>,<br>           node_color=&#39;orange&#39;,<br>           node_size=Stations[&#39;Traffic&#39;],<br>           alpha=0.8)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*Ce28_FprEDKn84WjdSfWvA.png" /></figure><p>Example with an anonymized Facebook dataset (<a href="http://snap.stanford.edu/data/egonets-Facebook.html">Stanford Large Network Dataset Collection</a>); 4,039 nodes 88,233 edges:</p><pre>data = pd.read_csv(&#39;facebook_combined.txt&#39;, sep=&#39;\t&#39;)</pre><pre><em># At this stage edges are strings. We transform them to tuples:<br></em>edges = [tuple(x[0].split(&#39; &#39;)) <strong>for</strong> x <strong>in</strong> data.values]</pre><pre><em># We use a set over a nested list comprehension to make a deduplicated list of nodes<br></em>nodes = set([item <strong>for</strong> edge <strong>in</strong> edges <strong>for</strong> item <strong>in</strong> edge])</pre><pre>plot_graph(nodes, edges, alpha=0.05)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-VFij8wo7XLxPtIO2HC_4Q.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=260309cce484" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/named tuple magic]]></title>
            <link>https://medium.com/@oliviercruchant/python-named-tuple-magic-8aad0bbca580?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/8aad0bbca580</guid>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:35:14 GMT</pubDate>
            <atom:updated>2018-05-08T21:05:55.737Z</atom:updated>
            <content:encoded><![CDATA[<pre><strong>import</strong> collections</pre><pre>company = collections.namedtuple(&#39;company&#39;, &#39;ceo, ticker, price&#39;)</pre><p>Create instances from lists</p><pre><em># load some data from a list, e.g.:</em><br>data = [‘Jeff Bezos’, ‘AMZN’, 997]</pre><pre><em># _make method</em><br>Amazon = company._make(data)</pre><pre><em># or list unpacking; same result</em><br>Amazon = company(*data)</pre><p>Get data back</p><pre>Amazon.ticker</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8aad0bbca580" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/Four linear fits]]></title>
            <link>https://medium.com/@oliviercruchant/python-four-linear-fits-809a29fb4afc?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/809a29fb4afc</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[machine-learning]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:34:42 GMT</pubDate>
            <atom:updated>2018-05-08T21:05:34.636Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>X</strong> refers to training dataframe, <strong>Y</strong> to the target vector. <strong>Xpred</strong> is an out-of-sample prediction input set</p><p><a href="http://www.statsmodels.org/dev/regression.html">Statsmodels</a>:</p><pre><strong>import</strong> statsmodels.api <strong>as</strong> sm</pre><pre># <em>fit</em><br>X = sm.add_constant(X)<br>model = sm.OLS(Y,X).fit()</pre><pre><em># get an exhaustive model summary</em><br>model.summary()</pre><pre><em># coefficients</em><br>model.params</pre><pre><em># rsquared</em><br>model.rsquared</pre><pre><em># predict out-of-sample</em><br>model.predict(Xpred)</pre><p><a href="http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html">Sklearn</a>:</p><pre><strong>import</strong> sklearn</pre><pre><em># fit</em><br>model = sklearn.linear_model.LinearRegression()<br>model.fit(X, Y)</pre><pre><em># coefficients</em><br>model.coef_</pre><pre><em># intercept</em><br>model.intercept_</pre><pre><em># sklearn does not have rsquare as a model attribute</em></pre><pre><em># predict out-of-sample</em><br>pred = model.predict(Xpred)</pre><p><a href="https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.linalg.lstsq.html">Numpy</a>:</p><pre><strong>import</strong> numpy <strong>as</strong> np</pre><pre>np.linalg.lstsq(x, y)</pre><p><a href="https://docs.scipy.org/doc/scipy-0.19.1/reference/generated/scipy.stats.linregress.html">Scipy</a>:</p><pre><strong>import</strong> scipy <strong>as</strong> sp</pre><pre><em># returns parameters in one method</em><br>slope, intercept, r_value, p_value, std_err = \<br>    sp.stats.linregress(x,y)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=809a29fb4afc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Python/exotic pandas filters]]></title>
            <link>https://medium.com/@oliviercruchant/python-exotic-pandas-filters-c094a3318633?source=rss-a2d4c58edd86------2</link>
            <guid isPermaLink="false">https://medium.com/p/c094a3318633</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Olivier Cruchant]]></dc:creator>
            <pubDate>Fri, 04 May 2018 17:34:20 GMT</pubDate>
            <atom:updated>2018-05-08T21:06:19.383Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>df </strong>is a pandas dataframe and <strong>col </strong>the filtered column</p><p>Values in list:</p><pre>df[df.col.isin(L)]</pre><p>String like:</p><pre>df[df.col.str.contains(&#39;text&#39;)]</pre><p>Not null:</p><pre>df[~df.col.isnull()]  <em># using filter negation ~</em></pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c094a3318633" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>