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

numpy.histogramの使い方

NumpyのHistgramの使い方メモ 任意のbinを指定 bins引数で任意のbinを指定することができる. bins引数で与える数値はbinのエッジを表している。bins=[0,1,2,3]の例では、 0以上1未満 [0,1) 1以上2未満 [1,2) 2以上3未満 [2,3) の3のつのbinが用意される。 下記の例では[0,1)に1つ, [1,2)に2つ, [2,3)に1つ入る. >>> np.histogram([0,1,1,2],bins=[0,1,2,3]) (array([1, 2, 1]), array([0, 1, 2, 3])) binsの範囲外の値はカウントされない. >>> np.histogram([0,1,1,2,4],bins=[0,1,2,3]) (array([1, 2, 1]), array([0, 1, 2, 3])) binの範囲外に落ちた値もカウントするなら、下記のようにする >>> np.histogram([-1,0,1,1,2,4],bins=[-sys.float_info.max,0,1,2,3,sys.float_info.max]) (array([1, 1, 2, 1, 1]), array([-1.79769313e+308, 0.0, 1.0, 2.0, 3.0, 1.79769313e+308])) binは単調増加であれば、等間隔である必要もない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyTorchを使ってCNNで文章分類を実装してみた

はじめに そういえば自然言語を畳み込んだことなかったなぁと思い、いつ必要になるかわからんので、どんなもんなのか雰囲気を確かめるために実装のお勉強をしてみました。 自分みたいに普段は自然言語処理ばかりしていて、CNNに不慣れな人向けに実装重視で解説してみます。 検証するタスクは、livedoorニュースコーパスの本文をカテゴリに分類する問題とします。 実装環境はGoogle Colabを使っています。 以下の文献を参考にしました! https://arxiv.org/pdf/1510.03820.pdf CNNで文章分類をしている論文で、今回の実装もこの論文で紹介されているアーキテクチャーを参考にしています。 https://tkengo.github.io/blog/2016/03/11/understanding-convolutional-neural-networks-for-nlp/ 私みたいにCNNで自然言語処理をしたことがない人はまず一度読んでほしい大変参考になる記事です。 実装 データなどを準備 事前にlivedoorニュースコーパスを以下のようにカテゴリと本文で分けたデータを用意しておきます。 # Google Driveをcolabにマウント from google.colab import drive drive.mount('/content/drive') import pandas as pd import numpy as np import pickle # colab上のデータ格納先を指定 drive_dir = "drive/My Drive/Colab Notebooks/livedoor_data/" with open(drive_dir + "livedoor_data.pickle", 'rb') as r: livedoor_df = pickle.load(r) # こんな感じで、categoryと本文(body)に分けたlivedoorニュースデータを事前に用意しておきます。 livedoor_df.sample(n=5).head() # body category # 「Ultrabook」の専門コンテンツ「Ultrabooker.jp」(ウルトラブッカージェ... it-life-hack # GALAPAGOSが大幅値下げ!UQコミュニケーションズは15日、同社のオンラインショップに... smax # アジア最大の映画の祭典「第24回東京国際映画祭(TIFF)」の開催日程が迫る中、学生応援団の... movie-enter # 「家電芸人」「雛壇芸人」など、毎回様々なテーマで芸人たちがトークを繰り広げる「アメトーーク!... topic-news # リニューアルしたPeachy iPhoneアプリの、便利な機能をご紹介します!メニューボタン... peachy 形態素解析の準備 形態素解析はpipで簡単にインストールできるmecabのラッパーであるfugashiを使います。 下記のように辞書も一緒にpipでインストールできます。 !pip install fugashi !pip install unidic-lite 形態素解析をする関数は以下のように特に前処理などは施さないシンプルなものにしました。 fugashiは以下のように、mecabと同様の使い方ができます。 import fugashi tagger = fugashi.Tagger("-Owakati") def make_wakati(text): text = tagger.parse(text) wakati = text.split(" ") wakati = list(filter(("").__ne__, wakati)) return wakati # 形態素解析テスト text = livedoor_df.sample(n=1)['body'].item() print(make_wakati(text)[:30]) # ['小腹', 'の', '空い', 'た', 'とき', 'に', 'つまみ', 'たく', 'なる', '人気', 'の', 'スナック', '菓子', 'と', 'いえ', 'ば', '「', 'ポテト', 'チップス', '」', '。', 'ついつい', '食べ', 'て', 'しまう', 'けれど', '、', '一', '袋', '空け'] torchtextでDataLoaderを作成 livedoorニュースコーパスを学習データ、検証データ、テストデータの3つに分割しています。 CNNの実装を確かめることがメインなので、単語ベクトルも今回はとりあえず学習データからvocabularyを生成して、ランダムなベクトルを扱うことにします。また、文章の最大長を指定するmax_lengthなどは特に指定していません。 from sklearn.model_selection import train_test_split from torchtext.legacy import data # カテゴリーをidに変換します。 categories = livedoor_df['category'].unique().tolist() livedoor_df['category_id'] = livedoor_df['category'].map(lambda x: categories.index(x)) # 元データを学習、検証、テストの3つに分割します。 train_val_df, test_df = train_test_split(livedoor_df[['body', 'category_id']], train_size=0.8) train_df, val_df = train_test_split(train_val_df, train_size=0.75) print('train size', train_df.shape) print('validation size', val_df.shape) print('test size', test_df.shape) # train size (4425, 2) # validation size (1475, 2) # test size (1476, 2) # torchtext用にtsvファイルで保存します。 train_df.to_csv(drive_dir + 'train.tsv', sep='\t', index=False, header=None) val_df.to_csv(drive_dir + 'val.tsv', sep='\t', index=False, header=None) test_df.to_csv(drive_dir + 'test.tsv', sep='\t', index=False, header=None) TEXT = data.Field(sequential=True, tokenize=make_wakati, lower=False, batch_first=True, pad_token='<pad>') LABEL = data.Field(sequential=False, use_vocab=False) train_data, val_data, test_data = data.TabularDataset.splits( path=drive_dir, train='train.tsv', validation='val.tsv', test='test.tsv', format='tsv', fields=[('Text', TEXT), ('Label', LABEL)]) # vocabulary生成 # 学習データだけでvocabを作成します。 TEXT.build_vocab(train_data, min_freq=1) BATCH_SIZE = 64 train_loader = data.Iterator(train_data, batch_size=BATCH_SIZE, train=True) val_loader = data.Iterator(val_data, batch_size=BATCH_SIZE, train=False, sort=False) test_loader = data.Iterator(test_data, batch_size=BATCH_SIZE, train=False, sort=False) CNNによるモデルの定義 自然言語処理におけるCNNの実装解説 自然言語処理では文章を行列(単語ベクトルの集まり)として扱うことが多いですが、その行列を(チャネル1の)画像とみなせば、自然言語に対してCNNを適用することができます。 適用するフィルター(カーネル)は画像処理であれば、以下のアニメーションのように画像の区間を右に下にスライドして畳み込み演算を行いますが (http://deeplearning.stanford.edu/tutorial/supervised/FeatureExtractionUsingConvolution/ から引用) 自然言語では、以下のように単語ベクトル方向(行方向)全体に対して、隣接する単語ベクトルをひとまとまりにして畳み込むようです。(以下のgifは自作しました。) 上のアニメーションは5$\times$5の文章の行列に対して、3$\times$5のフィルターをストライド1で畳み込んだ例になります。行方向全体に対して畳み込んでいるので、特徴マップ(Convolved Feature)はベクトルとして出力されます。 CNNで文章分類といっても、参考文献2.のように手法は色々あるようですが、今回参考にしたネットワークは参考文献1.の論文に記載されている以下のものです。 (説明しやすくするために、論文の図に番号と青い網掛けを施しています。) (↓論文に記載されている上図の説明文のDeepLによる翻訳) 文章分類のためのCNNアーキテクチャの説明図。3つのフィルタ領域サイズを描いている。 2、3、4の3種類のフィルター領域があり、それぞれに2つのフィルターがある。フィルターは文の行列に畳み込みをかけ、(可変長の)特徴マップを生成する。 各マップに対して1-maxプーリングを行い、各特徴マップから最大の数を記録する。 つまり、各特徴マップからの最大数が記録されます。このようにして、6つのマップすべてから一変量の特徴ベクトルが生成され、これらの 6つの特徴が連結され、最後の層の特徴ベクトルが形成されます。最後のソフトマックス層は は、この特徴ベクトルを入力として受け取り、それを使って文を分類します。ここでは2値分類を想定しているため、2つの出力状態が考えられます。 図や上の説明文でネットワークについては十分説明されていると思うのですが、理解を深めるために上の図の赤色の2つのフィルターを畳み込んでいる部分について、①〜④まで処理をPyTorchで実装しながら動きを確認してみます。 まずは文章の行列を用意します。要素がランダムなミニバッチサイズ2の7$\times$5の行列を用意しています。 自然言語処理で考えると、長さが7, 単語のベクトル次元数が5の文章を2つ用意した、ということになります。 import torch import torch.nn as nn mat = torch.rand(2, 7, 5) print(mat.size()) #torch.Size([2, 7, 5]) print(mat) #tensor([[[0.7829, 0.7500, 0.0219, 0.9649, 0.7882], # [0.3702, 0.4209, 0.4233, 0.9416, 0.2782], # [0.0063, 0.6495, 0.6619, 0.7269, 0.4565], # [0.3230, 0.5307, 0.4363, 0.1298, 0.6383], # [0.4901, 0.4339, 0.0146, 0.0567, 0.2657], # [0.0016, 0.6974, 0.0411, 0.5870, 0.9980], # [0.5924, 0.9639, 0.8020, 0.1496, 0.8250]], # # [[0.3794, 0.4447, 0.9487, 0.2464, 0.9708], # [0.9758, 0.2641, 0.1398, 0.2571, 0.4791], # [0.6313, 0.0070, 0.8250, 0.6199, 0.3993], # [0.6212, 0.0736, 0.5846, 0.0366, 0.3479], # [0.2804, 0.3105, 0.4070, 0.8294, 0.7184], # [0.2304, 0.5620, 0.6029, 0.1019, 0.1179], # [0.2298, 0.4102, 0.8991, 0.7467, 0.3501]]]) ②でこの文章を畳み込みます。畳み込みフィルターのサイズは図の通り、4$\times$5とし、ストライドは1とします。このフィルターを2枚畳み込みたいので、アウトプットのチャネルは2を指定すればOK。 自然言語処理でnn.LSTMなどを扱うとき、インプットの形式は(batch_first=Trueを指定した場合)ミニバッチサイズ×文章の長さ×単語ベクトル次元数のテンソルを扱いますが、nn.Conv2dのインプットの形式はミニバッチサイズ×チャネル数×高さ×幅である必要があります。なので、下記のようにmat.unsqueeze(1)をしてミニバッチサイズの次にチャネル1の次元を追加しています。 # 第1引数はインプットのチャネル(今回は1)を指定 # 自然言語処理で畳み込む場合、異なる単語分散表現(word2vecとfasttextみたいな)などを使って、 # 複数チャネルとみなす方法もあるようです。 # 第2引数はアウトプットのチャネル数で、今回は同じフィルターを2枚畳み込みたいので、2を指定 # カーネルサイズは高さ×幅を指定しており、幅は図で説明した通り、単語ベクトルの次元数5を指定 conv = nn.Conv2d(1, 2, kernel_size=(4, 5)) # チャネル数1を挿入 mat = mat.unsqueeze(1) print(mat.size()) # torch.Size([2, 1, 7, 5]) # ↑ミニバッチサイズ×チャネル数×文章の長さ×単語ベクトル次元数 # 畳み込む feature = conv(mat) print(feature.size()) # torch.Size([2, 2, 4, 1]) # ↑ミニバッチサイズ×特徴マップの数×(特徴マップの形式4×1) print(feature) #tensor([[[[ 0.0485], # [ 0.2434], # [-0.3744], # [-0.0895]], # # [[-0.2727], # [ 0.1079], # [-0.0427], # [ 0.1704]]], # # # [[[ 0.0854], # [ 0.0208], # [ 0.0618], # [ 0.1806]], # # [[ 0.0054], # [-0.2124], # [-0.0473], # [-0.0413]]]], grad_fn=<MkldnnConvolutionBackward>) 上で得られた2つの特徴マップを活性化関数に通して③が出来上がります。 import torch.nn.functional as F feature = F.relu(feature) print(feature.size()) # ↑ミニバッチサイズ×特徴マップの数×(特徴マップの形式4×1) print(feature) # reluに通したので負の要素は0になりますね。 # torch.Size([2, 2, 4, 1]) # tensor([[[[0.0485], # [0.2434], # [0.0000], # [0.0000]], # # [[0.0000], # [0.1079], # [0.0000], # [0.1704]]], # # # [[[0.0854], # [0.0208], # [0.0618], # [0.1806]], # # [[0.0054], # [0.0000], # [0.0000], # [0.0000]]]], grad_fn=<ReluBackward0>) 最後に④を得るために、1-max poolingを行います。ここですることは要は各特徴マップの最大要素を抽出することになります。つまりnn.MaxPool2dを使って、 # nn.MaxPool1dでも良いですが、そのときは上のfeatureに対して # feauture.unsqueeze(-1)をして最後の次元の1を除去しましょう。 pool = nn.MaxPool2d(kernel_size=(4, 1)) print(pool(feature)) # tensor([[[[0.2434]], # # [[0.1704]]], # # # [[[0.1806]], # # [[0.0054]]]], grad_fn=<MaxPool2DWithIndicesBackward>) としたくなりますが、上のpoolingのカーネルサイズ(上の4のところ)はインプットとなる特徴マップのサイズに依存するんですよね。図にもあるように特徴マップのサイズは元の文章の長さ(最初の行列の行方向)と畳み込みフィルターのサイズに依存するので、上のようにnn.MaxPool2dでpoolingするレイヤーのインスタンスを宣言しちゃうと、poolingのカーネルサイズを可変にできないので、F.max_pool2dを使って以下のようにpoolingする際、インプットとなる特徴マップのサイズを指定するようにします。 # feature.size()[2]で特徴マップの高さを取得しています。 feature = F.max_pool2d(feature, kernel_size=(feature.size()[2], 1)) print(feature.size()) # torch.Size([2, 2, 1, 1]) print(feature) # tensor([[[[0.2434]], # # [[0.1704]]], # # # [[[0.1806]], # # [[0.0054]]]], grad_fn=<MaxPool2DWithIndicesBackward>) # viewを使って次元数を整頓します。 feature = feature.view(-1, 2) print(feature.size()) # torch.Size([2, 2]) print(feature) # tensor([[0.2434, 0.1704], # [0.1806, 0.0054]], grad_fn=<ViewBackward>) これで④を得ることができました。あとは同様のことを異なる畳み込みフィルターにも適用して、最後に要素を結合して全結合層にぶち込めばOKですね。 ネットワークの定義 上の処理をまとめて、残り⑤、⑥の処理を加えると以下のようなクラスとしてネットワークを実装できるかと思います。 (論文ではdropoutとかも入れてますが、今回は入れていません。) class Net(nn.Module): def __init__(self, vocab_size, embedding_dim): super(Net, self).__init__() # 単語分散表現はランダムベクトルを使う self.embeddings = nn.Embedding(vocab_size, embedding_dim, padding_idx=TEXT.vocab.stoi['<pad>']) # 図の黄色い畳み込みフィルター self.conv1 = nn.Conv2d(1, 2, kernel_size=(2, embedding_dim)) # 図の緑色の畳み込みフィルター self.conv2 = nn.Conv2d(1, 2, kernel_size=(3, embedding_dim)) # 図の赤色の畳み込みフィルター self.conv3 = nn.Conv2d(1, 2, kernel_size=(4, embedding_dim)) # 3つ畳み込みの処理でそれぞれ2次元のベクトルが生成されるので、それらを全て結合して6次元のベクトルとなります。 # livedoorのカテゴリは9つなので、アウトプットサイズは9を指定 self.linear = nn.Linear(6, 9) def forward(self, input_ids): # ①文章の行列を取得 out = self.embeddings(input_ids) # チャネル数1を挿入 out = out.unsqueeze(1) # ②畳み込んでreluに通す out1 = F.relu(self.conv1(out)) out2 = F.relu(self.conv2(out)) out3 = F.relu(self.conv3(out)) # ③poolingして、各特徴マップの最大要素を取得 out1 = F.max_pool2d(out1, kernel_size=(out1.size()[2], 1)) out2 = F.max_pool2d(out2, kernel_size=(out2.size()[2], 1)) out3 = F.max_pool2d(out3, kernel_size=(out3.size()[2], 1)) # ④viewして次元を整えてあげる out1 = out1.view(-1, 2) out2 = out2.view(-1, 2) out3 = out3.view(-1, 2) # ⑤全部結合して1本のベクトルにする out = torch.cat([out1, out2, out3], dim=1) # ⑥全結合層で9つのカテゴリー分類できるように変換 out = self.linear(out) return out 学習 あとはこのネットワークでちゃんと学習できるか確かめて精度を確認して終わりです。 学習部分は以下のように実装しました。 学習データ、検証データともに順調に損失は減っていきますが、検証データの損失が最後らへんで増えてしまいます。エポック数30はちょっと多かったかもです。 import torch.optim as optim VOCAB_SIZE = len(TEXT.vocab.stoi) EMBEDDING_DIM = 200 net = Net(VOCAB_SIZE, EMBEDDING_DIM) loss_function = nn.CrossEntropyLoss() optimizer = optim.Adam(net.parameters(), lr=0.001) # GPUの設定 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") print(device) # ネットワークをGPUへ送る net.to(device) train_loss = [] val_loss = [] train_accuracy = [] val_accuracy = [] for epoch in range(30): # 学習 _train_loss = 0.0 _train_acc = 0.0 net.train() for batch in train_loader: inputs = batch.Text.to(device) y = batch.Label.to(device) optimizer.zero_grad() out = net(inputs) loss = loss_function(out, y) _, preds = torch.max(out, 1) loss.backward() optimizer.step() _train_loss += loss.item() _train_acc += torch.sum(preds == y).item() train_loss.append(_train_loss) train_epoch_acc = _train_acc / len(train_loader.dataset) train_accuracy.append(train_epoch_acc) # 検証 _val_loss = 0.0 _val_acc = 0.0 net.eval() with torch.no_grad(): for batch in val_loader: inputs = batch.Text.to(device) y = batch.Label.to(device) out = net(inputs) loss = loss_function(out, y) _, preds = torch.max(out, 1) _val_loss += loss.item() _val_acc += torch.sum(preds == y).item() val_loss.append(_val_loss) val_epoch_acc = _val_acc / len(val_loader.dataset) val_accuracy.append(val_epoch_acc) print("epoch", epoch, "\ttrain loss", round(_train_loss, 4), "\ttrain accuracy", round(train_epoch_acc, 4), "\tval loss", round(_val_loss, 4), "\tval accuracy", round(val_epoch_acc, 4)) #cuda:0 #epoch 0 train loss 157.78 train accuracy 0.1205 val loss 47.3475 val accuracy 0.1858 #epoch 1 train loss 118.2377 train accuracy 0.3776 val loss 32.1798 val accuracy 0.5973 #epoch 2 train loss 85.6465 train accuracy 0.6454 val loss 26.0927 val accuracy 0.6739 #〜省略〜 #epoch 26 train loss 6.5183 train accuracy 0.9853 val loss 14.9468 val accuracy 0.8203 #epoch 27 train loss 5.8604 train accuracy 0.9869 val loss 15.0671 val accuracy 0.8231 #epoch 28 train loss 5.3537 train accuracy 0.988 val loss 15.1578 val accuracy 0.8183 #epoch 29 train loss 5.0279 train accuracy 0.9896 val loss 15.6059 val accuracy 0.819 精度確認 最後にテストデータによる精度(Fスコア)を確認しましょう。 (30epoch学習した後に対する)全体のFスコアが0.77となりました。 BERT使えばFスコア0.9を超えるので、livedoorニュースコーパスの本文のカテゴリー分類問題としてはあまり良い値とは言えないですね。色々とチューニングが必要ですが、まぁ実装の確認という意味では良しとしましょう。 # 精度確認 from sklearn.metrics import classification_report with torch.no_grad(): test_loss = 0.0 net.eval() prediction = [] answer = [] for batch in test_loader: input_ids = batch.Text.to(device) y = batch.Label.to(device) out = net(input_ids) _, preds = torch.max(out, 1) prediction += list(preds.cpu().numpy()) answer += list(y.cpu().numpy()) print(classification_report(prediction, answer, target_names=categories)) # precision recall f1-score support # # movie-enter 0.86 0.86 0.86 166 # it-life-hack 0.84 0.74 0.79 198 # kaden-channel 0.78 0.85 0.81 155 # topic-news 0.78 0.91 0.84 148 #livedoor-homme 0.58 0.44 0.50 141 # peachy 0.55 0.64 0.59 151 # sports-watch 0.79 0.76 0.77 174 #dokujo-tsushin 0.70 0.73 0.72 173 # smax 0.99 0.96 0.97 170 # # accuracy 0.77 1476 # macro avg 0.76 0.76 0.76 1476 # weighted avg 0.77 0.77 0.77 1476 おわりに 自然言語処理に対するCNNの適用例ということでCNNによる文章分類の実装を確認しました。 正直今はAttentionだのBERTだの強力なアルゴリズムがあるので、CNNで文章分類する機会はあまりないかもしれませんが、急に文章を畳み込みたい衝動に駆られても対処できそうです。 おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder ZONeエナジー プログラミングコンテスト "HELLO SPACE" 参戦記

AtCoder ZONeエナジー プログラミングコンテスト "HELLO SPACE" 参戦記 ZONE2021A - UFO襲来 3分半で突破. 書くだけ. S = input() result = 0 i = 0 while True: i = S.find('ZONe', i) + 1 if i == 0: break result += 1 print(result) ZONE2021B - 友好の印 9分半で突破. にぶたんで書いたけど、今考えるとB問題でにぶたん要らんよな. ミスった. N, D, H = map(int, input().split()) dh = [tuple(map(int, input().split())) for _ in range(N)] def is_ok(n): for d, h in dh: if (H - n) / D * d + n >= h: continue return False else: return True ng = 0 ok = 10 ** 3 for _ in range(10000): m = (ok + ng) / 2 if is_ok(m): ok = m else: ng = m print(ok) ZONE2021D - 宇宙人からのメッセージ 7分で突破. 過去問に同じようなのが合ったような. 反転を真面目に実行すると TLE 一直線なので、フラグ管理して、くっつける向きを変えるだけ. 連続消去も最後にやるとめんどくさいだけなので、足しこみながらやればワンパス. from collections import deque S = input() T = deque([]) is_reversed = False for c in S: if c == 'R': is_reversed = not is_reversed continue if is_reversed: if len(T) != 0 and T[0] == c: T.popleft() else: T.appendleft(c) else: if len(T) != 0 and T[-1] == c: T.pop() else: T.append(c) if is_reversed: T = reversed(T) print(''.join(T)) ZONE2021E - 潜入 57分で突破、WA5、TLE3. 大体はダイクストラ法でいいのだが、素直にやると (r−i,c) の移動が重くて TLE になってしまう. (r+1, c) と (r−i,c) の移動の直後に (r−i,c) の移動をするのは無駄なので、フラグ管理でその時だけはしないようにしてやったら AC した. from heapq import heappop, heappush R, C = map(int, input().split()) A = [list(map(int, input().split())) for _ in range(R)] B = [list(map(int, input().split())) for _ in range(R - 1)] dp = [[10 ** 15] * C for _ in range(R)] dp[0][0] = 0 q = [(0, 0, 0, True)] while q: cost, r, c, no_warp = heappop(q) if dp[r][c] < cost: continue # (r, c + 1) if c < C - 1: ncost = dp[r][c] + A[r][c] if dp[r][c + 1] > ncost: dp[r][c + 1] = ncost heappush(q, (ncost, r, c + 1, False)) # (r, c - 1) if c > 0: ncost = dp[r][c] + A[r][c - 1] if dp[r][c - 1] > ncost: dp[r][c - 1] = ncost heappush(q, (ncost, r, c - 1, False)) # (r + 1, c) if r < R - 1: ncost = dp[r][c] + B[r][c] if dp[r + 1][c] > ncost: dp[r + 1][c] = ncost heappush(q, (ncost, r + 1, c, True)) if no_warp: continue # (r - i, c) for i in range(1, r + 1): ncost = dp[r][c] + (1 + i) if dp[r - i][c] > ncost: dp[r - i][c] = ncost heappush(q, (ncost, r - i, c, True)) print(dp[R - 1][C - 1])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでPython-Seleniumスクレイピング環境を立てた

はじめに メイン機以外で定期実行されるクローラを作りたいと思ったわけですが、その場合ローカル環境に全く依存しない形が取れれば最高だということで、Docker環境の構築に乗り出しました。 Dockerを全く知らない状態のプログラミング初心者でしたが、同じ境遇の方には役立つだろうと思います。 あまりに詳しく書けるほど余白は広くないので、あくまで備忘録的な範囲で留めておこうと思います。 ※作業は主にWindowsで行います。 全体図 かなり簡素な図ですが、こんな感じです。 Python実行環境とSeleniumHQ/docker-seleniumは別のコンテナとして立てて、docker-composeで関係性を持たせます。 そしてSeleniumHQ/docker-seleniumにより、簡単にVNC接続してブラウザの挙動をチェックできます。 ちなみに、SeleniumHQ/docker-seleniumがあれば、ChromeやChromeDriverのセットアップは完全に不要になります。 スクレイピング用のコードで、driverを定義する箇所を書き換えるだけです。 環境構築 Docker 最近はWindows10 Homeでも簡単にインストールできるようになったようです。 Dockerのインストーラの指示に従っていれば使える状態になります。 VSCode(任意) Remote-Containersプラグインによって、ローカル開発のような感覚でデバッグも楽々です。 docker-compose.ymlやDockerfileの他に、devcontainer.jsonなるVSCode用の設定ファイルを用意します。 devcontainer.jsonに盛り盛りの設定をすることも可能なようですが、あくまで素の状態で実行できなければあまり意味がないので、まずコマンドラインで動作確認をしてからVSCode用の設定を整えるのがいいと思われます。 VNC Docker環境は完全に閉じた環境なので、ホストマシンのブラウザを見ながらのデバッグができません。 そこで、Docker環境内にあるLinux上のデスクトップ画面を表示するために、VNCという技術を使います。 RealVNCで動作確認済みです。 ディレクトリ構成 全体 .devcontainer\ devcontainer.json app\ .vscode\ launch.json settings.json source\ test.py Dockerfile requirements.txt build.sh docker-compose.yml .devcontainer\*と.vscode\*はVSCode用の設定ファイルです。 dockcker-compose.ymlでapp\DockerfileによるイメージとSeleniumHQ/docker-seleniumを関連付けます。 build.sh build.sh #!/bin/sh cd `dirname $0` docker-compose build --no-cache docker-compose up -d docker-compose exec app bash dockerコマンド入力分の効率化目的です。 3行目(cd ...)があると、実行場所に制限がなくなって便利です。 docker-compose.yml docker-compose.yml version: "3" services: app: build: ./app volumes: - ./app:/app environment: SELENIUM_URL: http://selenium:4444/wd/hub tty: true selenium: image: selenium/standalone-firefox-debug ports: - 4444:4444 - 5900:5900 volumes: - /dev/shm:/dev/shm test.py内で、Selenium_URLを用いてdriverを定義しています。 tty: trueでコンテナの起動状態を維持します。(後でexecするため) SeleniumHQ/docker-seleniumでは、Firefoxを利用しています。 2021/05/01現在のDocker for Windowsのバグによって、Chromeが動作しないようです。(該当issue) どうしてもChromeを利用したければ、volumesを/dev/shm:/dev/shmでなく、shm_size: "2gb"などとすればいいようです。 Dockerfile Dockerfile FROM python:3.7-slim-buster ENV PYTHONIOENCODING utf-8 ENV TZ="Asia/Tokyo" ENV LANG=C.UTF-8 ENV LANGUAGE=en_US:en WORKDIR /app COPY ./requirements.txt ./requirements.txt RUN pip install -r requirements.txt Python環境のベースイメージは軽量版を利用しています。 wgetやcurlなどで色々入れたい場合は通常版を利用するのが吉でしょう。今回はシンプルな構成なので軽量版で十分でした。 requirements.txt requirements.txt autopep8 selenium test.py test.py from datetime import datetime as dt import os from time import sleep from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities driver = webdriver.Remote( command_executor=os.environ["SELENIUM_URL"], desired_capabilities=DesiredCapabilities.FIREFOX.copy() ) driver.get("https://www.nict.go.jp/JST/JST5.html") driver.implicitly_wait(3) sleep(1) driver.save_screenshot("{0:%Y_%m_%d__%H_%M_%S}".format(dt.now()) + ".png") driver.quit() 日本標準時を表示するサイトのスクリーンショットを保存するプログラムです。 重要な部分は、driver = webdriver.Remote(...あたりです。 docker-compose.ymlで定義した環境変数を利用しています。 FIREFOX.copy()も環境変数で表してもいいと思われます。 ちなみに、selenium/standalone-firefox-debugを単体で起動しても同様に使えるため、通常のローカルにおけるスクレイピングでもこれでよさそうです。(とはいえ今回Docker環境を用意したので、ローカルでの開発は不要になりましたが。) devcontainer.json devcontainer.json { "dockerComposeFile": "../docker-compose.yml", "service": "app", "workspaceFolder": "/app", "settings": { "terminal.integrated.shell.linux": "/bin/bash" }, "extensions": [ "streetsidesoftware.code-spell-checker", "ms-python.python", "ms-python.vscode-pylance", "njpwerner.autodocstring" ] } docker-compose.ymlをチラチラ見ながら書きます。 拡張機能は、好きなものを入れるといいでしょう。 launch.json launch.json { "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "args": [ "&&", "exit" ] } ] } これにより、Docker環境にRemote-Containersで接続した状態でデバッガを利用できます。 特筆事項はありませんが、普段から個人的な趣味として"args": ["&&", "exit"]をつけています。Python Debug Consoleだったかが無限増殖していくので、何となく気分が悪いという理由です。デバッグ終了後に当該コンソールを自動で終了します。 settings.json settings.json { "python.pythonPath": "/usr/local/bin/python", "python.languageServer": "Pylance" } 詳細なファイルの内容 GitHubで公開しています。 利用手順 上記リポジトリをクローン 自分が再利用しやすいように作ってあるので、それなりに便利ではあるだろうと思います。 git clone https://github.com/ryoheiszk/python-selenium-on-docker とりあえずビルド ./build.sh 基本的にLinuxで利用するためにシェルスクリプトとして用意していますが、WindowsユーザでGitBashなどを利用していない場合、次のコマンドを順番に実行しても同じことです。 docker-compose build --no-cache docker-compose up -d docker-compose exec app bash VNCテスト 何もしていない状態のブラウザが既に待機しているので、WindowsならRealVNC等で接続テストをします。 Linuxの場合は、TigerVNC Viewerなんかでいいのではないかと思います。 localhost:5900→secret 接続ができたら何かロゴが表示されている状態だろうと思います。 テストスクリプト実行 cd source/ pyhon test.py すると、VNC Viewerの方でブラウザが表示されると思います。 ルートディレクトリにスクリーンショットが保存されていたら成功です。 (要らないので消しておきましょう) VSCodeでコーディング あとは、Remote-Containersの機能を利用し、VSCodeでDocker環境に入りコーディングするだけです。 必要なパッケージなどあれば、requirements.txtに追記してリビルドしておきます。 さいごに この程度のことは結構な人がやっていそうでしたが、意外と正解の答えが見つからずに苦戦しました。 特に、Dockerに関して全く無知の状態からだったので大変でしたが、いい勉強になったと思います。 今後の開発環境はおおよそDockerで構築していければと思うようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのインストールやバージョンアップのまとめ

Pythonのバージョン確認やアップデート方法をメモ。 pythonバージョン確認 python --version brewバージョン確認 pyenvをインストールするのにHomebrewを使うのでインストール状況の確認をしておく。 brew -v pyenvのインストール brew install pyenv pyenvのバージョン確認 pyenv -v pyenv の設定(.bash_profile の設定変更) echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile echo 'eval "$(pyenv init -)"' >> ~/.bash_profile pyenvの設定確認 cat ~/.bash_profile インストール可能な Python のバージョンを確認 pyenv install --list ver.x.x.x をインストール pyenv install x.x.x pyenv versions global(* マーク)が system のままなので global を 3.9.4 に変更 pyenv global 3.9.4 pyenv versions 変更されていることを確認してから反映 source ~/.bash_profile 最後にバージョンを確認 python -V
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今日のPython (2021/5/1)

0. はじめに  毎日1問、Pythonの問題を出題します。出題範囲は特に定めていませんがはじめの1ヶ月くらいは『入門Python3 第2版』の第1~11章までのことが分かれば解ける問題にしたいと思います。 1. 問題 半径5の円の面積を求めなさい。ただし円周率は3.14とします。 2. 解答 r = 5 s = r * r * 3.14 print(s) # ->78.5 3. 解説  円の面積の求め方覚えていましたか?(小6で習っているらしいです) 冗談は置いておいてきちんと78.5と出力されましたか。今回伝えたかったことは「義務教育って案外難しい」ということではなく、変数をきちんと用いようということ、そして分かりやすい変数を使おうということです。 print(5 * 5 * 3.14)  もちろん上記のようなコードでも間違えではありません。しかし実は半径が10だったり今度は半径が別の円の面積を求めたくなるかもしれません。そんなとき、上記のようなコードだと修正する箇所が2箇所出てきてしまいます。なのでr = 5と変数を用いましょう。  また変数だからx, y, zを使うというのも間違えではありません。しかしxやyだと何を指しているか分かりません。それよりも半径を表す英単語radiusを省略したrを変数にした方が分かりやすくなります。 4. まとめ ・変数を用いよう。 ・分かりやすい変数名にしよう。 5. おまけトーク 初回だからprint("Hello World")ではなかったですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

東証上場の全銘柄の株価(日足、分足)を取得して分析してみた

はじめに 突然ですが、2021年4月から株式投資(デイトレード)を始めました。その中で「この銘柄は初めに(株価が)上がれば、陽線になることが多いなぁ」「この銘柄は一度(株価を)下げて利確をこなしてから(株価が)上がることが多いなぁ」と思うことがありました。もしも「一日の初めの5分足が陽線ならば、その日の日足も必ず陽線になる」銘柄があれば、何も考えずに9:05頃にエントリーするだけで利益出せそうですよね! ということで調べてみた Yahoo(米国)が株価取得のためのAPIを提供しているようでしたので、そちらを使い日足、分足(5分足)のデータを取得してみました。 APIの使い方 以下の通りです。簡単ですね!このURLにアクセスするとJSONが返されます。 RANGE = '60d' # 何日間のデータを取得するか DAY_INTERVAL = '1d' # これを'5m'にすれば5分足のデータを取得できる ticker = '2413.T' # 調べたい銘柄コード response_data = requests.get('https://query1.finance.yahoo.com/v7/finance/chart/' \ + ticker + '?range=' + RANGE + '&interval=' + DAY_INTERVAL + '&indicators=quote&includeTimestamps=true') 1銘柄のデータを取得するだけあればこれでいいのですが、複数銘柄のデータを取得しようとするとこの処理を繰り返すことになり、途中でサーバーからエラーのレスポンスが返ってきてしまいました。そこで次の銘柄のデータを取得するまで次のようにして5秒スリープすることにしエラー回避しました。 for row in df.itertuples(): analyzer(row) # データ取得&分析処理 time.sleep(5) 分析処理全体 GitHubに公開していますのでこちらをご確認下さい。 分析結果 2/3~4/30の60日間の東証上場の全銘柄(4,000社以上!)を対象に分析したところ、次の銘柄は初めの分足とその日の日足の型(陽線/陰線)の一致率が高いことがわかりました(一部抜粋)。反対に日足がその日の初めの分足と反対方向にいつも行くような天邪鬼な銘柄はなかったです。全結果はGitHubに公開しています。 銘柄コード 会社名 一致率 8628.T 松井証券 85.0% 4452.T 花王 80.0% 4088.T エア・ウォーター 78.3% 4502.T 武田薬品工業 78.3% 6902.T デンソー 78.3% 株のブログ始めました 株って美味しいんですか?を始めてみました。興味のある方はこちらも読んでみて下さい。 今回参考にしたサイト https://note.com/aiduudia/n/na9ea4f90e255
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで欠損値の処理をする

概要 読み込んだデータの欠損値を処理する方法 種類 明確な欠損値:「スペース」や「NaN」 曖昧な欠損値:「?」や「-」、本来のデータとは性質が違う値 1の「明確な欠損値」はpandasで自動認識ができるが、曖昧な欠損値は自動認識できない。 Pandasをインポート import pandas as pd 確認 DataFrame.isnull() 欠損値はTrue、欠損値以外はFalse DataFrame.isnull().sum() 上記のようにsum関数を加えることで、「どのカラム」に「どれだけ」の欠損値があるか確認可能 処理 削除する場合 欠損値が入っている場合は行自体が欠損値となるため、欠損値の行を削除 DataFrame.dropna() 補完する場合 補完する場合は数的データは平均値、質的データは最頻値で置き換えることが多い DataFrame.fillna(引数) 欠損値を引数で置き換え - Miyata Koki - O:inc.でAmplify×React×React Nativeを使用して開発しています。大学のゼミでは統計学をPythonで行っています。 インターンやゼミで学んだ情報を発信していくので、フォロバもしますのでぜひこちらのアカウントのフォローお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Learn Python In Seminars〜欠損値〜

概要 読み込んだデータの欠損値を処理する方法 種類 明確な欠損値:「スペース」や「NaN」 曖昧な欠損値:「?」や「-」、本来のデータとは性質が違う値 1の「明確な欠損値」はpandasで自動認識ができるが、曖昧な欠損値は自動認識できない。 Pandasをインポート import pandas as pd 確認 DataFrame.isnull() 欠損値はTrue、欠損値以外はFalse DataFrame.isnull().sum() 上記のようにsum関数を加えることで、「どのカラム」に「どれだけ」の欠損値があるか確認可能 処理 削除する場合 欠損値が入っている場合は行自体が欠損値となるため、欠損値の行を削除 DataFrame.dropna() 補完する場合 補完する場合は数的データは平均値、質的データは最頻値で置き換えることが多い DataFrame.fillna(引数) 欠損値を引数で置き換え - Miyata Koki - O:inc.でAmplify×React×React Nativeを使用して開発しています。大学のゼミでは統計学をPythonで行っています。 インターンやゼミで学んだ情報を発信していくので、フォロバもしますのでぜひこちらのアカウントのフォローお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

高次元統計学をちょっと勉強したメモ

これ の序盤を読んだのでまとめ。かんたんな数値実験もして遊んだ。 記法の整理 \begin{aligned} n &: サンプル数\\ d &: データベクトルの次元\\ X &:= (x_1, ..., x_n) \ \ (d\times n\ 行列)\\ X_{ij} &: j 番目のデータの i 次元目の要素\\ \bar X &: X の標本平均\\ X^T &: X の転置\\ \Sigma&: 分散共分散行列\\ S_n &:= \frac{1}{n-1}\left(X - \bar X\right)\left(X - \bar X\right)^T \ \ (d\times d\ 標本共分散行列)\\ S_{D, n} &:= \frac{1}{n-1}\left(X - \bar X\right)^T\left(X - \bar X\right) \ \ (n\times n\ 標本双対共分散行列)\\ \end{aligned} 気になること $d\gg 1$ のとき、統計量はどう振る舞うか。 例:データベクトルの全要素が独立で $N_d(0, I_d)$ に従う場合 ノルム |x| = \sqrt {\sum_{i=1}^d x_i^2}\sim \sqrt{d} データベクトル間の角度 Angle(x_1, x_2) = acos(x_1\cdot x_2 / (|x_1| |x_2|))\sim acos(0) = \frac{\pi}{2} データベクトル間の距離 |x_1 - x_2| \sim \sqrt {d + d} = \sqrt{2d} 分散共分散行列のトレース tr (I_d) = d 多くの量が次元に比例するため、無限次元の極限を取ると発散する。 一般の分散共分散行列で考えた場合、ノルムやデータベクトル間の距離は分散共分散行列のトレースの平方根に比例する。そのため、無限次元極限を考える際は分散共分散行列によってある程度振る舞いが記述されることが期待される。これを踏まえ、分散共分散行列に対して条件を2つ考える。 球形条件と一致性条件 球形条件 \frac{tr \Sigma^2}{\left(tr\Sigma\right)^2} \rightarrow 0 \ \ (d\rightarrow \infty) これは分散共分散行列の最大固有値を $\lambda_1$ と書いた場合、以下と同値。 \frac{\lambda_1}{tr \Sigma} \rightarrow 0 \ \ (d \rightarrow \infty) これは全分散 ($tr\Sigma$) に比べて最大固有値が非常に小さいこと、つまり全ての固有値の寄与が同程度効くことに対応する。例えば分散共分散行列が $I_d$ なら $tr I_d = d$ なのでこの条件を満たす。この条件は分散共分散行列のトレースが発散することを期待しているため、発散しないようなケース、例えば以下の場合なら満たさない。 \Sigma_{ij} = \begin{cases} i^{-2} & (i = j) \\ 0 & (i \neq j) \end{cases} 一致性条件 $\mu$ はデータベクトルの平均ベクトルとする。一致性条件は以下の通り。 \frac{Var\left(|x-\mu|^2\right)}{\left(tr\Sigma\right)^2} \rightarrow 0 \ \ (d\rightarrow \infty) (詳細の式を打つのがめんどくさいので、詳細は本を参照)2次の項同士の相関がない場合、この条件は球形条件の表式と等しくなる。 データベクトルの性質 $d\rightarrow\infty$ で3つの性質を示す。 $|x-\mu|\sim\sqrt{tr\Sigma}$ ラフな証明:Chebyshev の不等式より、以下の通り一致性条件より示せる。 P(|(x-\mu)^2 - tr\Sigma| \geq \tau\ tr\Sigma)\leq \frac{Var(|x-\mu|^2)}{(\tau\ tr\Sigma)^2}\sim 0 $|x_i - x_j| \sim \sqrt{2 tr\Sigma}$ ラフな証明:同様に Chebyshev の不等式を使用する。$i\neq j$ のとき、球形条件より、 P(|(x_i-\mu)^T(x_j-\mu)| \geq \tau\ tr\Sigma)\leq\frac{Var\left((x_i-\mu)^T(x_j-\mu)\right)}{(\tau\ tr\Sigma)^2} = \frac{tr(\Sigma^2)}{(\tau\ tr\Sigma)^2}\sim 0 これより以下の通り。 |x_i - x_j|^2 = |x_i-\mu|^2 + |x_j-\mu|^2 -2(x_i-\mu)^T(x_j-\mu)\sim 2d $Angle(x_i, x_j-\mu) \sim \frac{\pi}{2}\ \ (i\neq j)$ Angle(x_i, x_j-\mu) = acos\left(\frac{(x_i-\mu)^T(x_j-\mu)}{d}\right)\sim acos(0) = \frac{\pi}{2} 標本双対共分散行列の性質 球形条件を仮定した場合、標本双対共分散行列は以下を満たす。 $\frac{n}{tr\Sigma} S_{D, n} - D_n \sim 0$ ここで $D_n$ は非対角成分がゼロで対角成分がゼロでない行列で, 一致性条件が満たされる場合 $D_n=I_n$ となる。 これも Chebyshev の不等式で示せる。$i\neq j$ のとき P\left(|(x-\mu)_i (x-\mu)_j - \frac{\delta_{ij}}{n}tr \Sigma| \geq \frac{\tau}{n}tr \Sigma\right) \leq \frac{n^2}{(\tau\ tr\Sigma)^2} Var\left((x-\mu)_i (x-\mu)_j\right) \propto \frac{tr \Sigma^2}{(tr \Sigma)^2} = 0 \ \ (球形条件) $i = j$ のとき P\left(|(x-\mu)_i (x-\mu)_i - \frac{\delta_{ij}}{n}tr \Sigma| \geq \frac{\tau}{n}tr \Sigma\right) \leq \frac{n^2}{(\tau\ tr\Sigma)^2} Var\left((x-\mu)_i (x-\mu)_i\right) これは一致性条件の表式と同じのため、一致性条件が満たされる場合は右辺がゼロとなる。一方一致性条件が満たされない場合は右辺がゼロとならないため、$D_n$ が $I_n$ から乖離する。 数値実験 球形条件が満たされる場合、満たされない場合でノルム、標本双対共分散行列を計算して振る舞いを見てみる。 条件 データベクトルの次元 $d=1, ..., 10000$ データの観測数 $n=100$ データベクトルのノルム等 自身のノルム、データベクトル差のノルム、データベクトル間の角度を実験する。 使用したサンプル取得コード def compute(scales): samples1 = scipy.stats.norm.rvs(scale=scales) samples2 = scipy.stats.norm.rvs(scale=scales) diff = samples1 - samples2 return {\ 'dim': len(scales), \ 'norm': np.linalg.norm(samples1), \ 'diff': np.linalg.norm(diff), \ 'angl': np.arccos(np.inner(samples1, samples2) / (np.linalg.norm(samples1) * np.linalg.norm(samples2)))} 球形条件が満たされる場合 $i$ 番目のデータ $x_i \sim N(0, \Sigma)$ $\Sigma_{ij} = \delta_{ij}$ この場合、$tr \Sigma = d$ のため、$1/d\rightarrow 0$ となり球形条件が満たされる。 以下のコードを使用して、データベクトル次元が 1 から 10000 ごとに $N=100$ 回計算しプロット。 constant_scales = lambda d: np.full(d, 1) df = pd.concat([pd.DataFrame([compute(constant_scales(i+1)) for i in range(d)]) for n in range(N)]) tmp = df.groupby('dim').mean().reset_index() y = 'norm' tmp[y + '/ \\sqrt(d)'] = tmp[y] / np.sqrt(tmp['dim']) sns.lineplot(x='dim', y=y + '/ \\sqrt(d)', data=tmp, label=y + '/ \\sqrt(d)') y = 'diff' tmp[y + '/ \\sqrt(d)'] = tmp[y] / np.sqrt(tmp['dim']) sns.lineplot(x='dim', y=y + '/ \\sqrt(d)', data=tmp, label=y + '/ \\sqrt(d)') y = 'angl' sns.lineplot(x='dim', y=y, data=tmp, label=y) 右軸が次元で縦軸が値。データベクトル自身のノルムとデータベクトル間の差のノルムは $\sqrt{d}$ で割っている。自身のノルムは$\sqrt{d}$ 付近に集中し、差のノルムは $\sqrt{2d}$ に集中し、角度は $\pi/2$ に集中しており期待通りの振る舞いをしている。 球形条件が満たされない場合 $\Sigma_{ij} = i^{-2}\delta_{ij}$ この場合、$tr \Sigma = \Sigma_{n=1}^d n^{-2}\rightarrow\pi^2/6$ となり球形条件は満たさない。 以下のコードを使用し、球形条件が満たされる場合と同様の手法で計算しプロットした。 zeta_scales = lambda d: 1/np.linspace(1, d, d) zeta = pd.concat([pd.DataFrame([compute(zeta_scales(i+1)) for i in range(d)]) for n in range(N)]) tmp = zeta.groupby('dim').mean().reset_index() y = 'norm' sns.lineplot(x='dim', y=y, data=tmp, label=y) y = 'diff' sns.lineplot(x='dim', y=y, data=tmp, label=y) y = 'angl' sns.lineplot(x='dim', y=y, data=tmp, label=y) プロットした結果は以下の通り。自身のノルムも差のノルムも一定。これはデータベクトルの次元無限大極限でトレースが発散せず、最大固有値の影響が消えないことが起因。 標本双対共分散行列 $\frac{n}{tr\Sigma} S_{D, n} - D_n \sim 0$ を数値的に試してみる。 使用したサンプル取得コード def compute_converged_matrix_stat(scales): corr_matrix = np.diag(scales) samples = np.matrix([scipy.stats.norm.rvs(scale=scales) for n in range(N)]) samples_dual_matrix = samples * samples.T / N converged_matrix = samples_dual_matrix * N / np.trace(corr_matrix) dic = {} dic['Dim'] = len(scales) dic['FNorm'] = np.linalg.norm(converged_matrix) dic['Trace'] = np.trace(converged_matrix) return dic 球形条件が満たされる場合 データベクトル等で使用した条件と同じ。 計算結果のフロベニウスノルムは以下の通り。収束先が $I_n$ なので、フロベニウスノルムは $\sqrt{n} = 10$ に近づく。実際グラフでも次元が大きくなると 10 に近づいていることがわかる。 計算結果の Trace は以下の通り、収束先が $I_n$ なので、トレースは $n = 100$ に近づく。実際グラフでも次元が大きくなると 100 に近づいていることがわかる。 球形条件が満たされない場合 データベクトル等で使用した条件と同じで、分散が2乗で減衰していく場合を扱う。球形条件を満たしている場合と比べ振る舞いが違う(特にトレース)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動画像における超解像手法/VESPCNの実装

概要 深層学習を用いた、動画像における超解像手法であるVESPCNの実装したので、それのまとめの記事です。 Python + Tensorflowで実装を行いました。 今回紹介するコードはGithubにも載せています。 1. 超解像のおさらい 超解像について簡単に説明をします。 超解像とは解像度の低い画像に対して、解像度を向上させる技術のことです。 ここでいう解像度が低いとは、画素数が少なかったり、高周波成分(輪郭などの鮮鋭な部分を表す)がないような画像のことです。 以下の図で例を示します。(図は[論文]より引用) これは、超解像の説明をする時によく使われる画像です。 (a)は原画像、(b)は、画素数の少ない画像を見やすいように原画像と同じ大きさにした画像、(c)は、高周波成分を含まない画像の例です。 (b)と(c)は、荒かったりぼやけていたりしていると思います。 このような状態を解像度が低い画像といいます。 そして、超解像はこのような解像度が低い画像に処理を行い、(a)のような精細な画像を出力することを目的としています。 (今回紹介するVESPCNは、これを動画像に応用した超解像手法) 2. 論文の超解像アルゴリズム 超解像のアルゴリズムの概要図は以下の通りです。(図は論文から引用) この超解像手法は、3枚の連続するフレームを入力して、1枚の高解像度画像を出力します。 前半のMotion estimationが動き補償などのデータ前処理の部分、 後半のSpatio-temporal ESPCNが超解像を行う部分になっています。 それぞれの流れについてもう少し詳しく説明していきます。 ① Motion estimation 超解像アルゴリズムに入力する前のデータ前処理を行う箇所になります。 Motion estimation部分のアルゴリズムは以下の図の通りです。(図は論文から引用) このデータ前処理の部分では、2枚の連続したフレームを入力します。 ここでは、Coarse flow estimationとFine flow estimationという2つのニューラルネットワークを通してデータ前処理を行います。 $\Delta c$と$\Delta f$は移動量を表すパラメータみたいなもので、オプティカルフローのパラメータを求めるのと似ています。 今回は、このデータ前処理でもニューラルネットワークを使用しています。 過去の超解像手法では、オプティカルフローを事前に求めたり、Bicubic法などで補間処理を行っていたため、データ前処理にかなり時間がかかってました。 そのため、データ前処理にもニューラルネットワークを使用することで、計算速度を上げる狙いもあります。 ニューラルネットワークのパラメータは、以下の通りです。(図は論文から引用) kはフィルターのサイズ、nはフィルターの数、sはストライドの大きさを表しています。 それぞれ6つの層から構成されており、最後にupscaleで拡大しています。 この拡大の方法は、ESPCNで提案されたPixel shuffleを使用します。(図は論文から引用) 特徴としては、拡大したい倍率の2乗の値を入力する点です。 論文の例だと、5層でチャンネル数を32に拡張しています。 4倍拡大なので、4の2乗の16と2チャンネルというように分けることができ、4倍拡大された2チャンネルの結果を返します。 ② Spatio-temporal ESPCN Spatio-temporal ESPCNは、ESPCNを用いて、実際に超解像を行います。 ESPCNは、単一画像にのみ対応しているものでしたが、今回の論文では動画像に対応できるように入力チャンネル数を変更したりしています。 ESPCNの構造は前でも載せましたが、このようになっています。(図は論文から引用) ESPCNについては、以前に実装をしているので興味があればそちらもご覧ください。(実装記事) ESPCNで処理を行えば、結果が出力され本モデルの全ての工程が終わります。 3. 実装したアルゴリズム 今回は、VESPCNの全体を1つのニューラルネットワークのモデルとして実装してみました。 下の図が、本モデルの概要に近いと思います。(図は論文から引用) 入力する低解像度画像は3枚で、まずMotion estimationでデータ前処理を行います。 その後、ESPCNに3枚の画像を入力するという流れです。 コマンドラインでモデルを出力した例は、長すぎるので最後に参考として載せています。 4. 使用したデータセット 今回は、データセットにREDSを使用しました。 このデータセットは、動画像の超解像用のデータセットで、240種類の学習用データ、30種類の検証用データ、30種類のテスト用データの計300種類のデータセットです。 別の実装でもほとんどこのデータセットを使用しています。 パスの構造はこんな感じです。 train_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... val_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... このデータをBicubicで縮小したりしてデータセットを生成しました。 5. 画像評価指標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を計測する画像を示しています。 6. コードの使用方法 このコード使用方法は、自分が執筆した別の実装記事とほとんど同じです。 ① 学習データ生成 まず、Githubからコードを一式ダウンロードして、カレントディレクトリにします。 Windowsのコマンドでいうとこんな感じ。 C:~/keras_RVSR> 次に、main.pyから生成するデータセットのサイズ・大きさ・切り取る枚数、ファイルのパスなどを指定します。 main.pyの15~26行目です。 使うPCのメモリ数などに応じで、画像サイズや学習データ数の調整が必要です。 main.py train_height = 160 #HRのサイズ train_width = 160 test_height = 720 test_width = 1280 train_dataset_num = 20000 #生成する学習用データの数 test_dataset_num = 10 #生成するテスト用データの数 train_cut_num = 10 #一組のフレームから生成するデータの数 test_cut_num = 1 train_movie_path = "../../reds/train_sharp" #動画像のフレームが入っているパス test_movie_path = "../../reds/val_sharp" 指定したら、コマンドでデータセットの生成をします。 C:~/keras_RVSR>python main.py --mode train_datacreate これで、train_data_list.npzというファイルのデータセットが生成されます。 ついでにテストデータも同じようにコマンドで生成します。コマンドはこれです。 C:~/keras_RVSR>python main.py --mode test_datacreate ② 学習 次に学習を行います。 設定するパラメータの箇所は、epoch数と学習率とかですかね... まずは、main.pyの28~33行目 main.py input_LR_num = 3 #入力するLRの数 input_channels = 1 #入力するLRのチャンネル数 mag = 4 #拡大倍率 MAX_BATSH_SIZE = 128 #最大のバッチサイズ EPOCHS = 1000 後は、学習のパラメータをあれこれ好きな値に設定します。85~107行目です。 今回は、バッチサイズの開始が1で、10epochごとに2倍にしていく、という構造だったため、少しコードを変更しています。 具体的にはMAX_BATCH_SIZEを2で割り続けて、2を何乗すればいいか計算し、その回数に応じて10回で学習させています。 main.py optimizers = tf.keras.optimizers.Adam(learning_rate=1e-4) train_model.compile(loss = "mean_squared_error", optimizer = optimizers, metrics = [psnr]) x = MAX_BATSH_SIZE i = 0 while x > 1: x /= 2 i += 1 for n in range(i): train_model.fit({"input_t_minus_1":train_x[0], "input_t":train_x[1], "input_t_plus_1":train_x[2]}, train_y, epochs = 10, verbose = 2, batch_size = 2 ** n) train_model.fit({"input_t_minus_1":train_x[0], "input_t":train_x[1], "input_t_plus_1":train_x[2]}, train_y, epochs = EPOCHS - 10 * i, verbose = 2, batch_size = MAX_BATSH_SIZE) optimizerはAdam、損失関数は最小二乗法を使用しています。 入力画像は今回は3枚で出力は1枚です。 学習はデータ生成と同じようにコマンドで行います。 C:~/keras_RVSR>python main.py --mode train_model これで、学習が終わるとモデルが出力されます。 ③ 評価 最後にモデルを使用してテストデータで評価を行います。 これも同様にコマンドで行いますが、事前に①でテストデータも生成しておいてください。 C:~/keras_RVSR>python main.py --mode evaluate このコマンドで、画像を出力してくれます。 7. 結果 出力した画像はこのようになりました。 なお、今回は輝度値のみで学習を行っているため、カラー画像には対応していません。 対応させる場合は、modelのInputのchannel数を変えたり、データセット生成のchannel数を変える必要があります。 元画像 低解像度画像(4倍縮小) 生成画像 PSNR:27.54 分かりにくいので、低解像度画像を拡大にして生成画像と同じサイズにしたものも載せておきます。 低解像度画像(生成画像と同じサイズに拡大) だいぶ粗さが取れているのが目視でもわかります。 最後に元画像・低解像度画像・生成画像の一部を並べて表示してみます。 並べてみると、高解像度化がされているのが分かります。 実際の動画像に処理をかける場合は、動画像をフレームに分解して、1枚ずつ処理を行う必要があります。 OpenCVで動画像をフレームごとに取得するとOKです。 8. コードの全容 前述の通り、Githubに載せています。 pythonのファイルは主に3つあります。 各ファイルの役割は以下の通りです。 data_create.py : データ生成に関するコード。 model.py : 超解像のアルゴリズムに関するコード。 main.py : 主に使用するコード。 9. まとめ 今回は、最近読んだ論文のVESPCNを元に実装してみました。 モデルが複雑すぎていつもより実装に時間がかかりましたが、無事に実装できました。 次は単一画像の超解像手法の実装をしてみようと思います。 記事が長くなってしまいましたが、最後まで読んでくださりありがとうございました。 参考文献 ・Real-Time Video Super-Resolution with Spatio-Temporal Networks and Motion Compensation  今回実装の参考にした論文。 ・画素数の壁を打ち破る 複数画像からの超解像技術  超解像の説明のために使用。 ・REDSのデータセット  今回使用したデータセット。 [参考]本モデルのコマンドライン出力 参考として、上記で軽く触れた本モデルのコマンドラインを紹介します。 (各フレームのMotion estimationなども全て1つのモデルにまとめているので入り乱れています。) 結構長めのモデルになってしまいました。 __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_t_minus_1 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_t (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_t_plus_1 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ concatenate (Concatenate) (None, None, None, 2 0 input_t[0][0] input_t_minus_1[0][0] __________________________________________________________________________________________________ concatenate_2 (Concatenate) (None, None, None, 2 0 input_t[0][0] input_t_plus_1[0][0] __________________________________________________________________________________________________ conv2d (Conv2D) (None, None, None, 2 1224 concatenate[0][0] __________________________________________________________________________________________________ conv2d_10 (Conv2D) (None, None, None, 2 1224 concatenate_2[0][0] __________________________________________________________________________________________________ conv2d_1 (Conv2D) (None, None, None, 2 5208 conv2d[0][0] __________________________________________________________________________________________________ conv2d_11 (Conv2D) (None, None, None, 2 5208 conv2d_10[0][0] __________________________________________________________________________________________________ conv2d_2 (Conv2D) (None, None, None, 2 14424 conv2d_1[0][0] __________________________________________________________________________________________________ conv2d_12 (Conv2D) (None, None, None, 2 14424 conv2d_11[0][0] __________________________________________________________________________________________________ conv2d_3 (Conv2D) (None, None, None, 2 5208 conv2d_2[0][0] __________________________________________________________________________________________________ conv2d_13 (Conv2D) (None, None, None, 2 5208 conv2d_12[0][0] __________________________________________________________________________________________________ conv2d_4 (Conv2D) (None, None, None, 3 6944 conv2d_3[0][0] __________________________________________________________________________________________________ conv2d_14 (Conv2D) (None, None, None, 3 6944 conv2d_13[0][0] __________________________________________________________________________________________________ lambda (Lambda) (None, None, None, 2 0 conv2d_4[0][0] __________________________________________________________________________________________________ lambda_2 (Lambda) (None, None, None, 2 0 conv2d_14[0][0] __________________________________________________________________________________________________ multiply (Multiply) (None, None, None, 2 0 input_t_minus_1[0][0] lambda[0][0] __________________________________________________________________________________________________ multiply_2 (Multiply) (None, None, None, 2 0 input_t_plus_1[0][0] lambda_2[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem (Slici (None, None, None) 0 multiply[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_1 (Sli (None, None, None) 0 multiply[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_4 (Sli (None, None, None) 0 multiply_2[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_5 (Sli (None, None, None) 0 multiply_2[0][0] __________________________________________________________________________________________________ tf.expand_dims (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem[0][0] __________________________________________________________________________________________________ tf.expand_dims_1 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_1[0][0] __________________________________________________________________________________________________ tf.expand_dims_4 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_4[0][0] __________________________________________________________________________________________________ tf.expand_dims_5 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_5[0][0] __________________________________________________________________________________________________ add (Add) (None, None, None, 1 0 tf.expand_dims[0][0] tf.expand_dims_1[0][0] __________________________________________________________________________________________________ add_5 (Add) (None, None, None, 1 0 tf.expand_dims_4[0][0] tf.expand_dims_5[0][0] __________________________________________________________________________________________________ add_1 (Add) (None, None, None, 1 0 input_t_minus_1[0][0] add[0][0] __________________________________________________________________________________________________ add_6 (Add) (None, None, None, 1 0 input_t_plus_1[0][0] add_5[0][0] __________________________________________________________________________________________________ concatenate_1 (Concatenate) (None, None, None, 5 0 input_t[0][0] input_t_minus_1[0][0] lambda[0][0] add_1[0][0] __________________________________________________________________________________________________ concatenate_3 (Concatenate) (None, None, None, 5 0 input_t[0][0] input_t_plus_1[0][0] lambda_2[0][0] add_6[0][0] __________________________________________________________________________________________________ conv2d_5 (Conv2D) (None, None, None, 2 3024 concatenate_1[0][0] __________________________________________________________________________________________________ conv2d_15 (Conv2D) (None, None, None, 2 3024 concatenate_3[0][0] __________________________________________________________________________________________________ conv2d_6 (Conv2D) (None, None, None, 2 5208 conv2d_5[0][0] __________________________________________________________________________________________________ conv2d_16 (Conv2D) (None, None, None, 2 5208 conv2d_15[0][0] __________________________________________________________________________________________________ conv2d_7 (Conv2D) (None, None, None, 2 5208 conv2d_6[0][0] __________________________________________________________________________________________________ conv2d_17 (Conv2D) (None, None, None, 2 5208 conv2d_16[0][0] __________________________________________________________________________________________________ conv2d_8 (Conv2D) (None, None, None, 2 5208 conv2d_7[0][0] __________________________________________________________________________________________________ conv2d_18 (Conv2D) (None, None, None, 2 5208 conv2d_17[0][0] __________________________________________________________________________________________________ conv2d_9 (Conv2D) (None, None, None, 8 1736 conv2d_8[0][0] __________________________________________________________________________________________________ conv2d_19 (Conv2D) (None, None, None, 8 1736 conv2d_18[0][0] __________________________________________________________________________________________________ lambda_1 (Lambda) (None, None, None, 2 0 conv2d_9[0][0] __________________________________________________________________________________________________ lambda_3 (Lambda) (None, None, None, 2 0 conv2d_19[0][0] __________________________________________________________________________________________________ add_2 (Add) (None, None, None, 2 0 lambda[0][0] lambda_1[0][0] __________________________________________________________________________________________________ add_7 (Add) (None, None, None, 2 0 lambda_2[0][0] lambda_3[0][0] __________________________________________________________________________________________________ multiply_1 (Multiply) (None, None, None, 2 0 input_t_minus_1[0][0] add_2[0][0] __________________________________________________________________________________________________ multiply_3 (Multiply) (None, None, None, 2 0 input_t_plus_1[0][0] add_7[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_2 (Sli (None, None, None) 0 multiply_1[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_3 (Sli (None, None, None) 0 multiply_1[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_6 (Sli (None, None, None) 0 multiply_3[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_7 (Sli (None, None, None) 0 multiply_3[0][0] __________________________________________________________________________________________________ tf.expand_dims_2 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_2[0][0] __________________________________________________________________________________________________ tf.expand_dims_3 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_3[0][0] __________________________________________________________________________________________________ tf.expand_dims_6 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_6[0][0] __________________________________________________________________________________________________ tf.expand_dims_7 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_7[0][0] __________________________________________________________________________________________________ add_3 (Add) (None, None, None, 1 0 tf.expand_dims_2[0][0] tf.expand_dims_3[0][0] __________________________________________________________________________________________________ add_8 (Add) (None, None, None, 1 0 tf.expand_dims_6[0][0] tf.expand_dims_7[0][0] __________________________________________________________________________________________________ add_4 (Add) (None, None, None, 1 0 input_t_minus_1[0][0] add_3[0][0] __________________________________________________________________________________________________ add_9 (Add) (None, None, None, 1 0 input_t_plus_1[0][0] add_8[0][0] __________________________________________________________________________________________________ concatenate_4 (Concatenate) (None, None, None, 3 0 add_4[0][0] input_t[0][0] add_9[0][0] __________________________________________________________________________________________________ conv2d_20 (Conv2D) (None, None, None, 9 684 concatenate_4[0][0] __________________________________________________________________________________________________ conv2d_21 (Conv2D) (None, None, None, 3 2624 conv2d_20[0][0] __________________________________________________________________________________________________ conv2d_22 (Conv2D) (None, None, None, 1 4624 conv2d_21[0][0] __________________________________________________________________________________________________ lambda_4 (Lambda) (None, None, None, 1 0 conv2d_22[0][0] ================================================================================================== Total params: 114,716 Trainable params: 114,716 Non-trainable params: 0 __________________________________________________________________________________________________
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超解像手法/VESPCNの実装

概要 深層学習を用いた、動画像における超解像手法であるVESPCNの実装したので、それのまとめの記事です。 Python + Tensorflowで実装を行いました。 今回紹介するコードはGithubにも載せています。 1. 超解像のおさらい 超解像について簡単に説明をします。 超解像とは解像度の低い画像に対して、解像度を向上させる技術のことです。 ここでいう解像度が低いとは、画素数が少なかったり、高周波成分(輪郭などの鮮鋭な部分を表す)がないような画像のことです。 以下の図で例を示します。(図は[論文]より引用) これは、超解像の説明をする時によく使われる画像です。 (a)は原画像、(b)は、画素数の少ない画像を見やすいように原画像と同じ大きさにした画像、(c)は、高周波成分を含まない画像の例です。 (b)と(c)は、荒かったりぼやけていたりしていると思います。 このような状態を解像度が低い画像といいます。 そして、超解像はこのような解像度が低い画像に処理を行い、(a)のような精細な画像を出力することを目的としています。 (今回紹介するVESPCNは、これを動画像に応用した超解像手法) 2. 論文の超解像アルゴリズム 超解像のアルゴリズムの概要図は以下の通りです。(図は論文から引用) この超解像手法は、3枚の連続するフレームを入力して、1枚の高解像度画像を出力します。 前半のMotion estimationが動き補償などのデータ前処理の部分、 後半のSpatio-temporal ESPCNが超解像を行う部分になっています。 それぞれの流れについてもう少し詳しく説明していきます。 ① Motion estimation 超解像アルゴリズムに入力する前のデータ前処理を行う箇所になります。 Motion estimation部分のアルゴリズムは以下の図の通りです。(図は論文から引用) このデータ前処理の部分では、2枚の連続したフレームを入力します。 ここでは、Coarse flow estimationとFine flow estimationという2つのニューラルネットワークを通してデータ前処理を行います。 $\Delta c$と$\Delta f$は移動量を表すパラメータみたいなもので、オプティカルフローのパラメータを求めるのと似ています。 今回は、このデータ前処理でもニューラルネットワークを使用しています。 過去の超解像手法では、オプティカルフローを事前に求めたり、Bicubic法などで補間処理を行っていたため、データ前処理にかなり時間がかかってました。 そのため、データ前処理にもニューラルネットワークを使用することで、計算速度を上げる狙いもあります。 ニューラルネットワークのパラメータは、以下の通りです。(図は論文から引用) kはフィルターのサイズ、nはフィルターの数、sはストライドの大きさを表しています。 それぞれ6つの層から構成されており、最後にupscaleで拡大しています。 この拡大の方法は、ESPCNで提案されたPixel shuffleを使用します。(図は論文から引用) 特徴としては、拡大したい倍率の2乗の値を入力する点です。 論文の例だと、5層でチャンネル数を32に拡張しています。 4倍拡大なので、4の2乗の16と2チャンネルというように分けることができ、4倍拡大された2チャンネルの結果を返します。 ② Spatio-temporal ESPCN Spatio-temporal ESPCNは、ESPCNを用いて、実際に超解像を行います。 ESPCNは、単一画像にのみ対応しているものでしたが、今回の論文では動画像に対応できるように入力チャンネル数を変更したりしています。 ESPCNの構造は前でも載せましたが、このようになっています。(図は論文から引用) ESPCNについては、以前に実装をしているので興味があればそちらもご覧ください。(実装記事) ESPCNで処理を行えば、結果が出力され本モデルの全ての工程が終わります。 3. 実装したアルゴリズム 今回は、VESPCNの全体を1つのニューラルネットワークのモデルとして実装してみました。 下の図が、本モデルの概要に近いと思います。(図は論文から引用) 入力する低解像度画像は3枚で、まずMotion estimationでデータ前処理を行います。 その後、ESPCNに3枚の画像を入力するという流れです。 コマンドラインでモデルを出力した例は、長すぎるので最後に参考として載せています。 4. 使用したデータセット 今回は、データセットにREDSを使用しました。 このデータセットは、動画像の超解像用のデータセットで、240種類の学習用データ、30種類の検証用データ、30種類のテスト用データの計300種類のデータセットです。 別の実装でもほとんどこのデータセットを使用しています。 パスの構造はこんな感じです。 train_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... val_sharp - 001 - フレーム100枚 - 002 - フレーム100枚 - ... このデータをBicubicで縮小したりしてデータセットを生成しました。 5. 画像評価指標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を計測する画像を示しています。 6. コードの使用方法 このコード使用方法は、自分が執筆した別の実装記事とほとんど同じです。 ① 学習データ生成 まず、Githubからコードを一式ダウンロードして、カレントディレクトリにします。 Windowsのコマンドでいうとこんな感じ。 C:~/keras_RVSR> 次に、main.pyから生成するデータセットのサイズ・大きさ・切り取る枚数、ファイルのパスなどを指定します。 main.pyの15~26行目です。 使うPCのメモリ数などに応じで、画像サイズや学習データ数の調整が必要です。 main.py train_height = 160 #HRのサイズ train_width = 160 test_height = 720 test_width = 1280 train_dataset_num = 20000 #生成する学習用データの数 test_dataset_num = 10 #生成するテスト用データの数 train_cut_num = 10 #一組のフレームから生成するデータの数 test_cut_num = 1 train_movie_path = "../../reds/train_sharp" #動画像のフレームが入っているパス test_movie_path = "../../reds/val_sharp" 指定したら、コマンドでデータセットの生成をします。 C:~/keras_RVSR>python main.py --mode train_datacreate これで、train_data_list.npzというファイルのデータセットが生成されます。 ついでにテストデータも同じようにコマンドで生成します。コマンドはこれです。 C:~/keras_RVSR>python main.py --mode test_datacreate ② 学習 次に学習を行います。 設定するパラメータの箇所は、epoch数と学習率とかですかね... まずは、main.pyの28~33行目 main.py input_LR_num = 3 #入力するLRの数 input_channels = 1 #入力するLRのチャンネル数 mag = 4 #拡大倍率 MAX_BATSH_SIZE = 128 #最大のバッチサイズ EPOCHS = 1000 後は、学習のパラメータをあれこれ好きな値に設定します。85~107行目です。 今回は、バッチサイズの開始が1で、10epochごとに2倍にしていく、という構造だったため、少しコードを変更しています。 具体的にはMAX_BATCH_SIZEを2で割り続けて、2を何乗すればいいか計算し、その回数に応じて10回で学習させています。 main.py optimizers = tf.keras.optimizers.Adam(learning_rate=1e-4) train_model.compile(loss = "mean_squared_error", optimizer = optimizers, metrics = [psnr]) x = MAX_BATSH_SIZE i = 0 while x > 1: x /= 2 i += 1 for n in range(i): train_model.fit({"input_t_minus_1":train_x[0], "input_t":train_x[1], "input_t_plus_1":train_x[2]}, train_y, epochs = 10, verbose = 2, batch_size = 2 ** n) train_model.fit({"input_t_minus_1":train_x[0], "input_t":train_x[1], "input_t_plus_1":train_x[2]}, train_y, epochs = EPOCHS - 10 * i, verbose = 2, batch_size = MAX_BATSH_SIZE) optimizerはAdam、損失関数は最小二乗法を使用しています。 入力画像は今回は3枚で出力は1枚です。 学習はデータ生成と同じようにコマンドで行います。 C:~/keras_RVSR>python main.py --mode train_model これで、学習が終わるとモデルが出力されます。 ③ 評価 最後にモデルを使用してテストデータで評価を行います。 これも同様にコマンドで行いますが、事前に①でテストデータも生成しておいてください。 C:~/keras_RVSR>python main.py --mode evaluate このコマンドで、画像を出力してくれます。 7. 結果 出力した画像はこのようになりました。 なお、今回は輝度値のみで学習を行っているため、カラー画像には対応していません。 対応させる場合は、modelのInputのchannel数を変えたり、データセット生成のchannel数を変える必要があります。 元画像 低解像度画像(4倍縮小) 生成画像 PSNR:27.54 分かりにくいので、低解像度画像を拡大にして生成画像と同じサイズにしたものも載せておきます。 低解像度画像(生成画像と同じサイズに拡大) だいぶ粗さが取れているのが目視でもわかります。 最後に元画像・低解像度画像・生成画像の一部を並べて表示してみます。 並べてみると、高解像度化がされているのが分かります。 実際の動画像に処理をかける場合は、動画像をフレームに分解して、1枚ずつ処理を行う必要があります。 OpenCVで動画像をフレームごとに取得するとOKです。 8. コードの全容 前述の通り、Githubに載せています。 pythonのファイルは主に3つあります。 各ファイルの役割は以下の通りです。 data_create.py : データ生成に関するコード。 model.py : 超解像のアルゴリズムに関するコード。 main.py : 主に使用するコード。 9. まとめ 今回は、最近読んだ論文のVESPCNを元に実装してみました。 モデルが複雑すぎていつもより実装に時間がかかりましたが、無事に実装できました。 次は単一画像の超解像手法の実装をしてみようと思います。 記事が長くなってしまいましたが、最後まで読んでくださりありがとうございました。 参考文献 ・Real-Time Video Super-Resolution with Spatio-Temporal Networks and Motion Compensation  今回実装の参考にした論文。 ・画素数の壁を打ち破る 複数画像からの超解像技術  超解像の説明のために使用。 ・REDSのデータセット  今回使用したデータセット。 [参考]本モデルのコマンドライン出力 参考として、上記で軽く触れた本モデルのコマンドラインを紹介します。 (各フレームのMotion estimationなども全て1つのモデルにまとめているので入り乱れています。) 結構長めのモデルになってしまいました。 __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_t_minus_1 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_t (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ input_t_plus_1 (InputLayer) [(None, None, None, 0 __________________________________________________________________________________________________ concatenate (Concatenate) (None, None, None, 2 0 input_t[0][0] input_t_minus_1[0][0] __________________________________________________________________________________________________ concatenate_2 (Concatenate) (None, None, None, 2 0 input_t[0][0] input_t_plus_1[0][0] __________________________________________________________________________________________________ conv2d (Conv2D) (None, None, None, 2 1224 concatenate[0][0] __________________________________________________________________________________________________ conv2d_10 (Conv2D) (None, None, None, 2 1224 concatenate_2[0][0] __________________________________________________________________________________________________ conv2d_1 (Conv2D) (None, None, None, 2 5208 conv2d[0][0] __________________________________________________________________________________________________ conv2d_11 (Conv2D) (None, None, None, 2 5208 conv2d_10[0][0] __________________________________________________________________________________________________ conv2d_2 (Conv2D) (None, None, None, 2 14424 conv2d_1[0][0] __________________________________________________________________________________________________ conv2d_12 (Conv2D) (None, None, None, 2 14424 conv2d_11[0][0] __________________________________________________________________________________________________ conv2d_3 (Conv2D) (None, None, None, 2 5208 conv2d_2[0][0] __________________________________________________________________________________________________ conv2d_13 (Conv2D) (None, None, None, 2 5208 conv2d_12[0][0] __________________________________________________________________________________________________ conv2d_4 (Conv2D) (None, None, None, 3 6944 conv2d_3[0][0] __________________________________________________________________________________________________ conv2d_14 (Conv2D) (None, None, None, 3 6944 conv2d_13[0][0] __________________________________________________________________________________________________ lambda (Lambda) (None, None, None, 2 0 conv2d_4[0][0] __________________________________________________________________________________________________ lambda_2 (Lambda) (None, None, None, 2 0 conv2d_14[0][0] __________________________________________________________________________________________________ multiply (Multiply) (None, None, None, 2 0 input_t_minus_1[0][0] lambda[0][0] __________________________________________________________________________________________________ multiply_2 (Multiply) (None, None, None, 2 0 input_t_plus_1[0][0] lambda_2[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem (Slici (None, None, None) 0 multiply[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_1 (Sli (None, None, None) 0 multiply[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_4 (Sli (None, None, None) 0 multiply_2[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_5 (Sli (None, None, None) 0 multiply_2[0][0] __________________________________________________________________________________________________ tf.expand_dims (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem[0][0] __________________________________________________________________________________________________ tf.expand_dims_1 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_1[0][0] __________________________________________________________________________________________________ tf.expand_dims_4 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_4[0][0] __________________________________________________________________________________________________ tf.expand_dims_5 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_5[0][0] __________________________________________________________________________________________________ add (Add) (None, None, None, 1 0 tf.expand_dims[0][0] tf.expand_dims_1[0][0] __________________________________________________________________________________________________ add_5 (Add) (None, None, None, 1 0 tf.expand_dims_4[0][0] tf.expand_dims_5[0][0] __________________________________________________________________________________________________ add_1 (Add) (None, None, None, 1 0 input_t_minus_1[0][0] add[0][0] __________________________________________________________________________________________________ add_6 (Add) (None, None, None, 1 0 input_t_plus_1[0][0] add_5[0][0] __________________________________________________________________________________________________ concatenate_1 (Concatenate) (None, None, None, 5 0 input_t[0][0] input_t_minus_1[0][0] lambda[0][0] add_1[0][0] __________________________________________________________________________________________________ concatenate_3 (Concatenate) (None, None, None, 5 0 input_t[0][0] input_t_plus_1[0][0] lambda_2[0][0] add_6[0][0] __________________________________________________________________________________________________ conv2d_5 (Conv2D) (None, None, None, 2 3024 concatenate_1[0][0] __________________________________________________________________________________________________ conv2d_15 (Conv2D) (None, None, None, 2 3024 concatenate_3[0][0] __________________________________________________________________________________________________ conv2d_6 (Conv2D) (None, None, None, 2 5208 conv2d_5[0][0] __________________________________________________________________________________________________ conv2d_16 (Conv2D) (None, None, None, 2 5208 conv2d_15[0][0] __________________________________________________________________________________________________ conv2d_7 (Conv2D) (None, None, None, 2 5208 conv2d_6[0][0] __________________________________________________________________________________________________ conv2d_17 (Conv2D) (None, None, None, 2 5208 conv2d_16[0][0] __________________________________________________________________________________________________ conv2d_8 (Conv2D) (None, None, None, 2 5208 conv2d_7[0][0] __________________________________________________________________________________________________ conv2d_18 (Conv2D) (None, None, None, 2 5208 conv2d_17[0][0] __________________________________________________________________________________________________ conv2d_9 (Conv2D) (None, None, None, 8 1736 conv2d_8[0][0] __________________________________________________________________________________________________ conv2d_19 (Conv2D) (None, None, None, 8 1736 conv2d_18[0][0] __________________________________________________________________________________________________ lambda_1 (Lambda) (None, None, None, 2 0 conv2d_9[0][0] __________________________________________________________________________________________________ lambda_3 (Lambda) (None, None, None, 2 0 conv2d_19[0][0] __________________________________________________________________________________________________ add_2 (Add) (None, None, None, 2 0 lambda[0][0] lambda_1[0][0] __________________________________________________________________________________________________ add_7 (Add) (None, None, None, 2 0 lambda_2[0][0] lambda_3[0][0] __________________________________________________________________________________________________ multiply_1 (Multiply) (None, None, None, 2 0 input_t_minus_1[0][0] add_2[0][0] __________________________________________________________________________________________________ multiply_3 (Multiply) (None, None, None, 2 0 input_t_plus_1[0][0] add_7[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_2 (Sli (None, None, None) 0 multiply_1[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_3 (Sli (None, None, None) 0 multiply_1[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_6 (Sli (None, None, None) 0 multiply_3[0][0] __________________________________________________________________________________________________ tf.__operators__.getitem_7 (Sli (None, None, None) 0 multiply_3[0][0] __________________________________________________________________________________________________ tf.expand_dims_2 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_2[0][0] __________________________________________________________________________________________________ tf.expand_dims_3 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_3[0][0] __________________________________________________________________________________________________ tf.expand_dims_6 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_6[0][0] __________________________________________________________________________________________________ tf.expand_dims_7 (TFOpLambda) (None, None, None, 1 0 tf.__operators__.getitem_7[0][0] __________________________________________________________________________________________________ add_3 (Add) (None, None, None, 1 0 tf.expand_dims_2[0][0] tf.expand_dims_3[0][0] __________________________________________________________________________________________________ add_8 (Add) (None, None, None, 1 0 tf.expand_dims_6[0][0] tf.expand_dims_7[0][0] __________________________________________________________________________________________________ add_4 (Add) (None, None, None, 1 0 input_t_minus_1[0][0] add_3[0][0] __________________________________________________________________________________________________ add_9 (Add) (None, None, None, 1 0 input_t_plus_1[0][0] add_8[0][0] __________________________________________________________________________________________________ concatenate_4 (Concatenate) (None, None, None, 3 0 add_4[0][0] input_t[0][0] add_9[0][0] __________________________________________________________________________________________________ conv2d_20 (Conv2D) (None, None, None, 9 684 concatenate_4[0][0] __________________________________________________________________________________________________ conv2d_21 (Conv2D) (None, None, None, 3 2624 conv2d_20[0][0] __________________________________________________________________________________________________ conv2d_22 (Conv2D) (None, None, None, 1 4624 conv2d_21[0][0] __________________________________________________________________________________________________ lambda_4 (Lambda) (None, None, None, 1 0 conv2d_22[0][0] ================================================================================================== Total params: 114,716 Trainable params: 114,716 Non-trainable params: 0 __________________________________________________________________________________________________
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode 1000000本ノック【141. Linked List Cycle】

目次 概要 問題 解法 Bad Submit(個人的ミス) 概要 ●発端  ・競プロ初心者、コーディング面接でズタボロにされ、  ・最低限のアルゴリズム学習とコーディング力強化を習慣化するため記事化 ●環境  ・LeetCodeブラウザ上で実装(vscodeに拡張機能にするかもシレナイ)  ・言語はpython3 ●その他ルール  ・google検索は自由に(直接的なLeetCodeの問題解説ページはNG)   (60分実装して実装出来なければ解説ページ参照)  ・コーディング面接対策のために解きたいLeetCode 60問から問題選出  ・「1000000」は任意の2進数とする 問題 141. Linked List Cycle Given head, the head of a linked list, determine if the linked list has a cycle in it. There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail's next pointer is connected to. Note that pos is not passed as a parameter. Return true if there is a cycle in the linked list. Otherwise, return false. ⇨「引数に渡された連結リストがサイクルを持っていれば(循環していれば)Trueを、そうでなければFalseを返せ」と書いています。知らんけど。 Input: head = [3,2,0,-4], pos = 1 Output: true 解法 実施時間:30分 「LinkedList」で検索した下記ページを参考(というかほぼ答え) LinkedListが輪になっているかを判定する Hacker Rank 考え方としては、連結リストに2つのポインターを置いて別々の間隔で進め、 もし途中で一致したなら循環しているというもの。知らんけど。 # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def hasCycle(self, head: ListNode) -> bool: if head is None: return False slow = head fast = head.next while fast is not None and fast.next is not None and slow.next is not None: if fast == slow: return True slow = slow.next fast = fast.next.next return False 渡された連結リストが上記例題だとすると fastポインター(1つ飛ばしで進む) 2 → -4 → 2 → -4 slowポインター(順番に進む)   3 → 2 → 0 → -4 ※pos = 1なのでlist[1]の2に循環 ※pos = ここで一致するためTrue Bad Submit(個人的ミス) ●while文の条件不足でRuntime Error  ・要素数1の想定漏れ。fastポインターがnextから始まってるからそりゃあね... Input: head = [1], pos = -1 Bad while fast.next is not None and slow.next is not None: good while fast is not None and fast.next is not None and slow.next is not None: ●if文の条件記載ミスでWrong Answer  ・リスト内の値は重複しないという思い込み実装... Input: head = [1,1,1], pos = -1 Bad if fast.val == slow.val: good if fast == slow:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium + Headless Chrome + Jupyter Notebook + Docker

GWに調べたことのメモです。 Jupyter Notebook上でSeleniumを動かすことのできるDockerイメージを作成します。 Seleniumがどんなものか手っ取り早く試してみるための環境です。 参考記事 本記事の作成には、以下の記事を参考に作成させていただきました。 【2019年版】Ubuntu18.04 にChromeとSeleniumをインストール Rails + postgres + chromedriverのdocker-compose環境を作る Dockerイメージ作成 まずは、Dockerfileの全体です。 Dockerfile FROM jupyter/scipy-notebook:latest USER root WORKDIR /tmp # 必要なツールとIPAフォントをインストールします RUN apt-get update && apt-get install -y gnupg curl fonts-ipafont fonts-ipaexfont # Google Chromeをインストールします RUN wget https://dl.google.com/linux/linux_signing_key.pub \ && apt-key add linux_signing_key.pub \ && echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list \ && apt-get update \ && apt-get install -y google-chrome-stable \ && google-chrome --version # Chrome Driverをインストールします RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \ && curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip \ && unzip /tmp/chromedriver_linux64.zip \ && mv chromedriver /usr/local/bin/ # PermissionError: [Errno 13] Permission denied: '/home/jovyan/.local/share/jupyter' # となぜか怒られるのでとりあえず所有者を変更 RUN chown -R jovyan /home/jovyan/.local USER jovyan # selenium導入 RUN pip install selenium WORKDIR /home/jovyan 上記を使って、jupyter-seleniumというイメージを作成します。 $ docker build -t jupyter-selenium . Dockerfileポイント説明 上のDockerfileのポイントをいくつか説明していきます。 Dockerのベースイメージには、jupyter/scipy-notebookを使っています。 Google Chromeインストール 【2019年版】Ubuntu18.04 にChromeとSeleniumをインストールの記事を基にGoogle Chromeをインストールします。 RUN wget https://dl.google.com/linux/linux_signing_key.pub \ && apt-key add linux_signing_key.pub \ && echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list \ && apt-get update \ && apt-get install -y google-chrome-stable \ && google-chrome --version Chrome Driver導入 https://chromedriver.storage.googleapis.com/LATEST_RELEASE というところで、最新のChrome Driverのバージョンが取れるようなので、それを利用します。 解凍したChrome Driverをパスの通った場所に配置します。 RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \ && curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip \ && unzip /tmp/chromedriver_linux64.zip \ && mv chromedriver /usr/local/bin/ Jupyter Notebook起動 作成したイメージを使って、Jupyter Notebookを起動します。 カレントディレクトリにworkというディレクトリを作業用に作成し、マウントしておきます。 $ docker run -d -v $(pwd)/work:/home/jovyan/work -p 8888:8888 --name jupyter-selenium jupyter-selenium コンテナに入って、rootユーザで色々作業するときは、以下のように起動します。 参考: https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#docker-options $ docker run -u root -e GRANT_SUDO=yes -d -v $(pwd)/work:/home/jovyan/work -p 8888:8888 --name jupyter-selenium jupyter-selenium ブラウザでアクセスするためのURLは以下で取得します。 0.0.0.0の部分をlocalhost等に変えて、http://localhost:8888/?token=(トークン)で、Jupyter Notebookにアクセスします。 (手元のMac環境では、0.0.0.0そのままでも動きました。) $ docker exec -it jupyter-selenium jupyter notebook list Currently running servers: http://0.0.0.0:8888/?token=(トークン) :: /home/jovyan Jupyter Notebookを開いたら、Python 3のノートブックを作成します。 Seleniumコード ChromeをHeadlessモードで起動して、Google検索するためのコードを書きます。 Pythonは初心者なので、おかしなところはご容赦ください。 初期化 ChromeをHeadlessモードで起動するためのオプションを指定して、Web Driverを初期化します。 from selenium import webdriver from selenium.webdriver.chrome.options import Options from IPython.display import Image # WebDriverが既に起動したら閉じておく(Pythonでこんな書き方で良いかは自信はない) try: driver.quit() print("driver is closed.") except NameError: print("driver is not initialized.") options = Options() options.binary_location = '/usr/bin/google-chrome' options.add_argument('--headless') options.add_argument('--no-sandbox') options.add_argument('--disable-gpu') options.add_argument('--window-size=1280,1024') driver = webdriver.Chrome('chromedriver', options=options) Google検索 Googleのページを開きます。 スクリーンショットを取得して、保存します。 また、IPython.displayを使って、ノートブック上に画像を表示します。 参考: JupyterNotebook上に画像を表示する2つの方法 driver.get('https://www.google.com/') print(driver.title) assert driver.title == "Google" driver.save_screenshot('screenshot_google.png') Image("screenshot_google.png") 検索ボックスに「Selenide」と入力し、検索します。 ちなみにSelenideは、SeleniumのJavaラッパーのテストフレームワークです。 search_box = driver.find_element_by_name("q") search_box.send_keys('Selenide') search_box.submit() print(driver.title) driver.save_screenshot('screenshot_search.png') Image("screenshot_search.png") 最後に、Web Driverを閉じます。 driver.quit() 備考: 要素クリック直前・直後の処理を定義 要素をクリックする直前・直後に行う処理を定義する場合は、初期化時のコードをおそらく以下のようにすれば良さそうです。 待機処理やスクリーンショットの取得をクリック毎に行うような場合に使います。 使っているフレームワークや対象サイトの特性に応じて、上手に活用するとSeleniumの苦手なAjax処理の待機をうまくできたりします。 参考: 【Python】before_click/after_click・・・要素がクリックされる直前/直後の処理を実施する from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener # ・・・ class MyListener(AbstractEventListener): def before_click(self, element, driver): #要素をクリックする直前の処理 print("before_click:" + driver.current_url) def after_click(self, element, driver): #要素をクリックした直後の処理 print("after_click:" + driver.current_url) base_driver = webdriver.Chrome('chromedriver', options=options) driver = EventFiringWebDriver(base_driver, MyListener())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ArcGISユーザがPythonの仮想環境を構築する

対象読者 ArcGISユーザ Python未経験 PC環境 Windows10 はじめに 保守契約を結んでいるArcGIS Desktopのライセンスには、ArcGIS Proというアプリケーションが付属しています。 このArcGIS Proをインストールすると、Minicondaを通してPythonも同時にインストールされます。 Minicondaというのは、仮想環境を構築・管理するためのもので、Anacondaの軽量版的な位置づけです。 Python仮想環境やAnacondaについては、python JapanのPython環境構築ガイドが参考になります。 実際はArcGIS DesktopにもPythonは付属していますが、バージョンは2系で、しかもパッケージ管理されていない素の状態です。 素の状態でもPythonを使うことができますが、プロジェクト別に仮想環境を構築し、パッケージ管理を行うやり方が一般的です。 Esri社はArcGIS Desktop製品の軸足をMapからProに移しているようですし、Pythonも3系が主流になっているので、ここではArcGIS Proを出発点としたPythonの仮想環境の構築方法について簡単に説明したいと思います。 ArcGIS Proのインストール Esri製品サポートページからインストーラがダウンロードできます。 普段ArcMapしか使っていないという場合は、ArcGIS Proのインストールが必要です。 また、ArcPy(ArcGISのジオプロセシングツール等にアクセスするためのパッケージ)を利用する場合は、有効なライセンスでArcGIS Proにログインできている必要があります。 Pythonとcondaのパスを通す 環境変数にPythonとcondaを登録します。 方法が分からない場合は、「パスを通す」とかで検索してみてください。 なお、仮想環境 と 環境変数 は全く別物なので、混同しないように気を付けて下さい。 パスはデフォルトだと、こちらになると思います。 python.exe C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3 conda.exe C:\Program Files\ArcGIS\Pro\bin\Python\Scripts 登録してから、PCを再起動してください。 動作確認 コマンドプロンプトを起動します。(Win + R から cmd で起動) コマンドプロンプトからPythonを起動します。(python と打って Enter) $ python 以下のような表示になればOKです。 Python 3.7.9 [MSC v.1922 64 bit (AMD64)] :: Anaconda, Inc. on win32 Type "help", "copyright", "credits" or "license" for more information. >>> Pythonを抜けます。 >>> exit() 次にconda(パッケージ管理ツール)を起動します。 $ conda 以下のような表示になればOKです。 usage: conda [-h] [-V] command ... (中略) other commands, such as "conda build", are available when additional conda packages (e.g. conda-build) are installed 仮想環境を作成する まず、既存の仮想環境の一覧を確認します。 $ conda env list # conda environments: # arcgispro-py3 * C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3 root C:\Program Files\ArcGIS\Pro\bin\Python * が付いているarcgispro-py3が現在有効になっている仮想環境の名前です。 arcgispro-py3は、ArcGIS Proのための仮想環境です。 次にarcgispro-py3に含まれるパッケージを確認します。 $ conda list # packages in environment at C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3: # appdirs 1.4.4 py_0 arcgis 1.8.3 py37_1668 esri arcgispro 2.7 0 esri arcpy 2.7 py37_arcgispro_26810 [arcgispro] esri (後略) ArcPyを含む多数のパッケージが入っていることが分かります。 では、新しい仮想環境を追加します。 $ conda create -n [name] python=[version] [name]に仮想環境の名前、[version]にPythonのバージョンを記載します。 例えば $ conda create -n py39 python=3.9 途中で、 Proceed ([y]/n)? と聞かれるので、yを入力します。 仮想環境の有効化 つぎに、有効化されている仮想環境をarcgispro-py3から、新しく作成した仮想環境に切り替えます。 $ activate [name] [name]は新しく作成した仮想環境の名前に読み替えて下さい。 環境が切り替わり、プロンプトの頭に(name)が付きます。 有効化した仮想環境のパッケージを確認します。 $ conda list # packages in environment at C:\Users\***\AppData\Local\py39: # ca-certificates 2021.4.13 haa95532_1 certifi 2020.12.5 py39haa95532_0 openssl 1.1.1k h2bbff1b_0 pip 21.0.1 py39haa95532_0 python 3.9.4 h6244533_0 setuptools 52.0.0 py39haa95532_0 sqlite 3.35.4 h2bbff1b_0 tzdata 2020f h52ac0ba_0 vc 14.1 h0510ff6_4 vs2015_runtime 14.16.27012 hf0eaf9b_0 wheel 0.36.2 pyhd3eb1b0_0 wincertstore 0.2 py39h2bbff1b_0 新規で作成した仮想環境には、必要最低限のパッケージが入っています。 必要に応じて、この仮想環境にパッケージをインストールしていくことになります。 なお、パッケージをインストールする場合は以下のコマンドを使います。 $ conda install [package]=[version] [package]にはパッケージの名前、[version]にはパッケージのバージョンを記載します。 例えば、pandasというパッケージのバージョン1.2.3を現在の仮想環境にインストールしたい場合は、以下のコマンドを入力してください。 $ conda install pandas=1.2.3 最後に 以上、Pythonの仮想環境構築までの流れを簡単にまとめました。 ArcGIS Pro -> Miniconda -> Python という方法でPythonを使い始めると、Python3の実行環境がデフォルトでarcgispro-py3になります。 ArcPyを使わないのであれば、新規で仮想環境を構築して使っていくほうがよいでしょう。 また、condaの使い方については一通りどんなコマンドがあるのか調べてみるとよいかと思います。 なお、パッケージのインストールにはpipという方法がありますが、pipでインストールしたパッケージはcondaで管理できないので、pipは使わないように気を付けてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初学者によるチャットボットの作成

1.はじめに 今回 Aidemyのpremium planで三か月の間、自然言語処理を学びその知識の定着、復習の意を込め成果物として深層学習によるチャットボットを作成してみました。 Qiitaによる投稿も初めてですので拙い文章だとは思いますが、それでも良い方はご覧ください! 2.使用環境 今回は処理するデータ量が多いためGoogle Colaboratory環境で、無料で使えるGPUを使用しました。 フレームワークのバージョンは以下の通りです。 ・janome    0.4.1 ・torchvision  0.7.0 ・torchtext   0.7.0 ・PyTorch    1.6.0 3.記事の内容 タイトルにもあるように、今回は会話の入力に対して、深層学習を用いて応答を予測し出力させるモデルを構築し、ノートブック上で繰り返し会話できるボットを作ることを目標にコーディングしました。 深層学習のフレームワークとしてKeras、Tensorflow等、いろいろなものがありますが今回は最近流行りのPyTorchを使いました。 PyTorchを選んだ理由は、以下のように考えたからです。   ① コードが比較的短く、理解すれば便利である   ② 入力と正解のペアをまとめて学習できるため、会話を成立させるという     点で有利に働く   ③ 今後の開発につながる また、学習するにあたりデータの前処理、PyTorchによる深層学習はudemyの講座を参考にさせていただきました。 PyTorchによる学習は初めてだったので、かなりつまずきながら手探り状態でしたが、自分でコーディングすることでPyTorchの概要、実装方法などかなり実践的な知識が身についたと思います。 4.実装の手順 それではコードのほうに移っていきます。 工程は大まかに分けて3つです。 ① データの前処理 今回訓練に使用した会話コーパスは、NTT ドコモが一般公開している雑談会話コーパスになります。ライセンスはダウンロードページ記載のものに準拠します。 こちらは約1100もの対話が収録されている会話コーパスで、雑談対話APIと人間との間で会話が行われているため、明らかに破綻している会話も収録されているという点も考慮して前処理を行います。 また、チャットボットの作成には約10万もの会話データが必要になるといわれているので今回は一つの入力文に対し、重要である単語を含んだ複数の応答文でペアを作るという方法を用いてデータの水増しを行っています。 以下が、コードになります。 qiita.py !pip install janome==0.4.1 !pip install torchvision==0.7.0 !pip install torchtext==0.7.0 !pip install torch==1.6.0 # google driveをマウントします from google.colab import drive drive.mount("/content/drive/") #先に必要なものをインポートしときます import glob import json import re from janome.tokenizer import Tokenizer import csv from sklearn.model_selection import train_test_split import torch import torch.nn as nn import torch.nn.functional as F from torch import optim import dill path = "/content/drive/My Drive/対話コーパス 前処理/NTT data/projectnextnlp-chat-dialogue-corpus/json" # 成り立っている会話を訓練用データとテストデータに分割して、保存します。 files = glob.glob(path + "/*/*.json") dialogues = [] for file in files: with open(file, "r") as f: json_dic = json.load(f) dialogue = [] for turn in json_dic["turns"]: #会話データの["turns"]には各項目が格納されている annotations = turn["annotations"] #注釈 speaker = turn["speaker"] #話者 utterance = turn["utterance"] #会話内容 utterance = utterance.replace(".", "。").replace(",", "、") # 全角を統一 utterance = utterance.replace(".", "。").replace(",", "、") # 半角を統一 utterance = utterance.split("。")[0] #ここでは"。"が文中にある長い文章を短くする if speaker=="U": # "U"=人間 dialogue.append(utterance) else: is_wrong = False for annotation in annotations: breakdown = annotation["breakdown"] #会話が破綻しているかどうか if breakdown=="X": is_wrong = True break if is_wrong: dialogue.clear() #破綻していたらデータをクリア else: dialogue.append(utterance) if len(dialogue) >= 2: #会話成立で格納 dialogues.append(dialogue.copy()) dialogue.pop(0) #会話の分かち書きを行い、入力と応答それぞれのリストに格納します。 re_kanji = re.compile(r"^[\u4E00-\u9FD0]+$") # 漢字の検出用 re_katakana = re.compile(r"[\u30A1-\u30F4]+") # カタカナの検出用 janome_tk = Tokenizer() def tokenizer(text): return [token for token in janome_tk.tokenize(text, wakati=True)] wakati_input = [] #水増しのために入力文と応答文を分けます wakati_reply = [] for dialogue in dialogues: wakati_input.append(tokenizer(dialogue[0])[:10]) wakati_reply.append(tokenizer(dialogue[1])[:10]) #入力分を複数の応答分と組み合わせてデータの水増しを行います。 dialogues_mizumasi = [] #ここで水増しにより会話データ約10万個 for i, word_input in enumerate(wakati_input): inp_count = 0 for j, word_reply in enumerate(wakati_reply): if i==j: dialogues_mizumasi.append(["".join(word_input), "".join(word_reply)]) continue #入力と応答の類似度を判断し、類似度が1より大きいものを会話に追加します similarity = 0 for word in word_input: #カタカナや漢字を重要付けている if (word in word_reply) and (re_kanji.fullmatch(word) or re_katakana.fullmatch(word)): similarity += 1 if similarity >= 1: dialogue_mizumasi = ["".join(word_input), "".join(word_reply)] if dialogue_mizumasi not in dialogues_mizumasi: dialogues_mizumasi.append(dialogue_mizumasi) inp_count += 1 if inp_count >= 12: #1つの入力に対し、対応する応答文は12まで break dialogues = dialogues_mizumasi #作成した会話のリストを訓練用データとテストデータに分けて格納します。 dialogues_train, dialogues_test = train_test_split(dialogues, shuffle=True, test_size=0.05) #入力分と応答分のデータセットの列を定義します input_field = torchtext.data.Field( sequential=True, #文章データのため可変にする tokenize=tokenizer, #分かち書き batch_first=True, lower=True ) reply_field = torchtext.data.Field( sequential=True, tokenize=tokenizer, init_token="<sos>", #文のはじめに加える eos_token="<eos>", #文の終わりに加える batch_first=True, lower=True ) path2 = "/content/drive/My Drive/対話コーパス 前処理/NTT data/" #データセットを作成します train_data, test_data = torchtext.data.TabularDataset.splits( path=path2, train=mizumasi_dialogues_train, test=mizumasi_dialogues_test, format="csv", fields=[("input_text", input_field), ("reply_text", reply_field)] ) #辞書の作成をします(出現頻度が3以下のものは除く) input_field.build_vocab( train_data, min_freq=3 ) reply_field.build_vocab( train_data, min_freq=3, ) #データセットを保存します torch.save(train_data.examples, path+"mizumasi_train_examples.pkl", pickle_module=dill) torch.save(test_data.examples, path+"mizumasi_test_examples.pkl", pickle_module=dill) torch.save(input_field, path2+"mizumasi_input.pkl", pickle_module=dill) torch.save(reply_field, path2+"mizumasi_reply.pkl", pickle_module=dill) ②訓練モデルを作成し、学習させる 次に、訓練モデルの作成と学習を行います。 流れとしては、 1.Encoder,Decoder,Seq2Seqのクラス作成 2.誤差を測定する評価関数を定義 3.これらを用いて、誤差が小さくなるようモデルにデータを学習させる となります。 以下がコードになります。 qiita2.py #Batchの設定を行います batch_size = 32 train_iterator = torchtext.data.Iterator( train_data, batch_size=batch_size, train=True ) test_iterator = torchtext.data.Iterator( test_data, batch_size=batch_size, train=False, sort=False ) #Encoderクラスを作成します# class Encoder(nn.Module): def __init__(self, n_h, n_vocab, n_emb, num_layers=1, bidirectional=False, dropout=0): super().__init__() self.n_h = n_h self.num_layers = num_layers self.bidirectional = bidirectional self.dropout = dropout self.embedding = nn.Embedding(n_vocab, n_emb) self.embedding_dropout = nn.Dropout(self.dropout) self.gru = nn.GRU( input_size=n_emb, #入力サイズ hidden_size=n_h,#隠れ層のサイズ(ニューロン数) batch_first=True, #(バッチサイズ、時系列、入力数) num_layers=num_layers, bidirectional=bidirectional, ) #順伝播の関数を定義 def forward(self, x): index_pad = input_field.vocab.stoi["<pad>"] #"pad"のインデックス取得 sentence_lengths = x.size()[1] - (x == index_pad).sum(dim=1) #pad部分を引いて本来の文の長さを得る。 y = self.embedding(x) #埋め込みベクトル化 y = self.embedding_dropout(y)#過学習対策 y = nn.utils.rnn.pack_padded_sequence( y, sentence_lengths, batch_first=True, enforce_sorted=False ) #rnnに入れるためにpackedsequence型にする。 y, h = self.gru(y) #encoderでの出力と隠れ層の値を取得 y, _ = nn.utils.rnn.pad_packed_sequence(y, batch_first=True) #テンソルに戻す if self.bidirectional: #会話データにおける最後の重みが大きくなってしまうため y = y[:, :, :self.n_h] + y[:, :, self.n_h:]#ここでは出力と隠れ層の値は双方向の時間の値を足したものになる。 h = h[:self.num_layers] + h[self.num_layers:] return y,h #Decoderクラスを作成します class Decoder(nn.Module): def __init__(self, n_h, n_out, n_vocab, n_emb, num_layers=1, dropout=0): super().__init__() self.n_h = n_h self.n_out = n_out self.num_layers = num_layers self.dropout = dropout self.embedding = nn.Embedding(n_vocab, n_emb) self.embedding_dropout = nn.Dropout(self.dropout) self.gru = nn.GRU( input_size=n_emb, hidden_size=n_h, batch_first=True, num_layers=num_layers, ) self.fc = nn.Linear(n_h*2, self.n_out) #全結合層の導入 def forward(self, x, h_encoder, y_encoder): y = self.embedding(x) # 単語をベクトルに変換 y = self.embedding_dropout(y) y, h = self.gru(y, h_encoder) #ここでは出力と最後の時刻の隠れ層の値が渡される # Attention y_tr = torch.transpose(y, 1, 2) # 次元1と次元2を入れ替える ed_mat = torch.bmm(y_encoder, y_tr) # バッチごとに行列積 attn_weight = F.softmax(ed_mat, dim=1) # attention weightの計算 attn_weight_tr = torch.transpose(attn_weight, 1, 2) # 次元1と次元2を入れ替える context = torch.bmm(attn_weight_tr, y_encoder) # コンテキストベクトルの計算 y = torch.cat([y, context], dim=2) # 出力とコンテキストベクトルの合流 y = self.fc(y) y = F.softmax(y, dim=2) return y, h #seq2seqクラスを作成します class Seq2Seq(nn.Module): def __init__(self, encoder, decoder, is_gpu=True): super().__init__() self.encoder = encoder self.decoder = decoder self.is_gpu = is_gpu if self.is_gpu: self.encoder.cuda() self.decoder.cuda() def forward(self, x_encoder, x_decoder): #順伝播メソッドの設定 訓練時につかう if self.is_gpu: x_encoder = x_encoder.cuda() x_decoder = x_decoder.cuda() batch_size = x_decoder.shape[0] #x_decoderは(バッチサイズ,時系列の数,ニューロン数) n_time = x_decoder.shape[1] y_encoder, h = self.encoder(x_encoder) y_decoder = torch.zeros(batch_size, n_time, self.decoder.n_out) #バッチサイズ*時系列の数*出力数のすべて0のテンソル if self.is_gpu: y_decoder = y_decoder.cuda() #教師強制と呼ばれる方法で各時刻の出力が次の入力に近くなるよう学習していきます。 for t in range(0, n_time): #各時刻で処理 x = x_decoder[:, t:t+1] y, h = self.decoder(x, h, y_encoder) y_decoder[:, t:t+1, :] = y #得られた出力yを各時刻ごとにy_encoderに格納 return y_decoder def predict(self, x_encoder): #予測に使用 文章の生成、評価するときに使う if self.is_gpu: x_encoder = x_encoder.cuda() batch_size = x_encoder.shape[0] n_time = x_encoder.shape[1] y_encoder, h = self.encoder(x_encoder) y_decoder = torch.zeros(batch_size, n_time, dtype=torch.long) #ここではint型を用いるためtorch.long if self.is_gpu: y_decoder = y_decoder.cuda() y = torch.ones(batch_size, 1, dtype=torch.long) * input_field.vocab.stoi["<sos>"] #予測用では各時刻で処理を行う必要がある。出力が次の入力になるから for t in range(0, n_time): x = y #ここでまえの出力を入力に if self.is_gpu: x = x.cuda() y, h = self.decoder(x, h, y_encoder) y = y.argmax(2) #yの中で最も大きい数値のインデックス得ることで最適な文章が生成 y_decoder[:, t:t+1] = y return y_decoder #評価関数を定義します def evaluate(model, iterator): model.eval() # 評価モードにできる batch = next(iter(iterator)) x = batch.inp_text y = model.predict(x) for i in range(x.size()[0]): inp_text = "" for j in range(x.size()[1]): word = input_field.vocab.itos[x[i][j]] if word=="<pad>": break inp_text += word rep_text = "" for j in range(y.size()[1]): word = reply_field.vocab.itos[y[i][j]] if word=="<eos>": break rep_text += word print("input:", inp_text) print("reply:", rep_text) print() is_gpu = True # GPUを使用するかどうか n_h = 800 #隠れ層のニューロン数 n_vocab_inp = len(input_field.vocab.itos) #入力文の長さ n_vocab_rep = len(reply_field.vocab.itos) #応答文の長さ n_emb = 300 #埋め込みベクトルの要素数 n_out = n_vocab_rep #出力の数 early_stop_patience = 5 # 早期終了のタイミング(誤差の最小値が何回更新されなかったら終了か) num_layers = 1 #中間層の数 bidirectional = True dropout = 0.1 clip = 100 #勾配の上限 # Seq2Seqのモデルを構築します encoder = Encoder(n_h, n_vocab_inp, n_emb, num_layers, bidirectional, dropout=dropout) decoder = Decoder(n_h, n_out, n_vocab_rep, n_emb, num_layers, dropout=dropout) seq2seq = Seq2Seq(encoder, decoder, is_gpu=is_gpu) # 誤差関数(今回は分類問題なのでクロスエントロピー誤差を使います) loss_fnc = nn.CrossEntropyLoss(ignore_index=reply_field.vocab.stoi["<pad>"]) # 最適化アルゴリズム optimizer_enc = optim.Adam(seq2seq.parameters(), lr=0.0001) optimizer_dec = optim.Adam(seq2seq.parameters(), lr=0.0005) record_loss_train = [] record_loss_test = [] min_losss_test = 0.0 # 学習 for i in range(1000): #1000epoch 学習 seq2seq.train() loss_train = 0 for j, batch in enumerate(train_iterator): inp, rep = batch.inp_text, batch.rep_text x_enc = inp x_dec = rep[:, :-1] y_dec = seq2seq(x_enc, x_dec) t_dec = rep[:, 1:] t_dec = t_dec.cuda() if is_gpu else t_dec loss = loss_fnc( y_dec.view(-1, y_dec.size()[2]), t_dec.reshape(-1) ) loss_train += loss.item() optimizer_enc.zero_grad() optimizer_dec.zero_grad() loss.backward() nn.utils.clip_grad_norm_(encoder.parameters(), clip) nn.utils.clip_grad_norm_(decoder.parameters(), clip) optimizer_enc.step() optimizer_dec.step() if j%1000==0: print("batch:", str(j)+"/"+str(len(train_data)//batch_size+1), "loss:", loss.item()) loss_train /= j+1 record_loss_train.append(loss_train) # 評価モードにします seq2seq.eval() loss_test = 0 for j, batch in enumerate(test_iterator): inp, rep = batch.inp_text, batch.rep_text x_enc = inp x_dec = torch.ones(rep.size(), dtype=torch.long) * reply_field.vocab.stoi["<sos>"] x_dec[:, 1:] = rep[:, :-1] y_dec = seq2seq(x_enc, x_dec) t_dec = rep.cuda() if is_gpu else rep loss = loss_fnc( y_dec.view(-1, y_dec.size()[2]), t_dec.view(-1) ) loss_test += loss.item() loss_test /= j+1 record_loss_test.append(loss_test) if i%1 == 0: print("Epoch:", i, "Loss_Train:", loss_train, "Loss_Test:", loss_test) print() evaluate(seq2seq, test_iterator) #早期終了の設定をします latest_min = min(record_loss_test[-(early_stop_patience):]) # 直近の最小値 if len(record_loss_test) >= early_stop_patience: if latest_min > min_loss_test: # 直近で最小値が更新されていなければ print("Early stopping!") break min_loss_test = latest_min else: min_loss_test = latest_min torch.save(seq2seq.state_dict(), path2+"chat_bot.pth") #学習済みのモデルのステータスを保存 ③学習済みモデルを使ってユーザーの入力に対し応答を予測し、出力 最後に、ユーザーの入力を解析し、応答を出力するといった簡易的なチャットボットをノートブック上で動かします。 今回はユーザーが"さようなら"と入力するまで会話を続けることにしました。 以下が、コードになります。 qiita3.py #モデルの読み込み import dill path2 = "/content/drive/My Drive/対話コーパス 前処理/NTT data/" input_field = torch.load(path2+"input_field.pkl", pickle_module=dill) #インデックスと単語の対応付け reply_field = torch.load(path2+"reply_field.pkl", pickle_module=dill) is_gpu = True n_h = 800 n_vocab_inp = len(input_field.vocab.itos) n_vocab_rep = len(reply_field.vocab.itos) n_emb = 300 n_out = n_vocab_rep early_stop_patience = 5 # 早期終了のタイミング(誤差の最小値が何回更新されなかったら終了か) num_layers = 1 bidirectional = True dropout = 0.0 clip = 100 encoder = Encoder(n_h, n_vocab_inp, n_emb, num_layers, bidirectional) decoder = Decoder(n_h, n_out, n_vocab_rep, n_emb, num_layers, dropout=dropout) seq2seq = Seq2Seq(encoder, decoder, is_gpu=is_gpu) seq2seq.load_state_dict(torch.load(path2+"chat_bot.pth", map_location=torch.device("cpu"))) #パラメータ読み込んでCPU対応 janome_tk = Tokenizer() #入力に対して応答文生成する関数 定義 def reply(inp_text, tokenizer, max_length=10): wakati_list = [token for token in janome_tk.tokenize(inp_text, wakati=True)] word_index = [] for word in wakati_list: index = input_field.vocab.stoi[word] word_index.append(index) x = torch.tensor(word_index) #テンソル型に変換する x = x.view(1, -1) #バッチサイズの1に調整 y = seq2seq.predict(x, max_length) #予測文がインデックスで出力 reply_text = "" for j in range(y.size()[1]): word = reply_field.vocab.itos[y[0][j]]#インデックスを単語に変換 if word=="<eos>": break reply_text += word reply_text = reply_text.replace("<sos>", "") #不要なものを削除 reply_text = reply_text.replace("<eos>", "") reply_text = reply_text.replace("<pad>", "") reply_text = reply_text.replace("<unk>", "") return reply_text #ノートブック上でAIと会話してみる bot_name = "chat AI bot" your_name = input("あなたの名前を教えてください:") print() print(bot_name + ": " + "こんにちは!" + your_name + "さん" ) message = "" while message != "さようなら": message = input(your_name+ ": ") response = reply(message, janome_tk , max_length=10) print(bot_name + ": " + response) print(bot_name + ": 会話が出来て嬉しかったです!") 5.結果 結果は以下のような感じになりました。 result.py あなたの名前を教えてください:mrtts0622 chat AI bot: こんにちは!mrtts0622さん mrtts0622: こんにちは chat AI bot: こんにちはこんにちは mrtts0622: かなり暖かくなってきましたね chat AI bot: mrtts0622: お元気ですか? chat AI bot: 今日はいいですね mrtts0622: 夏の風物詩といえば? chat AI bot: 海水浴スイカも好きです mrtts0622: 少しおなかがすきました chat AI bot: 海水浴か? mrtts0622: いや、なんでやねん chat AI bot: が好きです mrtts0622: さようなら chat AI bot: chat AI bot: 会話が出来て嬉しかったです! 話し手が相手の意図を読み取ることで何とか会話が成立していますね。さすがにツッコミにまでは対応できなかったようです( ´∀` ) また、会話のデータに偏りがあったのか夏の話題には強いようで会話の節々に夏好きがあふれている気がします。 6.まとめ PyTorchによるチャットボットの作成はかなり複雑で一行一行なにを行っているか理解するのにかなり時間がかかってしまいました。 また、精度を上げることもなかなか骨が折れる作業だったのでやはりLINEやGoogleはとても偉大であるということが実感できる制作でもありました。 今後の課題としては、   ・Bertをつかって前後の文脈も考慮したより自然なボットを作る。   ・twitterなどで、より自然でかつ大量の会話データを用意する。  などが挙げられそうです。 また、僕自身の課題として来月はKaggleにも挑戦しようと思っています。 最後までお付き合いいただきありがとうございました! 参考にさせてもらった文献 雑談会話コーパス-対話破綻検出チャレンジhttps://sites.google.com/site/dialoguebreakdowndetection/chat-dialogue-corpus ○ 雑談対話APIサイト https://www.nttdocomo.co.jp/service/developer/smart_phone/analysis/chat/ udemyの講座:人工知能(AI)を搭載したTwitterボットを作ろう https://www.udemy.com/ PyTorchチュートリアル(日本語翻訳版) https://yutaroogawa.github.io/pytorch_tutorials_jp/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[python] 仮想通貨APIからローソク足チャートを作る簡単な方法

仮想通貨のAPIから日足データを取得してローソク足チャートを作ろうと調べてみたら、意外と簡単にできたので記事にしておきます。 大まかな手順は以下の通り APIからデータ取得 データをpandasのDataFrameに変換、成形 mplfinanceで描画 ローソク足チャートを作るには描画部分で色々と複雑なのですが、mplfinanceというパッケージを使ったら簡単でした。 matplotlib/mplfinance: Financial Markets Data Visualization using Matplotlib pandas, Matplotlib(mplfinance)でローソク足チャートを作成 | note.nkmk.me チャート画像を保存する方法もありますが、ここではJupiterを使う前提で行います。 完成したコードは以下の通りです。 #%% import json import urllib.parse import urllib.request import pandas as pd import mplfinance as mpf # 1. APIからデータ取得 def get_api(symbol: str, year: int) -> str: endPoint = "https://api.coin.z.com/public" klines = "/v1/klines" params = {"symbol": symbol, "interval": "1day", "date": year} url = endPoint + klines + "?" + urllib.parse.urlencode(params) req = urllib.request.Request(url) with urllib.request.urlopen(req) as response: return response.read() # 2. データをpandasのDataFrameに変換、成形 def json_to_df(json_str: str) -> pd.DataFrame: d = json.loads(json_str) df = pd.json_normalize(d, record_path="data") df = df.astype("float64") df["openTime"] = pd.to_datetime(df["openTime"], unit="ms") df.set_index("openTime", inplace=True) df.columns = ["Open", "High", "Low", "Close", "Volume"] return df.tz_localize("UTC").tz_convert("Asia/Tokyo") # 3. mplfinanceで描画 def plot(symbol: str, year: int, length: int) -> None: json_str = get_api(symbol, year) df = json_to_df(json_str) kwargs = dict(type="candle", volume=True, figratio=(12, 4)) mpf.plot(df[-length:], **kwargs, style="yahoo") plot("ETH", 2021, 50) いくつかポイントがあるので解説します。 1. APIからデータ取得 def get_api(symbol: str, year: int) -> str: endPoint = "https://api.coin.z.com/public" klines = "/v1/klines" params = {"symbol": symbol, "interval": "1day", "date": year} url = endPoint + klines + "?" + urllib.parse.urlencode(params) req = urllib.request.Request(url) with urllib.request.urlopen(req) as response: return response.read() GMOコインのAPIを使っています。 KLine情報の取得 – APIドキュメント| GMOコイン 特に難しいことはしていません。取得したJSON文字列をそのまま返しています。 2. データをpandasのDataFrameに変換、成形 def json_to_df(json_str: str) -> pd.DataFrame: d = json.loads(json_str) df = pd.json_normalize(d, record_path="data") df = df.astype("float64") df["openTime"] = pd.to_datetime(df["openTime"], unit="ms") df.set_index("openTime", inplace=True) df.columns = ["Open", "High", "Low", "Close", "Volume"] return df.tz_localize("UTC").tz_convert("Asia/Tokyo") まずjson.loadsで文字列をオブジェクト化します。 取得したJSONは以下のような形式です。 { "status": 0, "data": [ { "openTime": "1609448400000", "open": "2976000", "high": "3035000", "low": "2960005", "close": "3016650", "volume": "420.6594" }, { "openTime": "1609534800000", "open": "3012280", "high": "3437650", "low": "3000000", "close": "3300001", "volume": "838.9068" },# 以下略 ] } dataの部分だけ欲しいので取り出します。 df = pd.json_normalize(d, record_path="data") df = df.astype("float64")で型をfloat64にしています。bitcoinなんかはintで良さそうなのですが、銘柄によっては日本円換算で1円以下のものもあるのでfloatにしています。 pandasのデータ型dtype一覧とastypeによる変換(キャスト) | note.nkmk.me 次にAPIのopenTimeをTimestamp型にしてからindexにします。 pd.to_datetimeで型変換しますが、APIのopenTimeはUNIX時間のミリ秒なので引数にunit="ms"と加えます。 Pandasで時間や日付データに変換するto_datetime関数の使い方 - DeepAge df.set_indexでインデックスにします。 df["openTime"] = pd.to_datetime(df["openTime"], unit="ms") df.set_index("openTime", inplace=True) mplfinanceでチャート化する時に読み込めるようにカラム名を変更、最後にタイムゾーンを設定してdataframeの成形は完了です。 df.columns = ["Open", "High", "Low", "Close", "Volume"] return df.tz_localize("UTC").tz_convert("Asia/Tokyo") Open High Low Close Volume openTime 2021-01-01 06:00:00+09:00 76525.0 77695.0 73635.0 75270.0 2512.41 2021-01-02 06:00:00+09:00 75259.0 80970.0 74100.0 79199.0 3681.04 2021-01-03 06:00:00+09:00 78200.0 100000.0 77761.0 97404.0 7977.53 2021-01-04 06:00:00+09:00 97893.0 120419.0 90315.0 106086.0 18817.36 2021-01-05 06:00:00+09:00 105853.0 116900.0 100535.0 113100.0 7078.96 3. mplfinanceで描画 def plot(symbol: str, year: int, length: int) -> None: json_str = get_api(symbol, year) df = json_to_df(json_str) kwargs = dict(type="candle", volume=True, figratio=(12, 4)) mpf.plot(df[-length:], **kwargs, style="yahoo") 先程の関数を使い、APIから取得してdataframeに変換します。 あとはmpf.plotするだけです。style="yahoo"としていますが、ここで色々なデザインを設定できます。 mplfinance/styles.ipynb at master · matplotlib/mplfinance 実行 plot("ETH", 2021, 50) この例ではイーサリアムの2021年データを取得して50日分表示しています。 事前にpip install mplfinanceするのを忘れるとエラーが出るので注意。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonのリアルタイム性能

pythonでsleep(遅延)したときに、実際にどれ位の時間スリープするかという話です。マイクロ秒単位のリアルタイム処理が可能みたいです。ここで「リアルタイム性」とは、狙った時刻に処理を行えるという性質です。OSに依存する話で、以下の話はラズパイ4B上のリアルタイムパッチが当てられたDebian 11 BullseyeのLinuxカーネルと普通のカーネル実験しました。 Linux上のプログラムの実行優先度の変更 nice ナイス値(-20 〜 +19)を変化させます。ナイス値が小さいほうが優先して実行されるます。最も優先して実行するには nice --20 やりたいこと のようにプログラムを起動します。 chrt ナイス値の調整よりもさらに優先度を上げたい場合や下げたい使います。割り込み処理も含めてあらゆる処理を押しのけて実行させたい場合 chrt --fifo 99 やりたいこと とし、他に何もやることが無い場合にだけ実行させたい場合 chrt --idle 0 やりたいこと とします。 実験 実験の下準備 apt-get install stress-ng linux-cpupower cpupower frequency-set -g performance でCPU動作周波数を最高値に固定します stress-ng --timeout 300 --parallel 0 --class pipe & でOSとすべてのCPUに負荷を掛けます。pipe を interrupt に変えるとよりキビシクなります。 実験結果 狙った遅延と平均の誤差をマイクロ秒単位で表示しています。RTと標準はそれぞれLinuxのカーネルの種類を表します。nice -19 および chrt --idle 0 で起動したら遅延の計測プログラムが終了しなかったので、結果を載せていません。 実行優先度・OS種別\狙った遅延 100 1000 10000 100000 1000000 10000000 普通に起動・標準 47403.7 1216.6 14996.0 164.6 1061.6 8126.5 普通に起動・RT 79.9 69.1 85.5 223.6 1082.1 10211.5 nice --20・標準 2404.2 85.8 32285.5 162.5 982.1 7887.1 nice --20・RT 132.0 89.1 120.8 174.0 3862.4 10071.6 chrt --fifo 99・標準 30.6 38.5 64.3 61.8 69.7 63.1 chrt --fifo 99・RT 31.6 21.0 44.6 56.1 64.7 66.4 リアルタイム性が必要な場合は chrt --fifo 99 で起動するのがよいです。リアルタイムパッチを当てなくても chrt --fifo 99 でかなりのリアルタイム応答性が実現できるようです。 実験用プロググラム 以下のプログラムを用いて狙った遅延と実際の遅延の差(誤差)をマイクロ秒単位で測っています。time_ns() がPython 3.7で導入されたため、Python 3.7かそれ以降ではないと動作しません。使用したPythonバージョンはDebian 11標準の3.9です。 import time for 狙った遅延 in (0.0001, 0.001, 0.01, 0.1, 1.0, 10.0): print('狙った遅延=' + str(狙った遅延)) 誤差の合計 = 0 for i in range(3): 前の時刻 = time.time_ns() time.sleep(狙った遅延) 後の時刻 = time.time_ns() 誤差 = (後の時刻 - 前の時刻) / 1000000000 - 狙った遅延 print('誤差={:.1f}マイクロ秒'.format(誤差*1000000)) 誤差の合計 += 誤差 print('平均の誤差={:.1f}マイクロ秒'.format(1000000 * 誤差の合計 / 3)) 上記のpythonプログラムを以下のシェルスクリプトで起動しました #!/bin/sh export LANG=C.UTF-8 exec </dev/null >>log.txt 2>&1 set -ex date uname -a cpupower frequency-set -g performance # 次の行で pipe を interrupt に変えるとよりキビシクなります stress-ng --timeout 300 --parallel 0 --class pipe & python3 sleep-test.py nice --20 python3 sleep-test.py chrt --fifo 99 nice --20 python3 sleep-test.py nice -19 python3 sleep-test.py #chrt --idle 0 nice -19 python3 sleep-test.py pkill stress cpupower frequency-set -g schedutil
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python:Djangoで簡単なWebアプリの開発

はじめに Udemyで初心者向けのDjangoの研修があったので、受講しました。学んだことを忘れないようまとめた、個人的な備忘録です。Djangoを使って簡単なHello Worldを表示するアプリを作成します。 動作環境 OS:MacOS 11.2.3 (Big Sur) Python:3.9.0 Django:3.2 環境の構築 既にpythonとpyenv、pipはインストール済みだったので、以下のコマンドで簡単にインストールできた。 ~% pip install django プロジェクトの作成 適当なディレクトリの配下にcdして、以下のコマンドを入力。 ~% django-admin startproject プロジェクト名 プロジェクト名のディレクトリが作成され、関連するテンプレートの.pyファイルが作成されます。 開発用のWebサーバーを起動 プロジェクト名のディレクトリにcdして、以下のコマンドを実行。 ~% python manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). May 01, 2021 - 06:40:28 Django version 3.2, using settings 'myblogapp.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ブラウザで、localhost:8000でデフォルトの起動画面が確認できます。 初期設定とマイグレーション プロジェクトディレクトリ配下にある、setting.pyの以下のところを日本の設定にします。 # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ #LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'ja' #TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Tokyo' LANGUAGE_CODEをja、TIME_ZONEをAsia/Tokyoに変更します。 これで、開発サーバーを再起動させてもう一度localhost:8000をブラウザで確認すると、デフォルトの起動画面が日本語化されて表示されます。 Djangoのさまざまな機能を使うためにマイグレーションします。 ~% python manage.py migrate アプリケーションの作成 プロジェクトディクレトリの直下で、以下のコマンドを実行します。 ~% python manage.py startapp アプリケーション名 アプケーション名のディクレトリのその中にテンプレートファイルが自動作成されます。 このアプリケーションからWebから呼べるようにするために、setting.pyを修正します。 アプリケーション名のディクレトリの直下にapps.pyがあり、ここで定義されたクラスのメソッドを呼び出すための追記を行います。 セミナーでは、postsという名前のアプリケーションを作りましたので、下の記載はそれを元にしています。 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'posts.apps.PostsConfig' ] 最後の行のposts.apps.PostsConfigが追記したところになります。 アプリケーションへのルーティングの設定 ルーティングの設定は、urls.pyに記載します。 プロジェクトディクレトリにあるurls.pyにルーティングのパターンを追記していきますが、アプリケーションが複数になる場合は、アプリケーションのディクレクトリ内に新たにurls.pyを作成しルーティングパターンを作成するのが管理上も複雑にならずに良いです。 (プロジェクトのルートurls.py) → (アプリのurls.py) → (アプリのviews.py) というルーティングになります。まずは、簡単にHello Worldを出力するWebアプリを作成します。 具体的なコーディング例 アプリのviews.py from django.shortcusts import render from django.http import HttpResponse def index(request): return HttpResponse("Hello World!") # Create uour views here: アプリのurls.py from django.conf.urls import url from . import views urlspatterns = [url(r'^$', views.index, name='index')] プロジェクトのurls.py from django.contrib import admin from django.urls import path from django.conf.urls import include, url urlpatterns = [ path('post/', include('post.urls')), path('admin/', admin.site.urls), ] これで、 urlに"post/"が指定された場合は、post/urls.pyにルーティングされ、 posts/urls.pyにて、viewsパッケージのindex関数にルーティングされる ようになります。 テンプレートファイルを読み込んでWebに表示する 通常は、webページのコーディングを全てviews.pyに含めることはなくて、htmlファイルでページ作成し、ここにPythonのコードを埋め込んで動的ページを作成します。 テンプレートを格納するディレクトリは、アプリディレクトリの直下に「templates/アプリ名」のディレクトリを作成します。 プロジェクトDir   └アプリDir    └templates └アプリ名と同じDir  ← ここにテンプレートのHTMLファイルを格納する。 上記のディレクトリに簡単な、index.htmlを作成して格納します。 index.html <!DOCTYPE html> <html lang="ja-jp"> <head> <title>投稿一覧</title> </head> <body> <h2>これは投稿一覧のページです!</h2> </body> </html> views.pyも、templatesディレクトリにあるHTMLを読み込むように修正します。 views.pyの修正 from django.shortcuts import render from django.http import HttpResponse def index(request): return render(request, 'posts/index.html') # Create your views here. これで再びブラウザから、https://localhost:8000/posts/を見ると上記のindex.htmlが表示されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python環境(windows10)(2021年)

windows10でのpython環境を作成するときの一例 pyenvでpythonバージョンを切り替える パッケージはvenvで個々に管理する 全体の流れ pythonのインストール pyenv-winのインストール 必要に応じてvenv 1. pythonインストール 公式のインストーラーでインストール add pythonXX to PATH にチェックをいれないと面倒なのでチェックを必ず入れる それ以外はデフォルトのまま (必要な場合はカスタマイズしてください) 2. pyenv-winのインストール pyenv-win cmd pip install pyenv-win --target %USERPROFILE%/.pyenv ※ pythonインストール時にPATHに追加し忘れて、C:\Users\ユーザー名\AppData\Local\Programs\Python\Python39だけPATHに手で追加したがpipが動かないので先頭にpython -m をつけて(これ↓)実行した) cmd pipを直接指定して動かない場合 python -m pip install pyenv-win --target %USERPROFILE%/.pyenv ※PowerShell(or Git Bash)の場合は変数の名前が異なる。(\$HOMEを使用する) powershell pip install pyenv-win --target $HOME\.pyenv これで、C:\Users\ユーザー名.pyenv が作成される。 この2つのパスをPATHに追加する C:\Users\ユーザー名.pyenv\pyenv-win\bin は存在しているが 最初はshimsは無い。 %USERPROFILE%.pyenv\pyenv-win\bin %USERPROFILE%.pyenv\pyenv-win\shims 手で追加してもいいし、次のpowershellでやってもよい powershell(管理者実行が必要) [System.Environment]::SetEnvironmentVariable('PATH', $HOME + "\.pyenv\pyenv-win\bin;" + $HOME + "\.pyenv\pyenv-win\shims;" + $env:Path,"Machine") ※多くの解説記事にこれも書いてあるけどなくても動くかも 環境変数の「PYENV」に %USERPROFILE%.pyenv\pyenv-win を手で追加。または次のpowershell powershell [System.Environment]::SetEnvironmentVariable('PYENV',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") 新しくcmdかpowershellを立ち上げて(新しく立ち上げないと今設定した環境変数が反映されない) 正しく設定されているか確認。 pyenv --version pyenv 2.64.6.1 利用できるバージョンの確認 pyenv install --list または pyenv install -l (ずらずらと利用可能なバージョンが表示される) 使いたいバージョンを入れる pyenv install 3.7.9 : pyenv install 3.9.4 使いそうなものをいくつか入れておけば、瞬時に切り替えられる。(後述) installしたものは、versionsで確認できる pyenv versions 3.7.9 3.9.4 切り替えるときは pyenv local 3.9.4 local を使って切り替える。 local はユーザごとの環境を変えるコマンド。全ユーザに影響するのはglobal 。 (私はlocalしか使ったことがない) このコマンドを叩いたディレクトリに.python-versionという、バージョン番号が書かれたファイルが作成される。 その場所でpythonを実行するときは.python-versionに書かれたバージョンのpythonが実行される。 用途やプロジェクトに応じて、pythonのバージョンを切り替えられる。 現在設定されているバージョンの表示は pyenv version さっきのはversionsで複数形だったがこっちは単数形 バージョンがうまく切り替わらないとか、問題が発生した場合 pyenv rehash 今のところ使ったことはない。 3. 必要に応じてvenv 用途に応じてパッケージ プロジェクト(フォルダ)ごとにパッケージ管理を個別にしたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python環境(windows10)

windows10でのpython環境を作成するときの一例 pyenvでpythonバージョンを切り替える パッケージはvenvで個々に管理する 全体の流れ 1.pythonのインストール 2.pyenv-winのインストール 3.必要に応じてvenv 1. pythonインストール 公式のインストーラーでインストール add pythonXX to PATH にチェックをいれないと面倒なのでチェックを必ず入れる それ以外はデフォルトのまま (必要な場合はカスタマイズしてください) 2. pyenv-winのインストール pyenv-win cmd pip install pyenv-win --target %USERPROFILE%/.pyenv ※ pythonインストール時にPATHに追加し忘れて、C:\Users\ユーザー名\AppData\Local\Programs\Python\Python39だけPATHに手で追加したがpipが動かないので先頭にpython -m をつけて(これ↓)実行した) cmd pipを直接指定して動かない場合 python -m pip install pyenv-win --target %USERPROFILE%/.pyenv ※PowerShell(or Git Bash)の場合は変数の名前が異なる。(\$HOMEを使用する) powershell pip install pyenv-win --target $HOME\.pyenv これで、C:\Users\ユーザー名.pyenv が作成される。 この2つのパスをPATHに追加する C:\Users\ユーザー名.pyenv\pyenv-win\bin は存在しているが 最初はshimsは無い。 %USERPROFILE%.pyenv\pyenv-win\bin %USERPROFILE%.pyenv\pyenv-win\shims 手で追加してもいいし、次のpowershellでやってもよい (手で追加の場合は、先頭におくこと。  既存の "C:/Users/[user]/AppData/Local/Microsoft/WindowsApps" これより先に位置していないと、pythonコマンドを叩くとMicrosoftストアが表示されてしまう) powershell(管理者実行が必要) [System.Environment]::SetEnvironmentVariable('PATH', $HOME + "\.pyenv\pyenv-win\bin;" + $HOME + "\.pyenv\pyenv-win\shims;" + $env:Path,"Machine") ※多くの解説記事にこれも書いてあるけどなくても動くかも 環境変数の「PYENV」に %USERPROFILE%.pyenv\pyenv-win を手で追加。または次のpowershell powershell [System.Environment]::SetEnvironmentVariable('PYENV',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") 新しくcmdかpowershellを立ち上げて(新しく立ち上げないと今設定した環境変数が反映されない) 正しく設定されているか確認。 pyenv --version pyenv 2.64.6.1 利用できるバージョンの確認 pyenv install --list または pyenv install -l (ずらずらと利用可能なバージョンが表示される) 使いたいバージョンを入れる pyenv install 3.7.9 : pyenv install 3.9.4 使いそうなものをいくつか入れておけば、瞬時に切り替えられる。(後述) installしたものは、versionsで確認できる pyenv versions 3.7.9 3.9.4 切り替えるときは pyenv local 3.9.4 local を使って切り替える。 local はユーザごとの環境を変えるコマンド。全ユーザに影響するのはglobal 。 (私はlocalしか使ったことがない) このコマンドを叩いたディレクトリに.python-versionという、バージョン番号が書かれたファイルが作成される。 その場所でpythonを実行するときは.python-versionに書かれたバージョンのpythonが実行される。 用途やプロジェクトに応じて、pythonのバージョンを切り替えられる。 現在設定されているバージョンの表示は pyenv version さっきのはversionsで複数形だったがこっちは単数形 バージョンがうまく切り替わらないとか、問題が発生した場合 pyenv rehash 今のところ使ったことはない。 3. 必要に応じてvenv 仮想環境を作る 仮想環境ごとにパッケージを管理できるので、 用途やプロジェクトごとにそれ用のパッケージをインストールできる。 プロジェクトA:pandas1.0、numpy1.8 プロジェクトB:pandas1.2、numpy1.19 を使いたい場合とかに以下のように2つ仮想環境作って、切り替えて使用する。 プロジェクトA -> 仮想環境A プロジェクトB -> 仮想環境B 仮想環境は任意の場所にフォルダ名(仮想環境名)を指定して作成できる。 cmd python -m venv <任意の名前> この名前にすることが多い python -m venv .venv 切り替えるときはその場所のスクリプトを叩く。 cmdまたはVSCodeのターミナルなど 仮想環境名/Scripts/activate.bat settings.json // VSCodeでは ".vscode\settings.json" にこのスクリプトが実行されるように設定する { "python.pythonPath": ".venv\\Scripts\\python.exe", /* お好みで "python.linting.flake8Args": [ "--max-line-length=120", "--max-complexity","20" ], "python.dataScience.textOutputLimit": 0, "editor.mouseWheelZoom": true */ } Anaconda、virtualenv、pipenvなどpythonの環境管理はさまざまありますが、いろいろ試した結果この形に落ち着きました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コードテストで速度測定済!PythonによるAtCoderスニペット集 (2)応用編

これは更新型記事です。 応用編では、典型アルゴリズム別に、最新の「AtCoder 競プロ典型90問」を含め、実際の問題を積極的に取り上げていきます。また、本記事のコンセプトに沿って、パラメータを変えて速度測定を行います。 記事全体の説明は基本編を参照ください。 (1) 基本編 (2) 応用編(本稿) 基本編では、競プロ用の入出力系スニペット、リスト処理等のイテレーション関数、素数や組合せ等の数学関数などをとりあげています。 1. 累積和 1.1. 累積和(基本) 区間の総和を求める際は、予め累積和を求めてから、その差分を求めます。 問題: 競プロ典型90問_010: Score Sum Queries N = int(input()) CP = [list(map(int, input().split())) for _ in range(N)] Q = int(input()) LR = [list(map(int, input().split())) for _ in range(Q)] # クラス1、2の累積和をP1、P2とする -> O(N) P1, P2 = [0], [0] for c, p in CP: if c == 1: P1.append(P1[-1] + p) P2.append(P2[-1]) else: P1.append(P1[-1]) P2.append(P2[-1] + p) # 累積和の差分として、解答を出力する -> 1回ごとO(1) × Q for l, r in LR: print(P1[r] - P1[l-1], P2[r] - P2[l-1]) N=Q= 10**4 10**5 10**6 Python3.8 40ms 184ms 1965ms PyPy3 95ms 145ms 929ms ナイーブな解法では計算量は$O(NQ)$になりますが、累積和を使うことで$O(N+Q)$になります。 (速度計測準備中) 1.2. 累積和(いもす法) 累積和のアルゴリズムを多次元・他次数に拡張したものを、いもす法と呼びます。2次元・1次数版が頻出です。 問題: 競プロ典型90問_028: Cluttered Paper この問題の場合、重った紙の枚数が、紙の開始点での+1と終了点での-1の累積和で求められることに気づけば、累積和を2次元に応用することで、解くことができます。 N = int(input()) H = W = 1000 S = [[0] * (W + 1) for _ in range(H + 1)] # 四隅に差分をセット(2次元が重なった差分は+1とする) -> O(N) for _ in range(N): lx, ly, rx, ry = map(int, input().split()) S[ly][lx] += 1 S[ry][lx] -= 1 S[ly][rx] -= 1 S[ry][rx] += 1 # 累積和をとることで、Sは各座標の紙の枚数になる -> O(HW) for x in range(W + 1): for y in range(H): S[y + 1][x] += S[y][x] for x in range(W): for y in range(H + 1): S[y][x + 1] += S[y][x] # 紙の枚数ごとに集計することで、解答を得る -> O(HW) ans = [0] * (N + 1) for x in range(W + 1): for y in range(H + 1): ans[S[y][x]] += 1 for n in range(1, N + 1): print(ans[n]) ナイーブな解法では計算量は$O(NHW)$になりますが、いもす法を使うことで$O(N+HW)$になります。 N 10**5 10**6 10**7 Python3.8 783ms 1503ms 9001ms PyPy3 154ms 307ms 1792ms 2. 探索 2.1. bit全探索 なんからのon/offの選択肢を組み合わせて全探索する時に、bit全探索が活躍します。 問題: 競プロ典型90問_002: Encyclopedia of Parentheses bitシフトと&を組み合わせてbitテストするのが、典型的な書き方です。 N = int(input()) def parentheses_repr(bit): # bitを括弧表現に変換 res = [] for n in range(N): if (bit >> n) & 1: # bitテスト res.append(')') else: res.append('(') return ''.join(res[::-1]) # 逆順を元に戻す for bit in range(2 ** N): nest = 0 # 括弧の深さ for n in range(N): # 低位ビットを括弧列の右側(逆順)とする if (bit >> n) & 1: # bitテスト nest += 1 # bit==1を')'として、深さを増やす else: nest -= 1 # bit==0を'('として、深さを減らす if nest < 0: # 深さがマイナスになっらた条件不一致 break if nest == 0: # 最後に深さが0なら条件一致 print(parentheses_repr(bit)) 計算量は$O(2^N)$であり、Nが大きくなると急速に計算時間が増えます。N=20が出題されますので、PyPy3を使いましょう。なお、この問題に限り、固定となる両端を除いて処理をすることで、Nを2つ減らすことができます。 N 10 15 20 Python3.8 29ms 69ms 1413ms PyPy3 75ms 87ms 280ms 2.2. 二分探索 ソートされたリストから特定の値を効率良く探索するのに、二分探索が利用できます。 2.2.1. bisectの利用 探索すべきリストが予め与えられている場合は、bisectを使います。 問題: 競プロ典型90問_007: CP Classes N = int(input()) A = sorted(list(map(int, input().split()))) Q = int(input()) B = [int(input()) for _ in range(Q)] import bisect for b in B: left = bisect.bisect_left(A, b) right = bisect.bisect_right(A, b) cp = float('inf') if left < N: cp = min(cp, abs(b - A[left])) if left > 0: cp = min(cp, abs(b - A[left - 1])) if right < N - 1: cp = min(cp, abs(b - A[right + 1])) print(cp) 問題: ABC077_C: Snuke Festival N = int(input()) A = sorted(map(int, input().split())) B = sorted(map(int, input().split())) C = sorted(map(int, input().split())) import bisect # 各Aの下に位置することができる最小のBのインデックス B_min_under_A = [bisect.bisect_right(B, A[n]) for n in range(N)] # 各Bの下に位置することができるCの小計数 C_num_under_B = [N - bisect.bisect_right(C, B[n]) for n in range(N)] # 高速化のために累積和を前処理しておく Sum_of_upper_b_min_of_C_num_under_B = [0] for n in range(N)[::-1]: Sum_of_upper_b_min_of_C_num_under_B.append( Sum_of_upper_b_min_of_C_num_under_B[-1] + C_num_under_B[n]) Sum_of_upper_b_min_of_C_num_under_B = Sum_of_upper_b_min_of_C_num_under_B[::-1] ans = 0 for b_min in B_min_under_A: ans += Sum_of_upper_b_min_of_C_num_under_B[b_min] print(ans) 計算量は、ソートで$O(N \log N)$、二分探索1回あたり$O(\log N)$であるため二分探索の部分の小計は$O(N \log N)$、累積和の前処理で$O(N)$、最後の集計で$O(N)$です。よって、全体として$O(N \log N)$になります。ベンチマークとしては、初期のA、B、Cをソートの逆順にして、全ての組合せが問題条件を満たすようにして、測定しました。 N 10**4 10**5 10**6 Python3.8 41ms 177ms 1572ms PyPy3 91ms 239ms 402ms 2.2.2. 二分探索のスクラッチ実装 探索すべきリストが予め与えられていない場合は、二分探索をスクラッチで実装します。探索区間を順次半分に縮小していくことで、高速な探索が可能です。 問題: 競プロ典型90問_001: Yokan Party N, L = map(int, input().split()) K = int(input()) A = list(map(int, input().split())) + [L] import bisect def check(n): k = 0 left = 0 for a in A: if a - left >= n: k += 1 left = a return k > K low, up = 0, L while low + 2 <= up: n = (low + up) // 2 if check(n): low = n else: up = n ans = up if check(up) else low print(ans) 問題: ABC192_D: Base n X = input() M = int(input()) # Xの桁数 == 1 の場合の処理 if len(X) == 1: print(1 if int(X) <= M else 0) exit() # 以降は、Xの桁数 > 1 # Xをn進数と見た場合に <= M かどうかチェックする def check(n): res = 0 for digit in X: res = res * n + int(digit) if res > M: return False return True # 出題条件よりnの下界(lower_bound)はすぐにわかる d = max(map(int, list(X))) n_low = d + 1 # 下界がM以下でなければ答えは0 if not check(n_low): print(0) exit() # 上界を探索 n_up = n_low while check(n_up): n_up *= 2 # [下界, 上界)を初期探索区間として、Mにピッタリする境界を2分探索 while n_low + 2 <= n_up: # 同じ値を繰り返さないように間隔を2空ける n_new = (n_low + n_up) // 2 # 探索区間を半分に縮小する if check(n_new): n_low = n_new else: n_up = n_new ans = (n_up if check(n_up) else n_low) - d print(ans) 計算量は、二分探索について$O(\log M)$です。ただし、MとXが巨大な時はチェック計算に時間を要します。ベンチマークではX='10'固定でMを巨大にしてみました。このベンチマーク結果としては、$O((\log M)^2)$と想定できる結果になっています。 M 10**int(10**(7*0.5)) 10**int(10**(8*0.5)) 10**int(10**(9*0.5)) Python3.8 108ms 684ms 6209ms PyPy3 96ms 210ms 1187ms 3. グラフ グラフ構造特有のアルゴリズムです。迷路などの2次元構造を探索する際も利用します。 3.1. 深さ優先探索(DFS) グラフや迷路のゴールにたどり着けるかを調べる場合などに、DFSが使えます。 問題: ATC001_A: 深さ優先探索 3.1.1. 再帰 検知した新たな探索場所を再帰で呼び出します。また、訪問済みリストをseenで管理します。 再帰数の最大値を上げることを忘れないようにしましょう。 import sys sys.setrecursionlimit(10 ** 9) H, W = map(int, input().split()) c = [] for i in range(H): row = input() if 's' in row: start = (i, row.index('s')) c.append(row) seen = [[False] * W for _ in range(H)] def DFS(row, column): if (row < 0 or column < 0 or row >= H or column >= W or c[row][column] == '#' or seen[row][column]): # 正常性判断 return False if c[row][column] == 'g': # ゴール return True seen[row][column] = True # 探索済かどうかを記録 return (DFS(row - 1, column) | DFS(row + 1, column) | DFS(row, column - 1) | DFS(row, column + 1)) # 新たな探索場所を再帰呼び出し print('Yes' if DFS(*start) else 'No') 計算量は$O(H \times W)$です。ベンチマークは、正方形フィールドの左上にスタート、右下にゴールを設け、壁をなくした状態を、入力コード部分を置き換えて設定して、測定しました。サイズ1000*1000のデータをコードテストの標準入力で記述すると容量オーバーになるためです。 H*W 250*250 500*500 1000*1000 Python3.8 126ms 436ms 1792ms PyPy3 314ms 975ms 4721ms PyPy3の再帰は遅い、ということを、よく覚えておきましょう。 3.1.2. スタック 検知した新たな探索場所をtodoとしてdequeを使ったLIFOスタック構造にpushして、スタックが無くなるまでpopして処理をします。再帰と比較してやや複雑ですが、性能は向上します。 H, W = map(int, input().split()) c = [] for i in range(H): row = input() if 's' in row: start = (i, row.index('s')) c.append(row) from collections import deque def DFS(start): todo = deque() todo.append(start) # 初期探索場所をpush seen = [[False] * W for _ in range(H)] while len(todo) > 0: row, column = todo.pop() # LIFOでpop if (row < 0 or column < 0 or row >= H or column >= W or c[row][column] == '#' or seen[row][column]): # 正常性判断 continue if c[row][column] == 'g': # ゴール return True seen[row][column] = True # 探索済かどうかを記録 todo.append((row - 1, column)) # 新たな探索場所をpush todo.append((row + 1, column)) # 新たな探索場所をpush todo.append((row, column - 1)) # 新たな探索場所をpush todo.append((row, column + 1)) # 新たな探索場所をpush return False print('Yes' if DFS(start) else 'No') こちらも計算量は$O(H \times W)$です。再帰よりも全般的に高速で、なおかつPyPy3の高速性も発揮されています。 H*W 250*250 500*500 1000*1000 Python3.8 99ms 289ms 1153ms PyPy3 94ms 119ms 200ms 3.2. 幅優先探索(WFS) グラフや迷路の最短経路を調べる場合などに、WFSが使えます。 問題: ATC002_A: 幅優先探索 BFSのスタック版を、LIFOスタックからFIFOキューに変更するだけで、BFSが作れます。BFSの性質上、最小手順の1つは得られます。あわせて、位置だけでなく手数もキューに入れて記憶しておくことで、手数を求めることが可能です。 R, C = map(int, input().split()) sy, sx = map(int, input().split()) start = (sy - 1, sx - 1) gy, gx = map(int, input().split()) goal = (gy - 1, gx - 1) c = [] for i in range(R): c.append(input()) from collections import deque def BFS(start): todo = deque() todo.append((*start, 0)) # 初期探索場所をpush seen = [[False] * C for _ in range(R)] while len(todo) > 0: row, column, step = todo.popleft() # FIFOでpop if (row < 0 or column < 0 or row >= R or column >= C or c[row][column] == '#' or seen[row][column]): # 正常性判断 continue if (row, column) == goal: # ゴール return step seen[row][column] = True # 探索済かどうかを記録 todo.append((row - 1, column, step + 1)) # 新たな探索場所をpush todo.append((row + 1, column, step + 1)) # 新たな探索場所をpush todo.append((row, column - 1, step + 1)) # 新たな探索場所をpush todo.append((row, column + 1, step + 1)) # 新たな探索場所をpush return float('inf') print(BFS(start)) 計算量は$O(R \times C)$です。ベンチマークは、DFSと同様なダミーデータで行います。 R*C 250*250 500*500 1000*1000 Python3.8 135ms 458ms 1758ms PyPy3 130ms 143ms 352ms 3.3. ダイクストラ法 グラフの重み付き最短経路を調べる場合などに、ダイクストラ法が使えます。 問題: ABC035_D: トレジャーハント この問題は最初の町と(最初の町を含む)別の町を往復する最小時間を求め、それと各町のトレジャー価格との関係から解を求めるものです。往復する最小時間を求めるには、行きがけと帰りがけに分割して、それぞれにダイクストラ法を適用します。 3.3.1. 再帰 少し苦しいですが再帰で書くことができます。最短距離を高速で取り出せるようにヒープキューを使うのがポイントです。 import sys sys.setrecursionlimit(10 ** 9) N, M, T = map(int, input().split()) A = list(map(int, input().split())) adj_out = {} # 行きがけグラフ {始点: {(終点, コスト), ...}} adj_in = {} # 帰りがけグラフ {始点: {(終点, コスト), ...}} for _ in range(M): a, b, c = map(int, input().split()) adj_out[a - 1] = adj_out.get(a - 1, set()) adj_out[a - 1].add((b - 1, c)) adj_in[b - 1] = adj_in.get(b - 1, set()) adj_in[b - 1].add((a - 1, c)) import heapq def Dijkstra(pos, distance, h, unseen, adj, distances): unseen.remove(pos) for next_, cost in adj.get(pos, set()): if distance + cost < distances[next_]: distances[next_] = distance + cost # 最短距離を更新 heapq.heappush(h, (distance + cost, next_)) # 最短距離をヒープキューで管理 best_next = None while len(h) > 0 and best_next not in unseen: # 未訪問の頂点から最短距離のものを求める best_distance, best_next = heapq.heappop(h) if best_next in unseen: Dijkstra(best_next, best_distance, h, unseen, adj, distances) # 行きがけ distances_out = [float('inf')] * N distances_out[0] = 0 Dijkstra(0, 0, [], {n for n in range(N)}, adj_out, distances_out) # 帰りがけ distances_in = [float('inf')] * N distances_in[0] = 0 Dijkstra(0, 0, [], {n for n in range(N)}, adj_in, distances_in) # (T - 行きがけ最短距離 - 帰りがけ最短距離) * A の最大値が答え ans = max([max(0, T - x - y) * a for x, y, a in zip(distances_out, distances_in, A)]) print(ans) ダイクストラ法の計算量は、グラフの辺が頂点と同程度の量であるような疎な状態(本問題が好例です)を前提とすると$O(N \log N)$です。本問題は行きがけと帰りがけを計算するため、ベンチマークとしては、頂点き数Nが辺の数Mの約半分として、全ての辺を通る経路が最短距離になるように、グラフを構成しました。 N 10**4 10**5 10**6 Python3.8 46ms 237ms 3317ms PyPy3 111ms 351ms 3382ms やはりPyPy3の再帰は遅いです。 3.3.2. ヒープキューのみ こちらの方が自然ですね。 N, M, T = map(int, input().split()) A = list(map(int, input().split())) adj_out = {} # 行きがけグラフ {始点: {(終点, コスト), ...}} adj_in = {} # 帰りがけグラフ {始点: {(終点, コスト), ...}} for _ in range(M): a, b, c = map(int, input().split()) adj_out[a - 1] = adj_out.get(a - 1, set()) adj_out[a - 1].add((b - 1, c)) adj_in[b - 1] = adj_in.get(b - 1, set()) adj_in[b - 1].add((a - 1, c)) import heapq def Dijkstra(start, distance, adj): distances = [float('inf')] * N distances[start] = distance h = [] heapq.heappush(h, (distance, start)) unseen = {n for n in range(N)} while len(h) > 0: pos = None while len(h) > 0 and pos not in unseen: # 未訪問の頂点から最短距離のものを求める distance, pos = heapq.heappop(h) if pos not in unseen: break unseen.remove(pos) for next_, cost in adj.get(pos, set()): if distance + cost < distances[next_]: distances[next_] = distance + cost # 最短距離を更新 heapq.heappush(h, (distance + cost, next_)) # 最短距離をヒープキューで管理 return distances # 行きがけ distances_out = Dijkstra(0, 0, adj_out) # 帰りがけ distances_in = Dijkstra(0, 0, adj_in) # (T - 行きがけ最短距離 - 帰りがけ最短距離) * A の最大値が答え ans = max([max(0, T - x - y) * a for x, y, a in zip(distances_out, distances_in, A)]) print(ans) こちらも計算量は$O(N \log N)$です。ベンチマーク条件も再帰と同一にしました。 N 10**4 10**5 10**6 Python3.8 46ms 244ms 2928ms PyPy3 88ms 161ms 1108ms Python3.8では再帰とあまり変わりませんが、PyPy3ではかなり速くなっています。 3.4. Union Find 3.5. 強連結成分分解 SCC 4. 動的計画法 ある問題を、サイズの小さな部分問題に分割することを、動的計画法といいます。これまでの探索問題も一種の動的計画法でした。特に、動的計画法と呼ばれる問題の多くは、同一の部分問題を繰り返し呼び出すため、予め結果をキャッシュしておく「メモ化」と呼ばれるテクニックを併用します。 4.1. ナップザック問題(編集中) 制約条件下での最大化・最小化問題を、ナップザック問題といいます。 問題: ABC032_D: ナップサック問題 以下の漸化式を解くことで、答えが得られます。 \begin{align} dp[n][w] &= \left\{ \begin{array}{ll} max(dp[n-1][w], dp[n-1][w - w_n] + v_n) & (w \geq w_n) \\ dp[n-1][w] & (w \lt w_n) \end{array} \right. \\ dp[0][w] &= 0 \end{align} import sys sys.setrecursionlimit(10 ** 9) N, W = map(int, input().split()) v, w = [None], [None] # index=0はダミー for _ in range(N): v_, w_ = map(int, input().split()) v.append(v_) w.append(w_) def dp(N, W): if N == 0: return 0 if W >= w[N]: return max(dp(N - 1, W), dp(N - 1, W - w[N]) + v[N]) else: return dp(N - 1, W) print(dp(N, W)) 4.2. 最長増加部分列 4.3. 最長一致部分列 4.4. 耳DP 問題: 競プロ典型90問_008: AtCounter N = int(input()) S = input() target = 'atcoder' MOD = 10 ** 9 + 7 from functools import lru_cache import sys sys.setrecursionlimit(10 ** 9) @lru_cache(maxsize=None) def dp(n, s): if s == 0: return 1 if n == 0: return 0 if S[n-1] == target[s-1]: return (dp(n-1, s) + dp(n-1, s-1)) % MOD else: return dp(n-1, s) print(dp(N, len(target)))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Science】黒体放射の式Planckの法則(Planck's law)をPythonで計算&描写

はじめに 黒体放射の式Planckの法則(Planck's law)の計算と描写 今回は波長と周波数の関数を使用。 目次 Planck's law コード 波長の関数 周波数の関数 参考文献 Planck's law プランクの法則とは、 物質と熱平衡状態の放射が出すエネルギー密度(放射輝度)分布を表す法則 である(引用:天文学辞典)。 発見者はドイツの物理学者マックス・プランクである。 プランクの法則は、黒体の輝度(radiance) を表す式であり、温度と波長or波数or周波数の関数となっている。 式は以下のように表される(放射輝度:radiance$[\mathrm{ W m^{-3}sr^{-1}}]$ or $[\mathrm{ W m^{-2}sr^{-1} Hz}]$)。 $B(\lambda ,T)={\frac {2hc^{2}}{\lambda ^{5}}}{\frac {1}{{\mathrm {e}}^{{hc/\lambda kT}}-1}}$ $B(f ,T)={\frac {2hf^{3}}{c ^{2}}}{\frac {1}{{\mathrm {e}}^{{hf/kT}}-1}}$ B: Spectral radiance of Black body (Energy) $\lambda$: wavelength[m], f: frequency[Hz] h: Plank's constant($=6.62606896×10^{-34}$) k: Boltzmann constant($=1.3806504×10^{-23}$) c: Speed of light($=2.99792458×10^8$) コード 定数の定義 Python.ipynb c = 2.99792458e8 #c=speed of light[m/s] h = 6.62606896e-34 #h=Planck constant[Js] k = 1.3806504e-23 #k=Boltzmann constant[J/K] T_arr = np.array([7000, 6000, 5000, 4000, 3000, 255]) #temprature[K] lam_arr = np.linspace(1e-9,1e-2,1000000) #wavelength[m] f_arr = np.linspace(1e10, 1e16,1000000) #frequency[Hz] c,h,kは以下のサイトの値を使用 https://japanknowledge.com/contents/common/butsuriteisu.html 波長の関数 関数の定義 Python.ipynb def planck_lam(lam, T): """Definicion of Planck's law return B(lambda, temprature)[radiance:W/m^3/sr] calculation=>a * b """ a = 2.0 * h * (c**2) / (lam**5) b = 1 / (np.exp(h*c/(k*lam*T)) - 1.0) radiance = a * b return radiance 描写 Python.ipynb fig = plt.figure(figsize=(10,5)) plt.rcParams["font.size"] = 16 ax = plt.subplot(111) ax.set_title("Blackbody spectrum (Radiance)") for T in T_arr: B = planck_lam(lam_arr, T) # x-axis:lambda[m]→[nm](*1e9) y-axis: [W/m^3/sr]→[kW/m^2/sr/µm](*1e-3) ax.plot(lam_arr*1e9, B*1e-3*1e-6, label="T={}[K]".format(T)) #軸設定 ax.set_xscale("log") ax.set_yscale("log") ax.set_xlim(1e2, 1e4) ax.set_ylim(1e1, 1e5) ax.set_ylabel("Spectral radiance [kW $\mathrm{m^{-2} sr^{-1} \mu m^{-1}}$]") ax.set_xlabel("Wavelength [$\mathrm{n m}$]") ax.grid(which="both",color="gray",linewidth=0.2) ax.legend() 周波数の関数 関数の定義 Python.ipynb def planck_f(f, T): """Definicion of Planck's law return B(frequency, temprature)[radiance:W/m^2/sr/Hz] calculation=>a * b """ a = 2.0 * h * (f**3) / (c**2) b = 1 / (np.exp(h*f/(k*T)) - 1.0) radiance = a * b return radiance 描写 Python.ipynb plt.rcParams["font.size"] = 16 ax = plt.subplot(111) ax.set_title("Blackbody spectrum (Radiance)") for T in T_arr: B = planck_f(f_arr, T) # x-axis:frequency[Hz], y-axis: [W/m^2/sr/Hz]→[kW/m^2/sr/Hz](*1e-3) ax.plot(f_arr, B*1e-3, label="T={}[K]".format(T)) #軸設定 ax.set_xscale("log") ax.set_yscale("log") ax.set_xlim(1e12, 4e15) ax.set_ylim(1e-15, 1e-9) ax.set_ylabel("Spectral radiance [kW $\mathrm{m^{-2} sr^{-1} Hz^{-1}}$]") ax.set_xlabel("Frequency [$\mathrm{Hz}$]") ax.grid(which="both",color="gray",linewidth=0.2) ax.legend() 参考文献 shellとawkとGNUPLOT: Planckの法則をグラフに描く Blackbody Radiation 宇宙科学II (電波天文学)第五回 黒体放射&ビッグバン宇宙
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニアじゃない人の、Python3エンジニア認定基礎試験を受けた感想(2021年4月合格)

はじめに この記事は、python3エンジニア認定基礎試験の詳しいことは書いていません。 . 個人的な勉強方法や勉強時間、感想を述べているだけなので、 そのような内容を知りたい方にとって参考になればと思います。 投稿者情報 本職は、メカニック(普段は車のエンジンの試験)をやっている人で、 ガッツリプログラミングをやっている人ではありません。 この投稿日(2021年5月)から約1年前にプログラミング学習を 自己研鑽として始めて、HTML、CSS、Ruby(Rails)の基本的なことを学び、 (CSSがめちゃくちゃな)WEBアプリケーションを作ったりしてました。 Pythonは趣味で始めたRaspberryPiを使った電子工作をする上で 必要な知識(モジュール、GPIO、pipなど)をつまんで、 なんとか動かせるものを作って遊ぶために、学んでいた感じです。 . ・投稿者はそこそこプログラミングの知識を持っている ・本職はエンジニアではない ・Pythonの知識は中途半端 受験してみた感想 合格した時の嬉しさがあまりないのが正直な感想です。 . 企業や学校によって受験補助があれば、嬉しさはあるのかなーと思うのですが、 今回、個人で受験料11,000円を支払って受験してきました。 問題の内容が高校の小テストみたいな感じで、全部選択問題。 出題される問題は、DIVE INTO EXAMで無料で模試を受けれるサイト に近い問題が多い傾向がありました。 . あと、受験会場によると思いますが、私が受けた会場は とあるショッピングセンターの一角にあるパソコン教室(Odyssey認定試験会場)。 会場(教室)について受付を済ませると、 「じゃー、席について初めてどうぞ。」ってな感じで、 試験が終わるまで放置状態。 下手すりゃカンニングできそうな空間だったので、 やってるやついるだろうなー…なんて考えたりもしましたw . ・受験料がそこそこ(個人的に11,000円は)高い ・出題内容は高校生の小テストみたいな感じで簡単 ・合格した時の嬉しさは、あまり感じれない そもそもなんで受験した? 職場でPythonを使った機械学習、画像解析を活用してなんかやろうとしてたので。 趣味でPython使ってRaspberryPiを動かしていたこともあって、 将来的にそのプロジェクトに参加したいと思い、 「とりあえず、基礎的はところは抑えていた方がいいかなー」 って感じでおりました。 最終的なゴールは「Python3エンジニア認定データ分析試験」の合格なので、 その通過点として受験しようという感じです。 どんな感じで勉強した? 勉強期間、時間 だらだらやってても、モチベーションが下がっちゃうので、 勉強期間は約1ヶ月間で終わらせることにしました。 んで、終わらせました。 . 平日は基本的に朝の8時から夕方18時半まで仕事という条件。 朝の5時半に起床し6時から7時まで朝活。 仕事から帰って19時から20時まで夕活。 大体2時間は必ず確保していました。 . 休日は特にスケジュールは決まっていなくて、 「3時間以上できたらいいな」と思いながらやっていたので、 3〜5時間はやっていました。 なので、1ヶ月間の合計学習時間は、 80〜100時間やってきました。 勉強方法 過去の記事を参考にしていくと、 「Pythonチュートリアル」という書籍から出題されるとのこと。 実際に、受験した後のレポートの項目と「Pythonチュートリアル」の目次を見比べると やっぱり全く同じでした。 . とりあえず、最初は「Pythonチュートリアル」を理解度1〜2割程度でさらさらと読み進め、 なんとなくの全体像を掴むところからやりました。 が、振り返ってみればこの全体像を把握する作業は必要なかったと思います。 . 他の記事にも書いてある通り、 章によって出題割合に偏りがあるため、抑えるべき章のところと なんとなく理解できないところを解消していく方が、 「合格」を目指すための最短距離かと。 記事:Python3エンジニア認定基礎試験の効率的な勉強方法! . んで、私が実践してきた学習の流れは、 1、模試を解く 2、間違ったところを復習する ほとんど、これだけです。 1、模試を解く 比較的簡単な問題から、徐々に実践的な問題を解く流れで取り組みました。 簡単な方:G検定、Python、Rubyの模擬テスト | DIVE INTO EXAM 実践的な方:PRIME STUDY(プライム・スタディ) 簡単な方は2回、実践的な方は6回実施しました。 ▼簡単な方の結果 ▼実践的な方の結果 第x回(x回目) 実施日 点数 合否 第1回(1回目) 4月4日 45点/100点 不合格 第1回(2回目) 4月8日 82.5点/100点 合格 第2回(1回目) 4月10日 55点/100点 不合格 第1回(3回目) 4月13日 97.5点/100点 合格 第3回(1回目) 4月16日 87.5点/100点 合格 第1回(4回目) 4月17日 92.5点/100点 合格 2、間違ったところを復習する 意識してきたことは、インプットよりもアウトプットを増やすことで、 間違った問題のソースをエディタで動かしていきました。 とにかく手を動かし「なぜそのような出力になるのか?」 答えを見て覚えるよりも記憶の定着はよくなると思ってやってました。 . エディタは「Jupyter Notebook」もしくは「Google Colaboratory」がおすすめです。 ▼参考記事 Jupyter Notebookの使い方!インストール方法から解説 Google Colaboratoryの使い方とメリットを徹底解説! ▼模擬試験の問題の1部 ▼Jupyter Notebookで動作確認 上記のエディタを使う理由として、下記の2点があげられます。 1、対話型の開発環境であるため、プログラムを書いて、すぐに実行できる 2、実行した結果は作業履歴として記録に残こせる あとがき 先にPython3エンジニア認定データ分析試験を受けていてもよかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エンジニアじゃない人が、Python3エンジニア認定基礎試験を受けた感想(2021年4月合格)

はじめに この記事は、python3エンジニア認定基礎試験の詳しいことは書いていません。 . 個人的な勉強方法や勉強時間、感想を述べているだけなので、 そのような内容を知りたい方にとって参考になればと思います。 投稿者情報 本職は、メカニック(普段は車のエンジンの試験)をやっている人で、 ガッツリプログラミングをやっている人ではありません。 この投稿日(2021年4月)から約1年前にプログラミング学習を 自己研鑽として始めて、HTML、CSS、Ruby(Rails)の基本的なことを学び、 (CSSがめちゃくちゃな)WEBアプリケーションを作ったりしてました。 Pythonは趣味で始めたRaspberryPiを使った電子工作をする上で 必要な知識(モジュール、GPIO、pipなど)をつまんで、 なんとか動かせるものを作って遊ぶために、学んでいた感じです。 . ・投稿者はそこそこプログラミングの知識を持っている ・本職はエンジニアではない ・Pythonの知識は中途半端 受験してみた感想 合格した時の嬉しさがあまりないのが正直な感想です。 . 企業や学校によって受験補助があれば、嬉しさはあるのかなーと思うのですが、 今回、個人で受験料11,000円を支払って受験してきました。 問題の内容が高校の小テストみたいな感じで、全部選択問題。 出題される問題は、DIVE INTO EXAMで無料で模試を受けれるサイト に近い問題が多い傾向がありました。 . あと、受験会場によると思いますが、私が受けた会場は とあるショッピングセンターの一角にあるパソコン教室(Odyssey認定試験会場)。 会場(教室)について受付を済ませると、 「じゃー、席について初めてどうぞ。」ってな感じで、 試験が終わるまで放置状態。 下手すりゃカンニングできそうな空間だったので、 やってるやついるだろうなー…なんて考えたりもしましたw . ・受験料がそこそこ(個人的に11,000円は)高い ・出題内容は高校生の小テストみたいな感じで簡単 ・合格した時の嬉しさは、あまり感じれない そもそもなんで受験した? 職場でPythonを使った機械学習、画像解析を活用したプロジェクトが立ち上がったので。 趣味でPython使ってRaspberryPiを動かしていたこともあって、 将来的にそのプロジェクトに参加したいと思い、 「とりあえず、基礎的はところは抑えていた方がいいかなー」 ってな気持ちでいました。 最終的なゴールは「Python3エンジニア認定データ分析試験」の合格なので、 その通過点として受験しようという感じです。 どんな感じで勉強した? 勉強期間、時間 だらだらやってても、モチベーションが下がっちゃうので、 勉強期間は約1ヶ月間で終わらせることにしました。 んで、終わらせました。 . 平日は基本的に朝の8時から夕方18時半まで仕事という条件。 朝の5時半に起床し6時から7時まで朝活。 仕事から帰って19時から20時まで夕活。 大体2時間は必ず確保していました。 . 休日は特にスケジュールは決まっていなくて、 「3時間以上できたらいいな」と思いながらやっていたので、 3〜5時間はやっていました。 なので、1ヶ月間の合計学習時間は、 80〜100時間やってきました。 勉強方法 過去の記事を参考にしていくと、 「Pythonチュートリアル」という書籍から出題されるとのこと。 実際に、受験した後のレポートの項目と「Pythonチュートリアル」の目次を見比べると やっぱり全く同じでした。 . とりあえず、最初は「Pythonチュートリアル」を理解度1〜2割程度でさらさらと読み進め、 なんとなくの全体像を掴むところからやりました。 が、振り返ってみればこの全体像を把握する作業は必要なかったと思います。 . 他の記事にも書いてある通り、 章によって出題割合に偏りがあるため、抑えるべき章のところと なんとなく理解できないところを解消していく方が、 「合格」を目指すための最短距離かと。 記事:Python3エンジニア認定基礎試験の効率的な勉強方法! . んで、私が実践してきた学習の流れは、 1、模試を解く 2、間違ったところを復習する ほとんど、これだけです。 1、模試を解く 比較的簡単な問題から、徐々に実践的な問題を解く流れで取り組みました。 簡単な方:G検定、Python、Rubyの模擬テスト | DIVE INTO EXAM 実践的な方:PRIME STUDY(プライム・スタディ) 簡単な方は2回、実践的な方は6回実施しました。 ▼簡単な方の結果 ▼実践的な方の結果 第x回(x回目) 実施日 点数 合否 第1回(1回目) 4月4日 45点/100点 不合格 第1回(2回目) 4月8日 82.5点/100点 合格 第2回(1回目) 4月10日 55点/100点 不合格 第1回(3回目) 4月13日 97.5点/100点 合格 第3回(1回目) 4月16日 87.5点/100点 合格 第1回(4回目) 4月17日 92.5点/100点 合格 2、間違ったところを復習する 意識してきたことは、インプットよりもアウトプットを増やすことで、 間違った問題のソースをエディタで動かしていきました。 とにかく手を動かし「なぜそのような出力になるのか?」 答えを見て覚えるよりも記憶の定着はよくなると思ってやってました。 . エディタは「Jupyter Notebook」もしくは「Google Colaboratory」がおすすめです。 ▼参考記事 Jupyter Notebookの使い方!インストール方法から解説 Google Colaboratoryの使い方とメリットを徹底解説! ▼模擬試験の問題の1部 ▼Jupyter Notebookで動作確認 上記のエディタを使う理由として、下記の2点があげられます。 1、対話型の開発環境であるため、プログラムを書いて、すぐに実行できる 2、実行した結果は作業履歴として記録に残こせる あとがき 先にPython3エンジニア認定データ分析試験を受けていてもよかった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで競プロ練習

Pythonで競技プロを練習する 現在競技プログラミングを通じで自分でアルゴリズムを考える練習をしています。 某競プロの初心者用の問題ですが自力で考えてうまくいきましたので同じ初心者の方には参考になればと思いました。 問題としては単純でマスが3つあってそれぞれのマスに0か1が入ります。そして1が入ったマスをカウントするという問題です。 アルゴリズムを考える この問題を解くときに自分なりに考えたアルゴリズムが以下になります。 マスが3つあってその中に1か0がはいる 1マスにつき1か0が入るのかを3回繰り返している if文で1が入っていたらカウントを1増やすようにすればいい カウントを入れる変数countを作ってあげる 入力が手入力になるので、入力した値をリストで受け取ればいいと思った 以上がとりあえず思い付いたアルゴリズムになる コード Python s1,s2,s3 = list(map(int, input())) #入力した値をリスト型[]で受け取るイメージ t = s1, s2, s3 #[1,0,1]こんなイメージ count = 0 #カウントが増えた時にその値を保存するための変数 for i in t: #for i in [1,0,1]があってiのなかに1, 0, 1が順番に入るイメージ if i == 1: #if文でiが1ならば、カウントを1増やす count += 1 else: #ここは思いつきですが、0が入ればとりあえず何もしなくていいと思って,else:0としてみた 0 print(count) #最後にprint(count)とすることでcountの中身を出力してあげる 以上のコードで合格をもらえたのでよしとした。 別解 Python print(input().count('1')) #入力した値をcount関数で直接数える凄まじく効率にいい解答です 感想 別解で効率のいいコードはありますが、今回は自分で思った通りにアルゴリズムを作って、思った通りのコードを記述することができたので、競プロを初めて一週間ちょっとですが、初めて競プロを始めた時よりは進歩しているのでなはないかと思いました。 まだまだ、効率のいいコードの書き方などはできませんが、少しづつ進歩できればいいと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AI 機械学習でダイエット効果(体重変化)の分析と予測

時系列データの機械学習に最適(むしろ革命的に最強)のProphetを使って自分の体重変化を分析・予測してみました。 敬愛する山本義徳先生の動画で拝見した増量期とプチ減量期のサイクルを自分に合った期間で実施するための最適解を探します。 一連の実施内容と結果としてこちらに Jupyter Notebook を置いておきます。 https://github.com/GenkiOkuma/My-body-weight-analytics/blob/main/My%20Body%20Weight%20Analytics.ipynb 1. 体重計測 アマゾンで買った安い体重計で日々計測をする。 中国語に文字化けするけど、一応csv出力ができる。中山きんに君の動画でも見かけた。 2. Jupyter Notebook の Python環境にcsvファイルを取り込む。 column名は文字化けしてたので手動でcsvファイルを編集した。体重だけでなく体脂肪率とか筋肉量とか体内年齢とかが取れる。 3. ラインチャート df_ = df.copy() df_ = df_.set_index('ds') fig = plt.subplots(figsize=(20,5)) sns.lineplot(data=df_) fig = plt.subplots(figsize=(20,5)) sns.lineplot(data=df_.resample("D").min()) fig = plt.subplots(figsize=(20,5)) sns.lineplot(data=df_.resample("W").min()) fig = plt.subplots(figsize=(20,5)) sns.lineplot(data=df_.resample("m").min()) ・計測ごと ・1日の最小値 ・週の最小値 ・月の最小値 下記のようにやってきたつもりだが、いい感じにできていると思う。 2020年12月~2021年1月 → 減量期 2月~3月 → 増量期 4月 → 減量期 5月 → 増量期(予定) 4. 時間別の箱ひげ図 df_ = df.copy() df_ = df_[(df_['ds'] >= datetime(2021,3,1)) & (df_['ds'] < datetime(2021,4,30))] df_['time'] = df_['ds'].dt.hour plt.figure(figsize=(20,6)) sns.boxplot(x="time", y="weight(kg)", data=df_,) 1日に1~3回しか計測していないのでデータが足りない。朝低くて、夜高いという程度。 5. 曜日別の箱ひげ図 df_ = df.copy() df_ = df_[(df_['ds'] >= datetime(2021,3,1)) & (df_['ds'] < datetime(2021,4,30))] df_ = df_.set_index('ds') df_["day"] = df_.index.strftime("%A") plt.figure(figsize=(20,6)) sns.boxplot(x="day", y="weight(kg)", data=df_,) 金曜日はばらつきやすい。日曜日は高くなりやすい。金曜と日曜は要注意。 6. Prophetで分析・予測 7. Prophetで作ったmodelの誤差検証 平均誤差は2%程度と低く、精度が高い。まあ体重ってそんなに大きくブレないしね。 8. 考察 ・ダイエット始めてまだ半年くらいなので、増量期と減量期の切り替えに悩みがちですが、Prophetのトレンド分析が参考になりました。 ・予測がいい感じに痩せる方向を示しているのでプレッシャーになります。 ・78.0kg から 64.2kg にダイエットしたわけですが、見た目はこんな感じに変化しています。 ・体内年齢は実年齢 +2 から -5 に変化しました。体が変わると人生変わります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cvxpyを使ってモンスターハンターライズの防具の最適化してみた

はじめに  モンスターハンターライズを購入して一か月ほど経ち、システムを理解し始めると、「めっちゃ爆発する装備作りたい」や「当たらなければどうということはないがしたい」とかを考えるようになったのですが、4500万通り以上(下位も含むと14億通り以上)もある防具の組み合わせから、必要なスキルが最大LVになる防具を考えるのは結構な時間がかかる。  防具の構成を考えるのも楽しみの一つなのだが、ある程度当てを付けてくれるような物があったらいいなと思い、pythonのcvxpyを用いた数値最適化で、必要なスキルが最大LVになる防具の組み合わせを探すプログラムを作成した。  コードは以下のgithubに置いておきます。 - 防具最適化プログラム 目次 条件の設定 実装 結果 感想 条件の設定 「数値最適化」というからには制約条件や最適化条件をきめないといけません。  モンハンやってる人にとっては当たり前のようなことしか書いていませんが、ここをちゃんと定義しておかないと、後のデバッグで苦労するので、ちゃんと定義しておきましょう。(ちなみに、ガバガバ設定であることが、後でわかります。) 制約条件 1. 防具は「頭」「胴」「腕」「腰」「足」の5つあり、それぞれ1つしか装備できない。 2. スキルには最大値があり、それ以上は上がらない。 最適化条件 1. 選択したスキルが最大LVに近くなる。 実装  まず必要なものは3つ、「防具ごとのスキルレベル」「スキルごとの重み」「スキルの最大LV」を用意しなければなりません。  「防具ごとのスキルレベル」は(https://altema.jp/mhrize/boguichiran)こちらのサイトから情報をお借りして、横軸にスキル縦軸に防具が並んでいるスキルレベルが書かれた数列armor.csvを作成します。(装飾品Lvは正確にはスキルではないですが、今回はスキルとして計算します。)  こういう時に数列は使うから、数学の授業で寝ていてはいけない(戒め)  「スキルごとの重み」は正直わからないので、適当に、防具すべてのスキルを合計して、平方根通した値で30を割った値を使い、weight.csvを作成。(詳しい人が見たらめっちゃ怒られそう)  「スキルの最大LV」は(https://game8.jp/mhrise/363848)こちらのサイトから情報をお借りして、maxlv.csvを作成。(maxlv.csv)  後はcvxpyの説明を見ながら、pythonでコードを書いていく。 結果  ためしにすべてのスキルを最大化するようにしてもらうと以下のように、ガンナー用の「弾丸節約」や、剣士用の「業物」などがついてる装備をお勧めされた。(いや、すべてのスキルとは言ったけど、これはない。)  早々にガンナー用か剣士用かの判定が行われていないというガバが見つかりましたが、これはいつかどこかで入れるとして、使う武器を間接的に指定してあげます。  太刀とかで使えそうなスキル「攻撃」「集中」「強化継続」、lv2の装飾品をたくさんつけられるように「lv2」を最大にするように設定すると以下のようになった。  で、実際に勧められた装備を使ってみてどうだったかというと、現状の最強装備に近いものが選ばれるので、私が選んだやつよりかは強かったです。 感想  今回はこんなことにcvxpyを使いましたが、やろうと思えば「勤務表の自動作成」「スーパーの仕入れの最適化」など世の中の役に立つことが、式さえちゃんと考えれば、後は勝手に計算してくれるので、便利になったなと思う反面、内部でやってることを理解しないまま、使い続けるのも嫌なので、本格的に、数値最適化あたりの勉強を始めたい。  「防御が入ってない」、「装飾品が考慮されてない」など、かなりガバガバ設定なので、時間があれば修正したいと思います。(時間がなければやらないということ)  「Githubに上げとったら誰かがやってくれるやろ」という軽い気持ちでGithubを使ったが、Githubのリポジトリも初めて作成するので、設定方法やGithubを用いた今後の開発方法も全く知らない。  まだまだ勉強することは多いなと感じさせられました。 参考文献 Welcome to CVXPY 1.1 防具一覧 【モンハンライズ】スキルの一覧と変更点|仕様解説【MHRise】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Cartopy】Python Cartopyを用いた、Merra-2の描写

はじめに 今回は、NASA(アメリカ航空宇宙局, National Aeronautics and Space Administration)のGES DISCで無料で入手可能なnetCDFのデータをCartopyを用いて表示させたいと思います。 目次 netCDFの扱い方 データのダウンロード pythonでデータの中身を見る 単位の変換(K⇒℃) グラフの作成 Global Japan 参考文献 netCDFの扱い方 netCDFとは何だ??という方は、以下を参照ください。 netCDF データ格納形式の基礎—ヘルプ | ArcGIS for Desktop データのダウンロード GES DISCからのデータのダウンロードの仕方は、ホームページに描いてあるので割愛します。 →GES DISC Data Access pythonでデータの中身を見る 初めに、必要なものをインポートします Python.ipynb from netCDF4 import Dataset #netCDFを扱うため import numpy as np #数値計算用 import matplotlib.pyplot as plt #グラフ描写のため import cartopy.crs as ccrs #cartopy:地図のplotで使用 import cartopy.feature as cfeature #地図内の国境や海岸線などの描写に使用 #緯度経度グリッド作成のため import matplotlib.ticker as mticker #緯度経度線を度数表示にするため from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER 例として、1ヶ月あたりの気温の月平均値のみを使いました。2019年12月のデータです。 Python.ipynb nc = Dataset('MERRA2_400.tavgM_2d_slv_Nx.201912.nc4.nc4', mode='r') nc <class 'netCDF4._netCDF4.Dataset'> ・・・ SouthernmostLatitude: -90.0 NorthernmostLatitude: 90.0 WesternmostLongitude: -180.0 EasternmostLongitude: 179.375 LatitudeResolution: 0.5 LongitudeResolution: 0.625 DataResolution: 0.5 x 0.625 ・・・ https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2_MONTHLY/M2TMNXSLV.5.12.4/2019/MERRA2_400.tavgM_2d_slv_Nx.201912.nc4.nc4?T2M,time,lat,lon dimensions(sizes): time(1), lat(361), lon(576) variables(dimensions): float32 T2M(time,lat,lon), float64 lat(lat), float64 lon(lon), int32 time(time) 内容を確認します T2M:2 m地点の気温。(時間,緯度,経度)で格納されている。 lat :緯度。(緯度)で格納されている。 lon:経度。(経度)で格納されている。 それでは、データを変数に入れていきます。 Python.ipynb lon = nc.variables["lon"][:].data lat = nc.variables["lat"][:].data t2m0= nc.variables["T2M"][:,:,:].data .dataを設定すると、maskedarrayというのがなくなり、普通のarrayになります。 それぞれの形も確認しておきましょう Python.ipynb print(t2m0.shape) print(lat.shape) print(lon.shape) 今回は、月平均値を使ったのでtimeの次元が1でしたが、日平均や月平均値をとる場合は以下の操作を施します。 t2m.shapeをすると、(時間,緯度,経度)の配列数がでるので、それぞれが(0,1,2)番目であることから、 axis(軸)=0(時間軸)の平均をとります Python.ipynb t2m = t2m0.mean(axis=0) 単位の変換(K⇒℃) t2mはケルビンなので℃に変換します Python.ipynb temp = t2m -273.15 グラフの作成 それではグラフを作成していきます。 Cartopyを用いた基礎的な内容は、以下を見てください 【Cartopy】Python Cartopyを使ったMapping Global Python.ipynb fig = plt.figure(figsize=(10,5)) plt.rcParams["font.size"] = 18 ax = fig.add_subplot(1,1,1, projection=ccrs.PlateCarree()) ax.set_title("Temperature Dec. 2019") ax.coastlines(resolution="110m") ax.add_feature(cfeature.BORDERS, linestyle=":") #緯度・経度線のグリッドの設定(参考*2) gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle=':') gl.xlabels_top = False gl.ylabels_right = False dlon, dlat = 30,30 xticks = np.arange(-180, 180.1, dlon) yticks = np.arange(-90, 90.1, dlat) gl.xlocator = mticker.FixedLocator(xticks) gl.ylocator = mticker.FixedLocator(yticks) gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER gl.xlabel_style = {'size': 12} gl.ylabel_style = {'size': 12} #データ描写(参考*1) clevs = np.arange(-50,50,5) im = ax.contourf(lon, lat, temp,clevs,cmap="jet") #カラーバーの設定 cb = plt.colorbar(im, orientation="vertical", pad=0.02, aspect=25, shrink=0.85) cb.set_label("Temperature ($^\circ$C)",rotation=90,labelpad=5) cb.ax.tick_params(size=5) plt.show() fig.savefig("XXX.png", format="png", dpi=330) Japan Python.ipynb fig = plt.figure(figsize=(10,10)) plt.rcParams["font.size"] = 18 ax = fig.add_subplot(1,1,1, projection=ccrs.PlateCarree()) ax.set_title("Temperature Dec. 2019") ax.coastlines(resolution="10m") ax.add_feature(cfeature.BORDERS, linestyle=":") #緯度・経度線のグリッドの設定(参考*2) gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=2, color='gray', alpha=0.5, linestyle=':') gl.xlabels_top = False gl.ylabels_right = False dlon, dlat = 5, 5 xticks = np.arange(-180, 180.1, dlon) yticks = np.arange(-90, 90.1, dlat) gl.xlocator = mticker.FixedLocator(xticks) gl.ylocator = mticker.FixedLocator(yticks) gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER gl.xlabel_style = {'size': 18} gl.ylabel_style = {'size': 18} #描写(参考*1) clevs = np.arange(-50,50,5) im = ax.contourf(lon, lat, temp,clevs,cmap="jet") ax.set_extent([128, 148, 30, 50], crs=ccrs.PlateCarree()) #カラーバーの設定 cb = plt.colorbar(im, orientation="vertical", pad=0.02, aspect=25, shrink=0.8) cb.set_label("Temperature ($^\circ$C)",rotation=90,labelpad=5) cb.ax.tick_params(size=5) plt.show() fig.savefig("XXX.png", format="png", dpi=330) Keys glidlineの種類: "-", "--", "-.", ":"など matplotlib.axes.Axes.grid colorbarの種類: "jet", "rainbow"などたくさんある。"jet_r"のように_rをつけると、色が反対になる Choosing Colormaps in MatplotlibChoosing colorbarの設定 名前 内容 orientation vertical=縦方向, horizontal=横方向 pad グラフとカラーバーの間隔 aspect カラーバーの幅 shrink figに対するカラーバーの縦の長さ labelpad カラーバーラベルの文字の回転 shrink カラーバーとラベルの幅     matplotlib.pyplot.colorbar 4 単位とLaTeX表示 【Jupyter Notebook】Python Jupyter Notebook-単位表示のお話 参考文献 -*1 Merra-2のデータの使い方 How to read and plot NetCDF MERRA-2 data in Python - *2 緯度経度のグリッド設定Cartopy map gridlines and tick labels
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YayをPythonから使う(2)関数一覧

ライブラリの構造 YayをPythonから使う(1)ライブラリダウンロード 前回まででライブラリのダウンロードまで完了している前提で進めます。 yay |-- yay_api.py <<< モジュール |---YayApi <<< クラス 絶対的な構成は上のようになっており、yay_api.pyにはクラス(YayApi)しか実装されていません。必然的に関数もすべてクラスYayApiに属しています。 インスタンスの作成は yay_client = YayApi() から宣言されます。 関数一覧 1 YayApi.login(email, password) 2 YayApi.get_timeline(number=50) 3 YayApi.creat_likes(post_id) 4 YayApi.pin_post(post_id) 5 YayApi.make_review(user_id, text) 6 YayApi.delete_review(review_id) 7 YayApi.get_followings(user_id, count) 8 YayApi.get_followers(user_id, count) 9 YayApi.block_user(user_id) 10 YayApi.hima_now() 11 YayApi.get_user(user_id) 12 YayApi.get_user_by_nickname(nickname) 13 YayApi.get_user_grops(user_id) 14 YayApi.get_user_timeline(user_id, count) 15 YayApi.get_user_reviews(user_id, count) 16 YayApi.get_likers(post_id) 17 YayApi.make_post(text) 18 YayApi.make_post_with_image(text, image_url) 19 YayApi.reply_to_post(text, in_reply_to, mention_ids: list) ログイン: YayApi.login(email, password) loginではユーザーに登録されたメールアドレス、パスワードで認証を行いトークンを取得します。リクエストが成功した場合のレスポンス >>> from yay.yay_api import YayApi >>> yay_client = YayApi() >>> email = 'your email for yay' >>> password = 'password' >>> >>> result = yay_client.login(email, password) >>> print(result) {'result': 'success', 'user_id': XXXXXXXX, 'username': None, 'access_token': '7f7a884c3fd1014e1d6fc268d54d45f2a1xxxxxxxxxxxxxxxxxxxxxac1b6'} >>> 返り値resultのresultがsuccessの場合ログインは成功しました。 このとき、クラス内ではトークンが自動でセットされます。 タイムラインの取得: YayApi.get_timeline(number=50) この関数ではタイムラインを取得します。このタイムラインはユーザーのタイムラインとは違い、アプリ内のすべての投稿若しくはおすすめタイムラインが取得されています。 ログインなしでリクエスト可能 引数 number はint型を取るタイムラインの取得件数指定です。50が恐らく取得できる最高値です。 >>> timeline = yay_client.get_timeline() レスポンスサンプル """ レスポンスは膨大の為、jsonデータをそのまま返還しています。 詳細はレスポンスを実際に確認してください """ { "result": "success", "posts":[ "id": 投稿ID, "text": 投稿された文章, "user": { ユーザーの情報 } ] } いいねをつける: YayApi.creat_likes(post_id) 投稿IDを元にログインされたアカウントでいいねを付けます。 投稿IDはタイムラインの投稿で各投稿に対して取得できるIDと同一です。 ログイン必須 引数 post_id はstr型若しくはint型を取る投稿ID指定です。1件の投稿に対してのみ実行されます。 >>> yay_client.creat_likes(post_id=13XXXXX) {'result': 'success', 'like_ids': [13XXXXX]} 投稿を自分のプロフィールに固定する: YayApi.pin_post(post_id) 投稿IDを元にログインされたアカウントに固定ます。 投稿IDはタイムラインの投稿で各投稿に対して取得できるIDと同一です。 ログイン必須 引数 post_id はstr型若しくはint型を取る投稿ID指定です。1件の投稿に対してのみ実行されます。 >>> yay_client.pin_post(post_id) {'result': 'success', 'like_ids': [13XXXXX]} レビューを送信する: YayApi.make_review(user_id, text) ログインされたアカウントから指定されたユーザーにレビュー(DMメッセージのようなもの)を送信。 ログイン必須 引数 user_id はstr型若しくはint型を取るユーザーID指定です。ユーザーの固有idはユーザー情報の中に存在するidを指します。 引数 text はstr型を取る送信する文章指定です。 >>> yay_client.make_review(user_id=13XXXX, text="hello world!") {'result': 'success', 'like_ids': [13XXXXX]}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む