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

A guide to Persistent storage in Docker
Latest

A guide to Persistent storage in Docker

Last Updated on December 15, 2022 by Editorial Team

Author(s): Prithivee Ramalingam

Originally published on Towards AI the World’s Leading AI and Technology News and Media Company. If you are building an AI-related product or service, we invite you to consider becoming an AI sponsor. At Towards AI, we help scale AI and technology startups. Let us help you unleash your technology to the masses.

What is the need for persistent storage in Docker?

Applications generate 2 kinds of data, persistent and non-persistent. Non-persistent data can be ignored, and they don’t have to be saved anywhere. On the other hand, persistent data needs to be saved for future use; it can’t be lost at any cost. If the application is hosted as a container, persistent data must be accessible to multiple containers as they share the load and storage. The data must persist, devoid of the status of the container. Since we have understood the need for persisting data, let’s look at how data is stored inside a container.

A container consists of multiple layers, and the files inside the container are stored in the writable layer. The data can only be persisted as long as the container exists, which means when the container is deleted, all the data inside it will be lost. Which presents the following problems,

  1. It would be difficult for another container to access the data which is present inside the container.
  2. Since the container’s writable layer is tightly coupled to the host machine, it would be difficult to move data to a different system.

To solve this problem, docker came up with 2 ways of persistent storage, Volumes and bind mounts. Docker also supports temporary file storage for in-memory use cases.

In this article we will be learning about the different persistent storage options, their implementation, their use case along with code samples.

Photo by John Salvino on Unsplash

Table of contents

1. Code walkthrough

2. Bind Mounts

3. Volumes

4. Temporary file storage mounts

5. Conclusion

1. Code walkthrough

For this article, we have taken the example of a simple python application that takes in the file name and content of the file as parameters and creates the file with the specified content. The source code for this application can be found here.

from flask import Flask, request
import os

app = Flask(__name__)

if not os.path.exists("docker_bind"):
os.makedir("docker_bind")

@app.route("/create_file",methods=["POST"])
def run():
data = request.get_json()
file_name, content = data['file_name'], data['content']
file_path = f"docker_bind/{file_name}"
with open(file_path,'w') as write_file:
write_file.write(content)
return {"Status":"Success"}

app.run(debug=False,host='0.0.0.0',port = 5000)

To run this application as a container, the prerequisite is that docker has to be installed. After installing docker, open the command prompt and execute the following commands. The list of all the commands can be found here.

To build the container.

docker build -t create_file_py_image .

To run the container.

docker run --name create_file_py_container -p 5001:5000 create_file_py_image

After running the above command, we can open postman and send a request to the running container with file name and content as parameters. The application takes in the parameters, creates the file with the specified name and content, and returns the Status as success. We will be using the same code to explain both volume and bind mounts.

Image by Author — Sending request to the containers

2. Bind Mounts

2.1 What are Bind Mounts?

2.2 Creating a Bind Mount.

2.3 Multiple containers accessing the same Bind mount.

2.4 Demonstrating persistence with Bind mount.

2.5 Where can we use Bind Mounts

2.1 What are Bind Mounts?

Bind mounts are used for persistence, and they have been available since the early days of docker. When we use a bind mount, a directory on the host is mounted into a container. In bind mounts, the directory is managed by us and not by docker.

Bind Mounts also come up with a slight disadvantage as the containers have the ability to modify, delete and create resources in the host OS. Attention has to be provided if non-docker elements need to access the mount folder.

2.2 Creating a Bind Mount

The mount flag is used to mention the kind of persistence we require. It could be bind, tmpfs, or volume. In this case, we set it to bind. For creating a bind mount, we need to provide the source path explicitly. It has to be an absolute path and not a relative. The source path is the path in the host. Similarly, we need to provide the target path. This is the path inside the container which we want to mount.

docker run -d -it -p 5000:5000 --name create_file_py_container1 --mount type=bind,source="C:\Users\prithiveer\Documents\Docker_Bind",target=/app/docker_bind create_file_py_image

With the above command, a container will be created. We sent a request from Postman to create a file named “sample_1.txt” with the content “My first file”. As shown below, we can use exec inside the container and find the file which we created.

Image by Author — Creating a file using create_file_py_container1

2.3 Multiple containers accessing the same Bind mount

In real life, an application would be hosted in multiple containers, and we require them to be mounted to a single bind mount. So, for demonstration purposes, we create two more containers.

docker run -d -it -p 5002:5000 --name create_file_py_container2 --mount type=bind,source="C:\Users\prithiveer\Documents\Docker_Bind",target=/app/docker_bind create_file_py_image

docker run -d -it -p 5003:5000 --name create_file_py_container3 --mount type=bind,source="C:\Users\prithiveer\Documents\Docker_Bind",target=/app/docker_bind create_file_py_image

Like the first container, we send requests from containers 2 and 3 and generate files sample_2.txt and sample_3.txt, respectively. While creating the bind mount, we provided a source address. We can find all the files which we created inside the containers in the specified location. Similarly, we can also find all the files in the docker_bind folder of all the 3 containers, irrespective of the files which were created by each container.

Image by Author — Source Folder has all the files which were created inside the 3 containers

2.4 Demonstrating persistence with Bind mount.

To demonstrate persistence, we delete all the 3 containers and create them again. Since the data is written to the writable layer when we delete the container, all the data inside the container should be lost. But due to bind mounts, we will be able to see all the files created.

Image by Author — Creating the container again and finding the files created earlier

2.5 Where can we use Bind Mounts?

We can use Bind mounts when we are sure that the directory structure of the host will be consistent. It can also be used to share configuration files from containers to the host.

3. Volumes

3.1 What are volumes?

3.2 Creating a volume and mounting it to a container.

3.3 Multiple containers accessing the same volume.

3.4 Demonstrating persistence with volume.

3.5 Where can we use volumes?

3.1 What are volumes?

Volumes are the preferred way to handle persistent file storage. Volumes are basically bound mounts except docker manages the storage on the host. So you don’t have to know the fully qualified path to a file or directory.

1. Volumes are independent of containers.

2. They can be mapped to external storage

3. Multiple containers can access the same volume.

3.2 Creating a volume and mounting it to a container.

A volume is a first-class object in docker. It can be created explicitly or on the fly while mounting a container. During mounting, docker checks if the volume is available, if not, it creates a volume.

Creating volume explicitly

docker volume create my_volume 

Creating volume while mounting

docker run -d -p 5000:5000 --name container1 --mount type=volume, source="my_volume", target=/app/docker_bind create_file_py_image

Using the above command, we create a container called container1 with 5000 as the port. Using the mount flag type set to type volume, we mount “my_volume” to the container. The source will have the name of the volume, while the target will be the folder that needs to be connected inside the container. When we send the requests after mounting, the files are created in docker_bind folder and persisted in the volume created by us.

To list volumes and inspect it

docker volume ls
docker volume inspect my_volume

If we need to check where the files are persisted in the volume, we can use the inspect command. This will return the mountpoint location and metadata. All the files which are generated in /app/docker_bind can be found in the location /var/lib/docker/volumes/my_volume/_data.

Image by Author- Inspecting a volume

3.3 Multiple containers accessing the same volume.

docker run -d -p 5002:5000 --name container2 --mount type=volume, source="my_volume", target=/app/docker_bind create_file_py_image
.
docker run -d -p 5003:5000 --name container3 --mount type=volume, source="my_volume", target=/app/docker_bind create_file_py_image

Now container1, container2, and container3 are accessing the same volume. So, the files created by all the 3 containers will reside in the same volume.

3.4 Demonstrating persistence with volume.

We send a request to container1 to create a file called volume_file.txt. Inside the content, we write, “I am inside volume file storage”. The file will be created in container1's docker_bind folder. Then it will be copied to my_volume. To demonstrate persistence, we delete container1. After that, we exec into container2 and check if volume_file.txt exists. As we can see in the below image, we are able to display the contents of volume_file.txt, which was created in container1 from container2. This demonstrates persistence with volume.

Image by Author — Demonstrating persistence with volumes

3.5 Where can we use volumes?

  1. Volumes can be used when we want to store the data in a remote host or cloud provider instead of storing it locally.

2. Volumes can be used to migrate, create a backup or restore data from one Docker Host to another. We can stop the running container and get the data from the mount path directory.

4. Temporary file storage mounts(tmpfs)

4.1 What are temporary file storage mounts?

4.2 Creating a temporary file storage.

4.3 Demonstrating the “temporary” in temporary file storage.

4.4 Demonstrating the in-memory property.

4.5 Where can we use temporary file storage?

4.1 What are temporary file storage mounts?

As the name states, temporary file storage mounts do not store data permanently. They are ephemeral in nature. They are in-memory file storage. They can’t be accessed by any other container, and the information will be lost once the container is down. In the case of tmpfs, no volume will be created.

Image by Author — Creating a tmpfs doesn’t create a volume

4.2 Creating temporary file storage.

For creating temporary file storage, we need to set up the mount flag to type, tmpfs. After executing the below command, a folder will be created by the name my_temp_storage.

docker run -it --name ubututu_container1 --mount type=tmpfs,dst=/my_temp_storage ubuntu

After creating the temporary file storage, we can create a file in the tmpfs directory, in this case it will be my_temp_storage.

echo "This is my file in temporary file storage" > my_temp_storage/logs.txt
Image by Author — Creating a file in tmpfs

4.3 Demonstrating the “temporary” in temporary file storage.

To demonstrate the ephemeral behavior of temporary file storage, we stop the ubututu_container1 and start it again. After we do that, we can find that the file(logs.txt) which we had created earlier doesn’t exist anymore. This is because tmpfs doesn’t persist data.

Image by author — Demonstration of ephemeral behavior of tmpfs

4.4 Demonstrating the in-memory property.

While explaining Bind mounts and volumes, we created multiple containers to show how the data had persisted. But in the case of tmpfs we can’t do that as it is an in-memory property. It means that a file in the tmpfs folder of a container can’t be accessed by another container.

Image by Author — Demonstrating the in-memory property

Tmpfs is different from saving files in a different location. In the case of tmpfs while we stop or exit the container, the information would be lost. But in any other location, if we stop and start the container back, we will be able to find the file.

Image by Author — Demonstrating difference between normal folders and tmpfs folder

4.5 Where can we use temporary file storage?

Tmpfs mounts are best used when we do not want the data to persist in both the container as well as the local system. They are used for storing security-related information, such as tokens that need to expire once the container is down. This also improves the performance of the container.

5. Conclusion

In this article, we have learned the different types of persistent storage options provided by Docker, their use cases, and their implementation. These options guarantee that the data is not lost once the container is removed. If you are not sure which to choose, go with volumes. In the case of Bind Mounts, we have to provide the location of the mount, but in the case of volumes, docker takes care of that for us. For sensitive data, we can go with temporary file storage, but we have to be careful as they are ephemeral.

References

  1. Manage data in Docker | Docker Documentation
  2. Docker Volumes Explained (PostgreSQL example) — YouTube
  3. (7384) Introduction to Persistent Storage Options in Docker — YouTube

Want to Connect?

If you have enjoyed this article, please follow me here on Medium for more stories about machine learning and computer science.

Linked In — Prithivee Ramalingam | LinkedIn


A guide to Persistent storage in Docker was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.

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.

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'); -->