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

AI in Football — Biases in Expected Goals (xG) Models Confound Finishing Ability: Part 1
Data Science   Latest   Machine Learning

AI in Football — Biases in Expected Goals (xG) Models Confound Finishing Ability: Part 1

Last Updated on April 22, 2024 by Editorial Team

Author(s): Saankhya Mondal

Originally published on Towards AI.

This post will discuss the findings of the “Biases in Expected Goals Models Confound Finishing Ability” paper, published by researchers at the KU Leuven Institute for Artificial Intelligence, Belgium.

Part 1 discusses different biases in Expected Goal (xG) models when we use these models to judge the finishing prowess of a football player. The authors have performed simulation studies and statistical analysis in their work. Part 2 will discuss multi-calibration methods that can be used to mitigate biases in xG models.

Pic Credit — Here

What are the Expected Goals?

Expected Goals (xG) is a popular statistic to measure the quality of goals scored by a football player. xG is the likelihood of a player scoring a goal when he/she takes a shot. One can also define xG as a goal an average football player scored if he/she took the shot. Intuitively, a shot taken as a penalty kick or a tap-in will have a high xG whereas, a shot taken from outside the box with multiple defenders to beat will have a low xG. In machine learning terms, xG is a binary classification model. Given the attributes of a shot, we predict whether or not a shot will result in a goal. One can train a logistic regression model on player shot data containing features such as the coordinates of the location from which the shot has been taken, and body part with which the goal has been scored, and labels — whether the shot was a goal or not.

Over a period (match or season), cumulative xG may be a more insightful metric to evaluate a player’s quality rather than the number of goals scored. Football fans usually assess the player’s finishing quality with their cumulative xG. GAX (Goals Above Expectation) can be defined as the difference between the binary label goal/no goal and the xG of the particular shot. Over a period, GAX would be the difference between the actual goal tally of the player and the total xG accumulated by the player through his shots.

GAX (Goals Above Expectation), Pic source — 2401.09940.pdf (arxiv.org)

A player is termed a good finisher if he has overperformed his xG (positive GAX) over a given period. For example, Lionel Messi scored 375 goals in five seasons (from 2015/16 to 2020/21) from 1862 shots and accumulated an xG of 247 (according to the StatsBomb model) from these shots. Thus, he overperformed his xG by an incredible amount of 127 (GAX = 127)! No wonder he’s considered one of the best finishers in the world. xG or GAX can also be used to evaluate a team’s cumulative ability to generate goal-scoring chances in a match or over a season.

Limitations

However, in the paper, the authors have pointed out limitations of the xG statistic especially when it’s used to evaluate a player’s finishing ability. The authors have provided three possible explanations where xG models would fail to capture the finishing ability of a player. Players’ shot data from the top five European leagues has been considered for analysis in this paper. They have performed simulations studies and statistical analysis of the results to arrive at the following hypotheses.

Hypothesis 1: Limited sample sizes, high variances, and small variations in skill between players make GAX a poor metric for measuring finishing skills.

GAX is a poor metric to measure finishing skills. The player-shot data has a limited sample size and high variance. The players in the data have different shooting profiles. Defenders typically take a lower number of shots than forwards. Even among the forwards, there’s a huge variance in the quality of players.

The authors claim that the finishing quality and volume of shots taken greatly impact whether a player will overperform his/her xG over the season. Players are unlikely to overperform their cumulative xG unless they can pair exceptional finishing with high shot volumes due to limited sample sizes and high variances. This is even more difficult to maintain over multiple seasons.

The authors ran a simulation experiment with two variables — α and n- to gauge the order of magnitude of the sample size required. They computed the probability (P [G > xG]) that a player who is α% better finisher than average and takes n shots in a season would overcome their cumulative xG. This was done to estimate how many shots (n) a player with a certain finishing quality (α % better than average) has to take to have positive GAX (G > xG). The following is the process.

  1. They trained an xG model using logistic regression on the 2015/16 season data containing around 43K shots. For simplicity, the authors considered the location and body part as features. They derived features such as the coordinates of the location, the angle from the center of the goal, and the distance from the center of the goal.
  2. Then, using the same dataset, they divided the field into square grids of size 1m x 1m and computed the proportion of shots generated from each square grid.
  3. Thirdly, they sampled the locations of n shots (n ∈ {25, 50, 75, 100, 125, 150}) from this distribution. For each sampled shot, they extracted the features and obtained its xG from the trained model. Assuming a player who is α % (α ∈ {0, 5, 10, 15, 25}) better finisher than average, the adjusted xG of the shot is calculated as (1 + α/100) * xG.
  4. For each combination of n and α, they sampled an observed outcome (goal/no goal) based on the new distribution and computed its GAX. This simulated outcome represents what might happen if the player’s finishing ability were different from the average. For robustness, this sampling was repeated 10,000 times.

Suppose we have n = 100 and α = 25. We sample a shot which has xG of 0.7. The adjusted xG will be 0.875. Now, we sample a random number between 0 and 1 uniformly. If the outcome is 1, we get GAX = 1 –0.875 = 0.125. If the outcome is 0, we get GAX = 0 –0.875 = –0.875. This is done for every 100 shots to get a cumulative GAX.

The following heatmap shows the results.

Pic source — 2401.09940.pdf (arxiv.org)

The results show that players are unlikely to overperform their cumulative xG consistently unless they can pair exceptional finishing with high shot volume. As shown on the heatmap on the right, the P [G > xG] declines severely when computed over multiple seasons in cases of low and average shot volume.

Hypothesis 2: Including all shots when computing GAX is incorrect and obscures finishing ability.

Including all shots in the cumulative xG total is not appropriate. Some shots that have resulted in goals due to deflection may not represent the actual finishing skill of the player who took the shot. Some speculative shots (like long-range shots) resulting in goals may not represent the player’s actual finishing ability.

The authors illustrate it by giving the example of Riyad Mahrez. Mahrez took 289 shots during five seasons between 2017 and 2022. 17 (5.9%) of them were deflected. If we include these deflected shots while computing GAX, he achieves a GAX of 14.61. If we discard the deflected shots, he overperforms his xG by only 9.03 goals.

The number of goals scored in a football match, given the xG values of the shots follows Poisson Binomial Distribution (Poisson binomial distribution — Wikipedia). The following figure is a probability distribution curve for the number of goals scored, given the xG values of Mahrez’s shots.

Pic source — 2401.09940.pdf (arxiv.org)

Hypothesis 3: Interdependencies in the data bias GAX.

The assumption that xG captures the likelihood of the “average player” finishing a shot is violated because of the overrepresentation of shots from good finishers. In addition, there might be biases because players may have shots that appear in both training and test data. Consider three cases —

  1. Data only contains shots from top-quality finishers like Messi and Cristiano Ronaldo.
  2. Data only contains shots from players who typically take a large number of attempts and tend to underperform their xG.
  3. Data contains equal representation of every type of finisher.

When we compute Messi’s GAX in the first case, we would expect a low value as the training data contains only excellent finishers. Messi’s GAX would be much higher in the second case as the players in training data usually underperform their xG. In the third case, Messi’s GAX would be somewhere in the middle. The authors experimented to see how Messi’s GAX would vary when an xG model is trained with different sets of finishers.

  1. They began with the original dataset containing shots from 2015/16 season and excluded Messi’s shots from the data. They trained an xG model and computed Messi’s GAX which was 127.9.
  2. They performed data augmentation by adding 0 to 5000 shots for each cohort of players. In other words, they created a new dataset by sampling n shots from the distribution (1 + α/100) * xG (following the similar process as discussed in Hypothesis 1) and adding them to the original data.
  3. For each n and alpha, they trained an xG model on the new dataset and tracked the performance of Messi. They repeated the experiment 100 times for each case.
Pic source — 2401.09940.pdf (arxiv.org)

Messi’s GAX decreased to 120.8 when there was more representation of shots taken by top finishers in the training data. This experiment shows how the composition of the training data can have an effect on a player’s GAX.

The authors performed another experiment to better understand the biases that arise from variations in the number of shots players take and their finishing skill on xG models.

  1. They generated a training set of 1000k by sampling n shots from distribution (1 + α/100) * xG. The skill level, α ∈ {-5, 0, 10, 20}.
  2. They considered three different allocations for each skill type in the dataset, thereby forming three datasets. They trained xG models on these datasets.
  3. Next, they estimated the effect of the skill distribution in the training data on the GAX of a player with α ∈ {−5, 0, 10, 20} better finishing skills who took n ∈ {75, 100, 125} shots in a season. They sampled n shots from distribution (1+α/100) × xG (following the similar process as discussed in Hypothesis 1) and computed the xG from all three models, repeating it 10,000 times for each combination of α and n.
  4. For each sample, they computed the observed GAX and compare it to the average player GAX calculated by the ground truth model.
Pic source — 2401.09940.pdf (arxiv.org)

When the training data contains more shots from excellent finishers, the GAX decreases for players who are excellent finishers. From these two experiments, it is clear that the xG model overestimates the finishing of a poor finisher and underestimates the finishing of an excellent finisher.

Now the question is — how to solve the problem? The authors have derived an interesting parallel to the works done on fairness in AI and came up with a better approach to evaluate players based on the xG metric. They achieved it using a technique from fairness AI to learn an xG model by calibrating it on multiple subgroups of players. Part 2 will cover the multi-calibration techniques used.

References —

  1. [2401.09940] Biases in Expected Goals Models Confound Finishing Ability (arxiv.org)

Thank you for reading!

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