20210720のPythonに関する記事は30件です。

複数ホストから一斉にファイルを回収する

前回はこちら 前回のおまけのような内容です. 前回は一斉にプログラムを動作させる方法を記載しました. 今回は複数ホストから一斉にファイルを回収する方法を載せておきます. 使い方はなんら変わりません. paramiko_copy.py #!/usr/bin/python3 import paramiko import os import datetime as dt import scp user = "username" passwd = "PASSWD" with open("host.txt") as f: s = f.readlines() host_name = [] for i in range(len(s)): if s[i].startswith('#'): pass else: host_name.append(s[i].replace("\n",'')) print(host_name) path = "/home/user/paramiko/result/" for i in range(len(host_name)): target_host = host_name[i] file_name = path + "*.txt" with paramiko.SSHClient() as ssh: # 初回ログイン時 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # ssh接続する ssh.connect(target_host, username=user, password=passwd) print('[SCP転送開始]') # scp clientオブジェクト生成 with scp.SCPClient(ssh.get_transport(), sanitize=lambda x: x) as scp: # scp送信する場合はput #scp.put('test.csv', '/home/user/paramiko/result/') # scp受信する場合はget scp.get(file_name) プログラム下部のscp.SCPClient(ssh.get_transport(), sanitize=lambda x: x)の sanitize=lambda x: xは非常に重要である. 今回私は*.txtでファイルを回収しようと考えていたが,サニタイズをしなければ,*(アスタリスク)も通常の文字として判定されてしまう. ワイルドカードを使わない場合は必要ないかと思うが,実験の結果などは,同一拡張子で保存することが多いので,私はワイルドカードをたくさん使います. 躓いたので参考までに. サニタイズについて サニタイズをもっとわかりやすく
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

同一ネットワーク上の複数ホストに対して,一斉実行命令を出す(on python3 paramiko)

私が普段学校で実験を行う際に,複数のホストで一斉にプログラムを動かしたいときが有ります. 人手があれば,1人1台張り付いて実行すれば良いですが,深夜に実験を回すこともあるので,1人で複数ホストに対して一斉に実行命令を出すことができれば効率が上がると考え,実装しました. 同様の環境を構築したい方は,参考にしていただけると幸いです. 実行環境と使用ツールのインストールなど 命令を送るPC:Debian11(test) 命令を受け取るホスト:ラズパイ(Debian) 今回はラズパイに向けて実行してますが,PCでも問題ないです. 命令を送るPCのPythonのバージョン Python 3.9.2 環境構築に向けて,インストールしなければいけないものがあります. それはparamikoというパッケージ?モジュール?です. $ aptitude search paramiko i python3-paramiko - Make ssh v2 connections (Python 3) もしくは $ sudo apt-get install python3-paramiko -y もしくは $ pip3 search paramiko # - - - - - - - - - - - - - - - - - - # # pip3 search はうまく実行されないという記事が # 投稿されているので, # 素直にapt or aptitude で入れるのが # スマートでしょう. # - - - - - - - - - - - - - - - - - - # プログラムを動かす前の準備 今回私が作成したプログラムは,同一ディレクトリ内に,host.txtというテキストファイルをおいておきます. その内容は,記載してあるホスト名を読み込み,通信を行います. host.txtは以下のようになっています.(一例) host.txt host1 host2 #host3 #host4 host5 #host6 上記のようにホスト名を記載します. 先頭に#が付いているホストはプログラム内部では通信対象ホストから外れます. いちいち消して,通信するときに書いてもいいですが,めんどいのでコメントアウト的な感じにしておきました. リモートホストの場合は不具合などで通信対象から外れることもあるかと思いますので,そのような仕様にしています. host.txtに記載してあるホスト名は,/etc/hostsに記載しておいてください. そうしないと,ホスト名がわかりませんというエラー吐きます. 体感速度ですが,動作時間は非常に短いので,host.txtに記載する順番は大きく影響しませんが,順番通りに書いたほうがわかりやすいと思います. 使用方法 host.txtに書いてあるホストに対して,forループで順番にコマンドを送りつけます. paramiko_sample.py #!/usr/bin/python3 import paramiko import os import time user = "username" # sshログイン時のユーザー名 passwd = "PASSWD" # sshログイン時のパスワード command = "python3 /home/username/paramiko/sample.py" #commandには "ls" や "pwd" などターミナル上で打ち込むコマンドも使えます. host_name = [] # with open("host.txt") as f: s = f.readlines() for i in range(len(s)): if s[i].startswith('#'): pass # startswithは先頭が#で始まるかどうかを判定しています. else: host_name.append(s[i].replace("\n",'')) for i in range(len(host_name)): target_host = host_name[i] with paramiko.SSHClient() as ssh: ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 初回ログイン時の(yes/no)?ってやつを飛ばしてくれます.非常に便利 ssh.connect(target_host,username=user,password=passwd) # ここでリモートホストに対して接続を行います. stdin,stdout,stderr = ssh.exec_command(command) # ここで,コマンドを送りつけます. # stdin -> 標準入力 # stdout -> 標準出力 # stderr -> エラー表示 これであらかたの説明は終わりました. 最後にちょこっと諸注意を述べておきます. 諸注意 勘の良い方は気づいたかと思いますが,commandのファイル名を絶対パスにしています. それはなぜかといいますと,paramikoでログインした際の自分の居場所?(pwdコマンド)は/home/usernameにいます. ホームディレクトリでプログラム量産している方は少数派だと思います. 必ず,どこかのディレクトリ内部にプログラムなどなどが配置されていると思います. なので,絶対パスで記載してください. 確実にエラー吐きます. file not found系のエラー出てきますよ. 更に感のいい人は気づいたかな? リモートホストPCで実行する以下のプログラム command = "python3 /home/username/paramiko/sample.py" sample.pyの内部で,ファイルを読み書きするなど,ファイルパスを記載している方は更に注意が必要です. 仮にsample.pyの内部が以下のような内容だったとしましょう. sample.py FILE="./dir1/hogehoge.txt" with open(FILE) as f: s = f.readlines() 通常のsshログインで動かす場合は,以下のようにcdコマンドでディレクトリを移動するので問題ありません $ cd paramiko $ pwd /home/username/paramiko $python3 sample.py ですが,これがparamikoを使っての遠隔だと,cdコマンドを入力してないので, 自分の居場所はホームディレクトリです. そこで,sample.pyを実行したところで,ホームディレクトリ下にdir1というディレクトリが存在しないのです. もちろん実行できません. なので,ファイルのパスを記載する場合は,必ず,絶対パスで記載したほうが,読み書き関係でエラーはなくなると思います.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonを使ってリアクティブなWebアプリを作りたい【開発編3-2】

こちらの続きです。 pythonを使ってリアクティブなWebアプリを作りたい【開発編3】 今回は表示系をつくります 最終的なゴールはこちら ToDoの登録画面及び、登録されたToDoの表示部分を作ります。 変更を伴うファイルは以下の通り frontend/src/views/todo/ToDo.vue frontend/src/store/modules/todo.js backend/main.py dist distについてはbulid後のファイルそのままなので、特に自分で作るというものではありません。 ToDo.vueについて frontend/src/views/todo/ToDo.vue frontend/src/views/todo/ToDo.vue mounted() { this.$store .dispatch('fetchTodo', { user_id: this.$store.state.auth.user_id, }) .then(() => { //console.log('getToDos-----') //console.log(getToDos) }) .catch(() => { this.loading = false }) }, 見慣れないmountedという項目が出てきました。 これは、このページがロードされた際に実行するという意味です。 dispatchでfetchTodoを呼び出していますが、画面がロードされた際に、当該ユーザが持っているToDoのレコードを取得しますよという意味です。 user_id: this.$store.state.auth.user_id これも見慣れない表現ですが、auth.jsのstateに入っているuser_idを呼び出しますという意味です。 データの格納先は frontend/src/store/modules/auth.js で定義されており、ログイン時にUserテーブルに格納されているユーザーのIDを登録するようになっています。 画面ロード時はそれくらいですね。 登録部分について handleSubmitFormを呼び出してキーワードを登録しています。 frontend/src/views/todo/ToDo.vue methods: { handleSubmitForm() { console.log('handleSubmitForm') console.log(this.formModel) console.log('todo: ' + this.formModel.todo) this.loading = true if (this.formModel.todo != null) { this.$store .dispatch('addtodo', { todo: this.formModel.todo, user_id: this.$store.state.auth.user_id }) .then(() => { this.loading = false }) .catch(() => { this.loading = false }) } }, addtodoにJSON煮詰めたデータを渡しているというおなじみな形です。 削除部分について これは所見部分があります。 <v-data-table v-model="selected" show-select :headers="todo_headers" :items="$store.state.todo.todos" hide-default-footer class="elevation-0 table-striped" > v-modelにselectedというものが配置されているのですが、これは同ファイルのdataの中で定義されたただの配列の入れ物です。 v-data-tableでチェックを入れたデータがseledtedの中に入ります。あとはそれを使ってdispatchでデータを送信するだけですね。 このような形で、データの入力及び削除ができるようになります。お試しください。 関連リンク pythonを使ってリアクティブなWebアプリをお手軽に1分で作りたい pythonを使ってリアクティブなWebアプリを作りたい【基礎編】 pythonを使ってリアクティブなWebアプリを作りたい【開発編】 pythonを使ってリアクティブなWebアプリを作りたい【開発編2】 pythonを使ってリアクティブなWebアプリを作りたい【開発編3】 pythonを使ってリアクティブなWebアプリを作りたい【開発編3-2】←イマココ pythonを使ってリアクティブなWebアプリを作りたい【開発編4】パスワードの暗号化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(画像あり)コピペで使える、Googleスプレッドシートへ色をつけて見易く書き込むクラスの作成

書き込み後の画像 下記のように書き込まれます コードのサンプルは 自己紹介 普段私は、 一番得意な機械学習(深層学習)をしたり、 Python/Django でWebアプリを開発したり、 TypeScript/Vue or React でフロントエンドの開発をしたり、 PHP/Laravel でWebアプリを開発したり、 さまざまなことを行っています。 趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。 今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。 企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。 nagamatsu-k@dym.jp Googleスプレッドシートに書き込む超便利なクラスを作成した ライブラリのInstall 下記がrequirements.txtの中身になります。 gspread==3.7.0 gspread-dataframe==3.2.1 gspread-formatting==1.0.3 oauth2client==4.1.3 pandas==1.3.0 実際に下記のコマンドでinstallできるので実行していってください。 $ echo gspread==3.7.0"\n"gspread-dataframe==3.2.1"\n"gspread-formatting==1.0.3"\n"oauth2client==4.1.3"\n"pandas==1.3.0 >> requirements.txt $ pip install -r requirements.txt 先に使い方!! コードを全文をspread_tranceform.pyで保存していたとする。 # インスタンスの生成 from spread_tranceform import SpreadSheetManipulate """ 色々と処理をする。 スクレイピングなり、なんでも良い。 """ ss = SpreadSheetManipulate( "スプレッドシートID", "service-account.jsonのパス" ) # 書き込みたい2次元配列 array2D = [ ["会社名", "従業員数", "電話番号", "メールアドレス", "HP"], ["株式会社A社", "234名", "09000000000", "kabusikigaisya-a@a.com", "https://a.com/"], ["株式会社B社", "65名", "09011111111", "kabusikigaisya-b@b.com", "https://b.com/"], ["株式会社C社", "769名", "09022222222", "kabusikigaisya-c@c.com", "https://c.com/"] ] # シート名 sheet_name = "会社情報" # 2次元配列の書き込み ss.write_shape_of_array2D_to_spreadsheet( sheet_name, array2D ) これで終わりです!! 実際に下記写真のように書き込まれます。 コードを全文! 本来は3ファイルにそれぞれ分割していたが分けるのがめんどくさかったため1つにまとめた笑 許してください、、、 メソッドにコメント載せてるので深く知りたい人はリーディングしてください コピーして使用できます。 file名を下記にしてコードを貼り付けて保存する。 spread_tranceform.py from gspread_dataframe import set_with_dataframe from oauth2client.service_account import ServiceAccountCredentials import gspread from gspread_formatting import * import pandas as pd class SpreadSheetManipulate(SpreadBase): def __init__(self, spreadsheet_id: str, json_name: str): super().__init__(spreadsheet_id, json_name) def write_shape_of_array2D_to_spreadsheet( self, worksheet_name: str, array2D: list, background_color: bool = True ) -> None: """ 2次元配列をスプレッドシートに書き込むメソッド カラムの部分をピンク色にするだけでなく、 シート名を引数に渡すことで、spreadsheet(WorkBook)に存在しないシート名だった場合 新規にシートを作成して、そこに書き込む Args: worksheet_name (str): 書き込みたいシート名(既存でも新規でも良い) array2D (list): 書き込む値の入った2次元配列 => [[a,b,c],[1,2,3]] background_color (bool, optional): カラムに色をつけるかどうか. Defaults to True. """ # * 追加する項目の横の長さ width: int = len(array2D[0]) # * 追加する項目の縦の長さ height: int = len(array2D) try: self.workbook.add_worksheet( title=worksheet_name, rows=height, cols=width) print("追加") except BaseException: pass update_ws = self.workbook.worksheet(worksheet_name) # * Spreadsheetの背景の色を変える if background_color: self.background_color(width, update_ws) # * 行列の作成 cell_list = update_ws.get_all_values() # * 行の数 columns = len(cell_list) + 1 ranges = '{0}!A{1}'.format(worksheet_name, columns) # * print(ranges) self.workbook.values_update( ranges, params={'valueInputOption': 'USER_ENTERED'}, body={'values': array2D} ) def write_dataflame_to_spreadsheets( self, type_dict: dict, worksheet_name: str, background_color: bool = True ) -> None: """ 辞書型をスプレッドシートに書き込むメソッド カラムの部分をピンク色にするだけでなく、 シート名を引数に渡すことで、spreadsheet(WorkBook)に存在しないシート名だった場合 新規にシートを作成して、そこに書き込む Args: type_dict (dict): 書き込む値の入った辞書型 worksheet_name (str): 書き込みたいシート名(既存でも新規でも良い) background_color (bool, optional): カラムに色をつけるかどうか. Defaults to True. """ dataframe = self.pd_change(type_dict) # * dataframeの長さ取得(行, 列) -> (縦, 横) shape = dataframe.shape() try: self.workbook.add_worksheet( title=worksheet_name, rows=shape[0], cols=shape[1]) except BaseException: pass update_ws = self.workbook.worksheet(worksheet_name) # * Spreadsheetの背景の色を変える if background_color: self.backGroundColor(shape[1], update_ws) # * 作ったDataFrameを貼り付ける。 set_with_dataframe( update_ws, dataframe, resize=False, include_index=True ) class ChangeAlphabet(): def toAlpha(self, num: int) -> str: """数字からスプレッドシート用のアルファベットに変換 Args: num (int): アルファベットへ変換したい数字 Returns: str: アルファベット """ if num <= 26: return chr(64 + num) elif num % 26 == 0: return self.toAlpha(num // 26 - 1) + chr(90) else: return self.toAlpha(num // 26) + chr(64 + num % 26) def toNumber(self, alphabet: str) -> int: """アルファベットから数字へ変換 Args: alphabet (str): 数字に変換したいアルファベット Returns: int: 数字 """ num = 0 for index, item in enumerate(list(alphabet)): num += pow(26, len(alphabet) - index - 1) * \ (ord(item) - ord('A') + 1) return num class SpreadBase(ChangeAlphabet): def __init__(self, spreadsheet_id: str, json_name: str): """スプレッドシートのオブジェクトを取得する.""" SCOPES = [ 'https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive' ] credentials = ServiceAccountCredentials.from_json_keyfile_name( json_name, SCOPES) gc = gspread.authorize(credentials) self.workbook = gc.open_by_key(spreadsheet_id) def background_color(self, wide_length: int, worksheet_name: str): """ワークシートの背景を変える Args: wide_length (int): 何列まで変えるか指定するための長さ worksheet_name (str): 変更するシート名 """ alphabet = self.toAlpha(wide_length) fmt = cellFormat( backgroundColor=color(1, 0.9, 0.9), horizontalAlignment='CENTER' ) range_ = 'A1:{}1'.format(alphabet) format_cell_range( worksheet_name, range_, fmt ) def pd_change(self, dict_type: dict): """辞書からデータフレームを作成する Args: dict_type (dict): dataframeに変換したい辞書 Returns: [type]: データフレーム """ d2 = {} for k, v in dict_type.items(): # 一度pd.Seriesに変換 d2[k] = pd.Series(v) df = pd.DataFrame(d2) # df.to_csv("ロジオス.csv") return df
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(画像あり)コピペで使える、PythonでGoogleスプレッドシートへ色をつけて書き込む

書き込み後の画像 下記のように書き込まれます コードのサンプルは 自己紹介 普段私は、 一番得意な機械学習(深層学習)をしたり、 Python/Django でWebアプリを開発したり、 TypeScript/Vue or React でフロントエンドの開発をしたり、 PHP/Laravel でWebアプリを開発したり、 さまざまなことを行っています。 趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。 今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。 企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。 nagamatsu-k@dym.jp Googleスプレッドシートに書き込む超便利なクラスを作成した ライブラリのInstall 下記がrequirements.txtの中身になります。 gspread==3.7.0 gspread-dataframe==3.2.1 gspread-formatting==1.0.3 oauth2client==4.1.3 pandas==1.3.0 実際に下記のコマンドでinstallできるので実行していってください。 $ echo gspread==3.7.0"\n"gspread-dataframe==3.2.1"\n"gspread-formatting==1.0.3"\n"oauth2client==4.1.3"\n"pandas==1.3.0 >> requirements.txt $ pip install -r requirements.txt 先に使い方!! コードを全文をspread_tranceform.pyで保存していたとする。 # インスタンスの生成 from spread_tranceform import SpreadSheetManipulate """ 色々と処理をする。 スクレイピングなり、なんでも良い。 """ ss = SpreadSheetManipulate( "スプレッドシートID", "service-account.jsonのパス" ) # 書き込みたい2次元配列 array2D = [ ["会社名", "従業員数", "電話番号", "メールアドレス", "HP"], ["株式会社A社", "234名", "09000000000", "kabusikigaisya-a@a.com", "https://a.com/"], ["株式会社B社", "65名", "09011111111", "kabusikigaisya-b@b.com", "https://b.com/"], ["株式会社C社", "769名", "09022222222", "kabusikigaisya-c@c.com", "https://c.com/"] ] # シート名 sheet_name = "会社情報" # 2次元配列の書き込み ss.write_shape_of_array2D_to_spreadsheet( sheet_name, array2D ) これで終わりです!! 実際に下記写真のように書き込まれます。 コードを全文! 本来は3ファイルにそれぞれ分割していたが分けるのがめんどくさかったため1つにまとめた笑 許してください、、、 メソッドにコメント載せてるので深く知りたい人はリーディングしてください コピーして使用できます。 file名を下記にしてコードを貼り付けて保存する。 spread_tranceform.py from gspread_dataframe import set_with_dataframe from oauth2client.service_account import ServiceAccountCredentials import gspread from gspread_formatting import * import pandas as pd class SpreadSheetManipulate(SpreadBase): def __init__(self, spreadsheet_id: str, json_name: str): super().__init__(spreadsheet_id, json_name) def write_shape_of_array2D_to_spreadsheet( self, worksheet_name: str, array2D: list, background_color: bool = True ) -> None: """ 2次元配列をスプレッドシートに書き込むメソッド カラムの部分をピンク色にするだけでなく、 シート名を引数に渡すことで、spreadsheet(WorkBook)に存在しないシート名だった場合 新規にシートを作成して、そこに書き込む Args: worksheet_name (str): 書き込みたいシート名(既存でも新規でも良い) array2D (list): 書き込む値の入った2次元配列 => [[a,b,c],[1,2,3]] background_color (bool, optional): カラムに色をつけるかどうか. Defaults to True. """ # * 追加する項目の横の長さ width: int = len(array2D[0]) # * 追加する項目の縦の長さ height: int = len(array2D) try: self.workbook.add_worksheet( title=worksheet_name, rows=height, cols=width) print("追加") except BaseException: pass update_ws = self.workbook.worksheet(worksheet_name) # * Spreadsheetの背景の色を変える if background_color: self.background_color(width, update_ws) # * 行列の作成 cell_list = update_ws.get_all_values() # * 行の数 columns = len(cell_list) + 1 ranges = '{0}!A{1}'.format(worksheet_name, columns) # * print(ranges) self.workbook.values_update( ranges, params={'valueInputOption': 'USER_ENTERED'}, body={'values': array2D} ) def write_dataflame_to_spreadsheets( self, type_dict: dict, worksheet_name: str, background_color: bool = True ) -> None: """ 辞書型をスプレッドシートに書き込むメソッド カラムの部分をピンク色にするだけでなく、 シート名を引数に渡すことで、spreadsheet(WorkBook)に存在しないシート名だった場合 新規にシートを作成して、そこに書き込む Args: type_dict (dict): 書き込む値の入った辞書型 worksheet_name (str): 書き込みたいシート名(既存でも新規でも良い) background_color (bool, optional): カラムに色をつけるかどうか. Defaults to True. """ dataframe = self.pd_change(type_dict) # * dataframeの長さ取得(行, 列) -> (縦, 横) shape = dataframe.shape() try: self.workbook.add_worksheet( title=worksheet_name, rows=shape[0], cols=shape[1]) except BaseException: pass update_ws = self.workbook.worksheet(worksheet_name) # * Spreadsheetの背景の色を変える if background_color: self.backGroundColor(shape[1], update_ws) # * 作ったDataFrameを貼り付ける。 set_with_dataframe( update_ws, dataframe, resize=False, include_index=True ) class ChangeAlphabet(): def toAlpha(self, num: int) -> str: """数字からスプレッドシート用のアルファベットに変換 Args: num (int): アルファベットへ変換したい数字 Returns: str: アルファベット """ if num <= 26: return chr(64 + num) elif num % 26 == 0: return self.toAlpha(num // 26 - 1) + chr(90) else: return self.toAlpha(num // 26) + chr(64 + num % 26) def toNumber(self, alphabet: str) -> int: """アルファベットから数字へ変換 Args: alphabet (str): 数字に変換したいアルファベット Returns: int: 数字 """ num = 0 for index, item in enumerate(list(alphabet)): num += pow(26, len(alphabet) - index - 1) * \ (ord(item) - ord('A') + 1) return num class SpreadBase(ChangeAlphabet): def __init__(self, spreadsheet_id: str, json_name: str): """スプレッドシートのオブジェクトを取得する.""" SCOPES = [ 'https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive' ] credentials = ServiceAccountCredentials.from_json_keyfile_name( json_name, SCOPES) gc = gspread.authorize(credentials) self.workbook = gc.open_by_key(spreadsheet_id) def background_color(self, wide_length: int, worksheet_name: str): """ワークシートの背景を変える Args: wide_length (int): 何列まで変えるか指定するための長さ worksheet_name (str): 変更するシート名 """ alphabet = self.toAlpha(wide_length) fmt = cellFormat( backgroundColor=color(1, 0.9, 0.9), horizontalAlignment='CENTER' ) range_ = 'A1:{}1'.format(alphabet) format_cell_range( worksheet_name, range_, fmt ) def pd_change(self, dict_type: dict): """辞書からデータフレームを作成する Args: dict_type (dict): dataframeに変換したい辞書 Returns: [type]: データフレーム """ d2 = {} for k, v in dict_type.items(): # 一度pd.Seriesに変換 d2[k] = pd.Series(v) df = pd.DataFrame(d2) # df.to_csv("ロジオス.csv") return df
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データの前処理ですることリスト

重回帰分析を実装する前に、少しでも精度を上げるために、データの前処理をする必要があります。 1.機械学習のアルゴリズムに適用できる形に変換する前処理 2.モデルの精度を向上させるために行う前処理 の2つのフェーズに分けることができますので、それぞれ書いておきます。 1.機械学習のアルゴリズムに適用できる形に変換する前処理 1-1.データの確認 1-2.重複行の確認 1-3.欠損値処理 1-4.カテゴリカル変数処理 1-5.重回帰分析の実装 以上のような流れで機械学習のアルゴリズムに適用できる形に変換していきます。 重複行の確認 重複の確認には、df.duplicated()を使用します。 ()の中をkeep=Falseにすることで、重複データをすべてTrue, それ以外をFalseと返します。 # 重複行の数の確認 df.duplicated(keep=False).value_counts() # 重複行の削除 df.drop_duplicates() 欠損値処理 欠損値とは、NaNと表記されているもののことです。csvの中でいう空白のセルです。 欠損値の確認は、df.isnull()で確認できます。 df.isnull().sum()で各列ごとの欠損値の数の合計を確認できます。 欠損値の除去 df.dropna()で欠損値が含まれる全ての行が削除されます。 df.dropna(subset=['列名'], axis=0)とすると、'列名'の列に欠損値が含まれている行を削除できます。 列の中身のほとんどが欠損値の場合は、列ごと削ってしまってもいいかもしれません。 その場合は、df.drop(labels='列名', axis=1)で列を削除できます。 欠損値の補完 欠損値を平均値や中央値で補完する時もあります。 どちらを使うかはケースバイケースです。 正規分布に近いような分布をしている時は、平均値を当てはめるといいかもしれません。 df.describe()でデータ統計量の確認ができます。 plt.hist(df['列名']で列の中のデータがどのような分布をしているか可視化できます。 補完する時は.fillna()を使います。 # 欠損値を平均値で補完 df.fillna({'列名':df['列名'].mean()}) # 欠損値を中央値で補完 df.fillna({'列名':df['列名'].median()}) カテゴリカル変数処理 文字列データ含め、カテゴリを表すデータをカテゴリカル変数といいます。質的変数とも呼ばれます。 例えば「男性, 女性」というような文字列データはカテゴリカル変数です。 また、男性を 0, 女性を 1 と置き換えた「0,1」のデータも、値としては文字列ではなく数値ですが、カテゴリカル変数です。 補完する時は、最頻値を使用するケースが多いです。 最頻値は.mode()で取得できます。 どのような文字が入っているかを調べるときは.unique()で見ることができます。 # 最頻値(abc)を使用して欠損値を補完 df.fillna({'abc':df['列名'].mode()[0]}) Label Encodeing Label Encodingは各カテゴリに 0, 1, 2, ... と数値を割り振る変換です。 scikit-learnの中でも前処理系のものを取り扱うにはpreprocessingを使用します。 # モデルの宣言 from sklearn.preprocessing import LabelEncoder le = LabelEncoder() le.fit(df['列名']) # モデルの適用 le.transform(df['列名']) どのカテゴリがどのラベルに変換されたかはle.classes_で確認できます。 One-Hot Encoding One-Hot Encodingはダミー変数化とも言われ、各カテゴリの値ごとに0, 1の列を作成します。 カテゴリの値の種類の数だけ列が増えます。 pd.get_dummies()でできます。 2.モデルの精度を向上させるために行う前処理 2-1.特徴量エンジニアリング 2-2.外れ値除去 特徴量エンジニアリング 特徴量エンジニアリングとは現在あるデータを用い、より有用なデータを作成することです。 例えば、「カツオ、リンゴ、レモン、イワシ、ブタ、ウシ」という文字列が含まれる列の場合、 カツオとイワシは魚なので、1 リンゴとレモンは果物なので、2 ブタとウシは肉なので、3 のようにグルーピングしてみると、分析結果が良くなるかもしれません。 # クラス分けのリストの定義 class_1 = ['カツオ', 'イワシ'] class_2 = ['リンゴ', 'レモン'] class_3 = ['ブタ', 'ウシ'] # それぞれを置換するリストの作成 food = [] for i in df['列名']: if i in class_1: food.append(1) elif i in class_2: food.append(2) elif i in class_3: food.append(3) # データフレームに列を追加 df['food'] = food このように、多いカテゴリをより有用な少ないカテゴリに分け直すような方法以外にも、 a複数変数をかけ合わせて、交互作用特徴量を得る ある変数の累乗をとって、多項式特徴量を得る ある変数の対数をとる 連続値を離散値に変換する(ビニング) など、さまざまな方法があります。 外れ値除去 外れ値を除去する時は、「3σ法(平均値ベース)」と「ハンベル判別法(中央値ベース)」の2つの手法が主です。 基本的には3σ法を採用し、それでうまく外れ値を検知出来ない場合はハンペル判別法を使うといいです。 ハンペル判別法は外れ値の数が多く、平均が引っ張られている場合などに有用な手法です。 3σ法 # 3σ法 mu = df['列名'].mean() # 平均値 sigma = df['列名'].std() # 標準偏差 df = df[(mu - 3 * sigma <= df['列名']) & (df['列名'] <= mu + 3 * sigma)] ハンペル判別法 3σ法における平均値が中央値に、標準偏差が中央絶対偏差(MAD)に変わります。 # ハンペル判別法 median = df['列名'].median() #中央値 MAD = 1.4826 * np.median(abs(df['列名']-median)) df = df[(median - 3 * MAD <= df['列名']) & (df['列名'] <= median + 3 * MAD)]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaggleのコードメモ(自分向け)

1.データ import 全体的に使うライブラリです。 各項目で使うライブラリはそちら側でimportを記載しています。 import numpy as np import pandas as pd from matplotlib import pyplot as plt 使用するデータ Kaggleチュートリアルのタイタニックを使います。 コード df_train = pd.read_csv("/kaggle/input/titanic/train.csv") df_test = pd.read_csv("/kaggle/input/titanic/test.csv") 学習データとテストデータをまとめて管理する マージ # データをマージ df_test["Survived"] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) print(df.shape) # (1309, 12) ※目的変数(Survived)のテスト側の値はnanになります 分割 df_train = df[df["Survived"].notnull()] df_test = df[df["Survived"].isnull()] print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) 数値データの要約統計量を確認したい print(df[["Age", "Fare"]].describe()) Age Fare count 1046.000000 1308.000000 mean 29.881138 33.295479 std 14.413493 51.758668 min 0.170000 0.000000 25% 21.000000 7.895800 50% 28.000000 14.454200 75% 39.000000 31.275000 max 80.000000 512.329200 カテゴリデータの要約統計量を確認したい print(df[["Sex"]].describe(include="O")) Sex count 1309 unique 2 top male freq 843 ※欠損値があるとエラーが出るので注意 欠損値を確認したい 方法1 print(df.isnull().sum()) PassengerId 0 Survived 418 Pclass 0 Name 0 Sex 0 Age 263 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 1014 Embarked 2 is_train 0 dtype: int64 方法2 print(df.info()) <class 'pandas.core.frame.DataFrame'> RangeIndex: 1309 entries, 0 to 1308 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 1309 non-null int64 1 Survived 891 non-null float64 2 Pclass 1309 non-null int64 3 Name 1309 non-null object 4 Sex 1309 non-null object 5 Age 1046 non-null float64 6 SibSp 1309 non-null int64 7 Parch 1309 non-null int64 8 Ticket 1309 non-null object 9 Fare 1308 non-null float64 10 Cabin 295 non-null object 11 Embarked 1307 non-null object 12 is_train 1309 non-null int64 dtypes: float64(3), int64(5), object(5) memory usage: 133.1+ KB 方法2は欠損値以外の情報も表示されます。 2.データの可視化 カテゴリデータを表示したい(1変数) def plot_category(df, column, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(df[column]) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_category(df, "Pclass", scale=0.5, line=[300, 400]) 数値データを表示したい(1変数) def plot_float(df, column, bins=10, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.distplot(df[column], kde=True, rug=False, bins=bins) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_float(df, "Age", scale=0.5, line=20) ※binsはヒストグラムの分割数です カテゴリデータに対してカテゴリデータを比較したい(2変数) plot表示 シンプル def plot_category_category(df, column1, column2, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(column1, hue=column2, data=df) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.legend() plt.title(column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7, line=100) 元のデータとの割合線も表示するバージョン def plot_category_category(df, column1, column2, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 欠損値が入っているデータは除外する df = df.dropna(subset=[column1, column2]).copy() # 型を変換 df[column1] = df[column1].astype(str) df[column2] = df[column2].astype(str) # マージンを設定 margin = 0.2 totoal_width = 1 - margin # hue_data hue_counts = df[column2].value_counts() hue_keys = hue_counts.keys() # clumn_data column_keys = df[column1].value_counts().keys() hue_column_counts = {} for k2 in hue_keys: hue_column_counts[k2] = [] for k in column_keys: c = df[df[column1]==k][column2].value_counts() # hue key毎に分けておく for k2 in hue_keys: if k2 in c: hue_column_counts[k2].append(c[k2]) else: hue_column_counts[k2].append(0) column_counts = [] for k in column_keys: c = len(df[df[column1]==k]) column_counts.append(c) # barの座標を計算 bar_count = len(hue_keys) bar_width = totoal_width/bar_count x_bar_pos = np.asarray(range(bar_count)) * bar_width for i, k in enumerate(hue_keys): counts = hue_column_counts[k] # barを表示 x_pos = np.asarray(range(len(counts))) x_pos = x_pos + x_bar_pos[i] - (bar_count * bar_width) / 2 + bar_width / 2 plt.bar(x_pos, counts, width=bar_width, label=hue_keys[i]) # 線を表示 p_all = (hue_counts[k] / hue_counts.sum()) c = np.asarray(column_counts) * p_all plt.hlines(c, xmin=x_pos - bar_width/2, xmax=x_pos + bar_width/2, colors='r', linestyles='dashed') plt.xticks(range(len(column_keys)), column_keys) plt.xlabel(column1) plt.legend(title=column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7) print表示 集計 print(pd.crosstab(df["Pclass"], df["Sex"])) Sex female male Pclass 1 144 179 2 106 171 3 216 493 各Sexにおける、Pclassの比率 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='columns')) Sex female male Pclass 1 0.309013 0.212337 2 0.227468 0.202847 3 0.463519 0.584816 Sex female male 各Pclassにおける、Sexの割合 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='index')) Sex female male Pclass 1 0.445820 0.554180 2 0.382671 0.617329 3 0.304654 0.695346 カテゴリデータに対して数値データを比較したい(2変数) def plot_category_float( df, column1, column2, bins=10, exclude=[], scale=1.0, line=None, ): plt.figure(figsize=(6.4*scale, 4.8*scale)) for uniq in df[column1].unique(): if uniq is np.nan: continue if math.isnan(uniq): continue if uniq in exclude: continue # 表示メイン sns.distplot(df[df[column1]==uniq][column2], kde=True, rug=False, bins=bins, label=uniq) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.legend() plt.tight_layout() plt.show() # 表示例 plot_category_float(df, "Pclass", "Age", exclude=[2], scale=0.5, line=50) ※ascendingはNoneの場合ソートしません。  指定する場合はpandasのsort_valuesの引数と同じです。 ※excludeを指定すると任意のカテゴリデータを非表示にできます。 数値データに対して数値データを比較したい(2変数) def plot_float_float( df, column1, column2, hue=None, scale=1.0, ): # 表示メイン sns.jointplot(column1, column2, data=df, hue=hue, palette='Set2', size=6*scale) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float(df, "Age", "Fare", "Pclass", scale=0.5) 数値データと数値データに対してカテゴリデータを比較したい(3変数) plot_float_floatのhueを指定したバージョンです。 def plot_float_float_category(df, column1, column2, column3, scale=1.0,): plot_float_float(df, column1, column2, column3, scale) # 表示例 plot_float_float_category(df, "Age", "Fare", "Pclass", scale=0.7) 数値データと数値データに対して数値データを比較したい(3変数) def plot_float_float_float( df, column1, column2, column3, scale=1.0, ): _ds3 = (df[column3] - df[column3].min()) / (df[column3].max() - df[column3].min()) _ds3 = 10 + _ds3*1000 fig = plt.figure(figsize=(6.4*scale, 4.8*scale)) mappable = plt.scatter(df[column1], df[column2], s=_ds3, c=df[column3], cmap="Set2", alpha=0.3, label=column3) fig.colorbar(mappable) plt.xlabel(column1) plt.ylabel(column2) plt.grid(True) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float_float(df, "Age", "Pclass", "Fare", scale=0.7) ※表示例はPclassなので小数データではないですが… ※cmapの種類についてはここを参考 変数同士の相関係数をみたい ヒートマップの表示 def plot_corr(df, columns, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.heatmap(df[columns].corr(), annot=True, vmax=1, vmin=-1, fmt='.1f', cmap='RdBu') plt.tight_layout() plt.show() # 表示例 plot_corr(df, df.columns) ある変数に対して相関係数が高い変数のみ表示(1対多) def print_corr(df, columns, threshold=0.5): print("--- 相関係数(threshold: {})".format(threshold)) corr = df[columns].corr() count = 0 for i in range(len(corr.columns)): for j in range(i+1, len(corr.columns)): val = corr.iloc[i, j] if abs(val) > threshold: print("{} {}: {:.2f}".format(corr.columns[i], corr.columns[j], val)) count += 1 if count == 0: print("empty") # 表示例 print_corr(df, df.columns, threshold=0.4) --- 相関係数(threshold: 0.4) PassengerId is_train: -0.81 Pclass Age: -0.41 Pclass Fare: -0.56 ある配列データを横棒グラフで表示して比較したい def plot_bar(df, columns, ascending=None, scale=1.0, line=None): if ascending is None: df.plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) else: df.sort_values(columns, ascending=ascending).plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_bar(df[:5], ["Age", "Fare"], ascending=True, scale=0.5, line=20) ※ascendingがNoneの場合は並べ替えしません。  それ以外の場合はpandasのsort_valuesの引数と同じ動作です 3.データ加工 欠損値を補完 df["Age_org"] = df["Age"] # 後で使うので欠損値がある状態も保存 df["Age_na"] = df["Age"].isnull() df["Age"].fillna(df2["Age"].median(), inplace=True) df["Embarked"].fillna("S", inplace=True) df["Fare"].fillna(df2['Fare'].median(), inplace=True) Label Encording from sklearn.preprocessing import LabelEncoder df["Sex"] = LabelEncoder().fit_transform(df['Sex']) df["Embarked"] = LabelEncoder().fit_transform(df['Embarked']) ダミー化(One-hotエンコーディング) pandasのget_dummiesで簡単にできますが、カラム名も欲しい場合のものを作成しています。 def dummy(df, column): c_arr = [] for c in df[column].value_counts().keys(): name = column + "_" + str(c) c_arr.append(name) df[name] = np.asarray(df[column] == c).astype(int) return df, c_arr 4.目的変数に対する各説明変数の影響度を確認したい 確認に使っているカラムは以下です。 (Familyも何となく追加) df["Family"] = df["SibSp"] + df["Parch"] select_columns = [ "Pclass", "Sex", "Age_completion", "SibSp", "Parch", "Family", "Fare", "Embarked", ] 分類 ロジスティック回帰の係数 ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import sklearn.linear_model import xgboost as xgb import lightgbm as lgb def create_feature_effect_classifier(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # ロジスティック回帰 #---------------- if not exists_missing_value: model = sklearn.linear_model.LogisticRegression(random_state=1234) model.fit(x, y) if len(model.coef_) == 1: # 2クラス分類 df_result["LogisticRegression"] = model.coef_[0] else: # 多クラスの場合は各クラスの係数の平均を出す df_result["LogisticRegression"] = np.mean(model.coef_, axis=0) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestClassifier(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMClassifier(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 実行例 df_feature = create_feature_effect_classifier(df, select_columns, "Survived") print(df_feature) plot_bar(df_feature, "LogisticRegression", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) LogisticRegression RandomForest XGB LGBM name Pclass -1.050753 0.072651 0.233783 116 Sex -2.631111 0.257130 0.509833 97 Age -0.038077 0.255461 0.032465 1055 SibSp -0.182407 0.030305 0.078906 51 Parch 0.047843 0.024342 0.034393 56 Family -0.134563 0.055398 0.047479 154 Fare 0.002171 0.272152 0.032554 1344 Embarked -0.213182 0.032561 0.030586 125 回帰 重回帰分析の決定係数(とP値) ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import statsmodels.api as sm import sklearn.ensemble import xgboost as xgb import lightgbm as lgb def create_feature_effect_regressor(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # 重回帰分析 #---------------- if not exists_missing_value: model = sm.OLS(y, sm.add_constant(x)) fitted = model.fit() #print(fitted.summary()) if len(fitted.params) == len(select_columns): df_result["OLS_val"] = list(fitted.params) df_result["OLS_p"] = list(fitted.pvalues) elif len(fitted.params) == len(select_columns)+1: df_result["OLS_val"] = list(fitted.params)[1:] df_result["OLS_p"] = list(fitted.pvalues)[1:] df_result["OLS_p"] = np.round(df_result["OLS_p"], 3) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestRegressor(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBRegressor(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMRegressor(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 表示例 df_feature = create_feature_effect_regressor(df, select_columns, "Age") print(df_feature) plot_bar(df_feature, "OLS_val", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) OLS_val OLS_p RandomForest XGB LGBM name Pclass -5.622647 0.000 0.205440 0.496402 140 Sex 2.020035 0.004 0.054703 0.038413 190 SibSp -0.910016 0.003 0.062044 0.066298 141 Parch 0.036394 0.913 0.164997 0.187804 234 Family -0.873621 0.000 0.066830 0.086484 171 Fare 0.006825 0.400 0.395958 0.056573 1940 Embarked 0.244200 0.556 0.050028 0.068026 184 P値(OLD_p)が0.05以上の場合は有意性がありません。(決定係数(OLS_val)が0の可能性があります) 今回ですと、Parch,Fare,Embarked は有意性がないので説明変数としては採用しないほうが良いかもしれません。 決定木 import sklearn.tree def plot_decision_tree(df, columns, target_column, model_type, # "Classifier" or "Regressor" scale=1.0, max_depth=None, # 表示する木の深さ fontsize=None, # 文字の大きさ ): x = df[df[target_column].notnull()][columns] y = df[df[target_column].notnull()][target_column] if model_type == "Classifier": clf = sklearn.tree.DecisionTreeClassifier(random_state=1234) elif model_type == "Regressor": clf = sklearn.tree.DecisionTreeRegressor(random_state=1234) else: raise ValueError() clf = clf.fit(x, y) plt.figure(figsize=(6.4*scale, 4.8*scale)) sklearn.tree.plot_tree(clf, feature_names=columns, max_depth=max_depth, fontsize=fontsize, proportion=False, # Trueだと割合で表示 filled=True, # 純度に応じて色がつく ) plt.show() # 表示例 plot_decision_tree(df, select_columns, "Survived", "Classifier", scale=1, max_depth=2, fontsize=10) 5.Validation K-分割交差検証 層状K-分割交差検証の実装例です。 def validation_classifier( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 model_cls, # 使うモデル model_params, # モデルのパラメータ ): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # 交差検証 metrics = [] #kf = sklearn.model_selection.KFold(n_splits=3, shuffle=True, random_state=1234) # KFoldにしたい場合 kf = sklearn.model_selection.StratifiedKFold(n_splits=3, shuffle=True, random_state=1234) for train_idx, test_idx in kf.split(df_train, y=df_train[target_column]): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] model = model_cls(**model_params) model.fit(x_train, y_train) # 正解率 y_pred = model.predict(x_test) metric = sklearn.metrics.accuracy_score(y_test, y_pred) # logloss #y_pred = model.predict_proba(x_test) #metric = sklearn.metrics.log_loss(y_test, y_pred[:,1]) metrics.append(metric) # 交差検証の結果は平均を採用 metrics_mean = np.mean(metrics, axis=0) return metrics_mean # 実行例 metric = validation_classifier(df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}) print(metric) 0.8103254769921437 Adversarial Validation 学習データとテストデータの分布を確認し、分布が偏っている場合にテストデータに近いデータを検証に使う手法です。 モデルはLGBMで決め打ちしています。 import sklearn.model_selection import sklearn.metrics import lightgbm as lgb def adversarial_validation_check( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 result_column_name="", # 結果保存用のカラム名 ebable_plot=True, ): # モデル model = lgb.LGBMClassifier(**{"random_state": 1234}) # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df.copy() # 目的変数を作成 df_train["_target"] = df_train[target_column].apply(lambda x: np.isnan(x)).astype(int) # 学習データとテストデータに分割 df_train_sub, df_true_sub = sklearn.model_selection.train_test_split( df_train, test_size=0.3, stratify=df_train["_target"], random_state=1234) x_train = df_train_sub[select_columns] y_train = df_train_sub["_target"] x_true = df_true_sub[select_columns] y_true = df_true_sub["_target"] # 学習 model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_true, y_true)], eval_names=['train', 'valid'], eval_metric='auc', verbose=0) if ebable_plot: # 学習結果をplot lgb.plot_metric(model.evals_result_, metric='auc') plt.axhline(y=0.5, color="red", linestyle="--") plt.ylim(0.4, 1.0) plt.tight_layout() plt.show() # 分布に影響が大きい変数順に並べて表示する pd.DataFrame(model.feature_importances_, index=select_columns ).sort_values(0, ascending=True).plot.barh() plt.tight_layout() plt.show() # 結果を新しい特徴量として追加 if result_column_name != "": y_pred = model.predict_proba(x_true) df[result_column_name] = pd.Series(y_pred[:,1]) # auc値を返す return sklearn.metrics.roc_auc_score(y_true, y_pred[:,1]) # 実行例 adversarial_validation_check(df, select_columns, "Survived") adversarial_validation_check(df, select_columns, "Age_org", "train_ratio_Age") Survivedの結果 valid の値が0.5付近なので、学習データとテストデータを区別できない=学習データとテストデータの分布が同じです。 この場合はAdversarial Validationを使う必要はありません。 Ageの結果 Ageは欠損値のないデータ群と欠損値のあるデータ群を比較しています。 validが約0.8とかなり高い確率で予測できています。 Ageの欠損値がある場合とない場合で分布に偏りがある事が分かります。 また、変数の重要度を見ることで、分布の偏りの原因になっている変数が分かります。 (今回はFareが一番影響しているようですね) では、これを用いて検証するコード例です。 上記モデルで結果を予測し、テストデータである確率が高いデータを検証データに、 そうじゃないデータは学習データにして検証を行います。 def adversarial_validation_Age( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト model_cls, # 使うモデル model_params, # モデルのパラメータ ): target_column = "Age_org" # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # データをtrain_ratioに従って分割 test_size = 0.25 test_num = int(len(df_train) * test_size) df_train = df_train.sort_values("train_ratio_Age") df_train_sub = df_train[test_num:] df_test_sub = df_train[:test_num] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] # 学習 model = model_cls(**model_params) model.fit(x_train, y_train) y_pred = model.predict(x_test) # 評価 metric = np.sqrt(sklearn.metrics.mean_squared_error(y_test, y_pred)) return metric metric = adversarial_validation_Age(df, select_columns, lgb.LGBMRegressor, {"random_state": 1234}) print(print) 12.119246573863585 6.特徴量抽出 参考:特徴量選択のまとめ 全く関係ない特徴量を減らしたい 分散が0(すべて同じ値)データを削除 全く同じ特徴量のカラムを削除 import sklearn.feature_selection def feature_selection_no_related( df, columns, enable_print=False ): prev_columns = columns prev_len = len(prev_columns) #------------------------------- # 分散が0(すべて同じ値)のカラムは削除 #------------------------------- vt = sklearn.feature_selection.VarianceThreshold(threshold=0) vt.fit(df[prev_columns]) inc_columns = [] exc_columns = [] for i, flag in enumerate(vt.get_support()): if flag: inc_columns.append(prev_columns[i]) else: exc_columns.append(prev_columns[i]) if enable_print: print("var0 {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) #------------------------------- # 全く同じ特徴量のカラムは削除 #------------------------------- prev_columns = inc_columns prev_len = len(prev_columns) inc_columns = [] exc_columns = [] for c, flag in df[prev_columns].T.duplicated().items(): if not flag: inc_columns.append(c) else: exc_columns.append(c) if enable_print: print("dup {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 df["A"] = 1 df["B"] = df["Sex"] new_columns = util.feature_reduction_no_related(df, select_columns + ["A", "B"], enable_print=True) print(new_columns) var0 10 -> 9 exc: ['A'] dup 9 -> 8 exc: ['B'] ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Family', 'Fare', 'Embarked'] 多分関係ない特徴量を減らしたい 相関係数 相関係数は0なら相関なし、1または-1なら正か負の強い相関があります。 0に違い特徴量を減らします。 def feature_selection_corr( df, columns, target_columns, threshold, enable_print=False ): # target_columnをいれる prev_columns = list(set(list(columns) + [target_columns])) prev_len = len(prev_columns) # 相関係数 df_corr = df[prev_columns].corr() df_corr.fillna(0, inplace=True) # 閾値以下は除外 inc_columns = [] exc_columns = [] for c in df_corr.columns: val = df_corr[target_columns][c] if abs(val) < threshold: exc_columns.append([c, val]) else: inc_columns.append(c) if enable_print: print("corr {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 new_columns = util.feature_selection_corr(df, select_columns, "Survived", 0.1, enable_print=True) print(new_columns) corr 9 -> 5 exc: [['SibSp', -0.03532249888573556], ['Parch', 0.08162940708348335], ['Age', -0.07722109457217759], ['Family', 0.016638989282745195]] ['Embarked', 'Fare', 'Sex', 'Survived', 'Pclass'] 関係のある特徴量のみを抽出したい 評価の上がる特徴量を追加(または削除)していく 全説明変数を見て一番評価が上がる変数を追加または削除していく手法です。 Foward説明変数がない状態から1つずつ追加していく方法で、 Backwardは全説明変数から1つずつ減らしてい区方法です。 説明変数の数に対して最大O(N^2)時間がかかると思います。 import sklearn.feature_selection def feature_selection_SFS( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ direction, # "forward"で追加していく、"backward"で削除していく scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.SequentialFeatureSelector( model_cls(**model_params), direction=direction, scoring=scoring ) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_SFS( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, direction="forward", scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'Fare'] exc ['SibSp', 'Parch', 'Family', 'Embarked'] RFECV RFECVは簡単に言うとモデルを作成し、重要度が低い変数を除外するという特徴量選択方法らしいです。 その方法から、coef_またはfeature_importances_が作られるモデルしか適用できません。 参考:【Kaggle】タイタニックの振り返り#3 RFECVで特徴選択 import sklearn.feature_selection def feature_selection_RFECV( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.RFECV(model_cls(**model_params), scoring=scoring) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_RFECV( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'SibSp', 'Family', 'Fare', 'Embarked'] exc ['Parch'] 7.モデル モデル一覧 分類モデル import sklearn.ensemble import sklearn.gaussian_process import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.discriminant_analysis import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_classifier(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostClassifier, {"random_state": random_seed}], [sklearn.ensemble.BaggingClassifier, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesClassifier, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingClassifier, {"random_state": random_seed}], [sklearn.ensemble.RandomForestClassifier, {"random_state": random_seed}], #Gaussian Processes [sklearn.gaussian_process.GaussianProcessClassifier, {"random_state": random_seed}], #GLM [sklearn.linear_model.RidgeClassifier, {}], #Navies Bayes [sklearn.naive_bayes.BernoulliNB, {}], [sklearn.naive_bayes.GaussianNB, {}], #Nearest Neighbor [sklearn.neighbors.KNeighborsClassifier, {}], #Trees [sklearn.tree.DecisionTreeClassifier, {"random_state": random_seed}], [sklearn.tree.ExtraTreeClassifier, {"random_state": random_seed}], # SVM [sklearn.svm.SVC, {}], [sklearn.svm.NuSVC, {}], [sklearn.svm.LinearSVC, {}], # NN [sklearn.neural_network.MLPClassifier, {"random_state": random_seed}], #xgboost [xgb.XGBClassifier, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMClassifier, {"random_state": random_seed}], ] return models models = get_models_classifier(1234) 回帰モデル import sklearn.ensemble import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_regressor(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostRegressor, {"random_state": random_seed}], [sklearn.ensemble.BaggingRegressor, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesRegressor, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingRegressor, {"random_state": random_seed}], [sklearn.ensemble.RandomForestRegressor, {"random_state": random_seed}], # GLM [sklearn.linear_model.Lasso, {"random_state": random_seed}], [sklearn.linear_model.Ridge, {"random_state": random_seed}], [sklearn.linear_model.ElasticNet, {"random_state": random_seed}], #Nearest Neighbor [sklearn.neighbors.KNeighborsRegressor, {}], #Trees [sklearn.tree.DecisionTreeRegressor, {"random_state": random_seed}], [sklearn.tree.ExtraTreeRegressor, {"random_state": random_seed}], # SVM [sklearn.svm.SVR, {}], [sklearn.svm.NuSVR, {}], [sklearn.svm.LinearSVR, {}], # NN [sklearn.neural_network.MLPRegressor, {"random_state": random_seed}], #xgboost [xgb.XGBRegressor, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMRegressor, {"random_state": random_seed}], ] return models models = get_models_regressor(1234) ハイパーパラメータの調整(optuna) モデルパラメータ クリックで展開 def create_optuna_params(trial, model_cls, random_seed): #----------------------------------------------------------- # Classifier #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "algorithm": trial.suggest_categorical('algorithm', ["SAMME", "SAMME.R"]), # default=SAMME.R "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 #"max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingClassifier: return { "loss": trial.suggest_categorical('loss', ["deviance", "exponential"]), # default=deviance "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Gaussian Processes if model_cls == sklearn.gaussian_process.GaussianProcessClassifier: return {"random_state": random_seed} # GLM if model_cls == sklearn.linear_model.RidgeClassifier: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True "normalize": trial.suggest_categorical('normalize', [False, True]), # default=False "random_state": random_seed } #Navies Bayes if model_cls == sklearn.naive_bayes.BernoulliNB: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 } if model_cls == sklearn.naive_bayes.GaussianNB: return {} # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsClassifier: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVC: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2]), # default=1.0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVC: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVC: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["hinge", "squared_hinge"]), # default=squared_hinge "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPClassifier: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMClassifier: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } #----------------------------------------------------------- # Regressor #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "loss": trial.suggest_categorical('loss', ["linear", "square", "exponential"]), # default=linear "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 "max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingRegressor: return { "loss": trial.suggest_categorical('loss', ["ls", "lad", "huber", "quantile"]), # default=ls "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsRegressor: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVR: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0.1 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVR: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVR: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["epsilon_insensitive", "squared_epsilon_insensitive"]), # default=epsilon_insensitive "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPRegressor: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMRegressor: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } パラメータ調整コード例 from tqdm import tqdm import time # 結果格納用 df_compare = pd.DataFrame(columns=['name', 'metric1', "metric2", 'params', 'time']) #--------------------------- # optunaの定義 #--------------------------- # optuna silent optuna.logging.set_verbosity(optuna.logging.WARNING) def objective_degree(model_cls, df): def objective(trial): params = create_optuna_params(trial, model_cls, 1234) #--- モデルを評価する metric = validation_classifier( df, select_columns, "Survived", model_cls, params) return metric return objective #--------------------------- # モデル毎にパラメータをサーチする #--------------------------- for model_ in tqdm(get_models_classifier(1234)): model_cls = model_[0] model_params = model_[1] name = model_cls.__name__ try: # デフォルトパラメータでの結果を取得しておく metric1 = validation_classifier( df, select_columns, "Survived", model_cls, model_params) # 調整開始 t0 = time.time() study = optuna.create_study(direction="maximize") study.optimize(objective_degree(model_cls, df), timeout=60*1, n_trials=1000) # optunaの結果を取得 best_value = study.best_value best_params = study.best_params trial_count = len(study.trials) t1 = time.time() - t0 # 結果を保存 df_compare = df_compare.append({ "name": name, "metric1": metric1, "metric2": best_value, "params": best_params, "count": trial_count, "time": t1, }, ignore_index=True) except Exception as e: print("error: {}".format(name)) print(e) # 結果を表示 print(df_compare) df_compare.set_index('name', inplace=True) df_compare.plot.barh(y=["metric1", "metric2"]) plt.legend(loc='upper left') plt.tight_layout() plt.show() name metric1 metric2 time count 0 AdaBoostClassifier 0.795735 0.815937 63.645483 30.0 1 BaggingClassifier 0.804714 0.826038 60.307925 52.0 2 ExtraTreesClassifier 0.804714 0.823793 60.205603 330.0 3 GradientBoostingClassifier 0.829405 0.839506 60.315318 148.0 4 RandomForestClassifier 0.804714 0.826038 60.258668 112.0 5 GaussianProcessClassifier 0.710438 0.710438 60.037200 101.0 6 RidgeClassifier 0.792368 0.810325 41.930431 1000.0 7 BernoulliNB 0.784512 0.784512 30.653054 1000.0 8 GaussianNB 0.786756 0.786756 21.921596 1000.0 9 KNeighborsClassifier 0.694725 0.753086 60.289474 957.0 10 DecisionTreeClassifier 0.784512 0.808081 35.006361 1000.0 11 ExtraTreeClassifier 0.764310 0.797980 33.426985 1000.0 12 LinearSVC 0.738496 0.801347 60.149822 157.0 13 MLPClassifier 0.794613 0.819304 60.149625 101.0 14 XGBClassifier 0.812570 0.829405 60.045466 56.0 15 LGBMClassifier 0.810325 0.838384 61.060753 47.0 metric1がデフォルト値、metric2が調整後の数値です。 ハイパーパラメータ調整の効果はあるようです。 ただ、時間がかなりかかりますね… 8.その他テクニックなど 目的変数の対数変換(回帰) 目的変数の対数変換です。 参照:どのようなときに目的変数Yではなくlog(Y)にしたほうがよいのか?~対数変換するメリットとデメリット~ 参照のメリットを引用すると、 メリット: Y の値が小さいサンプルの誤差が小さくなる デメリット: Y の値が大きいサンプルの誤差が大きくなる です。 対数変換 df["Survived"] = np.log(df["Survived"]) 元に戻す model = 対数変換した目的変数でモデルを学習 # 予測する x = df[df["Survived"].isnull()][select_columns] y_pred = model.predict(x) # 予測結果を元に戻す y_pred = np.exp(y_pred) 例のSurvivedは分類問題なので実施するメリットはありませんけど… AutoML(PyCaret)の適用 PyCaret を使う例です。 表示はJupyter Notebookで使う場合の出力に最適化されているようです。 参考:最速でPyCaretを使ってみた 1.データ読み込み 学習に使うデータは以下です。 pcc_df = df[df["Survived"].notnull()][[ "Pclass", "Sex", "Age", "SibSp", "Parch", "Family", "Fare", "Embarked", "Survived", # 学習データなので目的変数もいれます ]] # データを渡す import pycaret.classification as pcc r = pcc.setup(pcc_df, target="Survived", silent=True) 結果は59項目ありました。 重要な項目は色がつくようです? 2.モデル選択 複数のモデルを比較 数分かかります。 best_model = pcc.compare_models() print(best_model.__class__.__name__) # LogisticRegression 任意のモデル 任意のモデルを選びたい場合はこちらから model = pcc.create_model("lightgbm") 3.ハイパーパラメータの調整 tuned_model = pcc.tune_model(model) print(tuned_model.get_params) <bound method LGBMModel.get_params of LGBMClassifier(bagging_fraction=0.7, bagging_freq=3, boosting_type='gbdt', class_weight=None, colsample_bytree=1.0, feature_fraction=0.8, importance_type='split', learning_rate=0.1, max_depth=-1, min_child_samples=36, min_child_weight=0.001, min_split_gain=0.4, n_estimators=230, n_jobs=-1, num_leaves=50, objective=None, random_state=8219, reg_alpha=0.1, reg_lambda=3, silent=True, subsample=1.0, subsample_for_bin=200000, subsample_freq=0)> 調整前後の評価値です。 predict_model はdataを指定しないとホールドアウトで実行した結果を返します。 4.モデルの可視化 pcc.plot_model(tuned_model) 5.モデル解釈の可視化 pcc.interpret_model(tuned_model) 6.予測 "Label"と"Score"カラムが追加され、Labelは予測結果、分類なら各クラスの予測確率がScoreに入っています。 x_pred = df[df["Survived"].isnull()] y_pred = pcc.predict_model(tuned_model, data=x_pred) print(y_pred["Label"]) 891 0.0 892 0.0 893 0.0 894 0.0 895 1.0 ... 1304 0.0 1305 1.0 1306 0.0 1307 0.0 1308 0.0 Name: Label, Length: 418, dtype: object
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaggleで書いたコードの備忘録(可視化、データ加工、検証、特徴量抽出、モデル、AutoML等)

また使うかもしれないので初心者ながらKaggleに挑戦した時のコードを備忘録として残しておきます。 間違いもかなり含んでいると思いますので参考にするか、コピペ編集して使ってください。 1.データ import 全体的に使うライブラリです。 各項目で使うライブラリはそちら側でimportを記載しています。 import numpy as np import pandas as pd from matplotlib import pyplot as plt 使用するデータ Kaggleチュートリアルのタイタニックを使います。 コード df_train = pd.read_csv("/kaggle/input/titanic/train.csv") df_test = pd.read_csv("/kaggle/input/titanic/test.csv") 学習データとテストデータをまとめて管理する マージ # データをマージ df_test["Survived"] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) print(df.shape) # (1309, 12) ※目的変数(Survived)のテスト側の値はnanになります 分割 df_train = df[df["Survived"].notnull()] df_test = df[df["Survived"].isnull()] print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) 数値データの要約統計量を確認したい print(df[["Age", "Fare"]].describe()) Age Fare count 1046.000000 1308.000000 mean 29.881138 33.295479 std 14.413493 51.758668 min 0.170000 0.000000 25% 21.000000 7.895800 50% 28.000000 14.454200 75% 39.000000 31.275000 max 80.000000 512.329200 カテゴリデータの要約統計量を確認したい print(df[["Sex"]].describe(include="O")) Sex count 1309 unique 2 top male freq 843 ※欠損値があるとエラーが出るので注意 欠損値を確認したい 方法1 print(df.isnull().sum()) PassengerId 0 Survived 418 Pclass 0 Name 0 Sex 0 Age 263 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 1014 Embarked 2 is_train 0 dtype: int64 方法2 print(df.info()) <class 'pandas.core.frame.DataFrame'> RangeIndex: 1309 entries, 0 to 1308 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 1309 non-null int64 1 Survived 891 non-null float64 2 Pclass 1309 non-null int64 3 Name 1309 non-null object 4 Sex 1309 non-null object 5 Age 1046 non-null float64 6 SibSp 1309 non-null int64 7 Parch 1309 non-null int64 8 Ticket 1309 non-null object 9 Fare 1308 non-null float64 10 Cabin 295 non-null object 11 Embarked 1307 non-null object 12 is_train 1309 non-null int64 dtypes: float64(3), int64(5), object(5) memory usage: 133.1+ KB 方法2は欠損値以外の情報も表示されます。 2.データの可視化 カテゴリデータを表示したい(1変数) def plot_category(df, column, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(df[column]) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_category(df, "Pclass", scale=0.5, line=[300, 400]) 数値データを表示したい(1変数) def plot_float(df, column, bins=10, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.distplot(df[column], kde=True, rug=False, bins=bins) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_float(df, "Age", scale=0.5, line=20) ※binsはヒストグラムの分割数です カテゴリデータに対してカテゴリデータを比較したい(2変数) plot表示 シンプル def plot_category_category(df, column1, column2, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(column1, hue=column2, data=df) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.legend() plt.title(column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7, line=100) 元のデータとの割合線も表示するバージョン def plot_category_category(df, column1, column2, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 欠損値が入っているデータは除外する df = df.dropna(subset=[column1, column2]).copy() # 型を変換 df[column1] = df[column1].astype(str) df[column2] = df[column2].astype(str) # マージンを設定 margin = 0.2 totoal_width = 1 - margin # hue_data hue_counts = df[column2].value_counts() hue_keys = hue_counts.keys() # clumn_data column_keys = df[column1].value_counts().keys() hue_column_counts = {} for k2 in hue_keys: hue_column_counts[k2] = [] for k in column_keys: c = df[df[column1]==k][column2].value_counts() # hue key毎に分けておく for k2 in hue_keys: if k2 in c: hue_column_counts[k2].append(c[k2]) else: hue_column_counts[k2].append(0) column_counts = [] for k in column_keys: c = len(df[df[column1]==k]) column_counts.append(c) # barの座標を計算 bar_count = len(hue_keys) bar_width = totoal_width/bar_count x_bar_pos = np.asarray(range(bar_count)) * bar_width for i, k in enumerate(hue_keys): counts = hue_column_counts[k] # barを表示 x_pos = np.asarray(range(len(counts))) x_pos = x_pos + x_bar_pos[i] - (bar_count * bar_width) / 2 + bar_width / 2 plt.bar(x_pos, counts, width=bar_width, label=hue_keys[i]) # 線を表示 p_all = (hue_counts[k] / hue_counts.sum()) c = np.asarray(column_counts) * p_all plt.hlines(c, xmin=x_pos - bar_width/2, xmax=x_pos + bar_width/2, colors='r', linestyles='dashed') plt.xticks(range(len(column_keys)), column_keys) plt.xlabel(column1) plt.legend(title=column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7) print表示 集計 print(pd.crosstab(df["Pclass"], df["Sex"])) Sex female male Pclass 1 144 179 2 106 171 3 216 493 各Sexにおける、Pclassの比率 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='columns')) Sex female male Pclass 1 0.309013 0.212337 2 0.227468 0.202847 3 0.463519 0.584816 Sex female male 各Pclassにおける、Sexの割合 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='index')) Sex female male Pclass 1 0.445820 0.554180 2 0.382671 0.617329 3 0.304654 0.695346 カテゴリデータに対して数値データを比較したい(2変数) def plot_category_float( df, column1, column2, bins=10, exclude=[], scale=1.0, line=None, ): plt.figure(figsize=(6.4*scale, 4.8*scale)) for uniq in df[column1].unique(): if uniq is np.nan: continue if math.isnan(uniq): continue if uniq in exclude: continue # 表示メイン sns.distplot(df[df[column1]==uniq][column2], kde=True, rug=False, bins=bins, label=uniq) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.legend() plt.tight_layout() plt.show() # 表示例 plot_category_float(df, "Pclass", "Age", exclude=[2], scale=0.5, line=50) ※ascendingはNoneの場合ソートしません。  指定する場合はpandasのsort_valuesの引数と同じです。 ※excludeを指定すると任意のカテゴリデータを非表示にできます。 数値データに対して数値データを比較したい(2変数) def plot_float_float( df, column1, column2, hue=None, scale=1.0, ): # 表示メイン sns.jointplot(column1, column2, data=df, hue=hue, palette='Set2', size=6*scale) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float(df, "Age", "Fare", "Pclass", scale=0.5) 数値データと数値データに対してカテゴリデータを比較したい(3変数) plot_float_floatのhueを指定したバージョンです。 def plot_float_float_category(df, column1, column2, column3, scale=1.0,): plot_float_float(df, column1, column2, column3, scale) # 表示例 plot_float_float_category(df, "Age", "Fare", "Pclass", scale=0.7) 数値データと数値データに対して数値データを比較したい(3変数) def plot_float_float_float( df, column1, column2, column3, scale=1.0, ): _ds3 = (df[column3] - df[column3].min()) / (df[column3].max() - df[column3].min()) _ds3 = 10 + _ds3*1000 fig = plt.figure(figsize=(6.4*scale, 4.8*scale)) mappable = plt.scatter(df[column1], df[column2], s=_ds3, c=df[column3], cmap="Set2", alpha=0.3, label=column3) fig.colorbar(mappable) plt.xlabel(column1) plt.ylabel(column2) plt.grid(True) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float_float(df, "Age", "Pclass", "Fare", scale=0.7) ※表示例はPclassなので小数データではないですが… ※cmapの種類についてはここを参考 変数同士の相関係数をみたい ヒートマップの表示 def plot_corr(df, columns, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.heatmap(df[columns].corr(), annot=True, vmax=1, vmin=-1, fmt='.1f', cmap='RdBu') plt.tight_layout() plt.show() # 表示例 plot_corr(df, df.columns) ある変数に対して相関係数が高い変数のみ表示(1対多) def print_corr(df, columns, threshold=0.5): print("--- 相関係数(threshold: {})".format(threshold)) corr = df[columns].corr() count = 0 for i in range(len(corr.columns)): for j in range(i+1, len(corr.columns)): val = corr.iloc[i, j] if abs(val) > threshold: print("{} {}: {:.2f}".format(corr.columns[i], corr.columns[j], val)) count += 1 if count == 0: print("empty") # 表示例 print_corr(df, df.columns, threshold=0.4) --- 相関係数(threshold: 0.4) PassengerId is_train: -0.81 Pclass Age: -0.41 Pclass Fare: -0.56 ある配列データを横棒グラフで表示して比較したい def plot_bar(df, columns, ascending=None, scale=1.0, line=None): if ascending is None: df.plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) else: df.sort_values(columns, ascending=ascending).plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_bar(df[:5], ["Age", "Fare"], ascending=True, scale=0.5, line=20) ※ascendingがNoneの場合は並べ替えしません。  それ以外の場合はpandasのsort_valuesの引数と同じ動作です 3.データ加工 欠損値を補完 df["Age_org"] = df["Age"] # 後で使うので欠損値がある状態も保存 df["Age_na"] = df["Age"].isnull() df["Age"].fillna(df2["Age"].median(), inplace=True) df["Embarked"].fillna("S", inplace=True) df["Fare"].fillna(df2['Fare'].median(), inplace=True) Label Encording from sklearn.preprocessing import LabelEncoder df["Sex"] = LabelEncoder().fit_transform(df['Sex']) df["Embarked"] = LabelEncoder().fit_transform(df['Embarked']) ダミー化(One-hotエンコーディング) pandasのget_dummiesで簡単にできますが、カラム名も欲しい場合のものを作成しています。 def dummy(df, column): c_arr = [] for c in df[column].value_counts().keys(): name = column + "_" + str(c) c_arr.append(name) df[name] = np.asarray(df[column] == c).astype(int) return df, c_arr 4.目的変数に対する各説明変数の影響度を確認したい 確認に使っているカラムは以下です。 (Familyも何となく追加) df["Family"] = df["SibSp"] + df["Parch"] select_columns = [ "Pclass", "Sex", "Age_completion", "SibSp", "Parch", "Family", "Fare", "Embarked", ] 分類 ロジスティック回帰の係数 ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import sklearn.linear_model import xgboost as xgb import lightgbm as lgb def create_feature_effect_classifier(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # ロジスティック回帰 #---------------- if not exists_missing_value: model = sklearn.linear_model.LogisticRegression(random_state=1234) model.fit(x, y) if len(model.coef_) == 1: # 2クラス分類 df_result["LogisticRegression"] = model.coef_[0] else: # 多クラスの場合は各クラスの係数の平均を出す df_result["LogisticRegression"] = np.mean(model.coef_, axis=0) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestClassifier(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMClassifier(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 実行例 df_feature = create_feature_effect_classifier(df, select_columns, "Survived") print(df_feature) plot_bar(df_feature, "LogisticRegression", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) LogisticRegression RandomForest XGB LGBM name Pclass -1.050753 0.072651 0.233783 116 Sex -2.631111 0.257130 0.509833 97 Age -0.038077 0.255461 0.032465 1055 SibSp -0.182407 0.030305 0.078906 51 Parch 0.047843 0.024342 0.034393 56 Family -0.134563 0.055398 0.047479 154 Fare 0.002171 0.272152 0.032554 1344 Embarked -0.213182 0.032561 0.030586 125 回帰 重回帰分析の決定係数(とP値) ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import statsmodels.api as sm import sklearn.ensemble import xgboost as xgb import lightgbm as lgb def create_feature_effect_regressor(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # 重回帰分析 #---------------- if not exists_missing_value: model = sm.OLS(y, sm.add_constant(x)) fitted = model.fit() #print(fitted.summary()) if len(fitted.params) == len(select_columns): df_result["OLS_val"] = list(fitted.params) df_result["OLS_p"] = list(fitted.pvalues) elif len(fitted.params) == len(select_columns)+1: df_result["OLS_val"] = list(fitted.params)[1:] df_result["OLS_p"] = list(fitted.pvalues)[1:] df_result["OLS_p"] = np.round(df_result["OLS_p"], 3) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestRegressor(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBRegressor(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMRegressor(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 表示例 df_feature = create_feature_effect_regressor(df, select_columns, "Age") print(df_feature) plot_bar(df_feature, "OLS_val", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) OLS_val OLS_p RandomForest XGB LGBM name Pclass -5.622647 0.000 0.205440 0.496402 140 Sex 2.020035 0.004 0.054703 0.038413 190 SibSp -0.910016 0.003 0.062044 0.066298 141 Parch 0.036394 0.913 0.164997 0.187804 234 Family -0.873621 0.000 0.066830 0.086484 171 Fare 0.006825 0.400 0.395958 0.056573 1940 Embarked 0.244200 0.556 0.050028 0.068026 184 P値(OLD_p)が0.05以上の場合は有意性がありません。(決定係数(OLS_val)が0の可能性があります) 今回ですと、Parch,Fare,Embarked は有意性がないので説明変数としては採用しないほうが良いかもしれません。 決定木 import sklearn.tree def plot_decision_tree(df, columns, target_column, model_type, # "Classifier" or "Regressor" scale=1.0, max_depth=None, # 表示する木の深さ fontsize=None, # 文字の大きさ ): x = df[df[target_column].notnull()][columns] y = df[df[target_column].notnull()][target_column] if model_type == "Classifier": clf = sklearn.tree.DecisionTreeClassifier(random_state=1234) elif model_type == "Regressor": clf = sklearn.tree.DecisionTreeRegressor(random_state=1234) else: raise ValueError() clf = clf.fit(x, y) plt.figure(figsize=(6.4*scale, 4.8*scale)) sklearn.tree.plot_tree(clf, feature_names=columns, max_depth=max_depth, fontsize=fontsize, proportion=False, # Trueだと割合で表示 filled=True, # 純度に応じて色がつく ) plt.show() # 表示例 plot_decision_tree(df, select_columns, "Survived", "Classifier", scale=1, max_depth=2, fontsize=10) 5.Validation K-分割交差検証 層状K-分割交差検証の実装例です。 def validation_classifier( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 model_cls, # 使うモデル model_params, # モデルのパラメータ ): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # 交差検証 metrics = [] #kf = sklearn.model_selection.KFold(n_splits=3, shuffle=True, random_state=1234) # KFoldにしたい場合 kf = sklearn.model_selection.StratifiedKFold(n_splits=3, shuffle=True, random_state=1234) for train_idx, test_idx in kf.split(df_train, y=df_train[target_column]): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] model = model_cls(**model_params) model.fit(x_train, y_train) # 正解率 y_pred = model.predict(x_test) metric = sklearn.metrics.accuracy_score(y_test, y_pred) # logloss #y_pred = model.predict_proba(x_test) #metric = sklearn.metrics.log_loss(y_test, y_pred[:,1]) metrics.append(metric) # 交差検証の結果は平均を採用 metrics_mean = np.mean(metrics, axis=0) return metrics_mean # 実行例 metric = validation_classifier(df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}) print(metric) 0.8103254769921437 Adversarial Validation 学習データとテストデータの分布を確認し、分布が偏っている場合にテストデータに近いデータを検証に使う手法です。 モデルはLGBMで決め打ちしています。 import sklearn.model_selection import sklearn.metrics import lightgbm as lgb def adversarial_validation_check( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 result_column_name="", # 結果保存用のカラム名 ebable_plot=True, ): # モデル model = lgb.LGBMClassifier(**{"random_state": 1234}) # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df.copy() # 目的変数を作成 df_train["_target"] = df_train[target_column].apply(lambda x: np.isnan(x)).astype(int) # 学習データとテストデータに分割 df_train_sub, df_true_sub = sklearn.model_selection.train_test_split( df_train, test_size=0.3, stratify=df_train["_target"], random_state=1234) x_train = df_train_sub[select_columns] y_train = df_train_sub["_target"] x_true = df_true_sub[select_columns] y_true = df_true_sub["_target"] # 学習 model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_true, y_true)], eval_names=['train', 'valid'], eval_metric='auc', verbose=0) if ebable_plot: # 学習結果をplot lgb.plot_metric(model.evals_result_, metric='auc') plt.axhline(y=0.5, color="red", linestyle="--") plt.ylim(0.4, 1.0) plt.tight_layout() plt.show() # 分布に影響が大きい変数順に並べて表示する pd.DataFrame(model.feature_importances_, index=select_columns ).sort_values(0, ascending=True).plot.barh() plt.tight_layout() plt.show() # 結果を新しい特徴量として追加 if result_column_name != "": y_pred = model.predict_proba(x_true) df[result_column_name] = pd.Series(y_pred[:,1]) # auc値を返す return sklearn.metrics.roc_auc_score(y_true, y_pred[:,1]) # 実行例 adversarial_validation_check(df, select_columns, "Survived") adversarial_validation_check(df, select_columns, "Age_org", "train_ratio_Age") Survivedの結果 valid の値が0.5付近なので、学習データとテストデータを区別できない=学習データとテストデータの分布が同じです。 この場合はAdversarial Validationを使う必要はありません。 Ageの結果 Ageは欠損値のないデータ群と欠損値のあるデータ群を比較しています。 validが約0.8とかなり高い確率で予測できています。 Ageの欠損値がある場合とない場合で分布に偏りがある事が分かります。 また、変数の重要度を見ることで、分布の偏りの原因になっている変数が分かります。 (今回はFareが一番影響しているようですね) では、これを用いて検証するコード例です。 上記モデルで結果を予測し、テストデータである確率が高いデータを検証データに、 そうじゃないデータは学習データにして検証を行います。 def adversarial_validation_Age( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト model_cls, # 使うモデル model_params, # モデルのパラメータ ): target_column = "Age_org" # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # データをtrain_ratioに従って分割 test_size = 0.25 test_num = int(len(df_train) * test_size) df_train = df_train.sort_values("train_ratio_Age") df_train_sub = df_train[test_num:] df_test_sub = df_train[:test_num] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] # 学習 model = model_cls(**model_params) model.fit(x_train, y_train) y_pred = model.predict(x_test) # 評価 metric = np.sqrt(sklearn.metrics.mean_squared_error(y_test, y_pred)) return metric metric = adversarial_validation_Age(df, select_columns, lgb.LGBMRegressor, {"random_state": 1234}) print(print) 12.119246573863585 6.特徴量抽出 参考:特徴量選択のまとめ 全く関係ない特徴量を減らしたい 分散が0(すべて同じ値)データを削除 全く同じ特徴量のカラムを削除 import sklearn.feature_selection def feature_selection_no_related( df, columns, enable_print=False ): prev_columns = columns prev_len = len(prev_columns) #------------------------------- # 分散が0(すべて同じ値)のカラムは削除 #------------------------------- vt = sklearn.feature_selection.VarianceThreshold(threshold=0) vt.fit(df[prev_columns]) inc_columns = [] exc_columns = [] for i, flag in enumerate(vt.get_support()): if flag: inc_columns.append(prev_columns[i]) else: exc_columns.append(prev_columns[i]) if enable_print: print("var0 {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) #------------------------------- # 全く同じ特徴量のカラムは削除 #------------------------------- prev_columns = inc_columns prev_len = len(prev_columns) inc_columns = [] exc_columns = [] for c, flag in df[prev_columns].T.duplicated().items(): if not flag: inc_columns.append(c) else: exc_columns.append(c) if enable_print: print("dup {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 df["A"] = 1 df["B"] = df["Sex"] new_columns = util.feature_reduction_no_related(df, select_columns + ["A", "B"], enable_print=True) print(new_columns) var0 10 -> 9 exc: ['A'] dup 9 -> 8 exc: ['B'] ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Family', 'Fare', 'Embarked'] 多分関係ない特徴量を減らしたい 相関係数 相関係数は0なら相関なし、1または-1なら正か負の強い相関があります。 0に違い特徴量を減らします。 def feature_selection_corr( df, columns, target_columns, threshold, enable_print=False ): # target_columnをいれる prev_columns = list(set(list(columns) + [target_columns])) prev_len = len(prev_columns) # 相関係数 df_corr = df[prev_columns].corr() df_corr.fillna(0, inplace=True) # 閾値以下は除外 inc_columns = [] exc_columns = [] for c in df_corr.columns: val = df_corr[target_columns][c] if abs(val) < threshold: exc_columns.append([c, val]) else: inc_columns.append(c) if enable_print: print("corr {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 new_columns = util.feature_selection_corr(df, select_columns, "Survived", 0.1, enable_print=True) print(new_columns) corr 9 -> 5 exc: [['SibSp', -0.03532249888573556], ['Parch', 0.08162940708348335], ['Age', -0.07722109457217759], ['Family', 0.016638989282745195]] ['Embarked', 'Fare', 'Sex', 'Survived', 'Pclass'] 関係のある特徴量のみを抽出したい 評価の上がる特徴量を追加(または削除)していく 全説明変数を見て一番評価が上がる変数を追加または削除していく手法です。 Foward説明変数がない状態から1つずつ追加していく方法で、 Backwardは全説明変数から1つずつ減らしてい区方法です。 説明変数の数に対して最大O(N^2)時間がかかると思います。 import sklearn.feature_selection def feature_selection_SFS( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ direction, # "forward"で追加していく、"backward"で削除していく scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.SequentialFeatureSelector( model_cls(**model_params), direction=direction, scoring=scoring ) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_SFS( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, direction="forward", scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'Fare'] exc ['SibSp', 'Parch', 'Family', 'Embarked'] RFECV RFECVは簡単に言うとモデルを作成し、重要度が低い変数を除外するという特徴量選択方法らしいです。 その方法から、coef_またはfeature_importances_が作られるモデルしか適用できません。 参考:【Kaggle】タイタニックの振り返り#3 RFECVで特徴選択 import sklearn.feature_selection def feature_selection_RFECV( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.RFECV(model_cls(**model_params), scoring=scoring) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_RFECV( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'SibSp', 'Family', 'Fare', 'Embarked'] exc ['Parch'] 7.モデル モデル一覧 分類モデル import sklearn.ensemble import sklearn.gaussian_process import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.discriminant_analysis import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_classifier(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostClassifier, {"random_state": random_seed}], [sklearn.ensemble.BaggingClassifier, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesClassifier, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingClassifier, {"random_state": random_seed}], [sklearn.ensemble.RandomForestClassifier, {"random_state": random_seed}], #Gaussian Processes [sklearn.gaussian_process.GaussianProcessClassifier, {"random_state": random_seed}], #GLM [sklearn.linear_model.RidgeClassifier, {}], #Navies Bayes [sklearn.naive_bayes.BernoulliNB, {}], [sklearn.naive_bayes.GaussianNB, {}], #Nearest Neighbor [sklearn.neighbors.KNeighborsClassifier, {}], #Trees [sklearn.tree.DecisionTreeClassifier, {"random_state": random_seed}], [sklearn.tree.ExtraTreeClassifier, {"random_state": random_seed}], # SVM [sklearn.svm.SVC, {}], [sklearn.svm.NuSVC, {}], [sklearn.svm.LinearSVC, {}], # NN [sklearn.neural_network.MLPClassifier, {"random_state": random_seed}], #xgboost [xgb.XGBClassifier, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMClassifier, {"random_state": random_seed}], ] return models models = get_models_classifier(1234) 回帰モデル import sklearn.ensemble import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_regressor(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostRegressor, {"random_state": random_seed}], [sklearn.ensemble.BaggingRegressor, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesRegressor, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingRegressor, {"random_state": random_seed}], [sklearn.ensemble.RandomForestRegressor, {"random_state": random_seed}], # GLM [sklearn.linear_model.Lasso, {"random_state": random_seed}], [sklearn.linear_model.Ridge, {"random_state": random_seed}], [sklearn.linear_model.ElasticNet, {"random_state": random_seed}], #Nearest Neighbor [sklearn.neighbors.KNeighborsRegressor, {}], #Trees [sklearn.tree.DecisionTreeRegressor, {"random_state": random_seed}], [sklearn.tree.ExtraTreeRegressor, {"random_state": random_seed}], # SVM [sklearn.svm.SVR, {}], [sklearn.svm.NuSVR, {}], [sklearn.svm.LinearSVR, {}], # NN [sklearn.neural_network.MLPRegressor, {"random_state": random_seed}], #xgboost [xgb.XGBRegressor, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMRegressor, {"random_state": random_seed}], ] return models models = get_models_regressor(1234) ハイパーパラメータの調整(optuna) モデルパラメータ クリックで展開 def create_optuna_params(trial, model_cls, random_seed): #----------------------------------------------------------- # Classifier #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "algorithm": trial.suggest_categorical('algorithm', ["SAMME", "SAMME.R"]), # default=SAMME.R "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 #"max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingClassifier: return { "loss": trial.suggest_categorical('loss', ["deviance", "exponential"]), # default=deviance "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Gaussian Processes if model_cls == sklearn.gaussian_process.GaussianProcessClassifier: return {"random_state": random_seed} # GLM if model_cls == sklearn.linear_model.RidgeClassifier: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True "normalize": trial.suggest_categorical('normalize', [False, True]), # default=False "random_state": random_seed } #Navies Bayes if model_cls == sklearn.naive_bayes.BernoulliNB: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 } if model_cls == sklearn.naive_bayes.GaussianNB: return {} # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsClassifier: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVC: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2]), # default=1.0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVC: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVC: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["hinge", "squared_hinge"]), # default=squared_hinge "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPClassifier: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMClassifier: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } #----------------------------------------------------------- # Regressor #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "loss": trial.suggest_categorical('loss', ["linear", "square", "exponential"]), # default=linear "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 "max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingRegressor: return { "loss": trial.suggest_categorical('loss', ["ls", "lad", "huber", "quantile"]), # default=ls "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsRegressor: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVR: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0.1 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVR: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVR: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["epsilon_insensitive", "squared_epsilon_insensitive"]), # default=epsilon_insensitive "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPRegressor: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMRegressor: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } パラメータ調整コード例 from tqdm import tqdm import time # 結果格納用 df_compare = pd.DataFrame(columns=['name', 'metric1', "metric2", 'params', 'time']) #--------------------------- # optunaの定義 #--------------------------- # optuna silent optuna.logging.set_verbosity(optuna.logging.WARNING) def objective_degree(model_cls, df): def objective(trial): params = create_optuna_params(trial, model_cls, 1234) #--- モデルを評価する metric = validation_classifier( df, select_columns, "Survived", model_cls, params) return metric return objective #--------------------------- # モデル毎にパラメータをサーチする #--------------------------- for model_ in tqdm(get_models_classifier(1234)): model_cls = model_[0] model_params = model_[1] name = model_cls.__name__ try: # デフォルトパラメータでの結果を取得しておく metric1 = validation_classifier( df, select_columns, "Survived", model_cls, model_params) # 調整開始 t0 = time.time() study = optuna.create_study(direction="maximize") study.optimize(objective_degree(model_cls, df), timeout=60*1, n_trials=1000) # optunaの結果を取得 best_value = study.best_value best_params = study.best_params trial_count = len(study.trials) t1 = time.time() - t0 # 結果を保存 df_compare = df_compare.append({ "name": name, "metric1": metric1, "metric2": best_value, "params": best_params, "count": trial_count, "time": t1, }, ignore_index=True) except Exception as e: print("error: {}".format(name)) print(e) # 結果を表示 print(df_compare) df_compare.set_index('name', inplace=True) df_compare.plot.barh(y=["metric1", "metric2"]) plt.legend(loc='upper left') plt.tight_layout() plt.show() name metric1 metric2 time count 0 AdaBoostClassifier 0.795735 0.815937 63.645483 30.0 1 BaggingClassifier 0.804714 0.826038 60.307925 52.0 2 ExtraTreesClassifier 0.804714 0.823793 60.205603 330.0 3 GradientBoostingClassifier 0.829405 0.839506 60.315318 148.0 4 RandomForestClassifier 0.804714 0.826038 60.258668 112.0 5 GaussianProcessClassifier 0.710438 0.710438 60.037200 101.0 6 RidgeClassifier 0.792368 0.810325 41.930431 1000.0 7 BernoulliNB 0.784512 0.784512 30.653054 1000.0 8 GaussianNB 0.786756 0.786756 21.921596 1000.0 9 KNeighborsClassifier 0.694725 0.753086 60.289474 957.0 10 DecisionTreeClassifier 0.784512 0.808081 35.006361 1000.0 11 ExtraTreeClassifier 0.764310 0.797980 33.426985 1000.0 12 LinearSVC 0.738496 0.801347 60.149822 157.0 13 MLPClassifier 0.794613 0.819304 60.149625 101.0 14 XGBClassifier 0.812570 0.829405 60.045466 56.0 15 LGBMClassifier 0.810325 0.838384 61.060753 47.0 metric1がデフォルト値、metric2が調整後の数値です。 ハイパーパラメータ調整の効果はあるようです。 ただ、時間がかなりかかりますね… 8.その他テクニックなど 目的変数の対数変換(回帰) 目的変数の対数変換です。 参照:どのようなときに目的変数Yではなくlog(Y)にしたほうがよいのか?~対数変換するメリットとデメリット~ 参照のメリットを引用すると、 メリット: Y の値が小さいサンプルの誤差が小さくなる デメリット: Y の値が大きいサンプルの誤差が大きくなる です。 対数変換 df["Survived"] = np.log(df["Survived"]) 元に戻す model = 対数変換した目的変数でモデルを学習 # 予測する x = df[df["Survived"].isnull()][select_columns] y_pred = model.predict(x) # 予測結果を元に戻す y_pred = np.exp(y_pred) 例のSurvivedは分類問題なので実施するメリットはありませんけど… 9.AutoML(PyCaret)の適用 PyCaret を使う例です。 表示はJupyter Notebookで使う場合の出力に最適化されているようです。 参考:最速でPyCaretを使ってみた 1.データ読み込み 学習に使うデータは以下です。 pcc_df = df[df["Survived"].notnull()][[ "Pclass", "Sex", "Age", "SibSp", "Parch", "Family", "Fare", "Embarked", "Survived", # 学習データなので目的変数もいれます ]] # データを渡す import pycaret.classification as pcc r = pcc.setup(pcc_df, target="Survived", silent=True) 結果は59項目ありました。 重要な項目は色がつくようです? 2.モデル選択 複数のモデルを比較 数分かかります。 best_model = pcc.compare_models() print(best_model.__class__.__name__) # LogisticRegression 任意のモデル 任意のモデルを選びたい場合はこちらから model = pcc.create_model("lightgbm") 3.ハイパーパラメータの調整 tuned_model = pcc.tune_model(model) print(tuned_model.get_params) <bound method LGBMModel.get_params of LGBMClassifier(bagging_fraction=0.7, bagging_freq=3, boosting_type='gbdt', class_weight=None, colsample_bytree=1.0, feature_fraction=0.8, importance_type='split', learning_rate=0.1, max_depth=-1, min_child_samples=36, min_child_weight=0.001, min_split_gain=0.4, n_estimators=230, n_jobs=-1, num_leaves=50, objective=None, random_state=8219, reg_alpha=0.1, reg_lambda=3, silent=True, subsample=1.0, subsample_for_bin=200000, subsample_freq=0)> 調整前後の評価値です。 predict_model はdataを指定しないとホールドアウトで実行した結果を返します。 4.モデルの可視化 pcc.plot_model(tuned_model) 5.モデル解釈の可視化 pcc.interpret_model(tuned_model) 6.予測 "Label"と"Score"カラムが追加され、Labelは予測結果、分類なら各クラスの予測確率がScoreに入っています。 x_pred = df[df["Survived"].isnull()] y_pred = pcc.predict_model(tuned_model, data=x_pred) print(y_pred["Label"]) 891 0.0 892 0.0 893 0.0 894 0.0 895 1.0 ... 1304 0.0 1305 1.0 1306 0.0 1307 0.0 1308 0.0 Name: Label, Length: 418, dtype: object
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaggleで書いたコードの備忘録~データ分析で使った手法等一通り~(可視化、データ加工、検証、特徴量抽出、モデル、AutoML等)

初心者ながらKaggleに挑戦した時のコードを備忘録として残しておきます。 1.データ import 全体的に使うライブラリです。 各項目で使うライブラリはそちら側でimportを記載しています。 import numpy as np import pandas as pd from matplotlib import pyplot as plt 使用するデータ Kaggleチュートリアルのタイタニックを使います。 コード df_train = pd.read_csv("/kaggle/input/titanic/train.csv") df_test = pd.read_csv("/kaggle/input/titanic/test.csv") 学習データとテストデータをまとめて管理する マージ # データをマージ df_test["Survived"] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) print(df.shape) # (1309, 12) ※目的変数(Survived)のテスト側の値はnanになります 分割 df_train = df[df["Survived"].notnull()] df_test = df[df["Survived"].isnull()] print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) 数値データの要約統計量を確認したい print(df[["Age", "Fare"]].describe()) Age Fare count 1046.000000 1308.000000 mean 29.881138 33.295479 std 14.413493 51.758668 min 0.170000 0.000000 25% 21.000000 7.895800 50% 28.000000 14.454200 75% 39.000000 31.275000 max 80.000000 512.329200 カテゴリデータの要約統計量を確認したい print(df[["Sex"]].describe(include="O")) Sex count 1309 unique 2 top male freq 843 ※欠損値があるとエラーが出るので注意 欠損値を確認したい 方法1 print(df.isnull().sum()) PassengerId 0 Survived 418 Pclass 0 Name 0 Sex 0 Age 263 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 1014 Embarked 2 is_train 0 dtype: int64 方法2 print(df.info()) <class 'pandas.core.frame.DataFrame'> RangeIndex: 1309 entries, 0 to 1308 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 1309 non-null int64 1 Survived 891 non-null float64 2 Pclass 1309 non-null int64 3 Name 1309 non-null object 4 Sex 1309 non-null object 5 Age 1046 non-null float64 6 SibSp 1309 non-null int64 7 Parch 1309 non-null int64 8 Ticket 1309 non-null object 9 Fare 1308 non-null float64 10 Cabin 295 non-null object 11 Embarked 1307 non-null object 12 is_train 1309 non-null int64 dtypes: float64(3), int64(5), object(5) memory usage: 133.1+ KB 方法2は欠損値以外の情報も表示されます。 2.データの可視化 カテゴリデータを表示したい(1変数) def plot_category(df, column, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(df[column]) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_category(df, "Pclass", scale=0.5, line=[300, 400]) 数値データを表示したい(1変数) def plot_float(df, column, bins=10, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.distplot(df[column], kde=True, rug=False, bins=bins) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_float(df, "Age", scale=0.5, line=20) ※binsはヒストグラムの分割数です カテゴリデータに対してカテゴリデータを比較したい(2変数) plot表示 シンプル def plot_category_category(df, column1, column2, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(column1, hue=column2, data=df) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.legend() plt.title(column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7, line=100) 元のデータとの割合線も表示するバージョン def plot_category_category(df, column1, column2, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 欠損値が入っているデータは除外する df = df.dropna(subset=[column1, column2]).copy() # 型を変換 df[column1] = df[column1].astype(str) df[column2] = df[column2].astype(str) # マージンを設定 margin = 0.2 totoal_width = 1 - margin # hue_data hue_counts = df[column2].value_counts() hue_keys = hue_counts.keys() # clumn_data column_keys = df[column1].value_counts().keys() hue_column_counts = {} for k2 in hue_keys: hue_column_counts[k2] = [] for k in column_keys: c = df[df[column1]==k][column2].value_counts() # hue key毎に分けておく for k2 in hue_keys: if k2 in c: hue_column_counts[k2].append(c[k2]) else: hue_column_counts[k2].append(0) column_counts = [] for k in column_keys: c = len(df[df[column1]==k]) column_counts.append(c) # barの座標を計算 bar_count = len(hue_keys) bar_width = totoal_width/bar_count x_bar_pos = np.asarray(range(bar_count)) * bar_width for i, k in enumerate(hue_keys): counts = hue_column_counts[k] # barを表示 x_pos = np.asarray(range(len(counts))) x_pos = x_pos + x_bar_pos[i] - (bar_count * bar_width) / 2 + bar_width / 2 plt.bar(x_pos, counts, width=bar_width, label=hue_keys[i]) # 線を表示 p_all = (hue_counts[k] / hue_counts.sum()) c = np.asarray(column_counts) * p_all plt.hlines(c, xmin=x_pos - bar_width/2, xmax=x_pos + bar_width/2, colors='r', linestyles='dashed') plt.xticks(range(len(column_keys)), column_keys) plt.xlabel(column1) plt.legend(title=column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7) print表示 集計 print(pd.crosstab(df["Pclass"], df["Sex"])) Sex female male Pclass 1 144 179 2 106 171 3 216 493 各Sexにおける、Pclassの比率 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='columns')) Sex female male Pclass 1 0.309013 0.212337 2 0.227468 0.202847 3 0.463519 0.584816 Sex female male 各Pclassにおける、Sexの割合 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='index')) Sex female male Pclass 1 0.445820 0.554180 2 0.382671 0.617329 3 0.304654 0.695346 カテゴリデータに対して数値データを比較したい(2変数) def plot_category_float( df, column1, column2, bins=10, exclude=[], scale=1.0, line=None, ): plt.figure(figsize=(6.4*scale, 4.8*scale)) for uniq in df[column1].unique(): if uniq is np.nan: continue if math.isnan(uniq): continue if uniq in exclude: continue # 表示メイン sns.distplot(df[df[column1]==uniq][column2], kde=True, rug=False, bins=bins, label=uniq) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.legend() plt.tight_layout() plt.show() # 表示例 plot_category_float(df, "Pclass", "Age", exclude=[2], scale=0.5, line=50) ※ascendingはNoneの場合ソートしません。  指定する場合はpandasのsort_valuesの引数と同じです。 ※excludeを指定すると任意のカテゴリデータを非表示にできます。 数値データに対して数値データを比較したい(2変数) def plot_float_float( df, column1, column2, hue=None, scale=1.0, ): # 表示メイン sns.jointplot(column1, column2, data=df, hue=hue, palette='Set2', size=6*scale) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float(df, "Age", "Fare", "Pclass", scale=0.5) 数値データと数値データに対してカテゴリデータを比較したい(3変数) plot_float_floatのhueを指定したバージョンです。 def plot_float_float_category(df, column1, column2, column3, scale=1.0,): plot_float_float(df, column1, column2, column3, scale) # 表示例 plot_float_float_category(df, "Age", "Fare", "Pclass", scale=0.7) 数値データと数値データに対して数値データを比較したい(3変数) def plot_float_float_float( df, column1, column2, column3, scale=1.0, ): _ds3 = (df[column3] - df[column3].min()) / (df[column3].max() - df[column3].min()) _ds3 = 10 + _ds3*1000 fig = plt.figure(figsize=(6.4*scale, 4.8*scale)) mappable = plt.scatter(df[column1], df[column2], s=_ds3, c=df[column3], cmap="Set2", alpha=0.3, label=column3) fig.colorbar(mappable) plt.xlabel(column1) plt.ylabel(column2) plt.grid(True) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float_float(df, "Age", "Pclass", "Fare", scale=0.7) ※表示例はPclassなので小数データではないですが… ※cmapの種類についてはここを参考 変数同士の相関係数をみたい ヒートマップの表示 def plot_corr(df, columns, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.heatmap(df[columns].corr(), annot=True, vmax=1, vmin=-1, fmt='.1f', cmap='RdBu') plt.tight_layout() plt.show() # 表示例 plot_corr(df, df.columns) ある変数に対して相関係数が高い変数のみ表示(1対多) def print_corr(df, columns, threshold=0.5): print("--- 相関係数(threshold: {})".format(threshold)) corr = df[columns].corr() count = 0 for i in range(len(corr.columns)): for j in range(i+1, len(corr.columns)): val = corr.iloc[i, j] if abs(val) > threshold: print("{} {}: {:.2f}".format(corr.columns[i], corr.columns[j], val)) count += 1 if count == 0: print("empty") # 表示例 print_corr(df, df.columns, threshold=0.4) --- 相関係数(threshold: 0.4) PassengerId is_train: -0.81 Pclass Age: -0.41 Pclass Fare: -0.56 ある配列データを横棒グラフで表示して比較したい def plot_bar(df, columns, ascending=None, scale=1.0, line=None): if ascending is None: df.plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) else: df.sort_values(columns, ascending=ascending).plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_bar(df[:5], ["Age", "Fare"], ascending=True, scale=0.5, line=20) ※ascendingがNoneの場合は並べ替えしません。  それ以外の場合はpandasのsort_valuesの引数と同じ動作です 3.データ加工 欠損値を補完 df["Age_org"] = df["Age"] # 後で使うので欠損値がある状態も保存 df["Age_na"] = df["Age"].isnull() df["Age"].fillna(df2["Age"].median(), inplace=True) df["Embarked"].fillna("S", inplace=True) df["Fare"].fillna(df2['Fare'].median(), inplace=True) Label Encording from sklearn.preprocessing import LabelEncoder df["Sex"] = LabelEncoder().fit_transform(df['Sex']) df["Embarked"] = LabelEncoder().fit_transform(df['Embarked']) ダミー化(One-hotエンコーディング) pandasのget_dummiesで簡単にできますが、カラム名も欲しい場合のものを作成しています。 def dummy(df, column): c_arr = [] for c in df[column].value_counts().keys(): name = column + "_" + str(c) c_arr.append(name) df[name] = np.asarray(df[column] == c).astype(int) return df, c_arr 4.目的変数に対する各説明変数の影響度を確認したい 確認に使っているカラムは以下です。 (Familyも何となく追加) df["Family"] = df["SibSp"] + df["Parch"] select_columns = [ "Pclass", "Sex", "Age_completion", "SibSp", "Parch", "Family", "Fare", "Embarked", ] 分類 ロジスティック回帰の係数 ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import sklearn.linear_model import xgboost as xgb import lightgbm as lgb def create_feature_effect_classifier(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # ロジスティック回帰 #---------------- if not exists_missing_value: model = sklearn.linear_model.LogisticRegression(random_state=1234) model.fit(x, y) if len(model.coef_) == 1: # 2クラス分類 df_result["LogisticRegression"] = model.coef_[0] else: # 多クラスの場合は各クラスの係数の平均を出す df_result["LogisticRegression"] = np.mean(model.coef_, axis=0) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestClassifier(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMClassifier(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 実行例 df_feature = create_feature_effect_classifier(df, select_columns, "Survived") print(df_feature) plot_bar(df_feature, "LogisticRegression", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) LogisticRegression RandomForest XGB LGBM name Pclass -1.050753 0.072651 0.233783 116 Sex -2.631111 0.257130 0.509833 97 Age -0.038077 0.255461 0.032465 1055 SibSp -0.182407 0.030305 0.078906 51 Parch 0.047843 0.024342 0.034393 56 Family -0.134563 0.055398 0.047479 154 Fare 0.002171 0.272152 0.032554 1344 Embarked -0.213182 0.032561 0.030586 125 回帰 重回帰分析の決定係数(とP値) ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import statsmodels.api as sm import sklearn.ensemble import xgboost as xgb import lightgbm as lgb def create_feature_effect_regressor(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # 重回帰分析 #---------------- if not exists_missing_value: model = sm.OLS(y, sm.add_constant(x)) fitted = model.fit() #print(fitted.summary()) if len(fitted.params) == len(select_columns): df_result["OLS_val"] = list(fitted.params) df_result["OLS_p"] = list(fitted.pvalues) elif len(fitted.params) == len(select_columns)+1: df_result["OLS_val"] = list(fitted.params)[1:] df_result["OLS_p"] = list(fitted.pvalues)[1:] df_result["OLS_p"] = np.round(df_result["OLS_p"], 3) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestRegressor(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBRegressor(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMRegressor(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 表示例 df_feature = create_feature_effect_regressor(df, select_columns, "Age") print(df_feature) plot_bar(df_feature, "OLS_val", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) OLS_val OLS_p RandomForest XGB LGBM name Pclass -5.622647 0.000 0.205440 0.496402 140 Sex 2.020035 0.004 0.054703 0.038413 190 SibSp -0.910016 0.003 0.062044 0.066298 141 Parch 0.036394 0.913 0.164997 0.187804 234 Family -0.873621 0.000 0.066830 0.086484 171 Fare 0.006825 0.400 0.395958 0.056573 1940 Embarked 0.244200 0.556 0.050028 0.068026 184 P値(OLD_p)が0.05以上の場合は有意性がありません。(決定係数(OLS_val)が0の可能性があります) 今回ですと、Parch,Fare,Embarked は有意性がないので説明変数としては採用しないほうが良いかもしれません。 決定木 import sklearn.tree def plot_decision_tree(df, columns, target_column, model_type, # "Classifier" or "Regressor" scale=1.0, max_depth=None, # 表示する木の深さ fontsize=None, # 文字の大きさ ): x = df[df[target_column].notnull()][columns] y = df[df[target_column].notnull()][target_column] if model_type == "Classifier": clf = sklearn.tree.DecisionTreeClassifier(random_state=1234) elif model_type == "Regressor": clf = sklearn.tree.DecisionTreeRegressor(random_state=1234) else: raise ValueError() clf = clf.fit(x, y) plt.figure(figsize=(6.4*scale, 4.8*scale)) sklearn.tree.plot_tree(clf, feature_names=columns, max_depth=max_depth, fontsize=fontsize, proportion=False, # Trueだと割合で表示 filled=True, # 純度に応じて色がつく ) plt.show() # 表示例 plot_decision_tree(df, select_columns, "Survived", "Classifier", scale=1, max_depth=2, fontsize=10) 5.Validation K-分割交差検証 層状K-分割交差検証の実装例です。 def validation_classifier( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 model_cls, # 使うモデル model_params, # モデルのパラメータ ): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # 交差検証 metrics = [] #kf = sklearn.model_selection.KFold(n_splits=3, shuffle=True, random_state=1234) # KFoldにしたい場合 kf = sklearn.model_selection.StratifiedKFold(n_splits=3, shuffle=True, random_state=1234) for train_idx, test_idx in kf.split(df_train, y=df_train[target_column]): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] model = model_cls(**model_params) model.fit(x_train, y_train) # 正解率 y_pred = model.predict(x_test) metric = sklearn.metrics.accuracy_score(y_test, y_pred) # logloss #y_pred = model.predict_proba(x_test) #metric = sklearn.metrics.log_loss(y_test, y_pred[:,1]) metrics.append(metric) # 交差検証の結果は平均を採用 metrics_mean = np.mean(metrics, axis=0) return metrics_mean # 実行例 metric = validation_classifier(df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}) print(metric) 0.8103254769921437 Adversarial Validation 学習データとテストデータの分布を確認し、分布が偏っている場合にテストデータに近いデータを検証に使う手法です。 モデルはLGBMで決め打ちしています。 import sklearn.model_selection import sklearn.metrics import lightgbm as lgb def adversarial_validation_check( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 result_column_name="", # 結果保存用のカラム名 ebable_plot=True, ): # モデル model = lgb.LGBMClassifier(**{"random_state": 1234}) # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df.copy() # 目的変数を作成 df_train["_target"] = df_train[target_column].apply(lambda x: np.isnan(x)).astype(int) # 学習データとテストデータに分割 df_train_sub, df_true_sub = sklearn.model_selection.train_test_split( df_train, test_size=0.3, stratify=df_train["_target"], random_state=1234) x_train = df_train_sub[select_columns] y_train = df_train_sub["_target"] x_true = df_true_sub[select_columns] y_true = df_true_sub["_target"] # 学習 model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_true, y_true)], eval_names=['train', 'valid'], eval_metric='auc', verbose=0) if ebable_plot: # 学習結果をplot lgb.plot_metric(model.evals_result_, metric='auc') plt.axhline(y=0.5, color="red", linestyle="--") plt.ylim(0.4, 1.0) plt.tight_layout() plt.show() # 分布に影響が大きい変数順に並べて表示する pd.DataFrame(model.feature_importances_, index=select_columns ).sort_values(0, ascending=True).plot.barh() plt.tight_layout() plt.show() # 結果を新しい特徴量として追加 if result_column_name != "": y_pred = model.predict_proba(x_true) df[result_column_name] = pd.Series(y_pred[:,1]) # auc値を返す return sklearn.metrics.roc_auc_score(y_true, y_pred[:,1]) # 実行例 adversarial_validation_check(df, select_columns, "Survived") adversarial_validation_check(df, select_columns, "Age_org", "train_ratio_Age") Survivedの結果 valid の値が0.5付近なので、学習データとテストデータを区別できない=学習データとテストデータの分布が同じです。 この場合はAdversarial Validationを使う必要はありません。 Ageの結果 Ageは欠損値のないデータ群と欠損値のあるデータ群を比較しています。 validが約0.8とかなり高い確率で予測できています。 Ageの欠損値がある場合とない場合で分布に偏りがある事が分かります。 また、変数の重要度を見ることで、分布の偏りの原因になっている変数が分かります。 (今回はFareが一番影響しているようですね) では、これを用いて検証するコード例です。 上記モデルで結果を予測し、テストデータである確率が高いデータを検証データに、 そうじゃないデータは学習データにして検証を行います。 def adversarial_validation_Age( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト model_cls, # 使うモデル model_params, # モデルのパラメータ ): target_column = "Age_org" # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # データをtrain_ratioに従って分割 test_size = 0.25 test_num = int(len(df_train) * test_size) df_train = df_train.sort_values("train_ratio_Age") df_train_sub = df_train[test_num:] df_test_sub = df_train[:test_num] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] # 学習 model = model_cls(**model_params) model.fit(x_train, y_train) y_pred = model.predict(x_test) # 評価 metric = np.sqrt(sklearn.metrics.mean_squared_error(y_test, y_pred)) return metric metric = adversarial_validation_Age(df, select_columns, lgb.LGBMRegressor, {"random_state": 1234}) print(print) 12.119246573863585 6.特徴量抽出 参考:特徴量選択のまとめ 全く関係ない特徴量を減らしたい 分散が0(すべて同じ値)データを削除 全く同じ特徴量のカラムを削除 import sklearn.feature_selection def feature_selection_no_related( df, columns, enable_print=False ): prev_columns = columns prev_len = len(prev_columns) #------------------------------- # 分散が0(すべて同じ値)のカラムは削除 #------------------------------- vt = sklearn.feature_selection.VarianceThreshold(threshold=0) vt.fit(df[prev_columns]) inc_columns = [] exc_columns = [] for i, flag in enumerate(vt.get_support()): if flag: inc_columns.append(prev_columns[i]) else: exc_columns.append(prev_columns[i]) if enable_print: print("var0 {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) #------------------------------- # 全く同じ特徴量のカラムは削除 #------------------------------- prev_columns = inc_columns prev_len = len(prev_columns) inc_columns = [] exc_columns = [] for c, flag in df[prev_columns].T.duplicated().items(): if not flag: inc_columns.append(c) else: exc_columns.append(c) if enable_print: print("dup {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 df["A"] = 1 df["B"] = df["Sex"] new_columns = util.feature_reduction_no_related(df, select_columns + ["A", "B"], enable_print=True) print(new_columns) var0 10 -> 9 exc: ['A'] dup 9 -> 8 exc: ['B'] ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Family', 'Fare', 'Embarked'] 多分関係ない特徴量を減らしたい 相関係数 相関係数は0なら相関なし、1または-1なら正か負の強い相関があります。 0に違い特徴量を減らします。 def feature_selection_corr( df, columns, target_columns, threshold, enable_print=False ): # target_columnをいれる prev_columns = list(set(list(columns) + [target_columns])) prev_len = len(prev_columns) # 相関係数 df_corr = df[prev_columns].corr() df_corr.fillna(0, inplace=True) # 閾値以下は除外 inc_columns = [] exc_columns = [] for c in df_corr.columns: val = df_corr[target_columns][c] if abs(val) < threshold: exc_columns.append([c, val]) else: inc_columns.append(c) if enable_print: print("corr {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 new_columns = util.feature_selection_corr(df, select_columns, "Survived", 0.1, enable_print=True) print(new_columns) corr 9 -> 5 exc: [['SibSp', -0.03532249888573556], ['Parch', 0.08162940708348335], ['Age', -0.07722109457217759], ['Family', 0.016638989282745195]] ['Embarked', 'Fare', 'Sex', 'Survived', 'Pclass'] 関係のある特徴量のみを抽出したい 評価の上がる特徴量を追加(または削除)していく 全説明変数を見て一番評価が上がる変数を追加または削除していく手法です。 Foward説明変数がない状態から1つずつ追加していく方法で、 Backwardは全説明変数から1つずつ減らしてい区方法です。 説明変数の数に対して最大O(N^2)時間がかかると思います。 import sklearn.feature_selection def feature_selection_SFS( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ direction, # "forward"で追加していく、"backward"で削除していく scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.SequentialFeatureSelector( model_cls(**model_params), direction=direction, scoring=scoring ) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_SFS( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, direction="forward", scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'Fare'] exc ['SibSp', 'Parch', 'Family', 'Embarked'] RFECV RFECVは簡単に言うとモデルを作成し、重要度が低い変数を除外するという特徴量選択方法らしいです。 その方法から、coef_またはfeature_importances_が作られるモデルしか適用できません。 参考:【Kaggle】タイタニックの振り返り#3 RFECVで特徴選択 import sklearn.feature_selection def feature_selection_RFECV( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.RFECV(model_cls(**model_params), scoring=scoring) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_RFECV( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'SibSp', 'Family', 'Fare', 'Embarked'] exc ['Parch'] 7.モデル モデル一覧 分類モデル import sklearn.ensemble import sklearn.gaussian_process import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.discriminant_analysis import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_classifier(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostClassifier, {"random_state": random_seed}], [sklearn.ensemble.BaggingClassifier, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesClassifier, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingClassifier, {"random_state": random_seed}], [sklearn.ensemble.RandomForestClassifier, {"random_state": random_seed}], #Gaussian Processes [sklearn.gaussian_process.GaussianProcessClassifier, {"random_state": random_seed}], #GLM [sklearn.linear_model.RidgeClassifier, {}], #Navies Bayes [sklearn.naive_bayes.BernoulliNB, {}], [sklearn.naive_bayes.GaussianNB, {}], #Nearest Neighbor [sklearn.neighbors.KNeighborsClassifier, {}], #Trees [sklearn.tree.DecisionTreeClassifier, {"random_state": random_seed}], [sklearn.tree.ExtraTreeClassifier, {"random_state": random_seed}], # SVM [sklearn.svm.SVC, {}], [sklearn.svm.NuSVC, {}], [sklearn.svm.LinearSVC, {}], # NN [sklearn.neural_network.MLPClassifier, {"random_state": random_seed}], #xgboost [xgb.XGBClassifier, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMClassifier, {"random_state": random_seed}], ] return models models = get_models_classifier(1234) 回帰モデル import sklearn.ensemble import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_regressor(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostRegressor, {"random_state": random_seed}], [sklearn.ensemble.BaggingRegressor, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesRegressor, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingRegressor, {"random_state": random_seed}], [sklearn.ensemble.RandomForestRegressor, {"random_state": random_seed}], # GLM [sklearn.linear_model.Lasso, {"random_state": random_seed}], [sklearn.linear_model.Ridge, {"random_state": random_seed}], [sklearn.linear_model.ElasticNet, {"random_state": random_seed}], #Nearest Neighbor [sklearn.neighbors.KNeighborsRegressor, {}], #Trees [sklearn.tree.DecisionTreeRegressor, {"random_state": random_seed}], [sklearn.tree.ExtraTreeRegressor, {"random_state": random_seed}], # SVM [sklearn.svm.SVR, {}], [sklearn.svm.NuSVR, {}], [sklearn.svm.LinearSVR, {}], # NN [sklearn.neural_network.MLPRegressor, {"random_state": random_seed}], #xgboost [xgb.XGBRegressor, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMRegressor, {"random_state": random_seed}], ] return models models = get_models_regressor(1234) ハイパーパラメータの調整(optuna) モデルパラメータ クリックで展開 def create_optuna_params(trial, model_cls, random_seed): #----------------------------------------------------------- # Classifier #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "algorithm": trial.suggest_categorical('algorithm', ["SAMME", "SAMME.R"]), # default=SAMME.R "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 #"max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingClassifier: return { "loss": trial.suggest_categorical('loss', ["deviance", "exponential"]), # default=deviance "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Gaussian Processes if model_cls == sklearn.gaussian_process.GaussianProcessClassifier: return {"random_state": random_seed} # GLM if model_cls == sklearn.linear_model.RidgeClassifier: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True "normalize": trial.suggest_categorical('normalize', [False, True]), # default=False "random_state": random_seed } #Navies Bayes if model_cls == sklearn.naive_bayes.BernoulliNB: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 } if model_cls == sklearn.naive_bayes.GaussianNB: return {} # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsClassifier: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVC: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2]), # default=1.0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVC: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVC: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["hinge", "squared_hinge"]), # default=squared_hinge "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPClassifier: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMClassifier: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } #----------------------------------------------------------- # Regressor #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "loss": trial.suggest_categorical('loss', ["linear", "square", "exponential"]), # default=linear "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 "max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingRegressor: return { "loss": trial.suggest_categorical('loss', ["ls", "lad", "huber", "quantile"]), # default=ls "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsRegressor: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVR: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0.1 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVR: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVR: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["epsilon_insensitive", "squared_epsilon_insensitive"]), # default=epsilon_insensitive "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPRegressor: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMRegressor: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } パラメータ調整コード例 from tqdm import tqdm import time # 結果格納用 df_compare = pd.DataFrame(columns=['name', 'metric1', "metric2", 'params', 'time']) #--------------------------- # optunaの定義 #--------------------------- # optuna silent optuna.logging.set_verbosity(optuna.logging.WARNING) def objective_degree(model_cls, df): def objective(trial): params = create_optuna_params(trial, model_cls, 1234) #--- モデルを評価する metric = validation_classifier( df, select_columns, "Survived", model_cls, params) return metric return objective #--------------------------- # モデル毎にパラメータをサーチする #--------------------------- for model_ in tqdm(get_models_classifier(1234)): model_cls = model_[0] model_params = model_[1] name = model_cls.__name__ try: # デフォルトパラメータでの結果を取得しておく metric1 = validation_classifier( df, select_columns, "Survived", model_cls, model_params) # 調整開始 t0 = time.time() study = optuna.create_study(direction="maximize") study.optimize(objective_degree(model_cls, df), timeout=60*1, n_trials=1000) # optunaの結果を取得 best_value = study.best_value best_params = study.best_params trial_count = len(study.trials) t1 = time.time() - t0 # 結果を保存 df_compare = df_compare.append({ "name": name, "metric1": metric1, "metric2": best_value, "params": best_params, "count": trial_count, "time": t1, }, ignore_index=True) except Exception as e: print("error: {}".format(name)) print(e) # 結果を表示 print(df_compare) df_compare.set_index('name', inplace=True) df_compare.plot.barh(y=["metric1", "metric2"]) plt.legend(loc='upper left') plt.tight_layout() plt.show() name metric1 metric2 time count 0 AdaBoostClassifier 0.795735 0.815937 63.645483 30.0 1 BaggingClassifier 0.804714 0.826038 60.307925 52.0 2 ExtraTreesClassifier 0.804714 0.823793 60.205603 330.0 3 GradientBoostingClassifier 0.829405 0.839506 60.315318 148.0 4 RandomForestClassifier 0.804714 0.826038 60.258668 112.0 5 GaussianProcessClassifier 0.710438 0.710438 60.037200 101.0 6 RidgeClassifier 0.792368 0.810325 41.930431 1000.0 7 BernoulliNB 0.784512 0.784512 30.653054 1000.0 8 GaussianNB 0.786756 0.786756 21.921596 1000.0 9 KNeighborsClassifier 0.694725 0.753086 60.289474 957.0 10 DecisionTreeClassifier 0.784512 0.808081 35.006361 1000.0 11 ExtraTreeClassifier 0.764310 0.797980 33.426985 1000.0 12 LinearSVC 0.738496 0.801347 60.149822 157.0 13 MLPClassifier 0.794613 0.819304 60.149625 101.0 14 XGBClassifier 0.812570 0.829405 60.045466 56.0 15 LGBMClassifier 0.810325 0.838384 61.060753 47.0 metric1がデフォルト値、metric2が調整後の数値です。 ハイパーパラメータ調整の効果はあるようです。 ただ、時間がかなりかかりますね… 8.その他テクニックなど 目的変数の対数変換(回帰) 目的変数の対数変換です。 参照:どのようなときに目的変数Yではなくlog(Y)にしたほうがよいのか?~対数変換するメリットとデメリット~ 参照のメリットを引用すると、 メリット: Y の値が小さいサンプルの誤差が小さくなる デメリット: Y の値が大きいサンプルの誤差が大きくなる です。 対数変換 df["Survived"] = np.log(df["Survived"]) 元に戻す model = 対数変換した目的変数でモデルを学習 # 予測する x = df[df["Survived"].isnull()][select_columns] y_pred = model.predict(x) # 予測結果を元に戻す y_pred = np.exp(y_pred) 例のSurvivedは分類問題なので実施するメリットはありませんけど… 9.AutoML(PyCaret)の適用 PyCaret を使う例です。 表示はJupyter Notebookで使う場合の出力に最適化されているようです。 参考:最速でPyCaretを使ってみた 1.データ読み込み 学習に使うデータは以下です。 pcc_df = df[df["Survived"].notnull()][[ "Pclass", "Sex", "Age", "SibSp", "Parch", "Family", "Fare", "Embarked", "Survived", # 学習データなので目的変数もいれます ]] # データを渡す import pycaret.classification as pcc r = pcc.setup(pcc_df, target="Survived", silent=True) 結果は59項目ありました。 重要な項目は色がつくようです? 2.モデル選択 複数のモデルを比較 数分かかります。 best_model = pcc.compare_models() print(best_model.__class__.__name__) # LogisticRegression 任意のモデル 任意のモデルを選びたい場合はこちらから model = pcc.create_model("lightgbm") 3.ハイパーパラメータの調整 tuned_model = pcc.tune_model(model) print(tuned_model.get_params) <bound method LGBMModel.get_params of LGBMClassifier(bagging_fraction=0.7, bagging_freq=3, boosting_type='gbdt', class_weight=None, colsample_bytree=1.0, feature_fraction=0.8, importance_type='split', learning_rate=0.1, max_depth=-1, min_child_samples=36, min_child_weight=0.001, min_split_gain=0.4, n_estimators=230, n_jobs=-1, num_leaves=50, objective=None, random_state=8219, reg_alpha=0.1, reg_lambda=3, silent=True, subsample=1.0, subsample_for_bin=200000, subsample_freq=0)> 調整前後の評価値です。 predict_model はdataを指定しないとホールドアウトで実行した結果を返します。 4.モデルの可視化 pcc.plot_model(tuned_model) 5.モデル解釈の可視化 pcc.interpret_model(tuned_model) 6.予測 "Label"と"Score"カラムが追加され、Labelは予測結果、分類なら各クラスの予測確率がScoreに入っています。 x_pred = df[df["Survived"].isnull()] y_pred = pcc.predict_model(tuned_model, data=x_pred) print(y_pred["Label"]) 891 0.0 892 0.0 893 0.0 894 0.0 895 1.0 ... 1304 0.0 1305 1.0 1306 0.0 1307 0.0 1308 0.0 Name: Label, Length: 418, dtype: object
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kaggleで書いたコードの備忘録~データ分析で使った手法一通り~(可視化、データ加工、検証、特徴量抽出、モデル、AutoML等)

初心者ながらKaggleに挑戦した時のコードを備忘録として残しておきます。 1.データ import 全体的に使うライブラリです。 各項目で使うライブラリはそちら側でimportを記載しています。 import numpy as np import pandas as pd from matplotlib import pyplot as plt import seaborn as sns 使用するデータ Kaggleチュートリアルのタイタニックを使います。 コード df_train = pd.read_csv("/kaggle/input/titanic/train.csv") df_test = pd.read_csv("/kaggle/input/titanic/test.csv") 学習データとテストデータをまとめて管理する マージ # データをマージ df_test["Survived"] = np.nan df = pd.concat([df_train, df_test], ignore_index=True, sort=False) print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) print(df.shape) # (1309, 12) ※目的変数(Survived)のテスト側の値はnanになります 分割 df_train = df[df["Survived"].notnull()] df_test = df[df["Survived"].isnull()] print(df_train.shape) # (891, 12) print(df_test.shape) # (418, 12) 数値データの要約統計量を確認したい print(df[["Age", "Fare"]].describe()) Age Fare count 1046.000000 1308.000000 mean 29.881138 33.295479 std 14.413493 51.758668 min 0.170000 0.000000 25% 21.000000 7.895800 50% 28.000000 14.454200 75% 39.000000 31.275000 max 80.000000 512.329200 カテゴリデータの要約統計量を確認したい print(df[["Sex"]].describe(include="O")) Sex count 1309 unique 2 top male freq 843 ※欠損値があるとエラーが出るので注意 欠損値を確認したい 方法1 print(df.isnull().sum()) PassengerId 0 Survived 418 Pclass 0 Name 0 Sex 0 Age 263 SibSp 0 Parch 0 Ticket 0 Fare 1 Cabin 1014 Embarked 2 is_train 0 dtype: int64 方法2 print(df.info()) <class 'pandas.core.frame.DataFrame'> RangeIndex: 1309 entries, 0 to 1308 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 PassengerId 1309 non-null int64 1 Survived 891 non-null float64 2 Pclass 1309 non-null int64 3 Name 1309 non-null object 4 Sex 1309 non-null object 5 Age 1046 non-null float64 6 SibSp 1309 non-null int64 7 Parch 1309 non-null int64 8 Ticket 1309 non-null object 9 Fare 1308 non-null float64 10 Cabin 295 non-null object 11 Embarked 1307 non-null object 12 is_train 1309 non-null int64 dtypes: float64(3), int64(5), object(5) memory usage: 133.1+ KB 方法2は欠損値以外の情報も表示されます。 2.データの可視化 カテゴリデータを表示したい(1変数) def plot_category(df, column, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(df[column]) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_category(df, "Pclass", scale=0.5, line=[300, 400]) 数値データを表示したい(1変数) def plot_float(df, column, bins=10, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.distplot(df[column], kde=True, rug=False, bins=bins) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_float(df, "Age", scale=0.5, line=20) ※binsはヒストグラムの分割数です カテゴリデータに対してカテゴリデータを比較したい(2変数) plot表示 シンプル def plot_category_category(df, column1, column2, scale=1.0, line=None): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.countplot(column1, hue=column2, data=df) if line is not None: if type(line) == list: for l in line: plt.axhline(y=l, color="red", linestyle="--") else: plt.axhline(y=line, color="red", linestyle="--") plt.legend() plt.title(column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7, line=100) 元のデータとの割合線も表示するバージョン def plot_category_category(df, column1, column2, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 欠損値が入っているデータは除外する df = df.dropna(subset=[column1, column2]).copy() # 型を変換 df[column1] = df[column1].astype(str) df[column2] = df[column2].astype(str) # マージンを設定 margin = 0.2 totoal_width = 1 - margin # hue_data hue_counts = df[column2].value_counts() hue_keys = hue_counts.keys() # clumn_data column_keys = df[column1].value_counts().keys() hue_column_counts = {} for k2 in hue_keys: hue_column_counts[k2] = [] for k in column_keys: c = df[df[column1]==k][column2].value_counts() # hue key毎に分けておく for k2 in hue_keys: if k2 in c: hue_column_counts[k2].append(c[k2]) else: hue_column_counts[k2].append(0) column_counts = [] for k in column_keys: c = len(df[df[column1]==k]) column_counts.append(c) # barの座標を計算 bar_count = len(hue_keys) bar_width = totoal_width/bar_count x_bar_pos = np.asarray(range(bar_count)) * bar_width for i, k in enumerate(hue_keys): counts = hue_column_counts[k] # barを表示 x_pos = np.asarray(range(len(counts))) x_pos = x_pos + x_bar_pos[i] - (bar_count * bar_width) / 2 + bar_width / 2 plt.bar(x_pos, counts, width=bar_width, label=hue_keys[i]) # 線を表示 p_all = (hue_counts[k] / hue_counts.sum()) c = np.asarray(column_counts) * p_all plt.hlines(c, xmin=x_pos - bar_width/2, xmax=x_pos + bar_width/2, colors='r', linestyles='dashed') plt.xticks(range(len(column_keys)), column_keys) plt.xlabel(column1) plt.legend(title=column2) plt.tight_layout() plt.show() # 表示例 plot_category_category(df, "Pclass", "Sex", scale=0.7) print表示 集計 print(pd.crosstab(df["Pclass"], df["Sex"])) Sex female male Pclass 1 144 179 2 106 171 3 216 493 各Sexにおける、Pclassの比率 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='columns')) Sex female male Pclass 1 0.309013 0.212337 2 0.227468 0.202847 3 0.463519 0.584816 Sex female male 各Pclassにおける、Sexの割合 print(pd.crosstab(df["Pclass"], df["Sex"], normalize='index')) Sex female male Pclass 1 0.445820 0.554180 2 0.382671 0.617329 3 0.304654 0.695346 カテゴリデータに対して数値データを比較したい(2変数) def plot_category_float( df, column1, column2, bins=10, exclude=[], scale=1.0, line=None, ): plt.figure(figsize=(6.4*scale, 4.8*scale)) for uniq in df[column1].unique(): if uniq is np.nan: continue if math.isnan(uniq): continue if uniq in exclude: continue # 表示メイン sns.distplot(df[df[column1]==uniq][column2], kde=True, rug=False, bins=bins, label=uniq) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.legend() plt.tight_layout() plt.show() # 表示例 plot_category_float(df, "Pclass", "Age", exclude=[2], scale=0.5, line=50) ※ascendingはNoneの場合ソートしません。  指定する場合はpandasのsort_valuesの引数と同じです。 ※excludeを指定すると任意のカテゴリデータを非表示にできます。 数値データに対して数値データを比較したい(2変数) def plot_float_float( df, column1, column2, hue=None, scale=1.0, ): # 表示メイン sns.jointplot(column1, column2, data=df, hue=hue, palette='Set2', size=6*scale) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float(df, "Age", "Fare", "Pclass", scale=0.5) 数値データと数値データに対してカテゴリデータを比較したい(3変数) plot_float_floatのhueを指定したバージョンです。 def plot_float_float_category(df, column1, column2, column3, scale=1.0,): plot_float_float(df, column1, column2, column3, scale) # 表示例 plot_float_float_category(df, "Age", "Fare", "Pclass", scale=0.7) 数値データと数値データに対して数値データを比較したい(3変数) def plot_float_float_float( df, column1, column2, column3, scale=1.0, ): _ds3 = (df[column3] - df[column3].min()) / (df[column3].max() - df[column3].min()) _ds3 = 10 + _ds3*1000 fig = plt.figure(figsize=(6.4*scale, 4.8*scale)) mappable = plt.scatter(df[column1], df[column2], s=_ds3, c=df[column3], cmap="Set2", alpha=0.3, label=column3) fig.colorbar(mappable) plt.xlabel(column1) plt.ylabel(column2) plt.grid(True) plt.legend() plt.tight_layout() plt.show() # 表示例 plot_float_float_float(df, "Age", "Pclass", "Fare", scale=0.7) ※表示例はPclassなので小数データではないですが… ※cmapの種類についてはここを参考 変数同士の相関係数をみたい ヒートマップの表示 def plot_corr(df, columns, scale=1.0): plt.figure(figsize=(6.4*scale, 4.8*scale)) # 表示メイン sns.heatmap(df[columns].corr(), annot=True, vmax=1, vmin=-1, fmt='.1f', cmap='RdBu') plt.tight_layout() plt.show() # 表示例 plot_corr(df, df.columns) ある変数に対して相関係数が高い変数のみ表示(1対多) def print_corr(df, columns, threshold=0.5): print("--- 相関係数(threshold: {})".format(threshold)) corr = df[columns].corr() count = 0 for i in range(len(corr.columns)): for j in range(i+1, len(corr.columns)): val = corr.iloc[i, j] if abs(val) > threshold: print("{} {}: {:.2f}".format(corr.columns[i], corr.columns[j], val)) count += 1 if count == 0: print("empty") # 表示例 print_corr(df, df.columns, threshold=0.4) --- 相関係数(threshold: 0.4) PassengerId is_train: -0.81 Pclass Age: -0.41 Pclass Fare: -0.56 ある配列データを横棒グラフで表示して比較したい def plot_bar(df, columns, ascending=None, scale=1.0, line=None): if ascending is None: df.plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) else: df.sort_values(columns, ascending=ascending).plot.barh(y=columns, figsize=(6.4*scale, 4.8*scale)) if line is not None: if type(line) == list: for l in line: plt.axvline(x=l, color="red", linestyle="--") else: plt.axvline(x=line, color="red", linestyle="--") plt.tight_layout() plt.show() # 表示例 plot_bar(df[:5], ["Age", "Fare"], ascending=True, scale=0.5, line=20) ※ascendingがNoneの場合は並べ替えしません。  それ以外の場合はpandasのsort_valuesの引数と同じ動作です 3.データ加工 欠損値を補完 df["Age_org"] = df["Age"] # 後で使うので欠損値がある状態も保存 df["Age_na"] = df["Age"].isnull() df["Age"].fillna(df2["Age"].median(), inplace=True) df["Embarked"].fillna("S", inplace=True) df["Fare"].fillna(df2['Fare'].median(), inplace=True) Label Encording from sklearn.preprocessing import LabelEncoder df["Sex"] = LabelEncoder().fit_transform(df['Sex']) df["Embarked"] = LabelEncoder().fit_transform(df['Embarked']) ダミー化(One-hotエンコーディング) pandasのget_dummiesで簡単にできますが、カラム名も欲しい場合のものを作成しています。 def dummy(df, column): c_arr = [] for c in df[column].value_counts().keys(): name = column + "_" + str(c) c_arr.append(name) df[name] = np.asarray(df[column] == c).astype(int) return df, c_arr 4.目的変数に対する各説明変数の影響度を確認したい 確認に使っているカラムは以下です。 (Familyも何となく追加) df["Family"] = df["SibSp"] + df["Parch"] select_columns = [ "Pclass", "Sex", "Age_completion", "SibSp", "Parch", "Family", "Fare", "Embarked", ] 分類 ロジスティック回帰の係数 ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import sklearn.linear_model import xgboost as xgb import lightgbm as lgb def create_feature_effect_classifier(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # ロジスティック回帰 #---------------- if not exists_missing_value: model = sklearn.linear_model.LogisticRegression(random_state=1234) model.fit(x, y) if len(model.coef_) == 1: # 2クラス分類 df_result["LogisticRegression"] = model.coef_[0] else: # 多クラスの場合は各クラスの係数の平均を出す df_result["LogisticRegression"] = np.mean(model.coef_, axis=0) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestClassifier(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBClassifier(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMClassifier(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 実行例 df_feature = create_feature_effect_classifier(df, select_columns, "Survived") print(df_feature) plot_bar(df_feature, "LogisticRegression", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) LogisticRegression RandomForest XGB LGBM name Pclass -1.050753 0.072651 0.233783 116 Sex -2.631111 0.257130 0.509833 97 Age -0.038077 0.255461 0.032465 1055 SibSp -0.182407 0.030305 0.078906 51 Parch 0.047843 0.024342 0.034393 56 Family -0.134563 0.055398 0.047479 154 Fare 0.002171 0.272152 0.032554 1344 Embarked -0.213182 0.032561 0.030586 125 回帰 重回帰分析の決定係数(とP値) ランダムフォレスト分類における各変数の重要度 XGBoost分類における各変数の重要度 LightGBM分類における各変数の重要度 import statsmodels.api as sm import sklearn.ensemble import xgboost as xgb import lightgbm as lgb def create_feature_effect_regressor(df, select_columns, target_column): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) x = df[df[target_column].notnull()][select_columns] y = df[df[target_column].notnull()][target_column] # 説明変数に欠損値があると判定できない手法があるので exists_missing_value = (np.sum(x.isnull().sum()) > 0) # 結果用 df_result = pd.DataFrame(select_columns, columns=["name"]) #---------------- # 重回帰分析 #---------------- if not exists_missing_value: model = sm.OLS(y, sm.add_constant(x)) fitted = model.fit() #print(fitted.summary()) if len(fitted.params) == len(select_columns): df_result["OLS_val"] = list(fitted.params) df_result["OLS_p"] = list(fitted.pvalues) elif len(fitted.params) == len(select_columns)+1: df_result["OLS_val"] = list(fitted.params)[1:] df_result["OLS_p"] = list(fitted.pvalues)[1:] df_result["OLS_p"] = np.round(df_result["OLS_p"], 3) #---------------- # RandomForest #---------------- if not exists_missing_value: model = sklearn.ensemble.RandomForestRegressor(random_state=1234) model.fit(x, y) df_result["RandomForest"] = model.feature_importances_ #---------------- # XGB #---------------- model = xgb.XGBRegressor(eval_metric="logloss", use_label_encoder=False, random_state=1234) model.fit(x, y) df_result["XGB"] = model.feature_importances_ #---------------- # LGBM #---------------- model = lgb.LGBMRegressor(random_state=1234) model.fit(x, y) df_result["LGBM"] = model.feature_importances_ df_result.set_index("name", inplace=True) return df_result # 表示例 df_feature = create_feature_effect_regressor(df, select_columns, "Age") print(df_feature) plot_bar(df_feature, "OLS_val", ascending=True, scale=0.5) plot_bar(df_feature, "RandomForest", ascending=True, scale=0.5) plot_bar(df_feature, "XGB", ascending=True, scale=0.5) plot_bar(df_feature, "LGBM", ascending=True, scale=0.5) OLS_val OLS_p RandomForest XGB LGBM name Pclass -5.622647 0.000 0.205440 0.496402 140 Sex 2.020035 0.004 0.054703 0.038413 190 SibSp -0.910016 0.003 0.062044 0.066298 141 Parch 0.036394 0.913 0.164997 0.187804 234 Family -0.873621 0.000 0.066830 0.086484 171 Fare 0.006825 0.400 0.395958 0.056573 1940 Embarked 0.244200 0.556 0.050028 0.068026 184 P値(OLD_p)が0.05以上の場合は有意性がありません。(決定係数(OLS_val)が0の可能性があります) 今回ですと、Parch,Fare,Embarked は有意性がないので説明変数としては採用しないほうが良いかもしれません。 決定木 import sklearn.tree def plot_decision_tree(df, columns, target_column, model_type, # "Classifier" or "Regressor" scale=1.0, max_depth=None, # 表示する木の深さ fontsize=None, # 文字の大きさ ): x = df[df[target_column].notnull()][columns] y = df[df[target_column].notnull()][target_column] if model_type == "Classifier": clf = sklearn.tree.DecisionTreeClassifier(random_state=1234) elif model_type == "Regressor": clf = sklearn.tree.DecisionTreeRegressor(random_state=1234) else: raise ValueError() clf = clf.fit(x, y) plt.figure(figsize=(6.4*scale, 4.8*scale)) sklearn.tree.plot_tree(clf, feature_names=columns, max_depth=max_depth, fontsize=fontsize, proportion=False, # Trueだと割合で表示 filled=True, # 純度に応じて色がつく ) plt.show() # 表示例 plot_decision_tree(df, select_columns, "Survived", "Classifier", scale=1, max_depth=2, fontsize=10) 5.Validation K-分割交差検証 層状K-分割交差検証の実装例です。 def validation_classifier( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 model_cls, # 使うモデル model_params, # モデルのパラメータ ): # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # 交差検証 metrics = [] #kf = sklearn.model_selection.KFold(n_splits=3, shuffle=True, random_state=1234) # KFoldにしたい場合 kf = sklearn.model_selection.StratifiedKFold(n_splits=3, shuffle=True, random_state=1234) for train_idx, test_idx in kf.split(df_train, y=df_train[target_column]): df_train_sub = df_train.iloc[train_idx] df_test_sub = df_train.iloc[test_idx] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] model = model_cls(**model_params) model.fit(x_train, y_train) # 正解率 y_pred = model.predict(x_test) metric = sklearn.metrics.accuracy_score(y_test, y_pred) # logloss #y_pred = model.predict_proba(x_test) #metric = sklearn.metrics.log_loss(y_test, y_pred[:,1]) metrics.append(metric) # 交差検証の結果は平均を採用 metrics_mean = np.mean(metrics, axis=0) return metrics_mean # 実行例 metric = validation_classifier(df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}) print(metric) 0.8103254769921437 Adversarial Validation 学習データとテストデータの分布を確認し、分布が偏っている場合にテストデータに近いデータを検証に使う手法です。 モデルはLGBMで決め打ちしています。 import sklearn.model_selection import sklearn.metrics import lightgbm as lgb def adversarial_validation_check( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト target_column, # 目的変数 result_column_name="", # 結果保存用のカラム名 ebable_plot=True, ): # モデル model = lgb.LGBMClassifier(**{"random_state": 1234}) # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df.copy() # 目的変数を作成 df_train["_target"] = df_train[target_column].apply(lambda x: np.isnan(x)).astype(int) # 学習データとテストデータに分割 df_train_sub, df_true_sub = sklearn.model_selection.train_test_split( df_train, test_size=0.3, stratify=df_train["_target"], random_state=1234) x_train = df_train_sub[select_columns] y_train = df_train_sub["_target"] x_true = df_true_sub[select_columns] y_true = df_true_sub["_target"] # 学習 model.fit(x_train, y_train, eval_set=[(x_train, y_train), (x_true, y_true)], eval_names=['train', 'valid'], eval_metric='auc', verbose=0) if ebable_plot: # 学習結果をplot lgb.plot_metric(model.evals_result_, metric='auc') plt.axhline(y=0.5, color="red", linestyle="--") plt.ylim(0.4, 1.0) plt.tight_layout() plt.show() # 分布に影響が大きい変数順に並べて表示する pd.DataFrame(model.feature_importances_, index=select_columns ).sort_values(0, ascending=True).plot.barh() plt.tight_layout() plt.show() # 結果を新しい特徴量として追加 if result_column_name != "": y_pred = model.predict_proba(x_true) df[result_column_name] = pd.Series(y_pred[:,1]) # auc値を返す return sklearn.metrics.roc_auc_score(y_true, y_pred[:,1]) # 実行例 adversarial_validation_check(df, select_columns, "Survived") adversarial_validation_check(df, select_columns, "Age_org", "train_ratio_Age") Survivedの結果 valid の値が0.5付近なので、学習データとテストデータを区別できない=学習データとテストデータの分布が同じです。 この場合はAdversarial Validationを使う必要はありません。 Ageの結果 Ageは欠損値のないデータ群と欠損値のあるデータ群を比較しています。 validが約0.8とかなり高い確率で予測できています。 Ageの欠損値がある場合とない場合で分布に偏りがある事が分かります。 また、変数の重要度を見ることで、分布の偏りの原因になっている変数が分かります。 (今回はFareが一番影響しているようですね) では、これを用いて検証するコード例です。 上記モデルで結果を予測し、テストデータである確率が高いデータを検証データに、 そうじゃないデータは学習データにして検証を行います。 def adversarial_validation_Age( df, # 全データ(DataFrame) select_columns, # 説明変数のリスト model_cls, # 使うモデル model_params, # モデルのパラメータ ): target_column = "Age_org" # select_columnsにtarget_columnが入っていれば削除 select_columns = list(select_columns) if target_column in select_columns: select_columns.remove(target_column) # 学習用データを作成 df_train = df[df[target_column].notnull()].copy() # データをtrain_ratioに従って分割 test_size = 0.25 test_num = int(len(df_train) * test_size) df_train = df_train.sort_values("train_ratio_Age") df_train_sub = df_train[test_num:] df_test_sub = df_train[:test_num] x_train = df_train_sub[select_columns] y_train = df_train_sub[target_column] x_test = df_test_sub[select_columns] y_test = df_test_sub[target_column] # 学習 model = model_cls(**model_params) model.fit(x_train, y_train) y_pred = model.predict(x_test) # 評価 metric = np.sqrt(sklearn.metrics.mean_squared_error(y_test, y_pred)) return metric metric = adversarial_validation_Age(df, select_columns, lgb.LGBMRegressor, {"random_state": 1234}) print(print) 12.119246573863585 6.特徴量抽出 参考:特徴量選択のまとめ 全く関係ない特徴量を減らしたい 分散が0(すべて同じ値)データを削除 全く同じ特徴量のカラムを削除 import sklearn.feature_selection def feature_selection_no_related( df, columns, enable_print=False ): prev_columns = columns prev_len = len(prev_columns) #------------------------------- # 分散が0(すべて同じ値)のカラムは削除 #------------------------------- vt = sklearn.feature_selection.VarianceThreshold(threshold=0) vt.fit(df[prev_columns]) inc_columns = [] exc_columns = [] for i, flag in enumerate(vt.get_support()): if flag: inc_columns.append(prev_columns[i]) else: exc_columns.append(prev_columns[i]) if enable_print: print("var0 {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) #------------------------------- # 全く同じ特徴量のカラムは削除 #------------------------------- prev_columns = inc_columns prev_len = len(prev_columns) inc_columns = [] exc_columns = [] for c, flag in df[prev_columns].T.duplicated().items(): if not flag: inc_columns.append(c) else: exc_columns.append(c) if enable_print: print("dup {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 df["A"] = 1 df["B"] = df["Sex"] new_columns = util.feature_reduction_no_related(df, select_columns + ["A", "B"], enable_print=True) print(new_columns) var0 10 -> 9 exc: ['A'] dup 9 -> 8 exc: ['B'] ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Family', 'Fare', 'Embarked'] 多分関係ない特徴量を減らしたい 相関係数 相関係数は0なら相関なし、1または-1なら正か負の強い相関があります。 0に違い特徴量を減らします。 def feature_selection_corr( df, columns, target_columns, threshold, enable_print=False ): # target_columnをいれる prev_columns = list(set(list(columns) + [target_columns])) prev_len = len(prev_columns) # 相関係数 df_corr = df[prev_columns].corr() df_corr.fillna(0, inplace=True) # 閾値以下は除外 inc_columns = [] exc_columns = [] for c in df_corr.columns: val = df_corr[target_columns][c] if abs(val) < threshold: exc_columns.append([c, val]) else: inc_columns.append(c) if enable_print: print("corr {} -> {} exc: {}".format(prev_len, len(inc_columns), exc_columns)) return inc_columns # 実行例 new_columns = util.feature_selection_corr(df, select_columns, "Survived", 0.1, enable_print=True) print(new_columns) corr 9 -> 5 exc: [['SibSp', -0.03532249888573556], ['Parch', 0.08162940708348335], ['Age', -0.07722109457217759], ['Family', 0.016638989282745195]] ['Embarked', 'Fare', 'Sex', 'Survived', 'Pclass'] 関係のある特徴量のみを抽出したい 評価の上がる特徴量を追加(または削除)していく 全説明変数を見て一番評価が上がる変数を追加または削除していく手法です。 Foward説明変数がない状態から1つずつ追加していく方法で、 Backwardは全説明変数から1つずつ減らしてい区方法です。 説明変数の数に対して最大O(N^2)時間がかかると思います。 import sklearn.feature_selection def feature_selection_SFS( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ direction, # "forward"で追加していく、"backward"で削除していく scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.SequentialFeatureSelector( model_cls(**model_params), direction=direction, scoring=scoring ) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_SFS( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, direction="forward", scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'Fare'] exc ['SibSp', 'Parch', 'Family', 'Embarked'] RFECV RFECVは簡単に言うとモデルを作成し、重要度が低い変数を除外するという特徴量選択方法らしいです。 その方法から、coef_またはfeature_importances_が作られるモデルしか適用できません。 参考:【Kaggle】タイタニックの振り返り#3 RFECVで特徴選択 import sklearn.feature_selection def feature_selection_RFECV( df, columns, target_columns, model_cls, # 使うモデル model_params, # モデルのパラメータ scoring, # 評価方法 ): x = df[df[target_columns].notnull()][columns] y = df[df[target_columns].notnull()][target_columns] rfe = sklearn.feature_selection.RFECV(model_cls(**model_params), scoring=scoring) rfe.fit(x, y) inc_columns = [] exc_columns = [] for i, flag in enumerate(rfe.get_support()): if flag: inc_columns.append(columns[i]) else: exc_columns.append(columns[i]) return inc_columns, exc_columns # 実行例 inc_columns, exc_columns = feature_selection_RFECV( df, select_columns, "Survived", lgb.LGBMClassifier, {"random_state": 1234}, scoring="accuracy") print("inc", inc_columns) print("exc", exc_columns) inc ['Pclass', 'Sex', 'Age', 'SibSp', 'Family', 'Fare', 'Embarked'] exc ['Parch'] 7.モデル モデル一覧 分類モデル import sklearn.ensemble import sklearn.gaussian_process import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.discriminant_analysis import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_classifier(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostClassifier, {"random_state": random_seed}], [sklearn.ensemble.BaggingClassifier, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesClassifier, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingClassifier, {"random_state": random_seed}], [sklearn.ensemble.RandomForestClassifier, {"random_state": random_seed}], #Gaussian Processes [sklearn.gaussian_process.GaussianProcessClassifier, {"random_state": random_seed}], #GLM [sklearn.linear_model.RidgeClassifier, {}], #Navies Bayes [sklearn.naive_bayes.BernoulliNB, {}], [sklearn.naive_bayes.GaussianNB, {}], #Nearest Neighbor [sklearn.neighbors.KNeighborsClassifier, {}], #Trees [sklearn.tree.DecisionTreeClassifier, {"random_state": random_seed}], [sklearn.tree.ExtraTreeClassifier, {"random_state": random_seed}], # SVM [sklearn.svm.SVC, {}], [sklearn.svm.NuSVC, {}], [sklearn.svm.LinearSVC, {}], # NN [sklearn.neural_network.MLPClassifier, {"random_state": random_seed}], #xgboost [xgb.XGBClassifier, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMClassifier, {"random_state": random_seed}], ] return models models = get_models_classifier(1234) 回帰モデル import sklearn.ensemble import sklearn.linear_model import sklearn.naive_bayes import sklearn.neighbors import sklearn.tree import sklearn.svm import sklearn.neural_network import xgboost as xgb import lightgbm as lgb def get_models_regressor(random_seed): models = [ #Ensemble Methods [sklearn.ensemble.AdaBoostRegressor, {"random_state": random_seed}], [sklearn.ensemble.BaggingRegressor, {"random_state": random_seed}], [sklearn.ensemble.ExtraTreesRegressor, {"random_state": random_seed}], [sklearn.ensemble.GradientBoostingRegressor, {"random_state": random_seed}], [sklearn.ensemble.RandomForestRegressor, {"random_state": random_seed}], # GLM [sklearn.linear_model.Lasso, {"random_state": random_seed}], [sklearn.linear_model.Ridge, {"random_state": random_seed}], [sklearn.linear_model.ElasticNet, {"random_state": random_seed}], #Nearest Neighbor [sklearn.neighbors.KNeighborsRegressor, {}], #Trees [sklearn.tree.DecisionTreeRegressor, {"random_state": random_seed}], [sklearn.tree.ExtraTreeRegressor, {"random_state": random_seed}], # SVM [sklearn.svm.SVR, {}], [sklearn.svm.NuSVR, {}], [sklearn.svm.LinearSVR, {}], # NN [sklearn.neural_network.MLPRegressor, {"random_state": random_seed}], #xgboost [xgb.XGBRegressor, {"eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed}], # light bgm [lgb.LGBMRegressor, {"random_state": random_seed}], ] return models models = get_models_regressor(1234) ハイパーパラメータの調整(optuna) モデルパラメータ クリックで展開 def create_optuna_params(trial, model_cls, random_seed): #----------------------------------------------------------- # Classifier #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "algorithm": trial.suggest_categorical('algorithm', ["SAMME", "SAMME.R"]), # default=SAMME.R "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 #"max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingClassifier: return { "loss": trial.suggest_categorical('loss', ["deviance", "exponential"]), # default=deviance "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Gaussian Processes if model_cls == sklearn.gaussian_process.GaussianProcessClassifier: return {"random_state": random_seed} # GLM if model_cls == sklearn.linear_model.RidgeClassifier: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True "normalize": trial.suggest_categorical('normalize', [False, True]), # default=False "random_state": random_seed } #Navies Bayes if model_cls == sklearn.naive_bayes.BernoulliNB: return { "alpha": trial.suggest_uniform('alpha', 0, 1), # default=1 } if model_cls == sklearn.naive_bayes.GaussianNB: return {} # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsClassifier: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeClassifier: return { "criterion": trial.suggest_categorical('criterion', ["gini", "entropy"]), # default=gini "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVC: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2]), # default=1.0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVC: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf", "sigmoid", "precomputed"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVC: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["hinge", "squared_hinge"]), # default=squared_hinge "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPClassifier: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBClassifier: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMClassifier: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } #----------------------------------------------------------- # Regressor #----------------------------------------------------------- #Ensemble Methods if model_cls == sklearn.ensemble.AdaBoostRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=50 "learning_rate": trial.suggest_uniform('learning_rate', 0, 1), # default=1 "loss": trial.suggest_categorical('loss', ["linear", "square", "exponential"]), # default=linear "random_state": random_seed } if model_cls == sklearn.ensemble.BaggingRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 500), # default=10 "max_samples": trial.suggest_uniform('max_samples', 0, 1), # default=1 "max_features": trial.suggest_uniform('max_features', 0, 1), # default=1 "bootstrap": trial.suggest_categorical('bootstrap', [False, True]), # default=True "bootstrap_features": trial.suggest_categorical('bootstrap_features', [False, True]), # default=False # oob_score # warm_start "random_state": random_seed } if model_cls == sklearn.ensemble.ExtraTreesRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 100), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 50]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } if model_cls == sklearn.ensemble.GradientBoostingRegressor: return { "loss": trial.suggest_categorical('loss', ["ls", "lad", "huber", "quantile"]), # default=ls "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 200), # default=100 "subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 "criterion": trial.suggest_categorical('criterion', ["friedman_mse", "mse"]), # default=friedman_mse # min_samples_split # min_samples_leaf # min_weight_fraction_leaf "max_depth": trial.suggest_int('max_depth', 1, 10), # default=3 # min_impurity_decrease # init "random_state": random_seed } if model_cls == sklearn.ensemble.RandomForestRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 150), # default=100 "criterion": trial.suggest_categorical('criterion', ["mse", "mae"]), # default=mse "max_depth": trial.suggest_categorical('max_depth', [None, 1, 2, 5, 10, 20]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # max_leaf_nodes # min_impurity_decrease # bootstrap # oob_score "random_state": random_seed } # Nearest Neighbor if model_cls == sklearn.neighbors.KNeighborsRegressor: return { "n_neighbors": trial.suggest_int('n_neighbors', 1, 100), # default=5 "weights": trial.suggest_categorical('weights', ["uniform", "distance"]), # default=uniform "algorithm": trial.suggest_categorical('algorithm', ["auto", "ball_tree", "kd_tree", "brute"]), # default=auto "leaf_size": trial.suggest_int('leaf_size', 1, 100), # default=30 "p": trial.suggest_categorical('p', [1, 2, 3, 4]), # default=2 # metric # metric_params } # Trees if model_cls == sklearn.tree.DecisionTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } if model_cls == sklearn.tree.ExtraTreeRegressor: return { "criterion": trial.suggest_categorical('criterion', ["mse", "friedman_mse", "mae"]), # default=mse "splitter": trial.suggest_categorical('splitter', ["random", "best"]), # default=random "max_depth": trial.suggest_categorical('max_depth', [None, 2, 10, 50, 100]), # default=None # min_samples_split # min_samples_leaf # min_weight_fraction_leaf # max_features # min_impurity_decrease # max_leaf_nodes # ccp_alpha "random_state": random_seed } # SVM if model_cls == sklearn.svm.SVR: return { "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 # tol "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0.1 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # cache_size # max_iter } if model_cls == sklearn.svm.NuSVR: return { "nu": trial.suggest_uniform('nu', 0, 1), # default=0.5 "C": trial.suggest_categorical('C', [0.01, 0.1, 1, 2, 10]), # default=1.0 "kernel": trial.suggest_categorical('kernel', ["linear", "poly", "rbf"]), # default=rbf # degree # gamma # coef0 "shrinking": trial.suggest_categorical('shrinking', [False, True]), # default=True # tol # cache_size # max_iter } if model_cls == sklearn.svm.LinearSVR: return { #"epsilon": trial.suggest_uniform('epsilon', 0, 1), # default=0 #"tol": trial.suggest_uniform('tol', 0, 1), # default=1e-4 "C": trial.suggest_uniform('C', 0, 1), # default=1.0 "loss": trial.suggest_categorical('loss', ["epsilon_insensitive", "squared_epsilon_insensitive"]), # default=epsilon_insensitive "fit_intercept": trial.suggest_categorical('fit_intercept', [False, True]), # default=True #"intercept_scaling": #"dual": trial.suggest_categorical('dual', [False, True]), # default=True "max_iter": trial.suggest_categorical('max_iter', [100, 1000, 10000]), # default=1000 } # NN if model_cls == sklearn.neural_network.MLPRegressor: return { "hidden_layer_sizes": (100,), # default=(100,) #"activation": trial.suggest_categorical('activation', ["identity", "logistic", "tanh", "relu"]), # default=relu #"solver": trial.suggest_categorical('solver', ["lbfgs", "sgd", "adam"]), # default=adam #"alpha": trial.suggest_uniform('alpha', 0, 0.1), # default=0.0001 "batch_size": trial.suggest_categorical('batch_size', ["auto", 32, 64, 128, 256, 512]), # default=auto "learning_rate": trial.suggest_categorical('learning_rate', ["constant", "invscaling", "adaptive"]), # default=constant "learning_rate_init": trial.suggest_uniform('learning_rate_init', 0, 0.1), # default=0.001 #"power_t": "max_iter": trial.suggest_categorical('max_iter', [100, 200, 500]), # default=200 "random_state": random_seed } #xgboost if model_cls == xgb.XGBRegressor: return { "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default= #"max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default= "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default= "eval_metric": "logloss", "use_label_encoder": False, "random_state": random_seed, } # light bgm if model_cls == lgb.LGBMRegressor: return { "boosting_type": trial.suggest_categorical('boosting_type', ["gbdt", "dart", "goss"]), # default=gbdt "num_leaves": trial.suggest_int('num_leaves', 2, 100), # default=31 "max_depth": trial.suggest_categorical('max_depth', [-1, 5, 10, 50, 100]), # default=-1 "learning_rate": trial.suggest_uniform('learning_rate', 0, 0.1), # default=0.1 "n_estimators": trial.suggest_int('n_estimators', 2, 1000), # default=1000 #"subsample_for_bin": trial.suggest_int('subsample_for_bin', 100, 1000000), # default=200000 #"objective": trial.suggest_categorical('objective', [None]), # default=None # class_weight #"min_split_gain": trial.suggest_uniform('min_split_gain', 0, 1), # default=0 #"min_child_weight": trial.suggest_uniform('min_child_weight', 0, 1), # default=1e-3 #"min_child_samples ": trial.suggest_int('min_child_samples ', 1, 100), # default=20 #"subsample": trial.suggest_uniform('subsample', 0, 1), # default=1 #"subsample_freq": trial.suggest_int('subsample_freq ', 0, 100), # default=0 #"colsample_bytree" #"reg_alpha": trial.suggest_uniform('reg_alpha', 0, 1), # default=0 #"reg_lambda": trial.suggest_uniform('reg_lambda', 0, 1), # default=0 "random_state": random_seed } パラメータ調整コード例 from tqdm import tqdm import time # 結果格納用 df_compare = pd.DataFrame(columns=['name', 'metric1', "metric2", 'params', 'time']) #--------------------------- # optunaの定義 #--------------------------- # optuna silent optuna.logging.set_verbosity(optuna.logging.WARNING) def objective_degree(model_cls, df): def objective(trial): params = create_optuna_params(trial, model_cls, 1234) #--- モデルを評価する metric = validation_classifier( df, select_columns, "Survived", model_cls, params) return metric return objective #--------------------------- # モデル毎にパラメータをサーチする #--------------------------- for model_ in tqdm(get_models_classifier(1234)): model_cls = model_[0] model_params = model_[1] name = model_cls.__name__ try: # デフォルトパラメータでの結果を取得しておく metric1 = validation_classifier( df, select_columns, "Survived", model_cls, model_params) # 調整開始 t0 = time.time() study = optuna.create_study(direction="maximize") study.optimize(objective_degree(model_cls, df), timeout=60*1, n_trials=1000) # optunaの結果を取得 best_value = study.best_value best_params = study.best_params trial_count = len(study.trials) t1 = time.time() - t0 # 結果を保存 df_compare = df_compare.append({ "name": name, "metric1": metric1, "metric2": best_value, "params": best_params, "count": trial_count, "time": t1, }, ignore_index=True) except Exception as e: print("error: {}".format(name)) print(e) # 結果を表示 print(df_compare) df_compare.set_index('name', inplace=True) df_compare.plot.barh(y=["metric1", "metric2"]) plt.legend(loc='upper left') plt.tight_layout() plt.show() name metric1 metric2 time count 0 AdaBoostClassifier 0.795735 0.815937 63.645483 30.0 1 BaggingClassifier 0.804714 0.826038 60.307925 52.0 2 ExtraTreesClassifier 0.804714 0.823793 60.205603 330.0 3 GradientBoostingClassifier 0.829405 0.839506 60.315318 148.0 4 RandomForestClassifier 0.804714 0.826038 60.258668 112.0 5 GaussianProcessClassifier 0.710438 0.710438 60.037200 101.0 6 RidgeClassifier 0.792368 0.810325 41.930431 1000.0 7 BernoulliNB 0.784512 0.784512 30.653054 1000.0 8 GaussianNB 0.786756 0.786756 21.921596 1000.0 9 KNeighborsClassifier 0.694725 0.753086 60.289474 957.0 10 DecisionTreeClassifier 0.784512 0.808081 35.006361 1000.0 11 ExtraTreeClassifier 0.764310 0.797980 33.426985 1000.0 12 LinearSVC 0.738496 0.801347 60.149822 157.0 13 MLPClassifier 0.794613 0.819304 60.149625 101.0 14 XGBClassifier 0.812570 0.829405 60.045466 56.0 15 LGBMClassifier 0.810325 0.838384 61.060753 47.0 metric1がデフォルト値、metric2が調整後の数値です。 ハイパーパラメータ調整の効果はあるようです。 ただ、時間がかなりかかりますね… 8.その他テクニックなど 目的変数の対数変換(回帰) 目的変数の対数変換です。 参照:どのようなときに目的変数Yではなくlog(Y)にしたほうがよいのか?~対数変換するメリットとデメリット~ 参照のメリットを引用すると、 メリット: Y の値が小さいサンプルの誤差が小さくなる デメリット: Y の値が大きいサンプルの誤差が大きくなる です。 対数変換 df["Survived"] = np.log(df["Survived"]) 元に戻す model = 対数変換した目的変数でモデルを学習 # 予測する x = df[df["Survived"].isnull()][select_columns] y_pred = model.predict(x) # 予測結果を元に戻す y_pred = np.exp(y_pred) 例のSurvivedは分類問題なので実施するメリットはありませんけど… 9.AutoML(PyCaret)の適用 PyCaret を使う例です。 表示はJupyter Notebookで使う場合の出力に最適化されているようです。 参考:最速でPyCaretを使ってみた 1.データ読み込み 学習に使うデータは以下です。 pcc_df = df[df["Survived"].notnull()][[ "Pclass", "Sex", "Age", "SibSp", "Parch", "Family", "Fare", "Embarked", "Survived", # 学習データなので目的変数もいれます ]] # データを渡す import pycaret.classification as pcc r = pcc.setup(pcc_df, target="Survived", silent=True) 結果は59項目ありました。 重要な項目は色がつくようです? 2.モデル選択 複数のモデルを比較 数分かかります。 best_model = pcc.compare_models() print(best_model.__class__.__name__) # LogisticRegression 任意のモデル 任意のモデルを選びたい場合はこちらから model = pcc.create_model("lightgbm") 3.ハイパーパラメータの調整 tuned_model = pcc.tune_model(model) print(tuned_model.get_params) <bound method LGBMModel.get_params of LGBMClassifier(bagging_fraction=0.7, bagging_freq=3, boosting_type='gbdt', class_weight=None, colsample_bytree=1.0, feature_fraction=0.8, importance_type='split', learning_rate=0.1, max_depth=-1, min_child_samples=36, min_child_weight=0.001, min_split_gain=0.4, n_estimators=230, n_jobs=-1, num_leaves=50, objective=None, random_state=8219, reg_alpha=0.1, reg_lambda=3, silent=True, subsample=1.0, subsample_for_bin=200000, subsample_freq=0)> 調整前後の評価値です。 predict_model はdataを指定しないとホールドアウトで実行した結果を返します。 4.モデルの可視化 pcc.plot_model(tuned_model) 5.モデル解釈の可視化 pcc.interpret_model(tuned_model) 6.予測 "Label"と"Score"カラムが追加され、Labelは予測結果、分類なら各クラスの予測確率がScoreに入っています。 x_pred = df[df["Survived"].isnull()] y_pred = pcc.predict_model(tuned_model, data=x_pred) print(y_pred["Label"]) 891 0.0 892 0.0 893 0.0 894 0.0 895 1.0 ... 1304 0.0 1305 1.0 1306 0.0 1307 0.0 1308 0.0 Name: Label, Length: 418, dtype: object
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSwitchBot温湿度計のデータ取り込み、GoogleAppsScriptでスプレットシートへの記録を自動化をする

初めまして、IoTが大好きで、Raspberry Piだのスマート家電だのを買いあさってる人です。 今回は、中国は深センのWonderlabsが出しているSwitchBot温湿度計を使って、 スプレッドシートに自動で温度と湿度を記録できるようにします。 事前準備 SwitchBot APIを使用するにはHub Mini,Hub Plusと連携させ、 オンラインサービスを有効にする必要があります。 温湿度計の設定を完了させていることを前提とします。 温湿度計のBluのMacアドレスを控えておいてください。 SwitchBotアプリで開発者用トークンを発行して、控えておいてください やり方はネットで検索すればたくさん出てきます。他人任せ Pythonのコード、設定 import requests import json import sys header = {"Authorization": "your_token"} device_id = "your_mac_address" url = "https://api.switch-bot.com/v1.0/devices/" + device_id + '/status' response = requests.get(url, headers=header) json_dict = json.loads(response.text) temperature = json_dict["body"]['temperature'] humidity = json_dict["body"]['humidity'] url2 = "your_gas_app_url" data = {'temp': temperature, 'humi': humidity} response = requests.post(url2, data=data) これが温湿度計のデータを取得し、GASにデータを送るプログラムです。 your_token には自分のトークンを your_mac_address には自分の温度計のMacアドレスを入れてください。 your_gas_app_url については次の項で説明します。 GoogleAppsScriptのコード、設定 Googleドライブにアクセスし、スプレッドシートを新規作成します。 作成したら「ツール」を開いて、「スクリプト エディタ」を開きます。 表示されたエディタに以下のコードを貼り付けてください function doPost(e) { var temp = e.parameter.temp; var humi = e.parameter.humi; var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); sheet.appendRow([new Date(), temp, humi]); } 出来たらデプロイ→新しいデプロイを開き、 種類の選択を「ウェブアプリケーション」 アクセスできるユーザーを「全員」に変更してデプロイします。 この画面になったら、アクセスを承認して Googleアカウントでログインします。 警告画面がでたら「詳細」→ 安全ではないページに移動 「Googleアカウントへのアクセスを~」の画面でアクセスを許可 GASのページに戻ると、デプロイIDとURLが表示されているので URL(script.google.com/macros/なんとか)をコピーしてください。 このURLを先ほどのyour_gas_app_urlの場所に貼り付けてください。 あとはPythonプログラムをcrontabなどで回せば 勝手に記録してくれるようになります。 APIは一日1000回までリクエストが可能です。自分は5分に一回回しています。 あとは煮るなり焼くなりご自由に。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonは_(アンダースコア)の使い方を理解するだけでプロフェッショナルになれる

自己紹介 普段私は、 一番得意な機械学習(深層学習)をしたり、 Python/Django でWebアプリを開発したり、 TypeScript/Vue or React でフロントエンドの開発をしたり、 PHP/Laravel でWebアプリを開発したり、 さまざまなことを行っています。 趣味で休みの日にGo言語で色々作成しているのですが、型のある世界は素敵だなと昨今感じています。 今最もやりたいことは、Goで大規模なWebアプリケーションを作成したい。 企業案件やご連絡等ございましたらお気軽に下記よりご連絡いただければと思います。 nagamatsu-k@dym.jp 第3次AIブームの到来 米Google DeepMindが開発した人工知能(AI)の囲碁プログラム「AlphaGo」が世界トップレベルの実力を持つ韓国のプロ棋士、李世ドル(イ・セドル)九段に4勝1敗と大きく勝ち越したことが着火剤となり、2015年より第3次AIブームへと突入した。(ちなみにAIが誕生したのは1950~1960年代で第1次AIブームの到来) 余談になるがAlphaGo(4億円の知能)はなぜすごいのか? AlphaGoがそれ以前のチェスや将棋のAIと異なるのは、畳み込みニューラルネットワーク(CNN)を応用している点だ。このCNNはさらに強化学習を行い、自分自身と対局を数千万回も繰り返した。 Pythonってすごくない?? 機械学習やデータ解析に必要なライブラリが充実しているだけでなく、Webアプリケーションを開発するフレームワークDjangoの完成度の高さ(特にセキュリティ面のサポートがすごい) できないことはないにも関わらず、簡単に書けてしまう。 やっぱりPythonってすごい。 Pythonは書きやすいが読みにくい? 柔軟なコードが書けるがゆえ、初心者が書いてしまうと2度見、いや4度見してしまうコードがたくさんある。 Pythonは「誰が書いても同じようなコードが完成する」をモットーに作られている、その真理から程遠い俗にいう「うんこーど」が生まれるのも事実である。 Pythonのコードが人より上手く書けるように伝授しようではないか Pythonを書いているのにアンダースコアの区別がつかないって? 下記のような疑問を抱く初学者は多くいるだろう。 「def __init__(self): の__init__のアンダースコアは何故二つなのか?」
 「def _function(x): と def function(x): と def function_(x): に違いはあるのか?」 
「y, _ = _function(x) のアンダースコアは何か?」 このようにわかりそうでわからないアンダースコアの使い方を、紐解いていこう。 そもそもアンダースコアはいつ使われるのか? 戻り値を無視する。 関数の名付けで使い方を区別する。 数字や文字を読みやすくする。(スネークケース) インタプリタで最後に表示された値を代表する。 以上4種類を意識しアンダースコアを使いこなす事により、読みやすいpythonicなコードを書くことができる。 1、Return値を無視 x, _, z = (1, 2, 3) # x=1, z=3 # * 2つの戻り値を返す関数 def func_return_2(): return 'X', 'Y' x, _ = funct_return_2() # x='X' # * enumerateはイテレータ(リスト等)の要素とIndexを返す関数 numbers = ["りんご", "ぶどう", "バナナ"] for idx, _ in enumerate(numbers): print(idx) """結果 0 1 2 """ これが一番多いアンダースコアの使い方だ。 Pythonはライブラリが沢山あって、関数をインポートして使う事が多い。 そういう時、もし関数からのreturn値が複数あって使わない部分があった場合、 アンダースコアを使ってreturn値のメモリの占用をしないまま廃棄ができる。 これを意識するだけで処理速度が速くなるかも?? 2、関数の名付けで使い方を区別 関数の名付けで使うアンダースコアは、アンダースコアの付ける位置と数で関数の使い方を4種類に区別します。 それぞれPEP8のコーディング規約で定義されている。 Ⅰ、_function(x): #関数前に一つ def _single_leading_underscore(x): return something # weak "internal use" indicator. E.g. from M import * does not # import objects whose names start with an underscore 関数前に一つアンダースコアを付ける事により、関数を”内部用”に定義できる。 他のプログラミング言語のPrivate属性と似たような物ですが、 Pythonには事実上のPrivate属性が存在しない。 PEP8の説明ではweak internal useと説明されていて、 from M import * の時では,一つのアンダースコアで始まる関数はインポートされませんが、class内の関数だとclassx._func()で関数を呼ぶ事が出来ます。 Ⅱ、function_(x): #関数後に一つ def single_trailing_underscore_: return something # used by convention to avoid conflicts with Python keyword, e.g. # Tkinter.Toplevel(master, class_='ClassName') 関数後に一つアンダースコアを付けるのはPython内の重要関数と名付けを被らせない為だ。 例えばclass内でどうしてもlistと言う関数や引数を設定したい時、 list_ と名付けてPythonのlistと被ることを避ける。 Ⅲ、__function(x): #関数前に二つ def __double_leading_underscore: return something # when naming a class attribute, invokes name mangling # (inside class FooBar, __boo becomes _FooBar__boo; see below). class内の関数前に二つアンダースコア付けることで、名前の マングリング機構を呼び出す。 ここのマングリングとは、インタプリタやコンパイラーが普通の方法で変数を扱わなくなる事です。 例えばclass FooBarの関数_booについて説明すると、Foobar.__barでは関数を呼べ出せなく、_Foobar_booという使い方になる。擬似的にPrivate属性を作れます。 Ⅳ、function(x): #関数前後に二つずつ def __double_leading_and_trailing_underscore__: return something # "magic" objects or attributes that live in user-controlled # namespaces. E.g. __init__, __import__ or __file__. Never invent # such names; only use them as documented. class内の関数の前後に二つずつアンダースコア付けることで、magic methodになります。 _init_や_call_、_str_、_iter_等既存のmagic methodがあってクラスを華やかに書けるが、普段の開発では自分で新しくマジックメソッドを定義しないことをお勧めする。 3、数字を読みやすく >>> 1000000 Out: 1000000 >>> 1_000_000 Out: 1000000 Python 3.6以降では、数字を読みやすくする様にアンダースコアを数字内に加える事が出来る。 一般で使われてる3桁ごとにカンマを実現可能。 結論 アンダースコアは使いこなすと凄く見える! 実は他にも使い方がありますが、あまりにも使わないので今回は割愛しました。 ちなみに _ はアンダーバーやアンダーラインとか呼ばれていますが、英語ではアンダースコア(underscore)が正しい読み方です。 __の様にアンダースコアが二回連続で使われてるとdunders (double underscore)と言うかっこいい名前がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSLambdaとAWSBatchを本番環境と開発環境で分ける

AWS BatchのジョブをAWS Lambdaから実行する時に、本番環境とステージング環境を分けたい場合の構成を作ってみました。 AWSBatch コンピューティング環境、ジョブキュー、ジョブ定義を環境別に分けます。 AWSLambda AWSLambdaで環境別の実行環境を作るためにエイリアスを利用しました。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-aliases.html Lambdaはエイリアス別に環境変数を定義することができません。(2021.07.03現在) なので、環境別のAWSBatchの定義を自前で用意します。 config.ini [dev] JOB_QUEUE = arn:aws:batch:ap-northeast-1:xxxxxxxxx-dev JOB_DEFINITION = arn:aws:batch:ap-northeast-1:xxxxxxxxx:job-definition/xxxx-dev:1 [stg] JOB_QUEUE = arn:aws:batch:ap-northeast-1:xxxxxxxxx-stg JOB_DEFINITION = arn:aws:batch:ap-northeast-1:xxxxxxxxx:job-definition/xxxx-stg:1 [prd] JOB_QUEUE = arn:aws:batch:ap-northeast-1:xxxxxxxxx-prd JOB_DEFINITION = arn:aws:batch:ap-northeast-1:xxxxxxxxx:job-definition/xxxx-prd:1 Lambda実行側ではconfig.iniを読み取ってジョブ定義を取得します。 どのエイリアスが使用されているかはcontext引数のinvoked_function_arnからわかります。 ※Lambdaの記述例 lambda_function.py import re import configparser import boto3 def lambda_handler(event, context):     mo = re.search(         r'func_name:(?P<alias>dev|stg|prd)',         context.invoked_function_arn     )     config = configparser.ConfigParser()     config.read('config.ini')     JOB_QUEUE = config[mo.group('alias')]['JOB_QUEUE']     JOB_DEFINITION = config[mo.group('alias')]['JOB_DEFINITION']     JOB_NAME = 'xxxxxxxxx'     client = boto3.client('batch')     response = client.submit_job( jobName = JOB_NAME, jobQueue = JOB_QUEUE, jobDefinition = JOB_DEFINITION,     )     return {     'statusCode': 200,         'body': json.dumps('run')     } さいごに AWSLambdaのエイリアス別に環境変数を定義できれば、わざわざconfig.iniを追加する必要ないのにと思いました。 もっと簡単な方法で環境別の構成が作っている方がいたら教えて下さい。 参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

reviewdogによるコードレビューの省力化

些細な指摘に時間を取られないようにreviewdogを導入してみました。 弊社のプランではGitHub Actionsが利用できないためJenkinsでの導入です。 導入の背景 ここ数ヶ月でチームの開発スピードが上がり、PRの数が増えてきました。 PRが増えるということはそれだけコードレビューに割く時間も増えることになります。 そうなってくると些細な指摘をするのが面倒になってきます。 def func(): ... if condition: return result else: return None 「これ、elseいらないけど。でもわざわざ指摘するほどでもないかな、、」 こういうのは事前にlintして潰しておいてほしいと思いつつも、すべてのPRがlinterの指摘を修正しているとは保証できません。 うっかりlintを忘れることもある。 じゃあ単純にCIでlintすればいいかと言うとそれも難しい。 なぜなら修正範囲外の古いコードまで指摘を受けて、どこがPRに関係する指摘なのかわからなくなるから。 そこでreviewdogの出番です。 このツールはlinterの指摘を変更のあった行だけに絞ってくれます。 さらににGitHubにコメントしてくれるので指摘を確認しやすいというオマケ付き。 早速設定していきます。 reviewdogのインストール 今回はdocker上で動くJenkinsへインストールします。 Dockerfile WORKDIR /tmp RUN curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s \ && mv /tmp/bin/reviewdog /usr/local/bin \ && rm -rf /tmp/bin GitHubのトークン発行 PRにコメントを残してもらうためにPersonal Access Tokenを発行します。 プライベートリポジトリなのでrepoにチェックをつけます。それ以外の権限は不要です。 Jenkinsの設定 予め上で発行したトークンを認証情報に追加しておきます。 reviewdogを動かすプロジェクトを作成してリポジトリをクローンします。 READMEによると4つの環境変数を設定するだけでGitHubにコメントしてくれるようです。 まず先程の認証情報をプロジェクトの設定でREVIEWDOG_GITHUB_API_TOKENという変数に紐付けます。 続いてプロジェクトの実行コマンドで残りの環境変数を設定します。 export CI_REPO_OWNER=xxx export CI_REPO_NAME=yyy export CI_COMMIT=$(git rev-parse HEAD) 最後にpylintの結果をreviewdogに食べさせます。 pip install pylint pylint src/ | reviewdog -efm="%f:%l:%c: %m" -reporter=github-pr-review -diff="git diff origin/release" あとはPRに反応してJenkinsプロジェクトを実行する設定ですが、これは省略。 GitHub Pull Request BuilderやGeneric Webhook Triggerを使って実現できます。 ここは一旦手動で動かします。 動かしてみる 冒頭の不要なelseを含むPRを出してみると、 できたー? まとめ Jenkinsでreviewdogを導入しているケースが少ないので調べるのに時間がかかりましたが、設定自体は簡単でした。 実は執筆時点で数ヶ月運用しているのですが、開発者以上にレビューしてくれることもあり助かっています。 現在はpylintのみ実行していますが、任意のlinterで使用可能なので今後mypyなどにも活用できたらと考えています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonに関する自分用メモ

○画面サイズを取得するには,pipでインストールが必要。 anaconda promptで pip install screeninfo ○pyinstallerでアイコンを変更する方法 --icon==**.ico を加えることで、アイコンを設定。 exe化する .py と同一ディレクトリに **.ico ファイルを置いておくと簡単。 ○画像をフォルダから選択して表示 import tkinter.filedialog as fd import cv2 global file_path,img1,winName file_path = fd.askopenfilename( title = "画像を選択" ) img1 = cv2.imread(file_path) winName = 'image' cv2.imshow(winName, img1) cv2.waitKey(0)  ←即時終了防止 cv2.destroyAllWindows() ○画像オープンエラー ファイル名が日本語だと、エラーになることがある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がPythonで5分足チャートからPyti使ってMACDを計算してグラフに表示してみた

これのpyti版です。 コード import pandas as pd import datetime import mplfinance as mpf from pyti.moving_average_convergence_divergence import moving_average_convergence_divergence as macd_func df = pd.read_csv('./temp_historical_data/USDJPY.csv', nrows=500) df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) # 5分足再計算 df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() # MACD計算(本題) macd = pd.DataFrame() macd['Close'] = df5['Close'] data = macd['Close'].values.tolist() macd['MACD'] = macd_func(data, 12, 26) macd['Signal'] = macd['MACD'].ewm(span=9).mean() macdplot = mpf.make_addplot(macd[['MACD', 'Signal']]) mpf.plot(df5, type='candle', addplot=[macdplot], datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_macd.png',dpi=100)) 所感 残念ながらシグナルは計算してくれないのでそちらは従来通りの計算方法。pandasだけで使って計算するより2行くらい短くなった(白目)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python exe化

こちらを参考に https://techacademy.jp/magazine/18963 anaconda prompt を起動し pip install pyinstaller を実行する。 最後に警告がでる WARNING: You are using pip version 21.1.2; however, version 21.1.3 is available. You should consider upgrading via the 'C:\Users\rie-s\anaconda3\python.exe -m pip install --upgrade pip' command. とあるので,書いてある通り C:\Users\rie-s\anaconda3\python.exe -m pip install --upgrade pip のコマンドを入力すると,なんかめっちゃエラーでる ERROR: Exception: Traceback (most recent call last): File "C:\Users\rie-s\AppData\Roaming\Python\Python38\site-packages\pip_internal\cli\base_command.py", line 180, in _main status = self.run(options, args) pipのアップデートしてみる pip install -U pip 同じエラーがでる。 spyder を終了してやってみる。→変わらずエラー 警告だったので,とりあえずexe化を試してみる。 pyinstaller MeasureJudge.py 結構時間かかる。5分くらい? buildとdistというフォルダが作成される。 dist\MeasureJudge.exe ファイルが作成されている。 pyinstaller MeasureJudge.py --onefile オプション--onefileで単体EXEを作成 いらないライブラリは--exclude-module panda で除ける。 pyinstaller --onefile --exclude-module numpy --exclude-module pandas example.py コンソール非表示 --noconsole
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がPythonで5分足チャートからボリンジャーバンドを計算してグラフに表示してみた

pyti(パイタイ)というライブラリを利用するのでpipでインストールしておく。 参考 下のコードとほぼ一緒です。 コード import pandas as pd import datetime import mplfinance as mpf from pyti.bollinger_bands import upper_bollinger_band as bb_up from pyti.bollinger_bands import middle_bollinger_band as bb_mid from pyti.bollinger_bands import lower_bollinger_band as bb_low df = pd.read_csv('./temp_historical_data/USDJPY.csv', nrows=500) df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) # 5分足に変換 df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() # ボリンジャーバンド計算 bb = pd.DataFrame() data = df5['Close'].values.tolist() period = 20 bb['Close'] = df5['Close'] bb['Up'] = bb_up(data, period) bb['Mid'] = bb_mid(data, period) bb['Low'] = bb_low(data, period) bbplot = mpf.make_addplot(bb[['Close', 'Up', 'Mid', 'Low']]) mpf.plot(df5, type='candle', addplot=[bbplot], datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_bb.png',dpi=100)) 出来上がったグラフ 説明 ptytiで簡単。pytiはMACDの計算もできる。以下の通り。 from pyti.moving_average_convergence_divergence import moving_average_convergence_divergence as macd_func macd = pd.DataFrame() macd['Close'] = df5['Close'] data = macd['Close'].values.tolist() macd['MACD'] = macd_func(data, 12, 26) macd['Signal'] = macd['MACD'].ewm(span=9).mean() macdplot = mpf.make_addplot(macd[['MACD', 'Signal']])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CPythonのmain関数

こんにちは。今回は最も普及しているPythonの処理系CPythonのソースを読んでいるとmain関数を見つけたので、少しだけそのことを書こうと思います。 main関数とは C言語を書いたことがある人ならば誰でも知っているであろうmain関数。 これはプログラムで始めに実行される関数です。(厳密には違うらしい?) コマンドの引数などはここで受け取ります。 CPythonはC言語で書かれているのでmain関数があるのは当然なんですが、あれほど大きいプロジェクトでも基礎通りなんだな~と感心したのでこの記事を書こうと思いました。 main関数の実装 CPythonのレポジトリのPrograms/python.cにあります。 Programs/python.c /* Minimal main program -- everything is loaded from the library */ #include "Python.h" #ifdef MS_WINDOWS int wmain(int argc, wchar_t **argv) { return Py_Main(argc, argv); } #else int main(int argc, char **argv) { return Py_BytesMain(argc, argv); } #endif #ifdef MS_WINDOWSがあるので、Windowsはwmain関数でそれ以外は普通にmain関数が呼ばれるみたいです。 wmainとはワイド文字のコマンドライン引数に対応したmainらしいです。 どうしてこのような実装にしたのかはわからないです... wmainとmainはそれぞれPy_Main, Py_BytesMain関数を呼び出しているのですが、その内部では両方がコマンドライン引数の情報を_PyArgv構造体に入れてpymain_main関数を実行していました。興味がある方は処理を追ってみてください。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cx_Freezeを使ってEXE化

1.anaconda promptでcx_Freezeをインストール pip install cx_Freeze --upgrade https://tomomai.com/python_cx_freeze/ を参考。 2.setup.pyファイルを作る。アイコンをつけたい場合は,icoファイルを作成する。   以下,setup.pyファイル。 # coding: utf-8 # cx_Freeze 用セットアップファイル import sys from cx_Freeze import setup, Executable base = None # GUI=有効, CUI=無効 にする if sys.platform == 'win32' : base = 'Win32GUI' # exe にしたい python ファイルを指定 exe = Executable(script = 'MeasureJudge.py', base = base, icon='kabutomusi.ico') # セットアップ setup(name = 'Image_Judge', version = '0.1', description = 'Image_Judge', executables = [exe]) 3.setup.py と exe化したい Pythonファイル(MeasureJudge.py)を所定の同じフォルダに入れる。アイコンをつけたい場合は,同じフォルダに入れる。 4.anaconda promptでsetup.pyがあるフォルダまで移動して (移動は,cd コマンドを使う。場所確認は dir コマンド。) python setup.py build を実行する。新しくフォルダが作成され,exeファイルとlibフォルダが作成されている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ChromeとToggl とSlackを連携して、リモートワークを快適にする(その1)

目標 Chrome上で管理している業務内容を、時間管理ツール「Toggl」で計測する Togglとビジネスチャットツール「Slack」を連携して、現時点の業務内容を共有する 背景 今年6月に株式会社RevCommに入社いたしました。 弊社RevCommは、フルリモートフルフレックス勤務体制というとても働きやすい環境となっています。 ただ、今自分がどんな作業を抱えているか、とか、どんな作業をしているか、というのが職場内の雰囲気でなんとなく伝わる感じではないので、自分から作業内容を明確にしていこうと思いました。 弊社では、GithubやGoogleカレンダーなど、基本的にはChrome上で業務内容を管理しています。 どうせならどのくらい作業に時間がかかっているかも実績として残していきたいと思ったので、Toggl+Chromeプラグインで、各タスクの入力を簡単に行なっていくことにしました。 そして、Togglの計測状況を随時Slackに流していくことで、自動的に「今、自分がどのような業務を行なっているか」を周知できるようにしてみようと思いました。 作業概要 ChromeにGoogleアカウントでログインする TogglにGoogleアカウントでログインする ChromeにTogglプラグインを入れる Togglプラグインの設定を行う Toggl API トークンを取得する Slack Webhook URL を取得する Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する 7.を実行する shellスクリプトを作成する launchd で 8. を定期的に自動実行する 手順詳細 1. ChromeにGoogleアカウントでログインする Chrome を入手して、Googleアカウントでログインする 2. TogglにGoogleアカウントでログインする 「Toggl」 の右上ページから、「Log in」 >  「Toogl track」(真ん中) > 「Login via Google」 でログインする 3. ChromeにTogglプラグインを入れる Chromeウェブストアから「Toggl Track: Productivity & Time Tracker」を導入する 4. Togglプラグインの設定を行う 拡張機能から先ほど導入したTogglプラグインの設定画面を開く 「Integrations」でタスクがあるサイトにチェックを入れていく Chromeを再起動すると、連携したサイトのタスク粒度ページに「Start Timer」のボタンが追加されている。 (例は、Githubのissueページ) 上記ボタンをクリックすると、Togglでタスクの計測が開始される プロジェクト、タグも登録しておくと、後でレポートの時に集計しやすくなる 5. Toggl API トークンを取得する 「Toggl Trackのプロフィールページ」の下部にある「API token」の欄で、「--Click to reveal--」をクリックすると、トークン値が表示されるので、コピーする 6. Slack Webhook URL を取得する Slackの公式手順に従って、Slack Webhook URL を取得する 次の画面でWebhook URLが表示されるので、コピーして保存。 ※投稿するチャネルごとにURLの取得が必要。意図しないチャネルへのURLを使わないよう注意! 7. Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する 投稿にはPythonを使います。 7.1. 必要ライブラリ pip install requests 7.2. 自動投稿スクリプト スクリプトの仕様 現在Togglでタスクが計測されていたら、そのタスクをSlackに投稿する Slackに投稿したタスクは前回タスクとして history.txt に保存する 次回スクリプトを起動した際 history.txtと同じタスクの場合 … Slackに投稿しない history.txtと違うタスクの場合 … Slackに投稿して、history.txtを上書きする タスクが計測されていない場合 … 日付が変わっていなければ、history.txtのタスクの終了をSlackに投稿する toggl2slack.py # -*- coding: utf-8 -*- import requests import json import datetime import os # Slack hooks SLACK_HOOKS_URL = '<6で取得したSlack Webhook URL>' TOGGL_API_TOKEN = '<5で取得したToggle API token>' file_name = './history.txt' def get_toggl(): headers = {'content-type': 'application/json'} auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token') current = requests.get('https://track.toggl.com/api/v8/time_entries/current', auth=auth, headers=headers) if current.status_code != 200: print("togglの情報取得に失敗しました") return None, [], None current_json = current.json() if not current_json['data']: return None, [], None print(f'{current_json["data"]}') description = current_json['data']['description'] # この時点でプロジェクト名をとってきておく pname = None if 'pid' in current_json['data']: pid = current_json['data']['pid'] pname = get_project_name(pid) print(f'pid: {pid}, pname: {pname}, description: {description}') return pname, current_json['data']['tags'], description def get_project_name(pid): headers = {'content-type': 'application/json'} auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token') current = requests.get(f'https://track.toggl.com/api/v8/projects/{pid}', auth=auth, headers=headers) if current.status_code != 200: print("togglの情報取得に失敗しました") return None current_json = current.json() if not current_json['data']: return None return current_json['data']['name'] def check_old_toggl(now_task): prev_task = None with open(file_name, mode='r', encoding='utf-8') as f: history = f.read() prev_task = history.strip() write_history(now_task) return (prev_task == now_task), prev_task def write_history(str_history): with open(file_name, mode='w', encoding='utf-8') as fw: fw.write(str_history) def write_slack(title, description): text = f"""【{title}】 `{description}` """ payload = {'username': 'Toggl', 'text': text, 'icon_emoji': ':clock10:'} requests.post(SLACK_HOOKS_URL, data=json.dumps(payload)) if __name__ == '__main__': now = datetime.datetime.now() now_str = now.strftime('%Y/%m/%d %H:%M:%S') # 現在のタスクの取得 pname, tags, description = get_toggl() now_task = f'{description} ({pname}:{", ".join(tags)})' is_same, prev_task = check_old_toggl(now_task) if not pname and not description: print(f'[{now_str}] not pname+description') stat = os.stat(file_name) mdt = datetime.datetime.fromtimestamp(os.stat(file_name).st_mtime) if now.day != mdt.day: # 日付が変わっていたら、問答無用で終了 write_history('') # ログクリア os.remove('./out.log') exit(1) elif prev_task and not description and 'None' not in prev_task: # slackへの書き込み(次が始まってない時だけ) print(f'[{now_str}] write end') write_slack("作業終了", prev_task) # 終了して次が始まってない場合 write_history('') exit(1) if not pname: # プロジェクト名が入ってない場合、スルー exit(1) if is_same: print(f'[{now_str}] same') # 前回取得したものと同じ場合は終了 exit(1) if description: print(f'[{now_str}] write start') # slackへの書き込み write_slack("作業開始", now_task) 8. 7.を実行する shellスクリプトを作成する 8.1. shellスクリプト 私はAnacondaを導入したので、conda activate も内部で行なっています。 toggl2slack.sh # >>> conda initialize >>> # !! Contents within this block are managed by 'conda init' !! __conda_setup="$('/Users/***/opt/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)" if [ $? -eq 0 ]; then eval "$__conda_setup" else if [ -f "/Users/***/opt/anaconda3/etc/profile.d/conda.sh" ]; then . "/Users/***/opt/anaconda3/etc/profile.d/conda.sh" else export PATH="/Users/***/opt/anaconda3/bin:$PATH" fi fi unset __conda_setup # <<< conda initialize <<< cd <toggl2slack.pyへのフルパス> && conda activate bot && python toggl2slack.py 8.2. shellの権限変更 シェルに実行権限を付与する chmod 744 toggl2slack.sh 9. launchd で 8. を定期的に自動実行する 9.1. 設定ファイル ~/Library/LaunchAgents ディレクトリ以下に toggl2slack.plist を配置する 5分おきに 8. で作成した toggl2slack.sh を実行する toggl2slack.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>toggl2slack</string> <key>ProgramArguments</key> <array> <string>sh</string> <string>/Users/***/work/bot/toggl2slack/toggl2slack.sh</string> </array> <key>StartInterval</key> <integer>300</integer> <key>StandardOutPath</key> <string>/Users/***/work/bot/toggl2slack/out.log</string> <key>StandardErrorPath</key> <string>/Users/※**/work/bot/toggl2slack/error.log</string> </dict> </plist> Slackへの投稿イメージ Togglで計測を開始すれば、自動的にSlackに作業状況が通知されます。 次回予告 せっかくTogglで時間を計測できるようにしたのですから、レポートも一括で取ってこれるようにしちゃいましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たけびしDxpSERVER向けOPC-UAクライアントを書いてみた

たけびしDxpSERVERのOPC-UAクライアントのサンプルを作ることになったので、手順を記録しておく。 1. 使用環境 Windows10 たけびしデバイスエクスプローラ OPCサーバー バージョン6 (DxpSERVER) デモ版が上記サイトからダウンロード可。OPC-UAも利用可。 2. OPC-UAサーバ設定 2-1. 共通プロパティ ツール(T)>オプション(O)で、共通プロパティタブを表示する。 OPC UAサーバー設定を有効にする。アプリケーションを再起動するように促されるので、再起動する。 2-2. UAサーバー設定 共通プロパティタブのOPC UAサーバー設定の右側に...が表示されているところをクリックすると、UAサーバー設定のダイアログが表示される。 UAサーバー使用 Anonymous接続の許可 セキュリティポリシー/セキュリティモード:None にチェックが入っていること。 2-3. タグの追加 プロジェクトエクスプローラからSYSTEMポートを選択して、タグを追加する。 タグの追加は プロジェクト(P)>新規作成(N)>タグ(T) で行う。 ここでは、以下の3つのタグを作っておく。 No. タグ名 データ型 サイズ 1 value_string STRING 32 2 value_long Long 1 3 value_float Float 1 注意事項: STRINGのサイズはデフォルトで1なので、そのままでは1文字しか保持できない 一度タグを設定してからサイズだけ変更すると、後続のタグのアドレス位置が移動いないので、領域を上書きされる場合がある(これでハマった) 2-4. プロジェクトの保存 ファイル(F)>保存(S)でプロジェクトを保存する。ここでは OPCUA_sampleで保存する。 3. OPC-UAクライアント 3-1. OPC-UAサーバーの設定 DxpSERVER v6のデフォルト設定は以下の通り。 No. 項目名 デフォルト値 内容 1 HOST - DxpSERVERのIPアドレス文字列。localhostも可。 2 PORT 52240 ポート番号 3 PATH (空文字列) end_pointのパス 4 URI urn:Takebishi:DxpOpcUaServer:customprovider 名前空間のURI 上記の設定値から、OPC-UAのアクセス先を示すend_pointは以下の文字列で設定する。 end_point = f'opc.tcp://{HOST}:{PORT}{PATH}' 3-2. パッケージのインストール asyncuaパッケージをインストールしておく。 $ pip install asyncua 3-3. サンプルプログラム 以下に示す。 dxp6_client.py import asyncio from asyncua import Client, ua # for v5 # URI = 'http://takebishi.co.jp/DeviceXPlorer5/UA' # PORT = 52210 # for v6 URI = 'urn:Takebishi:DxpOpcUaServer:customprovider' port = 52240 # local machine HOST = 'localhost' PATH = '' OBJ = 'SYSTEM' # オブジェクト名はプロジェクト直下のポート名と対応 async def main(): end_point = f'opc.tcp://{HOST}:{PORT}{PATH}' try: async with Client(url=end_point) as client: """ # URI一覧を表示 print('[URI List]') for uri in await client.get_namespace_array(): print(uri) print() """ # ノードのread/write idx = await client.get_namespace_index(URI) node1 = client.get_node(f'ns={idx};s={OBJ}.value_string') node2 = client.get_node(f'ns={idx};s={OBJ}.value_long') node3 = client.get_node(f'ns={idx};s={OBJ}.value_float') print('[Read]') print(node1.nodeid, ':', await node1.read_value()) print(node2.nodeid, ':', await node2.read_value()) print(node3.nodeid, ':', await node3.read_value()) print() # DxpSERVERだとこの形式が未サポート # await node1.write_value('HELLO') # Write # DataValueでset_valueを使用する print('[Write]') dv1 = ua.DataValue(ua.Variant(u'HELLO', ua.VariantType.String)) await node1.set_value(dv1) dv2 = ua.DataValue(ua.Variant(123, ua.VariantType.Int32)) await node2.set_value(dv2) dv3 = ua.DataValue(ua.Variant(9.8, ua.VariantType.Float)) await node3.set_value(dv3) print() print('[Read]') print(node1.nodeid, ':', await node1.read_value()) print(node2.nodeid, ':', await node2.read_value()) print(node3.nodeid, ':', await node3.read_value()) print() except Exception as e: print(e) exit() if __name__ == '__main__': asyncio.run(main()) read/writeについては、ここで示した方法で動作することを確認した。 3-4. コードについて補足 コメントにも書いているので、察した方もいらっしゃるかも知れませんが補足しておきます。 URIのリストはclient.get_namespace_array()で取得できる node.write_value()で書き込みができたOPC-UAサーバーもあるが、DxpSERVERではDataValueをnode.set_value()で設定する方法しか確認できなかった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ADX2 別プロセスで原音再生

はじめに ADX2スクリプトで原音再生の改良版の紹介。 環境:CRI AtomCraft 3.45.00 スクリプトの流れ UI系は外部プロセスにしないとCraftごと落ちてしまうことがわかっていて、 バッチみたいな処理にしか利用できていないのが現状なのだけど、 情報収集(選択キューのマテリアルを探し元ファイルパスを得る) 解析  (今回はとくになし) 実行  (音を再生する) という流れの、 情報収集だけCraft上で動作させて、 解析、実行を別プロセスにしておくと、 Craftを解放できるはず 別プロセスのメリット 中断ができる(プロセスを止めることができる) 非同期実行にできる(重い解析とかでCraftを待たせなくすることができる) Craft上でのスクリプトの実行の制限から解放される UIが表示できる(解析結果表示など) スクリプト分割時の更新など(Craftは一度importしてしまうとCraft再起動するまで外部ファイルの再読み込みをしないため) 別プロセスのデメリット ファイルが増える デバッグや、結果の収集、インタラクティブなやりとり(プロセス間通信)に少し工夫が必要になる。 (Craftはサーバー機能でもスクリプト動かせるので駆使すればできなくはないが) 改善点 長い音の再生をキャンセルできない問題 前回の再生の場合、同じプロセスで再生をしてしまうため、 曲のような長い音を再生した場合、再生が終わるまで待たないといけない状態でした。 そこで、再生処理を別プロセスに投げてしまうことで、キャンセルできるようにします。 キューを選択し、PlayWav.pyを実行すると マテリアルのフルパスを得ることができ、 subprocessを利用して Z_ExtAudioPlay.pyを実行します。 引数の波形を再生します。 また、再生しているウィンドウを閉じることで、中断することができます。 プロセスを起動するスクリプト PlayWav.py # --Description:[tatmos][Preview]選択したオブジェクトの原音を再生 import subprocess from time import sleep import cri.atomcraft.project as acproject import cri.atomcraft.debug as acdebug #-------- #選択中のマテリアルまたはウェーブフォームリージョンまたはキューを得る selectedMaterials = acproject.get_selected_objects("Material")["data"] selectedWaveformRegions = acproject.get_selected_objects("WaveformRegion")["data"] selectedCues = acproject.get_selected_objects("Cue")["data"] selectedMaterial = None if len(selectedMaterials) > 0: selectedMaterial = selectedMaterials[0] if len(selectedCues) > 0: selectedWaveformRegions = acproject.find_objects(selectedCues[0],"WaveformRegion")["data"] if len(selectedWaveformRegions) > 0: selectedMaterial = acproject.get_value(selectedWaveformRegions[0], "LinkMaterial")["data"] if not selectedMaterial: acdebug.warning("再生するマテリアルまたはウェーブフォームリージョンまたはキューを選択してください。") sys.exit() print(acproject.get_value(selectedMaterial, "Name")["data"]) input_path = acproject.get_value(selectedMaterial, "SrcFileAbsolutePath")["data"] if not input_path: acdebug.warning("マテリアルのパスがみつかりません。") sys.exit() print("Path:" + str(input_path)) #別プロセスで再生 command = ["python","C:/MyDearest/github/ADX2Robot/local/Z_ExtAudioPlay.py",input_path] proc = subprocess.Popen(command) proc.communicate() 再生をするスクリプト Z_ExtAudioPlay.py import pyaudio import wave import time import sys args = sys.argv input_path = args[1] p = pyaudio.PyAudio() #-------- #AUDIOのDEVICEを列挙 #hostAPICount = p.get_host_api_count() #for cnt in range(hostAPICount): # print(a.get_host_api_info_by_index(cnt)) #音を鳴らすDEVICEを指定 DEVICE_INDEX = 0 #-------- #ファイルを開く wf = wave.open(input_path, "rb") def callback(in_data, frame_count, time_info, status): data = wf.readframes(frame_count) return (data, pyaudio.paContinue) #ストリームの作成 stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), input_device_index = DEVICE_INDEX, output=True, stream_callback=callback) #再生 stream.start_stream() print("再生中 ... " + input_path) #再生終わりまで待つ while stream.is_active(): time.sleep(0.1) print("done.") stream.stop_stream() stream.close() p.terminate() wf.close()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python 画像処理 No module

エラー1 No module named 'skimage' 解決1 pip install scikit-image エラー2 No module named 'imutils' 解決2 pip install imutils
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がPythonで5分足チャートからMACDを計算してグラフに表示してみた

参考 MACDの計算部分以外は以下とほぼ一緒! コード import pandas as pd import datetime import mplfinance as mpf df = pd.read_csv('./temp_historical_data/USDJPY.csv', nrows=500) df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() macd = pd.DataFrame() macd['Close'] = df5['Close'] macd['EMA12'] = df5['Close'].ewm(span=12).mean() macd['EMA26'] = df5['Close'].ewm(span=26).mean() macd['MACD'] = macd['EMA12'] - macd['EMA26'] macd['Signal'] = macd['MACD'].ewm(span=9).mean() macd.head() macdplot = mpf.make_addplot(macd[['MACD', 'Signal']]) mpf.plot(df5, type='candle', addplot=[macdplot], datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_macd.png',dpi=100)) グラフ 説明 移動平均線と同時に表示したい場合は以下のようにする sma = pd.DataFrame() sma["SMA5"] = df5["Close"].rolling(window=5).mean() sma["SMA25"] = df5["Close"].rolling(window=25).mean() # 中略 smaplot = mpf.make_addplot(sma[['SMA5', 'SMA25']]) mpf.plot(df5, type='candle', addplot=[smaplot, macdplot], datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_macd.png',dpi=100)) 一緒に表示したグラフは以下。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像生成の話 -フォトリアリスティックなGIRAFFEを読んでみる-

最近の3D分野の技術の一部をざっと読んでいきます。 ・・・のつもりで書こうと思ったら、画像生成の分野の話でした。 CVPR2021のBestPaperに選ばれたという「GIRAFFE」を中心に、 「NERF」「GRAF」「GAN」と読んでみます。読むだけで組みません。 式の意味とか掘り下げていないので、どんなことできるか程度に読みます。 NERF git -> https://github.com/bmild/nerf paper -> https://arxiv.org/pdf/2003.08934.pdf 一言で言うなら、 たくさんの画像から↓みたいなの出力するぜ!!という技術。 概要 入力として 3D位置(x,y,z)、撮影者から見たときの2次元の角度情報を与えて、 出力として RGBの色情報とvolume densityという密度を出力します。 さて、ここで言う密度とは、イメージ的には空間的な広さを表現した数値です。 例えば、夏の青空に照らされた景色をみると遠くのものほど、物体が青っぽくなります。 画像的に言えば、物体の不透明度が下がり、背景の青が徐々に強くなっていく状態です。 この状態を再現することで空間的な広さを演出する方法をボリュームレンダリングと呼び、 ここで出力された密度はボリュームレンダリングに利用されます。 そして最終的な出力としては、ボリュームレンダリングにより、 「それっぽさ」がました画像が作成されます。 もう少し読む 入力として与えられる画像情報はスパースな情報です。 例えば図の(a)と(b)のように、撮影されたシーンの間は連続していません。 サンプルの学習データを見ると、系20個程度のデータしか有りません。 上の方の動画のような結果を得るには、間のシーンの補完が必要です。 ここでもう一度図を見ます。(上) 画像上では同じ位置・同じ箇所を示していながら、光の影響で刻一刻と色味が変わります。 画像間の間のシーンを補完しようと思ったら、各ポイントの変化分を推定しないといけません。 なので、微小変化分を積分でガチャッと合わせてやることで色を推定します。(5.1の内容) 各ポイントに対して上記のような処理を行うわけですが、 画像が重ならないところとか遮蔽物に隠れて見えないところは使わないほうがよくね? という考えのもと、全てのポイントではなく、そのうちのいくつかを「粗く」サンプリングして、 処理をかけているらしいです。 そのネットワークを司るのが、$C_c$という粗いネットワーク。 そして更に、「細かい」ネットワークも作成します。 「細かい」ネットワークは、$C_c$の結果と、別のサンプリング数で実行した結果も加えるらしいです。 そんでもって、こいつらのネットワークが出力した結果(色情報)と 正解の結果との誤差の二乗が最小化されるように最適化することで学習を行います。 アルゴリズムとしてはAdamを使っているようです。 GIRAFFE git -> https://github.com/autonomousvision/giraffe paper -> http://www.cvlibs.net/publications/Niemeyer2021CVPR.pdf 一言で言うなら、 自動でリアルな画像を生成するぜ!しかも位置とか背景とかも自在に出力するし、物体の位置も自由自在! 物体そのものを変化させることもできるぜ!!!という技術。すげー。 概要 フォトリアリスティック。photorealistic。 最近の映画は3Dがあまりに進化しすぎて、現実特別つかない域にまで到達してます。 でもこういうのって、ものすごいお金かかるんですよね。 近頃はGAN (参考:https://innolab.jp/work/gan/5200 ) を使って フォトリアリスティックな画像を自動生成できたりして、 フォトリアリスティックの業界に少しずつ変化が訪れてきています。 本稿では、画像にうつる各物体のスケール・移動・回転を自在にコントロールし、 加えて、形状・外観も変化させながらシーンに移る物体を自在にコントロールします。 また、従来技術では一枚のシーン全体を変化させますが(NERFとか)、 本技術では、シーンのうち特定の物体だけを動かすなどの制御が可能です。 物体 Neural Radiance Fields(NERF) NERFの論文の5.1を読み直します。 曰く、低次元の三次元座標$x$と、視線方向を司る二次元情報$d$を入力としてそのまま使うと、 最終的な出力は高周波(?)の情報が無視されてしまい、一部情報が失われてしまうようです。 この問題に対して、位置と視線の五次元の情報をそのまま使わず、 変換(位置エンコード)して使うことtで、より高次元の特徴の入力として取り扱うらしい。 NERFは最終的に上述の「色(RGB)」と「密度」を出力し、出力関数は下記のように表されます。 $f$は多層パーセプトロンて表される関数です。$f_\theta$でいうところの$\theta$とは、 多層パーセプトロンの重みとかバイアスとかのパラメータのことを指しています。 GIRAFFEではNERFを導入して、入力情報として位置・視線方向を扱います。 Generative Neural Feature Fields:(GRAF) https://arxiv.org/pdf/2007.02442.pdf GENFの論文のP5のところを読んでみます。 NERFでは、位置と視線方向を入力としてあたえることで、画像の位置と視線を自在にコントロールしました。 GRAFでは、これに形状・外観を追加で入力することで、形状・外観もコントロールします。 これがNERFの出力関数(MLP)。 入力として位置と視線方向の情報を入れいています。 そしてGENFでは、形状の情報$z_s$、外観の情報$z_a$の情報を追加しています。 NERFでは、位置と視線方向をモデル化することで画像の位置視点をコントロールし、動画のような映像を出力しました。 GENFでは、形状と外観を追加したことで、形状と外観を操作できるようになりました。 ↑gitのリンク先ではこれが動画になっています。刻一刻と画像がかわっていくことがわかります。 このように、形状と外観を制御することが可能です。 さて、出力される色情報と密度のイメージは図のようになります。 密度$\sigma_{\theta}$は位置と外観情報を結合したものが出力され、 色情報$c_{\theta}$は位置と外観情報、そして視線方向の情報と形状から出力されます。 なおGRAFでは色情報$c_{\theta}$を上式のように表されますが、 本稿では、$R^{M_f}$として表現しています。色情報はGRAFの導入により、特徴$f$として表されます。 物体ごとの表現 GIRAFFEは物体ごとのモデルを想定しています。 具体的なイメージとしては、 上記画像のようにそれぞれの車とそれぞれの背景が別々に入力/出力されます。 この構造により、図の赤い車のように赤い車だけ回転させるなどの制御を可能としています。 NERF/GRAFでは、 画像全体を対象にしているためシーン全体に対してしか操作できませんでした。 GIRAFFEでは各物体をそれぞれ制御することで、全体のシーンを自在に制御できるようになりました。 加えて、GIRAFFEでは物体のスケール、移動、回転を行うことが可能です。 なので、GRAFにおける入力としては、 これが、スケール、移動、回転によるアフィン変換が行われるので このような入力になります。 シーン構成 シーンが構成される形成過程としては 様々ある物体をそれぞれアフィン変換で変形させながら位置・視線方向・形状・外観を作成したあと、 それぞれの物体を1つのシーンに統合します。 合成演算子 (8)式の通り。N個のモデルを足して平均を求めます。 これが、最終的に一枚のシーンに合計された出力結果となります。 この出力結果には、各物体のモデルの位置形状その他諸々を反映された結果が収納されています。 シーン作成 3Dボリュームレンダリング ここまでの説明により、 各物体モデルの密度$\sigma$と色位置形状その他諸々の特徴を司る$f$を式で表しました。 そして、各物体は1つのシーンに統合されるため、 最終的な1つのシーンとして反映される$\sigma$と$f$は、合成演算子で表されました。 そしてシーン全体の特徴画像$f$は下式で表されます。 特徴画像は低解像度であえて出力するそうです。 2Dレンダリング 低解像度の画像をアップサンプリングしていきます。 $\pi^{neural}$により、最終的な出力画像$I \in R^{HxWx3}$を得ます。 $\pi^{neural}$は畳み込みニューラルネットワークです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XGBoost・LightGBM・CatBoostの比較検証

勾配ブースティングについて 勾配ブースティングは弱学習器が誤差に対して順にフィットしていく学習方法のことを言います。 つまり、一つの強力なモデルを用いるのではなく、微力なモデルを複数組み合わせて強力なモデルにします。 まるでスイミーのように。。。 勾配ブースティングを用いたXGBoostやLightGBM、CatBoostといった手法はかなり強力で kaggleでも人気の手法です。 [引用] LightGBMの解説 本記事について それぞれの特徴やアルゴリズムについては触れません。 本記事では3つの手法における学習時間と精度について検証してみようと思います。 ちなみにCatBoostのチュートリアルでも出てきた論文ではCatBoostに軍配が上がっておりましたが、こちらの論文ではLightGBMに軍配が上がっております。 検証に先立って 扱ったデータについて 今回用いるデータはkaggleのPredict Feature Salesです。 なお、扱ったデータは前処理、特徴量エンジニアリングなどを終えたもので、 実際のコンペでも扱ったデータになります。 扱うデータの概要を下記に記載しておきます。 レコード数 カラム数 train 7,593,847 30 valid 221,718 30 test 214,200 30 ハイパーパラメータおよび評価について 誤差関数はRMSEに設定し、他のハイパーパラメータはデフォルトにしてあります。 今回の検証は学習時間、train,validのスコア(小さいほど良い)で検証したいと思います。 XGBoostでの検証 import xgboost as xgb import time start = time.time() dtrain = xgb.DMatrix(X_train, label=y_train) dvalid = xgb.DMatrix(X_val, label=y_val) dtest = xgb.DMatrix(X_test) xgb_params = {'eval_metric': 'rmse'} evals = [(dtrain, 'train'), (dvalid, 'valid')] xgb_model = xgb.train(xgb_params, dtrain, evals=evals) print('elapsed_time:{}'.format(time.time()-start)) Time:51.72 Train:0.884 Valid:0.816 LightGBMでの検証 import lightgbm as lgb import time start = time.time() lgb_train = lgb.Dataset(X_train, y_train) lgb_val = lgb.Dataset(X_val, y_val, reference=lgb_train) params = {'metric' : 'rmse'} lgb_model = lgb.train(params=params, train_set=lgb_train, valid_sets=[lgb_train, lgb_val], valid_names=['Train', 'Valid']) print('elapsed_time:{}'.format(time.time()-start)) Time:13.50 Train:0.839 Valid:0.820 CatBoostでの検証 from catboost import CatBoost, Pool import time start = time.time() # 専用の型に変換 catb_train = Pool(X_train, label=y_train) catb_valid = Pool(X_val, label=y_val) # パラメータを設定 params = {'loss_function': 'RMSE'} # 学習 catb_model = CatBoost(params) catb_model.fit(cat_train, eval_set=[cat_valid], verbose=False) print('elapsed_time:{}'.format(time.time()-start)) Time:326.05 Train:0.782 Valid:0.799 3つのモデル比較 Time(s) Train Valid XGBoost 51.72 0.884 0.816 LightGBM 13.50 0.839 0.820 CatBoost 326.05 0.782 0.799 一番いい精度だったのはCatBoostでした! しかし、学習時間がダントツで長い。 時間をかければ精度が良くなるのは当たり前ですよね〜 その点、LightGBMは圧倒的早さに加え、精度もそれなりにいい! kaggleで人気なのも頷けますね! 今後の展望 今回の検証ではパラメータをデフォルトにしていたので optunaを用いてチューニングを行う。 イテレーションの回数を統一して学習する。 といったことを試していきたいと思います! 何かご指摘がございましたら、 コメントお願いいたします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pyATSを触るためにCMLでtestbedファイルを用意してみた

はじめに pyATSとはCiscoが無償で提供しているマルチベンダー対応のテストフレームワークです。 pyATSでCMLに建てた機器にConnectするまでを触ってみました。 testbedファイルの作成や生成手段がわからず困っていたのでここに残しておきます。 環境 ・Windows 10 Enterprise ・WSL2 → Ubuntu 20.04 LTS ・CML Version: 2.2.1+build36 構成図 かなり雑ですがやる気が消える前にアップしたかったのでスピード重視です。 経緯 ・手荒に扱える環境で試したかったのでCMLを対象にしました。 ・pyATSやPythonの記事を見てなんとなく何かをやりたかった。 ・Qitta投稿の初体験。 やりたい事 1.pyATSで利用するtestbedファイルをCMLのAPIから払い出す。 2.払い出したファイルで接続できるのか試す。 PC側の準備 pyATS_CMLファイルの中でvenvを使って仮想環境を用意する。 pyATSを使えるようにするため以下のコマンドを実行していきます。 $mkdir ~/pyats_CML $cd ~/pyats_CML $python3 -m venv . $source bin/activate $pip install --upgrade pip setuptools $pip install pyats[full] $mkdir work 以下のようなファイルが生成されてるはず。 (pyats_CML) pyats_CML hmasayuk$ ls bin include lib lib64 pyvenv.cfg share work CML側の準備 続いてCML側の情報を取りに行きます。 CML側のAPI Documentationを利用します。 以下、ログイン後のGUI操作です。 Try it outを選択したら以下のコードが出るので、利用しているCMLのID/PWに置き換えましょう。 ☞Executeで実行 { "username": "cgange me", "password": "change me" } 成功してればServer responseが 200でCMLに対してのtokenが払い出されたはずです。 これでCMLのAPIが操作できるのでtestbedファイルを作成していきましょう。 Labs> [GET]/labs/{lab_id}/pyats_testbedのAPIを使います。 lab_idはpyATSでConnectしたいラボのidを持ってきましょう。 ※私はURLからlab_idは取得しました。URL以外からも引っ張れるかもですがわからなければ参考までに) 上記の操作もTry it outを選択してから行います。 ☞Executeで実行 生成されたコードはこちら☟ testbed.yaml testbed.yaml testbed: name: LABNAME devices: terminal_server: os: linux type: linux credentials: default: username: change_me password: change_me connections: cli: protocol: ssh ip: CMLIP ubuntu-0: os: linux type: server credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n0/0 iosv-0: os: ios type: router series: iosv credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n2/0 server-0: os: linux type: server credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n4/0 iosv-1: os: ios type: router series: iosv credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n1/0 server-1: os: linux type: server credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n6/0 desktop-0: os: linux type: server credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n9/0 csr1000v-0: os: iosxe type: router series: csr1000v credentials: default: username: cisco password: cisco connections: defaults: class: unicon.Unicon a: protocol: telnet proxy: terminal_server command: open /ea18e3/n10/0 topology: ubuntu-0: interfaces: enp0s2: link: l0 type: ethernet enp0s3: type: ethernet iosv-0: interfaces: Loopback0: type: loopback GigabitEthernet0/0: link: l2 type: ethernet GigabitEthernet0/1: link: l3 type: ethernet GigabitEthernet0/2: type: ethernet GigabitEthernet0/3: type: ethernet server-0: interfaces: eth0: link: l7 type: ethernet iosv-1: interfaces: Loopback0: type: loopback GigabitEthernet0/0: link: l4 type: ethernet GigabitEthernet0/1: link: l5 type: ethernet GigabitEthernet0/2: type: ethernet GigabitEthernet0/3: type: ethernet server-1: interfaces: eth0: link: l6 type: ethernet desktop-0: interfaces: eth0: link: l8 type: ethernet csr1000v-0: interfaces: Loopback0: type: loopback GigabitEthernet1: link: l9 type: ethernet GigabitEthernet2: type: ethernet GigabitEthernet3: type: ethernet GigabitEthernet4: type: ethernet change meの部分を利用しているCMLの情報に書き換えたファイルを作業環境に持っていきます。 動作確認 先ほど作成したtestbedファイルをworkへ格納。 $cd ~/pyats_CML/work $touch testbed.yaml $vi testbed.yaml ###?ここでtestbedファイルをコピペしてます。### $ls testbed.yaml ここではviと記載してますが、エディタならemacsでもなんでもいいです。 実行してみましょう。 $ genie shell --testbed-file ~/pyats_CML/work/testbed.yaml Welcome to pyATS Interactive Shell ================================== Python 3.8.2 (default, Jul 16 2020, 14:00:26) [GCC 9.3.0] >>> from pyats.topology.loader import load >>> testbed = load('/home/honda/pyats_CML/work/testbed.yaml') ------------------------------------------------------------------------------- >>> うごいてるっぽい! ではこのままネットワーク情報を見ていきましょう。 >>> TEST = testbed.devices['iosv-0'] ###iosv-0を例として対象にしています。 >>> TEST.connect() Connecting to console for iosv-0 Connected to terminalserver. Escape character is '^]'. iosv-0> 2021-07-20 12:26:52,407: %UNICON-INFO: +++ initializing handle +++ これで接続できましたね。 show コマンドを打てるか試してみます。 >>> TEST.parse('show version') 2021-07-20 12:33:42,629: %UNICON-INFO: +++ iosv-0 with alias 'a': executing command 'show version' +++ show version Cisco IOS Software, IOSv Software (VIOS-ADVENTERPRISEK9-M), Version 15.9(3)M3, RELEASE SOFTWARE (fc1) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2021 by Cisco Systems, Inc. Compiled Wed 27-Jan-21 09:58 by prod_rel_team 取得完了!!無事にshow versionのコマンドの結果が見れますね。 最後に 今回の取り組みでCMLのAPIやpyATSを調べるきっかけになった事でpyATSの可能性を感じることができました。 次はコンフィグ投入やpyATSでのテストコマンド使いたいと思います。 参考先 pyATS公式ドキュメント pyATS/genieを触ってみよう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がPythonで5分足チャートから移動平均線を計算してグラフに表示してみた

SMA=Simple Moving Agerage。移動平均線。作業は超簡単。 参考 以下で作成しているコードを数行変更するだけです。 https://qiita.com/yamaji_daisuke/items/7f9d137ad3e557df0535 コード import pandas as pd import datetime import mplfinance as mpf df = pd.read_csv('./temp_historical_data/USDJPY.csv', nrows=2000) df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() mpf.plot(df5, type='candle', mav=(5, 25), datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_sma.png',dpi=100)) グラフ 説明 mpf.plotの引数にmavを追加するだけ。便利すぎるナリ。計算不要。 ちなみにpandasの方を使って計算するには以下のようにする。 df5["SMA5"] = df5["Close"].rolling(window=5).mean() df5["SMA25"] = df5["Close"].rolling(window=25).mean() 実際にはSMAの値も計算しておいてあれやこれやしたいと思うので、以下みたいな方がいいかもしれない? df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() sma = pd.DataFrame() sma["SMA5"] = df5["Close"].rolling(window=5).mean() sma["SMA25"] = df5["Close"].rolling(window=25).mean() addplot = mpf.make_addplot(sma[['SMA5', 'SMA25']]) mpf.plot(df5, type='candle', addplot=addplot, datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_sma.png',dpi=100))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がPythonでHistData.comから取得した1分足データを5分足データに変換してみた

1分足だとグラフ化の際にデータ数が多すぎてみづらかったので5分足にする。 参考 以下で作成したコードを利用しています。 コード import pandas as pd import datetime import mplfinance as mpf # CSVから読み込み df = pd.read_csv('./temp_historical_data/USDJPY.csv', nrows=1000) df.columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Volume'] df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) # 5分足変換 df5 = pd.DataFrame() rule = '5T' df5['Open'] = df['Open'].resample(rule).first() df5['Close'] = df['Close'].resample(rule).last() df5['High'] = df['High'].resample(rule).max() df5['Low'] = df['Low'].resample(rule).min() mpf.plot(df5, type='candle', datetime_format='%Y/%m/%d %H:%M', xrotation=90, style='yahoo', savefig=dict(fname='./figures/draw_sma.png',dpi=100)) 簡単な説明 Dateはいじらなくて大丈夫っぽい?ただし、元データ(ここではdf)にDatetime型のindexを設定しておかないとだめなようだった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む