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

sweeep Boxで使用している技術スタック

はじめに はじめましてsweeep CTOの平下です。今年からsweeepではテックブログを始めました(sweeep各メンバーのテック系ブログを紐付けるテックブログHub)。 先日請求書のオンライン受け取りと電子保管などの新サービス「sweeep Box」を立ち上げましたので、まずはそちらの技術について簡単にご紹介させていただこうと思います。 フロントエンド 現sweeepと同じくVue.jsで開発をしています。今回はTypeScriptを使用し、フレームワークはNuxt、アーキテクチャはClean Architectureを採用しています。また、CSSフレームワークはTailwindを使用しています。                                             BFF フロントエンドとバックエンドの間にはBFF(Backend For Frontend)を導入し、フロントエンド/モバイルアプリとバックエンドの間の仲介をしています。こちらはGo言語で開発し、アーキテクチャはCleanArhitectureを採用しています。また、フロントエンド/モバイルアプリとBFFの間はGraphQLで通信を行っています。                                             バックエンド バックエンドはGoで開発しています。現sweeepの技術負債を解消するべく、アーキテクチャはCleanArhitecture/Microservicesを採用しています。また、BFFとバックエンドの間はgRPCで通信を行っています。                                             CoreAI機能 sweeep BoxのCoreAI機能は引続きPythonを使用し、フレームワークはFastAPI、アーキテクチャはModulerMonolithを採用しています。                                             モバイルアプリ モバイルアプリはFlutterを使用し、こちらもCleanArhitectureを採用しています。                                             インフラ インフラはGCPを使用しています。それぞれ用途に合わせてGAE/CloudRun/GKEを使い分けています。DBは基本的にCloudSQLを使用しています。                                             スキーマ駆動開発 先にも述べましたが、プロトコルはフロントエンド/モバイルアプリとBFFの間はGraphQL、BFFとバックエンドの間はgRPCを採用しています。sweeep Boxではスキーマ駆動開発で、定義ファイルからクライアントとサーバーのプログラムを自動生成したものを使用し、クライアントとサーバー間で齟齬が生じないようにしています。また、定義ファイルを元にMockを作成することで、サーバーの開発が終わっていなくてもクライアントがMockへアクセスし通信部分の開発や動作確認できるようにしています。                                             まとめ 細かいものまで含めるとまだたくさんあるのですが、sweeep Boxの主な技術をご紹介させてきました。現在のチームの技術スタックに対し、sweeep Boxのサービス開発に最適と思われるものを選択したつもりですが、今後より最適な技術やアーキテクチャが登場した場合は検討し採用していきたいと思っています。 最後に宣伝になりますが、弊社ではフロントエンド・バックエンド・CoreAI機能の開発エンジニアを積極的に採用しています。ご興味のある方は下記リンクよりご応募お待ちしております!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DearPyGuiのプロットにテーマを設定する

やりたいこと DearPyGUiでプロットを作成したときにPaddingを設定したかった (左が未設定、右が設定済み) どうやるんだろうと探していたらドキュメントのテーマの項に記載がありました。 Padding以外の設定もできるようになったので記載します。 環境 Mac OS Python 3.10.1 dearpygui 1.3.1 numpy 1.22.1 pip install dearpygui numpy 詳細 手順は2つだけでした テーマを作成 表示するプロットにテーマを紐付ける """ DearPyGui plotのテーマ設定 https://dearpygui.readthedocs.io/en/latest/documentation/themes.html# """ import dearpygui.dearpygui as dpg import numpy as np # ################# プロットデータ作成 ################# data_len: int = 100 x_data = np.linspace(-np.pi, np.pi, data_len) sin_data = np.sin(x_data) cos_data = np.cos(x_data) # ##################### 描画設定 ##################### dpg.create_context() dpg.create_viewport(title='Plot view port', width=850, height=550) dpg.setup_dearpygui() dpg.show_viewport() # ##################### テーマ設定 ##################### with dpg.theme() as theme: # Plotに有効なテーマ with dpg.theme_component(dpg.mvPlot): # padding用のテーマ dpg.add_theme_style(dpg.mvPlotStyleVar_FitPadding, x=0.2, y=0.2, category=dpg.mvThemeCat_Plots) # ##################### 画面作成 ##################### with dpg.window(label='Plot window', width=800, height=500): with dpg.plot(label='left_plot', width=-1, height=-1) as theme_plt: dpg.add_plot_legend(horizontal=True) dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis') dpg.add_plot_axis(dpg.mvYAxis, label='y', tag='yaxis') dpg.add_line_series(x_data, sin_data, label='sin', parent=dpg.last_item(), tag='line') # テーマ適用 dpg.bind_item_theme(theme_plt, theme) dpg.start_dearpygui() dpg.destroy_context() 結果 タグを使用しても同じことができます # ##################### テーマ設定 ##################### with dpg.theme(tag='theme'): # Plotに有効なテーマ with dpg.theme_component(dpg.mvPlot): dpg.add_theme_style(dpg.mvPlotStyleVar_FitPadding, x=0.2, y=0.2, category=dpg.mvThemeCat_Plots) # ##################### 画面作成 ##################### with dpg.window(label='Plot window', width=800, height=500): with dpg.plot(label='left_plot',tag='theme_plt', width=-1, height=-1): dpg.add_plot_legend(horizontal=True) dpg.add_plot_axis(dpg.mvXAxis, label='x', tag='xaxis') dpg.add_plot_axis(dpg.mvYAxis, label='y', tag='yaxis') dpg.add_line_series(x_data, sin_data, label='sin', parent=dpg.last_item(), tag='line') dpg.bind_item_theme('theme_plt', 'theme') グラフに設定できるスタイルと色はこれくらいありそうです。 dpg.show_tool(dpg.mvTool_Style)で開けます。(この画面からでもスタイル、色の設定ができます) 適当にいじってみる with dpg.theme(tag='theme'): # Plotに有効なテーマ with dpg.theme_component(dpg.mvPlot): # Style設定 dpg.add_theme_style(dpg.mvPlotStyleVar_FitPadding, x=0.2, y=0.2, category=dpg.mvThemeCat_Plots) # Padding dpg.add_theme_style(dpg.mvPlotStyleVar_PlotBorderSize, 2, category=dpg.mvThemeCat_Plots) # プロットの外枠の太さ? dpg.add_theme_style(dpg.mvPlotStyleVar_MinorAlpha, 255, category=dpg.mvThemeCat_Plots) # 補助線の透明度 # Color設定 dpg.add_theme_color(dpg.mvPlotCol_XAxis, [255, 0, 0, 255], category=dpg.mvThemeCat_Plots) # x軸の色 dpg.add_theme_color(dpg.mvPlotCol_YAxis, [0, 255, 0, 255], category=dpg.mvThemeCat_Plots) # y軸の色 add_theme_styleはx,y引数にxy毎の値を入力すれば大丈夫です。 dpg.mvPlotStyleVar_MinorAlphaのようなxyで分けない場合はxに入れると適用できました。 参考 Themes — Dear PyGui documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mayaの環境設定 ~subdivisionMethod~

mayaのいつからか忘れましたが、smoothのsubdivisionMethodが導入され これもまた環境によって変わってしまう面倒なやつ。 環境ごとに違うと何が問題か? meshノードのsmoothMesh項目にsubdivisionMethod というのがあり、 useGlobal = True がデフォルトになっている。 useGlobal つまり環境設定に準ずるということ。 これが揃っていないとsmoothMesh使用時にメッシュの形状・UVなどに差異が生まれてきてしまう。 どこで設定しているか? 先述の通りprefrenceで設定してるのだけれども、どうやって設定しているのかがわからない。 ひとまず preferenceを更新しつつ、更新前・後のuserPref.melを比較してみる。 newPolyDefaultSmoothDrawType optionVarにてこの値が変動している。 newPolyDefaultSmoothDrawType 0 の状態だったので 試しに optionVar で変えてみる cmds.optionVar(intValue = ["newPolyDefaultSmoothDrawType",1]) 変わらない。ので、savePrefs もしてみる cmds.optionVar(intValue = ["newPolyDefaultSmoothDrawType",1]) cmds.savePrefs(general =True) すると savePref後に newPolyDefaultSmoothDrawTypeを問い合わせると元に戻ってしまっていた。 というわけで、mayaのどこかに別途設定を格納してるはず。 global 変数? global 変数に格納してるのではないかと仮定。 melで global変数名とその値を呼び出す。 これも preferenceの更新前・後で比較してみる・ { string $globalList[] = `env`; for($globalName in $globalList) { print("\n" + $globalName + "\n"); eval("print " + $globalName + ";\n"); } } 残念ながらそれらしい変数と変化は見られず。 AETemplateから探ってみる UI上に文字として出てるのだから、何かしら取得してるはず。 なので、AETemplateを探ってみる。 該当のAETemplate.mel C:\Program Files\Autodesk\Maya2019\scripts\AETemplates\AEmeshTemplate.mel なかみを拝見・・・発見。 global proc int GetSmoothDrawType(string $nodeName) { int $subdivisionVal = 0; // MayaCC int $useGlobal = `getAttr ($nodeName + ".useGlobalSmoothDrawType")`; if ($useGlobal == 1) { int $polyOpts[] = `polyOptions -q -smoothDrawType`; if ($polyOpts[0] == 1) $subdivisionVal = 2; // Osd-Uniform else if ($polyOpts[0] == 2) $subdivisionVal = 3; // Osd-Adaptive } else { $subdivisionVal = `getAttr ($nodeName + ".smoothDrawType")`; } return $subdivisionVal; } このコマンドで確認できるらしい。 polyOptions -q -smoothDrawType smoothDrawType(sdt) この設定は、newPolymesh フラグを使用する場合のみ機能します。ポリゴンメッシュ オブジェクト上の smoothDrawType アトリビュートの新しい既定のアトリビュート値を設定します。オプションは、0: Catmull-Clark 1: リニア 2: OpenSubdiv Catmull-Clark 均一 3: OpenSubdiv Catmull-Clark 適応です。 とのこと。 早速やってみる。 cmds.polyOptions(newPolymesh =True,smoothDrawType = 0) cmds.savePrefs(general =True) cmds.polyOptions(newPolymesh =True,smoothDrawType = 1) cmds.savePrefs(general =True) できた! 追記: 0: Catmull-Clark 1: リニア 2: OpenSubdiv Catmull-Clark 均一 3: OpenSubdiv Catmull-Clark とかいてあるが、手元の環境だと 0: Catmull-Clark 1: OpenSubdiv Catmull-Clark 均一 2: OpenSubdiv Catmull-Clark となっている。 AEの方が変わらない が、こっちの表記が変わらない。 cmds.polyOptions(smoothDrawType = 1) これを別途実行すると表記は変更されるが、処理内容は反映されていないように見える。 シーンを一旦保存して開きなおすと、ちゃんと反映されていた。 まとめ 反映されるタイミングがちょっと厄介なので、userSetup.pyで cmds.polyOptions(newPolymesh =True,smoothDrawType = 1) cmds.polyOptions(smoothDrawType = 1) cmds.savePrefs(general =True) を仕込んでおいた方が安全だと思われる。 かつ、preferenceの該当部分は変更禁止。 ※別途詳細確認中だが、useGlobalのチェックを外した時に 特定の条件下でmethodのsetAttr内容が消失する現象もあり。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NameError: name ‘os’ is not definedが表示される場合の2つの解決法

はじめに 僕はこのエラーに2回遭遇した経験があります。 1回目は、はじめてのDjango使用時にstartappしようとしたとき 2回目は、画像アップロード機能を追加するためsettings.pyにMEDIA_ROOTに以下の記述を追加し、migtateを実行したときです。 settings.py MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 両者とも同じ方法で解決することができました。 しかし2回目解決の際、1回目の解決法を思い出すことが出来ず調べていて、別の方法を発見しましたので、備忘録としてまとめます。 (思い出せなかった件がアカウント作成のきっかけでもあります。アウトプット、メモ書き大事) 解決方法 settings.py内に import os を記述する os.pathを利用せず、BASE_DIR/ ' ' というpathlib形式で記入する 詳細 の方法に関してはsettings.pyの最初の行にでも追記すればよいだけなので、非常に簡潔な方法ですね。startapp実行初期の状態では未記入の状態ですので、os.pathを利用する場合には忘れずに記入しましょう。 の方法に関しては、Django 3.1以上で利用できる方法になります。 Django3.1以降では、標準でpathlibというパッケージが入っています。 pathlibはファイルやディレクトリを操作することができるライブラリになります。 settings.py from pathlib import Path Djangoに標準搭載につき、この記述を自分でする必要はありません。 また、記述量も少なく見やすくなります。 os.pathの場合 MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') pathlibの場合 MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR/'media' また、今回の問題とは直接関係ありませんが、pathlib形式で記入することでBASE_DIRの記述方法も変化しています。ここは別記事でまとめようと思います。 どちらの場合でも動作自体は正しくすることが確認できましたが、 os.path と pathlib が混在している場合、500エラーというものが出る場合もあるそうです。 素人ながら、コードは短いに越したことは無いと思いますので、今後は pathlib を主に利用していきたいと考えています。 以上!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【FastAPI】親子関係を持つテーブルのデータを同時に作成する

やりたいこと いわゆる1:Nの関係にあるデータ(売上・売上明細など)をFastAPI経由で作成する必要があり、その方法をメモします。 製品コードなどの画面入力項目を主キーにしている場合は特に問題がないのですが、 連番やuuidなどを主キーにしている場合、事前(リクエスト前)にキー項目の値を把握することができず、データ作成時にAttributeError: 'dict' object has no attribute '_sa_instance_state'というような例外が発生してしまいます。 実現方法の概要 crud処理の中で、pydanticスキーマのdictをアンパック(**schema_create.dict())せず、key=valueを直接指定する sqlalchemy.orm.Session.flushを利用して親の主キーを取得し、子データを作成 ※FastAPIの勉強を始めたばかりですので、もっと良いやり方がある場合はご教示いただけると大変嬉しいです 詳細:ソースコード SQLAlchemyモデル Sales: 売上データ SalesMeisai: 売上明細データ (本来売上データは商品マスター、取引先マスターといったデータを持つすべきなのですが、シンプルにするため省略しています) models.py from sqlalchemy import Column, Date, DateTime, Float, ForeignKey, Integer, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm.decl_api import DeclarativeMeta from sqlalchemy_utils import UUIDType ModelBase: DeclarativeMeta = declarative_base() def now(): return datetime.now() class Sales(ModelBase): """売上""" __tablename__ = "t_sales" pk = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) # 伝票番号 slip_id = Column(String(500), nullable=False) # 取引日 torihiki_date = Column(Date, nullable=False) post_code = Column(String(10), nullable=True) address1 = Column(String(2000), nullable=False, default="") address2 = Column(String(2000), nullable=False, default="") address3 = Column(String(2000), nullable=False, default="") phone_number = Column(String(100), nullable=True, default="") fax_number = Column(String(100), nullable=True, default="") created_at = Column(DateTime, nullable=True, default=now) created_by = Column(String, nullable=True, default="") updated_at = Column(DateTime, nullable=True, default=now) updated_by = Column(String, nullable=True, default="") class SalesMeisai(ModelBase): """売上明細""" __tablename__ = "t_sales_meisai" pk = Column(UUIDType(binary=False), primary_key=True, default=uuid.uuid4) sales_header_id = Column( "sales_header_id", ForeignKey("t_sales.pk", onupdate="CASCADE", ondelete="CASCADE") ) # 売上明細番号 detail_no = Column(Integer, nullable=False, default=1) # 数量 quantity = Column(Float(asdecimal=True), nullable=False, default=0) # 売上額 sales_amount = Column(Float(asdecimal=True), nullable=False, default=0) created_at = Column(DateTime, nullable=True, default=now) created_by = Column(String, nullable=True, default="") updated_at = Column(DateTime, nullable=True, default=now) updated_by = Column(String, nullable=True, default="") pydanticスキーマ schemas/sales.py from pydantic import BaseModel from app.db.models import SalesMeisai class SalesBase(BaseModel): class Config: orm_mode = True class SalesMeisaiDetail(SalesBase): detail_no: int quantity: float sales_amount: float class SalesCreate(SalesBase): slip_id: str invoice_number: str torihiki_date: datetime.date post_code: str address1: str address2: str address3: str phone_number: str fax_number: str notes_in_house: str notes_customer: str # ここはpydanticのBaseModelを継承したクラスを指定する meisai: list[SalesMeisaiDetail] = [] CRUD crud/sales.py from sqlalchemy import select from sqlalchemy.orm import Session from app.api.schemas.sales import SalesCreate from app.db.models import Sales, SalesMeisai def create_sales(db: Session, sales_create: SalesCreate): sales = Sales( slip_id=sales_create.dict().get("slip_id"), invoice_number=sales_create.dict().get("invoice_number"), torihiki_date=sales_create.dict().get("torihiki_date"), post_code=sales_create.dict().get("post_code"), address1=sales_create.dict().get("address1"), address2=sales_create.dict().get("address2"), address3=sales_create.dict().get("address3"), phone_number=sales_create.dict().get("phone_number"), fax_number=sales_create.dict().get("fax_number"), ) db.add(sales) # ここでflushし、sales:ヘッダのuuidを取得 db.flush() meisai_list = sales_create.dict().get("meisai") for meisai_data in meisai_list: sales_meisai = SalesMeisai( sales_header_id=sales.pk, detail_no=meisai_data["detail_no"], product=meisai_data["product"], quantity=meisai_data["quantity"], sales_amount=meisai_data["sales_amount"], ) db.add(sales_meisai) db.commit() db.refresh(sales) return sales router router/sales.py from app.api import dependencies as deps from app.api.cruds import sales as sales_crud from app.api.schemas import sales as sales_schemas from fastapi import APIRouter, Depends, status router = APIRouter() @router.post( "/", response_model=sales_schemas.SalesCreate, status_code=status.HTTP_201_CREATED, ) async def sales_create(sales: sales_schemas.SalesCreate, db=Depends(deps.get_db)): return sales_crud.create_sales(db, sales) main.py from fastapi import FastAPI app = FastAPI() app.include_router(sales_router, prefix="/api/sales", tags=["sales"]) alembicでマイグレーションを実行 $ alembic revision --autogenerate -m "add_sales_data" $ alembic upgrade head apiを実行し、正常終了することを確認 これで安心してフロントエンド側からコールすることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scikit-learnのロジスティック回帰の正則化項ハマった話

 完全に2値分類できる問題があったとする。scikit-learn のロジスティック回帰を使って分類する場合、scikit-learn のロジスティック回帰ではデフォルトでL2ノルムが加えられている。この正則化項により、うまく分類できない場合がある。 解決策  そういうときは以下のようにpenalty=noneを追加して試してみるとうまくいくかも。 lr = LogisticRegression(penalty = "none")  完全分類できる問題でなければL2やL1の罰則項を加えておいたほうがいいと思う。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[python]オブジェクト指向で記述するgithubのURLのあれこれを取得

訳あってpythonを個人的に書いているのだが、最近の意識として、クソコードならクソコードらしく、ちゃんとオブジェクト指向で書いてみようじゃんかよ!ということで改めて書き直してみた。 ただ、この書き方が正しいのかどうかは分からん。 やっていることとしては、アカウント名とリポジトリ名を取得する事。 リポジトリ名については.gitが含まれる場合があるので、それについての判定も書いてみた。 main.py from array import array url = "https://github.com/TatsuyaMaeta/tama-camp-univ-janken.git" class SplitUrl: def __init__(self,url): self.url = url # self.lastArrIndex = len(self.url.split(self))-1 self.slsh = "/" self.dot = "." self.repositoryName = "" self.dotIndex = 0 self.arrIndex = 0 self.arrayURL = [] self.dotflg = False self.tmpStr = "" def split(self): self.arrayURL = self.url.split(self.slsh) # 配列を返す return self.arrayURL def getArrIndex(self): self.arrayURL = self.url.split(self.slsh) self.arrIndex = len(self.arrayURL) # 数値を返す return self.arrIndex def checkIncludeDot(self): self.arrayURL = self.split() self.arrIndex = self.getArrIndex() self.dotIndex = self.arrayURL[self.arrIndex - 1].find(self.dot) if self.dotIndex > -1: print(f'ドットの位置は{self.dotIndex}文字目です') self.dotflg = True return self.dotflg else: return self.dotflg def getRepoName(self): self.tmpStr = self.arrayURL[self.arrIndex -1] self.repositoryName = self.tmpStr[0:self.dotIndex] return self.repositoryName gitRepo = SplitUrl(url) print(gitRepo.split()) print(gitRepo.getArrIndex()) print(gitRepo.checkIncludeDot()) print(gitRepo.dotIndex) print(gitRepo.getRepoName()) プリント結果 ターミナル表示結果 ['https:', '', 'github.com', 'TatsuyaMaeta', 'tama-camp-univ-janken.git'] 5 ドットの位置は21文字目です True 21 tama-camp-univ-janken 書き方についてよく分からず書いているので、オブジェクト指向はこう書いたらもっと良くなるよ!とかあれば是非教えてもらいたいです。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列をwindowsでフォルダ名に使える文字列に変換するPythonプログラム

はじめに Pythonでどこかからとってきた文字列で自動的にフォルダを作成していると、winwowsで使用できないフォルダ名になってしまって、消すのがとても大変になることがあります。 そこでwindowsのディレクトリ名に使えるように名前を変更するプログラムを紹介します。 変更する文字 半角文字 まず、windowsのフォルダで使用できない文字があります。それが以下の文字です。 \ / : * ? " < > | そのため、以下の全角文字に変更します。 ¥ / : * ? ” < > | エスケープシーケンス 上の使用できない半角文字の中に\がありましたが、これだけを置き換えると、エスケープシーケンスとしてとらえておらず、おかしくなってしまいます。 例えば、皆さん\nこんにちはを変換すると皆さん¥nこんにちはというようになってしまします。 しかし、本当にしたいのは、\nを削除した皆さんこんにちはです。 そこで、エスケープシーケンスを削除します。なお、これは半角文字を置き換える前にやらないといけません。 具体的には、エスケープシーケンスには、以下のものがあります。 記号 意味 \a ベル文字(アラート) \b 1文字分戻る \f ページ送り(クリア) \n 改行、復帰 \r 同じ行の先頭に戻る \t 水平タブ \v 垂直タブ \\ \を表示 \? ?を表示 \' シングルクォーテーション(')を表示 \" ダブルクォーテーション(")を表示 \0 ヌル \N 8進定数(Nは8進数の定数) \xN 16進定数(Nは16進数の定数) これをすべて削除、もしくは置き換えます。 なお、\Nと\xNは変換は諦めます。 \?に関しても、ほかのサイトなどを見るとエスケープシーケンスとして処理されていなさそうなので無視します。 また、誰かがよく知らずにバックスラッシュを使用しているとどうしようもないことがあります。例えば、ここに\appleがありますという文字列は\aが認識されて削除されてしまいます。 先頭/末尾のドット/空白 先頭/末尾にドット/空白があると、ファイル削除すらできない大変なフォルダができてしまいます。 というのも多くのアプリケーションによって 「C:\hoge」(末尾にスペースやピリオドがない、ごく普通のフォルダ) 「C:\hoge.」(末尾にピリオドがあるフォルダ) 「C:\hoge 」(末尾にスペースがあるフォルダ)  なんと、この3つが同じものとして扱われ、不定な名称となります。 そのため削除します。 strip関数を使用して削除すると、先頭/末尾の1文字しか削除できないので、while Trueでなくなるまで回します。 プログラム rename_for_windows.py def rename_for_windows(name): while True: tmp = name #半角文字の削除 name = name.translate((str.maketrans({'\a':'','\b':'','\f':'','\n':'','\r':'','\t':'','\v':'',"\'":"'",'\"':'"','\0':''}))) #エスケープシーケンスを削除 name = name.translate((str.maketrans({'\\':'¥','/': '/' , ':': ':', '*': '*', '?': '?', '"': "”", '>': '>', '<': '<', '|': '|'}))) #先頭/末尾のドットの削除 name = name.strip(".") #先頭/末尾の半角スペースを削除 name = name.strip(" ") #先頭/末尾の全角スペースを削除 name = name.strip(" ") if name == tmp: break return name 間違えて作成した消せないフォルダの削除の仕方 ちなみに、間違えて作ってしまった消せないフォルダの削除の仕方は以下のページに書いてあります。 Microsoft 名前の最後にドットが入った消せないフォルダができてしまいました。 まとめ 文字列をwindowsでフォルダ名に使える文字列に変換するPythonプログラムを紹介しました。 私自身、スクレイピング時に自動的にフォルダを作っているのですが、この時にフォルダ作成前にこのプログラムを挟むようにしています。 同じことができるライブラリがあればいいのですが... 是非皆さん使って似てください。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

特定バージョンのtensorflowのインストールができないときの対処法

はじめに tensorflowで作成した深層学習モデルを学習させるときに,以下のようなエラーで学習ができず新たな仮想環境の構築を行なったのでまとめます. Conv2DCustomBackpropInputOp only supports NHWC. このエラーはおそらくCPU環境においてtensorflow2系でモデルの定義で data_format = "channels_first" 1とすると発生するもので,このエラーへの対処法は以下の二通りが考えられます. データ形状とモデルのdata_formatを全てchannels_lastにする tensorflowをダウングレードする ここでは二つ目のtensorflowをダウングレードする方法について紹介します. 今回はWindowsのPython3の環境でtensorflowのバージョン1.8.0へのダウングレードを行います. MacやLinuxでも似た流れで実行できると思います. 今回の流れ 今回の流れは以下の通りです. 現状の確認 tensorflowのバージョン1.8.0に対応しているPython3系の調査 調査で判明したバージョンのPythonのインストール インストールしたPythonで仮想環境構築 (venv使用) tensorflowのバージョン1.8.0のインストール 現状の確認 とりあえずtensorflowのバージョン1.8.0のインストールを試みます. $ pip install tensorflow==1.8.0 ERROR: could not find a version that satisfies the requirement tensorflow==1.8.0 (from versions: 2.5.0rc0, 2.5.0rc1, 2.5.0rc2, 2.5.0rc3, 2.5.0, 2.5.1, 2.5.2, 2.5.3, 2.6.0rc0, 2.6.0rc1, 2.6.0rc2, 2.6.0, 2.6.1, 2.6.2, 2.6.3, 2.7.0rc0, 2.7.0rc1, 2.7.0, 2.7.1, 2.8.0rc0, 2.8.0rc1, 2.8.0) ERROR: no matching distribution found for tensorflow==1.8.0 $ python -V Python 3.9.10 Python3.9においてtensorflowのバージョン1.8.0はインストールの候補になく,インストールすることができません. そこでtensorflowのバージョン1.8.0に対応しているPythonを探すことから始めます. tensorflowのバージョン1.8.0に対応しているPython3系の調査 まず https://pypi.org/ にアクセスして,検索欄で "tensorflow" と検索します. 検索すると以下の画像のように様々なパッケージが出てくるので,tensorflowに該当するもの(下の画像では一番上の tensorflow 2.8.0)をクリックします. tensorflowのページが開いたら,左側の"Release history"をクリックします. そうすると下の画像のようにtensorflowの様々なバージョンが一覧になって出てくるので,バージョン1.8.0を探してクリックします. tensorflowのバージョン1.8.0のダウンロードファイルは下の画像になっています. ここで下の画像の赤枠で示した部分に注目すると,これがtensorflowのバージョン1.8.0に対応しているPythonのバージョンを表しています. 下の画像ではcp27からcp36まで,つまりPython2.7とPython3.3からPython3.6までが対応しており,これらのPythonを導入する必要があるとわかります. もし既に導入しているPythonがPython2.7かPython3.3からPython3.6までの場合はtensorflowのバージョン1.8.0のインストールに進んでください. 現在のPythonのバージョンは以下のコマンドで確認できます. $ python3 -V 対応するPythonのインストール 前の調査でtensroflow1.8.0を使うためにはPython3系ではPython3.3からPython3.6が必要であることが分かったので,Python3.6のインストールを行います. まず https://www.python.org/downloads/windows/ にアクセスして,Python3.6を探します. Windows向けのインストーラが用意されている中で最も新しいPython3.6が3.6.8だったので,下の画像の赤枠の "Download Windows x86-64 executable installer" をクリックしてインストールを行います. インストールの際にインストールを行う場所をメモしておいてください. 通常は以下の場所にインストールされると思います. (usernameは適宜変更してください.) $ C:\Users\username\AppData\Local\Programs\Python\Python36 インストールしたPythonで仮想環境構築 様々なパッケージをルート環境にインストールすると,パッケージの依存関係でパッケージが正常に動作しなくなることがあるので,仮想環境を作成して作業を行うのが良いと思います. Pythonで仮想環境を構築する方法として,ここではvenvを用いて仮想環境構築を行います. ここからの操作はWindowsのコマンドプロンプトで実行します. venvはPython3.6では標準で使用可能な仮想環境作成コマンドで,以下のコマンドで仮想環境を構築することができます. ここで作成する仮想環境名を myenv,仮想環境を構築するフォルダの絶対パスを C:\Users\username\myenv とします. 仮想環境名と絶対パスは適宜変更してください. $ C:\Users\username\AppData\Local\Programs\Python\Python36\python -m venv C:\Users\username\myenv これで仮想環境を構築できたので,以下のコマンドで仮想環境を実行します. $ C:\Users\username\myenv\Scripts\activate 上記のコマンドを実行し,以下のように今いる絶対パスの前に (myenv) が付いていれば,仮想環境の実行ができています. (myenv) C:\Users\username> ここで念のために仮想環境内のPythonのバージョン確認を行います. 実行結果が以下のようになっていれば問題ありません. (myenv) C:\Users\username> python -V Python 3.6.8 仮想環境を終了するときは以下のコマンドで終了できます. (myenv) C:\Users\username> deactivate tensorflowのバージョン1.8.0のインストール tensorflowのバージョン1.8.0をインストールする場合は以下のコマンドでインストールできます. (myenv) C:\Users\username> pip install tensorflow==1.8.0 tensorflowがバージョン1.8.0でインストールできているかは以下のコマンドで検証できます. (myenv) C:\Users\username> python >>> import tensorflow >>> print(tensorflow.__version__) 1.8.0 最後に ここではtensorflowのバージョン1.8.0のインストールに関して扱いましたが,他のバージョンについても同様の方法で環境構築が可能だと思います. 参考資料 venvに関するPythonの資料 https://docs.python.org/ja/3/library/venv.html channels_firstとは,tensorflowでのデータの形状を表します.channels_firstはデータの形状がNCHW(ミニバッチのデータ数,チャンネル,高さ,幅)の順に並んでいます.一方,channels_lastはNHWC(ミニバッチのデータ数,高さ,幅,チャンネル)の順に並んだデータ形状を指します.TensorFlowのデフォルトのデータ形状はchannels_lastです. ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】同名でsquashmigrationsしたらshowmigrationsから消えてしまったとき

環境 $ python -V Python 3.9.1 $ python -m django --version 3.1.5 起こった問題 こういうマイグレーションがあるとします。 $ python manage.py showmigrations user [X] 0001_initial [X] 0002_user_name [X] 0003_auto_20220214_1200 squashmigrationsを使って0002と0003をまとめます。 このとき、--squashednameオプションでまとめた後のファイル名を指定します。 $ python manage.py squashmigrations user 0002 0003 --squashedname user_name Will squash the following migrations: - 0002_user_name - 0003_auto_20220214_1200 Do you wish to proceed? [yN] y Optimizing... No optimizations possible. Created new squashed migration /app/user/migrations/0002_user_name.py You should commit this migration but leave the old ones in place; the new migration will be used for new installs. Once you are sure all instances of the codebase have applied the migrations you squashed, you can delete them. この後、showmigrationsでマイグレーションを確認してみると、なんとuserアプリのマイグレーションが全て消えてしまっています。 $ python manage.py showmigrations user (no migrations) migrateしてみても、何も適用されません。--fakeをつけても同様。 原因 消えてしまったマイグレーションは、単にDjangoから認識されなくなったというだけで、ファイル自体は残っています。 squash後のファイルの中身を見てみましょう。 0002_user_name.py # Generated by Django 3.1.5 on 2022-02-14 06:56 from django.db import migrations, models class Migration(migrations.Migration): replaces = [('user', '0002_user_name'), ('user', '0003_auto_20220214_1200')] dependencies = [ ('user', '0001_initial'), ] operations = [ # 割愛 ] squashmigrationsで作られたマイグレーションには、replace属性が追加されます。 この属性で、どのマイグレーションをまとめて作られたのかがわかるようになっていて、このリストにあるマイグレーションは無視されます。 ここが今回の問題の原因です。 replacesに含まれるマイグレーションと自分の名前が同じなので(自分を無視する?)、依存関係がぐちゃぐちゃになってしまったようです。 こう直す replaces属性を削除することで解消しました。 その場合、残った0003_auto_20220214_1200.pyが実行されるようになるので、こちらも削除しておくこと。 参考 マイグレーション | Django ドキュメント | Django Django マイグレーション完全に理解した (基礎編) ? - くろのて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#5~8(第2章まとめ)

2章まとめ 【出典】「新・明解Pythonで学ぶアルゴリズムとデータ構造」 「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#5 2-1データ構造と配列 最初はデータ構造や配列とはなんぞやというところから始めるみたいですね。 配列の必要性 急に配列の必要性なんて言われても…。そもそも配列がピンとこない私には話が飛躍している気がするので…。 配列:コンピューターのプログラミング言語における、データ形式の一。同じ型のデータの集合を意味し、個々のデータは変数の添え字で区別する。【出典】デジタル大辞泉 とのことでした。 以下のプログラムでは5人の生徒の点数を合計した値、平均した値を求めます。 list2-1 #5人の点数を読みこんで、合計・平均点を表示 print('5人の点数の合計点と平均点を求めます。') tensu1 = int(input('1番目の点数:')) tensu2 = int(input('2番目の点数:')) tensu3 = int(input('3番目の点数:')) tensu4 = int(input('4番目の点数:')) tensu5 = int(input('5番目の点数:')) total = 0 total += tensu1 total += tensu2 total += tensu3 total += tensu4 total += tensu5 print(f'合計は{total}点です。') print(f'平均は{total / 5}点です。') さて、上記のコードでは、応用の利かない記述になっていることは明確です。 ①人数を可変、②特定の点数を調べる/書き換え、③最低点と最高点を求める/ソートの3つを実現することで、有用性のあるプログラムといえます。そのためにはプログラムの作り方を変える必要があります。 まず、各生徒の点数をひとまとめにして扱えるようにします。それを実現するのが配列と呼ばれるデータ構造です。 配列はオブジェクトの格納庫であって格納された個々の変数は要素と呼ばれます。各要素には先頭から順番に0,1,2・・・の添字が与えられる。 配列は生成時に要素を自由に指定できるため①はクリアできます。また、要素数の増減も可能です。添字を使った式(tensu[2]など)でアクセスできるため②の実現も容易。ここの要素を自由にアクセスできる結果として③も実現できます。 こうやって考えると配列の考えは重要でプログラミングには欠かせないものということがわかりました。 リストとタプル pythonで配列を実現するのがリストとタプルです。いずれも高機能なデータのコンテナ(格納庫)です。 リスト:変更可能(ミュータブル)なlist型のオブジェクト。list関数を使うと文字列やタプルなど様々な型のオブジェクトをもとに、リストを生成できます。要素数が決まっており、要素の値は未定(空の情報)の時 None を使うことが可能。[]を使う。 タプル:組とも呼ばれる。要素を順序付けて組み合わせたもの。変更不能(イミュータブル)オブジェクトで、tuple型。()を使う。 単一の値であってタプルではない値 v01 = 1 #1 v02 = (1) #1 アンパック:リストから要素の一括取り出しができる。 例2-1リストから要素の一括取り出し x = [1, 2, 3] a, b, c = x print(a,end = '') print(b,end = '') print(c,end = '') インデックス式によるアクセス リストやタプル内の個々の要素をアクセスする際のキーとなるのが、インデックス。    [1,2,3,4,5,6,7] 先頭0 1 2 3 4 5 6 末尾(非負のインデックス) 先頭-7-6-5-4-3-2-1末尾(負のインデックス) インデックス式 例2-2リストとインデックス式 x = [11, 22, 33, 44, 55, 66, 77] print(x[2]) print(x[-3]) x[-4] = 3.14 #要素の置換 print(x) 代入でコピーされるのは値でなく参照であるため、x[-4]の参照先がint型(44)からfloat型(3.14)に変わります。 なお、xがタプルだった場合は代入はエラーになります。(タプルは変更不能なため) 存在しないインデックス x[7] #存在しないインデックスの値は取り出せない x[7] = 3.14 #存在しないインデックスへの代入による要素の追加は行われない 存在しない要素をアクセスするインデックス式を、左辺に置く代入によって、要素が新しく追加することはできません。 スライス式によるアクセス スライス:リストやタプル内の部分を連続あるいは一定周期で新しいリストあるいはタプルとして取り出すこと スライス式による取り出し s[i:j] ・・・s[i]からs[j-1]までの並び s[i:j:k] ・・・s[i]からs[j-1]までのkごとの並び 例2-3 s = [11, 22, 33, 44, 55, 66, 77] s[0:6] s[0:7] s[0:7:2] s[-4:-2] s[3:1] ・iとjは、len(s)よりも大きければ、len(s)が指定されたものとみなされる。 インデックスとは異なり、正当な範囲外の値を指定してもえらーとならない。 ・iが省略されるかNoneであれば、0が指定されたものとみなされる。 ・jが省略されるかNoneであれば、len(s)がしていされたものとみなされる。 まとめると以下のようになります。 s[:]・・・すべて s[:n]・・・先頭のn要素 s[i:]・・・s[i]から末尾 s[-n:]・・・末尾の-n要素 s[::k]・・・k-1個おき s[::-1]・・・すべてを逆向き ※nが要素数を超える場合は全要素が取り出される。 コラム2-1 代入とイミュータブル(変更不能)/ミュータブル(変更可能) 変数には、格納済みの値とは異なる型の値を代入できます。 型にどんな種類があるかわかりませんが、できるそうです。 IDの参照 n = 5 id(n) #5のIDが参照される n = 'ABC' id(n) #ABCのIDが参照される 文字列の代入後にnの識別番号が変わります。(int型からstr型に更新) 変数への代入によってコピーされるのは参照先である識別番号(同一性)であって値ではないため、あらゆる型のオブジェクトを変数に代入できるそうです。 pythonの代入文は極めて多機能で、初めて使う名前の変数に値を代入するだけで、その名前の変数が自動的に用意される。それ以外にも多くの機能があるみたいです。 一括代入 a, b, c =1, 2, 3 print(a) print(b) print(c) ちょっと便利な用例 x = 6 y = 2 x, y = y + 2, x + 3 print(x) print(y) 累算代入によるインクリメント n = 12 id(n) n +=1 #nの値を1増やす id(n) #識別番号が変更される 聞くだけだとたくさんの機能がある分覚えること多くて大変そうと思いますが、一見すると便利感あります。 型について、int型やstr型はいったん与えられた値が変更できないイミュータブルな型だそうで。(nの値は変更できるが12など数字自体の値(id)は変更できないため) ・ミュータブルな型:リスト、辞書、集合 など ・イミュータブルな型:数値、文字列、タプル など pythonの代入の特性 ・左辺の変数名が初出であれば、その変数を定義する。 ・代入文は、値ではなく参照先(識別番号=同一性)を代入する。 ・複数の代入が一括で可能。 とまとめられています。 具体的な記述で見れば x + 17 は式。 x = 17 は文。 ということみたいです。 余談ですが、C,C++,javaなどは、 = は'右結合の演算子'として扱われるため a = b = 1 は a = (b =1) という解釈になるらしいです。(こちらの方が数学のイメージに近いですね) pythonでは、= は演算子でないため、結合性は存在しません。 データ構造 データ構造:構成要素との間に何らかの相互関係をもつデータの論理的な構成のこと。(つまり複数のデータが集まった構造だが、0個、1個もありうる) イメージをしようと思うと結構難しいので私は「へー」程度の理解にしておこうと思います。 コラム2-2リストとタプル(その1) リストやタプルの長さを表示 x = [15, 64, 7, 3.14, [32, 55], 'ABC'] len(x) #[32, 55]は1つとしてカウントされる min()やmax()はリストやタプルも引数として渡すことができる。 y = [2, 4, 5, 78, 98 ] print(min(y)) print(max(y)) 空リスト/空タプルの判定 空リスト、空タプルの判定は偽になります。 EX z = [] if z: print("True") else: print("False") z = [] if not z: print("True") else: print("False") 値比較演算子による大小関係および等価性の判定 リストどうし/タプルどうしの大小関係および等価性の判定は、値比較演算子によって行えます。 次に示すのはいずれも真となる判定の例。 真となる判定の例 [1, 2, 3] == [1, 2, 3] [1, 2, 3] < [1, 2, 4] [1, 2, 3, 4] <= [1, 2, 3, 4] [1, 2, 3] < [1, 2, 3, 5] [1, 2, 3] < [1, 2, 3, 5] < [1, 2, 3, 5, 6] インデックスが同じところどうしで判定が行われていき、条件が合えばTrueを返すようですね。 リストとタプルの共通点と相違点 リストとタプルの相違点は次の表のようになるようです。 Table 2C-1 性質/機能 リスト タプル ミュータブル 〇 × 辞書のキーとして利用できる × 〇 イテラブルである 〇 〇 帰属性演算子in演算子/not in 演算子 〇 〇 加算演算子+による連結 〇 〇 乗算演算子*による繰り返し 〇 〇 累算演算子+=による連結代入 〇 △ 累算*=による繰返し代入 〇 △ インデックス式 〇 △ スライス式 〇 △ len関数による要素数取得 〇 〇 min関数/max関数による最小値/最大値 〇 〇 sum関数による合計値 〇 〇 indexメソッドによる探索 〇 〇 countメソッドによる出現回数 〇 〇 del文による要素の削除 〇 × appendメソッドによる出現回数 〇 × clearメソッドによる全要素の削除 〇 × copyメソッドによるコピー 〇 × extendメソッドによる拡張 〇 × insertメソッドによる要素の挿入 〇 × popメソッドによる要素の取り出し 〇 × removeメソッドによる指定値の削除 〇 × reverseメソッドによるインプレースな反転 〇 × 内包表記による生成 〇 × コラム2-3へ続く・・・ 以上で2章1節のお話が終わります。リストとタプルの使い分けとかいまいちわからんなあと思っているので、追々理解できたらいいなと思います。 「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#6 2-2配列 いよいよリストやらタプルやらの使い方がわかりそうな予感がします。 今まで、どうやって活用すればいいのかわかっていなかったので楽しみです。 配列の要素の最大値を求める 最初に、配列の要素の最大値を求める手続きを考えるようです。 配列aの要素が4個として、三つの要素a[0], a[1], a[2], a[3]の最大値を、以下のプログラムで求めます。 a = [22, 57, 11, 91] maximum = a[0] if a[1] > maximum: maximum = a[1] if a[2] > maximum: maximum = a[2] #要素数が3であればif文を2回実行 if a[3] > maximum: maximum = a[3] #要素数が4であればif文を3回実行 maximum まず、配列の要素数とは無関係に、先頭要素a[0]の値をmaximumに代入する作業を行い、その後if文を実行する過程で必要に応じてmaximumの値を更新します。 要素数がnであれば、if文の実行は、n-1回必要で、その際、maximumとの比較やmaximumへの代入の対象となる添字(インデックスのことで、以後統一)は、1,2,・・・と増えていきます。 そのため、配列aの要素の最大値を求めるアルゴリズムのフローチャートは次のようになります。 このアルゴリズムに基づいて、配列aの要素の最大値を求める関数の関数定義と、その関数によって最大値を求める様子を配列の要素数が5としたとき def max_of(a): maximum = a[0] for i in range(1, len(a)): if a[i] > maxmum: maximum = a[i] という関数を定義し、最大値を求めることができます。 仮に[22, 57, 11, 91, 32]という配列があったとき 22>57 → 57 57>11 → 57 57>91 → 91 91>32 → 91 となことがわかります。 リストの表記(s[]←こういうやつ)に慣れが必要だなと感じますが、今回の記事を通して、少しでも慣れることがで切ればいいなと思います。(記号が多いと頭の整理が必要ですよね。) (余談ですが本書の流れに沿ている都合で関数定義をしています。) 配列の要素の最大値を求める関数の実装 関数の”実装”というとなんか難しい感じがしますが、関数を定義して使うという解釈でいいのかな? list2-2 #シーケンスの要素の最大値を表示する from typing import Any,Sequence #max_ofの定義 def max_of(a: Sequence) -> Any: '''シーケンスaの要素の最大値を表示する''' maximum = a[0] for i in range(1, len(a)): if a[i] > maximum: maximum = a[i] return maximum #関数max_ofをテストする if __name__ == '__main__': print('配列の最大値を求めます。') num = int(input('要素数:')) x = [None] * num #要素数numのリストを生成 for i in range(num): x[i] = int(input(f'x[{i}]:')) print(f'最大値は{max_of(x)}です。') 急に複雑になるやつ…。とりあえず定義のところは分かりますが、急に->みたいな記号使われると頭がパニックです。 意味としてはaのシークエンスの型はなんでも対応します的なニュアンスでいいですかね? ということで次で解説してくれています。 アノテーションと型ヒント まずは、プログラムの先頭行 from typing import Any,Sequence に着目します。 このインポートによって、AnyとSequenceが単純名で利用できるようになるそうです。 Anyは制約のない(任意の)型であることを表し、Squenceはシーケンス型を表します。 シーケンス型にはlist型,bytearray型,str型,tupple型,bytes型がある(つまり連続的なイメージでいいですか?)ため、関数max_ofの関数頭部のアノテーション(特定のデータに対して情報タグ(メタデータ)を付加することらしい【出典】デジタル大辞泉)は次の表明を行うことになります。 ・受け取る仮引数aの型として、シーケンス型であることを期待する。 ・返却するのは任意の型である。 以上より関数max_ofは次のような特性を持ちます。 ・関数内では、配列aの要素の値は変更しない。 ・呼び出し側が与える実引数の型は、変更可能なリスト、変更不能なタプルや文字列など、シーケンスであれば何でもよい。 ・呼び出し側が与えるシーケンスの要素としては、値比較演算子>で比較可能でさえあれば、異なる型(int型float型)が混在してもよい。 ・最大の値の要素を返却する(最大の値の要素がint型の要素であればint型の値を返却し、float型の要素であればfloat型を返却する。) なお、関数内で要素の値を変更する配列型の仮引数に対するアノテーションは、Sequenceではなく、MutableSequenceとしなければならない。 ※その場合、実引数として、ミュータブルリストは与えられるが、イミュータブルな文字列型、タプル型、バイト列型の実引数は与えられなくなる。 文字ばかりでわかりづらいですが、つまるところいろんな型に対応できますよということですかね? 再利用可能なモジュールの構築 pythonでは単一のスクリプトプログラムがモジュールとなります。 拡張子.pyを含まないファイル名がそのままモジュール名となるため、本プログラムのモジュール名はmaxになります。 これ、関数の勉強してたときなんかに既成ファイルから引っ張れるぜってところまでは知っていたのですが、自分で作れるんだあと思って夢が広がりました。 じゃあライブラリとかも最初に作った人がいてくれたおかげで楽な記述ができるんだと思うと頭が上がらないですね。(ほかの言語とかもやっぱりそういうのあるのかな?) さて、本プログラム後半のif文では__name__と'__main__'の等価性が判定されています。 左オペランドの__name__はモジュールの名前を表す変数であり、次のように決定されます。 スクリプトプログラムが: ・直接実行されたとき 変数__name__は'__main__' ・インポートされたとき 変数__name__は本来のモジュール名(今回の場合はmax) pythonはすべてをオブジェクトとみなすため、モジュールもオブジェクトとなります。(初耳ですわ) モジュールは、別のプログラムから初めてインポートされたタイミングで、そのモジュールオブジェクトが生成・初期化される仕組みになっています。 そのため、プログラム後半のif文の判定は、'max.py'を直接起動したときのみ真とみなされif __name__ == '__main__':以下のコードが実行されるそうです。つまりほかのファイルから読み込んだときは動かないということですね。(実装イメージがまだできないけど) ほかのスクリプトプログラムからインポートされたときは偽とみなされるためif __name__ == '__main__':以下は実行されない。 ※モジュールオブジェクトの中には、__name__のほかにも、__loader__、___package__、__spec__、__path__、__file__などの変数(属性)が入っている。 急に出てきて!?ってなりますが、ちゃんと抑えれば怖くないですね。とりあえず便利な機能程度に今は覚えておきます…。() モジュールのテスト list2-2のモジュール'max'で定義された関数'max_of'をほかのスクリプトプログラムから呼び出してみる。 値の読み込み時に要素数を決定する list2-3はキーボードからint型の整数値を次々と読み込んでいき、終了が指示されたら('End'と入力されたら)読み込みを終了する(その時点で要素数が確定する)ように実現したプログラムです。 ※本プログラムを含め、関数'max_of'を呼び出すプログラムは、'max.py'と同一フォルダに格納する必要がある。 list2-3 #max_of_test.py #配列の要素の最大値を求めて表示(要素の値を読み込む) from max import max_of print('配列の最大値を求めます。') print('注:"End"で入力終了。') number = 0 x = [] #空リスト while True: s = input(f'x[{number}]:') if s == 'End': break x.append(int(s)) #末尾に追加 number += 1 print(f'{number}個読み込みました。') print(f'最大値は{max_of(x)}です。') 'from max import max_of'はモジュールmaxで定義されている'max_of'を単純名で利用できるようにするためのimport文。 プログラムでは、まず最初にリストxを、空の配列(空リスト)として生成しています。 while文は無限ループであり、次々と文字列を読み込む。読み込んだ文字列がEndであれば、break文の働きによってwhile文を強制終了する。 読み込んだ文字列sが'End'出ない場合は読み込んだ(文字列sをint関数によって変換した)整数値を、配列xの末尾に追加する。 変数numberは0で初期化され、整数値を読み込むたびにインクリメントされるため、読み込んだ整数値の個数(配列xの要素数と一致する)が保持される。 ※インポートするモジュール'max.py'の'max_of'以外は__name__ == '__main__'が成立しないためif __name__ == '__main__':以下の文は実行されない。(アンダーバーが消えてるので注意。出し方わからんです。) 配列の要素の値を乱数で決定する 配列の要素はキーボードから読み込み、全要素の値は乱数で決定する。 list2-4 #配列の要素の最大値を求めて表示(要素の値を乱数で生成) import random from max import max_of print('乱数の最大値を求める') num = int(input('乱数の個数:')) lo = int(input('乱数の下限:')) hi = int(input('乱数の上限:')) x = [None] * num #要素数numのリストを生成 for i in range(num): x[i] = random.randint(lo, hi) print(f'{(x)}') print(f'最大値は{max_of(x)}です。') ここで、「おー」と思ったのは空リストの生成ですね。Noneとかなかなかうまく使えないので、こういう例があると助かります。 タプルの最大値/文字列の最大値/文字列のリストの最大値を求める list2-5はタプルの最大値、文字列(内の文字)の最大値/文字列のリストの最大値を求めるプログラム list2-5 #配列の要素の最大値を求めて表示(タプル/文字列/文字列のリスト) from max import max_of t = (4, 7, 5.6, 2, 3.14, 1) s = 'string' a = ['DTS', 'AAC', 'FLAC'] print(f'{t}の最大値は{max_of(t)}です。')#7 print(f'{s}の最大値は{max_of(s)}です。')#t print(f'{a}の最大値は{max_of(a)}です。')#FLAC 文字列のみの場合は文字コード、文字列リストの場合は文字列の数で判定される…。なんか面白いことできそうな…。?? list2-5(max/min) #配列の要素の最大値を求めて表示(タプル/文字列/文字列のリスト)max,min関数を使用 t = (4, 7, 5.6, 2, 3.14, 1) s = 'string' a = ['DTS', 'AAC', 'FLAC'] #最大値 print(f'{t}の最大値は{max(t)}です。')#7 print(f'{s}の最大値は{max(s)}です。')#t print(f'{a}の最大値は{max(a)}です。')#FLAC #最小値 print(f'{t}の最小値は{min(t)}です。')#1 print(f'{s}の最小値は{min(s)}です。')#g print(f'{a}の最小値は{min(a)}です。')#AAC(文字列の数、文字コードの順で検索される) コラム2-3 リストとタプル(その2) 前回出てきたコラムの続きですね。 別々に生成されたリスト/タプルの同一性 別々に生成されたリストはすべての要素が同じ値をもっていても、別の実体を持つ。 list1 = [1,1,1] list2 = [1,1,1] print(list1 is list2) print(list1 is list1) [1,1,1]は[]演算子によって新しいリストを生成する式であり、いわゆるリテラル(直接記述される数値や文字列)ではない。 つまり、式であることを認識できていれば大丈夫ですかね。なんか前にちらっと触れていた気もしますが忘れたのでまた復習します。 リスト/タプルの代入 リスト(を参照している変数)を代入しても、値でなく参照がコピーされるため、要素自体(要素の並び)はコピーされない。 参照の確認 lst1 = [1,1,1] lst2 = lst1 print(lst1 is lst2) lst1[2] = 9 print(lst1) print(lst2)#lst1を参照している。 is関数なんてあるんですね。 Excelなんかでも真偽判定の重要性は思い知ったので、こういう使い方できる情報はありがたいです。 リストの走査 リストを走査する4種類のプログラムを下記に示してます。 list2C-1 #リストの全要素を走査(要素数を事前に取得) x = ['john', 'geoge', 'paul', 'ringo']#[]を()にするとタプルになる for i in range(len(x)): print(f'x[{i}] = {x[i]}') print('_'*13) list2C-2 #リストの全要素をenumerate関数で走査 x = ['john', 'geoge', 'paul', 'ringo'] for i,name in enumerate(x): print(f'x[{i}] = {name}') print('_'*13) i, nameを使うことで、enumerate関数を使って同じ出力ができます。 list2C-3 #リストの全要素をenumerate関数で走査(1からカウント) x = ['john', 'geoge', 'paul', 'ringo'] for i,name in enumerate(x, 1): print(f'{i}番目 = {name}') print('_'*13) enumerate(x, 1)とすることで、カウントの開始を1にします。(0~3が1~4に代わる) list2C-4 #リストの全要素を走査(インデックス値を使わない) x = ['john', 'geoge', 'paul', 'ringo'] for i in x: print(i) また、インデックスの値が不要な場合は簡素に書くこともできます。 ・イテラブル 文字列、リスト、タプル、集合、辞書などの型をもつオブジェクトは、いずれもイテラブル=反復可能である'という共通点があります。 イテラブルオブジェクト(リストなど)を組み込み関数であるiter関数に引数として与えるとそのオブジェクトに対するイテレータが返却されるそうです。 イテレータ:データの並びを表現するオブジェクト。 イテレータの__next__メソッドを呼び出すか、組み込み関数であるnext関数にイテレータを与えるとその並びの要素が順次取り出される。取り出すべき要素が尽きた場合はStopIteration例外が送出されるとのこと。 iter/next関数の例 x = ['john', 'geoge', 'paul', 'ringo'] t = iter(x) while True: #Trueの間繰り返す try: #例外処理(例外が発生したときに処理する) print(next(t)) except StopIteration: #except エラー名 break #StopIterationが出たらループ終了 エラーが出たときに処理が行われるという。便利そうなやつですね。 「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#7 配列の要素の並びを反転する 配列の要素の並びを反転するプログラムを考えます。 [1, 2, 3, 4, 5, 6, 7]という配列があるとすると 1,7 2,6 3,5 のペアをそれぞれ入れ替えることを考えると次のようなアルゴリズムになります。 for i in range(n//2): a[i](左側の要素の添字)とa[n-i-1](右側の要素の添字)の値を交換します。 list2-6 #ミュータブルなシーケンスの要素の並びを反転 from typing import Any, MutableSequence def reverse_array(a: MutableSequence) -> None: '''ミュータブルなシーケンスaの要素の並びを反転''' n = len(a) for i in range(n // 2): a[i], a[n - i - 1] = a[n - i - 1], a[i] if __name__ == '__main__': print('配列の並びを反転します。') nx = int(input('要素数は:')) x = [None] * nx #要素nのリストを生成 for i in range(nx): x[i] = int(input(f'x[{i}]:')) reverse_array(x) #xの並びを反転 print('配列の要素の並びを反転しました') for i in range(nx): print(f'x[{i}] = {x[i]}') 反転とか並べ替えとは便利な気はしますが具体的にどのへんで使うんだろうという感じがしてイメージがわかないです…。(未熟故…。) コラム 2-4リストの反転 先ほどは関数を定義していましたが、メソッドや関数も存在しているみたいです ・リストの反転(標準ライブラリ) list型のreverseメソッドはインプレース(定位置)に反転する 使用例:x.reverse() ・反転したリストの生成(reversed関数) 使用例:y = list(reversed(x)) 基数変換 10進数を2進数にしたりするやつですね。 0~9、A~Zを使って最大36進数まで作れるみたいです。 コラム2-5基数について a**nという式があるとすれば、aが基数、n+1が桁数になる。 例)1234が10進数の場合 1234 = 1 * (10**3) + 2 * (10**2) + 3 * (10**1) + 4 * (10**0) 基数変換を行うプログラム list2-7では基数変換を行うプログラムを下記に記述します。 list2-7 #読み込んだ10進数を2進数から36進数に基数変換して表示 def card_conv(x: int, r: int) -> str: '''整数値xをr進数に変換した数値を表す文字列を返却''' d = '' #変換後の文字列 dchar = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' n = len(str(x)) #変換前の桁数 print(f'{r:2} | {x:{n}d}') while x > 0: d += dchar[x % r] #該当文字を取り出して連結 x //= r return d[::-1] #反転して返却 if __name__ == '__main__': print('10進数を基数変換します。') while True: while True: no = int(input('変換する非負の整数:')) #非負の整数値を読み込む if no > 0: break while True: cd = int(input('何進数に変換しますか(2-36):')) if 2 <= cd <= 36: break print(f'{cd}進数では{card_conv(no, cd)}です。') retry = input('もう一度しますか(Y…はい/N…いいえ)') if retry in {'N', 'n'}: break 多重ループが出てきましたね。2 <= cd <= 36のあたりも参考になります( ..)φメモメモ ちょっと前にやったd[::-1]という表記もありますね。 だいぶ慣れてきた気がします。 さて、このままでは関数内の動きがいまいち見えてこないため、以下のように変更します。 list2-7(基数変換の過程を表示する) #読み込んだ10進数を2進数から36進数に基数変換して表示 def card_conv(x: int, r: int) -> str: #今回では、xはno,rはcdを参照する '''整数値xをr進数に変換した数値を表す文字列を返却''' d = '' #変換後の文字列 dchar = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' #文字列36個 n = len(str(x)) #変換前の桁数  print(f'{r:2} | {x:{n}d}') #f{'r:2(rを2桁で表示)|{x}'} while x > 0: #非負の値の間実行 print(' +' + (n + 2) * '-') #' +'と'-*(n+2)個'を表示 if x // r: #xをrで割ることができる場合(最後以外の段の線) print(f'{r:2} | {x // r:{n}d} … {x % r}') #f'{r:2} (rを2桁で表示)| {x // r:{n}d(x//rをn桁の10進数で表示)} … {x % r}(x/rの余り)' else: #xをrで割ることができない場合(最後の段の線) print(f' {x // r:{n}d} … {x % r}') #f' (空白){x // r:{n}d}(x//rをn桁の10進数で表示) … {x % r}(x/rの余り)' d += dchar[x % r] #該当文字を取り出して連結(0~Zまでの値)(d = d + dchar[x % r]と同じ) x //= r #x = x // rと同じ return d[::-1] #反転して返却 if __name__ == '__main__': #このファイルを動かすときのみ実行 print('10進数を基数変換します。') while True: #Trueの間実行 while True: #Trueの間実行 no = int(input('変換する非負の整数:')) #非負の整数値を読み込む if no > 0: #読み込んだ数字が非負の整数であれば break #ループを抜ける while True: #Trueの間実行 cd = int(input('何進数に変換しますか(2-36):')) #数字を読み込む if 2 <= cd <= 36: #2から36までの数字であれば break #ループを抜ける print(f'{cd}進数では{card_conv(no, cd)}です。')#cdと関数card_conv()に数値が代入される retry = input('もう一度しますか(Y…はい/N…いいえ)') if retry in {'N', 'n'}: break 処理付きでの結果が見れるようになりました。 自分なりに整理するために細かくコメントをつけてみました。(間違っていたらすみません。) リストじゃなくて文字列でもリスト的な取出し方ができるんですね。ここでは、簡単な操作しかないからるリストに格納するまでもないということですかね? だいぶ表記も増えたので、頭がこんがらがりそうですが、一文ずつ見ていけば理解できますね。 コラム2-6 関数間の引数の受け渡し 関数が受け取る仮引数と、呼び出す側が与える実引数について、list2C-5で考えてみます。 xの参照先に注目してみます。 list2C-5 #1からnまでの総和を求める(3を入れた時を想定してコメントを入れます) def sum_1ton(n):#n = 3を代入してみる """1からnまでの整数の総和を求める""" s = 0 while n > 0:#3>0(True)→2>0(true)→1>0(True)→0>0(False) s += n#0+3→3+2→5+1→6(終了) n -= 1#3-1→2-1→1-1→0(終了) return s#3→5→6(終了) x = int(input('xの値:'))#3を入れる print(f'1から{x}までの総和は{sum_1ton(x)}です。')#1から3までの総和は6です。 list2C-5の場合、仮引数のnの値が3→2→1…と減っていきます。(最後は0になる) 関数sum_1tonに与えている実引数はxです。実行例の場合関数から戻って来た後に「1から3までの総和は6です」と返ってくるため、変数xの値は3であることが確認できます。この時、仮引数nに実引数xの値がコピーされているのではなく仮引数nに実引数xが代入されているそうです。これは代入でコピーされるものが値ではなく参照先のため、nとxの参照先が同じになるんですね。 つまり、関数実行時にはn, x = 3, 3であるのに対して、関数終了時にはn, x = 0, 3となります。(3自体がイミュータブルなオブジェクトのため、仮引数の参照先が変化します) 次はミュータブルオブジェクトの例です。 list2C-6 #リストの任意の要素の値を更新する(ここではindex, value = 2, 99を想定します) def change(lst, idx, val):#change([11,22,33,44,55]xを参照, 2, 99) """lst[idx]の値をvalに更新""" lst[idx] = val #x[2]なので33の位置に99が入り、print関数で出力される x = [11, 22, 33, 44, 55] print('x =', x) index = int(input('インデックス:'))#2を入力 value = int(input('新しい値  :'))#99を入力 change(x, index, value)#change([11,22,33,44,55], 2, 99) print(f'x = {x}')#変更後のxが参照される([11,22,99,44,55]) list2C-5ではxの参照先が3で固定されていたのに対して、list2C-6ではxの参照先が[11,22,33,44,55]から[11,22,99,44,55]に変更されました。 list2C-6の仮引数はlstにあたり、lstとxが同じ参照先になっています。 つまり、lst, x =[11,22,99,44,55], [11,22,99,44,55]で同様の参照先であることがわかりますね。 少しややこしい話な気はしますが、私の解釈ではint型(1などの数字)とlist型(任意で決定するもの)はオブジェクトとして同列に扱われるとなのではないかと思っています。(訂正があればコメントお願いします) 「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#8 素数の列挙 素数を列挙するプログラムを考える。 素数は2から始まり、n個まで列挙するのであれば2からn-1までで割り切れないものを見つければ可能です。 最初は効率を無視して仕組みを考えます。 list2-8 #1000以下の素数を列挙(第1版) counter = 0 #counterの参照を0 (どれくらいの計算量になるか確認するため) for n in range(2, 1001): #2から1001まででループ for i in range(2, n): #2からnまででループ counter += 1 #counterに1を追加して参照先を変更 if n % i == 0: #割り切れると素数ではない break #それ以上の繰り返しは不要 else: #最後まで割り切れなかったら下記を実行(elseはfor文の処理後に行われるもの。今回はbreakがあるためbreakされたらelseは処理されない) print(n) #割り切れなかった数字を表示 print(f'除算を行った回数:{counter}') 2重ループを使っていますね。 気を付けたい点はelseの部分がforに対して並列になっているところでしょうか。 if文の処理は割り切れる数が出た時点で処理を終了して次のループをします。 list2-8では除算の回数が多いため計算回数を減らすプログラムを考えます。 list2-9 #1000以下の素数を列挙(第2版) counter = 0 #除算の回数 ptr = 0 #得られた素数の個数 prime = [None] * 500 #素数を格納する配列(リストを作成) prime[ptr] = 2 #2は素数である(リストの0番目に2を格納する) ptr += 1 #(リストの添字を1増やすイメージ) for n in range(3, 1001, 2): #対象は奇数のみ(3から1001までの数字を+2ずつ繰り返す) for i in range(1, ptr): #既に得られた素数で割ってみる(1から添字の数だけ繰り返し処理) counter += 1 if n % prime[i] == 0: # break else: prime[ptr] = n #素数として配列に登録 ptr += 1 for i in range(ptr): print(prime[i]) print(f'除算を行った回数:{counter}') 更に、改良を考えます list2-10 #1000以下の素数を列挙 counter = 0 #乗除算の回数 ptr = 0 #得られた素数の個数 prime = [None] * 500 #素数を格納する配列 prime[ptr] = 2 #2は素数である ptr += 1 #(参照する格納先リストの添字を一つ増やす) prime[ptr] = 3 #3は素数である ptr += 1 #(参照する格納先リストの添字を一つ増やす) for n in range(5, 1001, 2): #対象は奇数のみ i = 1 #iは1を参照する while prime[i] * prime[i] <= n: #prime[i]×prime[i]以下のnを検討する(Falseの時はelseに行く) counter += 2#prime[i]*prime[i]とn%prime[i]の計算をカウント if n % prime[i] == 0: #割り切れると素数ではない break#それ以上の繰り返しは不要 i += 1 #(リスト(prime)の参照する添字を1つ増やす) else: #最後まで割り切れなかったら prime[ptr] = n#素数として配列に登録 ptr += 1 ##参照する格納先リストの添字を一つ増やす) counter += 1 #(whileの条件部分の計算に対するカウント) for i in range(ptr): #求めたptr個の素数を表示 print(prime[i]) print(f'乗除算を行った回数:{counter}') 今回はi×iがn以下の時にループするプログラムになっています。 prime[i]×prime[i]の説明として以下に記述します。 例として100の約数(i×n)で考えると 10×10以降の数字は10×10以前の約数と一致した計算になります。 つまりi×iがn以下の時に割り算をすればよいということになります。 一見するとprime[i]という表記に抵抗出てしまうのですが、一つずつ考えれば納得ですね。 コラム2-7リストの要素とコピー ここでは、リストについての参照とコピーについて書かれています。 リストの中にリストを作成している場合、階層というものが存在するという話だと思います。 list2C-7 #リストの要素の型が揃う必要がないことを確認 x = [15, 64, 7, 3.14, [32, 55],'ABC'] for i in range(len(x)): print(f'x[{i}] = {x[i]}') #リストはcopyメソッドによってコピーできるがリストを要素として持つリストのコピーはうまく行えない x = [[1, 2, 3],[4, 5, 6]] y = x.copy() #コピーのアクセス対象が[1,2,3][4,5,6](list型オブジェクト)になっている x[0][1] = 9 print(x) print(y) #シャロ―コピーが原因でyにも変更が反映される 上記の事態を避けるためには構成要素のレベルでコピーが必要でディープコピーと呼ぶ。copyモジュール内のdeepcopy関数を使用する。 変更版 import copy x = [[1, 2, 3],[4, 5, 6]] y = copy.deepcopy(x)#コピーのアクセス対象が1,2,3,4,5,6(int型オブジェクト)になり、リストを形成している x[0][1] = 9 print(x) print(y) 参照先の深さをイメージすると理解が進みそう。 シャロ―コピーでは、リストを参照している状態で、yの参照先がxのリストオブジェクト自体を参照先としているため、xを変更したときでもyはxのリストを参照してしまうため変更してしまいます。ディープコピーでは、数(int型でイミュータブルなオブジェクト)自体を参照する状態になっているため、xのリストが変更されてもyの参照先(リスト内の数)が変更することはないと考えればいいですかね。 以上で2章が終了です。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「新・明解Pythonで学ぶアルゴリズムとデータ構造」で勉強日記#8

【出典】「新・明解Pythonで学ぶアルゴリズムとデータ構造」 前回の記事はこちら 素数の列挙 素数を列挙するプログラムを考える。 素数は2から始まり、n個まで列挙するのであれば2からn-1までで割り切れないものを見つければ可能です。 最初は効率を無視して仕組みを考えます。 list2-8 #1000以下の素数を列挙(第1版) counter = 0 #counterの参照を0 (どれくらいの計算量になるか確認するため) for n in range(2, 1001): #2から1001まででループ for i in range(2, n): #2からnまででループ counter += 1 #counterに1を追加して参照先を変更 if n % i == 0: #割り切れると素数ではない break #それ以上の繰り返しは不要 else: #最後まで割り切れなかったら下記を実行(elseはfor文の処理後に行われるもの。今回はbreakがあるためbreakされたらelseは処理されない) print(n) #割り切れなかった数字を表示 print(f'除算を行った回数:{counter}') 2重ループを使っていますね。 気を付けたい点はelseの部分がforに対して並列になっているところでしょうか。 if文の処理は割り切れる数が出た時点で処理を終了して次のループをします。 list2-8では除算の回数が多いため計算回数を減らすプログラムを考えます。 list2-9 #1000以下の素数を列挙(第2版) counter = 0 #除算の回数 ptr = 0 #得られた素数の個数 prime = [None] * 500 #素数を格納する配列(リストを作成) prime[ptr] = 2 #2は素数である(リストの0番目に2を格納する) ptr += 1 #(リストの添字を1増やすイメージ) for n in range(3, 1001, 2): #対象は奇数のみ(3から1001までの数字を+2ずつ繰り返す) for i in range(1, ptr): #既に得られた素数で割ってみる(1から添字の数だけ繰り返し処理) counter += 1 if n % prime[i] == 0: # break else: prime[ptr] = n #素数として配列に登録 ptr += 1 for i in range(ptr): print(prime[i]) print(f'除算を行った回数:{counter}') 更に、改良を考えます list2-10 #1000以下の素数を列挙 counter = 0 #乗除算の回数 ptr = 0 #得られた素数の個数 prime = [None] * 500 #素数を格納する配列 prime[ptr] = 2 #2は素数である ptr += 1 #(参照する格納先リストの添字を一つ増やす) prime[ptr] = 3 #3は素数である ptr += 1 #(参照する格納先リストの添字を一つ増やす) for n in range(5, 1001, 2): #対象は奇数のみ i = 1 #iは1を参照する while prime[i] * prime[i] <= n: #prime[i]×prime[i]以下のnを検討する(Falseの時はelseに行く) counter += 2#prime[i]*prime[i]とn%prime[i]の計算をカウント if n % prime[i] == 0: #割り切れると素数ではない break#それ以上の繰り返しは不要 i += 1 #(リスト(prime)の参照する添字を1つ増やす) else: #最後まで割り切れなかったら prime[ptr] = n#素数として配列に登録 ptr += 1 ##参照する格納先リストの添字を一つ増やす) counter += 1 #(whileの条件部分の計算に対するカウント) for i in range(ptr): #求めたptr個の素数を表示 print(prime[i]) print(f'乗除算を行った回数:{counter}') 今回はi×iがn以下の時にループするプログラムになっています。 prime[i]×prime[i]の説明として以下に記述します。 例として100の約数(i×n)で考えると 10×10以降の数字は10×10以前の約数と一致した計算になります。 つまりi×iがn以下の時に割り算をすればよいということになります。 一見するとprime[i]という表記に抵抗出てしまうのですが、一つずつ考えれば納得ですね。 コラム2-7リストの要素とコピー ここでは、リストについての参照とコピーについて書かれています。 リストの中にリストを作成している場合、階層というものが存在するという話だと思います。 list2C-7 #リストの要素の型が揃う必要がないことを確認 x = [15, 64, 7, 3.14, [32, 55],'ABC'] for i in range(len(x)): print(f'x[{i}] = {x[i]}') #リストはcopyメソッドによってコピーできるがリストを要素として持つリストのコピーはうまく行えない x = [[1, 2, 3],[4, 5, 6]] y = x.copy() #コピーのアクセス対象が[1,2,3][4,5,6](list型オブジェクト)になっている x[0][1] = 9 print(x) print(y) #シャロ―コピーが原因でyにも変更が反映される 上記の事態を避けるためには構成要素のレベルでコピーが必要でディープコピーと呼ぶ。copyモジュール内のdeepcopy関数を使用する。 変更版 import copy x = [[1, 2, 3],[4, 5, 6]] y = copy.deepcopy(x)#コピーのアクセス対象が1,2,3,4,5,6(int型オブジェクト)になり、リストを形成している x[0][1] = 9 print(x) print(y) 参照先の深さをイメージすると理解が進みそう。 シャロ―コピーでは、リストを参照している状態で、yの参照先がxのリストオブジェクト自体を参照先としているため、xを変更したときでもyはxのリストを参照してしまうため変更してしまいます。ディープコピーでは、数(int型でイミュータブルなオブジェクト)自体を参照する状態になっているため、xのリストが変更されてもyの参照先(リスト内の数)が変更することはないと考えればいいですかね。 以上で2章が終了です。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python による画像生成で Mac のクソダサデスクトップにさよなら

前置き Mac使いの方。 マルチデスクトップを駆使していろんな作業を同時並行で進めていると、苦労することがありませんか……? どのデスクトップでどの作業をしているのか分からなくなる。 付箋アプリで作業内容を貼っておくのも鬱陶しい。 かっこいいMacの壁紙の情報量が逆に邪魔。 どんな壁紙を使っていてもそのうち飽きてくる。 ここで欲しくなってくるのは、 「任意のテキストが入れられた壁紙をさくっと作る」 プログラムです。そうですね。 というわけで作りました。 要するに、何がやりたいの? こんな壁紙を手軽に作りたいわけです。 一目で何をやりたいかわかるデスクトップ。 マルチデスクトップ間の移動をするときも、どこに行けばいいのか一目で分かります。 そしてシンプルでかっこいい。飽きてもすぐに配色が変えられる。 環境整備 まずは必要ライブラリをダウンロード。 pip3 install pillow pip3 install pyautogui pip3 install opencv-python これらがインストールできたら、下記(一番下の項目『プログラム本文』にあります)の python コード run.py を叩いて実行できます。ただし、フォントが適切に指定されている必要があるので注意。 python3 run.py また、下記の color.py が同じフォルダに入っている必要があります。 使い方 まず run.py 中、利用フォントを適切に設定します。 font_dir = '/Users/(your username)/Library/Fonts/' font_file = 'sawarabi-gothic/sawarabi-gothic-medium.ttf' この箇所を好きなフォントファイルに指定してください。 そうしたら、次はどんな文字を何色で入れるかです。 out_file_name = "desktop_01.png" string_txt = "デスクトップ01" rgb_txt=find_color("茜色").rgb rgb_bg=find_color("LightSalmon").rgb 改行を入れたければ\nで入れてください。 これでほぼ全てです。 配色 毎回実行の度に利用可能な色一覧が表示されますが、それでも配色の妙が一目で分からないのが目下の課題。とりあえず、アリな色の組み合わせを記載しておきます。 ウォームピンク どんな気分にも馴染む rgb_txt=find_color("茜色").rgb rgb_bg=find_color("LightSalmon").rgb 空色 ツイッターっぽさ rgb_txt=find_color("White").rgb rgb_bg=find_color("DeepSkyBlue").rgb オリーブグリーン 落ち着いた雰囲気に rgb_txt=find_color("DarkOliveGreen").rgb rgb_bg=find_color("OliveDrab").rgb 濃い紫にベージュ デザイナーチックな色合い rgb_txt=find_color("Beige").rgb rgb_bg=find_color("DarkMagenta").rgb 紺染め 和風の佇まい rgb_txt=find_color("灰汁色").rgb rgb_bg=find_color("紺色").rgb ビタミンカラー ビビッドでハイセンス rgb_txt=find_color("Tomato").rgb rgb_bg=find_color("Yellow").rgb その他 デスクトップの順番が入れ替わると不便なので入れ替え機能は外しておきましょう。 「システム環境設定>デスクトップとスクリーンセーバー」のフォルダの項目に、生成画像が入っているフォルダを追加しておいた方がいいようです。 カラーコードの一覧は以下のサイトを参考にさせていただきました。 このページの記載内容について、改変して公開などはご自由に。 プログラム本文 run.py import pyautogui as pag from PIL import Image, ImageFont, ImageDraw import cv2 import numpy as np from color import * out_file_name = "desktop_06.png" string_txt = "デスクトップ06" rgb_txt=find_color("Tomato").rgb rgb_bg=find_color("Yellow").rgb print("Available colors:") show_color_name() #利用可能な色名一覧を表示 rate_font_size = 0.1 rate_pos_txt = [0.05, 0.05] font_dir = '/Users/(your username)/Library/Fonts/' font_file = 'sawarabi-gothic/sawarabi-gothic-medium.ttf' screen = pag.size() print(" screen width = ",screen.width) print("screen height = ",screen.height) img = np.zeros((screen.height, screen.width, 3), np.uint8) for i in range(0,3): img[:, :, i] = rgb_bg[2-i] if ( font_dir == "" ): font_dir = "/System/Library/Fonts/" font_path = font_dir + font_file font_size = int(rate_font_size*screen.width) font = ImageFont.truetype(font_path, font_size) img = Image.fromarray(img) # cv2(NumPy)型の画像をPIL型に変換 draw = ImageDraw.Draw(img) pos_txt = ( int(screen.height*rate_pos_txt[1]),int(screen.width*rate_pos_txt[0]) ) color_txt =(rgb_txt[2],rgb_txt[1],rgb_txt[0],0) draw.text(pos_txt, string_txt, font=font, fill=color_txt) img = np.array(img) # PIL型の画像をcv2(NumPy)型に変換 cv2.imwrite(out_file_name, img) color.py def rgb_to_colorcode(rgb): red_str=str(hex(rgb[0])) red_str=red_str[2:] if len(red_str)==1: red_str="0"+red_str green_str=str(hex(rgb[1])) green_str=green_str[2:] if len(green_str)==1: green_str="0"+green_str blue_str=str(hex(rgb[2])) blue_str=blue_str[2:] if len(blue_str)==1: blue_str="0"+blue_str colorcode="#"+red_str+green_str+blue_str return colorcode class Color: def __init__(self, name, rgb): self.name = name self.rgb = rgb self.r = rgb[0] self.g = rgb[1] self.b = rgb[2] self.code = rgb_to_colorcode(rgb) def color_table_full(): color_table=[] color_table.append(Color("IndianRed",(205,92,92))) color_table.append(Color("LightCoral",(240,128,128))) color_table.append(Color("Salmon",(250,128,114))) color_table.append(Color("DarkSalmon",(233,150,122))) color_table.append(Color("LightSalmon",(255,160,122))) color_table.append(Color("Crimson",(220,20,60))) color_table.append(Color("Red",(255,0,0))) color_table.append(Color("FireBrick",(178,34,34))) color_table.append(Color("DarkRed",(139,0,0))) color_table.append(Color("Pink",(255,192,203))) color_table.append(Color("LightPink",(255,182,193))) color_table.append(Color("HotPink",(255,105,180))) color_table.append(Color("DeepPink",(255,20,147))) color_table.append(Color("MediumVioletRed",(199,21,133))) color_table.append(Color("PaleVioletRed",(219,112,147))) color_table.append(Color("Coral",(255,127,80))) color_table.append(Color("Tomato",(255,99,71))) color_table.append(Color("OrangeRed",(255,69,0))) color_table.append(Color("DarkOrange",(255,140,0))) color_table.append(Color("Orange",(255,165,0))) color_table.append(Color("Gold",(255,215,0))) color_table.append(Color("Yellow",(255,255,0))) color_table.append(Color("LightYellow",(255,255,224))) color_table.append(Color("LemonChiffon",(255,250,205))) color_table.append(Color("LightGoldenrodYellow",(250,250,210))) color_table.append(Color("PapayaWhip",(255,239,213))) color_table.append(Color("Moccasin",(255,228,181))) color_table.append(Color("PeachPuff",(255,218,185))) color_table.append(Color("PaleGoldenrod",(238,232,170))) color_table.append(Color("Khaki" ,(240,230,140))) color_table.append(Color("DarkKhaki",(189,183,107))) color_table.append(Color("Lavender",(230,230,250))) color_table.append(Color("Thistle",(216,191,216))) color_table.append(Color("Plum",(221,160,221))) color_table.append(Color("Violet" ,(238,130,238))) color_table.append(Color("Orchid",(218,112,214))) color_table.append(Color("Fuchsia",(255,0,255))) color_table.append(Color("Magenta",(255,0,255))) color_table.append(Color("MediumOrchid",(186,85,211))) color_table.append(Color("MediumPurple",(147,112,219))) color_table.append(Color("Amethyst",(153,102,204))) color_table.append(Color("BlueViolet",(138,43,226))) color_table.append(Color("DarkViolet",(148,0,211))) color_table.append(Color("DarkOrchid",(153,50,204))) color_table.append(Color("DarkMagenta",(139,0,139))) color_table.append(Color("Purple",(128,0,128))) color_table.append(Color("Indigo",(75,0,130))) color_table.append(Color("SlateBlue",(106,90,205))) color_table.append(Color("DarkSlateBlue",(72,61,139))) color_table.append(Color("GreenYellow",(173,255,47))) color_table.append(Color("Chartreuse",(127,255,0))) color_table.append(Color("LawnGreen",(124,252,0))) color_table.append(Color("Lime",(0,255,0))) color_table.append(Color("LimeGreen",(50,205,50))) color_table.append(Color("PaleGreen",(152,251,152))) color_table.append(Color("LightGreen",(144,238,144))) color_table.append(Color("MediumSpringGreen",(0,250,154))) color_table.append(Color("SpringGreen",(0,255,127))) color_table.append(Color("MediumSeaGreen",(60,179,113))) color_table.append(Color("SeaGreen",(46,139,87))) color_table.append(Color("ForestGreen",(34,139,34))) color_table.append(Color("Green",(0,128,0))) color_table.append(Color("DarkGreen",(0,100,0))) color_table.append(Color("YellowGreen",(154,205,50))) color_table.append(Color("OliveDrab",(107,142,35))) color_table.append(Color("Olive",(128,128,0))) color_table.append(Color("DarkOliveGreen",(85,107,47))) color_table.append(Color("MediumAquamarine",(102,205,170))) color_table.append(Color("DarkSeaGreen",(143,188,143))) color_table.append(Color("LightSeaGreen",(32,178,170))) color_table.append(Color("DarkCyan",(0,139,139))) color_table.append(Color("Teal",(0,128,128))) color_table.append(Color("Aqua",(0,255,255))) color_table.append(Color("Cyan" ,(0,255,255))) color_table.append(Color("LightCyan",(224,255,255))) color_table.append(Color("PaleTurquoise",(175,238,238))) color_table.append(Color("Aquamarine",(127,255,212))) color_table.append(Color("Turquoise",(64,224,208))) color_table.append(Color("MediumTurquoise",(72,209,204))) color_table.append(Color("DarkTurquoise",(0,206,209))) color_table.append(Color("CadetBlue",(95,158,160))) color_table.append(Color("SteelBlue",(70,130,180))) color_table.append(Color("LightSteelBlue",(176,196,222))) color_table.append(Color("PowderBlue",(176,224,230))) color_table.append(Color("LightBlue",(173,216,230))) color_table.append(Color("SkyBlue",(135,206,235))) color_table.append(Color("LightSkyBlue",(135,206,250))) color_table.append(Color("DeepSkyBlue",(0,191,255))) color_table.append(Color("DodgerBlue",(30,144,255))) color_table.append(Color("CornflowerBlue",(100,149,237))) color_table.append(Color("MediumSlateBlue",(123,104,238))) color_table.append(Color("RoyalBlue",(65,105,225))) color_table.append(Color("Blue",(0,0,255))) color_table.append(Color("MediumBlue",(0,0,205))) color_table.append(Color("DarkBlue",(0,0,139))) color_table.append(Color("Navy",(0,0,128))) color_table.append(Color("MidnightBlue",(25,25,112))) color_table.append(Color("Cornsilk",(255,248,220))) color_table.append(Color("BlanchedAlmond",(255,235,205))) color_table.append(Color("BlanchedAlmond",(255,228,196))) color_table.append(Color("NavajoWhite",(255,222,173))) color_table.append(Color("Wheat",(245,222,179))) color_table.append(Color("BurlyWood",(222,184,135))) color_table.append(Color("Tan",(210,180,140))) color_table.append(Color("RosyBrown",(188,143,143))) color_table.append(Color("SandyBrown",(244,164,96))) color_table.append(Color("Goldenrod",(218,165,32))) color_table.append(Color("DarkGoldenrod",(184,134,11))) color_table.append(Color("Peru",(205,133,63))) color_table.append(Color("Chocolate",(210,105,30))) color_table.append(Color("SaddleBrown",(139,69,19))) color_table.append(Color("Sienna",(160,82,45))) color_table.append(Color("Brown",(165,42,42))) color_table.append(Color("Maroon",(128,0,0))) color_table.append(Color("White",(255,255,255))) color_table.append(Color("Snow",(255,250,250))) color_table.append(Color("Honeydew",(240,255,240))) color_table.append(Color("MintCream",(245,255,250))) color_table.append(Color("Azure",(240,255,255))) color_table.append(Color("AliceBlue",(240,248,255))) color_table.append(Color("GhostWhite",(248,248,255))) color_table.append(Color("WhiteSmoke",(245,245,245))) color_table.append(Color("Seashell",(255,245,238))) color_table.append(Color("Beige",(245,245,220))) color_table.append(Color("OldLace",(253,245,230))) color_table.append(Color("FloralWhite",(255,250,240))) color_table.append(Color("Ivory",(255,255,240))) color_table.append(Color("AntiqueWhite",(250,235,215))) color_table.append(Color("Linen",(250,240,230))) color_table.append(Color("LavenderBlush",(255,240,245))) color_table.append(Color("MistyRose",(255,228,225))) color_table.append(Color("Gainsboro",(220,220,220))) color_table.append(Color("LightGrey",(211,211,211))) color_table.append(Color("Silver",(192,192,192))) color_table.append(Color("DarkGray",(169,169,169))) color_table.append(Color("Gray",(128,128,128))) color_table.append(Color("DimGray",(105,105,105))) color_table.append(Color("LightSlateGray",(119,136,153))) color_table.append(Color("SlateGray",(112,128,144))) color_table.append(Color("DarkSlateGray",(47,79,79))) color_table.append(Color("Black",(0,0,0))) color_table.append(Color("紺色",(35,59,108))) color_table.append(Color("藍色",(22,94,131))) color_table.append(Color("桔梗色",(106,76,156))) color_table.append(Color("江戸紫",(116,83,153))) color_table.append(Color("鶯色",(145,141,64))) color_table.append(Color("灰汁色",(188,176,156))) color_table.append(Color("亜麻色",(214,198,175))) color_table.append(Color("山吹色",(248,180,0))) color_table.append(Color("小豆色",(152,81,75))) color_table.append(Color("えんじ色",(179,66,74))) color_table.append(Color("茜色",(177,53,70))) return color_table def find_color(name_color): color_table = color_table_full() result = None for i in range(0,len(color_table)): if name_color.casefold() == color_table[i].name.casefold(): result = color_table[i] break return result def show_color_name(): color_table = color_table_full() for i in range(0,len(color_table)): print(color_table[i].name, end =" ") print("")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 87: 素数のべき乗の和

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 87. 素数のべき乗の和 原文 Problem 87: Prime power triples 問題の要約:素数の2乗・3乗・4乗の和で表せる数が$5*10^7$未満でいくつあるか求めよ 50未満では以下の4個だけです。 単純に全探索します。同じような処理が繰り返されるので再帰関数で実装しました。重複を除くためにセットを使っています。 import sympy def countPPT(ppt, pw, PPTmax): if pw == 1: if ppt < PPTmax: pptSet.add(ppt) return for p in Primes: ppt1 = ppt+p**pw if ppt1 >= PPTmax: break countPPT(ppt1,pw-1, PPTmax) return PPTmax, pptSet = 5*10**7, set({}) Primes = list(sympy.primerange(2,int(PPTmax**(1/2)))) countPPT(0, 4, PPTmax) print(f"Answer: {len(pptSet)}") (開発環境:Google Colab)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1Mac + Pyenv + Pyenv-Virtualenvでの'pyenv install'時エラー

前提 Mac M1Max Pyenv + Virtualenvを利用していて、ある日pyenv installができなくなった エラー内容 pyenvで新しいPythonをインストールしようとすると、下記のようなエラーが出る。 アーキテクチャ絡み?M1のせい? $ pyenv install 3.7.12 Last 10 log lines: "_libintl_textdomain", referenced from: _PyIntl_textdomain in libpython3.7m.a(_localemodule.o) _PyIntl_textdomain in libpython3.7m.a(_localemodule.o) ld: symbol(s) not found for architecture x86_64 ld: symbol(s) not found for architecture x86_64 clang: clangerror: : linker command failed with exit code 1 (use -v to see invocation)error: linker command failed with exit code 1 (use -v to see invocation) make: *** [Programs/_testembed] Error 1 make: *** Waiting for unfinished jobs.... make: *** [python.exe] Error 1 対処法 いろいろなサイトに転がっていた、下記対処法が効きそう。 必要ないものもあるかも。誰か教えてください。 binutilsのアンインストール そもそも私の環境では入っていなかった。 $ brew uninstall binutils Error: No such keg: /opt/homebrew/Cellar/binutils XcodeのCommand Line Toolsの設定 xcodeを起動 Preferenceをクリック Locationsタブを選択 Command Line Toolsが選択されていない場合は、選択する Pyenvの再インストール 上だけでは治らなかったので、再インストール。キャッシュの削除などは不要。 brew uninstall pyenv-virtualenv brew uninstall pyenv brew install pyenv brew install pyenv-virtualenv 実行 $ pyenv install 3.7.12 python-build: use openssl@1.1 from homebrew python-build: use readline from homebrew Installing Python-3.7.12... python-build: use readline from homebrew python-build: use zlib from xcode sdk Installed Python-3.7.12 to /Users/<USER NAME>/.pyenv/versions/3.7.12 動いた!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CLAM(Clustering-constrained Attention Multiple Instance Learning)

CLAMは、スライド画像からアノテーションせずに、特徴点を抽出し、クラス分類を行う弱教師あり学習です。 実行環境 構成 実行環境 OS linux(Ubuntu18.04 LTS) GPU TITAN RTX (なくても実行できます。) python 3.7.7 ライブラリ パッケージ バージョン h5py 2.10.0 matplotlib 3.1.1 numpy 1.18.1 opencv-python 4.1.1.26 openslide-python 1.1.1 openslides 3.0 pandas 0.25.3 pillow 7.0.0 PyTorch 1.3.1 scikit-learn 0.22.1 scipy 1.4.1 tensorflow 1.14.0 tensorboardx 1.14.0 torchvision 0.1.8 smooth-topk setup.py実行 (smooth-topkはCLAM-masterのsmooth-topk/setup.pyがあるので、ヒートマップの作成前までに実行すればいいです。) smooth-topk: https://github.com/mahmoodlab/CLAM/blob/master/docs/INSTALLATION.md ディレクトリ構成 CLAM ├── DATA_DIRECTORY/ │ ├── slide_1.svs │ ├── slide_2.svs │ └── ... │ ├── dataset_csv │   ├── tumor_subtyping_dummy_clean.csv │   └── tumor_vs_normal_dummy_clean.csv ├── heatmaps │   ├── demo / │   ├── configs │   │   └── config_template.yaml │   └── process_lists │   └── heatmap_demo_dataset.csv ├── presets │   ├── bwh_biopsy.csv │   └── ... ├── splits │   ├── task_1_tumor_vs_normal_75 / │   └── ... ├── datasets / ├── models / ├── utils / ├── vis_utils / ├── wsi_core / ├── build_preset.py ├── create_heatmaps.py ├── create_patches.py ├── create_patches_fp.py ├── create_splits_seq.py ├── main.py ├── extract_features.py ├── extract_features_fp.py └── eval.py ※ extract_features.py, create_patches.pyの語尾に"_fp"がついたものがあります。これは、パイプライン処理をしたもので、基本的に"_fp"がついているほうを使用します。 前提条件 CLAMで用いられる画像はバーチャルスライドとして使われるWSI(.svs, .ndpi, .tiff等)を想定しています。 今回は、.ndpiファイルで二値分類を行いました。 手順 完全自動実行 パラメータのカスタマイズ 特徴抽出 データセットの準備 学習 テスト・評価 ヒートマップの作成 感想 1. 完全自動実行  DATA_DIRECTORY内に入っているすべてのスライドに対して、デフォルトのパラメータで分割し、スライド領域内の組織を含むパッチを抽出し、つなぎ合わせて復元画像を作成します。 python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --patch --stitch 実行すると、以下のように出力されます。 source: 入力画像フォルダパス patch_save_dir: 出力パッチフォルダパス mask_save_dir: 出力マスクフォルダパス stitch_save_dir: 出力スティッチフォルダパス {'seg_params': ... } デフォルト設定 ... progress: 0.98, i/N <- N枚のうちi番目 processing ファイル名 segmentation took 0.1509876251220703 seconds patching took -1 seconds stitching took -1 seconds average segmentation time in s per slide: 2.1... average patching time in s per slide: -0.8... average stiching time in s per slide: -0.8... 復元された画像はRESULTS_DIRECTORYに以下のフォルダ構成で出力されます。masksでは組織を緑線で、空洞を青線で囲っています。stitchesはマスキング画像をもとに組織部分のみを抽出し、ほかの部分を黒くマスクしています。 RESULTS_DIRECTORY/ ├── masks │ ├── slide_1.png │ ├── slide_2.png │ └── ... ├── patches │ ├── slide_1.h5 │ ├── slide_2.h5 │ └── ... ├── stitches │ ├── slide_1.png │ ├── slide_2.png │ └── ... └── process_list_autogen.csv 2. パラメータのカスタマイズ  csvにデータセットに適用するパラメータ名と値を記載し、コマンド実行時に引数としてパスを指定することで、 実行時に指定したパラメータで処理させることができます。 (スライドに応じて像の境界や穴部分を検知させるために有効です。) コマンド実行の際に特定の引数をつけ、csvファイルパスを指定することで利用可能ですが、 以下の2つの方法があります。 1.コマンド実行時に--preset引数を付け、csvファイルパスを指定する。   ➡テンプレートとしてpresets/bwh_biopsy.csvを用いて値を変更します。    各行に、各スライド毎のパラメータが記載してあります。 2.コマンド実行時に--process_list引数を付け、csvファイルパスを指定する。   ➡初回実行後に出力されるRESULTS_DIRECTORY/process_list_autogen.csvを    ベースに値を変更します。1.と異なる点は主にprocess列があることです。 (process列については後述。)     どちらのcsvも基本的には同じですが、2.にはスライドを処理するかどうかを指定するprocess列があります。  データセットに適用したパラメータのテンプレートを定義します。テンプレートはpresets/bwh_biopsy.csvをもとにRESULTS_DIRECTORY/process_list_autogen.csvを変更していきます。(process_list_autogen.csvはパラメータ調整をする前に複製して別名で編集していったほうがいいです。) python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --process_list process_list_edited.csv 今回は、process_list_autogen.csvをprocess_list_edited.csvに変更して、パラメータ調整を行いました。 パラメータの種類には以下のようなものがあります。 パラメータ デフォルト 説明 seg_level -1 WSIを分割する際のダウンサンプリングレベル sthresh 8 セグメンテーション閾値。より高い閾値を用いることで、前景の検出が少なくなり、背景の検出が多くなる。 mthresh 7 平均フィルターサイズ(自然数、奇数) use_otsu False 大津の二値化フィルタ close 4 後の初期閾値化(二値化)の前に行う追加のモルフォロジークロージング処理(自然数 or -1) a_t 100 組織の領域フィルターの閾値(自然数, 考慮すべき検知される前景の輪郭の最小サイズ)レベル0での512x512の参照パッチサイズを基準とする。 a_h 16 穴用の領域フィルターの閾値 (自然数, 避けるべき前景輪郭の検知される最小の穴/空洞のサイズ) max_n_holes 10 検知された前景輪郭ごとに考慮すべき穴の最大数(自然数) vis_level -1 セグメンテーション結果を視覚化する際のダウンサンプリングレベル-1: 1/64にするダウンサンプリングに最も近いWSIのダウンサンプリングを用いる line_thickness 250 レベル0でのセグメンテーション結果を描画する線の太さ use_padding True スライドの境界を埋めるかどうか contour_fn four_pt パッチが前景か背景か判断する輪郭チェック機能 モルフォロジークロージング処理:物体の外郭(境界線)を得るための処理です。 process_list_autogen.csvのprocess列について:  process列のセルを0にすることで、その行のスライドの処理をスキップすることができ、1にすることでその行のスライドは処理されます。  調整したい画像が書かれた行のprocessセルを1にして、パラメータの変更→create_patches_fp.py実行→mask画像の確認... を繰り返して組織がちゃんと囲われているように調整していきます。  すべての画像でセグメンテーションの結果に満足できたら、process列をすべて1にして保存します。保存先は、presetsフォルダにしました。 python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --process_list CSV_FILE_NAME --patch --stitch 3. 特徴抽出 CUDA_VISIBLE_DEVICES=0,1 python extract_features_fp.py --data_h5_dir DIR_TO_COORDS --data_slide_dir DATA_DIRECTORY --csv_path CSV_FILE_NAME --feat_dir FEATURES_DIRECTORY --batch_size 512 --slide_ext .svs GPUを使わない場合は、"CUDA_VISIBLE_DEVICES=0,1"を削除してください。 create_patches_fp.py実行後に出力された.h5ファイルがDIR_TO_COORDSフォルダ下に保存されていることを想定しており、実行するとバッチサイズ512で各スライドの各組織パッチから1024次元の特徴を抽出し、以下のフォルダ構造を作成します。 FEATURES_DIRECTORY/ ├── h5_files │ ├── slide_1.h5 │ ├── slide_2.h5 │ └── ... └── pt_files ├── slide_1.pt ├── slide_2.pt └── ... 各.h5ファイルには、抽出された特徴配列およびパッチの座標が含まれます。 (学習を高速化するために、各スライドのパッチの特徴だけが含まれる.ptファイルも作成されます。) 4. データセットの準備 学習のための準備をします。 - データの整理(データセットが複数ある場合のみ) - データセットオブジェクト作成のためのコードの編集 - データセットの分割(train, validation, test用) データの整理は複数のデータセットがある場合に下のように一つのフォルダにまとめてください。 DATA_ROOT_DIR/ ├── DATASET_1_DATA_DIR/ │ ├── h5_files │ │ ├── slide_1.h5 │ │ ├── slide_2.h5 │ │ └── ... │ └── pt_files │ ├── slide_1.pt │ ├── slide_2.pt │ └── ... ├── DATASET_2_DATA_DIR/ │ ├── h5_files │ │ ├── slide_a.h5 │ │ ├── slide_b.h5 │ │ └── ... │ └── pt_files │ ├── slide_a.pt │ ├── slide_b.pt │ └── ... └── ...  各データセットはDATA_ROOT_DIRのサブフォルダに入っており、抽出された.ptファイルがpt_filesに保存されていることを想定しています。  DATA_ROOT_DIR内に保存されている学習用画像ファイルをまとめたテーブルを作成します。作成するテーブルは、case_id, slide_id, とラベル情報が入った3つの列で作成し、.csvで保存してください。(例:tumor_vs_normal_dummy_clean.csv) slide_idは.ptファイルと対応するため、画像ファイルから拡張子を削除した文字列にすれば問題ありません。 もし、自分で分類するモデルを作成する場合は、main.pyとeval.pyに以下のようなコードの追加を行います。 (例:"TASK NAME"という分類クラスを作る場合) if args.task == 'TASK NAME': args.n_classes=2 dataset = Generic_MIL_Dataset(csv_path = 'dataset_csv/DATASET.csv', data_dir= os.path.join(args.data_root_dir, 'tumor_vs_normal_feat_resnet'), shuffle = False, seed = args.seed, print_info = True, label_dict = {'LABEL_1':0, 'LABEL_2':1}, label_col = 'label', ignore=[]) 二値分類なら、TASK NAME, DATASET.CSV,label_dictの変更でいけると思います。多クラス分類の場合には、data_dirなどの変更が必要になる?と思います。 その後、TASK NAMEを引数として渡すため、--taskのリストに追加します。 parser.add_argument('--task', type=str, choices=[ 'task_1_tumor_vs_normal', 'task_2_tumor_subtyping', 'TASK NAME' <--- 追加 ]) データセットの分割します。アルゴリズムのパフォーマンスを評価するため、学習/検証/テスト用に分割し、複数のfoldに分けていきます。分割は以下のコマンドを実行します。 python create_splits_seq.py --task task_1_tumor_vs_normal --seed 1 --label_frac 0.75 --k 10 label_frac 0.75では(全体-検証データ数-テストデータ数)×0.75が学習データ数になります。 このコマンドの場合には、10個のfoldを作成し、学習データに(該当ラベルのデータ全数 - 検証データ数 - テストデータ数)の75%を用いることを意味します。 検証データ数とテストデータ数はデフォルトで該当ラベルのデータ全数×0.1ですが、 それぞれコマンド実行時に--val_frac引数と--test_frac引数を指定することで変更できます。 実行するとsplitsフォルダ下に'TASK NAME'_'label_frac'のフォルダが作成され、3種類k個のsplitデータファイルが保存されます。 splits └── TASK NAME_'label_frac' ├── splits_0.csv ├── splits_0_bool.csv ├── splits_0_descriptor.csv ├── ... └── splits_k_descriptor.csv 5. 学習(二値分類の場合) 以下のコマンドを引く数を変更します。--label_fracの閾値や--exp_codeのフォルダ名の後ろの数字は直前で使用した--label_fracの値と同じにしてください。もしわからなくなったら、CLAM/splits内に保存されているので確認しましょう。 引数が指定できたら、実行します。 (GPUを使用しない場合には、"CUDA_VISIBLE_DEVICES=0"を削除してください。) 出力はresults/--exp_codeにされていくことに注意してください。 CUDA_VISIBLE_DEVICES=0 python main.py --drop_out --early_stopping --lr 2e-4 --k 10 --label_frac 0.75 --exp_code task_1_tumor_vs_normal_CLAM_75 --weighted_sample --bag_loss ce --inst_loss svm --task task_1_tumor_vs_normal --model_type clam_sb --log_data --data_root_dir DATA_ROOT_DIR 6. テスト・評価 学習モデルのパフォーマンスをテスト・評価するコマンドを実行します。 (引数はこれまでの同様適宜変更して下さい。) CUDA_VISIBLE_DEVICES=0 python eval.py --drop_out --k 10 --models_exp_code task_1_tumor_vs_normal_CLAM_75_s1 --save_exp_code task_1_tumor_vs_normal_CLAM_75_s1_cv --task task_1_tumor_vs_normal --model_type clam_sb --results_dir results --data_root_dir DATA_ROOT_DIR 結果は、eval_results/'--save_exp_code_'に保存されているので、その下のsummary.csvを確認します。このファイルにはtest_auc, test_accが各foldごとにのっています。もし、評価が悪いようなら、スライドを見直すかパラメータ調整の調整を行ってください。 その結果を確認し、問題なさそうなら、heatmap.pyの作成に移ります。 7. ヒートマップの作成  ヒートマップの作成には、設定ファイルとして.yamlファイルを作成する必要があります。(/heatmaps/configs/config_template.yaml参照) process_listはパラメータのカスタマイズで使ったprocess_list_edited.csvを複製してslide_id, labelの列を残して、heatmaps/process_list下に保存しました。 作成した設定ファイルは/heatmaps/configsに保存してください。 config.yamlが作成できたら、以下のコマンドを実行します。 CUDA_VISIBLE_DEVICES=0,1 python create_heatmaps.py --config config.yaml 途中結果はheatmaps/heatmap_raw_results 最終結果はheatmaps/heatmap_production_results に保存されます。 8. 感想 私たちのチームでは、windowsとLinuxで試していきましたが、OSによって動作が少し変わるみたいで「"Not found"パスが見つかりません。」が散見されました。もし、実行するさいにはフルパスで書いたほうがいいかもしれないですね。 わからない点・誤認がありましたら、質問してもらえると助かります。 作成者:坂口 参考文献URL * CLAM github Lu, M.Y., Williamson, D.F.K., Chen, T.Y. et al. Data-efficient and weakly supervised computational pathology on whole-slide images. Nat Biomed Eng 5, 555–570 (2021). https://doi.org/10.1038/s41551-020-00682-w @article{lu2021data, title={Data-efficient and weakly supervised computational pathology on whole-slide images}, author={Lu, Ming Y and Williamson, Drew FK and Chen, Tiffany Y and Chen, Richard J and Barbieri, Matteo and Mahmood, Faisal}, journal={Nature Biomedical Engineering}, volume={5}, number={6}, pages={555--570}, year={2021}, publisher={Nature Publishing Group} }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CLAM(Clustering-constrained Attention Multiple Instance Learning)実行してみた

CLAMは、スライド画像からアノテーションせずに、特徴点を抽出し、クラス分類を行う弱教師あり学習です。 実行環境 構成 実行環境 OS linux(Ubuntu18.04 LTS) GPU TITAN RTX (なくても実行できます。) python 3.7.7 ライブラリ パッケージ バージョン h5py 2.10.0 matplotlib 3.1.1 numpy 1.18.1 opencv-python 4.1.1.26 openslide-python 1.1.1 openslides 3.0 pandas 0.25.3 pillow 7.0.0 PyTorch 1.3.1 scikit-learn 0.22.1 scipy 1.4.1 tensorflow 1.14.0 tensorboardx 1.14.0 torchvision 0.1.8 smooth-topk setup.py実行 (smooth-topkはCLAM-masterのsmooth-topk/setup.pyがあるので、ヒートマップの作成前までに実行すればいいです。) smooth-topk: https://github.com/mahmoodlab/CLAM/blob/master/docs/INSTALLATION.md ディレクトリ構成 CLAM ├── DATA_DIRECTORY/ │ ├── slide_1.svs │ ├── slide_2.svs │ └── ... │ ├── dataset_csv │   ├── tumor_subtyping_dummy_clean.csv │   └── tumor_vs_normal_dummy_clean.csv ├── heatmaps │   ├── demo / │   ├── configs │   │   └── config_template.yaml │   └── process_lists │   └── heatmap_demo_dataset.csv ├── presets │   ├── bwh_biopsy.csv │   └── ... ├── splits │   ├── task_1_tumor_vs_normal_75 / │   └── ... ├── datasets / ├── models / ├── utils / ├── vis_utils / ├── wsi_core / ├── build_preset.py ├── create_heatmaps.py ├── create_patches.py ├── create_patches_fp.py ├── create_splits_seq.py ├── main.py ├── extract_features.py ├── extract_features_fp.py └── eval.py ※ extract_features.py, create_patches.pyの語尾に"_fp"がついたものがあります。これは、パイプライン処理をしたもので、基本的に"_fp"がついているほうを使用します。 前提条件 CLAMで用いられる画像はバーチャルスライドとして使われるWSI(.svs, .ndpi, .tiff等)を想定しています。 今回は、.ndpiファイルで二値分類を行いました。 手順 完全自動実行 パラメータのカスタマイズ 特徴抽出 データセットの準備 学習 テスト・評価 ヒートマップの作成 感想 1. 完全自動実行  DATA_DIRECTORY内に入っているすべてのスライドに対して、デフォルトのパラメータで分割し、スライド領域内の組織を含むパッチを抽出し、つなぎ合わせて復元画像を作成します。 python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --patch --stitch 実行すると、以下のように出力されます。 source: 入力画像フォルダパス patch_save_dir: 出力パッチフォルダパス mask_save_dir: 出力マスクフォルダパス stitch_save_dir: 出力スティッチフォルダパス {'seg_params': ... } デフォルト設定 ... progress: 0.98, i/N <- N枚のうちi番目 processing ファイル名 segmentation took 0.1509876251220703 seconds patching took -1 seconds stitching took -1 seconds average segmentation time in s per slide: 2.1... average patching time in s per slide: -0.8... average stiching time in s per slide: -0.8... 復元された画像はRESULTS_DIRECTORYに以下のフォルダ構成で出力されます。masksでは組織を緑線で、空洞を青線で囲っています。stitchesはマスキング画像をもとに組織部分のみを抽出し、ほかの部分を黒くマスクしています。 RESULTS_DIRECTORY/ ├── masks │ ├── slide_1.png │ ├── slide_2.png │ └── ... ├── patches │ ├── slide_1.h5 │ ├── slide_2.h5 │ └── ... ├── stitches │ ├── slide_1.png │ ├── slide_2.png │ └── ... └── process_list_autogen.csv 2. パラメータのカスタマイズ  csvにデータセットに適用するパラメータ名と値を記載し、コマンド実行時に引数としてパスを指定することで、 実行時に指定したパラメータで処理させることができます。 (スライドに応じて像の境界や穴部分を検知させるために有効です。) コマンド実行の際に特定の引数をつけ、csvファイルパスを指定することで利用可能ですが、 以下の2つの方法があります。 1.コマンド実行時に--preset引数を付け、csvファイルパスを指定する。   ➡テンプレートとしてpresets/bwh_biopsy.csvを用いて値を変更します。    各行に、各スライド毎のパラメータが記載してあります。 2.コマンド実行時に--process_list引数を付け、csvファイルパスを指定する。   ➡初回実行後に出力されるRESULTS_DIRECTORY/process_list_autogen.csvを    ベースに値を変更します。1.と異なる点は主にprocess列があることです。 (process列については後述。)     どちらのcsvも基本的には同じですが、2.にはスライドを処理するかどうかを指定するprocess列があります。  データセットに適用したパラメータのテンプレートを定義します。テンプレートはpresets/bwh_biopsy.csvをもとにRESULTS_DIRECTORY/process_list_autogen.csvを変更していきます。(process_list_autogen.csvはパラメータ調整をする前に複製して別名で編集していったほうがいいです。) python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --process_list process_list_edited.csv 今回は、process_list_autogen.csvをprocess_list_edited.csvに変更して、パラメータ調整を行いました。 パラメータの種類には以下のようなものがあります。 パラメータ デフォルト 説明 seg_level -1 WSIを分割する際のダウンサンプリングレベル sthresh 8 セグメンテーション閾値。より高い閾値を用いることで、前景の検出が少なくなり、背景の検出が多くなる。 mthresh 7 平均フィルターサイズ(自然数、奇数) use_otsu False 大津の二値化フィルタ close 4 後の初期閾値化(二値化)の前に行う追加のモルフォロジークロージング処理(自然数 or -1) a_t 100 組織の領域フィルターの閾値(自然数, 考慮すべき検知される前景の輪郭の最小サイズ)レベル0での512x512の参照パッチサイズを基準とする。 a_h 16 穴用の領域フィルターの閾値 (自然数, 避けるべき前景輪郭の検知される最小の穴/空洞のサイズ) max_n_holes 10 検知された前景輪郭ごとに考慮すべき穴の最大数(自然数) vis_level -1 セグメンテーション結果を視覚化する際のダウンサンプリングレベル-1: 1/64にするダウンサンプリングに最も近いWSIのダウンサンプリングを用いる line_thickness 250 レベル0でのセグメンテーション結果を描画する線の太さ use_padding True スライドの境界を埋めるかどうか contour_fn four_pt パッチが前景か背景か判断する輪郭チェック機能 モルフォロジークロージング処理:物体の外郭(境界線)を得るための処理です。 process_list_autogen.csvのprocess列について:  process列のセルを0にすることで、その行のスライドの処理をスキップすることができ、1にすることでその行のスライドは処理されます。  調整したい画像が書かれた行のprocessセルを1にして、パラメータの変更→create_patches_fp.py実行→mask画像の確認... を繰り返して組織がちゃんと囲われているように調整していきます。  すべての画像でセグメンテーションの結果に満足できたら、process列をすべて1にして保存します。保存先は、presetsフォルダにしました。 python create_patches_fp.py --source DATA_DIRECTORY --save_dir RESULTS_DIRECTORY --patch_size 256 --seg --process_list CSV_FILE_NAME --patch --stitch 3. 特徴抽出 CUDA_VISIBLE_DEVICES=0,1 python extract_features_fp.py --data_h5_dir DIR_TO_COORDS --data_slide_dir DATA_DIRECTORY --csv_path CSV_FILE_NAME --feat_dir FEATURES_DIRECTORY --batch_size 512 --slide_ext .svs GPUを使わない場合は、"CUDA_VISIBLE_DEVICES=0,1"を削除してください。 create_patches_fp.py実行後に出力された.h5ファイルがDIR_TO_COORDSフォルダ下に保存されていることを想定しており、実行するとバッチサイズ512で各スライドの各組織パッチから1024次元の特徴を抽出し、以下のフォルダ構造を作成します。 FEATURES_DIRECTORY/ ├── h5_files │ ├── slide_1.h5 │ ├── slide_2.h5 │ └── ... └── pt_files ├── slide_1.pt ├── slide_2.pt └── ... 各.h5ファイルには、抽出された特徴配列およびパッチの座標が含まれます。 (学習を高速化するために、各スライドのパッチの特徴だけが含まれる.ptファイルも作成されます。) 4. データセットの準備 学習のための準備をします。 - データの整理(データセットが複数ある場合のみ) - データセットオブジェクト作成のためのコードの編集 - データセットの分割(train, validation, test用) データの整理は複数のデータセットがある場合に下のように一つのフォルダにまとめてください。 DATA_ROOT_DIR/ ├── DATASET_1_DATA_DIR/ │ ├── h5_files │ │ ├── slide_1.h5 │ │ ├── slide_2.h5 │ │ └── ... │ └── pt_files │ ├── slide_1.pt │ ├── slide_2.pt │ └── ... ├── DATASET_2_DATA_DIR/ │ ├── h5_files │ │ ├── slide_a.h5 │ │ ├── slide_b.h5 │ │ └── ... │ └── pt_files │ ├── slide_a.pt │ ├── slide_b.pt │ └── ... └── ...  各データセットはDATA_ROOT_DIRのサブフォルダに入っており、抽出された.ptファイルがpt_filesに保存されていることを想定しています。  DATA_ROOT_DIR内に保存されている学習用画像ファイルをまとめたテーブルを作成します。作成するテーブルは、case_id, slide_id, とラベル情報が入った3つの列で作成し、.csvで保存してください。(例:tumor_vs_normal_dummy_clean.csv) slide_idは.ptファイルと対応するため、画像ファイルから拡張子を削除した文字列にすれば問題ありません。 もし、自分で分類するモデルを作成する場合は、main.pyとeval.pyに以下のようなコードの追加を行います。 (例:"TASK NAME"という分類クラスを作る場合) if args.task == 'TASK NAME': args.n_classes=2 dataset = Generic_MIL_Dataset(csv_path = 'dataset_csv/DATASET.csv', data_dir= os.path.join(args.data_root_dir, 'tumor_vs_normal_feat_resnet'), shuffle = False, seed = args.seed, print_info = True, label_dict = {'LABEL_1':0, 'LABEL_2':1}, label_col = 'label', ignore=[]) 二値分類なら、TASK NAME, DATASET.CSV,label_dictの変更でいけると思います。多クラス分類の場合には、data_dirなどの変更が必要になる?と思います。 その後、TASK NAMEを引数として渡すため、--taskのリストに追加します。 parser.add_argument('--task', type=str, choices=[ 'task_1_tumor_vs_normal', 'task_2_tumor_subtyping', 'TASK NAME' <--- 追加 ]) データセットの分割します。アルゴリズムのパフォーマンスを評価するため、学習/検証/テスト用に分割し、複数のfoldに分けていきます。分割は以下のコマンドを実行します。 python create_splits_seq.py --task task_1_tumor_vs_normal --seed 1 --label_frac 0.75 --k 10 label_frac 0.75では(全体-検証データ数-テストデータ数)×0.75が学習データ数になります。 このコマンドの場合には、10個のfoldを作成し、学習データに(該当ラベルのデータ全数 - 検証データ数 - テストデータ数)の75%を用いることを意味します。 検証データ数とテストデータ数はデフォルトで該当ラベルのデータ全数×0.1ですが、 それぞれコマンド実行時に--val_frac引数と--test_frac引数を指定することで変更できます。 実行するとsplitsフォルダ下に'TASK NAME'_'label_frac'のフォルダが作成され、3種類k個のsplitデータファイルが保存されます。 splits └── TASK NAME_'label_frac' ├── splits_0.csv ├── splits_0_bool.csv ├── splits_0_descriptor.csv ├── ... └── splits_k_descriptor.csv 5. 学習(二値分類の場合) 以下のコマンドを引く数を変更します。--label_fracの閾値や--exp_codeのフォルダ名の後ろの数字は直前で使用した--label_fracの値と同じにしてください。もしわからなくなったら、CLAM/splits内に保存されているので確認しましょう。 引数が指定できたら、実行します。 (GPUを使用しない場合には、"CUDA_VISIBLE_DEVICES=0"を削除してください。) 出力はresults/--exp_codeにされていくことに注意してください。 CUDA_VISIBLE_DEVICES=0 python main.py --drop_out --early_stopping --lr 2e-4 --k 10 --label_frac 0.75 --exp_code task_1_tumor_vs_normal_CLAM_75 --weighted_sample --bag_loss ce --inst_loss svm --task task_1_tumor_vs_normal --model_type clam_sb --log_data --data_root_dir DATA_ROOT_DIR 6. テスト・評価 学習モデルのパフォーマンスをテスト・評価するコマンドを実行します。 (引数はこれまでの同様適宜変更して下さい。) CUDA_VISIBLE_DEVICES=0 python eval.py --drop_out --k 10 --models_exp_code task_1_tumor_vs_normal_CLAM_75_s1 --save_exp_code task_1_tumor_vs_normal_CLAM_75_s1_cv --task task_1_tumor_vs_normal --model_type clam_sb --results_dir results --data_root_dir DATA_ROOT_DIR 結果は、eval_results/'--save_exp_code_'に保存されているので、その下のsummary.csvを確認します。このファイルにはtest_auc, test_accが各foldごとにのっています。もし、評価が悪いようなら、スライドを見直すかパラメータ調整の調整を行ってください。 その結果を確認し、問題なさそうなら、heatmap.pyの作成に移ります。 7. ヒートマップの作成  ヒートマップの作成には、設定ファイルとして.yamlファイルを作成する必要があります。(/heatmaps/configs/config_template.yaml参照) process_listはパラメータのカスタマイズで使ったprocess_list_edited.csvを複製してslide_id, labelの列を残して、heatmaps/process_list下に保存しました。 作成した設定ファイルは/heatmaps/configsに保存してください。 config.yamlが作成できたら、以下のコマンドを実行します。 CUDA_VISIBLE_DEVICES=0,1 python create_heatmaps.py --config config.yaml 途中結果はheatmaps/heatmap_raw_results 最終結果はheatmaps/heatmap_production_results に保存されます。 8. 感想 私たちのチームでは、windowsとLinuxで試していきましたが、OSによって動作が少し変わるみたいで「"Not found"パスが見つかりません。」が散見されました。もし、実行するさいにはフルパスで書いたほうがいいかもしれないですね。 わからない点・誤認がありましたら、質問してもらえると助かります。 作成者:坂口 参考文献URL * CLAM github Lu, M.Y., Williamson, D.F.K., Chen, T.Y. et al. Data-efficient and weakly supervised computational pathology on whole-slide images. Nat Biomed Eng 5, 555–570 (2021). https://doi.org/10.1038/s41551-020-00682-w @article{lu2021data, title={Data-efficient and weakly supervised computational pathology on whole-slide images}, author={Lu, Ming Y and Williamson, Drew FK and Chen, Tiffany Y and Chen, Richard J and Barbieri, Matteo and Mahmood, Faisal}, journal={Nature Biomedical Engineering}, volume={5}, number={6}, pages={555--570}, year={2021}, publisher={Nature Publishing Group} }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 86: 直方体の最短経路

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 86.直方体の最短経路 原文 Problem 86: Cuboid route 問題の要約:最大値Mの3辺が整数の直方体で、頂点SからFへ壁を伝って行く最短経路の長さがやはり整数になる場合が何通りあるか数える。その数が初めて$10^6$を超えるような最小のMを求めよ 例として初めて2000通りを超えるMは100。 M=99の時は1975通り 以下のステップで考えて行きます。 3辺$a \le b \le c \le M$とする。例題では$(a,b,c) = (3,5,6)$ 直角三角形の底辺として$c$をカウントアップして行き、高さは$a+b$を$2c$までループする このとき斜辺$\sqrt{(a+b)^2+c^2}$が最短経路になりこれが整数となる場合が解の候補 $a+b$の$a \le b \le c$の条件で$a$と$b$の取りえる値を数える、$(a+b)$と$c$の大小関係で以下のように変わってきます。 これをプログラムにしたのがこちら。 import itertools # a<=b<=c<=M count = 0 for c in itertools.count(3): for ab in range(3,2*c+1): rt = (ab**2 + c**2)**(1/2) if rt == int(rt): count += ab // 2 if (ab <= c) else (2*(c+1) - ab)//2 if count > 10**6: print(f"Answer: {c}, count={count}") break (開発環境:Google Colab)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Project Euler】Problem 85: 長方形の数

本記事はProjectEulerの「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。 問題 85.長方形の数 原文 Problem 85: Counting rectangles 問題の要約:長方形の格子に置ける長方形の数を数えたとき、その数と$2*10^6$との差が最小になるような長方形の格子の面積を求めよ この2x3の例では置ける長方形の数は合計18となります。 図をよく見ると、置ける数は右下らかの座標を$(x,y)$とすると$x \times y$となっていることが分かります。したがっておける長方形の数はその合計なので素直に実装するとこうなります。 import itertools def countRect(xsize, ysize): return sum([x*y for x,y in itertools.product(range(1,xsize+1),range(1,ysize+1))]) Mil = 2*10**6 minDif = 100 for x,y in itertools.combinations(range(5,202),2): count = countRect(x, y) dif = abs(Mil - count) if dif < minDif: minDif, minArea = dif, x*y print(f"Answer: {minArea}") ここまで規則的だと和の式も出せそうなのでやってみます。 \large \sum_{x=1}^{X} \sum_{y=1}^{Y} x \times y \\ = \large \sum_{x=1}^{X} x \times \sum_{y=1}^{Y} y \\ = \large \frac{x(x+1)}{2} \times \frac{y(y+1)}{2} \\ = \large x(x+1)y(y+1)/4 ということでcountRectを以下のように書き換えるとかなり速くなります。 def countRect(x,y): return x*(x+1)*y*(y+1)//4 (開発環境:Google Colab)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Arduino IDE で ESP32 を利用しようとしてpython-serialがなくて動かない件(ubuntu20.04LTE)

Arduino IDE で ESP32 のモジュールを利用しようしてpythonのimport serial が通らなくて困った件があり、解消しましたので、記載します。 Arduino IDE での ESP32 モジュールの利用では、pythonを利用して、 更にimport serialしていますが、動きませんでした。 遅ればせながらの次のことでした。 pythonコマンドは、ubuntu20.04では、python2.7にリンクされています。 しかしながら、python2.7は、2020年1月1日でサポートを終了しており、 必要とされるpython2.7でのpip、およびpipを利用したpyserialのインストールはできなくなっています。 ご自身のほかの環境として、特段python2を利用する必要がないならば、 pythonコマンドが、python2にリンクされていることを修正するために sudo apt install python-is-python3 を入れてしまえばpythonコマンドでpython3にリンクされて、 これで、動くようになるということです。 Special Thanks to  https://chromebook.nomad-life.net/1923577/%E3%80%90chromebook_linux%E3%80%91arduino-ide%E3%81%A7esp32%E3%82%92%E4%BD%BF%E3%81%86 https://stackoverflow.com/questions/60762378/exec-python-executable-file-not-found-in-path
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1.1(1) 日本語をread_csv(encoding='shift_jis')で読み見込めない時

日本語を含むcsvファイルを読み込む場合は、encoding='shift_jis'を指定して、pd.read_csv('data.csv', encoding='shift_jis')と書くのは定石です。 しかし、それでもエラーとなってしまう場合があります。 例えば、以下のようなcsvファイルです。 # 例1: 'shift_jis'でエラーとなる clm0 clm1 clm2 clm3 1 Ⅰ APPLE りんご 2 Ⅱ apple リンゴ 3 Ⅲ aPPLe リンゴ 4 Ⅳ Apple 林檎 どれも日本語と英語のように見えますが、'shift_jis' ではエラーとなります。 しかし、以下は 'shift_jis' で読み込めます。 # 例2: 'shift_jis'でエラーとならない clm0 clm1 clm2 clm3 1 I APPLE りんご 2 II apple リンゴ 3 III aPPLe リンゴ 4 IV Apple 林檎 何が違うのでしょうか?そしてどうすればいいのでしょうか? ここでは、もとのcsvファイルを確認しても、おかしい部分が見当たらず、「何がおかしいの!(怒)」、という場面で対処する方法を説明します。上の2つの例が、大きなヒントとなります。 これを知っておくと、大きな時間短縮になります。 トピック 原因 対処方法 他にどんなencodingがあるか 参考ページhttps://shilabo.com/python/web_self/a001_000/a001_001/ 原因 多くの場合、ファイルの中の日本語の文字が原因です。 例えば、以下の2つの文字をご覧ください。 文字1: Ⅰ, Ⅱ, Ⅲ, Ⅳ 文字2: I, II, III, IV 文字1は日本語です。1とタイプして全角変換したものです。2,3,4も同様です。環境依存文字と出てくるのではないでしょうか。 文字2は英語です。I(英大文字のアイ)とV(英大文字のヴィ)の組み合わせです。 文字1のような日本語が含まれていると、'shift_jis'では読み込めません。 他に、①, ②, ③ (1,2,3とタイプして全角で変換)も文字1と同じ扱いで、読み込めません。 対処方法 元のファイルを見て、どう見ても日本語と英語であり、上記が原因であれば、以下で対処できます。 encoding='cp932'を指定します。 つまり、 df = pd.read_csv('data.csv', encoding='cp932') print(df) と書きます。これで①,②,③といった文字も読み込み可能です。 # clm0 clm1 clm2 clm3 #0 1 ① APPLE りんご #1 2 ② apple リンゴ #2 3 ③ aPPLe リンゴ #3 4 ④ Apple 林檎 但し、一番いいのは、'shift_jis'で読み込めないような文字は使わない(日本語変換する①やⅣなどは使わない)ことです。或るは、日本語を一切使わず英語だけにするのが一番いいです。Pythonは日本語が苦手です。 しかし、現実には、会社の共有ファイルでは、過去に作成された「見せるための」(分析のためではない)ファイルからデータを抽出する必要も多々あるでしょう。 encoding=shifut_jisでうまくいかない場合は、encoding='cp932'で対処を試みてください。 他にどんなencodingがあるか 以下のPyhonのドキュメント(英語)を調べると、日本語に関しては次のものが用意されてます。(リストをJapaneseでフィルター) https://docs.python.org/3/library/codecs.html#standard-encodings しかし、実際には、普通は'shift_jis'を使い、うまくいかない場合は'cp932'を試してみる、程度です。 それ以外は使いません。(少なくとも筆者は使った事がありません) 参考ページ 株式会社シラボのHPです。書籍のように必要な技術が掲載されています。 https://shilabo.com/ https://shilabo.com/python/web_self/a001_000/a001_001/ pandasのAPIドキュメント https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html Pythonのドキュメント https://docs.python.org/3/library/codecs.html#standard-encodings
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データサイエンスでWordleに立ち向かう

本記事の目的 インターネット上ではWordleというゲームを攻略するための様々な知見が既に見出されていますが、今回はデータサイエンスを駆使してWordleに立ち向かってみました。 記事にするとそれなりに長い解析をしているので、結論のみを知りたい方は後半 (まとめ) までスキップをお願いします。 What's Wordle? Webで遊べる日替わりの英単語当てゲームです。 ルール システムが独自の辞書から選択したアルファベット5文字の英単語を、プレイヤー (あなた) は最大6回の試行で推測します。 各試行ではプレイヤーが入力した5文字のアルファベットからなる英単語を受けて、真の単語にたどり着くためのヒントが表示されます。 真の単語と同じ位置にそのアルファベットが入力されているならば緑色のハイライト表示 真の単語に含まれているが位置が違うアルファベットが入力されていれば黄色のハイライト表示 かすりもしないアルファベットが入力された場合はグレーのハイライト表示 めちゃくちゃなアルファベットの羅列、あるいはWordleの内部辞書に登録されていない英単語をプレイヤーが入力することはできません。 詳細はWikipediaの記事を参照ください。 本検討の指針 下記の指針を簡単なPythonコードにより実装します。 柔軟にEDAを行うために、Jupyter Notebookを使用します。 英語コーパスから5文字の英単語リストを作成 1 リストに現れる英単語の素性をEDA (exploratory data analysis) EDAの結果をもとにWordleの独自の解法を練る 英単語リストの作成 まずは本検討で必要となるライブラリを一通りimportします。 ポイントとしては、英語コーパスに関するハンドリングのためにnltkを、EDAにおけるネットワーク可視化のためにnetworkxを使います。nltkは自然言語処理ではお馴染みですね。残りはごく一般的なライブラリです。 import copy import collections import itertools import matplotlib.pyplot as plt import networkx as nx import nltk from nltk.corpus import brown import numpy as np import pandas as pd %matplotlib inline pd.set_option('display.max_columns', 50) nltkを使って、英語コーパスを入手します。 英語リスト作成に用いるコーパスとしては一般的なものであれば何でも良いですが、有名なBrownコーパスを使うことにします。 # Brown corpusをダウンロード nltk.download('brown') # True (ダウンロードが完了すると表示される) Brownコーパスの全英単語を要素とするlistを作成します。 words = nltk.corpus.brown.words() # 小文字化しておく words_lower = [word.lower() for word in words] コーパスには多数の英文として、様々な英単語やその活用形が収録されています。 一方、Wordleには動詞の過去形、過去分詞形、現在進行形、(いわゆる) 三人称単数現在形が出現しないと海外サイトで噂されているので2、英語リストからこれらを除外します。 そのために、リストに含まれる英単語に対して品詞解析を行います。nltkライブラリでは追加機能をダウンロードすることで、品詞解析も実行することができます。 # pos_tagメソッドをダウンロード nltk.download('averaged_perceptron_tagger') # True (ダウンロードが完了すると表示される) 準備が整ったので品詞解析を実行します。 tagged_words = nltk.pos_tag(words_lower) for i, (word, tag) in enumerate(tagged_words): if i == 20: break print(i, word, tag) 品詞解析の実行結果です。品詞タグの見方はこちらのQiita記事を参照ください。 0 the DT 1 fulton NN 2 county NN 3 grand JJ 4 jury NN 5 said VBD 6 friday PDT 7 an DT 8 investigation NN 9 of IN 10 atlanta's JJ 11 recent JJ 12 primary JJ 13 election NN 14 produced VBD 15 `` `` 16 no DT 17 evidence NN 18 '' '' 19 that IN 無事、英単語リストに品詞タグを付与することができたので、ここからWordleの対象となる5文字の英単語を小文字化して重複なく抽出します。この際に、動詞の過去形 (VBD)、過去分詞形 (VBN)、三人称単数現在形 (VBZ) を除外します。3 words_5_let = sorted(set(word.lower() for word, tag in tagged_words if len(word) == 5 and word.isalpha() and tag != 'VBD' and tag != 'VBN' and tag != 'VBZ')) print(len(words_5_let)) print(words_5_let[:10]) print(words_5_let[-10:]) # 3896 # ['aaron', 'aback', 'abbas', 'abbey', 'abbot', 'abell', 'abide', 'abler', 'abner', 'abode'] # ['zabel', 'zadel', 'zebek', 'zebra', 'zeiss', 'zendo', 'zeros', 'ziggy', 'zones', 'zooey'] isalpha()メソッドを使うことにより、記号などを含まない、純粋なアルファベットのみで構成される英単語に制限しています。 結果、5文字の英単語 3896語が抽出されました。リストの冒頭・末尾に含まれる英単語を10語ずつprintしてみましたが、きちんと抽出できていそうですね。 次の処理は若干邪道ですが、Wordle辞書に含まれない数個の英単語をリストから手動で削除します。 実は本解析からWordleの解法を導いたとき、入力すると有望な英単語が複数パターン得られるのですが、上位解法パターンに含まれる英単語のいくつかが、Wordleに入力できませんでした (= Wordleの内部辞書に登録されていませんでした)。そこで、本解析を実施し、導かれた有望な英単語がWordleに入力可能かどうかを実際に試行し、Wordleに入力不可だった場合は解析用英単語リストから削除する処理を3回繰り返しました。 # Wordle辞書に含まれない単語リスト words_forbidden = ['digby', 'elman', 'bucky', 'danes', 'gorky', 'floyd'] # Wordle辞書に含まれない単語群を削除 words_5_let = [w for w in words_5_let if w not in words_forbidden] print(len(words_5_let)) # 3890 最終的に6単語を削除したので、5文字の英単語 3890語が残りました。 EDA ここまでで英語コーパスからWordle向けの英単語データセットを作成できたので、続いていくつかの解析を行います。 各アルファベットが英単語の5文字の枠にどのように使われているのか可視化 先に作成したデータセットに収録されている英単語において、1文字目から5文字目には、それぞれいずれのアルファベットが出現しやすいのか、集計して可視化します。 方針としては、まずデータセット中の全英単語の1文字目のアルファベットを取り出したリストを作成します。この作業をループで2から5文字目のアルファベットにも適用します。 list_lett_n = [] for i in range(5): list_lett_n.append([l[i] for l in words_5_let]) print(list_lett_n[i][:10]) # ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] # ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b'] # ['r', 'a', 'b', 'b', 'b', 'e', 'i', 'l', 'n', 'o'] # ['o', 'c', 'a', 'e', 'o', 'l', 'd', 'e', 'e', 'd'] # ['n', 'k', 's', 'y', 't', 'l', 'e', 'r', 'r', 'e'] collections.Counterを使って、リストの各要素の出現回数をカウントします。この際、most_common()メソッドを用いると、出現回数順にソートされた結果が返ってきます。もしアルファベット順にソートしたい場合には、temp = dict(sorted(collections.Counter(list_lett_n[i]).items())) のように書きます。 list_cnt_n = [] for i in range(5): temp = dict(collections.Counter(list_lett_n[i]).most_common()) list_cnt_n.append(temp) print({k:list_cnt_n[i][k] for k in list(list_cnt_n[i])[:10]}) # {'s': 484, 'b': 316, 'c': 314, 't': 238, 'a': 233, 'p': 230, 'm': 219, 'f': 207, 'd': 198, 'l': 197} # {'a': 681, 'o': 585, 'e': 487, 'i': 395, 'r': 364, 'u': 305, 'l': 267, 'h': 184, 'n': 103, 't': 98} # {'a': 447, 'i': 371, 'r': 364, 'o': 318, 'n': 292, 'l': 282, 'e': 276, 'u': 204, 't': 184, 's': 163} # {'e': 649, 't': 297, 'n': 288, 'l': 273, 'a': 267, 'i': 262, 'r': 229, 'o': 216, 's': 199, 'c': 186} # {'s': 887, 'e': 596, 'y': 406, 't': 265, 'n': 252, 'r': 243, 'a': 176, 'l': 169, 'h': 158, 'd': 153} 以上の処理を踏まえ、1文字目から5文字目における各アルファベットの出現回数を棒グラフとしてプロットします。4 出現回数をデータセットに占める単語総数 3890 で割ることにより、百分率に換算して表示します。 list_cnt_n_rate = [] for i in range(5): temp = np.array(list(list_cnt_n[i].values())) / sum(list_cnt_n[i].values()) * 100.0 list_cnt_n_rate.append(dict(zip(list(list_cnt_n[i].keys()), temp))) plt.bar(list(list_cnt_n_rate[i].keys()), list(list_cnt_n_rate[i].values())) # Convert a natural number to an ordinal ordinal = (lambda n: '%d%s' % (n, 'tsnrhtdd'[(n / 10 % 10 != 1) * (n % 10 < 4) * n % 10::4]))(i + 1) plt.title(f'Frequency of the alphabet at the {ordinal} letter') plt.ylabel('Frequency [%]') plt.show() 結果は以下の通りです。 1文字目にsが来る確率 (12.4%)、4文字目にeが来る確率 (16.7%)、5文字目にsが来る確率 (22.8%) は、それぞれ同じ文字位置における他のアルファベットと比較して高いことがわかります。すなわち、5文字の英単語の中では、sで始まる単語が最も多いことがわかります。5 5文字目にsが来る確率は全ての文字位置の中でも、絶対的に高くなっています。これは語尾がsで終わる複数形の英単語によって底上げされていることが予想されます。4文字目にeが来る確率が高いことは、5文字目にsが来る確率に共起されている可能性があると仮説を立てています。英語の発音上、eの長音に続き、sで終わる単語はよく見られますが (e.g. boxes)、この構造を反映しているのかもしれません。 2文字目の上位として、母音が現れていることも直感に反していないと考えられます。すなわち、1文字目の子音に続き、2文字目には子音ではなく母音が来ることで、英語の発音がしやすくなります。興味深いことは、1, 3, 4, 5文字目の出現確率のグラフは下位までテールを引いているのに対して、2文字目のグラフはバーの高さがより早く減衰していることです。この事実も、2文字目には子音よりも母音が来やすいという英語の発音上の特性が反映されていることが示唆されます。 もう一つ興味深いこととして、各文字位置に現れるアルファベットの出現回数は以下のようになります。 for i in range(5): print(i + 1, len(list_cnt_n[i])) # 1 26 # 2 26 # 3 26 # 4 26 # 5 24 1文字目から4文字目までは26アルファベット全てが使用されていますが、5文字目のみ24アルファベットしか使われていません。5文字目に出現するアルファベットを確認してみましょう。 print(sorted(list_cnt_n[4])) # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] すなわち、jとqで終わるアルファベットは (今回のデータセットの範疇では) 存在しないことになります。これはWordleの戦略に関わる大きな事実です。 各アルファベットが英単語にどのくらい使われているのか可視化 続いて、文字位置に関係なく、いずれのアルファベットが英単語に使用されやすいのか、集計して可視化します。 方針としては、先に作成した英単語の1文字目から5文字目までのアルファベットの出現回数をカウントしたリストを統合し、全英単語中の各アルファベットの出現回数を調べます。 def add_two_dicts(dict_a, dict_b): dict_new = {} for key in sorted(dict_a.keys() | dict_b.keys()): val_a = dict_a.get(key) or 0 val_b = dict_b.get(key) or 0 val = val_a + val_b dict_new[key] = val return dict_new list_cnt_all = add_two_dicts(list_cnt_n[0], list_cnt_n[1]) list_cnt_all = add_two_dicts(list_cnt_all, list_cnt_n[2]) list_cnt_all = add_two_dicts(list_cnt_all, list_cnt_n[3]) list_cnt_all = add_two_dicts(list_cnt_all, list_cnt_n[4]) list_cnt_all = dict(sorted(list_cnt_all.items(), key=lambda x:x[1], reverse=True)) こちらも百分率に換算してプロットします。 temp = np.array(list(list_cnt_all.values())) / sum(list_cnt_all.values()) * 100.0 list_cnt_all_rate = dict(zip(list(list_cnt_all.keys()), temp)) list_cnt_all_rate plt.bar(list(list_cnt_all_rate.keys()), list(list_cnt_all_rate.values())) plt.title(f'Frequency of the alphabet') plt.ylabel('Frequency [%]') plt.show() 上図より、eの出現確率が最も高く、10.83%となりました。すなわち、今回の解析からは、英単語中でeが最も多く使われるアルファベットであることが示唆されました。 この事実は、シャーロック・ホームズシリーズの短編小説『踊る人形 (The Adventure of the Dancing Men)』における、次の有名なセリフと相違ありません。6 As you are aware, E is the most common letter in the English alphabet, and it predominates to so marked an extent that even in a short sentence one would expect to find it most often. Out of fifteen symbols in the first message four were the same, so it was reasonable to set this down as E. 同作品では、「英語で最も多く使われる文字はEである」という手掛かりをもとに、「踊る人形」と称される暗号を解き、推理が進んでいきます。 話は解析に戻り、eに続いて出現しやすいaおよびsでも、出現確率がそれぞれ9.27%および9.11%と高い値を取ることがわかりました。つまり、e/a/sの3文字だけで約30%の実効的なアルファベット空間を占めることができます。 この解析からは様々な戦法が導出できそうなので、最後に全アルファベットの出現確率を降順にソートし、累積確率とともに示します。 df_let_all = pd.DataFrame(list(list_cnt_all_rate.values()), index=list(list_cnt_all_rate.keys()), columns=['rate']) df_let_all['accumulate'] = list(itertools.accumulate(list(list_cnt_all_rate.values()))) df_let_all alphabet rate accumulate e 10.832905 10.832905 a 9.269923 20.102828 s 9.110540 29.213368 r 7.028278 36.241645 o 6.704370 42.946015 l 6.102828 49.048843 i 5.866324 54.915167 t 5.562982 60.478149 n 5.275064 65.753213 c 3.588689 69.341902 d 3.434447 72.776350 u 3.434447 76.210797 h 3.012853 79.223650 m 2.910026 82.133676 y 2.832905 84.966581 p 2.632391 87.598972 b 2.580977 90.179949 g 2.313625 92.493573 k 1.845758 94.339332 f 1.717224 96.056555 w 1.444730 97.501285 v 1.192802 98.694087 z 0.416452 99.110540 j 0.401028 99.511568 x 0.339332 99.850900 q 0.149100 100.000000 共起ネットワークの解析 続いて、一つの英単語の中で、あるアルファベットとあるアルファベットが一緒に出現しやすいかどうか (共起しやすいかどうか) をネットワーク図として可視化します。ここでは、以下の例で示すルールに則って、各単語に出現するアルファベットのペアを抽出します。 _e_s_ (e, s)は抽出される (s, e)は抽出されない _s_e_ (s, e)は抽出される (e, s)は抽出されない _e_sE (二つのイーを大文字・小文字を用いて区別する) (e, s), (e, E), (s, E)は抽出される (s, e), (E, e), (E, s)は抽出されない すなわち、前から後ろへの文字の流れを考慮し、異なる文字位置にある同アルファベットは別物として扱い、アルファベットのペアを取得します。ペアの取得にはitertools.combinations()を用います。 combi_let = [list(itertools.combinations(word, 2)) for word in words_5_let] combi_let = list(itertools.chain.from_iterable(combi_let)) list_cnt_combi_let = dict(collections.Counter(combi_let).most_common()) for i, (k, v) in enumerate(zip(list_cnt_combi_let.keys(), list_cnt_combi_let.values())): if i == 10: break print(i, k, v) # 0 ('e', 's') 478 # 1 ('a', 's') 471 # 2 ('a', 'e') 446 # 3 ('r', 'e') 396 # 4 ('l', 'e') 378 # 5 ('e', 'r') 372 # 6 ('o', 's') 369 # 7 ('s', 'e') 359 # 8 ('a', 'n') 357 # 9 ('a', 'r') 346 取得したペアをネットワーク図として可視化します。networkx を用いてグラフを作成します。ノードには各アルファベット、エッジには各ノードペアを割り当てます。先ほどの例で云う (e, s)と (s, e) を区別し、有向グラフ DiGraph() としてプロットします。また、ノード・エッジの大きさ・太さ・色により、各アルファベットの出現数、各アルファベットペアの出現数を表現します。 G = nx.DiGraph() G.add_nodes_from(list(list_cnt_all.keys())) G.add_edges_from(list(list_cnt_combi_let.keys())) nx.set_edge_attributes(G, name='weight', values=0) for u, v in list_cnt_combi_let.keys(): G.edges[u, v]['weight'] = list_cnt_combi_let[u, v] # Visualize pos = nx.circular_layout(G) nx.draw_networkx(G, pos=pos, node_color="c", node_size=np.array(list(list_cnt_all.values()))/10, width=[G.edges[u, v]['weight']/500 for u, v in G.edges], edge_color=[G.edges[u, v]['weight']/500 for u, v in G.edges], connectionstyle='arc3, rad=0.2') やはり、出現頻度の高いアルファベットから集中してエッジが生えていますが、この図だけだと複雑で知見を得にくいです。そこで、もう少し単純化し、連続したアルファベットの共起関係のみを可視化します。つまり、scare という単語からは (s, c), (c, a), (a, r), (r, e) という4個のペアを抽出します。 def extract_consecutive_let(word): list_new = [] for i in range(len(word) - 1): temp = word[i], word[i + 1] list_new.append(temp) return list_new combi_let_consecutive = [extract_consecutive_let(word) for word in words_5_let] combi_let_consecutive = list(itertools.chain.from_iterable(combi_let_consecutive)) list_cnt_combi_let_consecutive = dict(collections.Counter(combi_let_consecutive).most_common()) for i, (k, v) in enumerate(zip(list_cnt_combi_let_consecutive.keys(), list_cnt_combi_let_consecutive.values())): if i == 10: break print(i, k, v) # 0 ('e', 'r') 260 # 1 ('e', 's') 234 # 2 ('a', 'r') 212 # 3 ('a', 'n') 208 # 4 ('i', 'n') 208 # 5 ('l', 'e') 194 # 6 ('r', 'a') 189 # 7 ('a', 'l') 174 # 8 ('r', 'e') 172 # 9 ('s', 't') 163 同じく、各アルファベットおよびアルファベットペアの出現個数をネットワーク図に可視化します。 G2 = nx.DiGraph() G2.add_nodes_from(list(list_cnt_all.keys())) G2.add_edges_from(list(list_cnt_combi_let_consecutive.keys())) nx.set_edge_attributes(G2, name='weight', values=0) for u, v in list_cnt_combi_let_consecutive.keys(): G2.edges[u, v]['weight'] = list_cnt_combi_let_consecutive[u, v] # Visualize pos = nx.circular_layout(G2) nx.draw_networkx(G2, pos=pos, node_color="c", node_size=np.array(list(list_cnt_all.values()))/10, width=[G2.edges[u, v]['weight']/500 for u, v in G2.edges], edge_color=[G2.edges[u, v]['weight']/500 for u, v in G2.edges], connectionstyle='arc3, rad=0.2') 依然として複雑ではありますが、先ほどのネットワーク図よりも大分シンプルになりました。ここでも出現頻度の高いノードから生えているエッジが多いこと、母音から生えているエッジが多いことは当然ですが、いくつか子音間でも太いエッジがあることがわかります。この図から得られる知見は、Wordleで穴抜きのアルファベットを推測するのに役立つかもしれません。 Wordleの戦略立案 さて、ここまでの知見を利用して、データサイエンス的にWordleの効率的な戦略を考えてみたいと思います。 目的関数 (評価関数・報酬関数) は色々考えられるので、それに応じて戦略も様々なパターンが考えられます。 まず、複数回の試行で同じアルファベットを入力することは冗長なので、同じアルファベットを使わない単語をリストから抽出します。単語内に包含される文字をset化することで、ユニークな文字のみが生き残ります。生き残った文字数が5文字であるかにより、冗長性のないアルファベットかどうかを判定します。 word_5_let_no_redun = [word for word in words_5_let if len(set(word)) == 5] print(word_5_let_no_redun[:10]) print(len(word_5_let_no_redun)) # ['abide', 'abler', 'abner', 'abode', 'about', 'above', 'abuse', 'aches', 'acids', 'acres'] # 2554 同じアルファベットを含まない英単語は、今回2554語見つかりました。この単語群を用いて、以下のいくつかの戦略を考えます。 出現確率の高いアルファベットをなるべく先に消化する作戦 先ほど、各アルファベットの出現確率の序列を調べました。e, a, s, r, o, l, i, t, n, c, d, u, h, m, y, p, b, ... という序列です。このランキング上位に現れるアルファベットからできるだけ順に使って、Wordleの試行として入力する英単語を定めます。 まずは、最上位のe, a, s, r, oを使った英単語をリストから探します。 [w for w in word_5_let_no_redun if 'e' in w and 'a' in w and 'r' in w and 'o' in w and 's' in w] # ['arose'] arose の1語のみ発見されました。 続いて、l, i, t, n, c を使った英単語をリストから探します。 [w for w in word_5_let_no_redun if 'l' in w and 'i' in w and 't' in w and 'n' in w and 'c' in w] # ['clint'] clint (岩棚) の1語のみ発見されました。 次の5文字 d, u, h, m y を使った英単語をリストから探します。 [w for w in word_5_let_no_redun if 'u' in w and 'd' in w and 'y' in w and 'h' in w and 'm' in w] # [] 流石にそう都合良くは見つかりませんでした。 そこで、既出の e, a, s, r, o, l, i, t, n, c を含まない英単語をリストから探します。 [w for w in word_5_let_no_redun if 'e' not in w and 'a' not in w and 'r' not in w and 'o' not in w and 's' not in w and 'l' not in w and 'i' not in w and 't' not in w and 'n' not in w and 'c' not in w] # ['jumpy'] jumpy (ガタガタする・ビクビクする) の1語のみ発見されました。 この単語群に含まれるアルファベットの出現確率から、全体に対する寄与を算出します。 contri_arose = list_cnt_all_rate['a'] + list_cnt_all_rate['r'] + list_cnt_all_rate['o'] + list_cnt_all_rate['s'] + list_cnt_all_rate['e'] contri_arose # 42.94525828835775 aroseの寄与は42.9%でした。 contri_clint = list_cnt_all_rate['c'] + list_cnt_all_rate['l'] + list_cnt_all_rate['i'] + list_cnt_all_rate['n'] + list_cnt_all_rate['t'] contri_clint # 26.3993831919815 clintの寄与は26.4%でした。 contri_jumpy = list_cnt_all_rate['j'] + list_cnt_all_rate['u'] + list_cnt_all_rate['m'] + list_cnt_all_rate['p'] + list_cnt_all_rate['y'] contri_jumpy # 12.212798766383962 jumpyの寄与は12.2%でした。 contri_arose + contri_clint # 69.34464148033925 すなわち、最初2回の試行でaroseとclintを入力すると、空間の69.3%を網羅できます。 contri_arose + contri_clint + contri_jumpy # 81.55744024672322 さらに、最初の3回の試行でaroseとclintとjumpyを入力すると、空間の81.6%を網羅できます。 【作戦1】 "arose", "clint", "jumpy" をこの順番で入力することは、3回の試行で81.6%の空間を網羅し得る最も効率的な戦略の一つである。 効率的な単語の組み合わせをbrute-forceに探索 アルファベットの出現確率の序列のうち、末尾の j, z, x, q は特に出現確率が低く、いずれも1%未満でした。また、そのすぐ上位に位置する w, v の出現確率も1.5%未満です。そこで、w, v, j, z, x, q の6文字 (総確率 3.9%) を除き、残り20文字のアルファベット (総確率 96.1%) で構成できる英単語の組み合わせをbrute-force (総当たり) で探索します。 まず、特定のアルファベットを含む英単語を親リストから削除する関数を実装しておきます。 # Remove words containing specific letters from the list def remove_specific_let(words, letters, verbose=False): words_new = copy.deepcopy(words) for let in letters: words_new = [word for word in words_new if let not in word] if verbose: print(f'{let} has been removed; #words = {len(words_new)}') return words_new # Remove words containing five specific letters from the list # (faster than the one above) def remove_specific_5_let(words, letters, verbose=False): words_new = [w for w in words if letters[0] not in w and letters[1] not in w and letters[2] not in w and letters[3] not in w and letters[4] not in w] if verbose: print(f'{letters} has been removed; #words = {len(words_new)}') return words_new 上の関数を用いて、探索空間から w, v, j, z, x, q を除外します。 word_5_let_no_redun_constrain = remove_specific_let(word_5_let_no_redun, 'wvzjxq', verbose=True) # w has been removed; #words = 2342 # v has been removed; #words = 2185 # z has been removed; #words = 2146 # j has been removed; #words = 2097 # x has been removed; #words = 2051 # q has been removed; #words = 2030 最終的に探索空間は2030語まで削減されました。 ここから除外されなかったアルファベット20語を重複なく含む、4語の英単語の組み合わせをbrute-force探索します。ここではstraightforwardに4重ループを書いて探索します。4重ループにすると計算コストが気になるので、実行時間を計測しておきます。今回はJupyter Notebookでコーディングしているので、%%timeコマンドを付けるだけでこのセルのCPU timeとwall timeを簡単に調べることができます。 %%time words_efficient = [] for w1 in word_5_let_no_redun_constrain: rest_words_1 = remove_specific_5_let(word_5_let_no_redun_constrain, w1) for w2 in rest_words_1: rest_words_2 = remove_specific_5_let(rest_words_1, w2) for w3 in rest_words_2: rest_words_3 = remove_specific_5_let(rest_words_2, w3) for w4 in rest_words_3: rest_words_4 = remove_specific_5_let(rest_words_3, w4) words_efficient.append([w1, w2, w3, w4]) # CPU times: user 32.4 s, sys: 0 ns, total: 32.4 s # Wall time: 32.5 s 探索は32.5秒のwall timeで完了しました。4重ループによるbrute-force探索でしたが、比較的短時間で探索することができました。組み合わせパターンが限定的であるためと推測されます。 発見された単語のパターンをデータフレームとして出力してみます。 df_words_efficient = pd.DataFrame(words_efficient, columns=['w1', 'w2', 'w3', 'w4']) df_words_efficient 全部で86544のパターンの単語群が抽出されました。画面出力されている組み合わせを見ると、20個のアルファベットが重複なく4単語に散らばっていることが確認できます。ここで注意すべきは、たとえばデータフレームの0行目と1行目を比べると、これらのパターンでは用いている4単語の組み合わせは同一ですが、3単語目と4単語目の順番のみが異なります。Wordleでは同じ単語の組み合わせを入力する場合でも、どの単語をより早く入力するかに依存して、その戦略の有効性が変わってくるため、本検討ではこのような出力の形式を取っています。 並び順を無視した、ユニークな組み合わせパターンの個数をカウントしてみましょう。 words_efficient = df_words_efficient.values.tolist() temp = [tuple(sorted(words)) for words in words_efficient] words_efficient_no_redun = sorted(set(temp)) print(words_efficient_no_redun[:3]) print(len(words_efficient_no_redun)) # [('abide', 'frock', 'lymph', 'stung'), ('abner', 'docks', 'fight', 'lumpy'), ('abner', 'dutch', 'folks', 'gimpy')] # 3606 ユニークな組み合わせは3606パターン存在することが確認できました。 さて、ここでは単語の入力順も考慮した全86544パターンの単語群それぞれに対して、いくつかの妥当なスコアリングを行い、Wordleにおける有効な戦術を導いていきましょう。 まずは、最も標準的なやり方として、先に調べた「単語リストにおける各アルファベットの出現確率」(文字位置に関係なく、いずれのアルファベットが英単語に使用されやすいのかという指標) によって、各単語のスコアリングをします。本検討ではこの評価指標をsスコアと称することにします。 scores_whole = [] for i in range(len(words_efficient)): scores_whole_temp = [] for j in range(4): word = words_efficient[i][j] score = 0.0 for k in range(len(word)): score += list_cnt_all_rate[word[k]] scores_whole_temp.append(score) scores_whole.append(scores_whole_temp) df_scores_whole = pd.DataFrame(scores_whole, columns=[f's{i+1}' for i in range(4)]) df_scores_whole.describe() 各パターンに現れる4単語のsスコアが算出されました。さらに、「sスコアの高い単語ほど先に入力すべきである」と考えられることから、4単語のsスコアに対して重み係数を導入し、先に入力する単語ほど重みが大きくなるように調整します。この指標をrスコアと呼ぶことにします。ここでは単純に4単語のsスコアに対して、前から順に0.4, 0.3, 0.2, 0.1という重み係数を掛けたものをrスコアとします。最終的にrスコアの総和 r_sum が、各単語パターンの良し悪しを判断する指標の一つとなります。 # Weights c1, c2, c3, c4 = 0.4, 0.3, 0.2, 0.1 df_scores_whole['r1'] = df_scores_whole['s1'] * c1 df_scores_whole['r2'] = df_scores_whole['s2'] * c2 df_scores_whole['r3'] = df_scores_whole['s3'] * c3 df_scores_whole['r4'] = df_scores_whole['s4'] * c4 df_scores_whole['r_sum'] = df_scores_whole['r1'] + df_scores_whole['r2'] + df_scores_whole['r3'] + df_scores_whole['r4'] df_scores_whole 最後に、pスコアという指標を導入します。こちらも先に算出した確率的指標で、「単語リストの各文字位置における各アルファベットの出現確率」(英単語の5文字の枠それぞれにおいて、いずれのアルファベットが使用されやすいのかという指標) によって、各単語のスコアリングをします。 scores_partial = [] for i in range(len(words_efficient)): scores_partial_temp = [] for j in range(4): word = words_efficient[i][j] score = 0.0 for k in range(len(word)): score += list_cnt_n_rate[k][word[k]] scores_partial_temp.append(score) scores_partial.append(scores_partial_temp) df_scores_partial = pd.DataFrame(scores_partial, columns=[f'p{i+1}' for i in range(4)]) df_scores_partial Wordleを遊ぶ上では、入力したアルファベットの文字位置が正しいかどうかも知りたい情報ですが、そもそも入力したアルファベットが単語の中に含まれるかどうかということの方が、多くの場合においてより重要な情報です。そこでpスコアは、先のrスコアおよびsスコアと組み合わせることで、補助的に用いることを考えます。ここでは、rスコアとpスコアの積をrpスコア、sスコアとpスコアの積をspスコアと呼称します。rpスコアおよびspスコアの総和もそれぞれ、各英単語パターンの評価指標の一つとなります。探索された英単語パターンとそれらのスコアをconcatして、最終的なデータフレームを作成しましょう。 df_words_efficient_with_scores = pd.concat([df_words_efficient, df_scores_whole, df_scores_partial], axis=1) df_words_efficient_with_scores['rp_sum'] = df_words_efficient_with_scores['r1'] * df_words_efficient_with_scores['p1'] + df_words_efficient_with_scores['r2'] * df_words_efficient_with_scores['p2'] + df_words_efficient_with_scores['r3'] * df_words_efficient_with_scores['p3'] + df_words_efficient_with_scores['r4'] * df_words_efficient_with_scores['p4'] df_words_efficient_with_scores['sp_sum'] = df_words_efficient_with_scores['s1'] * df_words_efficient_with_scores['p1'] + df_words_efficient_with_scores['s2'] * df_words_efficient_with_scores['p2'] + df_words_efficient_with_scores['s3'] * df_words_efficient_with_scores['p3'] + df_words_efficient_with_scores['s4'] * df_words_efficient_with_scores['p4'] df_words_efficient_with_scores.loc[:,['w1', 'w2', 'w3', 'w4', 'r_sum', 'rp_sum', 'sp_sum']] 戦略策定に向けて、統計情報も表示しておきましょう。 df_words_efficient_with_scores.loc[:,['w1', 'w2', 'w3', 'w4', 'r_sum', 'rp_sum', 'sp_sum']].describe() さて、各指標に従って、複数の有効な戦略を導出してみましょう。以下では、それぞれの指標を最大化するような単語パターンを抽出します。 【rスコア】 df_words_efficient_with_scores[df_scores_whole['r_sum'] == df_scores_whole['r_sum'].max()].loc[:,['w1', 'w2', 'w3', 'w4', 'r_sum', 'rp_sum', 'sp_sum']] rスコアが最大となるパターンは2組見つかりました。 (gears, doubt, flick, nymph あるいは rages, doubt, flick, nymph) 【rpスコア】 df_words_efficient_with_scores[df_words_efficient_with_scores['rp_sum'] == df_words_efficient_with_scores['rp_sum'].max()].loc[:,['w1', 'w2', 'w3', 'w4', 'r_sum', 'rp_sum', 'sp_sum']] rpスコアが最大となるパターンは1組見つかりました。 (bales, dingy, frock, thump) 【spスコア】 df_words_efficient_with_scores[df_words_efficient_with_scores['sp_sum'] == df_words_efficient_with_scores['sp_sum'].max()].loc[:,['w1', 'w2', 'w3', 'w4', 'r_sum', 'rp_sum', 'sp_sum']] 20パターンの単語群が得られました。 (bundy, frock, might, pales の並び替え) 目的関数 (評価関数・報酬関数) の定め方が一意ではないので、ここでは3種類の戦略が見出されました。このうち、敢えて一つの戦略のみを選出するならば、rpスコアの総和を最大化するパターン (bales, dingy, frock, thump7) が良いかもしれません。 なぜならば、rpスコアは「各アルファベットの出現確率」、「各文字位置における各アルファベットの出現確率」、および「sスコアの高い単語ほど先に入力すべきである」という最も多くの要素を同時に考慮している指標となっているからです。さらに、この英単語のパターンはrpスコアの総和を最大化するだけでなく、rスコアの総和およびspスコアの総和も、比較的高い値をとっていることがわかります。 この戦略における評価関数の定め方に関しては議論が尽きないところですが、本セクションの仮定より、いずれの英単語の組み合わせも出現しやすいアルファベット上位20種類のアナグラムで構成されていることから、どのようなミクロな戦略を取ったとしても、全空間の96.1%の可能性を網羅可能であることは確かです。 【作戦2】 "bales", "dingy", "frock", "thump" を入力することは、4回の試行で96.1%の空間を網羅し得る最も効率的な戦略の一つである。 まとめ Brownコーパスという英語コーパスから、5文字の英単語リストを作成しました。 作成した英単語リストに対する統計解析より、各アルファベットの出現確率を複数の観点から調べました。 解析結果に基づき、Wordleにおける複数の有効な戦略を見出しました。特に有効な二つの戦略 (入力パターン) は以下の通りです。 arose → clint → jumpy (3試行): 81.6%の空間を網羅可能 bales → dingy → frock → thump (4試行): 96.1%の空間を網羅可能 インターネット上ではWordleに搭載されている英語辞書が特定されているようですが、本検討ではそちらを参照せず、あくまで一般的な知識に基づいて挑みます。単語空間が完全に特定されているならば、最強の作戦を立案することができそうですよね。 ↩ 参考ページ ↩ 本当はWordleで対象外らしい固有名詞 (品詞タグ: NNP) も除外したかったのですが、nltkによる固有名詞の判定精度が今ひとつに感じたので、今回は省略しました。具体的には、大文字始まりの一般名詞まで固有名詞と判定されることが多いように見受けられました。 ↩ ここではグラフタイトルに序数を含めるために、こちらのページの方法を参考にさせていただきました。 ↩ この理由を考えてみましたが、あまり思いつきませんでした。単純に偶々と割り切るのが良いでしょうか…。情報をご存知の方、仮説を思いつかれた方はご教示いただけますと幸いです。 ↩ The Adventure of the Dancing Men (PDF) ↩ それぞれの英単語の意味は次の通りです。bales = bale (俵) の複数形、dingy = 薄汚れた、frock = 仕事着/僧服、thump = ゴツンと叩く。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Seleniumのチートシート

Seleniumのチートシートをまとめてみた。 ライブラリのインポート、ドライバ・ブラウザの開始 #1.ライブラリのインポート from selenium import webdriver #2.ウェブドライバを開始する # Chromeの場合 chromedriver = "C:/tests/chromedriver.exe" driver = webdriver.Chrome(executable_path = chromedriver) # Firefoxの場合 geckodriver = "C:/tests/geckodriver.exe" driver = webdriver.Firefox(executable_path = geckodriver) # Internet Explorerの場合 iedriver = "C:/tests/IEDriverServer.exe" driver = webdriver.Firefox(executable_path = iedriver) # Safariの場合(Safariの場合、ドライバがブラウザに組み込まれている) driver = webdriver.Safari() #3.ウェブサイトを開く the_url = "https://example.com" driver.get(the_url) ウェブサイトの要素取得 #4.要素(element)を検索する # 例、下記要素を探す場合 <a href="/sign-up" id="register" name="register" class="cta nav-link">Sign Up</a>path) # ID要素を探す the_id = 'register' element = driver.find_element_by_id(the_id) # 名前で要素を探す the_name = 'register' element = driver.find_element_by_id(the_name) # クラス名で要素を探す the_class_name = 'nav-link' element = driver.find_element_by_class_name(the_class_name) # Tag名で要素を探す the_tag_name = 'a' element = driver.find_element_by_tag_name(the_tag_name) # Linkテキストで要素を探す Works only for anchor elements. the_link_text = 'Sign Up' element = driver.find_element_by_link_text(the_link_text) # Partial Link Textで要素を探す Works only for anchor elements. the_partial_link_text = 'Sign' element = driver.find_element_by_partial_link_text(the_partial_link_text) 要素への処理 #5.要素をクリックする(例 登録ボタン) the_id = 'register' element = driver.find_element_by_id(the_id) element.click() #6.要素の中に書き込む(例 メールアドレスの入力) the_id = 'email' the_email = 'maruchin@maru.co.jp' element = driver.find_element_by_id(the_id) element.send_keys(the_email) #7.選択する(例 プルダウン選択) # 下記のように国選択がある <select id="country"> <option value="US">United States</option> <option value="CA">Canada</option> <option value="MX">Mexico</option> </select> # 名前=Canadaを選択する the_id = 'country' element = driver.find_element_by_id(the_id) select_element = Select(element) select_element.select_by_visible_text('Canada') # ID要素=CAから選択する the_id = 'country' element = driver.find_element_by_id(the_id) select_element = Select(element) select_element.select_by_value('CA') # indexから選択する the_id = 'country' element = driver.find_element_by_id(the_id) select_element = Select(element) select_element.select_by_index(1) スクリーンショット、アップロード、JavaScript #8.スクリーンショットを撮る the_path = 'C:/tests/screenshots/1.png' driver.save_screenshot(the_path) #9.ファイルをアップロードする <input type="file" multiple="" id="upload_button"> the_file_path = 'C:/tests/files/example.pdf' the_id = 'upload_button' element = driver.find_element_by_id(the_id) element.send_keys(the_file_path) # まずfile要素を探すことが必要 #10.JavaScriptを実行する js_code = 'document.getElementById("pop-up").remove()' driver = execute_script(js_code) インラインフレームの操作 11. iframe(インラインフレーム)に切り替える <iframe id="payment_section"> <input id="card_number"> <input id="card_name"> <input id="expiration_date"> <input id="cvv"> </iframe> # 上記のようなiframeに切り替える the_iframe_id = 'payment_section' the_element_id = 'card_number' the_iframe = driver.find_element_by_id(the_iframe_id) driver.switch_to.frame(the_iframe) element = driver.find_element_by_id(the_element_id) element.send_keys('41111111111111') driver.switch_to.default_content() タブの操作 #12.次のタブに切り替える # タブを1つだけ開いてるとき、handle=0である global nextTab global currentTab nextTab = currentTab + 1 driver.switch_to_window(driver.window_handles[nextTab]) currentTab = currentTab + 1 #13. 前のタブに切り替える global previousTab global currentTab previousTab = currentTab - 1 driver.switch_to_window(driver.window_handles[previousTab]) currentTab = currentTab - 1 #14. タブを閉じる driver.close() 各種操作①(カーソル等) 15. アラートを閉じる driver.switch_to.alert.accept() 16. 更新する driver.refresh() 17. Hover the_id = "register" the_element = driver.find_element_by_id(the_id) hover = ActionChains(driver).move_to_element(the_element) hover.perform() 18. 右クリック the_id = "register" the_element = driver.find_element_by_id(the_id) right_click = ActionChains(driver).context_click(the_element) right_click.perform() 各種操作②(カーソル等) #19.オフセットしてクリックする # オフセットは画面左上を座標(0, 0)としてピクセル表示される the_id = "register" the_element = driver.find_element_by_id(the_id) x = 30 y = 20 offset = ActionChains(driver).move_to_element_with_offset(the_element,x,y) offset.click() offset.perform() #20.キーを押す the_id = 'register' element = driver.find_element_by_id(the_id) element.send_keys(Keys.RETURN) #21.ドラッグ&ドロップ element_to_drag_id = 'ball' target_element_id = 'goal' element_to_drag = driver.find_element_by_id(element_to_drag_id) target_element = driver.find_element_by_id(target_element_id) ActionChains(driver).drag_and_drop(element_to_drag_id, target_element).perform() ソース、クッキーの操作 22. ページソースを取得する the_page_source = driver.page_source 23. クッキーを取得する cookies_list = driver.get_cookies() 24. クッキーを削除する cookie_item = 'shopping_cart' # ひとつのクッキーを削除する driver.delete_cookie(cookie_item) # すべてのクッキーを削除する driver.delete_all_cookies() リスト取得、タイムアウト #25.リストから最初の要素を取得する the_id = 'register' list_of_elements = driver.find_elements_by_id(the_id) first_element = list_of_elements[0] #26.ページ読み込みのタイムアウトを設定する driver.set_page_load_timeout(20) #27.要素のロードタイムアウトを設定する from selenium.webdriver.support.ui import WebDriverWait the_id = 'register' WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID, the_id))) サイズ、ユーザーエージェント #28.ウィンドウサイズを設定する driver.set_window_size(1600, 1200) #29.ユーザーエージェント文字列の変更 the_user_agent = 'hello' chromedriver = 'C:/tests/chromedriver.exe' options = webdriver.ChromeOptions() options.add_argument('--user-agent = '+ the_user_agent) driver = webdriver.Chrome( executable_path = chromedriver, chrome_options = options) 外部操作 30. ウェブカメラとマイクを使う chromedriver = 'C:/tests/chromedriver.exe' options = webdriver.ChromeOptions() options.add_argument("--use-fake-ui-for-media-stream") options.add_argument("--use-fake-device-for-media-stream") driver = webdriver.Chrome( executable_path = chromedriver, chrome_options = options) 31. chromeの拡張機能を使う chromedriver = 'C:/tests/chromedriver.exe' extension_path = 'C:/tests/my_extension.zip' options = webdriver.ChromeOptions() options.add_extension(extension_path) driver = webdriver.Chrome( executable_path = chromedriver, chrome_options = options) 32. モバイル端末をエミュレート(模倣)する google_pixel_3_xl_user_agent = 'Mozilla/5.0 (Linux; Android 9.0; Pixel 3 XL Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36' pixel_3_xl_emulation = { "deviceMetrics": { "width": 411, "height": 731, "pixelRatio": 3 }, "userAgent": google_pixel_3_xl_user_agent } options = webdriver.ChromeOptions() options.add_experimental_option("mobileEmulation", pixel_3_xl_emulation) driver = webdriver.Chrome( executable_path = chromedriver, chrome_options = options) 参照元 ・【Python】Seleniumチートシート https://qiita.com/DisneyAladdin/items/be771591c0a06b30a369 ・【完全版】PythonとSeleniumでブラウザを自動操作(クローリング/スクレイピング)するチートシート https://tanuhack.com/selenium/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでexcelファイルの正規表現置換をする

excelの内容を正規表現で置換したかった。 ある列に、数字をカンマで区切った内容を入れてあるのだが、 最後がカンマで終わっている場合、カンマを取りたい。 たとえば、 123, 124, 222, 234 333, 444, 555, 666, 111, 222, 333 みたいな場合、2行目の666, のカンマをとって 333, 444, 555, 666 としたい。 pythonの正規表現は使ったことがあり、 最初は resut = pattern.match(",$",s1) という風にしたのだが、なぜかマッチしない。 findallを使ったらマッチした。 置換はもっとスマートな方法があるかもしれないが、 最後の一文字を落とす、という風にした。 rep.py import glob import os import openpyxl import re import sys pattern = re.compile('.*,') bookname = sys.argv[1] newbook = sys.argv[2] wb = openpyxl.load_workbook(bookname,data_only=True) ws = wb[u"Sheet1"] for row in range(1000): c1 = ws.cell(row=row+1, column=9) if c1.value is None: pass else: s1 = str(c1.value) result = re.findall(r",$",s1) if result: print(str(s1[:len(s1)-1])) s2 = str(s1[:len(s1)-1]) c1.value = s2 wb.save(newbook) wb.close 列は9列目(「I列」)を固定で指定。 ファイル名と保存ファイル名は引数で指定 python rep.py filename1 filename2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Minecraftのserver起動のCommand入力を出来るだけ楽にしたい

こんにちは。里芋のすり身です。 突然ではありますが、 「Minecraft」(以下マイクラと表記)というゲームは好きですか? みんな好きだと思ってます。 ただ、マイクラサーバーをCommandで入力するとき、 $ ~~~ % cd ~~~/~~~/~~~/~~~/Minecraft/サーバー用ファイル $ ~~~ % java -Xmx~G -Xms~G -jar server.jar nogui のような、Command入力していませんか? これ、正直面倒ですよね。そこで、楽にしてみたいと思って、ちょこっとプログラミングを書いてみました。よければ見ていってください。 Minecraftの事前準備 Minecraftのインストールはお済みでしょうか?この記事はマインクラフトサーバーを自分で立てようと思ってない方以外は役に立てないと思うので、その点ご留意を。 本当に簡単なサーバーの立て方の説明 Minecraftからダウンロードしたserver.jarを[Application Support/Minecraft/サーバー用ファイル]の中に突っ込んでjarファイルを解凍。その後server.jarをクリック。Command入力の場合は、 $ ~~~ % cd ~~~/~~~/~~~/~~~/Minecraft/サーバー用ファイル $ ~~~ % java -Xmx~G -Xms~G -jar server.jar nogui で、起動します。(~は任意の数) cd-->java ~ が面倒で仕方がない これすら面倒になりました。そこで勝手に起動してくれるようにできないかなぁと思った時に、 pythonの $ ~~~ % pyinstaller mine_driver.py --onefile で、windowで言えばexe化、macで言えば起動アプリ化できることが閃きまして、pythonでterminalを操作できれば起動アプリが作れるぞ!と思い、ちょこっと書いてみました。 Commandを動かす上での注意 これは、server.jarが[Application Support/Minecraft/サーバー用ファイル]に入っていることが前提です。あと、これはターミナルを移動するコマンドを書いているだけなので、実際に動かしているのはterminal操作であってjavaをpythonで起動させている訳ではないです。初心者Commandなので、その点ご留意を #================================================# #Pythonとserver.jarを組み合わせた楽々起動マン #作成日:2022/02/13 #製作者:里芋のすり身 #================================================# import subprocess import os import sys #================================================# #1.PATHの取得から、Minecraftのファイルまでの自動移動 #================================================# path = os.getcwd() # PATHの取得 print('現在のPATHは ' + path + ' です。') #そのpathを出力 def change_file(): while True: choice = input("Minecraftファイルまで移動します。よろしいですか?[y/N]").lower() if choice in ['y', 'ye', 'yes']: return True elif choice in ['n', 'no']: return False if __name__ == '__main__': if change_file(): os.chdir('/Users/~~~~~~/~~~~~~~/Application Support/Minecraft/~~~~~~~~~~') #change directly path = os.getcwd() #PATHの更新 print('現在のPATHは ' + path + ' です。') else: print('変更しませんでした。') path = os.getcwd() #更新などないが、一応更新 print('現在のPATHは ' + path + ' です。') sys.exit() #================================================# #2.実際に移動したファイルでサーバーを起動 #================================================# def running_minecraft(): while True: choice = input("Minecraftを起動します。よろしいですか?[y/N]").lower() if choice in ['y', 'ye', 'yes']: return True elif choice in ['n', 'no']: return False if __name__ == '__main__': if running_minecraft(): try: subprocess.run("java -Xmx4G -Xms1G -jar server.jar nogui",shell = True) except: print ('ERROR') else: print('起動しませんでした。') こんな感じにしました。他の人のプログラミングを少し参考にさせてもらってます。 python内の os.chdir('/Users/~~~~~~/~~~~~~~/Application Support/Minecraft/~~~~~~~~~~') #change directly の部分がもしMinecraftファイルの中にserver専用ファイルを用いてた場合はこの~~~~の部分はそのファイル名にしてください。一回設定してしまえば、後で場所を動かさなければ書き換える必要がないので。 最後に起動ファイル化 あとは $ ~~~ % pyinstaller mine_driver.py --onefile で、はい!出来上がりです。 これで、起動も確認できると思います。わからなかったら質問待ってます。 初心者コマンドなので、どうか非難とかは怖いです。やめてほしいです。 それでは、ここまで読んで頂いて、ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ Chrome OS / Debian ] Tesseract + PyOCR で日本語 OCR 環境を構築する

はじめに a. 記事概要 タイトルの通り、Chromebook 上で Tesseract + PyOCR で日本語 OCR 環境を構築します。 b. 動作確認環境 # OS $ cat /etc/os-release PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" NAME="Debian GNU/Linux" VERSION_ID="11" VERSION="11 (bullseye)" VERSION_CODENAME=bullseye ID=debian HOME_URL="https://www.debian.org/" SUPPORT_URL="https://www.debian.org/support" BUG_REPORT_URL="https://bugs.debian.org/" # Python $ python --version Python 3.9.10 インストール手順 1. Tesseract OCR 下記コマンドを実行し、Tesseract OCR をインストールします。 $ sudo apt install tesseract-ocr $ sudo apt install libtesseract-dev 2. PyOCR 下記コマンドを実行し、PyOCR をインストールします。 $ pip install pyocr PyOCR 経由で Tesseract OCR を利用できるか確認します。Python Shell を起動し、下記スクリプトを実行します。 $ python Python 3.9.10 (main, Feb 2 2022, 12:14:21) [GCC 10.2.1 20210110] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import pyocr >>> >>> tools = pyocr.get_available_tools() >>> tool = tools[0] >>> tool <module 'pyocr.tesseract' from '/home/hroki/.pyenv/versions/sphinx/lib/python3.9/site-packages/pyocr/tesseract.py'> >>> >>> tool.get_available_languages() ['eng', 'osd'] Tesseract OCR を問題なくインストール出来ているようです。しかし、利用可能な言語に日本語 ("jpn") がありません。別途、日本語データを OCR エンジン側に認識させる必要があります。 3. 日本語データ GitHub のリポジトリ より、jpn.traineddata をダウンロードし、エンジン側が認識できる適切なディレクトリに配備します。ディレクトリの場所が分からない場合は、以下のコマンドを打つなどして他言語データ ("eng", "osd" など) がどこにあるかを確認すると良いと思います。 root@penguin:~# find / -name *.traineddata # (... omit ...) /usr/share/tesseract-ocr/4.00/tessdata/eng.traineddata /usr/share/tesseract-ocr/4.00/tessdata/osd.traineddata # (... omit ...) 日本語データ (jpn.traineddata) を同じ場所に移動またはコピーした上で、再度利用可能な言語を確認します。 >>> tool.get_available_languages() ['eng', 'jpn', 'osd'] きちんと日本語データが認識されています。 動作確認 適当な画像データで OCR の動作確認を行ってみます。例として、以下の画像を使用します。 In filepath = "./what-is-ocr.png" text = tool.image_to_string(Image.open(filepath), lang="jpn") print(text) Out 光 学 文 字 認 識 出 目 フ リ ー 百 斗 事 六 ` ウ ィ キ ペ デ ィ ア (Wikipeda)』 先 学 文 托 部 譚 ( こ う が く も じ に ん し き 、 奇 Optca chaxier ecognton) は 、 活 字 、 手 書 き テ キ ス ト の 画 像 を 文 学 コ ー ド の 列 に 変 探 す る ソ フ ト ウ ェ ア で あ る 。 画 像 は イ メ ー ジ ス キ ャ ナ ー や 冗 真 で 取 り 込 ま れ た 文 書 、 風 景 写 真 風 映 内 の 相 板 の 文 字 な ど 、 画 像 内 の 字 薇 ( テ レ ビ 政 画 像 内 な ど ) が 使 わ れ る J。 一 餐 に OCR と 協 記 さ れ る 。 パ ス ポ ー ト 、 語 求 書 銀 行 M 頓 組 哉 _ レ シ ー ト 、 名 粟 メ ー ル 、 デ ー タ や 文 書 の 印 別 物 な ど 、 紙 に 記 載 さ れ た デ ー タ を デ ー タ 入 力 す る 手 法 と し て 広 く 使 わ れ 、 紙 に 印 剛 さ れ た 文 書 を デ ジ タ イ ズ し 、 よ り コ ン パ ク ト な 形 で 記 録 す る の に 必 要 と さ れ る 。 さ ら に 、 文 字 コ ー ド に 変 換 す る こ と で コ グ ニ テ ィ ブ コ ン ピ ュ ー テ ィ ン グ 、 機 械 啓 許 や 音 田 合 成 の 入 力 に も 使 え る よ う に な り 、 テ キ ス ト マ イ ニ ン グ も 可 能 と な る 。 研 究 映 と し て は 、 パ タ ー ン 認 譚 、 人 工 知 彦 、 コ ン ピ ュ ー タ ビ ジ ョ ン が 刑 応 す る 。 初 朗 の シ ス テ ム は 特 定 の 言 体 を 読 む た め の ` ト レ ー ニ ン グ 」 が 哉 で あ っ た ( 事 前 に そ の 書 伸 の サ ン プ ル を 読 ま せ る こ と を 意 味 す る )。 現 在 で は 、 ほ と ん ど の 静 体 を 高 い 螺 字 振 変 探 す る こ と が 可 能 で あ る 。 い く つ か の シ ス テ ム で は 読 み 込 ま れ た 画 像 か ら そ れ と ほ ほ ぼ 呪 じ に な る よ う フ ォ ー マ ッ ト さ れ た 出 力 ( 初 え ば ぼ 、 ワ ー ド プ ロ セ ッ サ の フ ァ イ ル の よ う な も の ) を 生 成 す る こ と が 可 能 で あ り 、 中 に は 画 像 な ど の 文 書 以 外 の 部 分 が 君 ま れ て い て も 正 し く 認 霧 す る も の も ぁ る 。 所々間違って入るものの、人手による修正を前提とするならギリギリ我慢出来る範疇かなと思います。(我慢できなければ、有料のエンジン導入も検討した方が良いかもしれません) 出力結果の各文字の間に半角スペースが入ってしまっているので、実際に利用する際はこれらを取り除くクレンジング処理なども合わせて開発する必要がありそうです。 参考 tesseract-ocr / tesseract | GitHub tesseract-ocr / tessdata | GitHub EOF
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pytorchを理解したい。足し算をするだけのPytorchモデルをつくる。

足し算をするPytorchモデルを作ります Pytorchを理解したい 機械学習モデルの多くはPytorchやTensorFlowで書かれています。 機械学習モデルに触れてみたいけど、Pytorchコードの部分がなんか「むにゃむにゃむにゃ。。。」という感じ。 シンプルなもので理解してみる 小学校の算数も、数を数えたり足し算をすることから始めた気がするので、 とりあえず、入力に1を足すモデルを作って理解してみます。 device = "cuda" if torch.cuda.is_available() else "cpu" class AdditionModel(nn.Module): def __init__(self): super(AdditionModel, self).__init__() self.y = 1 def forward(self, x): x = torch.add(x,self.y) return x model = AdditionModel().to(device) init でモデルクラス自体を定義し、 forwardで入力を処理します。 モデルに入力してみます。 x = torch.Tensor([1]) x = model(x) x tensor([2.]) 無事に入力の1とモデルの1が足されて2が出力されました。 ? フリーランスエンジニアです。 お仕事のご相談こちらまで rockyshikoku@gmail.com Core MLやARKitを使ったアプリを作っています。 機械学習/AR関連の情報を発信しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

firebase emulator + Python

Firebase emulator がリリースされていて とても開発がはかどりました。 まだ情報がすくないのでPythonからの利用方法をまとめます。 Firebase emulator firestore をメインのDBとして使用しているので、単体テストで色々なテストデータを登録して作成するのに、ローカルでemulator が動かせると便利です。 下記のような仕組みが準備されているので、まずローカルにemulatorをInstallする 開発言語自体はPythonを使いたいのですが、emulatorは node, javaで動いているので事前にインストールしておく必要があります。 % node --version v16.13.2 % npm install -g firebase-tools % firebase login Already logged in as {$USER} firebaseのプロジェクトを選択時に下記のようなエラーがでることがあります % firebase projects:list ✖ Preparing the list of your Firebase projects Error: Failed to list Firebase projects. See firebase-debug.log for more info. 表示されているとおりfirebase-debug.log を開いてみると下記のError表示 Error: HTTP Error: 401, Request had invalid authentication credentials. Expected OAuth 2 access token login できているって表示されてたのに、なぜ!!と思いながら上記のエラーメッセージでググると下記のパラメータで強制的に再認証シーケンスを走らせることで対応できるとのことです。 shell % firebase login --reauth --no-localhost emulatorの設定 shell firebase init emulators emulator実行 % cd firebase_emulator % firebase emulators:start i emulators: Starting emulators: firestore, storage i firestore: Firestore Emulator logging to firestore-debug.log i ui: Emulator UI logging to ui-debug.log ┌─────────────────────────────────────────────────────────────┐ │ ✔ All emulators ready! It is now safe to connect your app. │ │ i View Emulator UI at http://localhost:4000 │ └─────────────────────────────────────────────────────────────┘ ┌───────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├───────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ localhost:8090 │ http://localhost:4000/firestore │ ├───────────┼────────────────┼─────────────────────────────────┤ │ Storage │ localhost:9199 │ http://localhost:4000/storage │ └───────────┴────────────────┴─────────────────────────────────┘ Emulator Hub running at localhost:4400 Other reserved ports: 4500 Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files. Firebase 単体テスト: https://firebase.google.com/docs/rules/unit-tests?hl=ja#rut-v2-common-methods vscode のPython開発設定情報参考1:https://atmarkit.itmedia.co.jp/ait/articles/2107/16/news029.html vscode のpython開発設定情報参考2 : https://dorapon2000.hatenablog.com/entry/2020/04/29/152251 python からemulatorに接続する 以下のように OSの環境変数にlocal emulator に設定することで 通常通りfirebase admin sdk を使用すると アクセス先がローカルのfirebase emulator になる この情報が公式手順上明記されておらず最初はできないのかとおもいました。 まだemulator 自体が、βバージョンということなので今後のドキュメント含めた拡張に期待します 注意点として本番のfirestoreを誤って上書きしてしまわにように、テストのときのProject,ルートドキュメントを別の名前にしておいたほうが良いと思います。 Localや、GCP上でインスタンスが生成されたままだとfirebase_admin.initialize_appで既にあるとエラーになります。下記のように_appsのlenを調べて初期化必要かどうかを判定しています import firebase_admin from firebase_admin import credentials from firebase_admin import firestore os.environ["FIRESTORE_EMULATOR_HOST"]="localhost:8090" os.environ["GCLOUD_PROJECT"]="my_project" cred = credentials.Certificate(self.config['firebase']['certjson']) if (not len(firebase_admin._apps)): firebase_admin.initialize_app(cred) self.db = firestore.client() pytest 例えば、pytest上で、emulator をつかって、テスト条件を一定にするために最初にdocument, collection をクリアにするというようなコードは以下のように書くことができます。(emulatorでも普通のfirestoreの使い方と変わりません。) def setup_method(self,method): print('method={}'.format(method.__name__)) delete_collection(self.db.collection(u'hoge').document(u'fuga1').collection(u'foo'),16) delete_collection(self.db.collection(u'hoge').document(u'fuga2').collection(u'baa'),16) doc_ref = self.db.collection(u'hoge').document(u'fuga1') doc_ref.set({ u'key1': False, u'key2': True }) doc_ref = self.db.collection(u'hoge').document(u'fuga2') doc_ref.set({ u'key1' : False, u'key2' : True }) Test例: 以下は、ab, cd というデータが事前に登録されていた場合正しく収集できるかという関数のテスト例です。 def test_idea_collection(self): col_ref = self.db.collection(u'hoge').document(u'fuga1').collection(u'foo') timestr1 = '2022/2/3 16:48:11' time1 = datetime.strptime(timestr1 + '+0900', '%Y/%m/%d %H:%M:%S%z') timestr2 = '2022/2/6 16:48:11' time2 = datetime.strptime(timestr2 + '+0900', '%Y/%m/%d %H:%M:%S%z') col_ref.add({ u'author': 'x-man', u'content':'ab', u'createdAt':timestr1 }) col_ref.add({ u'author': u'x-woman', u'content':'cd', u'createdAt':timestr1 }) collectresult,lastupdate = self.main_target.collect_foo('fuga1') assert collectresult.count('ab') == 1 assert collectresult.count('cd') == 1 assert collectresult.count('ef') == 0 test 実行例 emulator, pytestがローカルでInstallされていれば以下のようにテスト実行を確認できます % pytest test_hogehoge.py ========================================================================= test session starts ========================================================================= platform darwin -- Python 3.9.0, pytest-7.0.0, pluggy-1.0.0 rootdir: /Users/hoge/src collected 13 items test_hogehoge.py ............. [100%] ========================================================================= 13 passed in 20.66s ========================================================================= test 実装時の参考 Test setup, tear down Test全体のSetup,Teardown、 TestごとのSetupをどう書くかが効率的で安定したテストを書く肝になる。 https://udemyfun.com/python-pytest-setup-teardown/ vscode でのpytest 実行方法 https://gri.jp/media/entry/358
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバレス(Oracle Functions)でWeb動的サイトを構築しました

本投稿の背景 フットサル大会の運用をシステム化( こちらの記事 )したのですが、 2020年3月からコロナの影響でフットサルの大会は実施されていません。。 この状況で以下を考えました。 ・コロナで大会が中止。システム運用している仮想マシンは24時間 x 365日稼働する必要なし ・大会結果を参照したい人がいれば、そのタイミングで参照できればよい ・内容はチーム順位、得点者を表示するシンプルなものなので、Wordpressでなくてもよい それを解決するのが、「サーバレス」と思ったので、 実験的にOCIでサーバレス「Oracle Functions」でWeb動的サイトを作ってみました。 ※息子たちがF1マシンの絵をかいていたので、  それを使ったWebサイトを作ろうと思ったのも理由です。 構成図 ・DBは Autonomous Databaseを使用 ※無料枠の[Always free] ・Oracle Functionsでは(「チーム一覧ページ」、「チーム詳細ページ」)を作成   ※いずれのページもDBに接続してデータからページ内容を作成しています    (静的ページ表示ではありません)   ※詳細表示ページではオブジェクトストレージに格納した画像を参照しています ・API Gatewayでユーザから接続するエンドポイントをまとめる 実際につくってみたWebサイト サーバレス(Oracle Functions)でWebサイトを構築する流れ 以下の手順で構築します 1.Autonomous Databaseを作成 2.画像ファイルをオブジェクトストレージに格納 3.ネットワークの作成 4.Oracle Functions を使う前の準備 5.Oracle Functions 「チーム一覧ページ」作成 6.API Gatewayの作成 7.Oracle Functions 「チーム詳細ページ」作成 8.「チーム一覧ページ」の「チーム詳細ページ」動作確認 ※構築の事前準備として  コンパートメント「serverless」を作成しており、Oracle Functions等はコンパートメント「serverless」に作成します。 ※設定手順などの詳細は、折りたたんでいるので、確認は ▶ をクリックください 1.Autonomous Databaseを作成 Autonomous Databaseに2つのテーブルを作成します。 「チーム一覧ページ」、「チーム詳細ページ」で使用するテーブル・データとなります。 作成するテーブルとデータ 1-1.Autonomous Databaseを作成 Autonomous Databaseを作成する詳細手順はこちら 作成するWebサイトは、DBに格納しているデータを使用してWebページを作成します。 Always free(無料)でDBを作成します。 [Oracle Database] - [Autonomous Database]を選択 「コンパートメント」を確認して、[Autonomous Databaseの作成]をクリック 「コンパートメント」、「表示名」、「データベース名」を設定。 「ワークロード・タイプ」は[データウェアハウス]、「デプロイメント・タイプ」は[共有インフラストラクチャ」を選択 「Always Free」をON、「パスワード」を入力して、[Autonomous Databaseの作成]をクリック 作成されたDBをクリック 1-2.Autonomous Database の wallet を取得 Autonomous Database の wallet を取得する詳細手順はこちら Autonomous Database にDB接続する際、walletが必要になりますので取得します。 ※Oracle Functions でもwalletを使用します Autonomous Databaseには暗号化およびSSL相互認証を利用した接続が前提としており、そのため接続する際はクレデンシャル・ウォレット(Credential.zipファイル)を利用する必要があります https://oracle-japan.github.io/ocitutorials/database/adb104-connect-using-wallet/ [DB接続]をクリック [ウォレットのダウンロード]をクリック 設定したパスワードを入力して、[ダウンロード]をクリック 1-3.DBスキーマ、テーブル作成 DBスキーマ、テーブル作成する詳細手順はこちら Webサイトで使用するDBスキーマ、テーブルを作成します。 [データベース・アクション]をクリック [ユーザの作成]をクリック ユーザ情報とWebアクセスを有効、表領域を割り当て、[ユーザの作成]をクリック コンソール画面右上をみると、ADMINでログインになっているので、[サインアウト]をクリック 作成したユーザ名、パスワードを入力して[サインイン]をクリック 「SQL」をクリック [ワークシート]にSQLを入力して、[実行(赤枠)]をクリックしてSQLを実行 ddl文 CREATE TABLE TEAM ( TEAM_ID NUMBER , TEAM_NAME VARCHAR2 (100) , IMG_URL VARCHAR2 (100) , CONSTRAINT PK_TEAM PRIMARY KEY(TEAM_ID) ) ; CREATE TABLE F1DRIVER ( TEAM_ID NUMBER , DRIVER_NO NUMBER , DRIVER_NAME VARCHAR2 (100), CONSTRAINT PK_F1DRIVER PRIMARY KEY ( TEAM_ID, DRIVER_NO ) ) ; あとは、データを登録。 2.画像ファイルをオブジェクトストレージに格納 「チーム詳細ページ」で表示する画像をオブジェクトストレージに格納します。 バケットを作成して、画像ファイルをアップロードして、可視性の設定をします。 2-1.オブジェクト・ストレージの設定 オブジェクト・ストレージ設定する詳細手順はこちら [ストレージ]-[オブジェクト・ストレージ]をクリック コンパートメントを確認して、[バケットの作成]をクリック [バケット名]を入力して、[作成]をクリック [可視性の編集]をクリックして、[パブリック]を選択して、[変更の保存]をクリック [可視性]がパブリックになっていることを確認 画像をアップロードするため、[アップロード]をクリック 画像を赤枠にドロップして、[アップロード]をクリック [オブジェクト詳細の表示]をクリック URLパスをクリック 画像が表示されれば設定は完了 3.ネットワークの作成 Oracle Functions、API Gatewayを配置するネットワークを作成します。 ※VCNウィザードでネットワークを作成します 3-1.VCNウィザードを実行 VCNウィザードを実行する詳細手順はこちら [ネットワーキング]-[仮想クラウド・ネットワーク]をクリック [VCNウィザードの起動]をクリックします [インターネット接続性を持つVCNの作成]を選択して、[VCNウィザードの起動]をクリックします [VCN名]を入力、[コンパートメント]を選択して、[次]をクリック [作成]をクリック [仮想クラウド・ネットワークの表示]をクリック パブリックサブネットにSSLの通信許可をするため、「パブリックサブネット」をクリック 3-2.ネットワークセキュリティ設定 ネットワークセキュリティ設定する詳細手順はこちら セキュリティリストをクリック [イングレス・ルールの追加]をクリック [イングレス・ルールの追加]をクリック 「ソースCIDR」を[0.0.0.0/0]、「宛先ポート範囲」を[443]を設定して、[イングレス・ルールの追加]をクリック ポート443の許可設定を確認 4.Oracle Functions を使う前の準備 Oracle Functionsを使う前準備をします。 ここではCloud shellを使用してOracle Functionsをデプロイする設定とOracle Functionsを登録するアプリケーションの設定を行います。 事前準備の詳細は以下のページを参照ください(わかりやすく解説されています) https://oracle-japan.github.io/ocitutorials/cloud-native/functions-for-beginners/ 4-1.Cloud shellを設定 Cloud shellを設定する詳細手順はこちら コンソール画面の右上の Cloud Shellをクリック Cloud Shellのコンソールが起動(赤枠) ユーザ設定の認証トークンを取得します。 ※Oracle Functions管理するOracle提供のプライベートDockerレジストリのログインに必要 コンソール画面の人のアイコンをクリックして、[ユーザ設定]をクリック [認証トークン]-[トークンの生成]をクリック [説明]を入れて、[トークンの生成]をクリック 「生成されたトークン」の[コピー]をクリック ※この情報を控えておきます。あとで必要になります。 コンソール画面の人のアイコンをクリックして、[テナンシ]をクリック 「テナンシ情報」のOCIDの[コピー]をクリック ※この情報を控えておきます。あとで必要になります。 Oracle FunctionsをデプロイするためのにOracle Functions CLIを利用して環境を作成します。 開発環境の設定は、fn updateコマンドを使用して行います。 >https://oracle-japan.github.io/ocitutorials/cloud-native/functions-for-beginners/ ※cloud Shellで以下のコマンドを入力していきます。 リージョンを設定します。東京リージョンの場合はap-tokyo-1 CloudShellで実行 fn use context ap-tokyo-1 テナンシーのOCIDを設定 CloudShellで実行 fn update context oracle.compartment-id ocid1.tenancy.oc1..aaaaaaa<省略> レジストリ名を登録します ※Oracle Functionsを管理するOracle提供のプライベートDockerレジストリ CloudShellで実行 fn update context registry iad.ocir.io/xxxxx/functions-serverless fn update context registry iad.ocir.io/<オブジェクト・ストレージ・ネームスペース>/<任意の名前> ※オブジェクト・ストレージ・ネームスペースの確認方法 レジストリにログイン パスワードは「トークンの生成」で作成したものを入力 CloudShellで実行 docker login nrt.ocir.io Login Succceeded となれはOK 4-2.アプリケーションの作成 アプリケーションを作成する詳細手順はこちら   アプリケーションとOracle Functions の関係は以下のイメージは以下の図になります。 アプリケーションがいて、その下にファンクションがぶら下がる。 今回はアプリケーション名は「fn-f1site」 ファンクション名は、「f1_teamlist」、「f1_teamdetail」で作成します。 [開発者サービス]-[ファンクション]をクリック コンパートメント名を確認して、[アプリケーションの作成]をクリック 「名前」を入力、「VCN」を作成したVCN、「サブネット」はパブリックサブネットを選択して、[作成]をクリック アプリケーションが登録されたことを確認 5.Oracle Functions 「チーム一覧ページ」作成 ここからは、Oracle Functions を作成します。 ここで実施する作業は、 作業ディレクトリにPythonのコード、Dockerイメージを作成するための 各種ファイルを準備します。結構あります。。 今回、Oracle functions、Dockerfileをさわってみてのイメージ(自分の理解) 5-1.「チーム一覧ページ」functions [f1_teamlist]の作業ディレクトリを作成 「チーム一覧ページ」functions [f1_teamlist]作業ディレクトリを作成する詳細手順はこちら Oracle functions [f1_teamlist]の作業ディレクトリを作成 cloudshellで入力 fn init --runtime python f1_teamlist 作成されるのは、func.py、func.yaml、requirements.txt   5-2.Oracle Instantclientのメディア、Autonomous Database walletを配置 Oracle Instantclientのメディア、Autonomous Database walletを配置する詳細手順はこちら Oracle InstantClientはダウンロード、 Autonomos DatabaseのwalletはAutonomos Database作成時にダウンロードしたものを cloud shell のホームディレクトリにドラッグ&ドロップすることでコピーできます。 ※「1-2.Autonomous Database の wallet を取得」でwalletはダウンロードしています lsコマンドで各.zipがあることを確認 Oracle Instantclientの.zipを media フォルダにコピー、 walletファイルを dbwalletフォルダに解凍して格納 cloudshellで入力 cd mkdir f1_teamlist/media cp instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip f1_teamlist/media unzip Wallet_SERVERLESS.zip -d f1_teamlist/dbwallet 5-3.Pythonのメイン処理「func.py」を作成 Pythonのメイン処理「func.py」を作成する詳細手順はこちら func.py import io import os from fdk import response import cx_Oracle def handler(ctx, data: io.BytesIO=None): try: #DB接続情報設定 username = "SCOTT" password = "WElcome12345" db_url = "serverless_medium" #DB接続、データ取得 con = cx_Oracle.connect(username, password, db_url) cur = con.cursor() cur.execute('Select * from team') rows = cur.fetchall() strdata="" for r in rows: strdata = strdata + "<tr><td><a href='https://iba2rxxxxxx.apigateway.ap-tokyo-1.oci.customer-oci.com/f1detail/v1?teamid=" + str(r[0]) + "'>" + str(r[1]) + "</a></td></tr>" #HTML作成 html = ""\ "<html lang='ja'>"\ "<head><title>F1 Team</title></head>"\ "<meta charset='utf-8'>"\ "<body>"\ "<font size='5'><b>F1チーム一覧</b> </font>"\ "<table border='1' style='border-collapse: collapse'>"\ " %s "\ "</table>"\ "</body>"\ "</html>" resp = html % (strdata) except Exception as e: print('ERROR: Missing configuration keys, secret ocid and secret_type', e, flush=True) return response.Response( ctx, response_data=resp, headers={"Content-Type": "text/html"} ) 5-4.requirements.txtに cx_Oracle を追加 requirements.txtに cx_Oracle を追加する詳細手順はこちら Python で Oracleに接続する際に必要なモジュールです。 (過去の記事に詳しいことを書いています) requirements.txtに cx_Oracle を追加 requirements.txt fdk cx_Oracle 5-5.Dockerfile を新規追加 Dockerfile を新規追加する詳細手順はこちら Dockerイメージを作成する手順を記載。 FROM oraclelinux:7-slim WORKDIR /function RUN groupadd --gid 1000 fn && adduser --uid 1000 --gid fn fn RUN yum -y install python3 oracle-release-el7 && \ yum install -y unzip && \ yum install -y libaio-devel && \ rm -rf /var/cache/yum ENV ORACLE_HOME=/function/instantclient_19_14 ENV LD_LIBRARY_PATH=/function/instantclient_19_14/lib:$LD_LIBRARY_PATH ENV PATH=/function/instantclient_19_14:$PATH ADD media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip RUN unzip /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip -d /function/tmp && \ mkdir /function/instantclient_19_14 && \ mv /function/tmp/instantclient_19_14 /function/instantclient_19_14/lib COPY dbwallet/* /function/instantclient_19_14/network/admin/ RUN rm -f /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip ADD . /function/ RUN pip3 install --upgrade pip RUN pip3 install --no-cache --no-cache-dir -r requirements.txt RUN rm -fr /function/.pip_cache ~/.cache/pip requirements.txt func.yaml Dockerfile README.md ENV PYTHONPATH=/python ENTRYPOINT ["/usr/local/bin/fdk", "/function/func.py", "handler"] 5-6.「チーム一覧ページ」Oracle Functions [f1_teamlist]をデプロイ 「チーム一覧ページ」Oracle Functions [f1_teamlist]をデプロイする詳細手順はこちら f1_teamlistフォルダに移動してデプロイを実行 cloudshellで入力 cd cd f1_teamlist fn -v deploy --app fn-f1site 「Successfully created function」 で正常にデプロイ完了。 6.API Gatewayの作成 API Gatewayを使う事でUserからアクセスするエンドポイントを1つにまとめるメリットがあります。 (今回はOracle Functionsは2つしかありませんが、サイトのページが増える事を見越して設定します) 6-1.API GatewayからOracle Functions[f1_teamlist]を実行できる設定を行う API GatewayからOracle Functions[f1_teamlist]を実行できるよう設定する詳細手順はこちら [開発者サービス]-[ゲートウェイ]をクリック コンパートメントを確認、[ゲートウェイの作成]をクリック 「名前」:任意、「タイプ」:パブリック、「コンパートメント」:使用コンパートメント、 「VCN」は、作成したVCN、「サブネット」はパブリックサブネットを選択して、 [ゲートウェイの作成]をクリック [デプロイメントの作成]をクリック 「名前」:任意、「パス接頭辞」:/teamlist (今回は/teamlist)、コンパートメント:使用コンパートメント を設定したら、[次]をクリック 「パス」:/v1 (今回は/v1)、「メソッド」:GET、「タイプ」:Oracle Functions、 「アプリケーション」:fn-f1site(『4-2.アプリケーションの作成』で作ったもの)、 「機能名」:f1_teamlist(『5.Oracle Functions 「チーム一覧ページ」作成』で作ったもの)を 設定して、[次]をクリック [作成]をクリック デプロイメントが作成されたことを確認 6-2.API GatewayがOracle Functionsを使用できるように、動的グループ、ポリシーを作成 動的グループ、ポリシーを作成する詳細手順はこちら イメージとして、API Gatewayを動的グループに登録。 その動的グループにOracle Functionsを使用できる権限付与をします。 まずは、動的グループ設定をします。 [アイデンティティとセキュリティ]-[動的グループ]をクリック [動的グループの作成]をクリック 「名前」:任意、「説明」:任意、 「ルール」:使用コンパートメントのAPI Gatewayと指定 ※記載は下記に記載 [作成]をクリック ALL {resource.type = 'ApiGateway', resource.compartment.id = '<functionsがあるコンパートメントのOCID>'} 次はポリシーの作成 [ポリシーの作成]をクリック 「名前」:任意、「説明」:任意、「コンパートメント」:使用コンパートメント、 ポリシービルダでは、動的グループがコンパートメント内のファンクションを使用できると記載 ※記載は下記に記載 Allow dynamic-group <動的グループ> to use functions-family in compartment <コンパートメント> 6-3.API Gatewayのエンドポイントからアクセス API Gatewayのエンドポイントからアクセスする詳細手順はこちら API Gatewayの設定がおわったので、エンドポイントからアクセスします。 「デプロイメント」の「エンドポイント」の[コピー]をクリックします URLにアクセス(API Gatewayで設定したパスをつける) ※注意点は、パスを付けないといけません。パスは以下で設定しています。  『6-1.API GatewayからOracle Functions[f1_teamlist]を実行できる設定を行う』参照 https://ibxxx.apigateway.ap-tokyo-1.oci.customer-oci.com/teamlist/<API Gatewayで設定したパスを設定> API Gatewayのエンドポイントからアクセス。画面が表示されることを確認。 7.Oracle Functions 「チーム詳細ページ」作成 チーム詳細ページをOracle Functionsで作成します。 あわせてAPI Gatewayに作成するOracle Functionsを登録します。 ※基本的な流れは、『5.Oracle Functions 「チーム一覧ページ」作成 』と同じです Oracle Functions 「チーム詳細ページ」でも、作業ディレクトリに各種ファイルを作成していきます。 7-1.「チーム詳細ページ」functions [f1_teamdetail]の作業ディレクトリを作成 「チーム詳細ページ」functions [f1_teamdetail]の作業ディレクトリを作成する詳細手順はこちら Oracle functions [f1_teamlist]の作業ディレクトリを作成 CloudShellで実行 fn init --runtime python f1_teamdetail 「f1_teamdetail」フォルダが作成される 7-2.Oracle Instantclientのメディア、Autonomous Database walletを配置 Oracle Instantclientのメディア、Autonomous Database walletを配置する詳細手順はこちら 『5-2.Oracle Instantclientのメディア、Autonomous Database walletを配置』で Oracle Instantclientのメディア、Autonomous Database walletは、 Cloud Shellのホームディレクトリにあるので、ここでは、作業フォルダにコピーします。 Oracle Instantclientの.zipを media フォルダにコピー、 walletファイルを dbwalletフォルダに解凍して格納 cloudshellで入力 cd mkdir f1_teamdetail/media cp instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip f1_teamdetail/media unzip Wallet_SERVERLESS.zip -d f1_teamdetail/dbwallet 7-3.Pythonのメイン処理「func.py」を作成 Pythonのメイン処理「func.py」を作成する詳細手順はこちら func.py import io import os from fdk import response import cx_Oracle import logging def handler(ctx, data: io.BytesIO=None): try: #GETでteamidを連携。Teamidを取得 #getvalue = "teamid=3" getvalue = str(ctx.RequestURL()) pos = getvalue.find("teamid") teamid = getvalue[pos+7:] #logging.getLogger().info("function start") #DB接続 username = "SCOTT" password = "WElcome12345" db_url = "serverless_medium" #チーム名、画像URLを取得 sql="select * from team where team_id=" + str(teamid) con = cx_Oracle.connect(username, password, db_url) cur = con.cursor() cur.execute(sql) rows = cur.fetchall() strteam ="" strimg="" strdriver="" resp="" #Teamテーブルから情報取得 for r in rows: strteam = str(r[1]) strimg = "<img src='" + str(r[2]) + "' height='250' width='350' >" #F1ドライバ情報取得 sql="select driver_name from f1driver where team_id=" + str(teamid) + " order by driver_no " cur.execute(sql) rows = cur.fetchall() for r in rows: strdriver = strdriver + "<tr><td>" + str(r[0]) + "</td></tr>" #HTML作成 html = ""\ "<html lang='ja'>"\ "<head><title>F1 Team Deatail</title></head>"\ "<meta charset='utf-8'>"\ "<body>"\ "<font size='5'><B>"\ " %s "\ "</B></FONT>"\ "<p>"\ " %s "\ "</p>"\ "<table border='1' style='border-collapse: collapse'>"\ "<tr ><th width='346'>ドライバー</th> </tr>"\ " %s "\ "</table>"\ "<br><button type='button' onclick=\"location.href='https://iba2rxxxxx.apigateway.ap-tokyo-1.oci.customer-oci.com/teamlist/v1'\">戻る</button>"\ "</body>"\ "</html>" resp = html % (strteam,strimg,strdriver) except Exception as e: print('ERROR: Missing configuration keys, secret ocid and secret_type', e, flush=True) return response.Response( ctx, response_data=resp, headers={"Content-Type": "text/html"} ) 7-4.requirements.txtに cx_Oracle を追加 requirements.txtに cx_Oracle を追加する詳細手順はこちら requirements.txt fdk cx_Oracle 7-5.Dockerfile を新規追加 Dockerfile を新規追加する詳細手順はこちら FROM oraclelinux:7-slim WORKDIR /function RUN groupadd --gid 1000 fn && adduser --uid 1000 --gid fn fn RUN yum -y install python3 oracle-release-el7 && \ yum install -y unzip && \ yum install -y libaio-devel && \ rm -rf /var/cache/yum ENV ORACLE_HOME=/function/instantclient_19_14 ENV LD_LIBRARY_PATH=/function/instantclient_19_14/lib:$LD_LIBRARY_PATH ENV PATH=/function/instantclient_19_14:$PATH ADD media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip RUN unzip /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip -d /function/tmp && \ mkdir /function/instantclient_19_14 && \ mv /function/tmp/instantclient_19_14 /function/instantclient_19_14/lib COPY dbwallet/* /function/instantclient_19_14/network/admin/ RUN rm -f /function/media/instantclient-basiclite-linux.x64-19.14.0.0.0dbru.zip ADD . /function/ RUN pip3 install --upgrade pip RUN pip3 install --no-cache --no-cache-dir -r requirements.txt RUN rm -fr /function/.pip_cache ~/.cache/pip requirements.txt func.yaml Dockerfile README.md ENV PYTHONPATH=/python ENTRYPOINT ["/usr/local/bin/fdk", "/function/func.py", "handler"] 7-6.「チーム詳細ページ」Oracle Functions [f1_teamdetail]をデプロイ 「チーム詳細ページ」Oracle Functions [f1_teamdetail]をデプロイする詳細手順はこちら f1_teamdetailフォルダに移動してデプロイを実行 CloudShellで実行 cd cd f1_teamdetail fn -v deploy --app fn-f1site 「Successfully created function」 で正常にデプロイ完了。 7-7.API Gateway に Oracle Functions [f1_teamdetail]を設定 API Gateway に Oracle Functions [f1_teamdetail]を設定する詳細手順はこちら API GatewayからOracle Functions [f1_teamdetail]が実行できるように設定します。 [開発者サービス]-[ゲートウェイ]をクリック 『6-1.API GatewayからOracle Functions[f1_teamlist]を実行できる設定を行う』で 作成した、API Gateway をクリック [デプロイメントの作成]をクリック 「名前」:任意、「パス接頭辞」:/f1detail(今回は/f1detail) 、 「コンパートメント:使用コンパートメントを入力して、[次]をクリック 「パス」:/v1 (今回は/v1)、「メソッド」:GET、「タイプ」:Oracle Functions、 「アプリケーション」:fn-f1site(『4-2.アプリケーションの作成』で作ったもの)、 「機能名」:f1_teamdetailを設定して、[次]をクリック [作成]をクリック 作成した「f1detail」のエンドポイントの[コピー]をクリック URLにアクセス(API Gatewayで設定したパスをつける) ※注意点は、パスを付けないといけません。パスは今回は「v1」で設定しています。 ※「チーム詳細ページ」では、「チーム一覧ページ」で選択したTeamidを引数にしていますので ?teamidをつけています https://iba2xxxxx.apigateway.ap-tokyo-1.oci.customer-oci.com/f1detail/v1?teamid=2 8.「チーム一覧ページ」の「チーム詳細ページ」動作確認 「チーム一覧ページ」を開いて、選択したチームの詳細が表示されるかを確認 https://ibxxx.apigateway.ap-tokyo-1.oci.customer-oci.com/teamlist/v1 最後に Oracle Functionsを使用してWebサイト(動的)を作ることができました。 今回はDBのデータを参照するのみでしたが、 DB接続ができたのでデータ更新するページも作成できるはずです。 ※2022年はRed Bullは、  「ORACLE Red Bull Racing(オラクル・レッドブル・レーシング)」として参戦です。 参考URL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ステンレス板の欠陥分類

1. はじめに こんにちは。Aidemy Premium Planデータ分析コースの鈴木です。  私は現在製造業で機械設計を行っています。日本の製造業はまだまだAI技術の導入が遅れていると言われており、今後も製造業での設計過程や製造過程にて機械学習や深層学習を取り込んだ技術が更に増えていくと想定しています。私自身、製造工程で不良解析を行った経験がありますが、その当時は自動化がされていなかったり、各工程でのデータ非常に少なく、とても解析に時間が掛かりました。よって不良率の原因を探るに当たって、ラインの自動化や各工程でのデータが得られてれば、何が起因の不良かを解析していくにあたり、効率良く進められると思いました。  そこで私は不良分類というテーマにて、機械学習を行っていきたいと思います。 2. 学習データ SIGNATEの練習コンペである、ステンレス板の欠陥分類データを使います。 予測する欠陥の種類は下記7種類あり、データから7種類に分類します。 (1=Pastry,2=Z_Scratch,3=K_Scatch,4=Stains,5=Dirtiness,6=Bumps,7=Other_Faults) 精度評価はAccuracyを使用します。 3. 目的と進め方 ・目的 ①上記SIGNATE練習問題内にて、リーダーボードにて1位を目指す。  (2022/02/10時点で1位の正解率は0.7816684) ②下記手順で進めることにより、データ調整無しの場合と比較してデータの前処理やハイパーパラメータチューニングによるAccuracyの精度が向上するか確認をする。 ・進め方 手順1:データを調整無しで分類し精度の高い分類方法Best3を選定     手順2:データクレンジングを行い、手順1のBest3の方法で分類 手順3:手順2で最も精度の高い分類方法でハイパーパラメータチューニングを行う 4. 開発環境 Google Colaboratory Pyton 3.7.12 Numpy 1.19.5 Pandas 1.3.5 seaborn 0.11.2 sklearn 1.0.2 Light GBM 2.2.3 5. 学習モデルの構築 5.1 今回検証する機械学習モデル 分類手法は下記で行う。 ・Logistic回帰 ・線形SVM ・非線形SVM ・k近傍法 ・決定木 ・ランダムフォレスト ・Light GBM 5.2 ライブラリのインポート #ライブラリのインポート import numpy as np import pandas as pd from pandas import DataFrame import seaborn as sns import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score from sklearn.svm import LinearSVC from sklearn.svm import SVC from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import GridSearchCV 5.3 データの読み込み #trainデータの読み込み train_df = pd.read_csv("/~~/train_steel.csv") 5.4 データの確認 欠損値が無いことはわかっていますが、データを確認します。 train_df.shape #出力結果 (970, 29) train_df.dtypes #出力結果 #Unnamed: 0 int64 #X_Minimum int64 #X_Maximum int64 #Y_Minimum int64 #Y_Maximum int64 #Pixels_Areas int64 #X_Perimeter int64 #Y_Perimeter int64 #Sum_of_Luminosity int64 #Minimum_of_Luminosity int64 #Maximum_of_Luminosity int64 #Length_of_Conveyer int64 #TypeOfSteel_A300 int64 #TypeOfSteel_A400 int64 #Steel_Plate_Thickness int64 #Edges_Index float64 #Empty_Index float64 #Square_Index float64 #Outside_X_Index float64 #Edges_X_Index float64 #Edges_Y_Index float64 #Outside_Global_Index float64 #LogOfAreas float64 #Log_X_Index float64 #Log_Y_Index float64 #Orientation_Index float64 #Luminosity_Index float64 #SigmoidOfAreas float64 #Class int64 #dtype: object train_df.head() 出力結果 train_df.describe() 出力結果 COUNTがそれぞれ970より欠損値は無いことが確認できる。 5.5 訓練データを7:3に分割 #訓練データとテストデータに7:3に分割する target = train_df["Class"] #"Unnamed: 0","Class"列は削除 drop_col = ["Unnamed: 0","Class"] train_df = train_df.drop(drop_col,axis=1) X_train, X_val,y_train,y_val = train_test_split( train_df,target, test_size=0.3,shuffle=False,random_state=0) 5.6 手順1での分類と結果 #Logistic回帰 model_Log = LogisticRegression() model_Log.fit(X_train,y_train) y_pred_Log = model_Log.predict(X_val) print(accuracy_score(y_val,y_pred_Log)) #出力結果 0.4570446735395189 #線形SVM model_SVM = LinearSVC() model_SVM.fit(X_train,y_train) y_pred_SVM = model_SVM.predict(X_val) print(accuracy_score(y_val,y_pred_SVM)) #出力結果 0.371134020618556 #非線形SVM model_SVM_1 = SVC() model_SVM_1.fit(X_train,y_train) y_pred_SVM_1 = model_SVM_1.predict(X_val) print(accuracy_score(y_val,y_pred_SVM_1)) #出力結果 0.481099656357388 #k近傍法 model_k_NN = KNeighborsClassifier() model_k_NN.fit(X_train,y_train) y_pred_k_NN = model_k_NN.predict(X_val) print(accuracy_score(y_val,y_pred_k_NN)) #出力結果 0.436426116838488 #決定木 model_Dec = DecisionTreeClassifier() model_Dec.fit(X_train,y_train) y_pred_Dec = model_Dec.predict(X_val) print(accuracy_score(y_val,y_pred_Dec)) #出力結果 0.656357388 #ランダムフォレスト model_Ran = RandomForestClassifier() model_Ran.fit(X_train,y_train) y_pred_Ran = model_Ran.predict(X_val) print(accuracy_score(y_val,y_pred_Ran)) #出力結果 0.718213058 #Light GBM model_LGB = lgb.LGBMClassifier() model_LGB.fit(X_train, y_train) y_pred_lgb = model_LGB.predict(X_val) print(accuracy_score(y_val,y_pred_lgb)) #出力結果 0.7525773195876289 下表が手順1、Accuracy ScoreのBest3となります。 順位 分類方法 手順1(調整無し) 1 Light GBM 0.7525773 2 ランダムフォレスト 0.7182131 3 決定木 0.6563574 5.7 手順2の実施 手順2ではデータクレンジングを行い、手順1のBest3の方法で分類していく。 まずは特徴量それぞれの相関を確認していく。 5.7.1相関確認 #相関を見ていく sns.heatmap( train_df[['X_Minimum','X_Maximum','Y_Minimum','Y_Maximum','Pixels_Areas','X_Perimeter','Y_Perimeter','Sum_of_Luminosity','Minimum_of_Luminosity','Maximum_of_Luminosity','Length_of_Conveyer','TypeOfSteel_A300','TypeOfSteel_A400','Steel_Plate_Thickness','Edges_Index','Empty_Index','Square_Index','Outside_X_Index','Edges_X_Index','Edges_Y_Index','Outside_Global_Index','LogOfAreas','Log_X_Index','Log_Y_Index','Orientation_Index','Luminosity_Index','SigmoidOfAreas','Class']].corr(), vmax=1,vmin=-1,annot=True,annot_kws = {'size':15} ) plt.gcf().set_size_inches(70,20) 相関ヒートマップより説明変数の中に、相関係数が高い組み合わせが見られ、多重共線性の可能性がある。それによって過学習による精度低下が起こる可能性があるため、それらは特徴量を削除する。今回削除対象とするものは 相関係数 <= -0.88 AND 0.88 <= 相関係数 とする。 よって削除対象の下記7つを訓練データから削除。 #7つの特徴量を削除 drop_col=["X_Maximum","Y_Maximum","X_Perimeter","Sum_of_Luminosity","TypeOfSteel_A300","Y_Perimeter","LogOfAreas"] train_df=train_df.drop(drop_col,axis=1) 5.7.2 外れ値の確認と対策 続いてヒストグラムとボックスプロットを用いて外れ値を確認する。 #ヒストグラム作成 train_df.hist(figsize=(15,15)) plt.subplots_adjust(wspace=0.5, hspace=0.5) plt.show() #Box plot作成 train_df.plot(kind='box', layout=(5,5), subplots=True,sharex=False, sharey=False, figsize=(15,15) ) plt.subplots_adjust(wspace=0.5, hspace=0.5) plt.show() 上記結果より、"Pixels_Areas","Log_Y_Index"に外れ値が存在する。外れ値が存在するとモデルの学習に悪影響を及ぼす可能性がある為、対策として数値データを特定の区間で分類し、カテゴリカル変数に変化させることにより、外れ値の影響を減らすことにする。今回は"Pixels_Areas","Log_Y_Index"それぞれ4つの区間に分類してカテゴリカル変数に変換する。 #カテゴリカル変数に変換 train_df['Pixels_Areas_qcut'] = pd.qcut(train_df['Pixels_Areas'], 4) train_df['Log_Y_Index_qcut'] = pd.qcut(train_df['Log_Y_Index'], 4) 次に、カテゴリカル変数に対しOne-Hot Encodingを施し、モデルが扱えるよう変換する。One-Hot Encodingとは、各カテゴリカル変数に対して該当するカテゴリかどうかを0,1で表現したベクトルに変換する処理。 #One-Hot Encoding train_df = pd.get_dummies(train_df, columns=['Log_Y_Index_qcut','Pixels_Areas_qcut']) 5.7.3 手順2での分類と結果 特徴量作成が完了したので、予測モデルに学習させるために訓練データと検証データを作成後、手順1、Best3の手法で分類し、スコアを確認する。 #今回学習に用いないカラムを削除 drop_col_2 = ["Pixels_Areas","Log_Y_Index"] train_df=train_df.drop(drop_col_2,axis=1) #今回は訓練データとテストデータを9:1に分割する X_train, X_val,y_train,y_val = train_test_split( train_df,target, test_size=0.1,shuffle=False,random_state=42) #決定木 model_Dec = DecisionTreeClassifier() model_Dec.fit(X_train,y_train) y_pred_Dec = model_Dec.predict(X_val) print(accuracy_score(y_val,y_pred_Dec)) #出力結果 0.6688659793814433 #ランダムフォレスト model_Ran = RandomForestClassifier() model_Ran.fit(X_train,y_train) y_pred_Ran = model_Ran.predict(X_val) print(accuracy_score(y_val,y_pred_Ran)) #出力結果 0.7422680412371134 #Light GBM model_LGB = lgb.LGBMClassifier() model_LGB.fit(X_train, y_train) y_pred_lgb = model_LGB.predict(X_val) print(accuracy_score(y_val,y_pred_lgb)) #出力結果 0.7731958762886598 下表が手順2でのAccuracy Scoreである。手順1よりも各分類方法で精度は上がっていることが分かる。 順位 分類方法 手順1(調整無し) 手順2(データクレンジング) 1 Light GBM 0.7525773 0.7731959 2 ランダムフォレスト 0.7182131 0.7182131 3 決定木 0.6563574 0.6688660 5.8 手順3の分類と結果 手順3では手順2で最も精度の高い分類方法でハイパーパラメータチューニングを行う。 手順2よりLight GBMでのAccuracy Scoreが最も高いことが分かっている。よって、Light GBMのハイパーパラメータを調整することにより、更に精度を高めていくことする。今回はグリッドサーチにて調整したいハイパーパラメーターの値の候補を、明示的に複数指定してパラメーターセットを作成する。 # Light GBMのハイパーパラメーター候補値 paramG = {'num_leaves':[31, 50, 100], 'bagging_fraction':[0.1, 0.5, 1], 'min_child_samples':[0,5,100], 'max_depth':[4,5,6] } model_LGB = lgb.LGBMClassifier() #Light GBM grid_search = GridSearchCV(model_LGB, param_grid=paramG, cv=3) #グリッドサーチ grid_search.fit(X_train, y_train) #モデルの学習 print(grid_search.best_params_) #出力結果 #{'bagging_fraction': 0.1, 'max_depth': 6, 'min_data_in_leaf': 5, 'num_leaves': 31} 上記結果から、LightGBMのハイパーパラメータを設定した場合のテストデータの結果を確認する。 # モデルの学習 model_LGB_grid = lgb.LGBMClassifier(num_leaves=31,bagging_fraction=0.1,min_child_sample=5,max_depth=6) model_LGB_grid.fit(X_train, y_train) # モデルの学習 y_pred_lgb_test_grid = model_LGB_OK.predict(X_val) print(accuracy_score(y_val,y_pred_lgb_test_grid)) #出力結果 0.7938144329896907 ハイパーパラメータを調整することにより更に精度の高い正解率となった。 5.9.テストデータの予測と結果の保存 SIGNATEへ投票するにあたり、testデータを予測し、提出用ファイルに保存します。 #testデータの読み込みと前処理 test_df = pd.read_csv("/~~//test_steel.csv") test_df = test_df.drop(drop_col,axis=1) test_df['Pixels_Areas_qcut'] = pd.qcut(test_df['Pixels_Areas'], 4) test_df['Log_Y_Index_qcut'] = pd.qcut(test_df['Log_Y_Index'], 4) test_df = pd.get_dummies(test_df, columns=['Log_Y_Index_qcut','Pixels_Areas_qcut']) test_df = test_df.drop(drop_col_2,axis=1) test_df = test_df.drop("Unnamed: 0",axis=1) #testデータの予測 y_pred_lgb_test = model_LGB.predict(test_df) #提出データの読み込み submit_df = pd.read_csv("/~~/sample_submit.csv",header=None) #提出データの編集と書き込み data_df = pd.DataFrame(y_pred_lgb_test) data_df.reset_index(drop=True, inplace=True) submit_df[1]=data_df[0] submit_df.to_csv("/~~/sample_submit.csv",index=False,header=None) 6. 結果  手順1,2,3より、データの調整無しから、データクレンジング後、ハイパーパラメータを調整することにより、LightGBMではAccuracy Scoreを0.7525→0.7938まで上げることができ、精度を高めることができました。 分類方法 手順1(調整無し) 手順2(データクレンジング) 手順3(ハイパーパラメータ調整) Light GBM 0.7525773 0.7731959 0.7938144 ランダムフォレスト 0.7182131 0.7422680 -------------------- 決定木 0.6563574 0.6688660 -------------------- また下図よりSIGNATEへの投票結果は0.7909372より、目標通り1位を獲得することができました。(2022/02/11時点) 7. 終わりに  今回ステンレス板の欠陥分類を行い、データ分析における精度を高めていくために、適切なデータの前処理、分類方法の適切な選定、またハイパーパラメータの適切な設定がそれぞれ非常に重要であることが分かりました。それが一つでも欠けることにより、精度が低くなってしまうため、今後も上記に気を付けて解析をしていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む