Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Read by thought-leaders and decision-makers around the world. Phone Number: +1-650-246-9381 Email: pub@towardsai.net
228 Park Avenue South New York, NY 10003 United States
Website: Publisher: https://towardsai.net/#publisher Diversity Policy: https://towardsai.net/about Ethics Policy: https://towardsai.net/about Masthead: https://towardsai.net/about
Name: Towards AI Legal Name: Towards AI, Inc. Description: Towards AI is the world's leading artificial intelligence (AI) and technology publication. Founders: Roberto Iriondo, , Job Title: Co-founder and Advisor Works for: Towards AI, Inc. Follow Roberto: X, LinkedIn, GitHub, Google Scholar, Towards AI Profile, Medium, ML@CMU, FreeCodeCamp, Crunchbase, Bloomberg, Roberto Iriondo, Generative AI Lab, Generative AI Lab Denis Piffaretti, Job Title: Co-founder Works for: Towards AI, Inc. Louie Peters, Job Title: Co-founder Works for: Towards AI, Inc. Louis-François Bouchard, Job Title: Co-founder Works for: Towards AI, Inc. Cover:
Towards AI Cover
Logo:
Towards AI Logo
Areas Served: Worldwide Alternate Name: Towards AI, Inc. Alternate Name: Towards AI Co. Alternate Name: towards ai Alternate Name: towardsai Alternate Name: towards.ai Alternate Name: tai Alternate Name: toward ai Alternate Name: toward.ai Alternate Name: Towards AI, Inc. Alternate Name: towardsai.net Alternate Name: pub.towardsai.net
5 stars – based on 497 reviews

Frequently Used, Contextual References

TODO: Remember to copy unique IDs whenever it needs used. i.e., URL: 304b2e42315e

Resources

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

Publication

Here’s How to Create a Bar Chart Race in Minutes for Any Data
Latest   Machine Learning

Here’s How to Create a Bar Chart Race in Minutes for Any Data

Last Updated on June 11, 2024 by Editorial Team

Author(s): Muhammad Saad Uddin

Originally published on Towards AI.

Before diving into this article, let me share a little story with you. I had two potential titles for this piece: “Building Dynamic Bar Chart Races for LLM Leaderboards in Python” and “Here’s How to Create a Bar Chart Race in Minutes for Any Data.” I found myself awkwardly torn between the two. So, I turned to GPT-4 for some much-needed advice (don’t judge me!). After weighing the pros and cons that GPT-4 provided, I ended up choosing the latter, even though my personal preference was for the former. This time, the LLM managed to convince me to go with the second option. Cheers to being open to constructive criticism and embracing suggestions, even from an AI!

The idea to create a bar chart race was originally sparked after I stumbled upon an eye-catching analysis on LinkedIn, which originally lead to this post on X and some code inspiration from

Karolina Stawicka. Motivated by these influences, I decided to create my own version, which ultimately inspired me to write this article. 😊

Before diving into the technical details, I want to give a shout-out to the sources of our data and tools. All the data used in this project is sourced from the LMSYS Leaderboard available on HuggingFace 🤗. For creating these stunning visuals, I utilized the bar char race library from PyPi. Thanks to these resources, I am able to bring this project to life!

Setting Up

We begin by installing the bar_chart_race package. This package, available on PyPi, provides the tools necessary to create dynamic and engaging bar chart race visuals. To install it, simply use the following command in your terminal:

pip install bar_chart_race

Next, we import the necessary modules. These modules include the bar_chart_race package, as well as other essential libraries for data manipulation and visualization.

import bar_chart_race as bcr
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
import pandas as pd
import seaborn as sns

With these imports, we’re all set to begin building our bar chart race visualizations!

Data Preparation

Next, we import and read data from the LMSYS repository. The data available there is not uniform and has some data quality issues. I will not go into details about data preprocessing step but if you wan to know about it contact me directly and I will share the notebook with you.

df = pd.read_csv('/content/lmsys.csv',sep=';')

df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')

# Pivot the DataFrame
df = df.drop_duplicates(subset=["date", "model"])
elo_pivot = df.pivot(index='date', columns='model', values='elo_rating_final')
elo_pivot = elo_pivot.replace('-', 0).fillna(0)
elo_pivot = elo_pivot.astype(float)
elo_pivot

We converted the cleaned and preprocessed CSV into a pivot table where the index consists of all dates, the columns are the model names, and the values contain the Elo ratings. The “Arena Elo rating” is a measurement system used within the Chatbot Arena platform to evaluate the relative skill levels of various LLMs based on their performance in anonymous, randomized battles.

Here’s a snapshot of what the DataFrame looks like now:

Creating the Bar Chart Race

Now, we can directly create a visualization by calling the bar_chart_race function. However, trust me, the default results aren't very impressive. For those who want to give it a try, here's the basic code:

#bcr.bar_chart_race(elo_pivot)
#bcr.bar_chart_race(elo_pivot, n_bars=10)
#bcr.bar_chart_race(elo_pivot, n_bars=10, steps_per_period=20, period_length=1000)

Next, I built some functions on top of the default setup to add customizations and make the visuals more dynamic and appealing. These enhancements include adjusting colors, fonts, labels, and other stylistic elements to create a more engaging and professional presentation. Here’s an example of how you can enhance the visualization:

def customize_bcr_chart(title):

"""
This function initiates a bar chart with a given title. It sets up the figure and axes, applies styling to the plot,
and returns the figure and axes objects for further customization and plotting.

Parameters:
title (str): The title of the bar chart.

Returns:
fig (Figure): The figure object for the plot.
ax (Axes): The axes object for the plot.
"""


# Set the font family to Helvetica
plt.rcParams['font.family'] = 'Helvetica'

# Initiate the figure and axes with specified size, background color, and resolution
fig, ax = plt.subplots(figsize=(12,8), facecolor='white', dpi=80)
ax.margins(0, 0.01) # Set margins
ax.set_axisbelow(True) # Ensure gridlines are below other elements

# Configure grid and tick parameters
ax.grid(which='major', axis='x', linestyle='-', linewidth=0.2, color='dimgrey')
ax.tick_params(axis='x', colors='dimgrey', labelsize=12, length=0)
ax.tick_params(axis='y', colors='dimgrey', labelsize=12, length=0)

# Customize the spines of the plot
for pos in ['top', 'bottom', 'right', 'left']:
if pos == 'top':
ax.spines[pos].set_edgecolor('dimgrey')
else:
ax.spines[pos].set_edgecolor('white')

# Format the x-axis to show numbers with commas
ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.xaxis.set_ticks_position('top') # Position x-axis ticks on top

# Set the title of the plot
ax.set_title(title, fontsize=18, color='dimgrey')

return fig, ax

and we used it as:

palette = sns.color_palette("mako", 24).as_hex()
title = 'LLM Leaderboard 2023-24'
fig, ax = customize_bcr_chart(title)
bcr.bar_chart_race(df=elo_pivot,
n_bars=15,
fig=fig,
steps_per_period=25,
period_length=2500,
cmap=palette,
period_fmt='%b %-d, %Y',
filter_column_colors=True,
filename='elo_mako.mp4')

The reason for the high steps and period length was to ensure smooth and detailed transitions in the animation, making it visually appealing and informative. The palette can be changed via the color parameter, allowing you to customize the colors used in the bar chart race to fit your preferences or branding. Additionally, you can save the animation directly as an MP4 file by specifying the filename parameter in the bcr.bar_chart_race function, providing a convenient way to share or present your visualizations.

Enhancing with Dark Mode

Next, I thought, let’s improve the customization further and build a BCR visual in “dark mode” because all devs like it 🙂

def customize_bcr_chart(title):

"""
This function initiates a bar chart with a given title. It sets up the figure and axes, applies styling to the plot,
and returns the figure and axes objects for further customization and plotting.

Parameters:
title (str): The title of the bar chart.

Returns:
fig (Figure): The figure object for the plot.
ax (Axes): The axes object for the plot.
"""


# Set the font family to Helvetica
plt.rcParams['font.family'] = 'Helvetica'

# Initiate the figure and axes with specified size and resolution
fig, ax = plt.subplots(figsize=(12,8), dpi=80)

# Set the background color of the figure and axes to black
fig.patch.set_facecolor('black')
ax.set_facecolor('black')

# Set margins and ensure gridlines are below other elements
ax.margins(0, 0.01)
ax.set_axisbelow(True)

# Configure grid and tick parameters
ax.grid(which='major', axis='x', linestyle='-', linewidth=0.2, color='dimgrey')
ax.tick_params(axis='x', colors='white', labelsize=12, length=0)
ax.tick_params(axis='y', colors='white', labelsize=12, length=0)

# Customize the spines of the plot
for pos in ['top', 'bottom', 'right', 'left']:
if pos == 'top':
ax.spines[pos].set_edgecolor('dimgrey')
else:
ax.spines[pos].set_edgecolor('black')

# Format the x-axis to show numbers with commas
ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.xaxis.set_ticks_position('top') # Position x-axis ticks on top

# Set the title of the plot
ax.set_title(title, fontsize=18, color='white')

return fig, ax

These are the improvements made in the revised function customize_bar_chart:

  • Background Color Adjustment:
    The background color of the figure and axes is set to black (fig.patch.set_facecolor('black') and ax.set_facecolor('black')) .
  • Color Contrast for Text and Grid:
    The tick parameters and title color are changed to white to ensure they are readable against the black background (ax.tick_params(axis='x', colors='white', labelsize=12, length=0) and ax.tick_params(axis='y', colors='white', labelsize=12, length=0), and ax.set_title(title, fontsize=18, color='white'))
  • Consistency in Spine Colors:
    The spines colors are updated to match the background (ax.spines[pos].set_edgecolor('black')) except for the top spine which is set to dimgrey. This ensures that the plot has a consistent look with the black background.
  • Removed Unnecessary Facecolor Setting:
    The facecolor parameter in plt.subplots is omitted since the background color is set later with fig.patch.set_facecolor and ax.set_facecolor. This reduces redundancy and potential conflicts.

These adjustments improve the visual appeal and readability of the chart in scenarios where a dark theme is preferable.

Adding Period Summary

def period_summary_func(values, ranks): #### not used
"""
This function summarizes the period information for the bar chart race.
It extracts the last date from the DataFrame index and formats it for display.

Parameters:
values (DataFrame): The DataFrame containing the values for the bar chart.
ranks (DataFrame): The DataFrame containing the ranks for the bar chart.

Returns:
dict: A dictionary containing the formatted date and text properties for display.
"""

# Get the last date from the index of 'values' DataFrame
date = values.index[-1]
return {'s': date.strftime('%b %-d, %Y'), 'x': .99, 'y': .05, 'ha': 'right', 'size': 16, 'family': 'DejaVu Sans', 'weight': 'bold', 'color': 'white'}

The period_summary_func function is designed to create a summary of the period information for a bar chart race. It extracts the last date from the index of a provided DataFrame (values), formats this date, and returns a dictionary with the formatted date and text properties for display. The text properties include position, alignment, font details, and color, making the summary suitable for visual representation in the bar chart race.

Customizing Bar Labels

def customize_bar_labels(bars): #### not used
"""
This function customizes the bar labels for the bar chart race.
It adjusts the color of the text labels on each bar to white.

Parameters:
bars (BarContainer): The container with all the bars to be labeled.
"""

# Adjust the bar label properties
for bar in bars:
# Get the text from each bar (matplotlib.container.BarContainer)
text = bar.get_text()
bar.set_color('white')

The customize_bar_labels function customizes the appearance of bar labels in a bar chart race by setting their color to white.

Final Touches

Finally, we put everything together:

title = 'LLM Leaderboard 2023-24'
fig, ax = customize_bcr_chart(title)
bcr.bar_chart_race(
df=elo_pivot,
n_bars=15,
fig=fig,
steps_per_period=25,
period_length=2500,
cmap='ice_r',
period_fmt='%b %-d, %Y',
filter_column_colors=True,
filename='llms_elo.mp4',
period_label={'color': 'white', 'fontsize': 16, 'x': 0.99, 'y': 0.1, 'ha': 'right'}, # Corrected period label
bar_kwargs={'alpha': 0.8, 'ec': 'white', 'lw': 1.5}, # Set bar transparency for better visibility of labels if needed
bar_label_size=12, # Increase the bar label size for better visibility
tick_label_size=12, # Increase tick label size for better visibility
#bar_label_font = {'size': 12, 'family': 'Helvetica', 'color': '#7f7f7f'}
period_summary_func=period_summary_func
)

# Modify the text properties for the bar labels
for container in ax.containers:
customize_bar_labels(container.get_children())

Let’s see the changes:

Extending to MMLU Ratings

I did it for MMLU rating as well, MMLU (Massive Multitask Language Understanding) is a benchmark for evaluating LLMs through multiple-choice questions. For scoring, MMLU averages each model’s performance per category (humanities, social science, STEM, and others) and then averages these four scores for a final score.

MMLU Purpose:

  • Evaluation of LLMs: MMLU is used to assess the capability of LLMs to understand and process information across various domains.
  • Diversity of Tasks: It covers a wide array of subjects, ensuring that models are tested on multiple facets of knowledge.

MMLU Significance:

  • Comprehensive Evaluation: By spanning a wide range of subjects, MMLU ensures that LLMs are not only specialized in specific areas but have a broad understanding.
  • Benchmarking Progress: MMLU helps track the progress and improvements in LLMs over time, providing a standard against which different models can be compared.
title = 'LLM Leaderboard 2023-24: MMLU'
fig, ax = customize_bcr_chart(title)
bcr.bar_chart_race(
df=elo_pivot,
n_bars=10,
fig=fig,
steps_per_period=15,
period_length=2000,
cmap='ice_r', #lighter color on a dark background
period_fmt='%b %-d, %Y',
filter_column_colors=True,
filename='mmlu_icr.mp4',
period_label={'color': 'white', 'fontsize': 16, 'x': 0.99, 'y': 0.1, 'ha': 'right'}, # Corrected period label
bar_kwargs={'alpha': 0.8, 'ec': 'white', 'lw': 1.5}, # Set bar transparency for better visibility of labels if needed
bar_label_size=12, # Increase the bar label size for better visibility
tick_label_size=12, # Increase tick label size for better visibility
)

# Modify the text properties for the bar labels
for container in ax.containers:
for text in container.get_children():
# Check if the object is an instance of Text
if isinstance(text, plt.Text):
text.set_color('white')

for label in ax.get_yticklabels():
label.set_color("white")
label.set_fontsize(12)

Lets see how this one looks.

Amazing, isn’t it?

That’s it for today. I hope you enjoyed this article and are excited to create your own stunning visuals with BCR. Recently, I’ve been focusing on topics related to LLMs, which is why this article also includes information about them. If you’re interested in learning more about LLMs and how to build your own applications, whether it’s RAG, function calling, or agents, be sure to follow me. I will be sharing the latest trends in this domain and showing you how to work with them in the most engaging and straightforward ways. Stay tuned for more exciting projects and insightful guides!. Furthermore:

  • 👏 Clap for the story (50 claps) to help this article be featured
  • 🔔 Follow Me: LinkedIn |Medium | Website
  • 🌟 Need help in converting these prototypes to products? Contact Me!

Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

Published via Towards AI

Feedback ↓

Sign Up for the Course
`; } else { console.error('Element with id="subscribe" not found within the page with class "home".'); } } }); // Remove duplicate text from articles /* Backup: 09/11/24 function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag elements.forEach(el => { const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 2) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); */ // Remove duplicate text from articles function removeDuplicateText() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, strong'); // Select the desired elements const seenTexts = new Set(); // A set to keep track of seen texts const tagCounters = {}; // Object to track instances of each tag // List of classes to be excluded const excludedClasses = ['medium-author', 'post-widget-title']; elements.forEach(el => { // Skip elements with any of the excluded classes if (excludedClasses.some(cls => el.classList.contains(cls))) { return; // Skip this element if it has any of the excluded classes } const tagName = el.tagName.toLowerCase(); // Get the tag name (e.g., 'h1', 'h2', etc.) // Initialize a counter for each tag if not already done if (!tagCounters[tagName]) { tagCounters[tagName] = 0; } // Only process the first 10 elements of each tag type if (tagCounters[tagName] >= 10) { return; // Skip if the number of elements exceeds 10 } const text = el.textContent.trim(); // Get the text content const words = text.split(/\s+/); // Split the text into words if (words.length >= 4) { // Ensure at least 4 words const significantPart = words.slice(0, 5).join(' '); // Get first 5 words for matching // Check if the text (not the tag) has been seen before if (seenTexts.has(significantPart)) { // console.log('Duplicate found, removing:', el); // Log duplicate el.remove(); // Remove duplicate element } else { seenTexts.add(significantPart); // Add the text to the set } } tagCounters[tagName]++; // Increment the counter for this tag }); } removeDuplicateText(); //Remove unnecessary text in blog excerpts document.querySelectorAll('.blog p').forEach(function(paragraph) { // Replace the unwanted text pattern for each paragraph paragraph.innerHTML = paragraph.innerHTML .replace(/Author\(s\): [\w\s]+ Originally published on Towards AI\.?/g, '') // Removes 'Author(s): XYZ Originally published on Towards AI' .replace(/This member-only story is on us\. Upgrade to access all of Medium\./g, ''); // Removes 'This member-only story...' }); //Load ionic icons and cache them if ('localStorage' in window && window['localStorage'] !== null) { const cssLink = 'https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css'; const storedCss = localStorage.getItem('ionicons'); if (storedCss) { loadCSS(storedCss); } else { fetch(cssLink).then(response => response.text()).then(css => { localStorage.setItem('ionicons', css); loadCSS(css); }); } } function loadCSS(css) { const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); } //Remove elements from imported content automatically function removeStrongFromHeadings() { const elements = document.querySelectorAll('h1, h2, h3, h4, h5, h6, span'); elements.forEach(el => { const strongTags = el.querySelectorAll('strong'); strongTags.forEach(strongTag => { while (strongTag.firstChild) { strongTag.parentNode.insertBefore(strongTag.firstChild, strongTag); } strongTag.remove(); }); }); } removeStrongFromHeadings(); "use strict"; window.onload = () => { /* //This is an object for each category of subjects and in that there are kewords and link to the keywods let keywordsAndLinks = { //you can add more categories and define their keywords and add a link ds: { keywords: [ //you can add more keywords here they are detected and replaced with achor tag automatically 'data science', 'Data science', 'Data Science', 'data Science', 'DATA SCIENCE', ], //we will replace the linktext with the keyword later on in the code //you can easily change links for each category here //(include class="ml-link" and linktext) link: 'linktext', }, ml: { keywords: [ //Add more keywords 'machine learning', 'Machine learning', 'Machine Learning', 'machine Learning', 'MACHINE LEARNING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ai: { keywords: [ 'artificial intelligence', 'Artificial intelligence', 'Artificial Intelligence', 'artificial Intelligence', 'ARTIFICIAL INTELLIGENCE', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, nl: { keywords: [ 'NLP', 'nlp', 'natural language processing', 'Natural Language Processing', 'NATURAL LANGUAGE PROCESSING', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, des: { keywords: [ 'data engineering services', 'Data Engineering Services', 'DATA ENGINEERING SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, td: { keywords: [ 'training data', 'Training Data', 'training Data', 'TRAINING DATA', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, ias: { keywords: [ 'image annotation services', 'Image annotation services', 'image Annotation services', 'image annotation Services', 'Image Annotation Services', 'IMAGE ANNOTATION SERVICES', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, l: { keywords: [ 'labeling', 'labelling', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, pbp: { keywords: [ 'previous blog posts', 'previous blog post', 'latest', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, mlc: { keywords: [ 'machine learning course', 'machine learning class', ], //Change your article link (include class="ml-link" and linktext) link: 'linktext', }, }; //Articles to skip let articleIdsToSkip = ['post-2651', 'post-3414', 'post-3540']; //keyword with its related achortag is recieved here along with article id function searchAndReplace(keyword, anchorTag, articleId) { //selects the h3 h4 and p tags that are inside of the article let content = document.querySelector(`#${articleId} .entry-content`); //replaces the "linktext" in achor tag with the keyword that will be searched and replaced let newLink = anchorTag.replace('linktext', keyword); //regular expression to search keyword var re = new RegExp('(' + keyword + ')', 'g'); //this replaces the keywords in h3 h4 and p tags content with achor tag content.innerHTML = content.innerHTML.replace(re, newLink); } function articleFilter(keyword, anchorTag) { //gets all the articles var articles = document.querySelectorAll('article'); //if its zero or less then there are no articles if (articles.length > 0) { for (let x = 0; x < articles.length; x++) { //articles to skip is an array in which there are ids of articles which should not get effected //if the current article's id is also in that array then do not call search and replace with its data if (!articleIdsToSkip.includes(articles[x].id)) { //search and replace is called on articles which should get effected searchAndReplace(keyword, anchorTag, articles[x].id, key); } else { console.log( `Cannot replace the keywords in article with id ${articles[x].id}` ); } } } else { console.log('No articles found.'); } } let key; //not part of script, added for (key in keywordsAndLinks) { //key is the object in keywords and links object i.e ds, ml, ai for (let i = 0; i < keywordsAndLinks[key].keywords.length; i++) { //keywordsAndLinks[key].keywords is the array of keywords for key (ds, ml, ai) //keywordsAndLinks[key].keywords[i] is the keyword and keywordsAndLinks[key].link is the link //keyword and link is sent to searchreplace where it is then replaced using regular expression and replace function articleFilter( keywordsAndLinks[key].keywords[i], keywordsAndLinks[key].link ); } } function cleanLinks() { // (making smal functions is for DRY) this function gets the links and only keeps the first 2 and from the rest removes the anchor tag and replaces it with its text function removeLinks(links) { if (links.length > 1) { for (let i = 2; i < links.length; i++) { links[i].outerHTML = links[i].textContent; } } } //arrays which will contain all the achor tags found with the class (ds-link, ml-link, ailink) in each article inserted using search and replace let dslinks; let mllinks; let ailinks; let nllinks; let deslinks; let tdlinks; let iaslinks; let llinks; let pbplinks; let mlclinks; const content = document.querySelectorAll('article'); //all articles content.forEach((c) => { //to skip the articles with specific ids if (!articleIdsToSkip.includes(c.id)) { //getting all the anchor tags in each article one by one dslinks = document.querySelectorAll(`#${c.id} .entry-content a.ds-link`); mllinks = document.querySelectorAll(`#${c.id} .entry-content a.ml-link`); ailinks = document.querySelectorAll(`#${c.id} .entry-content a.ai-link`); nllinks = document.querySelectorAll(`#${c.id} .entry-content a.ntrl-link`); deslinks = document.querySelectorAll(`#${c.id} .entry-content a.des-link`); tdlinks = document.querySelectorAll(`#${c.id} .entry-content a.td-link`); iaslinks = document.querySelectorAll(`#${c.id} .entry-content a.ias-link`); mlclinks = document.querySelectorAll(`#${c.id} .entry-content a.mlc-link`); llinks = document.querySelectorAll(`#${c.id} .entry-content a.l-link`); pbplinks = document.querySelectorAll(`#${c.id} .entry-content a.pbp-link`); //sending the anchor tags list of each article one by one to remove extra anchor tags removeLinks(dslinks); removeLinks(mllinks); removeLinks(ailinks); removeLinks(nllinks); removeLinks(deslinks); removeLinks(tdlinks); removeLinks(iaslinks); removeLinks(mlclinks); removeLinks(llinks); removeLinks(pbplinks); } }); } //To remove extra achor tags of each category (ds, ml, ai) and only have 2 of each category per article cleanLinks(); */ //Recommended Articles var ctaLinks = [ /* ' ' + '

Subscribe to our AI newsletter!

' + */ '

Take our 85+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!

'+ '

Towards AI has published Building LLMs for Production—our 470+ page guide to mastering LLMs with practical projects and expert insights!

' + '
' + '' + '' + '

Note: Content contains the views of the contributing authors and not Towards AI.
Disclosure: This website may contain sponsored content and affiliate links.

' + 'Discover Your Dream AI Career at Towards AI Jobs' + '

Towards AI has built a jobs board tailored specifically to Machine Learning and Data Science Jobs and Skills. Our software searches for live AI jobs each hour, labels and categorises them and makes them easily searchable. Explore over 10,000 live jobs today with Towards AI Jobs!

' + '
' + '

🔥 Recommended Articles 🔥

' + 'Why Become an LLM Developer? Launching Towards AI’s New One-Stop Conversion Course'+ 'Testing Launchpad.sh: A Container-based GPU Cloud for Inference and Fine-tuning'+ 'The Top 13 AI-Powered CRM Platforms
' + 'Top 11 AI Call Center Software for 2024
' + 'Learn Prompting 101—Prompt Engineering Course
' + 'Explore Leading Cloud Providers for GPU-Powered LLM Training
' + 'Best AI Communities for Artificial Intelligence Enthusiasts
' + 'Best Workstations for Deep Learning
' + 'Best Laptops for Deep Learning
' + 'Best Machine Learning Books
' + 'Machine Learning Algorithms
' + 'Neural Networks Tutorial
' + 'Best Public Datasets for Machine Learning
' + 'Neural Network Types
' + 'NLP Tutorial
' + 'Best Data Science Books
' + 'Monte Carlo Simulation Tutorial
' + 'Recommender System Tutorial
' + 'Linear Algebra for Deep Learning Tutorial
' + 'Google Colab Introduction
' + 'Decision Trees in Machine Learning
' + 'Principal Component Analysis (PCA) Tutorial
' + 'Linear Regression from Zero to Hero
'+ '

', /* + '

Join thousands of data leaders on the AI newsletter. It’s free, we don’t spam, and we never share your email address. Keep up to date with the latest work in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.

',*/ ]; var replaceText = { '': '', '': '', '
': '
' + ctaLinks + '
', }; Object.keys(replaceText).forEach((txtorig) => { //txtorig is the key in replacetext object const txtnew = replaceText[txtorig]; //txtnew is the value of the key in replacetext object let entryFooter = document.querySelector('article .entry-footer'); if (document.querySelectorAll('.single-post').length > 0) { //console.log('Article found.'); const text = entryFooter.innerHTML; entryFooter.innerHTML = text.replace(txtorig, txtnew); } else { // console.log('Article not found.'); //removing comment 09/04/24 } }); var css = document.createElement('style'); css.type = 'text/css'; css.innerHTML = '.post-tags { display:none !important } .article-cta a { font-size: 18px; }'; document.body.appendChild(css); //Extra //This function adds some accessibility needs to the site. function addAlly() { // In this function JQuery is replaced with vanilla javascript functions const imgCont = document.querySelector('.uw-imgcont'); imgCont.setAttribute('aria-label', 'AI news, latest developments'); imgCont.title = 'AI news, latest developments'; imgCont.rel = 'noopener'; document.querySelector('.page-mobile-menu-logo a').title = 'Towards AI Home'; document.querySelector('a.social-link').rel = 'noopener'; document.querySelector('a.uw-text').rel = 'noopener'; document.querySelector('a.uw-w-branding').rel = 'noopener'; document.querySelector('.blog h2.heading').innerHTML = 'Publication'; const popupSearch = document.querySelector$('a.btn-open-popup-search'); popupSearch.setAttribute('role', 'button'); popupSearch.title = 'Search'; const searchClose = document.querySelector('a.popup-search-close'); searchClose.setAttribute('role', 'button'); searchClose.title = 'Close search page'; // document // .querySelector('a.btn-open-popup-search') // .setAttribute( // 'href', // 'https://medium.com/towards-artificial-intelligence/search' // ); } // Add external attributes to 302 sticky and editorial links function extLink() { // Sticky 302 links, this fuction opens the link we send to Medium on a new tab and adds a "noopener" rel to them var stickyLinks = document.querySelectorAll('.grid-item.sticky a'); for (var i = 0; i < stickyLinks.length; i++) { /* stickyLinks[i].setAttribute('target', '_blank'); stickyLinks[i].setAttribute('rel', 'noopener'); */ } // Editorial 302 links, same here var editLinks = document.querySelectorAll( '.grid-item.category-editorial a' ); for (var i = 0; i < editLinks.length; i++) { editLinks[i].setAttribute('target', '_blank'); editLinks[i].setAttribute('rel', 'noopener'); } } // Add current year to copyright notices document.getElementById( 'js-current-year' ).textContent = new Date().getFullYear(); // Call functions after page load extLink(); //addAlly(); setTimeout(function() { //addAlly(); //ideally we should only need to run it once ↑ }, 5000); }; function closeCookieDialog (){ document.getElementById("cookie-consent").style.display = "none"; return false; } setTimeout ( function () { closeCookieDialog(); }, 15000); console.log(`%c 🚀🚀🚀 ███ █████ ███████ █████████ ███████████ █████████████ ███████████████ ███████ ███████ ███████ ┌───────────────────────────────────────────────────────────────────┐ │ │ │ Towards AI is looking for contributors! │ │ Join us in creating awesome AI content. │ │ Let's build the future of AI together → │ │ https://towardsai.net/contribute │ │ │ └───────────────────────────────────────────────────────────────────┘ `, `background: ; color: #00adff; font-size: large`); //Remove latest category across site document.querySelectorAll('a[rel="category tag"]').forEach(function(el) { if (el.textContent.trim() === 'Latest') { // Remove the two consecutive spaces (  ) if (el.nextSibling && el.nextSibling.nodeValue.includes('\u00A0\u00A0')) { el.nextSibling.nodeValue = ''; // Remove the spaces } el.style.display = 'none'; // Hide the element } }); // Add cross-domain measurement, anonymize IPs 'use strict'; //var ga = gtag; ga('config', 'G-9D3HKKFV1Q', 'auto', { /*'allowLinker': true,*/ 'anonymize_ip': true/*, 'linker': { 'domains': [ 'medium.com/towards-artificial-intelligence', 'datasets.towardsai.net', 'rss.towardsai.net', 'feed.towardsai.net', 'contribute.towardsai.net', 'members.towardsai.net', 'pub.towardsai.net', 'news.towardsai.net' ] } */ }); ga('send', 'pageview'); -->