20210416のPythonに関する記事は25件です。

混同行列のF値を眺めてみる

混同行列の総合評価でF値ってよく出現しますよね。 pをprecision(適合率), rをrecall(再現率)としたとき、F値は、 F = \frac{2pr}{p+r} = \frac{2}{\frac{1}{p} + \frac{1}{r}} で定義されます。p, rが動くときどんな挙動を示すのでしょうか? 描画 pythonを使って描画してみます: ## set libraries import numpy as np import seaborn as sns ## 値の計算 F = np.array([2 / (1/r+1/p) for p in np.arange(0, 1, 0.1) for r in np.arange(0, 1, 0.1)]).reshape(10, 10) ## 描画 sns.heatmap(F) するとこうなります: →縦軸、横軸の目盛が0, 1, .., 9なのはご愛嬌。本来は0.0, 0.1, .., 0.9にして欲しいですが、取り敢えず概観みたいだけなので放っておきますw 気づくこと p or rが0の時はFの定義から値自体が0(青枠部分) pとrの和が一定でも、porrのどちらかが高くないとF値は下がる、つまりpとrのトレードオフを考えた場合、pとrの両方がバランス良くないとF値は下がりがち 青枠部分はp+rが一定のラインですが、端っこにいくと色が黒く、つまり0に近く、真ん中辺りは比較的オレンジっぽく値が高いですね。 実務で言えば、適合率、再現率の両者ともに良くないと使い物にならないよね、っていう場合に使ったらいいってことですかね。 Reference Heatmap in seaborn
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

状態空間モデルとカルマンフィルタのpython実装

はじめに 本稿では線形ガウス状態空間モデルを扱います 以下書籍・記事を参考にしています 予測にいかす統計モデリングの基本―ベイズ統計入門から応用まで(樋口知之) https://atrg.jp/ja/index.php?plugin=attach&pcmd=open&file=04nagao.pdf&refer=ATTA2014 https://qiita.com/hanon/items/7f03621414c59f06d7ca 線形ガウス状態空間モデル 線形ガウス状態空間モデルを以下のように定義する。 システムモデル(状態方程式) $$ \boldsymbol{x}_t = F_t\boldsymbol{x}_{t-1} + G_t\boldsymbol{v}_t,\qquad\boldsymbol{v}_t~\sim N(0,Q_t) \tag{3.16}$$ 観測モデル(観測方程式) $$ \boldsymbol{y}_t = H_t\boldsymbol{x}_{t} + \boldsymbol{w}_t,\qquad\boldsymbol{w}_t~\sim N(0,R_t) \tag{3.17}$$ 3つの分布 $$p(\boldsymbol{x}_j|\boldsymbol{y}_{1:k})=N(\boldsymbol{x}_{j|k},V_{j|k})$$ 上記定義のもとで3つの分布は以下のようにかける。ここで$0\leqq t \leqq T$である。 予測分布・・・$p(\boldsymbol{x}_t|\boldsymbol{y}_{1:t-1})$ $$\boldsymbol{x}_{t|t-1}=F_t\boldsymbol{x}_{t-1|t-1},\qquad V_{t|t-1}=F_tV_{t-1|t-1}F_t^T+Q_t$$ フィルタ分布・・・$p(\boldsymbol{x}_t|\boldsymbol{y}_{1:t})$ $$\boldsymbol{x}_{t|t}=\boldsymbol{x}_{t|t-1} + K_t(\boldsymbol{y}_t - H_t\boldsymbol{x}_{t|t-1}),\qquad V_{t|t}=(I-K_tH_t)V_{t|t-1}$$ $$カルマンゲイン:K_t=V_{t|t-1}H_t^T(H_tV_{t|t-1}H_t^T+R_t)^{-1}$$ 平滑化分布・・・$p(\boldsymbol{x}_t|\boldsymbol{y}_{1:T})$ 式変形 実装
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

二次元リストの平坦化

二次元リストなどイテレータの二重構造になっているものの平坦イテレータを取得するには、itertoolsのchain.from_iterableを使う。 import itertools l_2d = [[0, 1], [2, 3]] print(list(itertools.chain.from_iterable(l_2d))) # [0, 1, 2, 3] ちなみに、itertools.chain()は、イテレータの結合につかうもの。 from itertools import chain import numpy as np nums = np.array([[0, 1, 2], [10, 11, 12], [20, 21, 22]]) zeros = np.zeros((2, 3)) print(list(chain(nums, zeros))) # [array([0, 1, 2]), array([10, 11, 12]), array([20, 21, 22]), array([0., 0., 0.]), array([0., 0., 0.])]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sorted()やsort()でソート条件を指定する

lambda式を使う l_2d = [[2, 10], [1, -30], [-3, 20]] print(sorted(l_2d, key=lambda x: x[1], reversed=True)) # [[-3, 20], [2, 10], [1, -30]] max()などの関数でも使える。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PandasのDataFrameを二重リストから作成する&CSVに書きだす

二重リストからDataFrameを作成する(カラム名を指定) import pandas as pd import numpy as np df_simple = pd.DataFrame(np.arange(12).reshape(3, 4), columns=['col_0', 'col_1', 'col_2', 'col_3']) print(df_simple) # col_0 col_1 col_2 col_3 #0 0 1 2 3 #1 4 5 6 7 #2 8 9 10 11 DataFrameをCSVに書き出す(indexなしでエンコードを指定して) df_simple.to_csv("df_simple.csv", encoding="utf-8-sig", index = False)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで設定ファイルなどに環境変数の内容を展開する方法

設定ファイルやちょっとしたファイルで、一部のみ動的に変更したい場合があります。 とくに、Dockerコンテナなんかで使う場合は、環境変数で動作を変えられると便利です。 かっこいいテンプレートエンジンを使ってもいいのですが、それほどでもない場合のやり方です。 いつも忘れてしまうので、ここにメモしておきます。 設定ファイルのようなもの 次のようなファイルの${NAME}の部分を環境変数で置き換える方法を考えます。 something.conf [Setting] name=${NAME} 環境変数設定 次のように環境変数に入れておきます。 # export NAME=hirachan シェルスクリプトの場合 実は、シェルスクリプトでもできます。envsubstというコマンドでできるので、紹介しておきます。入っていない場合は、gettextのパッケージを入れましょう。 # envsubst < something.conf [Setting] name=hirachan おーー。置き換わりました。 いい感じです。 Pythonの場合 さて、Pythonの場合です。 Pythonには、os.path.expandvarsという便利な関数があります。 os.pathにあるのでファイルのPATHをごにょごにょするための関数のようですが、問題なく使えます。 expand.py import os import sys with open("something.conf", "r") as fr: out = os.path.expandvars(fr.read()) sys.stdout.write(out) 実行してみましょう。 # python3 expand.py [Setting] name=hirachan おーー。すばらしいですね。 まとめ 環境変数の展開は、シェルではenvsubst、Pythonではos.path.expandvarsが便利です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Slack Bot 】チャンネルが作成されたら通知しよう!

Slack Appを使用し、さらにサーバレスで実装してみました!今回使用するのはPython、AWSのLambda、API Gatewayです。  API GatewayとLambdaの設定   AWSのLambda、API Gatewayについては過去のhttps://qiita.com/ymktmk_tt/items/7ad4e63e62795bb2418b に細かいことを書いてあるので同じように設定してみてください。   では、始めます !!  Slack Appを作成   https://api.slack.com/ にアクセスして「 Create a custom app 」 を押して、「 Create New App 」を押す。すると下の画像のような画面に遷移するかと思います。   そして、「 App Name 」と「 Development Slack Workspace 」を入力し、「 Create App 」をクリックするとアプリケーションが作成されます  Slack Appの設定 その① -- 「 Event Subscriptions 」  Lambda関数のlambda_fanction.pyに一旦下記のように記述してください。Slack Appとの連携確認が必要なためです。 lambda_fanction.py import json def lambda_handler(event, context): return json.loads(event['body'])['challenge']  そして、Slack API メニューから「 Event Subscriptions 」を選択 「 Request URL 」にAPI GatewayとLambdaを設定するで作成したAPI Gatewayのエンドポイントを入力します。しばらくして「Verified」になればOKです! 少し下にスクロールして「 Subscribe to Bot Events 」に「 channel_created 」のイベントを追加します  Slack Appの設定 その② -- 「 Install App 」 Slack API メニューから「 Install App 」を選択 「 Install to Workspace 」をクリック! アプリケーションインストールの確認画面が表示されるので問題なければ「 Allow 」をクリックします。 インストールされるとSlackの画面でアプリケーションが起動しているのを確認できます。   プログラムの作成 Botのtokenが必要なのでそれをメモしておきます。Slack API メニューから「 OAuth & Permissions 」を選択して「 Bot User OAuth Access Token 」に書かれているtokenをコピーします。 そして、少しスクロールしてこの3つを追加しましょう。 さて、いよいよ Lambda関数にコードを書いていきましょう! lambda_fanction.py import logging import os import json import urllib.request logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): logger.info(event) channelName = json.loads(event['body'])['event']['channel']['name'] channelId = json.loads(event['body'])['event']['channel']['id'] url = "https://slack.com/api/chat.postMessage" headers = { "Content-type" : "application/json", "Authorization" : "Bearer "+ os.environ['TOKEN'] } data = { 'channel': '通知したいチャンネルID(このAppが存在する)', 'text': '新しいチャンネル: ' + '#' + channelName, 'link_names' : 1, } req = urllib.request.Request(url=url, data=json.dumps(data).encode('utf-8'), method='POST', headers=headers) with urllib.request.urlopen(req) as res: logger.info(res.read().decode("utf-8"))   試してみよう 通知したいチャンネルに作成したアプリケーションを追加しましょう。 できました! slack API ドキュメントを見るとさらに色々な機能をつけることができます。もしエラーが出てしまってもドキュメントに解決方法が載っていますよ!   参考記事 ・ https://coxcox.hatenablog.com/entry/2017/08/16/163719 ・ https://api.slack.com/events/channel_created ・ https://api.slack.com/methods/chat.postMessage#arg_link_names
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonではじめる機械学習 カーネル法を用いたサポートベクタマシン(p90~102)

はじめに "Pythonではじめる機械学習"の決定木のアンサンブル法(p82~90)の学習記録です。 ・分からなかったコードやドキュメント ・自分にとって理解するのに時間がかかった内容 ・用語の定義 に関する説明を主に記述しています。 参考書を読んでいて容易に理解できたことは記述していません。 2.3.7 カーネル法を用いたサポートベクタマシン 2.3.7.1 線形モデルと非線形特徴量 低次元における線形モデルは非常に制約が強いので、線形モデルを柔軟にするために特徴量を加える。 #In[76] 2番目の特徴量の2乗を追加。 #np.hstackで横方向に配列を連結する(feature0, feature1)が(feature0, feature1, feature1 ** 2)となる X_new = np.hstack([X, X[:, 1:] ** 2]) # 3Dで可視化する from mpl_toolkits.mplot3d import Axes3D, axes3d figure = plt.figure() # elevはz方向から見た仰角、azimはx,y軸方向の方位角を指定 ax = Axes3D(figure, elev=-152, azim=-26) # y== 0の点をプロットしてからy == 1の点をプロット mask = y == 0 # 青点の設定 ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60) # 赤点の設定 ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60) ax.set_xlabel("feature 0") ax.set_ylabel("feature 1") ax.set_zlabel("feature 1 ** 2") Out[76] 疑問 ・maskって何? #In[77] #ravelで多次元のリストを一次元として返す linear_svm_3d = LinearSVC().fit(X_new, y) coef, intercept = linear_svm_3d.coef_.ravel(), linear_svm_3d.intercept_ # 線形決定境界を描画 figure = plt.figure() ax = Axes3D(figure, elev=-152, azim=-26) xx = np.linspace(X_new[:, 0].min() - 2, X_new[:, 0].max() + 2, 50) yy = np.linspace(X_new[:, 1].min() - 2, X_new[:, 1].max() + 2, 50) # xx, yyの各座標の要素列から格子座標を作成する XX, YY = np.meshgrid(xx, yy) ZZ = (coef[0] * XX ++ coef[1] * YY + intercept) / -coef[2] ax.plot_surface(XX, YY, ZZ, rstride=8, cstride=8, alpha=0.3) # 青点の作成 ax.scatter(X_new[mask, 0], X_new[mask, 1], X_new[mask, 2], c='b', cmap=mglearn.cm2, s=60) # 赤点の作成 ax.scatter(X_new[~mask, 0], X_new[~mask, 1], X_new[~mask, 2], c='r', marker='^', cmap=mglearn.cm2, s=60) ax.set_xlabel("feature0") ax.set_ylabel("feature1") ax.set_zlabel("feature1 ** 2") Out[77] 疑問 ・linspace(数列作成)の引数 ・plot_surfaceの引数 #In[78] ZZ = YY **2 # 横方向に配列を連携する(np.hstackと同じ) dec = linear_svm_3d.decision_function(np.c_[XX.ravel(), YY.ravel(), ZZ.ravel()]) #plt.contourfで塗りつぶした等高線を描画する plt.contourf(XX, YY, dec.reshape(XX.shape), levels=[dec.min(), 0, dec.max()], cmap=mglearn.cm2, alpha=0.5) mglearn.discrete_scatter(X[:, 0], X[:, 1], y) plt.xlabel("Feature 0") plt.ylabel("Feature 1") Out[78] 2.3.7.2 カーネルトリック 以上から、非線形の特徴量をデータ表現に加えることで、線形モデルがはるかに強力になるということが分かる。 非常に大きくなりうる表現(たくさんの特徴量など)を実際に計算せずに、高次元空間でのクラス分類器を学習させる巧妙な数学的トリックがカーネルトリックである。 サポートベクタマシンで広く用いられている高次元空間へのマップ方法 ・もとの特徴量の特定の次数までのすべての多項式を計算する多項式カーネル ・放射基底関数と呼ばれるガウシアンカーネル  SVMを理解する SVMでは2つのクラスの決定境界に位置するごく一部の訓練データポイント(多くの場合、これらのデータポイントが決定境界を決定する)をサポートベクタと呼ぶ。 #In[79] from sklearn.svm import SVC X, y = mglearn.tools.make_handcrafted_dataset() svm = SVC(kernel='rbf', C=10, gamma=0.1).fit(X, y) mglearn.plots.plot_2d_separator(svm, X, eps=.5) mglearn.discrete_scatter(X[:, 0], X[:, 1], y) #サポートベクタをプロットする sv = svm.support_vectors_ #サポートベクタのクラスラベルはdual_coef_の正負によって決まる sv_labels = svm.dual_coef_.ravel() > 0 mglearn.discrete_scatter(sv[:, 0], sv[:, 1], sv_labels, s=15, markeredgewidth=3) plt.xlabel("Feature 0") plt.ylabel("Feature 1") Out[79] 疑問 ・SVCと2dとscatterの引数 2.3.7.5 SVMのためのデータ前処理 In[86]の出力が参考書と何故か違う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ROS Python でService Call するまでの流れ

はじめに roscore実行時自動で出てくる/rosout/get_loggersと/rosout/set_logger_levelの2つのサービスを例にROS PythonからServiceを呼ぶ時の手順をまとめました。 サービスについて調べる 1.まず,Service名の確認 $ rosservice list /rosout/get_loggers /rosout/set_logger_level 2.次に,Serviceの型とその型を定義しているパッケージの確認 Serviceの型のスラッシュの前部分,今回はroscppであることが判明した。 $ rosservice type /rosout/get_loggers roscpp/GetLoggers $ rosservice type /rosout/set_logger_level roscpp/SetLoggerLevel 3.最後に,Serviceの型の構成を確認 $ rossrv show roscpp/GetLoggers --- roscpp/Logger[] loggers string name string level $ rossrv show roscpp/SetLoggerLevel string logger string level --- Pythonからの呼び出し サービス名が"/rosout/get_loggers"のサービスを呼び出す get_loggers.py #!/usr/bin/env python import rospy from roscpp.srv import GetLoggers rospy.wait_for_service('/rosout/get_loggers') get_loggers = rospy.ServiceProxy('/rosout/get_loggers', GetLoggers) response = get_loggers() print(response) サービス名が"/rosout/set_logger_level"のサービスを呼び出す set_logger_level.py #!/usr/bin/env python import rospy from roscpp.srv import SetLoggerLevel rospy.wait_for_service('/rosout/set_logger_level') set_logger_level = rospy.ServiceProxy('/rosout/set_logger_level', SetLoggerLevel) set_logger_level("rosout","DEBUG") 参考 ROS Tutorial Python Service Clientの書き方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Hello Worldの次はマインスイーパを作ろう(React+FastAPI)

はじめに 言語やフレームワークのサンプルやHello Worldを動かすことが出来た新人プログラマの皆さんは学習の次ステップで何をすればいいか悩むと思います。 世間一般ではブラックジャックがメジャーなようですが、 私はマインスイーパを作ることで理解を深める機雷処理言語学習法を実践しています。 この記事ではReact+FastAPIでマイスイーパを作成する流れを記載します。 Windows 10にはマインスイーパが初期インストールされていないので、新入社員や学生さんにマインスイーパを知らない層がいそうなのが怖いですが、気にしないことにします。 開発環境 前回、学習用のReact+FastAPI環境を作成したので、こちらを利用していきます。 FastAPIは新規のソースを用意するので、uvicornコマンドが動作中の場合はCtrl+Cで停止してください。 ReactはApp.jsをそのまま編集していくので、yarn startしたままでOKです。 バックエンド(Python+FastAPI) APIの仕様 以下のAPIで実装します。 API メソッド パラメータ 説明 /ms GET なし session IDの一覧取得 /ms POST width : intheight : intmine : intseed : int sessionの作成 /ms/{session} GET session : str 画面に表示する情報の取得 /ms/{session} POST session : strx : inty : int セルを開く /ms/{session}/flag POST session : strx : inty : intflag : int(1=ON,0=OFF) フラッグの設定 DELETEメソッドでsessionを削除するAPIも作成したほうがよいが、今回は不採用。 モデル セルの状態を管理するモデルクラスを実装します。 getPublicInfo()というjsonに変換可能なdictで情報を返すメソッドを用意しています。 このdictをFastAPIで返すとjson型の応答になります。 ロジックの説明は以前に作成した仕組みを参照。 ms_model.py import random import json class pointOffset: def __init__(self, x, y): self.x = x self.y = y @staticmethod def rounds(): return [pointOffset(-1, -1), pointOffset(0, -1), pointOffset(1, -1), pointOffset(-1, 0), pointOffset(1, 0), pointOffset(-1, 1), pointOffset(0, 1), pointOffset(1, 1)] class Cell: def __init__(self, isOpen, isFlag, isMine): self.isOpen = isOpen self.isFlag = isFlag self.isMine = isMine def __repr__(self): return json.dumps(self.getPublicInfo()) def getPublicInfo(self): result = { 'open': int(self.isOpen), 'flag': int(self.isFlag), } if self.isOpen: result['mine'] = int(self.isMine) return result class Field: def __init__(self, width, height, mine): self.cells = list() self.width = width self.height = height self.mine = mine for i in range(self.width * self.height): cellMine = True if i < mine else False self.cells.append(Cell(False, False, cellMine)) random.shuffle(self.cells) def __repr__(self): return json.dumps(self.getPublicInfo()) def getPublicInfo(self): rest = self.mine publicCells = list() for i, cell in enumerate(self.cells): cellInfo = cell.getPublicInfo() if cellInfo["open"] == 1 and cellInfo["mine"] == 0: cellInfo["number"] = self.roundNum( i % self.width, int(i / self.width)) if cellInfo["open"] == 0 and cellInfo["flag"] == 1 : rest = rest - 1 publicCells.append(cellInfo) status = "continue" if self.isOver(): status = "over" elif self.isClear(): status = "cleared" result = { 'cells': publicCells, 'width': self.width, 'height': self.height, 'mine': self.mine, 'status': status, 'rest' : rest } return result def cell(self, x, y): if 0 > x or 0 > y or self.width <= x or self.height <= y: return None return self.cells[y * self.width + x] def open(self, x, y): cell = self.cell(x, y) if cell is None: return if cell.isOpen: return cell.isOpen = True if not cell.isMine: if self.roundNum(x, y) == 0: # 0なら隣接cellをOpenする for offset in pointOffset.rounds(): self.open(x + offset.x, y + offset.y) def flag(self, x, y, isFlag): cell = self.cell(x, y) if cell is None: return cell.isFlag = isFlag def roundNum(self, x, y): round = 0 for offset in pointOffset.rounds(): if self.isMine(x + offset.x, y + offset.y): round += 1 return round # 指定セルが存在してmine状態の場合にTrueを返す def isMine(self, x, y): cell = self.cell(x, y) if cell is None: return False return cell.isMine def isOver(self): for y in range(self.height): for x in range(self.width): if self.cell(x, y).isOpen and self.cell(x, y).isMine: return True return False def isClear(self): closeCount = 0 for y in range(self.height): for x in range(self.width): if not self.cell(x, y).isOpen: closeCount += 1 return True if closeCount == self.mine else False モデルとAPIを繋ぐ管理クラスの実装 シングルトンで保持する動作にします。 本来はsessionをキーにしてモデルの状態をDBに格納するべきですが、今回は簡易実装にしました。 ms_manager.py from ms_model import Field import uuid import random class ms_manager: singleton_instance = None sessions = dict() def __new__(cls, *args, **kwargs): # シングルトン if cls.singleton_instance == None: cls.singleton_instance = super().__new__(cls) return cls.singleton_instance def create(self, width, hight, mine, seed_value): random.seed(seed_value) hash = str(uuid.uuid4()).replace("-","") self.sessions[hash] = { "session": hash, "status": "new", "field": Field(width, hight, mine) } return {'session': hash} def get_list(self): session_list = list() for session in self.sessions.keys(): session_list.append(session) return session_list def get_sessison(self, session): if not session in self.sessions: return {"error": "session is not found"} return self.sessions[session]["field"].getPublicInfo() def open(self, session, x, y): if not session in self.sessions: return {"error": "session is not found"} self.sessions[session]["field"].open(x, y) return self.sessions[session]["field"].getPublicInfo() def flag(self, session, x, y, flag): if not session in self.sessions: return {"error": "session is not found"} self.sessions[session]["field"].flag(x, y, bool(flag)) return self.sessions[session]["field"].getPublicInfo() pythonでシングルトンは初めて作りました。不思議な仕様ですね。 APIの実装 FastAPIで、APIの定義を行います。 APIが呼ばれたらmanagerにそのまま引数を渡します。 レスポンスもそのままmanagerの戻り値の辞書を返します。 ms_main.py from fastapi import FastAPI from ms_manager import ms_manager from starlette.middleware.cors import CORSMiddleware from pydantic import BaseModel app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) class CreateParam(BaseModel): width: int hight: int mine: int seed: int class OpenParam(BaseModel): x: int y: int class FlagParam(BaseModel): x: int y: int flag: int @app.post("/ms") def ms_post_root(param : CreateParam): return ms_manager().create(param.width, param.hight, param.mine, param.seed) @app.get("/ms") def ms_get_root(): return {"session": ms_manager().get_list()} @app.get("/ms/{session}") def ms_get_session(session: str): return ms_manager().get_sessison(session) @app.post("/ms/{session}") def ms_post_session(session: str, param : OpenParam): return ms_manager().open(session, param.x, param.y) @app.post("/ms/{session}/flag") def ms_post_flag(session: str, param:FlagParam): return ms_manager().flag(session, param.x, param.y, param.flag) 以下のコマンドでFastAPIを起動してください。 uvicorn ms_main:app --reload --host 0.0.0.0 APIのテスト用のスクリプトも補足に記載しておきます。* フロントエンド(React) 実装するイベントは下表とします。 操作 説明 API リセットボタン 状態をリセットします。 POST /ms 描画 セルの状態を取得します。 GET /ms/{session} クリック セルを開きます。 POST /ms/{session} Ctrl+クリック フラッグを設定/解除します。 POST /ms/{session}/flag 各種操作でバックエンドへの要求し、取得した情報の表示を行います。 画像ファイル類はpublicフォルダに格納して参照します。* Table.js import './Table.css'; import React from "react"; import axios from "axios"; class Table extends React.Component { constructor(props) { super(props); this.state = { table : '' , message:'' }; this.base_url = "http://localhost:8000/ms"; this.session = ''; this.width = 30; this.hight = 16; this.mine = 99; this.fields = null; } componentDidMount = () => { this.updateTable(); } handleClick = () => { this.updateTable(); } updateTable = () => { axios .post(this.base_url , { "width": this.width, "hight": this.hight, "mine": this.mine, "seed" : Date.now()}) .then(res => { var data = res.data; this.get_fields(data.session); }) .catch(err =>{ console.log(err); }); } get_fields = (session) => { this.session = session; const session_url = this.base_url + "/" + this.session; axios .get(session_url) .then(res => { this.showTable(res.data); }) .catch(err =>{ console.log(err); }); } showTable = (fields) => { this.fields = fields; var items = [] for(var y = 0 ; y < fields.height ; y++){ for(var x = 0 ; x < fields.width ; x++){ var i = y * fields.width + x; var c = fields.cells[i]; var filename = ''; if(c.open === 0) if(c.flag === 1) filename = `${process.env.PUBLIC_URL}/flag.png`; else filename = `${process.env.PUBLIC_URL}/close.png`; else if(c.mine === 1) filename = `${process.env.PUBLIC_URL}/mine.png` else if(c.number) filename = `${process.env.PUBLIC_URL}/` + c.number + `.png` else filename = `${process.env.PUBLIC_URL}/open.png`; items.push(<img name={i} src={filename} onClick={(e)=>{ this.choiceDiv(e) } } />); } items.push(<br/>); } this.setState({table : (<dev>{items}</dev>)}); if(fields.status === "over") alert("over"); else if(fields.status === "cleared") alert("cleared"); var rest_label = "[Rest:" + fields.rest + "]" ; this.setState({message : rest_label}); } choiceDiv = (e) => { var num = parseInt(e.currentTarget.name); var x = num % this.width; var y = num / this.width; if( this.fields.status === "over" || this.fields.status === "cleared") { return; } if (e.ctrlKey){ if( this.fields.cells[num].open === 0){ var flag = 0; if(this.fields.cells[num].flag === 0){ flag = 1 } this.setFlag(x, y, flag) } } else{ if( this.fields.cells[num].open === 0 && this.fields.cells[num].flag === 0){ this.openCell(x,y) } } } setFlag = (x,y,flag) => { const flag_url = this.base_url + "/" + this.session + "/flag"; axios .post(flag_url , { "x": x, "y": y, flag: flag}) .then(res => { this.showTable(res.data); }) .catch(err =>{ console.log(err); }); } openCell = (x,y) =>{ const session_url = this.base_url + "/" + this.session; axios .post(session_url , { "x": x, "y": y}) .then(res => { this.showTable(res.data); }) .catch(err =>{ console.log(err); }); } render() { return ( <div> <button onClick={() => this.handleClick()}>[リセット]</button> <div>{this.state.message}</div> <div className="Table-fields">{this.state.table}</div> </div> ); } } export default Table; セルの配置がずれないようにcssを用意します。 Table.css .Table-fields { font-size: 0; } デフォルトのApp.jsから不要な物を削除し、Tableのみを表示させます。 App.js import './App.css'; import Table from "./Table"; function App() { return ( <div className="App"> <header className="App-header"> <Table /> </header> </div> ); } export default App; 動作確認 ブラウザでReactのページを表示します。 ボタンやセルをクリックしてみてください。 おわりに モデルがバックエンドにあるので、Reactは操作のイベントと表示だけの実装となりシンプルになったように感じました。 Reactでは描画更新のためにstateに設定するのが重要ということは理解しましたが、ちゃんと画面設計できるようになるには慣れが必要そう。 Reactの勉強というよりは、Web開発の練習にちょうどいい難易度だと思いますので、皆さんも自分で使用とロジックを考えて作ってみてください。 補足 アイコンの作成 使用したアイコンのPNG画像はpythonで作成しました。 生成スクリプトも置いておきます。 generate_icons.py from PIL import Image, ImageDraw, ImageFont w = 24 h = 24 lineWidth = 2 font_path = 'C:/WINDOWS/Fonts/impact.ttf' # for Windows 太目のフォント font_size = 20 font_y_offset = -2 # 計算で中央に配置してもいまいちなので微調整 OpenBgColor = (220, 220, 220) OpenLineColor = (255, 255, 255) param = [ {"filename": "1.png", "text": "1", "textColor": (0, 0, 255), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "2.png", "text": "2", "textColor": (0, 100, 0), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "3.png", "text": "3", "textColor": (255, 0, 0), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "4.png", "text": "4", "textColor": (0, 0, 100), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "5.png", "text": "5", "textColor": (100, 25, 25), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "6.png", "text": "6", "textColor": (0, 100, 100), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "7.png", "text": "7", "textColor": (0, 0, 0), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "8.png", "text": "8", "textColor": (100, 100, 100), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "open.png", "text": "", # 0の場合は数字表示しない "textColor": (0, 0, 0), "bgColor": OpenBgColor, "lineColor": OpenLineColor}, {"filename": "mine.png", "text": "", "textColor": (0, 0, 0), "bgColor": (255, 0, 0), "lineColor": OpenLineColor}, # 開く前のはLineの色が違う {"filename": "close.png", "text": "", "textColor": (0, 0, 0), "bgColor": (128, 128, 128), "lineColor": (64, 64, 64)}, {"filename": "flag.png", "text": "", # TODO 旗のデザインにしたいが単色 "textColor": (0, 0, 0), "bgColor": (0, 0, 255), "lineColor": (64, 64, 64)}, ] font = ImageFont.truetype(font_path, font_size) for item in param: image = Image.new("RGB", (w, h), item["bgColor"]) draw = ImageDraw.Draw(image) if item["text"]: text_w, text_h = draw.textsize(item["text"], font=font) draw.text(((w - text_w) / 2, (h - text_h) / 2 + font_y_offset), item["text"], item["textColor"], font=font) draw.line(((0, h-lineWidth), (w, h-lineWidth)), item["lineColor"], width=lineWidth) draw.line(((w-lineWidth, 0), (w-lineWidth, h)), item["lineColor"], width=lineWidth) image.save(item["filename"]) APIのテスト APIを動作確認するためのPythonスクリプトです。 dockerコンテナで別なターミナルを起動して実行してください。 Python環境があれば、ホストPCで実行してもよいです。 ms_client.py import requests import json import time base_url = "http://127.0.0.1:8000/ms" def printField(response): cells = response["cells"] width = response["width"] hight = response["height"] status = response["status"] print("[{}*{}]".format(width, hight)) for i, cell in enumerate(cells): if i % width == 0 and i != 0: print("") # 改行 if cell["open"] == 0 and cell["flag"] == 1: print("F", end="") elif cell["open"] == 0 and cell["flag"] == 0: print("/", end="") elif cell["open"] == 1: if "mine" in cell and cell["mine"] == 1: print("*", end="") elif "number" in cell: print("{}".format(cell["number"]), end="") print("\nstatus={}\n".format(status)) def main(): response = requests.post(base_url, json={'seed': int(time.time()), 'width': 5, 'hight': 6, 'mine': 2, }) res_create = response.json() session_id = res_create['session'] session_url = "{}/{}".format(base_url, session_id) response = requests.get(session_url) printField(response.json()) loop = True while loop: print("input [x y]") # "1 0"のようにスペースで区切ってxとyの座標を入力しEnterキー input_x, input_y = map(int, input().split()) response = requests.post(session_url, json={'x': input_x, 'y': input_y}) res_open = response.json() printField(res_open) if res_open["status"] != "continue": loop = False if __name__ == '__main__': main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

boolのリストの中身が全部Trueか、あるいはTrueが含まれるかの判定

全部Trueか(Falseが含まれるか)どうかはall()で確認できる。 print(all([True, True, True])) # True print(all([True, False, True])) # False Trueが含まれるか(全部Falseか)どうかはany()で確認できる。 print(any([True, False, False])) # True print(any([False, False, False])) # False まとめると、以下。 <bool> 全部<>か <>が含まれるか True all(hoge) any(hoge) False not any(hoge) not all(hoge) ※hogeはイテラブルな何か。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

上のモジュールが読み込めねぇえ!読み込ませろぉお!問題でいつまでも消耗しないためのPython初心者メモ

はじめに ひさしぶりにPythonでパッケージ書いてて前にも躓いた親モジュールをインポートできないことで数時間消耗したのでメモを残す。 結論 sys.path.extend()はIDEで自動整形できなくなって結果的にコード全体がきたなくなるのでやめる PYTHONPATHにパッケージのパスを追加する systemのpythonつかってようと、pyenvでバージョン管理してようと問題なし ~/.zshrc export PYTHONPATH="/home/username/package_path:$PYTHONPATH"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データサイエンス ロードマップ

職業・業界研究 データサイエンティストについて調べる 必要なスキルとレベル 1.とりあえずライブラリを使って実装できる 2.最適な手法を選んでライブラリを使って実装できる 3.ライブラリを使いつつカスタマイズできる 4.論文から独自に実装できる  企業 学習内容 プログラミング言語 Python @Scientific Computing with Python データサイエンス/ライブラリ/フレームワーク 機械学習・基礎 データサイエンス @Data Analysis with Python データ分析用ライブラリ Numpy フレームワーク 統計学 実装・演習 機械学習・実装・理論 Kaggle/SIGNATE
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python2.7 と python3.7 と ctypes

python2.7 で動いていたプログラムを python3.7 で動かそうとすると時々変なことが起こる。 ctypes C言語で記述されたライブラリを他の言語から利用したいことがある。 python では ctypes というモジュールが標準で用意されており、これを利用すればC言語のソースコードに手を加えることなく python から利用することができる。 問題 例えば、以下のようなC言語のプログラムがあり、これを python で動かしたいとする。 単に引数で与えられた文字列を標準出力するプログラムである。 printstr.c #include <stdio.h> void printstr(char* str) { printf("%s\n", str); } これを libprintstr.so として動的リンクができるようにコンパイルする。gcc なら以下のように。 gcc -c -fPIC printstr.c gcc -fPIC -shared -o libprintstr.so printstr.o 以下のように python から利用することができる。 ctype.py from ctypes import * lib = cdll.LoadLibrary("./libprintstr.so") str = "hoge" lib.printstr(str) python2.7, python3.7 での実行結果はそれぞれ以下の通り。 見ての通り、python3.7 では意図しない結果となっている。 python2.7 hoge python3.7 h 対応 ctype.py from ctypes import * lib = cdll.LoadLibrary("./libprintstr.so") str = "hoge".encode('utf-8') lib.printstr(str) python3.7 hoge
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cartopy でローカルにあるデータを利用する

basemap と比べて cartopy について日本語で書かれたサイトは少ない気がする。何故だろう……? Qiita でも cartopy の記事数は basemap の記事数に比べて少ないし…… そもそも cartopy とは何か cartopy とは python で地理データを描画する為のライブラリである。 anaconda 環境なら conda install で簡単にインストールすることができる。 conda install -c conda-forge cartopy 似たようなライブラリに basemap があるが、こちらは既に開発が終了しており、cartopy はその後継らしい。 であれば、今から使うのであれば cartopy で描画するのが良いだろう。 何がしたいのか cartopy では、スクリプト実行時に Natural Earth というサイトから地理データを自動でダウンロードして描画する。 しかし、セキュリティの問題等何らかの理由でインターネットからダウンロードが行えない環境では、これでは困ってしまう。 また、cartopy を頻繁に使うのであれば、データをわざわざインターネットからダウンロードするのではなく、ローカルにあった方が良いだろう。 ということで、あらかじめローカルにデータを手動でダウンロードしておき、そのデータを参照して描画を行いたい。 どうすれば良いのか Natural Earth のダウンロードページから zip データをダウンロードする。 この zip データを解凍し、anaconda のあるパスに以下のようなディレクトリ構造でデータを置く。シンボリックリンクでも良い。 ここで、category は Cultural, Physical, Raster で、resolution (解像度)は 10m, 50m, 110m のいずれかである。 name はデータの名前を指しており、例えば海岸線なら category が Physical の Coastline である。 anaconda3/lib/python3.7/site-packages/cartopy/data/shapefiles/natural_earth/{category}/ne_{resolution}_{name}.[ext] そして、ソースコードに以下のように書いておく。 import cartopy cartopy.config["data_dir"] = cartopy.config["repo_data_dir"] これで、ローカルにあるデータを参照し、地理データを描画してくれる。めでたしめでたし。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kerasで学習済みVGGを使ったPerceptual Lossを導入する

Kerasで自作のU-netを用いて画像のスタイル変換をしようとしましたが、Loss関数をMSEで学習しても、スタイルをうまく学習できませんでした。 そこで、スタイル変換の論文でよく使われているPerceptual Lossを使ってみることにしました。 Perceptual Loss Perceptual Lossとは、画像を学習済みネットワークに通して得られる特徴マップ同士でlossを計算します。画像同士でのMSEでは、ピクセル単位でlossが発生してしまい、スタイルがうまく学習できなかったり、ぼやけた出力になってしまったりします。それに対し、特徴マップ同士でのlossでは、ピクセルのずれでlossが発生しにくいので、スタイルの学習やシャープな出力が期待されます。 また、ネットワークの最後の層の出力だけでなく、途中の層の特徴マップもlossの計算に使うことで、様々な解像度のスタイルで学習を行うことができます。 学習済みのモデルとしてVGG-16などがよく使われているようですが、このモデルは特徴抽出器の後に全結合層のクラス分類器が接続されています。Perceptual Lossの計算に必要なのは特徴マップのみなので、全結合層は使わず特徴抽出器のみを使います。 実装 U-NetなどのAuto Encoderのネットワークを用いて、画像のスタイル変換を行う場合の実装です。スタイル変換のネットワークには、自作またはimportしたモデルを使うことを前提としています。 学習済みVGG-16 今回はKerasのApplicationsで様々な学習済みモデルが提供されているので、その中のVGG-16を使います。今回は全結合層は必要ないので、モデルを作る際に引数に include_top=False を指定することで、特徴抽出器のみになります。また、この場合、モデルに入力するデータ形状を指定することができます。 from keras.models import Model from keras.applications.vgg16 import VGG16 # Kerasの学習済みVGG-16 vgg_model = VGG16(weights='imagenet', # ImageNetで学習したモデル include_top=False, # 特徴抽出器のみ input_shape=(512, 512, 3)) # 入力形状 vgg_model.trainable = False # 学習時にVGGのパラメータ更新をしない loss_model = Model(vgg_model.input, vgg_model.output) # Loss計算のためのモデルを定義 loss_model.compile(optimizer='adam', loss='mse', metrics=['accuracy']) スタイル変換を含めたネットワーク 上記で実装したLoss計算モデルに、スタイル変換モデルを追加します。 transfer_model = unet_model(shape=(512, 512, 3)) # 512x512,3chの画像を変換するモデル loss_model_outputs = loss_model(transfer_model.output) # transfer_modelの出力をloss_modelに入力 full_model = Model(transfer_model.input, loss_model_outputs) # 全体のネットワーク full_model.compile(optimizer='adam', loss='mse', metrics=['accuracy']) # 特徴マップをMSEでLoss計算 unet_model(shape=(512, 512, 3)) は、自作のモデル定義関数なので、適切なものに変更してください。 学習&テスト 上記で実装したモデルの出力は、VGGの特徴マップになっていますが、目的の出力はtransfer_modelで変換した画像になります。そこで、モデルに入力する教師データと、テスト時の出力を工夫する必要があります。 以下のデータがあることを前提としています。 x_train : 512x512x3の画像が複数枚あるnumpy array y_train : x_trainに対応する教師画像 x_test : 512x512x3の画像が複数枚あるnumpy array y_test : x_testに対応する教師画像 学習 実装したfull_modelの入力は画像、出力はloss_modelの特徴量であるので、full_modelに与える教師データもloss_model特徴量に変換します。 y_train_feature = loss_model.predict(y_train) # 教師画像を特徴量に変換 full_model.fit(x_train, y_train_feature, epoch=10) テスト テスト時の出力は、full_modelの中のtransfer_modelの出力とします。 full_model.summary() で、モデルの層を調べて、対応する層の出力をpred_modelの出力とします。 pred_model = Model(full_model.input, full_model.layers[50].output) # 数字はtransfer_modelの層数に応じて変更 predict = pred_model.predict(x_test) 補足 複数の層でLossを計算 VGGの最後の出力でLossを計算しましたが、複数の層の出力でLossを計算するには、loss_modelのところで以下のコードを追加・変更します。 ##### 追加 select_layers = [2, 9, 18] # Lossを計算する層の選択 vgg_outputs = [vgg_model.layers[i].output for i in select_layers] # 選択した層の出力 #### loss_model = Model(vgg_model.input, vgg_outputs) # 変更 画像のMSEもLossに追加する 特徴マップでのLossだけでなく、画像のピクセル単位のMSEもLossに追加したいです。 vgg_outputs.append(vgg_model.input) で、vgg_modelの入力(transfer_modelの出力画像)をloss_modelの出力に加えることで、画像のLossも追加しようとしましたが、「モデルの入力を出力にできない」エラーが出てしまい、できませんでした。 そこで、画像の値は正であるので、入力にReLUを通して、その出力をloss_modelの出力に加えることで対処しました。(もっといい対処法があると思います) from keras.layers import Input, Activation #### 追加 input_batch = Input(shape=(512, 512, 3)) relu_output = Activation('relu')(input_batch) #### #### 変更 vgg_model = VGG16(weights='imagenet', # ImageNetで学習したモデル include_top=False, # 特徴抽出器のみ input_tensor=input_batch, # 追加 input_shape=(512, 512, 3)) # 入力形状 #### vgg_outputs.append(relu_output) # 追加 loss_model = Model(input_batch, vgg_outputs) # 変更 参考にしたサイト Keras Applications https://keras.io/ja/applications/ Implement perceptual loss with pretrained VGG using keras https://stackoverflow.com/questions/47675094/implement-perceptual-loss-with-pretrained-vgg-using-keras
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 Macでpythonのバージョンの設定について

はじめに この記事では、M1のMacでpythonのデフォルトバージョンの設定について解説します。 brewからpyenvをインストールし、pyenvでpythonのバージョンを切り替えという方法をよく見ますが、僕の場合pyenvをインストールするときにエラーが発生したので、より簡単な方法でやっていきます。 pythonバージョン、path確認 まず、もともとMacにインストールされているpythonを確認 $ python -V デフォルトではpython 2.7.~ になると思います。 それから自分でインストールしたpython3系を確認 $ python3 -V 出力に自分がインストールしたpythonのバージョンが出てくると思います。 次に、python3系のpathを出力させ、後ほどそれを使います。 $ which python3 僕の場合は/usr/bin/python3になってます。 次は自分のTerminalはbashかzshかを確認してください。 確認方法としてはTerminalのウインドの上部で、自分のユーザ名の後にあります。 Big Surではほぼzshになってると思いますが、両方やります。 bashの場合 Terminalがbashの場合は、 $ open ~/.bash_profile と上記のファイルを開きます。 # Setting PATH for Python 3.7 # The original version is saved in .bash_profile.pysave PATH="/Library/Frameworks/Python.framework/Versions/3.7/bin:${PATH}" export PATH 中身は大体上記のようになってます。そして最後の行(下の空白のところ)に $ alias python="先ほど確認した自分のpythonのpath" と書き加えます。僕の場合は $ alias python="/usr/bin/python3" を入力しました。 それから、ファイルを保存し、Terminalで $ source ~/.bash_profile と打ちます。これは先の変更を有効化する意味です。 1回Terminalを開き直して、python -Vで確認してみてください! zshの場合 zshの場合ほとんどだと思います。先ほどを大体同じやり方で、 $ open ~/.zshrc と入力します。ファイルを開いたら $ alias python="自分のpython3のpath" と下に追加します。 もしかしてopenのところでファイルが存在しないというエラーが吐くかもしれないので、その場合は、 $ echo 'alias python="自分のpython3のpath"' >> ~/.zshrc と入力し、またopenで.zshrcファイルを開いて、alias python="自分のpython3のpath"というコードは入っているかを確認してください。 最後terminalを再起動し、python -V でバージョンがpython3の方になっているかを確認してください! 今回は以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【DRF】Django REST FrameworkのSerializerとF()を同時に使う

今回のテーマはdjango.db.modelsにあるFクラスと、DRFのSerializerを使う時に気をつけなければならないことを短く紹介したいと思います。 結論 Serializerをイニシャライズする前に、F()を使って.save()したインスタンスに対して、 instance.refresh_from_db() を呼びましょう。 説明 基本的に、Fを使った後にインスタンスの情報にアクセスしたいなと思ったときにはこれを思い出すといいと思います。もちろんSerializerに限る話ではありません。 例えば、いいね機能を実装しているとします。 いいねをすると言う機能は、ユーザーが同時に大量にアクセスすることがあり得るので、Fを使うことでちゃんと+1、-1されるように実装します。 しかし、Serializerでシリアライズするときに内部では、定義されたフィールドに応じて型がキャストされるので、F('count') + 1のような表現は許容されません。 F('count') + 1のような表現はCombinedExpressionと呼ばれていて、int型に変換することはできませんよというエラーが出てしまいます。 それを回避するために、いったんDBとのやりとりをリフレッシュすることで、モデルのインスタンスから正しい形の(Fが走り終えた後の)データを取得することができ、正しくシリアライズすることができるようになります。 さらに詳しい説明は、Django refresh_from_db コロナに負けず頑張りましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SBI証券の取引履歴から決済損益トップ5、ワースト5を抽出してみた

やってみたこと SBI証券を使って、株のデイトレードをしている。 SBI証券HPへのログインを自動化してみた で、少しLGTMをいただけてうれしかったので、 もう少し実用的なこととして、ある期間のトレードでいくら儲かってるか、いくら損しているかがわかるようにしてみた。 具体的には、SBI証券HPから取引履歴のCSVファイルをダウンロードして、 各売買の決済損益を算出し、指定した日付からの決済損益と、その中からトップ5、ワースト5を出力してみた。 環境 Chrome 89.0.4389.128 ChromeDriver Python 3.8 手順 まずは以下Pythonコードを実行。 以下コードでは、2021年1月1日以降の履歴を取得している。 このPythonコードでは、ひとまず取引履歴のCSVファイルをダウンロードし、 そのファイルパス取得するまでとする。 from selenium import webdriver from selenium.webdriver import ChromeOptions from selenium.webdriver.chrome.webdriver import WebDriver import sys import os import time WAIT_TIME = 5 def login(driver): driver.get('https://www.sbisec.co.jp/ETGate') loginid = driver.find_element_by_name('user_id').send_keys('****') passwd = driver.find_element_by_name('user_password').send_keys('****') Log_in = driver.find_element_by_name('ACT_login').click() def download_csvfile(driver, year, month, day): driver.get('https://site2.sbisec.co.jp/ETGate/?_ControlID=WPLETacR007Control') From_year = driver.find_element_by_name('ref_from_yyyy').send_keys(year) From_month = driver.find_element_by_name('ref_from_mm').send_keys(month) From_day = driver.find_element_by_name('ref_from_dd').send_keys(day) Search = driver.find_element_by_name('ACT_search').click() Download_csv = driver.find_element_by_id('csvlink').click() def main(): # setting folder for downloading downloadsFilePath = 'C:' options = ChromeOptions() prefs = { "profile.default_content_settings.popups": 1, "download.default_directory": os.path.abspath(downloadsFilePath) + r"\\", "directory_upgrade": True } options.add_experimental_option("prefs", prefs) driver = webdriver.Chrome(executable_path=r'./chromedriver.exe', options=options) driver.implicitly_wait(WAIT_TIME) login(driver) Account = driver.find_element_by_xpath('//a[img/@title="口座管理"]').click() # download csv file from 2021/01/01 download_csvfile(driver, '2021', '01', '01') time.sleep(WAIT_TIME) # get the latest csv file name filename = None if len(os.listdir(downloadsFilePath)) != 0: filename = max([downloadsFilePath + '\\'+ f for f in os.listdir(downloadsFilePath) if f.endswith('.csv')], key=os.path.getctime) # return the file name to shell sys.stdout.write(filename) driver.close() if __name__== '__main__': main() 2. ダウンロードしたCSVファイルパスを引数として、以下Pythonコードを実行すると、 合計の決済損益と、決済損益のトップ5とワースト5が出力される。 import sys import io import csv sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') args = sys.argv input_csv = args[1] item_code = None if len(args) == 3: item_code = args[2] output_csv = 'output.csv' output_csv_header = ['現買日', '現売日', '銘柄', '銘柄コード', '購入数量', '現買単価', '現売単価', '決済損益'] def main(): with open(input_csv, newline='', encoding='shift-jis') as f: # pass head rows for (n, h) in enumerate(csv.DictReader(f), start=1): if n >= 5: break input_csv_header = ['約定日', '銘柄', '銘柄コード', '市場', '取引', '期限', '預り', '課税', '約定数量', '約定単価', '手数料/諸経費等', '税額', '受渡日', '受渡金額/決済損益'] input_csv_data = [] output_csv_data = [] reader = csv.DictReader(f, input_csv_header) for row in reader: if item_code is None: # pass the exception rows of stock trade if row['取引'] != '株式現物買' and row['取引'] != '株式現物売': continue else: if row['銘柄コード'] != item_code or (row['取引'] != '株式現物買' and row['取引'] != '株式現物売'): continue input_csv_data.append(row) for i, row in enumerate(input_csv_data): amount = 0 price = 0 for j in range(i + 1, len(input_csv_data)): if row['銘柄コード'] == input_csv_data[j]['銘柄コード']: brand = input_csv_data[j]['銘柄'] brand_code = input_csv_data[j]['銘柄コード'] if row['取引'] == '株式現物買': sell_day = input_csv_data[j]['約定日'] buy_day = row['約定日'] sell_price = input_csv_data[j]['約定単価'] buy_price = row['約定単価'] sell_amount = input_csv_data[j]['約定数量'] buy_amount = row['約定数量'] if amount == 0: amount = int(buy_amount) - int(sell_amount) price = float(input_csv_data[j]['受渡金額/決済損益']) - float(row['受渡金額/決済損益']) else: amount -= int(sell_amount) price += float(input_csv_data[j]['受渡金額/決済損益']) if amount == 0: item = {'現買日': buy_day, '現売日': sell_day, '銘柄': brand, '銘柄コード': brand_code, '購入数量': buy_amount, '現買単価': buy_price, '現売単価': sell_price, '決済損益': price} else: buy_day = input_csv_data[j]['約定日'] sell_day = row['約定日'] buy_price = input_csv_data[j]['約定単価'] sell_price = row['約定単価'] buy_amount = input_csv_data[j]['約定数量'] sell_amount = row['約定数量'] if amount == 0: amount = int(buy_amount) - int(sell_amount) price = float(row['受渡金額/決済損益']) - float(input_csv_data[j]['受渡金額/決済損益']) else: amount += int(buy_amount) price -= float(input_csv_data[j]['受渡金額/決済損益']) if amount == 0: item = {'現買日': buy_day, '現売日': sell_day, '銘柄': brand, '銘柄コード': brand_code, '購入数量': buy_amount, '現買単価': buy_price, '現売単価': sell_price, '決済損益': price} if amount == 0: input_csv_data.pop(j) output_csv_data.append(item) break with open(output_csv, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=output_csv_header) writer.writeheader() total = 0 for row in output_csv_data: writer.writerow(row) total += float(row['決済損益']) print('\"total record: {}'.format(len(output_csv_data))) print('total gain/loss: {}'.format(total)) top_list = {} top_list = sorted(output_csv_data, key=lambda x: x['決済損益'], reverse=True) bottom_list = {} bottom_list = sorted(output_csv_data, key=lambda x: x['決済損益']) print('\n=TOP5=') for i in range(5 if len(top_list) >= 5 else len(top_list)): print(top_list[i]) print('\n=BOTTOM5=') for i in range(5 if len(bottom_list) >= 5 else len(bottom_list)): print(bottom_list[i]) # quotation print('\"') if __name__ == '__main__': main() これからやってみること 合計決済損益とトップ5、ワースト5を、毎日自分のメールアドレスに送信するようにサーバーに設定すれば、毎日自動的にトレード状況をチェックできてうれしいかも
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenCVのスーパーピクセル(2). 分割領域を平均化してみる

はじめに OpenCVのスーパーピクセル(領域分割)画像処理について、前回記事を書きましたが 分割してどうする?のどうするかについての部分を書いていませんでしたので 書きたいと思います。 スーパーピクセルについて スーパーピクセルは領域分割とかセグメンテーションアルゴリズムのことで 領域を分割した上で画像処理をします。 今回は、スーパーピクセルで分割した領域のラベル情報(マスク)を取得し それを表示します。 また、そのラベル情報(マスク)で、分割領域のBGR平均値を取得し、画像に 反映することをやってみようと思います。 1)スーパーピクセルインスタンスの生成 まず、スーパーピクセルインスタンス生成し、画像データをインスタンスに入力します。 seeds = cv2.ximgproc.createSuperpixelSEEDS(width, height, channels, num_superpixels, num_levels, prior, num_histogram_bins, double_step) # 画像のスーパーピクセルセグメンテーションを計算 # 入力画像は,HSVまたはL*a*b* converted = cv2.cvtColor(input_image, cv2.COLOR_BGR2HSV) seeds.iterate(converted, num_iterations) 入力画像はHSV色空間かまたはL*a*b*色空間を使います。 上記サンプルconverted画像はHSV色空間です。 2)スーパーピクセルセグメンテーションの境界を取得 各分割された領域を分ける線を黄色で線引きします。 これは、人にわかりやすくするための処理で、必ずしも必須ではありません。 # スーパーピクセルセグメンテーションの境界を取得 contour_mask = seeds.getLabelContourMask(False) result = input_image.copy() result[0 < contour_mask] = ( 0, 255, 255) 領域を分ける線情報へのアクセス方法を掲載しています。 3)セグメンテーション情報取得 セグメンテーション(領域分割)の分割数と、ラベル情報を取得します。 ラベル情報は、np.int32型の2次元配列です。 ここに分割猟奇事に数字が入っています。 ちょうど、粒子解析ラベリングとかブロブ処理と言われる場合にも 同様なイメージが使われているかと思います。 # セグメンテーション数の取得 nseg = seeds.getNumberOfSuperpixels() # セグメンテーション分割情報の取得 labels = seeds.getLabels() 4)セグメンテーション状況(ラベル情報)を表示 セグメント分けがどのようになっているかを見る為に ラベル情報をランダムBGR表示しています。 これは確認用の処理です。 randcolor = np.random.randint(255, size=[nseg,3]) lbsegimg = np.zeros((height, width, channels), dtype=np.uint8) for m in range(0, nseg): lbsegimg[labels == m] = randcolor[m] lbsegimgがラベル情報をランダムBGRタイリング画像 5)セグメンテーション平均化処理 セグメンテーション(領域分割)のセグメント毎にBGR平均値をマスク処理で取得します。 返却値は浮動小数点型のタプルなので、これをint型配列に変換し、画像に設定します。 # セグメンテーション毎のBGR平均値を取得 segavgimg = np.zeros((height, width, channels), dtype=np.uint8) lb = np.zeros((height, width), dtype=np.uint8) for m in range(0, nseg): lb.fill(0) lb[labels == m] = 255 bgrm = cv2.mean(input_image, lb) # BGR平均値を取得 # tuple float形式の平均値情報をint形式に変換 bgr = [int(bgrm[0]), int(bgrm[1]), int(bgrm[2])] segavgimg[lb == 255] = bgr segavgimg画像が、セグメンテーションの平均化処理イメージです。 cv2.mean()は、マスク画像をしていすることで、マスク領域の平均値を取得することができます。 サンプルプログラム import cv2 import math import numpy as np def main(): input_image = cv2.imread('Vegetables.jpg') if input_image is None: print("ファイルオープンエラー") return -1 # スーパーピクセルセグメンテーションの生成 height, width, channels= input_image.shape[:3] num_iterations = 4 prior = 2 double_step = False num_superpixels = 500 num_levels = 4 num_histogram_bins = 5 seeds = cv2.ximgproc.createSuperpixelSEEDS(width, height, channels, num_superpixels, num_levels, prior, num_histogram_bins, double_step) # 画像のスーパーピクセルセグメンテーションを計算 # 入力画像は,HSVまたはL*a*b* converted = cv2.cvtColor(input_image, cv2.COLOR_BGR2HSV) seeds.iterate(converted, num_iterations) # スーパーピクセルセグメンテーションの境界を取得 contour_mask = seeds.getLabelContourMask(False) result = input_image.copy() result[0 < contour_mask] = ( 0, 255, 255) # セグメンテーション数の取得 nseg = seeds.getNumberOfSuperpixels() # セグメンテーション分割情報の取得 labels = seeds.getLabels() # セグメンテーション状況(ラベル情報)をランダムBGRでタイリング randcolor = np.random.randint(255, size=[nseg,3]) lbsegimg = np.zeros((height, width, channels), dtype=np.uint8) for m in range(0, nseg): lbsegimg[labels == m] = randcolor[m] # セグメンテーション毎のBGR平均値を取得 segavgimg = np.zeros((height, width, channels), dtype=np.uint8) lb = np.zeros((height, width), dtype=np.uint8) for m in range(0, nseg): lb.fill(0) lb[labels == m] = 255 bgrm = cv2.mean(input_image, lb) # BGR平均値を取得 # tuple float形式の平均値情報をint形式に変換 bgr = [int(bgrm[0]), int(bgrm[1]), int(bgrm[2])] segavgimg[lb == 255] = bgr # 画像表示 cv2.imshow('Vegetableslbsegimg', lbsegimg) cv2.imshow('Vegetablesresult', result) cv2.imshow('Vegetables means image', segavgimg) cv2.waitKey(0) return 0 if __name__ == '__main__': main() 結果 入力画像 入力画像は、野菜の盛り合わせ写真。 「野菜生活」 photoAC掲載 koma-komaさんの写真です。記事掲載用に少し縮小しました。 この画像に対してスーパーピクセル処理を行います。 スーパーピクセルセグメンテーションの境界画像 セグメンテーションのラベル情報 黄色い線の黄色い線の内側をランダムカラーで塗ったイメージです。 セグメンテーション状況(ラベル情報)画像 各セグメント毎に平均色で色分けしています。 最後に 前回記事を書いた後、すぐにこれのような記事を書こうとしていたのですが、いろいろ忙しく、 また興味が他に移っていたこともあり保留となっていました。 残件を仕上げた感じで少しさわやかないいかんじです。 実行環境 Windows10 Anaconda (Miniforge3) Python 3.9.2 packaged by conda-forge OpenCV 4.5.1 参考 Qiita - OpenCVのスーパーピクセル Emotion Explorer - OpenCVのスーパーピクセル(3) - SuperpixelLSCクラスを試す。 Emotion Explorer - OpenCVのスーパーピクセル(2) - SuperpixelSLICクラスを試す。 Emotion Explorer - OpenCVのスーパーピクセル(1) - SuperpixelSEEDSクラスを試す。 Emotion Explorer - Watershedアルゴリズムの領域分割 Emotion Explorer - 画素の平均を計算 docs.opencv.org - Superpixels Extended Image Processing docs.opencv.org - cv::ximgproc::SuperpixelSEEDS Class Reference docs.opencv.org - cv::ximgproc::SuperpixelSLIC Class Reference docs.opencv.org - cv::ximgproc::SuperpixelLSC Class Reference 写真引用 「野菜生活」 photoAC掲載 koma-komaさんの写真
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

京都の衛星画像を機械学習で解析した比較 - 平成6年Win3.1,Visual Basic2.0と令和3年Win10,Python3.8

Windowsがまだ3.1だったころ、某大学の学生時代、研究室でリモートセンシングの研究をしていました。衛星画像を解析して土地の被覆状況を調べるようなことをしていました。 これを読んでいる方の中にはまだ生まれていない方も多くいると思います。その当時はフロッピーディスクの時代で使えるPCも今では絶滅危惧種か絶滅しているようなものばっかりの中で、衛星画像の解析をしていました。最近Pythonを使って同じことを今の技術で行うと、いかに技術革新が進んだかわかったのでそのあたりをご紹介したいと思います。 平成6年 当時のIT周りの背景と使ったもの この研究を始めたのは1994年、平成6年で元号が平成になって10年もたっていないころです。大ヒットしたWindows95がまた発売されていないころで、インターネットも一般では使われておらず大学などの研究で使われていたような時代です。ブラウザーは今はなきNetscape Navigatorの最初のバージョンがリリースされたころで、GAFAのうちAppleだけが存在していてAmazonは立ち上げたばかりのころです。また当時はフロッピーディスクの時代で、データのやり取りにCDすら使えなかった時代です。 当然、プログラミング言語についても今では使われないようなものばっかりで、Pythonはありましたが今のように機械学習で多く使われるようなものでもなく、そもそも機械学習という言葉自体あまり知れ渡っていないころです。自分も教師なしクラスター分析という言葉は研究中多く使ってきましたが、機械学習という言葉を使ったことはあまり記憶にありません。 そのような環境で当時大学の研究室で使えたものが以下の通りです Windows 3.1 32bit 5インチ、3.5インチフロッピーディスク(容量は3.5インチで1.4Mほどです) RAM 512M, 1cpu Visual Basic2.0 今IT業界で仕事をしていますが、これはほぼ化石レベルの古さですね。 衛星画像データ 使用した衛星画像データはランドサット5号のTMデータで、研究費用で日本各地のデータを購入しました。前述のように一般に使われている保存媒体は1.4M のフロッピーディスクでインターネットもまともに利用されていないころですので、当然衛星画像データをダウンロードなど出来ません。 購入したデータはフィルム映画の映写機にのような機械を使用して読み取る磁気テープで、何時間もかけてテープからデータを読み取り、保存したデータを使用するパソコンへコピーといった作業をしていました。 使用したデータは以下の通りです ランドサット5号: 1992年4月21日 分解能: 30m (30メートル以上のものを見分けることとができます。) 解析都市: 京都市近郊 解析範囲: 縦501、横325ピクセル(山崎ジャンクションから左京区北白川ぐらいの範囲を長方形で切り取ったぐらいです) 使用バンド: 1-7 教師なしクラスター分析 当時の環境では、ほぼ選択肢がなかったように記憶していますが、非階層クラスター分析を使っています。教師あり、なしについて同じ研究室で教師ありを行っている人がいたので教師なしを使うことにしたはずです。また当時は今のようにGoogle Mapなどなかったので、教師ありにすると土地被覆状況をあるていど実測しないといけなくなるので、教師なしにしたように思います。基本的なロジックはISODATA (Interactive Self-Organization of Data)法を少し変更したものをもとにVisual Basic2.0を使って開発しました。 Visual Basic2.0で機械学習 Pythonのようにライブラリーがなかったので、基本的には自力でアルゴリズムを作る必要があります。 パラメーターの決定 初期クラスタの数、再配置の収束条件、解析の終了条件などは解析を始めるまえに入力してあらかじめ設定しておきます。 項目 値 使用バンド BAND1-7 初期クラスタ数 7 再配置収束条件 5% 解析終了条件 0.7% 初期クラスタの重心の決定 初期クラスタ数: n、バンド: b、クラスタ: c として各バンドの平均値αと標準偏差βをもとに適当な重心値を計算しています。 ^{b} g _c = ^{b} \alpha _c + ^{b} \beta _c \left( \frac{ 2\left(c -1 \right)}{n-1} - 1\right) For c = 1 To n g(b,c) = a(b) + std(b) * ((2*(c-1))/(n-1) -1) Next c 再配置法 バンド: b、クラスタ: c、使用バンド数: k それぞれの地点μと各クラスタの重心χとのユーグリッドの距離を計算し、最も近いクラスタに再配置をします。 d _c = \left\{ \sum_{b=1}^{k} \left(^{b} \chi_c - ^{b} \mu \right)^2\right\}^\frac{1}{2} '各個体とクラスターの重心との距曜の計算 For yl = starty To endy For xl = startx To endx min = 10^4 '初期最低距離 place = XMAX*(y1-1) + x1 + head For c = 1 To n For b = 0 To k-1 Get filenum(b), place,resdata rdata = Asc(resdata) d1 = (g(b,c)- rdata)^2 d(c)= d (c) + d l Next b '距離が最低距離より小さい場合は再配置して最低距離の再設定 If d(c)< min Then min = d(c) classnum = c End If d(c) = 0 Next c Next xl Next yl 512Mのメモリーではデータを一括で読み取ってメモリーにキープできるわけもないので、ループでいちいち読み取っています。 再配置収束条件 再配置後に所属するクラスタを変えた個体数が再配置収束条件5%以上の場合は、再配置後のクラスタの重心を再計算し再配置を繰り返します。5%以下の場合は収束したとみなします。 'クラスタを変えた個体数算出 Get filenumB,place,Gcmoji Gclassnum = Asc(Gcmoji) If Gclassnum <> classnum Then change = change + 1 'クラスタ番号を分析結果ファイルに書き込み Pcmoji = Chr$(classnum) Put filenumB,place,Pcmoji End If '収束条件 calend = 5 Pchange = 100 * (change / X*Y) If Pchenge <= calend Then MsgBox "計算が終了しました" End If 解析終了条件 再配置収束後、クラスタの各バンドごとの標準偏差を求め、最大値を持つクラスタを割り出します。最大値が、画像全体のそのバンドの標準偏差と比較して70%以上の時はクラスタを2分割します。標準偏差が大きすぎるクラスタを減らす作業をしています。 標準偏差が大きすぎるクラスタはこんな感じです。 これをそのままにしておくとこのクラスタの土地被覆状況を判断するのは難しくなります。 '標準偏差が最大値のクラスタをバンドごとに求める For b = O To k-1 stdmax(b) = 0 For c = l To n std(b, c)= Sqr(gg(b, c)-g(b, c)^2) If stdmox(b) < std(b, c) Then stdmax(b) = std(b, c) stdmac(b) = c End If Next c Next b '標準偏差が最大のクラスタを分裂し、クラスタ番号を追加して重心を再計算 For b = O To k-1 If stdmax(b) > stdall(b) * 0.7 Then np = np + 1 For fc = stdmac(b) To n + np Step n + np - stdmac(b) For fb = O To k-1 g(fb,fc) = GetCenter(fb,stdmac(b)) Next fb Next fc End If Next b 'クラスタ総数の追加 n = n + np 解析結果 これら以外にも、各クラスタに色を割り当てて画像を保存するコードや、各クラスタの個体数を計算するプログラムなどほぼほぼすべて手書きでロジックを考えて行った結果が、以下の通りです。 当時の研究のコピーで白黒しかないのであまりはっきりとは出ませんが、桂川、鴨川、宇治川などがまずまずはっきりと出ています。また御所や二条城のあたりも、それがあるというのがわかります。東山のところは山と街の境がはっきりと出ていて、土地の被覆状況が異なるのが分かります。 項目 値 計算回数 22 計算開始日 12月26日20:52:33 計算終了日 12月28日14:35:17 分裂したクラスタ数 8 見ての通り22回の反復計算をするのに40時間以上かかっています。 分裂したクラスタ数が8なので、合計クラスタ数は15となります。しかしながら、実際クラスタを分類すると特に小さいクラスタはどのように土地が使われているかはっきり分らず、未分類となってしまったクラスタが4つ出てきました。また、同じ用途で使われていても、違うクラスタとして判別されたものもいくつか出てきました。現在のようにGoogle Mapなどがあればもう少し正確な分類ができたかもしれませんが、当時は地図と比べての分類だったので、これぐらいが限界でした。その内訳は以下の通りです。 クラスタ番号 個体数 分類 1 2017 水域 2 9040 森林 3 3045 未分類 4 27303 住宅地 5 14798 水田 6 14836 住宅地 7 1618 未分類 8 5404 未分類 9 7103 森林 10 7374 荒地 11 27081 市街地 12 4209 草地 13 16946 市街地 14 13406 水田・畑 15 9472 未分類 その結果、解析範囲の京都市近郊の土地被覆状況は以下の通りでした。 土地被覆分類 割合 住宅地 25.7% 市街地 24.7% 水田・畑 10.3% 森林 9.8% 水田 9.0% 荒地 4.5% 草地 2.5% 水域 1.2% 未分類 11.9% 令和3年 解析に使用したもの 最新ではありませんが当然平成6年のころに比べたら格段に良くなっています。 Windows10 64bit RAM 16G, 2 cores, 4プロセッサ Python3.8 衛星画像データのダウンロード 20数年前とちがい現在はインターネットの環境も整っていますし、利用規約範囲内であれば無料でダウンロードして使用することもできます。 【実習編】~Landsat8衛星画像で植生を映えさせた地図を描こう~  を参考に、https://landbrowser.airc.aist.go.jp/landbrowser/index.html より以下のデータをダウンロードして、今回の解析に使いました。 ランドサット8号: 2020年10月27日 分解能: 30m (30メートル以上のものを見分けることとができます。) 解析都市: 京都市近郊 解析範囲: 縦500、横300ピクセル(山崎ジャンクションから左京区北白川ぐらいの範囲を長方形で切り取ったぐらいです)平成6年と完全に一致はしていませんが、ほぼ同じ個所を同じようなサイズで切り取りました。 使用バンド: 1-7 k-meansとx-meansで解析 パラメーターの決定 使用するバンド数は前回同様7です。ライブラリーを使用するので、計算終了条件などは指定する必要がありません。 ただ、k-meansの場合はクラスタ数をあらかじめ決めている必要があり、x-meansの場合は最大クラスタ数を設定しないとクラスタ数が多くなりすぎ、最終的に土地の被覆状況が分類できなる恐れがあります。 まず、エルボー法でクラスタ数を求めてみました。 def k_get_elbow(X,cluster_num): distortions = [] for i in range(1,cluster_num+1): km = cluster.KMeans(n_clusters=i) km.fit(X) distortions.append(km.inertia_) #y_km = km.fit_predict(X) plt.plot(range(1,cluster_num+1),distortions,marker='o') plt.xlabel('Number of clusters') plt.ylabel('Distortion') plt.show() 結構なだらかな曲線で表示され、はっきりとどのあたりが一番良いかというのは、今回使用したデータではわかりませんでした。 また、x-meansで最大クラスタ数を例えば30ぐらに指定すると、30個までクラスタ数が分けられるような結果になります。クラスター分析という意味ではおそらく良い結果なのでしょうが、今回の目的である土地被覆状況を分類し以前の結果と比較する目的ではかなり使いにくい結果となりました。コードは後程紹介しますが、最大クラスタ数を30にしてx-meansで解析した結果はこのような感じです。 ほぼ、地図みたいになって、詳細すぎてどのクラスタが何を表しているかを判別するのはほぼ不可能です。 したがって、今回は前回の解析結果と比較するのが目的なので、解析の精度という点ではあまり深く追求せずk-meansのクラスタ数、x-meansの最大クラスタ数ともに10として解析を進めました。 項目 値 使用バンド BAND1-7 k-means クラスタ数 10 x-means 最大クラスタ数 10 データの読み取り 以前と違ってメモリーも十分あるのでデータを一気に読み取ります。データは各バンドごとに読み取り7次元のndarrayを返します。 def k_get_raw_data(city,dataset,r,x,y,h,w): mimage = np.zeros((h, w, r), dtype=int) for i in range(r): b = i + 1 with rasterio.open(city + '\\' + dataset + str(b) + '.tiff') as src: data = src.read() img = data[0][y:y+h, x:x+w] mimage[:, :, i] = img new_shape = (mimage.shape[0] * mimage.shape[1], mimage.shape[2]) X = mimage[:, :, :r].reshape(new_shape) return X k-meansで解析 前述のVisual Basicの場合はデータの読み取り、再計算、分裂など多くの行数を費やしてロジックを作りました。紹介したものもほんの一部で、もっと長いコードを使っています。(自分のプログラミング技術も相当乏しかったのですが) k-meansを使って解析すると解析部分は数行で済ませることができます。先ほど読み込んだデータと、初期設定のクラスタ数をcluster.KMeans(n_clusters=cluster_num)に渡すだけです。 from sklearn import cluster def k_cal_show_image(X,cluster_num): k_means = cluster.KMeans(n_clusters=cluster_num) k_means.fit(X) 解析結果は最終的には先ほどのように画像に表示したいので、それぞれの地点のクラスタ番号を読み取って新たに画像サイズにあったndarrayを作ります。 X_cluster = k_means.labels_ X_cluster_new = X_cluster.reshape([h,w]) x-meansで解析 k-meansと同様に解析部分は数行です。 from pyclustering.cluster.xmeans import xmeans, kmeans_plusplus_initializer def x_cal_plot(X,cluster_num): # 初期クラスタ数を2からスタート initial_centers = kmeans_plusplus_initializer(X, 2).initialize() analysis = xmeans(X, initial_centers, kmax=cluster_num, tolerance=0.025, criterion=0, ccore=True) analysis.process() こちらも解析気結果を画像で表示するために、各クラスタにクラスタ番号を割り当てそれぞれの地点に書き込みします。 例えばあるクラスタにクラスタ番号7を割り当てて、それに属する個体の位置を表示すると以下のようになります。 print('Cluster ' + str(7) + ' first 10: ' + str(clusters[7][:10])) Cluster 7 first 10: [593, 605, 769, 1128, 3284, 3285, 3556, 3581, 3582, 3769] これは593番目、605番目・・・はクラスタ7に所属するということです。 クラスタ番号の割り当てを行って、先ほど同様に画像サイズにあったndarrayを作ります。 clusters = analysis.get_clusters() X_cluster_new = np.zeros(X.shape[0], dtype=int) for i in range(len(clusters)): for j in range(len(clusters[i])): X_cluster_new[clusters[i][j]] = i X_cluster_new = X_cluster_new.reshape([h,w]) 画像保存 k-means、x-meansともに各クラスタに任意の色を割り当てて解析結果を画像として保存します。 import matplotlib.patches as mpatches im = plt.imshow(X_cluster_new,cmap='jet') colors = [ im.cmap(im.norm(value)) for value in cluster_size[:,0]] patches = [ mpatches.Patch(color=colors[i], label="Cluster {l}".format(l=cluster_size[:,0][i]) ) for i in range(len(cluster_size[:,0])) ] plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0. ) 各クラスタの割合を表す円グラフも表示します。 plt.title('% of clusters') plt.pie(x=cluster_size[:,1], labels=cluster_size[:,0], autopct='%.2f%%',colors=colors) 解析結果 k-meansとx-meansを使った解析結果を比較します。 k-means x-means 色合いは異なりますが、出された結果はかなり似通っていました。平成6年の結果と違いカラーで表示されているのでさらにわかりやすくなっています。桂川、鴨川、宇治川はもちろん、京都競馬場、二条城、御所などもはっきりと出ています。また、名神高速道路や、平成6年にはなかった第二京阪道路なども見て取れます。二条城や御所には多くの木々があるのですが、それが東山の森林などと同じクラスタに分類されているのもわかります。また、街中の碁盤の目の道路も大通りの分は読み取ることができます。 (地名などを入れた画像は後の解析画像の比較をご覧ください) 変動係数(Coefficient of variation) 変動係数(Coefficient of variation)を比較してみましたが両方とも下のグラフのようにほぼ同じ感じで出ました。 これはx-meansでの変動係数ですが、k-meansの結果もほとんど同じでした。同じクラスタでも、Band5で係数が大きくなっているのが分かります。これは、次のクラスタごとのヒストグラムでも見て取ることができます。 ヒストグラム ヒストグラムを作ってばらつきの少なかったクラスターと大きかったものを比べてみました。 まずは、先ほどの変動係数のグラフで係数が小さかったクラスタ3と4です。全体的にきれいな山型になっていますが、変動係数のグラフ通りBand5でばらつきが大きくなっています。 それに比べてばらつきが最も大きかったクラスタ6と7です。クラスタ7はばらつきが大きすぎるので変動係数のグラフからは省いています。全体的に偏っていたり、山型がきれいに出ていませんし飛び地もあったりします。 ばらつきが目立ったのがBand5で、これは植生を判別するのにつかわれます。下のクラスターの分類を見ての通り、多くのクラスターで植生が入っているのでこれが原因かと考えています。また、クラスタ6と7は小さいクラスタで、どうしてもばらつきが出やすかったと考えられます。 結果 項目 値 計算回数 55(k-meansの場合) 計算時間 k-means、x-meansともに1分 画像や変数係数を比較してもk-meansとx-meansでほぼ差がなかったので、土地被覆分類は先ほど変数係数を用いたx-meansの結果を元に分類しました。 クラスタ番号 土地被覆分類 割合 3 住宅地 28.60% 4 市街地 28.47% 2 水田 8.96% 1 芝、野原 8.79% 8 森林 8.34% 0 畑 5.91% 9 芝・広葉樹 5.34% 5 水/水辺/芝 4.61% 6 工場・ビル 0.91% 7 工場・ビル 0.06% 平成6年と令和3年の解析結果の比較 平成6年にWindows3.1とVisual Basic2.0でほぼ自前でロジックを組んで解析した結果と、令和3年にWindows10とPython3.8でライブラリーを活用して解析した結果を比較します。比較・考察するのは解析のIT技術的なところと、実際の土地被覆状況などの2点です。 IT技術的な比較 一番の違いはやはり計算時間の差です。平成6年の解析では40時間以上かかったのが令和3年の解析は1分で終了しています。 平成6年の解析と令和3年のk-meansの解析は同じユーグリッドの距離を使っているのですが、平成6年は22回の計算を40時間以上かけているのに対して、令和3年は55回の計算を1分で終わっています。 当然ですが、使っている環境が大きく違いメモリーは512Mと16G、プロセッサも数だけでも1と4の違いもありますし、性能もはるかに良くなっています。データのI/Oではいちいち読み書きしていたのが、すべてメモリー内で処理され最終結果のみ書き出しているのでI/Oに費やされる時間も大きな差が出ているはずです。 プログラミングでは、Visual Basic2.0でロジックをすべて手書きしているのでかなりの行数を使います。ソースコードをすべて保管していないのですが何ページ分もあったのを覚えています。一方、Pythonだとライブラリーを使うので、全体的にはるかに行数も少なくすんでいます。 画像処理以外のデータの処理(変動係数、ヒストグラム、クラスタ数など)も、使ったライブラリーをもとに変数を取り出すだけなど簡単な処理で、新たにロジックなどを作ることもなくできました。 ライブラリーで簡単にできてしまうので、統計学的な知識がなくてもある程度のプログラミングができれば誰でも簡単にできるのではと思いました。AIや機械学習を軽く勉強するには、かなり取り掛かりやす物だと思います。また、平成6年の解析ではクラスタの精度を高めるために標準偏差を用いて分裂などの作業をしていましたが、令和3年の解析ではライブラリーですべて行ってくれてヒストグラムでも見て取れる通りある程度の精度のクラスタを手間をかけることなく作れました。 解析用のデータを取得するのも、平成6年はデータをテープ状態で購入してそこから読み取るといた作業をしていましたが、今回はサイトよりダウンロードしてすぐに使用することができました。一見普通のことのようですが、二十数年前では考えられなかったことです。 土地被覆状況の比較 解析画像の比較 考察 目立った違いは平成6年の解析では未分類が約12%も出たことです。プログラムの精度、ランドサットの違いもありますが、大きな原因の一つは判別方法だと思っています。当時はGoogle Mapなどないですし現地に向かう時間もなかったので、地図などをもとに分類を判別しました。紙の地図とGoogle Mapの衛星写真をつかって判別するのでは大きく精度も異なると思っています。未分類になったのは、地図と比較してはっきり何に使われているかわからなかったところで、市街地や住宅地のようにはっきりとわかるところではなく、水田と畑、芝地と荒地、森林と草地など分かりにくかったところが大半だっと記憶しています。 下の二つの解析画像を比べるとわかるのですが、1992年の画像は2020年の画像に比べて若干反時計回りに傾いています。今回はそれを是正するような詳細な解析はしておりませんが、これにより2020年の解析範囲のほうが東山近辺の森林地区が多く含まれ、向日市・長岡京市の市街地範囲が少なく含まれています。 平成6年と比べて令和3年解析では市街地・住宅地が約7%ほど増加しています。使用したランドサットは共に30メートルの分解能なので約9.4㎢ほど市街地・住宅地が1992年から2020年の28年ほどの間で増えたということでしょうか。それとは逆に水田や畑は5%ほど減少しています。前述の通り、平成6年にはなかった第二京阪道路なども見て取れます。これは市街地として分類されたクラスタ4に含まれており、傾きによって含まれる市街地部が少ないにもかかわらず市街地が増加したのは、このような都市開発が市街地・住宅地の増加に関連していると考えられます。 平成6年の解析では森林は1つのクラスタで分類していますが、今回は森林部分と広葉樹や芝を含んだ部分の2つのクラスタで分類できています。クラスタ9が広葉樹、芝部分なのですが桂川・宇治川河岸の樹木のあるところがはっきりと出ています。ただ、近辺にある芝部分との区別はできていないようです。 令和3年の解析では、前回なかった工場・ビルと分類されたクラスタが出てきました。Google Mapと見比べてもはっきりとそれとわかるクラスタ6及び7が、山崎ジャンクションおよび太秦の近辺に見られます。京都駅もクラスタ6で分類できています。 平成6年の解析では御所は二条城があるというのはわかる程度でしたが、令和3年の解析ではそれぞれの樹木の部分とそうでないところがはっきりと分類できています。 両方とも宇治川の南の巨椋近辺の水田、畑はかなり明確に表示されています。ある程度大きな範囲で被覆状況が同じであれば、27年前のプログラムでもそれなりに分析を行えていたのが分かりました。当時の自分の技術力を考えると、悪くないのではと思っています。 京都競馬場拡大図 クラスタではありませんが、令和3年の解析では京都競馬場もはっきりと見分けることができます。外の芝のコースは芝・広葉樹と分類されたクラスタ9、ダートのコースは水田と分類されたクラスタ2となっています。10月末のデータなので実際の水田は水も抜けて土状になっていると思われ、それとダートコースが同じクラスタと分類されたのではと考えられます。また、京都競馬場の中心部は池なのですが、水/水辺/芝と分類されたクラスタ5と分類されています。 その他の解析 拡大範囲で解析 次に、比較をするために解析範囲を少し拡大して分析をしてみました。使用しているのは全く同じランドサットデータと解析プログラムです。 クラスタ番号 土地被覆分類 割合 6 市街地 25.62% 7 住宅地 17.77% 1 森林・西向き 12.67% 0 森林・東向き 10.07% 2 広葉樹 9.70% 5 水田 9.37% 8 畑 6.18% 3 水/水辺 4.10% 4 芝、野原 3.89% 9 工場・ビル 0.66% 全体の数が増えたせいか、機械学習がより正確にクラスタを分類できるように判別できたと思います。森林の中での違いやと広葉樹林の違いなどを判別できたり、河岸も河川と岸をはっきり区別できています。また、市街地でも高速道路などがよりはっきりと表れました。 京都は盆地と言われますが、この画像からもはっきり市街地住宅分が東西および北側の山に囲まれた盆地であることが良くわかります。 温度分布 京都は盆地で夏は暑く冬は底冷えと言われますが、実際どんな感じか、同じ市内で差があるのか、それぞれの時期のランドサットデータで比べてみました。熱赤外はバンド 10、11で使用され、それを使って解析すると地表面温度を見ることができます。それを使って2017年の12月と翌年7月の比較をしてみました。 赤色が濃いのは温度が高く、薄くなるほど温度が低いことを表しています。 緑で囲まれた御所の西の部分7月は特に色が濃くなっています。同じ場所を12月の左側の画像と比較すると、他の地域より薄くなっています。それと比較して黄色で囲まれた右京区の嵐山から西院近郊は夏は、御所西側より色が薄く、冬には濃くなっています。 このことからすると、御所西側は他の地区と比較して夏は暑く冬は寒いと考えられます。逆に嵐山から西院近郊は、同じ京都市内でも比較的夏は涼しく冬は暖かく住みやすいのではとも考えられます。2日分のデータなので断言できるようなものではないですが、機械学習をつかうとこのようなことも調べることができるとわかりました。 まとめ 27年間でITの世界は全く違うものになりました。この業界がいかに早く進化しているか、改めて考えられるきっかけになりました。 27年前の技術でもロジックさえしっかりしていれば、ある程度の解析ができていたことが判明しました。当然今の技術を使った方が、精度もよく時間も短くできますが、あの当時のレベルでもそれなりの解析ができていたのは良かったと思っています。 20年以上前のプログラムは今はほぼ役に立つことはないですが、クラスター分析をするための統計学的な基礎は一般的に機械学習と言われるITの技術の中でも生かせることが分かりました。 今回のような簡単な解析でも、リモートセンシングを使って土地の被覆状況の変化を見て取ることができました。市街地・住宅地が広がっていることや、水田・畑が減少していることなど正確でないにしてもより精度を高くすれば環境問題などにも利用できるものと思われます。 温度分布を解析することによって、その地域で比較的住みやすい気候の所とそうでないところを見つけることができそうです。 参考文献および注釈 他にも多くのサイトを参考にさせていただきましたが、主たるものは以下の通りです。 画像解析ハンドブック 単行本 – 1991/1/1 高木幹雄 (著), 下田陽久 (著) リモートセンシング Netscape Navigator フロッピーディスク ランドサット5号 ランドサット8号 【実習編】~Landsat8衛星画像で植生を映えさせた地図を描こう~ ランドサットデータについて (https://landbrowser.airc.aist.go.jp/landbrowser/index.html) “ The source data were downloaded from AIST’s LandBrowser, (https://landbrowser.airc.aist.go.jp/landbrowser/). Landsat 7/8 data courtesy of the U.S. Geological Survey.”
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パケットキャプチャファイルからTCPセッション一覧を作成するPython

概要 wiresharkもtsharkも使わないでscapyでpcapngファイル中のTCPセッション情報を表にまとめます。数百MBあるような巨大なパケットキャプチャはかなり時間がかかると思うので想定していないです。あくまで簡単なファイルのみでお使いください。UDPに書き換えることも難しくないです。 pcapng形式のファイル を scapy で精査して、 pandas.DataFrame 型にします。 openpyxlを入れていれば、エクセルファイルに出力も簡単です。 パケットキャプチャ wiresharkのページにサンプルのパケットキャプチャがいくつかありますので、そこから選びました。 今回の対象は 200722_win_scale_examples_anon.pcapng です。 コード from scapy import all as sc import pandas as pd import datetime from collections import OrderedDict f = '200722_win_scale_examples_anon.pcapng' i,s_,t_,b_ = 0,[],{},{} for p in sc.PcapNgReader(f): if sc.TCP in p.layers(): s = [(p[sc.IP].src,p[sc.TCP].sport),(p[sc.IP].dst,p[sc.TCP].dport)] t = datetime.datetime.fromtimestamp(float(p.time)) b = p.wirelen if s not in s_: s_ += [s] t_[i] = [t] b_[i] = b i += 1 else: t_[s_.index(s)] += [t] b_[s_.index(s)] += b streams = [OrderedDict(zip(['hosts','start','end','seconds','packets','bytes','bps'], [s,min(t_[i]),max(t_[i]),(max(t_[i])-min(t_[i]))/datetime.timedelta(seconds=1),len(t_[i]),b_[i],int(8*b_[i]/((max(t_[i])-min(t_[i]))/datetime.timedelta(seconds=1)))])) for i,s in enumerate(s_)] pd.DataFrame(streams) hosts start end seconds packets bytes bps 0 [(192.168.200.135, 6711), (192.168.200.21, 2000)] 2020-07-2305:22:25.360596 2020-07-23 05:22:38.626700 13.266104 5 288 173 1 [(192.168.200.21, 2000), (192.168.200.135, 6711)] 2020-07-2305:22:25.363991 2020-07-23 05:22:38.629675 13.265684 4 246 148 2 [(192.168.200.135, 6712), (192.168.200.21, 2000)] 2020-07-2305:23:03.937420 2020-07-23 05:23:18.498647 14.561227 5 288 158 3 [(192.168.200.21, 2000), (192.168.200.135, 6712)] 2020-07-2305:23:03.942168 2020-07-23 05:23:18.501610 14.559442 4 242 132 4 [(192.168.200.21, 2000), (192.168.200.135, 6713)] 2020-07-2305:27:07.859997 2020-07-23 05:27:21.989981 14.129984 4 246 139 5 [(192.168.200.135, 6713), (192.168.200.21, 2000)] 2020-07-2305:27:07.860088 2020-07-23 05:27:21.986733 14.126645 4 222 125 wiresharkの機能との比較 同等の機能はwiresharkのStatistics → Conversationsにあります。 行数が違いますが、これは上りと下りのストリームをあわせて1行にまとめているためです。用途次第ですが、等価な情報が得られていることが分かります。 wiresharkのConversationsに似せてみる DataFrame型のスライスは修行が足らないので、list型のデータを加工します。※DataFrameは後日頑張ります。 import numpy as np [sorted(e) for e in np.unique([set(s) for s in [e['hosts'] for e in streams]])] [[('192.168.200.135', 6711), ('192.168.200.21', 2000)], [('192.168.200.135', 6712), ('192.168.200.21', 2000)], [('192.168.200.135', 6713), ('192.168.200.21', 2000)]] sessions = [OrderedDict(zip(['Address A','Port A','Address B','Port B'],[A,a,B,b])) for (A,a),(B,b) in [sorted(e) for e in np.unique([set(s) for s in [e['hosts'] for e in streams]])]] pd.DataFrame(sessions) Address A Port A Address B Port B 0 192.168.200.135 6711 192.168.200.21 2000 1 192.168.200.135 6712 192.168.200.21 2000 2 192.168.200.135 6713 192.168.200.21 2000 Packets_AtoB = [sum([stream['packets'] for stream in streams if stream['hosts'] == [(session['Address A'],session['Port A']),(session['Address B'],session['Port B'])] ]) for session in sessions] Packets_AtoB [5, 5, 4] Packets_BtoA = [sum([stream['packets'] for stream in streams if stream['hosts'] == [(session['Address B'],session['Port B']),(session['Address A'],session['Port A'])] ]) for session in sessions] Packets_BtoA [4, 4, 4] Packets = [a+b for a,b in zip(Packets_AtoB,Packets_BtoA)] Packets [9, 9, 8] Bytes_AtoB = [sum([stream['bytes'] for stream in streams if stream['hosts'] == [(session['Address A'],session['Port A']),(session['Address B'],session['Port B'])] ]) for session in sessions] Bytes_AtoB [288, 288, 222] Bytes_BtoA = [sum([stream['bytes'] for stream in streams if stream['hosts'] == [(session['Address B'],session['Port B']),(session['Address A'],session['Port A'])] ]) for session in sessions] Bytes_BtoA [246, 242, 246] Bytes = [a+b for a,b in zip(Bytes_AtoB,Bytes_BtoA)] Bytes [534, 530, 468] Start = [min([stream['start'] for stream in streams if set(stream['hosts']) == {(session['Address A'],session['Port A']),(session['Address B'],session['Port B'])} ]) for session in sessions] Start [datetime.datetime(2020, 7, 23, 5, 22, 25, 360596), datetime.datetime(2020, 7, 23, 5, 23, 3, 937420), datetime.datetime(2020, 7, 23, 5, 27, 7, 859997)] End = [max([stream['end'] for stream in streams if set(stream['hosts']) == {(session['Address A'],session['Port A']),(session['Address B'],session['Port B'])} ]) for session in sessions] End [datetime.datetime(2020, 7, 23, 5, 22, 38, 629675), datetime.datetime(2020, 7, 23, 5, 23, 18, 501610), datetime.datetime(2020, 7, 23, 5, 27, 21, 989981)] Duration = [(e-s)/datetime.timedelta(seconds=1) for s,e in zip(Start,End)] Duration [13.269079, 14.56419, 14.129984] BPS_AtoB = [int(8*b/d) for b,d in zip(Bytes_AtoB,Duration)] BPS_AtoB [173, 158, 125] BPS_BtoA = [int(8*b/d) for b,d in zip(Bytes_BtoA,Duration)] BPS_BtoA [148, 132, 139] conversations = pd.DataFrame(sessions) conversations['Packets']=Packets conversations['Bytes']=Bytes conversations['Packets A → B']=Packets_AtoB conversations['Bytes A → B']=Bytes_AtoB conversations['Packets B → A']=Packets_BtoA conversations['Bytes B → A']=Bytes_BtoA conversations['Abs Start']=Start conversations['Duration']=Duration conversations['Bit/s A → B']=BPS_AtoB conversations['Bit/s B → A']=BPS_BtoA conversations Address A Port A Address B Port B Packets Bytes Packets A → B Bytes A → B Packets B → A Bytes B → A Abs Start Duration Bit/s A → B Bit/s B → A 0 192.168.200.135 6711 192.168.200.21 2000 9 534 5 288 4 246 2020-07-23 05:22:25.360596 13.269079 173 148 1 192.168.200.135 6712 192.168.200.21 2000 9 530 5 288 4 242 2020-07-23 05:23:03.937420 14.564190 158 132 2 192.168.200.135 6713 192.168.200.21 2000 8 468 4 222 4 246 2020-07-23 05:27:07.859997 14.129984 125 139 ブサイクなプログラムですが目的は果たしています。 エクセル形式にしてみる conversations.to_excel(f+'.xlsx',index=False) import subprocess subprocess.run(['start',f+'.xlsx'],shell=True) 貧乏なので画面はOpenOffice Calcです。 wiresharkを起動することなくscapyをつかって、wiresharkのconversationsと同等の表を作成できました。お試しを!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(Python超初心者が)ツイッターを感情分析して為替(ドル円)を予測した結果

はじめに  私はプログラム知識はほぼありませんが、最近なにかとAIを聞くので興味がありました。そこでAidemyプレミアムプランというものを知り、さらに今なら教育訓練給付制度が利用出来る(最大70%OFF)ということなので、プログラム超初心者ですが思いきって受講させて頂きました。 今回は成果物として為替を予測してみましたので紹介させて頂きます。 本記事の概要 ・Twitterからツイートを取得 ・VADERによる感情分析 ・Yahooから為替データを取得 ・感情分析結果と為替データからロジスティック回帰、SVN、ランダムフォレスト、KNNのモデルで予測 作成したもの 下記が今回作成したコードになります。 import tweepy import csv consumer_key = '' # Consumer Key consumer_secret = '' # Consumer Secret access_key = '' # Access Token access_secret = '' # Accesss Token Secert auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_key, access_secret) api = tweepy.API(auth) #ツイート取得 tweet_data = [] tweets = tweepy.Cursor(api.user_timeline,screen_name = "ツイッターユーザー名",exclude_replies = True,tweet_mode = 'extended') for tweet in tweets.items(): tweet_data.append([tweet.id,tweet.created_at,tweet.full_text.replace('\n',''),tweet.favorite_count,tweet.retweet_count]) # tweets.csvという名前で保存 with open('./tweets.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f, lineterminator='\n') writer.writerow(["id", "text", "created_at", "fav", "RT"]) writer.writerows(tweet_data) Twitter APIの利用登録を行い、Consumer key, Consumer Secret, Access Token, Accesss Token Secertを取得したら、ツイートを取得し(今回は英文)、tweets.csvという名前で保存します。 import pandas as pd import nltk nltk.download('vader_lexicon') from nltk.sentiment.vader import SentimentIntensityAnalyzer #VADERによる感情分析 vader_analyzer = SentimentIntensityAnalyzer() df_tweets = pd.read_csv('tweets.csv', names=['id', 'date', 'text', 'fav', 'RT'], index_col='date') df_tweets = df_tweets.drop('text', axis=0) df_tweets.index = pd.to_datetime(df_tweets.index) df_tweets = df_tweets[['text']].sort_index(ascending=True) scores = [] for tweet in df_tweets['text']: result = vader_analyzer.polarity_scores(tweet) scores.append(result['compound']) df_tweets['score'] = scores df_tweets = df_tweets.resample('D').mean() 次にVADERによる感情分析を行います。分析結果のcompoundをscoreとしています。 import pandas_datareader.data as pdr #Yahoo Financeから為替レート(ドル円)を取得 df = pdr.get_data_yahoo('JPY=X',end='2021-04-07',start='2020-09-01') df = df.drop(['High','Low','Open','Volume','Adj Close'],axis=1) df = df.sort_index(ascending=True) df.to_csv("./time_data.csv") Yahoo Financeから為替データを取得し、time_data.csvという名で保存 table = df_tweets.join(df, how='right').dropna() table.to_csv("./table.csv") dfとdf_tweetsの二つのテーブルを結合し、Nan(欠損値)を消去し、table.csvとして出力 from sklearn.model_selection import train_test_split X = table.values[:, 0] y = table.values[:, 1] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, shuffle=False) X_train_std = (X_train - X_train.mean()) / X_train.std() X_test_std = (X_test - X_train.mean()) / X_train.std() df_train = pd.DataFrame( {'score': X_train_std, 'close': y_train}, columns=['score', 'close'], index=table.index[:len(X_train_std)]) df_train.to_csv('./df_train.csv') df_test = pd.DataFrame( {'score': X_test_std, 'close': y_test}, columns=['score', 'close'], index=table.index[len(X_train_std):]) df_test.to_csv('./df_test.csv') df_train、 df_testというテーブルを作りそこにindexを日付、カラム名をscore、closeにしてdf_train.csv, df_test.csvという名前で出力。 import numpy as np def make_dataset(filepath): rates_fd = open(str(filepath), 'r',encoding='utf-8') rates_fd.readline() #1行ごとにファイル終端まで全て読み込み next(rates_fd) # 先頭の行を飛ばす。 exchange_dates = [] score_rates = [] score_rates_diff = [] exchange_rates = [] exchange_rates_diff = [] prev_score = df_train['score'][0] prev_exch = df_train['close'][0] for line in rates_fd: splited = line.split(",") time = splited[0] # table.csvの1列目日付 score_val = float(splited[1]) # table.csvの2列目score exch_val = float(splited[2]) # table.csvの3列目為替のclose exchange_dates.append(time) # 日付 score_rates.append(score_val) score_rates_diff.append(score_val - prev_score) # scoreの変化 exchange_rates.append(exch_val) exchange_rates_diff.append(exch_val - prev_exch) # 為替の変化 prev_score = score_val prev_exch = exch_val rates_fd.close() INPUT_LEN = 3 data_len = len(score_rates_diff) test_input_mat = [] test_angle_mat = [] for i in range(INPUT_LEN, data_len): test_arr = [] for j in range(INPUT_LEN): test_arr.append(exchange_rates_diff[i - INPUT_LEN + j]) test_arr.append(score_rates_diff[i - INPUT_LEN + j]) test_input_mat.append(test_arr) # i日目の直近3日間の為替とscoreの変化 if exchange_rates_diff[i] >= 0: # i日目の為替の上下、プラスなら1、マイナスなら0 test_angle_mat.append(1) else: test_angle_mat.append(0) feature_arr = np.array(test_input_mat) label_arr = np.array(test_angle_mat) return feature_arr, label_arr データセットを作成するための関数を作成 train_feature_arr, train_label_arr = make_dataset("./df_train.csv") test_feature_arr, test_label_arr = make_dataset("./df_test.csv") 作成した関数から、訓練データとテストデータを作成 from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.neighbors import KNeighborsClassifier # train_feature_arr, train_label_arr,test_feature_arr, test_label_arrを特徴量にして、予測モデル(ロジスティック回帰、SVM、ランダムフォレスト、KNN)を構築し予測精度を計測 for model in [LogisticRegression(), RandomForestClassifier(n_estimators=200, max_depth=8, random_state=0), SVC(), KNeighborsClassifier(n_neighbors=5, p=2, metric='minkowski')]: model.fit(train_feature_arr, train_label_arr) print("--Method:", model.__class__.__name__, "--") print("Cross validatin scores:{}".format(model.score(test_feature_arr, test_label_arr))) ロジスティック回帰、SVN、ランダムフォレスト、KNNのモデルで予測 予測結果はすべて6割越えというという結果となりました。 おわりに  為替を予測するというのはかなり困難な事だと思いますが、今回は練習の題材として為替の予測をしてみました。結果は正解率6割と正直、良すぎると思いますが、もしかしたら後出しジャンケン(為替が動いた後のツイートで分析している)になっているので、そうなったのかもしれません。そこは見直したいと思います。 読んでいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自然言語処理用学習データを動画内から収集したかった...

目的 動画内からセリフのみを抽出して文字起こしをする。 実行環境 Windows10 Home Python3.7 使用したライブラリなど youtube-dl speech_recognition spleeter 2.1.2 FFmpeg 実行する順番 諸々のインストール 目的の動画取得 動画内の音声抽出 文字起こし 1. 諸々のインストール ⚠️ Pythonの環境が一通り整っている事を想定しております。ごめんなさい。 今回はpipを使用して色々インストールさせて頂きます。 youtube-dl のインストール youtubeに限らずダウンロードする時に便利なのです! pip install youtube_dl speech_recognition のインストール 音声認識をする時などにお世話になっております。 pip install SpeechRecognition ↓ Pyaudioも欲しい ↓ pip install pyaudio インストール確認のために以下のコマンドを実行!!! python -m speech_recognition 何か言って! と言われるので 英語 で話してみると... 認識結果が表示されるようであれば完了です!!! spleeter のインストール ボーカルやらドラムやらベースやら音声を分離できます!!! pip install spleeter インストールできたか確認します! spleeter --help 以下のような文やhelpが出たら完了です!!! Usage: spleeter [OPTIONS] COMMAND [ARGS]... FFmpeg のインストール 音声や動画を扱うときによく見かけます FFmpegのサイトをスクロールして release のLinksから自分に合ったものを選んでDLしてください! D や C の直下にreleaseフォルダを作ると後々楽です。 ※ システム環境変数にPathを通してほしいです!!! 2. 動画の取得 youtube-dlで動画を取得してみましょう! youtube-dl (DLする動画のURL) -x -f "bestaudio" --audio-format wav --audio-quality 0 これを実行するとwav形式で高音質downloadをしてくれます。 オプションの説明 -x or --extract-audio → 音声のみダウンロード -f or --format ”piyopiyo” → ファイル形式を指定できますくわしくはこちらへ --audio-format → downloadフォーマットを指定してます --audio-quality → 音質を指定できます(0~9 小さいほど高音質) 他にも色々あるみたいです。 3. 動画内の音声抽出 spleeterを使って動画内の音声を抽出します! 抽出というよりは分割なんですけどね。 spleeter separate (分割したいファイルへのPath) -o (出力先のPath) -p spleeter:2stems これを実行すると音声がvocalとaccompanimentに分割されます! 初回実行のみ、自動でdownloadするものがあるので少し時間がかかります。 オプションの説明 -o (出力先のPath) → 出力先のフォルダを指定してください -p spleeter: n stems → ( n = 2 , 4 , 5 ) で分割されます 2stems → vocal / accompaniment 4stems → vocal / bass / drum / other 5stems → vocal / bass / piano / drum / other ※ 分割元動画が長すぎると途中で処理が落ちる場合があります!!! 4. 文字起こし speech_recognitionを使って実際に文字起こしをしてみます Python import speech_recognition as sr AUDIO_FILE = " " #ここに抽出した音声ファイルのPathを r = sr.Recognizer() with sr.AudioFile(AUDIO_FILE) as source: audio = r.record(source) print('音声データの文字起こし結果:\n\n', r.recognize_google(audio, language='ja')) すると... 結果 まとめ 一応目的としていた、動画からセリフのみを抽出して文字起こしをすることはできた。 しかし、文字起こし結果は扱えるようなものではなかった... 結局人力で書き起こすことにした。 人力最強!人力最強! 何か良い方法ご存じでしたら是非教えてほしいです。m(_ _)m 最後に 今回初めてQiitaに記事を投稿しました。 至らない点ばかりではありますが、目に留めていただきありがとうございました。 参考 youtube-dl https://github.com/ytdl-org/youtube-dl/blob/master/README.md#readme speech_recognition https://pypi.org/project/SpeechRecognition/ spleeter https://github.com/deezer/spleeter FFmpeg https://rikoubou.hatenablog.com/entry/2019/11/07/144533
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

M1 Macのpython環境構築について

はじめに 最近M1チップのMacの環境構築でよくエラーが出たりする問題をよく見かけます。 この記事では、M1チップのMac上で、pythonのよく使うライブラリであるmatplotlibのインストール方法について備忘録として残します。 テスト済み環境は以下です。 MacOS Big Sur 11.2.3 M1 MacBook Pro 13 python 3.8 pip 20.0.2 環境構築 pythonのライブラリ(ここでmatplotlib)をインストールする一般的な方法は pip install matplotlib あるいは python3 -m pip install matplotlib 僕は最近この方法ではうまく行かなくて。。。 どうやらmatplotlibをインストールするときに、Pillowというライブラリも一緒にインストールされるので、そこでエラーを吐いちゃったみたいです。 僕のところで解決できた方法を以下に示す。 $ brew install libjpeg $ git clone https://github.com/python-pillow/Pillow.git $ cd Pillow $ pip3 install . --no-binary :all: --no-use-pep517 Pillowをインストールするために、先に必要なlibjepgというライブラリをbrewでインストールします。 Pillowもlibjepgも描画用のライブラリらしいです。 それから、 $ git clone https://github.com/matplotlib/matplotlib.git $ cd matplotlib $ pip3 install . --no-binary :all: でやってみてください。 ダメだった場合は、pip3をpython3 -m pipにかえてからやってみてください。 今回は以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む