まるっとワーク

データ分析・電子工作など気になることを残していきます

ChatGPTを使った自動レシピジェネレーターの紹介(ChatGPT API + AWS + LINE)

ChatGPTを使った自動レシピジェネレーター サービスの作成方法を紹介します。
このサービスは、食材を入力するだけでレシピを考案し、提供するサービスです。システムの構成には、ChatGPT API + AWSサービス(Lambda + API Gateway)とLINEを使用し、連携させて、構築しています。

正直言うと、AWSサービスとLINEの連携さえ済めば、ChatGPT APIを使ってメッセージを返して貰う所だけを追加するだけなので、そこまでハードルは高くない。
それ以上に重要なことは、望んだ答えを出すような命令方法(プロンプトの設計)とアウトプットのさせ方だなと感じた。

以下先人の皆様の情報を利用して作成しており、尊敬を込めてご紹介します。
[ChatGPT]OpenAI APIでGPT-3.5系のモデル「gpt-3.5-turbo」と「text-davinci-003」をLambdaで試してみた | DevelopersIO
[ChatGPT API][AWSサーバーレス]ChatGPT APIであなたとの会話・文脈を覚えてくれるLINEボットを作る方法まとめ | DevelopersIO

目次

使用環境

AWS Lambda ランタイム python3.9
AWS API Gateway
・LINE (developers)
・ChatGPT API

開発手順

  1. LINE Message APIを使うための環境を整える/AWS の環境を整える+LINEと連携する[省略:別ページで紹介]
  2. AWS Lambdaでコード実行(動作確認)

実装

LINE Message APIを使うための環境を整える/AWS の環境を整える+LINEと連携する[省略:別ページで紹介]

これらの手順は過去のブログで説明をしているので、そちらをご参照ください。
AWS Lambda環境では、openaiモジュールを使用するので、AWS Lambdaのレイヤーにはopenaiモジュールを入れる必要があります。
dango-study.hatenablog.jp

AWS Lambdaでコード実行(動作確認)

コード作成

Lambda関数では、以下コードを作成/実行する。
openai.ChatCompletion.createの戻り値は、class 'openai.openai_object.OpenAIObject'であり、戻り値の読み込みには注意が必要。
以下の通り対応することで、辞書形式に変更ができ、扱いやすくなる。

dictionary = openai_object.__dict__#辞書形式に変更


コード詳細↓

import json
import urllib.request
import os
import openai 
from linebot import LineBotApi
from linebot.models import TextSendMessage
from datetime import datetime 

#######APIキーを取得
openai.api_key = os.environ["Openai_ACCESS_TOKEN"] #自身で設定した環境変数の値に変更
# 環境変数からLINE Botのチャネルアクセストークンを取得
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN'] #自身で設定した環境変数の値に変更
# チャネルアクセストークンを使用して、LINE BotのAPIインスタンスを作成
LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

########説明
#LINEで入力されたテキストは ['events’][0]['message’]['text’] に入っています。
#また、受信したメッセージに対してリプライを行うには、replyTokenの値を使用します。

# ログ出力関数
def logging(errorLv, lambdaName, errorMsg):
    loggingDateStr=(datetime.now()).strftime('%Y/%m/%d %H:%M:%S')
    print(loggingDateStr + " " + lambdaName + " [" + errorLv + "] " + errorMsg)
    return

def make_prompt(input):
    prompt_h = "{Input}を用いて、{Goal}を達成するための料理のレシピを{提案フォーマット}で回答する。\n"\
                "{Input}が食材名の場合は、その内容に応じてランダムに選んだ料理名を{Goal}とする。\n"\
                "{Input}が料理名の場合は、料理名を{Goal}とする。\n"\
                "{Input}に食材名や料理名以外が含まれる場合、料理のレシピ提案をせず、{代替案フォーマット}で回答する。\n"\
                "{提案フォーマット}と{代替案フォーマット}のどちらか一方のみで回答することを厳格に守る。\n"
    prompt_i = "{Input} = "
    prompt_c =  "/n{提案フォーマット}\n"\
                "【提案する料理】\n"\
                "{料理名}\n"\
                "【材料】\n"\
                "{食材1}:{分量1}\n"\
                "【手順】\n"\
                "{手順1}\n"\
                "{代替案フォーマット}\n"\
                "{Input}には対応するレシピを提案できません。別の食材や料理名を入力してください。"
    prompt_all = prompt_h+prompt_i+input+prompt_c
    
    return prompt_all

def use_chatgpt(input):
    prompt_ = make_prompt(input)
    
    #モデルを指定
    model_engine = "gpt-3.5-turbo"
    
    # 推論
    response = openai.ChatCompletion.create(
      model="gpt-3.5-turbo",
      messages=[
        {"role": "user", "content": prompt_}
      ])
    
    # 回答
    print(response.__dict__)
    #出力結果は、class 'openai.openai_object.OpenAIObject'であり、これを辞書形式で見込む
    answer = response.__dict__["_previous"]["choices"][0]["message"]["content"]
    logging("info", answer, "回答出力")

    return answer
    
def lambda_handler(event, context):
    logging("info", context.function_name, "実行開始")
    if json.loads(event["body"])["events"][0]["type"] == "message":
        if json.loads(event["body"])["events"][0]["message"]["type"] == "text":
            reply_token = json.loads(event["body"])["events"][0]['replyToken']# リプライ用トークン
            message_text = json.loads(event["body"])["events"][0]["message"]["text"]# 受信メッセージ
            
            #chatgptにpromptを送る
            logging("info", context.function_name, "use_api")
            response_text = use_chatgpt(message_text)
            
            print("response",response_text)
            
            # メッセージを返信
            LINE_BOT_API.reply_message(reply_token, TextSendMessage(text=response_text))

    return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}

コード(関数)解説
  • def logging(errorLv, lambdaName, errorMsg):

AWS CloudWatch Logsにログを残すための関数。特になくても処理には問題ない

  • def make_prompt(input):

ChatGPTに渡すプロンプトを作成する関数。引数inputにはLINEで受け付けた文字が入り、その文字を加えてプロンプトが完成し、戻り値returnとして返す

  • def use_chatgpt(input):

ChatGPT APIに値を渡し、結果をもらうためのメソッド

  • def lambda_handler(event, context):

イベントを処理するメソッドで、関数が呼び出される(LINEでメッセージを受け付けたタイミングでAWS API Gatewayに呼び出される)と、このLambda_habdlerメソッドが実行される。LINEからのメッセージは、引数eventに含まれる。

実行結果

LINEでメッセージを送った際の実行結果は以下の通り、意図通り、レシピを返してくれます。

実行結果

エラーが出た時の対処法

Amazon CloudWatchの、ログ->ロググループを選び、対象となるLambda関数を選択すると、実行毎のログを確認できます。
これで、LINEにメッセージを送った後、の結果を追うことができ、エラー処理も対応できるので、便利です。

CloudWatch

まとめ

今回は、AWSサービスとLINEを連携させ、ChatGPTを組み合わせた自動レシピジェネレーター サービスの作成方法を紹介しました。
このサービスは、食材を入力するだけで簡単に美味しい料理のアイデアを提供してくれます。ChatGPTは、豊富なレシピデータと自然言語処理アルゴリズムを組み合わせ、リアルな文章でレシピを生成することができます。実際に試してみたところ、多くの料理のアイデアを得ることができたため、日々の料理のバリエーションを増やすことができました。自動レシピジェネレーターを使うことで、手軽に料理のアイデアを得られるため、忙しい人や初心者の方にもおすすめです。
こういったサービスがどんどん世に出ていくのだなぁと思い、ChatGPTなどのAI技術の発展に今後も期待です!

AWSを活用してLINEのチャットボットを作る

AWSサービス(Lambda + API Gateway)とLINEを連携させて、チャットボットを作成した時のメモを残します。
最終目標として、LINEで尋ねたワードをChatGPTに投げて、返答結果をLINEに返すといったシステムを作ることを想定しており、
その前段階として、まずはAWSサービスとLINEを連携させるところまでを作り、LINEメッセージをオウム返しするシステムを作ります。

ほぼ同一のシステムを作られた、ありがたい先人方(以下)の知恵を借りて作っていったのですが、クラウドを用いたシステムに慣れていないところがあり、ハマった部分が各所にあるので、そこを重点的に記載して、同じミスをしないようにする。
AWS Lambda + Python + LINE Botで傘が必要か教えてもらう - Qiita
AWSを活用してLINEチャットボットを作ってみた!① | リウコムTechブログ
Lambdaを使用してLINEとChatGPTを連携してみた。 - Qiita


目次

使用環境

AWS Lambda ランタイム python3.9
AWS API Gateway
・LINE (developers)

開発手順

  1. LINE Message APIを使うための環境を整える
  2. AWS の環境を整える+LINEと連携する
  3. AWS Lambdaでコード実行(動作確認)

実装

LINE Message APIを使うための環境を整える:様々な方がやり方を説明している為、簡潔に説明

  • LINE Developersに登録する
  • チャネルを作成する

まずは、LINE Developersに登録します。(URL)
その後、公式ドキュメントの記載に従って、チャネルを作成します。今回は、メッセージのやり取りをしたいので、Messaging APIチャネルを選択して、作成する。
作成が完了したらこのステップは終了!ただ、後のことを考えて、"Messaging API設定"の一番下、"チャンネルアクセストークン"を押下し、その値は控えておく。
Message APIを使用するときに、認証が必要であり、チャンネルアクセストークンはその認証手段として利用する。

LINE Developer


AWS の環境を整える+LINEと連携する:結構苦戦したので、詳しく説明

AWS Lambda関数の作成

AWSコンソールにログインして、Lambdaの画面を開き、関数->関数作成を押下する。
以下のような画面が出るので、関数名は適当に記入、ランタイムは"Python3.9"を設定する。

関数

タイムアウト設定

デフォルトは5秒に設定されていますが、API等 別のシステムとやり取りをする場合は、タイムアウトが起こり得るため、30秒程度まで伸ばした設定に変更する。

AWS Lambdaレイヤー作成

AWS Lambda上で使えないモジュールがある場合は、レイヤーを作成して対応する。
今回は、LINE BOT開発用のline-bot-sdkをインストールして、レイヤーとして設定する必要があります。
また、ChatGPTの使用を想定すると、openaiもインストールして、同様にレイヤーとして設定する必要があります。

  • ローカルでpipコマンドを使って必要モジュールファイルをダウンロード


以下コードでは、"python”フォルダを作成して、そのフォルダに必要モジュールファイルを格納、zip化する

$ mkdir python
$ cd C:\Users\user\python
$ pip install line-bot-sdk -t ./
$ pip install openai -t ./ #openaiモジュールもレイヤーに追加する場合
$ cd ../
$ zip python.zip python/* #zip化する

  • zipファイルをアップロードする

初歩的なのですが、フォルダ構成を間違えて、lambdaで必要なモジュールのimportでエラーが出来ないことで詰まりました。
フォルダ構成は以下(URL)の通りに設定して、アップロードする必要があります。
zipファイルの構成は以下の通り

@@.zip
  -python
    |-@@@
    |-@@@

  • 作成したzipファイルをLambdaレイヤーに設定する。
レイヤー作成

環境変数の登録

環境変数の役割: 環境毎に値が変わるものの管理に使用し、ソースの修正の手間を省かせる。
特定パラメータ(API Key等)を環境変数に設定して、呼び出して使う。

環境変数の設定方法は、URLを参考にする。
今回のシステムでは、以下2点(Openai_ACCESS_TOKENはChatGPT APIを使用する時だけ使用)を環境変数として設定した。
キーは任意に入力し、値にAPIキーを入力して、コード内で呼び出して(LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']という感じ)使う。
LINE Message APIのチャンネルアクセストークンはキー: LINE_CHANNEL_ACCESS_TOKENの値に入力して、今回は設定した。

環境変数

API Gatewayの作成/設定

ここがかなり苦戦したので、詳しく説明していくが、まだ完全に理解できておらず随時付け足す。

Lambdaの関数画面で、以下画像では左下に"API Gateway"という文字とアイコンが存在するが、初期は存在しない。
"+トリガーを追加"個所をクリックして、ウィンドウを開く。

トリガー追加


ウィンドウが開いたのち、"API Gateway"を選択すると以下画像のような設定画面が現れる。

詳細設定

今回はAPIタイプ REST API/セキュリティは"開く"を選択して使用する。
正直APIの種類については分かっていない部分が多く(以下のようなwebページで勉強したのですが・・・)、後々勉強して記載していく。
qiita.com
また、セキュリティに関しても本来は、IAMポリシーを使って使用ユーザーを制限することがベストプラクティスみたいですが・・・一旦は全てに開放する設定にしています。
Amazon API Gateway のセキュリティのベストプラクティス - Amazon API Gateway

上記の通り作成すると、先程の関数ウィンドウのトリガー欄に、API Gatewayが作成される。

API Gateway


API Gatewayの名前をクリックして、詳細を設定する。
以下画像では、"Resources"欄に"/POST"という1つしか存在しないが、当初は"ANY"が存在している。
"ANY"はすべてのHTTPメソッドをサポートしており、そのままでもこのような目的のために使えるのでは?と思っているが、具体的な設定方法が分からなかった為、先人たちの方法にしたがって対応する。
"ANY"メソッドは削除して"POST"メソッドを作成する。POSTメソッドは、HTTP POSTリクエストをサポートしており、HTTP POSTとは、クライアントからの入力内容をWebサーバに送信するために使用するメソッドのこと。

詳細設定


POSTメソッド作成時の設定は以下の通り、"Lambda Function"の入力を求められるが、そこにはLambda 関数名を入力する。

POST

作ろうとしているシステムでは、AWS API GatewayへのHTTP POSTリクエストはLINEから来ることを想定しているが、LINEから来たかどうかの検証をする方法として、署名の検証方法が推奨されているようなので試しに入れてみる。恐らく、この設定がなくても問題なく動作すると思っているが、検証はしていない。もし間違っていたらコメントで教えていただけますと嬉しいです。
メッセージ(Webhook)を受信する | LINE Developers

設定方法は、"Method Request"を選択

メソッドリクエス


HTTP Request Headers欄に"X-Line-Signature"を入力して設定する。

メソッドリクエスト詳細

  • API のデプロイ

"Resources"の横の"Actions"欄から、Deploy APIを選択することで、APIをデプロイ(設置)することができる。
これを忘れて、次工程がうまくいかず、、、参考までにデプロイ方法はこちらのURLに載っています。
デプロイ時のStage名などは、任意に設定する。

  • Webhookの設定

"Stages"選択し、デプロイ時のStage名を選択すると、以下のような画面が現れる。
Invoke URLがリクエストするURLになるので、ここにLINEの送信先が来るように設定する。

リクエスト確認


Webhookとは、ウェブアプリケーションの機能の1つで、外部のサービスやアプリケーションに対して、特定のイベントが発生した際に自動的に通知を送信することができる仕組み。
今回では、LINE上でメッセージを受けた際にそのメッセージと通知の連絡をAWS API Gatewayにリクエストする際に使う。以下の通り、LINE DevelopersのページでWebhook設定個所があるので、上記API GatewayInvoke URLを入力し、"検証"ボタンを押下する。その結果、"成功"と記載されたウィンドウが出れば、問題なく設定できている。

Webhook設定

AWS Lambdaでコード実行(動作確認)

コード作成

Lambda関数では、以下コードを作成/実行する。
LINEからはjson形式のリクエストが送信される。json形式というのをすっかり忘れており、json形式の読み込みでも少しはまってしまった・・
jsonデータは"json.loads"を使うことで、辞書dictなどのオブジェクトとして読み込むことができ、処理ができる。

import json
import urllib.request
import os
from linebot import LineBotApi
from linebot.models import TextSendMessage
from datetime import datetime 

#######APIキーを取得
# 環境変数からLINE Botのチャネルアクセストークンを取得
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
# チャネルアクセストークンを使用して、LINE BotのAPIインスタンスを作成
LINE_BOT_API = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)

########説明
#LINEで入力されたテキストは ['events’][0]['message’]['text’] に入っている
#受信したメッセージに対してリプライを行うには、replyTokenの値を使用する

# ログ出力関数
def logging(errorLv, lambdaName, errorMsg):
    loggingDateStr=(datetime.now()).strftime('%Y/%m/%d %H:%M:%S')
    print(loggingDateStr + " " + lambdaName + " [" + errorLv + "] " + errorMsg)
    return

def lambda_handler(event, context):
    logging("info", context.function_name, "実行開始")
    logging("error", context.function_name, "エラーログテスト")
    if json.loads(event["body"])["events"][0]["type"] == "message":
        if json.loads(event["body"])["events"][0]["message"]["type"] == "text":
            replyToken = json.loads(event["body"])["events"][0]['replyToken']# リプライ用トークン
            messageText = json.loads(event["body"])["events"][0]["message"]["text"]# 受信メッセージ
            LINE_BOT_API.reply_message(replyToken, TextSendMessage(text=messageText))# メッセージを返信(受信メッセージをそのまま返す)

    return {'statusCode': 200, 'body': json.dumps('Reply ended normally.')}


Lambda関数を実行する際、手動で実行(Test)する際は、"Execution results"欄(以下画像)に実行結果が吐き出されるが、今回ケースの場合のエラー確認方法に困った。

Test


そう、今回の系は、LINEにメッセージを送り、そのリクエストがAWS API Gatewayに送られ、Lambda関数が実行される。こういうケースの場合、どこにエラー結果が表示されるのだろう・・・
そうですAmazon CloudWatch この存在を知って助かりました。

Amazon CloudWatchの、ログ->ロググループを選び、対象となるLambda関数を選択すると、実行毎のログを確認できる。
これで、LINEにメッセージを送った後、の結果を追うことができ、エラー処理も対応できる。

CloudWatch

実行結果

LINEでメッセージを送った際の実行結果は以下の通り、意図通り、オウム返しをすることができました。
>

実行結果

まとめ

今回は、AWSサービスとLINEを連携させ、LINEメッセージをオウム返しするシステムを作りました。
次は、ChatGPTと連携させて、ChatGPTへの質問結果をLINEで返すところまでをまとめようと思います。

フィルタの設計_メモ

フィルタ設計について、関連用語等を簡単にまとめます。

目次

フィルタ設計について

フィルタ設計は、以下項目を検討します。

  1. 周波数特性(フィルタ種類)
  2. 設計仕様(フィルタ仕様)
  3. (アナログ回路の場合)フィルタ回路を構成する素子や接続方法
  4. ノイズ除去等、入力データの処理

周波数特性(フィルタ種類)

振幅特性に対する設計要求が与えられることが多く、通過域や阻止域の周波数によって、以下のように呼ばれ方が変わる。

  • 低域通過(ローパス)フィルタ
  • 高域通過(ハイパス)フィルタ
  • 帯域通過(バンドパス)フィルタ
  • 帯域阻止(バンドエリミネーション)フィルタ

 阻止域の幅が狭いものはノッチ・フィルタと呼ばれる

  • 全域通過(オールパス)フィルタ

 移送特性を変化させる目的で使われる

フィルタ種類

設計仕様(フィルタ仕様)

その他設計項目として、以下が存在

  • パスバンド(角)周波数
  • ストップバンド(角)周波数
  • パスバンド リップル
  • ストップバンド減衰量

フィルタ仕様


上記任意の値の仕様を満たす伝達関数を求め、フィルタとして使う。
伝達関数の求める方法として、多くの近似手法があり、代表的な近似結果が以下の通り。


それぞれのフィルタの特徴は、以下記事に示す。
dango-study.hatenablog.jp

ノイズ除去等、入力データの処理

観測したい信号にノイズが含まれる場合は、ノイズを除去することが求められる場合がある。

ノイズ除去方法としては、以下の通り。

  • 特定の周波数成分を抽出

ローパスやハイパス等のフィルタを組み合わせて、通過周波数帯を選択する。この手法は、信号とノイズとがそれぞれ異なる周波数である場合に有効。

  • 特定の周波数成分を除去

特定の決まった周波数を持つノイズが信号に入る場合は、ノッチフィルタが有効なケースがある。このフィルタは、特定の周波数成分だけを除去し、他の周波数帯には大きな影響を与えない。ただし、上記同様に信号とノイズとがそれぞれ異なる周波数である場合に有効。

Python: lambda式を使う

Pythonプログラムにおいて、複数回同じ作業を実施したいときは関数を定義して実行しますが、関数に名前を付けずに簡潔に宣言するlambda式を使った書き方をまとめます。

基本の書き方

lambda 引数: 処理内容
のように書く。

通常の関数を用いた書き方

import numpy as np
import pandas as pd

def calc(value, add):
    return value + add
 
print(calc(1,0.2))


lambda式を用いる場合

print((lambda value,add:value+add)(1,0.2))



listを対象としたlambda式での処理

lambda, mapメソッドを組み合わせる
mapメソッドの書き方: map(関数, リスト)

#データ
values = np.arange(100)

#lambda関数
print(list(map(lambda value:value+0.2, values)))


DataFrameを対象としたlambda式での処理

基本の使い方

lambda, applyメソッドを組み合わせる

#データ
df_values = pd.Series(np.arange(100))

#lambda関数
df_calc_value = df_values.apply(lambda x: x*2)
print(df_calc_value)

※2次元配列では、applyメソッドではなくapplymapメソッドを使用する

出力結果は以下の通り

0       0
1       2
2       4
3       6
4       8
     ... 
95    190
96    192
97    194
98    196
99    198
Length: 100, dtype: int64


if文処理含めたもの

同じくlambda, applyメソッドを組み合わせて使用する
lambda 引数 "Trueの処理" if 条件式 else "Falseの処理"

#データ
df_values = pd.Series(np.arange(100))

#lambda関数
df_calc_value = df_values.apply(lambda x: 100 if x > 10 else 0)
print(df_calc_value)


出力結果は以下の通り

0       0
1       0
2       0
3       0
4       0
     ... 
95    100
96    100
97    100
98    100
99    100
Length: 100, dtype: int64


まとめ

今回は無名関数と呼ばれるlambda式の書き方についてまとめました。
すべてを関数として書くと、コードが長くなってしまい、可読性が下がる場合があるので、意識して使いこなしていきたいですね

Python: mapやapply、applymapメソッドを使う

Pythonプログラムにおいて、DataFrameの各カラムに対してforループで処理をする際、非常に時間がかかる場合があります。
今回は、処理時間低減にも有効なmapメソッド/applyメソッドの使い方についてまとめます。

開発環境について

$wmic os get caption
>Microsoft Windows 10 Pro

$wmic os get osarchitecture
>64ビット

$python -V
Python 3.7.6


目次


mapメソッドとapplyメソッドの違い

対象となるデータ構造が違う

  • mapメソッド: Serieseに対応 (DataFrame意外だとlistにも対応)
  • applyメソッド: Seriese、DataFrame(各列/各行)に対応
  • applymapメソッド: DataFrameに対応

SerieseはDataFrameの1つのカラムを指すデータ構造であり、DataFrameはSerieseの集合体であるため、1次元データ構造, 2次元のデータ構造のどちらか一方もしくはその両方に対応したメソッドであるという違いがあるようです。

それぞれの使い方

mapメソッド

- list 対象の書き方
  map(関数, リスト)

  • Seriese対象の書き方

  Seriese.map(関数)

import pandas as pd
import numpy as np
# 対象データ作成

df = pd.DataFrame([[80,90], [50,40], [60,55], [70, 55], [90,95]], 
                  index=['Japanese', 'math', 'science', 'society', 'English'],
                  columns=['A','B'])

sample_list = [80, 50, 60, 70, 90]

# 関数定義
def evaluate_score(x):
    if x >= 60:
        y = "合格"
    else: 
        y = "不合格"
    return y

# list対象の書き方
A_score_list = map(evaluate_score, sample_list)

#Seriese対象の書き方
df['A_score'] = df['A'].map(evaluate_score)

#結果出力 list (map型のオブジェクト: イテレータ)
for i in A_score_list:
    print(i)

#結果出力 Seriese
print(df["A_score"])


出力結果は以下の通り

・list
合格
不合格
合格
合格
合格
・Seriese
Japanese     合格
math        不合格
science      合格
society      合格
English      合格

applyメソッド

- Seriese/DataFrame対象の書き方
  DataFrame.apply(関数, axis=@)

※@=0 or 1 DataFrameを渡したときに列/行毎に計算するのかを分ける

>|memo|
# 対象データ作成
df = pd.DataFrame([[80,90], [50,40], [60,55], [70, 55], [90,95]],
index=['Japanese', 'math', 'science', 'society', 'English'],
columns=['A','B'])

sample_list = [80, 50, 60, 70, 90]

# 関数定義
def evaluate_score(x):
if np.mean(x) >= 60:
y = "合格"
else:
y = "不合格"
return y

#DataFrame対象の書き方
print(df.apply(evaluate_score,axis=0))
#Series対象の書き方
print(df['A'].apply(evaluate_score))

|


出力結果は以下の通り。2次元配列を渡した方は、各列/各行データが関数の引数として渡されるため、戻り値も変わる。

・DataFrame
A    合格
B    合格
・Seriese
dtype: object
Japanese     合格
math        不合格
science      合格
society      合格
English      合格

applymapメソッド

- DataFrame対象の書き方
  DataFrame.apply(関数)

# 対象データ作成
df = pd.DataFrame([[80,90], [50,40], [60,55], [70, 55], [90,95]], 
                  index=['Japanese', 'math', 'science', 'society', 'English'],
                  columns=['A','B'])

sample_list = [80, 50, 60, 70, 90]

# 関数定義
def evaluate_score(x):
    if np.mean(x) >= 60:
        y = "合格"
    else: 
        y = "不合格"
    return y


#Seriese対象の書き方
print(df.applymap(evaluate_score))


出力結果は以下の通り

            A    B
Japanese   合格   合格
math      不合格  不合格
science    合格  不合格
society    合格  不合格
English    合格   合格

速度の違いについて

forループとapply等を使用した時の速度の違いを計算します。
上記サンプルデータはデータサイズが小さすぎるので、np.arangeで適当に1000万行分のデータを作成して時間を比較しました。
※処理時間計測にはTimeモジュールを使用

  • For Loop 処理時間: 4.781sec
  • Apply 処理時間: 2.62sec


検証用コード

import time
# 対象データ作成
sample = pd.Series(np.arange(10000000))

# 関数定義
def evaluate_score(x):
    if x >= 60:
        y = "合格"
    else: 
        y = "不合格"
    return y

start = time.time()
for i in sample:
    y = evaluate_score(i)
elapsed_time = time.time() - start
print ("ForLoop_elapsed_time:{0}".format(elapsed_time) + "[sec]")

start = time.time()
sample.apply(evaluate_score)
elapsed_time = time.time() - start
print ("apply_elapsed_time:{0}".format(elapsed_time) + "[sec]")


まとめ

今回はDataFrameの各カラムに対して一連の処理をするために使えるmapやapply, applymapメソッドについてまとめました。
データ数が膨大にあると、処理に時間がかかりますので、コードの組み方次第でだいぶ変わりますね。

機械学習について(言葉の定義など、はじめの一歩)


機械学習について学習を始めたので、理解した内容についてまとめて説明していきたいと思います。

目次

機械学習とは?

コンピューターサイエンティストで有名なRobert Elias Schapire氏は以下の通り述べている。

studies how to automatically learn automatically learn to make accurate
predictions predictions based on past observations

(Introduction to Machine Learning)


"過去の観察に基づいた正確な予想を自動的に行うこと"といった感じで、人が行う学習行為をPC上で表現するというニュアンスが入っている。
また、機械学習では主体はデータであり、データ内の関係を把握して、その法則(ルール)やアルゴリズムを学習するといったことに使われる。
物理法則や人間が考えたルールから答えを導くといった、ルール主体の考え方ではないことがポイント

機械学習の手法

色々なサイトでも書かれているが、以下の通り3つに分類される。
使うアルゴリズムが違うというよりは、解くべき問題(使用目的)が異なるという観点での分類

  解析目標のラベルが存在し、目標値とその他データとの相関関係の学習から、主に予想で使用される

  データ間の関係などの構造把握に使用される

  ゲームのスコアなどの目標値を最大化することを目的に、その仕組みを学習する為に使用される
※それぞれの代表モデル等は勉強を進めた後まとめていく

精度の高いモデル構築に向けて

より多くの種類・量のデータがあるほど、良い回答を導出できる。
ただし、ある程度目標値と関係あるデータが必要であり、目標値の説明に必要なデータ(変数)作成作業は特徴量エンジニアリングと言われるなど、機械学習において一番難しい。

AutoML(Automated Machine Learning)とは?

機械学習の機能を複数呼び出して使うことができるフレームワークであり、アルゴリズム固有値(ハイパーパラメータ)までも最適化対象となっている。
データの整形やエラー値の修正といった機械学習の前処理が自動化されたものや、ディスプレイツールを含んだものも存在する。
プログラムが得意でない方でも使うことができるという利点やデータサイエンティストの業務時間の削減に効果が期待できる。
対象データにどういったモデルが適しているのか、データ間の関係性の把握のためにAutoMLを使用するケースも多い。

代表的なAutoML

もっとも知名度が高いGoogleが開発したAutoML
文字列などのデータを自動的に整形する機能も有している。
cloud.google.com

Microsoftが開発したAutoML
docs.microsoft.com

IBMが開発したAutoML
www.ibm.com

まとめ

今回は導入ということで機械学習の概要やエンジニア向けの関連情報をまとめました。具体的なモデルについても勉強してまとめていきたいと思います。

matplotlibとseabornの日本語の文字化けを修正する

Pythonから利用できる可視化ライブラリ、matplotlibやseabornにて可視化を行った際の日本語の文字化けを防ぐ方法をまとめていきます。

目次

開発環境について

$wmic os get caption
>Microsoft Windows 10 Pro

$wmic os get osarchitecture
>64ビット

$python -V
Python 3.7.6

$pip list -V
scipy 1.4.1

問題事例

日本語が含まれるグラフを作成する場合、以下の通り文字化けが発生する。

import numpy as np
import matplotlib.pylab as plt

x = np.arange(100)
y = np.sin(x*0.1)
plt.plot(x, y)
plt.xlabel("時間")
plt.ylabel("値")


どうやら、本モジュールでは日本語に対応していない欧文フォント(sans-serif)をしているため日本語表示ができないようです。
f:id:toku_dango:20210924091239p:plain

対応方法

対応方法を調べてみると、Jupyterの設定ファイルを書き換えるなどの方法があるようですが、少し大変そうなので、これとは異なるより簡単な方法についてまとめます。

japanize-matplotlibモジュールをインストール

 このモジュールはmatplotlibのフォント設定を自動で日本語化してくれます。
pypi.org

$ pip install japanize-matplotlib

対応結果

ラベルの文字化けがなくなり、正常に日本語表示されるようになりました。

import numpy as np
import japanize_matplotlib #モジュール追加
import matplotlib.pylab as plt

x = np.arange(100)
y = np.sin(x*0.1)
plt.plot(x, y)
plt.xlabel("時間")
plt.ylabel("値")

f:id:toku_dango:20210924091257p:plain

まとめ

今回はデータ可視化の際の文字化けに対する対応方法をまとめました。
報告用で日本語表記のグラフを作成したい場合に、こういった対応をふと忘れてしまうので、忘れずに使えるようにしたいです。