はじめに
N1! Machine Learning Product Engineerの中村です。
普段はニュースサイトやアプリのバックエンド開発を担当し、その他機械学習をプロダクトに実装することを目的としたスペシャリスト(ニフティにはN1!というスペシャリスト制度があります)としても活動しています。
https://recruit.nifty.co.jp/interview/nakamura.htm
本記事では、現在検証中である自然言語処理のためのHuggingFaceモデルをSageMakerにデプロイする手順を説明します。
SageMakerを使用することで、インスタンスタイプ変更やスケーリング、Lambdaなどと組み合わせたWebAPI化も可能なため、機械学習プロダクトの開発が楽に行えます。
注意
この記事ではSageMakerへのデプロイを目的としているため、自然言語処理モデルの具体的な内容や学習手順、JupyterNotebookの詳細な使い方については説明しません。
また、途中で公開している自然言語処理モデルは学習元データ数を極端に落としているため、精度が非常に低いです。
(高精度なモデルを求めている人はぜひ採用サイトまで!)
全体の構成について
今回は上のような構成をTerraformで構築します。
SageMakerでNotebookインスタンスを立ち上げ、S3に自作のHuggingFaceモデルを配置します。
Notebookインスタンス内でデプロイを実行することで、S3からモデルがSageMakerのエンドポイントに配置されます。(SageMaker Serverless Inferenceを使うことで、実行時間だけ課金が発生するようになります)
インフラ環境の構築
Terraformでインフラ環境を構築します
main.tf
AWS Provider Version 4を使用します。
profileの設定などは自分の環境用に変更してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.9.0" } } } provider "aws" { shared_credentials_files = ["~/.aws/credentials"] region = "ap-northeast-1" profile = "xxxxxx" } |
s3.tf
モデルを配置するためのS3を作成します。
バケット名は全世界で一意である必要があるため、bucket名を適宜修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
resource "aws_s3_bucket" "model" { bucket = "huggingface-sagemaker-base-model-bucket-XXXXX" tags = { Name = "HuggingFace Model Bucket" } # 空でない場合も削除する force_destroy = true } resource "aws_s3_bucket_acl" "model_private" { bucket = aws_s3_bucket.model.id acl = "private" } |
sagemaker.tf
Notebookインスタンスを作成します。
SageMakerの実行権限と、S3からモデルを読み出すためのS3の権限を付与します。
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 |
resource "aws_sagemaker_notebook_instance" "notebook_instance" { name = "huggingface-notebook-instance" role_arn = aws_iam_role.sagemaker.arn instance_type = "ml.t3.medium" tags = { Name = "huggingface notebook" } } resource "aws_iam_role" "sagemaker" { name = "huggingface-sagemaker-role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "sagemaker.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "sagemaker_role_attachment" { role = aws_iam_role.sagemaker.name policy_arn = "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess" } resource "aws_iam_role_policy_attachment" "sagemaker_s3_role_attachment" { role = aws_iam_role.sagemaker.name policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" } |
環境を立ち上げる
ファイルを配置して、Terraformで環境を立ち上げます。
SageMaker Notebookのインスタンスが立ち上がるのにやや時間がかかります。
1 2 3 |
|--main.tf |--s3.tf |--sagemaker.tf |
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 |
aws_iam_role.sagemaker: Creating... aws_s3_bucket.model: Creating... aws_iam_role.sagemaker: Creation complete after 2s [id=huggingface-sagemaker-role] aws_iam_role_policy_attachment.sagemaker_s3_role_attachment: Creating... aws_iam_role_policy_attachment.sagemaker_role_attachment: Creating... aws_sagemaker_notebook_instance.notebook_instance: Creating... aws_s3_bucket.model: Creation complete after 2s [id=huggingface-sagemaker-base-model-bucket] aws_s3_bucket_acl.model_private: Creating... aws_iam_role_policy_attachment.sagemaker_role_attachment: Creation complete after 0s [id=huggingface-sagemaker-role-20220505060125809500000001] aws_iam_role_policy_attachment.sagemaker_s3_role_attachment: Creation complete after 0s [id=huggingface-sagemaker-role-20220505060126018500000002] aws_s3_bucket_acl.model_private: Creation complete after 0s [id=huggingface-sagemaker-base-model-bucket,private] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [30s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [40s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [50s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m0s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m30s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m40s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [1m50s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [2m0s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [2m10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [2m20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [2m30s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still creating... [2m40s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Creation complete after 2m45s [id=huggigface-notebook-instance] Apply complete! Resources: 6 added, 0 changed, 0 destroyed. |
自作モデルをS3に配置する
作成したS3バケットに自作モデルを配置していきます。
以下からモデルをダウンロードするか、または自作のHuggingFaceモデルをtar.gzの形で圧縮したものをアップロードします。
こちらで作成したモデルは以下からダウンロードできます。
(学習データを抑えて学習を行ったサンプル用モデルである点はご了承ください)
自作モデルを使用する場合は以下のようにして圧縮します。
1 |
tar zcvf model.tar.gz config.json pytorch_model.bin special_tokens_map.json spiece.model tokenizer_config.json |
モデルが用意できたらS3にアップロードします。
SageMakerでデプロイする
配置したモデルをデプロイしていきます。
Notebookを開く
「JupyterLabを開く」を選択します。
「conda_pytorch_p36」のNotebookを開きます
デプロイのためのコードの記述
まずSageMakerをアップグレードするために、以下のコードをセルに貼り付け、実行します。
1 |
!pip install -U sagemaker==2.84.0 |
次に、inference.pyというファイルを作成するために以下のコードを実行し、ファイルを作成します。
1 |
!touch inference.py |
inference.pyの編集
inference.pyではSageMakerが予測時にどのような処理を行うかを記述します。
model_fn
予測のための関数であるpredict_fnに渡すモデル形式を決定します。
今回はモデルのパスを受け取り、HuggingFaceのモデルとトークナイザーを返す処理を実装しています。
predict_fn
input_fnによって解析された入力データと、model_fnでロードしたモデルデータを受け取り、予測のための処理を行います。今回はT5によるプロンプト予測を行い、記事データから要約を生成します。
この他にinput_fn, output_fnという入出力について決める関数もありますが、今回は特にカスタマイズせずに使用します。
前節でダウンロードしたモデルを使う場合はinference.pyは以下のようになり、これをSageMakerのルートディレクトリに配置します。
(オリジナルのモデルを使う場合はモデルに合わせたpredict_fnの処理変更が必要になります)
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
from transformers import T5ForConditionalGeneration, T5Tokenizer import torch import re from collections import OrderedDict tag_regex = re.compile(r"<[^>]*?>") # https://github.com/neologd/mecab-ipadic-neologd/wiki/Regexp.ja から引用・一部改変 import re import unicodedata def unicode_normalize(cls, s): pt = re.compile('([{}]+)'.format(cls)) def norm(c): return unicodedata.normalize('NFKC', c) if pt.match(c) else c s = ''.join(norm(x) for x in re.split(pt, s)) s = re.sub('-', '-', s) return s def remove_extra_spaces(s): s = re.sub('[ ]+', ' ', s) blocks = ''.join(('\u4E00-\u9FFF', # CJK UNIFIED IDEOGRAPHS '\u3040-\u309F', # HIRAGANA '\u30A0-\u30FF', # KATAKANA '\u3000-\u303F', # CJK SYMBOLS AND PUNCTUATION '\uFF00-\uFFEF' # HALFWIDTH AND FULLWIDTH FORMS )) basic_latin = '\u0000-\u007F' def remove_space_between(cls1, cls2, s): p = re.compile('([{}]) ([{}])'.format(cls1, cls2)) while p.search(s): s = p.sub(r'\1\2', s) return s s = remove_space_between(blocks, blocks, s) s = remove_space_between(blocks, basic_latin, s) s = remove_space_between(basic_latin, blocks, s) return s def normalize_neologd(s): s = s.strip() s = unicode_normalize('0-9A-Za-z。-゚', s) def maketrans(f, t): return {ord(x): ord(y) for x, y in zip(f, t)} s = re.sub('[˗֊‐‑‒–⁃⁻₋−]+', '-', s) # normalize hyphens s = re.sub('[﹣-ー—―─━ー]+', 'ー', s) # normalize choonpus s = re.sub('[~∼∾〜〰~]+', '〜', s) # normalize tildes (modified by Isao Sonobe) s = s.translate( maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~。、・「」', '!”#$%&’()*+,-./:;<=>?@[¥]^_`{|}〜。、・「」')) s = remove_extra_spaces(s) s = unicode_normalize('!”#$%&’()*+,-./:;<>?@[¥]^_`{|}〜', s) # keep =,・,「,」 s = re.sub('[’]', '\'', s) s = re.sub('[”]', '"', s) return s def normalize_text(text): text = text.replace("\t", " ") text = text.strip() text = normalize_neologd(text) text = tag_regex.sub("", text) return text def preprocess_body(text): return normalize_text(text.replace("\n", " ")) def model_fn(model_dir): # Load model from HuggingFace Hub tokenizer = T5Tokenizer.from_pretrained(model_dir, is_fast=True) model = T5ForConditionalGeneration.from_pretrained(model_dir) return model, tokenizer def predict_fn(data, model_and_tokenizer): # destruct model and tokenizer model, tokenizer = model_and_tokenizer # 推論モードにする model.eval() # GPUの利用有無 USE_GPU = torch.cuda.is_available() if USE_GPU: model.cuda() # 受け取ったデータをトークン化する sentences = data.pop("inputs", data) preprocessed_body = preprocess_body(sentences) ### キーワード以外の生成処理 ### inputs = ["title: " + preprocessed_body, "long_title: " + preprocessed_body, "topics_title: " + preprocessed_body, "summary_1: " + preprocessed_body, "summary_2: " + preprocessed_body, "summary_3: " + preprocessed_body] batch = tokenizer.batch_encode_plus( inputs, max_length=512, truncation=True, padding="longest", return_tensors="pt") input_ids = batch['input_ids'] input_mask = batch['attention_mask'] if USE_GPU: input_ids = input_ids.cuda() input_mask = input_mask.cuda() # 生成処理を行う # ビームサーチの幅を広くすると多様で高品質な文章が生成されやすい outputs = model.generate( input_ids=input_ids, attention_mask=input_mask, max_length=48, temperature=1.0, # 生成にランダム性を入れる温度パラメータ num_beams=2, # ビームサーチの探索幅 diversity_penalty=1.0, # 生成結果の多様性を生み出すためのペナルティ num_beam_groups=2, # ビームサーチのグループ数 num_return_sequences=1, # 生成する文の数 repetition_penalty=1.5, # 同じ文の繰り返し(モード崩壊)へのペナルティ ) # 生成されたトークン列を文字列に変換する generated = [tokenizer.decode(ids, skip_special_tokens=True, clean_up_tokenization_spaces=False) for ids in outputs] # 結果を返す return {"title": generated[0], "long_title": generated[1], "topics_title": generated[2], "summary_1": generated[3], "summary_2": generated[4], "summary_3": generated[5] } |
デプロイの実行
HuggingFaceのデプロイを行うための定義を行います。
(model_dataのbucket名はアップロードしたS3のバケット名を指定します)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from sagemaker.huggingface import HuggingFaceModel import sagemaker role = sagemaker.get_execution_role() # create Hugging Face Model Class huggingface_model = HuggingFaceModel( entry_point='inference.py', model_data="s3://sagemaker-huggingface-base-model-bucket-XXXXX/model.tar.gz", # path to your trained sagemaker model role=role, # iam role with permissions to create an Endpoint transformers_version="4.17.0", # transformers version used pytorch_version="1.10.2", # pytorch version used py_version="py38", # python version of the DLC image_uri="763104351884.dkr.ecr.ap-northeast-1.amazonaws.com/huggingface-pytorch-inference:1.10.2-transformers4.17.0-cpu-py38-ubuntu20.04" ) |
SageMaker Serverless Inferenceへデプロイするためのコードを記述し、実行します
1 2 3 4 5 6 7 8 |
# deploy model to SageMaker Serverless Inference # サーバレスで実行する from sagemaker.serverless import ServerlessInferenceConfig serverless_config = ServerlessInferenceConfig(memory_size_in_mb=6144) predictor = huggingface_model.deploy( endpoint_name="sagemaker-model-endpoint", serverless_inference_config=serverless_config, ) |
実行するとデプロイが行われます
!になったら、デプロイ完了です
デプロイされたエンドポイントはSageMakerの推論エンドポイントから確認できます
デプロイされたエンドポイントで要約推論を行う
では、実際にエンドポイントで要約を行ってみます。
以下の形式で記事データを渡すことで、記事要約を行ってくれます。
1 2 3 4 5 6 7 |
# "inputs"をデータの中に含めてください data = { "inputs": "ふるさと納税は、実質、自分の選んだ自治体に納税ができる制度です。正式には、任意の自治体に寄付をして、寄付した額から自己負担額である2,000円分をひいた額が住民税・所得税から控除されます。さらに、寄付をした自治体から肉や魚介・果物などの返礼品がもらえるので、納税の仕組みとして非常にお得な制度だと言えます。しかし、ふるさと納税に興味はあるけど具体的にどうしたらいいか分からないという人も多いはず。今回は、ふるさと納税について、できるだけ分かりやすく解説していきたいと思います。まず、ふるさと納税をして得をすることは、返礼品がもらえることです。この返礼品は、寄付額のおよそ30%くらいが目安です。なので10,000円寄付したら3,000円分相当の返礼品が届きます。もともと税金の支払いに当てる予定であったお金を使って、返礼品がもらえることは非常にお得です。もちろん、寄付した金額は、自己負担金の2,000円を除いた額が住民税・所得税から控除されます。次に知っておきたいのが、ふるさと納税には、収入によって控除上限がある点です。控除の上限額は収入によって異なります。ふるさと納税サイトのシミュレーションを使えば簡単に調べることができるので簡単ですが、重要な工程です。控除の上限を超えてふるさと納税しようとしても、税金の控除は受けられないので、損をしたくない方は、前もって自身の控除の上限額の目安を知っておきましょう。最後に、ふるさと納税をする際に、「さとふる」や「ふるさとチョイス」などのふるさと納税サイトを利用することになると思いますが、ポイントサイトを経由するとポイントがついてさらにお得になります。そして、今回紹介するのが、ニフティポイントクラブです。20年を超える運営実績と登録人数が300万人超えの老舗ポイントサイトで、昨年、ライフメディアからニフティポイントクラブに名称が変更されました。様々なショッピングなどでポイントが貯まるのはもちろん、ふるさと納税をしてもポイントが貯まる仕組みになっていますので、お得さを重視される方は、ニフティポイントクラブに登録してからふるさと納税を行うのをおすすめします。" } # エンドポイントにリクエストします predictor.predict(data) |
性能にはまだ改善点が見られますが、記事からタイトル・要約などを生成することが出来ました。
(オプション)環境の削除
一通りの実行が終わったら、不要であれば環境を削除していきます。
エンドポイントとモデルの削除
1 2 |
predictor.delete_model() predictor.delete_endpoint() |
※Terraformでは管理されていない部分のため、必ず実行してエンドポイントを削除しましょう。
terraform destroyの実行
terraform destroyを実行して環境を削除します。
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 |
aws_iam_role_policy_attachment.sagemaker_s3_role_attachment: Destroying... [id=huggingface-sagemaker-role-20220505060126018500000002] aws_iam_role_policy_attachment.sagemaker_role_attachment: Destroying... [id=huggingface-sagemaker-role-20220505060125809500000001] aws_s3_bucket_acl.model_private: Destroying... [id=huggingface-sagemaker-base-model-bucket,private] aws_sagemaker_notebook_instance.notebook_instance: Destroying... [id=huggigface-notebook-instance] aws_s3_bucket_acl.model_private: Destruction complete after 0s aws_s3_bucket.model: Destroying... [id=huggingface-sagemaker-base-model-bucket] aws_iam_role_policy_attachment.sagemaker_s3_role_attachment: Destruction complete after 1s aws_iam_role_policy_attachment.sagemaker_role_attachment: Destruction complete after 1s aws_s3_bucket.model: Destruction complete after 1s aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 30s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 40s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 50s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m0s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m30s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m40s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 1m50s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 2m0s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 2m10s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Still destroying... [id=huggigface-notebook-instance, 2m20s elapsed] aws_sagemaker_notebook_instance.notebook_instance: Destruction complete after 2m28s aws_iam_role.sagemaker: Destroying... [id=huggingface-sagemaker-role] aws_iam_role.sagemaker: Destruction complete after 1s Destroy complete! Resources: 6 destroyed. |
不要なリソースの削除
SageMakerによってS3バケットとCloudWatch Logsが生成されているため、必要に応じて削除します。
S3バケット
- sagemaker-ap-northeast-1-XXXXXXXXXXXX
CloudWatch Logs
- /aws/sagemaker/Endpoints/sagemaker-model-endpoint
- /aws/sagemaker/NotebookInstances
最後に
今回はSageMakerでHuggingFaceモデルをデプロイする方法を紹介しました。
次回はLambdaを接続し、WebAPIとしてデプロイする手順を公開しますのでお楽しみに。