市场营销的机器学习和生成式人工智能(二)
在当今的数字时代,理解客户情感对于塑造营销策略、完善品牌信息和提升客户体验至关重要。情感分析,作为自然语言处理(NLP)的一个子集,赋予营销人员处理大量非结构化文本数据的能力,如客户反馈、社交媒体对话和产品评论,以衡量公众情绪。这种分析方法不仅有助于监控品牌声誉,还能根据客户偏好定制营销信息,从而增强整体客户洞察。本章探讨了营销领域的情感分析世界。利用 Python 的力量,你将学习如何将情感分类
原文:
annas-archive.org/md5/83a239e384affb58e49aef6cce500be9
译者:飞龙
第五章:利用情感分析增强客户洞察
在当今的数字时代,理解客户情感对于塑造营销策略、完善品牌信息和提升客户体验至关重要。情感分析,作为自然语言处理(NLP)的一个子集,赋予营销人员处理大量非结构化文本数据的能力,如客户反馈、社交媒体对话和产品评论,以衡量公众情绪。这种分析方法不仅有助于监控品牌声誉,还能根据客户偏好定制营销信息,从而增强整体客户洞察。
本章探讨了营销领域的情感分析世界。利用 Python 的力量,你将学习如何将情感分类为正面、负面或中性,并识别客户反馈中嵌入的细微差别。我们还将使用基于 Kaggle 的“Twitter 航空情感”数据集的实战示例,为你提供执行情感分析、解读结果并将这些见解应用于制定更有效的营销策略的技能。
总体而言,本章将为你全面介绍营销中情感分析的基础知识,然后指导你通过数据准备、分析和结果可视化的实际方面。到本章结束时,你将精通:
-
理解情感分析在营销中的关键作用
-
预处理文本数据以准备分析
-
应用 Python 库使用传统自然语言处理和生成式 AI 元素执行情感分析
-
解释和可视化结果以得出可操作的营销见解
营销中情感分析简介
在营销这个快节奏的世界里,关注客户情感不仅有益,而且是必需的。情感分析,即检测文本数据中的正面、负面或中性语调的过程,是这个努力的前沿,为营销人员提供了一个观察和理解客户互动情感基调的视角。这种方法使用自然语言处理(NLP)、机器学习和计算语言学来系统地识别、提取、量化和研究文本中的模式。这些模式可以从某些关键词和短语的呈现到句子的结构和术语使用的上下文。
情感分析的重要性
情感分析在营销中的重要性不容小觑。它就像指南针,引导品牌穿越浩瀚且常常波涛汹涌的公众舆论大海。通过分析客户反馈、社交媒体对话和产品评论,情感分析帮助营销人员理解人们说了什么,以及他们的感受。
关于营销中情感分析的进一步阅读
关于营销中自动化文本分析方法的概述,请参阅文章“团结各部落:利用文本进行营销洞察”(journals.sagepub.com/doi/full/10.1177/0022242919873106
)。
更具体地说,情感分析在营销中的应用开辟了一系列机会:
-
品牌监控:情感分析能够实时监控品牌在各个数字平台上的感知。通过跟踪情感变化,营销人员可以预测并减轻潜在的公关危机。例如,在 2017 年 4 月 9 日,由于超售,一名乘客被从航班上强制带离的事件中,情感分析本可以帮助联合航空检测到社交媒体上病毒式传播的负面情绪的迅速升级,并更积极地做出反应。
-
活动分析:了解营销活动引起的情感反应,允许灵活调整策略。情感分析可以揭示活动是否与目标受众产生积极共鸣,或者是否偏离了目标。例如,百事公司在 2017 年推出的以肯达尔·詹纳为特色的广告,因其被认为轻视社会正义运动而受到抨击。早期的情感分析本可以识别出负面反馈,并允许及时调整活动策略。
-
产品反馈:详细的情感洞察有助于精确指出产品或服务中令客户满意或不满意的特定方面。这种反馈循环对于持续改进和创新至关重要。以苹果公司发布 iPhone 6 为例,对客户反馈的情感分析突出了“弯曲门”问题,促使苹果公司迅速解决问题。
-
市场研究:情感分析为更广泛的市场格局提供了一个窗口,通过揭示趋势、竞争对手地位和市场空白,提供竞争优势。例如,Netflix 利用情感分析来了解观众偏好和趋势,这有助于内容创作和推荐算法。
通过利用情感分析获得的洞察,营销人员不仅可以监控品牌声誉,还可以调整他们的沟通方式,使其更深入地与受众产生共鸣,从而实现更有效和有影响力的营销策略。
人工智能和情感分析方面的进步
大型语言模型(LLMs)和生成式人工智能(GenAI)的出现已经改变了情感分析,为理解文本数据提供了前所未有的深度和准确性。LLMs 在包含各种语言模式的庞大数据集上训练,使它们能够理解和生成类似人类的文本。例如,LLMs 可以以以前无法实现的方式理解上下文和细微差别,最先进的模型可以区分讽刺和真正的不满,或者识别包含改进建议的投诉中的潜在积极因素。
这些进步对于情感分析尤其相关,因为在满意和不满意的客户之间,差异往往微妙且依赖于上下文。然而,需要注意的是,尽管 LLMs 提供了这些优势,但它们也带来了一定的局限性。它们可能计算成本高昂,需要大量的处理能力,这可能不是所有用户都能负担得起,或者可能需要付出代价。此外,LLMs 并非完美无缺,它们有时可能会基于训练数据产生有偏见或不准确的结果。
尽管对 GenAI 模型及其局限性的更深入讨论将在本书的第四部分中展开,但本章将涉及 GenAI 在情感分析方面的针对性应用。
实际示例:Twitter Airline Sentiment 数据集
我们对情感分析的研究将基于Twitter Airline Sentiment
数据集。这个针对不同航空公司的推文集合为理解如何将情感分析应用于现实世界的营销挑战提供了一个丰富的数据集。
在这里,情感通过人工标注被分类为正面、负面或中性,反映了从满意到挫败的多种客户情绪。
数据来源:www.kaggle.com/datasets/crowdflower/twitter-airline-sentiment
便利的是,这个数据集不仅包含推文及其情感分类,在某些情况下还包含对负面情感推文的解释。这些将为我们提供有用的基准,以评估我们在本章中开发的方法。
在深入数据之前,以下是几个样本推文,突出了情感分类、存在的主题以及推文内容的性质。正如您所看到的,数据集的前几行包含由现实世界数据输入错误产生的术语,这些问题将在数据预处理阶段解决。
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_01.png
图 5.1:Twitter 航空公司情绪数据集的样本推文
在接下来的章节中,我们将处理数据准备、模型构建、分析和可视化阶段,同时采用传统的 NLP 技术和使用 LLMs 的更现代方法。最初,我们将应用 NLP 工具进行数据清理和结构化,为传统的情绪分析打下基础。
认识到类别不平衡的挑战,我们将展示如何使用 GenAI 作为工具,根据需要通过添加额外的示例来增强我们的数据集。然后,我们将使用我们构建的模型来得出可操作的见解,这些见解可以帮助指导营销活动。
准备数据以进行情绪分析
在进行情绪分析之前,有效地准备数据至关重要。数据准备是一个涉及清理、结构和增强数据以改善分析结果的过程。这些步骤的目标是确保数据以可以直接用于分析的形式存在,并消除任何不准确或不规则性。
让我们从加载Twitter Airline Sentiment
数据集开始:
import pandas as pd
df = pd.read_csv('Tweets.csv')
df.head(5)
使用df.columns
,我们可以看到许多列,例如包含推文本身的text
列,以及几个有价值的元数据和情绪相关字段。以下是对列的总结,以及它们含义的简要描述:
列 | 描述 |
---|---|
tweet_id |
推文的 ID |
airline_sentiment |
推文的类别标签(正面、中性或负面) |
airline_sentiment_confidence |
情绪分类的置信水平 |
negative_reason |
负面情绪的原因 |
airline |
航空公司的官方名称 |
airline_sentiment_gold |
航空公司情绪分类的黄金标准 |
name |
用户的名称 |
negativereason_gold |
负面原因背后的黄金标准 |
retweet_count |
表示转发次数的数值 |
text |
用户输入的推文文本 |
tweet_coord |
Twitter 用户的纬度和经度 |
tweet_created |
推文的创建日期 |
tweet_location |
发送推文的位置 |
user_timezone |
用户的时区 |
表 5.2:Twitter 航空公司情绪数据集的列及其描述
传统的 NLP 数据准备技术
在传统的 NLP 技术中,仔细的文本准备——包括清理、分词、停用词去除和词形还原——对于构建输入文本以进行有效分析非常重要。我们将在本节中详细讨论这些过程。
相比之下,现代技术如词嵌入(例如 Word2Vec 和 GloVe)和上下文嵌入(例如 BERT 和 GPT-4)提供了更高级的表示和处理文本数据的方法。这些现代技术将在本书的第 IV 部分中更详细地解释。与依赖于手动特征提取的传统方法不同,现代技术会自动从其他文本的预训练中学习单词和上下文的密集表示。
为了说明,我们将选取五个推文文本的样本,并使用它们作为示例来观察传统数据准备步骤的影响。我们还将通过pd.set_option
设置列宽来显示 DataFrame 的全列宽,从而显示完整的推文文本:
pd.set_option("max_colwidth", None)
examples_idx = df.sample(5).index # [1106, 4860, 6977, 8884, 9108]
df_sample = df.loc[examples_idx]
清洗文本数据
文本清洗通过去除噪声并使文本格式统一,从而提高了数据分析的质量。其主要优势包括提高模型准确性和加快计算速度。然而,谨慎地处理清洗过程是必要的,以避免删除上下文中重要的信息。对于 Twitter 数据集来说,由于社交媒体文本的非正式和多样性,清洗特别有用。推文通常包含 URL、提及、表情符号和标签,这些有时会分散对主要情感分析的关注。
我们的方法针对这些具体内容,以保留核心信息的同时消除无关元素:
!python -m space download en_core_web_sm
import re
import spacy
nlp = spacy.load("en_core_web_sm")
def clean_text(text):
text = re.sub(r'@\w+|#\w+|https?://\S+', '', text)
text = re.sub(r'[^\w\s]', '', text)
return text.lower()
df_sample['cleaned_text'] = df_sample['text'].apply(clean_text)
df_sample[["text", "cleaned_text"]]
这产生了以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_02.png
图 5.2:清洗前后推文文本的示例
注意,从文本中移除(@
)提及对上下文的影响很小,因为我们的数据集已经通过airline
列捕获了推文的主题。
额外的文本预处理包
在本节中没有使用的其他有用的 Python NLP 和文本预处理工具包括:
-
NLTK (
www.nltk.org/
) -
TextBlob (
textblob.readthedocs.io
) -
Gensim (
radimrehurek.com/gensim/
)
分词和停用词移除
分词将文本划分为更小的单元,如单词或短语,这使得算法更容易理解语言结构。停用词是常用词,如“是”、“和”、“the”,它们通常被移除,因为它们添加的语义价值很小,允许模型关注更有意义的内容。
在此实现中,我们使用 spaCy 的模型来应用分词和停用词移除,并展示每个步骤的结果:
def tokenize_and_remove_stopwords(row):
doc = nlp(row['cleaned_text'])
all_tokens = [token.text for token in doc]
tokens_without_stop = [token.text for token in doc if not token.is_stop]
processed_text = ' '.join(tokens_without_stop)
row['all_text_tokens'] = all_tokens
row['without_stop_words_tokens'] = tokens_without_stop
row['processed_text'] = processed_text
return row
df_sample = df_sample.apply(tokenize_and_remove_stopwords, axis=1)
df_sample[['cleaned_text', 'all_text_tokens', 'without_stop_words_tokens', 'processed_text']]
这给我们以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_03.png
图 5.3:分词和停用词移除后的推文文本示例
词形还原
词形还原将单词还原为其词典形式,确保结果是一个有效的单词。这个过程旨在将单词的不同形式合并为单个项目进行分析,从而提高下游任务的效率和准确性。
观察以下代码的最后一行,我们看到最终词元还原后的文本将wheels
标准化为单数形式wheel
,并将thanks
转换为基本动词形式thank
:
def lemmatize_text(text):
doc = nlp(text)
lemmatized = [token.lemma_ for token in doc]
return ' '.join(lemmatized)
df_sample['final_text'] = df_sample['processed_text'].apply(lemmatize_text)
df_sample[['processed_text', 'final_text']]
这给我们以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_04.png
图 5.4:词元还原后的推文文本示例
类别不平衡
现在,我们将通过在我们数据集上的探索性数据分析来介绍理解类别平衡的重要性。类别平衡直接影响模型从数据中有效学习的能力。未解决的类别不平衡可能会掩盖洞察力,导致模型在实际应用中表现不佳。
在接下来的章节中,我们不仅将量化类别不平衡,还将讨论解决它的简单和更高级的策略。
评估类别平衡
确定整体情感分布至关重要,因为它有助于我们了解数据集中表达的一般情绪和观点。这种理解对于理解由于类别不平衡可能存在的任何偏差也是必不可少的。
以下代码按航空公司和情感分组推文,计算每个组的大小,并生成条形图来可视化结果:
import matplotlib.pyplot as plt
from datetime import datetime
sentiment_by_airline = df.groupby(['airline', 'airline_sentiment']).size().unstack().fillna(0)
plt.figure(figsize=(14, 6))
sentiment_by_airline.plot(kind='bar', stacked=True, color=['red', 'yellow', 'green'])
plt.title('Sentiment Distribution by Airline')
plt.xlabel('Airline')
plt.ylabel('Number of Tweets')
plt.xticks(rotation=45)
plt.legend(title='Sentiment')
plt.tight_layout()
plt.show()
此代码生成了以下图表,展示了该数据集中捕获的航空公司推文的情感主要是负面的:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_05.png
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_05.png
如果我们查看数据集中的类别平衡,我们可以看到有 9,178 个负面示例,3,099 个中性示例,以及 2,363 个正面推文。这可以通过以下方式展示:
df['airline_sentiment'].value_counts()
在现实世界的数据集中,经常遇到具有类别不平衡的数据集,因此,我们将保留本章中的数据特征,以便我们能够理解类别不平衡对建模结果可能产生的影响。
解决类别不平衡
以下是一些可以用来解决类别不平衡的传统策略:
-
欠采样通过减少多数类的样本大小以匹配少数类来平衡数据集。这有助于平衡数据集,但可能会导致丢失有价值的多数类示例。
-
过采样通过复制现有示例来增加少数类的样本大小以匹配多数类。这改善了平衡,但可能导致模型在重复数据示例上过拟合。
-
合成少数过采样技术(SMOTE)与过采样类似,不同之处在于它生成合成示例而不是简单地复制现有的示例。它是通过基于特征空间中相似的示例生成新实例来实现的。
作为一项练习,你可以对多数类(负面情感)进行欠采样,以实现更平衡的数据集。这可以通过以下代码实现,我们首先将我们的训练数据分为负面、中立和正面类别,然后对负面类别进行下采样以匹配少数(正面)类别的示例数量,从而得到一个更平衡的起始数据集:
from sklearn.utils import resample
negative = df[df.airline_sentiment == 'negative']
neutral = df[df.airline_sentiment == 'neutral']
positive = df[df.airline_sentiment == 'positive']
negative_downsampled = resample(negative, n_samples=len(positive))
df_downsampled = pd.concat([negative_downsampled, neutral, positive])
进行欠采样实验
作为一项练习,运行文本中给出的下采样代码,并在本章剩余部分将df_downsampled
替换为df
,以查看你的结果如何随着更平衡的数据集而变化。
数据增强的 GenAI
在本节中,我们将展示一种通过使用 GenAI 的种子文本生成新示例来增强代表性不足的正面类别的策略。这种更新颖的方法补充了传统技术以解决类别不平衡问题,但新示例具有更大的潜在多样性。然而,使用 AI 生成数据可能会引入风险,例如过度拟合到生成的模式或反映生成模型本身的潜在偏差。确保种子文本的多样性可以帮助减轻这些问题。
我们将利用 Hugging Face 的 Transformers 库中的distilgpt2
模型来增强我们的数据集。这个模型是 GPT-2 的简化版本,旨在提高资源效率,因此使得这个例子对具有不同计算资源的用户都易于访问:
from transformers import pipeline
generator = pipeline('text-generation', model='distilgpt2')
def augment_text(text, augment_times=2):
augmented_texts = []
for _ in range(augment_times):
generated = generator(text, max_length=60, num_return_sequences=1)
new_text = generated[0]['generated_text'].strip()
augmented_texts.append(new_text)
return augmented_texts
seed_text = "Fantastic airline service on this flight. My favorite part of the flight was"
augmented_examples = augment_text(seed_text)
def remove_extra_spaces(text):
words = text.split()
return ' '.join(words)
for example in augmented_examples:
print("------\n", remove_extra_spaces(example))
记住,GenAI 的概率性质意味着输出可能随着每次执行而变化。通过使用精心选择的种子文本“``这次航班的航空公司服务太棒了。我最喜欢的飞行部分是”我们能够生成关于航空公司服务的各种正面情感。当在上述text-generation
管道中使用此种子文本时,我们生成如下输出:
-
这次航班的航空公司服务太棒了。我最喜欢的飞行部分是 享受所有乘务员和跑道的壮观景色。
-
这次航班的航空公司服务太棒了。我最喜欢的飞行部分是 这是我第一次乘坐它,而且非常值得。
默认情况下,大多数 LLM 将产生反映你期望在日常生活中听到的语言的输出。例如,尝试移除种子语句的结尾部分“我最喜欢的飞行部分是
"并看看 LLM 产生具有正面情感的示例有多困难。
GenAI 模型选择和提示敏感性的重要性
探索 Hugging Face 提供的更复杂的模型,如gpt-neo
、gpt-j
和EleutherAI/gpt-neo-2.7B
,将产生更细腻和逼真的增强效果。
提示的选择在引导生成模型输出中也起着至关重要的作用。种子文本的微妙变化可能导致结果发生巨大变化,这强调了提示设计在 GenAI 应用中的重要性。这个主题在本书的第九章中进行了详细探讨。
虽然这个增强步骤增强了我们数据集中积极情感推文的代表性,但要实现真正的类别平衡需要更广泛的数据增强以及各种种子文本,以确保模型不会过度拟合推文的开始部分。我们可以通过更改上述 augment_text()
函数中的 augment_times
参数来增加生成的增强示例的数量:
augmented_data = pd.DataFrame({
'text': augmented_examples,
'airline_sentiment': ['positive'] * len(augmented_examples)
})
df_augmented = pd.concat([df, augmented_data], ignore_index=True)
通过仔细使用 GenAI 进行数据增强,我们现在有了 df_augmented
数据框,其中包含了可以添加到现有数据集以减轻类别不平衡并增强数据集中积极情感表达的额外数据。然而,为了说明原始数据集中类别不平衡对我们结果的影响,我们将不将这些示例添加到我们的数据集中。
执行情感分析
情感分析的力量在于其揭示文本数据背后情感的能力,为顾客情感提供了无价的见解。虽然 Twitter Airline Sentiment 数据集的焦点是将情感分类为积极、消极和中性类别,但情感分析也可以超出这些基本类别。根据应用的不同,情感可以分析以检测更细微的情感状态或态度,如快乐、愤怒、惊讶或失望。
构建自己的 ML 模型
训练 sentiment analysis 模型的基本方面,尤其是在使用传统 NLP 技术时,是预标注数据的需求。这些标签通常来源于人工标注,这个过程涉及个人评估文本的情感并根据其进行分类。这个 Twitter 数据集中的情感分数是在志愿者的帮助下收集的,其中一些负面推文也根据它们所强调的具体问题进行了分解,例如航班延误或服务质量差,从而提供了对客户不满的更细致的视角。
使用 scikit-learn
,我们将构建利用此预标注数据集的 sentiment analysis 模型。这些模型将利用前一小节中展示的文本预处理方法从文本中提取 TF-IDF 特征,这些特征反过来又作为机器学习推理的输入,可以预测未见过的推文的情感。
特征工程
在训练模型之前,我们需要将文本数据转换为数值特征。一种常见的方法是使用TF-IDF(词频-逆文档频率)技术。TF-IDF 是一种用于评估一个词在集合或语料库中对于文档重要性的统计度量。我们将利用之前执行的文本预处理步骤,并将tfidf
向量器直接应用于处理后的文本,将特征限制在顶级 1,000 个术语(max_features=1000
)。这一步很重要,因为降低维度有助于简化模型,使其训练更快,并减少过拟合的风险。通过关注最相关的词语,我们确保模型捕捉到数据中最显著的模式,同时忽略不那么重要的细节:
from sklearn.feature_extraction.text import TfidfVectorizer
df['cleaned_text'] = df['text'].apply(clean_text)
df = df.apply(tokenize_and_remove_stopwords, axis=1)
df['processed_text'] = df['cleaned_text'].apply(tokenize_and_remove_stopwords)
df['final_text'] = df['processed_text'].apply(lemmatize_text)
tfidf_vectorizer = TfidfVectorizer(max_features=1000)
X = tfidf_vectorizer.fit_transform(df['final_text'])
y = df['airline_sentiment']
探索 TF-IDF 的特征工程
在 TF-IDF 特征工程期间,你可以探索的另一个参数是ngram_range
。
N-gram 允许你超越单个单词,将连续单词的成对(bigram)或三元组(trigram)视为单个特征。这可以捕捉到更多的上下文和词语之间的关系——例如,虽然“not”和“good”单独可能并不很有信息量,但 bigram“not good”却传达了明确的情感。
模型训练
我们的特性准备就绪后,我们可以继续使用 scikit-learn 训练一个简单的模型。逻辑回归是一种简单而强大的算法,适用于包含在这些简短推文中的数据的维度。它为具有两种可能结果的分类问题建模概率,但它可以扩展以处理多个类别,这在我们的案例中适用:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
我们的逻辑回归模型训练完成后,现在我们可以解释模型的系数,以了解特定词语如何影响情感分类。对于每个情感类别,系数代表这些术语对文本被分类为该特定情感类别的可能性的影响,而其幅度代表每个术语在模型决策过程中的重要性。
为了实现这一点,我们遍历每个类别标签,提取并显示系数绝对值排序的最具影响力的特征:
feature_names = tfidf_vectorizer.get_feature_names_out()
class_labels = model.classes_
for index, class_label in enumerate(class_labels):
coefficients = model.coef_[index]
coefficients_df = pd.DataFrame({
'Feature': feature_names,
'Coefficient': coefficients
})
coefficients_df['Absolute_Coefficient'] = coefficients_df['Coefficient'].abs()
coefficients_df = coefficients_df.sort_values(by='Absolute_Coefficient', ascending=False)
print(f"Class: {class_label}")
print(coefficients_df[['Feature', 'Coefficient']].head(10))
这产生了以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_06.png
图 5.6:逻辑回归模型按情感类别排序的最具影响力的特征
我们可以从上述系数中看到以下词语对三个情感类别的影响:
-
负面情感:单词
thank
(-3.88)降低了推文被分类为负面的可能性,而像hour
(3.61)、bad
(2.96)、delay
(2.84)和cancel
(2.63)这样的词语则增加了这种可能性,并且是投诉的特征。 -
中性情感:例如,
customer
(-2.24
)、experience
(-1.91
)和fix
(-1.88
)等词语会降低中性分类的可能性,表明这些术语更常用于非中性语境中。 -
正面情感:例如,
thank
(4.36
)、great
(3.54
)、awesome
(3.18
)和amazing
(3.07
)等词语会显著增加推文被分类为正面的可能性。
模型评估
训练模型后的关键一步是评估我们的模型性能。如果没有适当的评估,我们可能会部署一个在实际场景中表现不佳的模型,这可能导致基于其预测的错误结论。
分类报告
scikit-learn 的分类报告为我们提供了每个类别的精确度、召回率和 F1 分数。这些指标至关重要,因为它们不仅告诉我们整体准确度,还告诉我们模型在每个情感类别上的表现:
from sklearn.metrics import accuracy_score, classification_report
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_07.png
宏平均和加权平均分数让我们了解模型在所有类别上的性能。宏平均对待所有类别同等,而加权平均则考虑了类别不平衡。这些分数之间的差异突出了类别不平衡对模型性能的影响。
让我们更仔细地看看每个类别的结果:
-
负面情感,作为多数类,具有高精确度(
0.82
)和高召回率(0.92
),表明模型在识别负面推文方面特别出色。这是由于我们的类别不平衡的预期副作用,因为模型有更多这个类别的例子来学习,导致预测这个类别正确的可能性更高。 -
中性情感,比负面情感少,但比正面情感多,显示出显著较低的精确度(
0.61
)和召回率(0.49
)。精确度相当好,这意味着当模型预测一条推文为中性时,正确率略超过一半。 -
正面情感,最少代表的类别,具有相对较高的精确度(
0.79
),但比负面类别的召回率(0.61
)低。这里的精确度较高表明,大多数被预测为正面的推文确实是正面的,但模型未能捕捉到许多正面情感(召回率低)。
混淆矩阵
混淆矩阵是进一步了解模型性能的一个很好的下一步。它显示了一个矩阵,其中实际类别在一轴上,预测类别在另一轴上。通过分析混淆矩阵,我们可以看到哪些类别被相互混淆。例如,如果许多中性推文被错误地分类为负面,这可能会表明模型倾向于预测负面,并且中性推文的特征不够独特。我们可以使用以下方法计算和可视化混淆矩阵:
from sklearn.metrics import confusion_matrix
import seaborn as sns
cm = confusion_matrix(y_test, y_pred, labels=['negative', 'neutral', 'positive'])
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['negative', 'neutral', 'positive'], yticklabels=['negative', 'neutral', 'positive'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
我们可以接着看到以下混淆矩阵:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_08.png
图 5.8:逻辑回归模型的推文情感类别混淆矩阵
让我们将其分解以更好地理解:
-
与我们从分类报告中观察到的结果一致,模型在正确识别负面推文方面表现出色,这在混淆矩阵的第一行中有所体现。
-
当涉及到中性情感时,第二行表明模型倾向于将中性推文与负面推文混淆。
-
最后,第三行对应于积极情感。虽然模型正确识别积极推文的时间超过一半,但仍有一大部分被误认为是负面或中性情感。
理解误分类
在用各种指标评估我们的模型后,我们可以调查模型失败时的示例,从而了解其局限性和改进的机会。
让我们看看模型预测与airline_sentiment_gold
数据标签提供的可信分类发生冲突的实例:
gold_df = df[df['airline_sentiment_gold'].notnull()]
X_gold = tfidf_vectorizer.transform(gold_df['final_text'])
y_gold = gold_df['airline_sentiment_gold']
y_gold_pred = model.predict(X_gold)
gold_df['predicted_sentiment'] = y_gold_pred
misclassified = gold_df[gold_df['airline_sentiment_gold'] != gold_df['predicted_sentiment']]
misclassified[['airline_sentiment_gold', 'predicted_sentiment', 'text', 'final_text', 'negativereason_gold']]
我们得到以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_09.png
图 5.9:数据集标签与逻辑回归模型预测不一致的示例推文
对误分类示例的分析表明,我们的模型的predicted_sentiment
在准确捕捉完整推文语境方面存在不足。例如,一个表达对取消航班失望的推文的第二个误分类示例,以幽默的语气包装,突显了检测讽刺的内在挑战,并强调了模型训练涵盖更广泛情感表达的重要性。然后,对于最后一个例子,我们有一个令人鼓舞的推文,承认从负面经历中看到改进的机会,但预测为负面。这说明了 TF-IDF 特征在模型中解释细微积极情感时的困难,尤其是在与负面词汇交织在一起时。这些误分类可以从更先进的 NLP 模型中受益,这些模型能够更好地辨别语境和细微差别,这是我们将在下一节关于预训练 LLMs 的讨论中探讨的主题。
提高传统情感模型性能
-
解决训练数据差距:确保您的数据集包括广泛的情感表达,如讽刺、幽默和条件积极。模型准确解释情感的能力直接与其从中学到的例子多样性相关。可以使用分层抽样等采样技术来确保所有情感类型在训练集中得到充分代表。
-
理解特征表示的限制:传统的特征表示方法,如 TF-IDF,可能无法完全捕捉复杂的情感细微差别,尤其是在整体情感不是其各部分之和的情况下。
-
增强模型中的上下文特征:通过结合上下文线索如 n-gram 或词性标注来丰富特征集。这些添加有助于通过考虑词序和语法结构来捕捉情感细微差别。
-
探索更稳健的机器学习算法:除了像逻辑回归和朴素贝叶斯这样的简单方法之外,集成方法如随机森林和提升算法(XGBoost)能更好地捕捉复杂模式。此外,对逻辑回归进行超参数调整(例如,调整正则化强度)可以显著提高性能。深度学习方法如 CNN 和 RNN 通常提供最佳性能,前提是有足够的训练示例。
在探索了各种增强传统情感模型的技术,包括解决数据差距、特征表示和适当的算法之后,我们现在将注意力转向情感分析领域的一项更现代的进步,即使用预训练的 LLM。
使用预训练的 LLM
在应用预训练的 LLM 进行情感分析之前,理解嵌入的概念非常重要,这些嵌入是这些高级模型的基础。本质上,嵌入是数据的密集向量表示,可以是任何东西,从单词和整个文档到图像和关系数据。这些向量被设计用来在多维空间中捕捉数据的关键特征。
自然语言处理嵌入的早期例子包括 Word2Vec 和 GloVe,它们生成静态嵌入。在 Word2Vec 中,嵌入受到局部上下文的影响,通过跳字模型和连续词袋模型(CBOW)等技术,但一旦训练完成,相同的词无论在更广泛的上下文中都有相同的向量表示。然而,像 BERT 和 GPT 这样的最先进 LLM 引入了真正的上下文嵌入,其中词的表示根据其上下文使用动态变化。有效的 NLP 嵌入的关键属性是它在向量空间中保留了原始数据的语义关系,这意味着相似的向量(单词、短语或文档)比不相似的数据更接近。
将 LLM 应用于情感分析标志着自然语言处理领域的一项重大进步,简化了理解复杂文本数据的过程。这些模型通过在多样化的数据集上进行广泛的预训练,在捕捉人类语言的细微差别方面表现出色,从而绕过了复杂的文本预处理、超参数调整——甚至预标注数据的需要。对于市场营销专业人士来说,这意味着更有效地衡量不同平台上的客户情绪。
实施预训练模型
为了展示预训练 LLM 的有效性,我们将使用 Transformers 库中 distilbert-base-uncased-finetuned-sst-2-english
模型的 sentiment-analysis
管道。DistilBERT 是 BERT 的一个更小、更快的版本,保留了 95% 的上下文嵌入性能。这个特定的变体在斯坦福情感树库(SST-2)数据集上进行了微调,这是一个情感分析的标准基准,包含带有人类标注的正面
或负面
情感的电影评论。
由于其二元分类性质,中性
情感被排除在我们的测试集之外,以使模型在正面
和负面
之间的可用预测保持一致。在这个例子中,我们还将time
和tqdm
模块纳入我们的代码中,以跟踪执行时间。在模型加载后,使用sentiment-analysis
管道对所有测试文本进行推理可能需要几分钟才能完成:
from tqdm.auto import tqdm
import time
filtered_df = df[df['airline_sentiment'] != 'neutral']
X = filtered_df['text']
y = filtered_df['airline_sentiment']
X_train_texts, X_test_texts, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
sentiment_pipeline = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
start_time = time.time()
results = []
for text in tqdm(X_test_texts, desc="Analyzing sentiments"):
result = sentiment_pipeline(text)
results.append(result[0]['label'].lower())
end_time = time.time()
total_time = end_time - start_time
print(f"Total time for analyzing {len(X_test_texts)} tweets: {total_time:.2f} seconds")
使用 DistilBERT 的 sentiment-analysis
管道简化了情感分析过程,因为它将几个复杂的步骤封装成一个高效的过程。如果没有这个,标记化、文本嵌入、推理和后处理都需要单独处理。
评估模型性能
可以使用以下代码从 LLM 推理中获取分类报告:
print(classification_report(y_test, results))
这产生了以下结果:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_10.png
图 5.10:评估 LLM 性能的分类报告指标
接下来,让我们生成混淆矩阵:
cm = confusion_matrix(y_test, results, labels=['negative', 'positive'])
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['negative', 'positive'], yticklabels=['negative', 'positive'])
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
这给出了以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_11.png
图 5.11:LLM 对推文情感类别的混淆矩阵
当比较使用 TF-IDF 特征的前一个逻辑回归模型和我们的预训练 LLM 的性能时,出现了一些观察结果。虽然逻辑回归模型为情感分析提供了一个强大的基线,但在处理自然语言的细微差别方面存在局限性。虽然类别不平衡可能在模型性能问题中起到了一定作用,但重要的是要注意,LLM 理解语言细微差别的能力,源于其广泛的预训练和微调,可能在其优越性能中发挥了更大的作用。此外,逻辑回归模型仅使用了 TF-IDF 的单词,这未能捕捉到上下文,并导致了错误的描述。因此,它在分类主导的负面情感类别中的正面(和中性)情感分类上尤其困难。
LLM(考虑到我们排除了中性类别以保持与微调过程性质的一致性)表现出更稳健的性能。
这通过其在区分积极和消极情感方面的提高准确性得到展示。需要注意的是,逻辑回归模型从不平衡数据开始,以说明其对结果的影响,而 LLM 没有在 Twitter 数据上进行任何特定任务的训练,而是在 SST-2 电影评论上进行了训练。关键要点是 LLM 的知识被有效地推广,从电影评论到 Twitter 数据,突显了其强大的语言理解能力。对于消极情感的精确度为0.96
,召回率为0.90
,该模型强调了利用预训练神经网络进行情感分析的可能性。在积极情感检测方面的改进,精确度达到0.68
,召回率为0.84
,进一步强调了模型的预测能力。
使用像 LLM(大型语言模型)这样的高级机器学习模型的一个缺点是它们缺乏可解释性。理解这些黑盒模型中确切是什么创造了负面情绪可能具有挑战性。可以使用如**LIME(局部可解释模型无关解释)**等技术来提高可解释性。有关模型透明度和阐明 LLM 决策的技术更详细讨论,请参阅第十三章。
虽然 LLM 的表现值得注意,但将 LLM 微调到我们航空公司推文数据集中存在的特定情感标签将进一步提高其性能。微调使模型更紧密地适应任务的独特上下文,使模型能够利用并理解分类任务的细微差别。
除了微调之外,迁移学习和少样本学习是进一步细化模型在情感分类上高精度分类能力的强大方法。迁移学习涉及将预训练模型在相关任务上调整以在目标任务上表现良好,即使只有最少量的额外训练数据。相反,少样本学习训练模型仅使用少量目标任务的示例来做出准确预测。
改进 LLM:迁移和少样本学习
在本书的第四部分中,我们将更深入地探讨这些尖端方法。我们将探讨如何将微调、迁移学习和少样本学习应用于预训练模型,将它们转化为针对特定领域任务(如情感分类)的高度专业化的工具。
当我们到达本书的第四部分时,我们将更深入地探讨适应预训练模型以用于特定领域任务的尖端方法。
将情感转化为可操作的见解
到目前为止,在本章中,我们已经探讨了理解和应用情感分析到您的数据中所需的工具和策略,从使用传统 NLP 方法的数据准备和预测的基础技术到 GenAI 的高级功能。在本章的最后一部分,我们将讨论如何分析这些见解以生成可操作的战略,这些战略可以指导品牌在营销活动的各个阶段取得成功。
创建您的数据集
在将此分析应用于您的用例之前,我们需要一种收集数据的方法,该方法能够捕捉与您的品牌相关的潜在客户情绪。虽然本章以 Twitter 航空数据集为例,但我们探讨的技术适用于任何行业或数据源。本节将介绍您可以采取的一般步骤,以创建用于分析的专有数据集,无论这些数据来自 Twitter 还是其他主要数据平台。
人工智能赋能营销中的伦理和治理
在人工智能赋能的营销中的伦理和治理是第十三章的主题,在收集数据时遵守尊重消费者隐私和数据保护法律的伦理准则和治理框架至关重要。营销中的伦理实践包括为数据收集获得同意,确保数据匿名化以保护个人身份,以及提供明确的退出机制。公司应建立强大的数据治理政策,定义数据处理程序和遵守如欧盟的通用数据保护条例(GDPR)或加利福尼亚消费者隐私法案(CCPA)等法律标准。
收集 Twitter 数据
要开始,请确保您拥有 Twitter 开发者账户并可以访问 Twitter(现在更名为 X)API。您可以遵循以下步骤:
-
您首先需要创建一个项目并获取您的 API 密钥和令牌,然后,使用您的 Twitter 开发者账户的凭证(
developer.twitter.com/en/portal/petition/essential/basic-info
),验证您的会话以访问 Twitter API。以下是一般步骤:!pip install tweepy import tweepy # Replace these with your API keys and tokens consumer_key = 'YOUR_CONSUMER_KEY' consumer_secret = 'YOUR_CONSUMER_SECRET' access_token = 'YOUR_ACCESS_TOKEN' access_token_secret = 'YOUR_ACCESS_TOKEN_SECRET' auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth)
-
现在我们已经可以访问 Twitter API,您可以使用与您的品牌相关的 Twitter 账号或相关标签,并结合
search_tweets
方法来查找相关推文。以下示例收集了提及"@YourBrandHandle"
的最新 100 条推文:query = "@YourBrandHandle -filter:retweets" tweets = api.search_tweets(q=query, lang="en", count=100)
由于最近的变化,Twitter(X)API 可能访问受限,某些端点可能需要提高访问级别。如果您遇到403
Forbidden
错误,您可能需要升级访问级别或使用 API 文档中提供的替代端点。更多详情可以在 Twitter 开发者门户(developer.twitter.com/en/portal/petition/essential/basic-info
)找到。
-
收集推文后,我们可以提取相关信息,例如推文 ID、文本、创建时间和位置。然后我们可以将这些数据结构化为
pandas
DataFrame,以便更容易分析:data = [{ 'tweet_id': tweet.id, 'text': tweet.text, 'tweet_created': tweet.created_at, 'tweet_location': tweet.user.location, } for tweet in tweets] your_brand_df = pd.DataFrame(data)
API 响应文档中还有许多其他值得考虑的元数据字段,包括转发计数(tweet.retweet_count
)、标签(tweet.entities['hashtags']
)和提及(tweet.entities['user_mentions']
)。如以下章节所述,这些字段可以为理解您的品牌情感叙事提供有价值的见解,包括显著话题、推文参与度和病毒性。
从其他平台收集数据
分析品牌情感不仅限于 Twitter,还包括 Facebook、Instagram、Google 评论等多种社交媒体平台。每个平台都面临着独特的挑战和机会,以收集和分析数据。收集数据的方法根据每个平台的 API 功能和数据可用性而有所不同。对于 Google 评论等平台,API 可能允许您直接访问评论和评分。在 Facebook 和 Instagram 等平台上,您可能需要依赖帖子、评论和标签来衡量情感。
访问开发者 API
要从不同的社交媒体平台收集数据,您需要访问它们各自的开发者 API。以下是一些有用的链接,以帮助您开始:
-
Reddit:
www.reddit.com/dev/api/
-
YouTube:
developers.google.com/youtube/v3
一旦获取数据,一个主要挑战就是准确识别与您的品牌相关的提及和讨论。这正是命名实体识别(NER)和实体映射技术发挥作用的地方。NER 可以帮助识别文本中的专有名词,如品牌名称或产品,而实体映射可以将这些提及与数据集中的您的品牌联系起来。
在一个虚构零售商的数据集上执行命名实体识别(NER)
例如,让我们考虑一系列来自虚构在线零售商 Optimal Hiking Gear(销售户外装备)的客户评论。为了提取品牌提及,我们可以使用 spaCy 语言模型内建的 NER 功能,并查找其ORG
标签以识别相关提及:
nlp = spacy.load("en_core_web_sm")
reviews = [
"I recently purchased a sleeping bag from Optimal Hiking Gear and it exceeded my expectations.",
"The tent I bought from Optimal Hiking was damaged on arrival. Very disappointed.",
"The Optimal Hiking company makes a backpack that's the best. I've been using mine for years without any issues."
]
for review in reviews:
doc = nlp(review)
for ent in doc.ents:
print(f"Entity: {ent.text}, Label: {ent.label_}")
这会产生以下结果:
Entity: Optimal Hiking Gear, Label: ORG
Entity: Optimal Hiking, Label: ORG
Entity: Optimal Hiking, Label: ORG
Entity: years, Label: DATE
通过定制训练增强 NER
为了使 NER 模型更贴近您的需求,考虑使用您的数据进行训练。这包括提供一些文本示例,其中包含您品牌或行业特有的手动标注实体。通过这样做,模型可以更准确地识别和分类这些自定义实体。spaCy 等工具提供了训练您的 NER 模型的功能。
理解主题和主题
本节深入探讨了各种工具,以从您的数据集中提取见解,概述关键主题和主题。这些见解对于理解数据中客户情绪的更广泛背景至关重要。作为一个起点,我们可以参考该数据集标注者标记的负面情感的不同原因的数量:
df.negativereason.value_counts()
这给我们以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_12.png
图 5.12:Twitter 数据集中不同负面情感推文原因的计数
使用词云
词云,其中每个词在图中的大小对应其出现的频率,是可视化文本数据的有价值起点工具。为了丰富我们的词云并确保可视化不仅反映最频繁的术语,还反映最能指示独特情感和主题的术语,我们将结合 TF-IDF 分析来生成我们的图。通过将 ngram_range=(1, 2)
参数引入我们的 tfidf_vectorizer
,我们创建了单语和双语。利用 WordCloud
库,我们可以通过以下方式创建词云:
from wordcloud import WordCloud
tfidf_vectorizer = TfidfVectorizer(max_features=1000, ngram_range=(1, 2))
tfidf_matrix = tfidf_vectorizer.fit_transform(df['final_text'])
tfidf_scores = dict(zip(tfidf_vectorizer.get_feature_names_out(), tfidf_matrix.sum(axis=0).tolist()[0]))
wordcloud_tfidf = WordCloud(width=800, height=400, background_color='white').generate_from_frequencies(tfidf_scores)
plt.figure(figsize=(10, 5))
plt.imshow(wordcloud_tfidf, interpolation='bilinear')
plt.axis('off')
plt.show()
我们接下来会看到类似以下这样的词云:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_13.png
图 5.13:显示 TF-IDF 分析中不同术语出现频率的词云
到目前为止的分析通过诸如 delay
、cancel
和 help
等常见词汇揭示了关键主题。通过包含双语,我们还可以捕捉到诸如 customer service
和 cancel flight
这样的关键术语。在 Twitter 数据的背景下,标签提供了直接了解核心主题和情感的方法。为了深入了解,我们将继续从推文中提取标签,创建一个词云来以不同的方式可视化这些关键短语。这种方法旨在为我们数据集中的主题提供不同的视角:
import nltk
def extract_hashtags(text):
return re.findall(r"#(\w+)", text)
hashtags = sum(df['text'].apply(extract_hashtags).tolist(), [])
hashtag_freq_dist = nltk.FreqDist(hashtags)
wordcloud_hashtags = WordCloud(width=800, height=400, background_color='white').generate_from_frequencies(hashtag_freq_dist)
plt.figure(figsize=(10, 5))
plt.imshow(wordcloud_hashtags, interpolation='bilinear')
plt.axis('off')
plt.show()
我们接下来看到以下内容:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_14.png
图 5.14:显示 Twitter 标签出现频率的词云
排除直接提及航空公司的情况,词云通过诸如 #Fail
、#Disappointed
和 #BadCustomerService
这样的标签清晰地突出了普遍的负面情绪。与这些相反,#DestinationDragons
也显著出现,这标志着西南航空公司的广为人知的旅游活动,展示了我们数据集中捕获的客户反馈的双重性。
使用 LDA 发现潜在主题
潜在狄利克雷分配(LDA)是一种超越简单频率指标的高级技术,如词云中所示,它通过无监督机器学习识别文本语料库中的潜在主题。与仅突出最频繁单词的词云不同,LDA 通过将文档视为主题的混合体来辨别隐藏的主题结构,其中每个主题由一组特定的单词定义。这个过程涉及贝叶斯推理,使用狄利克雷分布来估计文档中主题的存在及其比例。例如,在酒店评论集合中,LDA 可能会识别与位置、停车、浴室清洁和入住体验相关的主题。LDA 的复杂性在于其能够捕捉语料库中单词的上下文和共现,从而在不进行预先标记的情况下提供对文本主题内容的更细致的理解。
我们可以通过以下方式实施这种方法:
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer(max_df=0.95, min_df=2, stop_words='english')
doc_term_matrix = count_vect.fit_transform(df['final_text'])
LDA = LatentDirichletAllocation(n_components=5, random_state=42)
LDA.fit(doc_term_matrix)
for i, topic in enumerate(LDA.components_):
print(f"Top words for topic #{i}:")
print([count_vect.get_feature_names_out()[index] for index in topic.argsort()[-10:]])
print("\n")
这给我们以下输出:
Top words for topic #0:
['fly', 'follow', 'send', 'dm', 'flight', 'hour', 'sit', 'gate', 'seat', 'plane']
Top words for topic #1:
['tomorrow', 'change', 'amp', 'time', 'late', 'delay', 'fly', 'flightle', 'cancel', 'flight']
Top words for topic #2:
['response', 'bad', 'good', 'time', 'flight', 'bag', 'great', 'customer', 'service', 'thank']
Top words for topic #3:
['wait', 'need', 'help', 'problem', 'delay', 'luggage', 'hour', 'miss', 'bag', 'flight']
Top words for topic #4:
['service', 'number', 'customer', 'minute', 'email', 'hour', 'help', 'try', 'phone', 'hold']
分析 LDA 生成的聚类使我们能够确定推文中的重要主题,例如在主题#1
和#2
中分别表示的航班延误和客户服务问题。通过改变n_components
参数,我们可以改变算法对存在主题数量的假设,随着参数值的增加,导致更细粒度的主题。
重要的是要认识到,LDA 识别出的所有主题并不都会直接与关键情感表达相一致,其中一些可能只是简单地反映了数据集中普遍存在的、非特定的语言,正如在主题#0
中看到的那样。
为了提取有意义的见解,通常需要额外的审查来辨别哪些主题对于分析最为相关。这个过程可以通过人工审查或通过为语言模型设计一个精心设计的提示来促进,帮助根据可能的主题总结单词分组。
时间趋势:追踪品牌叙事
在社交媒体的动态领域,推文的病毒性往往充当衡量对品牌感知最具影响力的情感的温度计。这对于负面情感尤其如此,因为它们对品牌形象的风险很大。通过审查围绕航空公司(如 JetBlue)的 Twitter 活动,我们可以揭示可能显著影响品牌声誉的推文。
本文介绍了一种基于社交媒体的品牌声誉追踪器,该追踪器实时监控品牌事件,并将它们与品牌声誉的具体驱动因素相连接:ora.ox.ac.uk/objects/uuid:00e9fcb7-9bf1-486a-b4dd-3c1d086af24e/files/rz316q188f
。
这项分析涉及筛选提及@JetBlue
账号的推文。下面的代码将随着时间的推移对这些推文的情感进行分类,其中每个数据点的气泡大小对应于那天总的转发量,提供了一个参与度的视觉尺度:
df['tweet_created'] = pd.to_datetime(df['tweet_created']).dt.tz_convert(None)
df['date'] = df['tweet_created'].dt.date
airline_handle = "@JetBlue"
airline_tweets = df[df.text.str.contains(airline_handle)]
grouped = airline_tweets.groupby(['airline_sentiment', 'date']).agg({'tweet_id':'count', 'retweet_count':'sum'}).reset_index()
positive_tweets = grouped[grouped['airline_sentiment'] == 'positive']
neutral_tweets = grouped[grouped['airline_sentiment'] == 'neutral']
negative_tweets = grouped[grouped['airline_sentiment'] == 'negative']
plt.figure(figsize=(14, 7))
scale_factor = 3
for tweets, sentiment, color, linestyle in zip(
[positive_tweets, neutral_tweets, negative_tweets],
['Positive', 'Neutral', 'Negative'],
['green', 'orange', 'red'],
['-', '--', '-.']
):
scaled_retweet_count = tweets['retweet_count'] * scale_factor
plt.plot(tweets['date'], tweets['tweet_id'], linestyle=linestyle, label=sentiment, color=color)
plt.scatter(tweets['date'], tweets['tweet_id'], scaled_retweet_count, color=color)
plt.title(f'Daily Sentiment Trend for {airline_handle} with Bubble Size Indicating Retweets')
plt.xlabel('Date')
plt.ylabel('Number of Tweets')
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
这产生了以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_15.png
图 5.15:JetBlue 推文随时间变化的情感,其中每个数据点的气泡大小对应于那天总的转发量
在 2 月 22 日和 23 日观察到显著的负面情感推文高峰,后者转发量的异常激增表明广泛的参与。那些关注 JetBlue 品牌声誉的人可能会发现深入研究这些推文的细节很有价值。
我们可以根据转发量最高的推文,以及在此日期范围内以及前一天,对这些推文进行汇总和排名,以下代码可以实现:
dates_of_interest = [pd.to_datetime('2015-02-22').date(), pd.to_datetime('2015-02-23').date(), pd.to_datetime('2015-02-24').date()]
filtered_df = airline_tweets[(airline_tweets['date'].isin(dates_of_interest)) & (airline_tweets['airline_sentiment'] == 'negative')]
top_tweets_per_date = filtered_df.groupby('date').apply(lambda x: x.nlargest(3, 'retweet_count'))
top_tweets_per_date[['text', 'retweet_count', 'negativereason']]
这给我们以下结果:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_16.png
图 5.16:2015 年 2 月 22 日至 24 日 JetBlue 负面情感推文高峰的分析
结果的第一天和最后一天揭示了典型的抱怨,如晚点航班和糟糕的服务,这在航空业很常见。然而,2 月 23 日的反弹突显了 JetBlue 一项营销活动的争议,该活动被短语“我们的机队很酷”所概括。正如本章引言中讨论的,实时监控品牌认知将使他们能够及早跟踪这种营销情感的变化,使他们能够预测并可能减轻随之而来的公关问题。
这种负面宣传的潜在长期影响需要进一步的监控和分析——使用第二章中讨论的 KPI 等指标——以评估对品牌声誉的影响。尽管如此,这项分析说明了随着时间的推移监控社交媒体趋势的重要性,以便提前解决可能损害品牌认知的问题。
使用地理空间分析映射情感
地理空间分析提供了一个强大的视角,通过这个视角可以观察客户情感,使公司能够识别出关注点——从客户服务问题到设计不佳的市场营销活动——甚至在这些问题对现场工作人员变得明显之前。为了说明如何生成这样的见解,让我们利用folium
包创建一个热图,该热图基于推文坐标精确指出负面情感的来源:
!pip install folium
import folium
from folium.plugins import HeatMap
filtered_df = df[(df['text'].str.contains('@JetBlue') & (df['airline_sentiment'] == 'negative'))]
filtered_df = filtered_df.dropna(subset=['tweet_coord'])
valid_coords = []
for coord in filtered_df['tweet_coord']:
try:
lat, long = eval(coord)
valid_coords.append((lat, long))
except (TypeError, SyntaxError, NameError):
continue
if valid_coords:
map_center = [sum(x)/len(valid_coords) for x in zip(*valid_coords)]
else:
map_center = [0, 0]
tweet_map = folium.Map(location=map_center, zoom_start=4)
HeatMap(valid_coords).add_to(tweet_map)
tweet_map
这段代码产生了以下地图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_05_17.png
图 5.17:基于推文坐标的热图,精确指出负面 JetBlue 情感的来源
生成的热图揭示了 JetBlue 主要运营区域中负面情感的集中——这一相关性是预期的,因为更多的航班自然会导致更多负面经历的报告。然而,通过观察这些模式的时间演变,我们可以看到这种方法可以作为实时工具来发现异常的情感模式。
例如,来自新位置的负面推文的突然涌入可能表明服务问题,并预示着可能出现的公关挑战。相反,识别出拥有大量正面推文的高频区域可能凸显出公司内的优势。将地理空间分析与之前介绍的主题建模方法相结合,也可以解锁更多见解,不仅揭示讨论的内容,还揭示这些对话发生的地方,提供宝贵的可操作营销情报。
摘要
本章强调了情感分析在现代营销策略中的重要性。它介绍了情感分析作为一项关键工具,用于解释大量非结构化文本数据,例如社交媒体对话,以精炼营销策略、品牌信息或客户体验。通过利用 Twitter 航空公司数据集,我们涵盖了将情感分类为正面或负面的端到端流程,使用了传统的 NLP 和更先进的涉及预训练 LLMs 的 GenAI 方法。然后,我们介绍了一系列工具,用于可视化这些结果,以得出可操作的营销见解。本章应该使你具备有效利用情感分析所需的基本技能,适用于从品牌声誉监控到将营销信息与客户偏好对齐的各种应用。
展望下一章,我们将从理解客户情感进步到利用预测分析积极塑造客户参与度,重点关注通过 A/B 测试对营销策略的实证验证。我们将讨论识别预测客户参与度的特征、训练机器学习模型、模型评估以及 A/B 测试的实施。本章旨在通过提供特征选择、构建预测模型、优化模型性能、进行 A/B 测试以及将见解融入有效营销策略的技能来提升你的知识。这一步将使你不仅能够预测客户行为,而且能够通过实证验证和精炼营销策略以提高其有效性。
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人交流,并与其他 5000 多名成员一起学习:
第六章:利用预测分析和 A/B 测试提升客户参与度
我们可以通过多种方式从数据驱动和 AI/ML 驱动的营销技术中受益。例如,您可以根据我们已在第二章和第三章中讨论的先前营销活动的成功和失败背后的关键驱动因素来优化您的营销策略。您还可以根据您业务中的趋势和季节性,或者根据您产品和业务周围的客户情绪来优化您的营销策略,正如我们在第四章和第五章中所讨论的那样。目标产品推荐(第七章)和利用生成式 AI 优化营销内容(第九章和第十章)是应用 AI/ML 在营销中的其他一些关键好处。
在所提及的主题中,我们将在本章中尝试预测分析,以及如何在您的下一次营销活动中利用这些预测模型。通过智能预测客户的预期行为,您可以针对可能对您有利的客户群体进行定位。这样,您就不必对整个潜在客户群进行大规模营销,而可以更好地定制您的营销信息,并且由于您只针对成功率更高的群体,因此还可以节省营销成本。我们还将讨论如何进行 A/B 测试来决定您下一次营销活动的最佳预测模型。
在本章中,我们将涵盖以下主题:
-
使用基于树的算法预测客户转化
-
使用深度学习算法预测客户转化
-
进行 A/B 测试以选择最佳模型
使用基于树的算法预测客户转化
预测分析或建模可以在客户生命周期的各个阶段应用。如果您还记得第二章,我们可以将客户生命周期分解为以下五个主要阶段:意识、参与、转化、保留和忠诚度,如下面的图所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_01.png
图 6.1:第二章中的客户生命周期图
预测建模的应用范围很广,取决于你的营销目标。例如,如果你有一个新品牌或产品发布,并希望通过社交媒体上的广告提高新产品知名度,你可以构建预测模型来帮助你识别可能点击广告的目标客户。另一方面,如果你想提高产品购买转化率,你可以构建预测模型来识别在接下来的 X 天内更有可能进行购买的客户,并针对他们进行营销。这会导致更有效的营销,因为你可以避免客户因频繁接触不相关的内容而感到疲劳,这种情况通常发生在你没有针对正确的客户子群体进行大规模营销时。此外,你可以通过只向特定的客户子群体发送营销材料来降低营销成本。这将帮助你将剩余的营销预算重新用于其他营销活动。
不仅你可以利用预测分析来提高品牌知名度、参与度和转化率,预测分析还可以用来提高客户保留率。通常,会建立客户流失可能性模型来识别哪些客户有离开你生意的风险。通过这些客户流失预测,你可以构建针对这一高流失风险群体的定制化营销策略和营销内容,以将他们重新变为活跃客户。折扣、免费订阅试用或免费计划升级通常作为保留策略的一部分提供给这一高流失风险群体。
基于树的机器学习算法
可以使用多种 AI/ML 算法进行预测建模,例如线性回归、逻辑回归和决策树模型,这些我们在前面的章节中已经讨论过,以及正在日益普及的深度学习模型。在本章中,我们将使用基于树的模型构建预测模型,例如随机森林和梯度提升树,以及神经网络模型,它们是深度学习模型的核心。任何基于树的机器学习模型下面都有一个决策树。正如我们在第三章中讨论的,决策树就像一个流程图,它根据获得的信息分成子节点。每个节点代表一个分割的问题或标准,每个分支或边代表在节点提出的问题的结果。以下图表展示了决策树可能构建的高级概述:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_02.png
图 6.2:决策树的示意图
在众多的基于树的机器学习模型中,梯度提升决策树(GBDT)和随机森林是最常用的两种模型,经常用于预测建模。GBDT 和随机森林模型都是由多个决策树构建的。然而,主要区别在于这些决策树是如何构建的。
简而言之,随机森林模型是一个包含许多决策树的模型,其中每个决策树都是使用数据集的随机子样本和特征子集构建的。这样,随机森林中的每个决策树都会以略微不同的方式学习数据中的信息或关系,并具有不同的关注区域。
最终预测是这些单个决策树的所有结果或预测的平均值。以下是一个随机森林的示意图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_03.png
图 6.3:随机森林的示意图
与此同时,GBDT 模型也由许多决策树组成,但每个决策树是顺序构建的,每个后续的决策树都是基于前一个决策树所犯的错误进行训练的。GBDT 模型的最终预测是所有单个决策树预测的加权平均值。以下是一个 GBDT 模型的示意图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_04.png
图 6.4:GBDT 的示意图
构建随机森林模型
在本章中,我们将使用在线购买数据集作为示例来构建一个预测模型,以预测客户是否会进行转换。首先,我们将讨论如何使用scikit-learn
包在 Python 中构建随机森林模型。
源代码和数据:github.com/PacktPublishing/Machine-Learning-and-Generative-AI-for-Marketing/tree/main/ch.6
数据来源:archive.ics.uci.edu/dataset/468/online+shoppers+purchasing+intention+dataset
目标变量和特征变量
我们首先需要定义目标和特征变量,其中目标变量是我们想要预测的因素,特征变量是模型将学习以进行预测或决策的因素。为此,你可以遵循以下步骤:
-
让我们先加载数据到一个 DataFrame 中,并检查我们可以用于我们的随机森林模型的特征:
import pandas as pd df = pd.read_csv("./data.csv") df.info()
当你运行此代码时,你应该看到以下输出,这是关于此数据的信息:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_05.png
图 6.5:示例数据集摘要
-
在前面的输出中,首先要注意的是列,
Revenue
,这是目标变量,它告诉我们客户是否进行了购买或转换,其类型为布尔型
。另一列,Weekend
,也具有布尔型
数据类型。我们将使用以下代码将它们编码为0
表示 False 和1
表示 True:df["Revenue"] = df["Revenue"].astype(int) df["Weekend"] = df["Weekend"].astype(int)
-
然后,有两列的数据类型为
object
,分别是Month
和VisitorType
。如果你仔细观察,Month
列的月份值是字符串,我们将将其转换为相应的月份数字。VisitorType
列有三个唯一值,New_Visitor
、Returning_Visitor
和Other
。我们将分别将其编码为0
、1
和2
,如下所示:from time import strptime df["MonthNum"] = df["Month"].apply(lambda x: strptime(x[:3],'%b').tm_mon) df["VisitorTypeNum"] = df["VisitorType"].apply( lambda x: 0 if x == "New_Visitor" else 1 if x == "Returning_Visitor" else 2 )
如此代码所示,我们正在使用 time
模块的 strptime
函数将三个字母的月份字符串值编码为相应的月份数字。然后,我们使用 pandas
DataFrame 的 apply
函数将 VisitorType
列的每个值编码为相应的整数值。
-
现在我们已经将所有列值转换为数值,我们将定义目标和特征变量,如下所示:
TARGET = "Revenue" FEATURES = [ 'Administrative', 'Administrative_Duration', 'BounceRates', 'Browser', 'ExitRates', 'Informational', 'Informational_Duration', 'MonthNum', 'OperatingSystems', 'PageValues', 'ProductRelated', 'ProductRelated_Duration', 'Region', 'SpecialDay', 'TrafficType', 'VisitorTypeNum', 'Weekend' ] X = df[FEATURES] Y = df[TARGET]
如此代码所示,我们已定义目标变量 TARGET
使用 Revenue
列,其余列作为特征变量 FEATURES
。然后,我们创建了一个 DataFrame X
,它是特征集,以及 Y
,它是目标序列。
-
最后,我们将使用以下代码将这些目标和特征集拆分为训练集和测试集:
from sklearn.model_selection import train_test_split train_x, test_x, train_y, test_y= train_test_split( X, Y, test_size=0.2 )
我们正在使用 sklearn.model_selection
模块中的 train_test_split
函数。从 test_size
参数可以看出,我们正在使用数据集的 80%进行训练,其余 20%用于测试。
使用这些训练集和测试集,我们现在可以准备训练一个随机森林模型。
训练随机森林模型
Python 的 scikit-learn
包提供了一个方便的方式来创建随机森林模型。看看以下代码:
from sklearn.ensemble import RandomForestClassifier
rf_model = RandomForestClassifier(
n_estimators=250, max_depth=5, class_weight="balanced", n_jobs=-1
)
rf_model.fit(train_x, train_y)
让我们更仔细地看看这段代码。我们正在使用 sklearn.ensemble
模块中的 RandomForestClassifier
类,并使用 n_estimators
、max_depth
、class_weight
和 n_jobs
参数初始化一个随机森林模型:
-
n_estimators
参数定义了要构建多少个单独的决策树。 -
max_depth
参数定义了每个决策树可以生长多深。与其他参数,如min_samples_split
、min_samples_leaf
和max_features
一起,max_depth
通过限制决策树的生长来帮助防止过拟合问题。 -
class_weight
参数定义了每个类的权重。当数据集不平衡时,此参数非常有用。在我们的示例数据集中,只有大约 15%属于正类,这意味着只有 15%的目标变量,收入,其值为 1,或者只有 15%的客户已经转化。你可以为每个类提供自定义权重,作为一个字典,或者使用"balanced"
选项来自动调整与实际类频率成反比的权重。 -
最后,
n_jobs
参数定义了要并行运行多少个作业。如果您还记得我们关于随机森林和 GBDTs 的讨论,随机森林是一系列决策树,因此可以并行构建单个树,而不依赖于其他树。通过将-1
作为此参数的输入,您指示它使用所有可用资源来训练此随机森林模型。
使用这些参数,我们现在可以使用 fit
函数和训练集来训练这个随机森林模型。
过拟合与欠拟合?
过拟合是指模型拟合到训练集过于紧密,在训练数据上表现良好,但在模型之前未见过的数据上表现较差。另一方面,欠拟合是指模型过度泛化或没有足够地调整到训练集,以至于没有学习到特征变量和目标变量之间的关系。通常需要多次迭代超参数调整,以找到最小过拟合的最佳点。
预测和评估随机森林模型
RandomForestClassifier
对象提供了从训练好的随机森林模型进行预测的便捷函数。请看以下代码:
rf_pred = rf_model.predict(test_x)
rf_pred_proba = rf_model.predict_proba(test_x)[:,1]
如其名所示,predict
函数对给定的输入进行预测。在我们的例子中,结果将是测试集中每个记录的 0 和 1 的列表,因为我们正在预测客户是否已经转换。
predict_proba
函数也对给定的输入进行预测,但不同之处在于它给出介于 0
和 1
之间的预测概率。它为每个记录和每个类别返回预测概率,因此,在我们的例子中,它为每个记录返回两个值,其中第一个元素是预测为类别 0
的概率,第二个元素是预测为类别 1
的概率。由于我们只对类别 1 的预测概率感兴趣,我们使用 [:,1]
来切片,这样我们就有了一个转换预测概率的列表。
现在我们有了预测的转换概率,我们需要评估我们的预测有多好。
评估预测模型的准确性和有效性的方法有很多,但我们将主要关注整体准确率、精确率、召回率、曲线下面积(AUC)- 接收者操作特征(ROC)曲线和混淆矩阵。我们将通过示例深入探讨这些指标。
正如其名所示,准确率是所有预测中正确预测或真正例(TP)的百分比。精确率是预测正例中正确预测的百分比,或者是预测为正例中包括假正例(FP)的真正例的百分比。召回率是模型识别出的正例的百分比,或者是实际正例中包括真正例和假负例的真正例的百分比。准确率、精确率和召回率的公式如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_001.png
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_002.png
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_003.png
在 Python 中,scikit-learn 包提供了一个方便的工具来计算这些指标,如下面的代码所示:
from sklearn import metrics
accuracy = (test_y == rf_pred).mean()
precision = metrics.precision_score(test_y, rf_pred)
recall = metrics.recall_score(test_y, rf_pred)
在我们的例子中,当运行这些代码时,这些关键指标的结果如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_06.png
图 6.6:随机森林模型性能指标摘要
这些结果表明,我们训练的随机森林模型具有相当的整体准确率和召回率,但在精确率方面似乎表现不佳。这表明,在这个模型预测为正例或可能转换的客户中,只有大约 54%实际上已经转换。然而,如果你还记得,实际的总体转换率是 15%;换句话说,如果你随机猜测谁会转换,你可能只有 15%的时间是正确的。
因此,由于这个模型有 54%的时间预测了转换客户,这证明了它在选择更有可能转换的客户方面比随机猜测要有效得多。此外,高召回率表明,实际上已经转换的客户中有大约 85%是那些被这个模型预测为高度可能转换的客户。
从实际的市场营销角度来看,如果你只针对这个模型预测可能转换的客户进行营销,你仍然会捕获到大多数这些转换。此外,如果你针对你的整个客户群进行营销,85%(100%减去 15%,这是整体转换率)的营销支出将会浪费。但是,如果你只针对这些高度可能的客户进行营销,只有大约 46%(100%减去 54%,这是该模型的精确度)的营销支出将会浪费。
我们将要关注的另一个关键评估指标是AUC-ROC 曲线。简单来说,ROC 曲线显示了在牺牲假正率(FPR)的情况下,每个牺牲所获得的真正率(TPR)的权衡。AUC,正如其名所示,是 ROC 曲线下的面积,告诉我们模型在区分正例和负例方面的表现如何。AUC 的范围从0
到1
,数值越高,模型越好。在 AUC 为0.5
时,表明模型的表现与随机猜测相同。
下面的代码可以用来绘制 ROC 曲线:
dp = metrics.RocCurveDisplay.from_predictions(
test_y,
rf_pred_proba,
name="Conversion",
color="darkorange",
)
_ = dp.ax_.set(
xlabel="False Positive Rate",
ylabel="True Positive Rate",
title="ROC Curve",
)
plt.grid()
plt.show()
在这里,我们再次使用metrics
模块来绘制 ROC 曲线。计算准确率、精确率和召回率与计算 AUC-ROC 曲线之间的主要区别在于我们如何使用预测概率rf_pred_proba
而不是预测标签pred
。这是因为 ROC 曲线检查在不同概率水平下 TPR 和 FPR 如何变化。通过使用预测概率,我们可以计算随着决策阈值的改变,TPR 和 FPR 如何变化。生成的图表如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_07.png
图 6.7:随机森林模型预测的 AUC-ROC 曲线
如您从这张图表中可以看到,您可以轻松评估每个 FPR 的牺牲如何影响 TPR。例如,在这张图表中,在 20% FPR 的情况下,我们已经实现了大约 90%的 TPR,这表明该模型在区分正例和负例方面表现良好。这里的 AUC 为0.93
,这也表明模型在识别正例和负例方面表现良好。
最后,我们将查看混淆矩阵。正如其名所示,混淆矩阵是查看模型在何处以及如何最容易被混淆的好方法。以下是一个示例,可以帮助理解。请看以下代码:
import seaborn as sns
cf_matrix = metrics.confusion_matrix(test_y, rf_pred)
ax = plt.subplot()
sns.heatmap(
cf_matrix,
annot=True,
annot_kws={"size": 10},
fmt="g",
ax=ax
)
ax.set_xlabel("Predicted")
ax.set_ylabel("Actual")
ax.set_title(f"Confusion Matrix")
plt.show()
与之前类似,我们使用metrics
模块构建混淆矩阵。confusion_matrix
函数接受实际值和预测值,并构建混淆矩阵。然后,我们使用seaborn
Python 包中的heatmap
函数绘制热图。生成的图表如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_08.png
图 6.8:随机森林模型预测的混淆矩阵
如您从这张图中可以看到,y 轴表示实际类别,x 轴表示预测类别。例如,左上方的框是实际类别为 0 或无转换,预测类别也是0
的地方。右上方的框是实际类别为 0,但模型预测它们属于类别1
或转换的地方。
如您所见,混淆矩阵显示了模型最困惑的地方。一个预测结果准确率高的模型将在对角线框中有大量数字或百分比,而在其他框中有小数字。
我们已经尝试使用随机森林模型预测客户转换的可能性。在这里,我们观察并讨论了如何使用这个随机森林预测模型帮助定位客户群的一部分,同时不会失去太多已转换的客户,以及这如何导致更具成本效益的营销策略。
梯度提升决策树(GBDT)建模
我们将使用相同的 dataset 和 train/test sets 来构建 GBDT 模型,并将其性能与我们刚刚构建的随机森林模型进行比较。XGBoost 是 Python 中用于训练 GBDT 模型最常用的库。您可以在终端或 Jupyter Notebook 中使用以下命令安装此包:
pip install xgboost
训练 GBDT 模型
XGBoost 包遵循与scikit-learn
包相同的模式。请看以下代码:
from xgboost import XGBClassifier
xgb_model = XGBClassifier(
n_estimators=100,
max_depth=5,
scale_pos_weight=1/train_y.mean(),
)
xgb_model.fit(train_x, train_y)
如此代码所示,我们使用n_estimators
、max_depth
和scale_pos_weight
参数初始化了一个XGBClassifier
:
- 与随机森林模型的情况类似,
n_estimators
参数定义了要构建多少个单个决策树。你可能已经注意到,我们为这个参数使用了一个非常低的数字。如果你还记得,GBDT 模型是顺序构建的,其中每个后续的决策树都会学习前一个决策树犯的错误。这通常会导致更少的单个树表现出与随机森林模型相当或更好的性能。
此外,由于 GBDT 模型是顺序构建的,与可以并行构建单个决策树的随机森林模型相比,大量决策树会导致训练时间更长。
-
另一个参数
max_depth
与随机森林模型的情况相同。此参数限制了每个决策树可以生长的深度。 -
最后,
scale_pos_weight
参数定义了正负类权重的平衡。你可能还记得,我们有一个不平衡的数据集,其中正类仅占数据的约 15%。通过使用1/train_y.mean()
对正类给予成反比的权重,我们指示模型调整不平衡数据集。这强制 GBDT 模型对正类的不正确预测进行更多惩罚,从而使模型对正类更加敏感。
当 GBDT 模型中包含大量单个决策树时,通常会存在潜在的过拟合问题,处理这些问题的方法有很多。除了max_depth
参数之外,XGBoost 包还提供了其他各种参数,例如max_leaves
、colsample_bytree
、subsample
和gamma
,这些参数可以帮助减少过拟合。
我们建议您查看文档,并尝试各种参数,看看它们如何帮助您防止过拟合。
这里是文档:xgboost.readthedocs.io/en/latest/python/python_api.html#xgboost.XGBClassifier
。
预测和评估 GBDT 模型
与随机森林模型的RandomForestClassifier
类似,XGBClassifier
对象也提供了相同的语法来预测训练好的 GBDT 模型。请看以下代码:
xgb_pred = xgb_model.predict(test_x)
xgb_pred_proba = xgb_model.predict_proba(test_x)[:,1]
如此代码所示,你可以使用predict
函数预测测试集中每个记录的正负标签,并使用predict_proba
函数预测测试集中每个个体记录的每个类别的概率。与之前一样,我们通过使用[:,1]
切片输出以获取正类预测概率(即转换的预测概率),来获取测试集中每个记录的预测概率。
为了比较这个 GBDT 模型与随机森林模型的性能,我们将使用相同的评估指标和方法。你可能还记得,我们可以使用以下代码来获取准确率、精确率和召回率的关键指标:
accuracy = (test_y == xgb_pred).mean()
precision = metrics.precision_score(test_y, xgb_pred)
recall = metrics.recall_score(test_y, xgb_pred)
由于构建这些树时存在一些随机性,每次训练 GBDT 模型时都可能会有一些方差和差异,但在此写作时,结果如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_09.png
图 6.9:GBDT 模型性能指标总结
这些关键指标看起来与随机森林模型非常相似。这个 GBDT 模型的整体准确率和精确率略高于随机森林模型。然而,召回率略低于随机森林模型。
同样,可以使用以下代码绘制 AUC-ROC 曲线:
dp = metrics.RocCurveDisplay.from_predictions(
test_y,
xgb_pred_proba,
name="Conversion",
color="darkorange",
)
_ = dp.ax_.set(
xlabel="False Positive Rate",
ylabel="True Positive Rate",
title="ROC Curve",
)
plt.grid()
plt.show()
生成的 AUC-ROC 曲线如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_10.png
图 6.10:GBDT 模型预测的 AUC-ROC 曲线
当你将此与随机森林模型进行比较时,结果几乎相同。AUC 约为0.93
,在 FPR 为0.2
时,GBDT 模型的 TPR 也约为0.9
,这与之前构建的随机森林模型的情况相同。
最后,让我们通过以下代码查看混淆矩阵:
import seaborn as sns
cf_matrix = metrics.confusion_matrix(test_y, xgb_pred)
ax = plt.subplot()
sns.heatmap(
cf_matrix,
annot=True,
annot_kws={"size": 10},
fmt="g",
ax=ax
)
ax.set_xlabel("Predicted")
ax.set_ylabel("Actual")
ax.set_title(f"Confusion Matrix")
plt.show()
生成的混淆矩阵如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_11.png
图 6.11:GBDT 模型预测的混淆矩阵
当你将此与随机森林模型的混淆矩阵进行比较时,你会注意到 GBDT 模型的 TPs 较低,但 GBDT 模型的假阴性也较低。这是预期的,因为我们已经看到 GBDT 模型的精确率较高,但与随机森林模型相比,召回率较低。
总体而言,随机森林和 GBDT 模型的性能非常相似,从这些例子中难以区分。根据你如何微调你的模型,你可能会得到一个更好的随机森林或 GBDT 模型。
有许多方法和参数可以微调基于树的模型。我们建议你尝试不同的参数集,看看它们如何影响模型性能!
使用深度学习算法预测客户转化
深度学习已成为热门话题,其流行度和使用率都在上升,因为深度学习模型已被证明在数据变量之间存在复杂关系时表现良好,并且可以从数据中自主学习和提取特征,尽管基于树的模型也非常频繁地被使用,并且在预测建模方面非常强大。我们在第五章中提到了深度学习,当时我们使用了预训练的语言模型进行情感分析和分类。在本节中,我们将在此基础上构建知识,并实验开发深度学习模型用于预测建模,特别是用于预测哪些客户可能转化的预测。
深度学习基本上是一种人工神经网络(ANN)模型,具有许多隐藏和复杂的神经元层,或者说,是一种深度 ANN。ANN 是一种受动物和人类大脑中生物神经网络启发的模型。ANN 通过类似动物和人类大脑的相互连接的神经元层来学习数据。以下图表显示了 ANN 的高级结构:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_12.png
图 6.12:示例 ANN 架构
如此图表所示,任何深度学习或 ANN 模型都有三个层次:输入层、隐藏层和输出层。ANN 模型如何学习或构建隐藏层权重的详细解释超出了本书的范围,但就高层次而言,它们通过前向传播和反向传播的迭代进行:
-
前向传播是将数据从输入层通过网络传递到输出层,并应用激活函数的过程,无论每个神经元是否被激活,并返回每个节点的输出。
-
反向传播是从输出层到输入层的移动过程,通过分析前一次迭代的损失或误差来调整网络的权重。
通过这些前向和反向传播的迭代,神经网络学习每个神经元的权重,以最小化预测误差。
应该尝试不同的隐藏层数量和每层的神经元数量,以找到最适合每个预测模型案例的最佳神经网络架构。在本节中,我们将实验比较宽神经网络架构与深度神经网络架构的性能。
何时使用宽神经网络与深度神经网络?
宽神经网络指的是具有较少层但每层有更多神经元的 ANN 模型,而深度神经网络指的是具有许多隐藏层的 ANN 模型。除了比较模型性能以确定哪种架构更适合您的案例之外,还有一些关键因素您可能需要考虑或限制采取哪种方法:
-
训练时间和计算资源:随着层数的增加,训练需要更多的时间,因为每一层的反向传播都是计算密集型的,这也导致了更高的计算成本。宽神经网络可能在缩短训练时间和降低计算成本方面具有优势。
-
模型可解释性:与深度神经网络相比,较浅的架构可能提供更好的可解释性。如果可解释性是一个要求,浅层架构可能更适合。
-
泛化能力:深度神经网络模型通常能够通过更多的层捕获更抽象的模式,并且与宽神经网络相比,能够学习更高阶的特征。这可能导致深度架构模型在新的和未见过的数据上具有更好的性能。
深度学习模型的训练集和测试集
为了神经网络模型的最佳性能,您需要在训练模型之前对数据进行归一化。我们将使用之前用于树模型的相同训练集和测试集,但对其进行归一化。看看以下代码:
mean = train_x.mean()
std = train_x.std()
normed_train_x = (train_x - mean)/std
normed_test_x = (test_x - mean)/std
如此代码所示,我们首先从训练集中获取均值和标准差,并通过减去均值并除以标准差来归一化训练集和测试集。标准化后,normed_train_x
DataFrame 的所有列的均值应为0
,标准差为1
。
宽神经网络建模
我们将首先使用 Python 中的keras
包构建宽神经网络模型。
要在您的机器上安装keras
,请在您的终端中运行以下命令:
pip install keras
训练宽神经网络模型
使用归一化的训练集和测试集,让我们开始构建神经网络模型。看看以下代码,了解如何初始化神经网络模型:
import keras
model = keras.Sequential(
[
keras.Input(shape=normed_train_x.shape[1:]),
keras.layers.Dense(2048, activation="relu"),
keras.layers.Dropout(0.2),
keras.layers.Dense(1, activation="sigmoid"),
]
)
如您从以下代码中可以看到,我们使用Sequential
类创建了一系列层。我们以Input
类定义的输入层开始,其中我们定义了形状或输入神经元的数量,以匹配训练集的列数或特征数。然后,我们有一个包含 2,048 个神经元的宽隐藏层。我们添加了一个Dropout
层,它定义了要丢弃的神经元百分比,这有助于减少过拟合问题。我们指示神经网络模型在隐藏层和输出层之间丢弃 20%的神经元。最后,我们有一个输出层的Dense
层,它有一个输出神经元,将输出正例的预测概率或转换的可能性。
您可能已经注意到我们用于隐藏层和输出层的两个激活函数。ReLU(修正线性单元)激活函数是最常用的激活函数之一,它只激活正值并关闭所有负值。ReLU 激活函数的方程如下:
ReLU(x) = max(0, x)
ReLU 激活函数的行为如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_13.png
图 6.13:ReLU 激活函数
另一方面,sigmoid函数将值转换为0
到1
的范围。这使得 sigmoid 函数成为预测概率输出的首选。sigmoid 函数的方程如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_004.png
sigmoid 函数的行为如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_14.png
图 6.14:sigmoid 激活函数
除了 ReLU 和 sigmoid 之外,还有许多其他激活函数,如 tanh、leaky ReLU 和 softmax。我们建议您做一些研究,并实验不同激活函数的工作方式和它们对神经网络模型性能的影响!
在我们能够训练神经网络模型之前,还有一些步骤需要完成:
-
首先,我们需要定义我们将通过模型训练迭代跟踪的关键指标,并编译模型。请看以下代码:
model_metrics = [ keras.metrics.Accuracy(name="accuracy"), keras.metrics.Precision(name="precision"), keras.metrics.Recall(name="recall"), ] model.compile( optimizer=keras.optimizers.Adam(0.001), loss="binary_crossentropy", metrics=model_metrics )
正如我们之前与树形模型讨论的那样,我们将跟踪准确度、精确度和召回率。我们将使用 Adam 优化器进行此练习。简单来说,优化器是用于根据损失函数(在这种情况下是binary_crossentropy
,它衡量预测值和实际二进制结果之间的差异)更新网络权重的算法。由于我们的预测输出或目标变量是一个表示转换与否的二进制变量,binary_crossentropy
将适合作为损失函数。对于多类预测,其中目标变量不是二进制变量并且有超过两个可能的结果,categorical_crossentropy
将是一个更好的损失函数。
-
接下来,我们将在训练模型时运行的所有迭代或 epoch 中保存最佳模型。请看以下代码:
best_model_path = "./checkpoint.wide-model.keras" model_checkpoint_callback = keras.callbacks.ModelCheckpoint( filepath=best_model_path, monitor="val_precision", mode="max", save_best_only=True )
在这里,我们使用精确度作为指标来监控并基于该指标值将最佳模型存储到本地驱动器。最佳模型将被存储在best_model_path
变量中定义的方式。
-
最后,现在是时候使用以下代码训练这个宽神经网络模型了:
class_weight = {0: 1, 1: 1/train_y.mean()} history = model.fit( normed_train_x.to_numpy(), train_y.to_numpy(), batch_size=64, epochs=30, verbose=2, callbacks=[ model_checkpoint_callback, ], validation_data=(normed_test_x.to_numpy(), test_y.to_numpy()), class_weight=class_weight, )
与我们之前给树形模型的类权重类似,我们也将定义与类组成成反比的类权重。正如您从代码中可以看到的,我们指示模型以 64 个批次的规模运行 30 次迭代或 epoch。我们将之前创建的用于存储最佳模型的回调与自定义类权重注册。当您运行此代码时,它将运行 30 个 epoch 并报告我们之前定义的准确度、精确度和召回率指标。此代码的输出应类似于以下内容:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_15.png
图 6.15:神经网络模型的样本输出
预测和评估宽神经网络模型
为了从我们刚刚训练的模型中进行预测,我们首先需要加载最佳模型。您可以使用以下代码来加载最佳模型:
wide_best_model = keras.models.load_model(best_model_path)
使用此模型进行预测与scikit-learn
包的语法相似。请看以下代码:
wide_pred_proba = wide_best_model.predict(normed_test_x).flatten()
wide_preds = [1 if x > 0.5 else 0 for x in wide_pred_proba]
如您从这段代码中可能注意到的,您可以使用keras
模型的predict
函数来获取每个记录的预测概率。我们使用flatten
函数将预测概率输出从二维数组转换为一个预测概率列表。然后,为了说明目的,我们将预测概率高于0.5
的任何记录视为有很高的转换可能性,并假设它们是阳性案例。概率阈值是您可以为转换与否的最终预测进行微调的另一个因素。
我们将使用之前用于基于树的模型的关键指标、AUC-ROC 曲线和混淆矩阵的相同代码。结果如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_16.png
图 6.16:宽神经网络模型的评估结果总结
如果您将这些结果与基于树的模型进行比较,您会注意到宽神经网络模型的表现略逊于基于树的模型。精确度和召回率略有下降,与基于树的模型相比,AUC 也有所下降。我们将在更深入地讨论 A/B 测试时,更深入地探讨这些模型的实际用途以及如何选择最适合营销活动的最佳模型。
深度神经网络建模
在本节中,我们将讨论如何构建深度神经网络模型,并将结果与我们在上一节中构建的宽神经网络模型进行比较。这两个神经网络模型之间的唯一区别在于我们如何构建或设计它们。
训练深度神经网络模型
请看以下代码:
model = keras.Sequential(
[
keras.Input(shape=normed_train_x.shape[1:]),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dropout(0.2),
keras.layers.Dense(128, activation="relu"),
keras.layers.Dropout(0.2),
keras.layers.Dense(1, activation="sigmoid"),
]
)
与之前一样,我们使用keras.Sequential
来构建模型,但这次在第一个Input
层和最终的Dense
输出层之间有更多的层:
-
我们有四个隐藏层,每个层包含 128 个神经元,并使用 ReLU 激活函数。
-
在最后一个隐藏层和输出层之间,以及最后一个隐藏层和倒数第二个隐藏层之间,也有两个
Dropout
层。
与之前的宽神经网络模型相比,每个层中的神经元数量更少,但层数更多。正如您从这个示例中可以看到的,模型架构取决于构建模型的人,并且应该基于性能。我们建议尝试不同的模型架构,并分析它如何改变模型性能。
使用之前用于宽神经网络模型的相同 Keras 模型训练代码,你也可以训练这个深度神经网络模型。确保你有保存最佳模型的回调函数,这样我们就可以用它来评估模型的表现。
预测和评估深度神经网络模型
如前所述,你可以使用以下代码根据从训练深度神经网络模型中找到的最佳模型进行预测:
deep_best_model = keras.models.load_model(best_model_path)
deep_pred_proba = deep_best_model.predict(normed_test_x).flatten()
使用预测概率,你可以运行相同的评估指标和图表。由于模型中的随机性,每次训练模型时结果可能会有所不同。在这次运行时,结果如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_17.png
图 6.17:深度神经网络模型评估结果总结
与我们之前构建的宽神经网络模型相比,深度神经网络模型的表现略差。AUC 值大致相同,但精确率和召回率低于宽神经网络模型。这些评估指标和图表有助于检查模型在表面价值和训练时间时的表现。对于模型之间更实际的评估,你可能想要考虑运行 A/B 测试来选择你营销活动的最终模型。
进行 A/B 测试以选择最佳模型
A/B 测试,简单来说,就是比较两个版本的功能或模型,以确定哪一个更好。它在各个行业的决策过程中发挥着关键作用。网页开发者可能会使用 A/B 测试来测试两个版本的软件哪个表现更好。营销人员可能会使用 A/B 测试来测试哪种营销信息版本可能更有效地吸引潜在客户。同样,A/B 测试可以用来比较两个不同模型在性能和有效性方面的差异。在本章的例子中,我们可以使用 A/B 测试来选择我们基于训练和测试集构建的模型中哪一个在实际的真实世界环境中可能表现最佳。
A/B 测试通常在一个预定义的时期内进行,或者直到收集到预定义数量的样本。这是为了确保你有足够的样本来基于它们做出决策。例如,你可能想要进行为期两周的 A/B 测试,并在两周结束时收集测试期间的结果进行分析。或者,你可能想要为 A 和 B 两种场景设定 2,000 个样本的目标,而不设定特定的时期。
你可能还想将周期和样本数量都设置为目标,哪个先到就哪个。例如,你将样本目标设置为 2,000 个样本和两周的时间框;如果你在两周内收集到 2,000 个样本,你可能想早点结束测试,因为你已经收集了足够多的样本来分析结果。A/B 测试的周期和样本大小应根据业务需求或限制以及测试结果的置信度来确定。
检查 A/B 测试的结果通常使用统计假设检验。两样本 t 检验经常用于确定 A/B 测试中 A 和 B 案例观察到的差异是否具有统计学意义。t 检验中有两个重要的统计量——t 值和p 值:
- t 值衡量两个均值之间的差异程度,相对于两个总体的方差。t 值越大,两组之间的差异就越明显。
获取两个样本t 检验的t 值的方程如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_005.png
M[1]和M[2]是平均值,S[1]和S[2]是标准差,N[1]和N[2]是第一组和第二组的样本数量。正如你可能从这个方程中推断出的,大的负t 值将表明第二组的平均值显著大于第一组的平均值,而大的正t 值将表明第一组的平均值显著大于第二组的平均值。这也被称为双尾t 检验。
- 另一方面,p 值衡量结果偶然发生的概率。因此,p 值越小,两组之间的差异就越具有统计学意义,差异不是偶然的。
模拟 A/B 测试
我们将模拟 A/B 测试,就像我们正在用我们的模型实时运行这个实验一样。为了说明目的,我们将对之前构建的 XGBoost 或 GBDT 模型和宽神经网络模型进行 A/B 测试。我们将为每种情况运行 1,000 个样本,并分析结果,以查看哪个模型可能更适合我们的营销活动,以最大化客户转化率。
首先,我们将随机将流量分配到 A 组(GBDT 模型)和 B 组(宽神经网络模型)。请看以下代码:
group_a_indexes = sorted(list(np.random.choice(2000, 1000, replace=False)))
group_b_indexes = [x for x in range(2000) if x not in group_a_indexes]
在这里,我们使用numpy
包的random.choice
函数从 2,000 个项目中随机选择 1,000 个项目。我们将这些项目分配到 A 组,其余的分配到 B 组。然后,我们像以下代码那样模拟 A/B 测试:
group_a_actuals = test_y.iloc[group_a_indexes].to_numpy()
group_b_actuals = test_y.iloc[group_b_indexes].to_numpy()
group_a_preds = []
group_b_preds = []
for customer in range(2000):
if customer in group_a_indexes:
# route to XGBoost
conversion = test_y.iloc[customer]
pred_prob = xgb_model.predict_proba(
np.array([test_x.iloc[customer].to_numpy(),])
)[0][1]
pred = 1 if pred_prob > 0.5 else 0
group_a_preds.append(pred)
elif customer in group_b_indexes:
# route to Wide Net
conversion = test_y.iloc[customer]
pred_prob = wide_best_model.predict(
np.array([normed_test_x.iloc[customer].to_numpy(),]), verbose=0
)[0][0]
pred = 1 if pred_prob > 0.5 else 0
group_b_preds.append(pred)
如此代码所示,对于前 2,000 名客户,我们将一半分配到 A 组,其余的分配到 B 组。A 组客户使用 XGBoost 或 GBDT 模型预测其转换的可能性。B 组客户使用宽神经网络模型预测其转换的可能性。
然后,我们将检查这些预测的实际结果。在实际测试设置中,这些结果可能在预测做出后的几天或几周后出现,因为客户需要时间来决定是否购买。我们将评估那些被预测会转换的客户以及被预测不会转换但已经转换的客户(即被遗漏的机会)。请看以下代码:
def get_cumulative_metrics(actuals, preds):
cum_conversions = []
missed_opportunities = []
customer_counter = 0
cum_conversion_count = 0
missed_opp_count = 0
for actual, pred in zip(actuals, preds):
customer_counter += 1
if pred == 1:
if actual == 1:
cum_conversion_count += 1
else:
if actual == 1:
missed_opp_count += 1
cum_conversions.append(cum_conversion_count/customer_counter)
missed_opportunities.append(missed_opp_count/customer_counter)
return cum_conversions, missed_opportunities
如你所见,我们正在计算那些被预测会转换的客户以及那些被预测不会转换但已经转换的客户的总转换累积数。这些本质上是被遗漏的机会,因为我们没有向这些客户发送我们的营销信息,但他们本可以完成转换。我们将使用以下代码对这些每个组的这些结果进行汇总:
a_cum_conv_rates, a_missed_opp_rates = get_cumulative_metrics(
group_a_actuals, group_a_preds
)
b_cum_conv_rates, b_missed_opp_rates = get_cumulative_metrics(
group_b_actuals, group_b_preds
)
ab_results_df = pd.DataFrame({
"group_a_cum_conversion_rate": a_cum_conv_rates,
"group_a_cum_missed_opportunity_rate": a_missed_opp_rates,
"group_b_cum_conversion_rate": b_cum_conv_rates,
"group_b_cum_missed_opportunity_rate": b_missed_opp_rates,
})
现在,让我们用以下代码查看随时间推移的累积转换率:
ax = (
ab_results_df[[
"group_a_cum_conversion_rate", "group_b_cum_conversion_rate"
]]*100
).plot(
style=['-', '--'],
figsize=(8, 5),
grid=True
)
ax.set_ylabel("Conversion Rate (%)")
ax.set_xlabel("Customer Count")
ax.set_title(
"Cumulative Conversion Rates over Time (A: XGBoost, B: Wide Net)"
)
plt.show()
这将生成如下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_18.png
图 6.18:A 组和 B 组随时间推移的累积转换率
如此图表所示,接受宽神经网络模型预测的 B 组,与接受 XGBoost 模型预测的 A 组相比,整体转换率更高。让我们也看看每个组在遗漏机会方面的表现,以下代码:
ax = (
ab_results_df[[
"group_a_cum_missed_opportunity_rate",
"group_b_cum_missed_opportunity_rate"
]]*100
).plot(
style=['-','--'],
figsize=(8, 5),
grid=True
)
ax.set_ylabel("Missed Opportunity Rate (%)")
ax.set_xlabel("Customer Count")
ax.set_title(
"Cumulative Missed Opportunity Rates over Time (A: XGBoost, B: Wide Net)"
)
plt.show()
当你运行这段代码时,你应该得到一张看起来类似于以下图表的图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_19.png
图 6.19:A 组和 B 组随时间推移的累积遗漏机会率
此图表表明,使用 XGBoost 模型预测的 A 组遗漏机会率高于使用宽神经网络模型预测的 B 组。
这些视觉图表是检查不同组或模型表现的好方法。然而,为了验证这些差异是否具有统计学意义,我们不得不运行 t 检验。
双尾 T 检验
Python 中的scipy
包提供了一个方便的工具来计算双尾 t 检验的 t 值和 p 值。
要在你的机器上安装scipy
,请在你的终端中运行以下命令:
pip install scipy
一旦你安装了scipy
包,你可以运行以下命令来检查 A 组和 B 组在转换率和遗漏机会率之间的差异的统计学意义:
from scipy.stats import ttest_ind
t, p = ttest_ind(a_cum_conv_rates, b_cum_conv_rates)
print(
f"Conversion Rate Difference Significance -- t: {t:.3f} & p: {p:.3f}"
)
t, p = ttest_ind(a_missed_opp_rates, b_missed_opp_rates)
print(
f"Missed Opportunity Rate Difference Significance -- t: {t:.3f} & p: {p:.3f}"
)
如你所见,从这段代码中,scipy.stats
模块中的ttest_ind
函数让你可以轻松地得到两组之间的双尾 t 检验的 t 值和 p 值。这段代码的输出应类似于以下内容:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_06_20.png
图 6.20:双尾 t 检验结果
让我们更仔细地看看这个输出。首先,转换率 t 检验的 t 值和 p 值分别是-13.168
和0
。这表明两组之间的转换率差异在统计学上是显著的。t 检验的负值表明 A 组的平均值小于 B 组。
由于从 p 值我们得出差异是显著的,这转化为结果,即 A 组的转换率显著低于 B 组。换句话说,宽神经网络模型在捕捉高转换可能性客户方面比 XGBoost 模型表现显著更好。
其次,错过机会率 t 检验的 t 值和 p 值分别是2.418
和0.016
。这表明 A 组的平均值显著大于 B 组。这意味着 A 组的错过机会显著多于 B 组。换句话说,XGBoost 模型比宽神经网络模型错过的机会更多。
如你所见,从 A/B 测试模拟结果来看,A/B 测试为现实世界设置中哪个模型表现更好提供了强大的见解。每个构建数据科学或 AI/ML 模型的人都应该养成一种习惯,不仅基于训练集和测试集评估模型,而且通过 A/B 测试评估模型,以便他们可以了解所构建的模型在现实世界设置中的实际性和适用性。通过短期的小样本 A/B 测试,你可以选择用于即将到来的营销活动的最佳模型。
摘要
在本章中,我们讨论了使用在线购买数据集构建预测模型的大量内容。我们探讨了两种不同的基于树的模型,即随机森林和 GBDT,以及如何构建预测模型来预测谁可能进行转换。使用相同的例子,我们还讨论了如何构建神经网络模型,这些模型是深度学习模型的基础。在构建神经网络模型时,你有很大的灵活性,例如宽网络、深网络或宽深网络。我们在构建神经网络模型时简要提到了激活函数和优化器,但我们建议你深入研究它们如何影响神经网络模型的表现。最后,我们讨论了 A/B 测试是什么,如何进行 A/B 测试,以及如何解释 A/B 测试结果。我们使用我们构建的模型模拟了 A/B 测试,以选择捕捉最多客户转换的最佳模型。
在接下来的章节中,我们将进一步探讨使用 AI/ML 进行精准营销。更具体地说,我们将讨论如何以各种方式构建个性化的产品推荐,以及这如何导致微目标营销策略。
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人交流,并在以下地点与超过 5000 名成员一起学习:
第七章:个性化产品推荐
随着技术的进步和收集的数据量的增加,个性化无处不在。从流媒体服务,如 Netflix 和 Hulu,到你在手机上看到的营销信息和广告,现在展示给你的大部分内容都是个性化的。在营销中,个性化的或目标营销活动已被证明比通用的或大众营销活动在推动客户参与和转化方面效果显著。
在本章中,我们将讨论如何构建个性化的推荐模型,以便我们能更好地针对客户推荐他们最感兴趣的产品。我们将探讨如何在 Python 中执行市场篮子分析,这有助于营销人员更好地理解哪些商品经常一起购买,如何构建两种方法的协同过滤算法以实现个性化产品推荐,以及为推荐针对个别客户的个性化产品所采取的其他方法。
在本章中,我们将涵盖以下主题:
-
基于市场篮子的产品分析
-
基于用户和基于项目的协同过滤
-
其他常用的推荐方法
基于市场篮子的产品分析
市场篮子分析的经典例子是研究人员发现,购买尿布的客户也倾向于购买啤酒。尽管啤酒和尿布看起来像是太远的商品而无法搭配,但这一发现表明,我们可以从数据中找到不同产品之间隐藏的关联。市场篮子分析的目标是找到这些产品之间的隐藏关系,并有效地为或向客户安排或推荐产品。市场篮子分析有助于回答以下问题:
-
应该向购买产品 X 的客户推荐哪些产品?
-
在商店中,哪些产品应该放在一起,以便客户可以轻松找到他们想要购买的产品?
为了回答这些问题,需要找到市场篮子分析的关联规则。简单来说,关联规则通过基于规则的机器学习方法展示一个项集在交易中出现的信心或显著性。一个关联规则有两个部分:先行词,即规则的条件,和结果词,即规则的结果。考虑以下陈述:
“购买尿布的客户很可能也会购买啤酒。”
在这个陈述中,尿布是先行词,啤酒是结果词。使用五个指标来评估这些关联规则的强度:
- 支持:支持表示一个项集在数据集中出现的频率。计算项集(X, Y)的支持的方程式是:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_001.png
例如,如果数据中的 10 笔交易中有 3 笔是尿布交易,那么尿布的支持度为 3/10 或 0.3。如果数据中的 10 笔交易中有 2 笔是尿布和啤酒的交易,那么尿布和啤酒的支持度为 2/10 或 0.2。
- 置信度:置信度是在前导事件 X 发生的情况下,项集(X, Y)发生的条件概率。计算给定前导事件 X 的后续事件 Y 的置信度的公式为:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_002.png
例如,如果数据中的 10 笔交易中有 3 笔是尿布交易,2 笔是尿布和啤酒的交易,那么在尿布购买的情况下购买啤酒的置信度为 2/3 或 0.67。如果后续事件总是与前导事件一起发生,置信度值将为 1。
- 提升比:提升比是一个度量项集(X, Y)与前导事件和后续事件独立事件相比同时发生的频率的指标。前导事件 X 和后续事件 Y 的提升比公式为:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_003.png
例如,如果尿布和啤酒的置信度为 2/3,啤酒的支持度为 5/10,那么尿布对啤酒的提升比为 2/3 除以 5/10,即 20/15 或 4/3。如果前导事件和后续事件是独立的,提升分数将为 1。
- 杠杆率:杠杆率是一个度量前导事件和后续事件同时发生的频率与它们独立时发生的频率之间差异的指标。杠杆率的公式为:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_004.png
例如,如果尿布和啤酒在总共 10 笔交易中的 4 笔交易中发生,尿布和啤酒的支持度分别为 0.6 和 0.5,那么杠杆率将是 0.4 减去(0.6 * 0.5),即 0.1。杠杆率的值介于-1 和 1 之间,如果前导事件和后续事件是独立的,杠杆率值将为 0。
- 信念度:信念度衡量后续事件对前导事件依赖的程度。信念度的公式为:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_005.png
例如,如果尿布和啤酒之间的置信度为0.67
,啤酒的支持度为0.2
,意味着在总共 10 笔交易中有 2 笔是啤酒交易,那么信念度大约为2.42
。如果前导事件和后续事件是独立的,那么信念度将为 1。另一方面,如果前导事件和后续事件的置信度为 1,或者后续事件总是与前导事件一起发生,那么分母变为 0,信念度值变为无穷大。
在这个基础上,让我们深入探讨如何在电子商务数据集中寻找关联规则。我们将使用在线零售数据集和mlxtend
包来展示如何使用 Python 进行市场篮子分析。
源代码和数据: github.com/PacktPublishing/Machine-Learning-and-Generative-AI-for-Marketing/tree/main/ch.7
数据来源: github.com/PacktPublishing/Machine-Learning-and-Generative-AI-for-Marketing/blob/main/ch.7/data.csv
这是原始数据源的链接:archive.ics.uci.edu/dataset/352/online+retail
以下是为安装mlxtend
包的命令:
pip install mlxtend
Apriori 算法 – 寻找频繁项集
Apriori 算法用于识别和生成我们之前讨论的关联规则。我们将通过一个示例来讨论如何在 Python 中使用 Apriori 算法进行市场篮子分析。首先,我们使用以下代码将数据加载到 DataFrame 中:
import pandas as pd
df = pd.read_csv("./data.csv")
df = df.dropna(subset=['CustomerID'])
一旦运行此代码,我们现在就有所有客户所做的购买。如果你还记得我们之前讨论过的市场篮子分析,两个关键组件是找到项集和找到项集之间的规则。在本节中,我们将专注于找到项集,即商品的组合。
当你运行df["StockCode"].nunique()
命令时,你会看到在这个数据集中有 3,684 个独特的商品或产品。正如你可以想象的那样,你可以创建的商品组合的数量会随着商品数量的增加而呈指数增长。所有可能商品组合的数量是:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_006.png
在我们的数据集中有超过 3,000 个商品,检查所有可能的项集是不切实际的,因为有 2³⁶⁸⁴-1 个项集需要检查。这就是 Apriori 算法发挥作用的地方。直观地说,交易频率低或支持度低的商品或项集在结果中可能价值较低。因此,Apriori 算法提取了被认为足够频繁的项集,这些项集由预定义的支持度阈值确定。
要使用 Apriori 算法找到频繁项集,我们需要将 DataFrame 转换成一种矩阵形式,其中每一行代表客户,每一列代表商品或产品。看看以下代码:
customer_item_matrix = df.pivot_table(
index='CustomerID',
columns='StockCode',
values='Quantity',
aggfunc='sum'
)
在这里,我们使用pivot_table
函数来创建一个客户到商品的矩阵,每个值将代表每个客户购买的商品的总数量。有了这个客户到商品的矩阵,我们可以使用以下代码运行 Apriori 算法来找到频繁项集:
from mlxtend.frequent_patterns import apriori
frequent_items = apriori(
customer_item_matrix,
min_support=0.03,
use_colnames=True
)
frequent_items["n_items"] = frequent_items["itemsets"].apply(
lambda x: len(x)
)
如此代码所示,我们正在使用 mlxtend
包中的 apriori
函数。我们使用 min_support
参数定义每个项集所需的最低支持为 0.03
,这意味着我们将忽略那些在交易中发生次数少于 3% 的项集。然后,我们添加一个名为 n_items
的列,它简单地计算每个项集中的项目数量。
最终的 DataFrame 看起来如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_01.png
图 7.1:频繁项 DataFrame
如果您还记得,项集的可能组合总数是 2³⁶⁸⁴-1。然而,正如您从应用 Apriori 算法的结果中可以看到的,我们现在有 1,956 个项,这是一个可管理的合理数量的项集,我们可以检查其关联规则。
关联规则
使用我们构建的频繁项集,我们现在可以生成关联规则,这些规则将告诉我们哪些项集经常一起购买。请看以下代码:
from mlxtend.frequent_patterns import association_rules
rules = association_rules(
frequent_items,
metric="confidence",
min_threshold=0.6,
support_only=False
)
在这里,我们使用 mlxtend
包中的 association_rules
函数。有几个关键参数需要考虑:
-
metric
:此参数定义了用于选择关联规则要使用的指标。在这里,我们使用confidence
来选择具有高 confidence 的关联规则,但如果您对找到具有高 lift 的规则感兴趣,也可以使用lift
。 -
min_threshold
:此参数定义了基于您选择的指标选择关联规则的阈值。在这里,我们只对找到 confidence 超过 60% 阈值的规则感兴趣。 -
support_only
:如果您只对计算规则的 support 感兴趣,或者由于某些数据缺失而无法计算其他指标,可以将此参数设置为True
。在这里,我们将其设置为False
,因为我们对找到具有高 confidence 的规则感兴趣。
这些规则的输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_02.png
图 7.2:基于 confidence 超过 60% 的关联规则
关联规则输出包含先行项、后续项和关键指标,例如 support
、confidence
和 lift
。您可以按每个指标排序,并生成关于哪些项集具有紧密关系的见解。例如,您可能希望找到具有最高 lift 的前 20 个项集或具有最高 confidence 的前 10 个项集。您还可以可视化这些关系,以便您可以轻松查看关系及其强度。请看以下代码:
most_lift = rules.sort_values(
by="lift", ascending=False
).head(20).pivot_table(
index='antecedents',
columns='consequents',
values='lift',
aggfunc='sum'
)
most_lift.index = [
" + ".join([
df.loc[df["StockCode"] == item]["Description"].unique()[0]
for item in list(x)
]) for x in most_lift.index
]
most_lift.columns = [
" + ".join([
df.loc[df["StockCode"] == item]["Description"].unique()[0]
for item in list(x)
]) for x in most_lift.columns
]
在这里,我们首先使用 sort_values
函数选择具有最高 lift 的前 20 个规则。然后,我们将表格进行转置,使得每一行是先行项,每一列是后续项,单元格的值是 lift。可以使用热图轻松可视化这些规则的强度。以下代码可以用于可视化这 20 个具有最高 lift 的规则:
ax = plt.subplot()
sns.heatmap(
most_lift,
annot=True,
annot_kws={"size": 6},
# fmt=".1f",
ax=ax
)
ax.set_title("Top 20 Rules by Lift")
ax.set_xlabel("Consequent")
ax.set_ylabel("Antecedent")
ax.yaxis.set_ticklabels(list(most_lift.index), fontsize=6)
ax.xaxis.set_ticklabels(list(most_lift.columns), fontsize=6)
plt.show()
这将生成如下所示的热图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_03.png
图 7.3:具有最高升力的前 20 个关联规则
在这个图表中,颜色越浅,给定规则的升力就越高。例如,当前提项目集是GREEN REGENCY TEACUP AND SAUCER
、REGENCY TEA PLATE ROSES
,而结果项目集是ROSES REGENCY TEACUP AND SAUCER
、REGENCY TEA PLATE GREEN
时,升力约为 26,是最高的。另一方面,当前提项目集是ROSES REGENCY TEACUP AND SAUCER
、REGENCY TEA PLATE ROES
,而结果项目集是GREEN REGENCY TEACUP AND SAUCER
、REGENCY TEA PLATE GREEN
时,升力约为 20,是前 20 条规则中最低的。
从这个例子中可以看出,通过关联规则,我们可以轻松地找出哪些产品是共同购买的,以及它们之间的关系。这可以用于个性化营销,根据这些规则推荐某些产品。如果一个客户购买了项目集 A,那么自然可以推荐项目集 B,因为关联规则表明 A 和 B 经常一起购买,具有显著性。这比在营销信息中盲目推荐随机产品集,希望客户对购买它们表示兴趣,更接近一步推荐个性化的产品集。
最后,如果你还记得,升力与置信度呈线性关系,与支持度呈反向关系,因为升力定义为:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_007.png
这个关系可以通过我们用mlxtend
包找到的规则轻松展示。看看下面的代码:
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twiny()
rules[["support","lift"]].plot(
kind="scatter", x="support", y="lift", ax=ax1
)
rules[["confidence","lift"]].plot(
kind="scatter", x="confidence", y="lift", ax=ax2, color="orange", alpha=0.3
)
ax1.legend(["lift vs. support",], loc="upper right")
ax2.legend(["lift vs. confidence",], loc="upper left")
plt.grid()
plt.show()
在这里,y 轴上有升力值,x 轴上有支持度和置信度值。图表将如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_04.png
图 7.4:升力与置信度以及升力与支持之间的关系
从这个图表中可以看出,x 轴的顶部显示了置信度值,升力与置信度的散点图显示了一种线性关系,即随着置信度值的增加,升力值也增加。另一方面,x 轴的底部显示了支持度值,升力与支持度的散点图显示了一种反向关系,即随着支持度值的增加,升力值减少。
协同过滤
在构建推荐系统中有各种方法,通常有多种方法参与其中。在构建推荐系统中,一些常用的基于 AI/ML 的方法包括:
-
协作过滤:这种方法使用之前用户的行为,如他们查看的页面、购买的产品或之前给出的评分。该算法利用这类数据来找到与用户之前表示兴趣的产品或内容相似的产品或内容。例如,如果用户在流媒体平台上观看了几部惊悚电影,可能会向这位用户推荐其他一些惊悚电影。或者,如果一位客户在电子商务平台上购买了连衣裙衬衫和连衣裙鞋,可能会向这位客户推荐连衣裙裤。协作过滤算法通常基于用户或项目之间的相似性构建:
-
基于用户的协作过滤使用数据来根据之前查看的页面或购买的产品找到相似的用户。
-
基于项目的协作过滤使用数据来寻找经常一起购买或查看的项目。
-
-
基于内容的过滤:正如其名所示,这种方法使用产品、内容或用户的特征。协作过滤算法侧重于用户到用户或项目到项目的相似性,而基于内容的过滤算法侧重于特征相似性,例如流派、关键词和元数据。例如,如果你正在推荐观看的电影,这种方法可能会查看电影的描述、流派或演员,并推荐那些符合用户偏好的电影。另一方面,如果你正在推荐时尚商品,那么这种方法可能会查看产品类别、描述和品牌,以向用户推荐某些商品。
-
预测建模:如第六章所述,也可以为推荐系统构建预测模型。用户之前查看的内容、他们购买的产品或网页会话历史可以作为识别用户很可能感兴趣查看或购买的项目特征。这些预测模型的概率输出可以用来对项目进行排序,以便在显示其他不太可能的项目之前向用户展示更可能的项目。
-
混合模型:通常,推荐系统会结合之前提到的所有方法来构建。将不同的推荐方法混合使用可以帮助提高推荐准确性,这是单一方法推荐系统可能遗漏的。
在本章中,我们将重点关注协同过滤算法,因为它作为许多推荐系统常用的骨干算法,并且更具体地,我们将探讨如何使用基于用户的协同过滤和基于商品的协同过滤算法来构建推荐系统。这种技术在推荐系统中经常被使用,因为它以非常直观的方式很好地捕捉了用户和商品之间的交互。许多在业务中推荐至关重要的组织,如 Netflix、Amazon 和 Spotify,都将协同过滤算法作为其推荐系统的一部分。
协同过滤算法的关键是找到相似的用户或商品。可以有多种度量标准来衡量用户或商品之间的相似性,例如欧几里得距离、曼哈顿距离或 Jaccard 距离。然而,余弦相似度是协同过滤中最常用的相似性度量之一,因为它更多地关注距离的定向相似性,而不是距离的大小。
余弦相似度简单地说就是两个向量之间角度的余弦值。在这里,向量可以是协同过滤算法中的用户向量或商品向量。由于余弦相似度在高维空间中具有优势,它通常被选为协同过滤算法的距离度量。计算两个向量之间余弦相似度的方程如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_008.png
在这个方程中,V[1][i]和V[2][i]代表每个向量中的每个商品,这些向量可以是用户向量或商品向量。这些余弦相似度值介于-1 和 1 之间。如果两个向量相似,余弦相似度值将接近 1;如果两个向量独立,则余弦相似度值将为 0;如果两个向量是相反向量,则余弦相似度值将接近-1。
要构建协同过滤算法,我们首先需要构建客户-商品矩阵。我们将使用与市场篮子分析相同的代码,如下所示:
customer_item_matrix = df.pivot_table(
index='CustomerID',
columns='StockCode',
values='Quantity',
aggfunc='sum'
)
如前所述,这个矩阵显示了每个客户(行)为每个商品(列)购买的量。我们不是使用商品购买的原始数量,而是将要使用独热编码,使得如果某个客户购买了某个商品,则值为 1,如果没有购买,则值为 0,如下面的代码所示:
customer_item_matrix = customer_item_matrix.map(
lambda x: 1 if x > 0 else 0
)
customer_item_matrix
矩阵应该看起来像下面这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_05.png
图 7.5:客户-商品矩阵
如预期的那样,每一行代表一个特定用户是否购买了每个商品。这个矩阵将成为我们在以下章节中构建基于用户协同过滤和基于商品协同过滤算法的基础矩阵。
基于用户的协同过滤
构建基于用户的协同过滤模型的第一步是构建相似用户矩阵。看看下面的代码:
from sklearn.metrics.pairwise import cosine_similarity
user_user_sim_matrix = pd.DataFrame(
cosine_similarity(customer_item_matrix)
)
user_user_sim_matrix.columns = customer_item_matrix.index
user_user_sim_matrix['CustomerID'] = customer_item_matrix.index
user_user_sim_matrix = user_user_sim_matrix.set_index('CustomerID')
如果你还记得,余弦相似度度量是协同过滤算法中最常用的相似度指标之一。在这里,我们使用 scikit-learn 包的cosine_similarity
函数来计算用户之间的余弦相似度。新创建的变量user_user_sim_matrix
将是一个矩阵,其中每一行和每一列代表一个用户,以及两个用户之间的相似度。
看看下面的示例:
user_user_sim_matrix.loc[
[12347.0, 12348.0, 12349.0, 12350.0, 12352.0],
[12347.0, 12348.0, 12349.0, 12350.0, 12352.0]
]
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_06.png
图 7.6:用户之间的相似性样本
这个示例显示了用户之间的余弦相似度指标。正如预期的那样,对角线上的值是 1,因为它们代表用户与其自身的相似度。例如,用户12349
和12350
的余弦相似度约为0.057
,而用户12349
和12352
的余弦相似度为0.138
。这表明用户12349
与用户12352
的相似度高于与用户12350
的相似度。这样,你可以通过用户与目标用户的相似度来识别和排名用户。
让我们选择一个客户来看看我们如何可能构建一个推荐系统。我们将选择用户 ID14806
作为这个练习的示例:
TARGET_CUSTOMER = 14806.0
print("Similar Customers to TARGET")
user_user_sim_matrix.loc[
TARGET_CUSTOMER
].sort_values(ascending=False).head(10)
在这里,我们通过在user_user_sim_matrix
矩阵中选择TARGET_CUSTOMER
并按降序排序值,选择具有最高余弦相似度测量的前 10 个用户。输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_07.png
图 7.7:与目标客户相似的客户
在这里,你可以看到客户13919
与目标客户14806
最相似,客户12561
排在第二位,客户13711
排在第三位。
通过最相似客户进行推荐
向目标客户推荐产品的最简单方法就是查看目标客户已经购买的商品,并将它们与最相似客户13919
进行比较。然后,推荐目标客户尚未购买但最相似客户已经购买的商品。这是基于之前的讨论,即相似的客户可能具有相似的行为或兴趣,并且他们可能购买相似的产品。
要做到这一点,我们可以遵循以下步骤:
-
我们将首先找出目标客户已经购买的商品,使用以下代码:
items_bought_by_target = set( df.loc[ df["CustomerID"] == TARGET_CUSTOMER ]["StockCode"].unique() )
这将获取目标客户购买的所有商品,并将它们存储在items_bought_by_target
变量中。
-
接下来,我们需要获取与最相似客户
13919
购买的所有商品。看看下面的代码:items_bought_by_sim = set( df.loc[ df["CustomerID"] == 13919.0 ]["StockCode"].unique() )
这将找出最相似客户13919
购买的所有商品,并将它们存储在items_bought_by_sim
变量中。
-
使用
set
操作,很容易找出最相似客户购买但目标客户未购买的商品,如下所示代码所示:items_bought_by_sim_but_not_by_target = items_bought_by_sim - items_bought_by_target
如您所见,我们只是从items_bought_by_target
集合中减去另一个集合items_bought_by_sim
,这将得到所有目标客户尚未购买但最相似客户已购买的商品,并将它们存储在items_bought_by_sim_but_not_by_target
中。
-
我们可以使用以下代码获取这些商品的详细信息:
df.loc[ df["StockCode"].isin(items_bought_by_sim_but_not_by_target) ][["StockCode", "Description"]].drop_duplicates()
此输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_08.png
图 7.8:推荐给目标客户的项目
这是购买但尚未被目标客户购买的最相似客户13919
购买的商品及其描述列表。在推荐产品的最简单方法中,此列表可以显示给目标客户作为推荐商品。
由于这两个客户在过去购买相似商品时表现出相似的兴趣,因此目标客户购买这些商品的可能性比随机选择产品要高。您可以为每个客户应用相同的流程,并根据最相似客户的购买历史构建推荐产品集合。
根据相似客户购买的前列产品进行推荐
另一种方法是按照最相似客户购买产品的频率来对产品进行排名。
我们可以从以下代码开始:
top10_similar_users = user_user_sim_matrix.loc[
TARGET_CUSTOMER
].sort_values(
ascending=False
).head(11).to_dict()
potential_rec_items = {}
for user, cos_sim in top10_similar_users.items():
if user == TARGET_CUSTOMER:
continue
items_bought_by_sim = list(set(
df.loc[
df["CustomerID"] == user
]["StockCode"].unique()
))
for each_item in items_bought_by_sim:
if each_item not in potential_rec_items:
potential_rec_items[each_item] = 0
potential_rec_items[each_item] += cos_sim
potential_rec_items = [(key, val) for key, val in potential_rec_items.items()]
potential_rec_items = sorted(
potential_rec_items, key=lambda x: x[1], reverse=True
)
在这里,我们首先通过余弦相似度得到前 10 个最相似客户。然后,对于每个相似客户,我们遍历那些由相似客户购买但目标客户未购买的商品。我们计算相似客户购买指定产品的次数,按购买频率降序排序,并将结果存储在potential_rec_items
变量中。结果应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_09.png
图 7.9:相似客户购买的前列产品
这表明 10 个客户中有 7 个购买了21500
和21499
产品,10 个客户中有 4 个购买了21498
和POST
产品,等等。我们可以通过以下代码查询从这些产品 ID 获取产品描述:
top_10_items = [x[0] for x in potential_rec_items[:10]]
df.loc[
df["StockCode"].isin(top_10_items)
][["StockCode", "Description"]].drop_duplicates().set_index(
"StockCode"
).loc[top_10_items]
此输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_10.png
图 7.10:相似客户购买的前列产品
然而,这种方法并没有考虑到每个客户与目标客户之间的相似度或差异度。它只是对每个产品的前 10 个相似客户进行了计数,并基于简单的总和进行推荐。
由于我们为前 10 个相似客户中的每一个都设置了相似度度量,因此在我们推荐产品时,我们也可能想要考虑这一点。我们不仅可以进行简单的计数,还可以进行加权计数。请看以下代码:
potential_rec_items = {}
for user, cos_sim in top10_similar_users.items():
if user == TARGET_CUSTOMER:
continue
items_bought_by_sim = list(set(
df.loc[
df["CustomerID"] == user
]["StockCode"].unique()
))
for each_item in items_bought_by_sim:
if each_item not in potential_rec_items:
potential_rec_items[each_item] = 0
potential_rec_items[each_item] += cos_sim
potential_rec_items = [(key, val) for key, val in potential_rec_items.items()]
potential_rec_items = sorted(
potential_rec_items, key=lambda x: x[1], reverse=True
)
如您可能注意到的,与之前的代码不同的是potential_rec_items[each_item] += cos_sim
。我们不再只是计数,现在我们正在添加一个余弦相似度指标,该指标衡量每个客户与目标客户相似或不同的程度。
当我们根据余弦相似度查询前 10 个物品时,结果如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_11.png
图 7.11:类似客户购买的前列产品(按余弦相似度加权)
在这个例子中,简单计数的结果与加权计数的结果相同。您不仅可以做余弦相似度的加权求和,还可以做加权平均。您还有许多其他方法可以聚合分数并推荐产品,所以富有创意是构建最终推荐输出的关键,这个输出是基于余弦相似度度量的。
如您从这些示例中可以看到,我们可以构建基于过去表现出相似行为的客户的产品集进行推荐。通常情况下,有相似口味的人会购买相似的商品,所以当根据客户群中的相似性推荐产品时,基于用户的协同过滤效果很好。除了基于客户相似性进行推荐外,我们还可以根据单个物品与其他物品的相似性进行推荐。在下一节中,我们将讨论如何基于物品的协同过滤构建推荐系统。
基于物品的协同过滤
与基于用户的协同过滤的情况一样,构建基于物品的协同过滤算法的关键起点是构建一个物品-物品相似度矩阵。从我们之前构建的客户-物品矩阵中,我们可以使用以下代码来构建一个物品-物品相似度矩阵:
item_item_sim_matrix = pd.DataFrame(
cosine_similarity(customer_item_matrix.T)
)
如此代码所示,我们首先转置客户-物品矩阵,customer_item_matrix
,这将使其成为一个物品-客户矩阵,其中每一行是一个物品,每一列是一个客户,每个值是 0 或 1,表示给定列是否购买了给定物品。然后,我们使用cosine_similarity
函数计算余弦相似度,并将结果保存为名为item_item_sim_matrix
的 pandas DataFrame 变量。
结果应该看起来像以下这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_12.png
图 7.12:物品-物品相似度矩阵
根据最相似物品进行推荐
基于项目相似性生成推荐的最简单方法是为给定项目找到最相似的项目并推荐它们。当一个人查看某个产品时,您想展示相关或相似的项目,可以使用这种方法。例如,当您在亚马逊上搜索产品并点击搜索页面上的某个产品时,亚马逊将在页面底部显示经常一起购买的相关或相似项目。这就是基于项目的协同过滤可以发挥作用的地方,更具体地说,当您想根据一个给定的产品推荐产品时。
使用我们的示例数据集,假设一位客户正在查看一个产品,“中号陶瓷顶盖储物罐”,其产品代码为23166
。从我们刚刚构建的项目到项目矩阵item_item_sim_matrix
中,我们可以使用以下代码找到最相似的 10 个项目:
most_similar_items = item_item_sim_matrix.loc[
"23166"
].sort_values(ascending=False).head(10)
如您在这段代码中所见,我们通过按余弦相似度降序排列项目来选择最相似的 10 个项目。输出结果应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_13.png
图 7.13:与产品 23166 最相似的 10 个项目
我们也可以根据这些产品代码查询描述,如下所示:
rec_items = [
x for x in most_similar_items.index if x != "23166"
]
df.loc[
df['StockCode'].isin(rec_items),
['StockCode', 'Description']
].drop_duplicates().set_index('StockCode').loc[rec_items]
如您所见,我们排除了产品23166
,因为它是要查看的目标项目或客户当前查看的项目,并展示了其余的 10 个最相似的项目。输出结果应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_14.png
图 7.14:包括自身在内的与产品 23166 最相似的 10 个项目
如您所想象,当客户查看目标产品时,我们可以在页面上展示这 10 个项目,包括它本身。这样,我们展示了最相似或最常一起购买的项目,以便客户有更多选择或更多商品可以购买。
根据购买历史推荐
在之前基于单个项目推荐产品的例子基础上,我们还可以为多个项目构建基于项目的协同过滤推荐系统。特别是当您可能正在发送营销邮件或新闻通讯时,您可能希望随邮件或通讯发送产品推荐。在这种情况下,考虑客户的购买历史将很有用,例如他们购买过哪些产品,他们在线查看过哪些产品页面,或者他们放入购物车中的产品。使用这些信息,我们可以利用基于项目的协同过滤算法为每位客户构建个性化的产品推荐,这些推荐对每位客户都是不同的。
以我们的示例数据集为例,假设一位客户购买了三个产品,产品代码分别为23166
、22720
和23243
。在我们的数据集中,这些项目是“3 个蛋糕托盘储物设计套装”、“中号陶瓷顶盖储物罐”和“茶咖啡糖罐储物套装”。您可以使用以下代码为这些项目获取最相似的 10 个项目:
item_item_sim_matrix[[
"23166", "22720", "23243"
]].mean(axis=1).sort_values(
ascending=False
).head(10)
如您从以下代码中看到的,我们查询了客户购买的那 3 个物品的物品到物品矩阵,并计算了余弦相似度的平均值。然后,我们将这些平均值按降序排序,以获取给定 3 个物品的最相似的前 10 个物品。输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_15.png
图 7.15:与给定 3 个物品最相似的 10 个物品
排除已经购买的商品,产品22722
排在第一位,是与给定项目集最相似的物品,其次是23165
。我们可以查询数据以获取这些最相似的前 10 个物品的产品描述。请看以下代码:
rec_items = [
x for x in item_item_sim_matrix[[
"23166", "22720", "23243"
]].mean(axis=1).sort_values(ascending=False).head(13).index
if x not in ["23166", "22720", "23243"]
]
df.loc[
df['StockCode'].isin(rec_items),
['StockCode', 'Description']
].drop_duplicates().set_index('StockCode').loc[rec_items]
在这里,我们从推荐列表中排除了客户已经购买的那 3 个物品,并检索了与这 3 个物品最相似或最常一起购买的顶级 10 个物品。此查询的输出应如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_07_16.png
图 7.16:与给定 3 个物品最相似的 10 个物品
如您所想,这些基于物品协同过滤算法结果的产品推荐可以用于任何营销活动。如果您正在构建电子邮件营销活动,您可以使用这个基于物品的协同过滤算法来选择基于先前购买、页面浏览或购物车项目的前相似产品,并将它们作为营销电子邮件的一部分。您还可以在网页上设置弹出窗口,以便当用户下次登录您的在线页面时使用。对这些商品提供折扣或其他促销活动也可以提高转换率。
您可以以多种方式应用基于用户和基于物品的协同过滤来构建推荐系统。随着您在推荐系统中的进展,跟踪您的推荐表现并调整您对它们的使用方式将是明智的。您很可能会想要跟踪您推荐的项目中有多少已经转换。
如在第二章中讨论 KPI 时所述,跟踪推荐项目中的转换和其他 KPI 可以告诉您您的推荐系统的有效性以及哪种方法更有效。
其他常用的推荐方法
我们已经深入讨论了市场篮子分析和协同过滤来构建个性化推荐系统。然而,还有许多其他方法可以构建这些推荐系统。如前所述,一些常见的基于 AI/ML 的方法是关联规则和协同过滤算法,这些我们在本章中已经介绍;预测建模方法也经常被使用,如今,所有这些方法的混合是构建更全面的推荐系统的典型方法。
不仅存在 AI/ML 驱动的推荐系统方法,还可以有各种其他方式来推荐产品或内容,甚至无需使用 AI/ML。以下是一些常用的推荐方法:
-
畅销书或热门观看:正如其名所示,基于畅销产品或最常观看的内容的推荐仍然被频繁使用。这有助于新用户或客户通过轻松查看他人查看或购买最多的内容来熟悉您的产品或平台。这也可以是构建您的入门级营销内容的好方法。
-
趋势:与畅销书类似,趋势商品显示了在特定时刻流行的内容。可能会有一些事件,例如某些地区的灾难、特定时刻的突发新闻或社区周围的假日活动,这些事件会偶尔激发客户的兴趣。对这些事件进行调整是明智的,因为这些特殊事件会触发某些客户行为,并且可以成为极佳的营销机会。
-
新品上市:作为营销人员,您很可能不想错过推广新产品的机会。现有客户或新客户通常会被新品吸引,这可以是一个很好的营销机会,以保留现有客户,同时吸引新客户。通过向对类似商品表示过兴趣的客户推荐新品,营销活动可以取得巨大成功。
-
促销:您不仅可以根据流行度或相关性推荐产品或内容,还可以推荐正在促销的产品或内容。客户通常会被特别优惠所吸引,您希望尽可能积极地展示和营销特别促销。这些是清理过剩库存、提高品牌知名度、吸引新客户和保留不活跃客户的好方法。
正如您所看到的,您可以在利用 AI/ML 驱动的技术基础上,以多种方式构建推荐系统。鉴于竞争环境和全球范围内几乎所有企业进行的营销活动丰富多样,仅使用一种方法来推荐产品或内容不太可能取得成功。您拥有的推荐系统越全面,您在吸引和保留客户方面就越成功。在设计推荐系统时,明智的做法是考虑本章中提到的所有方法,并在适当的时间和地点应用它们。
摘要
在本章中,我们讨论了构建个性化产品推荐系统。首先,我们讨论了如何通过分析哪些项目集经常一起购买来识别产品之间的关系。我们介绍了如何使用 Apriori 算法和关联规则在 Python 中进行市场篮子分析。然后,我们深入探讨了构建推荐系统的一种 AI/ML 驱动方法。我们看到了如何使用基于用户和基于物品的协同过滤算法来识别相似的用户或物品以及经常一起购买的产品。反过来,这些发现可以用来推荐其他客户很可能感兴趣并购买的产品。最后,我们讨论了可用于推荐产品和内容的各种其他方法。我们展示了如何结合非 AI/ML 方法可以导致一个更全面和多样化的推荐系统。
在下一章中,我们将继续讨论个性化的精准营销努力。更具体地说,我们将探讨如何在 Python 中进行客户细分,以及为什么深入了解您客户群中不同的客户细分有助于并影响您营销活动的成功与失败。
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人相聚,并在以下超过 5000 名成员的陪伴下学习:
第八章:使用机器学习进行客户细分
随着关于客户特征和行为数据的日益丰富,用更深入的见解来接近客户变得更加容易。从分析我们在第三章中讨论的客户参与背后的驱动因素,到理解个人客户可能喜欢哪些具体产品,这是我们第七章中提到的,这些方法的假设是基于存在某些行为方式相似的客户群体这一事实。
由于定向营销方法已被证明比大众营销更有效,因此客户细分一直是该领域经常讨论的话题。此外,随着无 cookie 世界的到来,第一方数据预计将发挥更加关键的作用。因此,基于客户细分(如地理细分、人口统计细分或兴趣主题细分)的策略,这些细分在浏览器 cookie 不可用的情况下仍然可用,对于成功至关重要。
在本章中,我们将涵盖以下主题:
-
一次性客户与回头客
-
使用 K-means 聚类和购买行为进行客户细分
-
使用大型语言模型(LLMs)和产品兴趣进行客户细分
一次性客户与回头客
根据提供了一些非常清晰的指标的BIA 顾问服务,回头客平均比新客户多花费 67%。此外,BIA 顾问服务进行的一项调查表明,超过一半的调查企业收入来自回头客,而不是新客户。这表明保留现有客户与扩大客户基础同样重要。然而,企业往往为了获取新客户而牺牲客户服务。
BIA 顾问服务报告
保留客户的需求
保留客户对业务来说如此重要的几个好处表明了原因:
- 投资减少:第一个显而易见的原因是新客户成本更高。如果您还记得第二章中典型的客户生命周期,您需要投入资本和营销资源来提高潜在新客户的品牌知名度,与潜在客户建立业务联系,然后最终将他们转化为付费客户。
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_01.png
图 8.1:客户生命周期
对于现有客户,您可以跳过这些步骤,专注于通过提供优质的客户服务和介绍他们感兴趣的产品,将他们作为回头客保留下来。获取新客户通常比保留现有客户成本高出数倍。
- 更高的可靠性: 重复客户为你的业务带来了更可靠和可重复的收入。如前所述,重复客户通常贡献了超过一半的业务收入,并且比新客户花费更多。随着你提供现有客户喜欢的产品或服务,以及你提供更好的客户服务,你的客户可能会对你的品牌忠诚,并继续购买产品。
例如,如果你在销售宠物产品,比如宠物食品或宠物玩具,并且他们喜欢你的产品,他们下个月可能会回来购买更多,甚至订阅以每月获得宠物食品的配送。这导致了一个可靠且可重复的收入流,这将加强你业务的现金流,你可以用这些资金投资更多到你的产品上,这将带来更多的收入。这开始了你业务的积极循环。
- 建立品牌忠诚度: 对你的业务忠诚的重复客户会带来更多的新客户。如你可能在 第二章 中回忆的那样,忠诚的客户充当你的品牌大使和营销代理,他们传播关于你业务的信息并吸引新客户。无需花费更多的营销费用,这些忠诚的客户就会推广你的业务并吸引新客户。
拥有一个强大的重复客户基础还有许多其他微妙的好处,但这些都是拥有重复客户最明显的三个好处,并展示了它们如何显著帮助业务。
分析保留客户的影响
让我们看看一个实际例子,看看这些重复客户与新客户相比对业务产生的影响:
源代码和数据: github.com/PacktPublishing/Machine-Learning-and-Generative-AI-for-Marketing/tree/main/ch.8
数据来源: archive.ics.uci.edu/dataset/352/online+retail
和往常一样,我们将首先将数据导入到一个 pandas
DataFrame 中。看看下面的代码:
import pandas as pd
df = pd.read_csv("./data.csv")
df = df.dropna()
df = df.loc[
df["Quantity"] > 0
]
这里,我们将数据加载到一个 pandas
DataFrame,df
中。因为我们只对比较新客户和重复客户感兴趣,我们将使用 dropna
函数删除包含 NaN
值的行,并通过 df["Quantity"] > 0
过滤只取至少购买了一件或更多商品的客户。
-
然后,我们将创建以下额外的变量:
-
销售额: 通过简单地将客户购买的数量乘以单个商品的价格,我们可以得到每个订单的总销售额。在下面的代码中,我们创建了一个名为
Sales
的新列,它包含每个订单的总销售额:df["Sales"] = df["Quantity"] * df["UnitPrice"].
-
月份变量:为了确定一个客户是否是新客户,我们需要考虑数据的时间范围。如果一个客户之前没有购买任何商品,那么这个客户将被视为新客户。另一方面,如果一个客户之前购买过商品,那么我们将这个客户视为回头客。在这个练习中,我们将查看月度数据,这需要我们创建一个变量来表示发票是在哪个月创建的。在下面的代码中,我们将
InvoiceDate
列转换为datetime
类型,并将InvoiceDate
转换为每个月。例如,日期2011-02-23
将被转换为2011-02-01
:df["InvoiceDate"] = pd.to_datetime(df["InvoiceDate"]) df["month"] = df["InvoiceDate"].dt.strftime("%Y-%m-01")
-
-
现在我们有了这两个变量,我们可以开始分析销售是否来自新客户或回头客。看看下面的代码:
monthly_data = [] for each_month in sorted(df["month"].unique()): up_to_last_month_df = df.loc[ df["month"] < each_month ] this_month_df = df.loc[ df["month"] == each_month ] curr_customers = set(this_month_df["CustomerID"].unique()) prev_customers = set(up_to_last_month_df["CustomerID"].unique()) repeat_customers = curr_customers.intersection(prev_customers) new_customers = curr_customers - prev_customers curr_sales = this_month_df["Sales"].sum() sales_from_new_customers = this_month_df.loc[ this_month_df["CustomerID"].isin(new_customers) ]["Sales"].sum() sales_from_repeat_customers = this_month_df.loc[ this_month_df["CustomerID"].isin(repeat_customers) ]["Sales"].sum() avg_sales_from_new_customers = this_month_df.loc[ this_month_df["CustomerID"].isin(new_customers) ]["Sales"].mean() avg_sales_from_repeat_customers = this_month_df.loc[ this_month_df["CustomerID"].isin(repeat_customers) ]["Sales"].mean() monthly_data.append({ "month": each_month, "num_customers": len(curr_customers), "repeat_customers": len(repeat_customers), "new_customers": len(new_customers), "curr_sales": curr_sales, "sales_from_new_customers": sales_from_new_customers, "sales_from_repeat_customers": sales_from_repeat_customers, "avg_sales_from_new_customers": avg_sales_from_new_customers, "avg_sales_from_repeat_customers": avg_sales_from_repeat_customers, })
让我们更仔细地看看这段代码。我们首先遍历month
变量中的每个月。对于每次迭代,我们找到给定月份的唯一客户,并将它们存储为一个集合,命名为curr_customers
。我们同样为过去客户做同样的操作,通过获取到给定月份的唯一客户,并将它们存储为一个集合,命名为prev_customers
。基于这两个变量,我们可以通过一些集合操作来识别新客户和回头客:
-
首先,我们找到
curr_customers
和prev_customers
的交集,这代表回头客,因为我们已经在销售数据中看到了这些客户。 -
接下来,我们从
curr_customers
集合中减去prev_customers
集合,这给出了新客户,因为这些是我们之前没有见过的客户。通过这些操作,我们已经成功识别了新客户和回头客。
基于这些prev_customers
和curr_customers
集合,我们可以找到新客户和回头客带来的收入。使用isin
函数,我们选择与new_customers
集合中的 ID 匹配的CustomerIDs
,通过将这些客户的全部销售额相加来计算新客户的总销售额,并通过取这些客户的全部销售额的平均值来计算新客户的平均销售额。同样,使用isin
函数,我们选择与repeat_customers
集合中的 ID 匹配的CustomerIDs
,通过将这些客户的全部销售额相加来计算回头客的总销售额。我们通过取这些客户的全部销售额的平均值来计算回头客的平均销售额。我们将这些数据保存到一个变量中,名为monthly_data
。
-
我们进行最后一组计算,如下所示,并将准备好查看新客户和回头客之间的差异:
monthly_data_df = pd.DataFrame(monthly_data).set_index("month").iloc[1:-1] monthly_data_df["repeat_customer_percentage"] = monthly_data_df["repeat_customers"]/monthly_data_df["num_customers"] monthly_data_df["repeat_sales_percentage"] = monthly_data_df["sales_from_repeat_customers"]/monthly_data_df["curr_sales"]
这段代码只是将数据转换成pandas
DataFrame,并计算有多少比例的客户是回头客,以及有多少比例的销售来自回头客。
现在我们已经完成了,让我们使用以下代码查看每月客户数量以及新客户和回头客之间的细分:
ax = monthly_data_df[[
"new_customers", "repeat_customers"
]].plot(kind="bar", grid=True, figsize=(15,5))
(monthly_data_df["repeat_customer_percentage"]*100).plot(
ax=ax, secondary_y=True, color="salmon", style="-o"
)
ax.right_ax.legend()
ax.right_ax.set_ylim([0, 100.0])
ax.right_ax.set_ylabel("repeat customer percentage (%)")
ax.set_ylabel("number of customers")
ax.set_title("number of new vs. repeat customers over time")
plt.show()
这应该生成以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_02.png
图 8.2:新客户与回头客的数量对比
每个时间段的左侧条形表示每个月的新客户数量,右侧条形表示回头客的数量。正如你所看到的,回头客的构成随着时间的推移而增长,新客户的流入量也存在某种周期性。我们在年初和年底看到更多的新客户,而在六月至八月夏季期间有所下降。
线形图显示了每个月客户中回头客所占的百分比,正如这个图表所暗示的,它从 2011 年 1 月的大约 40%增长到 2011 年 11 月的大约 80%。
这表明这是一个非常健康的业务。它显示了来自已经从这个业务中进行过购买的客户持续的需求和购买。回头客的数量持续增长,这表明这个业务提供的产品和服务持续吸引那些曾经与这个业务互动过的客户。对于一个缺乏吸引人的产品以及/或者良好客户服务的业务,回头客的数量通常会减少。不过,这里需要注意的是,新客户的流入率相对稳定,并没有增长。当然,这比随着时间的推移新客户数量减少要好,但这表明有增长潜力来吸引更多的新客户。鉴于有一个健康的回头客和重复购买客户群,营销人员可以更多地关注这个业务的新客户获取。
同样,让我们看看新客户和回头客的销售金额。看看以下代码:
ax = (monthly_data_df[[
"sales_from_new_customers", "sales_from_repeat_customers"
]]/1000).plot(kind="bar", grid=True, figsize=(15,5))
(monthly_data_df["repeat_sales_percentage"]*100).plot(
ax=ax, secondary_y=True, color="salmon", style="-o"
)
ax.set_ylabel("sales (in thousands)")
ax.set_title("sales from new vs. repeat customers over time")
ax.right_ax.legend()
ax.right_ax.set_ylim([0, 100.0])
ax.right_ax.set_ylabel("repeat customer percentage (%)")
plt.show()
这段代码生成以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_03.png
图 8.3:新客户与回头客的销售对比
与之前类似,左侧的条形表示新客户的销售额,右侧的条形表示回头客的销售额。正如之前在许多客户比较中看到的那样,回头客的销售额超过了新客户的销售额。这表明回头客有很强的持续重复收入。回头客的销售额百分比在 2011 年 11 月达到了 80%以上。这在我们之前讨论过回头客通常占企业收入一半以上时是预料之中的。之前讨论并报告的另一个观点是由BIA Advisory Services提出的,即回头客通常比新客户多花费 67%。
现在让我们通过比较新客户和回头客的平均月销售额来查看我们的数据对这个业务说了什么。看看以下代码:
monthly_data_df["repeat_to_new_avg_sales_ratio"] = (
monthly_data_df["avg_sales_from_repeat_customers"]
/
monthly_data_df["avg_sales_from_new_customers"]
)
ax = monthly_data_df[[
"avg_sales_from_new_customers", "avg_sales_from_repeat_customers"
]].plot(kind="bar", grid=True, figsize=(15,5), rot=0)
ax.set_ylabel("average sales")
ax.set_title("sales from new vs. repeat customers over time")
monthly_data_df["repeat_to_new_avg_sales_ratio"].plot(
ax=ax, secondary_y=True, color="salmon", style="-o"
)
ax.right_ax.set_ylim([0, 2.0])
ax.right_ax.set_ylabel("repeat to new customer avg sales ratio")
plt.show()
这段代码应该生成以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_04.png:新客户与回头客的平均销售额
类似地,左侧的柱状图是新客户的平均销售额,右侧的柱状图是回头客的平均销售额,每个月都有。折线图显示了新客户平均销售额与回头客平均销售额之间的比率。例如,如果比率是1.5
,这意味着回头客在那个月平均花费是新客户的1.5
倍。根据这个示例数据集,我们看到,在所有报告的月份中,回头客的平均花费都高于平均客户。在 2011 年 11 月,比率是1:1.58
,这意味着回头客平均比新客户多花费了大约 60%。这与BIA 顾问服务的报告相符,该报告指出回头客平均比新客户花费更多。
在这个练习中,我们将客户基础划分为两个简单的部分:新客户与回头客。正如你可能已经注意到的,这是一项简单的分析练习,但它能产生对业务动态和健康状况的深刻见解,同时也告诉营销人员应该关注哪一群客户。
如果有一个强大的回头客基础,并且有持续的收入,但新客户基础显示出稳定或下降的趋势,这表明营销人员应该更加关注新客户的获取。
另一方面,如果回头客的数量下降,这可能表明产品和服务可能对顾客没有吸引力,或者客户服务未能达到顾客的期望。在这种情况下,营销人员应该专注于改进产品营销策略、客户满意度策略或其他营销策略,以吸引顾客进行重复购买。
基于购买行为的客户细分
根据新客户与回头客对客户进行细分是进行的基本且关键的分析之一。然而,很多时候,我们希望根据多个因素对客户进行细分,这些因素可以是人口统计因素,如年龄、地理位置和职业,或者是购买历史,如他们在过去一年中花费了多少钱,他们购买了哪些商品,以及他们请求了多少次退货。你还可以根据客户的网络活动进行细分,例如过去 X 天内登录的次数,他们在你的网页上停留的时间,以及他们查看的页面。
根据这些因素对客户进行细分仍然存在挑战,因为你可以以无数种方式和值来细分客户。例如,如果你根据年龄细分客户,可能会出现一些问题,比如“我应该创建多少个桶?”或者“我应该选择什么年龄截止阈值?”类似地,如果你想根据过去销售量细分客户,你仍然需要选择用于细分客户基础和希望创建多少个细分的阈值。
此外,当你将多个因素结合这些细分时,细分数量会呈指数增长。例如,如果你基于销售量有 2 个细分,并将其与其他基于购买数量的 2 个细分相结合,你将最终得到 4 个细分。如果你对两个因素都有 3 个细分,那么总共将得到 9 个细分。总之,在执行客户细分时,主要有三个关键问题需要回答:
-
应该使用哪些因素进行客户细分?
-
应该创建多少个细分?
-
应该使用什么阈值来细分到细分?
我们将使用之前使用过的在线零售数据集作为示例,并讨论如何使用 K-means 聚类算法和轮廓分数来回答这些关键问题,以衡量聚类的有效性。
K-means 聚类
K-means 聚类是用于聚类和分割的最常用的机器学习算法之一。它是一种将数据划分为k个聚类的算法。简而言之,该算法迭代地找到质心并将数据点分组到最近的质心,直到数据点比其邻近的质心更接近其质心。正如你可以想象的那样,我们案例中的数据点将是感兴趣的因子,例如销售额、数量和退款,而在 K-means 聚类中的“k”是我们希望创建多少个聚类或客户细分。
为了根据总销售额、订单数量和退款创建客户细分,我们需要做一些准备工作。看看以下代码:
# Net Sales & Quantity
customer_net_df = df.groupby('CustomerID')[["Sales", "Quantity"]].sum()
customer_net_df.columns = ['NetSales', 'NetQuantity']
# Total Refunds
customer_refund_df = df.loc[
df["Quantity"] < 0
].groupby("CustomerID")[["Sales", "Quantity"]].sum().abs()
customer_refund_df.columns = ['TotalRefund', 'TotalRefundQuantity']
customer_df = customer_net_df.merge(
customer_refund_df, left_index=True, right_index=True, how="left"
).fillna(0)
在这里,我们首先获取每个客户的净销售额和数量。这将减去任何退款和退货项,因为有一些记录的销售额和数量值为负。接下来,我们获取关于退款的信息。我们假设任何负数数量都是退款。因此,我们通过求和所有负数量值的销售额来获取总退款金额,通过求和所有负数量值的订单数量来获取总退款数量。最后,我们通过索引(即客户 ID)合并这两个 DataFrame。生成的 DataFrame 应该看起来像以下这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_05.png
图 8.5:生成的净销售额、净数量、退款金额和退款数量
这里要注意的一点是数据高度偏斜。看看以下代码,这是我们用来生成直方图的代码:
customer_df.hist(bins=50, figsize=(15,10))
plt.show()
这给我们以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_06.png
图 8.6:数据分布
如从这些直方图中所见,数据高度右偏斜。这种情况经常发生,尤其是在数据具有货币或数量值时。数据的偏斜会导致不均衡的聚类,数据分割不理想。克服这种偏斜的一个简单方法是对数据进行对数变换,以下代码为证:
log_customer_df = np.log(customer_df - customer_df.min() + 1)
我们可以使用以下代码生成对数变换数据的直方图:
log_customer_df.hist(bins=50, figsize=(15,10))
plt.show()
以下是由此生成的直方图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_07.png
图 8.7:对数变换后的数据分布
如预期的那样,对数变换后的数据更集中在平均值周围,更接近钟形曲线。我们将检查带有和对数变换的聚类,并看看它如何影响聚类结果。
无对数变换
在 Python 中训练 K-means 聚类算法很简单。看看以下代码,我们在scikit-learn
包中导入KMeans
模块来构建 K-means 聚类算法:
from sklearn.cluster import KMeans
COLS = ['NetSales', 'NetQuantity', 'TotalRefundQuantity']
kmeans = KMeans(
n_clusters=4, n_init="auto"
).fit(
customer_df[COLS]
)
在这个例子中,我们根据三个列,NetSales
、NetQuantity
和TotalRefundQuantity
构建客户细分。然后,我们使用参数n_clusters
构建四个聚类。训练好的KMeans
模型的labels_
属性为每一行或客户分配了标签(0 到 3),而cluster_centers_
属性显示了每个聚类的中心。
K-means 聚类中的随机性
由于 K-means 聚类是一种迭代方法,用于从原始随机选择的中心更新中心,因此 K-means 聚类中存在随机性,导致每次运行时结果略有不同。如果您希望每次都得到相同的结果,您应该设置random_state
变量。
本章中的示例没有使用random_state
变量,所以您的结果可能看起来与您看到的图表略有不同。
现在,让我们可视化聚类,这样我们就可以直观地检查 K-means 聚类算法如何使用我们感兴趣的三个因素(NetSales
、NetQuantity
和TotalRefundQuantity
)将客户群分割开来:
import matplotlib.colors as mcolors
def plot_clusters(c_df, col1, col2): colors = list(mcolors.TABLEAU_COLORS.values())
clusters = sorted(c_df["cluster"].unique())
for c in clusters:
plt.scatter(
c_df.loc[c_df['cluster'] == c][col1],
c_df.loc[c_df['cluster'] == c][col2],
c=colors[c]
)
plt.title(f'{col1} vs. {col2} Clusters')
plt.xlabel(col1)
plt.ylabel(col2)
plt.legend(clusters)
plt.show()
cluster_df = customer_df[COLS].copy()
cluster_df["cluster"] = kmeans.labels_
plot_clusters(cluster_df, "NetSales", "NetQuantity")
plot_clusters(cluster_df, "NetSales", "TotalRefundQuantity")
plot_clusters(cluster_df, "NetQuantity", "TotalRefundQuantity")
在这里,我们首先定义一个函数,plot_clusters
,用于绘制每个聚类的 2D 图,它接受 DataFrame 作为输入,其中包含用于 x 轴和 y 轴的两个列。然后,我们创建三个图表:一个用于根据NetSales
和NetQuantity
可视化聚类,另一个用于NetSales
和TotalRefundQuantity
,第三个用于NetQuantity
和TotalRefundQuantity
。这三个生成的图表应该看起来像以下这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_08.png
图 8.8:基于 NetSales 与 NetQuantity 的聚类
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_09.png
图 8.9:基于 NetSales 与 TotalRefundQuantity 的聚类
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_10.png
图 8.10:基于 NetQuantity 与 TotalRefundQuantity 的聚类
在图 8.8、8.9和8.10中,我们可以很容易地看到聚类是如何基于成对形成的。例如,聚类0
似乎代表的是那些净销售额、净数量和退款都较低的客户,而聚类1
似乎代表的是那些净销售额、净数量和退款处于中低水平的客户。然而,在这个例子中有两点值得关注:
-
一些聚类具有广泛的点范围。以聚类
0
为例,如果查看图 8.10,其退款数量范围从0
到80,000
,这使得描述聚类0
实际上代表什么以及它与其他聚类有何不同变得困难。 -
这里还有一个需要注意的事项,那就是大多数数据点都在聚类
0
中,而其他聚类中的数据点非常少。聚类3
似乎只包含两个数据点。这些聚类大小的巨大不平衡使得从这些聚类中得出的概括不太可靠,因为基于少量数据点的见解并不可靠。
您可以使用以下代码查看每个聚类中的数据点数量:
cluster_df.groupby('cluster')['NetSales'].count()
下面是每个聚类的详细信息:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_11.png
图 8.11:每个聚类中的数据点数量
数据偏斜经常导致这些问题,因为它使得 K-means 聚类算法难以找到或有效地聚类数据点。这就是为什么当数据存在偏斜时,我们需要在应用聚类算法之前对数据进行归一化的原因。
使用对数变换
让我们看看对数变换如何可能帮助使用 K-means 聚类进行客户细分。看一下以下代码:
COLS = ['NetSales', 'NetQuantity', 'TotalRefundQuantity']
kmeans = KMeans(
n_clusters=4, n_init="auto"
).fit(
log_customer_df[COLS]
)
cluster_df = log_customer_df[COLS].copy()
cluster_df["cluster"] = kmeans.labels_
在这里,我们使用之前定义的变量log_customer_df
,它是使用np.log
函数进行对数变换的数据。然后我们拟合了一个包含四个聚类的 K-means 聚类模型。现在我们可以看到聚类的大小如下:
cluster_df.groupby('cluster')['NetSales'].count()
这给我们以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_12.png
图 8.12:对数变换后每个聚类中的数据点数量
与本章早期我们拟合聚类模型时没有进行对数变换相比,聚类现在更加均衡,每个聚类都有显著数量的客户。这将使我们能够更好地了解客户是如何基于我们感兴趣的三个因素(净销售额、净数量和总退款)进行细分的。让我们用以下代码可视化聚类:
plot_clusters(cluster_df, "NetSales", "NetQuantity")
plot_clusters(cluster_df, "NetSales", "TotalRefundQuantity")
plot_clusters(cluster_df, "NetQuantity", "TotalRefundQuantity")
这段代码应该创建三个类似于以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_13.png
图 8.13:转换后的 NetSales 与 NetQuantity 的聚类
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_14.png
图 8.14:NetSales 与转换后的 TotalRefundQuantity 的聚类
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_15.png
图 8.15:转换后的 NetQuantity 与 TotalRefundQuantity 的聚类
让我们深入探讨这些集群的可视化。如果您查看 图 8.13,基于净销售额和净数量,集群之间没有明显的分离。集群 3
似乎更集中在平均值附近,而集群 1
则更倾向于更高的净销售额和更高的净数量,集群 0
和 2
则更倾向于更低的净销售额和更低的净数量。尽管有这些细微的差异,所有集群似乎都聚集在净销售额和净数量的平均值周围。
在 图 8.14 和 图 8.15 中,集群之间的区别更为明显。集群 1
似乎是一个具有高总退款的客户群体,其截止阈值在对数尺度上约为 4
。您可以通过撤销我们之前应用的转换或使用以下代码来转换这个对数转换的值:
np.exp(X) + customer_df.min()[COLUMN] - 1
代码中的 X
是对数尺度上的值,而 COLUMN
是感兴趣的因子。让我们更仔细地看看这些集群:
-
集群 0:这个集群似乎是一个具有低总退款和低净数量的集群。
TotalRefundQuantity
在对数尺度上的阈值约为2
和6.7
对于NetQuantity
。这些数字相当于总退款中的6.4
和净数量中的811.4
,使用前面的公式。这表明这个集群中的客户总退款少于6.4
,净数量少于811.4
。 -
集群 1:这个集群似乎是一组退款频率最高的客户。集群
1
的截止阈值在对数尺度上TotalRefundQuantity
中约为4
,使用上面的代码,这相当于54 (np.exp(4) + customer_df.min()["TotalRefundQuantity"] - 1)
。因此,集群1
是总退款超过54
的客户群体。 -
集群 2:这个集群似乎是一个具有中等总退款且对数尺度上
TotalRefundQuantity
的阈值约为2
的客户群体,这相当于实际总退款中的6
。因此,集群2
是总退款在6
到54
之间的客户群体。 -
集群 3:集群
3
中的客户似乎具有低总退款但高净数量的特点。这个客户群体可能是业务的甜点,因为它表明他们经常从该业务购买,但退款数量并不像其他不同客户群体中的客户那么多。鉴于他们在对数尺度上总退款的下限约为2
和6.7
对于净数量,这个客户群体在总退款中少于6
,而在净数量中多于811
。
正如你在这次练习中看到的,K-means 聚类算法有助于以机器学习的方式对客户进行分段,而无需你自己手动为每个客户分段定义阈值。这种程序化方法帮助你更动态、更数据驱动地定义客户分段。这样,你可以更好地理解不同的客户分段是如何组合的,以及分离因素是什么,这些因素随后可以用来为你的未来营销工作制定策略和优先级。
轮廓系数
我们已经看到了如何使用 K-means 聚类算法构建客户分段。构建聚类的一个关键论点是聚类的数量。然而,你可能想知道在构建这样的聚类之前,你如何决定或如何知道正确的聚类数量。在实际环境中,你可能想要构建具有不同数量的聚类的多个聚类,并决定哪一个效果最好。这就是轮廓系数发挥作用的地方。简单来说,轮廓系数是一个度量,它量化了给定数据点与其聚类中其他点的匹配程度以及它与其他聚类的可区分性。轮廓系数的公式如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_001.png
在这里,a[i] 是第 i 个数据点到同一聚类中所有其他点的平均距离,而 b[i] 是第 i 个数据点到其他聚类中所有点的最小平均距离。
为了获得聚类算法的轮廓系数,你需要计算每个数据点的所有单个轮廓系数的平均值。轮廓系数的范围在 -1
和 1
之间。分数越接近 1
,数据点聚在一起的程度就越好,它们与相邻聚类的区别就越明显。
轮廓系数在 Python 中很容易计算。Python 的 scikit-learn
包中有一个名为 silhouette_score
的函数,它可以计算聚类的平均轮廓系数:
from sklearn.metrics import silhouette_score
silhouette_score(
log_customer_df[COLS],
kmeans.labels_
)
由于更高的轮廓系数值表明聚类更好,我们可以利用这一点来决定理想的聚类数量。看看以下代码,我们在 4
到 8
之间尝试不同的聚类大小:
COLS = ['NetSales', 'NetQuantity', 'TotalRefundQuantity']
f, axes = plt.subplots(2, 3, sharey=False, figsize=(12, 7))
for i, n_cluster in enumerate([4,5,6,7,8]):
kmeans = KMeans(n_clusters=n_cluster, n_init="auto").fit(
log_customer_df[COLS]
)
silhouette_avg = silhouette_score(
log_customer_df[COLS],
kmeans.labels_
)
print('Silhouette Score for %i Clusters: %0.4f' % (n_cluster, silhouette_avg))
each_cluster_size = [
(kmeans.labels_ == i).sum()/len(kmeans.labels_) for i in range(n_cluster)
]
ax = axes[i//3][i%3]
pd.DataFrame(each_cluster_size).plot(ax=ax, kind="barh", color="orange")
for p in ax.patches:
ax.annotate(f'{p.get_width()*100:.01f}%', (p.get_width(), p.get_y()+0.2))
ax.axvline(x=(1/n_cluster), color="red", linestyle="--")
ax.set_xlabel("Cluster Size", size=8)
ax.set_title(f"Cluster #{n_cluster} - Silhouette: {silhouette_avg:.02f}")
ax.title.set_size(8)
f.subplots_adjust(hspace=0.3)
plt.show()
对于每个聚类,我们计算轮廓系数并检查每个聚类中有多少百分比的数据点。理想情况下,最佳的聚类是轮廓系数最高且每个聚类中数据点均匀分布的聚类。以下代码应该生成以下输出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_16.png
图 8.16:聚类大小实验结果
此图表显示了当你确定最佳聚类大小时应考虑的几个重要决策因素:
-
轮廓得分:首先,我们可以看到每个簇大小的轮廓得分,其中大小为
4
的簇得分为0.4986
,大小为5
的簇得分为0.5391
,依此类推。在这里,大小为5
的簇的轮廓得分似乎是最高的,为0.5391
。 -
簇大小和组成:我们还应该评估每个簇中的数据点是否分布得相对均匀,因为簇之间的大规模不平衡可能不足以生成最佳的洞察。图 8.16中的水平条形图显示了每个簇大小的簇组成。虚线垂直线显示了如果组成完全均匀分布,条形应该在哪里。从这些图表中可以看出,高轮廓得分的簇并不一定意味着最佳的簇。例如,簇
5
具有最高的整体轮廓得分,但存在很大的不平衡,其中第0
个簇有接近 70%的数据点,其余部分共享一小部分数据。这不是一个理想的簇,因为数据过于集中在单个簇中。
在这个例子中,大小为4
的簇可能是最佳选择,因为与其它簇相比,数据点在簇之间分布得更均匀,尽管轮廓得分不是最高的。
基于产品兴趣的客户细分
我们在上一节中讨论了如何根据他们的购买历史构建客户细分,以及这如何帮助营销人员确定优先考虑和为下一次营销活动制定策略的细分。我们不仅可以根据购买历史或更具体地说,根据数值对客户进行细分,还可以根据他们的产品兴趣找到客户细分。
客户购买的商品中隐藏着关于每个客户感兴趣的商品类型以及他们可能购买更多的商品的见解。根据客户过去购买的产品进行客户细分有多种方法,例如简单地根据他们购买的产品类别进行分组。然而,在这个练习中,我们将扩展到第五章中提到的嵌入向量这一主题。
如果您还没有安装,您可能需要使用pip
安装 Hugging Face 的transformers
:
pip install transformers
如前文在第五章中所述,现代 LLM 如BERT和GPT引入了上下文嵌入,将单词和句子转换成表示上下文意义的数值或向量。我们将使用 Hugging Face 的预训练 LLM all-MiniLM-L6-v2
对示例数据集中每个客户的过去产品购买进行编码,并使用这些嵌入向量构建客户细分。请看以下代码:
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
from sentence_transformers import SentenceTransformer, util
customer_item_df = pd.DataFrame(
df.groupby("CustomerID")["Description"].apply(
lambda x: ", ".join(list(set(x)))
)
)
embedding_model = SentenceTransformer(
"sentence-transformers/all-MiniLM-L6-v2"
)
encoded = embedding_model.encode(
list(customer_item_df["Description"]),
show_progress_bar=True
)
with open('tmp.npy', 'wb') as f:
np.save(f, encoded)
在这里,我们首先获取客户购买的所有产品描述,然后为每个客户创建一个以逗号分隔的列表,该列表存储在变量customer_item_df
中。然后,我们加载预训练的 LLM,sentence-transformers/all-MiniLM-L6-v2
,并使用预训练 LLM 的encode
函数将每个客户的商品描述列表编码成数值向量。这将导致每个客户有一个 384 值的向量。然后我们将这个数组保存到tmp.npy
中,以供将来使用。
在高维空间中,随着维度的增加,数据点之间的距离变得不那么有意义,因为更大的维度使得数据过于稀疏。由于 K-means 聚类算法使用距离度量来聚簇数据点,这成为一个问题。为了克服这个问题,我们需要应用一些降维技术。在这个练习中,我们将简单地应用主成分分析(PCA)来降低嵌入向量的维度,同时保留数据中的大部分方差:
-
请看以下代码:
from sklearn.decomposition import PCA with open('tmp.npy', 'rb') as f: encoded = np.load(f) pca = PCA(n_components=5) transforemd_encoded = pca.fit_transform(encoded)
在这里,我们导入scikit-learn
包中的PCA
模块。我们从临时位置tmp.npy
导入之前构建的嵌入向量,并使用fit_transform
函数对向量进行拟合和转换。我们将其定义为返回 5 个成分,正如您可以从n_components
参数中看到的那样。结果向量transforemd_encoded
应该有 5 个向量,每个客户一个。
其他降维方法
除了 PCA 之外,还有许多降维技术。在我们的练习中,我们使用 PCA 是因为它的简单性,但 T-SNE 和 UMAP 是处理高维数据时经常使用的另外两种技术。务必查看它们,看看它们是否可能更适合这个练习!
T-SNE: scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html
UMAP: umap-learn.readthedocs.io/en/latest/
-
现在是时候根据这些嵌入向量构建客户细分或聚类了,这些嵌入向量对每个客户购买的产品有上下文理解。请看以下代码:
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_samples, silhouette_score for n_cluster in [4,5,6,7,8]: kmeans = KMeans(n_clusters=n_cluster, n_init="auto").fit( transforemd_encoded ) silhouette_avg = silhouette_score( transforemd_encoded, kmeans.labels_ ) print('Silhouette Score for %i Clusters: %0.4f' % (n_cluster, silhouette_avg))
这段代码看起来应该很熟悉,因为这与我们之前使用 K-means 聚类算法根据购买历史构建聚簇时几乎完全相同的代码。这里的主要区别是,我们不是使用销售指标,而是使用嵌入向量作为KMeans
的输入来构建聚类。这段代码的输出应该看起来像以下这样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_17.png
图 8.17:不同聚类大小的轮廓得分
实际值可能会有所不同,因为拟合 K-means 聚类算法时存在一些随机性,但趋势应该是相似的。
-
基于此,我们打算基于嵌入向量构建 7 个集群,如下面的代码所示:
n_cluster = 7 kmeans = KMeans(n_clusters=n_cluster, n_init="auto").fit( transforemd_encoded ) customer_item_df["cluster"] = kmeans.labels_ from collections import Counter n_items = 5 common_items = [] for i in range(n_cluster): most_common_items = Counter(list(df.set_index("CustomerID").loc[ customer_item_df.loc[ customer_item_df["cluster"] == i ].index ]["Description"])).most_common(n_items) common_items.append({ f"item_{j}": most_common_items[j][0] for j in range(n_items) }) common_items_df = pd.DataFrame(common_items)
在此代码中,我们构建了 7 个集群。然后,对于每个集群,我们使用 Python 中的collections
库获取每个集群中客户购买的最常见的 5 个商品。然后,我们将每个集群中最常购买的 5 个商品存储在一个名为common_items_df
的 DataFrame 中。这个 DataFrame 应该能让我们了解哪些类型的商品最吸引每个客户细分市场。
让我们更仔细地看看这个 DataFrame:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-genai-mkt/img/B30999_08_18.png
图 8.18:每个集群前 5 个常见商品
这给我们带来了以下见解:
-
第一集群或索引为 0 的集群的客户似乎对一些装饰品最感兴趣,例如饰品和木框。
-
第二集群或索引为 1 的集群的客户似乎对与派对相关的商品感兴趣,例如烘焙套装、派对彩旗和卡片。
-
第三集群或索引为 2 的集群的客户似乎对茶或茶道很感兴趣,因为他们购买了茶杯和茶托。
-
第四集群的客户似乎喜欢购买包。
-
第五集群的客户似乎喜欢水壶等等。
如您所见,这些客户集群显示了他们最感兴趣的商品,以及如何根据他们的产品兴趣将不同的客户分组在一起。这些信息将在您构建下一轮营销策略和活动时非常有用。您可能不想向那些对购买水壶感兴趣的人推广茶具,反之亦然。这种不匹配的目标定位将导致浪费的营销活动,参与和转化的成功率低。您希望根据不同客户细分市场的产品兴趣,为每个细分市场构建更精准的营销活动,并针对他们最感兴趣的品类进行推广。
这样做,你更有可能进行成功的营销活动,在吸引客户参与和转化方面取得更多成功。
摘要
在本章中,我们讨论了不同分割客户群的方法。我们首先探讨了新客户与回头客对收入的影响,以及新客户和回头客数量的月度进展如何告诉我们下一次营销活动应关注哪个细分市场或客户群体。然后,我们讨论了如何使用 K-means 聚类算法编程构建和识别不同的客户细分市场。通过销售金额、订单数量和退款,我们实验了如何使用这些因素构建不同的客户细分市场。在不进行实际操作的情况下,我们提到了轮廓分数作为寻找最佳聚类数量的标准,以及在对高度偏斜的数据集进行处理时,对数变换可能是有益的。最后,我们使用单词和句子嵌入向量将产品描述转换为具有上下文理解的数值向量,并基于他们的产品兴趣进一步构建客户细分市场。
在接下来的章节中,我们将进一步探索 LLMs。从使用预训练的零样本模型创建引人入胜的内容,到更高级的少样本和 RAG 方法,我们将在下一章更多地涉及 LLMs 和生成式 AI。
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人交流,并和超过 5000 名成员一起学习:
更多推荐
所有评论(0)