使用持续集成构建可靠的机器学习管道
使用持续集成自动化机器学习工作流程
·发表于 Towards Data Science ·阅读时长 8 分钟·2023 年 4 月 6 日
–
情境
作为数据科学家,你负责改进当前在生产中的模型。在花费几个月时间微调模型后,你发现了一个比原始模型更准确的模型。
对你的突破感到兴奋,你创建了一个拉取请求,将你的模型合并到主分支中。
作者提供的图片
不幸的是,由于众多的变更,你的团队需要超过一周的时间来评估和分析这些变更,这最终阻碍了项目进展。
此外,在部署模型后,你发现由于代码错误导致的意外行为,使公司损失了金钱。
作者提供的图片
回顾来看,在提交拉取请求后自动化代码和模型测试本可以避免这些问题,并节省时间和金钱。
持续集成(CI)为这一问题提供了简单的解决方案。
什么是 CI?
CI 是将代码更改持续合并和测试到共享仓库中的实践。在机器学习项目中,CI 因多种原因非常有用:
-
尽早捕获错误:CI 通过自动测试任何代码更改来促进早期错误的识别,从而在开发阶段实现及时的问题检测
-
确保可重复性:CI 通过建立明确且一致的测试程序来帮助确保可重复性,使得复制机器学习项目结果变得更容易。
-
更快的反馈和决策:通过提供明确的指标和参数,CI 使得反馈和决策更加迅速,释放了审阅者的时间以处理更关键的任务。
作者提供的图片
本文将展示如何为机器学习项目创建一个 CI 流水线。
随意尝试和分叉本文的源代码:
[## GitHub - khuyentran1401/cicd-mlops-demo: 机器学习项目中的 CI/CD 演示
这是文章《构建可靠的机器学习流水线与持续集成》的一个示例项目。CI/CD…
github.com](https://github.com/khuyentran1401/cicd-mlops-demo/?source=post_page-----ea822eb09bf6--------------------------------)
CI 流水线概述
为机器学习项目构建 CI 流水线的方法可以根据每个公司的工作流程有所不同。在本项目中,我们将创建一个最常见的工作流程来构建 CI 流水线:
-
数据科学家对代码进行更改,在本地创建一个新模型。
-
数据科学家将新模型推送到远程存储。
-
数据科学家为更改创建一个拉取请求。
-
CI 流水线被触发以测试代码和模型。
-
如果更改被批准,它们将合并到主分支中。
作者提供的图片
让我们基于这个工作流程举一个例子。
构建工作流
假设实验 C 在尝试了各种处理技术和机器学习模型后表现异常出色。因此,我们的目标是将代码和模型合并到主分支中。
作者提供的图片
为了实现这一点,我们需要执行以下步骤:
-
对实验的输入和输出进行版本控制。
-
将模型和数据上传到远程存储。
-
创建测试文件以测试代码和模型。
-
创建一个 GitHub 工作流。
作者提供的图片
现在,让我们详细探讨这些步骤。
对实验的输入和输出进行版本控制
我们将使用 DVC 对流水线实验的输入和输出进行版本控制,包括代码、数据和模型。
## DVC + GitHub Actions: 自动重新运行流水线中修改过的组件
快速迭代你的数据科学项目的完美组合
towardsdatascience.com
流水线是根据项目中的文件位置定义的:
作者提供的图片
我们将在 dvc.yaml
文件中描述流水线的阶段及其之间的数据依赖关系:
stages:
process:
cmd: python src/process_data.py
deps:
- data/raw
- src/process_data.py
params:
- process
- data
outs:
- data/intermediate
train:
cmd: python src/train.py
deps:
- data/intermediate
- src/train.py
params:
- data
- model
- train
outs:
- model/svm.pkl
evaluate:
cmd: python src/evaluate.py
deps:
- model
- data/intermediate
- src/evaluate.py
params:
- data
- model
metrics:
- dvclive/metrics.json
要运行在 dvc.yaml
中定义的实验流水线,请在终端中输入以下命令:
dvc exp run
我们将获得以下输出:
'data/raw.dvc' didn't change, skipping
Running stage 'process':
> python src/process_data.py
Running stage 'train':
> python src/train.py
Updating lock file 'dvc.lock'
Running stage 'evaluate':
> python src/evaluate.py
The model's accuracy is 0.65
Updating lock file 'dvc.lock'
Ran experiment(s): drear-cusp
Experiment results have been applied to your workspace.
To promote an experiment to a Git branch run:
dvc exp branch <exp> <branch>
运行将自动生成dvc.lock
文件,该文件存储数据、代码和它们之间依赖项的精确版本。使用相同版本的输入和输出可以确保将来可以重现相同的实验。
schema: '2.0'
stages:
process:
cmd: python src/process_data.py
deps:
- path: data/raw
md5: 84a0e37242f885ea418b9953761d35de.dir
size: 84199
nfiles: 2
- path: src/process_data.py
md5: 8c10093c63780b397c4b5ebed46c1154
size: 1157
params:
params.yaml:
data:
raw: data/raw/winequality-red.csv
intermediate: data/intermediate
process:
feature: quality
test_size: 0.2
outs:
- path: data/intermediate
md5: 3377ebd11434a04b64fe3ca5cb3cc455.dir
size: 194875
nfiles: 4
将数据和模型上传到远程存储
DVC 使得将管道阶段产生的数据文件和模型上传到dvc.yaml
文件中的远程存储位置变得简单。
就像 Git 一样,但带有数据!
towardsdatascience.com
在上传文件之前,我们将在文件.dvc/config
中指定远程存储位置:
['remote "read"']
url = https://winequality-red.s3.amazonaws.com/
['remote "read-write"']
url = s3://winequality-red/
确保将 S3 桶的 URI 替换为“读写”远程存储 URI。
图片由作者提供
将文件推送到名为“读写”的远程存储位置:
dvc push -r read-write
创建测试
我们还将生成测试,以验证处理数据、训练模型以及模型本身的代码性能,确保代码和模型符合我们的期望。
图片由作者提供
查看所有测试文件 这里。
创建 GitHub 工作流
现在进入激动人心的部分:创建一个 GitHub 工作流以自动化测试您的数据和模型!如果您不熟悉 GitHub 工作流,我建议阅读这篇文章以获得快速概述。
我们将在文件.github/workflows/run_test.yaml
中创建名为Test code and model
的工作流:
图片由作者提供
name: Test code and model
on:
pull_request:
paths:
- conf/**
- src/**
- tests/**
- params.yaml
jobs:
test_model:
name: Test processed code and model
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v2
- name: Environment setup
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: pip install -r requirements.txt
- name: Pull data and model
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: dvc pull -r read-write
- name: Run tests
run: pytest
- name: Evaluate model
run: dvc exp run evaluate
- name: Iterative CML setup
uses: iterative/setup-cml@v1
- name: Create CML report
env:
REPO_TOKEN: ${{ secrets.TOKEN_GITHUB }}
run: |
# Add the metrics to the report
dvc metrics show --show-md >> report.md
# Add the parameters to the report
cat dvclive/params.yaml >> report.md
# Create a report in PR
cml comment create report.md
on
字段指定管道在拉取请求事件时触发。
test_model
作业包括以下步骤:
-
查看代码
-
设置 Python 环境
-
安装依赖项
-
使用 DVC 从远程存储位置提取数据和模型
-
使用 pytest 运行测试
-
使用 DVC 实验评估模型
-
设置Iterative CML(持续机器学习)环境
-
使用 CML 创建包含指标和参数的报告,并在拉取请求中评论该报告。
请注意,为了使作业正常运行,需要以下内容:
-
AWS 凭证以提取数据和模型
-
GitHub token用于评论拉取请求。
为了确保在我们的仓库中安全存储敏感信息,并允许 GitHub Actions 访问它们,我们将使用加密密钥。
图片来自作者
就这样!现在让我们尝试这个项目,看看它是否按照我们预期的方式工作。
尝试一下
设置
要尝试这个项目,首先创建一个新的仓库,使用项目模板。
克隆仓库到本地机器:
git clone https://github.com/your-username/cicd-mlops-demo
设置环境:
# Go to the project directory
cd cicd-mlops-demo
# Create a new branch
git checkout -b experiment
# Install dependencies
pip install -r requirements.txt
从名为“read”的远程存储位置拉取数据:
dvc pull -r read
创建实验
如果对params.yaml
文件或src
和tests
目录中的文件进行任何更改,将触发 GitHub 工作流。为了说明这一点,我们将对params.yaml
文件进行一些小改动:
图片来自作者
接下来,让我们创建一个带有更改的新实验:
dvc exp run
将修改后的数据和模型推送到名为“read-write”的远程存储:
dvc push -r read-write
添加、提交并推送更改到仓库:
git add .
git commit -m 'add 100 for C'
git push origin experiment
创建拉取请求
接下来,通过点击 Contribute 按钮创建拉取请求。
图片来自作者
在仓库中创建拉取请求后,将触发 GitHub 工作流来对代码和模型进行测试。
如果所有测试都通过,将会在拉取请求中添加一个评论,包含新实验的度量和参数。
图片来自作者
这些信息使得审查更容易理解对代码和模型所做的更改。因此,他们可以快速评估这些更改是否符合预期的性能标准,并决定是否批准将 PR 合并到主分支。这是多么酷啊?
结论
恭喜!你刚刚学会了如何为你的机器学习项目创建 CI 管道。我希望这篇文章能激励你创建自己的 CI 管道,以确保可靠的机器学习工作流程。
我喜欢写关于数据科学的概念,并玩各种数据科学工具。你可以通过LinkedIn和Twitter与我联系。
如果你想查看我写的文章中的代码,请给这个仓库加星。关注我在 Medium 上的账号,以便接收我最新的数据科学文章:
[## 使用 Pydantic 和 Prefect 构建全栈机器学习应用程序
用一行代码创建一个机器学习特征工程的 UI
如何构建一个全栈 ML 应用程序与 Pydantic 和 Prefect
4 个 pre-commit 插件以自动化 Python 代码审查和格式化
使用此模板开始您的下一个机器学习项目
数据科学项目的 Pytest 完整指南
使用 black, flake8, isort 和 interrogate 编写高质量代码
使用 Python 自动化 PLAXIS 中的土壤剖面
原文:
towardsdatascience.com/build-soil-profile-in-plaxis-using-python-a9c870e253c7
PLAXIS 自动化系列
自动化的逐步指南
·发表于 Towards Data Science ·10 分钟阅读·2023 年 1 月 4 日
–
作为一名岩土工程师,最重要的 PLAXIS 工作流之一是建立土壤剖面并分配正确的土壤属性。虽然土壤输入界面对用户友好,但这个过程可能会耗时。
可能有改进的空间:
-
在一个表格中创建多个钻孔。
-
自动分配每一层的土壤属性。
-
能够使用重复的土壤属性。当然,这也可以通过 PLAXIS 内置的“.matXdb”来存储材料数据库来实现。然而,Excel 格式提供了更多的灵活性,以根据项目更改材料属性,并且可以链接到其他主电子表格。
本教程旨在扩展从 第四个教程 中学到的内容。我们将进一步开发我们的 Excel 界面,以定义土壤剖面并在 PLAXIS 中分配土壤属性。
-
使用 Pandas 从 Excel 中读取值。
-
使用 Excel 输入土壤深度并创建土壤剖面
-
使用 Excel 输入土壤属性并分配材料
与之前一样,本教程要求读者安装 VS Code 和 PLAXIS 环境。如果你对这个页面不熟悉,请按照下面文章中的说明进行操作。
自动化的逐步指南
towardsdatascience.com
此外,我们还需要在 PLAXIS 环境中安装 pandas。如果还未安装外部模块,请按照以下说明进行安装。
PLAXIS 输出可视化使用 Python
自动化的逐步指南
towardsdatascience.com
Excel 输入界面
类似于第 4 个教程,我们希望创建一个 Excel 输入模板,并在 PLAXIS 中创建土壤剖面。
我们将创建一个空的 Excel 表格,命名为“Soil_input”。该界面包含两个工作表:
-
OHE Ground Profile
-
土壤属性
作者提供的 Excel 截图
创建工作表时,确保它们遵循上述相同的名称,因为名称是区分大小写的。
OHE Ground Profile
“OHE Ground Profile”工作表的目的是定义土壤单元的名称和深度。这些值将用于在 PLAXIS 中创建相应的钻孔。它涉及以下四个输入值:
-
名称:指定每个钻孔的名称。此行不会被 Python 提取,但它是我们工作的钻孔的良好跟踪器。例如,B 列包含“BH1”的所有输入值。
-
x 坐标:定义每个钻孔的 x 坐标。
-
顶部:定义每个钻孔顶部的 y 坐标。换句话说,就是地质工程中的顶部 R.L.。
-
土壤单元名称及层级坐标:从第 4 行开始,我们首先在 A 列定义土壤单元名称。然后我们在每个钻孔下方输入每个单元的底部 y 坐标。
应强调的是,这个模板旨在输入每个单元的底部坐标。另一个重要说明是,我们应在 A 列中输入所有钻孔的所有单元。类似于 PLAXIS 界面的逻辑,如果我们遇到一个在钻孔中不存在的单元,我们将使用与前一个单元相同的 y 坐标。
以“BH1”为例,我们为“SSIV”和“SSIII”都设置 4 米,这意味着“SSIII”在“BH1”中不存在。
作者提供的 Excel 截图
土壤属性
该工作表的目的是定义每个单元的土壤属性。这包括以下列:
-
名称
-
材料
-
单位重量(kN/m³)
-
杨氏模量,E’(kPa)
-
泊松比,v(nu)(-)
-
粘聚力,c’(kPa)
-
摩擦角,phi(度)
-
拉伸强度(kPa)
-
界面强度(-)
-
K0 自动?:T/F 确定 K0 是否在 PLAXIS 中自动计算
-
K0x = K0y?:T/F 确定 K0x 是否等于 K0y
-
K0x(-)
-
K0y(-)
作者提供的 Excel 截图
这些是 PLAXIS 2D 中 Mohr-Coulomb 模型的典型材料属性,只是为了确保所有输入属性都在正确的单位中。还应注意,此模板仅设计用于排水条件。
确保你已经创建了包含上述工作表和数值的 Excel 电子表格。
输入文件创建后,我们准备进入下一步。
步骤 1:使用 Pandas 从 Excel 中读取值
步骤 1 的主要目标是设置土壤轮廓并读取 Excel 输入模板。
首先,我们创建一个空的 Python 文件,并将其命名为 “soil_geometry.py”。
导入模块并启动服务器
与之前的教程类似,我们将首先导入相关模块并启动服务器。
from plxscripting.easy import *
import subprocess, time
import pandas as pd
###############################################
PLAXIS_PATH = r'C:\Program Files\Bentley\Geotechnical\PLAXIS 2D CONNECT Edition V22\\Plaxis2DXInput.exe' # Specify PLAXIS path on server.
PORT_i = 10000 # Define a port number.
PORT_o = 10001
PASSWORD = 'SxDBR<TYKRAX834~' # Define a password.
subprocess.Popen([PLAXIS_PATH, f'--AppServerPassword={PASSWORD}', f'--AppServerPort={PORT_i}'], shell=False) # Start the PLAXIS remote scripting service.
time.sleep(5) # Wait for PLAXIS to boot before sending commands to the scripting service.
# Start the scripting server.
s_i, g_i = new_server('localhost', PORT_i, password=PASSWORD)
s_o, g_o = new_server('localhost', PORT_o, password=PASSWORD)
s_i.new()
g_i.SoilContour.initializerectangular(-15, -10, 15, 10)
读取 Excel 文件
文件位置:C:\Users\phtsang\Desktop\PLAXIS_V22\Python_automation
文件名:我们之前创建的电子表格,即 “Soil_input.xlsx”
由于我们要从 “OHE Ground Profile” 中提取值,我们将使用 “pd.read_excel()” 并通过 “sheet_name” 指定要读取的工作表。
source=r"C:\Users\phtsang\Desktop\PLAXIS_V22\Python_automation"
file="Soil_input"+".xlsx"
soilsheet="OHE Ground Profile"
一旦我们设置好 Excel 模板,就可以使用这些值创建土壤剖面。
步骤 2:使用 Excel 输入土壤深度并创建土壤剖面
在步骤 2 中,我们将从 “OHE Ground Profile” 工作表中提取钻孔值,并相应地创建土壤剖面。
输入表格涉及的信息如下所示。我们需要这些信息来参考我们的代码。
作者从 Excel 截图
我们将使用pandas的方法从 Excel 中提取值。详细说明请参阅以下文章。
## 使用 Python 与 Excel (PLAXIS 输入) 交互
自动化的逐步指南
[towardsdatascience.com
- 首先,我们需要从 “OHE Ground Profile” 工作表中提取值,并将其存储为 “df_soil”。
#Soil
df_soil = pd.read_excel(file, sheet_name = soilsheet,engine="openpyxl")
- 然后,我们需要使用 “BH1” 列中的 X 坐标(即 df_soil.iloc[0,1])创建第一个钻孔。需要注意的是,数据框的第一行被跳过了。
g_i.borehole(df_soil.iloc[0,1]) # Create borehole at x coord
- 使用 PLAXIS 命令 ‘g_i.soillayer(0)’ 创建第一个土壤层
g_i.soillayer(0) # Create first layer in 1st borehole
- 之后,我们将使用如下命令设置第一个钻孔的顶部 y 坐标。
Soillayers[0]:表示第一个土壤层。Soillayers[1] 将表示第二层,依此类推。
Zones[0]:表示第一个钻孔。
Top.set:允许我们指定土壤单元的顶部 y 坐标。只需为第一层设置一次,因为随后的层将自动从前一层获取底部 y 坐标作为其顶部 y 坐标(类似于 PLAXIS 的做法)。
g_i.Soillayers[0].Zones[0].Top.set(df_soil.iloc[1,1]) # Set top y coord of 1st Bh
- 接下来,我们将遍历其他钻孔列(即 BH2–4),以 (1) 创建具有给定 x 坐标的钻孔,并 (2) 设置顶部 y 坐标。
for j in range(len(df_soil.columns)-2):
g_i.borehole(df_soil.iloc[0,j+2]) # X coord for other Bhs
g_i.Soillayers[0].Zones[j+1].Top.set(df_soil.iloc[1,j+2]) #Top y coord for other Bhs
-
一旦我们设置了每个钻孔的顶部坐标,我们就遍历其余的层,并为第一个钻孔设置相应的底部坐标。
-
这里我们需要检查循环是否到达最后一层。如果没有到达最后一层,需要使用命令 ‘g_i.soillayer(1)’ 创建新层,否则,不创建新层。
for i in range(df_soil.count()[0]-2): # Loop through the number of layers
if i == df_soil.count()[0]-3: # Don't create new layer if we are at last unit
g_i.Soillayers[i].Zones[0].Bottom.set(df_soil.iloc[i+2,1]) #Set bottom y coord for 1st Bh
else:
g_i.soillayer(1) #Create new layer if we aren't at last unit
g_i.Soillayers[i].Zones[0].Bottom.set(df_soil.iloc[i+2,1])
- 最后,我们遍历其余的钻孔(除第一个钻孔外),并使用以下代码设置底部坐标。
for j in range(len(df_soil.columns)-2):
for i in range(df_soil.count()[0]-2):
g_i.Soillayers[i].Zones[j+1].Bottom.set(df_soil.iloc[i+2,j+2]) #Set bottom y coord for other Bhs
最终脚本应如下所示:
df_soil = pd.read_excel(file, sheet_name = soilsheet,engine="openpyxl")
g_i.borehole(df_soil.iloc[0,1])
g_i.soillayer(0)
g_i.Soillayers[0].Zones[0].Top.set(df_soil.iloc[1,1])
for j in range(len(df_soil.columns)-2):
g_i.borehole(df_soil.iloc[0,j+2])
g_i.Soillayers[0].Zones[j+1].Top.set(df_soil.iloc[1,j+2])
for i in range(df_soil.count()[0]-2):
if i == df_soil.count()[0]-3:
g_i.Soillayers[i].Zones[0].Bottom.set(df_soil.iloc[i+2,1])
else:
g_i.soillayer(1)
g_i.Soillayers[i].Zones[0].Bottom.set(df_soil.iloc[i+2,1])
for j in range(len(df_soil.columns)-2):
for i in range(df_soil.count()[0]-2):
g_i.Soillayers[i].Zones[j+1].Bottom.set(df_soil.iloc[i+2,j+2])
作者提供的 VS Code 截图
创建土壤剖面后,我们可以为每个土壤单元定义土壤属性。
第 3 步:用 Excel 输入土壤属性并分配材料
在第 3 步中,我们旨在从 “土壤属性” 工作表中提取土壤属性,并将这些属性分配给前一节中在 A 列指定的层对象(即 “SZ”,“SSIV”等)。
输入表中涉及的信息如下所示。
作者提供的 Excel 截图
- 与之前的步骤类似,我们首先从 “土壤属性” 中读取值并将其存储为 dataframe。
soilmatsheet="Soil properties"
df_soilmat = pd.read_excel(file, sheet_name = soilmatsheet,engine="openpyxl")
-
“Excel 输入界面” 下提到的典型土壤属性为 Mohr-Coulomb 模型所用。我们将遍历表中的行,使用 iloc 查找每个值并将其分配给相应的变量。
-
要在 PLAXIS 中使用 Python 访问土壤模型,应使用在 PLAXIS 中显示的数字值。例如,“线性弹性”模型用 1 表示,“莫尔-库仑”模型用 2 表示,以此类推。
for i in range(df_soilmat.count()[0]):
name = df_soilmat.iloc[i,1]
if df_soilmat.iloc[i,2] == 'MC':
materialmodel=2
gammaUnsat=df_soilmat.iloc[i,3]
gammaSat=df_soilmat.iloc[i,3]
Eref=df_soilmat.iloc[i,4]
nu=df_soilmat.iloc[i,5]
cref=df_soilmat.iloc[i,6]
phi= df_soilmat.iloc[i,7]
TensileStrength=df_soilmat.iloc[i,8]
- 然后,如果 Excel 模板中提供了值,我们将分配一个界面强度比。为此,我们需要将 “手动” 作为字符串分配给 ‘InterfaceStrength’,并将给定的比率分配给 ‘Rinter’。如果没有提供值,“刚性” 将自动分配给 ‘InterfaceStrength’。
if df_soilmat.iloc[i,9] >0:
InterfaceStrength = 'Manual'
Rinter=df_soilmat.iloc[i,9]
else:
InterfaceStrength = 'Rigid'
-
土壤属性的最后一个组件是 K0 条件。我们需要决定 K0 确定是 “手动” 还是 “自动”。在 PLAXIS-Python 环境中,“自动”和“手动”分别用 0 和 1 表示。
-
我们还需要根据用户输入检查 K0x 是否等于 K0y。Python 中 “K0x = K0y” 的选中和未选中框分别用 True 和 False 表示。
if df_soilmat.iloc[i,10] =='F':
K0Determination=1
if df_soilmat.iloc[i,11] == 'T':
K0PrimaryIsK0Secondary=True
K0Primary=df_soilmat.iloc[i,12]
K0Secondary=df_soilmat.iloc[i,12]
else:
K0PrimaryIsK0Secondary=False
K0Primary=df_soilmat.iloc[i,12]
K0Secondary=df_soilmat.iloc[i,13]
else:
K0Determination=0
- 然后,使用 PLAXIS 命令 ‘setproperties()’ 设置材料属性,类似于第 4 个教程。
material1 = g_i.soilmat()
material1.setproperties(
"Identification",name,
"SoilModel",materialmodel,
"gammaUnsat", gammaUnsat,
"gammaSat", gammaSat,
"Eref",Eref,
"nu", nu,
"cref", cref,
"phi", phi,
"TensileStrength",TensileStrength,
"InterfaceStrengthDetermination",InterfaceStrength,
"Rinter",Rinter,
"K0Determination",K0Determination,
"K0PrimaryIsK0Secondary",K0PrimaryIsK0Secondary,
"K0Primary",K0Primary,
"K0Secondary",K0Secondary
)
- 之后,我们将土壤材料存储为 ‘soilmat’ 对象。
soilmat=[mat for mat in g_i.Materials[:] if mat.TypeName.value == 'SoilMat']
- 最后一步是根据 “OHE 地面剖面” 工作表中的 “名称” 列(即 A 列)将材料设置到土壤层。我们需要使用 if 检查表中提供的材料名称。如果材料名称与现有材料对象匹配,则使用 ‘Soils.setmaterial()’ 将该材料分配给土壤层。
for j in range(df_soil.count()[0]-2):
for i in range(len(soilmat)):
if df_soil.iloc[j+2,0] == soilmat[i].Name:
g_i.Soils[j].setmaterial(soilmat[i])
最终脚本如下所示:
soilmatsheet="Soil properties"
df_soilmat = pd.read_excel(file, sheet_name = soilmatsheet,engine="openpyxl")
for i in range(df_soilmat.count()[0]):
name = df_soilmat.iloc[i,1]
if df_soilmat.iloc[i,2] == 'MC':
materialmodel=2
gammaUnsat=df_soilmat.iloc[i,3]
gammaSat=df_soilmat.iloc[i,3]
Eref=df_soilmat.iloc[i,4]
nu=df_soilmat.iloc[i,5]
cref=df_soilmat.iloc[i,6]
phi= df_soilmat.iloc[i,7]
TensileStrength=df_soilmat.iloc[i,8]
if df_soilmat.iloc[i,9] >0:
InterfaceStrength = 'Manual'
Rinter=df_soilmat.iloc[i,9]
else:
InterfaceStrength = 'Rigid'
if df_soilmat.iloc[i,10] =='F':
K0Determination=1
if df_soilmat.iloc[i,11] == 'T':
K0PrimaryIsK0Secondary=True
K0Primary=df_soilmat.iloc[i,12]
K0Secondary=df_soilmat.iloc[i,12]
else:
K0PrimaryIsK0Secondary=False
K0Primary=df_soilmat.iloc[i,12]
K0Secondary=df_soilmat.iloc[i,13]
else:
K0Determination=0
material1 = g_i.soilmat()
material1.setproperties(
"Identification",name,
"SoilModel",materialmodel,
"gammaUnsat", gammaUnsat,
"gammaSat", gammaSat,
"Eref",Eref,
"nu", nu,
"cref", cref,
"phi", phi,
"TensileStrength",TensileStrength,
"InterfaceStrengthDetermination",InterfaceStrength,
"Rinter",Rinter,
"K0Determination",K0Determination,
"K0PrimaryIsK0Secondary",K0PrimaryIsK0Secondary,
"K0Primary",K0Primary,
"K0Secondary",K0Secondary
)
soilmat=[mat for mat in g_i.Materials[:] if mat.TypeName.value == 'SoilMat']
for j in range(df_soil.count()[0]-2):
for i in range(len(soilmat)):
if df_soil.iloc[j+2,0] == soilmat[i].Name:
g_i.Soils[j].setmaterial(soilmat[i])
作者在 VS Code 中的截图
使用以下脚本运行。
(PLAXIS) C:\Users\phtsang\Desktop\PLAXIS_V22\Python_automation>python soil_geometry.py
你应该会看到在 PLAXIS 2D 中创建了如下内容。从土壤窗口中可以看到,所有钻孔都是根据 Excel 输入创建的。
作者在 PLAXIS 中的截图
作者在 PLAXIS 中的截图
在土壤属性方面,所有土壤单元都已根据我们在输入表中指定的属性创建。
太棒了!我们刚刚通过 PLAXIS 2D 的用户界面创建了一个带有材料定义的土壤剖面。
结论
这就是第五个关于使用 Python 创建土壤剖面(包括材料定义)的教程。完成此教程后,你应该能够从 Excel 获取土壤输入并创建剖面,同时在 PLAXIS 中分配土壤属性。
如果你喜欢阅读这种内容,可以随时关注我的页面。我会继续发布关于使用 Python 自动化 PLAXIS 的系列教程。除此之外,我还热衷于分享如何使用 Python 自动化工程工作流程的知识。
[## 通过我的推荐链接加入 Medium - Philip Tsang
阅读 Philip Tsang 的每一个故事(以及 Medium 上其他成千上万的作者)。你的会员费用直接支持…
medium.com](https://medium.com/@philip.studio11/membership?source=post_page-----a9c870e253c7--------------------------------)
为 TrailForks 构建推荐系统
我是如何赢得 Outside 2022 创新日奖的
·
关注 发表在 Towards Data Science · 11 分钟阅读 · 2023 年 1 月 4 日
–
摄影:Kristin Snippe 摄于 Unsplash
长背景故事
当我在 2021 年 5 月首次加入 Outside Inc 时,我的工作是构建一个个性化推荐系统,以驱动 Outside Feed。Outside Feed 包含各种户外和活跃生活方式的内容,以文章、视频、Outside 电影和播客等混合媒介格式呈现。我们的目标是激励人们走出户外。
快进到 17 个月后,我们对 Outside Feed 的推荐系统进行了两次重大改造:
-
从逆时间顺序的动态信息流到由混合推荐器驱动的动态信息流,其中包括三种经典的推荐系统功能:协同过滤、基于内容的过滤和热门与趋势。模型训练和推理是基于批次的。使用 80–20 法则,这种系统可以让你在提供个性化推荐时,借助相对较少的努力达到 80%。这里的“低”努力主要是指算法复杂性方面。
-
从基于批次的混合推荐系统到实时推荐系统。我们开始集成 Miso.ai —— 一个第三方工具,用来充分利用 MetaRouter 收集的点击流事件数据,并生成实时推荐。由于我们使用 GraphQL,关于在 Apollo 中封装 REST API 以及在 DataDog 中进行监控,我们学到了很多经验,这些经验值得另写一篇文章。
在这篇文章中,我记录了我参加 Outside Innovation Day 的经验,在一天内使用 Trailforks 数据构建了一个原型小径推荐系统。(PS:是的,Trailforks 现在是 Outside 家族的一部分!)也许因为我的很多同事都是热衷的徒步旅行者、山地自行车爱好者和户外运动爱好者,他们亲身体验了有一个小径推荐系统来帮助发现探索小径的乐趣和放松。他们都友好地为我的项目投了票,我赢得了 Outside Innovation Day 最能体现我们使命的奖项!(非常幸运和开心 😁 ~~~)。
以下是我将分享的三件事:
🏔️ 1. 如何以 20% 的努力构建 80% 个性化的推荐系统(使用 Trailforks 数据)?
🏔️ 2. 为准备黑客马拉松类型的项目考虑的事项
🏔️ 3. 生产化推荐系统的进一步考虑
🏔️ 1. Trail Recommender 的 80–20 法则
有很多高级推荐系统,但协同过滤 + 基于内容的推荐系统的混合真的是个性化推荐系统的最佳选择。它可以以约 20% 的工程努力覆盖 80% 的内容。
协同过滤涉及分析其他具有相似口味的用户的偏好,并基于这些用户的偏好推荐小径。
由 Wen Yang 创建的图像
另一方面,基于内容的过滤涉及分析小径本身的特征,并推荐与用户偏好相似的小径。
Wen Yang 制作的图像
构建一个 80–20 小径推荐系统涉及三个步骤。
步骤 1:首先,你需要收集数据。
有两种数据对于构建推荐系统特别有用。
- 交互数据集:包括
userid
、trailid
和activitytype
。前两个最为重要,而第三个特征activitytype
在我的项目中未使用。
预览 Trailforks 互动数据集
- 目录数据集: 包括选定小径的信息,如小径的位置、长度、难度等级、全球排名分数等。
步骤 2: 使用 Altair 进行数据可视化
从互动数据集中,只有 10 个用户,其中一个用户(userid = 454369)探索了 2524 条独特的小径。(PS:他的名字是 Trevor,他是 TrailForks 的创始工程师——这完全合理!)
import altair as alt
bars = alt.Chart(df_raw).mark_bar().encode(
x='n_trails_interacted:Q',
y='userid:O')
text = bars.mark_text(
align='left',
baseline='middle',
dx=3 # Nudges text to right so it doesn't appear on top of the bar
).encode(
text='n_trails_interacted:Q'
)
(bars + text).properties(height=400)
以下代码可以为每个变量名生成一个柱状图:
def plot_variable(variable):
source = df_trail_sub
bar = alt.Chart(source).mark_bar().encode(
x='count():Q',
y=alt.Y(f'{variable}:N', sort='-x')
)
return bar
比如,我们来看看小径方向、物理评分难度标题和国家名称:
起初,我尝试使用 global_rank_score
作为“流行度”特征,但有太多记录缺少这个特征。幸运的是,rating
与 global_rank_score
存在正相关。下面是我最喜欢的可视化类型之一,使用 altair
可以很容易地完成。
def plot_points_bar():
brush = alt.selection(type='interval')
points = alt.Chart(df_trail_sub).mark_point().encode(
x='global_rank_score',
y='rating',
color=alt.condition(brush, 'difficulty_title', alt.value('lightgray'))
).add_selection(
brush
)
bars = alt.Chart(df_trail_sub).mark_bar().encode(
y='difficulty_title',
color='difficulty_title',
x='count(difficulty_title)'
).transform_filter(
brush
)
return points & bars
同样,我们可以使用 difficulty_title
替代 physical_rating
来表示难度等级,因为前者的缺失值比后者少。
-
中等 → 蓝色,黑钻
-
困难 → 黑钻,蓝色
-
极端 → 双黑钻
-
简单 → 绿色
alt.Chart(dt).mark_circle().encode(
x='difficulty_title:O',
y='physical_rating:O',
size= alt.Size('pct:Q',scale=alt.Scale(range=[10, 2000])),
color= 'physical_rating'
).properties(width=400, height=300)
步骤 3a: 使用 **implicit**
构建协同过滤模型
对于协同过滤,你所需要的唯一数据集是互动数据集。这里我计算了三个其他特征:
-
n_times_interacted
: 该用户与此小径互动了多少次 -
n_trails_interacted
: 该用户探索了多少条小径 -
n_users_interacted
: 多少独特用户探索了这条小径
思路是使用 n_times_interacted
作为隐式反馈特征:用户探索某条小径的次数越多,就越能确定用户喜欢这条小径。
模型训练:
import implicit
import scipy.sparse as sparse
n_user = df_raw.userid.nunique()
n_item = df_raw.trailid.nunique()
# Prepare ALS training data
sparse_user_item = sparse.csr_matrix((df_raw['n_times_interacted'].astype(float),
(df_raw['userid'], df_raw['trailid'])))
# initialize a model: set random_state for reproducibility!
model = implicit.als.AlternatingLeastSquares(factors=40,
regularization=0.1,
iterations=15,
random_state=10)
# train the model on a sparse matrix of user/item/confidence weights
model.fit(sparse_user_item)
推理的有用函数:
import requests
from ipywidgets import Image
def get_recommendations(userid, n):
rec, relevance = model.recommend(userid, sparse_user_item[userid],
N=n,
filter_already_liked_items=True
)
df_rec = df_trail.loc[df_trail['trailid'].isin(rec)]
return df_rec
def show_photo(df_rec):
photo_url = df_rec['cover_photo_url'].iloc[0]
image = Image(value=requests.get(photo_url).content)
return image
def show_map(df_rec):
map_url = df_rec['static_map_url'].iloc[0]
image = Image(value=requests.get(map_url).content)
return image
🌄 DEMO: 使用协同过滤为 Trevor 推荐小径
我使用了 pandas-profiling
来了解 Trevor 对小径的偏好,以下是观察结果:
-
physical_rating: 中等,困难
-
difficulty_title: 蓝色,黑钻
-
trailtype: 单轨
-
方向:仅限下坡
-
最多被探索的小径:Fitzsimmons Connector(319 次!)
让我们从协同过滤模型中获取推荐 →
以及第一个推荐小径的照片和地图:
步骤 3b: 基于内容的过滤
由于协同过滤基于交互数据集,这意味着它在冷启动用户场景下不起作用。如果新用户没有探索任何路线,我们将无法找到与该新用户相似的用户。这就是为什么我们需要基于内容的过滤来填补这个空白。
基于内容的过滤完全是关于寻找“相似的路线”,因此我们需要决定两件事:
-
在什么特征上相似
-
如何测量相似性
第一个问题是特征工程问题。以下特征被选为路线特征:
-
数值特征:
stat_climb
、stat_descent
、stat_distance
、rating
-
文本特征:
title
、difficulty_title
、trailtype
、direction
和country_title
对于第二个问题,我们使用余弦相似度,这是在推荐系统实践中常用的度量方法。
💻 特征工程和相似度计算的代码示例
from sklearn.feature_extraction.text import TfidfVectorizer,CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import pairwise_distances
def model_tfidf_num(trail_data):
# scale numerical data (continuous) : global_rank_score missing values
trail_data_numerical = trail_data[['stat_climb', 'stat_descent',
'stat_distance','rating']]
scaler_num = StandardScaler().fit(trail_data_numerical)
df_num_scaled = scaler_num.transform(trail_data_numerical)
# vectorize text data
tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(trail_data['comb_text_clean']).toarray()
# concatenate and get similarity
all_features = np.concatenate([tfidf_matrix,df_num_scaled],axis=1)
#cosine_sim = cosine_similarity(all_features, all_features)
return all_features
def get_similar(idx, n):
#idx: target item's index
# 1\. compute distance
target_feature = all_features[idx]
couple_dist = pairwise_distances(all_features,
target_feature, metric='cosine')
# 2\. get similar dataframe: no need to filter out the first
# because the first won't be the unseen url
indices = list(
map(lambda x: x.item(), np.argsort(couple_dist.ravel())))
# similar_score
cosine_similarity = 1 - couple_dist[indices].ravel()
df_sim_all = pd.DataFrame(
{"tfidf_index": indices, "similar_score": cosine_similarity})
df_sim = df_sim_all[1:n+1]
df_out = df_sim.merge(df_mapper, on='tfidf_index')
return df_out
🌄 DEMO:使用基于内容的过滤为 Trevor 推荐路线
让我们找到与 Trevor 的第二条最常探索的路线(‘A-Line — Lower’)相似的路线
哇!基于内容的推荐器非常令人印象深刻,它推荐了大多数中等到困难难度的山地车路线和下坡方向的路线。我的同事 Trevor 是一个资深的山地车骑行者,他对这样的推荐非常满意!
好吧,现在艰苦的工作已经完成。我想分享一些反思。
🏔️ 2. 准备黑客马拉松类型项目时需要考虑的事项
从参与黑客马拉松类型的项目中我学到的最大教训是“少即是多”。尽管很诱人去构建一个包括数据科学组件、后端数据库组件以及前端或只是用于更好演示的 streamlit 组件的推荐系统,但黑客马拉松通常时间有限,你需要意识到哪些部分你愿意稍微牺牲一点,以便留出时间做其他事情。对我来说,这些部分是:
-
EDA 和数据可视化:探索性数据分析就像一个黑洞,它可以把你吸进去,并且没有明显的结束点。数据可视化也是如此,你可能会花费无尽的时间来美化一个图表,但这只会将你的项目从 80 提升到 82,这与 80-20 规则相反。我的解决方案是
pandas-profiling
和altair
。在进行任何数据分析之前,我会使用pandas-profiling
来检查缺失值、总体分布和相关性,这将帮助我缩小可以保留的数据范围并为特征工程做准备。altair
使用起来非常简单,通过几行紧凑的代码,你可以轻松制作出漂亮的条形图和散点+分布图。我有一个小技巧,就是提前在笔记本中复制几个代码片段,只使用已经在笔记本中的那些代码,以避免任何进一步的诱惑。 -
演示是关于讲故事的。你可以熬夜制作最华丽和最复杂的黑客松项目,但你需要留出足够的时间来准备如何讲述这个故事。一个故事可以从 Why(你为什么要做这个项目的动机和背景)、What(发生了什么,什么令人惊讶,什么有效)、一点点 How(保持在高层次,方程式和代码本身很令人印象深刻,但它会给那些只有 5 分钟时间理解你的项目的人带来不必要的威慑和疲惫)开始。例如,这篇文章绝对过长,细节过多,不符合演示标准。最后,加入一些戏剧性和幽默感总是一个优势。
🏔️ 3. 将推荐系统生产化的进一步考虑
最后,如果你已经看到这里,并且真的想将原型推荐系统投入生产,我有三点幽灵知识*想与大家分享:
幽灵知识 (来源) :
知识存在于某个认知社区中,也许某个社区的核心成员很容易获得,但实际上并没有被写下来,也不清楚如何获取它。
-
批量 vs 实时推荐系统:批量系统听起来简单直接,但它有其自身的复杂性。如果你使用
implicit
进行协同过滤并使用cosine similarity
检索类似内容,很可能延迟无法满足你的前端 API 要求。你可以尝试使用像 FAISS 这样的索引来提高速度,但它可能仍然高于 500ms。这就是为什么你可能考虑对每个用户和每个项目进行批量计算。根据用户和项目的规模,批量推荐可能需要超过 4 小时,并且直到你重新训练模型才会刷新。一个快速提示是,仅使用过去 90 天内有任何互动的用户,或仅对过去 90 天内发布的项目进行批量更新。 -
“Cold-Start” 可能并不像你想象的那么严重:我记得当我第一次学习推荐系统时,书籍和讲座总是强调“cold-start”问题,好像这是最难解决的场景。然而,对于 Outside Feed 来说,这个问题甚至不在前 3 名。原因是很多人习惯于在 Outside Feed 上阅读最新的内容,因此通过发布日期对内容进行排序,以便展示新项(冷项)是相当容易的,从而能够保证新项获得页面浏览(互动)数据。对我们来说,更难的问题实际上是“如何推荐永恒的内容”?如果我们推荐一篇最初于 2016 年撰写的全时热门文章,即使这篇内容可能符合用户的口味,用户可能会觉得我们没有新鲜的内容可以推荐。
-
第三个知识点是我从另一位推荐系统从业者(Andy - 来自miso.ai的 CTO)那里听到的趣闻:他曾经为一个服装网站构建了一个主页推荐系统。尽管学习排名的系统了解到“黑色”可能是最受欢迎的颜色,因此推荐了首页上全是黑色的不同风格的服装。好吧,模型并没有错,但乍一看效果很糟糕,并且在吸引人们购买衣物方面表现不佳。他开玩笑说,他研究推荐系统已经很多年了,但没有人提到好的颜色搭配设计可能会有很大的影响。
这就是我在 2022 年的最后一篇中等帖子。非常感谢你的支持。如果你有任何想法或反思,或者更好——更多的推荐系统趣闻,请发送给我!我很乐意将它们编入我梦想写的迷人书籍《构建推荐系统的乐趣与悲哀》中 < The Pleasure and Sorrow of Building RecSys> 🐳~~~
在一个小时内构建你的第一个深度学习应用
使用 HuggingFace Spaces 和 Gradio 部署图像分类模型
·
关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 7 月 21 日
–
Thought Catalog 提供的照片,来自 Unsplash
我从事数据分析工作已经将近十年了。时不时地,我会使用机器学习技术从数据中获取见解,而且我对使用经典的机器学习方法感到很自如。
尽管我通过了一些关于神经网络和深度学习的 MOOC 课程,但我从未在工作中使用过这些技术,这个领域对我来说似乎非常具有挑战性。我有所有这些偏见:
-
你需要学习很多东西才能开始使用深度学习:数学、不同的框架(我至少听说过三种:
PyTorch
、TensorFlow
和Keras
)以及网络架构。 -
训练模型需要大量的数据集。
-
没有强大的计算机(它们还必须有 Nvidia GPU),就不可能获得令人满意的结果,因此获取这样的设备相当困难。
-
要使一个机器学习驱动的服务运行起来,需要处理大量的样板工作:你需要处理前端和后端的部分。
我相信分析的主要目标是帮助产品团队根据数据做出正确的决策。如今,神经网络可以显著提升我们的分析能力,例如,自然语言处理可以从文本中获得更多的见解。因此,我决定再尝试一下利用深度学习的力量对我来说会有帮助。
这就是我开始Fast.AI 课程的方式(它在 2022 年初进行了更新,所以我猜内容自之前的 TDS 评论以来可能有所变化)。我意识到,使用深度学习解决任务并不是那么困难。
这个课程采用自上而下的方法。所以你从构建一个工作系统开始,之后才会深入了解所有必要的基础知识和细节。
我在第二周制作了我的第一个机器学习驱动应用程序(你可以在 这里 尝试一下)。这是一个图像分类模型,可以识别我最喜欢的狗品种。令人惊讶的是,即使我的数据集中只有几千张图片,它的表现也很好。这让我感到振奋,我们现在可以如此轻松地构建一个十年前还完全是魔法的服务。
照片由Shakti Rajpurohit拍摄,发布在Unsplash
所以在这篇文章中,你将找到一个关于构建和部署第一个机器学习驱动服务的初学者级教程。
什么是深度学习?
深度学习是机器学习的一个特定应用场景,其中我们使用多层神经网络作为模型。
神经网络非常强大。根据通用逼近定理,神经网络可以逼近任何函数,这意味着它们能够解决任何任务。
目前,你可以将这个模型视为一个黑箱,它接受输入(在我们的例子中——一张狗的图片)并返回输出(在我们的例子中——一个标签)。
作者拍摄的照片
构建模型
你可以在Kaggle上找到这个阶段的完整代码。
我们将使用 Kaggle Notebooks 来构建我们的深度学习模型。如果您还没有 Kaggle 账户,值得注册。Kaggle 是一个流行的数据科学平台,您可以在这里找到数据集、参与竞赛以及运行和分享代码。
您可以在 Kaggle 创建一个 Notebook,并像在本地 Jupyter Notebook 一样执行代码。Kaggle 甚至提供 GPU,所以我们可以很快训练神经网络模型。
图片来源:作者
让我们先导入所有包,因为我们将使用许多 Fast.AI 工具。
from fastcore.all import *
from fastai.vision.all import *
from fastai.vision.widgets import *
from fastdownload import download_url
加载数据
不用说,我们需要一个数据集来训练我们的模型。获取一组图像的最简单方法是使用搜索引擎。
DuckDuckGo搜索引擎有一个易于使用的 API 和方便的 Python 包duckduckgo_search
(更多信息),所以我们将使用它。
让我们尝试搜索一张狗的图片。我们已指定license_image = any
,只使用具有创作共用许可的图片。
from duckduckgo_search import DDGS
import itertools
with DDGS() as ddgs:
res = list(itertools.islice(ddgs.images('photo samoyed happy',
license_image = 'any'), 1))
在输出中,我们获得了关于图片的所有信息:名称、URL 和大小。
{
"title": "Happy Samoyed dog photo and wallpaper. Beautiful Happy Samoyed dog picture",
"image": "http://www.dogwallpapers.net/wallpapers/happy-samoyed-dog-wallpaper.jpg",
"thumbnail": "https://tse2.mm.bing.net/th?id=OIP.BqTE8dYqO-W9qcCXdGcF6QHaFL&pid=Api",
"url": "http://www.dogwallpapers.net/samoyed-dog/happy-samoyed-dog-wallpaper.html",
"height": 834, "width": 1193, "source": "Bing"
}
现在我们可以使用 Fast.AI 工具来下载图片并显示缩略图。
图片由 Barcs Tamás 提供,来源于 Unsplash
我们看到一只快乐的萨摩耶,这意味着它在正常工作。所以让我们加载更多照片。
我旨在识别五种不同的犬种(我最喜欢的那些)。我将为每种犬种加载图片,并将它们存储在不同的目录中。
breeds = ['siberian husky', 'corgi', 'pomeranian', 'retriever', 'samoyed']
path = Path('dogs_breeds') # defining path
for b in tqdm.tqdm(breeds):
dest = (path/b)
dest.mkdir(exist_ok=True, parents=True)
download_images(dest, urls=search_images(f'photo {b}'))
sleep(10)
download_images(dest, urls=search_images(f'photo {b} puppy'))
sleep(10)
download_images(dest, urls=search_images(f'photo {b} sleep'))
sleep(10)
resize_images(path/b, max_size=400, dest=path/b)
运行此代码后,您将看到 Kaggle 右侧面板上的所有加载的照片。
图片来源:作者
下一步是将数据转换为适用于 Fast.AI 模型的格式——DataBlock
。
对于这个对象,您需要指定几个参数,但我只会强调最重要的几个:
-
splitter=RandomSplitter(valid_pct=0.2, seed=18)
:Fast.AI 要求选择一个验证集。验证集是用来估计模型质量的保留数据。为了防止过拟合,训练时不会使用验证数据。在我们的例子中,验证集是数据集的 20%的随机部分。我们指定了seed
参数,以便下次能够精确地重复相同的划分。 -
item_tfms=[Resize(256, method=’squish’)]
:神经网络以批量处理图像。这就是为什么我们必须将图片调整为相同的大小。目前我们使用了squish
方法,但我们会在后面更详细地讨论它。
我们已经定义了一个数据块。函数show_batch
可以向我们展示一组随机的带标签的图像。
照片由 Angel Luciano 提供,来源于 Unsplash | 照片由 Brigitta Botrágyi 提供,来源于 Unsplash | 照片由 Charlotte Freeman 提供,来源于 Unsplash
数据看起来没问题,所以我们继续训练。
训练模型
你可能会感到惊讶,但下面这两行代码将完成所有工作。
我们使用了一个预训练模型(18 层深度的卷积神经网络 — Resnet18
)。这就是我们称之为 fine_tune
的原因。
我们训练了模型三轮,这意味着模型看到了整个数据集 3 次。
我们还指定了度量标准 — accuracy
(正确标记图片的比例)。你可以在每一轮后的结果中看到这个度量标准(它仅使用验证集计算,以免影响结果)。然而,它没有用于优化过程,仅供参考。
整个过程花了大约 30 分钟,现在我们的模型可以以 94.45% 的准确率预测狗的品种。干得好!但我们能否改善这个结果?
改进模型:数据清理和增强
如果你希望尽快看到你的第一个模型运行,可以先跳过这部分,继续进行模型部署。
首先,让我们看看模型的错误:它是否无法区分柯基和哈士奇或博美和拉布拉多。我们可以使用 confusion_matrix
来实现。请注意,混淆矩阵也是仅使用验证集计算的。
Fast.AI 课程中分享的另一个小窍门是可以使用模型来清理我们的数据。为此,我们可以查看损失最大的图像:这些可能是模型自信度高但错误的情况,或是正确但信心低的情况。
照片由 Benjamin Vang 提供,来源于 Unsplash | 照片由 Xennie Moore 提供,来源于 Unsplash | 照片由 Alvan Nee 提供,来源于 Unsplash
显然,第一张图片的标签不正确,而第二张图片包含了哈士奇和柯基。因此还有改进的空间。
幸运的是,Fast.AI 提供了一个方便的 ImageClassifierCleaner
小部件,它可以帮助我们快速解决数据问题。你可以在你的笔记本中初始化它,然后你将能够更改数据集中的标签。
cleaner = ImageClassifierCleaner(learn)
cleaner
在每个类别之后,你可以运行以下代码来解决问题:删除图片或将其移动到正确的文件夹。
for idx in cleaner.delete(): cleaner.fns[idx].unlink()
for idx,breed in cleaner.change(): shutil.move(str(cleaner.fns[idx]), path/breed)
现在我们可以再次训练我们的模型,并看到准确率提高了:95.4% 对比 94.5%。
正确识别的柯基犬比例已从 88% 提高到 96%。太棒了!
改善我们模型的另一种方法是改变我们的缩放方法。我们使用了压缩方法,但正如你所看到的,它可能会改变自然物体的比例。让我们尝试更具创意地使用增强。
增强是对图片的更改(例如,对比度改进、旋转或裁剪)。这将为我们的模型提供更多变的数据显示,并希望提高其质量。
与 Fast.AI 一样,你只需更改几个参数即可添加增强。
此外,由于在每个周期中模型会看到略有不同的图片,我们可以增加周期数。经过六个周期,我们达到了 95.65% 的准确率——结果稍微好一点。整个过程大约花了一个小时。
下载模型
最后一步是下载我们的模型。这非常简单。
learn.export('cuttest_dogs_model.pkl')
然后你将有一个标准的 pickle
文件(常见的 Python 对象存储格式)。只需在 Kaggle Notebook 右侧面板中选择文件旁的 更多操作
,你将可以将模型下载到你的计算机上。
现在我们有了训练好的模型,让我们部署它,这样你就可以将结果分享给全世界。
部署你的模型
我们将使用 HuggingFace Spaces 和 Gradio 来构建我们的网页应用。
设置 HuggingFace Space
HuggingFace 是一家提供实用机器学习工具的公司,例如流行的变换器库或分享模型和数据集的工具。今天我们将使用他们的 Spaces 来托管我们的应用。
首先,如果你还没有注册,你需要创建一个账户。这只需几分钟。请点击这个 链接。
现在是创建新空间的时候了。前往 Spaces 选项卡并点击“创建”按钮。你可以在 文档中找到更多详细说明。
然后你需要指定以下参数:
-
name(它将用于你的应用 URL,所以请谨慎选择),
-
license(我选择了开源 Apache 2.0 许可证)
-
SDK(在这个示例中我将使用 Gradio)。
然后用户友好的 HuggingFace 将向你展示说明。TL;DR 现在你有了一个 Git 仓库,你需要将你的代码提交到那里。
关于 Git 有一个细节。由于你的模型可能相当大,最好设置 Git LFS(大型文件存储),这样 Git 就不会跟踪这个文件的所有更改。有关安装,请参考网站上的说明。
-- cloning repo
git clone https://huggingface.co/spaces/<your_login>/<your_app_name>
cd <your_app_name>
-- setting up git-lfs
git lfs install
git lfs track "*.pkl"
git add .gitattributes
git commit -m "update gitattributes to use lfs for pkl files"
Gradio
Gradio 是一个框架,它允许你只使用 Python 构建愉快且友好的 Web 应用。这就是它成为原型设计的宝贵工具的原因(尤其是对于像我这样没有深厚 JavaScript 知识的人)。
在 Gradio 中,我们将定义我们的接口,指定以下参数:
-
输入 — 一张图像,
-
输出 — 带有五个可能类别的标签,
-
标题、描述 和 一组示例 图像(我们也需要将它们提交到仓库中),
-
enable_queue=True
可以帮助应用程序处理大量流量,特别是当它变得非常受欢迎时, -
函数 用于处理输入图像。
要为输入图像获取标签,我们需要定义一个预测函数,该函数加载我们的模型并返回一个包含每个类别概率的字典。
最后,我们将得到 app.py
的以下代码
import gradio as gr
from fastai.vision.all import *
learn = load_learner('cuttest_dogs_model.pkl')
labels = learn.dls.vocab # list of model classes
def predict(img):
img = PILImage.create(img)
pred,pred_idx,probs = learn.predict(img)
return {labels[i]: float(probs[i]) for i in range(len(labels))}
gr.Interface(
fn=predict,
inputs=gr.inputs.Image(shape=(512, 512)),
outputs=gr.outputs.Label(num_top_classes=5),
title="The Cuttest Dogs Classifier 🐶🐕🦮🐕🦺",
description="Classifier trainded on images of huskies, retrievers, pomeranians, corgis and samoyeds. Created as a demo for Deep Learning app using HuggingFace Spaces & Gradio.",
examples=['husky.jpg', 'retriever.jpg', 'corgi.jpg', 'pomeranian.jpg', 'samoyed.jpg'],
enable_queue=True).launch()
如果你想了解更多关于 Gradio 的信息,请阅读文档。
让我们还创建一个requirements.txt
文件,其中包含fastai
,这样这个库就会在我们的服务器上安装。
所以剩下的唯一步骤就是将所有内容推送到 HuggingFace Git 仓库。
git add *
git commit -am 'First version of Cuttest Dogs app'
git push
你可以在GitHub上找到完整的代码。
在推送文件后,返回到 HuggingFace Space,你将看到类似的图片显示构建过程。如果一切正常,你的应用将在几分钟内运行。
如果出现任何问题,你将看到一个堆栈跟踪。然后你需要返回到你的代码中,修复错误,推送新版本,并再等几分钟。
它正在运行
现在我们可以使用这个模型处理真实照片,例如验证我家狗是否确实是柯基犬。
作者提供的照片
今天我们已经完成了构建深度学习应用程序的整个过程:从获取数据集和拟合模型到编写和部署 Web 应用。希望你已经完成了这个教程,现在你正在生产环境中测试你精彩的模型。
非常感谢你阅读这篇文章。希望它对你有启发。如果你有任何后续问题或评论,请在评论区留言。此外,也不要犹豫,分享你应用程序的链接。
使用 Streamlit 创建你自己的类似 ChatGPT 的应用
利用 OpenAI 的 API 绕过官方 ChatGPT 应用
·
关注 发布于 Towards Data Science ·6 分钟阅读·2023 年 4 月 3 日
–
作者提供的图像 — 使用 Stable Diffusion 创建
这是什么?
当 GPT-4 在 2023 年 3 月 14 日宣布时,我立即注册了 ChatGPT Plus——这是 ChatGPT 应用程序中的一个付费层级,可以立即访问新模型。它每月花费 20 美元,最初非常值得。然而,几天后,我的使用量减少了——别误解我:我仍然经常使用,只是我不确定是否会使用到足以证明其成本的程度。然后,几天前,我通过 OpenAI 的 API 获得了 GPT-4 的访问权限,尽管新模型比其前身 GPT-3.5 贵得多,但我仍然认为通过 API 互动可能对我来说更经济。
但我确实希望在与模型互动时保持类似聊天的体验。虽然已经有相当多的开源应用提供流畅的用户体验,但我不想使用 React 或类似的前端框架——它们非常适合构建出色的网页应用,但这不是我喜欢做的事情。相反,我决定用 Streamlit 构建自己的聊天界面,它提供了一个更基本的用户体验,并且功能远不如其他框架丰富——但对我来说,从零开始开发自己的 UI(而且是用 Python)要有趣得多。😃
作者图片
在本教程中,我将带你了解这个应用程序——所有代码也可以在这个GitHub 仓库中找到。
为什么这很重要?
通过实践学习
除了我已经提到的成本方面,还有一些额外的优势在于构建自己的聊天界面。首先,它迫使我更深入地研究 Chat API,因为到目前为止,我只使用过文本生成 API。使用 Chat API 类似,但有一些关键区别需要注意。
独立性
其次,这使我完全独立于 ChatGPT 应用程序。无论应用程序是否出现了重大故障或者应用程序限制了我可以向模型发送的推理请求数量(目前每 3 小时限制 25 条消息),这些都不适用于我运行自己的应用程序时。
数据隐私
第三,数据隐私。默认情况下,ChatGPT 会收集数据并用于改进服务(尽管可以选择退出)。然而,在使用 API 时,默认情况下不会收集数据,除非我们特别选择加入。更多信息请参见 OpenAI 的API 使用文档。
有趣多了!
最后,如前所述,构建这样的东西要有趣得多(至少对像我这样的极客来说🤓)。我已经在应用中加入了一些功能,例如显示令牌数量和每次对话的价格。也许在某个时候,我可以扩展应用以利用其他模型(例如来自 Hugging Face)🤗。
让我们开始行动吧!💪
构建应用程序
先决条件
为了开发这个应用程序,我们需要确保已安装openai、streamlit和streamlit-chat包:
pip install openai streamlit streamlit-chat
跟踪对话历史
聊天完成指南提到,我们需要将对话历史传递给 API,以便模型理解背景;换句话说,我们必须管理聊天模型的记忆,因为 API 不会为我们处理这一点。为此,我们创建了一个会话状态列表,在会话开始时存储系统消息,然后附加与模型的互动。
if 'messages' not in st.session_state:
st.session_state['messages'] = [
{"role": "system", "content": "You are a helpful assistant."}
]
def generate_response(prompt):
st.session_state['messages'].append({"role": "user", "content": prompt})
completion = openai.ChatCompletion.create(
model=model,
messages=st.session_state['messages']
)
response = completion.choices[0].message.content
st.session_state['messages'].append({"role": "assistant", "content": response})
显示对话
为了展示对话,我们利用了message函数,这个函数来自streamlit-chat包。我们遍历存储的互动,并按时间顺序展示对话,从最早的对话开始(就像在 ChatGPT 中一样)。
from streamlit_chat import message
if st.session_state['generated']:
with response_container:
for i in range(len(st.session_state['generated'])):
message(st.session_state["past"][i], is_user=True, key=str(i) + '_user')
message(st.session_state["generated"][i], key=str(i))
打印附加信息
我认为一个额外有用的功能是打印每次互动的一些元数据。为此,我们可以,例如,打印使用的模型(这可能会在不同互动之间变化)、这次互动使用了多少令牌及其成本(根据OpenAI 的定价页面)。
total_tokens = completion.usage.total_tokens
prompt_tokens = completion.usage.prompt_tokens
completion_tokens = completion.usage.completion_tokens
if model_name == "GPT-3.5":
cost = total_tokens * 0.002 / 1000
else:
cost = (prompt_tokens * 0.03 + completion_tokens * 0.06) / 1000
st.write(
f"Model used: {st.session_state['model_name'][i]}; Number of tokens: {st.session_state['total_tokens'][i]}; Cost: ${st.session_state['cost'][i]:.5f}")
图片由作者提供
请注意,随着对话的延长,令牌的数量(从而价格)会增加。这是因为我们需要提交所有先前的问题和回答,以便模型理解互动的背景。
为了节省开支,因此建议在开始新的聊天话题时清除对话记录。
侧边栏
在侧边栏中,我们提供了切换模型和清除对话历史的选项。此外,我们还可以显示当前对话的累计费用:
图片由作者提供
结论
通过这些步骤,我们成功地开发了一个易于使用且可定制的聊天界面,使我们能够与基于 GPT 的模型互动,而无需依赖像 ChatGPT 这样的应用程序。我们现在可以使用以下命令运行应用程序:
streamlit run app.py
这如何改变了我的工作流程
我现在实际上已经取消了 ChatGPT Plus 的订阅,并且我专门使用我的应用程序与 GPT 模型进行互动。默认情况下,我使用 GPT-3.5 模型,这使得使用这些模型非常实惠。只有在处理更复杂的任务时,或者当我对 GPT-3.5 的结果不完全满意时,我才会切换到 GPT-4。很可能,我会继续随着时间推移向应用程序中添加新功能,因为这是我最喜欢做的事情——敬请期待未来的更新😊
进一步改进的想法
希望这对你有所帮助——请使用本教程作为起点来构建你自己的聊天 UI。我很想了解你正在构建的内容,所以请在评论中与我联系。这里有一些关于如何改进这个应用的想法,供你参考:
编程愉快!
海科·霍茨
👋 关注我在Medium和LinkedIn上的内容,了解更多关于生成式 AI、机器学习和自然语言处理的信息。
👥 如果你在伦敦,可以加入我们的NLP London Meetups。
从零开始使用 Pytorch 构建自己的 Transformer
在 Pytorch 中逐步构建一个 Transformer 模型
·
关注 发表在Towards Data Science ·7 分钟阅读·2023 年 4 月 26 日
–
在本教程中,我们将使用 PyTorch 从头开始构建一个基本的 Transformer 模型。Transformer 模型由 Vaswani 等人提出,在论文“Attention is All You Need”中介绍,是一种针对序列到序列任务(如机器翻译和文本摘要)设计的深度学习架构。它基于自注意力机制,已成为许多最先进自然语言处理模型的基础,如 GPT 和 BERT。
要详细了解 Transformer 模型,请访问这两篇文章:
1. 关于“注意力”和“Transformer”的一切——深入理解——第一部分
2. 关于“注意力”和“Transformer”的一切——深入理解——第二部分
要构建我们的 Transformer 模型,我们将遵循以下步骤:
-
导入必要的库和模块
-
定义基本构建模块:多头注意力、位置逐层前馈网络、位置编码
-
构建编码器和解码器层
-
结合编码器和解码器层以创建完整的 Transformer 模型
-
准备示例数据
-
训练模型
让我们首先导入必要的库和模块。
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy
现在,我们将定义 Transformer 模型的基本构建块。
多头注意力
图 2. 多头注意力(来源:作者创建的图像)
多头注意力机制计算序列中每对位置之间的注意力。它由多个“注意力头”组成,这些头捕捉输入序列的不同方面。
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super(MultiHeadAttention, self).__init__()
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, -1e9)
attn_probs = torch.softmax(attn_scores, dim=-1)
output = torch.matmul(attn_probs, V)
return output
def split_heads(self, x):
batch_size, seq_length, d_model = x.size()
return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)
def combine_heads(self, x):
batch_size, _, seq_length, d_k = x.size()
return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)
def forward(self, Q, K, V, mask=None):
Q = self.split_heads(self.W_q(Q))
K = self.split_heads(self.W_k(K))
V = self.split_heads(self.W_v(V))
attn_output = self.scaled_dot_product_attention(Q, K, V, mask)
output = self.W_o(self.combine_heads(attn_output))
return output
MultiHeadAttention 代码用输入参数和线性变换层初始化模块。它计算注意力得分,将输入张量重塑为多个头,并结合所有头的注意力输出。前向方法计算多头自注意力,使模型能够关注输入序列的不同方面。
位置逐层前馈网络
class PositionWiseFeedForward(nn.Module):
def __init__(self, d_model, d_ff):
super(PositionWiseFeedForward, self).__init__()
self.fc1 = nn.Linear(d_model, d_ff)
self.fc2 = nn.Linear(d_ff, d_model)
self.relu = nn.ReLU()
def forward(self, x):
return self.fc2(self.relu(self.fc1(x)))
PositionWiseFeedForward 类扩展了 PyTorch 的 nn.Module,并实现了位置逐层前馈网络。该类初始化时包含两个线性变换层和一个 ReLU 激活函数。前向方法依次应用这些变换和激活函数以计算输出。这个过程使模型在做出预测时能够考虑输入元素的位置。
位置编码
位置编码用于注入输入序列中每个标记的位置信息。它使用不同频率的正弦和余弦函数来生成位置编码。
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_seq_length):
super(PositionalEncoding, self).__init__()
pe = torch.zeros(max_seq_length, d_model)
position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]
PositionalEncoding 类初始化时包含输入参数 d_model 和 max_seq_length,创建一个张量来存储位置编码值。该类根据缩放因子 div_term 计算偶数和奇数索引的正弦和余弦值。forward 方法通过将存储的位置信息编码值添加到输入张量中来计算位置编码,从而使模型能够捕捉输入序列的位置信息。
现在,我们将构建编码器和解码器层。
编码器层
图 3. Transformer 网络的编码器部分(来源:原始论文中的图像)
编码器层由一个多头注意力层、一个逐位置前馈层和两个层归一化层组成。
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout):
super(EncoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask):
attn_output = self.self_attn(x, x, x, mask)
x = self.norm1(x + self.dropout(attn_output))
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout(ff_output))
return x
EncoderLayer 类初始化时包含输入参数和组件,包括一个多头注意力模块、一个逐位置前馈模块、两个层归一化模块和一个丢弃层。forward 方法通过应用自注意力、将注意力输出添加到输入张量并归一化结果来计算编码器层输出。然后,它计算逐位置前馈输出,将其与归一化的自注意力输出结合,并在返回处理后的张量之前归一化最终结果。
解码器层
图 4. Transformer 网络的解码器部分(来源:原始论文中的图像)
解码器层由两个多头注意力层、一个逐位置前馈层和三个层归一化层组成。
class DecoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout):
super(DecoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.cross_attn = MultiHeadAttention(d_model, num_heads)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.norm3 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, enc_output, src_mask, tgt_mask):
attn_output = self.self_attn(x, x, x, tgt_mask)
x = self.norm1(x + self.dropout(attn_output))
attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)
x = self.norm2(x + self.dropout(attn_output))
ff_output = self.feed_forward(x)
x = self.norm3(x + self.dropout(ff_output))
return x
解码器层初始化时包含输入参数和组件,如用于掩蔽自注意力和交叉注意力的多头注意力模块、一个逐位置前馈模块、三个层归一化模块和一个丢弃层。
forward 方法通过执行以下步骤计算解码器层输出:
-
计算掩蔽自注意力输出并将其添加到输入张量中,然后进行丢弃和层归一化。
-
计算解码器和编码器输出之间的交叉注意力输出,并将其添加到归一化的掩蔽自注意力输出中,然后进行丢弃和层归一化。
-
计算逐位置前馈输出并将其与归一化的交叉注意力输出结合,然后进行丢弃和层归一化。
-
返回处理后的张量。
这些操作使解码器能够根据输入和编码器输出生成目标序列。
现在,让我们将编码器和解码器层结合起来,创建完整的 Transformer 模型。
Transformer 模型
图 5. Transformer 网络(来源:原始论文中的图像)
将所有内容合并在一起:
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):
super(Transformer, self).__init__()
self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)
self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model, max_seq_length)
self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])
self.fc = nn.Linear(d_model, tgt_vocab_size)
self.dropout = nn.Dropout(dropout)
def generate_mask(self, src, tgt):
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
seq_length = tgt.size(1)
nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
tgt_mask = tgt_mask & nopeak_mask
return src_mask, tgt_mask
def forward(self, src, tgt):
src_mask, tgt_mask = self.generate_mask(src, tgt)
src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))
tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))
enc_output = src_embedded
for enc_layer in self.encoder_layers:
enc_output = enc_layer(enc_output, src_mask)
dec_output = tgt_embedded
for dec_layer in self.decoder_layers:
dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
output = self.fc(dec_output)
return output
Transformer 类结合之前定义的模块,创建一个完整的 Transformer 模型。在初始化期间,Transformer 模块设置输入参数并初始化各种组件,包括源序列和目标序列的嵌入层、位置编码模块、EncoderLayer 和 DecoderLayer 模块以创建堆叠层、用于投影解码器输出的线性层以及 dropout 层。
generate_mask
方法创建源序列和目标序列的二进制掩码,以忽略填充标记,并防止解码器关注未来的标记。forward
方法通过以下步骤计算 Transformer 模型的输出:
-
使用
generate_mask
方法生成源序列和目标序列的掩码。 -
计算源序列和目标序列的嵌入,并应用位置编码和 dropout。
-
通过编码器层处理源序列,更新
enc_output
张量。 -
通过解码器层处理目标序列,使用
enc_output
和掩码,并更新dec_output
张量。 -
将线性投影层应用于解码器输出,获取输出 logits。
这些步骤使 Transformer 模型能够处理输入序列,并基于其组件的组合功能生成输出序列。
准备示例数据
在这个例子中,我们将创建一个玩具数据集用于演示。实际操作中,你会使用更大的数据集,预处理文本,并为源语言和目标语言创建词汇映射。
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1
transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)
# Generate random sample data
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
训练模型
现在我们将使用示例数据训练模型。在实际操作中,你会使用更大的数据集,并将其拆分为训练集和验证集。
criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)
transformer.train()
for epoch in range(100):
optimizer.zero_grad()
output = transformer(src_data, tgt_data[:, :-1])
loss = criterion(output.contiguous().view(-1, tgt_vocab_size), tgt_data[:, 1:].contiguous().view(-1))
loss.backward()
optimizer.step()
print(f"Epoch: {epoch+1}, Loss: {loss.item()}")
我们可以通过这种方式从零开始在 Pytorch 中构建一个简单的 Transformer。所有的大型语言模型都使用这些 Transformer 编码器或解码器模块进行训练。因此,了解最初的网络极为重要。希望这篇文章能帮助所有希望深入了解 LLM 的人。
参考文献
《Attention is all you need》
A. Vaswani、N. Shazeer、N. Parmar、J. Uszkoreit、L. Jones、A. Gomez、{. Kaiser 和 I. Polosukhin。《神经信息处理系统的进展》, 第 5998–6008 页. (2017)
在 Python 中构建基础机器学习模型
关于如何选择合适问题和如何开发基础分类器的详细论文
·
点击查看 发布于 Towards Data Science ·20 min 阅读·2023 年 1 月 2 日
–
照片由 charlesdeluvio 提供,来源于 Unsplash
目前,我们都见过各种基础机器学习(ML)模型的结果。互联网充斥着展示计算机如何识别各种动物的图像、视频和文章,无论识别是否正确。
尽管我们已经朝着更复杂的机器学习模型迈进,例如生成或提升图像的模型,但这些基础模型仍然构成了这些努力的基础。掌握基础知识可以成为未来更大事业的跳板。
所以,我决定自己重新审视基础知识,并构建一个具有几个警告的基本机器学习模型——它必须具有一定的实用性,尽可能简单,并返回合理准确的结果。
然而,与互联网上的许多其他教程不同,我想从头到尾展示我的整个思考过程。因此,编码部分将会稍晚开始,因为理论和实践领域中的问题选择同样重要。最后,我相信理解为什么比如何更为重要。
选择适合机器学习的问题
尽管机器学习可以解决许多挑战,但它并不是一种万能的解决方案。即使我们暂时忽略财务、时间和其他资源成本,机器学习模型在某些方面仍然表现出色,而在其他方面则表现糟糕。
分类是机器学习可能发挥作用的一个很好的例子。每当我们处理真实世界的数据(即我们不处理代码中创建的类别)时,找出定义现象的所有可能规则几乎是不可能的。
正如我之前所写的,如果我们尝试使用基于规则的方法来分类一个物体是否是猫,我们会很快遇到问题。似乎没有定义任何物理对象的特征——有些猫没有尾巴、毛发、耳朵、一只眼睛、不同数量的腿等等,但它们仍然都属于同一类别。
列举所有可能的规则及其例外可能是不可能的,也许甚至没有某种永恒的清单,我们只能在过程中逐步制定。机器学习在某种程度上通过消耗大量数据来进行预测,模仿了我们的思维。
换句话说,我们应该在尝试确定哪种模型最合适、需要多少数据以及开始任务后关注的其他事项之前,仔细考虑我们要解决的问题。
寻求实际应用
制作区分狗和猫的模型确实有趣且有趣,但即使我们将操作规模扩大到巨大的程度,也不太可能获得任何好处。此外,已经有数以百万计的此类模型教程在网上创建。
我决定选择词汇分类,因为它相对较少被写到,并且具有一定的实际应用。我们的 SEO 团队提出了一个有趣的提议——他们需要根据三种类型来分类关键词:
-
信息型 — 寻找关于某个主题的知识的用户(例如,“什么是代理”)
-
交易型 — 寻找产品或服务的用户(例如,“最佳代理”)
-
导航型——用户寻找特定品牌或其分支(例如,“Oxylabs”)
手动分类成千上万的关键词有点麻烦。这样的任务(几乎)完美适合机器学习,尽管存在一个几乎无法解决的固有问题,我将在后面详细说明。
最终,它使数据收集和管理变得比其他情况下要简单得多。SEO 专家使用各种工具来跟踪关键词,其中大多数可以将它们导出到 CSV 表中。只需将类别分配给关键词即可。
构建一个预 MVP
在建立模型之前决定需要多少数据点几乎是不可能的。虽然有一些依赖于既定目标(即,更多或更少的类别),但精确计算这些数据几乎是不可能的。选择一个足够大的数字(例如,1000 条记录)是一个好的起点。
我建议不要一开始就处理整个数据集。由于这是你第一次开发模型,很多事情可能会出错。一般来说,最好先编写代码并在小样本(例如总数据的 10%)上运行,以确保没有语义错误或其他问题。
一旦你得到所需的结果,就开始处理整个数据集。虽然你可能不会完全放弃项目,但你不希望花费几个小时(枯燥)的工作却没有任何成果。
无论如何,有了一些样本,我们可以正式开始开发过程。我选择了 Python,因为它是一种相当常见的语言,并且通过众多库为机器学习提供了不错的支持。
库
-
Pandas。虽然不是绝对必要,但读取和导出 CSV 文件将大大简化我们的工作。
-
SciKit-Learn。这是一个相当强大且灵活的机器学习库,它将成为我们分类模型的基础。在整个教程中,我们将使用各种 sklearn 功能。
-
NLTK(自然语言工具包)。由于我们将处理自然语言,NLTK 完美地完成了这个任务。停用词 是包中绝对必要的内容。
导入
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, chi2
from nltk.corpus import stopwords
第 1 行
相当自解释。Pandas 允许我们通过创建数据框来读取和写入 CSV 以及其他电子表格文件。由于我们将处理关键词,大多数 SEO 工具会将它们导出为 CSV,这将减少我们需要手动处理的数据。
第 2 行
从 SciKit-Learn 库中,我们将挑选几个东西,TfidfVectorizer 是我们的首选。
向量化器将我们的字符串转换为特征向量,这会导致两个重要的变化。首先,字符串被转换为数值表示。每个唯一的字符串被转换为一个索引,然后转化为向量(矩阵的衍生物)。
句子 #1:“狗是棕色的。”
句子 #2:“狗是黑色的。”
向量化将处理这两个句子并创建一个索引:
E(w) =
[0, if "the"
1, if "dog"
2, if "is"
3, if "brown"
4, if "black"]
除了将字符串转换为数值外,向量化还优化了数据处理。与其多次处理相同的字符串,不如使用相同的索引,类似于文件压缩。
最后,TFIDF(词频-逆文档频率)是衡量文档中词语重要性的一种方法。简单来说,它对每个词进行处理,评估其频率与文档长度的比值,并分配一个加权值。因此,重复出现的词语被认为更重要。
第三行
LogisticRegression 是发现变量之间关系的一种方法。由于我们的任务是经典的分类问题,逻辑回归非常适合,因为它接受某些输入变量 x(关键字),并将其分配一个值 y(信息性/交易性/导航性)。
还有其他选项,例如 LinearSVC,它涉及到更复杂的数学运算。极其简单地说,SVC 会对多个数据点簇进行处理,找到每个簇中最接近对方簇的值。这些值称为支持向量。
一个超平面(即在 n+1 维 空间中的 n 维 几何对象)被绘制成使其与每个支持向量的距离最大化。
作者提供的图片
已有研究表明使用支持向量机 可能在文本分类中产生更好的结果,但这可能是由于任务的复杂性显著增加。这些优势在我们的情况下并不完全相关,因为它们在特征数量达到极高水平时才会显现,因此线性回归应该也能很好地工作。
第四行
Pipeline 是一个灵活的机器学习工具,它让你创建一个对象,将整个过程的多个步骤组合成一个。它有许多好处——从帮助你编写更整洁的代码到防止数据泄漏。
第五行
虽然在我们的情况下并不是绝对必要的,SelectKBest 和 chi2 通过提高准确性和减少训练时间来优化模型。SelectKBest 允许我们设置最大特征数量。
Chi2(或卡方检验)是一种用于变量独立性的统计测试,有助于我们选择最佳特征(因此,SelectKBest)进行训练:
作者提供的图片。
where: c is degrees of freedom (i.e. sample size minus one)
O is the observed value(s)
E is the expected value(s)
期望值是通过接受原假设(变量独立)来计算的。这些值然后与我们的观测值进行对比。如果观测值与期望值有显著偏差,我们可以拒绝原假设,这迫使我们接受变量之间的依赖关系。
如果变量是相关的,它们对于机器学习模型是可接受的,因为这正是我们所寻找的 —— 对象之间的关系。反过来,SelectKBest 获取所有 chi2 结果,并选择那些具有最强关系的结果。
在我们的情况下,由于特征数量相对较少,SelectKBest 可能无法带来我们感兴趣的优化,但一旦数量开始增加,它就变得至关重要。
行 6
我们最终的导入来自 NLTK,我们将仅将其用于 stopwords 列表。不幸的是,默认列表不适合我们当前的任务。大多数这样的列表包含像“how”,“what”,“why”等词,这些词在常规分类中无用,但能指示搜索意图。
事实上,可以说这些词比“如何构建网页抓取器”这样的关键词中的任何剩余词更重要。由于我们对句子的类别感兴趣而非其他值,stopwords 是决定它可能是什么的最佳途径。
因此,删除一些停用词列表中的条目是至关重要的。幸运的是,NLTK 的停用词只是文本文件,你可以使用任何文字处理器进行编辑。
NLTK 下载默认存储在用户目录中,但可以通过使用 download_dir= 进行更改(如果需要的话)。
数据框和停用词
所有机器学习模型都从数据准备和处理开始。由于我们处理的是 SEO 关键词,这些关键词可以通过流行的性能测量工具轻松导出为 CSV。
选择一个随机样本,其中应包括接近相等数量的各类别,这一点是值得注意的。由于我们正在制作一个前期 MVP,这不应该成为问题,因为如果模型提供了我们需要的结果,可以随时添加数据。
在继续之前,明智的做法是从 CSV 文件中选择几打关键词并进行标注。一旦我们得到一个有效的模型,就可以标注其余的。由于 Pandas 以表格格式创建数据框,最简单的方法是添加一个新列,“Category” 或 “Label”,并将每个关键词行标记为 Informational, Transactional, or Navigational。
df = pd.read_csv('[KEYWORD_LIST].csv')
data = pd.DataFrame(df)
words = stopwords.words('english_adjusted')
行 1 和 2
每当我们有任何形式的 CSV 时,Pandas 要求我们创建一个数据框。首先,我们将读取由 SEO 工具提供的关键词列表。请记住,CSV 文件应该已经包含一些关键词分类,否则将没有东西可以用于训练模型。
阅读文件后,我们从 CSV 文件创建一个数据框对象。
行 3
我们将使用 NLTK 获取停用词文件,不过我们不能直接使用它。NLTK 的默认列表包含许多我们认为对关键词分类至关重要的词(例如,“what”,“how”,“where”等)。因此,它必须调整以适应我们的目的。
虽然在这种情况下没有硬性规定,但不定冠词和定冠词可以保留(例如,“a”,“an”,“the”等),因为它们不提供信息。然而,所有可能显示用户意图的内容都必须从默认文件中删除。
我创建了一个名为‘english_adjusted’的副本,以便于操作。此外,以防万一我需要原始版本,它将始终可用,无需重新下载。
最后,你可能需要运行一次 NLTK,使用常规参数‘english’来下载文件,这可以在任何阶段完成。否则,你会收到错误。
设置管道
在所有这些准备步骤之后,我们终于可以进入实际的机器学习部分。这些是模型中最重要的部分。你可能会花费相当多的时间调整这些参数,以找到最佳选项。
不幸的是,没有很多指导方针适用于所有情况。需要进行一些实验和推理,以减少所需的测试量,但完全消除测试是不可能的。
pipeline = Pipeline([('vect', TfidfVectorizer(ngram_range=(1, 3), stop_words=words)),
('chi', SelectKBest(chi2, k='all')),
('clf', LogisticRegression(C=1.0, penalty='l2', max_iter=1000, dual=False))])
有些人可能会注意到我没有通过scikit-learn将数据集拆分为训练集和测试集。这是问题的性质所赋予的奢侈。SEO 工具可以在不到一分钟的时间内导出数千个(未标记的)关键字,这意味着你可以单独采购测试集而不费吹灰之力。
因此,出于优化原因,我将使用没有标签的第二个数据集作为我们的测试基础。然而,由于train_test_split 非常普遍,我将在文章末尾的附录中展示一个使用它的相同模型版本。
第 1 行
管道允许我们将长时间的过程简化为一个对象,使处理模型设置变得更加容易。它还将减少出错的可能性。
我们将从定义我们的向量化器开始。我在上面提到过我们将使用TFIDFVectorizer,因为它根据文档中单词的权重来产生更好的结果。CountVectorizer 是一个选项,但你需要导入它,结果可能会有所不同。
Ngram_range 是一个有趣的推理挑战。为了获得最佳结果,你必须决定要计算多少个词元(在我们的情况下是单词)。Ngram_range 为 (1, 1) 会计算单个词(单词),(1, 2) 会计算单个词和两个相邻的词(双词组)的组合,(1, 3) 会计算单个词、两个词和三个词(三词组)的组合。
我选择了ngram_range(1, 3),有几个原因。首先,由于模型相对简单,性能不是问题,我可以运行更大范围的 n-gram,因此下限可以设置为最小。
另一方面,一旦我们去除停用词,我们应该考虑什么样的 ngram 上限足以从关键词中提取意义。如果可能,我发现从数据集中选择最难和最简单的例子更容易。在我们的情况下,最简单的例子是任何问题(“如何获取代理”),最难的是名词(“网络爬虫”)或名称(“Oxylabs”)。
由于我们将移除像“to”这样的词,我们会在问题案例中得到三元组(“how get proxies”),这是完全清晰的。事实上,你可以认为二元组(“how get”)也足够,因为意图仍然清晰。
然而,最难的例子通常会比三元组短,因为理解搜索意图的难易程度与查询长度相关。因此,ngram_range (1, 3) 应该在性能和准确性之间取得一个不错的平衡。
最后,对于 sublinear_tf 有一个论点,即它是常规 TF-IDF 计算的一个修改。如果设置为 True,权重通过对数函数计算:1 + log(tf)。换句话说,词频会获得递减的回报。
使用 sublinear_tf 时,频繁出现且出现在多个文档中的词语不会被赋予过重的权重。由于我们有一组相对随机的关键词,我们无法知道哪些会得到优待,但这些通常是像“how”,“what”等我们希望被赋予较重权重的词。
在测试过程中,我发现模型在没有 sublinear_tf 的情况下表现更好,但我建议稍微调整一下,看看是否会带来任何好处。
Stopwords 参数现在已经不言自明,因为我们之前已经讨论过了。
第 2 行
虽然不严格来说是新的一行,但我将为清晰和简洁的目的将其分开。我们现在将调用 SelectKBest,我在上面已经对其进行了相当详细的描述。我们的关注点是 k 值。
这些会有所不同,具体取决于你的数据集的大小。SelectKBest 旨在优化性能和准确性。在我的情况下,使用“all”是有效的,但你通常需要选择一个足够大的 N 来匹配你自己的数据集。
第 3 行
最后,我们来到将用于模型的方法。LogisticRegression 是我们的选择,如前所述,但需要对参数进行大量的调整。
“C”值是一个超参数,它告诉模型应该选择哪些参数。超参数是模型中非常复杂的部分,对最终结果有着巨大的影响。
从极其简单的角度来看,C 值是你训练数据的信任分数。较高的 C 值意味着在拟合时,对训练数据的权重会较高,而对惩罚的权重较低。较低的 C 值则将更多强调惩罚,训练数据的权重较低。
应始终存在一定的惩罚,因为训练永远无法完全代表现实世界的值(因为它只是一个小的子集,无论你收集多少)。此外,如果存在异常值而不进行惩罚,模型将会越来越贴近过拟合。
penalty 参数是用于超参数的操作。SciKit-Learn 提供了三种类型的惩罚——‘l1’、‘l2’和‘elasticnet’。‘None’也是一个选项,但如果使用的话应该尽量少。
‘L1’ 是所有系数的绝对值之和。简单来说,它将所有系数拉向某个中心点。如果施加了大的惩罚,一些数据点可能会变成零(即被消除)。
在存在多重共线性(多个变量相关)或需要简化模型的情况下,应该使用‘L1’。由于L1会消除一些数据点,因此模型几乎总是变得更简单。然而,当数据点的分布已经相对简单时,它的效果不如预期。
‘L2’ 是类似过程的不同版本。它不是绝对和,而是所有系数值的平方和。因此,所有系数都按相同的值缩小,但没有被消除。‘L2’ 是默认设置,因为它最灵活且很少引发问题。
‘Elasticnet’ 是上述两种方法的结合。关于是否应该将‘elasticnet’作为默认方法,已有相当广泛的评论,然而,并不是所有的求解器都支持它。在我们的情况下,我们需要切换到“saga”求解器,它是为大型数据集设计的。
在教程级别的机器学习模型中使用‘elasticnet’可能收益甚微。只需记住,将来它可能会有益。
继续讨论*‘max_iter’*,该参数将设置模型在收敛之前执行的最大迭代次数。简单来说,收敛是指进一步迭代不太可能发生的点,作为停止点。
较高的值会增加计算复杂性,但可能会导致更好的整体表现。在数据集相对简单的情况下,‘max_iter’ 可以设置为数千及以上,因为这对系统的负担不会太大。
如果值过低且收敛失败,将显示警告消息。因此,找到最低可能的值并从中开始并不困难。
拟合模型并输出数据
我们接近教程的结束,最终进入模型拟合和接收输出的阶段。
model = pipeline.fit(data.Keyword, data.Type)
chi = model.named_steps['chi']
clf = model.named_steps['clf']
doutput = pd.read_csv('[TEST_KEYWORD_LIST].csv')
doutput['Type'] = model.predict(doutput['Keyword'])
doutput.to_csv('[RESULT_LIST].csv')
##print('Accuracy score ' + str(model.score(x_test, y_test)))
第 1-3 行
在第 1 行中,我们使用我们建立的管道将模型拟合到训练数据中。如果需要进行调试或额外的分析,管道允许我们创建命名的步骤,这些步骤可以在后续调用。
第 4–8 行
我们从一个只包含关键词的 CSV 文件中创建另一个数据框。我们将使用新创建的模型来预测每个关键词及其类别。
由于我们的数据框仅包含关键词,我们添加了一个新的列“类型”,并运行model.predict以提供输出结果。
最终,所有结果被移动到一个输出的 CSV 文件中,该文件将在本地目录中创建。通常,你会想设置一些目标,但为了测试目的,通常没有必要这样做。
有一行被注释掉的代码我想提一下,它调用了score函数。SciKit为我们提供了多种方法来估计模型的预测能力。这些方法不应被视为绝对真理,因为预测准确度与实际准确度通常可能有所偏差。
然而,得分作为经验法则和快速评估参数对模型的影响是有用的。虽然有很多评分方法,但基本的model.score使用R 平方,在调整参数时通常很有帮助。
结果分析
我的训练数据仅有 1300 条条目,包含三种不同的类别,如上所述。即使在这样的小数据集中,模型仍然达到了约 80%的不错准确度。
其中一些,如预期的那样,是有争议的,甚至 Google 也这么认为。例如,“网页抓取”是一个经常被搜索的关键词。是否查询是交易性的还是信息性的没有明确的指示。Google 的搜索结果页面显示,前 5 条结果中有产品和信息文章。
模型在一个领域遇到了困难——导航关键词。如果我要猜测,模型大约 5-10%的时间能正确预测类别。出现这种情况有几个原因。
数据集的分布可能是一个问题,因为它严重不平衡:
-
交易型 — 0.457353%
-
信息型 — 0.450735%
-
导航型 — 0.091912%
虽然实际世界场景会呈现出类似的分布(由于导航关键词的固有稀有性),但训练数据过于稀疏,无法进行适当的拟合。此外,导航关键词的频率非常低,以至于模型通过总是分配其他两个类别可以获得更高的准确性。
然而,我认为展示更多的导航关键词的训练数据不会产生更好的结果。这是一个通过文本分析解决的极其困难的问题,无论我们选择哪种方法。
导航关键词主要由品牌名称组成,这些名称是新造词或其他新产生的词。它们中没有任何内容遵循自然语言,因此,它们之间的联系只能事后发现。换句话说,我们必须首先从其他数据源知道这是一个品牌名称,才能正确分配类别。
如果我得猜测,谷歌和其他搜索引擎可能通过用户查询新词时的行为来发现品牌名称。他们可能会查找域名匹配或其他数据,但在没有人工互动的情况下预测某个词是导航关键词是极其困难的。
特征工程可能是解决问题的潜在方案。我们需要发现导航类别和其他类别之间的新联系,并通过其他方法实施分配。
由于特征工程是一个完全不同的主题,并且值得单独写一篇文章,所以我将提供一个示例。导航关键词很少会以问题的形式被查询(除了“什么是”),否则它们没有意义(例如,“如何使用 Oxylabs”,“如何获取 Oxylabs”)。
是否将“如何获取 Oxylabs 代理”视为交易型还是导航型存在争议。然而,它确实符合交易型类别,因此可以被认为是交易型。
通过知道相对较少的导航关键词会以问题的形式出现,我们可以构建一个模型来过滤掉大多数问题,留下较小的潜在目标子集。
此外,许多导航关键词的查询长度显著较短,通常由单个词组成,而其他类别的查询长度相对较少。
这些方法及其他许多方法可以组合使用,以提高选择导航关键词时模型的准确性。然而,深入特征工程要比基础教程覆盖的内容复杂得多。
目前,词汇分类应通过对机器学习模型如何工作的整体更好理解来覆盖。希望对众多参数和工具的解释能让你从一开始就创建一个功能性的模型。
结论
即使文章非常长,你可能已经注意到,编写机器学习模型并不那么困难。事实上,可以说,在这个案例中,这只是项目的一小部分。
机器学习在很大程度上依赖于准备,我们可以概述几个部分:
-
选择正确的问题。有些问题用其他方法解决可能更好。不要被炒作所迷惑,尝试通过机器学习解决所有问题。使用基于规则的系统,你可能能够节省时间和资源,同时产生更好的结果。
-
准备数据。一个模型的好坏取决于数据。如果你的数据标记不正确、缺乏真实性或其他方面存在问题,那么再多的开发和资源也无法创建出可靠的输出。
-
选择模型。由于你已经做过很多次,可能很容易选择逻辑回归或其他模型。Sci-Kit Learn还有其他选项,比如我没有提到的PassiveAggressiveClassifier,它们使用不同的数学方法。再次强调,选择正确的问题非常重要,因为它应该决定你选择什么建模方法。
我希望这篇文章能为许多机器学习新手提供帮助,不仅提供实践部分,还提供处理问题的思路。
附录:原始完整代码块
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, chi2
df = pd.read_csv('[KEYWORD_LIST].csv')
data = pd.DataFrame(df)
words = stopwords.words('english_adjusted')
pipeline = Pipeline([('vect', TfidfVectorizer(ngram_range=(1, 3), stop_words=words)),
('chi', SelectKBest(chi2, k='all')),
('clf', LogisticRegression(C=1.0, penalty='l2', max_iter=1000))])
model = pipeline.fit(data.Keyword, data.Type)
chi = model.named_steps['chi']
clf = model.named_steps['clf']
doutput = pd.read_csv('[TEST_KEYWORD_LIST].csv')
doutput['Type'] = model.predict(doutput['Keyword'])
doutput.to_csv('[RESULT_LIST].csv')
##print('accuracy score ' + str(model.score(x_test, y_test)))
附录 II:Train_test_split
导入
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectKBest, chi2
from nltk.corpus import stopwords
按照惯例,我们需要导入train_test_split本身(第 5 行)。
设置拆分
x_train, x_test, y_train, y_test = train_test_split(data.Keyword, data.Type, test_size=0.3)
pipeline = Pipeline([('vect', TfidfVectorizer(ngram_range=(1, 3), stop_words=words)),
('chi', SelectKBest(chi2, k='all')),
('clf', LogisticRegression(C=1.0, penalty='l2', max_iter=1000, dual=False))])
第 1 行
由于我们的数据集只有两个特征(keyword和category),我们需要为每个特征准备两个变量。其中一个用于存储训练数据,另一个用于测试目的。
我们将使用之前步骤中创建的数据框,并指定列名(在我的数据集中,它们被称为“Keyword”和“Type”,如参数所示)。
最后,SciKit-Learn通过允许对两个数据集进行自动分割来解决数据拆分问题。train_test_split接受表示测试集大小或训练集大小百分比的浮点和整数值。如果两个值都设置为None,默认值将为 0.25。
需要进行一些调整才能获得最佳结果。我尝试了许多不同的拆分,其中 0.3 产生了最佳结果。一般来说,你会发现许多模型在 0.2 到 0.3 范围内的拆分效果最佳。
特定的拆分对准确性的影响较小,当数据点数量增加时更是如此。实际上,在极大的数据集上,拆分为 0.1 可能会提高计算性能。
统计单位之间的关系很复杂,但是可以建立的连接的抽象领域是有限的,因此准确性可以理解为对一定数量的数据点的要求,而不是特定的比例。换句话说,有一个N,在这个点上结果不会再变得更好,因此如果数据集很大,较小的比例可能更为优化。
有一些关于这个主题的高度技术文章深入解释了这个想法,并提供了计算最佳拆分的方法。
此代码块中的其他部分遵循与原始教程相同的步骤。
拟合模型并输出数据(再次)
model = pipeline.fit(x_train, y_train)
chi = model.named_steps['chi']
clf = model.named_steps['clf']
doutput = pd.DataFrame({'Keyword': x_test, 'Type': model.predict(x_test)})
doutput.to_csv('[RESULT_LIST].csv')
##print('Accuracy score ' + str(model.score(x_test, y_test)))
第 1 行
我们不会直接在标记数据集上训练模型,而是在之前拆分的那个数据集上进行训练,命名为x_train和y_train。第 2 行和第 3 行保持不变。
第 4 行
由于没有单独的数据集,我们将使用初始数据集中的测试部分进行预测。因此,我们创建一个数据框,其中包含关键词这一列,我们将在该列中输出测试数据集中的所有关键词。在第二列类型中,我们将使用模型来预测关键词的类别,依然使用相同的数据集。
最终,按照原始版本,所有结果将输出到一个结果文件中。如果有人对模型的表现如何感兴趣,也可以选择打印准确率分数。