信頼はずっと、挑戦はもっと。

お問い合わせ
TEL:03-3496-3888

BLOG コアテックの社員ブログ (毎週月曜~金曜更新中)

LIST OF ARTICLES

記事一覧

  • テクログ

    お話アプリ2

    こんにちは。前回の記事の続きになりますが、時間が空いてしまいだいぶ忘れました。こちらの記事の実動作を目指します。https://qiita.com/kenchin110100/items/b34f5106d5a211f4c004実際に文章を生成するところからだったと思いますが、忘れているので、応答処理実行で作成したソースを以下に記載します。'train'が実行なのですが、訓練ではないですね。。。動作は保証しません。# !curl https://colab.chainer.org/install | sh - # coding: utf-8 """ Sequence to Sequenceのchainer実装 Sutskever, Ilya, Oriol Vinyals, and Quoc V. Le., "Sequence to sequence learning with neural networks.", Advances in neural information processing systems. 2014. """ import numpy as np from chainer import Chain, Variable, cuda, functions, links, optimizer, optimizers, serializers import datetime # from filer2 import Filer # import glob import random import sys import MeCab as mcb import h5py #numpyの結果を省略しない np.set_printoptions(threshold=np.inf) class LSTM_Encoder(Chain):     def __init__(self, vocab_size, embed_size, hidden_size):         """         クラスの初期化         :param vocab_size: 使われる単語の種類数(語彙数)         :param embed_size: 単語をベクトル表現した際のサイズ         :param hidden_size: 隠れ層のサイズ         """         super(LSTM_Encoder, self).__init__(             xe = links.EmbedID(vocab_size, embed_size, ignore_label=-1),             eh = links.Linear(embed_size, 4 * hidden_size),             hh = links.Linear(hidden_size, 4 * hidden_size)         )     def __call__(self, x, c, h):         """         :param x: one-hotな単語         :param c: 内部メモリ         :param h: 隠れ層         :return: 次の内部メモリ、次の隠れ層         """         e = functions.tanh(self.xe(x))         return functions.lstm(c, self.eh(e) + self.hh(h)) class LSTM_Decoder(Chain):     def __init__(self, vocab_size, embed_size, hidden_size):         """         クラスの初期化         :param vocab_size: 使われる単語の種類数(語彙数)         :param embed_size: 単語をベクトル表現した際のサイズ         :param hidden_size: 隠れ層のサイズ         """         super(LSTM_Decoder, self).__init__(             ye = links.EmbedID(vocab_size, embed_size, ignore_label=-1),             eh = links.Linear(embed_size, 4 * hidden_size),             hh = links.Linear(hidden_size, 4 * hidden_size),             he = links.Linear(hidden_size, embed_size),             ey = links.Linear(embed_size, vocab_size)         )     def __call__(self, y, c, h):         """         :param y: one-hotな単語         :param c: 内部メモリ         :param h: 隠れそう         :return: 予測単語、次の内部メモリ、次の隠れ層         """         e = functions.tanh(self.ye(y))         c, h = functions.lstm(c, self.eh(e) + self.hh(h))         t = self.ey(functions.tanh(self.he(h)))         return t, c, h class Seq2Seq(Chain):     def __init__(self, vocab_size, embed_size, hidden_size, batch_size, flag_gpu=True):         """         Seq2Seqの初期化         :param vocab_size: 語彙サイズ         :param embed_size: 単語ベクトルのサイズ         :param hidden_size: 中間ベクトルのサイズ         :param batch_size: ミニバッチのサイズ         :param flag_gpu: GPUを使うかどうか         """         super(Seq2Seq, self).__init__(             # Encoderのインスタンス化             encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),             # Decoderのインスタンス化             decoder = LSTM_Decoder(vocab_size, embed_size, hidden_size)         )         self.vocab_size = vocab_size         self.embed_size = embed_size         self.hidden_size = hidden_size         self.batch_size = batch_size         # GPUで計算する場合はcupyをCPUで計算する場合はnumpyを使う         # if flag_gpu:         #     self.ARR = cuda.cupy         # else:         #     self.ARR = np         self.ARR = np     def encode(self, words):         """         Encoderを計算する部分         :param words: 単語が記録されたリスト         :return:         """         # 内部メモリ、中間ベクトルの初期化                   c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))         h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))         # エンコーダーに単語を順番に読み込ませる         for w in words:             c, h = self.encoder(w, c, h)         # 計算した中間ベクトルをデコーダーに引き継ぐためにインスタンス変数にする         self.h = h         # 内部メモリは引き継がないので、初期化         self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))     def decode(self, w):         """         デコーダーを計算する部分         :param w: 単語         :return: 単語数サイズのベクトルを出力する         """         t, self.c, self.h = self.decoder(w, self.c, self.h)         return t     def reset(self):         """         中間ベクトル、内部メモリ、勾配の初期化         :return:         """                self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))         self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))         self.zerograds() def forward(enc_words, model, ARR, id_to_word, question):     """     順伝播の計算を行う関数     :param enc_words: 発話文の単語を記録したリスト     :param dec_words: 応答文の単語を記録したリスト     :param model: Seq2Seqのインスタンス     :param ARR: cuda.cupyかnumpyか     :return: 応答文     """     enc_words = make_minibatch(enc_words)     # エンコードの計算 ①     model.encode(enc_words)         # <eos>をデコーダーに読み込ませる ②     k = ARR.array([0 for _ in range(1)], dtype='int32')     # デコーダーの計算     loop = 0     before_k = 1 #とりあえす初期値として1     answer_word = ''     while (before_k != 0) and (loop <= 30):         # 1単語ずつをデコードする ③         # print(k)         y = model.decode(k)         u = functions.softmax(y)         ul = u[0].data #2次元配列になっているので1次元に。                  # # np.random.choiceでエラーが出るので正規化。float64にするとエラーが出ない         # ul = ul.astype(np.float64)         # ul /= ul.sum()         # # 確率分布に従いランダムで1つチョイス。         # p = ul.tolist()         # before_k = np.random.choice(len(p), size=1, replace=True, p=p)[0]         before_k = np.argmax(u[0].data) #一番確率の高いものはこちら         answer_word += id_to_word[before_k]         k = Variable(ARR.array([before_k], dtype='int32'))         loop += 1          answer_word = answer_word.replace("<eos>", "")          return answer_word # trainの関数 def make_minibatch(minibatch):     # enc_wordsの作成     enc_words = minibatch     enc_max = np.max([len(row) for row in enc_words])#一番長い文は何単語か。     enc_words = np.array([[-1]*(enc_max - len(row)) + row for row in enc_words], dtype='int32')#単語がないところは-1埋めで     enc_words = enc_words.T     return enc_words def train(question):     utt_file = 'Utterance_wakati.txt'     rtt_file = 'Response_wakati.txt'     ulines = open(utt_file).read().split('\n')#テキストを1行ずつ取りだして配列に     rlines = open(rtt_file).read().split('\n')#テキストを1行ずつ取りだして配列に     jvocab = {}     id_to_word = {}     jvocab['<eos>'] = len(jvocab)     id_to_word[len(id_to_word)] = '<eos>'          for i in range(len(ulines)):#jlinesの数は1500         ltt = ulines[i].split()#引数を省略したデフォルトでは空白文字で分割。#1行の文章を文字ごとの配列にする。         for w in ltt:             if w not in jvocab:                 jvocab[w] = len(jvocab)                 id_to_word[len(id_to_word)] = w                   #jvocabはword_to_id     for i in range(len(rlines)):#jlinesの数は1500         rtt = rlines[i].split()#引数を省略したデフォルトでは空白文字で分割。#1行の文章を文字ごとの配列にする。         for rw in rtt:             if rw not in jvocab:                 jvocab[rw] = len(jvocab)                 id_to_word[len(id_to_word)] = rw              # jvocabはword_to_id     vocab_size = len(jvocab)     # モデルのインスタンス化     model = Seq2Seq(vocab_size=vocab_size,                     embed_size=EMBED_SIZE,                     hidden_size=HIDDEN_SIZE,                     batch_size=BATCH_SIZE,                     flag_gpu=FLAG_GPU)     serializers.load_hdf5('EMBED300_HIDDEN150_BATCH40_EPOCH30.weights', model)     model.reset()     ent = question     mecab = mcb.Tagger("-Owakati")     jln = mecab.parse(ent)     # jln = mcb.construct_BoW(utterance)     jln = jln.split()#一行の文章を配列化     jl_ids = []     for jln_word in jln:         jl_ids.append(jvocab[jln_word])        enc_words = [jl_ids]     return forward(enc_words=enc_words,model=model,ARR=np,id_to_word=id_to_word,question=question) FLAG_GPU = False EMBED_SIZE = 300 HIDDEN_SIZE = 150 BATCH_SIZE = 40 EPOCH_NUM = 30 username='Seq2Seq' Djangoではanswer = train(request.POST['question'])みたいな感じで実行します。身の丈にあわない、背伸びしてつくったものですが、動いた時はうれしかったです。この実装に限らず、プログラムが動いたときはいつもうれしいですね。
  • テクログ

    slackコマンドっぽいことをchatworkでしたい!

    そろそろ夏も終わりですね。はやく涼しくなってほしいですね。そういう気持ちからslackコマンドっぽいことをchatworkでしたい!ということになりました。-----------------------◆本当の経緯もとからslackでのchatops的なものは結構やっていたんですが、弊社slackだけではなくchatworkも使うんですね(ぼやかしで、chatworkもWebhookがあって、slackよりも機能や設定できることは少ないけど、なんとか似たことができそうだな、となったので作りました。-----------------------◆構成chatwork→APIGateway→Tokenやコマンドを検証し、振り分けるLambdaFunction→(SNS)→コマンドの実態LambdaFunction+chatworkへ結果返答わかりやすくするためTokenやコマンドを検証し、振り分けるLambdaFunction を [振り分けLambda]コマンドの実態Lambda を [実態Lambda]とします-----------------------◆設定を簡単に解説細かい設定は書きませんが、いろいろ試行錯誤するのも経験のうちですよね。(1)APIGatewayを作成chatworkのWebhookが叩く入り口です。URLがあればひとまずOKです。パス設定とかはおまかせします。(2)[振り分けLambda]の殻を作成APIGatewayの先のLambdaを作成します。(3)APIGatewayを設定(1)と(2)をつなぎまーす叩くとレスポンスが来るだけ確認しておくのもいいですね。(4)chatworkのWebhook設定chatworkにボットアカウントを作り、そのアカウントでWebhookの設定をします参考:https://help.chatwork.com/hc/ja/articles/115000169541-Webhook%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8BURLはAPIGatewayのURLで、イベントはルームイベントにしました。(なぜなら、目に見えないところではなく、その部屋だけで使ってほしいからです。)メッセージ作成のみ、にしました。(5)[振り分けLambda]を実装ソース!だよ!何もライブラリが要らないってのを重視。なんか貼ったら改行があれなのでまあそこはいいかんじにしてください。何かあってもこちらはサンプルですのでそこはいつものとおりご了承ください。見直すと入ってきた文字整形とか無理矢理感。chatworkはクライアントごとにメッセージの形式が微妙に違ったりしてね…必要な環境変数はCHATWORK_API_TOKEN (KMSで暗号化してある、postに使うchatworkのTokenWEBHOOK_TOKEN (渡ってきたchatworkのメッセージが正しいものかを判定するためのTokenです。# -*- coding: utf-8 -*- # chatwork commanderの入り口 import boto3 import json import logging import os import sys from datetime import datetime from botocore.exceptions import ClientError import copy import urllib.request, urllib.error from base64 import b64decode, b64encode import hmac import hashlib valid_command_list = ['こまんど1','こまんど2','こまんど3'] to_str = "[To:1111111111]" usage_str = "【コマンド一覧】\n" + \ "※それぞれのコマンドの使い方については[コマンド usage]を打って確認してください\n\n" + \ "こまんど1\n" + \ "こまんど2\n" + \ "こまんど3\n" SNS_ARN_PREFIX = "arn:aws:sns:ap-northeast-1:11111111111:chat_command_" #### #メイン #### def lambda_handler(event, context):   #先にイベントは取っておく(判定とかに使うから   eventjson = json.loads(json.dumps(event))   bodyjson = json.loads(eventjson['body'])   room_id    = str(bodyjson['webhook_event']['room_id'])   message_id = str(bodyjson['webhook_event']['message_id'])   body      = str(bodyjson['webhook_event']['body'])   account_id = str(bodyjson['webhook_event']['account_id'])   #まず   #[To:1111111111]   #がないなら無視(全メッセージ流れてくるから   if not to_str in body:     print('not me.END')     return {'statusCode': 200,'body': json.dumps('not me.END')}   #毎回kms使うのがやだからここでやってます   ENCRYPTED_WEBHOOK_TOKEN = os.environ['WEBHOOK_TOKEN']   WEBHOOK_TOKEN = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_WEBHOOK_TOKEN))['Plaintext']   ENCRYPTED_CHATWORK_API_TOKEN = os.environ['CHATWORK_API_TOKEN']   CHATWORK_API_TOKEN = boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_CHATWORK_API_TOKEN))['Plaintext']   #Tokenの確認   if check_request(event, WEBHOOK_TOKEN) == True:     print("Token OK")     #body整形     command_text = body.replace('\n', '').replace('[To:1111111]', '').replace('aaaaa さん', '').replace('aaaaaさん', '').strip()     command_args = command_text.split(" ")     #コマンドの確認     command = command_args[0]     nextStatus = checkCommandStrAndMakeNextStatus(command, room_id, account_id, message_id, command_text, CHATWORK_API_TOKEN)     #nextStatusがTrue(続行)の場合、次のlambdaをSNSで呼ぶ     #続行ではない場合は特に何もせずreturnして終わり     if nextStatus == True:       message = {"room_id"   : room_id,             "command_args": command_args,             "account_id" : account_id,             "message_id" : message_id       }       #SNS_ARN_PREFIX+command がARNになるようになっている       sendSNS(message, SNS_ARN_PREFIX+command)     return {       'statusCode': 200,       'body': json.dumps('OK')     }   else:     print("Token NG")     return {       'statusCode': 403,       'body': json.dumps('NG')     } ##----------------------- def sendSNS(message, sns_TOPIC):   sns_client = boto3.client(     'sns'   )   #メッセージ整形   message=json.dumps(message)   message=json.dumps({'default': message, 'lambda': message})   response = sns_client.publish(     TopicArn=sns_TOPIC,     Subject='/lambda',     MessageStructure='json',     Message=message   )   print(response) #### #コマンドの内容を確認してchatwork通知 #また、今後の続行ステータスを返す #### def checkCommandStrAndMakeNextStatus(command, room_id, account_id, message_id, command_text, CHATWORK_API_TOKEN):   if command == 'usage':     postChatwork(usage_str,room_id, CHATWORK_API_TOKEN)     return False#終了   else:     if not command in valid_command_list:       postChatwork("対象コマンドがありません。\n"+usage_str,room_id, CHATWORK_API_TOKEN)       return False#終了     else:       #chatworkに受付状況を通知       postChatwork(         makeReplyMessage(account_id, room_id, message_id, command_text),         room_id,         CHATWORK_API_TOKEN       )       return True#続行 #### #メッセージをつけるだけ #### def makeReplyMessage(account_id, room_id, message_id, command_text):   return "コマンド:" + command_text + "\nを受け付けました。(コマンドの実態に渡しました)\nお待ち下さい。" #### #chatworkに通知 #### def postChatwork(message, room_id, CHATWORK_API_TOKEN):   print("in postChatwork")   END_POINT_BASE = "https://api.chatwork.com/v2"   END_POINT   = "/rooms/"   ACTION     = "/messages"   headers = { 'X-ChatWorkToken': CHATWORK_API_TOKEN }   data = { 'body': message }   data = urllib.parse.urlencode(data)   data = data.encode('utf-8')   # リクエストの生成と送信   post_message_url = END_POINT_BASE + END_POINT + room_id + ACTION   request = urllib.request.Request(post_message_url, data=data, method="POST", headers=headers)   with urllib.request.urlopen(request) as response:     response_body = response.read().decode("utf-8")     print(response_body) #参考 #https://qiita.com/ikedaosushi/items/b01183f207ea1db6a8d3 def check_request(event, WEBHOOK_TOKEN):   webhook_sig = event['headers']['X-ChatWorkWebhookSignature'].encode('utf-8')   sig = build_sig(event['body'], WEBHOOK_TOKEN)   is_valid = webhook_sig == sig   return is_valid def build_sig(body, WEBHOOK_TOKEN):   token_decode = b64decode(WEBHOOK_TOKEN)   sig = hmac.new(token_decode, body.encode('utf-8'), hashlib.sha256).digest()   sig_encoded = b64encode(sig)   return sig_encoded だいたいでいえば・メッセージはルームのものがすべて来るので、処理対象メッセージなのかを判定・tokenの確認・コマンドの確認・受け付けたことを返答・SNSで次のLambdaを呼ぶ(このSNSを一定の形式に統一することによって、後ろのコマンドを増やしやすくなってます。(6)実態Lambdaを呼ぶためのSNSを作るarn:aws:sns:ap-northeast-1:11111111111:chat_command_ ってなっているようにSNSをまず作りますたとえばmake_EC2みたいなコマンドだった場合、SNS名はchat_command_make_EC2になりますね。(7)[実態Lambda]を作るここは自分でつくってね!SNSを受け取って、分解して、処理をすればいいです。終わったらchatworkに通知したり、いろいろ内部でやればいいですね。夢が広がる。(8)SNSの先に[実態Lambda]を追加まあ追加するだけですあとはchatworkでボットにはなしかけたりしてテストをしてみよう!-----------------------コマンドを増やすときも、許可するコマンドを追加して、SNS作成コマンド実態Lambda作成だけでぽんぽんふえるよ!ルームを分けることによって、重要度ごとに使える人を分けられたりもします。以上夏休みの自由研究にどうぞ。もうおわってますか。-----------------------<以下はただの一覧用の画像でーす>
  • テクログ

    お話アプリ

    今年のGWは10連休でしたね。個人的に興味のあった機械学習による会話アプリを試してみました。「ありがとう」と文字を入力しますと「どういたしまして」のように返答文を考えて、返してくれるアプリです。以下のqiitaの記事を実動作させることを目指します。日本発の機械学習フレームワークchainerでの実装ですね。Seq2Seqといった「ある文章A」⇨「ある文章B」に変換する技術になります。https://qiita.com/kenchin110100/items/b34f5106d5a211f4c004独学で本当に正しいやり方かは保証できませんが以下に試したことを記載します。日本語の語句ごとの分解はMeCabを使用します。pipで入れます。MeCabで生成した際、応答側のテキストは各応答文末に、文の終わりを示すため'<eos>'を入れます。学習にはGoogleのColaboratoryを使用します。Colaboratoryを使用する理由は、設定を変えると無料で(時価70万円相当!?)ハイスペックGPUを使用できるからになります。CPUでも学習は可能ですが遅いです。。Colaboratoryによるファイルのアップロードダウンロードがわかりにくいのですが、以下を実行する感じです。□アップドーロ-----------------from google.colab import filesuploaded = files.upload()-----------------□ダウンロード-----------------from google.colab import filesdownloaded = files.download('{ファイルのお名前.weights}')-----------------□ちゃんとアップロードできたか確認%%bashlsここでの学習のポイントは学習はGPUで行いますが、重みの書き出しはCPU用に変換します。webアプリ化のため機械学習したモデルをDjangoからつなぎ込みますが、ここでの文章生成の推論はCPUで行うとになると思います。-----------------model.to_cpu()serializers.save_hdf5(outputpath, model)model.to_gpu(0)#また処理をGPUに戻してあげる。-----------------またtotal_lossをprintして確認しながら進めました。損失が減っていくとそのデータに対しての学習がうまくいっていることになります。機会がありましたら、続きの学習した重みとモデルを使用して実際に文章を生成するところを記載したいと思います。
  • テクログ

    コスト配分タグを自動でチェックしたい!

    恒例のしたいシリーズです。いまつくりました。●なにがしたいの?AWSアカウント内に複数の媒体があって、どの媒体にどれだけお金がかかっているかをチェックするために、リソースにProjectタグをつけると簡単に集計できます。でも、リソースは随時増えるし、ちゃんとProjectタグついているのかわからない。なんとかしたいという気持ち。●どうやるの?LambdaでリソースにProjectタグがついているかをチェック。+Projectタグの値もチェック(ちがうのがついてたらこまるからね)チェックしない、というのも設定したい。あと、結果はSlackに通知したりしたい。●具体的には?ソースをはりますんで見てね!EC2のタグチェックの例です。サンプルですよ。・追加ライブラリslackwebいれてください。・使っている環境変数ACCOUNT_NAME AWSアカウントの名前とかをいれます。通知用です。CHECK_TAG_NAME チェックしたいタグをいれます。今回はProjectです。CHECK_TAG_VALUES カンマ区切りで、入っていていほしいタグの値をいれます。aaa.jp,bbb.jp  みたいなのですSLACK_URL SlackのWebhookURLです。・ソース(Pythonらしさはかけらもありませんがゆるしてください# -*- coding: utf-8 -*- # EC2のタグ設定チェック import boto3 import os import slackweb import sys # アカウント名 ACCOUNT_NAME   = os.environ['ACCOUNT_NAME'] # チェックするタグ名 CHECK_TAG_NAME  = os.environ['CHECK_TAG_NAME'] # ついていてほしいタグの内容(どれかならOK) CHECK_TAG_VALUES = os.environ['CHECK_TAG_VALUES'].split(",") #slackurl SLACK_URL    = os.environ['SLACK_URL'] REGION_NAME = 'ap-northeast-1' ##################################################### #本体 ##################################################### def lambda_handler(event, context):   #タグチェック   results = checkTags()   print('checkTags results:')   print(results)   checkResult_AND_Notify(results) ##################################################### # インスタンスID一覧を取得 ##################################################### def checkTags():   instance_list = []   #ページャーで全て取得   client  = boto3.client('ec2', region_name=REGION_NAME)   paginator = client.get_paginator('describe_instances')   # Filterで止まっているものは除外   page_iterator = paginator.paginate(     Filters=[     {       'Name': 'instance-state-name',       'Values': [         'running',       ]     }   ],)   #チェックしていく   for page in page_iterator:     for reservation in page["Reservations"]:       for instance in reservation["Instances"]:         instance_id = instance["InstanceId"]         #Tagsを見て、CheckProjectSettingがNOならば除外(存在しない、またはYESなら追加         check_flag       = True         project_tag_exist_flag = False         project_tag_value   = ""         name_tag_value     = ""         Tags = instance["Tags"]         for tag in Tags:           if tag['Key'] == 'CheckProjectSetting':             if tag['Value'] == 'NO':               print('CheckProjectSetting,NO:' + instance_id)               check_flag = False           if tag['Key'] == 'Project':             project_tag_exist_flag = True             project_tag_value = tag['Value']             print('Project tag_value:' + project_tag_value)           #Nameはわかりやすさのため取ります           if tag['Key'] == 'Name':             name_tag_value = tag['Value']             print('Project tag_value:' + name_tag_value)         if check_flag == True:           print('CheckProjectSetting,YES:' + instance_id)           if project_tag_exist_flag == False:             #タグ自体がない場合は即追加             instance_id_dict = {               "instance_id"    : instance_id,               "name_tag_value"  : name_tag_value,               "project_tag_value" : project_tag_value,               "reason"      : "no_project_tag"             }             instance_list.append(instance_id_dict)           else:             #タグはあった場合、中身を確認し、なかったら一覧追加             if project_tag_value not in CHECK_TAG_VALUES:               instance_id_dict = {                 "instance_id"    : instance_id,                 "name_tag_value"  : name_tag_value,                 "project_tag_value" : project_tag_value,                 "reason"      : "project_tag_not_match"               }               instance_list.append(instance_id_dict)   return instance_list ##################################################### # 漏れがあるものを通知 ##################################################### def checkResult_AND_Notify(results):   print('checkResult_AND_Notify')   exist_no_Tag_Flag = False   res_str = ''   for result in results:     exist_no_Tag_Flag = True     res_str += '&gt;  Name:' + result['name_tag_value'] + ' ID:' + result['instance_id'] + ' 理由:' + result['reason'] +'\n'   if exist_no_Tag_Flag:     title_str = "ProjectタグをつけてないEC2インスタンスがあったよ!付けてね!\n"     slack = slackweb.Slack(url=SLACK_URL)     slack.notify(text=ACCOUNT_NAME + "のProjectタグチェックをしたよ!\n\n" + title_str + res_str)   else:     print("ProjectタグをつけてないEC2インスタンスはなかったよ!") ※Tagsを見て、CheckProjectSettingがNOならば除外やったね!もうタグがあるかどうかをチェックしなくていいよ!肝心のタグ設定は場合によりますので、手動で付けてくださいね。以上!(見栄え用の画像↓)