进行机器学习项目意味着我们需要进行实验。拥有一种轻松配置脚本的方法将帮助您更快地工作。在 Python 中,我们有一种方法可以从命令行调整代码。在本教程中,我们将了解如何利用 Python 脚本的命令行参数来帮助您更好地完成机器学习项目。
完成本教程后,你将学到:
- 为什么我们希望在命令行中控制 Python 脚本
- 如何高效地在命令行中工作
使用我的新书《Python 机器学习》启动您的项目,其中包括逐步教程和所有示例的Python 源代码文件。
让我们开始吧。
您的 Python 脚本的命令行参数。照片来自 insung yoon。保留部分权利
概述
本教程分为三个部分;它们是
- 在命令行中运行 Python 脚本
- 在命令行中工作
- 命令行参数的替代方案
在命令行中运行 Python 脚本
运行 Python 脚本有多种方式。有些人可能将其作为 Jupyter notebook 的一部分运行。有些人可能在 IDE 中运行。但在所有平台上,始终可以在命令行中运行 Python 脚本。在 Windows 中,您有命令提示符或 PowerShell(或者,更好的是,Windows Terminal)。在 macOS 或 Linux 中,您有 Terminal 或 xterm。在命令行中运行 Python 脚本功能强大,因为您可以向脚本传递额外的参数。
以下脚本允许我们从命令行将值传递到 Python
1 2 3 4 |
import sys n = int(sys.argv[1]) print(n+1) |
我们将这几行保存到一个文件中,并在命令行中带一个参数运行它
1 2 |
$ python commandline.py 15 16 |
然后,您将看到它获取我们的参数,将其转换为整数,加一,然后打印。列表 sys.argv
包含我们的脚本名称和所有参数(所有字符串),在上述情况下,它是 ["commandline.py", "15"]
。
当您使用更复杂的参数集运行命令行时,处理列表 sys.argv
需要一些努力。因此,Python 提供了 argparse
库来帮助。这假设是 GNU 风格,可以用以下示例解释
1 |
rsync -a -v --exclude="*.pyc" -B 1024 --ignore-existing 192.168.0.3:/tmp/ ./ |
可选参数由“-
”或“--
”引入,其中单个连字符表示单个字符的“短选项”(如上面的 -a
、-B
和 -v
),两个连字符表示多个字符的“长选项”(如上面的 --exclude
和 --ignore-existing
)。可选参数可能包含额外的参数,例如 -B 1024
或 --exclude="*.pyc"
;其中 1024
和 "*.pyc"
分别是 -B
和 --exclude
的参数。此外,我们可能还有必选参数,我们只需将其放入命令行。上面的 192.168.0.3:/tmp/
和 ./
部分就是示例。必选参数的顺序很重要。例如,上面的 rsync
命令将文件从 192.168.0.3:/tmp/
复制到 ./
,而不是反过来。
以下是使用 argparse 在 Python 中复制上述示例的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import argparse parser = argparse.ArgumentParser(description="只是一个例子", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("-a", "--archive", action="store_true", help="存档模式") parser.add_argument("-v", "--verbose", action="store_true", help="增加详细程度") parser.add_argument("-B", "--block-size", help="校验和块大小") parser.add_argument("--ignore-existing", action="store_true", help="跳过已存在的文件") parser.add_argument("--exclude", help="要排除的文件") parser.add_argument("src", help="源位置") parser.add_argument("dest", help="目标位置") args = parser.parse_args() config = vars(args) print(config) |
如果您运行上述脚本,您将看到
1 2 3 |
$ python argparse_example.py 用法: argparse_example.py [-h] [-a] [-v] [-B BLOCK_SIZE] [--ignore-existing] [--exclude EXCLUDE] src dest argparse_example.py: 错误: 需要以下参数: src, dest |
这意味着您没有为 src
和 dest
提供必需的参数。也许使用 argparse 的最佳原因是,如果您提供了 -h
或 --help
作为参数,就可以免费获得帮助屏幕,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ python argparse_example.py --help 用法: argparse_example.py [-h] [-a] [-v] [-B BLOCK_SIZE] [--ignore-existing] [--exclude EXCLUDE] src dest 只是一个例子 位置参数 src 源位置 dest 目标位置 可选参数 -h, --help 显示此帮助信息并退出 -a, --archive 存档模式 (默认值: False) -v, --verbose 增加详细程度 (默认值: False) -B BLOCK_SIZE, --block-size BLOCK_SIZE 校验和块大小 (默认值: None) --ignore-existing 跳过已存在的文件 (默认值: False) --exclude EXCLUDE 要排除的文件 (默认值: None) |
虽然脚本没有做任何实际操作,但如果您按照要求提供了参数,您将看到
1 2 |
$ python argparse_example.py -a --ignore-existing 192.168.0.1:/tmp/ /home {'archive': True, 'verbose': False, 'block_size': None, 'ignore_existing': True, 'exclude': None, 'src': '192.168.0.1:/tmp/', 'dest': '/home'} |
由 ArgumentParser()
创建的解析器对象具有 parse_args()
方法,该方法读取 sys.argv
并返回一个命名空间对象。这是一个带有属性的对象,我们可以例如使用 args.ignore_existing
来读取它们。但通常,如果它是一个 Python 字典,处理起来会更容易。因此,我们可以使用 vars(args)
将其转换为字典。
通常,对于所有可选参数,我们提供长选项,有时也提供短选项。然后,我们可以使用长选项作为键(如果不是长版本,则将连字符替换为下划线或单字符短选项作为键)来访问从命令行提供的值。“位置参数”不是可选的,它们的名称在 add_argument()
函数中提供。
有多种类型的参数。对于可选参数,有时我们将其用作布尔标志,但有时我们期望它们带来一些数据。在上面,我们使用 action="store_true"
使该选项默认为 False
,如果指定则切换为 True
。对于其他选项,例如上面的 -B
,默认情况下,它期望后面跟着额外的数据。
我们可以进一步要求参数为特定类型。例如,在上面的 -B
选项中,我们可以通过添加 type
如下所示使其期望整数数据
1 |
parser.add_argument("-B", "--block-size", type=int, help="校验和块大小") |
如果我们提供了错误的类型,argparse 将会终止我们的程序并显示一个有用的错误消息
1 2 3 |
python argparse_example.py -a -B hello --ignore-existing 192.168.0.1:/tmp/ /home 用法: argparse_example.py [-h] [-a] [-v] [-B BLOCK_SIZE] [--ignore-existing] [--exclude EXCLUDE] src dest argparse_example.py: 错误: 参数 -B/--block-size: 无效的整数值: 'hello' |
在命令行中工作
使用命令行参数增强您的 Python 脚本可以使其可重用性达到一个新的水平。首先,让我们看一个将 ARIMA 模型拟合到 GDP 时间序列的简单示例。世界银行收集了许多国家的历史 GDP 数据。我们可以使用 pandas_datareader
包来读取数据。如果您尚未安装它,可以使用 pip
(如果您安装了 Anaconda,则使用 conda
)来安装该包
1 |
pip install pandas_datareader |
我们使用的 GDP 数据代码是 NY.GDP.MKTP.CN
;我们可以通过以下方式获取一个国家的 pandas DataFrame 形式的数据
1 2 3 |
from pandas_datareader.wb import WorldBankReader gdp = WorldBankReader("NY.GDP.MKTP.CN", "SE", start=1960, end=2020).read() |
然后我们可以使用 pandas 提供的工具对 DataFrame 进行一些整理
1 2 3 4 5 6 7 8 9 |
import pandas as pd # 从索引中删除国家名称 gdp = gdp.droplevel(level=0, axis=0) # 按时间顺序排序数据,并设置年底数据点 gdp.index = pd.to_datetime(gdp.index) gdp = gdp.sort_index().resample("y").last() # 将 pandas DataFrame 转换为 pandas Series gdp = gdp["NY.GDP.MKTP.CN"] |
拟合 ARIMA 模型并使用该模型进行预测并不困难。在下文中,我们使用前 40 个数据点进行拟合,并预测接下来的 3 个数据点。然后,根据相对误差将预测与实际值进行比较
1 2 3 4 5 6 7 |
import statsmodels.api as sm model = sm.tsa.ARIMA(endog=gdp[:40], order=(1,1,1)).fit() forecast = model.forecast(steps=3) compare = pd.DataFrame({"实际值":gdp, "预测值":forecast}).dropna() compare["相对误差"] = (compare["预测值"] - compare["实际值"])/compare["实际值"] print(compare) |
将所有代码放在一起,并稍作润色,以下是完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import warnings warnings.simplefilter("ignore") from pandas_datareader.wb import WorldBankReader import statsmodels.api as sm import pandas as pd series = "NY.GDP.MKTP.CN" country = "SE" # 瑞典 length = 40 start = 0 steps = 3 order = (1,1,1) # 从世界银行数据库读取 GDP 数据 gdp = WorldBankReader(series, country, start=1960, end=2020).read() # 从索引中删除国家名称 gdp = gdp.droplevel(level=0, axis=0) # 按时间顺序排序数据,并设置年底数据点 gdp.index = pd.to_datetime(gdp.index) gdp = gdp.sort_index().resample("y").last() # 将 pandas dataframe 转换为 pandas series gdp = gdp[series] # 拟合 arima 模型 result = sm.tsa.ARIMA(endog=gdp[start:start+length], order=order).fit() # 预测,并计算相对误差 forecast = result.forecast(steps=steps) df = pd.DataFrame({"实际值":gdp, "预测值":forecast}).dropna() df["相对误差"] = (df["预测值"] - df["实际值"]) / df["实际值"] # 打印结果 with pd.option_context('display.max_rows', None, 'display.max_columns', 3): print(df) |
此脚本打印以下输出
1 2 3 4 |
实际值 预测值 相对误差 2000-12-31 2408151000000 2.367152e+12 -0.017025 2001-12-31 2503731000000 2.449716e+12 -0.021574 2002-12-31 2598336000000 2.516118e+12 -0.031643 |
上述代码虽然简短,但我们通过将一些参数保存在变量中使其足够灵活。我们可以将上述代码更改为使用 argparse,以便我们可以从命令行更改一些参数,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter import warnings warnings.simplefilter("ignore") from pandas_datareader.wb import WorldBankReader import statsmodels.api as sm import pandas as pd # 解析命令行参数 parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument("-c", "--country", default="SE", help="两位国家代码") parser.add_argument("-l", "--length", default=40, type=int, help="拟合 ARIMA 模型的时间序列长度") parser.add_argument("-s", "--start", default=0, type=int, help="拟合 ARIMA 模型的起始偏移量") args = vars(parser.parse_args()) # 设置参数 series = "NY.GDP.MKTP.CN" country = args["country"] length = args["length"] start = args["start"] steps = 3 order = (1,1,1) # 从世界银行数据库读取 GDP 数据 gdp = WorldBankReader(series, country, start=1960, end=2020).read() # 从索引中删除国家名称 gdp = gdp.droplevel(level=0, axis=0) # 按时间顺序排序数据,并设置年底数据点 gdp.index = pd.to_datetime(gdp.index) gdp = gdp.sort_index().resample("y").last() # 将 pandas dataframe 转换为 pandas series gdp = gdp[series] # 拟合 arima 模型 result = sm.tsa.ARIMA(endog=gdp[start:start+length], order=order).fit() # 预测,并计算相对误差 forecast = result.forecast(steps=steps) df = pd.DataFrame({"实际值":gdp, "预测值":forecast}).dropna() df["相对误差"] = (df["预测值"] - df["实际值"]) / df["实际值"] # 打印结果 with pd.option_context('display.max_rows', None, 'display.max_columns', 3): print(df) |
如果我们从命令行运行上面的代码,我们可以看到它现在可以接受参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ python gdp_arima.py --help 用法: gdp_arima.py [-h] [-c COUNTRY] [-l LENGTH] [-s START] 可选参数 -h, --help 显示此帮助信息并退出 -c COUNTRY, --country COUNTRY 两位国家代码 (默认值: SE) -l LENGTH, --length LENGTH 拟合 ARIMA 模型的时间序列长度 (默认值: 40) -s START, --start START 拟合 ARIMA 模型的起始偏移量 (默认值: 0) $ python gdp_arima.py 实际值 预测值 相对误差 2000-12-31 2408151000000 2.367152e+12 -0.017025 2001-12-31 2503731000000 2.449716e+12 -0.021574 2002-12-31 2598336000000 2.516118e+12 -0.031643 $ python gdp_arima.py -c NO 实际值 预测值 相对误差 2000-12-31 1507283000000 1.337229e+12 -0.112821 2001-12-31 1564306000000 1.408769e+12 -0.099429 2002-12-31 1561026000000 1.480307e+12 -0.051709 |
在上面的最后一个命令中,我们传入 -c NO
以将相同的模型应用于挪威(NO)而不是瑞典(SE)的 GDP 数据。因此,在不弄乱代码的情况下,我们重复使用了我们的代码来处理不同的数据集。
引入命令行参数的强大之处在于,我们可以轻松地使用不同的参数测试我们的代码。例如,我们想看看 ARIMA(1,1,1) 模型是否是预测 GDP 的好模型,并且我们想用北欧国家的不同时间窗口进行验证
- 丹麦 (DK)
- 芬兰 (FI)
- 冰岛 (IS)
- 挪威 (NO)
- 瑞典 (SE)
我们想检查 40 年的窗口,但起始点不同(从 1960 年、1965 年、1970 年、1975 年开始)。根据操作系统,您可以使用 bash shell 语法在 Linux 和 mac 中构建一个 for 循环
1 2 3 4 5 |
for C in DK FI IS NO SE; do for S in 0 5 10 15; do python gdp_arima.py -c $C -s $S done 完成 |
或者,根据 shell 语法允许,我们可以将所有内容放在一行中
1 |
for C in DK FI IS NO SE; do for S in 0 5 10 15; do python gdp_arima.py -c $C -s $S ; done ; done |
或者更好的是,在循环的每次迭代中提供一些信息,我们就可以多次运行脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
$ for C in DK FI IS NO SE; do for S in 0 5 10 15; do echo $C $S; python gdp_arima.py -c $C -s $S ; done; done 丹麦 0 实际值 预测值 相对误差 2000-12-31 1.326912e+12 1.290489e+12 -0.027449 2001-12-31 1.371526e+12 1.338878e+12 -0.023804 2002-12-31 1.410271e+12 1.386694e+12 -0.016718 丹麦 5 实际值 预测值 相对误差 2005-12-31 1.585984e+12 1.555961e+12 -0.018931 2006-12-31 1.682260e+12 1.605475e+12 -0.045644 2007-12-31 1.738845e+12 1.654548e+12 -0.048479 丹麦 10 实际值 预测值 相对误差 2010-12-31 1.810926e+12 1.762747e+12 -0.026605 2011-12-31 1.846854e+12 1.803335e+12 -0.023564 2012-12-31 1.895002e+12 1.843907e+12 -0.026963 ... 瑞典 5 实际值 预测值 相对误差 2005-12-31 2931085000000 2.947563e+12 0.005622 2006-12-31 3121668000000 3.043831e+12 -0.024934 2007-12-31 3320278000000 3.122791e+12 -0.059479 瑞典 10 实际值 预测值 相对误差 2010-12-31 3573581000000 3.237310e+12 -0.094099 2011-12-31 3727905000000 3.163924e+12 -0.151286 2012-12-31 3743086000000 3.112069e+12 -0.168582 瑞典 15 实际值 预测值 相对误差 2015-12-31 4260470000000 4.086529e+12 -0.040827 2016-12-31 4415031000000 4.180213e+12 -0.053186 2017-12-31 4625094000000 4.273781e+12 -0.075958 |
如果您使用的是 Windows,可以在命令提示符中使用以下语法
1 |
对于 %C 在 (DK FI IS NO SE) 执行 对于 %S 在 (0 5 10 15) 执行 python gdp_arima.py -c $C -s $S |
或者在 PowerShell 中使用以下代码
1 |
foreach ($C in "DK","FI","IS","NO","SE") { foreach ($S in 0,5,10,15) { python gdp_arima.py -c $C -s $S } } |
两者应该产生相同的结果。
虽然我们可以在 Python 脚本中放入类似的循环,但有时在命令行中执行会更容易。当我们探索不同的选项时,它可能更方便。此外,通过将循环移出 Python 代码,我们可以确保每次运行脚本时都是独立的,因为我们不会在迭代之间共享任何变量。
命令行参数的替代方案
使用命令行参数并不是向 Python 脚本传递数据的唯一方法。至少还有其他几种方法
- 使用环境变量
- 使用配置文件
环境变量是操作系统用于在内存中存储少量数据的功能。我们可以使用以下语法在 Python 中读取环境变量
1 2 |
import os print(os.environ["MYVALUE"]) |
例如,在 Linux 中,上述两行脚本在 shell 中会如下工作
1 2 3 |
$ export MYVALUE="hello" $ python show_env.py 你好 |
在 Windows 中,命令提示符中的语法类似
1 2 3 4 |
C:\MLM> set MYVALUE=hello C:\MLM> python show_env.py 你好 |
您也可以使用控制面板中的对话框在 Windows 中添加或编辑环境变量
因此,我们可以将脚本参数保存在一些环境变量中,并让脚本调整其行为,例如设置命令行参数。
如果我们需要设置很多选项,最好将选项保存到文件中,而不是让命令行不堪重负。根据我们选择的格式,我们可以使用 Python 的 configparser
或 json
模块分别读取 Windows INI 格式或 JSON 格式。我们也可以使用第三方库 PyYAML 来读取 YAML 格式。
对于上面在 GDP 数据上运行 ARIMA 模型的示例,我们可以修改代码以使用 YAML 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import warnings warnings.simplefilter("ignore") from pandas_datareader.wb import WorldBankReader import statsmodels.api as sm import pandas as pd import yaml # 从 YAML 文件加载配置 with open("config.yaml", "r") as fp: args = yaml.safe_load(fp) # 设置参数 series = "NY.GDP.MKTP.CN" country = args["country"] length = args["length"] start = args["start"] steps = 3 order = (1,1,1) # 从世界银行数据库读取 GDP 数据 gdp = WorldBankReader(series, country, start=1960, end=2020).read() # 从索引中删除国家名称 gdp = gdp.droplevel(level=0, axis=0) # 按时间顺序排序数据,并设置年底数据点 gdp.index = pd.to_datetime(gdp.index) gdp = gdp.sort_index().resample("y").last() # 将 pandas dataframe 转换为 pandas series gdp = gdp[series] # 拟合 arima 模型 result = sm.tsa.ARIMA(endog=gdp[start:start+length], order=order).fit() # 预测,并计算相对误差 forecast = result.forecast(steps=steps) df = pd.DataFrame({"实际值":gdp, "预测值":forecast}).dropna() df["相对误差"] = (df["预测值"] - df["实际值"]) / df["实际值"] # 打印结果 with pd.option_context('display.max_rows', None, 'display.max_columns', 3): print(df) |
YAML 配置文件名为 config.yaml
,其内容如下
1 2 3 |
country: SE length: 40 start: 0 |
然后我们可以运行上述代码并获得与之前相同的结果。JSON 对应部分非常相似,我们使用 json
模块中的 load()
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import json import warnings warnings.simplefilter("ignore") from pandas_datareader.wb import WorldBankReader import statsmodels.api as sm import pandas as pd # 从 JSON 文件加载配置 with open("config.json", "r") as fp: args = json.load(fp) # 设置参数 series = "NY.GDP.MKTP.CN" country = args["country"] length = args["length"] start = args["start"] steps = 3 order = (1,1,1) # 从世界银行数据库读取 GDP 数据 gdp = WorldBankReader(series, country, start=1960, end=2020).read() # 从索引中删除国家名称 gdp = gdp.droplevel(level=0, axis=0) # 按时间顺序排序数据,并设置年底数据点 gdp.index = pd.to_datetime(gdp.index) gdp = gdp.sort_index().resample("y").last() # 将 pandas dataframe 转换为 pandas series gdp = gdp[series] # 拟合 arima 模型 result = sm.tsa.ARIMA(endog=gdp[start:start+length], order=order).fit() # 预测,并计算相对误差 forecast = result.forecast(steps=steps) df = pd.DataFrame({"实际值":gdp, "预测值":forecast}).dropna() df["相对误差"] = (df["预测值"] - df["实际值"]) / df["实际值"] # 打印结果 with pd.option_context('display.max_rows', None, 'display.max_columns', 3): print(df) |
而 JSON 配置文件 config.json
将是
1 2 3 4 5 |
{ "country": "SE", "length": 40, "start": 0 } |
您可以了解更多关于您的项目中的 JSON 和 YAML 语法。但这里的想法是,我们可以将数据和算法分离,以更好地重用我们的代码。
想开始学习机器学习 Python 吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
库
- argparse 模块,https://docs.pythonlang.cn/3/library/argparse.html
- Pandas Data Reader,https://pandas-datareader.readthedocs.io/en/latest/
- statsmodels 中的 ARIMA,https://statsmodels.cn/devel/generated/statsmodels.tsa.arima.model.ARIMA.html
- configparser 模块,https://docs.pythonlang.cn/3/library/configparser.html
- json 模块,https://docs.pythonlang.cn/3/library/json.html
- PyYAML,https://pyyaml.org/wiki/PyYAMLDocumentation
文章
- 使用 JSON,https://mdn.org.cn/en-US/docs/Learn/JavaScript/Objects/JSON
- 维基百科上的 YAML,https://en.wikipedia.org/wiki/YAML
书籍
- Python Cookbook,第三版,作者 David Beazley 和 Brian K. Jones,https://www.amazon.com/dp/1449340377/
总结
在本教程中,您已经了解了如何使用命令行更有效地控制 Python 脚本。具体来说,您学习了
- 如何使用 argparse 模块向 Python 脚本传递参数
- 如何在不同操作系统下的终端中高效控制支持 argparse 的 Python 脚本
- 我们还可以使用环境变量或配置文件向 Python 脚本传递参数
你好,
感谢您的文章。
另一种选择是 typer。我认为它非常适合 Python 包结构。
感谢您的文章。