在当今的数字世界中,垃圾邮件早已不再只是令人烦恼的东西——它正逐渐成为一种严重的安全威胁。为了应对这一挑战,开发者们常常会借助机器学习技术来构建智能过滤器,以便区分合法邮件和恶意邮件。
虽然在使用笔记本环境构建机器学习模型相对较为简单,但真正的难点在于将这个模型部署到一个可扩展、能够投入实际使用的系统中。
在这个项目中,我开发了一个端到端的无服务器垃圾邮件分类系统。该系统结合了Scikit-learn进行模型开发,同时利用AWS Lambda、Amazon S3和Amazon API Gateway来完成模型的部署工作。最终得到的结果是一个轻量级、可扩展的API,它能够实时地对邮件内容进行分类。
这个系统的设计理念是模块化且成本效益高,因此用户可以独立地对模型进行重新训练或更新,而不会影响到正在运行的API服务。无论是检测“免费iPhone”骗局,还是识别网络钓鱼攻击,这个项目都展示了如何将机器学习实验成果转化为实际应用。
目录
1. 先决条件
-
基本要求:具备Python编程基础,并了解机器学习中的分类等相关概念。
-
AWS账户:需要拥有一个具有Lambda、S3和API Gateway使用权限的AWS账户。
-
开发环境:需安装Python 3.11,同时还需要scikit-learn、pandas和joblib等相关库。
-
AWS CLI:需要在本地机器上配置好AWS CLI工具,以便进行文件上传操作。
-
HuggingFace账户:你可以直接从我的账户下载所需的模型文件。
2. 构建模型

图片由 Steve A Johnson 在 Unsplash 上提供。
这个项目的核心是一种监督学习方法。我们不会简单地指定哪些词属于垃圾邮件,而是为计算机提供一组数据集和相应的算法,让它能够自行学习和识别垃圾邮件的特征。
1. 向量化:将文本转化为数学表达式
机器学习模型无法阅读文本,它们需要数值形式的输入。为了解决这个问题,我们使用了TF-IDF向量化工具。
feature_extraction = TfidfVectorizer(min_df=1, stop_words='english', lowercase=True)
X_train_features = feature_extraction.fit_transform(X_train)
其数学公式如下:
$$w_{i,j} = tf_{i,j} \times \log \left( \frac{N}{df_i} \right)$$
TF-IDF的相关术语定义如下:
-
wᵢ,ⱼ(权重):某个特定单词在文档中的最终重要性得分。
-
tfᵢ,ⱼ(词频):一个单词在某封邮件中出现的次数。
-
N(总文档数):数据集中所有邮件的总数。
-
dfᵢ(文档频率):包含某个特定单词的不同邮件的数量。
-
log(N/dfᵢ)(IDF值):用于降低那些在所有邮件中都出现的常见单词的得分。
该方法会通过去除常见词汇、将所有文本转换为小写来保持数据的一致性,同时赋予罕见且具有意义的词汇更高的重要性,而降低常用词汇的重要性。
2. 训练:逻辑回归模型的训练过程
在这里,我们将使用逻辑回归算法,这种分类算法可以预测某种结果出现的概率。
在这一阶段,我们会将经过向量化处理的训练数据输入到逻辑回归模型中。我们的目标是建立特定单词的权重与“垃圾邮件”或“合法邮件”标签之间的数学关系。
在训练过程中,模型会不断调整自身的内部参数以最小化误差,最终它会学会识别出像“winner”或“free”这样的词与垃圾邮件有很高的关联度,而像“conversational language”这样的词则与合法邮件相关。
model = LogisticRegression()
model.fit(X_train_features, Y_train)
在我们的案例中,该模型会计算出一封邮件属于垃圾邮件还是合法邮件的概率。
该算法使用Sigmoid函数将任何实数映射到0到1之间的值。
$$P(y=1|x) = \frac{1}{1 + e^{-(z)}}$$
其中z = β₀ + β₁x₁ + … + βₙxₙ。
3. 评估:测试模型的智能水平
在完成训练之后,我们需要验证该模型是否真的能够处理那些它之前从未见过的数据。
prediction_on_test_data = model.predict(X_test_features)
accuracy_on_test_data = accuracy_score(Y_test, prediction_on_test_data)
通过将模型的预测结果与我们测试集中的实际标签进行比较,我们可以计算出准确率。这一数据让我们确信该模型已经具备了在实际环境中运行的能力(在我们的测试中,其准确率达到了约94%)。
4. 逻辑代码的导出与序列化
为了将这个模型从我们的本地Python环境转移到AWS云平台上,我们将使用Joblib工具将我们的代码保存为二进制文件(.pkl格式)。
joblib.dump(model, 'spam_model.pkl')
joblib.dump(feature_extraction, 'vectorizer.pkl')
我们选择使用Pickle格式,是因为它能够将复杂的Python对象(比如数学权重和词汇映射关系)转换为一种便携的二进制格式,这样这些数据就可以在云环境中被立即重新使用。
我们需要Vectorizer工具来将新用户输入的文本转换成模型所能识别的具体数值坐标。如果没有Vectorizer,就相当于只有钥匙而没有锁一样,根本无法使用这个模型。
我们已经训练好的逻辑回归模型以及TF-IDF向量化工具,可以在Hugging Face网站上供大家免费使用:在Hugging Face上获取这些模型。
3. 将模型部署到AWS上
训练模型属于科学范畴,而将其部署到实际环境中则属于工程技术。为了让全世界的人都能使用这个分类器,我们将采用一种无需维护、能够自动扩展的无服务器架构。
1. 模型存储:Amazon S3
首先,我们会将那些.pkl文件上传到Amazon S3桶中。由于模型代码与业务逻辑是分离的,因此我们只需更新S3中的文件即可更新模型的功能,而无需重新部署后端代码。这样的设计使得系统具有很高的可维护性。
2. 生产环境:AWS Lambda
为了让人工智能服务能够被广泛使用,我们会从本地脚本转向无服务器云架构。这种架构可以确保模型随时都能正常运行,而且无需花费额外的成本来维持24/7运行的服务器。
我们的部署环境是AWS Lambda(使用Python 3.11语言)。由于Lambda是一个轻量级的平台,因此它并不包含Scikit-Learn或Joblib这些工具。为了使用这些库,我们会将它们下载下来并存储在S3桶中,然后通过模型结构导入到相应的位置。
AWS CLI中的相关命令:
# 1. 创建工作目录
mkdir ml_layer & cd ml_layer
# 2. 将scikit-learn及其依赖库安装到指定文件夹中
pip install \
--platform manylinux2014_x86_64 \
--target=python/lib/python3.11/site-packages \
--implementation cp \
--python-version 3.11 \
--only-binary=:all: \
scikit-learn joblib
# 3. 将安装好的库压缩成.zip文件
zip -r sklearn_lib.zip python
# 4. 使用AWS CLI将压缩文件上传到S3桶
aws s3 cp sklearn_lib.zip s3://YOUR-Bucket-NAME/
我们将Scikit-Learn库存储在S3中,并将其压缩成ZIP文件,这样就可以绕过AWS Lambda对部署包大小的限制。这样一来,该函数只有在需要时才会动态加载那些占用大量资源的依赖库,从而避免使核心代码变得过于庞大。
Lambda函数:
import json
import boto3
import os
import sys
from io import BytesIO
# 确保能够找到包含scikit-learn/joblib的定制Lambda层
sys.path.append('/opt/python')
try:
import joblib
except ImportError:
# 为特定的Scikit-Learn版本提供备用解决方案
from sklearn.utils import _joblib as joblib
# 初始化S3客户端
s3 = boto3.client('s3')
# 使用占位符来表示文章内容,以便读者可以自行填写实际数据
BUCKET_NAME = 'YOUR_S3_BUCKET_NAME'
MODEL_KEY = 'spam_model.pkl'
VECTORIZER_KEY = 'vectorizer.pkl'
# 用于“热启动”缓存机制的全局变量(这种机制可以通过将模型保留在内存中来提升性能)
model = None
vectorizer = None
def load_model():
"""仅当模型文件尚未存在于内存中时,才会从S3下载这些文件"""
global model, vectorizer
if model is None or vectorizer is None:
try:
# 1. 从S3下载逻辑回归模型文件
m_obj = s3.get_object(Bucket=BUCKET_NAME, Key=MODEL_KEY)
model = joblib.load(BytesIO(m_obj['Body'].read()))
# 2. 直接从S3下载TF-IDF向量器文件
v_obj = s3.get_object(Bucket=BUCKET_NAME, Key=VECTORIZER_KEY)
vectorizer = joblib.load(bytesIO(v_obj['Body'].read()))
except Exception as e:
raise Exception(f"无法从S3下载.pkl文件:{str(e)}")
def lambda_handler(event, context):
try:
# 在开始处理之前,确保模型和向量器已经准备好
load_model()
# 该函数既能够处理直接的Lambda测试请求,也能够处理API Gateway发送的POST请求
body = event.get('body', event)
if isinstance(body, str):
body = json.loads(body)
text = body.get('text', '')
if not text:
return {
'statusCode': 400,
'body': json.dumps({'error': '未提供文本内容。'}
}
# 1. 使用训练好的向量器将输入文本转换为数值特征
data_vec = vectorizer.transform([text])
# 2. 使用逻辑回归模型进行预测
prediction = int(model.predict(data_vec)[0])
# 3. 将预测结果转换为人类可识别的标签
result_label = "HAM" if prediction == 1 else "SPAM"
# 返回响应信息,并设置CORS头
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' # 这个头部是用于实现跨域Web集成的
},
'body': json.dumps({
'status': 'success',
'classification': result_label,
'input_text': text
})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error_message': f"推理过程中出现错误:{str(e)}')
}
Lambda函数的主要特性:
-
预热缓存机制:通过在lambda_handler之外定义模型和向量化器变量,我们可以将这些数据存储在容器的内存中。这样一来,后续请求的启动延迟就会显著降低。
-
动态依赖项加载:通过执行sys.path.append(‘/opt/python’)这条指令,我们能够从S3或Layers服务器导入大型库文件,而不会超过上传限制。
-
双模式输入处理:该函数被设计为既能接收来自AWS控制台的直接JSON格式数据,也能处理通过API Gateway发送的字符串化请求体。
3. API Gateway——连接互联网的桥梁

创建REST API
接下来,我们将创建一个仅包含POST方法的REST API。为什么选择POST方法呢?因为我们需要将包含用户文本信息的JSON数据安全地发送给我们的模型。
-
首先登录Amazon API Gateway控制台,然后选择“创建API”→“REST API”。
-
为你的API起一个名称,比如“EmailSpamPredictor-API”,并将端点类型设置为“区域级”。
-
在左侧侧边栏中点击“资源”,然后输入一个资源名称(例如我输入的/predict)。
-
接下来点击“创建方法”,选择POST,再将集成类型设置为Lambda Function。
-
确保已启用Lambda Proxy集成功能——这样整个请求流程才能顺利传递到你的代码中。
CORS配置(故障排查的关键环节)
许多开发者都会在这里遇到令人头疼的连接错误问题。由于我们的API托管在AWS上,而如果前端应用位于另一个网站上,浏览器的同源策略会默认阻止这种请求。
为了解决这个问题,我们需要启用CORS配置:
-
Access-Control-Allow-Origin:将其设置为*(或具体指定你的域名),这样浏览器就会知道该API可以被前端应用访问。
-
OPTIONS方法:API Gateway会自动生成OPTIONS请求。这个请求用于验证:在发送实际数据之前,浏览器会先询问“是否允许从我的端点接收数据?”
-
Access-Control-Allow-Headers:如截图所示,Content-Type和Authorization等头部信息是被允许的。这样,当我们的JavaScript代码使用fetch()方法并设置内容类型为application/json时,API Gateway就不会拒绝这些请求。

该图片展示了我们项目的CORS配置情况。(图片由作者提供)
部署阶段
一旦API被部署到生产环境,AWS会生成一个永久性的调用URL。这个URL充当我们模型的公共访问接口,其格式通常如下:https://[api-id].execute-api.[region].amazonaws.com/prod/classify。
连接前端与JavaScript层
现在API已经可以正常使用了,我们可以编写一个简单的JavaScript函数来与我们的模型进行交互。每当用户点击网站上的分析按钮时,这个脚本就会被执行。
async function checkSpam() {
const message = document.getElementById("userInput").value;
const apiUrl = "YOUR_API_GATEWAY_INVOKE_URL";
try {
const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ "text": message })
});
const data = await response.json();
// 在网页上显示结果
const resultElement = document.getElementById("result");
resultElement.innerText = `预测结果:${data.classification}`;
resultElement.style.color = data.classification === "SPAM" ? "red" : "green";
} catch (error) {
console.error("错误发生:", error);
alert("无法连接到Spam检测API。");
}
}
4. 如何在本地运行该项目
你可以将前端代码保存为HTML文件。准备就绪后,不要直接双击该.html文件——在浏览器中将其作为普通文件打开可能会引发安全问题。相反,你应该使用一个简单的本地服务器来托管它。
步骤1: 打开终端或命令提示符。
步骤2: 导航到你的项目文件夹。
cd [PATH_TO_YOUR_FOLDER]
步骤3: 启动一个本地的Python Web服务器。
python -m http.server 8000
步骤4: 访问应用程序。
打开浏览器,然后访问以下地址:
http://localhost:8000/your-file-name.html
观看演示:
5. 我们的项目架构

这张图片展示了我们项目的架构(即构建一个无服务器垃圾邮件分类系统)。它展示了从用户输入到最终模型输出这一整个处理流程。(图片由作者提供)
-
客户端前端交互:整个过程最左侧开始。用户通过网页界面或桌面应用程序进行操作,他们输入诸如“立即赢取免费iPhone”这样的文本,从而触发请求。
-
入口点:API网关:请求首先到达Amazon API网关,该网关充当“安全守卫”和数据转换器的作用。
(a) CORS OPTIONS机制用于处理预请求握手过程,以确保浏览器获得与AWS云平台进行交互的权限。
(b)分类请求(POST请求)会将实际的消息数据转发到后端逻辑处理模块。 -
核心处理引擎:AWS Lambda(Python 3.11语言):位于中间的这个组件代表Lambda函数。你编写的代码就存储在这里。Lambda函数并不会全天候运行,只有当有请求到达时才会被激活。
-
数据存储与检索:S3存储桶:由于Lambda函数的体积较小,因此它不会将庞大的机器学习模型文件保存在自身内部。
依赖库及模型的下载:Lambda函数会从S3存储桶中下载sklearn库以及.pkl格式的模型文件。
必需的依赖资源:这些文件会被加载到Lambda函数的临时内存中,以便进行后续的预测计算。 -
推理流程:在Lambda函数内部,会执行三个步骤的数学处理:
(a) 文本向量化:将文本中的单词转换为数字形式。
(b) 逻辑回归:根据这些数字计算出垃圾邮件的概率。(c> 分类结果生成:最终确定邮件属于“垃圾邮件”还是“正常邮件”。 -
结果输出:处理结果会通过API网关返回给客户端,同时附带必要的CORS头部信息,以确保浏览器能够正确接收这些数据。前端界面随后会更新显示“结果:垃圾邮件”这一结论,并附有相应的视觉提示。
6. 结论:无服务器AI的强大能力
通过将逻辑回归的数学简洁性与AWS无服务器架构的强大功能相结合,我们成功地将一个普通的Python脚本转化成了一个可全球使用的、具备扩展性的API服务。
这个项目证明了:部署高质量的机器学习系统并不需要巨额预算,也不必使用全天候运行的专用服务器。
通过使用S3与Lambda相结合的解决方案,我们成功绕过了常见的存储障碍,从而确保我们的“大脑”(模型)及其“肌肉”(Scikit-Learn框架)能够在云环境的临时性环境中顺畅运行。这一方案有效地弥合了实验研究与实际应用之间的差距,使人工智能系统变得实用、高效且易于使用。
7. 致谢 / 参考文献
-
预训练的垃圾邮件分类模型:可访问Hugging Face查看rakshath1/mail-spam-detector · Hugging Face
-
Scikit-learn的文档资料
-
AWS Lambda的文档资料
-
Amazon S3的文档资料
-
Amazon API Gateway的文档资料
与我联系
您可能还会喜欢:



