万万没想到,用Shell脚本做AI批处理,真香

本文介绍了如何使用Shell脚本进行AI批处理,包括脚本基础、应用示例,如配置Python环境、文本标注。通过案例展示了如何利用Shell自动化Python环境配置和CRF模型训练,强调Shell在数据处理和模型训练pipeline中的胶水语言角色。
点击上方“AI派”,选择“设为星标
最新分享,第一时间送达!
640?wx_fmt=jpeg
640?wx_fmt=gif

作者:Leon Wang,现为中科院特别研究助理 (博士后),在 AI、数据科学和科学计算等方面相关的工程实践上积累了丰富的经验。
编辑:王老湿

大家好,我们的

前面两篇文章已经为大家介绍了Linux的Shell命令相关的知识,我们已经可以在命令行下单独的完成一些操作。但在实际工作中,我们通常还需要利用这些命令,结合一些逻辑控制编写AI批处理脚本。

本篇文章就来介绍Shell脚本编程。因为bash是目前最常见的Shell环境,下文都将以bash为例进行介绍。

脚本基础

编写脚本文件

Shell脚本文件本质就是一个纯文本文件,只要把我们常用的txt文件的后缀名改成sh,甚至不改(只要你自己不混淆就可以)就可以作为脚本文件来用。一般,sh脚本应注意以下几点:shebang符号:

#!/bin/bash

保存文件后,为方便脚本文件能直接和命令一样运行,需要执行chmod +x fileshebang的可执行权限的内容可以回顾专栏里的 

变量

  • 定义赋值

定义Shell变量的形式为:变量名=变量值。例如:

PATH1="/tmp"

这里,注意变量名不能以数字开头,中间不能有空格和标点符号,以及Shell里面的一些关键字。变量值可以通过一条命令的结果动态赋值,例如:

PATH1=$(pwd)

注意,pwd是查看当前路径的命令,通过$()的形式,把里面pwd的结果直接赋值给PATH1

  • 使用变量

Shell中需要用$变量名的形式使用变量。结合前面所讲的定义赋值,我们可以使用一个已有变量的值,直接赋给另一个变量:

PATH1=$PWD

注意,$PWD是系统维护的环境变量,和pwd效果一致。

  • 命令行参数

与很多命令行环境相似,Shell脚本需要对多个参数进行处理。我们编写脚本时,可以通过一些特殊的变量获取命令行参数。首先,我们先写一个脚本testpara.sh,内容如下:

#!/bin/bash

param1=$1
param2=$2
echo "Running :$0"
echo "Parameter1: $param1"
echo "Parameter1: $param2"

当我们运行./testpara.sh p1 p2后,显示结果如下:

Running:./testpara.sh
Parameter1: p1
Parameter1: p2

请注意,如果参数很多时,脚本最好支持缺省参数,这样就不用每个参数都必须写全。我们可以把testpara.sh略微改造下,支持缺省参数:

#!/bin/bash
param1=${1:-para1}
param2=${2:-para2}
echo "Running :$0"
echo "Parameter1: $param1"
echo "Parameter1: $param2"

我们直接运行命令./testpara.sh,看看不给参数值的效果:

Running :./testpara.sh
Parameter1: para1
Parameter1: para2

可以看到,参数的缺省值已经发挥作用。

在实际的AI批处理脚本中,缺省参数也很实用。例如,一些超参数的设置经常会有一些默认的经验值,常见的训练集、验证集和测试集划分是6:2:2,tensorflow常用的版本号是1.13.2(因为1.14.0的tf.keras的卷积函数有一点Bug)。在这些场合灵活运用缺省参数,会提高我们的工作效率。

  • 退出状态码

基本上所有命令在退出时,都会返回一个状态值,表示这条命令是否成功。类似于c里面的int functionreturn 1return 0一样。在Linux中,一般定义0表示成功,非0表示失败。在Shell环境里,可以用$?获取上条命令的状态。例如,我们在成功执行testpara.sh后,可以运行echo $?,Shell这时返回了0,提示我们命令成功。

常见的非0码包括:127表示命令没找到,126表示不可执行。

逻辑控制

  • if

Shell的if语句与其他语言的很类似,唯一需要注意的是语法,以及与if成对的关键字fi。我们通过pip命令来举例:

sudo pip3 install numpy

  if [ $? = 0 ]; then
    echo "Installation Successfully"
  else
    echo "Problems to install Essential packages, check this!"
    exit 1
  fi

注意,Shell脚本的语句段不需要括号包围。

pip3 是指明使用与系统中python3想关联的pip命令安装。一般系统默认的pip指向pip2,即python2对应的pip命令。一种更推荐的的使用方式是python -m pip install numpy,这样可以避免混淆pythonpip的关系。

  • case

Shell中的case语句与其他语言的case也很相似,是多分支选择结构。注意,与case对应的结束关键字是esac。我们来看一个判断输入数字的例子:

echo 'Please press a number to select options'
read num
case $num in
    1)  echo 'option 1'
    ;;
    2)  echo 'option 2'
    ;;
    3)  echo 'option 3'
    ;;
    *)   echo 'Other'
    ;;
esac

注意,read读取键盘输入,并保存在变量num中。同时,注意case语句选项的写法。

也许读者们已经发现,Shell的条件选择命令的结束词,都是该命令反过来拼写。比如ifficaseesac

  • 循环

上面的例子为我们展示了Shell中的多分支结构,但我们注意到这个脚本只能执行一次选择就退出。在其他语言中,我们可以使用循环不断重复这个选择过程。同样,Shell中可以这样实现:

echo 'Input a number(press q to quit)'
read num
while [ $num != "q" ];do
    case $num in
        1)  echo 'option 1'
            ;;
        2)  echo 'option 2'
            ;;
        3)  echo 'option 3'
            ;;
        *)   echo 'Other'
            ;;
    esac
    echo 'Input a number'
    read num
done

这里特别注意,while中的do其实需要另起一行,上面的例子是通过;把两行合并在一行,显的紧凑。同时,为了脚本不会死循环,设置了q作为退出键。最后的donewhile ;do的结束词。

编程语言中还有for循环,适合迭代执行:

for ((i=1;i<=5;i++))
do   
    echo $i
done

另外,for语句更好用的是后面跟一个数组形式的参数,类似python中的list。数组可以是数字组成的序列:

for i in $(seq 1 5) 
do   
   echo $i
done   

这里的seq命令可以生成15的序列,等价于{1..5}。在批处理时,我们经常需要对多个文件进行处理,可以使用ls返回一个文件名的序列,作为for的参数:

for i in `ls`
do   
   echo $i 
done  

当我们知道一个路径的一部分,还可以通过通配符进行遍历,迭代每个符合的路径:

for file in /tmp/*  
do  
    echo $file  
done  

for in这种形式,非常类似python中的形式。可以利用各种函数或命令来构造循环迭代。另外,循环命令的执行体是do开头,done结尾,这点需要与ifcase的反写结尾区分开。

函数

Shell中支持用户自定义函数,形式如下:

[ function ] funname [()]

{

    XXXXX;

    [return int;]

}

这里,方括号表示可选内容。即function关键词可以省略,直接写函数名()就可以定义:

test(){
 echo hello
}

test

特别注意的是,函数也支持参数,使用$1$2等变量进行处理。

应用示例

配置python环境

开发环境中的配置,是个琐碎机械的工作。使用批处理进行环境配置,是脚本编程最常使用的场景。这里我引用一个小的项目prashant2018/MLSetup中的脚本,可以通过下面命令下载:

wget https://raw.githubusercontent.com/prashant2018/MLSetup/master/MLSetup_python3.sh

该脚本的使用方法很简单,可以给出一个模块的名字,单个安装;也可以直接给all,进行批量的全部安装:

./MLSetup_python3.sh numpy
./MLSetup_python3.sh pandas
./MLSetup_python3.sh all

这个脚本提供了一键安装包括numpy等多种常用python库的功能,综合运用了本篇文章讲解的命令行参数、退出状态码、case和函数多个知识点。请读者仔细研究,模仿其中写法。

文本标注

这里介绍作者以前实际工作中涉及到的一个小实验。我们使用CRF对地址文本进行识别,把相应的省、市、区、街道等等实体提取出来。在NLP中,这种任务属于命名实体识别(NER),完成NER任务所使用的模型是条件随机场(CRF)。

CRF是统计机器学习中的经典序列标注方法。关于CRF的更多介绍可以查阅文末给出的参考资料。若需要更多相关理论方面的介绍可以直接阅读李航老师的《统计机器学习》一书。

  • 安装依赖库

我使用的CRF工具来源于taku910/crfpp项目。crfpp本身是C++库,也通过Swig提供了Python的封装库,但是安装过程复杂。为了方便起见,我将其重新封装成标准的扩展模块crfpy,可以通过pip install crfpy直接安装好。除了crf_test命令暂时没加进来,其他都与原crfpp相同。

整个脚本文件在文末给出的代码仓库中的src/basic-02-shell/crf/位置,文件名为do.sh

  • 数据处理

首先,为了让脚本出错后就终止运行,方便查看问题,需要在脚本的起始位置(shebang行后的第一个非注释行)设置:

set -e

我使用了libpostal项目中分享的部分中文地址开源数据,从中截取了1000条记录作为整个数据集 ( 就是用的head命令 ),另存为了allchina_addr.txt。在这个文件里面,前两列都是国家和语言,只有最后一列才是需要的标注信息。通过awk命令对原数据进行提取:

cat allchina_addr.txt| awk '{$1=""}{$2=""}1'> input.data

通过shuf命令对数据集进行打散:

shuf input.data > shuffled_input

  • 划分数据集

按照7:3的比例,对数据集划分训练集和测试集:

split -l $[ $(wc -l shuffled_input|cut -d" " -f1) * 70 / 100 ] \
    shuffled_input crfdata_
mv crfdata_aa train.txt
mv crfdata_ab test.txt

因为这个数据集的标注格式和crfpp要求的不同,所以我准备了make_crfpp_data.py这个脚本进行转换:

echo converting
./make_crfpp_data.py -i train.txt -o  train.data
./make_crfpp_data.py -i test.txt -o  test.data

  • 模型训练

最后,调用crf_learn命令训练模型:

echo training
time crf_learn -p 30 template train.data model.crf 2>&1 | tee  train.log

注意,time是显示命令执行时间。其中的templatecrfpp需要的特征模板文件,后面的2>&1 | tee train.log整个实现了屏幕上打印日志信息,又同时保存在train.log中。读者们可以自己试验,通过拆解各部分来体会各个作用。

目前在crfpy中还没有封装crf_test命令,所以暂时没法直接进行验证集测试。在后面的文章中,会配合python模块封装技术的讲解,带着大家去实战一下,怎么样把这个命令也封装进来,让大家都学会如何自己把一个C++库封装为python模块。

后记

本篇文章首先简单介绍了一些Shell编程最常需要的一些基础知识,然后提供了两个例子。

  • 第一个例子是通过批处理自动配置Python环境,把前面几个知识进行了串联。但有个问题是,这个脚本直接把模块安装到了系统的python中,在实际工作中,不一定合适。下一篇文章会为大家介绍一种更推荐的方式——虚拟环境

  • 第二个例子,是通过基于CRF的NER任务实战脚本,演示了Shell脚本的批处理。我们特别注意,在第二个例子中,除了转换标签格式部分额外写了Python脚本,模型训练使用了crf_learn命令,其余都是利用Linux常用的shell命令就完成了。特别是最后的模型训练的日志记录,模型训练的计时都没有额外编程。同时,Shell脚本完成了比Python更高一层的“胶水语言”,把Python和C++实现的Shell命令与其他内部命令很好的衔接起来,完成一整套pipeline

PS: 本文所需代码和演示数据,可通过https://github.com/ai-union/ai_engineering_practice_guide  中的src文件夹获得。本篇文章的代码文件夹是basic-02-shell,CRF的相关代码文件在crf中。

参考资料

prashant2018/MLSetup

(https://github.com/prashant2018/MLSetup)

如何轻松愉快地理解条件随机场(CRF)?- 简书

(https://www.jianshu.com/p/55755fc649b1)



/ 每日赠书专区 /

为了回馈一直以来支持我们的读者,“每日赠书专区”会每天从留言支持我们的读者中选择一名最脸熟的读者来赠予实体书籍(包邮),当前通过这种方式我们已赠送出 30+ 本书籍。

1.脸熟的评判标准是根据通过留言的次数来决定的

2.留言时需要按照今日留言主题来用心留言,否则不计入总数

3.每日赠书专区会出现在AI派当天发布文章的头条或次条的文章末尾


如果不理解头条/次条的含义的读者可看下面的图。

640?wx_fmt=jpeg



今天我们的每日赠书专区出现在“”的位置上,书籍为《Linux实战》

640?wx_fmt=jpeg

本书简介:

本书中共有12个实际项目,包括自动备份与恢复系统、建立一个私有的Dtropbox风格的文件云以及构建你自己的MediaWiki服务器等。当你开展诸如虚拟化、灾难恢复、安全、备份、DevOps以及系统故障诊断等核心实践时,你将会接触到一些有趣的例子。每章都以回顾主要名词、安全实践、命令行以及习题结束。

?↑↑点击上方小程序即可购买

恭喜上期通过留言成功混脸熟的读者:彳亍、羽,赠送一本《利用Python进行数据分析

请中奖同学联系小编:wanglaoshi201907

640?wx_fmt=png

/ 今日留言主题 /

你在Linux上做过最牛逼的操作是什么?rm -rf / ? ?

近期专栏推荐

1. 

2. 

3. 

4. 


点下「在看」,给文章盖个戳吧!?

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值