20210507のPythonに関する記事は28件です。

GitHub Actions で開発中の Python を使ってテストを実行する

Python 3.10 もついに beta1 がリリースされ、いよいよ正式版のリリースの足音も徐々に聞こえてきました。 PEP-619 では以下のようにリリーススケジュールが設定されています。 3.10.0 beta 2: Tuesday, 2021-05-25 3.10.0 beta 3: Thursday, 2021-06-17 3.10.0 beta 4: Saturday, 2021-07-10 3.10.0 candidate 1: Monday, 2021-08-02 3.10.0 candidate 2: Monday, 2021-09-06 (if necessary) 3.10.0 final: Monday, 2021-10-04 beta1 が出たことにより、フィーチャーフリーズにより追加となる機能も確定したので、Python の開発としては 3.10 を安定させていくというフェーズに入っていくはずです。ここに対して僕らができることといえば、3.10 を使って CI を回していき、問題点をレポートしていくことですよね。 ということで、今回は GitHub Actions で開発中の (正式リリース前の) Python を使ったテスト方法についてご紹介します。 最新のβ版パッケージをインストールする と前口上を述べたのですが、最新のβ版パッケージをインストールするのは非常にかんたんです。 GitHub Actions で Python をインストールする際に actions/setup-python を使っている方が多いと思いますが、なんとこの actions/setup-python はβ版のインストールに対応しているのです1。 3.10 系の開発版をインストールする場合は python-version に '3.10-dev' と指定します。すると、自動的に最新の開発バージョンが選択され、インストールされます。 steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: '3.10-dev' - run: python my_script.py 流石に開発版オンリーでテストするひとはいないでしょうから、matrix build の指定に '3.10-dev' を加えると良いでしょう。 jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10-dev'] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - run: python my_script.py これだけで、秋にリリースされる 3.10 系でのテストができるので、お得な感じですね。 真の最新版を追いかける しかし、もっとディープに Python の最新版を追いかけたいという方も世には多くいるはずです。そんなあなたは deadsnakes/action を使いましょう。 deadsnakes プロジェクトは新旧さまざまな Python の Ubuntu パッケージをビルドして提供しているプロジェクトです。開発環境の構築で使ったことがある方もいらっしゃると思います。 この deadsnakes プロジェクトは nightly build の Python パッケージも提供しており、さらに GitHub Actions 用のアクションも提供しているので、これを組み込むだけでかんたんに nightly build でテストができるようになります。 deadsnakes/action は、次のように actions/setup-python と組み合わせて使うことが推奨されています。 jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9-dev, 3.10-dev] name: main steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 if: "!endsWith(matrix.python-version, '-dev')" with: python-version: ${{ matrix.python-version }} - uses: deadsnakes/action@v2.1.1 if: endsWith(matrix.python-version, '-dev') with: python-version: ${{ matrix.python-version }} - run: python --version --version && which python -dev が末尾についている場合は deadsnakes/action が、それ以外の場合は actions/setup-python が利用されるように if 文で分岐します。 nightly build 版はベータ版がリリースされる前から(それこそアルファ版が出る前から)提供され続けているので、いまから 3.11 向けの CI を動かしてみると面白いかもしれませんね。すでに 3.11-dev として提供が開始されています。 まとめ 今回は開発版の Python をインストールする方法を 2種類を紹介しました。 actions/setup-python: リリースされた開発版パッケージ (α版、β版) をインストールする deadsnakes/action: nightly build をインストールする このふたつのうち、好みに合わせてお好きな方を使い分けると良さそうです。 開発中のバージョンで CI をしながら、Python 本体や関連ツールの不具合を見つけてバグ報告するというのも、OSS への貢献のひとつです 2。ぜひチャレンジしてみてください。 README などには記載がないのですが、 Python 3.10 alphas? · Issue #150 · actions/setup-python というイシューのコメントで紹介されています。 ↩ たとえば 3.10.0b1 リリース直前には pytest が動かなくなる問題を見つけたりしました。すでに他の方にバグ報告されていたので、結果的に眺めていただけだったのですが、誰がかみつけてくれるだろう、と取りこぼすよりハッピーになれる気がします。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

しゃくとり法のDequeを使ったバグりにくい実装

この記事の目的 しゃくとり法のバグりにくい実装の紹介です! しゃくとり法(尺取り法)って? しゃくとり法の説明自体は、とってもいいまとめがあるのでこちらをご覧ください。 しゃくとり法 (尺取り法) の解説と、それを用いる問題のまとめ でも、しゃくとり法ってバグりません? しゃくとり法っていざ書いてみるとかなりの確率でバグります。 区間の端を表す添え字 $l$ と $r$ の動かし方が結構混乱します。 この記事ではdeque(両端キュー)を使った実装を紹介します。 なんとこの実装では添え字を使う必要がありません! deque(両端キュー)によるしゃくとり法の実装 次の問題を例にとって説明します。 ABC 032 C - 列 長さ $n$ の整数列 $A = \{a_1, a_2,..., a_n\}$の連続部分列で、その要素の積が $K$ 以下となるものの長さの最大値を求める問題です。 次のように、連続部分列をdequeで表現して実装します。 from collections import deque ## 入力の受け取り n,k=map(int,input().split()) a=[int(input()) for i in range(n)] ans=0 q=deque() p=1 ## 今、見ている区間の要素の積をpで管理する。 for c in a: q.append(c) ## dequeの"右端"に要素を一つ追加する。 p*=c while p>K: ## 要素の積がKを超えているか? rm=q.popleft() ## 条件を満たさないのでdequeの"左端"から要素を取り除く p//=rm ## 取り除いた値に応じて要素の積を更新する ans=max(ans,len(q)) ## Dequeに入っている要素の積がK以下になるまで区間を縮めた。 print(ans) はい!添え字が一切登場しませんね! 連続部分列をdequeの両端への追加と削除で管理しているので添え字が必要ありません。 右端への追加が区間を右に伸ばす事に相当し、条件を満たさないなら左端の要素を取り除いていく事によって区間の左を縮めています。 whileがbreakした時には条件を満たす最大の長さの連続部分列がdequeに入っている事になります。 printデバッグで確認すると分かりやすいでしょう。 N, K = 7 , 6 A = [4, 3, 1, 1, 2, 10, 2] deque([4]) deque([3]) deque([3, 1]) deque([3, 1, 1]) deque([3, 1, 1, 2]) deque([]) deque([2]) 標準的な実装でありがちな、$l$ が $r$ を追い越してしまった!という事はdequeの要素の数が負になる事に相当するので起こりえません。 空の区間は条件を満たす事がほとんどなので、whileがbreakするからです。 稀に空の区間が条件を満たす事もありますが、その場合は空のdequeからpopしようとする例外が発生します。 怖い時はwhileの条件に $len(q)>0$ を追加しておくとよいでしょう。 一般の場合 より一般的には下のようなコードになります。 q=deque() for c in a: q.append(c) ## dequeの右端に要素を一つ追加する。 (追加した要素に応じて何らかの処理を行う) while (満たすべき条件): rm=q.popleft() ## 条件を満たさないのでdequeの左端から要素を取り除く (取り除いた要素に応じて何らかの処理を行う) (何らかの処理を行う。whileがbreakしたので、Dequeに入っている連続部分列は条件を満たしている。特に右端の要素から左に延ばせる最大の長さになっている。) あなたもdequeで素敵なしゃくとりライフを!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[素人エンジニア]がscikit-learningのEncodingを勉強した

Pythonデータ分析認定試験の合格を目指し 公式テキストを元に学習しているが表題の部分で詰まった この記事でやってること ・データフレームをOneHotEncodingしたよ ・上記のエンコード済のデータをデータフレーム型に戻してカラム名を表記したよ 環境 python : 3.9.4 scikit-learn : 0.24.2 numpy : 1.20.2 使用教本 Pythonによる新しいデータ分析の教科書 エラーの経緯 教本p219を参考に以下のデータフレームを作成 import pandas as pd import numpy as np df = pd.DataFrame({'A': np.arange(1,6),'B': ['a','b','c','b','c']}) df sklearnを使って、B列のa,b,c,d,eでOneHotEncodeする (文字列変数を数値データに変換したい) #データフレームのカテゴリ変数を数値化したい from sklearn.preprocessing import LabelEncoder,OneHotEncoder #defaultでdeep=trueなので、深い複製 df_ohe = df.copy() #ラベルエンコーダーをインスタンス化 le = LabelEncoder() #B列の値を数値化 df_ohe['B']=le.fit_transform(df_ohe['B']) ohe = OneHotEncoder(categorical_features=[1]) ohe.fit_transform(df_ohe.toarray()) 以下のエラーが発生 TypeError: __init__() got an unexpected keyword argument 'ColumnTransformer' 先生、LabelEncodingとOneHotEncodingの違いからわからないです。。。 ということでまとめました。 エンコーダーの公式ドキュメントも確認してね Labelエンコーダー 文字列の項目を数値に変換 イメージ #LabelEncoding #①このようなモデルを作って ラベル : [りんご,ごりら,らっぱ] ラベルID : [1,2,3] #②この配列を作る [ごりら,ごりら,らっぱ] #①ラベルを適用してLabelEncoding [2,2,3] OneHotエンコーダー 文字列の項目の有無を0,1で新しい列を作成 #OneHotEncoding #①このようなモデルを作る A:[0,1,2,3,4] B:[a,b,c,d,e] #②このモデルをOneHotencoding A:[0,1,2,3,4] B_a:[1,0,0,0,0] B_b:[0,1,0,0,0] B_c:[0,0,1,0,0] B_d:[0,0,0,1,0] B_e:[0,0,0,0,1] #列の項目のカテゴリ別に0,1の配列が作成される #このように0が多い行列を疎行列という(<>密行列) 本題 このエラーはなんだったのか #エラー TypeError: __init__() got an unexpected keyword argument 'ColumnTransformer' 原因 教本の環境が今のバージョンと異なっており、OneHotEncoderの仕様が変わり categorical_featuresはバージョン0.22で排除済だった #教本 scikit-learn===0.19.1 #使用する環境 scikit-learn===0.24.2 よって公式例を参考にして以下でOneHotEncodingを実践しました from sklearn.preprocessing import OneHotEncoder import pandas as pd #データフレーム作成 import numpy as np df = pd.DataFrame({ 'A': np.arange(1,6), 'B': ['a','b','c','b','c'] }) df_ohe = df.copy() #特に引数はいらないみたい ohe = OneHotEncoder() ohe.fit_transform(df_ohe).toarray() 結果はscipy.sparse型の行列なので人間の私にはわかりにくい ちなみにOneHotEncodingのインスタンス時に'sparse = False'にするとNumpy.ndarrayの一次元配列になる array([[1., 0., 0., 0., 0., 1., 0., 0.], [0., 1., 0., 0., 0., 0., 1., 0.], [0., 0., 1., 0., 0., 0., 0., 1.], [0., 0., 0., 1., 0., 0., 1., 0.], [0., 0., 0., 0., 1., 0., 0., 1.]]) カラム名のつけたデータフレーム型に変換して表示してみた OnehotEncode型のデータを複数のカテゴリ名を持つデータフレームに変換する仕方がわからなかったので、ここではカテゴリが一つの配列をOneHotEncodingした疎行列データをデータフレームに変換します。 #データの準備 from sklearn.preprocessing import OneHotEncoder import pandas as pd import numpy as np df = pd.DataFrame({ 'A': np.arange(1,6), 'B': ['a','b','c','b','c'] }) #OneHotEncoderのインスタンス化 ohe = OneHotEncoder() #yにデータフレームのB列のndarray型の配列を代入 y = df.B.values #yを5行1列にしたものをOneHotEncoding #OneHotEncodingは二次元以上の配列しかできないので元々1次元のyを変換する必要があった ohe.fit(y.reshape(-1,1)) #OneHotEncodingしたoheインスタンスをtransformでコミットしたcsr.csr_matrix型のデータをy_dataに代入 y_data = ohe.transform(y.reshape(-1,1)) #DataFrame型で再定義 #引数のデータにはy_dataをndarray型に変換したもの #引数のカラム名はOneHotEncoder型のデータoheがCategories_として保持しているもの y_df = pd.DataFrame(y_data.toarray(),columns=ohe.categories_) y_df 結果 なんとかOneHotEncodeしたデータをカラム名をつけて表現できた。 今後調べること ① 疎行列で(5,1)の構造であるcsr.csr_matrix型のデータをtoarray()関数でndarray型にしたら、(5,3)の構造になったのは何故か ② エンコード方法は調べただけでも沢山存在しているらしく、他にはどんなエンコードがどんな状況で使われるのか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python から HTTP POST リクエストで Node-RED と連携するメモ(Python3版)

Pythonプログラムの結果をNode-REDに送りたくて 1ft-seabass.jp.MEMOさんの記事 Python から HTTP POST リクエストで Node-RED と連携するメモを参考に試したら Traceback (most recent call last): File "nodered.py", line 3, in <module> import urllib2 ModuleNotFoundError: No module named 'urllib2' とエラーが出ました。 色々ググってみると urllib2がPython3では使えないとわかりました。 参考サイト python のModule urllib2 を利用する方法を教えて下さい Python3のモジュールは urllib.requestに変更する事で 動き出したので変更箇所をまとめます。 3行目 import urllib2 モジュールを import urllib.request import urllib.parse に変更 33行目 request = urllib2.Request(url) を request = urllib.request.Request(url, data.encode()) に変更 37行目 response = urllib2.urlopen(request,data) を response = urllib.request.urlopen(request) に変更 まとめプログラムは 違いが分かりやすいように 1ft-seabass.jp.MEMOさんのプログラムで試しました。 import urllib.request import urllib.parse #import urllib2 import json params = {} ### JSON データ設定 # https://docs.python.org/ja/3/library/json.html # Dictionary(object) params["x"] = 10 params["y"] = -10 params["z"] = 5 # Array params["arr"] = [] params["arr"].append(["id1","A"]) # append Array params["arr"].append(["id2","B"]) # append Array params["arr"].append({"id3":"C"}) # append Object params["arr"].append({"id4":"D"}) # append Object ### HTTP リクエスト設定 # python3 https://docs.python.org/ja/3/library/urllib.request.html # python2 https://docs.python.org/ja/2.7/library/urllib2.html # JSON データを文字列化 data = json.dumps(params) # print(data) # Content-Type application/json を HTTP Header に加える url = 'http://localhost:1880/test' #request = urllib.request.Request(url) #request.add_header('Content-Type','application/json') # POST リクエスト #response = urllib.request.urlopen(request,data) request = urllib.request.Request(url, data.encode()) request.add_header('Content-Type','application/json') # POST リクエスト response = urllib.request.urlopen(request) ### HTTP レスポンス print('code',response.getcode()) # 結果 : ('code', 200) # 返答データを抽出 response_data = response.read() print('response_data',response_data) # 結果 : ('response_data', '{"result":"OK"}') # さらに返答データを JSON として取り出す response_json = json.loads(response_data) print('response_json["result"]',response_json["result"]) # 結果 : ('response_json["result"]', u'OK') これで念願の画像処理はPythonで行い他との連携はNode-REDでできるよになりました。 また次頑張ります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCode 拡張機能の中で Python を動かす

TL;DR Node.js の python-shell パッケージを使うと VSCode 拡張機能内で Python が呼び出せます。 つくったもの VSCode の拡張機能は TypeScript で開発しますが、JavaScript や TypeScript に不慣れなため、Python で書きたいなと思っていました。調べると python-shell という Node.js のパッケージがあり、これを使えばできるのでは?、と思いやってみました。 動かすと以下のような感じです。見栄えしないですが、右下のメッセージは Python から出したものです。 やった手順 Your First Extension を作る VSCode 拡張機能の開発環境を作るのは、元々 VSCode を使っていれば非常に簡単です。以下の記事に従っていけばすぐにデバッグ環境が整います。 python-shell を追加 npm install python-shell で python-shell を追加します。その他の変更点は以下の通りです。 pn11/vscode-extension-with-python/compare/Your-First-Extension...For-Qiita README.md などは無視してもらって、Your First Extension を元に自分の手で変更したのは python/hello.py と src/extension.ts だけです。 Python 側 python/hello.py が拡張機能から呼び出すスクリプトで、今回は第一引数をプリントするだけのものにしています。Python2 でも Python3 でも動きます。 hello.py from __future__ import print_function import sys print(sys.argv[1]) TypeScript 側 呼び出し側の src/extension.ts ですが、まず L. 4 で python-shell をインポートしています。 extension.ts import { Options, PythonShell } from 'python-shell'; L.24 - L.30 で インストールされた拡張機能への path と、Python インタプリタへの path を取得しています。 extension.ts // ログ出力などは省略 let ext_path = vscode.extensions.getExtension('undefined_publisher.vscode-extension-with-python')?.extensionPath; let pythonpath = vscode.workspace.getConfiguration('python').get<string>('pythonPath'); 拡張機能名 'undefined_publisher.vscode-extension-with-python' は作った拡張機能によって変わります。vscode.workspace.getConfiguration('python').get<string>('pythonPath') は VSCode の現在のワークスペースで選択しているインタプリタになります。 L.31 - L.49 は Python スクリプトを作らず、Python の文をそのまま実行する例です (略)。 L.51 - L.69 が python/hello.py を呼び出しているところです。 extension.ts // ログ出力などは省略 let options2: Options = { mode: 'text', pythonPath: pythonpath, pythonOptions: ['-u'], scriptPath: ext_path, args: ['Hello world from Python'] }; PythonShell.run('python/hello.py', options2, function (err, res) { if (err) { console.log(err); throw err; } const res_str: string = (res || [''])[0]; vscode.window.showInformationMessage(res_str); }); まず python-shell に渡すための Options オブジェクトを作り、pythonPath, scriptPath, args などを設定します。オプションについて詳細は公式情報を参照してください。 scriptPath は実行するスクリプトの置かれた path です。実行したいスクリプトは 拡張機能のインストール場所/python/hello.py にあるので、 scriptPathを拡張機能のインストール場所に設定しておきます。 PythonShell.run('python/hello.py', options2, function (err, res) の部分で実行するスクリプトとオプションを指定して実行しています。 実行時にエラーが起こると if (err) {} の中が実行されます。正常に実行されると res に標準出力が改行で区切ったリストで入っているので1、1つ目の要素を取得して VSCode 内に表示しているのが vscode.window.showInformationMessage(res_str); の部分です。 おわりに VSCode 拡張機能から Python を呼び出す方法を紹介しました。筆者のように TypeScript は分からないけど、Python なら書けるという人にとっては便利なのではないかと思います2。本記事の TypeScript は雰囲気で書いているので、書き方がおかしいなどあればご指摘ください。 オプションの mode を json や binary にすると変わります。 ↩ もちろん、エディタとやりとりするには VSCode の API を使うので TypeScript で書く必要がありますが、例えば Web上の API にアクセスする処理を既に Python で書いている、といった場合に使えるかと思います。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS][boto3][create_stack] CloudFormationスタックをシリアルに実行するサンプルスクリプト

概要 以前作ったスクリプト の拡張版 CloudFormationスタックを順次実行するboto3スクリプト 実行中のスタックが完了するまで次のスタックは実行しない 以下の要望に対応できそう CloudFormationスタックで作成したAWSリソースに依存したAWSリソースをboto3(Pythonスクリプト)で作りたい CloudFormationスタックの実行回数や実行ごとのパラメータを動的に変更したい 環境 Windows 10 の以下バージョンの環境にて動作を確認 VPC→サブネット→SG→EC2の順でシリアルにスタックを実行している。実行後に、作成したAWSリソースの情報(EC2のIDなど)が取得できていることがわかる。 PS C:\> python3 --version Python 3.8.10 PS C:\> aws --version aws-cli/2.2.3 Python/3.8.8 Windows/10 exe/AMD64 prompt/off PS C:\> python3 Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import boto3 >>> import botocore >>> print(f'boto3 version: {boto3.__version__}') boto3 version: 1.14.43 >>> print(f'botocore version: {botocore.__version__}') botocore version: 1.17.43 >>> PS C:\> aws configure AWS Access Key ID [****************XXXX]: AWS Secret Access Key [****************XXXX]: Default region name [ap-northeast-1]: Default output format [json]: スクリプト スクリプトはgithubにおきました。 実行前の準備 S3バケットを作成し、さらにprefix(フォルダ)を作成し以下のようにymlファイルを格納します。 PS C:\> aws s3 ls s3://{{S3バケット名}} --recursive 2021-05-07 18:32:58 1940 test/ec2-linux.yml 2021-05-07 17:36:00 1112 test/sg.yml 2021-05-07 17:46:45 1821 test/subnet-public.yml 2021-05-07 17:36:00 1062 test/vpc.yml 実行方法 コマンド例 C:\Users\usr001\tmp> python3 .\test.py -f sample.json -s3 {{S3バケット名}} -p test -f に sample.json を、-s3 に実行前の準備でymlファイルを格納したS3バケット名を、-p prefix(フォルダ)名を指定します。 制御について CloudFormaionスタック実行時に使用するymlファイルと指定するパラメータは、sample.json で指定します。 例1)sample.jsonが以下の場合、ymlファイルは vpc.yml でPJPrefixとVPCCIDRがパラメータになります。StackNameはスタック名です。 sample.json [ {"StackName": "test001-vpc","Code": "vpc.yml", "PJPrefix": "Project1","VPCCIDR": "10.11.0.0/16"} ] 例2)sample.jsonが以下の場合、2つのスタックを実行します。vpc.ymlを使用したスタックの実行が完了した後で、subnet-public.ymlを使用したスタックが実行を開始します。 sample.json [ {"StackName": "test001-vpc","Code": "vpc.yml", "PJPrefix": "Project1","VPCCIDR": "10.11.0.0/16"}, {"StackName": "test001-subnet1","Code": "subnet-public.yml", "PJPrefix": "Project1","NetworkName": "Net001","PublicSubnetCIDR": "10.11.1.0/24", "AZName": "ap-northeast-1a"}, ] 本リポジトリに格納したsample.jsonは以下であり、VPC→サブネットワーク→セキュリティグループ→EC2インスタンスの順にシリアルにスタックを実行します。※EC2起動は課金の可能性があるので注意※ sample.json [ {"StackName": "test001-vpc","Code": "vpc.yml", "PJPrefix": "Project1","VPCCIDR": "10.11.0.0/16"}, {"StackName": "test001-subnet1","Code": "subnet-public.yml", "PJPrefix": "Project1","NetworkName": "Net001","PublicSubnetCIDR": "10.11.1.0/24", "AZName": "ap-northeast-1a"}, {"StackName": "test001-sg","Code": "sg.yml", "PJPrefix": "Project1","ServiceName": "ServiceA","SGNo": "001"}, {"StackName": "test001-ec2","Code": "ec2-linux.yml", "PJPrefix": "Project1","ServiceName": "ServiceA","SGNo": "001", "NetworkName": "Net001", "KeyPairName": "keypair_ap-northeast-1_01", "EC2InstanceName": "ec2-001", "EC2InstanceAMI": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2", "EC2InstanceInstanceType": "t2.micro", "EC2InstanceVolumeType": "gp2", "EC2InstanceVolumeSize": "8" } ] 実行例 スクリプトを実行したときに出力される標準出力の例 PS C:\Users\usr001\tmp> python3 .\test.py -f sample.json -s3 {{S3バケット名}} -p test {'StackName': 'test001-vpc', 'TemplateURL': 'https://{{S3バケット名}}.s3-ap-northeast-1.amazonaws.com/test/vpc.yml', 'Parameters': [{'ParameterKey': 'PJPrefix', 'ParameterValue': 'Project1'}, {'ParameterKey': 'VPCCIDR', 'ParameterValue': '10.11.0.0/16'}]} [LOG] CFn Stack [test001-vpc] start. [LOG] CFn Stack [test001-vpc] end. スタック名 : test001-vpc スタックID : arn:aws:cloudformation:ap-northeast-1:121212121212:stack/test001-vpc/00000000-0000-0000-0000-000000000000 パラメータ VPCCIDR = 10.11.0.0/16 PJPrefix = Project1 [CloudFormation] Outputs VPCCIDR : 10.11.0.0/16 : Project1-vpc-cidr InternetGateway : igw-00000000000000000 : Project1-igw VPC : vpc-00000000000000000 : Project1-vpc {'StackName': 'test001-subnet1', 'TemplateURL': 'https://{{S3バケット名}}.s3-ap-northeast-1.amazonaws.com/test/subnet-public.yml', 'Parameters': [{'ParameterKey': 'PJPrefix', 'ParameterValue': 'Project1'}, {'ParameterKey': 'NetworkName', 'ParameterValue': 'Net001'}, {'ParameterKey': 'PublicSubnetCIDR', 'ParameterValue': '10.11.1.0/24'}, {'ParameterKey': 'AZName', 'ParameterValue': 'ap-northeast-1a'}]} [LOG] CFn Stack [test001-subnet1] start. [LOG] CFn Stack [test001-subnet1] end. スタック名 : test001-subnet1 スタックID : arn:aws:cloudformation:ap-northeast-1:121212121212:stack/test001-subnet1/0000000-0000-0000-0000-000000000000 パラメータ AZName = ap-northeast-1a PublicSubnetCIDR = 10.11.1.0/24 NetworkName = Net001 PJPrefix = Project1 [CloudFormation] Outputs PublicSubnet01 : subnet-0000000000000000 : Net001-ap-northeast-1a PublicSubnetCIDR : 10.11.1.0/24 : Net001-ap-northeast-1a-cidr PublicRouteTable01 : rtb-0000000000000000 : Net001-ap-northeast-1a-routetbl {'StackName': 'test001-sg', 'TemplateURL': 'https://{{S3バケット名}}.s3-ap-northeast-1.amazonaws.com/test/sg.yml', 'Parameters': [{'ParameterKey': 'PJPrefix', 'ParameterValue': 'Project1'}, {'ParameterKey': 'ServiceName', 'ParameterValue': 'ServiceA'}, {'ParameterKey': 'AWSResource', 'ParameterValue': 'EC2'}, {'ParameterKey': 'SGNo', 'ParameterValue': '001'}]} [LOG] CFn Stack [test001-sg] start. [LOG] CFn Stack [test001-sg] end. スタック名 : test001-sg b7a1f パラメータ SGNo = 001 ServiceName = ServiceA AWSResource = EC2 PJPrefix = Project1 [CloudFormation] Outputs SecurityGroupEC2 : sg-00000000000000000 : sg-Project1-EC2-001 [LOG] CFn Stack [test001-ec2] start. [LOG] CFn Stack [test001-ec2] end. スタック名 : test001-ec2 スタックID : arn:aws:cloudformation:ap-northeast-1:121212121212:stack/test001-ec2/00000000-0000-0000-0000-000000000000 パラメータ KeyPairName = keypair_ap-northeast-1_01 NetworkName = Net001 EC2InstanceName = ec2-001 SGNo = 001 EC2InstanceVolumeSize = 8 ServiceName = ServiceA EC2InstanceAMI = /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 PJPrefix = Project1 EC2InstanceInstanceType = t2.micro EC2InstanceVolumeType = gp2 [CloudFormation] Outputs EC2InstancePrivateIp : 10.11.1.45 : Project1-ec2-001-private-ip EC2InstanceID : i-00000000000000000 : Project1-ec2-001-id PS C:\Users\usr001\tmp> ーーー おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandasのメソッドだけでそこそこのグラフを描画したい

はじめに 本記事はこちらの取り組みの一環です。 使うデータ ↓このようにして集めてきたデータを使います。 結果(画像) やりたいこと Matplotlibは覚えること多くて嫌!サンプルは美しいけど、プレゼン用じゃなくて対象データの概観を掴みたいだけなのに、何でそんな行数書かなきゃいけないの??1行で何とかしたい! 使うライブラリ pandasのグラフ描画用メソッドを使います。裏で動いているのはMatplotlibで、拘らなければこれである程度いけます。 Matplotlib.pyplotもimport必要です。 実践 準備 %pip install japanize-matplotlib import matplotlib.pyplot as plt import japanize_matplotlib japanize_matplotlib.japanize() %matplotlib inline matplotlibの日本語化についてはこちら。 pandas.DataFrame.hist() df.hist()とするだけでとりあえずヒストグラムを書いてくれる。 ちょっと狭いので、画像サイズを指定し、また、ビン分割を増やして、度数を対数軸にしてみました。 df.hist(figsize=(20,10), bins=20, log=True) バルコニー140m2とか、平米単価900万とか一体何なんでしょうね。後ほど異常値(元データの入力ミスなど)かどうか調べることにします。 pandas.DataFrame.plot() これはさすがにdf.plot()では何が起きたのかよく分からないグラフが吐き出されます。(エラーにはならない) しかし、グラフの種類と使うカラムを指定すれば、df.plot(kind='scatter', x="総面積(m2)", y='価格(万円)')でExcelのデフォルトの散布図くらいのものは描けます。 もう少しいろいろオプションを指定するとこんな感じ。 df.plot(kind='scatter', x="総面積(m2)", y='価格(万円)', c='築年数', colormap='jet', loglog=True, figsize=(10,6), alpha=0.3, sharex=False) c='築年数', colormap='jet' カラーマップを使えば、3次元データを可視化できます。jetのほかにも以下から選べばOK loglog=True両対数軸の指定。片対数はlogx=Trueあるいはlogy=Trueです。尚、ヒストグラムのときの縦軸に対数を取るときは、logyではなく単にlog=Trueとしました。粉体の粒子径とか、ポリマーの分子量とか、所得とか、ピラミッド状に分布しているものは対数軸を取ると見やすいですね。 figsize=(10,6)単位はインチ。 alpha=0.3不透明度?プロットが多い時はalphaを指定したほうがデータの重なり具合が分かりやすい。 sharex=Falseはpandasのバグ対応で、デフォルトだとx軸のラベルや目盛り数字が消えてしまいます。 住所(行政区)別にグルーピングしてプロットの色を変えたい、などと思うときには、seabornでhueを指定するのがよいようですが、ここでは扱いません。 pandas.DataFrame.boxplot() df.boxplot(column='価格(万円)', by='住所2', figsize=(10,6), rot=90) 上側の外れ値が多くて見にくいですが、df.boxplot()メソッドの引数ではlogy=Trueと指定することが出来ません。これ以上拘るなら、サンプル事例の記事も少ないpandasのメソッドで無理に頭を捻るよりも、Figure, Axesのクラスの意味するところを理解してmatplotlibやseabornの公式のサンプルを参考にしたほうが良さそうです。 fig1, ax1 = plt.subplots(figsize=(10, 6)) ax1.set_yscale('log') ax1.set_ylabel('価格(万円)') df.boxplot(ax=ax1, column='価格(万円)', by='住所2', rot=90, showmeans=True) その他参考情報 xlabelが表示されないpandasのバグの情報は大変助かりました。 こういう気合の入ったブログには頭が下がります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python研修の振り返り②

python研修を終えたので、その備忘録としてメモ書きをしていきたい。 前回の①に続く形となります。 混乱しやすいところを中心に書きます。 誤りなどがあれば指摘いただければと思います エラーと例外処理 error.py my_list = [1,2,3] my_list[4] print("hello") ----- IndexError Traceback (most recent call last) <ipython-input-3-edac7686cc0b> in <module>() 1 my_list = [1,2,3] ----> 2 my_list[4] 3 4 print("hello") IndexError: list index out of range error2.py my_list = [1,2,3] try: my_list[4] except: print("error") print("hello") ---- error hello try~exceptを使用することで'error.py'ではエラーが返されたが、2つ目ではエラーが帰ってこず最後の"hello"が出力されていることが確認できる。エラー部分はexceptで処理が行われており、"error"が出力されている。 エラー処理.py try: 実行したい処理 except: エラー時の処理 以下でエラー処理の例をもう1つ挙げておく。 error3.py try: r = 4/0 print(r) except ZeroDivisionError as err: print("error: ", err) else: print("there's no error") finally: print("all are done") print("hello") ---- error: division by zero all are done hello 上記のコードをみると4/0のため"0で割ることができない"というエラーが返ってくるはずである。そのためexceptではこのエラーが返ってきた時、エラーを出力するようにしている。もしエラーが返ってこなければelse文以下が出力される。最後のfinallyはエラーが起きても起こらなくても出力がなされる。 クラス クラスが自分(プログラミング初学者)にとって最も理解しづらかったところであった。自分は"同じものを何回も使い回すことができる便利なもの"と解釈している。 簡単な例を以下に示す。 class.py class food(): def __init__(self, menu): self.menu = menu mesi = food("rise") main = food("meet") drink = food("milk") print(mesi.menu) print(main.menu) print(drink.menu) ---- rise milk meet class2.py class greeting(): def say_hello(self): print("hello") Hi = greeting() Hi.say_hello() ---- hello def()内にある引数selfは慣習的なものらしい。ないとダメなようなので記載している。またclass.pyからわかるように「.」をつけることで、その内容にアクセスすることができる。また"_init _”は初期化メソッドであり必ず実行されるものである。 car.py class Car(): def __init__(self, model ="None"): #modelの初期名はなし self.model = model def run(self): print("run") class Toyota(Car): def run(self): print("fast") class Tesla(Car): #Teslaの時のみmodelを先に宣言する def __init__(self, model="model S"): self.model = model def run(self): print("super fast") def auto_run(self): print("auto_run") car = Car() car.run() print("----------") toyota_car = Toyota("Lexus") print(toyota_car.model) toyota_car.run() print("----------") tesla_car = Tesla() print(tesla_car.model) tesla_car.run() tesla_car.auto_run() ---- run ---------- Lexus fast ---------- model S super fast auto_run 長々となってしまったが、ToyotaおよびTeslaの括弧の中のCarについて注目したい。各々はCarの特徴を受け取っており(今回であればmodel)、Toyota内でmodelの定義をしなくても、Carの特徴を引き継いでいることがわかると思う。 クラスに関して言えば自分の中で言語化できていないぶん、まだ理解しているとは言い難いのだとわかった。 研修におけるpythonの文法で気をつけなければいけないと思ったことは以上である。 最後までお付き合いいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandas DataFrameでの条件抽出

すぐ忘れてしまいそうなので、忘備録及び にそこまで載っていなかったので。 レファレンス: 困ったら、ここで調べるととりあえずは載っている。 で実施 元のデータのダウンロード data_dl.ipynb import pandas as pd url_train = "http://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data" url_test = "http://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test" cols= ["age", "workclass", "fnlwgt", "education", "educationnum", "maritalstatus", "occupation", "relationship", "race", "sex", "capitalgain", "capitalloss", "hoursperweek", "nativecountry","incomelevel"] df_train = pd.read_csv(url_train, names=cols) df_test = pd.read_csv(url_test, names=cols, skiprows=1) 1994 US Census Income Dataを使用 read_csv()が便利すぎ nominal(名義尺度)な列の確認 df_nominal.ipynb df_nominal=df_train.describe(include='all').head(4).dropna(axis=1) df_nominal index workclass education maritalstatus occupation relationship race sex nativecountry incomelevel count 32561 32561 32561 32561 32561 32561 32561 32561 32561 unque 9 16 7 15 6 5 2 42 2 top Private HS-grad Married-civ-spouse Prof-specialty Husband White Male United-States <=50K freq 22696 10501 14976 4140 13193 27816 21790 29170 24720 describe()をinclude='all'で基本統計量を出すと、数値の列はuniqueの行がNaNになるので、それをaxis=1で列を指定してdropna()で落としている。 binary(2値)な列の抽出 df_binary.ipynb df_binary=df_nominal.loc[:, (df_nominal.loc['count'] == 32561) & (df_nominal.loc['unique']==2)].T.drop(['unique','top'], axis=1) df_binary index count freq sex 32561 21790 incomelevel 32561 24720 調べるのに結構苦労した、2つの条件による抽出 (df_nominal.loc['count'] == 32561) & (df_nominal.loc['unique']==2) ()で囲うのと&や|を使用しなければいけない。 loc[行,列]の指定なので、[:,条件式]としている。 新な列の作成と条件による値の変更 balanced.ipynb df_binary=df_binary.assign(balanced=lambda df: df["freq"] / df["count"]) df_binary # # いったん数値列を作成 # df_binary["balanced"]=df_binary["balanced"].apply(lambda x: "balanced" if x > 0.4 and x < 0.6 else "imbalanced") df_binary index count freq balanced sex 32561 21790 0.669205 incomelevel 32561 24720 0.75919 index count freq balanced sex 32561 21790 imbalanced incomelevel 32561 24720 imbalanced assign()で新しい列を作成。 lambdaの引数はこの場合DataFrame(らしい・・・) そのあとapply()で条件により値を文字列に変更。 いろいろ試したところ、Seriesでやらないとうまくいかなかった。 ここではandが使えている。0.4 < x < 0.6とやりたかったけど、なんかうまくいかなかった。 まとめ pandasのDataFrame内での条件判定はandやorではなく& や|を使用するところや、列の値によって条件判定して値を変えるのはそれなりに使うと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで関数をいい感じに使うためのパッケージ

久々に読めないコードに対峙 これまでPython以外にもJava、JavaScript、PHP、C言語、C#、VBA、VB、Delphi、CSS、HTMLなんかを触ってきて、「プログラムってだいたいこんな感じの書き方」みたいなものがあると思っている。 そんな中、他人のコードを読もうとしたときに???となった。 一般的なのか分からんけど、メモしておく。 謎のコードは関数をいい感じに使うためのものだった まず、何を見たのかというと、 from functools import partial, reduce from toolz import pipe from toolz.functoolz import curry from toolz.curried import map as cmap class Xxx: def hogehoge(self, a, b): ... def bar(self, a, b, c): ... def fugafuga(self, a, b, c): ... def main(self): hoge = curry(self.hogehoge)("XXX") foo = partial(self.bar, a='xxx') fuga = reduce(self.fugafuga, "yyy") ... with path.open() as fp: return pipe( json.load(fp), cmap(itemgetter("from", "to")), partial(sorted, key=len, reverse=True) ) (゚Д゚)ハァ? カレー? パーシャル?直訳だと「部分的」とかそんな感じだよな パイプはシェルとかのアレだよな リデュース・・・・英語の意味も知らん とりあえず意味を調べていく。 curry カリー化という概念があるらしい。 Wikipediaによると、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)であるとある。 はぁ(;・∀・) JSの書き方が分かりやすい。 普通の関数は function sum(x, y){ return x + y; } console.log(sum(1, 2)) // 3 として、カリーっぽい書き方をすると function _sum(x){ return function(y){ return x + y; } } tochu = _sum(1) console.log(tochu(2)) // 3 となる。 tochuにセットされた戻り値は、普通の関数sumでいうところの第一引数が埋まった関数(第二引数だけ足らないsum)になる。 なので、tochuに引数を与えると、sumと同様の結果になる。 なるほど、これがカリー化か。 メリット・使いどころ ググったところだと、 引数がすぐに揃わない、順々に埋めていきたいとき # hogehoge が hoge(a, b, c) をカリー化したものとして tochu = hogehoge(a) # b を算出処理 ... tochu = tochu(b) # c を算出処理 ... kekka = tochu(c) 引数を部分的な固定をしたいとき(これはpartialとも機能が被る) # hogehoge が sum(a, b) をカリー化したものとして kotei = hogehoge(1) print(kotei(2)) # 3 print(kotei(3)) # 4 partial 部分適用という概念になるらしい。 カリー化とは厳密には区別されるものらしい。 関数をカリー化すると、結果的に部分適用の関数ができるみたいな理解をしている。(正確性は謎) カリー化の引数を部分的に固定したいときの例のままで、指定の関数の引数を一部固定したものを新たな関数の変数として生成する。 メリット・使いどころ 引数を部分的な固定をしたいとき kotei = partial(sum_, a=1) print(kotei(2)) # 3 print(kotei(3)) # 4 reduce これは結構解説しているサイトも多い。 python2.xの時は標準の関数だったらしい。 中々動作が言葉にするとややこしい。 reduce関数では、 第一引数に引数を2つ取る関数を指定する 第二引数にはイテラブルなオブジェクト(リストや辞書)を指定する ことが前提となる。 動作は、以下のとおり。 def func(x, y): return x * y some_list = [1, 2, 3, 4, 5] print(reduce(func, some_list)) # 120 some_listの最初の2つの要素に関数funcが適用される。 → func(some_list[0], some_list[1]) 最初の2つの要素を使って計算された値をresult1とすると、今度はこの値とsome_list[2]をfuncは引数にして、計算する。 → func(result1, some_list[[2]) あとは、リストの要素がなくなるまで繰り返す。 → func(...(func(func(some_list[0], some_list[[1]), some_list[2])...), some_list[[n]) メリット・使いどころ 同じ関数を繰り返し適用する場合に使用するのだろうけど、なかなか「ここだ!」というところが出てこない。 階乗の算出や合計の算出、パンくずリストの文字列生成に使うというのも考えられるけど、すでにそれ用の関数はあるので、これを使うことはパッと思いつかないかな・・・。 覚えておいて、「あ、reduce使えばいいじゃん」となる日を待つ感じになりそう。 pipe shellで使うパイプと同じように、最初の関数の結果(値でも良さそう)が、次に指定した関数の引数になる。 def double(a): return a + a def square(a): return a * a print(pipe(1, double, square)) # 4 メリット・使いどころ 値の編集において、順繰りに編集関数をかけたいときに使えそう ただ、使用する関数は、引数が1つでないと動かなそう(イテラブルのオブジェクトをどうにかするるとか、実は複数引数でも動かせるとかあるかもだけど) 複数必要な場合は、partialやcurryを使って引数を抑える工夫が必要・・・ だから今回見つけたコードはやたら部分適用やカリー化を使ってたのかΣ(゚Д゚) まとめ 使わないでもコードは書けるけど、書き方をコンパクトにできるという利点のあるこれらの機能。 誰かにコードを見られたとき、「こいつ・・・できる!!」感を出せる反面、ぱっと理解してもらえないというデメリットもあると思う。 ここぞというときのテクニックですな。 ちなみにカリーとは人の名前(ハスケル・カリー)から来ていて、食べ物とは何ら関係ないらしい(;´Д`)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出しpythonエンジニアのまとめ デザインパターンまとめ4(prottype)

趣旨 駆け出しエンジニアの備忘録の垂れ流し。  ※ 出典漏れがあればご指摘ください 出典: https://yamakatsusan.web.fc2.com/ https://dackdive.hateblo.jp/ https://www.oreilly.co.jp/books/9784873117393/ prottypeパターン概要 生成に関するデザインパターンの1つ。 「生成すべきオブジェクトの種類を原型となるインスタンスを作り、それを複製することで目的のオブジェクトの生成を行う」 インスタンスの生成にコスト(時間的や資源的)がかかる場合や、クラスとして規定するまでもないが(もしくはすべての種別をクラスとして実装するのが現実的ではないが)複数のインスタンスに個別の属性値を付与したい、異なる振る舞いのメソッドを実装する場合などに利用する。 利用頻度はそんなに高くなさそう クラス図とシーケンス図 wikipediaより引用 実装方針 Prototypeクラスはインスタンスを複製するための基底メソッド(clone)を規定する(template methodパターン)。※インスタンスを利用するための規定メソッド(use)も必要であれば規定する。 Prototypeサブクラス(ConcretePrototypeクラス)にインスタンスを実際に複製する責務を持たせる。cloneメソッドをオーバーライド。 selfをdeepcopyしたオブジェクトを返すメソッドとする。 オブジェクトの生成を管理するManagerクラスがあると便利。Managerクラスは、複製元のインスタンスを保持させ、指定に応じたインスタンスを複製するメソッドを実装する。 Client側ではManagerのIFを呼び出す処理を実装する。 実装例1 # prototype側 class Prototype: @abstractmethod def use(self): pass @abstractmethod def create(self) pass class ConcretePrototype1(Prototype): def use(self): do_something1() def create(self) clone = copy.deepcopy(self) # deep copyとshallow copyに注意 clone.set_parameter1(some_args) return clone class ConcretePrototype2(Prototype): def use(self): do_something2() def create(self, some_args) clone = copy.deepcopy(self) clone.set_parameter2(some_args) return clone # Manager側(例) class CreatingPrototypeManager: def __init__(self): self.__ins_dict = {} def register_ins(self, prot_ins_key: str, prot_ins: Prototype): self.__ins_dict[prot_ins_key] def create_ins(self, prot_ins_keye, some_args: obj) ins = self.__ins_dict[prot_ins_key] ins.set_params(some_args) return ins # client側 def main(): prot1 = ConcretePrototype1() prot2 = ConcretePrototype2() prot_mgr = CreatingPrototypeManager() prot_mgr.register_ins("prot1", prot1) prot_mgr.register_ins("prot2", prot2) # register_insまでの処理はManagerに持たせても良い) ins1 = prot_mgr.create_ins("prot1", params1) ins1 = prot_mgr.create_ins("prot1", params2) ins1 = prot_mgr.create_ins("prot1", params3) ins2 = prot_mgr.create_ins("prot1", params4) 使い所 使い所 インスタンスの生成にコスト(時間的や資源的)がかかる、かつ、複数のインスタンスを生成するケース  クラスとして規定するするまでもないが(もしくはすべての種別をクラスとして実装するのが現実的ではないが)複数のインスタンスに個別の属性値を付与したい、異なる振る舞いのメソッドを実装したいケース
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出しpythonエンジニアのまとめ デザインパターンまとめ4(prototype)

趣旨 駆け出しエンジニアの備忘録の垂れ流し。  ※ 出典漏れがあればご指摘ください 出典: https://yamakatsusan.web.fc2.com/ https://dackdive.hateblo.jp/ https://www.oreilly.co.jp/books/9784873117393/ prottypeパターン概要 生成に関するデザインパターンの1つ。 「生成すべきオブジェクトの種類を原型となるインスタンスを作り、それを複製することで目的のオブジェクトの生成を行う」 インスタンスの生成にコスト(時間的や資源的)がかかる場合や、クラスとして規定するまでもないが(もしくはすべての種別をクラスとして実装するのが現実的ではないが)複数のインスタンスに個別の属性値を付与したい、異なる振る舞いのメソッドを実装する場合などに利用する。 利用頻度はそんなに高くなさそう クラス図とシーケンス図 wikipediaより引用 実装方針 Prototypeクラスはインスタンスを複製するための基底メソッド(clone)を規定する(template methodパターン)。※インスタンスを利用するための規定メソッド(use)も必要であれば規定する。 Prototypeサブクラス(ConcretePrototypeクラス)にインスタンスを実際に複製する責務を持たせる。cloneメソッドをオーバーライド。 selfをdeepcopyしたオブジェクトを返すメソッドとする。 オブジェクトの生成を管理するManagerクラスがあると便利。Managerクラスは、複製元のインスタンスを保持させ、指定に応じたインスタンスを複製するメソッドを実装する。 Client側ではManagerのIFを呼び出す処理を実装する。 実装例1 # prototype側 class Prototype: @abstractmethod def use(self): pass @abstractmethod def create(self) pass class ConcretePrototype1(Prototype): def use(self): do_something1() def create(self) clone = copy.deepcopy(self) # deep copyとshallow copyに注意 clone.set_parameter1(some_args) return clone class ConcretePrototype2(Prototype): def use(self): do_something2() def create(self, some_args) clone = copy.deepcopy(self) clone.set_parameter2(some_args) return clone # Manager側(例) class CreatingPrototypeManager: def __init__(self): self.__ins_dict = {} def register_ins(self, prot_ins_key: str, prot_ins: Prototype): self.__ins_dict[prot_ins_key] def create_ins(self, prot_ins_keye, some_args: obj) ins = self.__ins_dict[prot_ins_key] ins.set_params(some_args) return ins # client側 def main(): prot1 = ConcretePrototype1() prot2 = ConcretePrototype2() prot_mgr = CreatingPrototypeManager() prot_mgr.register_ins("prot1", prot1) prot_mgr.register_ins("prot2", prot2) # register_insまでの処理はManagerに持たせても良い) ins1 = prot_mgr.create_ins("prot1", params1) ins1 = prot_mgr.create_ins("prot1", params2) ins1 = prot_mgr.create_ins("prot1", params3) ins2 = prot_mgr.create_ins("prot1", params4) 使い所 使い所 インスタンスの生成にコスト(時間的や資源的)がかかる、かつ、複数のインスタンスを生成するケース  クラスとして規定するするまでもないが(もしくはすべての種別をクラスとして実装するのが現実的ではないが)複数のインスタンスに個別の属性値を付与したい、異なる振る舞いのメソッドを実装したいケース
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pixivpyでpixivのブックマークした画像全てダウンロードしてくる

まえおき どうもこんにちは、なにもしていないのにいつの間にか4年生になっていたげんしです。 4年生は授業時間自体は少なくなったんですけど課題が多すぎてやばいです頑張ります。 退屈なことはpythonにやらせよう 誰でも1度は「手動でpixivのブックマークした画像全てをダウンロードしてくるのは面倒、、」と思ったことがあると思います。 思ったことがあると思います。思いましょう。思ってください。 そうすれば必然的に退屈なこと→pythonにやらせようとなるはずです(突然のタイトル回収) pixivpyの発見 非公式のライブラリですがpixivpyなるものを見つけました。 pixivpyを使うにはrefresh tokenとpixivアカウントが必要です。 refresh tokenを取得する 以下実行環境 ソフト バージョン python 3.8.5 pip 20.2.4 selenium 3.141.0 Pixivpy 3.5.10 SeleniumとPixivpyのインストール $ pip3 install selenium $ pip3 install pixivpy Google Chromeをインストールしておいてください。 お願いします。 Chrome Driverのインストール chromeの右上のメニュー/設定/chromeについて からお使いのchromeのバージョンを確認することができます。 僕の場合90.0.4430.72(2021/5/7現在)だったので こちらから合ったバージョンのchromedriverをダウンロードしてきましょう。 seleniumを使って取得する pixivpy製作者こちらのgithub gistに一通りかいてありますが要約すると pixiv_auth.py #!/usr/bin/env python import time import json import re import requests from argparse import ArgumentParser from base64 import urlsafe_b64encode from hashlib import sha256 from pprint import pprint from secrets import token_urlsafe from sys import exit from urllib.parse import urlencode from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities # Latest app version can be found using GET /v1/application-info/android USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)" REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback" LOGIN_URL = "https://app-api.pixiv.net/web/v1/login" AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token" CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT" CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" def s256(data): """S256 transformation method.""" return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii") def oauth_pkce(transform): """Proof Key for Code Exchange by OAuth Public Clients (RFC7636).""" code_verifier = token_urlsafe(32) code_challenge = transform(code_verifier.encode("ascii")) return code_verifier, code_challenge def print_auth_token_response(response): data = response.json() try: access_token = data["access_token"] refresh_token = data["refresh_token"] except KeyError: print("error:") pprint(data) exit(1) print("access_token:", access_token) print("refresh_token:", refresh_token) print("expires_in:", data.get("expires_in", 0)) def login(): caps = DesiredCapabilities.CHROME.copy() caps["goog:loggingPrefs"] = {"performance": "ALL"} # enable performance logs driver = webdriver.Chrome("./chromedriver", desired_capabilities=caps) code_verifier, code_challenge = oauth_pkce(s256) login_params = { "code_challenge": code_challenge, "code_challenge_method": "S256", "client": "pixiv-android", } driver.get(f"{LOGIN_URL}?{urlencode(login_params)}") while True: # wait for login if driver.current_url[:40] == "https://accounts.pixiv.net/post-redirect": break time.sleep(1) # filter code url from performance logs code = None for row in driver.get_log('performance'): data = json.loads(row.get("message", {})) message = data.get("message", {}) if message.get("method") == "Network.requestWillBeSent": url = message.get("params", {}).get("documentURL") if url[:8] == "pixiv://": code = re.search(r'code=([^&]*)', url).groups()[0] break driver.close() print("[INFO] Get code:", code) response = requests.post( AUTH_TOKEN_URL, data={ "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "code": code, "code_verifier": code_verifier, "grant_type": "authorization_code", "include_policy": "true", "redirect_uri": REDIRECT_URI, }, headers={"User-Agent": USER_AGENT}, ) print_auth_token_response(response) def refresh(refresh_token): response = requests.post( AUTH_TOKEN_URL, data={ "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "grant_type": "refresh_token", "include_policy": "true", "refresh_token": refresh_token, }, headers={"User-Agent": USER_AGENT}, ) print_auth_token_response(response) def main(): parser = ArgumentParser() subparsers = parser.add_subparsers() parser.set_defaults(func=lambda _: parser.print_usage()) login_parser = subparsers.add_parser("login") login_parser.set_defaults(func=lambda _: login()) refresh_parser = subparsers.add_parser("refresh") refresh_parser.add_argument("refresh_token") refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token)) args = parser.parse_args() args.func(args) if __name__ == "__main__": main() このgithub gistにかかれている上のプログラムを先程ダウンロードしてきたchromedriverを同じディレクトリに作って $ python3 pixiv_auth.py login を実行します。 実行するとログインページが出てくるのでダウンロードしたいブックマークの画像があるアカウントでログインします。 ログインするとターミナルにaccess_tokenとrefresh_tokenが表示されます。 いざ画像のダウンロード 5行目の変数REFRESH_TOKENに先程表示されたrefresh tokenを 6行目の変数USER_IDをご自身のpixivのユーザーID(pixiv.net/users/?????/ の?????の部分の数字)に書き換えて以下のプログラムを実行しましょう。 download.py from pixivpy3 import AppPixivAPI from time import sleep import json, os REFRESH_TOKEN = "先程表示されたrefresh token" USER_ID = "pixivのユーザーID" # フォルダの作成 if not os.path.exists("./pixiv_images"): os.mkdir("./pixiv_images") # login api = AppPixivAPI() api.auth(refresh_token=REFRESH_TOKEN) # ブックマークした画像のjsonを取得 users_data = api.user_bookmarks_illust(USER_ID, restrict='public') # なぜかAPIが30枚分の情報しかとってこないので30枚ごとを確認する変数 count = 1 #全体の画像の枚数をカウントする変数 allCount = 1 def downloadImage(users_data, count, allCount): # イラストの数だけ繰り返す ilustNum = len(users_data.illusts) for illust in users_data.illusts[:ilustNum]: author = illust.user.name.replace("/", "-") # ダウンロードフォルダなかったら作る if not os.path.exists("./pixiv_images/" + author): os.mkdir("./pixiv_images/" + author) # 保存先の指定 savepath = "./pixiv_images/" + author # 保存 api.download(illust.image_urls.large, path = savepath) print(str(allCount) + "枚目の画像: " + str(author)+" " + str(illust.title)) count += 1 allCount += 1 sleep(1) #30回目以降 if count > 30: next_url = users_data.next_url next_qs = api.parse_qs(next_url) # users_dataに30以降のjsonデータを再代入 users_data = api.user_bookmarks_illust(**next_qs) count = 1 downloadImage(users_data, count, allCount) downloadImage(users_data, count, allCount) print("ダウンロード終了") $ python3 download.py これでpixiv_imagesディレクトリにブックマークした画像が保存されているはずです。 まとめ 短く簡単なプログラムでやりたいことができるpythonは素敵です。 これからも退屈なことはpythonにやらせようの精神でやっていきたいと思います。 このプログラムはGithubで公開しているのでurlを貼っておきます。よければstarお願いします。 参考記事 Pixiv OAuth Flow (with Selenium) pixivpy Markdown記法 チートシート pixivのフォローユーザーの漫画・イラストを一括DL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pixivpyでpixivのブックマークした画像を全てダウンロードしてくる

まえおき どうもこんにちは、なにもしていないのにいつの間にか4年生になっていたげんしです。 4年生は授業時間自体は少なくなったんですけど課題が多すぎてやばいです頑張ります。 退屈なことはpythonにやらせよう 誰でも1度は「手動でpixivのブックマークした画像全てをダウンロードしてくるのは面倒、、」と思ったことがあると思います。 思ったことがあると思います。思いましょう。思ってください。 そうすれば必然的に退屈なこと→pythonにやらせようとなるはずです。 pixivpyの発見 非公式のライブラリですがpixivpyなるものを見つけました。 pixivpyを使うにはrefresh tokenとpixivアカウントが必要です。 refresh tokenを取得する 以下実行環境 ソフト バージョン python 3.8.5 pip 20.2.4 selenium 3.141.0 Pixivpy 3.5.10 SeleniumとPixivpyのインストール $ pip3 install selenium $ pip3 install pixivpy Google Chromeをインストールしておいてください。 お願いします。 Chrome Driverのインストール chromeの右上のメニュー/設定/chromeについて からお使いのchromeのバージョンを確認することができます。 僕の場合90.0.4430.72(2021/5/7現在)だったので こちらから合ったバージョンのchromedriverをダウンロードしてきましょう。 seleniumを使って取得する pixivpy製作者こちらのgithub gistに一通りかいてありますが要約すると pixiv_auth.py #!/usr/bin/env python import time import json import re import requests from argparse import ArgumentParser from base64 import urlsafe_b64encode from hashlib import sha256 from pprint import pprint from secrets import token_urlsafe from sys import exit from urllib.parse import urlencode from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities # Latest app version can be found using GET /v1/application-info/android USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)" REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback" LOGIN_URL = "https://app-api.pixiv.net/web/v1/login" AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token" CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT" CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" def s256(data): """S256 transformation method.""" return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii") def oauth_pkce(transform): """Proof Key for Code Exchange by OAuth Public Clients (RFC7636).""" code_verifier = token_urlsafe(32) code_challenge = transform(code_verifier.encode("ascii")) return code_verifier, code_challenge def print_auth_token_response(response): data = response.json() try: access_token = data["access_token"] refresh_token = data["refresh_token"] except KeyError: print("error:") pprint(data) exit(1) print("access_token:", access_token) print("refresh_token:", refresh_token) print("expires_in:", data.get("expires_in", 0)) def login(): caps = DesiredCapabilities.CHROME.copy() caps["goog:loggingPrefs"] = {"performance": "ALL"} # enable performance logs driver = webdriver.Chrome("./chromedriver", desired_capabilities=caps) code_verifier, code_challenge = oauth_pkce(s256) login_params = { "code_challenge": code_challenge, "code_challenge_method": "S256", "client": "pixiv-android", } driver.get(f"{LOGIN_URL}?{urlencode(login_params)}") while True: # wait for login if driver.current_url[:40] == "https://accounts.pixiv.net/post-redirect": break time.sleep(1) # filter code url from performance logs code = None for row in driver.get_log('performance'): data = json.loads(row.get("message", {})) message = data.get("message", {}) if message.get("method") == "Network.requestWillBeSent": url = message.get("params", {}).get("documentURL") if url[:8] == "pixiv://": code = re.search(r'code=([^&]*)', url).groups()[0] break driver.close() print("[INFO] Get code:", code) response = requests.post( AUTH_TOKEN_URL, data={ "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "code": code, "code_verifier": code_verifier, "grant_type": "authorization_code", "include_policy": "true", "redirect_uri": REDIRECT_URI, }, headers={"User-Agent": USER_AGENT}, ) print_auth_token_response(response) def refresh(refresh_token): response = requests.post( AUTH_TOKEN_URL, data={ "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "grant_type": "refresh_token", "include_policy": "true", "refresh_token": refresh_token, }, headers={"User-Agent": USER_AGENT}, ) print_auth_token_response(response) def main(): parser = ArgumentParser() subparsers = parser.add_subparsers() parser.set_defaults(func=lambda _: parser.print_usage()) login_parser = subparsers.add_parser("login") login_parser.set_defaults(func=lambda _: login()) refresh_parser = subparsers.add_parser("refresh") refresh_parser.add_argument("refresh_token") refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token)) args = parser.parse_args() args.func(args) if __name__ == "__main__": main() このgithub gistにかかれている上のプログラムを先程ダウンロードしてきたchromedriverを同じディレクトリに作って $ python3 pixiv_auth.py login を実行します。 実行するとログインページが出てくるのでダウンロードしたいブックマークの画像があるアカウントでログインします。 ログインするとターミナルにaccess_tokenとrefresh_tokenが表示されます。 いざ画像のダウンロード 5行目の変数REFRESH_TOKENに先程表示されたrefresh tokenを 6行目の変数USER_IDをご自身のpixivのユーザーID(pixiv.net/users/?????/ の?????の部分の数字)に書き換えて以下のプログラムを実行しましょう。 download.py from pixivpy3 import AppPixivAPI from time import sleep import json, os REFRESH_TOKEN = "先程表示されたrefresh token" USER_ID = "pixivのユーザーID" # フォルダの作成 if not os.path.exists("./pixiv_images"): os.mkdir("./pixiv_images") # login api = AppPixivAPI() api.auth(refresh_token=REFRESH_TOKEN) # ブックマークした画像のjsonを取得 users_data = api.user_bookmarks_illust(USER_ID, restrict='public') # なぜかAPIが30枚分の情報しかとってこないので30枚ごとを確認する変数 count = 1 #全体の画像の枚数をカウントする変数 allCount = 1 def downloadImage(users_data, count, allCount): # イラストの数だけ繰り返す ilustNum = len(users_data.illusts) for illust in users_data.illusts[:ilustNum]: author = illust.user.name.replace("/", "-") # ダウンロードフォルダなかったら作る if not os.path.exists("./pixiv_images/" + author): os.mkdir("./pixiv_images/" + author) # 保存先の指定 savepath = "./pixiv_images/" + author # 保存 api.download(illust.image_urls.large, path = savepath) print(str(allCount) + "枚目の画像: " + str(author)+" " + str(illust.title)) count += 1 allCount += 1 sleep(1) #30回目以降 if count > 30: next_url = users_data.next_url next_qs = api.parse_qs(next_url) # users_dataに30以降のjsonデータを再代入 users_data = api.user_bookmarks_illust(**next_qs) count = 1 downloadImage(users_data, count, allCount) downloadImage(users_data, count, allCount) print("ダウンロード終了") $ python3 download.py これでpixiv_imagesディレクトリにブックマークした画像が保存されているはずです。 まとめ 短く簡単なプログラムでやりたいことができるpythonは素敵です。 これからも退屈なことはpythonにやらせようの精神でやっていきたいと思います。 このプログラムはGithubで公開しているのでurlを貼っておきます。よければstarお願いします。 参考記事 Pixiv OAuth Flow (with Selenium) pixivpy Markdown記法 チートシート pixivのフォローユーザーの漫画・イラストを一括DL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

forループを用いて辞書のキーとバリューを同時に表示させる(今日のPython Day7)

0. はじめに  ついに1週間たちました。投稿している本人が一番驚いています。だってまだ7回しか投稿してないから。自分はもう100件くらい投稿したと思っていたらまだ7件とは。  話は変わりますが、1週間に1回くらいはやや難しめの問題を出そうと記事投稿をする前から考えていました。とりあえず1週間記念ということで今日はやや難しめの問題を出題です。でも、きちんと『入門Python3 第2版』の第11章までの知識で解けます。 1. 問題 以下の辞書dのキーとバリューを1つずつ出力せよ (以下のようになっていれば正解)。 # 辞書d d = {"C++": "ネコ", "Java": "ヤギ", "JavaScript": "サイ", "python": "ニシキヘビ"} # 結果 # C++ ネコ # Java ヤギ # JavaScript サイ # python ニシキヘビ 2. ヒント 辞書のキーとバリューをすべて取得するにはitems()関数を用います。 3. 解答 d = {"C++": "ネコ", "Java": "ヤギ", "JavaScript": "サイ", "python": "ニシキヘビ"} for k, v in d.items(): print(k, v) 4. 解説  まず辞書dのキーとバリューを1つずつ出力するのでfor文を用います。次に辞書のキーとバリューをすべて取得するにはitems()関数を用います。ここが難しかったかもしれません。 5. まとめ ・反復処理にはfor文を用いよう (今回はwhile文よりもfor文の方がやりやすいと思います)。 ・辞書のキーとバリューをすべて取得するにはitems()関数を用いよう。 6. おまけトーク  なんでPythonのイメージはニシキヘビなんですかね?最後まで読んでいただき本当にありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

銀魂のキャラで画像認識を行う

はじめに AIについて何か学びたいの思って、いろいろ探していて何から始めようと迷っていた時、Aidemyというプログラミングスクールを見つけた。「自然言語処理」について学んでいたのだが、大学の授業で画像処理をやっていて、それで何かできないかと思い、画像認識プログラムを作った。 画像認識を使い何をしようかと迷っていたらその時読んでいた漫画「銀魂」でなにかできないかと思いメインキャラクターを認識するプログラムを作った。銀魂はギャグ線が高くキャラクターが変装したりすることがおおく、それでも判別できるのかと気になり、それも分類できるのか試してみた。 分類は「坂田銀時」「志村新八」「神楽」の3キャラクターにした。画象の収集にはgoogle、bingなどの検索エンジンからひたすら集めた。googlecolabolatoryを使えば集めなくても済んだかもしてないが、集めているのも楽しかったのでよしとした。 それではプログラムを作るためにしたことと、実際のコードを見せる。著作権の問題で画象は見せれないということだけご了承願いたい。銀魂を読んだことがある人なら著者の空知英秋先生なら、許してくれるだろうと思うと思うがが、念のためcodeだけにしておく。 目次 1.画像認識を行う上で学ぶ必要があること 2.画像を集めること 3.画像の水増し 4.画像の学習とモデル構築 5.実際に別の画像でテスト 画像認識を行う上で学ぶ必要があること 画像認識を行う上で学ぶ必要があることは多いので、ここでは簡潔に説明する。今回のプログラムを作るのに必要な知識はnumpy,keras,matplotlibがメインで必要になってくる。これらはpythonで機械学習を行う上で必要不可欠な知識である。ほかにも必要なものはあるが、重要なものだけ紹介した。 画像を集めること 画象を集めることはとても大変だ。いちいち保存してというめんどくさい作業だからだ。初めはflickrのAPIを利用して行うつもりだったが、あまり画像がなく、自分で集める方法に切り替えた。自分で集める方法でも集まったのは ・銀時 約200枚 ・神楽 約150枚 ・新八 約150枚 これらをそれぞれのファイルにいれて今回はプログラムを実行する。これは機械学習を行う上で非常に少ない数値である。精度の高いモデルを作るには1000枚程度の画像が必要になってくる。今回は銀魂で行いたかったので、このままの数でやっていくことにした。 画像の水増し 画像の数が単純に少ないのでとりあえず水増しする #データの水増し #銀時、新八、神楽のデータを選択するパート for index, classlabel in enumerate(classes): photos_dir = '/Users/shoma.k/Desktop/gintama/' + classlabel files = glob.glob(photos_dir + "/*.jpeg") #画象データを50×50のnumpy形式に変換 for i, file in enumerate(files): if i >=253: break image = Image.open(file) image = image.convert("RGB") image = image.resize((image_size, image_size)) data = np.asarray(image) #50枚をテストデータにする if i < num_testdata: X_test.append(data) y_test.append(index) else: X_train.append(data) y_train.append(index) #角度を5度づつ、±30度までずらしてn増し(trainデータのみ) for angle in range(-30, 30, 5): num = 1 img_r = image.rotate(angle) data = np.asarray(img_r) X_train.append(data) y_train.append(index) #反転 img_trans = image.transpose(Image.FLIP_LEFT_RIGHT) data = np.asarray(img_trans) X_train.append(data) y_train.append(index) num +=num 画像の学習とモデル構築 kerasをつかったCNNのモデルを作る #テストデータと教師データをnumpy形式に変換 X_train = np.array(X_train) X_test = np.array(X_test) y_train = np.array(y_train) y_test = np.array(y_test) #分割したデータを保存 xy = (X_train, X_test, y_train, y_test) np.save("./ginnpy.npy", xy) #データの正規化、カテゴリカル化 X_train = X_train.astype("float")/256 X_test = X_test.astype("float")/256 y_train = np_utils.to_categorical(y_train, num_classes) y_test = np_utils.to_categorical(y_test, num_classes) #CNNの実装 model = Sequential() model.add(Conv2D(32, kernel_size=(3, 3),activation='relu',input_shape = X_train.shape[1:])) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D((2, 2))) model.add(Conv2D(128, (3, 3), activation='relu')) model.add(MaxPooling2D((2, 2))) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.25)) model.add(Dense(num_classes, activation='softmax')) #モデルをコンパイル model.compile(loss=keras.losses.binary_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy']) #学習の開始 hist = model.fit(X_train, y_train, batch_size=30, epochs=100, validation_split=0.1, verbose=1) #スコア scores1 = model.evaluate(X_test, y_test) print('loss = {:.4} '.format(scores1[0])) print('accuracy = {:.4%} '.format(scores1[1])) model.save('./gintama_cnn.h5') 画象が少ないためモデルの精度が悪く、ロスも多いが、結果はこうなった。 ・・・ 303/303 [==============================] - 11s 36ms/step - loss: 0.3481 - accuracy: 0.7790 - val_loss: 0.7810 - val_accuracy: 0.3119 Epoch 99/100 303/303 [==============================] - 11s 36ms/step - loss: 0.3514 - accuracy: 0.7779 - val_loss: 0.7851 - val_accuracy: 0.3109 Epoch 100/100 303/303 [==============================] - 11s 37ms/step - loss: 0.3547 - accuracy: 0.7768 - val_loss: 0.7690 - val_accuracy: 0.3218 5/5 [==============================] - 0s 8ms/step - loss: 0.4548 - accuracy: 0.7067 loss = 0.4548 accuracy = 70.6667% 実際に別の画像でテスト 実際にモデルの構築ができたのでやってみたかった画像でテストを行なった。 import keras import sys,os import numpy as np from keras.models import load_model from PIL import Image imsize = (50, 50) testpic = '/Users/shoma.k/Desktop/gintoki1.jpeg' keras_param = '/Users/shoma.k/Desktop/gintama/gintama_cnn.h5' def load_image(path): img = Image.open(path) img = img.convert('RGB') img = img.resize(imsize) img = np.asarray(img) img = img / 255.0 return img model = load_model(keras_param) img = load_image(testpic) prd = model.predict(np.array([img])) #精度の表示 print(prd) prelabel = np.argmax(prd, axis = 1) if prelabel == 0: print("gintoki") elif prelabel == 1: print("kagura") elif porelabel == 2: print("shinpati") これでCNNを使ったモデルテストを行えた。銀時の変顔を使ったのだが、実行結果に驚きの数値がでた [[0.849497 0.04320009 0.10730284]] gintoki もしかしたら似たような画像があったのかもしれないがある程度の判別できるモデルが作れたのではないかと思う。 最後に Aidemyを受講していなければ、このようなプログラムを自分で書こうとも思っていなかったし、作れるようになるとも思っていなかった。途中でやる気がなくなり、一度プログラミングから離れた時期があったが、自分で払ったお金のことを考えた時に、このままではいけないと思い、本当の本当にギリギリだが、自分の作品を作りこうしてブログの完成を目指してやれている。モチベーションが続かない時に何をしていたかと言うと、銀魂を読んでいた。結果こうして銀魂に関する画像認識プログラムも作れて、今回は本当に銀魂に救われたと思っている。まだ学生の期間が2年弱残っているので、なにかウェブアプリやスマホアプリをつくろうかなと考えている。まだまだ自分とプログラミングの戦いは長いので頑張っていきたいと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python,Django ブログを作って学ぶモデル入門!に挑戦

今回はブログを作りながら、モデルに触れてみよう!!挑戦です。 がんばるぞ〜!! まずは、いつのようにPaizaでサーバーを立ち上げてプロジェクトとアプリを作ります。 そして、models.pyの中に を記述。。 1行目はmodelsをインポートしている 6行目のcharFieldが文字って意味らしい・・そしてそれにはmax_lengthを設定するのが必須みたい。 大体の長さに今回は指定。。 あれか・・railsでいうstring型みたいな? blank=Falseはこの項目は必須 null=Falseはデータベース上中身が無いのはダメ unique=Trueは同じやつはダメよっていうバリデーションってことかな? その次の def __str__(self) はpythonのきまりみたいなもんって事で覚えておこう。 Tagも同じ内容なのでパス・・・ Postno方は 28行目は日付を取得してます 29行目は自動で最新日時を取得するってことらしい・・しかも新規追加のみ。。便利ね〜 30行目はユーザーに編集されたくないから、False 34行目は編集の場合ってことかな? 35行目で表示が変わっておる、多分これが編集の時は日時が変更しないってことだな 45行目のTextFieldは文字数に制限がないやつ。ブログの本文になる部分なので制限を設けない 49行目からはRailsでいうアソシエーションかな?? それぞれのClassの関係性を表してる感じですよね。 Django 1対多の関係をForeignKey 多対多の関係をManyToMany になるそうなぁ〜 51行目はこのカテゴリーが削除されたらpostも消えるというオプションらしい。 models.pyに記述をしたら今度はこれらをデータベースに知らせる工程 その前にsetting.pyにアプリを追加 Create migration file python manage.py makemigrations blog これをやったらこんなエラーが(~O~;) しかしあせらずにエラー文をよんだら、models.pyの記述がたりなかった↓ 一番下に追加 よかったぁ〜!!そして Migrate DB python manage.py migrate をしたらこんなかんじ railsからやった私的には、マイグレーションファイルを作ってマイグレートしたって感じかなぁ?と解釈 そして確認してみる python manage.py shell このコマンドでターミナルでpythonがつかえる・・つまりrailsの rails c みたいなものか・・ いろいろとデータベースに登録してみる にゃるほど〜。。。 途中エラーがでて焦り・・・それは、save()の記述の()を忘れてただけ〜 これを、ブラウザで確認するには、、 admin.pyに以下を記述 そしてスーパーユーザー作って、アクセスを許可 ありました〜!!中身を確認       しっかり保存されておりました!! 今回の挑戦も成功!!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django3のmakemigrationsの小技

概要 今、開発しているdjangoのprojectのmigration時に、models.pyのコートを書いては消し、としているので、よくデータベースを消すはめになります そこで、自分で調べてみて、簡単にmigrationの変更を削除、更新できるようにしたいと考え、忘備録として記していこうと思います。また私の環境で find . -path "*/migrations/*.py" -not -name "__init__.py" -delete find . -path "*/migrations/*.pyc" -delete と書いたら、Djangoを再インストールするハメになったので。このコマンドは使わないようにします。 開発環境 django==3.2.1 python==3.9.1 データベース==MySQL きっかけ 今回、私はaccountsというアプリケーションの中のmodelsに、UserというAbstractBaseUserをオーバーライドさせたmodelsを作成したのですが、primary_keyを設定して、makemigrationsしてしまったので、こんなのがターミナルに毎回出てきてしまいます。 WARNINGS: accounts.User: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. これを消すために、migrateして、データベースに保存されてしまったprimary_keyの値をなかったことにしたいと思います。 migrateのコマンドまとめ makemigrations python manage.py makemigrations (アプリケーション名)   migrate python manage.py migrate (アプリケーション名) migrationした時のnameを変更 python manage.py makemigrations --name (付けたい名前) migrateの履歴を見る python manage.py showmigrations (アプリケーション名) [ ]=migrateしてない [ X ]=migrate済み migrateの巻き戻し python manage.py migrate (ファイル名) ファイル名というのは0001や0002から始まるファイルを指しています。指定したファイル名の直前まで、巻き戻すことになります。 migrateを全てやり直し python manage.py migrate (アプリケーション名) zero 履歴からも削除 migrationファイルを削除 私の場合 python manage.py showmigrations accounts 結果 accounts [X] 0001_initial []の中のXはmigrateした際に、表示されます。(makemigrations時には空のままです。) migrate時にデータベースの方にmodelが保存されるようです。つまり、このXが消えれば、データベースの変更がなくなるらしい。なので、次のコマンドを使って、migrateをなかったことにしてみます。 python manage.py migrate accounts zero 結果 accounts [ ] 0001_initial となりました。 これを完全に消すためには、アプリケーション内のmigrationsと__pychahe__の中にある0001から始まるファイルを消します。 この後、使っていたデータベースも削除して、makemigrations,migrateを行いましたが、warningは消えませんでした。。。 とりあえず、errorにはなっていないので、このまま進めていこうと思います。 このwarningについて、知見のある方は連絡をしていただけると助かります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者が過去問精選 10問やっていく②

この記事の過去問を初心者が解いていきます。続きです 問4 ABC 087 B - Coins (200 点) x = int(input()) y = int(input()) z = int(input()) total=int(input()) count =0 for i in range(x+1):#500円が〇枚あるときの探索(x枚までで) for j in range(y+1):#100円が〇枚あるとき(y枚までで) for k in range(z + 1):#50円が〇枚あるとき(z枚までの間で) if total == i * 500 + j * 100 + k * 50:#totalが合計と同じになればcount+1する count = count +1 print(count) はじめ見たときは全く別の解き方で解こうとしていましたが効率の良い解き方はないかなと思って全探索の考え方を検索してときました。 最初何しているのかわからなかったですが一つ一つ何をしているのかをほどいていくと意外に単純なことしてるのだと気づきました。 第 5 問: ABC 083 B - Some Sums (200 点) N,x,y = map(int,input().split()) lst=[] def GG(n):#数値nの各桁の合計を得る関数 sum2 = 0 while n > 0: sum2 += n % 10 n //= 10 return sum2 for i in range(N+1):#N+1にしないと0から始まってるのでー1の状態で終わる if x <= GG(i) <= y: lst.append(i) print(sum(lst)) #ここから下は最初にやろうとして失敗したコード while i < N+1: sun = 0 s = i % 10 n =int( i / 10 ) sun = sun + s if s == 0: sun = sun+ n print(sun) print(type(sun)) if x <= sun <= y: lst.append(i) i += 1 print(sum(lst)) 結構苦戦しました。さいしょにやろうとしたコードだと桁の合計を求めようとしてぐちゃぐちゃになったり一回しかiを10で割れてなかったりして失敗しました。 なら関数作ったりしたらいいというヒントをもとに関数作ってそれをFORの中で実行という形にしました。 第六問 ABC 088 B - Card Game for Two (200 点) n = int(input()) ss = list(map(int,input().split())) #print(n,ss) ss.sort(reverse=True)#降順にソート odd = 0 #奇数 even = 0 #偶数 #print(ss)#listの確認 for i in range(n): if i % 2 ==0:#0,2,4,6,8~の時 even = even + ss[i] elif i % 2 == 1:#13579~の時 odd = odd + ss[i] print(even-odd) ソートの問題・・ということなのでソートを使った解き方をしています。 大きいほうから交互にとるという条件なので奇数偶数で行ける層と思いやってみたらいけました。 問5のような系統の問題のほうが苦手意識がありました。 今週はこのくらいで練習問題をいくつか挙げてくれているのでそれをやりつつ、土日で一気に残りの4問やっていこうと思います! ここは競技プログラミングのメモ(解けなかった)なので無関係なやつです ZONE4問目 t = [] x = input() a = list(x) count= 0 print(a) nagasa = len(x) for i in range(nagasa): if a[i] == "R": t =t[::-1] count = count + 1 print(count) else: gg = a[i] t.append(gg) print(t) print(type(t)) for aa in range(nagasa): if t[aa] == t[aa-1]: del t[aa] del t[aa-1] print(t)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リストと参照 Python競プロメモ⑤

list_a = [1,2,3] list_b = list_a list_b[0] = "a" print(list_a) print(list_b) 上のコードを実行すると、結果は以下のようになる。 ["a",2,3] ["a",2,3] 書き換えたのは、list_bの0番目の要素だけのはずだが、printしてみると明らかなように、list_aもまた0番目の要素が"a"となっている。これはリストが参照され、同じリストとして扱われるためである。 ここで、この解決策を記述しておく。 参照せずに、まったく同じリストを作成したいときはcopy()を用いることで可能となる。 そのコードを実際に記述すると import copy list_a = [1,2,3] list_b = copy.copy(list_a) list_b[0] = "a" print(list_a) print(list_b) 先ほどのコードと、copyの部分だけが異なっているが、このコードを実行すると、以下のようになる。 [1,2,3] ["a",2,3] このように、list_bだけが書き換えられて、list_aはそのままのリストとして取り出すことに成功した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Pi 4上のDockerコンテナでtensorflowを走らせるのに少しハマったので書く

タイトルの通り 前提 docker-composeを使ってpythonコンテナを走らせようと思いました. 何の変哲もないpythonコンテナで,x86_64のDocker環境だと普通に動いていました. docker-compose.yml version: '3' services: hogehoge-backend: restart: always build: context: ./hogehoge-backend dockerfile: ./Dockerfile container_name: hogehoge-backend working_dir: '/root/' tty: true volumes: - ./opt:/root/opt Dockerfile FROM python:3.6 USER root RUN apt-get update RUN apt-get -y install locales && \ localedef -f UTF-8 -i ja_JP ja_JP.UTF-8 ENV LANG ja_JP.UTF-8 ENV LANGUAGE ja_JP:ja ENV LC_ALL ja_JP.UTF-8 ENV TZ JST-9 ENV TERM xterm RUN apt-get install -y vim less RUN pip install --upgrade pip RUN pip install --upgrade setuptoolslibxkbcommon0 xdg-utils libgtk-3-0 RUN apt-get update RUN apt-get -y install python3-pip コンテナ内でアプリケーションを利用するためにpip用のrequirements.txtを用意していました requirements.txt pandas tensorflow requests keras urllib3 sklearn 初回起動時に pip3 install -r requirements.txt でインストールするのを想定してました(ビルド時に自動化はしてない) 前述の通り,x86_64環境でアプリケーション作ってから,「RPi4で動かせば電気代安いんじゃね・・・?」と素朴に欲を出して試してみたところ次節に述べる通りハマりました ハマったところ tensorflowがpipで入らない 状況 pipでrequirements.txtを用いてtensorflowをインストールしようと試みると下記のようなエラーが出る $ pip3 install -r requirements.txt ERROR: Could not find a version that satisfies the requirement tensorflow ERROR: No matching distribution found for tensorflow $ uname -a Linux 24aa5e15abb5 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux 解決策 pipをupgradeしてもダメだったので,結局wheelをダウンロードしてきた. $ wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v2.0.0/tensorflow-2.0.0-cp37-none-linux_armv7l.whl $ python3 -m pip uninstall tensorflow $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl バージョン選びたい方はこちらから選択して所望のバージョンのwhlを落としてくると良いと思います ※なお,tensorflow-on-armは cp35 か cp37 しか用意されていない様子だったので(どうやらcpの後の数字はpythonのバージョンらしい),あらかじめコンテナ上ではpython3.7を利用するようにDockerfileを書き換えてビルドしておいた Dockerfile - FROM python:3.6 + FROM python:3.7 h5pyが入らない 状況 前述の通りtensorflowをインストールしたところ,h5pyのインストール時にエラーが出る $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl Building wheels for collected packages: numpy, termcolor, wrapt, h5py Building wheel for numpy (PEP 517) ... done Created wheel for numpy: filename=numpy-1.20.2-cp37-cp37m-linux_armv7l.whl size=15411079 sha256=1934c0d7b18fcc872ff7b95a361b9580d7586aabab8bada8991e6df443ed047f Stored in directory: /root/.cache/pip/wheels/dc/89/4e/d661a082dcb028182ea7b4561c34dbcf717169c443ea0087a2 Building wheel for termcolor (setup.py) ... done Created wheel for termcolor: filename=termcolor-1.1.0-py3-none-any.whl size=4830 sha256=0844dee89ff65f17c6d1d00bd40ef6ac414156937121fa8e51773d7658511f64 Stored in directory: /root/.cache/pip/wheels/3f/e3/ec/8a8336ff196023622fbcb36de0c5a5c218cbb24111d1d4c7f2 Building wheel for wrapt (setup.py) ... done Created wheel for wrapt: filename=wrapt-1.12.1-cp37-cp37m-linux_armv7l.whl size=72200 sha256=e7331863b9a094e6ab7520c5157b321fc81739732dfc835b241a0f916a07c9ce Stored in directory: /root/.cache/pip/wheels/62/76/4c/aa25851149f3f6d9785f6c869387ad82b3fd37582fa8147ac6 Building wheel for h5py (PEP 517) ... error Loading library to get build settings and version: libhdf5.so (中略) running build_ext Loading library to get build settings and version: libhdf5.so error: Unable to load dependency HDF5, make sure HDF5 is installed properly error: libhdf5.so: cannot open shared object file: No such file or directory ---------------------------------------- ERROR: Failed building wheel for h5py 解決策 libhdf5-devをインストールする(※) $ apt-get install libhdf5-dev この後再度tensorflowのインストールを実行すると進む. $ python3 -m pip install tensorflow-2.0.0-cp37-none-linux_armv7l.whl ※はじめからDockerfileに記述しておいたほうが良いかも
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【数理考古学】群論とシミュレーション原理②群導出演算としてのガウスの巡回群

【数理考古学】群論とシミュレーション原理①これまでの投稿内容のまとめ。 吃驚するほどグチャグチャになってしまったので整理を試みます。 ガウスの巡回群(Gauss Cyclic Group) 「正n角形の作図からコンパスと定規を追放した」と言われる、コンピューターグラフィック史上最も重要な演算結果集合の一つ。群論においては、この演算こそが「半径1の単位円」を観測結果集合とする円周群=リー群$S_0$=1次元トーラスのコンストラクタ(初期化定義)に指定されています。 1の冪根 - Wikipedia 1の冪根は全て、複素数平面における単位円周上にある。また概要で述べたことより、1のn乗根の全体は位数nの巡回群である。これは円周群の正規部分群である。 1のn乗根は、複素数平面では、単位円に内接する正n角形の頂点である 代数学的背景はこんな感じ。 【数理考古学】代数方程式について。 数学において公式(Fomura)とは多くの場合恒等式(Identity)を意味するが、その定義は「どの様な値を代入しても両辺が等しくなる」式である。 これに対し「ある特定の値でなければ統合が成立しない式」を方程式(Equatation)と呼び、その値を根(Root)あるいは解(Solution)と言う。 未知数xを含む方程式(Formula)f(x)=0には様々な形式があり、このうち代数的演算(Algebraic operations、加減算、乗除算、冪根)を有限回用いて表せるものを代数方程式(Algebraic Formula)という。 大数学者ガウス(C.F.Gauss, 1777年〜1855年 )は「n次代数方程式はn個の根を複素数の範囲に持つ」事を証明した。これは代数学の基本定理と呼ばれている(ただし四次以上の方程式の解は代数的には求められない)。 ①「ガウスの巡回群」を図示化した正n角形には興味深い特徴がある。これを元gとして逆元$g^{−1}$を求めると次数が2n+1(奇数)の場合重ならず、倍の次数2(2n+1)(偶数)で初めて統合されるのである。【Python演算処理】単位トーラスを巡る数理②トーラス群の設定 以下の計算結果も、この数理と密接な関係にある。 cos(π)=-1\\cos(\frac{π}{2})=0\\cos(\frac{π}{3})=\frac{1}{2}\\cos(\frac{π}{5})+cos(\frac{3π}{5})=\frac{1}{2}\\cos(\frac{π}{7})+cos(\frac{3π}{7})+cos(\frac{5π}{7})=\frac{1}{2}\\cos(\frac{π}{9})+cos(\frac{3π}{9})+cos(\frac{5π}{9})+cos(\frac{7π}{9})=\frac{1}{2}\\cos(\frac{π}{11})+cos(\frac{3π}{11})+cos(\frac{5π}{11})+cos(\frac{7π}{11})+cos(\frac{9π}{11})=\frac{1}{2} 要するに自然数集合(Natural Set)$\mathbb{N}$に単位元(Identity Element)0と逆元(Inverse Element)$\mathbb{N}^{-1}$を加え整数群(Integer Set)に発展させる時、ガウスの巡回群の元と逆元は半径1の単位円上にかかる一対の奇数図形を描き続けるので、その頂点のコサイン値の総和もそれぞれ$\frac{1}{2}$となり続けるのである。最終的に正n角形のnが∞に到達すると$\frac{∞}{2}=∞$が成立してかかる半減は意識されなくなるが、これこそがまさに共役概念(Conjugated Concept)の出発地点といえよう。 位相が等差数列である三角関数の和の公式 以下の数理もこうした全体像の一部と推察される。 【数理考古学】とある円周率への挑戦? 【数理考古学】解析学史に「虚数概念」をもたらした交代級数 ライプニッツ級数(Leibniz series,1674年,グレゴリー級数がx=1の場合) \sum_{n}^{\infty}\frac{-1^n}{2n+1}=\frac{π}{4} グレゴリー級数(Gregory series, 1671年) \sum_{n}^{\infty}\frac{-1^n}{2n+1}x^{2n+1}=\frac{π}{4} ②複素数概念の導入が不可避となるのは三次方程式以上となる。 【数理考古学】三次方程式から虚数へ。 幾何学的には三角不等式(Triangle Inequality)$‖x+y‖ \leqq ‖x‖+‖y‖$が成立し面積の概念が生じる「円周上の三点以上の操作」に対応? そして幸いにも四点までの操作なら「ターレスの定理」により直角の概念が使えるのである。 【初心者向け】「三角不等式の体感方法? この様な数学的背景など知らなくても、(代数学的方法では求められないn次方程式を数値的に導出する)以下の計算方法に馴染みがある人なら少なくないはずです。 2πを角数n+1で割った集合θを作る。 それぞれのCos(θ)を求めX軸に置き、Sin(θ)を求めY軸に置く。 そしてこの演算群を導入すると、以下の様な計算も可能となるのです。 【初心者向け】挟み撃ち定理による円周率πの近似 一辺の長さがaの正n角形に外接する円の半径r $r=\frac{a}{2\tan(\frac{π}{n})}$ $a=r(2\tan(\frac{π}{n}))$ 一辺の長さがAの正n角形に内接する円の半径R $R=\frac{A}{2\tan(\frac{π}{n})cos(\frac{π}{n})}$ $A=R(2\tan(\frac{π}{n})cos(\frac{π}{n}))$ 一辺の長さがaの正n角形の外接円の半径と内接円の半径の関係 $r=Rcos(\frac{π}{n})$ $R=\frac{r}{cos(\frac{π}{n})}$ 外接円を単位円(Unit Circle)としたのが上記アニメーション。 外接円の半径Rは単位円の定義に従って1 これに内接する正多辺形の1辺の長さAは$2\tan(\frac{π}{辺数})\cos(\frac{π}{辺数})$ 外接円に内接する正多辺形の内接円の半径rは$\cos(\frac{π}{辺数})$ この演算の極限には半径1の単位円の場合における円周長として2πが現れます。半径rの場合に一般化すると算数段階で習う2πrとなりますね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pandasの使い方5

1 この記事は DataFrame型データの各種操作方法をメモする。 2 内容 2-1 日付から第何週なのかを計算する。 sample.py import datetime print("2021/5/3(月)が第何週になるかを自動計算する") datetime.date(2021, 5, 3).isocalendar() print("帰り値 -> 年,第何週,曜日。月曜日が1になる") 実行結果 #返り値 -> 年,第何週,曜日。月曜日が1になる (2021, 18, 1) 2-2 Series型データから実データを抽出する sample.py import pandas as pd s1 = pd.Series([70, 45, 80, 60, 90], index=['国語', '数学', '理科', '社会', '英語']) print("Series型データを表示する") print(s1) print("Series型からindexを抽出する") print(s1.index) print("Series型からデータを抽出する") print(s1.values) 実行結果 Series型データを表示する 国語 70 数学 45 理科 80 社会 60 英語 90 dtype: int64 Series型からindexを抽出する Index(['国語', '数学', '理科', '社会', '英語'], dtype='object') Series型からデータを抽出する [70 45 80 60 90]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

工業部品(O-ring)の外観検査を機械学習を使って行えるか?

1.目的 本記事は、昨今の製造業の生産ラインにおける省人化・自動化に変革を起こしつつある(と私が勝手に思っている)機械学習を用いた画像認識による外観検査機を、非IT系エンジニアの私でも作ることができるのか、実際にやってみて検証する記事です。 そこで本記事の目的を、「工業部品(O-ring)の外観検査を機械学習を用いて行うことができるか検証する」と設定しました。 なぜO-ringかというと、身近にいっぱい転がっていたからです。深い意味はありません。 2.結論 先に結論を言うと「単純な形状(O-ring)に限って言えば機械学習を用いて外観検査機を作ることは十分可能そうだ」となります。しかもかなり手軽に実現できそうです。 3.実現したい事 外観検査機でやりたいことは、以下のような製品画像を入力として、その製品画像が良品のものなのか、不良品のものなのか判別することです。 入力画像は300x300ピクセルのjpeg画像です。 見ての通り、右の不良品ではゴムが派手に切断されています。 これを検知するプログラムを作ります。 4.実装 環境は ・google colaboratory ・keras ・numpy ・matplot です。 google colaboratoryではgpuの設定をonにしておくと、学習がとても速く実行されます。 フォルダ構成は、作業フォルダ下のkeras_imageフォルダの中にngフォルダとokフォルダを用意して、その中に上で例を示した良品、不良品の画像をそれぞれ3600枚用意しました。 これら用意した画像を学習用データと妥当性検証データに8:2で分配して使用しました。 下記にソースを示します。 O-ringInspectionWithML.ipynb import keras from keras.utils import np_utils from keras.models import Sequential from keras.layers.convolutional import Conv2D, MaxPooling2D from keras.layers.core import Dense, Dropout, Activation, Flatten import numpy as np from sklearn.model_selection import train_test_split from PIL import Image import glob import tensorflow as tf import matplotlib.pyplot as plt import os from google.colab import drive drive.mount('/content/drive/') %cd "/content/drive/My Drive/O-ringClassify" folder = ['ok','ng'] image_size = 300 epo = 100 # 画像の読み込み、リサイズ、ndarrayへの格納 X = [] Y = [] for index, name in enumerate(folder): dir = 'keras_image/' + name files = glob.glob(dir + '/*.jpg') print(files) for i, file in enumerate(files): image = Image.open(file) image = image.convert('L') image = image.resize((image_size, image_size)) data = np.asarray(image) X.append(data) Y.append(index) X = np.array(X) Y = np.array(Y) # RGBデータの正規化 X = X.astype('float32') X = X / 255.0 Y = np_utils.to_categorical(Y, 2) # 訓練データとテストデータの分配(2割をテストデータへ) X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.20) X_train = X_train.reshape(X_train.shape[0],image_size,image_size,1) X_test = X_test.reshape(X_test.shape[0],image_size,image_size,1) input_shape = (image_size,image_size,1) model = Sequential() model.add(Conv2D(32,kernel_size=(3,3), activation='relu', input_shape=input_shape)) model.add(Conv2D(32,(3,3),activation='relu')) model.add(MaxPooling2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(64,activation='relu')) model.add(Dropout(0.5)) model.add(Dense(2,activation='sigmoid')) model.summary() # コンパイル model.compile(optimizer="SGD", loss=keras.losses.categorical_crossentropy, metrics=['accuracy']) #訓練 result = model.fit(X_train, Y_train,batch_size=20, epochs=epo,validation_data=(X_test,Y_test)) print(result.history.keys()) plt.plot(range(1, epo+1), result.history['accuracy'], label="training") plt.plot(range(1, epo+1), result.history['val_accuracy'], label="validation") plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show() model.save('100epochs20batches') 学習過程における正確性と妥当性の推移を以下に示します。 横軸がエポックで縦軸が正確性です。 結果として正確性が0.95まで上がっていて、妥当性も同様に上がっていることから、過学習を起こさずに正確性が順調に上がっていったことがわかります。 5.モデルの保存と読み込み 上のソースの最後の行で"100epochs20batches"という名前で保存したモデルを読み込んで実際に予測を行ってみましょう。 loadModelAndPredict.ipynb (省略) model = keras.models.load_model('100epochs20batches') folder = ['randomTestNg'] image_size = 300 X = [] Y = [] for index, name in enumerate(folder): dir = name files = glob.glob(dir + '/*.jpg') print(files) for i, file in enumerate(files): image = Image.open(file) image = image.convert('L') image = image.resize((image_size, image_size)) data = np.asarray(image) X.append(data) Y.append(index) X = np.array(X) Y = np.array(Y) # RGBデータの正規化 X = X.astype('float32') X = X / 255.0 X = X.reshape(X.shape[0],image_size,image_size,1) result = model.predict_classes(X) print(result) 作業フォルダ下のrandomTestNgフォルダに適当に選んだNg画像を保存して実行すると今回はたまたま [1 1 1 1 1 1 1 1 1 1] を出力し、0:ok, 1:ngなのですべて正しく識別できていることが確認できました。 6.本記事は以下のサイトを参考にさせていただきました。ありがとうございます。 7.謝辞 本記事は近藤春奈さん、角野卓三さん、マイケルムーアさんの貢献が極めて多きいところとなりました。感謝します。 そして、コードを見て非効率部分を指摘してくれた友人に感謝します。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python研修の振り返り①

python研修を終えたので、その備忘録としてメモ書きをしていきたい。 混乱しやすいところを中心に書きます。 誤りなどがあれば指摘いただければと思います。 演算子 演算子.py 8/2 #割り算 5//4 #商 10%4 #余り --- 4 1 2 インデックス pythonのインデックスは 0, 1, 2, 3, 4, 5, -6,-5,-4,-3,-2,-1 で示される。 index.py "python"[0] ---- "p" 一文字目のpが指定されている。 index.py "python"[-1] ---- "n" 引数を-1と指定すると最後尾の"n"が返される。 スライス スライス.py "apple"[2:4] ---- "pl" 文字列[開始地点:終了地点]で文字列をスライスすることができる。開始地点のインデックス番号は含むが、終了地点のインデックス番号は含まないのがややこしいと感じた。 zipを使って辞書を作成 words.py list_1 = [1,2,3] list_2 = ["APPLE", "ORANGE", "WOWOW"] dict(zip(list_1, list_2)) ---- {1: 'APPLE', 2: 'ORANGE', 3: 'WOWOW'} ループ words.py words = {1:"a", 2:"b", 3:"c", 4:"d", 5:"e"} 辞書型のwordsを作成 for.py for x in words: print(x) ---- 1 2 3 4 5 for.key().py for x in words.keys(): print(x) ---- 1 2 3 4 5 辞書の後にkey()で辞書のキー部分を取り出すことができる。 for.values().py for x in words.values(): print(x) ---- a b c d e 一方でvalue()を指定すると辞書のバリュー部分を抽出することができる。 for.items().py for key, value in words.items(): print(key, value) ---- 1 a 2 b 3 c 4 d 5 e items()をwordsに追加し、key,valueとすることで辞書内で対応するペアを抽出することができる。 関数 以下が関数の最もベーシックな型。 def.py def plus(a, b): return a+b plus(10, 10) ---- 20 〜以下怪しい範囲〜 args → 複数の引数を受け取ることができる args.py def greeting(*args): for arg in args: print(arg) greeting("hello", "Moimoi", "kon") ---- hello Moimoi kon t=[]のようにひとまずリストでまとめてからでも、*argsで受け取ることができる args2.py def greeting(*args): for arg in args: print(arg) t = ["hello", "Moimoi", "kon"] greeting(*t) ---- hello Moimoi kon kwargs → 複数のキーワード引数を辞書化する kwargs.py def haraheri(**kwargs): print(kwargs) haraheri(main="beef", drink="coffee") ---- {'main': 'beef', 'drink': 'coffee'} for.items.pyでのitems()をここでも利用してみると、(**kwargs)で受け取った辞書型の引数を対応するペア同士で出力することができる。 kwargs2.py def haraheri(**kwargs): for kind, food in kwargs.items(): print(kind, food) haraheri(main="beef", drink="coffee") ---- main beef drink coffee argsと*kwargsの併用 args_kwargs.py def menu(food, *args, **kwargs): print(food) print(args) print(kwargs) menu("apple", "banana", "orange", main="beef", drink="coffee") ---- apple ('banana', 'orange') {'main': 'beef', 'drink': 'coffee'} apple-food, banana/orange→*arges, main/drink→kwargsとなっている。 ~~~~~~~~~~~ リスト内包表記 リスト内包表記.py [i**2 for i in range(5)] ---- [0, 1, 4, 9, 16] リスト内包表記1.py [i*j for i in range(1,5) for j in range(1,3)] リスト内包表記1'.py for i in range(1,5): for j in range(1,3): print(i*j) 上記2つは同様の内容を出力している。 map()とlambdaを使ったリスト内包表記.py num = [i for i in range(7)] print(num) [x for x in map(lambda x: "even" if x%2 == 0 else "odd", num)] ---- [0, 1, 2, 3, 4, 5, 6] ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even'] map(実行したい処理, 実行対象)で行うことができる。 とりあえず今回はこの辺りで。 また次回に続きを書きます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ardino-firmata-pyMataを使ったI2Cセンサー接続GUIを作る

前記載のPCのI/OとしてのArduinoから ArduinoとFirmataを使えばPCから簡単にI/Oを操作できる事がわかりました。 そこで、pythonを使ってGUIでI2CセンサーをコントロールできるGUIサンプルを作りました。 接続図 手元にあったMPU6500を使って汎用的なI2Cセンサーを接続できるような Pythonプログラムを作ってみました。 下記にテスで使ったBMG160( TDK : IMU - 慣性測定ユニット 6-Axis MEMS MotionTracking Device)を使ったレジスタ設定テキストサンプル # Register Configration sample SLAVE,0x68 REG,0x00,1,0x00,R,CHIP_ID REG,0x02,1,0x00,R,RATE_X_LSB REG,0x03,1,0x00,R,RATE_X_MSB REG,0x04,1,0x00,R,RATE_Y_LSB REG,0x05,1,0x00,R,RATE_Y_MSB REG,0x06,1,0x00,R,RATE_Z_LSB REG,0x07,1,0x00,R,RATE_Z_MSB REG,0x08,1,0x00,R,TEMP REG,0x09,1,0x00,R,INT_STATUS_0 REG,0x0a,1,0x00,R,INT_STATUS_1 REG,0x0b,1,0x00,R,INT_STATUS_2 REG,0x0c,1,0x00,R,INT_STATUS_3 REG,0x0e,1,0x00,R,FIFO_STATUS REG,0x0f,1,0x00,RW,RANGE REG,0x10,1,0x00,RW,BW REG,0x11,1,0x00,RW,LPM1 REG,0x12,1,0x00,RW,LPM2 REG,0x13,1,0x00,RW,RATE_HBW REG,0x14,1,0x00,W,BGW_SOFTRESET REG,0x15,1,0x00,RW,INT_EN_0 REG,0x16,1,0x00,RW,INT_EN_1 REG,0x17,1,0x00,RW,INT_MAP_0 REG,0x18,1,0x00,RW,INT_MAP_1 REG,0x19,1,0x00,RW,INT_MAP_2 REG,0x1a,1,0x0000,RW,Interrput Select 上記レジスタ設定を交換すれば別センサーを簡単にI2C Read / Writeできるようにしてみました。 Python(winpython)を使ってGUI化した結果下記のような実行結果を得ました。 問題点として、動作が非常に重い事(通信速度9600bps固定)と、I2Cの制御が不安定な事が気になります。 I2CReadを行う場合、下記のような手続きが必要となります。 board.i2c_read(slave_addr, addr, 1, board.I2C_READ) time.sleep(0.3) data = board.i2c_get_read_data(slave_addr) pyMataの場合、このI2C Readタイミングをユーザ側で決めなければならないらしく非常に遅い。 300ms間隔開けても普通に失敗することもあるので、安全を見ると500msはほしいところ。 レジスタを読むだけならこれでいいのかもしれませんが、センサーをウォッチするには向いていない性能。 pyMataは既に更新止まっているので、別のpythonモジュールも探してみます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【なんだろう...】ソーシャル匿名掲示板を作った件Part.1【ちょっと使ってみてもらっていいすか?】

7秒で読める忙しい人用まとめ ここはすばらしいインターネッツですね 趣味でソーシャル匿名掲示板fora.socialを作りました。 アルファテスト中です。 ログイン不要でコメントができるのでぜひ記念カキコしてみてください。 サーバーよわよわなんで、アクセス集中するとサーバーダウンするかもです。 レスポンシンブ(スマホから見るやつ)は一応対応してますけど、完全対応ではないのでちょっと使いにくいかもです。 また、予告なくサイトを一時閉鎖する可能性があるのでご了承ください。 この記事では、 「なぜ今更匿名掲示板を作ったか」 「実装についてあれこれ」 「セキュリティ対策について」 「機能説明」 「今後について」 などざっくりと説明していきます。 1分で読めるまとめ 初カキコ…ども…ソーシャル匿名掲示板fora.socialを作りました... 俺みたいな、いまさら匿名掲示板作る腐れ野郎、他に、いますかっていねーか、はは 今日のyoutubeの急上昇ランキング マインクラフトやAPEXがどうとか 芸能人が○○してみた とか ま、それが普通ですわな そして俺は配信者勢いランキングを見て、呟くんすわ うんこちゃんと、兎田ぺこらどっちにしようかな え、どっちもやってない?それじゃあ it’a Abema TV.呪術廻戦でも見るか 好きな音楽 YOASOBI 尊敬する人間 にしむらひろゆき氏(愛してるって言うのはNO) なんつってる間に4時っすよ(笑) あ~あ、コロナ早く収まってほしいね、これ ちなみにくだらないノリはまだ続きますよ。 現在fora.socialはアルファテスト中です。 なんで「ソーシャル」なのかと言うと、スレッドがいいね数やコメント数に従ってランキング化されたり、各スレッドの人気コメントがスレッド一覧に表示されたりする部分がソーシャル?ということです。文字だと伝わりにくいですね。 あとは、「匿名掲示板」だけだと新しい響きじゃないし、パンチが弱いと思ったんです。 匿名掲示板の名の通り、ログイン不要でスレ建てやコメントの投稿ができます。 【トップページの画面。デフォルトでは全板総合の毎時ランキングです。】 【各トピックの詳細画面。画面下部のボタンからコメントできます。】 左上のボタンでカテゴリ移動ができます 全板総合、カテゴリ内、板内で、各種ソートが選べます。 板ごとにスレ投稿画面へ遷移できます スレ投稿画面では、「文字(1024文字以内)」「リンク」「画像(5MBまで)」「動画(youtubeとニコニコ動画のURLを貼ってください)」張り付けることができます ログインするとマイページから板の作成ができます いわゆる「ポートフォリオ?」というものではないです。作ってみようと思い立って、勢いで作ってみちゃった系です。 現在ユーザー登録(メアド不要)すると、板が二つまで作れる設定となっておりますので、是非板を作成してみてください!その他ご意見、ご感想お待ちしております。→運営への意見スレ はじめに うそはうそであると見抜ける人でないと(掲示板を使うのは)難しい くぅ~疲れましたw これにて一旦動くものができたみたいです! 実は、某Nchのトラフィックを見たら、じゃあ、自分も作ってみようというのが始まりでした 本当はweb系の技術なんてなかったのですが← やる気を無駄にするわけには行かないので流行りのネタで挑んでみた所存ですw 以下、ぼく達のみんなへのメッセジをどぞ 戦略家ぼく「みんな、見てくれてありがとう!ちょっと1000番煎じ感もあるけど・・・気にしないでね!」 フロントエンドぼく「いやーありがと!jqueryのすばらしさは二十分に伝わったかな?」 バックエンドぼく「見てくれたのは嬉しいけど、可用性がオワってるのが恥ずかしいわね・・・早めにAWSに移行しないときつくない?」 デザイナーぼく「見てくれありがとな!正直、全部Bootstrapだよ!角は丸くしろってジョブズも言ってたよ!」 マーケターぼく「・・・Qiitaに記事書いただけだけど、ありがと」ファサ では、 戦略家ぼく、フロントエンドぼく、バックエンドぼく、デザイナーぼく、マーケターぼく、俺「皆さんありがとうございました!」 終 戦略家ぼく、フロントエンドぼく、バックエンドぼく、デザイナーぼく、マーケターぼく「って、なんで俺くんが!?改めまして、ありがとうございました!」 本当の本当に終わり 閑話休題~名前について ソーシャル匿名掲示板を作りました。その名も、fora.socialです。 ダサいとか言ったら異界送りにします。 キマリ、ユウナ、マモル foraでもfora.socialでもどっちの呼び方でもいいんですが、ドメイン名も含めた方がオシャレじゃないですか。(musical.lyとかyoutu.beみたいなのが頭に残っていたんですよ。ちなみにmusical.lyはtiktokの前身のサイトです。https://www.musical.ly へ飛ぶとtiktokへリダイレクトされると思います。) はい、本当はfora.comとかfora.net、fora.siteというドメインが埋まっていて、有名なドメインが取れなかっただけです。 名前の命名も、匿名掲示板という性質上、本当はもっとアンダーグラウンドっぽい名前にしようかと思ったんです。ですが、アンダーグラウンドの雰囲気にし過ぎてしまうのはリスク高いな~と思い、この名前にしました。(2chも訴訟問題とか散々ありましたね)といっても、「聖なる雰囲気」というか、ちゃんとした雰囲気にし過ぎてしまうのもそれはそれで面白くないですよね。名前変えるかもしれないです。 ちなみにforaという言葉は、古代ローマ都市の公共広場、フォルム(forum)の複数形(fora)です。いろいろな人々が広場に集まって談笑や議論している姿を想像して、まさにfora.socialの行く末になって欲しいと思って命名しました。 ただ、日本的な名前にしたいなーという思いもあります。サイト内でも英語はできるだけ使わないようにしていますので。 「亀山社中」。。とか?意味不明ですかね。「にっぽん昔ばなし」とか、意味不明ですね。 「よもやまばなし」「よもやま」「四方八方」「四方山」「にっぽんよもやまばなし」 よもやまから離れられなくなってきました 「よろず報知」 とか 「瓦版(かわらばん)」 とかも面白いですね。ただオシャレな名前じゃないと若い人集まらなさそうです。 卍インターネッツ ...意味不明ですね うーん。難しいですね。 人々の話のログを整理し、残していくという意味で、「アカーシャクロニクル」とか「アカシックレコード」とか厨二病みたいな名前も考えました。かっこいいですけど宗教的な意味合いに曲解してしまわれそうで微妙ですかね 「人々が集まっていろいろと議論する場所」みたいないい感じの言葉ないですかね。 いい名前があれば募集中です。 個人的にfora.socialという名前は匿名掲示板としてクールすぎる気がしているんですよ なんだかんだバランス取れている気もしますけど ドメインの設定とかもめんどくさいので、とりあえずこの名前にしてます。 これでいいさ。20年…待たせたからな。もう おまえたちの時代だ! 戦略編 戦略家ぼく(数か月前)「匿名掲示板なんて3日でできるだろwよゆーよゆー」 そういえば、2ch(5chという名前になったことすら知りませんでした)やそのまとめサイトをあまり見なくなり、久しく経った気がします。TwitterやInstagram、Facebook、Youtubeといったアメリカ製のサービスが日本でも猛威を振るい、インターネッツ上での盛り上がりをごっそり奪い取っていってしまったのでしょうか。 なんとなく感じる物足りない感 今、何が流行ってるんだ? もっぱらYoutubeでお気に入りYoutuberの動画やライブ配信を見る日々。ニコニコ動画すらも見なくなりました。 何か物足りない。急上昇ランキングは格闘家しかいないし、ゲームはマイクラとAPEXしか世界に存在しないかのようです。 フラッシュ倉庫で赤い部屋やウォーリーを探さないでを見て眠れなくなったり、2chでポケモンのコイルを人気投票1位にしようとお祭りになったり、ニコニコ動画で謎のインディーズゲーム「東方」が盛り上がったり、パソコンに歌を歌わせる「ボーカロイド」が生まれたり.....。懐かしきインターネッツの日々が思い出されます。 何か足りない。困っちまうこれは誰かのせい。あてもなく混乱するエイデイ そんな日々の中で、創設から20年以上もたつ2ch(5ch)は、日本でもいまだにTOP50以内の優秀なトランザクション量を誇るとひょんなことから知りました。20年もたつのに、、、ぐぎぎ、、、、、。 3日間くらい考えて、匿名掲示板というもののインターネッツにおける役割について興味を持ち始めた戦略家ぼくは、匿名掲示板について調査を開始しました。匿名掲示板の定義を厳密に行うのは難しいですが、「インターネットにおける人格」というのは面白い研究分野であることがわかりました。4chan創業者、クリストファーブール氏の発言なども結構興味深かったです。 そして、暇な時間に匿名掲示板に類すると思われるサービスを調査という名のROMを続けていたところ、自分もじゃあ作ってみようかと、思い始めてしまったです.....。 調査結果 以下のような部分をどうするかで、サイトの方向性が決まるような気がします。 コメントのデフォルトの表示順 連番(2ch、Dcard)なのか、人気順か。スレの「流れ」を生み出すには、連番の必要がある。カスタムで新着順に表示できたとしても(youtube,yahooニュース,redditなど)、多くの人はわざわざカスタムで新着順にしてコメント欄を見るというめんどくさいことはしないため、スレの流れは生まれにくい。デフォルトの表示がどうなっているかが重要。 スレッド一覧画面の順 スレッドフロート方式(2ch)、人気順(reddit)、ユーザーへのサジェスト(youtubeのアルゴリズムみたいな)。 コメントの通報と削除、書き込み制限の仕組み コメント/スレッド作成の権限(ログインの要不要)-気軽にコメントできるかどうか。→気軽にコメントできる方が盛り上がりやすいが、削除・通報の仕組みや荒らしスパム対策をしっかり行う必要がある コメントに表示されるID-ユーザー名が表示される(reddit,youtube)のか。その場限りのID(2ch)か、完全に何もないか(爆サイ、がるちゃん) 中央集権かモデレータ主導か タイムスタンプとその表示形式→スレの流れが生まれるかどうかに影響する 以下は、各サービスをあっさ~く使ってみての、完全に個人の主観的感想ですので、実際のコアユーザーの意見と異なると思います。 匿名掲示板に類する有名なサービス 日本 2ch-日本最大規模の匿名掲示板。スレッドに「雰囲気」というか「流れ」がある。 5ch-2chと色々あったらしい。2chのコメントは5chに転送されている。エッティな広告が多い。 ガールズちゃんねる-通称ガルチャン。男がいると「こいつ男だろ」 と言われる。 はてな匿名ダイアリー-通称増田。独自の文化を生成。 したらば-2ch型の掲示板をレンタルできるサービス。中央集権型の2chとは違い、各板に管理人がいる。2chの避難所だったりする。 mixiコミュニティ-いまでも一定のユーザーがいる。この中ではかなりマイルド?なコミュニティ lobi-ゲームコミュニティ。若い人も多く、秩序が保たれている印象。 Yahoo知恵袋-質問形式の知恵共有サービス。ベストアンサーへのインセンティブ設計がよくできてる。 ヤフーニュースのコメント欄-真面目系のコメントが多い。コメントの「流れ」があまりなく、単発のコメントの集合体。 Youtubeのコメント欄-あくまでも動画サイトであり、動画についての話題しかない。コメントの「流れ」があまりなく、単発のコメントの集合体。だが、youtube自体の規模が巨大なため、コメントも多い。有名な動画は日本語と英語が入り混じっている。動画によっては掲示板っぽい使われ方がされている場合もあり。PV数からしたら、視聴者のコメント「率」はかなり低い 爆サイ-ローカルクチコミサイト。ID表示がなく、雰囲気はかなりフリーダム。 各種まとめサイト 匿名掲示板に類する有名なサービス 世界 reddit-おそらく英語圏最大の掲示板サイト。世界TOP10のトラフィックを誇る。掲示板の運営はモデレータによって行われており、サイトの構造はしたらばに近い。karmaというポイントシステムがあり、ポストに対するインセンティブがある。ログイン必須。 digg-ソーシャルニュースサイト。一時はredditと双璧を成したっぽいが、今は掲示板というよりもニュースサイトとしての側面が強い。 4chan-日本のふたば☆ちゃんねるに影響されて作られた英語圏の画像掲示板。登録システムが存在しなく、2chに近い。投稿が一定期間で削除されるという性質上、英語圏で最もアンダーグラウンドな雰囲気漂う匿名掲示板。 PTT (台湾)-台湾の2ch 巴哈姆特:バハムート-台湾のゲームコミュニティ。台湾におけるゲーム、アニメ、漫画の発信地である。 Dcard-台湾の大学生専用のコミュニティサイト。台湾有数のSNSとしても有名。ユーザーが大学生という性質上、この手のサイトにしては珍しくアングラ感はあまりなく、女性ユーザーが多い。メイクや化粧、恋愛相談の話題も多くなされている印象。 Plurk-台湾のサブカル系SNS。肉が歩いている。変なタコみたいなキャラクターが特徴。twitterと共存している?もともとはカナダで開発された? craygslist-アメリカ最大のクラシファイドサイト。シンプルなUIながら、月間10億PV以上を誇る。 slashdots-テクノロジー系の匿名掲示板。日本では「スラド」として知られている。 tumblr twitter 世界と言っておきながら、アメリカと台湾しかないですが。中国にもいろいろあるのでしょうが、あまり調べられませんでした。 いろいろと匿名掲示板に類するサービスを調べていくうちに、思ったこと 日本の匿名掲示板系のサービスは英語圏のものに比べてシステムが古い傾向 台湾と比べても、経済規模は日本の方が上なのに、台湾の方がシステムが優れている印象 しかし、日本の各掲示板にも、一定のユーザーがいて、独自のコミュニティを形成している。=匿名掲示板というコミュニケーションの形は、古今東西問わず一定の需要があるのでは? なぜ英語圏の素晴らしいサイト(主にreddit)を使わず、日本の古いサイトを使い続ける人が多いのか?→言語、文化が違うから。+redditが本気で日本を狙っていないから?日本向けのチューニング(翻訳やマーケティング)が細かいところまで行き届いていなくて、「日本人ですけどredditはん、使わせてもらいまっせ。おおきに~」みたいな微妙な居心地の悪さを感じ、なんというか「おれたちのコミュニティ」みたいなアットホーム感がない。わざわざスレッドにご丁寧に「日本語」みたいなタグがついてても拍子抜けしちゃうでしょ。「ここはおれたちの居場所じゃねえ」って だいたい、サブレってなんですか。鳩のあれですか?板って呼んで、くれないか コメントの匿名性と、サイトの秩序というものに相関がある。コメントの匿名性が高いほど秩序を保つことが難しくなるが、思いもよらない化学反応的な面白さが生み出されることもある。→創発現象 中央集権型のサービスと、モデレータ主導型のサービスがある 匿名掲示板というサービスの性質上、自然と男性の割合が多くなる傾向にある気がする。 日本では匿名掲示板系のサービスは、ユーザーの高齢化が進んでいる印象。若い人は匿名掲示板でやり取りするという概念がそもそも無い。twitter、instagramまたはyoutubeのコメント欄が3大若い人のコメントが多いサイトか。しかしtwitterやinstagramはリアルの友達と繋がっていることもあり、息苦しく感じる人も少なくない→何か言いたいことがあるけど内に秘めたまま言えず、ストレスを抱え込む人がわりと多いのではないか? 匿名ということもあり、トラブルの際どうするかのルール・線引き(権利、責任)をきちんと行う必要がある。 コミュニティが廃れてしまう原因の大きなものとして、荒らしやスパムの存在がでかい 以上を踏まえ、作る方向性 「匿名」掲示板である。→コメント/スレッドの作成や、スレッドやコメントへの「いいね」はログインしなくてもできるように。 サイト全体の構成→「サイト全体」にユーザーが付くのではなく、「板」に対するユーザーがつく(=板ごとに文化が生成される)ように、板へのフォロー機能を実装。(ログインユーザーのみ) 秩序を保てる掲示板システム→スレッド内にはID制度(IPアドレス×スレッド×日づけのため、スレッドが変わればIDが違くなる。)を導入し、自作自演をしにくくした。ユーザー同士のチャットはできず、ユーザーフォローはできないようにすることで、個人VS個人の争いができないようにした。各スレッドやコメントに報告ボタンを付け、ログイン不要で規約違反や不快なコメントの報告ができる。(報告はIPアドレスに紐づいているので連投はできない。)多く報告されたコメントやスレッドは自動的に削除(非表示)される(いいね数が多いものはその分たくさん報告される必要あり)。また、そのコメントやスレッドを投稿したIPアドレスを内部的に管理し、一定期間で多く報告がされたIPアドレスを、板ごとに一定期間書き込み制限とする。(IPアドレス×板×一日くらいの期間で書き込み制限がかかる。) モデレータ主導型サービスにしたい→アカウントを持つユーザーが板とそのルールを作成、スレッドやコメントの削除権限を付与。 若い人でも使えるように→動画やリンクの埋め込みを可能とし、見た目的にもとっつきやすく。また、ランキング機能の充実によりトレンドを知りたい若い人のニーズにもこたえる。さらに、PWAの技術でスマホからでもアプリライクなアクセスを可能に。 インセンティブ→ログインしていると、もらったいいね数が蓄積していく。(redditのkarmaに近い)ただ、板のモデレータのインセンティブ設計が難しい。。。。「自分の考えた板が盛り上がると嬉しい」だけだとインセンティブとして弱いので、考える必要がある。wikipediaみたいに「モデレータコミュニティ」みたいなのを作って発展させていければ。。。? スレッドの整理と蓄積-Googleは「検索」型サービス。目的があって、使うサイト。匿名掲示板は「探索」型。探索型サイトとして最善のやり方で、スレッドやコメント情報の整理と蓄積を目指す→カテゴリによる分類、タグによるメタ情報整理、PV数といいね数による人気度の数値化(←twitterは過去情報は蓄積されないし、整理もされない。最大の違い。twitterはフロー型。foraはストックを目指す)詳しくはフォークソノミーのwikiとか読んでみると結構おもしろいですよ 実装編 ジェバンニが一晩でやってくれました うそです。バックエンドぼくが一生懸命、数か月頑張ってくれました。 まずは技術的な仕様をご紹介します。 ざっくり技術的な仕様 バックエンド python3.7.5 Django2.2.17 (pythonフレームワーク。元はニュース系のサイトを管理するために作られたフレームワークらしい。instagramやpinterestのバックエンドはdjangoらしいと聞いてdjangoを選択。テンプレート(html生成)機能が標準で搭載されているのも良い。) フロントエンド 主にDjango標準のテンプレート機能。モダンなjsフレームワークは使っておりません。reactとかvueの勉強がめんどくさかったわけではないですよ・・・・。 js系 9割jquery、その他lightbox.jsとかlazyload.jsとか便利系ちょこちょこ css系 bootstrap アイコンはfontawesome DBサーバー-MySQL on pythonanywhere django標準のインストールだとsqlite3ってのが入っていると思いますが、さすがにsqlite3で運用は厳しいんじゃないかと。 バックエンドサーバー-pythonanywhere djangoならAWSよりも手軽にデプロイできると思います。手軽さと、趣味で作ってみましたってことでpythonanywhereを選びました。バッチ処理とかも簡単にできます。課金するとストレージ容量だったり、サーバーのスペックをある程度強化できます。ただ、細かいDBの(水平・垂直)分割とかの設定は難しいみたいなので、スケーリングを考えるといずれAWSかその他のホスティングサービスの方がいいと思います。あとはネット上にpythonanywhereの日本語の情報がめちゃ少ないので、pythonanywhereのforumのQ&Aとかは結構見てました。 ストレージ-pythonanywhere ドメイン-お名前.com Djangoあれこれ →気が向いたら書きます。 フルスタックMVCフレームワーク REST化も簡単 テンプレートタグとフィルター バッチ 優秀なORマッパーで、SQL文知らなくても使える セキュアなユーザー管理システム adminサイト標準搭載 豊富なmiddlewareでセキュリティ対策もやりやすい pythonanywhereあれこれ →気が向いたら書きます。 実はAWSのEC2? デプロイが簡単 監理サイトが使いやすい 無料モードもあり お名前コムあれこれ →気が向いたら書きます。 PWA(progressive web application) これは、webサイトをスマホアプリのように振舞わせることができる手法です。 一応iOSの方は、上記サイトにsafariでアクセスしてもらって、「ホーム画面へ追加」を押すと、アプリっぽい動作をするものがスマホで使えます。(スタンドアロンで起動します) manifest.jsonとserviceworkerを設定するのですが、気が向いたら書きます。(わたしも深くは理解してないですが) ページの表示スピードを速くしたい ハイパフォーマンスwebサイトこのあたりを読んだりしましたが、、そこまでチューニングできていないです。 問題はフロントエンドだ cssはインラインに書くな→レンダリングの阻害 jsとcssの場所に注意しろ CDNを使え gzip化しろ facebookとyoutubeに気を付けろ 画像のlazyload stdimageの活用 django DBの仕様ー外部キーにはインデックスがデフォルトで付いている djangoのキャッシングフレームワークは優秀だが、使いどころには気を付けろ セキュリティ対策編 webアプリケーションにはセキュリティ対策が必須です。特に掲示板のようなサービスは、気を付けなければいけないですね セキュリティ対策については、このあたりのサイトを参考にさせていただきました。 IPA 安全なウェブサイトの作り方 現場で使えるdjangoのセキュリティ対策 Djangoで開発する際にセキュリティについて気をつけること GoogleSearchconsoleと、webサイトの脆弱性診断サイトgredでの結果は次のようになりました パスワード系 パスワード不正取得 →ブルートフォースアタック対策を行う必要があります。 ログイン機能には、django-allauthを使っています 詳しくはこの辺りでhttps://qiita.com/jansnap/items/62f0de512bc8771ebd58 パスワードのバリデーションは、settings.pyの以下のコードで行われてます。 AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] ユーザー登録の際には、ある程度のパスワードの複雑さが必要です。推測されやすいパスワードの登録はできません。 ログイン制限 ログイン画面でパスワードを一定回数間違えると、一定時間ログイン制限がかかります。 なんでその部分だけ英語なの?というと、django-alltuhをインストールして、標準のテンプレートを使うとこうなるからですね。日本語にする方法はちょっとよくわかりません。いずれ対応します。 パスワード漏洩 データベースにパスワードが平文、あるいは容易に復号可能な状態で保存されている場合、データベースからデータが流出した場合に、不正アクセスされるので、対策の必要がある。 結論から言うと、foraでパスワードは平文で保存されていません。入力されたパスワードとランダムな文字列を結合し、ハッシュ関数を一定回数繰り返し使ってハッシュ化してからデータベースに保存しているため、複合ができないです。たとえデータベースからパスワードが流出しても元のパスワードを流出データから知ることはできません。 パスワードはこんな感じで保存されています ちょっと詳細 foraで使っているユーザーモデルは、AbstractUserを継承しています。 from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): django/contrib/auth/models.pyをみてみるとAbstractUserはAbstractBaseUserを継承していて from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager class AbstractUser(AbstractBaseUser, PermissionsMixin): django/contrib/auth/base_user.pyをみてみると from django.contrib.auth.hashers import ( check_password, is_password_usable, make_password, ) class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) # Stores the raw password if set_password() is called so that it can # be passed to password_changed() after the model is saved. _password = None def set_password(self, raw_password): self.password = make_password(raw_password) self._password = raw_password def check_password(self, raw_password): """ Return a boolean of whether the raw_password was correct. Handles hashing formats behind the scenes. """ def setter(raw_password): self.set_password(raw_password) # Password hash upgrades shouldn't be considered password changes. self._password = None self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) 入力されたパスワードをmake_password関数で何らかの処理をして、結果をpasswordテーブルに保存しているようです 入力された生のパスワードは、self._password = Noneの処理で消されているようですね。 以下の処理は、Djangoはパスワードをどうやって保存しているのかと同じですので、気になる方はこちらをチェックしてみてください。 認証系 cookieとセッション(セッションハイジャック) →セッションハイジャックされないようにする必要がある Djangoでは、セッション管理はCookieベースであり、URLにcookieの埋め込みはしません。また、セッションIDはランダムな文字列として生成されてます。 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', ] また、settings.pyに以下の設定をすることで、cookieにsecure属性を付与しています SESSION_COOKIE_SECURE = True アクセス制御 →非公開情報を第三者に見られないようにする デコレータの活用 from django.contrib.auth.decorators import login_required @login_required def hoge(request): 管理サイトのURLをデフォルトから変える https://www.fora.social/admin/ にアクセスすると debug=Falseにする インジェクション SQLインジェクション DjangoのORマッパーはセキュアなSQLを組み立てるため、Djangoを普通に使っている限り心配する必要はないかと HTTPヘッダインジェクション setting.pyのALLOWED_HOSTSでホワイトリストを設定してます クロスサイト系 XSS(クロスサイトスクリプティング) →画面への出力処理にスクリプトを埋め込める脆弱性がある場合、利用者のブラウザで不正なスクリプトが実行される危険性がある djangoのテンプレートを使っている場合基本的にはオートエスケープ機能が働いていて、XSSの心配はないです。ただし、以下のような記法は注意する必要があります。 {{ hogepiyo | safe }} この「safe」フィルタは、文字列に対して、さらなるエスケープが必要でないことをマークするのに使います。 ユーザーが投稿可能なフィールドの出力に「safe」を付けると、 <script>alert(1)</script> などがエスケープされずにそのままhtmlの要素としてブラウザでレンダリングされるため、悪意あるユーザーにスクリプトを埋め込まれる危険性があります。 foraではいろいろとカスタムテンプレートフィルタを作っていて、板や、コメントやスレッドの出力画面では、{% autoescape off %} や、safeフィルタの使用は避けるよう注意をしています。 import html text = html.escape(text) CSRF(クロスサイトリクエストフォージェリ) MIDDLEWARE = [ 'django.middleware.csrf.CsrfViewMiddleware', ] 上記のミドルウェアを読み込み、POSTのフォーム要素には{% csrf_token %}をつけると、django側でCSRF検証をしてくれます。 一部、ajaxのPOSTを使っていますが、下記のようにcsrf_tokenを生成すれば問題ありません function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } var csrftoken = getCookie('csrftoken'); (中略) $.ajax({ type: 'POST', data: { 'data': data, }, beforeSend: function(xhr) { xhr.setRequestHeader('X-CSRFToken', csrftoken); xhr.overrideMimeType('application/json;charset=Shift_JIS'); }, クリックジャッキング →攻撃対象サイトの上にiframeを重ねて、わからないように誘導 XFrameOptionsMiddlewareをいれる。(デフォルトで入ってますけど) MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] その他 ファイルアップロード時に気を付けること POSTフォームはform.is_valid()つかう foraでは、modelsでvalidationを設定 models.py def validate_image(fieldfile_obj): filesize = fieldfile_obj.file.size megabyte_limit = 5.0 if filesize > megabyte_limit*1024*1024: raise ValidationError("アップロード可能なのは、 %sMB以下のファイルです。" % str(megabyte_limit)) image = StdImageField(blank=True,null=True, validators=[validate_image,FileExtensionValidator(['jpg','jpeg','png','gif', ])],variations={ 'thumbnail': (50, 50, True), 'medium': (150, 150), }) https化 pythonanywhereではワンクリックで対応可能(認証局はlets enclyptですけど) Djangoのセキュリティチェック機能 manage.py check --deploy コマンドでセキュリティチェックができます。 いくつかWARNIGSが出ているので対応します。 #security.W004 SECURE_HSTS_SECONDS = 315360000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True #security.W006 SECURE_CONTENT_TYPE_NOSNIFF = True #security.W007 SECURE_BROWSER_XSS_FILTER = True #security.W008 SECURE_SSL_REDIRECT = True #security.W016 CSRF_COOKIE_SECURE = True #security.W019 X_FRAME_OPTIONS = 'DENY' #security.W021 SECURE_HSTS_PRELOAD = True コメントの投稿制限 連投防止 機能編(サイトの仕様) サイトの構造 サイトの構造はこんな感じです。(ER図とか画面遷移図、アーキテクチャの細かい説明はめんどくさいので割愛します....) 権限 板にスレッドの投稿を行うことができ、スレッドにコメントの投稿を行うことができます。 板の作成は誰でもできますが、ログインが必要です。スレッドやコメントの投稿は、ログイン不要です。スレッドやコメントは、内部的にはipアドレスと紐づいていますが、他のユーザーからipアドレスを識別することはできません。 ログイン機能 メール登録→任意です。一応ちゃんと登録できます。メールアドレスを登録しておくと、パスワードを紛失したときにアカウントの復活ができます。support@fora.socialから自動で返信が来ると思います。 板 モデレータが作成→現在ログインすると、2つまで板が作成できます。 モデレータの権限→自分の作った板のスレッドやコメントで、板の雰囲気にそぐわないものがあった場合、モデレータによって削除ができます。 スレッドの削除 コメントの削除 通報 ログインしていなくても、報告ができます。 報告画面 報告後(新規報告の場合) 報告後(すでに報告済みの場合) 多数報告されたスレッドやコメントは、自動的に削除になります。(いいね数が多いスレッドやコメントの場合、削除されるためにはその分たくさんの報告が必要になります。) その他、お問い合わせはこちらまで(対応遅いかもしれません) 書き込み制限 板ごとに設定されます。 書き込んだコメントやスレッドが、一日の中で多く報告されるか、またはモデレータによって削除にされた場合、その板内で一定期間書き込み制限となります。 スレッド投稿しようとするとこんな画面に コメント投稿しようとするとこんな画面に 一日以内に解除されます。 その他、fora全体に書き込み禁止措置をすることもできますが、詳細はまた今度。 30秒以内に連続で同じスレッド内でコメントを投稿しようとすると、以下のようになります スレの終了 コメントが1000を超えると、それ以上書き込めなくなります。 スレの終了については、一定期間書き込みが無かったら強制終了するような機能の実装をするかもしれないですが、まだ検討中です。 コメント コメントにはIDがつきます。このIDは、日×スレッド×ipアドレスに基づいて計算されています。IDからipアドレスの復号はできません。 「名無し」の名前は板ごとに設定されています。 今後について 機能 機能的には、ベースのものは一通り実装済みかなと思っています。 が、板のライフサイクルみたいなところはちゃんとしなきゃなと思ってます。具体的には、合併と分割、板の終了ですね。 「過疎板の合併」みたいなことができればいいなと考えているんですよね。合併のプロセスはなにもアイデアありませんが。 板の分割は、どのスレッドをどのように割り振るか難しいので厳しそうですけど(というか板の分割は、板内で抗争?が発生して自然発生するのが自然なのでしょうね)。 板の終了に関しては、一定期間スレッドが生成されない板に関して、なんらかの処理をしなきゃと思ってます 結局ユーザーに比べて板が多すぎると人が分散しちゃうんで、それは避けたいですね。サイト全体として適切な盛り上がりを維持できる板のライフサイクル設計にしたいですね 板に関しては、板ごとに名前の入力のON/OFFを可能にしたりとか、IDの表示をなくしたりとか、ログインユーザー(承認ユーザー)のみ書き込み可能にしたりとか、ランキングに載せないように(URLを知ってる人しか使えない)ようにしたりとか、いろいろ板ごとになバリエーションが選択できるようにしてもおもしろいかもしれませんね。 板の話題によって、ニーズも変わってきそうですし。 あとはスレッド内のまとめ機能ですね。まだたくさんコメントがあるスレッドが無いので、あまり考えられてないですが。各コメントの「いいね数」をなんらかのアルゴリズムで弄れば簡単にできそうですね。 サーバー移行(スケーリングについて) 本格的に運用するのであれば、pythonanywhereはスケーリングが難しそうなため、AWSへ移行の必要がありそうです。そのさいに、データベースを一旦リセットする可能性があります。 本格的に運用するのかどうかは未定ですけどね。 DBについて やはり本格的に勉強する必要がありそうです。分割とか、シャーディング?レプリケーション?redisとmemcachedってなんぞや?とか、いろいろわからないことがあります。たぶんコメントテーブルとかいいねテーブルはシャーディングみたいのしないといけないんだろうなあ。 https://techplay.jp/column/239 こういうの見ると、foraのサーバー、DBアーキテクチャはほんとへなちょこなんだなと思います SEO対策 →そのうち 各種監視ツール munin(未導入) pingdom(未導入) PagerDuty(未導入) GoogleSearchConsole(導入済み) GoogleAnalytics(導入済み) ここまで読んでいただきありがとうございます。 ここはすばらしいインターネッツですね fora.social
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【なんだろう...】ソーシャル匿名掲示板を作った件Part.1【ちょっと使ってみてもらっていいですか?】

7秒で読める忙しい人用まとめ ここはすばらしいインターネッツですね 趣味でソーシャル匿名掲示板fora.socialを作りました。 アルファテスト中です。 ログイン不要でコメントができるのでぜひ記念カキコしてみてください。 サーバーよわよわなんで、アクセス集中するとサーバーダウンするかもです。 レスポンシンブ(スマホから見るやつ)は一応対応してますけど、完全対応ではないのでちょっと使いにくいかもです。 また、予告なくサイトを一時閉鎖する可能性があるのでご了承ください。 この記事では、 「なぜ今更匿名掲示板を作ったか」 「実装についてあれこれ」 「セキュリティ対策について」 「機能説明」 「今後について」 などざっくりと説明していきます。 1分で読めるまとめ 初カキコ…ども…ソーシャル匿名掲示板fora.socialを作りました... 俺みたいな、いまさら匿名掲示板作る腐れ野郎、他に、いますかっていねーか、はは 今日のyoutubeの急上昇ランキング マインクラフトやAPEXがどうとか 芸能人が○○してみた とか ま、それが普通ですわな そして俺は配信者勢いランキングを見て、呟くんすわ うんこちゃんと、兎田ぺこらどっちにしようかな え、どっちもやってない?それじゃあ it’a Abema TV.呪術廻戦でも見るか 好きな音楽 YOASOBI 尊敬する人間 にしむらひろゆき氏(愛してるって言うのはNO) なんつってる間に4時っすよ(笑) あ~あ、コロナ早く収まってほしいね、これ ちなみにくだらないノリはまだ続きますよ。 現在fora.socialはアルファテスト中です。 なんで「ソーシャル」なのかと言うと、スレッドがいいね数やコメント数に従ってランキング化されたり、各スレッドの人気コメントがスレッド一覧に表示されたりする部分がソーシャル?ということです。文字だと伝わりにくいですね。 あとは、「匿名掲示板」だけだと新しい響きじゃないし、パンチが弱いと思ったんです。 匿名掲示板の名の通り、ログイン不要でスレ建てやコメントの投稿ができます。 【トップページの画面。デフォルトでは全板総合の毎時ランキングです。】 【各トピックの詳細画面。画面下部のボタンからコメントできます。】 左上のボタンでカテゴリ移動ができます 全板総合、カテゴリ内、板内で、各種ソートが選べます。 板ごとにスレ投稿画面へ遷移できます スレ投稿画面では、「文字(1024文字以内)」「リンク」「画像(5MBまで)」「動画(youtubeとニコニコ動画のURLを貼ってください)」張り付けることができます ログインするとマイページから板の作成ができます いわゆる「ポートフォリオ?」というものではないです。作ってみようと思い立って、勢いで作ってみちゃった系です。 現在ユーザー登録(メアド不要)すると、板が二つまで作れる設定となっておりますので、是非板を作成してみてください!その他ご意見、ご感想お待ちしております。→運営への意見スレ はじめに うそはうそであると見抜ける人でないと(掲示板を使うのは)難しい くぅ~疲れましたw これにて一旦動くものができたみたいです! 実は、某Nchのトラフィックを見たら、じゃあ、自分も作ってみようというのが始まりでした 本当はweb系の技術なんてなかったのですが← やる気を無駄にするわけには行かないので流行りのネタで挑んでみた所存ですw 以下、ぼく達のみんなへのメッセジをどぞ 戦略家ぼく「みんな、見てくれてありがとう!ちょっと1000番煎じ感もあるけど・・・気にしないでね!」 フロントエンドぼく「いやーありがと!jqueryのすばらしさは二十分に伝わったかな?」 バックエンドぼく「見てくれたのは嬉しいけど、可用性がオワってるのが恥ずかしいわね・・・早めにAWSに移行しないときつくない?」 デザイナーぼく「見てくれありがとな!正直、全部Bootstrapだよ!角は丸くしろってジョブズも言ってたよ!」 マーケターぼく「・・・Qiitaに記事書いただけだけど、ありがと」ファサ では、 戦略家ぼく、フロントエンドぼく、バックエンドぼく、デザイナーぼく、マーケターぼく、俺「皆さんありがとうございました!」 終 戦略家ぼく、フロントエンドぼく、バックエンドぼく、デザイナーぼく、マーケターぼく「って、なんで俺くんが!?改めまして、ありがとうございました!」 本当の本当に終わり 閑話休題~名前について ソーシャル匿名掲示板を作りました。その名も、fora.socialです。 ダサいとか言ったら異界送りにします。 キマリ、ユウナ、マモル foraでもfora.socialでもどっちの呼び方でもいいんですが、ドメイン名も含めた方がオシャレじゃないですか。(musical.lyとかyoutu.beみたいなのが頭に残っていたんですよ。ちなみにmusical.lyはtiktokの前身のサイトです。https://www.musical.ly へ飛ぶとtiktokへリダイレクトされると思います。) はい、本当はfora.comとかfora.net、fora.siteというドメインが埋まっていて、有名なドメインが取れなかっただけです。 名前の命名も、匿名掲示板という性質上、本当はもっとアンダーグラウンドっぽい名前にしようかと思ったんです。ですが、アンダーグラウンドの雰囲気にし過ぎてしまうのはリスク高いな~と思い、この名前にしました。(2chも訴訟問題とか散々ありましたね)といっても、「聖なる雰囲気」というか、ちゃんとした雰囲気にし過ぎてしまうのもそれはそれで面白くないですよね。名前変えるかもしれないです。 ちなみにforaという言葉は、古代ローマ都市の公共広場、フォルム(forum)の複数形(fora)です。いろいろな人々が広場に集まって談笑や議論している姿を想像して、まさにfora.socialの行く末になって欲しいと思って命名しました。 ただ、日本的な名前にしたいなーという思いもあります。サイト内でも英語はできるだけ使わないようにしていますので。 「亀山社中」。。とか?意味不明ですかね。「にっぽん昔ばなし」とか、意味不明ですね。 「よもやまばなし」「よもやま」「四方八方」「四方山」「にっぽんよもやまばなし」 よもやまから離れられなくなってきました 「よろず報知」 とか 「瓦版(かわらばん)」 とかも面白いですね。ただオシャレな名前じゃないと若い人集まらなさそうです。 卍インターネッツ ...意味不明ですね うーん。難しいですね。 人々の話のログを整理し、残していくという意味で、「アカーシャクロニクル」とか「アカシックレコード」とか厨二病みたいな名前も考えました。かっこいいですけど宗教的な意味合いに曲解してしまわれそうで微妙ですかね 「人々が集まっていろいろと議論する場所」みたいないい感じの言葉ないですかね。 いい名前があれば募集中です。 個人的にfora.socialという名前は匿名掲示板としてクールすぎる気がしているんですよ なんだかんだバランス取れている気もしますけど ドメインの設定とかもめんどくさいので、とりあえずこの名前にしてます。 これでいいさ。20年…待たせたからな。もう おまえたちの時代だ! 戦略編 戦略家ぼく(数か月前)「匿名掲示板なんて3日でできるだろwよゆーよゆー」 そういえば、2ch(5chという名前になったことすら知りませんでした)やそのまとめサイトをあまり見なくなり、久しく経った気がします。TwitterやInstagram、Facebook、Youtubeといったアメリカ製のサービスが日本でも猛威を振るい、インターネッツ上での盛り上がりをごっそり奪い取っていってしまったのでしょうか。 なんとなく感じる物足りない感 今、何が流行ってるんだ? もっぱらYoutubeでお気に入りYoutuberの動画やライブ配信を見る日々。ニコニコ動画すらも見なくなりました。 何か物足りない。急上昇ランキングは格闘家しかいないし、ゲームはマイクラとAPEXしか世界に存在しないかのようです。 フラッシュ倉庫で赤い部屋やウォーリーを探さないでを見て眠れなくなったり、2chでポケモンのコイルを人気投票1位にしようとお祭りになったり、ニコニコ動画で謎のインディーズゲーム「東方」が盛り上がったり、パソコンに歌を歌わせる「ボーカロイド」が生まれたり.....。懐かしきインターネッツの日々が思い出されます。 何か足りない。困っちまうこれは誰かのせい。あてもなく混乱するエイデイ そんな日々の中で、創設から20年以上もたつ2ch(5ch)は、日本でもいまだにTOP50以内の優秀なトランザクション量を誇るとひょんなことから知りました。20年もたつのに、、、ぐぎぎ、、、、、。 3日間くらい考えて、匿名掲示板というもののインターネッツにおける役割について興味を持ち始めた戦略家ぼくは、匿名掲示板について調査を開始しました。匿名掲示板の定義を厳密に行うのは難しいですが、「インターネットにおける人格」というのは面白い研究分野であることがわかりました。4chan創業者、クリストファーブール氏の発言なども結構興味深かったです。 そして、暇な時間に匿名掲示板に類すると思われるサービスを調査という名のROMを続けていたところ、自分もじゃあ作ってみようかと、思い始めてしまったです.....。 調査結果 以下のような部分をどうするかで、サイトの方向性が決まるような気がします。 コメントのデフォルトの表示順 連番(2ch、Dcard)なのか、人気順か。スレの「流れ」を生み出すには、連番の必要がある。カスタムで新着順に表示できたとしても(youtube,yahooニュース,redditなど)、多くの人はわざわざカスタムで新着順にしてコメント欄を見るというめんどくさいことはしないため、スレの流れは生まれにくい。デフォルトの表示がどうなっているかが重要。 スレッド一覧画面の順 スレッドフロート方式(2ch)、人気順(reddit)、ユーザーへのサジェスト(youtubeのアルゴリズムみたいな)。 コメントの通報と削除、書き込み制限の仕組み コメント/スレッド作成の権限(ログインの要不要)-気軽にコメントできるかどうか。→気軽にコメントできる方が盛り上がりやすいが、削除・通報の仕組みや荒らしスパム対策をしっかり行う必要がある コメントに表示されるID-ユーザー名が表示される(reddit,youtube)のか。その場限りのID(2ch)か、完全に何もないか(爆サイ、がるちゃん) 中央集権かモデレータ主導か タイムスタンプとその表示形式→スレの流れが生まれるかどうかに影響する 以下は、各サービスをあっさ~く使ってみての、完全に個人の主観的感想ですので、実際のコアユーザーの意見と異なると思います。 匿名掲示板に類する有名なサービス 日本 2ch-日本最大規模の匿名掲示板。スレッドに「雰囲気」というか「流れ」がある。 5ch-2chと色々あったらしい。2chのコメントは5chに転送されている。エッティな広告が多い。 ガールズちゃんねる-通称ガルチャン。男がいると「こいつ男だろ」 と言われる。 はてな匿名ダイアリー-通称増田。独自の文化を生成。 したらば-2ch型の掲示板をレンタルできるサービス。中央集権型の2chとは違い、各板に管理人がいる。2chの避難所だったりする。 mixiコミュニティ-いまでも一定のユーザーがいる。この中ではかなりマイルド?なコミュニティ lobi-ゲームコミュニティ。若い人も多く、秩序が保たれている印象。 Yahoo知恵袋-質問形式の知恵共有サービス。ベストアンサーへのインセンティブ設計がよくできてる。 ヤフーニュースのコメント欄-真面目系のコメントが多い。コメントの「流れ」があまりなく、単発のコメントの集合体。 Youtubeのコメント欄-あくまでも動画サイトであり、動画についての話題しかない。コメントの「流れ」があまりなく、単発のコメントの集合体。だが、youtube自体の規模が巨大なため、コメントも多い。有名な動画は日本語と英語が入り混じっている。動画によっては掲示板っぽい使われ方がされている場合もあり。PV数からしたら、視聴者のコメント「率」はかなり低い 爆サイ-ローカルクチコミサイト。ID表示がなく、雰囲気はかなりフリーダム。 各種まとめサイト 匿名掲示板に類する有名なサービス 世界 reddit-おそらく英語圏最大の掲示板サイト。世界TOP10のトラフィックを誇る。掲示板の運営はモデレータによって行われており、サイトの構造はしたらばに近い。karmaというポイントシステムがあり、ポストに対するインセンティブがある。ログイン必須。 digg-ソーシャルニュースサイト。一時はredditと双璧を成したっぽいが、今は掲示板というよりもニュースサイトとしての側面が強い。 4chan-日本のふたば☆ちゃんねるに影響されて作られた英語圏の画像掲示板。登録システムが存在しなく、2chに近い。投稿が一定期間で削除されるという性質上、英語圏で最もアンダーグラウンドな雰囲気漂う匿名掲示板。 PTT (台湾)-台湾の2ch 巴哈姆特:バハムート-台湾のゲームコミュニティ。台湾におけるゲーム、アニメ、漫画の発信地である。 Dcard-台湾の大学生専用のコミュニティサイト。台湾有数のSNSとしても有名。ユーザーが大学生という性質上、この手のサイトにしては珍しくアングラ感はあまりなく、女性ユーザーが多い。メイクや化粧、恋愛相談の話題も多くなされている印象。 Plurk-台湾のサブカル系SNS。肉が歩いている。変なタコみたいなキャラクターが特徴。twitterと共存している?もともとはカナダで開発された? craygslist-アメリカ最大のクラシファイドサイト。シンプルなUIながら、月間10億PV以上を誇る。 slashdots-テクノロジー系の匿名掲示板。日本では「スラド」として知られている。 tumblr twitter 世界と言っておきながら、アメリカと台湾しかないですが。中国にもいろいろあるのでしょうが、あまり調べられませんでした。 いろいろと匿名掲示板に類するサービスを調べていくうちに、思ったこと 日本の匿名掲示板系のサービスは英語圏のものに比べてシステムが古い傾向 台湾と比べても、経済規模は日本の方が上なのに、台湾の方がシステムが優れている印象 しかし、日本の各掲示板にも、一定のユーザーがいて、独自のコミュニティを形成している。=匿名掲示板というコミュニケーションの形は、古今東西問わず一定の需要があるのでは? なぜ英語圏の素晴らしいサイト(主にreddit)を使わず、日本の古いサイトを使い続ける人が多いのか?→言語、文化が違うから。+redditが本気で日本を狙っていないから?日本向けのチューニング(翻訳やマーケティング)が細かいところまで行き届いていなくて、「日本人ですけどredditはん、使わせてもらいまっせ。おおきに~」みたいな微妙な居心地の悪さを感じ、なんというか「おれたちのコミュニティ」みたいなアットホーム感がない。わざわざスレッドにご丁寧に「日本語」みたいなタグがついてても拍子抜けしちゃうでしょ。「ここはおれたちの居場所じゃねえ」って だいたい、サブレってなんですか。鳩のあれですか?板って呼んで、くれないか コメントの匿名性と、サイトの秩序というものに相関がある。コメントの匿名性が高いほど秩序を保つことが難しくなるが、思いもよらない化学反応的な面白さが生み出されることもある。→創発現象 中央集権型のサービスと、モデレータ主導型のサービスがある 匿名掲示板というサービスの性質上、自然と男性の割合が多くなる傾向にある気がする。 日本では匿名掲示板系のサービスは、ユーザーの高齢化が進んでいる印象。若い人は匿名掲示板でやり取りするという概念がそもそも無い。twitter、instagramまたはyoutubeのコメント欄が3大若い人のコメントが多いサイトか。しかしtwitterやinstagramはリアルの友達と繋がっていることもあり、息苦しく感じる人も少なくない→何か言いたいことがあるけど内に秘めたまま言えず、ストレスを抱え込む人がわりと多いのではないか? 匿名ということもあり、トラブルの際どうするかのルール・線引き(権利、責任)をきちんと行う必要がある。 コミュニティが廃れてしまう原因の大きなものとして、荒らしやスパムの存在がでかい 以上を踏まえ、作る方向性 「匿名」掲示板である。→コメント/スレッドの作成や、スレッドやコメントへの「いいね」はログインしなくてもできるように。 サイト全体の構成→「サイト全体」にユーザーが付くのではなく、「板」に対するユーザーがつく(=板ごとに文化が生成される)ように、板へのフォロー機能を実装。(ログインユーザーのみ) 秩序を保てる掲示板システム→スレッド内にはID制度(IPアドレス×スレッド×日づけのため、スレッドが変わればIDが違くなる。)を導入し、自作自演をしにくくした。ユーザー同士のチャットはできず、ユーザーフォローはできないようにすることで、個人VS個人の争いができないようにした。各スレッドやコメントに報告ボタンを付け、ログイン不要で規約違反や不快なコメントの報告ができる。(報告はIPアドレスに紐づいているので連投はできない。)多く報告されたコメントやスレッドは自動的に削除(非表示)される(いいね数が多いものはその分たくさん報告される必要あり)。また、そのコメントやスレッドを投稿したIPアドレスを内部的に管理し、一定期間で多く報告がされたIPアドレスを、板ごとに一定期間書き込み制限とする。(IPアドレス×板×一日くらいの期間で書き込み制限がかかる。) モデレータ主導型サービスにしたい→アカウントを持つユーザーが板とそのルールを作成、スレッドやコメントの削除権限を付与。 若い人でも使えるように→動画やリンクの埋め込みを可能とし、見た目的にもとっつきやすく。また、ランキング機能の充実によりトレンドを知りたい若い人のニーズにもこたえる。さらに、PWAの技術でスマホからでもアプリライクなアクセスを可能に。 インセンティブ→ログインしていると、もらったいいね数が蓄積していく。(redditのkarmaに近い)ただ、板のモデレータのインセンティブ設計が難しい。。。。「自分の考えた板が盛り上がると嬉しい」だけだとインセンティブとして弱いので、考える必要がある。wikipediaみたいに「モデレータコミュニティ」みたいなのを作って発展させていければ。。。? スレッドの整理と蓄積-Googleは「検索」型サービス。目的があって、使うサイト。匿名掲示板は「探索」型。探索型サイトとして最善のやり方で、スレッドやコメント情報の整理と蓄積を目指す→カテゴリによる分類、タグによるメタ情報整理、PV数といいね数による人気度の数値化(←twitterは過去情報は蓄積されないし、整理もされない。最大の違い。twitterはフロー型。foraはストックを目指す)詳しくはフォークソノミーのwikiとか読んでみると結構おもしろいですよ 実装編 ジェバンニが一晩でやってくれました うそです。バックエンドぼくが一生懸命、数か月頑張ってくれました。 まずは技術的な仕様をご紹介します。 ざっくり技術的な仕様 バックエンド python3.7.5 Django2.2.17 (pythonフレームワーク。元はニュース系のサイトを管理するために作られたフレームワークらしい。instagramやpinterestのバックエンドはdjangoらしいと聞いてdjangoを選択。テンプレート(html生成)機能が標準で搭載されているのも良い。) フロントエンド 主にDjango標準のテンプレート機能。モダンなjsフレームワークは使っておりません。reactとかvueの勉強がめんどくさかったわけではないですよ・・・・。 js系 9割jquery、その他lightbox.jsとかlazyload.jsとか便利系ちょこちょこ css系 bootstrap アイコンはfontawesome DBサーバー-MySQL on pythonanywhere django標準のインストールだとsqlite3ってのが入っていると思いますが、さすがにsqlite3で運用は厳しいんじゃないかと。 バックエンドサーバー-pythonanywhere djangoならAWSよりも手軽にデプロイできると思います。手軽さと、趣味で作ってみましたってことでpythonanywhereを選びました。バッチ処理とかも簡単にできます。課金するとストレージ容量だったり、サーバーのスペックをある程度強化できます。ただ、細かいDBの(水平・垂直)分割とかの設定は難しいみたいなので、スケーリングを考えるといずれAWSかその他のホスティングサービスの方がいいと思います。あとはネット上にpythonanywhereの日本語の情報がめちゃ少ないので、pythonanywhereのforumのQ&Aとかは結構見てました。 ストレージ-pythonanywhere ドメイン-お名前.com Djangoあれこれ →気が向いたら書きます。 フルスタックMVCフレームワーク REST化も簡単 テンプレートタグとフィルター バッチ 優秀なORマッパーで、SQL文知らなくても使える セキュアなユーザー管理システム adminサイト標準搭載 豊富なmiddlewareでセキュリティ対策もやりやすい pythonanywhereあれこれ →気が向いたら書きます。 実はAWSのEC2? デプロイが簡単 監理サイトが使いやすい 無料モードもあり お名前コムあれこれ →気が向いたら書きます。 PWA(progressive web application) これは、webサイトをスマホアプリのように振舞わせることができる手法です。 一応iOSの方は、上記サイトにsafariでアクセスしてもらって、「ホーム画面へ追加」を押すと、アプリっぽい動作をするものがスマホで使えます。(スタンドアロンで起動します) manifest.jsonとserviceworkerを設定するのですが、気が向いたら書きます。(わたしも深くは理解してないですが) ページの表示スピードを速くしたい ハイパフォーマンスwebサイトこのあたりを読んだりしましたが、、そこまでチューニングできていないです。 問題はフロントエンドだ cssはインラインに書くな→レンダリングの阻害 jsとcssの場所に注意しろ CDNを使え gzip化しろ facebookとyoutubeに気を付けろ 画像のlazyload stdimageの活用 django DBの仕様ー外部キーにはインデックスがデフォルトで付いている djangoのキャッシングフレームワークは優秀だが、使いどころには気を付けろ セキュリティ対策編 webアプリケーションにはセキュリティ対策が必須です。特に掲示板のようなサービスは、気を付けなければいけないですね セキュリティ対策については、このあたりのサイトを参考にさせていただきました。 IPA 安全なウェブサイトの作り方 現場で使えるdjangoのセキュリティ対策 Djangoで開発する際にセキュリティについて気をつけること GoogleSearchconsoleと、webサイトの脆弱性診断サイトgredでの結果は次のようになりました パスワード系 パスワード不正取得 →ブルートフォースアタック対策を行う必要があります。 ログイン機能には、django-allauthを使っています 詳しくはこの辺りでhttps://qiita.com/jansnap/items/62f0de512bc8771ebd58 パスワードのバリデーションは、settings.pyの以下のコードで行われてます。 AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] ユーザー登録の際には、ある程度のパスワードの複雑さが必要です。推測されやすいパスワードの登録はできません。 ログイン制限 ログイン画面でパスワードを一定回数間違えると、一定時間ログイン制限がかかります。 なんでその部分だけ英語なの?というと、django-alltuhをインストールして、標準のテンプレートを使うとこうなるからですね。日本語にする方法はちょっとよくわかりません。いずれ対応します。 パスワード漏洩 データベースにパスワードが平文、あるいは容易に復号可能な状態で保存されている場合、データベースからデータが流出した場合に、不正アクセスされるので、対策の必要がある。 結論から言うと、foraでパスワードは平文で保存されていません。入力されたパスワードとランダムな文字列を結合し、ハッシュ関数を一定回数繰り返し使ってハッシュ化してからデータベースに保存しているため、複合ができないです。たとえデータベースからパスワードが流出しても元のパスワードを流出データから知ることはできません。 パスワードはこんな感じで保存されています ちょっと詳細 foraで使っているユーザーモデルは、AbstractUserを継承しています。 from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): django/contrib/auth/models.pyをみてみるとAbstractUserはAbstractBaseUserを継承していて from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager class AbstractUser(AbstractBaseUser, PermissionsMixin): django/contrib/auth/base_user.pyをみてみると from django.contrib.auth.hashers import ( check_password, is_password_usable, make_password, ) class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) # Stores the raw password if set_password() is called so that it can # be passed to password_changed() after the model is saved. _password = None def set_password(self, raw_password): self.password = make_password(raw_password) self._password = raw_password def check_password(self, raw_password): """ Return a boolean of whether the raw_password was correct. Handles hashing formats behind the scenes. """ def setter(raw_password): self.set_password(raw_password) # Password hash upgrades shouldn't be considered password changes. self._password = None self.save(update_fields=["password"]) return check_password(raw_password, self.password, setter) 入力されたパスワードをmake_password関数で何らかの処理をして、結果をpasswordテーブルに保存しているようです 入力された生のパスワードは、self._password = Noneの処理で消されているようですね。 以下の処理は、Djangoはパスワードをどうやって保存しているのかと同じですので、気になる方はこちらをチェックしてみてください。 認証系 cookieとセッション(セッションハイジャック) →セッションハイジャックされないようにする必要がある Djangoでは、セッション管理はCookieベースであり、URLにcookieの埋め込みはしません。また、セッションIDはランダムな文字列として生成されてます。 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', ] また、settings.pyに以下の設定をすることで、cookieにsecure属性を付与しています SESSION_COOKIE_SECURE = True アクセス制御 →非公開情報を第三者に見られないようにする デコレータの活用 from django.contrib.auth.decorators import login_required @login_required def hoge(request): 管理サイトのURLをデフォルトから変える https://www.fora.social/admin/ にアクセスすると debug=Falseにする インジェクション SQLインジェクション DjangoのORマッパーはセキュアなSQLを組み立てるため、Djangoを普通に使っている限り心配する必要はないかと HTTPヘッダインジェクション setting.pyのALLOWED_HOSTSでホワイトリストを設定してます クロスサイト系 XSS(クロスサイトスクリプティング) →画面への出力処理にスクリプトを埋め込める脆弱性がある場合、利用者のブラウザで不正なスクリプトが実行される危険性がある djangoのテンプレートを使っている場合基本的にはオートエスケープ機能が働いていて、XSSの心配はないです。ただし、以下のような記法は注意する必要があります。 {{ hogepiyo | safe }} この「safe」フィルタは、文字列に対して、さらなるエスケープが必要でないことをマークするのに使います。 ユーザーが投稿可能なフィールドの出力に「safe」を付けると、 <script>alert(1)</script> などがエスケープされずにそのままhtmlの要素としてブラウザでレンダリングされるため、悪意あるユーザーにスクリプトを埋め込まれる危険性があります。 foraではいろいろとカスタムテンプレートフィルタを作っていて、板や、コメントやスレッドの出力画面では、{% autoescape off %} や、safeフィルタの使用は避けるよう注意をしています。 import html text = html.escape(text) CSRF(クロスサイトリクエストフォージェリ) MIDDLEWARE = [ 'django.middleware.csrf.CsrfViewMiddleware', ] 上記のミドルウェアを読み込み、POSTのフォーム要素には{% csrf_token %}をつけると、django側でCSRF検証をしてくれます。 一部、ajaxのPOSTを使っていますが、下記のようにcsrf_tokenを生成すれば問題ありません function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } var csrftoken = getCookie('csrftoken'); (中略) $.ajax({ type: 'POST', data: { 'data': data, }, beforeSend: function(xhr) { xhr.setRequestHeader('X-CSRFToken', csrftoken); xhr.overrideMimeType('application/json;charset=Shift_JIS'); }, クリックジャッキング →攻撃対象サイトの上にiframeを重ねて、わからないように誘導 XFrameOptionsMiddlewareをいれる。(デフォルトで入ってますけど) MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] その他 ファイルアップロード時に気を付けること POSTフォームはform.is_valid()つかう foraでは、modelsでvalidationを設定 models.py def validate_image(fieldfile_obj): filesize = fieldfile_obj.file.size megabyte_limit = 5.0 if filesize > megabyte_limit*1024*1024: raise ValidationError("アップロード可能なのは、 %sMB以下のファイルです。" % str(megabyte_limit)) image = StdImageField(blank=True,null=True, validators=[validate_image,FileExtensionValidator(['jpg','jpeg','png','gif', ])],variations={ 'thumbnail': (50, 50, True), 'medium': (150, 150), }) https化 pythonanywhereではワンクリックで対応可能(認証局はlets enclyptですけど) Djangoのセキュリティチェック機能 manage.py check --deploy コマンドでセキュリティチェックができます。 いくつかWARNIGSが出ているので対応します。 #security.W004 SECURE_HSTS_SECONDS = 315360000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True #security.W006 SECURE_CONTENT_TYPE_NOSNIFF = True #security.W007 SECURE_BROWSER_XSS_FILTER = True #security.W008 SECURE_SSL_REDIRECT = True #security.W016 CSRF_COOKIE_SECURE = True #security.W019 X_FRAME_OPTIONS = 'DENY' #security.W021 SECURE_HSTS_PRELOAD = True コメントの投稿制限 連投防止 機能編(サイトの仕様) サイトの構造 サイトの構造はこんな感じです。(ER図とか画面遷移図、アーキテクチャの細かい説明はめんどくさいので割愛します....) 権限 板にスレッドの投稿を行うことができ、スレッドにコメントの投稿を行うことができます。 板の作成は誰でもできますが、ログインが必要です。スレッドやコメントの投稿は、ログイン不要です。スレッドやコメントは、内部的にはipアドレスと紐づいていますが、他のユーザーからipアドレスを識別することはできません。 ログイン機能 メール登録→任意です。一応ちゃんと登録できます。メールアドレスを登録しておくと、パスワードを紛失したときにアカウントの復活ができます。support@fora.socialから自動で返信が来ると思います。 板 モデレータが作成→現在ログインすると、2つまで板が作成できます。 モデレータの権限→自分の作った板のスレッドやコメントで、板の雰囲気にそぐわないものがあった場合、モデレータによって削除ができます。 スレッドの削除 コメントの削除 通報 ログインしていなくても、報告ができます。 報告画面 報告後(新規報告の場合) 報告後(すでに報告済みの場合) 多数報告されたスレッドやコメントは、自動的に削除になります。(いいね数が多いスレッドやコメントの場合、削除されるためにはその分たくさんの報告が必要になります。) その他、お問い合わせはこちらまで(対応遅いかもしれません) 書き込み制限 板ごとに設定されます。 書き込んだコメントやスレッドが、一日の中で多く報告されるか、またはモデレータによって削除にされた場合、その板内で一定期間書き込み制限となります。 スレッド投稿しようとするとこんな画面に コメント投稿しようとするとこんな画面に 一日以内に解除されます。 その他、fora全体に書き込み禁止措置をすることもできますが、詳細はまた今度。 30秒以内に連続で同じスレッド内でコメントを投稿しようとすると、以下のようになります スレの終了 コメントが1000を超えると、それ以上書き込めなくなります。 スレの終了については、一定期間書き込みが無かったら強制終了するような機能の実装をするかもしれないですが、まだ検討中です。 コメント コメントにはIDがつきます。このIDは、日×スレッド×ipアドレスに基づいて計算されています。IDからipアドレスの復号はできません。 「名無し」の名前は板ごとに設定されています。 今後について 機能 機能的には、ベースのものは一通り実装済みかなと思っています。 が、板のライフサイクルみたいなところはちゃんとしなきゃなと思ってます。具体的には、合併と分割、板の終了ですね。 「過疎板の合併」みたいなことができればいいなと考えているんですよね。合併のプロセスはなにもアイデアありませんが。 板の分割は、どのスレッドをどのように割り振るか難しいので厳しそうですけど(というか板の分割は、板内で抗争?が発生して自然発生するのが自然なのでしょうね)。 板の終了に関しては、一定期間スレッドが生成されない板に関して、なんらかの処理をしなきゃと思ってます 結局ユーザーに比べて板が多すぎると人が分散しちゃうんで、それは避けたいですね。サイト全体として適切な盛り上がりを維持できる板のライフサイクル設計にしたいですね 板に関しては、板ごとに名前の入力のON/OFFを可能にしたりとか、IDの表示をなくしたりとか、ログインユーザー(承認ユーザー)のみ書き込み可能にしたりとか、ランキングに載せないように(URLを知ってる人しか使えない)ようにしたりとか、いろいろ板ごとになバリエーションが選択できるようにしてもおもしろいかもしれませんね。 板の話題によって、ニーズも変わってきそうですし。 あとはスレッド内のまとめ機能ですね。まだたくさんコメントがあるスレッドが無いので、あまり考えられてないですが。各コメントの「いいね数」をなんらかのアルゴリズムで弄れば簡単にできそうですね。 サーバー移行(スケーリングについて) 本格的に運用するのであれば、pythonanywhereはスケーリングが難しそうなため、AWSへ移行の必要がありそうです。そのさいに、データベースを一旦リセットする可能性があります。 本格的に運用するのかどうかは未定ですけどね。 DBについて やはり本格的に勉強する必要がありそうです。分割とか、シャーディング?レプリケーション?redisとmemcachedってなんぞや?とか、いろいろわからないことがあります。たぶんコメントテーブルとかいいねテーブルはシャーディングみたいのしないといけないんだろうなあ。 https://techplay.jp/column/239 こういうの見ると、foraのサーバー、DBアーキテクチャはほんとへなちょこなんだなと思います SEO対策 →そのうち 各種監視ツール munin(未導入) pingdom(未導入) PagerDuty(未導入) GoogleSearchConsole(導入済み) GoogleAnalytics(導入済み) ここまで読んでいただきありがとうございます。 ここはすばらしいインターネッツですね fora.social
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む