前沿
这次我们使用 Amazon review 食物评论数据集,使用OpenAI API获取评论文本的 Embedding 数据,然后在完整的数据集上,训练一个能把从 1 分到 5 分的每一个级别都区分出来的机器学习模型,看看最终的文本分类效果。
(整个数据集转为embeddings会消耗约$0.66,如果不想浪费tokens,可以私信我获取已经完成的Embedding 文件)
美食评论数据集
首先介绍下此次项目的数据集 Amazon Fine Food Reviews (kaggle.com)https://www.kaggle.com/datasets/snap/amazon-fine-food-reviews
背景
本数据集包含亚马逊上精美食品的评论。数据涵盖了超过10年的时间跨度,包括截至2012年10月的所有约50万条评论。评论包括产品和用户信息、评分以及纯文本评论。此外,数据集还包含了亚马逊所有其他类别的评论。
内容
Reviews.csv:从数据库sqlite.db中名为Reviews的相应SQLite表中提取
database.sqlite:包含名为'Reviews'的表
数据
- 1999年10月至2012年10月的评论
- 568,454条评论
- 256,059名用户
- 74,258种产品
- 超过50条评论的260名用户
关键
关于此次项目最重要的三列分别是Score,Summary和Text
Score就是该用户该评论的评分
而Summary和Text分别是评论的总结和具体内容
我们要做的就是用这三列数据做训练,得到一个模型,使得每次输入用户评论,就能预测出该用户的评分
项目任务
“文本分类”任务,是指我们根据一段文字,去判断它属于哪个预定义的类别。在互联网产品和服务中,这经常被用来分析用户对产品或服务的评价。例如,在电商平台上,用户对商品的评论可以根据满意度被分类为1到5分,其中1分代表非常不满意,5分代表非常满意。这样的分类可以帮助商家或服务提供者了解客户的具体反馈,并据此改进产品或服务。有些品牌也会从社交网络中抓取用户对自己产品的评价,并进行文本分类,以了解消费者对产品的满意度,并据此进行产品改进。
对于“文本分类”类型的问题,传统的解决方案是将其视为一个多类别分类问题。首先,需要一部分评论数据,并人工标注这些评论的分数,比如1到5分。如果用户评论说“这家餐馆真好吃”,那么可以标注为5分。如果用户评论说“这个手机质量不好”,那么可以标注为1分。将这些标注好的数据用于训练机器学习模型,从而学习到如何根据文本内容预测其分类。训练完成后,模型可以对未标注的数据进行预测,给出一个分数,告诉我们这段评论的满意度等级。这种方法可以帮助自动化和标准化用户反馈的分析过程。
传统机器学习方法
在文本分类任务中,传统的机器学习方法通常涉及以下几个步骤:
-
文本预处理:
- 清洗:移除文本中的噪声,如标点符号、特殊字符等。
- 分词:将文本分割成单词或短语。
- 停用词去除:删除常见的、意义不大的词,如“的”、“和”等。
- 词干提取或词形还原:将单词还原到基本形式,如将动词的过去式还原为原形。
-
特征提取:
- 词袋模型(Bag of Words):将文本转换为单词出现次数的向量。
- TF-IDF(Term Frequency-Inverse Document Frequency):考虑单词在文档中出现的频率以及在整个数据集中的分布,用以突出重要单词。
- 词嵌入(Word Embeddings):使用预训练的模型(如Word2Vec、GloVe)将单词转换为稠密向量,以捕捉语义信息。
-
特征选择:
- 选择最重要的特征:通过统计测试或其他方法选择对分类最有帮助的特征。
-
模型训练:
- 选择算法:根据问题的性质选择合适的算法,如朴素贝叶斯、支持向量机(SVM)、决策树、随机森林等。
- 训练模型:使用标注好的训练数据集来训练模型。
-
模型评估:
- 交叉验证:通过将数据集分成多个子集,进行多次训练和验证,以评估模型的泛化能力。
- 性能指标:使用准确率、召回率、F1分数等指标来评估模型的性能。
-
模型优化:
- 参数调优:通过调整模型参数来提高性能。
- 特征工程:可能需要回到特征提取步骤,尝试不同的特征组合或提取方法。
利用OpenAI API
传统机器学习中最重要的特征提取需要有相当丰富的模型训练经验,而使用大模型提供的Embedding API进行文本分类,可以让没有经验的小白也能快速上手:
-
文本预处理:
- 清洗:移除文本中的噪声,如标点符号、特殊字符等。
- 分词:将文本分割成单词或短语。
- 停用词去除:删除常见的、意义不大的词,如“的”、“和”等。
- 词干提取或词形还原:将单词还原到基本形式。
-
使用Embedding API:
- 调用Embedding API:将预处理后的文本输入到大模型提供的Embedding API中。
- 转换为向量:API将文本转换为固定长度的向量表示。
-
数据划分:
- 划分训练集和测试集:将带有评分标注的数据集划分为训练集和测试集。
-
模型选择:
- 选择机器学习算法:根据问题的性质选择合适的算法,如线性回归、支持向量机(SVM)、随机森林、梯度提升机(GBM)等。
-
模型训练:
- 训练模型:使用训练集数据训练选定的机器学习模型。
-
模型评估:
- 交叉验证:通过交叉验证方法评估模型的泛化能力。
- 性能指标:使用均方误差(MSE)、均方根误差(RMSE)、决定系数(R²)等指标评估模型性能。
项目代码实现
在项目开始前首先在环境中设置OPENAI_API_KEY
在Linux环境中设置OPENAI_API_KEY
export OPENAI_API_KEY=在这里写你获取到的ApiKey
在Windows conda 环境中使用一下命令来设置OPENAI_API_KEY
conda env config vars set OPENAI_API_KEY=sk-xxxxxyyyyzzzzz
首先引入不同的库和模块,以便在程序中使用它们提供的功能
import pandas as pd
import numpy as np
import tiktoken
from openai import OpenAI
import os
from sklearn.metrics import classification_report
将下载到本地的数据读取成pandas可处理的dataframe格式,只关注其中的三个字段,并将Summary+Text组合成新字段combined,以此作为评论内容去预测评分Score
datafile_path = "data/food_reviews.csv"
df = pd.read_csv(datafile_path, index_col=0)
df = df[[ "Summary", "Text", "Score"]]
df = df.dropna()
# 将 "Summary" 和 "Text" 字段组合成新的字段 "combined"
df["combined"] = (
df.Summary.str.strip() + ": " + df.Text.str.strip()
)
df.head()
然后设置embedding模型,并预计算需要消耗的tokens数,因为text-embedding-3-small模型最大上下文长度是8191,所以设置超过8000的样例删除
# 模型设置&预处理
openai.api_key = os.environ.get("OPENAI_API_KEY")
# embedding model parameters
embedding_model = "text-embedding-3-small"
embedding_encoding = "cl100k_base" # this the encoding for text-embedding-ada-002
max_tokens = 8000 # the maximum for text-embedding-3-small is 8191
print("Lines of text before filtering: ", len(df))
encoding = tiktoken.get_encoding(embedding_encoding)
# omit reviews that are too long to embed
df["n_tokens"] = df.combined.apply(lambda x: len(encoding.encode(x)))
df = df[df.n_tokens <= max_tokens]
print("Lines of text after filtering: ", len(df))
然后定义两个函数,用于通过OpenAI的API获取文本的嵌入表示(此处需要在环境变量中设置自己的OPENAI_API_KEY)
# 调用embedding-api函数
client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])
EMBEDDING_MODEL = "text-embedding-3-small"
# 单样本
def get_embedding(text, model=EMBEDDING_MODEL):
text = text.replace("\n", " ")
return client.embeddings.create(input = [text], model=model).data[0].embedding
# 批处理
def get_embeddings(
list_of_text: list[str], model="text-embedding-3-small", **kwargs
) -> list[list[float]]:
assert len(list_of_text) <= 2048, "The batch size should not be larger than 2048."
# replace newlines, which can negatively affect performance.
list_of_text = [text.replace("\n", " ") for text in list_of_text]
data = client.embeddings.create(input=list_of_text, model=model, **kwargs).data
return [d.embedding for d in data]
批量调用api处理数据,将评论内容转换为embeddings,记得即时保存
# 批量大小
batch_size = 1000
# group prompts into batches of 1000
prompts = df.combined.tolist()
prompt_batches = [prompts[i:i+batch_size] for i in range(0, len(prompts), batch_size)]
# 批量处理
embeddings = []
for batch in prompt_batches:
batch_embeddings = get_embeddings(list_of_text=batch, model=embedding_model)
embeddings += batch_embeddings
# 保存embeddings
output_datapath = "data/food_reviews_all_with_embeddings.csv"
df.to_csv(output_datapath)
(此步骤预计要花几个小时,并且需要消耗实际的Tokens约33087363,按照text-embedding-3-small的价格$0.020 / 1M tokens,总共花费约$0.66)
然后从csv文件读取数据
# 读取文件
data_path = "data/food_reviews_all_with_embeddings.csv"
training_data = pd.read_csv(data_path)
training_data.head()
由于读取出来的embeddings是string类型,需要做一步类型转换,转成numpy.ndarray类型
type(training_data['embedding'][0])
training_data["embedding"] = training_data.embedding.apply(eval).apply(np.array)
type(training_data['embedding'][0])
training_data
然后开始用机器学习方法做多分类任务,首先使用逻辑回归训练模型并测试
'''使用逻辑回归预测美食评论评分'''
from sklearn.linear_model import LogisticRegression # 导入逻辑回归模型
from sklearn.model_selection import train_test_split # 导入数据集分割函数
from sklearn.metrics import classification_report, accuracy_score # 导入分类报告和准确率评分函数
df = training_data # 将训练数据赋值给df变量
# 将特征和标签分割为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
list(df.embedding.values), # 特征:将DataFrame中的'embedding'列转换为列表
df.Score, # 标签:DataFrame中的'Score'列
test_size=0.2, # 测试集占总数据的20%
random_state=42 # 设置随机状态以确保结果可复现
)
clf = LogisticRegression() # 创建逻辑回归模型实例
clf.fit(X_train, y_train) # 训练模型
preds = clf.predict(X_test) # 使用训练好的模型对测试集进行预测
probas = clf.predict_proba(X_test) # 获取测试集的预测概率
# 生成分类报告,包括准确率、召回率、F1分数等
report = classification_report(y_test, preds)
print(report) # 打印分类报告
再换成随机森林模型
'''使用随机森林预测美食评论评分'''
from sklearn.ensemble import RandomForestClassifier # 导入随机森林分类器
from sklearn.model_selection import train_test_split # 导入数据集分割函数
from sklearn.metrics import classification_report, accuracy_score # 导入分类报告和准确率评分函数
# df = training_data.sample(50000, random_state=42) # 可选:从训练数据中随机抽取50000个样本进行训练,以加快训练速度
df = training_data # 使用全部训练数据
# 将特征和标签分割为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
list(df.embedding.values), # 特征:将DataFrame中的'embedding'列转换为列表
df.Score, # 标签:DataFrame中的'Score'列
test_size=0.2, # 测试集占总数据的20%
random_state=42 # 设置随机状态以确保结果可复现
)
clf = RandomForestClassifier(n_estimators=500) # 创建随机森林分类器实例,设置500棵树
clf.fit(X_train, y_train) # 训练模型
preds = clf.predict(X_test) # 使用训练好的模型对测试集进行预测
probas = clf.predict_proba(X_test) # 获取测试集的预测概率
# 生成分类报告,包括准确率、召回率、F1分数等
report = classification_report(y_test, preds)
print(report) # 打印分类报告
可以使用Jupyter Lab将上面的代码一段段复制,分别执行,体验代码调试运行的效果
文本分类效果展示
逻辑回归
首先是逻辑回归的分类结果
precision recall f1-score support 1 0.73 0.82 0.77 5577 2 0.50 0.33 0.39 3107 3 0.54 0.52 0.53 4622 4 0.59 0.36 0.44 8633 5 0.87 0.96 0.91 37917 accuracy 0.79 59856 macro avg 0.64 0.60 0.61 59856 weighted avg 0.77 0.79 0.78 59856
这段分类报告提供了逻辑回归模型在五个不同评分类别上的精确度(precision)、召回率(recall)、F1分数(f1-score)和支持度(support)。下面是对这些指标的解读:
-
精确度(Precision):预测为正类别中实际为正类别的比例。例如,在评分为1的类别中,精确度为0.73,意味着在所有被模型预测为评分1的评论中,大约73%实际上是评分1。
-
召回率(Recall):实际为正类别中被正确预测为正类别的比例。例如,在评分为1的类别中,召回率为0.82,意味着在所有实际评分为1的评论中,模型成功识别出了82%。
-
F1分数(F1 Score):精确度和召回率的调和平均数,用于综合考虑精确度和召回率。F1分数越高,模型的性能越好。例如,在评分为1的类别中,F1分数为0.77,表示模型在评分为1的类别上的整体表现较好。
-
支持度(Support):每个类别中的实际样本数量。例如,评分为5的类别中有37917个样本,这是所有类别中样本数量最多的。
-
准确率(Accuracy):所有预测中正确预测的比例。在这个数据集中,准确率为0.79,意味着模型正确预测了约79%的评论评分。
-
宏平均(Macro Avg):计算每个类别的指标,然后取平均值,不考虑每个类别的样本数量。宏平均值可以帮助我们理解模型在所有类别上的平均性能。
-
加权平均(Weighted Avg):计算每个类别的指标,然后根据每个类别的支持度(样本数量)进行加权平均。这可以帮助我们理解模型在最常见类别上的性能。
结果解读:
- 评分1:模型在识别评分为1的评论上表现良好,具有较高的精确度和召回率。
- 评分2:模型在识别评分为2的评论上表现较差,精确度和召回率都较低。
- 评分3:模型在识别评分为3的评论上表现一般,精确度和召回率都适中。
- 评分4:模型在识别评分为4的评论上表现不佳,尽管精确度较高,但召回率较低。
- 评分5:模型在识别评分为5的评论上表现最好,具有最高的精确度和召回率。
总体来说,模型在高评分(尤其是评分5)上的预测性能较好,而在低评分(尤其是评分2和4)上的预测性能较差。这可能意味着模型对于正面评论的识别能力较强,而对于负面评论的识别能力较弱。
随机森林
然后是随机森林的分类结果
precision recall f1-score support 1 0.74 0.84 0.79 5577 2 0.94 0.35 0.51 3107 3 0.81 0.44 0.57 4622 4 0.94 0.33 0.49 8633 5 0.81 1.00 0.89 37917 accuracy 0.81 59856 macro avg 0.85 0.59 0.65 59856 weighted avg 0.83 0.81 0.78 59856
结果解读:
-
评分1:模型在识别评分为1的评论上表现良好,精确度为0.74,召回率为0.84,F1分数为0.79。
-
评分2:尽管精确度非常高(0.94),但召回率较低(0.35),这意味着模型很少将评论错误地分类为评分2,但同时也错过了很多实际为评分2的评论。
-
评分3:模型在识别评分为3的评论上表现一般,精确度为0.81,召回率为0.44,F1分数为0.57。
-
评分4:与评分2类似,精确度非常高(0.94),但召回率较低(0.33),F1分数为0.49。
-
评分5:模型在识别评分为5的评论上表现非常好,精确度为0.81,召回率为1.00(意味着没有错过任何实际为评分5的评论),F1分数为0.89。
-
总体性能:模型的总体准确率为0.81,这意味着模型正确预测了约81%的评论评分。宏平均精确度为0.85,召回率为0.59,F1分数为0.65。加权平均精确度为0.83,召回率为0.81,F1分数为0.78。
总结
随机森林模型在高评分(尤其是评分5)上的预测性能非常好,这表明模型能够很好地识别正面评论。然而,对于低评分(尤其是评分2和4),模型的召回率较低,这可能意味着模型错过了一些实际为负面评论的样本。尽管如此,模型的整体性能(准确率)比逻辑回归模型略高,这表明随机森林在处理这个特定数据集时可能更为有效。
项目总结
总的来说,该项目展示了如何结合机器学习模型和先进的文本嵌入技术来处理和分析文本数据,以及如何通过模型选择和调优来提高预测性能。OpenAI的Embedding模型在提供高质量的文本向量表示方面发挥了关键作用,为构下游应用提供了强大的支持。