<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Data Science | Sasan Jafarnejad | AI Researcher</title><link>https://sasan.jafarnejad.io/category/data-science/</link><atom:link href="https://sasan.jafarnejad.io/category/data-science/index.xml" rel="self" type="application/rss+xml"/><description>Data Science</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Wed, 02 Oct 2024 14:36:11 +0200</lastBuildDate><item><title>Dataset Tracking With DVC: A Practical Guide for MLOps</title><link>https://sasan.jafarnejad.io/post/dataset-tracking-with-dvc-mlflow-mlops/</link><pubDate>Wed, 02 Oct 2024 14:36:11 +0200</pubDate><guid>https://sasan.jafarnejad.io/post/dataset-tracking-with-dvc-mlflow-mlops/</guid><description>&lt;p&gt;In the rapidly evolving field of machine learning, managing and tracking datasets is crucial for reproducibility, collaboration, and efficient workflow. Data Version Control (DVC) has emerged as a powerful tool in the MLOps toolkit, allowing data scientists and ML engineers to version their datasets alongside their code. This blog post will guide you through setting up DVC with a DigitalOcean Spaces backend, an S3-compatible storage solution, to track your datasets effectively.&lt;/p&gt;
&lt;h2 id="why-dataset-tracking-matters"&gt;Why Dataset Tracking Matters&lt;/h2&gt;
&lt;p&gt;Before we dive into the setup, let&amp;rsquo;s briefly discuss why dataset tracking is essential in machine learning workflows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;: Ensures that experiments can be replicated with the exact same data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaboration&lt;/strong&gt;: Enables team members to share and sync datasets effortlessly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioning&lt;/strong&gt;: Allows tracking of data changes over time, similar to code versioning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage Efficiency&lt;/strong&gt;: Stores only the changes, not duplicate copies of entire datasets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt;: Seamlessly integrates with existing Git workflows for code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, let&amp;rsquo;s get started with setting up DVC in your project.&lt;/p&gt;
&lt;h2 id="setup-dvc"&gt;Setup DVC&lt;/h2&gt;
&lt;h3 id="step-1-initialize-dvc"&gt;Step 1: Initialize DVC&lt;/h3&gt;
&lt;p&gt;Before you start, make sure you have DVC installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install dvc dvc-s3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Begin by initializing DVC in your project root:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dvc init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command creates a &lt;code&gt;.dvc&lt;/code&gt; directory to store DVC-specific files and configurations.&lt;/p&gt;
&lt;h3 id="step-2-configure-the-remote-storage"&gt;Step 2: Configure the Remote Storage&lt;/h3&gt;
&lt;p&gt;Next, we&amp;rsquo;ll configure DVC to use DigitalOcean Spaces as our remote storage. Add the following to the &lt;code&gt;.dvc/config&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[core]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; analytics = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; remote = mapllm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[&amp;#39;remote &amp;#34;mapllm&amp;#34;&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; url = s3://mapllm/data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; endpointurl = https://ams3.digitaloceanspaces.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; profile = mapllm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration tells DVC:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To use a remote named &amp;ldquo;mapllm&amp;rdquo;&lt;/li&gt;
&lt;li&gt;The S3 bucket URL where data will be stored&lt;/li&gt;
&lt;li&gt;The endpoint URL for DigitalOcean Spaces&lt;/li&gt;
&lt;li&gt;The AWS credential profile to use&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="step-3-set-up-credentials"&gt;Step 3: Set Up Credentials&lt;/h3&gt;
&lt;p&gt;For security, we&amp;rsquo;ll store our DigitalOcean Spaces credentials in the AWS credentials file. Add the following to your &lt;code&gt;~/.aws/credentials&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[mapllm]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws_access_key_id = YOUR_ACCESS_KEY_ID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;YOUR_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;YOUR_SECRET_ACCESS_KEY&lt;/code&gt; with your actual DigitalOcean Spaces credentials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Keep these credentials secure and never commit them to your repository.&lt;/p&gt;
&lt;h2 id="tracking-data-with-dvc"&gt;Tracking Data with DVC&lt;/h2&gt;
&lt;p&gt;Now that we&amp;rsquo;ve set up DVC and configured our remote storage, let&amp;rsquo;s start tracking some data.&lt;/p&gt;
&lt;h3 id="adding-data-to-dvc"&gt;Adding Data to DVC&lt;/h3&gt;
&lt;p&gt;To track a data file with DVC, use the &lt;code&gt;dvc add&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dvc add data/cities.parquet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;.dvc&lt;/code&gt; file (&lt;code&gt;data/cities.parquet.dvc&lt;/code&gt;) that contains a reference to the data file.&lt;/li&gt;
&lt;li&gt;Add the original data file to &lt;code&gt;.gitignore&lt;/code&gt; to prevent it from being tracked by Git.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should see output similar to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;100% Adding...|████████████████████████████████████████████████████████████████|1/1 [00:00, 2.01file/s]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;To track the changes with git, run:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; git add data/.gitignore data/cities.parquet.dvc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;To enable auto staging, run:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dvc config core.autostage true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="committing-and-pushing-data"&gt;Committing and Pushing Data&lt;/h3&gt;
&lt;p&gt;After adding your data to DVC, you need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Commit the DVC tracking file to Git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add data/cities.parquet.dvc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Add cities data&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Push the actual data to your remote storage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dvc push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="pulling-data"&gt;Pulling Data&lt;/h3&gt;
&lt;p&gt;Later when clonning the repo in a new machine, assuming AWS credentials are in place all you have to do is to pull the data from the remote storage is to use the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dvc pull
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This process separates the concerns of version control: Git tracks the metadata and DVC handles the large data files.&lt;/p&gt;
&lt;h2 id="a-few-tips"&gt;A few tips&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regular Updates&lt;/strong&gt;: Whenever you update your dataset, re-run &lt;code&gt;dvc add&lt;/code&gt; and &lt;code&gt;dvc push&lt;/code&gt; to keep everything in sync.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Branching&lt;/strong&gt;: Use Git branches for different versions of your data, just like you would with code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pulling Data&lt;/strong&gt;: When collaborating, use &lt;code&gt;dvc pull&lt;/code&gt; to fetch the latest version of the data after a &lt;code&gt;git pull&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Checking Status&lt;/strong&gt;: Use &lt;code&gt;dvc status&lt;/code&gt; to see if your local data is in sync with the remote storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Exploration&lt;/strong&gt;: DVC allows you to switch between different versions of your data easily, facilitating A/B testing and model comparison.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Setting up DVC with a DigitalOcean Spaces backend provides a robust solution for dataset tracking in your MLOps workflow. By versioning your data alongside your code, you ensure reproducibility, enhance collaboration, and maintain a clear history of your dataset evolution.&lt;/p&gt;</description></item><item><title>Solving Memory Issues When Loading Parquet Files into Pandas DataFrames</title><link>https://sasan.jafarnejad.io/post/pandas-dataframe-parquet-memory-issue/</link><pubDate>Wed, 02 Oct 2024 12:10:22 +0200</pubDate><guid>https://sasan.jafarnejad.io/post/pandas-dataframe-parquet-memory-issue/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Have you ever encountered unexpected memory issues when loading a seemingly small Parquet file into a Pandas DataFrame? You&amp;rsquo;re not alone! In this article, we&amp;rsquo;ll explore a common but often overlooked problem that can cause your Python process to consume gigabytes of memory when working with Parquet files containing dictionary columns.&lt;/p&gt;
&lt;h2 id="the-problem-unexpected-memory-consumption"&gt;The Problem: Unexpected Memory Consumption&lt;/h2&gt;
&lt;p&gt;Recently, I faced a perplexing issue while working with a 145MB Parquet file containing over 2 million rows. When attempting to load this file into a Pandas DataFrame using the standard method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/my/file.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To my astonishment, the process consumed more than 90GB of memory before being terminated by the operating system. In 2024, a 145MB file shouldn&amp;rsquo;t be considered large, so what was causing this extreme memory usage?&lt;/p&gt;
&lt;h2 id="investigating-the-cause"&gt;Investigating the Cause&lt;/h2&gt;
&lt;p&gt;After some debugging, I discovered that the culprit was a single column with a dictionary data type. This column contained dictionaries with unpredictable keys - potentially thousands of unique keys across the dataset.&lt;/p&gt;
&lt;h3 id="understanding-parquets-dictionary-encoding"&gt;Understanding Parquet&amp;rsquo;s Dictionary Encoding&lt;/h3&gt;
&lt;p&gt;Parquet uses a technique called dictionary encoding to efficiently store and compress columns with repeated values. When storing a pandas DataFrame as a Parquet file, this encoding works well. However, when the data is sparse and most keys are not frequently reused, loading the Parquet file back into memory can cause issues.&lt;/p&gt;
&lt;p&gt;When Parquet loads a dictionary-encoded column with sparse data, it creates a map for each entry containing all unique keys, with most values set to None. For millions of rows, this results in a massive memory footprint - in our case, over 90GB!&lt;/p&gt;
&lt;h2 id="the-solution-converting-dictionary-columns-to-json-strings"&gt;The Solution: Converting Dictionary Columns to JSON Strings&lt;/h2&gt;
&lt;p&gt;Since I didn&amp;rsquo;t need to perform queries on this problematic column, the solution was surprisingly simple: convert the column to a string representation before saving to Parquet. Specifically, using &lt;code&gt;json.dumps&lt;/code&gt; to serialize the dictionaries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# When saving the DataFrame to Parquet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/output.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# When reading the Parquet file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/output.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If you need the column as dictionaries again:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;column_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This one-liner drastically reduced memory usage, allowing the file to load into a DataFrame without issues.&lt;/p&gt;
&lt;h2 id="key-takeaways"&gt;Key Takeaways&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Be cautious when using dictionary columns in Parquet files, especially with sparse data.&lt;/li&gt;
&lt;li&gt;Consider converting dictionary columns to JSON strings if you don&amp;rsquo;t need to query their contents directly.&lt;/li&gt;
&lt;li&gt;Always profile your data and memory usage when working with large datasets.&lt;/li&gt;
&lt;li&gt;Understand the underlying storage mechanisms of file formats like Parquet to optimize your data pipeline.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While Parquet&amp;rsquo;s dictionary encoding is generally beneficial for data compression and query performance, it can lead to unexpected memory issues in certain scenarios. By being aware of these potential pitfalls and applying simple solutions like converting to JSON strings, you can significantly optimize your data processing workflows.&lt;/p&gt;</description></item></channel></rss>