原文:
towardsdatascience.com/enhancing-e-commerce-with-generative-ai-part-1-9e402fb30e7b
图片来自 unsplash.com
生成式 AI 正在改变各个领域商业运作的方式,电子商务也不例外。随着电子商务的持续增长,对更可扩展和复杂的解决方案的需求也在增加。在这些系列文章中,我们探讨了生成式 AI 在电子商务中的各种应用。在第一部分,我们探讨了生成式 AI 如何帮助产品推荐。我们首先使用产品元数据来学习它们的代表性嵌入,并展示了这些嵌入如何帮助分类物品。然后,我们根据用户的历史购买计算用户嵌入。最后,我们使用学习到的用户嵌入和物品嵌入来推荐产品。在系列的第二部分,我们将探讨生成吸引人的产品描述以及如何从评论中提取关键的正负特征。
让我们开始第一部分。以下是本文的目录:
目录
1. 数据集
-
合成数据生成
-
物品元数据集生成
-
用户-物品数据集生成
3. 学习物品的代表性嵌入
-
数据准备和清理
-
结合特征、描述和标题
-
使用 Sentence-Transformers 进行嵌入
-
评估物品嵌入
-
子类别分配
-
TSNE 可视化
-
KNN 分类
4. 学习用户的表示
-
高质量用户选择
-
用户数据的训练-测试分割
-
添加负面示例
5. 评估推荐质量
-
Precision@k 度量
-
实现 Precision@k
6. 构建用户的嵌入
-
方法 1:用户作为其购买物的集合
-
方法 2:指数衰减加权平均
8. 结论
-
研究发现总结
-
总结观点
数据集
虽然网上有大量的用户-物品数据集,但为了本文的目的,我们合成了这个数据集。这个数据集包含两部分:1) 物品元数据,2) 用户-物品数据。
物品元数据集包含 item_id、标题、特征、描述和类别。用户-物品购买数据包含 user_id、item_id 和时间戳。这个数据集表明了哪个用户在什么时间购买了哪个物品。
物品元数据集生成
为了生成物品元数据集,我使用了 openAI API。您需要在 openAI 上有足够的信用并创建一个 API 密钥。以下是生成物品元数据集的代码。您可以在下面的代码中替换您的 API 密钥并生成一些数据点。
from openai import OpenAI
client = OpenAI(api_key='your-api-key')
def form_prompt():
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user",
"content": """
Generate a item-metadata dataset for me which has five columns: item_id, title, feature, description, category. I want feature and description to be a list, and category to be set to "fashion".
The item_id column is an integer that is picked randomly.
"""
}
]
)
return(completion.choices[0].message.content)
if __name__ == '__main__':
response = form_prompt()
print(response)
如您所见,我们正在使用 GPT-4o,并提示它生成时尚类别的项目。以下生成了五个数据示例:
由作者从 openAI-图像生成的 item-metadata
现在我们看到我们的提示正在起作用,让我们稍作修改以传递一个 item_id 并生成一个数据点:
def form_prompt(item_id):
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user",
"content": """
I give you an item_id for an item in fashion category, and want you to generate title, feature and description for it.
I want the the description to be full sentences and both features and description to be a list.
item id: {item_id}
""".format(item_id=item_id)
}
]
)
return(completion.choices[0].message.content)
if __name__ == '__main__':
response = form_prompt(1234)
print(response)
下面是输出:
**Item ID: 1234**
**Title:**
Elegant Women's Floral Summer Dress - Lightweight and Breathable
**Features:**
1\. Made from lightweight, breathable fabric.
2\. Beautiful floral print design.
3\. Adjustable spaghetti straps for a perfect fit.
4\. Flattering A-line silhouette.
5\. Perfect for casual outings, beach days, or summer parties.
6\. Available in various sizes from S to XL.
7\. Easy to wash and maintain.
8\. Offers both comfort and style.
**Description:**
1\. This elegant women's floral summer dress is crafted from lightweight and breathable fabric, ensuring you stay cool and comfortable even on the hottest days.
2\. Featuring a beautiful floral print, this dress captures the essence of summer and adds a touch of femininity to your wardrobe.
3\. The adjustable spaghetti straps allow for a customized fit, making sure you not only look good but feel good too.
4\. Designed with a flattering A-line silhouette, this dress enhances your natural curves while providing a relaxed and comfortable fit.
5\. Whether you're heading to a casual outing, a beach day, or a summer party, this versatile dress is perfect for various occasions.
6\. Available in a range of sizes from S to XL, there's a perfect fit for everyone.
7\. Maintenance is a breeze – it's easy to wash and maintain, keeping you looking fresh and stylish with minimal effort.
8\. Combining comfort and style, this summer dress is a must-have addition to any fashion-forward wardrobe.
我们可以轻松地调用这个提示为多个 item_id 生成多个数据点:
if __name__ == '__main__':
for item_id in range(1,1000):
response = form_prompt(item_id)
print(response)
我们将处理响应并将它们保存到 item_metadata.json 文件中,以供以后使用。
用户-项目数据集生成
生成用户-项目数据集是一个更简单的任务。这个数据集包含 user_id、item_id 和时间戳。要生成它,我们可以这样做:
import json
import random
from datetime import datetime, timedelta
def generate_random_timestamp(start_year=2022, end_year=2024):
start = datetime(start_year, 1, 1)
end = datetime(end_year, 12, 31)
return start + (end - start) * random.random()
def create_json_file(num_records, filename):
data = []
for _ in range(num_records):
item_id = random.randint(1, 1000)
user_id = random.randint(1, 200)
timestamp = generate_random_timestamp().strftime('%Y-%m-%d %H:%M:%S')
record = {
"item_id": item_id,
"user_id": user_id,
"timestamp": timestamp
}
data.append(record)
with open(filename, 'w') as file:
json.dump(data, file, indent=4)
在上面的代码中,我们假设有 1000 个项目和 200 个用户。我们可以按照以下方式生成 item_user_data.json 文件:
create_json_file(7000, 'item_user_data.json')
现在两个数据集都准备好了,让我们开始解决项目推荐问题。
为项目学习代表性嵌入
在向用户推荐产品时,我们首先需要学习项目/产品的代表性嵌入。一旦我们有了这个,然后我们使用用户的购买历史来根据他们购买的项目为他们获得一个表示。只有在我们有了用户和产品的表示之后,我们才能进行产品推荐。
因此,首先我们根据项目的特征、标题和描述计算一个代表性嵌入。这里的目的是有一个反映项目类别或子类别的嵌入。如果我们找到这样的嵌入,我们可以用它来识别新项目的类别(或子类别)。
数据准备和清理
我们首先加载数据。我们读取包含时尚类别产品元数据的 item-metadata 数据集:
import pandas as pd
meta_df = pd.read_json("item_metadata.json", lines=True)
为了确保我们处理的是高质量的数据点,我们应该过滤掉那些特征和描述为空列表的行。我们还应该过滤掉特征少于 10 个的行,因为产品拥有的特征越多,我们就能计算更好的嵌入。清理过程的目标是仅保留高质量行。
mask1 = meta_df['features'].apply(lambda x: x != [])
mask2 = meta_df['description'].apply(lambda x: x != [])
df2 = meta_df[mask1 & mask2]
df2.loc[:, 'Length'] = df2['features'].apply(len)
df3 = df2[df2['Length'] >= 10]
print(df3.shape)
现在,df3 包含了具有足够描述和特征的高质量项目。
结合特征、描述和标题
接下来,我们将 features、description 和 title 的三个列合并到一个列中,并使用它来计算产品的嵌入向量:
df3.loc[:, 'j_features'] = df3['features'].apply(lambda x: ''.join(x))
df3.loc[:, 'j_description'] = df3['description'].apply(lambda x: '.'.join(x))
df3.loc[:, 'combined'] = df3[['title', 'j_features', 'j_description']].agg(' '.join, axis=1)
好的,我们完成了。combined 是我们将用于为每个项目计算嵌入的列。
使用 Sentence-Transformers 进行嵌入
我们可以使用任何预训练的 LLM 来计算项目嵌入,但我更喜欢使用sentence-transformers,因为描述包含句子,而且已知sentence-transformers能够学习更好的句子表示。如果你不熟悉sentence-transformers,请阅读我关于它的其他文章我的其他文章:
首先,在你的笔记本环境中安装 sentence transformer:
!pip install sentence-transformers
第二,加载他们的模型之一:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
然后计算嵌入:
df3.loc[:, 'embeddings'] = df3['combined'].apply(lambda x: model.encode(x))
评估项目嵌入
现在,让我们检查嵌入的质量。它是否真正代表了项目的类别或子类别?
在这篇文章中,我选择检查子类别,因为我们加载并推断嵌入的所有项目都属于一个类别,即时尚。
子类别分配
因此,首先让我们在我们的数据集中添加一个sub_category列:
def assign_sub_cat(x):
x = x.lower()
for sub_cat in ['clothing', 'jewelry', 'shoe', 'watch']:
if sub_cat in x:
return sub_cat
for sub_cat in ['handbag', 'wallet']:
if sub_cat in x:
return 'handbag & wallet'
for sub_cat in ['belt', 'hat', 'scarf', 'sunglass', 'tie']:
if sub_cat in x:
return sub_cat
for sub_cat in ['activewear', 'swimwear', 'outerwear', 'socks']:
if sub_cat in x:
return sub_cat
for sub_cat in ['luggage','backpack', 'travel']:
if sub_cat in x:
return 'luggage & travel gear'
for sub_cat in ['intimates', 'underwear']:
if sub_cat in x:
return 'intimates'
return 'other'
df3.loc[:, 'sub_category'] = df3['combined'].apply(lambda x: assign_sub_cat(x))
正如你所见,基于combined列中的单词,我们选择了sub_category。sub_category包含以下 14 个字符串值:
{0: 'activewear',
1: 'belt',
2: 'clothing',
3: 'handbag & wallet',
4: 'hat',
5: 'jewelry',
6: 'luggage & travel gear',
7: 'other',
8: 'outerwear',
9: 'scarf',
10: 'shoe',
11: 'socks',
12: 'sunglass',
13: 'tie',
14: 'watch'}
TSNE 可视化
现在,既然每个项目都有一个嵌入和一个子类别,让我们通过 TSNE 在二维中绘制它们,看看是否有明显的聚类:
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
embeddings_array = np.array(df3['embeddings'].tolist())
categories = df3['sub_category'].astype('category').cat.codes
# Apply t-SNE to reduce embeddings to 2D
tsne = TSNE(n_components=2, random_state=42)
embeddings_2d = tsne.fit_transform(embeddings_array)
plt.figure(figsize=(14, 10))
scatter = plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1], c=categories, cmap='viridis', edgecolor='k', s=50)
plt.legend(*scatter.legend_elements(), title="Subcategories")
plt.title('2D t-SNE of Product Embeddings by Subcategory')
plt.xlabel('Dimension 1')
plt.ylabel('Dimension 2')
plt.show()
它给出了以下图表。
项目嵌入的 TSNE 表示 - 作者图片
如果你仔细观察图表,你可以看到一些项目的聚类。例如,黄色点形成了一个与数字 13 相对应的聚类,这是手表项目。另一个聚类是图表左侧的太阳镜(数字 12),另一个更广泛的聚类是数字 11,它对应于袜子。
嵌入的 2D t-SNE 表示中的聚类 - 作者图片
这是一个好消息!尽管我们在二维中,并且由于降维我们丢失了很多信息,但嵌入仍然代表了子类别。
KNN 分类
接下来,让我们使用这些嵌入将项目分类到它们的子类别。为此,我们利用 Scikit-learn 中的 KNN(K 最近邻)分类算法:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report
X = np.array(df3['embeddings'].tolist())
y = df3['sub_category']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
# Initialize and train the k-NN classifier
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
# Make predictions
y_pred = knn.predict(X_test)
# Evaluate the classifier
print(classification_report(y_test, y_pred))
正如你所见,我们取了*30%*的数据作为测试数据,其余的作为训练数据。我们打印分类报告,结果如下:
knn 分类的性能 - 作者图片
我们看到加权平均精确度、召回率和 f1 分数都在 70%的范围内,这是好的。这是另一个信号,表明嵌入代表了子类别领域。
为用户学习表示
要构建用户嵌入,我们加载 item_user_data.json 文件。这个数据集包含用户购买的商品:
import pandas as pd
purchase_df = pd.read_json("item_user_data.json", lines=True)
数据集看起来如下:
Item_user_data 数据集 – 作者图片
我们想要做的是选择高质量的用户,这些用户有长期的购买历史。然后我们按时间顺序排列他们的购买,并选择前 N 次购买作为训练数据,其余的作为测试数据。训练数据将帮助我们构建他们的表示(或嵌入),测试数据允许我们测试嵌入的质量。
高质量用户选择
让我们首先将时间戳列转换为以秒为单位的 Unix 纪元时间。这将得到一个整数:
# Convert 'timestamp' to Unix epoch time in seconds
purchase_df.loc[:, 'timestamp_int'] = purchase_df['timestamp'].astype(int) // 10**9
新增了一个列 timestamp_int:
作者图片
然后我们选择“高质量”的用户,这意味着他们有长期的购买历史。在这里,我们定义“长期”为 30 个商品或更多:
users_to_items = {}
for _,row in purchase_df.iterrows():
user = row['user_id']
item = row['item_id']
time = row['timestamp_int']
if user not in users_to_items:
users_to_items[user] = []
users_to_items[user].append((item, time))
selected_users = {}
for k,v in users_to_items.items():
items = list(set([x[0] for x in v]))
if len(items)>=30:
selected_users[k] = v
好的,selected_users 是我们工作的字典。
用户数据的训练-测试分割
现在,让我们将这些用户的分割成训练数据和测试数据:
from collections import defaultdict
def split_user_data(selected_users, test_size=10):
train_data = defaultdict(list)
test_data = defaultdict(list)
for user, purchases in selected_users.items():
# Sort purchases by time
sorted_purchases = sorted(purchases, key=lambda x: x[1])
# Split into train and test
if len(sorted_purchases) > test_size:
train_data[user] = sorted_purchases[:-test_size]
test_data[user] = sorted_purchases[-test_size:]
else:
train_data[user] = []
test_data[user] = sorted_purchases
return dict(train_data), dict(test_data)
train_data, test_data = split_user_data(selected_users, test_size=10)
正如你所见,我们选择每个用户的最后 10 次购买作为他们的测试数据,其余的作为他们的训练数据。这 10 个商品是他们的正例,意味着这些是用户实际购买的商品。
添加负例
现在,我们需要添加一些负例,即用户未购买的商品。为此,我们从所有现有商品中采样 10 个商品,这些商品既不在用户的购买历史中,也不在我们想要预测的测试数据中。这形成了我们的负例。
import random
# Collect all unique items
all_items = set()
for purchases in selected_users.values():
for item, _ in purchases:
all_items.add(item)
# Sample 10 items for each user that they have not purchased
negative_examples = {}
for user, purchases in selected_users.items():
user_items = {item for item, _ in purchases}
available_items = list(all_items - user_items)
if len(available_items) >= 10:
sampled_items = random.sample(available_items, 10)
else:
sampled_items = available_items # If fewer than 10, return all available
negative_examples[user] = sampled_items
我们的正例也是用户购买的商品集合。这个集合也包含每个用户的 10 个正例。
# Positive examples
positive_examples = {}
for k,v in test_data.items():
positive_examples[k] = [item[0] for item in v]
让我说明一下,我们的测试集是每个用户的正例和负例的并集:
test_set = {}
for user, v in positive_examples.items():
test_set[user] = list(set(v + negative_examples[user]))
评估推荐质量
评估推荐质量的一个常用指标是 precision@k。这个指标查看前 k 个推荐项目,并衡量其中有多少百分比是相关的;在这里,相关意味着它们在用户的正例中。
precision@k 的定义如下:
precision at k 度量 – 作者图片
precision@k 是最常用的度量之一,但并非唯一。还有许多其他度量,例如 标准化折现累积增益 (NDCG),它通过考虑推荐项目在列表中的位置来衡量排名质量。在这篇文章中,我们将使用 precision@k。
def precision_at_k(user_recommendations, k):
"""
Compute Precision at k for a set of user recommendations.
:param user_recommendations: dict, where key is a user ID and value is a list of (item, relevance) tuples
:param k: int, the number of top recommendations to consider
:return: average of precision at k scores for all users
"""
precision_scores = {}
for user, recommendations in user_recommendations.items():
# Take the top k recommendations
top_k_recommendations = recommendations[:k]
# Count the number of relevant items in the top k recommendations
relevant_count = sum([1 for item, relevance in top_k_recommendations if relevance == 1])
# Calculate precision at k
precision = relevant_count / k
precision_scores[user] = precision
l = list(precision_scores.values())
return 100 * sum(l)/len(l)
在上述代码中,user_recommendations是一个字典,将用户映射到一个有序的(item,relevancy)列表,其中如果项目在用户的正例中,则相关性为 1,否则为 0。此列表按顺序排列,列表中的第一个项目是最推荐的。推荐总是按相似度分数降序排列,相似度分数通常是余弦相似度。余弦相似度是通过用户嵌入和项目嵌入来计算的,以衡量用户购买该项目的可能性。
def cosine_similarity(vec1, vec2):
"""Compute the cosine similarity between two vectors."""
return np.dot(vec1, vec2) / (norm(vec1) * norm(vec2))
构建用户嵌入
现在我们有了评估指标,数据也准备好了,我们可以构建用户嵌入。从他们的物品嵌入构建用户嵌入有许多技术和方法。这里有一些:
-
最简单的方法是将用户嵌入定义为他们的物品嵌入的集合。在这个方法中,没有为用户分配一个嵌入,而是将嵌入列表视为用户的代表。
-
另一种方法是通过对嵌入的集合进行平均或通过加权衰减平均来将其减少到一个单一的嵌入,这里的权重是时间戳。想法是最新的购买将对用户的嵌入产生比早期购买更大的影响。
-
第三种方法是使用更复杂的方法,例如注意力机制来计算用户嵌入。
我们从用户嵌入的第一个方法开始,这是最简单的一个。
方法 1:将用户视为其购买的集合
这是定义用户嵌入为他们的物品嵌入集合的最简单方法。
让我们加载嵌入并计算test_set(包含正例和负例)与用户嵌入之间的余弦相似度分数。
import numpy as np
from numpy.linalg import norm
from collections import defaultdict
import ast
import json
def load_embeddings(file_path):
"""
Load embeddings from a file. Assumes each line is formatted as 'item, embedding'.
"""
with open(file_path, 'r') as file:
embeddings = json.load(file)
for k,v in embeddings.items():
embeddings[k] = ast.literal_eval(v)
return embeddings
def compute_scores(train_data, test_set, embeddings_file):
# Load embeddings
embeddings = load_embeddings(embeddings_file)
# Initialize the result dictionary
scores = defaultdict(list)
# Compute scores for each user
for user, train_items in train_data.items():
train_items = [item for item, _ in train_items]
train_vectors = [embeddings[item] for item in train_items if item in embeddings]
if user in test_set:
test_items = test_set[user]
for test_item in test_items:
if test_item in embeddings:
test_vector = embeddings[test_item]
similarity_sum = sum(cosine_similarity(test_vector, train_vector) for train_vector in train_vectors)
scores[user].append((test_item, similarity_sum))
return dict(scores)
embeddings_file = 'fashion_item_emb.json' # this file contains item embeddings
scores = compute_scores(train_data, test_set, embeddings_file)
现在我们已经为每个用户计算了分数,让我们按降序排列并添加相关性(1 或 0):
users_recom = {}
for user, v in scores.items():
users_recom[user] = []
ll = sorted(v, key=lambda x: x[1], reverse=True)
for item,_ in ll:
if item in positive_examples[user]:
users_recom[user].append((item, 1))
else:
users_recom[user].append((item, 0))
我们现在可以计算precision@k。让我们打印 k=1,3,5 的precision@k。
print(f"precision@1 is {round(precision_at_k(users_recom, 1),2)}")
print(f"precision@3 is {round(precision_at_k(users_recom, 3),2)}")
print(f"precision@5 is {round(precision_at_k(users_recom, 5),2)}")
结果如下:
precision@1 is 68.82
precision@3 is 62.01
precision@5 is 57.74
如我们所见,使用简单的用户表示,precision@k并不差。
接下来,让我们尝试将用户嵌入计算为他们的物品嵌入的加权平均,其中权重反映了购买的时效性。
方法 2:指数衰减加权平均
在这个方法中,我们计算物品嵌入的指数衰减加权平均。简单来说,用户嵌入是其物品嵌入的加权平均,其中权重由指数函数定义。
import numpy as np
def compute_user_embedding(purchases, item_embeddings, decay_rate=0.001):
"""
Compute the user embedding as the weighted average of item embeddings.
"""
if not purchases:
return np.zeros_like(next(iter(item_embeddings.values())))
# Extract item_ids and timestamps
item_ids, timestamps = zip(*purchases)
valid_item_ids, valid_timestamps = [],[]
for i, item_id in enumerate(item_ids):
if item_id in item_embeddings:
valid_item_ids.append(item_id)
valid_timestamps.append(timestamps[i])
# Normalize timestamps
normalized_timestamps = max(valid_timestamps) - np.array(valid_timestamps)
# Compute weights using exponential decay
weights = np.exp(-decay_rate * normalized_timestamps)
# Compute weighted embeddings
embeddings = np.array([item_embeddings[item_id] for item_id in valid_item_ids])
weighted_embeddings = embeddings * weights[:, np.newaxis]
# Compute the weighted average
user_embedding = weighted_embeddings.sum(axis=0) / weights.sum()
return user_embedding
在上述函数中,参数如下:
-
purchases:包含元组(item_id,timestamp)的列表 -
item_embeddings:将 item_id 映射到嵌入向量的字典 -
decay_rate:加权时效性的指数衰减率以及平均的指数权重在以下行中定义:
weights = np.exp(-decay_rate * normalized_timestamps)
好的,现在我们有了用户嵌入,让我们计算推荐并测量precision@k。
以下代码计算推荐:
def compute_recoms_exp_decay(user_embedding, test_set, embeddings_file, positive_examples):
# Load embeddings
embeddings = load_embeddings(embeddings_file) # Initialize the result dictionary
scores = defaultdict(list)
users_recom = defaultdict(list)
# Compute scores for each user
for user, user_emb in user_embedding.items():
if user in test_set:
test_items = test_set[user]
for test_item in test_items:
if test_item in embeddings:
test_vector = embeddings[test_item]
similarity_score = cosine_similarity(test_vector, user_emb)
scores[user].append((test_item, similarity_score))
# Sort recoms based on scores and add relevancy
ll = sorted(scores[user], key=lambda x: x[1], reverse=True)
for item,_ in ll:
if item in positive_examples[user]:
users_recom[user].append((item, 1))
else:
users_recom[user].append((item, 0))
return users_recom
现在我们可以调用函数并为其运行precision@k:
users_recom = compute_recoms_exp_decay(user_embedding, test_set, "fashion_item_emb.json", positive_examples)
print(f"precision@1 is {round(precision_at_k(users_recom, 1),2)}")
print(f"precision@3 is {round(precision_at_k(users_recom, 3),2)}")
print(f"precision@5 is {round(precision_at_k(users_recom, 5),2)}")
结果如下,并不如方法 1 好:
precision@1 is 62.37
precision@3 is 61.29
precision@5 is 58.92
结论
在这篇文章中,我们探讨了电子商务领域的推荐问题,并探讨了生成式 AI 如何帮助计算针对用户的相关推荐。我们首先展示了如何合成生成数据,然后将问题分解为两个子问题:1)学习项目嵌入,2)学习用户嵌入,最后使用这两个嵌入来计算一个相似度得分作为推荐得分。我们还讨论了一个常见的评估指标,称为 precision@k,它衡量了前 k 个推荐项目的质量。
推荐是生成式 AI 在电子商务领域可以显著提升的许多方面之一。在下一篇文章中,我们将探讨生成式 AI 如何帮助生成吸引人的产品描述,以及从评论中提取情感并将其作为产品关键的正负方面。
感谢您的阅读。
关注我,获取更多关于大型语言模型和机器学习一般性的类似文章。如果您有任何问题或建议,请随时联系我:邮箱:[email protected] 领英:www.linkedin.com/in/minaghashami/
生成式AI赋能电商推荐系统

被折叠的 条评论
为什么被折叠?



