不到1美元!1小时内训练自己的中文词向量
自然语言处理 NLP 是人工智能非常热门的一个领域,采用 NLP 技术构建的语言模型可以帮助业务系统提供更丰富的语言洞察和交互能力,譬如构建自己的智能客服、对于用户评价进行情感分析、舆情分析等。客户想要构建出自己的语言模型面临着诸多挑战,其中之一就是训练自己语言模型的词向量,因为词向量是构建各种业务语言模型的基础,但是面对海量的语料库,需要海量训练计算资源的时候,往往需要很大的技术、资源、时间的投入,代价很大。
SageMaker 是亚马逊云科技上一个托管的机器学习平台服务,能够帮助客户高效的自动标注数据、训练和部署机器学习模型。SageMaker 内置了二十多种亚马逊云科技优化过的机器学习高性能算法,包括常见的线性回归、神经网络、时间序列等,并且还支持 MXNet、TensorFlow、PyTorch 等主流深度学习框架。今天我们将介绍如何使用 Amazon SageMaker 服务的 BlazingText 在一小时内快速训练 wiki 中文的词向量。
Amazon SageMaker BlazingText 算法提供了 Word2vec 和文本分类算法的高度优化的实现。利用 BlazingText 算法,您可以轻松扩展到大型语言数据集。与 Word2vec 类似,它提供了 Skip-gram 和持续单词袋 (CBOW) 两种训练架构来训练词向量,称之为(WordEmbedding),并且还实现了监督学习的 fastText 文本分类器,您可以使用多台 CPU 服务器器 或 GPU 服务器在几分钟内对超过 10 亿个单词的模型进行训练。
管理亚马逊云科技资源
登录控制台1. 首先在亚马逊云科技控制台进入 SageMaker 服务,如下图点击右上方黄色菜单 “Create notebook instance”, 创建一个 Jupyter notebook 实例。
2. 在配置 Jupyter notebook 实例页面输入实例名称,以及您所需要的实例的机器配置,这里选择 ml.c5.2xlarge 配置, 这是一台 8vCPU 16G 内存的机器,采用用 Intel Sky-lake 白金处理器,主频高达 3.1GHz, 按秒计费。当然您也可以选择其他硬件配置的 notebook,甚至是带 Nvida V100 的 GPU 配置。
3. 在配置过程中您需要给 SageMaker 创建一个角色,使得 SageMaker 服务能够有权限访问 S3 存储桶上的 wiki 中文训练数据,并把训练好的模型上传到 S3 存储桶中。点击 “Create a new role” 系统会帮您自动创建,在测试环境下您可以选择 “Any S3 bucket”,代表 SageMaker 服务可以访问任意一个 S3 存储桶,生产环境建议指定某个具体存储桶。
4. 创建 Notebook 实例大约需要几分钟的时间,我们可以在这期间在 S3 上创建一个存储桶,用于存放 wiki 中文的训练数据和模型。大约几分钟之后 SageMaker 就创建好了 Notebook 实例,点击 “Open Jupyter” 将自动跳转到 Notebook 页面。
5. 因为我们对中文进行训练,在分析中会用到 matplot 画一些图显示中文,但 notebook 实例上默认没有安装中文字体,需要给 notebook 实例安装一个中文字体,譬如 Simhei。点击右上角 “New”,选择 “Terminal” 就可以进入 Notebook 命令行界面进行字体安装。
因为 Notebook 实例中已经预先安装好了常用的 Anaconda 环境,如 MxNet,TensorFlow 等。我们点击右上角 “New”,“Conda_Python3” 创建一个 Python3。
2. 首先我们加载相应的 Python 库,并将我们之前创建的 S3 存储桶用于存放 wiki 中文训练数据的 S3 存储桶
importsagemaker fromsagemaker import get_execution_role importboto3 importjson sess = sagemaker.Session() role = get_execution_role() print(role) # This is the role that SageMaker would use to leverage Amazon Web Services resources (S3, CloudWatch) on your behalf bucket = '421710401846-sagemaker-us-west-2'# Replace with your own bucket name if needed prefix = 'nlp-handson'#Replace with the prefix under which you want to store the data if needed
3. 接下来我们下载 wiki 中文的中文数据集,原始数据是一个 xml 压缩包,经过了简单的预处理后并被转换为简体中文,总大小 1.3G,包含约 447 万行,4.5 亿个中文字。
!wget https://421710401846-sagemaker-us-west-2.s3-us-west-2.amazonaws.com/nlp-handson/zh-train/wiki_zh_full
4. 下载后的中文数据集合需要进行清洗,主要两个清洗步骤
- 原始数据包含许多英文单词,阿拉伯数字和标点符号,删除这些噪音信息
- 使用 Jieba 分词工具解析中文单词,以空格分开。因为在解析单词时,我们会遇到很多毫无意义的中文词,如 “的、有然、因为”,我们称这些单词停止词(Stopwords),也需要删除
# Install jieba tool !pip install jieba # Download stopwords !wget https://421710401846-sagemaker-us-west-2.s3-us-west-2.amazonaws.com/nlp-handson/zhstopwords.txt importlogging,jieba,os,re defget_stopwords(): basicConfig(format='%(asctime)s:%(levelname)s:%(message)s',level=logging.INFO) #加载停用词表 stopword_set = set() with open("zhstopwords.txt",'r',encoding="utf-8") as stopwords: for stopword in stopwords: add(stopword.strip("\n")) return stopword_set
5. 这里我们定义一个分词方法 parse_zh_words,对原始 wiki 中文数据进行分词,以空格隔开,并把分词结果存到本地文件。我们使用的是 8 vCPU,16G RAM 的 C5 notebook 实例,整个清洗过程大约 20 分钟左右,清洗完后的 wiki 中文语料库大约 966MB,这就是我们需要的的训练数据。
defparse_zh_words(read_file_path,save_file_path): file = open(read_file_path,"r",encoding="utf-8") #过滤掉英文和数字等特殊字符 r1 = '[^\u4e00-\u9fa5]' #写文件 output = open(save_file_path,"w+",encoding="utf-8") content_line = file.readline() #获取停用词表 stopwords = get_stopwords() #定义一个字符串变量,表示一篇文章的分词结果 article_contents = "" while content_line: content_line = content_line.strip("\n") if len(content_line) > 0: #去除数字和英文等特殊字符 zh_content_line = re.sub(r1, '', content_line) #使用jieba进行分词 words = jieba.cut(zh_content_line,cut_all=False) …… input_file = './data/wiki_zh_full' output_file = './data/wiki_zh_corpus' parse_zh_words(input_file, output_file)
6. 清洗完成之后我们可以查看一下清洗完后的文本是不是已经分好词,并且没有其他英文特殊符号。下面的脚本可以看到语句已经被空格分割成一个个中文单词了。
7. 我们将清洗好的 wiki 中文语料库上传到 S3 的存储桶中,并定义 BlazingText 训练任务的 S3 数据通道和模型输出位置。
train_channel = prefix + '/zh-train' upload_data(path='data/wiki_zh_corpus', bucket=bucket, key_prefix=train_channel) s3_train_data = 's3://{}/{}'.format(bucket, train_channel) s3_output_location = 's3://{}/{}/zh-output'.format(bucket, prefix)
8. 接下来使用 BlazingText 算法进行中文词向量的模型训练。BlazingText 除了skip-gram 和 CBOW 之外,还支持 “batch skip-gram” 模式,该模式使用高效的迷你批处理和矩阵矩阵操作(BLAS Level 3 routines)。 还支持跨多个 CPU 节点的分布式 Word2vec 训练,加快训练的速度。在不同类型的实例上,BlaingText 支持以下模式:
9. 我们定义一个模型训练器(Estimator),其中 BlazingText 算法亚马逊云科技已经封装在一个 Docker 容器中,容器存放在一个固定的 ECR 镜像仓库中,我们可以直接使用。在模型训练器中我们指定了需要什么样的机器配置,以及用于训练机器数量等参数。在这里我们选择了拥有最新的 Nvida V100 GPU 的 P3 实例。值得一提的是红色参数部分,SageMaker 现在支持使用 Amazon Spot 实例进行训练,Spot 实例只有按需实例 30% 左右的价格,能够极大节约您的训练成本。
container = sagemaker.amazon.amazon_estimator.get_image_uri(region_name, "blazingtext", "latest") bt_model = sagemaker.estimator.Estimator(container, role, train_instance_count=1, train_instance_type='ml.p3.2xlarge', train_use_spot_instances = True, train_volume_size = 10, train_max_run = 3600, train_max_wait = 3600, input_mode= 'File', output_path=s3_output_location, sagemaker_session=sess)
10. 之后继续配置训练过程中的超参数,我们使用 skipgram 模式,学习因子设置为 0.05、负采样率为 5 等,将词向量目标维度 vector_dim 设置成 300 维,然后定义训练数据通道 data_channel,这个将指向我们之前创建的 S3 存储桶。
set_hyperparameters(mode= "skipgram", #"batch_skipgram", epochs=10, min_count=5, sampling_threshold=0.0001, learning_rate=0.05, window_size=5, vector_dim=300, negative_samples=5, batch_size=11, evaluation=False subwords=False) train_data = sagemaker.session.s3_input(s3_train_data, distribution='FullyReplicated', content_type='text/plain', s3_data_type='S3Prefix') data_channels = {'train': train_data}
11. 以上设置完成之后,只需要调用fit方法,就可以自动进行模型训练了。在这个 fit 方法背后,SageMaker 会根据之前的配置自动下载 BlazingText 算法的容器,自动启动运行算法的GPU 服务器,自动训练并打印出训练信息,最后自动吧训练好的 wiki 中文词向量模型存储到S3中,所有的一切都是自动化完成。
fit(inputs=data_channels, logs=True)
11. 训练完后您会看到整个训练过程的用时,在训练阶段用时 365.86 秒,训练 + 上传模型共 973 秒,大约 16 分钟,但因为我们使用 Spot 机型,所以计费只有 292 秒,节约了大约 70% 的成本。训练好的模型文件 SageMaker 会自动上传到之前指定的 S3 存储桶中,去查看一下,模型大约 1.1G 左右。
1. 首先我们把模型文件下载下来并解压缩,得到一个 vectors.txt 文件,大约 1.5G,这个文件就是 wiki 中文的词向量文件,包含了大约 56.7 万个词向量。我们把 vectors.txt 的前 400 个中文词向量进行二维可视化,我们使用 TSNE 降维方式。
frommanifold importTSNE frommatplotlib import pylab frompylab import mpl tsne = TSNE(perplexity=40, n_components=2, init='pca', n_iter=10000) two_d_embeddings = tsne.fit_transform(word_vecs[:num_points]) labels = index_to_word[:num_points] rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体 rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题 %matplotlib inline defplot(embeddings, labels): figure(figsize=(20,20)) fori, label in enumerate(labels): x, y = embeddings[i,:] scatter(x, y) annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points',ha='right', va='bottom') #pylab.savefig('tsne-ch.png') show() plot(two_d_embeddings, labels)
2. 二维可视化如下图所示,我们可以看到类似的中文单词被归类到一起,说明词向量的训练明效果还不错。因为之前安装了 Simhei 字体,所以画布上能显示中文。
3. 训练出来的 vectors.txt 是一个词向量文件,可以与 Gensim 和 Spacy 等其他工具存储格式兼容。因此我们使用另一个 Python 工具 Gensim 来验证。Gensim 是一个用于从文档中自动提取语义主题的 Python 库。看看中文单词的类比性和相似性。可以看到,[女人,国王] 对应了 [男人,王后] 的类比性比较,和牛肉不属于 [天才,傻瓜,疯子] 范畴相似性比较。或者也可以使用 wordsim-240、wordsim-297 中文数据集进行相似度评测,CA8 数据集进行类比度评测。
!pip install gensim frommodels importKeyedVectors word_vectors = KeyedVectors.load_word2vec_format('vectors.txt', binary=False) result1 = word_vectors.most_similar(positive=['女人', '国王'], negative=['男人']) print("{}: {:.4f}".format(*result1[0])) print(word_vectors.doesnt_match("天才傻瓜 疯子 牛肉".split()))
4. 我们还可以用 wordcloud 绘制一个中文词相似度云,把和某个中文单词相似的前 100 个词语都绘制在一个圆形的词云中。
!pip install wordcloud importpyplot as plt fromwordcloud import WordCloud ... #输入一个词找出相似的前100个词 one_corpus = ["美丽"] result = word_vectors.most_similar(one_corpus[0],topn=100) #将返回的结果转换为字典,便于绘制词云 word_cloud = dict() forsim inresult: word_cloud[sim[0]] = sim[1] #绘制词云 draw_word_cloud(word_cloud)
5. 可以看到类似如下的单词词云,三个图片分别代表了中文的形容词、名词和动词。
6. 最后看一下成本,我们在 notebook 中我们不到一个小时内基于 wiki 中文上亿数量级的中文文本训练出了自己 56.7 万个中文单词的词向量,并且验证效果还不错。现在我们以美西俄勒冈区域为例:
notebook 实例 C5 2xlarge $0.538/小时,计费使用一小时;
GPU 训练实例 P3 2xlarge $4.284/小时,计费使用 292 秒;
S3 存储成本 $0.023 每 GB/月,存储大约 3GB;
总成本 Cost = 0.538*1 + (4.284/3600)*292 + (0.023*3)/30 = 0.88778,
连1美元都不到!出乎意料的实现了我们的目标!
总结一下,利用 SageMaker 内置的 BlazingText 算法,可以帮助客户多快好省的训练自己的语言模型词向量。不需要自己写算法,不用担心算力、不用担心成本,只要准备好您的数据,SageMaker 都将自动化的构建各种业务的语言模型,为系统提供简单、高效的 NLP AI 能力集成。