本章揭示了使用PySpark
处理文本数据的一些基本技术。如今数据的文本形式正在闪电般地生成,多个社交媒体平台为用户提供了分享他们的意见,建议,评论等的选项。专注于使机器学习和理解文本数据以便执行某些操作的领域有用的任务称为自然语言处理(NLP)。文本数据可以是结构化的或非结构化的,我们必须应用多个步骤才能使分析准备就绪。 NLP已经成为多个应用程序的巨大贡献者。如今,企业大量使用NLP的许多应用,例如聊天机器人,语音识别,语言翻译,推荐系统,垃圾邮件检测和情绪分析。本章演示了一系列步骤,以便处理文本数据并在其上应用机器学习算法。它还展示了序列嵌入,可用作传统输入特征的替代分类。
没有正确的方法进行NLP分析,因为人们可以探索多种方式并采用不同的方法来处理文本数据。 但是,从机器学习的角度来看,应该采取五个主要步骤来使文本数据准备好进行分析。 NLP涉及的五个主要步骤是: 1.阅读语料库(Corpus) 2.标记化(Tokenization) 3.清除/删除停用词 4.Stemming 5.转换为数字形式 在进入加载和清理文本数据的步骤之前,让我们熟悉一个称为语料库的术语,因为这将继续出现在本章的其余部分。
语料库被称为整个文本文档集合。 例如,假设我们在一个集合中有数千封电子邮件,我们需要处理和分析这些电子邮件以供我们使用。 这组电子邮件称为语料库,因为它包含所有文本文档。 文本处理的下一步是标记化。
将给定句子或文本文档的单词集合划分为单独/单独单词的方法称为标记化。 它删除了不必要的字符,如标点符号。 例如,如果我们有一个句子,例如:
输入:He really liked the London City. He is there for two more days.
Tokens: He, really, liked, the, London, City, He, is, there, for, two, more, days
对于上面的输入句子,我们最终得到13个Tokens。
让我们看看如何使用PySpark
进行标记化。 第一步是创建一个包含文本数据的数据框。
[In]: df=spark.createDataFrame([(1,'I really liked this movie'),
(2,'I would recommend this movie to my friends'),
(3,'movie was alright but acting was horrible'),
(4,'I am never watching that movie ever again')],
['user_id','review'])
[In]: df.show(4,False)
[Out]:
+-------+------------------------------------------+
|user_id|review |
+-------+------------------------------------------+
|1 |I really liked this movie |
|2 |I would recommend this movie to my friends|
|3 |movie was alright but acting was horrible |
|4 |I am never watching that movie ever again |
+-------+------------------------------------------+
在这个dataframe
中,我们有四个用于标记化的句子。 下一步是从Spark
库导入Tokenizer
。 然后我们必须传递输入列并在标记化后命名输出列。 我们使用transform
函数将标记化应用于审阅列。
[In]: from pyspark.ml.feature import Tokenizer
[In]: tokenization=Tokenizer(inputCol='review',outputCol='tokens')
[In]: tokenized_df=tokenization.transform(df)
[In]: tokenized_df.show(4,False)
[Out]:
我们得到一个名为tokens的新列,其中包含每个句子的标记。
正如您所看到的,令牌列包含非常常见的单词,例如'this','the','to','was','that'等。这些单词被称为停用词,它们似乎增加了很少的价值 分析。 如果将它们用于分析,则会增加计算开销,而不会增加太多的价值或洞察力。
因此,从令牌中删除这些停用词总是被认为是个好主意。 在PySpark
中,我们使用StopWordsRemover
来删除停用词。
[In]: from pyspark.ml.feature import StopWordsRemover
[In]: stopword_removal=StopWordsRemover(inputCol='tokens',outputCol='refined_tokens')
##We then pass the tokens as the input column and name the output column as refined tokens.
[In]: refined_df=stopword_removal.transform(tokenized_df)
[In]: refined_df.select(['user_id','tokens','refined_tokens']).show(4,False)
[Out]:
+-------+--------------------------------------------+--------------------------------+
|user_id|tokens |refined_tokens |
+-------+--------------------------------------------+--------------------------------+
|1 |[I,really,liked,this,movie] |[really,liked,movie] |
|2 |[I,would,recommend,this,movie,to,my,friends]|[recommend,movie,friends] |
|3 |[movie,was,alright,but,acting,was,horrible] |[movie,alright,acting,horrible] |
|4 |[I,am,never,watching,that,movie,ever,again] |[never,watching,movie,ever] |
+-------+--------------------------------------------+--------------------------------+
正如你所观察到的那样,诸如‘I’, ‘this’, ‘was’, ‘am’, ‘but’, ‘that’这样的停用词,将从标记列中删除。
这是一种方法,通过该方法,我们可以将文本数据表示为数字形式,供机器学习或任何其他分析使用。 文本数据通常是非结构化的,并且其长度不同。 BOW(Bag of Words)允许我们通过考虑文本文档中单词的出现将文本形式转换为数字向量形式。 例如,
Doc 1: The best thing in life is to travel Doc 2: Travel is the best medicine Doc 3: One should travel more often
词汇表 Vocabulary:
出现在所有文档中的唯一单词列表,称为词汇表。 在上面的例子中,我们有13个独特的单词,它们是词汇表的一部分。 每个文档可以由这个固定大小为13的向量表示。
The best thing in life is to travel medicine one should more often
另一个元素是使用布尔值表示特定文档中的单词。(1 or 0)
Doc1:
The | best | thing | in | life | is | to | travel | medicine | one | should | more | often |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
Doc2:
The | best | thing | in | life | is | to | travel | medicine | one | should | more | often |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
Doc3:
The | best | thing | in | life | is | to | travel | medicine | one | should | more | often |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
BOW不考虑文档中单词的顺序和单词的语义,因此是将文本数据表示为数字形式的最基线方法。 我们可以通过其他方式将文本数据转换为数字形式,这将在下一节中提到。 我们将使用PySpark
来完成这些方法中的每一种。
在BOW中,我们通过简单的1或0看到了单词出现的表示,并没有考虑单词的频率。 计数矢量化器改为计算出现在特定文档中的单词。我们将使用我们之前使用标记化创建的相同文本文档。 我们首先导入Count Vectorizer。
[In]: from pyspark.ml.feature import CountVectorizer
[In]: count_vec=CountVectorizer(inputCol='refined_tokens',outputCol='features')
[In]: cv_df=count_vec.fit(refined_df).transform(refined_df)
[In]: cv_df.select(['user_id','refined_tokens','features']).show(4,False)
[Out]:
+--------+-------------------------------------+---------------------------------+
|usr_id | refined_tokens | features |
+--------+-------------------------------------+---------------------------------+
| 1 |[really, liked, movie] |(11,[0,4,9],[1.0,1.0,1.0]) |
| 2 |[recommend, movie, friends] |(11,[0,6,10],[1.0,1.0,1.0]) |
| 3 |[movie, alright, acting, horrible] |(11,[0,2,3,5],[1.0,1.0,1.0,1.0]) |
| 4 |[never, watching, movie, ever] |(11,[0,1,7,8],[1.0,1.0,1.0,1.0]) |
+--------+-------------------------------------+---------------------------------+
我们可以观察到,每个句子都表示为一个密集的向量。 它表明向量长度为11,第一个句子在第0,第4和第9个索引处包含3个值。 为了验证计数向量化器的词汇表,我们可以简单地使用词汇表功能。
[In]: count_vec.fit(refined_df).vocabulary
[Out]:
['movie',
'horrible',
'really',
'alright',
'liked',
'friends',
'recommend',
'never',
'ever',
'acting',
'watching']
因此,上述句子的词汇量大小为11,如果仔细查看这些特征,它们就类似于我们在PySpark
中用于机器学习的输入特征向量。 使用Count Vectorizer方法的缺点是它不考虑其他文档中单词的共现。 简单来说,更频繁出现的词会对特征向量产生更大的影响。 因此,将文本数据转换为数字形式的另一种方法称为术语频率 - 逆文档频率(TF-IDF)。
该方法尝试基于其他文档规范化单词出现的频率。 如果在同一文档中出现很多次,那么整个想法是给予该词更多的权重,但是如果它在其他文档中出现的次数更多则会受到惩罚。 这表明一个单词在语料库中是通用的,并不像当前文档中的频率那样重要。
Term Frequency(词频): 根据当前文档中的单词频率进行评分。 Inverse Document Frequency(逆词频):根据包含当前单词的文档的频率进行评分。
现在,我们使用相同的refined_df
dataframe
在PySpark
中创建基于TF-IDF的特征。
[In]: from pyspark.ml.feature import HashingTF, IDF
[In]: hashing_vec=HashingTF(inputCol='refined_tokens',outputCol='tf_features')
[In]: hashing_df=hashing_vec.transform(refined_df)
[In]: hashing_df.select(['user_id','refined_tokens','tf_features']).show(4,False)
[Out]:
[In]: tf_idf_vec=IDF(inputCol='tf_features',outputCol='tf_idf_features')
[In]: tf_idf_df=tf_idf_vec.fit(hashing_df).transform(hashing_df)
[In]: tf_idf_df.select(['user_id','tf_idf_features']).show(4,False)
[Out]:
现在我们已经基本了解了处理文本处理和特征向量化所涉及的步骤,我们可以构建一个文本分类模型,并将其用于文本数据的预测。 我们将要使用的数据集是开源的Movie Lens评论数据,我们将预测任何给定评论(正面或负面)的情绪类别。 让我们首先阅读文本数据并创建Spark数据帧。
[In]: text_df=spark.read.csv('Movie_reviews.csv',inferSchema=True,header=True,sep=',')
[In]: text_df.printSchema()
[Out]:
root
|-- Review: string (nullable = true)
|-- Sentiment: string (nullable = true)
您可以在StringType
中观察Sentiment列
,我们将需要它将其转换为Integer或float类型
。
[In]: text_df.count()
[Out]: 7087
我们有近七千条记录,其中一些可能没有正确标记。 因此,我们只过滤那些标记正确的记录。
[In]: text_df=text_df.filter(((text_df.Sentiment =='1') | (text_df.Sentiment =='0')))
[In]: text_df.count()
[Out]: 6990
一些记录被过滤掉了,我们现在剩下6,990条记录用于分析。 下一步是验证每个班级的一些评论。
[In]: text_df.groupBy('Sentiment').count().show()
[Out]:
+---------+-----+
|Sentiment|count|
+---------+-----+
| 0 | 3081|
| 1 | 3909|
+---------+-----+
我们正在处理一个平衡的数据集,因为这两个类的评论数量几乎相同。 让我们看一下数据集中的一些记录。
[In]: from pyspark.sql.functions import rand
[In]: text_df.orderBy(rand()).show(10,False)
[Out]:
在下一步中,我们将新标签列创建为Integer类型
,并删除原始的Sentiment列
,该列是String类型
。
[In]: text_df=text_df.withColumn("Label",
text_df.Sentiment.cast('float')).drop('Sentiment')
[In]: text_df.orderBy(rand()).show(10,False)
[Out]:
我们还添加了一个附加列,用于记录审核的长度。
[In]: from pyspark.sql.functions import length
[In]: text_df=text_df.withColumn('length',length(text_df['Review']))
[In]: text_df.orderBy(rand()).show(10,False)
[Out]:
[In]: text_df.groupBy('Label').agg({'Length':'mean'}).show()
[Out]:
+-----+-----------------+
|Label| avg(Length) |
+-----+-----------------+
| 1.0 |47.61882834484523|
| 0.0 |50.95845504706264|
+-----+-----------------+
正面和负面评论的平均长度没有重大差异。 下一步是启动标记化过程并删除停用词。
[In]: tokenization=Tokenizer(inputCol='Review',outputCol='tokens')
[In]: tokenized_df=tokenization.transform(text_df)
[In]: stopword_removal=StopWordsRemover(inputCol='tokens',outputCol='refined_tokens')
[In]: refined_text_df=stopword_removal.transform(tokenized_df)
由于我们现在只处理tokens而不是整个评论,因此在每个评论中捕获一些tokens比使用评论核的长度会更有意义。 我们创建了另一列(token的个数),它给出了每行中token的数量。
[In]: from pyspark.sql.functions import udf
[In]: from pyspark.sql.types import IntegerType
[In]: from pyspark.sql.functions import *
[In]: len_udf = udf(lambda s: len(s), IntegerType())
[In]: refined_text_df = refined_text_df.withColumn("token_count",len_udf(col('refined_tokens')))
[In]: refined_text_df.orderBy(rand()).show(10)
[Out]:
现在我们在删除停用词之后,我们可以使用上述任何方法将文本转换为数字特征。 在这种情况下,我们使用countvectorizer
来进行机器学习模型的特征向量化。
[In]: count_vec=CountVectorizer(inputCol='refined_tokens',outputCol='features')
[In]: cv_text_df=count_vec.fit(refined_text_df).transform(refined_text_df)
[In]: cv_text_df.select(['refined_tokens','token_count','features','Label']).show(10)
[Out]:
+--------------------+-----------+--------------------+-----+
| refined_tokens |token_count| features |Label|
+--------------------+-----------+--------------------+-----+
|[da, vinci, code,...| 5 |(2302,[0,1,4,43,2...| 1.0 |
|[first, clive, cu...| 9 |(2302,[11,51,229,...| 1.0 |
|[liked, da, vinci...| 5 |(2302,[0,1,4,53,3...| 1.0 |
|[liked, da, vinci...| 5 |(2302,[0,1,4,53,3...| 1.0 |
|[liked, da, vinci...| 8 |(2302,[0,1,4,53,6...| 1.0 |
|[even, exaggerati...| 6 |(2302,[46,229,271...| 1.0 |
|[loved, da, vinci...| 8 |(2302,[0,1,22,30,...| 1.0 |
|[thought, da, vin...| 7 |(2302,[0,1,4,228,...| 1.0 |
|[da, vinci, code,...| 6 |(2302,[0,1,4,33,2...| 1.0 |
|[thought, da, vin...| 7 |(2302,[0,1,4,223,...| 1.0 |
+--------------------+-----------+--------------------+-----+
[In]: model_text_df=cv_text_df.select(['features','token_count','Label'])
#一旦我们有每行的特征向量,我们就可以利用VectorAssembler为机器学习模型创建输入特征。
[In]: from pyspark.ml.feature import VectorAssembler
[In]: df_assembler = VectorAssembler(inputCols=['features','token_count'],outputCol='features_vec')
[In]: model_text_df = df_assembler.transform(model_text_df)
[In]: model_text_df.printSchema()
[Out]:
root
|-- features: vector (nullable = true)
|-- token_count: integer (nullable = true)
|-- Label: float (nullable = true)
|-- features_vec: vector (nullable = true)
# 我们可以对这些数据使用任何分类模型,但我们继续训练Logistic回归模型。
[In]: from pyspark.ml.classification import LogisticRegression
[In]: training_df,test_df=model_text_df.randomSplit([0.75,0.25])
# 为了验证列车和测试数据集中两个类是否存在足够的记录,我们可以在Label列上应用groupBy函数。
[In]: training_df.groupBy('Label').count().show()
[Out]:
+-----+-----+
|Label|count|
+-----+-----+
| 1.0| 2979|
| 0.0| 2335|
+-----+-----+
[In]: test_df.groupBy('Label').count().show()
[Out]:
+-----+-----+
|Label|count|
+-----+-----+
| 1.0| 930|
| 0.0| 746|
+-----+-----+
[In]: log_reg=LogisticRegression(featuresCol='features_vec',
labelCol='Label').fit(training_df)
[In]: results=log_reg.evaluate(test_df).predictions
[In]: results.show()
[In]: from pyspark.ml.evaluation import BinaryClassificationEvaluator
[In]: true_postives = results[(results.Label == 1) & (results.prediction == 1)].count()
[In]: true_negatives = results[(results.Label == 0)&(results.prediction == 0)].count()
[In]: false_positives = results[(results.Label == 0)&(results.prediction == 1)].count()
[In]: false_negatives = results[(results.Label == 1)&(results.prediction == 0)].count()
[In]: recall = float(true_postives)/(true_postives + false_negatives)
[In]:print(recall)
[Out]: 0.986021505376344
[In]: precision = float(true_postives) / (true_postives + false_positives)
[In]: print(precision)
[Out]: 0.9572025052192067
[In]: accuracy=float((true_postives+true_negatives) /(results.count()))
[In]: print(accuracy)
[Out]: 0.9677804295942721
每天有数百万人访问商业网站,每个人都采取不同的步骤来寻找正确的信息/产品。然而,他们中的大多数人由于某种原因而失望或沮丧,很少有人进入网站的正确页面。在这种情况下,很难确定潜在客户是否真正获得了他正在寻找的信息。此外,由于每个人都做了不同的活动,因此这些观众的个人旅程无法相互比较。那么,我们怎样才能更多地了解这些旅程并将这些访客相互比较呢?序列嵌入是一种强大的方式,它为我们提供了灵活性,不仅可以比较任何两个不同的观众在相似性方面的整个旅程,还可以预测他们转换的概率。序列嵌入本质上有助于我们不再使用传统功能进行预测,不仅考虑用户活动的顺序,还考虑每个独特页面上花费的平均时间,以转换为更强大的功能;它还用于多个用例的监督机器学习(下一个可能的行动预测,转换与未转换,产品分类)。在序列嵌入等高级功能上使用传统的机器学习模型,我们可以在预测准确性方面取得巨大成果,但真正的好处在于可视化所有这些用户旅程并观察这些路径与理想路径的区别。
本章的这一部分将展示为每个用户在PySpark
中的旅程创建序列嵌入的过程。
到目前为止,我们已经看到使用计数矢量化器,TF-IDF和散列矢量化等技术将文本数据表示为数字形式。 然而,上述技术都没有考虑单词的语义含义或单词存在的上下文。 嵌入在捕获单词的上下文和以这样的方式表示它们方面是独特的,即具有相似含义的单词用相似类型的嵌入来表示。 有两种方法可以计算嵌入。
- Skip Gram
- Continuous Bag of Words (CBOW)
两种方法都给出了嵌入值,这些值只是神经网络中隐藏层的权重。 根据需要,这些嵌入矢量可以是100或更大。 word2vec给出了每个单词的嵌入值,其中doc2vec给出了整个句子的嵌入。 序列嵌入与doc2vec类似,是单个嵌入出现在句子中的单词的加权方法的结果。 让我们以一个样本数据集来说明我们如何从用户的在线零售旅程中创建序列嵌入。
[In]: spark=SparkSession.builder.appName('seq_embedding').getOrCreate()
[In]:
df = spark.read.csv('embedding_dataset.csv',header=True,inferSchema=True)
[In]: df.count()
[Out]: 1096955
数据集中的记录总数接近一百万,并且有十亿个唯一用户。 如果用户购买产品,则还跟踪每个用户在每个网页上花费的时间以及最终状态。
[In]: df.printSchema()
[Out]:
root
|-- user_id: string (nullable = true)
|-- page: string (nullable = true)
|-- timestamp: timestamp (nullable = true)
|-- visit_number: integer (nullable = true)
|-- time_spent: double (nullable = true)
|-- converted: integer (nullable = true)
[In]: df.select('user_id').distinct().count()
[Out]: 104087
[In]: df.groupBy('page').count().orderBy('count',ascending=False).show(10,False)
[Out]:
+-------------+------+
|page |count |
+-------------+------+
|product info |767131|
|homepage |142456|
|added to cart|67087 |
|others |39919 |
|offers |32003 |
|buy |24916 |
|reviews |23443 |
+-------------+------+
[In]: df.select(['user_id',
'page',
'visit_number',
'time_spent',
'converted']).show(10,False)
[Out]:
序列嵌入的整个思想是将用户在他或她的在线旅程中所采取的一系列步骤转换为页面序列,其可用于计算嵌入分数。 第一步是在用户旅程中删除任何连续的重复页面。 我们创建了一个额外的列来捕获用户的上一页。 Window是spark中的一个函数,它有助于应用特定于数据集中的单个或一组行的特定逻辑。
[In]:w = Window.partitionBy("user_id").orderBy('timestamp')
[In]: df = df.withColumn("previous_page", lag("page", 1,'started').over(w))
[In]: df.select('user_id','timestamp','previous_page','page').show(10,False)
[Out]:
[In]:
def indicator(page, prev_page):
if page == prev_page:
return 0
else:
return 1
[In]:page_udf = udf(indicator,IntegerType())
[In]: df = df.withColumn("indicator",page_udf(col('page'),col('previous_page'))).
withColumn('indicator_cummulative',
sum(col('indicator')).over(w))
现在,我们创建一个函数来检查当前页面是否与上一页类似,并在新列指示符中指示相同。 累加指标是用于跟踪用户旅程中不同页面数量的列。
[In]: df.select('previous_page',
'page',
'indicator',
'indicator_cummulative').show(20,False)
[Out]:
# 我们不断创建新的windows对象以进一步分区数据,以便为eadch用户构建序列。
[In]: w2=Window.partitionBy(["user_id",'indicator_cummulative']).orderBy('timestamp')
[In]: df= df.withColumn('time_spent_cummulative',sum(col('time_spent')).over(w2))
[In]: df.select('timestamp',
'previous_page',
'page','indicator',
'indicator_cummulative',
'time_spent',
'time_spent_cummulative').show(20,False)
# 在下一阶段,我们计算在类似页面上花费的聚合时间,以便只能保留单个记录来表示连续页面。
[In]: w3 = Window.partitionBy(["user_id",
'indicator_cummulative']).
orderBy(col('timestamp').desc())
[In]: df = df.withColumn('final_page',
first('page').over(w3)).
withColumn('final_time_spent',first('time_spent_cummulative').over(w3))
[In]: df.select(['time_spent_cummulative',
'indicator_cummulative',
'page',
'final_page',
'final_time_spent']).show(10,False)
[Out]:
[In]: aggregations=[]
[In]: aggregations.append(max(col('final_page')).alias('page_emb'))
[In]: aggregations.append(max(col('final_time_spent')).alias('time_spent_emb'))
[In]: aggregations.append(max(col('converted')).alias('converted_emb'))
[In]: df_embedding = df.select(['user_id',
'indicator_cummulative',
'final_page',
'final_time_spent',
'converted']).groupBy(['user_id',
'indicator_cummulative']).
agg(*aggregations)
[In]: w4 = Window.partitionBy(["user_id"]).orderBy('indicator_cummulative')
[In]: w5 = Window.partitionBy(["user_id"]).orderBy(col('indicator_cummulative').desc())
# 最后,我们使用收集列表将用户旅程的所有页面合并到一个列表中,并将时间花费在一起。 因此,我们以页面列表和花费时间列表的形式结束用户旅程。
[In]:df_embedding = df_embedding.withColumn('journey_page',collect_list(col('page_emb')).over(w4))\
.withColumn('journey_time_temp',collect_list(col('time_spent_emb')).over(w4)) \
.withColumn('journey_page_final',first('journey_page').over(w5))\
.withColumn('journey_time_final',first('journey_time_temp').over(w5)) \
.select(['user_id','journey_page_final','journey_time_final','converted_emb'])
# 我们继续只有独特的用户旅程。 每个用户由单个旅程和时间花费矢量表示。
[In]: df_embedding = df_embedding.dropDuplicates()
[In]: df_embedding.count()
[Out]: 104087
[In]: df_embedding.select('user_id').distinct().count()
[Out]: 104087
[In]: df_embedding.select('user_id','journey_page_final','journey_time_final').show(10)
[Out]:
# 现在我们有了用户旅程和花费的时间,我们将这个数据帧转换为Pandas数据帧,并使用这些旅程序列构建word2vec模型。 我们必须首先安装gensim库才能使用word2vec。我们使用100的嵌入大小来保持简单。
[In]: pd_df_emb0 = df_embedding.toPandas()
[In]: pd_df_embedding = pd_df_embedding.reset_index(drop=True)
[In]: !pip install gensim
[In]: from gensim.models import Word2Vec
[In]: EMBEDDING_SIZE = 100
[In]: model = Word2Vec(pd_df_embedding['journey_page_final'],size=EMBEDDING_SIZE)
[In]: print(model)
[Out]: Word2Vec(vocab=7, size=100, alpha=0.025)
# 我们可以观察到,词汇量大小为7,因为我们只处理7个页面类别。 现在可以借助于大小为100的嵌入向量来表示这些页面类别中的每一个。
[In]: page_categories = list(model.wv.vocab)
[In]: print(page_categories)
[Out]:
['product info', 'homepage', 'added to cart', 'others',
'reviews', 'offers', 'buy']
[In]: print(model['reviews'])
[Out]:
[In]: model['offers'].shape
[Out]: (100,)
# 要创建嵌入矩阵,我们可以使用模型并传递模型词汇表; 它会产生一个大小的矩阵(7,100。)
[In]: X = model[model.wv.vocab]
[In]: X.shape
[Out]: (7,100)
# 为了更好地理解这些页面类别之间的关系,我们可以使用降维技术(PCA)并将这七个页面嵌入绘制在二维空间上。
[In]: pca = PCA(n_components=2)
[In]: result = pca.fit_transform(X)
[In]: plt.figure(figsize=(10,10))
[In]: plt.scatter(result[:, 0], result[:, 1])
[In]: for i,page_category in enumerate(page_categories):
plt.annotate(page_category,horizontalalignment='right',
verticalalignment='top',xy=(result[i, 0], result[i, 1]))
[In]: plt.show()
我们可以清楚地看到,购买和添加到购物车的嵌入在相似性方面彼此接近,而主页和产品信息也彼此更接近。 在通过嵌入表示时,优惠和评论是完全独立的。 这些单独的嵌入可以组合并用于使用机器学习的用户旅程比较和分类。
在本章中,我们熟悉了文本处理和为机器学习创建特征向量的步骤。 我们还经历了从在线用户旅程数据创建序列嵌入的过程,以比较各种用户旅程。