{"id":1020,"date":"2019-10-28T12:46:04","date_gmt":"2019-10-28T11:46:04","guid":{"rendered":"http:\/\/35.180.88.53\/?p=1020"},"modified":"2019-10-28T12:46:09","modified_gmt":"2019-10-28T11:46:09","slug":"turbocharging-python-with-command-line-tools","status":"publish","type":"post","link":"https:\/\/www.sergilehkyi.com\/es\/2019\/10\/turbocharging-python-with-command-line-tools\/","title":{"rendered":"Turbocharging Python with Command Line Tools"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>It\u2019s as good a time to be writing code as ever \u2013 these days, a little bit of code goes a long way. Just a single function is capable of performing incredible things. Thanks to GPUs, Machine Learning, the Cloud, and Python, it\u2019s is easy to create \u201cturbocharged\u201d command-line tools. Think of it as upgrading your code from using a basic internal combustion engine to a nuclear reactor. The basic recipe for the upgrade? One function, a sprinkle of powerful logic, and, finally, a decorator to route it to the command-line.<\/p>\n\n\n\n<p>Writing and maintaining traditional GUI applications \u2013 web or desktop \u2013 is a Sisyphean task at best. It all starts with the best of intentions, but can quickly turn into a soul crushing, time-consuming ordeal where you end up asking yourself why you thought becoming a programmer was a good idea in the first place. Why did you run that web framework setup utility that essentially automated a 1970\u2019s technology \u2013 the relational database \u2013 into series of python files? The old Ford Pinto with the exploding rear gas tank has newer technology than your web framework. There has got to be a better way to make a living.<\/p>\n\n\n\n<p>The answer is simple: stop writing web applications and start writing nuclear powered command-line tools instead. The turbocharged command-line tools that I share below are focused on fast results vis a vis minimal lines of code. They can do things like learn from data (machine learning), make your code run 2,000 times faster, and best of all, generate colored terminal output.<\/p>\n\n\n\n<p>Here are the raw ingredients that will be used to make several solutions:<\/p>\n\n\n\n<ul><li><a href=\"https:\/\/click.palletsprojects.com\/en\/7.x\/\" target=\"_blank\" rel=\"noreferrer noopener\">Click Framework<\/a><\/li><li><a href=\"https:\/\/developer.nvidia.com\/how-to-cuda-python\" target=\"_blank\" rel=\"noreferrer noopener\">Python CUDA Framework<\/a><\/li><li><a href=\"http:\/\/numba.pydata.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Numba Framework<\/a><\/li><li><a href=\"http:\/\/scikit-learn.org\/dev\/tutorial\/machine_learning_map\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">Scikit-learn Machine Learning Framework<\/a><\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><\/strong>Using The Numba JIT (Just in time Compiler)<\/h2>\n\n\n\n<p>Python has a reputation for slow performance because it\u2019s fundamentally a scripting language. One way to get around this problem is to use the Numba JIT. Here\u2019s what that code looks like:<\/p>\n\n\n\n<p>First, use a timing decorator to get a grasp on the runtime of your functions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def timing(f):\n    @wraps(f)\n    def wrap(*args, **kwargs):\n        ts = time()\n        result = f(*args, **kwargs)\n        te = time()\n        print(f'fun: {f.__name__}, args: [{args}, {kwargs}] took: {te-ts} sec')\n        return result\n    return wrap<\/code><\/pre>\n\n\n\n<p>Next, add a numba.jit decorator with the \u201cnopython\u201d keyword argument, and set to true. This will ensure that the code will be run by the JIT instead of regular python.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@timing\n@numba.jit(nopython=True)\ndef expmean_jit(rea):\n    \"\"\"Perform multiple mean calculations\"\"\"\n\n    val = rea.mean() ** 2\n    return val<\/code><\/pre>\n\n\n\n<p>When you run it, you can see both a \u201cjit\u201d as well as a regular version being run via the command-line tool:<\/p>\n\n\n\n<p><code>$ python nuclearcli.py jit-test<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Running NO JIT\nfunc:'expmean' args:[(array([[1.0000e+00, 4.2080e+05, 4.2350e+05, ..., 1.0543e+06, 1.0485e+06,\n        1.0444e+06],\n       [2.0000e+00, 5.4240e+05, 5.4670e+05, ..., 1.5158e+06, 1.5199e+06,\n        1.5253e+06],\n       [3.0000e+00, 7.0900e+04, 7.1200e+04, ..., 1.1380e+05, 1.1350e+05,\n        1.1330e+05],\n       ...,\n       [1.5277e+04, 9.8900e+04, 9.8100e+04, ..., 2.1980e+05, 2.2000e+05,\n        2.2040e+05],\n       [1.5280e+04, 8.6700e+04, 8.7500e+04, ..., 1.9070e+05, 1.9230e+05,\n        1.9360e+05],\n       [1.5281e+04, 2.5350e+05, 2.5400e+05, ..., 7.8360e+05, 7.7950e+05,\n        7.7420e+05]], dtype=float32),), {}] took: 0.0007 sec<\/code><\/pre>\n\n\n\n<p>$ python nuclearcli.py jit-test \u2013jit<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Running with JIT\nfunc:'expmean_jit' args:[(array([[1.0000e+00, 4.2080e+05, 4.2350e+05, ..., 1.0543e+06, 1.0485e+06,\n        1.0444e+06],\n       [2.0000e+00, 5.4240e+05, 5.4670e+05, ..., 1.5158e+06, 1.5199e+06,\n        1.5253e+06],\n       [3.0000e+00, 7.0900e+04, 7.1200e+04, ..., 1.1380e+05, 1.1350e+05,\n        1.1330e+05],\n       ...,\n       [1.5277e+04, 9.8900e+04, 9.8100e+04, ..., 2.1980e+05, 2.2000e+05,\n        2.2040e+05],\n       [1.5280e+04, 8.6700e+04, 8.7500e+04, ..., 1.9070e+05, 1.9230e+05,\n        1.9360e+05],\n       [1.5281e+04, 2.5350e+05, 2.5400e+05, ..., 7.8360e+05, 7.7950e+05,\n@click.option('--jit\/--no-jit', default=False)\n        7.7420e+05]], dtype=float32),), {}] took: 0.2180 sec<\/code><\/pre>\n\n\n\n<p>How does that work? Just a few lines of code allow for this simple toggle:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@cli.command()\ndef jit_test(jit):\n    rea = real_estate_array()\n    if jit:\n        click.echo(click.style('Running with JIT', fg='green'))\n        expmean_jit(rea)\n    else:\n        click.echo(click.style('Running NO JIT', fg='red'))\n        expmean(rea)<\/code><\/pre>\n\n\n\n<p>In some cases a JIT version could make code run thousands of times faster, but benchmarking is key. Another item to point out is the line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>click.echo(click.style('Running with JIT', fg='green'))<\/code><\/pre>\n\n\n\n<p>This script allows for colored terminal output, which can be very helpful it creating sophisticated tools.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><\/strong>Using the GPU with CUDA Python<\/h2>\n\n\n\n<p>Another way to nuclear power your code is to run it straight on a GPU. This example requires you run it on a machine with a CUDA enabled. Here\u2019s what that code looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@cli.command()\ndef cuda_operation():\n    \"\"\"Performs Vectorized Operations on GPU\"\"\"\n\n    x = real_estate_array()\n    y = real_estate_array()\n\n    print('Moving calculations to GPU memory')\n    x_device = cuda.to_device(x)\n    y_device = cuda.to_device(y)\n    out_device = cuda.device_array(\n        shape=(x_device.shape[0],x_device.shape[1]), dtype=np.float32)\n    print(x_device)\n    print(x_device.shape)\n    print(x_device.dtype)\n\n    print('Calculating on GPU')\n    add_ufunc(x_device,y_device, out=out_device)\n\n    out_host = out_device.copy_to_host()\n    print(f'Calculations from GPU {out_host}')<\/code><\/pre>\n\n\n\n<p>It\u2019s useful to point out is that if the numpy array is first moved to the GPU, then a vectorized function does the work on the GPU. After that work is completed, then the data is moved from the GPU. By using a GPU there could be a monumental improvement to the code, depending on what it\u2019s running. The output from the command-line tool is shown below:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ python nuclearcli.py cuda-operation\nMoving calculations to GPU memory\n\n(10015, 259)\nfloat32\nCalculating on GPU\nCalculcations from GPU [[2.0000e+00 8.4160e+05 8.4700e+05 ... 2.1086e+06 2.0970e+06 2.0888e+06]\n [4.0000e+00 1.0848e+06 1.0934e+06 ... 3.0316e+06 3.0398e+06 3.0506e+06]\n [6.0000e+00 1.4180e+05 1.4240e+05 ... 2.2760e+05 2.2700e+05 2.2660e+05]\n ...\n [3.0554e+04 1.9780e+05 1.9620e+05 ... 4.3960e+05 4.4000e+05 4.4080e+05]\n [3.0560e+04 1.7340e+05 1.7500e+05 ... 3.8140e+05 3.8460e+05 3.8720e+05]\n [3.0562e+04 5.0700e+05 5.0800e+05 ... 1.5672e+06 1.5590e+06 1.5484e+06]]<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><\/strong>Running True Multi-Core Multithreaded Python using Numba<\/h2>\n\n\n\n<p>One common performance problem with Python is the lack of true, multi-threaded performance. This also can be fixed with Numba. Here\u2019s an example of some basic operations:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@timing\n@numba.jit(parallel=True)\ndef add_sum_threaded(rea):\n    \"\"\"Use all the cores\"\"\"\n\n    x,_ = rea.shape\n    total = 0\n    for _ in numba.prange(x):\n        total += rea.sum()\n        print(total)\n\n@timing\ndef add_sum(rea):\n    \"\"\"traditional for loop\"\"\"\n\n    x,_ = rea.shape\n    total = 0\n    for _ in numba.prange(x):\n        total += rea.sum()\n        print(total)\n\n@cli.command()\n@click.option('--threads\/--no-jit', default=False)\ndef thread_test(threads):\n    rea = real_estate_array()\n    if threads:\n        click.echo(click.style('Running with multicore threads', fg='green'))\n        add_sum_threaded(rea)\n    else:\n        click.echo(click.style('Running NO THREADS', fg='red'))\n        add_sum(rea)<\/code><\/pre>\n\n\n\n<p>Note that the key difference between the parallel version is that it uses&nbsp;<code>@numba.jit(parallel=True)<\/code>&nbsp;and numba.prange to spawn threads for iteration. Looking at the picture below, all of the CPUs are maxed out on the machine, but when almost the exact same code is run without the parallelization, it only uses a core.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/kite.com\/wp-content\/uploads\/2019\/03\/graph.c2ee5067-1024x507.png\" alt=\"\" class=\"wp-image-246\"\/><\/figure><\/div>\n\n\n\n<p><code>$ python nuclearcli.py thread-test<\/code><\/p>\n\n\n\n<p><code>$ python nuclearcli.py thread-test --threads<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><\/strong>KMeans Clustering<\/h2>\n\n\n\n<p>One more powerful thing that can be accomplished in a command-line tool is machine learning. In the example below, a KMeans clustering function is created with just a few lines of code. This clusters a pandas DataFrame into a default of 3 clusters.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def kmeans_cluster_housing(clusters=3):\n    \"\"\"Kmeans cluster a dataframe\"\"\"\n    url = 'https:\/\/raw.githubusercontent.com\/noahgift\/socialpowernba\/master\/data\/nba_2017_att_val_elo_win_housing.csv'\n    val_housing_win_df =pd.read_csv(url)\n    numerical_df =(\n        val_housing_win_df.loc[:,['TOTAL_ATTENDANCE_MILLIONS', 'ELO',\n        'VALUE_MILLIONS', 'MEDIAN_HOME_PRICE_COUNTY_MILLIONS']]\n    )\n    #scale data\n    scaler = MinMaxScaler()\n    scaler.fit(numerical_df)\n    scaler.transform(numerical_df)\n    #cluster data\n    k_means = KMeans(n_clusters=clusters)\n    kmeans = k_means.fit(scaler.transform(numerical_df))\n    val_housing_win_df['cluster'] = kmeans.labels_\n    return val_housing_win_df<\/code><\/pre>\n\n\n\n<p>The cluster number can be changed by passing in another number (as shown below) using click:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@cli.command()\n@click.option('--num', default=3, help='number of clusters')\ndef cluster(num):\n    df = kmeans_cluster_housing(clusters=num)\n    click.echo('Clustered DataFrame')\n    click.echo(df.head())<\/code><\/pre>\n\n\n\n<p>Finally, the output of the Pandas DataFrame with the cluster assignment is show below. Note, it has cluster assignment as a column now.<\/p>\n\n\n\n<p><code>$ python -W nuclearcli.py cluster<\/code><\/p>\n\n\n\n<table class=\"wp-block-table\"><tbody><tr><td><strong>Clustered DataFrame<\/strong><\/td><td>0<\/td><td>1<\/td><td>2<\/td><td>3<\/td><td>4<\/td><\/tr><tr><td><strong>TEAM<\/strong><\/td><td>Chicago Bulls<\/td><td>Dallas Mavericks<\/td><td>Sacramento Kings<\/td><td>Miami Heat<\/td><td>Toronto Raptors<\/td><\/tr><tr><td><strong>GMS<\/strong><\/td><td>41<\/td><td>41<\/td><td>41<\/td><td>41<\/td><td>41<\/td><\/tr><tr><td><strong>PCT_ATTENDANCE<\/strong><\/td><td>104<\/td><td>103<\/td><td>101<\/td><td>100<\/td><td>100<\/td><\/tr><tr><td><strong>WINNING_SEASON<\/strong><\/td><td>1<\/td><td>0<\/td><td>0<\/td><td>1<\/td><td>1<\/td><\/tr><tr><td><strong>\u2026<\/strong><\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><\/tr><tr><td><strong>COUNTY<\/strong><\/td><td>Cook<\/td><td>Dallas<\/td><td>Sacremento<\/td><td>Miami-Dade<\/td><td>York-County<\/td><\/tr><tr><td><strong>MEDIAN_HOME_PRICE_COUNTY_MILLIONS<\/strong><\/td><td>269900.0<\/td><td>314990.0<\/td><td>343950.0<\/td><td>389000.0<\/td><td>390000.0<\/td><\/tr><tr><td><strong>COUNTY_POPULATION_MILLIONS<\/strong><\/td><td>5.20<\/td><td>2.57<\/td><td>1.51<\/td><td>2.71<\/td><td>1.10<\/td><\/tr><tr><td><strong>cluster<\/strong><\/td><td>0<\/td><td>0<\/td><td>1<\/td><td>0<\/td><td>0<\/td><\/tr><\/tbody><\/table>\n\n\n\n<p><code>$ python -W nuclearcli.py cluster --num 2<\/code><\/p>\n\n\n\n<table class=\"wp-block-table\"><tbody><tr><td><strong>Clustered DataFrame<\/strong><\/td><td>0<\/td><td>1<\/td><td>2<\/td><td>3<\/td><td>4<\/td><\/tr><tr><td><strong>TEAM<\/strong><\/td><td>Chicago Bulls<\/td><td>Dallas Mavericks<\/td><td>Sacramento Kings<\/td><td>Miami Heat<\/td><td>Toronto Raptors<\/td><\/tr><tr><td><strong>GMS<\/strong><\/td><td>41<\/td><td>41<\/td><td>41<\/td><td>41<\/td><td>41<\/td><\/tr><tr><td><strong>PCT_ATTENDANCE<\/strong><\/td><td>104<\/td><td>103<\/td><td>101<\/td><td>100<\/td><td>100<\/td><\/tr><tr><td><strong>WINNING_SEASON<\/strong><\/td><td>1<\/td><td>0<\/td><td>0<\/td><td>1<\/td><td>1<\/td><\/tr><tr><td><strong>\u2026<\/strong><\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><td>\u2026<\/td><\/tr><tr><td><strong>COUNTY<\/strong><\/td><td>Cook<\/td><td>Dallas<\/td><td>Sacremento<\/td><td>Miami-Dade<\/td><td>York-County<\/td><\/tr><tr><td><strong>MEDIAN_HOME_PRICE_COUNTY_MILLIONS<\/strong><\/td><td>269900.0<\/td><td>314990.0<\/td><td>343950.0<\/td><td>389000.0<\/td><td>390000.0<\/td><\/tr><tr><td><strong>COUNTY_POPULATION_MILLIONS<\/strong><\/td><td>5.20<\/td><td>2.57<\/td><td>1.51<\/td><td>2.71<\/td><td>1.10<\/td><\/tr><tr><td><strong>cluster<\/strong><\/td><td>1<\/td><td>1<\/td><td>0<\/td><td>1<\/td><td>1<\/td><\/tr><\/tbody><\/table>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><\/strong>Summary<\/h2>\n\n\n\n<p>The goal of this article is to show how simple command-line tools can be a great alternative to heavy web frameworks. In under 200 lines of code, you\u2019re now able to create a command-line tool that involves GPU parallelization, JIT, core saturation, as well as Machine Learning. The examples I shared above are just the beginning of upgrading your developer productivity to nuclear power, and I hope you\u2019ll use these programming tools to help build the future.<\/p>\n\n\n\n<p>Many of the most powerful things happening in the software industry are based on functions: distributed computing, machine learning, cloud computing (functions as a service), and GPU based programming are all great examples. The natural way of controlling these functions is a decorator-based command-line tool \u2013 not clunky 20th Century clunky web frameworks. The Ford Pinto is now parked in a garage, and you\u2019re driving a shiny new \u201cturbocharged\u201d command-line interface that maps powerful yet simple functions to logic using the Click framework.<\/p>\n\n\n\n<p>You can check out the code from this and other posts on this\u00a0<a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/kiteco\/kite-python-blog-post-code\" target=\"_blank\">GitHub repository<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p><em>This article was originally published at\u00a0<a href=\"https:\/\/kite.com\/blog\/python\/python-command-line-tools\/\">Kite\u2018s<\/a>\u00a0blog and republished here as part of content partnership program.\u00a0<a href=\"https:\/\/kite.com\/\">Kite<\/a>\u00a0is a plugin for your IDE that uses machine learning to give you\u00a0useful code completions for Python. Available for Atom, PyCharm, Sublime, VS Code, and Vim.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction It\u2019s as good a time to be writing code as ever \u2013 these days, a little bit of code&hellip;<\/p>\n","protected":false},"author":1,"featured_media":1021,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,10],"tags":[],"translation":{"provider":"WPGlobus","version":"3.0.0","language":"es","enabled_languages":["gb","es","uk"],"languages":{"gb":{"title":true,"content":true,"excerpt":false},"es":{"title":false,"content":false,"excerpt":false},"uk":{"title":false,"content":false,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/1020"}],"collection":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/comments?post=1020"}],"version-history":[{"count":1,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/1020\/revisions"}],"predecessor-version":[{"id":1022,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/posts\/1020\/revisions\/1022"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/media\/1021"}],"wp:attachment":[{"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/media?parent=1020"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/categories?post=1020"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sergilehkyi.com\/es\/wp-json\/wp\/v2\/tags?post=1020"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}