【Stable Diffusion】Google ColabでLoRAファインチューニング!
〜Lora Trainer by Hollowstrawberryを使ってみた〜
今回は、Stable Diffusionのファインチューニングに関する体験談を共有したいと思います。
特に「Kohya Trainer」のColabサポート終了後、「Lora Trainer by Hollowstrawberry」を使って学習を行った流れや、独自に書いたinferenceコード、そして注意点についてまとめました。
背景:Kohya TrainerのColabサポート終了
最近、Google Colab向けのメンテナンスが終了してしまいました。これにより、手軽にColab上で学習できる手段を探していたところ、「Lora Trainer by Hollowstrawberry」に出会いました。
このツールは比較的新しく、シンプルながら必要な機能が揃っており、Colab環境でもスムーズに動作します。
学習環境:Lora Trainer by Hollowstrawberry
「Lora Trainer by Hollowstrawberry」は、セットアップも簡単で、GUIベースでLoRAのトレーニングができる非常に便利なツールです。
github.com
から、Open in colabを選択 (今回は、Lora Trainer)
基本的な流れは以下の通りです:
設定で、最低限設定するのは、project_nameのところ (ここが、google drive側のディレクトリと対応する)

今回は「zunda」にしました。
また、学習用のデータは、以下のページの「AI画像モデル用学習データ」を使いました。
zunko.jp
ダウンロードして、google driveの「Loras/zunda/dataset」に格納します。(bmpやpngが混じっていますが、どちらも対応しているのでそのままでOK。またtxtファイルも一緒におきます。)
推論コード
Lora Trainer by Hollowstrawberryには、学習したLoRAを適用するための推論(inference)コードがなさそう...
そのため、今回は、推論用のスクリプトを自分で書きました。
書いた内容は非常にシンプルで、通常のStable Diffusionの推論に、追加でLoRAを読み込むだけです。
また、注意点として、学習に使うベースのモデルは、合わせる必要があります。
今回は、animefull-final-pruned-fp16.safetensorsをベースとしているので、civitaiから拾ってきます。
!wget "https://civitai.com/api/download/models/481732" -O /content/animefull-final-pruned-fp16.safetensors
推論用のコード (これもcolabで動かしています。)
from google.colab import drive drive.mount('/content/drive') # Hugging FaceからPipelineを読み込み from diffusers import StableDiffusionPipeline from transformers import CLIPTextModel #from diffusers import StableDiffusionPipeline, CLIPTextModel import torch # ベースモデルを読み込む model_path ="/content/animefull-final-pruned-fp16.safetensors" text_encoder = CLIPTextModel.from_pretrained("runwayml/stable-diffusion-v1-5", subfolder="text_encoder", torch_dtype=torch.float16) # Load text encoder separately pipe = StableDiffusionPipeline.from_single_file( model_path, text_encoder=text_encoder, torch_dtype=torch.float16, safety_checker=None ).to("cuda") # LoRAファイルを指定(Google Driveとかにアップしておいて) lora_path = "/content/drive/MyDrive/Loras/zunda/output/zunda-10.safetensors" # LoRA適用 pipe.load_lora_weights(lora_path) # 生成プロンプト prompt = "zdm,masterpiece, 1girl, detailed face + light blue :1.5, nose, chestnut, ( jammers :1.2), closed mouth, looking at viewer, short hair , background: arcade , solo, upper body" negative_prompt = "Nsfw, nuidy, naked, blurry, bad anatomy, wrinkled skin, floating objects, mouth, depth of field, sharp focus, lowres, bad anatomy, nail polish, floating objects, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry" # 推論実行 image = pipe( prompt, negative_prompt=negative_prompt, num_inference_steps=30, guidance_scale=8, ).images[0] # 結果保存 image.save("/content/drive/MyDrive/zundamon.png") print("画像生成完了!")
結果は... (RoLAありだと、しっかり、ずんだもんになっている)


clineでOthelloゲームを作らせてみた話
最近、AIにコードを書かせる流行りにのって「一行もコードを書かずにゲームを作る」というチャレンジをしてみました。
使ったのは、AIツールの Cline (モデルは copilot-o3-mini)。
テーマは、「オセロ(Othello)」。
そして、その成果物がこちら
https://othellite.netlify.app/
最初はルールすら曖昧だった…
正直、オセロのルールは、ほとんど伝えない状態からスタートしました。
でもClineのすごいところは、「とりあえず指示を出してみると、動くものが返ってくる」ところ。
最初は
「オセロ作って、nextjsでよろしく」みたいな、ざっくりしたプロンプトを渡しただけ。
※実際の指示は、英語で行なっています。
当然、はじめはちょっと変なルールで動いてました(笑)
でも「ここは石をひっくり返すべきだよ」とか、「合法手がないときはパスして」とか、
細かくコメントしていくと、AIがどんどん改善していくのです。
人間は口を出すだけ。AIがやりきった。
このプロジェクトで自分がやったことは、
コードのレビューとフィードバックだけ。
実装、デバッグ、スタイル調整まで、全部AIがやってくれました。
※細かいところだと、gitのリポジトリ管理は、人間がやりました。
(一度大きく間違えたので、その時は、手動で切り戻しました。)
AIプレイヤーもついてる
ちなみに、このOthelloにはAIプレイヤー機能も搭載されています。
今のところは「なんちゃってAI」で、とりあえず動くレベルです。
(たまに変な手も打ちますが、それも愛嬌ということで…)
まとめ
今回、cline × copilot-o3-miniを使って、
完全にノーコードでゲーム開発をしてみましたが、
予想以上にスムーズにできて驚いています。
(所要時間は、1日くらいでした。)
今後はもうちょっと頭のいいAIプレイヤーに進化させていきたいところ。
よかったら遊んでみてください
https://othellite.netlify.app/
AWSのEC2上でblenderを動作させてNice DCVを使ってリモート接続する方法
はじめに
blenderなどの3Dアプリケーションを利用する場合、そこそこのマシンスペックが必要になります。
私は、Mac Book(ノートPC)を愛用しているのですが、少々厳しい(デュアルコアだし、GPUも無い)です。
そこで、AWS上のEC2でblenderを動作させ、Nice DCVで接続することにします。
blenderの公式サイトを参考にすると、2022年10月現在のオススメスペックは以下の通りです。
- 64-bit eight core CPU
- 32 GB RAM
- 2560×1440 display
- Three button mouse or pen+tablet
- Graphics card with 8 GB RAM
上記を満たすには、g4dn.2xlarge (8コア、32GbyteのRAM) 程度がちょうど良さそうですね。
設定手順
今回は、手元のmacから接続します。EC2インスタンスは、LinuxとWindowsが選択できますが、Linux側を選択します。
(ここは、慣れている方で良いと思います。)
ざっくりとは、以下の手順です。
- blenderとNice DCV設定済みのAMIからEC2インスタンスを起動
- ユーザ作成 (Nice DCVで接続した時のログイン用兼作業用)
- Nice DCVの設定ファイルの書き換え
- クライアントのインストール(既にインストール済みでなければ)
EC2インスタンスの起動
- AMIは、「Nimble Studio Linux Workstation AMI」を選択します。
- EC2インスタンスでは、Nice DCVで利用する8443ポートを空けておきます。
- EC2のIAMRoleを設定します。
- 以下の通り、Nice DCVのライセンスを取得できる様に設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::dcv-license.ap-northeast-1/*"
}
]
}
ユーザの作成
起動したEC2インスタンスにログインします(以下、SSHでログインしている前提)。
centosなので、amazon linuxのデフォルトユーザec2-userではなく、centosユーザでのログインとなります。
(1)ユーザを作成します。
sudo adduser [USER_NAME]
(2)パスワードを変更します。
sudo passwd [USER_NAME]
(3)wheelグループへ追加します。
sudo usermod -aG wheel [USER_NAME]
Nice DCVの設定
nice DCVの設定で、作成したユーザでのログインを許可します。
まずは、permissionファイルの作成します。
作成するpermissionファイル( /etc/dcv/permissions.conf としました)の内容は、以下の通りです。
[permissions] [USER_NAME] allow builtin %owner% allow builtin
※[USER_NAME]は、上で作成したユーザ名に置き換えてください。
次に、そのファイルを、設定ファイル (/etc/dcv/dcv.conf)で指定します。
[session-management/automatic-console-session] permissions-file='/etc/dcv/permissions.conf'
後は、dcvserverを再起動して、上の設定を読み込みます。
sudo systemctl restart dcvserver
Nice DCVクライアントのインストール
以下から、ご自分の端末に合うアプリをインストールしてください。
docs.aws.amazon.com
後は、Nice DCVクライアントを起動して、接続します。
ちなみに、グローバルIPを調べたい場合は、以下が便利です。
curl ifconfig.io
SSHのProxyCommandを利用してEC2インスタンスを自動で起動する設定
毎回、EC2インスタンスの起動・終了を実施するのは面倒です。
そこで、SSHのProxyCommandとSSMエージェントを利用し、sshコマンドを実行すると、自動でEC2が起動する様にします。
また、cron コマンドを利用して、一定時間操作がない場合は、自動で終了する様に設定します。
EC2インスタンスのIAM Roleの設定
- AWSにて、あらかじめ用意されている「AmazonSSMManagedInstanceCore」を追加します。
- これで、SSMが利用できる様になります。
- 注:SSMのエージェントは、上で選択したAMIでは、既にインストール済みのため、特段することはないですが、もしSSMエージェントがない場合は、追加します。
IAMユーザのプロファイルのポリシー
接続にいくユーザの権限として、以下を付与します。
インスタンスの起動や終了、ssm周りの権限を付与しています。
とりあえず、グローバルIPで絞っています。ころころ変わって面倒な場合は、MFA認証を必須にするなどでも良いと思います。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ec2:StartInstances",
"Resource": [
"arn:aws:ec2:ap-northeast-1:[AWS アカウントのID]:instance/[EC2インスタンスのID]",
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "[接続元のIPアドレス]" #ここは必要に応じて
}
}
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "[接続元のIPアドレス]"
}
}
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": "ec2:StopInstances",
"Resource": [
"arn:aws:ec2:ap-northeast-1:[AWSアカウントのID]:instance/[EC2インスタンスのID]",
]
},
{
"Sid": "VisualEditor3",
"Effect": "Allow",
"Action": "ssm:StartSession",
"Resource": [
"arn:aws:ec2:ap-northeast-1:[AWSアカウントのID]:instance/[EC2インスタンスのID]",
"arn:aws:ssm:ap-northeast-1::document/AWS-StartSSHSession"
]
},
{
"Effect": "Allow",
"Action": [
"ssm:TerminateSession",
"ssm:ResumeSession"
],
"Resource": [
"arn:aws:ssm:*:*:session/${aws:username}-*"
]
}
]
}
SSH ProxyCommandの設定
インスタンス起動のスクリプトの準備
まずは、インスタンスを起動するためのスクリプトを用意します。
ここでは、IAMユーザのプロファイルを「blender」としています。ここは、前の章のポリシーが付いていれば何でも良いです。
渡されたIDのインスタンスが「stopped」状態であれば、起動する様にしています。
(10秒待っているのは、wait_until_running関数だけだと、初回の接続に失敗することがあったためです。)
import time
import boto3
import argparse
class Agent():
def __init__(self,host):
self._TARGET_INSTANCE_ID = host
self._PROFILE_NAME = "blender"
self._REGION_NAME = "ap-northeast-1"
self._session = boto3.Session(profile_name=self._PROFILE_NAME)
self._ec2 = self._session.resource("ec2",region_name=self._REGION_NAME)
def start_instance(self):
target_instance = self._ec2.Instance(self._TARGET_INSTANCE_ID)
status = target_instance.state["Name"]
if status == "running":
return True
elif status == "stopped":
target_instance.start()
target_instance.wait_until_running()
time.sleep(10)
target_instance.reload()
return target_instance.state["Name"] == "running"
else:
return False
def stop_instance(self):
target_instance = self._ec2.Instance(self._TARGET_INSTANCE_ID)
target_instance.stop()
target_instance.wait_until_stopped()
target_instance.reload()
return target_instance.state["Name"] == "stopped"
def setup_arg_parser():
parser = argparse.ArgumentParser(description="start ec2")
parser.add_argument("--host",help="EC2 instance ID",required=True)
return parser
def main():
parser = setup_arg_parser()
args = parser.parse_args()
agent = Agent(host=args.host)
if agent.start_instance():
print("EC2 instance is running")
else:
agent.stop_instance()
print("Failed to start the instance")
if __name__ == "__main__":
main()上のpythonスクリプトは、setup_blender.pyとして保存しました。
次に、bashコマンドで、ラップしておきます。
pythonの環境をvirtualenvで設定しているので、環境を呼び出してから、pythonファイルを実行しているだけです。
#!/bin/bash HOST=$1 source [virtualenvを設定したパス]/bin/activate python [pythonファイルを置いたパス]/setup_blender.py --host $HOST
.ssh/configの設定
HOST blender_ec2
HostName [EC2インスタンスのID]
User centos
Port 22
IdentityFile ~/.ssh/[SSHのキー※インスタンス作成時に設定したもの]
ProxyCommand sh -c "[パス]/setup.sh %h; aws ssm start-session --profile blender --region ap-northeast-1 --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"※blenderと言うIAMユーザのプロファイルを作っています。
上記を全て設定すると、以下を実行するだけで、EC2インスタンスの起動を自動で実施してくれます。
ssh blender_ec2
一定時間操作がない場合、cronによりEC2インスタンスを自動で終了する
以下の、シャットダウンスクリプトを、cronで、5分置きに実行する様にしました。
動きとしては、5分して、ローカルからのsshセッションが1本も無ければ・・・
まずは、フラグを立て(/tmpにファイルを作成し)ます。
再び5分して、フラグが立った状態で、なおsshセッションが1本も無ければインスタンスを落とします。
なので、少なくとも5分は待ってくれて、最長でも10分以内に落としてくれる動きを想定しています。
#/bin/bash SESSION_COUNT=`w -h | grep localhost | wc -l` THERE_IS_NO_SESSION=/tmp/there_is_no_session if [ $SESSION_COUNT -gt 0 ] then rm -rf $THERE_IS_NO_SESSION elif [ -e $THERE_IS_NO_SESSION ] then rm -rf $THERE_IS_NO_SESSION sudo shutdown -h now else touch $THERE_IS_NO_SESSION fi
これを、cronに設定します。
*/5 * * * * /usr/local/bin/shutdown.sh
(/usr/local/bin/にshutdown.shとして置いています。)
AWS SESを使って独自ドメインのメールアドレスの受信と転送設定を行う
はじめに
(システムのテストなどで)メールアドレスが大量に必要になった時に、AWS SESは1つの解になるかも知れません。
ドメインの取得
- freenomなどで取得しても良いですし、Route53で直接取得しても良いです。
- 既に、Route53を使って、ドメインは取得(設定)済みとして進めます。
- 今回は、example.comと言うドメインを取得したことにします。
S3のバケットの取得
SESの設定からもバケットは作成することができますが、今回は予め作成しておきます。
- S3のコンソールから、「バケットを作成」を押して、好きな名前をつけます。
- SESのリージョンとS3のリージョンを一致させる必要はありません。
- SESは、受信機能の制限によりオレゴンを選択していますが、S3は、東京リージョンを選択しました。
- 作成したS3のバケットには、以下のバケットポリシーを設定しておきます。
- 自分のアカウントのSESからのputObject操作を受けつけると言う意味です。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSESPuts-1660060428208",
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::[ここにバケット名]/*",
"Condition": {
"StringEquals": {
"AWS:SourceAccount": "[ここにアカウントの番号]"
},
"StringLike": {
"AWS:SourceArn": "arn:aws:ses:*"
}
}
}
]
}
AWS SESの設定
(注意) メールを受信できるリージョンは、限られています。残念ながら22年8月12日現在では、東京リージョンは対象外でした。
詳細は、以下の「E メールの受信」の章をご確認ください。
docs.aws.amazon.com
リージョン選択
今回は、オレゴンで設定していきます。
- 右上のリージョン選択で、米国西部(オレゴン)を選びます。
- 左の「Configuration」メニューに「Email receiving」のメニューがあることを確認しておきます。
受信ルールの設定
次に、受信ルールを設定します。
- 左の「Configuration」メニューから「Email receiving」を選択します。
- リージョンが非対応の場合は、表示されていません。
- 「Create rule set」ボタンを押します。
- 「Create rule」 ボタンを押します。
- Rule nameは、なんでも良いので、自分にとってわかりやすい名前をつけて、「Next」ボタンを押します。
- 「Add new recipient condition」ボタンを押して、利用したいメールアドレスを入力します。
- ドメイン部分は、example.comを使うので、例えば、「hogehoge@example.com」などとします。
- 次ページでは、「Add new action」から「Deliver to S3 bucket」を選択します。
- S3 bucketに上で、バケットポリシーを設定したバケットを選択します。(バケットポリシーが間違っているとここで警告を受けます。)
- prefixは、お好みで。
- 次ページで、最終確認をして、画面下部の「Create rule」ボタンを押してルールを作成します。
ルールを作った後は、activateします。
- 先ほど作成したルールを選択して、「Set as active」ボタンを押します。
MXレコードの設定
- Route53のMXレコードに、以下のリンクで指定されているメールサーバを指定します。
(受信の場合、CUSTOM MAIL FROMは特に設定する必要はありません。送信の場合でも、MAIL FROMが気にならないのであれば、設定する必要はありません。)
転送設定
毎回、直接s3のファイルを読みに行くのは面倒なので、転送設定をしておきます。
Lambda関数の作成
- SESの受信設定を行なったのと同じリージョンで、Lambda関数を作成します。
Lambda関数の設定ロール
最低限、以下の権限を付与したロールを作成します。
- S3のオブジェクトの読み込み (今回、SESで受信したメールをS3へ書き込むので、読み出す権限が必要になる)
- SESを用いた送信 (メールを転送するためです)
具体的には、以下の様に設定します。
S3のポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[バケット名]/[プレフィックス]/*"
}
]
}SESのポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ses:SendRawEmail",
"ses:SendEmail"
],
"Resource": "arn:aws:ses:us-west-2:[アカウントID]:identity/*"
}
]
}※ses:SendRawEmailの方は、使わなければはずしてください(以下のlambda関数では使っていません。)
Lambda関数の内容
ざっくり以下の内容です。
「バケット名」「プレフィックス」「転送先のメールアドレス」となっている部分は、ご自身の内容に置き換えてください。
gmailへ転送してうまくいくことは確認しました。
lambda関数にライブラリを追加しなくて済む様に、デフォルトで入っているemailライブラリを使っています。
import json import boto3 from botocore.exceptions import ClientError from email import policy from email.parser import BytesParser def parse_eml(eml_data): try : msg = BytesParser(policy=policy.default).parsebytes(eml_data) charset = msg.get_body(preferencelist=("plain","html"))["content-type"].params["charset"] # parse text body text_content,text_charset = None,None text_body = msg.get_body(preferencelist=("plain")) if text_body is not None: text_content = text_body.get_content() text_charset = text_body["content-type"].params["charset"] # parse html body html_content,html_charset = None,None html_body = msg.get_body(preferencelist=("html")) if html_body is not None: html_content = html_body.get_content() html_charset = html_body["content-type"].params["charset"] return { "to" : msg["to"], "subject" : msg["subject"], "charset" : charset, "text_body" : {"Data":text_content,"Charset":text_charset} if text_body is not None else None, "html_body" : {"Data":html_content,"Charset":html_charset} if html_body is not None else None } # TODO : handle error except Exception as e: return None def build_body(parsed_data): body = { "Text" : parsed_data["text_body"], "Html" : parsed_data["html_body"] } if parsed_data["text_body"] is None: del body["Text"] if parsed_data["html_body"] is None: del body["Html"] return body def lambda_handler(event, context): # S3 params BUCKET_NAME = "バケット名" PREFIX = "プレフィックス" # SES params RECIPIENT = "転送先のメールアドレス" ERROR_CODE = { 'statusCode' : 500, 'body' : json.dumps('Internal Server Error') } try : s3_client = boto3.client("s3") ses_client = boto3.client("ses") for item in event["Records"]: source = item["ses"]["mail"]["source"] message_id = item["ses"]["mail"]["messageId"] s3_key = f"{PREFIX}/{message_id}" s3_response = s3_client.get_object( Bucket = BUCKET_NAME, Key = s3_key ) eml_data = s3_response["Body"].read() parsed_data = parse_eml(eml_data) ses_client.send_email( Source = parsed_data["to"], Destination = { "ToAddresses":[RECIPIENT] }, Message={ 'Subject': { 'Data': f'Fw: {parsed_data["subject"]}', 'Charset': parsed_data["charset"] }, 'Body': build_body(parsed_data) } ) break # TODO : Handle Error except KeyError: return ERROR_CODE except ClientError: return ERROR_CODE else: return { 'statusCode': 200, 'body': json.dumps('OK') }
SESからのLambda関数の呼び出し
Raspberry Pi4でpythonの環境を作ってOpenCVをインストールするまで
Raspberry Piの購入
MacBook(USB-C対応)で作業する前提で記載します。
この場合、必要な物品は以下の4点です。
- Raspberry Pi (本体)
- SDカード
- SDカードライター
- USB-Cのケーブル
(最近は、Macを買うと電源などでUSB-Cのケーブルが付いていることが多いので周りを見渡せばあるでしょう)
(Raspberry Piは、USBで結線すれば、給電およびsshのログインが可能です。)
ですので、Raspberry Pi用のケースや電源ケーブルは、お好みで大丈夫です。
Raspberry Piの本体は、amazonなどでも売っていますが、今回は正規代理店のKSY経由で、
Pi4のメモリが8Gbyte版を購入しました。
SDカードの選び方は、以下のブログが詳しいです。
blog.soracom.com
Raspberry Piのセットアップ
以下がスタートページで、ここからRaspberry Pi Imagerをダウンロードします。
www.raspberrypi.org
32bit OSが安定版ですが、8Gbyte版を買った身としては、32bitだとメモリが勿体無いことになるので、以下の64bit版をインストールします(今回は、GUIが要らないのでLiteを選択しました)。
www.raspberrypi.org
ダウンロードしたImagerを開き、「Use custom img」から上でダウンロードした64bit版のRaspberry Piを指定して書き込みます。
Raspberry PiとMacの接続
基本設定
SSHでログインするために、Raspberry PiのSSHサーバを有効にします。
先ほどRaspberry Pi OSを書き込んだSDカードが、/Volumes/boot配下にmountされているとすると、以下のコマンドで空のファイルを作成します。
touch /Volumes/boot/ssh
WiFiに接続するのであれば、WiFiの設定をして終わりです。
しかし、今回はMacからUSB-Cで有線接続するため、以下の設定(1)(2)を行います。
(1)/Volumes/boot直下のconfig.txtの最後に、以下を追記します。
dtoverlay=dwc2
(2)/Volumes/boot直下のcmdline.txt の途中(rootwaitとquietの間)に以下を追記します。
modules-load=dwc2,g_ether
パスワードログインの廃止と証明書ログイン
初期設定では、以下でコマンドの後(ユーザ名が「pi」、ホストがraspberrypi.local)、
パスワードとして、「raspberry」を入れるとログインできます。
ssh pi@raspberrypi.local
この状態だと、raspberrypiにパケットが届く人が全員ログインできてしまうので、証明書を使った認証に切り替えます。
(隣の席に悪戯っ子が座っていると、slコマンドを入れられてしまったりします)
(1)証明書を作成する
以下のコマンドで、コマンドを実行した直下に、秘密鍵(raspi01)と公開鍵(raspi01.pub)が作成されます。
※fオプションを外すと、デフォルトの位置(/Users/ユーザ名/.ssh 配下)に作成してくれます。
※passphraseをなしにする場合は、2回enterキーを叩きます。
(2)秘密鍵を配置する
以下で、所定のディレクトリへ移動します。
mv raspi01 raspi01.pub ~/.ssh
※公開鍵を移動させなくても動きますが、無くすと面倒なので忘れない所に保存しておいた方が良いと思います。
秘密鍵の権限を自分だけが読み取れる様に書き換えます。
chmod 600 ~/.ssh/raspi01
(3)公開鍵を配置する
以下のコマンドで、Raspberry Pi側へ公開鍵を配置します。
(パスワードは、変えていなければ「raspberry」です。)
(4)証明書でログインできることを確認する
以下でログインできれば、問題なく設定できています。
(5)パスワードのログインを無効化する。
(Raspberry Piにログインして)/etc/ssh/sshd_config に以下を追記します。
※ 既に、該当行がコメントされているはずなので、以下の様に書き換えます。
PasswordAuthentication no
普通に開くと、read onlyなのでsudoで実行します。
その後、Raspberry Piを再起動します。
sudo reboot
(6)パスワードでログイン出来なくなっていることを確認する。
ssh pi@raspberrypi.local
だと、こんな感じに弾かれる様になりました。
pi@raspberrypi.local: Permission denied (publickey).
※外部に公開する時は、ちゃんとセキュリティを固めましょう。あくまで隣人対策です。
Raspberry Piからの(Macを介した)インターネット接続
(Macを介してインターネットに接続します。)
システム環境設定 > 共有 > インターネット共有 を選び
「RNDIS/Ethernet Gadget」にチェックボックスを入れます。
Python環境の構築
Python環境の構築前に、ラズパイ自体をアップデートします。
sudo apt update
sudo apt upgrade -y
sudo reboot
pythonは、既に入っているはずなので、以下で、pipだけインストール/アップデートします。
sudo apt install python3-pip -y
pip3 install --upgrade pip
jupyterのインストール
pip3 install jupyter
ただ、このままだと、Macからラズパイ上で動いているJupyter-notebookに接続できません。
そこで、以下のコマンドで、configファイルを作成します。
jupyter notebook --generate-config
~/..jupyter/jupyter_notebook_config.py の以下の箇所で、リモートのIPを許可します。
## The IP address the notebook server will listen on. # Default: 'localhost' c.NotebookApp.ip = '*'
後は、いつものコマンドでjupyterを起動します。
jupyter-notebook [jupyterで使うディレクトリ]
virtualenvの設定
virtualenvの環境を構築し、プロジェクトごとにpythonのライブラリの依存関係が混らない様にします。\
pip3 install virtualenv
(sudo reboot) # 必要に応じてリブート
※同じものを指しているので上はpipでも可 (whichコマンドで調べてください)
例えば、PoseNet用の環境だと、以下の様な感じです。(例は、「pose_net」名前で作っています)
virtualenv pose_net
source pose_net/bin/activate
※環境外に出る場合は、deactivateです。
OpenCVのインストール
OpenCVのインストールは、以下が詳しいです。
64bit版なので、こちらを参照します。
qengineering.eu
ドキュメントの前の方は、設定の確認になります。
当該のスクリプト(のDLと実行方法)は、「Installation script」の章にありますので、そちらを実施します。
(注意:当該スクリプトの実行には、1時間以上かかりました)
32bit版の場合は、以下です(こちらは、以前、別の32bit版のラズパイで試して動きました)。
qengineering.eu
python(のインタプリタ)を起動して、OpevCVがimportできれば大丈夫です。
>>> import cv2
以上で、Macからラズパイ上のJupyterに接続して、画像解析ができる様になりました。
Raspberry Piの終了
SSHで接続した状態で以下のコマンドで終了
> sudo shutdown -h now
SSHの小技集(ログインからポートフォワード、Pythonを利用した自動化まで)
はじめに
MacBook Proとモバイル回線で作業をする様な場合に良く利用するSSHの使い方です。また、サーバー側は、awsの利用を想定しています。
設定編
モバイル回線なので、デフォルトの設定だと頻繁に接続が切れます。
そんな時は、~/.ssh/configに以下の設定を記載します。
これで10秒ごとにパケットを流して接続を維持しようとしてくれます。
(勿論、切られることもあるので、そんな時はscreenなりを活用します。)
ServerAliveInterval 10 TCPKeepAlive yes
基本
ログインしたいサーバが、インターネットに公開されている場合は、以下のように単発のコマンドでログインします。
(証明書での認証を想定しています。)
ssh -i ~/.ssh/<i>foo.pem</i> <i>user</i>@<i>hostname</i>
図にすると以下の様になります。
踏み台の先にあるサーバにログインする
ログインして作業したいサーバが踏み台の先にある場合、一旦、踏み台にログインした後、さらに目的のサーバにログインするのは、面倒なことがあります。そういう場合は、以下の様に
~/.ssh/configファイルに記載しておくことで、自動化されます。
具体的には、ssh target_server とコマンドを打つだけで、自動でJUMP_SERVERを経由してtarget_serverに接続されます。
HOST [target_server]
HostName [IP address or host name (1)]
User [user name(1)]
ProxyCommand ssh -W %h:%p JUMP_SERVER
IdentityFile ~/.ssh/foo.pem
HOST JUMP_SERVER
HostName [IP address or hostname (2)]
User [user name(2)]
IdentityFile ~/.ssh/bar.pem※target_serverは、自分の覚えやすい名前に変更してください
※[IP address or host name(1)] は、target_serverのものです。
※[IP address or host name(2)]は、踏み台サーバ(jump server)のものです。
※JUMP_SERVERの所は、字面が一致していれば、なんでも良いです。
踏み台だけをパブリックサブネットに置いて、プライベートサブネット内のワーカーにログインするのは、良く利用するパターンなので、上記の設定で2度ログインする手間を省けます。
ポートフォワード
セキュリティを考慮して、データベース(RDS)やElasticSearchは、外部からは直接接続できない様にすることが普通です。勿論、前述の通り、踏み台を経由して、プライベートサブネット内のマシンにログインして、開発をすることも可能です。しかし、効率面を考慮すると、極力ローカルの慣れたエディタを使いたいケースが多いと思います。
そこでオススメなのが、ポートフォワードです。
例えば、以下の様に、データベースとローカルマシンの間にトンネルを作っておくと、データベースへのアクセスを透過的に扱えます。
これで、踏み台をトンネルして、データベースサーバの5439ポートをローカルの5439ポートへバインドします。
(5439は、Redshiftのデフォルトポートです。適宜バックエンドで動作しているDBのポートに置き換えてください)
ssh -N -i ~/.ssh/[踏み台へ接続するための秘密鍵] -L 5439:[データベースサーバのアドレス]:5439 [ユーザ名]@[踏み台のアドレス]
PythonからSSHで踏み台サーバへログインしてプライベート領域のDBへ接続
上記は、トンネリングはあくまで手動で実施することを想定していました。
ただ、全てをプログラムで制御したい場合も往々にして存在します。
特に、AWS lambdaと組み合わせると、ローカルPCのcronに対するクラウドのcronのごとく、一定間隔で様々な処理が実施できて大変便利です。
では、早速Pythonでのやり方を見ていきたいと思います。
前準備
- 以下のコマンドで、sshtunnelをインストールします。
- pip install sshtunnel
- また、DB接続用のライブラリも好みのものをインストールします(例では、psycopg2)。
コードスニペット
上で出てきた踏み台サーバを経由してバックエンドのDBへアクセスするサンプルは以下の通りです。
必要に応じて、wait(スニペットでwaitのコメントを付けている部分)を入れて接続が整うのを待ってください。
from sshtunnel import SSHTunnelForwarder import psycopg2 as psy2 server = SSHTunnelForwarder( (踏み台サーバのアドレス, 22), ssh_host_key=None, ssh_username=踏み台サーバのユーザ名, ssh_password=None, ssh_pkey=踏み台サーバのSSH秘密鍵, set_keepalive=10.0, remote_bind_address=(データベースのアドレス, 5439) ) server.start() con = psy2.connect( dbname = データベース名, user = データベースのユーザ名, password = パスワード, host = "localhost", port=server.local_bind_port, async_=True(非同期) or False(同期) ) while True : #wait state = con.poll() time.sleep(1) if state == psy2.extensions.POLL_OK: break cur = con.cursor() cur.execute(任意のSQL文) con.close()
その他
- ファイルのやりとりは、上で述べてきたsshコマンドをsftpコマンドに置き換えることで可能です。
AWS Lambdaでpandasを利用する(Lambda Layers編)
概要
AWSのlambda関数で、外部のライブラリを実行したい場合、昔は、デプロイパッケージに、利用するコードを全て含めてやる必要がありました。
ただ、pandasやscikit-learnなどの重量級のライブラリの場合、毎回アップロードするのは、きついものがあります。
例えば、pandas(依存関係のあるnumpyなども含む)だと、圧縮状態で35Mbyteほどあります。
そこで、AWS Lambda layersの出番です。
これを使うことで、外部ライブラリのアップロードは、1回で済む様になり、自分で書いた部分のみを更新していけば良いことになります。
また、他のlambda関数でも使い回し可能です。
docs.aws.amazon.com
手順
以下、pandasを例にした手順です。
- とりあえず、lambda関数の実行環境を確認します。
- 19年2月現在は、以下の通りでした。
- Python 3.7.2
- 19年2月現在は、以下の通りでした。
- lambdaと実行環境を合わせたEC2を作成します。
- EC2へログインして、調べておいたlambdaで実行されるバージョンのpython(今回は、3.7.2)をインストールします。
- python.orgからソースを取得します。
- 解凍して、解凍した先のフォルダへ移動します。
- 必要なライブラリをインストールします。
- コンパイルしてインストールします。
- ./configure --enable-optimizations
- sudo make altinstall
- pandasをインストールするディレクトリ「python/lib/python3.7/site-packages/」を作成します。
- 利用したいライブラリ (この場合は、pandas)を上で作ったディレクトリへインストールします。
- pip3.7 install pandas -t ./python/lib/python3.7/site-packages/
- 「-t」オプションで、書き出し先を指定しています。
- pip3.7 install pandas -t ./python/lib/python3.7/site-packages/
- zipで固めます。
- これをLambda layersとして、publishしておきます (が、上のAMIのawsコマンドは古いのでアップデートが必要です。)
- 利用したいlambda関数と紐つけます。
以上で、lambdaでpandasを利用する準備が整いました。
最後に、pandasの使用例と実行結果を貼り付けておきます。
import sys; import json; import pandas as pd; def lambda_handler(event,context): df = pd.DataFrame([[1,2,3],[4,5,6]]); print(df); return { "statusCode": 200, "body": json.dumps('Hello pandas!'), "version": sys.version, "path": sys.path };
