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

BlenderをPythonで操作する

1.初めに

blender上のPythonで簡単なモデリングを行う手順を紹介します。
なお、環境はblenderのバージョンは2.8.5、OSはWindows10です。
なお2.7から2.8で仕様に大幅な変更があったのでご注意ください。2.7と2.8の互換性はほぼないと思っていただいてよいと思います。

2.日本語設定

日本語を使う必要がある方は以下の設定をします。特に難しいことはありません。
User Preferences → Interfaceタブ
Translationにチェックを入れて、LangageをJapaneseに設定
Tooltips、Interface、New Data3つすべてをONにし、ユーザー設定の保存をクリックします。
なお、現時点でのバージョンではエディタ上で日本語は直接入力ができません。必要な時は他のテキストエディタで入力した文字をコピペします。

3.blender上でのPythonの使い方

blender上でPythonコードを実行しエラーが発生した場合は、コンソール画面を表示する必要があります。
コンソール画面を表示するには

Windowsの場合

上部のWindowメニューからToggle System Consoleをクリックし、コンソール画面を表示します。

Macの場合

blenderの起動をアイコンをダブルクリックするのではなく、ターミナルから以下のコマンドを入力してblenderを起動してください。
/Applications/blender.app/Contents/MacOS/blender
(アプリフォルダの位置(/Applications/blender.app)は環境によって変わります)

3.要素の基本的な構成

blenderはプリミティブと呼ばれる要素を配置します。

models.py
import bpy  # blenderインポート

#1.円柱location:図形の中心座標 radius:円の半径 depth:高さ rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_cylinder_add(location=(-5, 5, 0), radius=1, depth=3, rotation=(0, 0, 0))

#2.立方体 location:図形の中心座標 size:立方体の一辺の長さ rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_cube_add(location=(5, 0, 0), size=1.5, rotation=(0, 0, 0))

#3.球体 location:図形の中心座標  radius :球の半径 subdivisions:分割数
bpy.ops.mesh.primitive_ico_sphere_add(location=(-5, 0, 0), radius=1, subdivisions=5)

#4.monkey location:図形の中心座標  size:サイズ
bpy.ops.mesh.primitive_monkey_add(location = (-5, -5, 0), size = 3.0)

#5.ドーナツ形 location:図形の中心座標 major_radius:輪の半径 minor_radius:筒の半径 rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_torus_add(location=(0, 5, 0), major_radius=1.0, minor_radius=0.1, rotation=(0, 0, 0))

#6.平板(円) location:図形の中心座標 fill_type:塗りつぶし radius:半径 rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_circle_add(location=(5, 5, 0), fill_type="NGON", radius=2, rotation=(0, 0, 0))

#7.平板(正方形) location:図形の中心座標  size :辺の長さ rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_plane_add(location=(5, -5, 0), rotation=(0, 0, 0), size=2)

#8.多角錐 location:図形の中心座標 vertices:頂点の数 radius1,radius2:円の半径 depth:高さ rotation:立体の回転角(rad)
bpy.ops.mesh.primitive_cone_add(location=(0,-5,0),vertices=10,radius1=0.5,radius2=1,depth=3, rotation=(0, 0, 0))

sample4.png

4.様々なモデリング

4.1 ループによる球体描画

loop.py
import bpy
import math

# 既存要素削除
for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

N = 12
RR1 = 10.0
RR2 = 2.0
for i in range(0, N):
    rad = 2 * math.pi * i /N  # 角度計算2π i /n
    xx = RR1 * math.cos(rad) # x座標計算 半径*cosθ
    yy = RR1 * math.sin(rad) # y座標計算 半径*sinθ
    # 球体作成
    bpy.ops.mesh.primitive_ico_sphere_add(location=(xx, yy, 0),radius= RR2, subdivisions = 5 )

sample2.png

4.2 プリミティブの変形・回転・移動

rectangle.py
import bpy

# 既存要素削除
for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)
#立方体の描画
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=1.5, rotation=(0, 0, 0))
#図形を変形(X方向2倍、Y方向1倍、厚さ方向0.5倍)
bpy.ops.transform.resize(value=(2.0,1.0,0.1))
#図形を回転(Y軸周りに 30°)
bpy.ops.transform.rotate(value=3.1415/6 ,orient_axis='Y')
#図形を移動(Z軸方向に 5移動)
bpy.ops.transform.translate(value=(0,0,5))

sample5.png

4.3 材質の設定

material.py
import bpy

# 1.既存要素削除
for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

# 2.材質の定義(赤色)
mat1 = bpy.data.materials.new('Red')
mat1.diffuse_color = (1.0, 0.0, 0.0, 1.0)

# 3.材質の定義(青色)
mat2 = bpy.data.materials.new('blue')
mat2.diffuse_color = (0.0, 0.0, 1.0, 1.0)

# 4.球体作成
bpy.ops.mesh.primitive_ico_sphere_add(location=(0, 0, 1), radius = 0.5, subdivisions=5 )
bpy.context.object.data.materials.append(mat1) # 材質(赤)指定

# 5.平板作成
bpy.ops.mesh.primitive_cube_add(location=(0, -0.5, 0), size=2.5)
bpy.ops.transform.resize(value=(2.0,2.0,0.05)) # 図形を変形(X方向2倍、Y方向2倍、厚さ方向0.05倍)
bpy.context.object.data.materials.append(mat2) # 材質(青)指定

sample6.png

4.4 メッシュの作成1

mesh1.py
import bpy

# 既存要素削除
for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

# 頂点データの作成
verts = [[-2.0, -2.0, 0.0], [-2.0, 2.0,  0.0], [2.0, 2.0,  0.0] , [2.0, -2.0,  0.0], [2.0, -2.0, 4.0] , [2.0, 2.0, 4.0]  ]
# 面データの作成        
faces = [[0,1,2,3], [2,3,4,5]]

msh = bpy.data.meshes.new("cubemesh") #Meshデータの宣言
msh.from_pydata(verts, [], faces) # 頂点座標と各面の頂点の情報でメッシュを作成
obj = bpy.data.objects.new("cube", msh) # メッシュデータでオブジェクトを作成
bpy.context.scene.collection.objects.link(obj) # シーンにオブジェクトを配置

sample7.png

4.5 メッシュの作成2

mesh2.py
import bpy
import math

# 既存要素削除
for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

# 頂点の作成
verts = []
for i in range(0,21):
    x = 2 *math.pi / 20 * i
    verts.append([x, -1, math.sin(x)])

for i in range(0,21):
    x = 2 * math.pi / 20 * i
    verts.append([x,  1, math.sin(x)])

# 面データの作成        
faces = []
for i in range(0,20):
    faces.append([i, i+1, i+22, i+21])

msh = bpy.data.meshes.new("sinmesh") 
msh.from_pydata(verts, [], faces) 
obj = bpy.data.objects.new("sin", msh) 
bpy.context.scene.collection.objects.link(obj) 

sample8.png

5.最後に

Blenderもオープンソースのフリーウェアなのでモデリングをするには悪くありません。
2.7から2.8の仕様変更が衝撃的だったので、これ以上blenderに深入りしないことにしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chalice を使ってみるなかで調べた小ネタ3点

1. 静的ファイルを Lambda 関数に含める方法

  • シナリオ: 簡単な設定ファイル (YAML, JSONなど) を Lambda 関数内で読み込むファイルとして扱いたい。
  • 問題: app.py と同じディレクトリに env.yml を置いたが、 chalice deploy してもファイルがアップロードされなかった
  • 解決策: chalice プロジェクトの下に chalicelib というディレクトリを作って、そこにファイルを置く。 以下のtreeの結果を参照。
$ tree -a .
.
├── .chalice
│   └── config.json
├── .gitignore
├── app.py
├── chalicelib
│   └── env.yml
└── requirements.txt

なお、chaliceでデプロイをする場合、Linux/Mac などの場合はファイルのパーミッションをそのまま受け継ぐ。 そのため、例えば 750 などのファイルをデプロイした場合、そのファイルを読もうとすると実行時に Permission Denied となってしまうため注意。

  • 同様の事例 。アップロード時に作った zip file を展開するときにおそらく保存時のパーミッションをそのまま展開していることが原因。

2. IAM Policy が自動生成されない / 自分でPolicyを設定する方法

  • シナリオ: Chalice の便利な機能の1つにIAM Policyの自動生成がある。 これは、Lambda 内で boto3 ライブラリを使ってAWSリソースにアクセスしている場合、それを自動的に判断してIAM Policyを動的に自動生成してくれる機能である。
  • 問題: なぜか自分のプログラムではこの自動生成・付与を行ってくれなかった。

調べてみると、boto3.client を利用したプログラムのみ自動生成の対象 であるのが原因だった。 boto3 には resource を使う方法もあるのだが、こちらを使った場合ですらIAMの自動生成の対象にならない。 自分のプログラムは全て boto3.resource を利用していた。

そのため、IAM Policy を自動生成するため、 Chaliceでboto3を使うときは常に boto3.client を利用する というのは1つの指針になり得る。
逆に、既存のIAM Roleを利用したい、自分でPolicyの記述内容を管理したい、というケースの場合は .chalice/config.json 内で autogen_policy を無効にする。 例えば以下の通り。

.chalice/config.jsonから抜粋
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": false
    }
  }

これを無効にした場合、デフォルトでは .chalice 内の policy-<stage name>.json (この場合は policy-dev.json) を読み込む。 別名を指定したい場合は iam_policy_file でファイル名を指定する。 より詳細な説明はこちら

.chalice/policy-dev.jsonにはRole内でのアクセス権限を書いていけばよい
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:*"
      ],
      "Resource": [
        "*"
      ],
      "Sid": "xxxx-xxxx-xxxx-xxxx-1234"
    },
    {
        "Effect": "Allow",
        "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        "Resource": "arn:aws:logs:*:*:*",
        "Sid": "xxxx-xxxx-xxxx-xxxx-1235"
    }
  ]
}

外部のライブラリなどを使いたい場合、boto3.resource を利用する場合はこちらを採用することになるだろう。 IAM権限の関係から、そもそもIAMの操作権限がない場合などはIAM Role ARNを指定して付与することもできる (iam_role_arn) 。

client しか IAM Policy の自動生成にならないという記述は、例えば AWS BlackBelt シリーズでの説明 とか この記事よりもはるかに詳しい説明とかで見つけることはできたが、公式ドキュメント内でこの記述を見つけられなかった。 あっても良いはずなのだが……

3. CORS対応

  • シナリオ: ブラウザから ajax で API Gateway - Lambda をコールし、レスポンスを取得したい
  • 問題: ブラウザから ajax でアクセスしたら CORS が原因ではじかれる。 chalice local で稼働させている場合も同様。

ユースケースに合わせた CORS の設定をすることになるのだが、もうこれはそのまま 公式を読んでもらう方が早い ぐらい説明が充実している。

概略としては chalice.CORSConfig のインスタンスに適当な設定をした後、@app.route(path, cors=config) のようにデコレータ内の cors に対して設定を渡してやればあとはよしなにしてくれる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10 分で終わる Django の外部キーのチュートリアル

2020-04-01 作成: windows10/Python-3.8.2-amd64/Django-3.0.4

Django の外部キーについての日本語の情報は、ネットでも豊富ではありません。
しかし普通は、「好きな食べ物」と「住所録」は、別のアプリにするはずで、
外部キーの使い方は基本必修項目なのだろうと思います。

同じデータベース内と、外部のデータベースへの外部キーについて、
簡単なチュートリアルを作成しました。
複数のデータベースを相互に関連付ける方法を手軽に知りたい、という人向きです。

  • ねこカフェ用のねこ管理アプリ cafe
    • ねこの名前のテーブルと、好きな食べ物のテーブルをつくり、外部キーで関連付ける。
  • ねこの住所録のアプリ negura
    • ねこのすみかのデータベースを作る。cafe アプリから外部キーで呼ばれる。

なお、Django を初めて使う人は、まず基本編のチュートリアルを読んでください。
10 分で終わる Django の実用チュートリアル

前準備

プロジェクトの作成

ソースを置きたい場所で以下を実行しプロジェクトを新規作成。

django-admin startproject mysite

作成したディレクトリ mysite に入ってアプリケーションを 2 つ、新規作成。

cd mysite
python manage.py startapp cafe
python manage.py startapp sumika

ここまでのファイルの配置はこうなる

mysite/
    mysite/
        __pycashe__/    <- 気にしなくていい
        setting.py, urls.py など *.py が 5 個
    neko/
        migrations/     <- 気にしなくていい
        models.py, views.py など *.py が 6 個
    sumika/
        migrations/     <- 気にしなくていい
        models.py, views.py など *.py が 6 個
    manage.py  

プロジェクトを構成するアプリの登録

cafe と negura のふたつのアプリを登録。

mysite/mysite/settings.py
INSTALLED_APPS = [
    'cafe.apps.CafeConfig',
    'negura.apps.NeguraConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
mysite/mysite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('cafe/', include('cafe.urls')),
    path('negura/', include('negura.urls')),
    path('admin/', admin.site.urls),
]

アプリの実装

cafe アプリの作成

モデル、ビュー、url の作成

neko_food は、cafe アプリ内の同一データベース内での外部キー
neko_negura は、negura アプリ、つまり隣のアプリの別データベースへの外部キー

def __str__(self):メソッドは、ForeignKey を使ったとき、django の汎用ビューでは普通は必要になる。
このメソッドをオーバーライドしておかないと、ブラウザに表示したときに、
さんま ではなく food_name.object(1) のようにインスタンスの名前が表示されてしまう。
このメソッドのおかげで、CreateView や FormView を使うときに、プルダウンリストに文字列が表示される。

neko_negura は、外のアプリからモデルを引っ張ってきているので、
negura.NeguraModel と、アプリ名を先頭につけてからモデルを呼び出す。

mysite/cafe/models.py
from django.db import models
from negura.models import NeguraModel

class NekoModel(models.Model):
    neko_name = models.CharField(max_length = 20)
    neko_food = models.ForeignKey('FoodModel', null = True,on_delete = models.SET_NULL)
    neko_negura = models.ForeignKey('negura.NeguraModel', null = True,on_delete = models.SET_NULL)


class FoodModel(models.Model):
    food_name = models.CharField(max_length = 20)
    def __str__(self):
        return self.food_name

どのビューがどのテンプレートを使うのか、ややこしくならないように、
くどいファイル名を付けている。

mysite/cafe/views.py
from django.views import generic
from .models import NekoModel, FoodModel

class NekoListView(generic.ListView):
    model = NekoModel
    context_object_name = 'nekolistview_context'
    template_name = 'cafe/nekolistview_template.html'

class NekoCreateView(generic.CreateView):
    model = NekoModel
    context_object_name = 'nekocreateview_context'
    template_name = 'cafe/nekocreateview_template.html'
    fields = ['neko_name', 'neko_food', 'neko_negura']
    success_url = '/cafe/nekolist_url'

class FoodListView(generic.ListView):
    model = FoodModel
    context_object_name = 'foodlistview_context'
    template_name = 'cafe/foodlistview_template.html'

urls.py はファイルを新設する
どのパス名がどのビューを指すのか混乱しないように、くどい名前を付けている。

mysite/cafe/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('nekolist_url', views.NekoListView.as_view(), name = 'nekolistview_path'),
    path('nekocreate_url', views.NekoCreateView.as_view(), name = 'nekocreateview_path'),
    path('foodlist_url', views.FoodListView.as_view(), name = 'foodlistview_path'),
]

食べ物や住所の入力用ビューは、チュートリアルの手数が多くなるので作成しない。
代わりに admin 画面から入力するため、admin.py を編集し、有効化する。

mysite/cafe/admin.py
from cafe.models import NekoModel, FoodModel
from django.contrib import admin

admin.site.register(NekoModel)
admin.site.register(FoodModel)

テンプレートの作成

テンプレートを 3 つ作成。
置き場所はデフォルトのディレクトリにしたが、階層が深すぎて本当は好きじゃない。

mysite/cafe/template/cafe/nekolistview_template.html
<h1>ねこのいちらん</h1>
 <table>
  {% for neko_param in nekolistview_context %}
    <tr>
      <td>{{ neko_param.neko_name }}</td>
      <td>{{ neko_param.neko_food.food_name }}</td>
      <td>{{ neko_param.neko_negura.negura_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'foodlistview_path' %}">ねこのたべもの</a></p>
<p><a href = "{% url 'neguralistview_path' %}">ねこのねぐら</a></p>
<p><a href = "{% url 'nekocreateview_path' %}">ねこのとうろく</a></p>
mysite/cafe/template/cafe/foodlistview_template.html
<h1>ねこのたべもの</h1>
<table>
  {% for food_param in foodlistview_context %}
    <tr>
      <td>{{ food_param.food_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>
mysite/cafe/template/cafe/nekocreateview.html
<h1>ねこのとうろく</h1>
<form method = "post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type = "submit" value = "とうろく" />
</form>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>

negura アプリの作成

モデル、ビュー、url の作成

基本的にはさきほどの cafe でやったことの繰り返し。

mysite/negura/models.py
from django.db import models

class NeguraModel(models.Model):
    negura_name = models.CharField(max_length = 20)
    def __str__(self):
        return self.negura_name
mysite/negura/views.py
from django.views import generic
from .models import NeguraModel

class NeguraListView(generic.ListView):
    model = NeguraModel
    context_object_name = 'neguralistview_context'
    template_name = 'negura/neguralistview_template.html'
mysite/negura/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('neguralist_url', views.NeguraListView.as_view(), name = 'neguralistview_path'),
]
mysite/negura/admin.py
from cafe.models import NeguraModel
from django.contrib import admin

admin.site.register(NeguraModel)

テンプレートの作成

テンプレートを 1 つ作成
置き場所はデフォルトの場所で。

mysite/negura/template/negura/neguralistview_template.html
<h1>ねこのねぐら</h1>
<table>
  {% for negura_param in neguralistview_context %}
    <tr>
      <td>{{ negura_param.negura_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>

仕上げ

マイグレーションと admin の設定

マイグレーションしたあとは、admin サイト用の管理者を作成し、
admin 画面にアクセスできるようにしておく。

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

HTTP サーバーの起動と初期データの入力

開発用のサーバーを起動する。デフォルトで起動しているので、ポート番号は 8000。

python manage.py runserver

ブラウザで以下にアクセスして admin 画面を出す。
「たべもの」、「すみか」のふたつは、このアプリでは直接入力できないので、admin 画面から入力する。

http://localhost:8000/admin/

たとえば、たべものは、「さんま」「かりかり」、すみかは、「だんぼーる」「もうふ」など。

動作確認

以下を入力すると、ねこのリストが出る。
最初は登録されたねこがいないので、表にはないっていない。

http://localhost:8000/neko/

「ねこのとうろく」リンクから登録用画面に入ると、
フォームにねこの名前を入力し、
「たべもの」と「ねぐら」はドロップダウンリストから選べるようになっている。

おしまい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

活性化関数のいらないニューラルネットワークΠ-Netを試した

はじめに

CVPR2020に採択された以下の論文で提案された,新しいニューラルネットワークである$\Pi$-NetをPyTorchで実装しました。

Chrysos, Grigorios G., et al. "$\Pi-$ nets: Deep Polynomial Neural Networks." arXiv preprint arXiv:2003.03828 (2020).

学習に使った全体のコードはGitHubにあります。

Π-Netとは?

$\Pi$-Netではネットワークを途中で分岐させ,再び合流する部分で掛け算を行います。
これにより,出力が入力の多項式で表現されます。

普通のニューラルネットワークでは,各層の出力にReLUやSigmoidなどの活性化関数を適用することで非線形性を持たせています。
活性化関数を使わないと,ネットワークの層数をいくら増やしても入力に対して線形な出力しかできないため,意味がありません。

しかし,$\Pi$-Netでは中間層の出力同士で掛け算を行うことでネットワークに非線形性を持たせているので,活性化関数を用いなくても多層にすることで高い表現能力を得ることができます。

論文中ではいくつかネットワーク構造が提案されていますが,今回はその中の1つである以下の構造をベースに実装しました。

image.png

(論文から引用)

Skip-connectionがありResNetのような構造をしていますが,合流する部分が足し算ではなく掛け算(アダマール積)になっています。
各ブロックで前のブロックの出力が2乗されるので,ブロックを$N$個重ねることで$2^N$次の多項式になり,指数的にネットワークの表現能力が増加していきます。

実装

上図のブロックを5つ重ね,以下のようなモデルを書きました。
従って,ネットワークの出力は$2^5=32$次の多項式で表現されます。
活性化関数を全く使っていない点に注目してください。

model
class PolyNet(nn.Module):
    def __init__(self, in_channels=1, n_classes=10):
        super().__init__()
        N = 16
        kwds1 = {"kernel_size": 4, "stride": 2, "padding": 1}
        kwds2 = {"kernel_size": 2, "stride": 1, "padding": 0}
        kwds3 = {"kernel_size": 3, "stride": 1, "padding": 1}
        self.conv11 = nn.Conv2d(in_channels, N, **kwds3)
        self.conv12 = nn.Conv2d(in_channels, N, **kwds3)
        self.conv21 = nn.Conv2d(N, N * 2, **kwds1)
        self.conv22 = nn.Conv2d(N, N * 2, **kwds1)
        self.conv31 = nn.Conv2d(N * 2, N * 4, **kwds1)
        self.conv32 = nn.Conv2d(N * 2, N * 4, **kwds1)
        self.conv41 = nn.Conv2d(N * 4, N * 8, **kwds2)
        self.conv42 = nn.Conv2d(N * 4, N * 8, **kwds2)
        self.conv51 = nn.Conv2d(N * 8, N * 16, **kwds1)
        self.conv52 = nn.Conv2d(N * 8, N * 16, **kwds1)

        self.fc = nn.Linear(N * 16 * 3 * 3, n_classes)

    def forward(self, x):
        h = self.conv11(x) * self.conv12(x)
        h = self.conv21(h) * self.conv22(h)
        h = self.conv31(h) * self.conv32(h)
        h = self.conv41(h) * self.conv42(h)
        h = self.conv51(h) * self.conv52(h)
        h = self.fc(h.flatten(start_dim=1))

        return h

結果

MNISTとCIFAR-10の分類を学習しました。

MNIST

Accuracy
Screenshot from 2020-03-31 23-22-56.png

Loss
Screenshot from 2020-03-31 23-22-44.png

約99%のテスト精度です!

CIFAR-10

Accuracy
Screenshot from 2020-03-31 23-19-35.png

Loss
Screenshot from 2020-03-31 23-20-46.png

テストが70%くらいの精度ですが過学習していますね・・・

おわりに

出力が入力の多項式になるので活性化関数を使わずに学習ができました。

ブロックを重ねることで表現力が指数的に向上すると上述しましたが、
普通のニューラルネットワークでも層数に対して指数的に表現力が向上することが知られている1ので、正直$\Pi$-Netの利点はよく分からなかったです・・・


  1. Montufar, Guido F., et al. "On the number of linear regions of deep neural networks." Advances in neural information processing systems. 2014. 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[CentOS8]Pythonの標準出力をsystemdのログに吐く方法

はじめに

PythonでTwitterのボットを作ったのだが、print関数で出力したログsystemctl statusコマンドでみることができずはまったので解決方法を覚え書きです。もっといい方法があったら教えていただけると嬉しいです。

環境

CentOS Linux release 8.1.1911 (Core)

Unitファイルの記述

etc\systemd\system\におくUnitファイルの記述

bot.service
[Unit]
Description=Python Tweet Collector

[Service]
ExecStart=実行ファイル(ここではBotを起動するシェルスクリプト)のパス
Type=simple
StandardOutput=journal #標準出力をjournalctlで見られるようにする
StandardError=journal #標準エラーをjournalctlで見られるようにする

[Install]
WantedBy=multi-user.target

Pythonコマンド実行時のオプション

上記の設定だけだとPythonの標準出力がバッファされてprint文の出力が表示されないことがあるらしい...
ということでシェルスクリプトを書き、そこからBotを起動するようにしました

start_bot.sh
#!/bin/bash

cd $(dirname $0) #このファイルがあるディレクトリに移動

. venv/bin/activate #Pythonの仮想環境を有効化

python -u bot.py # -u オプションでバッファを無効化

最後にchmod 744 start_bot.shでシェルスクリプトに実行権限を付与

Botを起動

systemctl start bot.service

Botの状態を確認

systemctl status bot.service

ログを確認

journalctl -u bot.service

Pythonのプリント文で出力した内容がログに表示されているのが確認できると思います

おわり

Linuxのことはまだまだ初心者なので、不適切な箇所の指摘、アドバイス等ありましたらご教授いただけるととてもうれしいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】ある条件式を満たす要素のインデックスを取得したい

背景

プロpythonistからすれば普通のことなのかもしれないんですが、
自分は理解するのに時間がかかり、
理解できたらその強さに感動した。。。ので。。。

ndarray[条件式]

行列のままで各要素がある条件を満たすか満たさないかを判別し、
その結果のTrue/Falseを同じ形状の行列として出力してくれます。

arr = np.array([[0,1,2,3],[0,2,4,6]])
print(arr<3)
# [[ True  True  True False]
#  [ True  True False False]]

numpy.nonzero

行列の0でない要素のインデックスを取得してくれ、x,y別のndarray配列として出力されます。

# 例の定義
arr_int = np.array([[3,5,0],[0,4,0]])
arr_bool = np.array([[True,True,False],[False,True, False]])

# np.nonzeroの利用
nonzero_int_row, nonzero_int_column = np.nonzero(arr_int)
nonzero_bool_row, nonzero_bool_column = np.nonzero(arr_bool)

# 各変数の値
# row: array([0, 0, 1])
# column: array([0, 1, 1]))

この例では、0でないのがarr[0,0],arr[0,1],arr[1,1]なのでこういう結果になります。

合体!

arr = np.array([[0,1,2,3],[0,2,4,6]])
arr_bool = arr<3
nonzero_row, nonzero_column = np.nonzero(arr_bool)
# row: [0 0 0 1 1]
# column: [0 1 2 0 1]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの基礎を学ぶのにおすすめの教材・方法を紹介(初学者向け)

はじめに

この記事を書く前に簡単な自己紹介をさせてもらいます。

2017年からバルセロナの街クラブにてサッカーのトレーナーをしてる鍼灸師
現在は、AIでスポーツ・医療の発展に貢献をしたくAIエンジニアを目指して勉強中(今年の1月28日から)もう直ぐ日本帰国予定。

この記事の対象者

・Pythonを学びたいが勉強方法がわからない方
・色んな教材を試したが上手くスキルがアップしなかった方
・AIエンジニアを目指してる方
・スクールではなく、独学で学びたい方

大まかな流れ

とみーさんのロードマップでPythonの基礎を学ぶ

 ・https://obgynai.com/python-roadmap/

UdemyやYouTube、その他記事で理解不足のところは復習する

中学生でも分かるPython入門シリーズ

【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 初級編

DSのためのPython入門講座

【世界で21万人が受講】データサイエンティストを目指すあなたへデータサイエンス25時間ブートキャンプ

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

ゴール

・ロードマップで記載されてるPythonの基礎の課題をある程度できるようになる

こちらの課題はロードマップで学んだ内容を勉強のために作成したのですが
ロードマップを終了後には、問題を見ただけである程度コーディングすることができるようにまで成長しました。
ロードマップ内の課題

・他の教材のPython講座をみてもある程度理解ができるようになる。

【世界で21万人が受講】データサイエンティストを目指すあなたへ〜データサイエンス25時間ブートキャンプ〜

こちらのUdemyの教材に、Pythonの基礎動画だけで3時間あるのですが
小テストが27問あり、ロードマップ終了後は3問ミスだけで講座自体もほとんど理解できました。

ロードマップがオススメの理由

私自身1月28日から独学でAIエンジニアを目指し
Python基礎、数学、統計学、機械学習、DeepLearningと自分なりに学んでたのですが
広く浅く、いろいろな教材を学んでは他の教材へ行き、終了すれば他の教材を学ぶを1日平均4-8時間を1ヶ月半ほど繰り返してました。

しかし、勉強時間の割りに、できるようなことが少なく初学者には適切なロードマップが必要だと感じました。
その後、こちらのロードマップにしたがって勉強したところ約10日間で、Pythonのコードが少し書けるようになってきており、こちらのロードマップはPython習得基礎への再現性が高いと思い紹介させてもらうことにしました。

勉強方法

とみーさんのロードマップでPythonの基礎を学ぶ

 ・https://obgynai.com/python-roadmap/

主な勉強方法ですが、シンプルでこちらのロードマップにしたがって学んでいくです。
個人的に、理解しにくいところがあれば、先ほど紹介した動画なので学んでからロードマップに戻るとさらに良しです。

私は3月21日から昨日までの11日間毎日平均7時間ほど繰り返し4周ほどやりました。(約80時間ほどですね)
同じ教材を繰り返すことによって毎週新たな課題が発見でき前回理解できなかったところも理解できるようになってきます。

守るべきルール2つ

1. 1周やったから終了ではなく何周も繰り返す

1周程度では人にもよるかもしれませんが、理解は多少できるようになりますが、ほとんど身につかないかと思います。 (繰り返し大事です)

2. 必ずコーディングする

こちらは思ってるよりも大切で、理解してるからコーディングは必要ないと思ってても実際にコードを書くとなると書けないものです。(以前の私は理解してるからいいやと飛ばしてました)

より効率よく、集中して学びたい方におすすめ

ポモドーロメゾットを紹介します。

クリスさんという方がYouTubeで話しており、
効率化・集中力アップを目的に取り入れてみました。

ポモドーロメゾットとは

・25分作業、5分休憩(やり方)
・1つの作業に集中、邪魔されない環境を作る(ルール)
・音楽でプラスアルファー

ルールの2つ

・1つの作業に集中(これはマルチタスクは集中力の低下につながると言われてるから)
・邪魔されない環境を作る(集中すると発生するゾーンに上手く入る為)

音楽

脳波は集中してる時は12-18hzと言われております。
ある音楽を聴くと、脳波の波動が集中してる波動に変えてくれるといいます。
脳波を変えるには10分ぐらいかかるそう。
YouTubeで検査可能です。

詳しく知りたい方はYoutubeをご覧ください。

こちらのロードマップもオススメです

・米国でデータサイエンティストとして活躍されてるかめさんの記事もとても有益なのでこちらも同時に利用するのもありですね!
DSのためのPython入門講座

世の中には無料、有料関わらず、たくさん優良な記事があるので自分にあった教材を選択していきましょう

最後に

今回は私自身AIエンジニアを目指す中で、こちらのロードマップはオススメだと思ったので紹介させてもらいました。

とはいえ、まだPythonを使ってコーディングを完璧に出来るようになったわけではないので、引き続き勉強をしつつ、AIエンジニアに向けて毎日努力していきたいと思います。

私と同じように学ばれてる方。諦めずに一緒にがんばっていきましょう!!

参考文献

・とみーさんのロードマップ
 https://obgynai.com/python-roadmap/

・ロードマップ内の内容を自作
ロードマップ内の課題

中学生でも分かるPython入門シリーズ

【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 初級編

DSのためのPython入門講座

【世界で21万人が受講】データサイエンティストを目指すあなたへデータサイエンス25時間ブートキャンプ

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【文系出身の僕がオススメする】Python初心者のための入門から応用までの学習方法を紹介します【教材紹介】

はじめに

この記事を書く前に簡単な自己紹介をさせてもらいます。

2017年からバルセロナの街クラブにてサッカーのトレーナーをしてる鍼灸師
現在は、AIでスポーツ・医療の発展に貢献をしたくAIエンジニアを目指して勉強中(今年の1月28日から)もう直ぐ日本帰国予定。

この記事の対象者

・Pythonを学びたいが勉強方法がわからない方
・色んな教材を試したが上手くスキルがアップしなかった方
・AIエンジニアを目指してる方
・スクールではなく、独学で学びたい方

大まかな流れ

とみーさんのロードマップでPythonの基礎を学ぶ

 ・https://obgynai.com/python-roadmap/

UdemyやYouTube、その他記事で理解不足のところは復習する

中学生でも分かるPython入門シリーズ

【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 初級編

DSのためのPython入門講座

【世界で21万人が受講】データサイエンティストを目指すあなたへデータサイエンス25時間ブートキャンプ

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

ゴール

・ロードマップで記載されてるPythonの基礎の課題をある程度できるようになる

こちらの課題はロードマップで学んだ内容を勉強のために作成したのですが
ロードマップを終了後には、問題を見ただけである程度コーディングすることができるようにまで成長しました。
ロードマップ内の課題
ロードマップ内の課題の回答

・他の教材のPython講座をみてもある程度理解ができるようになる。

【世界で21万人が受講】データサイエンティストを目指すあなたへ〜データサイエンス25時間ブートキャンプ〜

こちらのUdemyの教材に、Pythonの基礎動画だけで3時間あるのですが
小テストが27問あり、ロードマップ終了後は3問ミスだけで講座自体もほとんど理解できました。

ロードマップがオススメの理由

私自身1月28日から独学でAIエンジニアを目指し
Python基礎、数学、統計学、機械学習、DeepLearningと自分なりに学んでたのですが
広く浅く、いろいろな教材を学んでは他の教材へ行き、終了すれば他の教材を学ぶを1日平均4-8時間を1ヶ月半ほど繰り返してました。

しかし、勉強時間の割りに、できるようなことが少なく初学者には適切なロードマップが必要だと感じました。
その後、こちらのロードマップにしたがって勉強したところ約10日間で、Pythonのコードが少し書けるようになってきており、こちらのロードマップはPython習得基礎への再現性が高いと思い紹介させてもらうことにしました。

勉強方法

とみーさんのロードマップでPythonの基礎を学ぶ

 ・https://obgynai.com/python-roadmap/

主な勉強方法ですが、シンプルでこちらのロードマップにしたがって学んでいくです。
個人的に、理解しにくいところがあれば、先ほど紹介した動画なので学んでからロードマップに戻るとさらに良しです。

私は3月21日から昨日までの11日間毎日平均7時間ほど繰り返し4周ほどやりました。(約80時間ほどですね)
同じ教材を繰り返すことによって毎週新たな課題が発見でき前回理解できなかったところも理解できるようになってきます。

キカガクのいまにゅさんのYouTube動画も有益

実際に私がやってた方法ですが
ロードマップの内容で理解があまりできなかった時は、キカガクのいまにゅさんのYouTubeを見てからロードマップに入るとさらに理解が深まるのでオススメです。

また説明もうまく他の動画もオススメなのでぜひ利用してみてください。
中学生でも分かるPython入門シリーズ

具体的な例

ロードマップのStep17にあるオブジェクト指向ですが、動画を見る前はあまり理解ができなく、オブジェクト指向とはどういうことなのか。
何のためにあるのかよく理解できませんでしたが、いまにゅさんの3つの動画を見てからロードマップに戻ると驚くほど理解ができ、その後のロードマップの助けになりました。

おすすめとしては、まずは動画で概要を理解する方法もありかと思います。

オブジェクト指向とは
クラス
継承

守るべきルール2つ

1. 1周やったから終了ではなく何周も繰り返す

1周程度では人にもよるかもしれませんが、理解は多少できるようになりますが、ほとんど身につかないかと思います。 (繰り返し大事です)

2. 必ずコーディングする

こちらは思ってるよりも大切で、理解してるからコーディングは必要ないと思ってても実際にコードを書くとなると書けないものです。(以前の私は理解してるからいいやと飛ばしてました)

より効率よく、集中して学びたい方におすすめ

ポモドーロメゾットを紹介します。

クリスさんという方がYouTubeで話しており、
効率化・集中力アップを目的に取り入れてみました。

ポモドーロメゾットとは

・25分作業、5分休憩(やり方)
・1つの作業に集中、邪魔されない環境を作る(ルール)
・音楽でプラスアルファー

ルールの2つ

・1つの作業に集中(これはマルチタスクは集中力の低下につながると言われてるから)
・邪魔されない環境を作る(集中すると発生するゾーンに上手く入る為)

音楽

脳波は集中してる時は12-18hzと言われております。
ある音楽を聴くと、脳波の波動が集中してる波動に変えてくれるといいます。
脳波を変えるには10分ぐらいかかるそう。
YouTubeで検査可能です。

詳しく知りたい方はYoutubeをご覧ください。

こちらのロードマップもオススメです

・米国でデータサイエンティストとして活躍されてるかめさんの記事もとても有益なのでこちらも同時に利用するのもありですね!
DSのためのPython入門講座

世の中には無料、有料関わらず、たくさん優良な記事があるので自分にあった教材を選択していきましょう

最後に

今回は私自身AIエンジニアを目指す中で、こちらのロードマップはオススメだと思ったので紹介させてもらいました。

とはいえ、まだPythonを使ってコーディングを完璧に出来るようになったわけではないので、引き続き勉強をしつつ、AIエンジニアに向けて毎日努力していきたいと思います。

私と同じように学ばれてる方。諦めずに一緒にがんばっていきましょう!!

参考文献

・とみーさんのロードマップ
 https://obgynai.com/python-roadmap/

・ロードマップ内の内容を自作
ロードマップ内の課題
ロードマップ内の課題の回答

中学生でも分かるPython入門シリーズ

【キカガク流】人工知能・機械学習 脱ブラックボックス講座 - 初級編

DSのためのPython入門講座

【世界で21万人が受講】データサイエンティストを目指すあなたへデータサイエンス25時間ブートキャンプ

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

その他オススメの記事

【独学可能】Python初心者のための機械学習に向けた学習ロードマップ

AI Academy

codExa

cousera 機械学習

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCP Compute engine でGPUを使ってPythonの実行環境を構築する

経緯とやりたいこと

卒業研究で、tensorflowで書かれたコードを実行する必要があったのですが、
そのプログラムが中々メモリを消費するもので、研究室のパソコンくんが悲鳴をあげて動いてくれなかったので、泣く泣くGCPを利用して実行環境を構築する必要がありました...ううう....

しかも、GCPを使わないと私の卒業に間に合わない疑惑(?!)があったので(大変です)
GCPも使えるようにPythonの実行環境を構築しました...。

最初はやり方が全然わからなくで、半泣きになりながらググりまくるという日々を過ごしました(これによって、私の研究の進捗が芳しくなかったことはいうまでもありません:innocent:)!!

同じようなことで困っている方々の為に少しでもなれば...ということで始めていきたいと思います!

環境設定

今回は、以下の設定で環境を構築していきます。

  • Ubuntu 16.04 LTS
  • GCE
  • CUDA 9.0
  • cuNN 7.4
  • TensolFlowGPU 1.12.0

TensolFlowはバージョンによって、CUDAとバージョンを合わせたりする必要があるみたいです...:neutral_face:
このバージョンがずれていたりするとうまくGCPが認識できなかったりするみたいなので、
以下から確認することをお勧めします。(ちなみに私は何回かミスりました、はい。)

TensolFlowGPU ビルド設定

環境構築の手順

0. GCPへの登録

  • CCPへアカウント登録
  • VMインススタンスの課金の有効化
  • VMインスタンスの作成
    • vCPU x 24
    • メモリ 90 GB
    • GPU : NVIDIA Tesla T4
    • Boot disk : Ubuntu 16.04 LTS (Size 250GB)
    • ZONE : us-west1-b
    • HTTPトラフィックを許可

参考サイト: タダでGCPのGPU(NVIDIA Tesla K80)を使ってディープラーニング(NVIDIA DIGITS)する方法

1. CUDAとNVIDIAドライバのインストール

VMインスタンスで以下のcommandを実行し,CUDAとドライバをインストールする

$ curl -O http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_9.0.176-1_amd64.deb
$ sudo dpkg -i cuda-repo-ubuntu1604_9.0.176-1_amd64.deb
$ sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
$ sudo apt-get update
$ sudo apt-get install cuda-9-0

さらに、GPUのパフォーマンスを最適化するために、以下のcommandを実行します。

$ sudo nvidia-smi -pm 1

2. cuDNN7.0のインストール

ここからでdeveloperアカウントを作成して、cuDNNの以下の3つのファイルをダウンロードします。

version : ubuntu 16.04 cuda-9.0 version

  • libcudnn7_7.6.4.38-1+cuda9.0_amd64.deb
  • libcudnn7-dev_7.6.4.38-1+cuda9.0_amd64.deb
  • libcudnn7-doc_7.6.4.38-1+cuda9.0_amd64.deb

ダウンロードが完了したら、3つのファイルをStarageへアップロードします。
ここではバケット名はcuda_9とします(お好みで変更してください!)。

アップロードが完了したら gsutil command でそのままインスタンスへの転送します。
アップロードするディレクトリはお好みでどうぞ。

$ cd {UP_LOAD_PATH}
$ gsutil cp gs://cuda_9/libcudnn7_7.6.4.38-1+cuda9.0_amd64.deb .
$ gsutil cp gs://cuda_9/libcudnn7-dev_7.6.4.38-1+cuda9.0_amd64.deb .
$ gsutil cp gs://cuda_9/libcudnn7-doc_7.6.4.38-1+cuda9.0_amd64.deb .

転送が完了したらファイルを展開してインストールしていきます。

$ sudo dpkg -i *.deb

3. swap file の設定

swap file がない場合、プログラム実行時にメモリリークする可能性があります。
GCEでLinux仮想マシンを作成するとmUbuntuだろうがCentOSだろうが swap file がない状態で仮想マシンが作成される...らしい...です。(そんなことを1ミリも知らなかった私は、ここで詰みました)

ということで、まずはfree commandでswapの有無を確認。

$ free -m

以下のようになっていたらSwap:がゼロになっているので、swapファイルを作成する必要があります。

               total        used        free      shared  buff/cache   available
Mem:            581         148          90           0         342         336
Swap:             0           0           0

swap file の作成。swap file の容量はお好みで(今回は10G)

$ sudo fallocate -l 10G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile

swap file の確認

$ free -m
               total        used        free      shared  buff/cache   available
Mem:            581         148          88           0         344         336
Swap:          1023           0        10023

Tips : 再起動時にスワップファイルを自動マウントするには,/etc/fstabに以下を追加したらいいみたいです。

/swapfile none swap sw 0 0

4. GPU認識確認とCUDAの設定

CUDAの設定を行っていきます。

$ echo "export PATH=/usr/local/cuda-9.0/bin\${PATH:+:\${PATH}}" >> ~/.bashrc
$ source ~/.bashrc
$ sudo /usr/bin/nvidia-persistenced

その後、GPUが認識されているかを確認します。

$ nvidia-smi

以下のようなresponseがあれば、GPUの設定は完了です!

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.72       Driver Version: 410.72       CUDA Version: 10.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 00000000:00:04.0 Off |                    0 |
| N/A   42C    P0    65W / 149W |      0MiB / 11441MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

5. Python環境の構築

最後に、Python環境をAnadondaで構築していきます。
(普段からAnacondaを使っているのと、他の方法だとなぜがプログラムが動かなかったので今回はAnacondaにしました)

wgetでAnaconda をダウンロードします。

$ wget https://repo.anaconda.com/archive/Anaconda3-5.3.1-Linux-x86_64.sh
$ sh ./Anaconda3-5.3.1-Linux-x86_64.sh
$ echo ". /home/{USER_NAME}/anaconda3/etc/profile.d/conda.sh" >> ~/.bashrc
$ source ~/.bashrc

次に、Anaondaの仮装環境を構築します。PythonのversionとENV_NAMEはお好みで。
(今回はtensorflow==1.12.0を使いたいので、Python3.6.5

$ conda create -n {ENV_NAME} python=3.6.5
$ conda activate {ENV_NAME}

6. tensorflow-gpuのインストール

condaからinstall(ここまでくると安心感ありますね...)

$ conda install tensorflow-gpu==1.12.0

以下のプログラムを実行して、GPUと出てきたらtensorflow-gpuがGPUを認識してくれています。

test.py
from tensorflow.python.client import device_lib
device_lib.list_local_devices()
  • OUT
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 2319180638018740093
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 11324325888
locality {
  bus_id: 1
}
incarnation: 13854674477442207273
physical_device_desc: "device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7"
]

その他、必要なライブラリがあればcondaからinstallしていきます。(↓こんな感じ)

$ conda install numpy==1.15.4
$ conda install scipy==1.1.0 
$ conda install scikit-learn==0.20.0

これで、conpute engine のインスタンスでGCPを使ってPythonのプログラムを実行できるはず...です!お疲れ様でした...!!

なにか、間違いなどがあればコメントの方宜しくお願いします:bow_tone2:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django3で管理者ページにログインできない

概要

タイトルの通り、http://localhost:8000/admin/ からsuper userでログインしようとするとrunserverが落ちて失敗する。

ログインに失敗した環境

macOS Catalina 10.15.3
Django 3.0.5
Python 3.7.0

pythonをアップグレードすることで解決

3.7.4と3.8.0で動作することを確認しました。一応referenceでは3.6から対応しているはずですが、なぜか3.7.0では正常に動作しませんでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python&機械学習 勉強メモ③



の続き

参考教材

Udemy みんなのAI講座 ゼロからPythonで学ぶ人工知能と機械学習

課題設定

緯度・経度を与えると、東京・神奈川のどちらに属するかを判定するプログラムを作成する.

判定方法

入力層2つ・中間層2つ・出力層1つのニューラルネットワークで判定する。

ニューロンクラス

class Neuron:
    input_sum = 0.0
    output = 0.0

    def setInput(self, inp):
        self.input_sum += inp

    def getOutput(self):
        self.output = sigmoid(self.input_sum)
        return self.output

    def reset(self):
        self.input_sum = 0
        self.output = 0

setInput

入力を合計する。

getOutput

入力値を活性化関数で変換した値を出力する。
ニューロンは入力値が閾値を超えると発火する特徴があるが、上記のように活性化関数にシグモイド関数を適用することでそれを模倣する。

reset

入力値をリセットする。

ニューラルネットワーク

class NeuralNetwork:
    input_layer = [0.0, 0.0, 1.0]
    middle_layer = [Neuron(), Neuron(), 1.0]
    ouput_layer = Neuron()
    ...

input_layermiddle_layerの3番目の要素はバイアス。
ニューロンクラスを組み合わせて

入力形式とファイル読み込み

入力ファイル

1行に 経度,緯度 を記載しているファイルを読み込む。

35.48,137.76
35.47,137.81
35.29,138.06
...

得られる結果

Figure_1.png

複雑な境界線に対して東京・神奈川を区別できている。
きちんと判定するには、各入力の重みづけや閾値を適切に設定する必要があるはず。
その設定方法は次回以降に学習。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】PythonでWebスクレイピング 「ページ内のURLにアクセスして内容を取得する」

はじめに

前回のおさらい

【初心者向け】PythonでWebスクレイピングをやってみるの記事の続きになります。
前回は、日経ビジネス電子版
https://business.nikkei.com/
から、新着記事の見出しとURLを取得しました。

しかし、これだけでは、実際にこのURLにアクセスすれば知る事ができます。

今回の目的

ニュースサイトを閲覧した際に、気になったニュースがあればクリックをして、詳細な内容を閲覧すると思います。
日経ビジネスの記事には、全てのニュースでは無いのですが、内容の前に読みたいと思わせるような150文字程度の記事紹介文があります。
この内容も一緒に表示する事で、記事を読むか読まないかの判断材料にします。
いちいち全ての記事にアクセスして、記事の紹介文を読むのは大変です。
Webスクレイピングの良さを、引き出していきます。

前回のコードのおさらい

code.py
import requests
from bs4 import BeautifulSoup
import re

urlName = "https://business.nikkei.com"
url = requests.get(urlName)
soup = BeautifulSoup(url.content, "html.parser")

elems = soup.find_all("span")

for elem in elems: 
  try:
    string = elem.get("class").pop(0)
    if string in "category":
      print(elem.string)
      title = elem.find_next_sibling("h3")
      print(title.text.replace('\n',''))
      r = elem.find_previous('a')
      #記事のURLを取得している
      print(urlName + r.get('href'), '\n')

      #この部分にURL先の記事紹介文を取得するプログラムを書く

  except:
    pass

詳しくは、前回の記事を参照してください。
ニュースをクリックした際に、遷移するURLを表示して前回は終わりました。今回は、そのURLにアクセスして内容を取得します。

プログラミング

まず初めに、今回はrequestsとBeautifulSoupの部分を関数にします。

subFunc.py
import requests
from bs4 import BeautifulSoup

def setup(url):
  url = requests.get(url)
  soup = BeautifulSoup(url.content, "html.parser")
  return url, soup
main.py
import re
import subFunc

urlName = "https://business.nikkei.com"
url, soup = subFunc.setup(urlName)

elems= soup.find_all("span")

for elem in elems: 
  try:
    string = elem.get("class").pop(0)
    if string in "category":
      print('\n', elem.string)

      title = elem.find_next_sibling("h3")
      print(title.text.replace('\n',''))

      r = elem.find_previous('a')
      nextPage = urlName + r.get('href')
      print(nextPage)

      #ここから新しく記述した部分
      nextUrl, nextSoup = subFunc.setup(nextPage)
      abst = nextSoup.find('p', class_="bplead")
      if len(abst) != 0:
        print(abst.get_text().replace('\n',''))
  except:
    pass

正直、やる事は変わりません。
遷移先のURLを、requestsとBeautifulSoupを使って情報を取得します。
記事の紹介文は、classがbpleadの要素にありました。しかし、紹介文が無い記事もあるので、あった場合に表示するようにしました。

実行結果は以下の通りです。(省略しています)

 共創/競争・スタートアップ
新型コロナは長期戦 xxxxxxxxxxx
https://business.nikkei.com/atcl/gen/19/00101/040100009/    
新型コロナウイルスの流行xxxxxxxxxxxxと訴えた。

さいごに

調べてみると、他の方法もいくつか紹介されていますが、簡単な方法で遷移先の内容を取得してみました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TeamsやZoomでカメラ画像を加工する方法 Tensorflowで感情分析の巻

本記事はこちらでも紹介しております。
https://cloud.flect.co.jp/entry/2020/04/01/201158

みなさんこんにちは。

前回はTeamsやZoomでカメラ画像を加工する方法をご紹介し、
笑顔を検出してニコニコマークを表示するデモをご紹介いたしました。
https://qiita.com/wok/items/0a7c82c6f97f756bde65

今回は、これを少し拡張して、AI(Tensorflow)でリアルタイムに感情分析することに挑戦しましたので、ご紹介いたします。
具体的には、次のように、画像に写った人物の表情から悲しみや怒りと言った感情を読み取り、それに合わせた画像を画面に表示します。これでビデオ会議で言葉を発しなくても感情を伝えることができそうですね。(いや待て、、)

out2.gif

前提

前回の記事を参考に、v4l2loopbackなどを設定しておいてください。

Webカメラのフックの拡張

今回は、カメラで取られた人物の感情を分析して、対応する画像をビデオストリーム上の映像に表示します。
感情分析にはTensorflowを用いますが、次のサイトでトレーニング済みのモデルがMITライセンスで提供されているので、これを利用させてもらいましょう。

https://github.com/oarriaga/face_classification

まず、最初に前回と同様に下記のリポジトリから、スクリプトをcloneして、必要なモジュールをインストールしてください。

$ git clone https://github.com/dannadori/WebCamHooker.git
$ cd WebCamHooker/
$ pip3 install -r requirements.txt

次に、先程のサイトから感情分析用のトレーニング済みのモデルを取得します。
なお、今回は、適切な画像を表示するために、性別判断も同時に行おうと思います。

$ wget https://github.com/oarriaga/face_classification/raw/master/trained_models/emotion_models/fer2013_mini_XCEPTION.110-0.65.hdf5 -P models # 感情分析用のモデル
$ wget https://github.com/oarriaga/face_classification/raw/master/trained_models/gender_models/simple_CNN.81-0.96.hdf5 -P models/ # 性別判定用のモデル

また、画像を再びいらすとやさんからお借りしましょう。

$ wget https://4.bp.blogspot.com/-8DirG_alwXo/V5Xc1SMykvI/AAAAAAAA8u4/krI2n_SWimUBGEyMWCw5kZZ-HzoUKrY8ACLcB/s800/pose_sugoi_okoru_woman.png -P images/
$ wget https://4.bp.blogspot.com/-EBpxVigkCCY/V5Xc1CHSeEI/AAAAAAAA8u0/9XIAzDJaQNU3HIiXi4PCPK3aMip3aoGyACLcB/s800/pose_sugoi_okoru_man.png -P images/

$ wget https://4.bp.blogspot.com/-HJ0FUQz67AA/XAnvUxSRsLI/AAAAAAABQnM/3XzIWzvW6L80aGB-geaHvAQETlJTAwkYQCLcBGAs/s800/business_woman2_4_think.png -P images/
$ wget https://3.bp.blogspot.com/-S7iQQCOgfWY/XAnvQWwBGtI/AAAAAAABQmc/z7yIqGjIQr88Brc_QNdOGsrJRLvqY1hcQCLcBGAs/s800/business_man2_4_think.png -P images/

$ wget https://4.bp.blogspot.com/-PQQV4wfGlNI/XAnvQBMeneI/AAAAAAABQmU/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png] -P images/
$ wget https://3.bp.blogspot.com/-QcDbWqQ448I/XAnvUT4TMDI/AAAAAAABQnE/_H4XzC4E93AEU2Y7fHMDBjri1drdyuAPQCLcBGAs/s800/business_woman2_2_shock.png -P images/

$ wget https://3.bp.blogspot.com/-dSPRqYvIhNk/XAnvPdvjBFI/AAAAAAABQmM/izfRBSt1U5o7eYAjdGR8NtoP4Wa1_Zn8ACLcBGAs/s800/business_man1_4_laugh.png -P images/
$ wget https://1.bp.blogspot.com/-T6AOerbFQiE/XAnvTlQvobI/AAAAAAABQm8/TYVdIfxQ5tItWgUMl5Y0w8Og_AZAJgAewCLcBGAs/s800/business_woman1_4_laugh.png -P images/

$ wget https://4.bp.blogspot.com/-Kk_Mt1gDKXI/XAnvS6AjqyI/AAAAAAABQm4/LQteQO7TFTQ-KPahPcAqXYannEArMmYfgCLcBGAs/s800/business_woman1_3_cry.png -P images/
$ wget https://4.bp.blogspot.com/-3IPT6QIOtpk/XAnvPCPuThI/AAAAAAABQmI/pIea028SBzwhwqysO49pk4NAvoqms3zxgCLcBGAs/s800/business_man1_3_cry.png -P images/

$ wget https://3.bp.blogspot.com/-FrgNPMUG0TQ/XAnvUmb85VI/AAAAAAABQnI/Y06kkP278eADiqvXH5VC0uuNxq2nnr34ACLcBGAs/s800/business_woman2_3_surprise.png -P images/
$ wget https://2.bp.blogspot.com/-i7OL88NmOW8/XAnvQacGWuI/AAAAAAABQmY/LTzN4pcnSmYLke3OSPME4cUFRrLIrPsYACLcBGAs/s800/business_man2_3_surprise.png -P images/

$ cp images/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png]  images/lN7zIROor9oi3q-JZOBJiKKzfklzPE1hwCLcBGAs/s800/business_man2_2_shock.png

上記のうち、最後のコマンドは、ファイル名にゴミ(末尾のカギカッコ)がついているので取り除いているだけです。

実行は次のように行います。オプションが一つ追加されてます。

  • input_video_num には実際のウェブカメラのデバイス番号を入れてください。/dev/video0なら末尾の0を入力します。
  • output_video_dev には仮想ウェブカメラデバイスのデバイスファイルを指定してください。
  • emotion_mode はTrueにしてください。

なお、終了のさせ方はctrl+cでお願いします。

$ python3 webcamhooker.py --input_video_num 0 --output_video_dev /dev/video2 --emotion_mode True

上のコマンドを実行するとffmpegが動き、仮想カメラデバイスに映像が配信されはじめます。

ビデオ会議をしてみよう!

前回と同様に、ビデオ会議をするときにビデオデバイスの一覧にdummy〜〜というものが現れると思うのでそれを選択してください。
これはTeamsの例です。表情に合わせて画面上部の文字列が変化し、合わせて対応する画像が表示されますね。大成功です。
out2.gif

最後に

在宅勤務が長引き、気軽なコミュニケーションがなかなか難しいかもしれませんが、こういった遊び心をビデオ会議に持ち込んで、会話を活性化させるのもいいのではないかと思っています。
もっといろいろできると思いますので、みなさんもいろいろ試してみてください。

参考

Tensorflowによる感情分析については次のサイトを参考にさせていただきました。
(このサイトではtensorflowjsでの紹介をされております)

https://book.mynavi.jp/manatee/detail/id=99887

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LDAPのことを意識させずに操作できるPYTHON用クラス

はじめに

pythonでLDAPを操作する方法を3回に分けてまとめてきました。複数人で開発する場合は全員がldap3のライブラリやLDAPのことについて理解する必要がありますが、それには時間と労力がかかります。そこでLDAPはツリー構造のデータであるということだけを知っていれば誰でも使えるようにクラスを作成しました。

環境

  • python:3.6.5
  • ldap3:2.7
  • イメージ:osixia/openldap

メリット

  • LDAPのことを意識せずに使える
  • ルールを加えられる(dc=xxxの後はouにするなど)
  • ぱっと見で分かりやすい

今回のクラスを使用した例

まずは、今回のクラスを使用した場合にどのようにLDAPを操作するのかを記載します。使用するときはクラス名を変えるとより分かりやすくなるため、この後の説明で出てくるDomainクラスをAddress、OrganizationをOwner、CommonをPetに変更することでAddress -> Owner -> Petの階層であることが意識できます。

main.py
from ldap_obj.Address import Address

# 場所の追加
address = Address('localhost', 'LdapPass', 'admin', 'sample-ldap')
address.set_item('tokyo')
address.save()

# 人の追加
owner = address.create_owner()
owner.set_item('sato')
owner.save()

# ペット(たま)の追加
pet_tama = owner.create_pet()
pet_tama.set_item('tama')
pet_tama.save({'sn': 'sato', 'st': 'tama', 'title': 'cat'})

# ペット(ぽち)の追加
pet_pocho = owner.create_pet()
pet_pocho.set_item('pochi')
pet_pocho.save({'sn': 'sato', 'st': 'pochi', 'title': 'dog'})

# titleを検索条件にしてdomain層からcnの値を取得する
address.set_filter('title')
print(address.get_common())

print('***********************')
# Addressを生成
address_get = Address('localhost', 'LdapPass', 'admin', 'sample-ldap')
address_get.set_item('tokyo')
# Address -> Ownerの生成
owner_get = address_get.create_owner()
owner_get.set_item('sato')
# Address -> Owner -> Petの生成
pet_get = owner_get.create_pet()
pet_get.set_item('tama')
print(pet_get.get_common())

LDAP用クラス

LDAP用クラスは各階層ごとにクラスを作成します。

ディレクトリ構成

\--
  |--ldap_obj\
  |    |--BaseClass.py (基準のクラス)
  |    |--CommonClass.py (cnのクラス)
  |    |--DomainClass.py (dcのクラス)
  |    |--OrganizationClass.py (ouのクラス)
  |
  |--main.py

基準のクラス

基準のクラスは基本的にldap3のラッパーになります。特に複雑なことはしていません。コントラクタでは接続に必要なクラスの生成をして、get_xxx()では_read_ldap()にオブジェクトクラスを渡してLDAPから情報を取得しています。オブジェクトクラスが増えた場合はこの関数が増えます。

BaseClass.py
from ldap3 import Server, Connection, ObjectDef, Reader

class BaseClass(object):

    def __init__(self, host, passwd, user, top_domain, dn=None):
        self.passwd = passwd
        self.user = user
        self.host = host
        self.top_domain = top_domain
        self.filter = None

        if (dn):
            self.dn = dn
        else:
            self.dn = 'dc=' + top_domain

        self.server = Server(self.host)
        self.conn = Connection(self.host, 'cn={},dc={}'.format(user, top_domain),  password=passwd)

    def set_filter(self, filter):
        self.filter = filter

    def get_domain(self):
        return self._read_ldap('domain', self.filter)

    def get_organizational(self):
        return self._read_ldap('organizationalUnit', self.filter)

    def get_common(self):
        return self._read_ldap('inetOrgPerson', self.filter)

    def get_domain_dict(self):
        return self._read_ldap_dict('domain', self.filter)

    def get_organizational_dict(self):
        return self._read_ldap_dict('organizationalUnit', self.filter)

    def get_common_dict(self):
        return self._read_ldap_dict('inetOrgPerson', self.filter)

    def _read_ldap(self, object_type, search_attr = None):
        data_list = []
        self.conn.bind()
        obj_dn = ObjectDef(object_type, self.conn)
        data_reader = Reader(self.conn, obj_dn, self.dn)
        data_reader.search(search_attr)
        for data in data_reader:
            data_list.append(data)

        data_reader.reset()
        self.conn.unbind()
        return data_list

    def _read_ldap_dict(self, object_type, search_attr = None):
        data_list = []
        self.conn.bind()
        obj_dn = ObjectDef(object_type, self.conn)
        data_reader = Reader(self.conn, obj_dn, self.dn)
        data_reader.search(search_attr)
        for data in data_reader:
            data_list.append(data.entry_attributes_as_dict)

        data_reader.reset()
        self.conn.unbind()
        return data_list

拡張クラス

上の基準のクラスを継承したクラスをそれぞれの階層ごとに作成していきます。

Domainクラス

dcの値が必要なためset_item()でdnの文字列にdcの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()で情報を取得したり、save()で追加します。create_organization()はou用のクラスを生成して返却しています。今回の例は、dc=xxx,ou=yyyとしてほしいのでou用の生成関数しかないですがdc=xxx,dc=yyyとしたいときはdc用の生成関数を同じように作成すればできます。

DomainClass.py
from ldap_obj.BaseClass import BaseClass
from ldap_obj.OrganizationClass import OrganizationClass

class DomainClass(BaseClass):

    def set_item(self, item):
        self.dn = 'dc=' + item + ',' + self.dn

    def create_organization(self):
        return OrganizationClass(self.host, self.passwd, self.user, self.top_domain, self.dn)

    def save(self):
        self.conn.bind()
        result = self.conn.add(self.dn, 'domain')
        self.conn.unbind()
        return result

Organizationクラス

Domainクラスから生成したOrganizationクラスにはself.dnにcnまでのパスが入っているのでset_item()でouの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()で情報を取得したり、save()で追加します。create_common()はcn用のクラスを生成して返却しています。今回の例は、dc=xxx,ou=yyy,cn=zzzzとしてほしいのでcn用の生成関数しかないですが他の構成にしたいときはそれ用の生成関数を作成します。

OrganizationClass.py
from ldap_obj.BaseClass import BaseClass
from ldap_obj.CommonClass import CommonClass

class OrganizationClass(BaseClass):

    def set_item(self, item):
        self.dn = 'ou=' + item + ',' + self.dn

    def create_common(self):
        return CommonClass(self.host, self.passwd, self.user, self.top_domain, self.dn)

    def save(self):
        self.conn.bind()
        result = self.conn.add(self.dn, 'organizationalUnit')
        self.conn.unbind()
        return result

Commonクラス

Organizationクラスから生成したCommonクラスにはself.dnにouまでのパスが入っているのでset_item()でcnの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()で情報を取得したり、save()で追加します。今回の例は、このcnで最後なので生成関数はありませんがさらに階層が深い場合はそれ用の生成関数を作成します。

CommonClass.py
from ldap_obj.BaseClass import BaseClass

class CommonClass(BaseClass):

    def set_item(self, item):
        self.dn = 'cn=' + item + ',' + self.dn

    def save(self, attr_dict):
        self.conn.bind()
        result = self.conn.add(self.dn, 'inetOrgPerson', attr_dict)
        self.conn.unbind()
        return result

呼び出し元メイン関数

これらのクラスの使い方は、最初にDomainClassを生成してからdcの値を入れます、次に生成したDomainClassのcreate_organization()を使用してOrganizationクラスを生成してouの値をいれます。commonの生成は生成したOrganizationクラスのcreate_common()を使用してcnの値をいれます。それぞれの生成クラスで取得関数や追加関数を使用してLDAPの操作をします。

main.py
from ldap_obj.DomainClass import DomainClass

domain = DomainClass('localhost', 'LdapPass', 'admin', 'sample-ldap')
domain.set_item('sample-component')
domain_item_list = domain.get_domain()
for domain_item in domain_item_list:
    print(domain_item)

print("=====================")

domain.set_filter('st')
domain_item_list = domain.get_common()
for domain_item in domain_item_list:
    print(domain_item)

print("=====================")

organization = domain.create_organization()
organization.set_item('sample-unit')
organization_list = organization.get_organizational()
for organization_item in organization_list:
    print(organization_item)

print("=====================")

common = organization.create_common()
common.set_item('sample-name')
common_list = common.get_common()
for common_item in common_list:
    print(common_item)

print("***********************************")

new_domain = DomainClass('localhost', 'LdapPass', 'admin', 'sample-ldap')
new_domain.set_item('new-component')
print(new_domain.save())
print("=====================")

new_organization = new_domain.create_organization()
new_organization.set_item('new-organization')
print(new_organization.save())
print("=====================")

new_common = new_organization.create_common()
new_common.set_item('new-common')
print(new_common.save({'st':'new-st', 'sn': 'new-sn'}))
print("=====================")

new_common_list = new_common.get_common()
for common_item in new_common_list:
    print(common_item)

おわりに

極力LDAPの要素を排して操作できるようなクラスを作ってみました。ldap3のライブラリを知っているとクラス経由の操作は面倒に思えますが実際にソースを見てみるとぱっと見で何をしているのかがわかりやすくなりました。今回は取得と追加の機能を持たせましたが、削除と移動と更新は基準のクラスにldap3を使用する関数を追加すれば良いと思います。ここまで分かりやすくすればデータの格納の候補として使用にも躊躇がなくなるのではないかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

scikit-learnとpyclusteringのk-means実装例を比較

はじめに

Pythonで機械学習を実装する際のデファクトスタンダードはscikit-learnだが、クラスタリングにおいては一部かゆいところに手が届いていない部分もあるため、pyclusteringが選択肢として入ってくる。

しかし、pyclusteringはscikit-learnと比較すると若干使いにくい部分があるため、利用方法の備忘のため、最も基本的なk-meansにおける実装例をまとめる。

K-meansの実行例

使用データ

データ定義
from sklearn.datasets import make_blobs

X, _ = make_blobs(n_features=2, centers=5, random_state=1)
散布図
import matplotlib.pyplot as plt

plt.scatter(X[:, 0], X[:, 1])

image.png

scikit-learn

scikit-learnを用いたk-meansの実装は以下の通りである。

なお、scikit-learnでの初期値設定方法はinitオプションで設定でき、デフォルトでha
k-means++になっている。

scikit-learnでのk-means
from sklearn.cluster import KMeans

sk_km = KMeans(n_clusters=3).fit(X)
plt.scatter(X[:, 0], X[:, 1], c=sk_km.labels_)

image.png

pyclustering

pyclusteringを用いたk-meansの実装は以下の通りである。

scikit-learnとは異なり、初期値設定とその後のクラスタ学習を分けて指定する必要がある。
後、こちらで用意されている可視化用の関数を使うと少し情報がリッチになる。

from pyclustering.cluster import kmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer

initial_centers = kmeans_plusplus_initializer(X, 3).initialize()  # k-means++で初期値設定
pc_km = kmeans.kmeans(X, initial_centers)  # kmeansクラスの定義
pc_km.process()  # 学習の実行

_ = kmeans.kmeans_visualizer.show_clusters(X, pc_km.get_clusters(), pc_km.get_centers(), initial_centers=initial_centers)  # 可視化

image.png

pyclusteringで得られたクラスタは、predictメソッドとget_clustersメソッドで参照できる。

predictでは、scikit-learnと同様に、入力データに対してラベルを返す。

get_clustersは、学習に使ったデータに対するインデックスをクラスタ別に返す。こちらはpandasなどで取り扱うのに適していないので、別途加工する必要がある。(predict使った方が簡単)

predictを用いたクラスタ番号の取得(1)
labels = pc_km.predict(X)
get_clustersを用いたクラスタ番号の取得
import numpy as np

clusters = pc_km.get_clusters()
labels = np.zeros((np.concatenate([np.array(x) for x in clusters]).size, ))
for i, label_index in  enumerate(clusters):
    labels[label_index] = i

クラスタ数の決定

scikit-learn

scikit-learnにおけるエルボー法について示す。

シルエット分析も可能だが、pyclusteringと同様に実装が複雑になるため、割愛する。

なお、どちらにおいても自動的にクラスタ数を決定することはできず、分析者が確認した上で決定する必要がある。

エルボー法
sse = list()
for i in range(1, 11):
    km = KMeans(n_clusters=i).fit(X)
    sse.append(km.inertia_)

plt.plot(range(1, 11), sse, 'o-')

image.png

pyclustering

pyclusteringの場合、エルボー法でクラスタ数の決定まで行ってくれる。
なお、クラスタ数は、探索範囲内でクラスタ内誤差平方和が大きく下がったクラスタ数を採用しているようだ。

エルボー法
from pyclustering.cluster.elbow import elbow

kmin, kmax = 1, 10  # 探索する値域
elb = elbow(X, kmin=kmin, kmax=kmax)  # 探索範囲は、kmin~kmax-1までなことに注意
elb.process()
elb.get_amount()  # クラスタ数を参照できる

plt.plot(range(kmin, kmax), elb.get_wce())

image.png

pyclusteringでは、x-meansやg-meansに対応しているため、それを利用することもできる。

x-means
from pyclustering.cluster import xmeans
from pyclustering.cluster.kmeans import kmeans_visualizer

initial_centers = xmeans.kmeans_plusplus_initializer(X, 2).initialize() # k=2以上で探索
xm = xmeans.xmeans(X, initial_centers=initial_centers, )
xm.process()

_ = kmeans_visualizer.show_clusters(X, xm.get_clusters(), xm.get_centers())

image.png

g-means
from pyclustering.cluster import gmeans
from pyclustering.cluster.kmeans import kmeans_visualizer

initial_centers = gmeans.kmeans_plusplus_initializer(X, 2).initialize()
gm = gmeans.gmeans(X, initial_centers=initial_centers, )
gm.process()

_ = kmeans_visualizer.show_clusters(X, gm.get_clusters(), gm.get_centers())

image.png

scikit-learnに比べ、pyclusteringが優位な点

敷居の低さで選ぶのであればscikit-learnではあるが、細かくクラスタリングアルゴリズムに調整したい場合はpyclusteringが優位である。

pyclusteringはそもそも対応しているアルゴリズムが多く、細かく処理内容を定義できる。例えば、距離定義をユークリッド距離からマンハッタン距離やユーザが定義した距離指標に変えることが出来る。

以下にコサイン距離でクラスタリンを行う例を示す。

※コサイン距離でやるだけなら、sphereclusterの方が簡単

コサイン距離でk-means
import numpy as np
from pyclustering.cluster import kmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
from pyclustering.utils.metric import distance_metric, type_metric

X = np.random.normal(size=(100, 2))

def cosine_distance(x1, x2):
    if len(x1.shape) == 1:
        return 1 - np.dot(x1, x2) / (np.linalg.norm(x1) * np.linalg.norm(x2))
    else:
        return 1 - np.sum(np.multiply(x1, x2), axis=1) / (np.linalg.norm(x1, axis=1) * np.linalg.norm(x2, axis=1))

initial_centers = kmeans_plusplus_initializer(X, 8).initialize()
pc_km = kmeans.kmeans(X, initial_centers, metric=distance_metric(type_metric.USER_DEFINED, func=cosine_distance))
pc_km.process()

plt.scatter(X[:, 0], X[:, 1], c=pc_km.predict(X))

image.png

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]マルコフ連鎖則で友人っぽいツイートを生成するTwitterのBotを作った

はじめに

大学生の暇な春休み、クローリング&スクレイピングの勉強をとだらだらやってきましたが、ここらでいっちょなんか作るかということでTwitterのBotを作ってみることにしました。
友人Bの協力を得て、友人Bのツイートを分析して友人BっぽいツイートをするBotを作成しました。

開発環境

Windows10上のvirtualboxでUbuntuの仮想環境を使用
Ubuntu 18.04.4 LTS

使ったものたち

  • マルコフ連鎖則
  • MeCab
  • tweepy
  • MySQL
  • VPS (Virtual Private Server)

それぞれちょっとずつ説明を書きますね

マルコフ連鎖則

「さて、どうやって生成する文章にその人らしさを出そうかな、っていうかそもそも文章の自動生成ってどうやるの?」っということで調べたところ、マルコフ連鎖則というものを発見。

マルコフ連鎖則についてはこちらの記事にとってもわかりやすく書いてありますのでこちらをご参照ください。Pythonのコードも参考にさせていただきました。マルコフ連鎖則すげーっ!てなります。

[Python]N階マルコフ連鎖で文章生成](https://qiita.com/k-jimon/items/f02fae75e853a9c02127)

MeCab

取得したツイートを分析する手段としてMeCabを使います。
「取得したツイートに対してMeCabを用いて形態素解析を行い単語のリストを作成しマルコフ連鎖則に従って単語を並べる」というのが基本的な文章生成の流れになります。

MeCabについての詳しい説明はこちらをご覧ください。

【技術解説】形態素解析とは?MeCabインストール手順からPythonでの実行例まで

tweepy

ツイッターを自動操作するにはTwitterが公式に提供しているTwitterAPIを使います。
TwitterAPIを使うとアカウントをプログラムから自動操作することができるようになります。

PythonからTwitterAPIを操作するときはPythonのライブラリであるtweepyが便利とのことで、この本を参考にしつつネットで調べながら使い方を勉強しました。

Pythonクローリング&スクレイピング ―データ収集・解析のための実践開発ガイド―

MySQL

どうせならデータベースの勉強もしてやろう思い立ち、TwitterAPIで取得したデータの保存をMySQLを使って保存することにしました。MySQLを使うことであとからアプリケーションを拡張しやすいなどのメリットが生まれます。試作時にはテキストファイルにデータを入れてみていましたが少し動作が遅かった印象です。

MySQLについて詳しく知りたい方は下記のリンク先を参照してみてください。イメージがつかめるかと思います。

https://www.atoone.co.jp/column/10114/

VPS

Botを運営するためのサーバーが必要だということで、VPSを借りました。常時起動しているパソコンを1台使えるみたいなイメージ。

必要な環境の構築にはLinuxの知識が必要で、サーバー初心者の私にはそこそこハードな作業になりましたが、MySQLやMeCabなど必要なソフトウェアをインストールしました。

CohoHaVPSで手軽に借りることができました。そのうちWebサーバーとしても使っていこうと思います。月880円でサーバーで遊べるなら安いもんです...

ConoHaVPS

Botアプリの構成

やっと本題ですが、図にするとこんな感じ。
Bot.png

各スクリプトの役割は以下のようにしている
@twitter_collector.py(データ収集用スクリプト)
1. TwitterAPIをPythonライブラリであるtweepyで利用して、ツイートデータを取得
2. 取得したデータから必要な要素(本文、投稿日時など)を抜きだし、データベースへ保存
3. ストリーミングAPIを利用して新規ツイートがあれば随時データベースへ追加する

@bot.py(Bot本体)
1. データベースからツイート本文を読み出し、処理しやすい形に整形
2. テキストデータをMeCabで形態素解析し、友人Bのツイートの内容の傾向をモデル化
3. 作成したモデルにしたがって友人Bらしいツイートを生成
4. TwitterAPI(Tweepy)で生成したツイートを投稿

データを集める部分とツイートを行うBot本体をわけることで管理がしやすくなっていると思います。

Botの自動化

サーバーにCentOS8の環境を用意し、そこにMeCab、MySQL、Pythonなど必要な各ツールをインストールし環境構築を行いました

Pythonスクリプトを実行するためのシェルスクリプトをsystemdによって自動実行することでBotを動かしています。systemdについてはこちらの記事にわかりやすく解説されています。

systemdによる自動起動

もっと似せるために

ただマルコフ連鎖則にしたがって文章をつくらせているだけでも結構面白いツイートを生み出してくれたので楽しかったのですが、もっと似せたいなということで現時点で下記2点を工夫しています

  • 本人のツイート間隔のパターンを時間帯ごとの分布を調べ、それに従ってツイートさせる
  • 本人のツイートの文字数の分布を調べそれに従ってツイートする文字数(の範囲)を確率的に決定する

どちらも、友人Bが実際にしたツイートからデータを取得、リスト化し、その中からランダムに値を選ぶことで実現しています。

感想

さすがに長い文章になるほど文の意味はとおらなくなってしまうが、短いツイートなら本当に本人がいったようなツイートをすることもあるし、本人が過去につぶやいた内容からランダムにワードが選ばれているので、組み合わせによっては思わず笑ってしまうツイートも生成することができました。

アイコンなど全く同じにしてしまえばBotと本人の区別がつかないこともあるのではないかと思っており、チューリングテストしてみたいというお気持ち。

友人B君、協力してくれてありがとう。

おわりに

機械学習でもできるのかなとも思ったんだけど、それは次回以降の課題ということにしよう...もう春休みが終わるし...。オンライン授業だけど。

最後まで読んでいただきありがとうございます。
なにかアドバイス等ありましたらコメントいただけると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SQLAlchemyでのsessionの扱い方

はじめに

SQLAlchemyでは,sessionを生成したあと,必要に応じてcommit()rollback(),close()を行う必要がある.
ここでは,DB操作を行うクラスを作成し,sessionの受け渡し方についてまとめる.
以下では,下のようなモデルクラスが存在しているとする.

models.py
class User(Base):
    __tablename__="user" #テーブル名を指定
    id=Column(Integer, primary_key=True)
    first_name=Column(String(255))
    last_name=Column(String(255))

sessionの悪い扱い方

以下のソースコードのようにしてしまうと良くない.

wrong_way.py
from models import User

class FirstName(object):
    def update_first_name(self, user_id, first_name):
        session=Session()
        try:
            user=session.query(User).filter(User.id==user_id).one() #id=user_idであるobjを取り出す
            user.first_name=first_name #first_nameを変更
            session.commit()
        except:
            session.rollback()
            raise


class LastName(object):
    def update_last_name(self, user_id, last_name):
        session=Session()
        try:
            user=session.query(User).filter(User.id==user_id).one() #id=user_idであるobjを取り出す
            user.first_name=first_name #last_nameを変更
            session.commit()
        except:
            session.rollback()
            raise


def run_my_program():
    FirstName().update_first_name(1, "update_first_name")
    LastName().update_last_name(1, "update_last_name")

なぜなら,run_my_program中のupdate_first_nameupdate_last_nameで,同一sessionが使われていないため,
「first_nameはupdateされたが,last_nameはupdateされなかった」
のようなことが起きてしまうからである.

sessionの良い扱い方1

そこで,以下のソースコードのようにすれば解決する.

right_way_1.py
from models import User

class FirstName(object):
    def update_first_name(self, user_id, first_name, session):
        user=session.query(User).filter(User.id==user_id).one()
        user.first_name=first_name


class LastName(object):
    def update_last_name(self, user_id, last_name, session):
        user=session.query(User).filter(User.id==user_id).one()
        user.first_name=first_name


def run_my_program():
    session = Session()
    try:
        FirstName().update_first_name(session)
        LastName().update_last_name(session)
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

こうすることで,update_first_nameupdate_last_nameで,同一sessionが使われ,
「片方だけが正常に実行された」
のような状況が起こらない.

sessionの良い扱い方2

次に,context managerを用いたsessionの扱い方を紹介する.
なお,context managerについては,以下の記事が大変わかりやすく参考になるのでここでは説明を省略する.
Pythonのコンテキストマネージャってなんなの?と思って調べた話

right_way_2.py
from models import User

from contextlib import contextmanager

@contextmanager
def session_scope():
    session = Session()  # def __enter__
    try:
        yield session  # with asでsessionを渡す
        session.commit()  # 何も起こらなければcommit()
    except:
        session.rollback()  # errorが起こればrollback()
        raise
    finally:
        session.close()  # どちらにせよ最終的にはclose()


class FirstName(object):
    def update_first_name(self, user_id, first_name, session):
        user=session.query(User).filter(User.id==user_id).one()
        user.first_name=first_name


class LastName(object):
    def update_last_name(self, user_id, last_name, session):
        user=session.query(User).filter(User.id==user_id).one()
        user.first_name=first_name


def run_my_program():
    with session_scope() as session:
        FirstName().update_first_name(session)
        LastName().update_last_name(session)

参考文献

この記事は以下の情報を参考にして執筆しました.
公式ドキュメント(Session Basics)
Pythonのコンテキストマネージャってなんなの?と思って調べた話

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像を二値化処理させる。さらに、二領域間の最短距離を算出できるようにした(Ver1.1)。

はじめに

 今回は、前回記事二領域間の最短距離を求めるプログラムについて、修正を行った内容です。
 前回は、画像サイズをそのまま処理していたことと、必ずしも二領域間の最短距離を求めることができていなかった課題がありました。

画像を二値化処理させる。さらに、二領域間の最短距離を算出できるようにした。
https://qiita.com/Fumio-eisan/items/05a6506da8cc88d89e49

要点は下記です。

  • 画像を小さくして処理しやすくする
  • 任意の領域を二値化させる
  • 領域間の距離を求め、そこから最短距離を求める

画像を小さくして処理しやすくする

 今回はこちらの焼肉画像を用いて二つの肉の最短距離を求めたいと思います。

image.png

 前回の記事ではそのままの画像サイズで処理を行っていました。この場合、画像サイズが大きくなると領域を囲む処理が重くなってしまいます。従って、画像サイズを小さくして処理しやすくします。

 今回は、幅を300ピクセルに固定して同じアスペクト比(幅:高さ比)で縮小させます。

img.ipynb
def scale_to_width(img, width):
    scale = width / img.shape[1]
    return cv2.resize(img, dsize=None, fx=scale, fy=scale)

img = scale_to_width(img, 300)#300ピクセルに固定

画像を二値化させる

次に、二値化により肉部分だけを色を抽出したいと思います。cv2.inRangeメソッドによって特定の色を白、特定の色を黒とします。

img.ipynb
import numpy as np
bgrLower = np.array([0, 0, 130])    # 抽出する色の下限(BGR)
bgrUpper = np.array([120,120, 255])    # 抽出する色の上限(BGR)
img_mask = cv2.inRange(img, bgrLower, bgrUpper) 
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(1,1,1)
ax.imshow(img_mask)
ax.axis("off")

niku_wb.jpg

 見事肉だけ白く表示させることができました。ただ、これは元画像の色合いによって手動で抽出する色を調整する必要があるため、手間になります。この表示させたい色だけを抽出して二値化させる手法は、別途まとめたいと思います。

領域間の距離を求め、そこから最短距離を求める

 次に、領域間の距離を求めます。前回は、各領域のx座標最大値/最小値を取る座標を取得させてから距離を求めました。しかし、その求め方は必ずしも最短距離をとれるわけではありません(下右図の例)。

image.png

 対策としては、各領域を囲む点通しの組み合わせによる距離を全て求めて、それが最小値を取る座標を取得することです。
 この時、画像サイズを調整しないとかなり計算数が大きくなるため、前に示した画像サイズを縮小化させることが効いてきます。

img.ipynb
import numpy as np
dd=np.array([300])#空白の配列だとif min(dd)> dでエラーが出るため
for i in range(len(contours[0])):
    x3 = contours[0][i][0][0]
    y3 = contours[0][i][0][1]
    for ii in range (len(contours[1])):
        x4 = contours[1][ii][0][0]
        y4 = contours[1][ii][0][1]
        d = get_distance(x3, y3, x4, y4)

        if min(dd) > d:
            x3min  =x3
            y3min  =y3
            x4min  =x4
            y4min  =y4
        dd = np.append(dd,d)

26.248809496813376

niku_wb1.jpg

無事に最短距離を求めることができました。

おわりに

 画像ファイルを小さくするだけで、処理がかなり早くなりました。一方、色の抽出は手動で行いますので、ここは改善の余地があります。二値化したいところだけ自在に分けられるような改造を検討したいと思います。

 簡単ですが改善しましたので、下記に格納しています(日付が新しいものがバージョンアップしたものです)。

https://github.com/Fumio-eisan/img_distance20200315

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 088 過去問復習

所要時間

スクリーンショット 2020-04-01 16.39.23.png

感想

以前解いた過去問になりますが、D問題でまた同じミスをしてしまいました。
D問題にてミスの内容については言及しておきたいと思います。

A問題

500円通貨は無限に持っているので、nを500で割った余りがaより小さいかどうかを考えます。

answerA1.py
n=int(input())
a=int(input())
print("Yes" if n%500<=a else "No")

B問題

大きい順に並べて0-indexedで偶数のインデックスのものをAliceがとり、奇数のインデックスのものをBobがとるのが、二人の自分の得点を最大化する戦略となります。

answerB.py
n=int(input())
a=sorted(list(map(int,input().split())),reverse=True)
ans=0
for i in range(n):
    if i%2==0:
        ans+=a[i]
    else:
        ans-=a[i]
print(ans)

C問題

i行目について$a_i+b_1,a_i+b_2,a_i+b_3$であり、それぞれの行について(2列目の値-1列目の値)と(3列目の値-2列目の値)はそれぞれ等しいことが高橋君の情報が正しいために必要な条件であり、この条件を満たしている時は適当にaの値を定めれば十分であることも実験をすると明らかになります。(説明が適当ですが、(2列目の値-1列目の値)と(3列目の値-2列目の値)をそれぞれ実際に計算すると明らかです。)

answerC.py
c=[list(map(int,input().split())) for i in range(3)]
x=c[0][2]-c[0][1]
y=c[0][1]-c[0][0]
for i in range(1,3):
    if c[i][1]-c[i][0]!=y:
        print("No")
        break
    if c[i][2]-c[i][1]!=x:
        print("No")
        break
else:
    print("Yes")

D問題

この問題は前に解いて印象的だったので解き方は覚えていましたが、最後ミスを多発してしまいました。
すぬけ君はできるだけスコアを伸ばしたいので、できるだけ多くのマス目を黒に変える必要があります。しかし、マス目を多く変えすぎるとけぬす君がゴールにたどり着くことができません。つまり、けぬす君が辿ってゴールにいく際に通るマス目以外の全てを塗りつぶせば良く、最大化するには最短経路で通るところ以外の全てを塗りつぶせば良いです。
したがって、この問題は単純な最短経路問題とみなすことができます。僕はdfsによりコードを書きました。しかし、Pythonの場合は再帰の深さの上限がそれぞれの環境によって設定されているので、sys.setrecursionlimit(x)によって再帰の制限回数をxに設定しました。しかし、このxが大きすぎるとTLEしてしまうので、できるだけ小さくする必要があります。この問題ではHが50でWが50なので2500以上であれば良いかと思ったのですがREになってしまったので再帰回数が2500より多くなるパターンがあるようです(パッと思いつきません…)。
再帰回数をギリギリに設定しようとしましたが、Writerの意向なのかうまくいきませんでした。ここで最後にPyPyで高速化することでうまくいきました。また、最短距離を求める際には幅優先探索の方が有効でWriter解は幅優先探索になります(深さ優先探索だと余計に深く調べ過ぎてしまうので)。

answerD.py
import sys
h,w=map(int,input().split())
sys.setrecursionlimit(h*w+10)
s=[list(input()) for i in range(h)]
inf=1000000000000000
d=[[inf]*w for i in range(h)]


def dfs(i,j):
    global h,w,s
    nex=[(0,-1),(0,1),(-1,0),(1,0)]
    for k in range(4):
        l,m=i+nex[k][0],j+nex[k][1]
        if 0<=i+nex[k][0]<h and 0<=j+nex[k][1]<w:
            if s[l][m]=="." and d[l][m]>d[i][j]+1:
                d[l][m]=d[i][j]+1
                dfs(l,m)
d[0][0]=0
dfs(0,0)
ans=0
for i in range(h):
    for j in range(w):
        if s[i][j]==".":
            ans+=1
if d[h-1][w-1]==inf:
    print(-1)
else:
    print(ans-d[h-1][w-1]-1)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Keras] CIFAR10データセットで正確度75%を実現

1.はじめに

今日は、CIFAR10データセットで、正確度75%を達成する内容をご紹介します。
最初は、90%以上を目標としましたので、これからもファインチューニングを続ける予定です。

2.やりたいこと

下記の図のように、普段のAIプロジェクトで行われる内容を一通り実行します。

image.png

今回のタスクの特徴です。

1. データセットはCIFAR10を利用する。データセットを(Train, Validation, Test) = (0.8, 0.1, 0.1)に分けて利用する。
2. Kerasのgeneratorを利用し、Data Augmentationを利用する。
3. 学習済みモデルVGG16を利用した、転移学習を利用する。
4. ファインチューニングされた学習モデルをh5形式で保存する。
5. 推論部では、モデルを呼び出し、テストを行う。
6. テスト結果を混合行列(Confusion Matrix)でプロットする。
7. 学習と推論の部分を別のPythonファイルにする。

3.転移学習内容

VGG16は、五つのConv Blockと最後のFull Connected Layerで構成されています。
今回は、一つ目から四つ目のConv Blockはそのままにし、五つ目と最後のFull Connected Layerを学習することにします。
image.png

4.実行結果

4.1.学習の結果 (train.py)

下記の図に学習時の正確度Accuracyと損失関数Lossの推移を示します。200Epochsまでの学習で、75%の正確度が得られました。
ただし、TrainデータとValidaionデータの結果が離れていくので、Overfittingが発生しているようにも見えます。
Figure_1.png
Figure_2.png

4.2.推論の結果(Inference.py)

推論の結果です。
テストデータによる正確度は平均で74.5%です。

test acc: 0.7450000047683716
Confusion Matrixの処理には、ScikitLearnのConfusion Matrixモジュールを利用しました。

5000個のテストデータの推論結果です。(5000個=500個*10クラス)

image.png

上記のテキスト形式の混同行列をMatplotlibでプロットします。

Figure_3.png
そして、各クラスごとのPrecision, Recall、F1-scoreの結果も教えてくれます。
(Precision, Recallの説明は、こちらを参考にしてください。)
image.png

5.コード

学習train.py

プログラムの構造
image.png

train.py
##Import

import os
import keras
from keras.preprocessing.image import ImageDataGenerator
from keras import models, layers
from keras.applications import VGG16
from keras import optimizers
import numpy as np
import matplotlib.pyplot as plt
from keras.callbacks import EarlyStopping




#1.plot loss and accuracy
def plot_acc(hist):
    acc = hist.history['acc']
    val_acc = hist.history['val_acc']
    epochs = range(len(acc))
    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b', label='Validation acc')
    plt.title('Training and Validation accuracy')
    plt.legend()
    pass

def plot_loss(hist):
    loss = hist.history['loss']
    val_loss = hist.history['val_loss']
    epochs = range(len(loss))
    plt.plot(epochs, loss, 'ro', label='Training loss')
    plt.plot(epochs, val_loss, 'r', label='Validation loss')
    plt.title('Training and Validation loss')
    plt.legend()


def main():

    #Initial Setting
    width_x, width_y = 32, 32
    batch_size = 32
    num_of_train_samples = 40000
    num_of_val_samples = 5000
    num_of_test_samples = 5000 #CIFAR100
    epochs = 1000


    # label_class
    classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
    nb_classes = len(classes)

    ## 01. Data Input
    # folder information
    base_dir = 'E:\\Dataset\CIFAR10\cifar10_keras_training'
    train_data_dir = os.path.join(base_dir, 'train')
    val_data_dir = os.path.join(base_dir, 'val')
    test_data_dir = os.path.join(base_dir, 'test')

    print(train_data_dir)
    print(val_data_dir)
    print(test_data_dir)

    # Input Data Generation (with Data Augmentation)
    train_datagen = ImageDataGenerator(rescale=1. / 255,
                                       rotation_range=20,
                                       width_shift_range=0.1,
                                       height_shift_range=0.1,
                                       shear_range=0.1,
                                       zoom_range=0.1,
                                       horizontal_flip=True,
                                       fill_mode='nearest')
    val_datagen = ImageDataGenerator(rescale=1. / 255)
    test_datagen = ImageDataGenerator(rescale=1. / 255)

    train_generator = train_datagen.flow_from_directory(
        train_data_dir,
        target_size=(width_x, width_y),
        color_mode='rgb',
        classes=classes,
        class_mode='categorical',
        batch_size=batch_size,
        shuffle=False)

    val_generator = val_datagen.flow_from_directory(
        val_data_dir,
        target_size=(width_x, width_y),
        color_mode='rgb',
        classes=classes,
        class_mode='categorical',
        batch_size=batch_size,
        shuffle=False)

    test_generator = test_datagen.flow_from_directory(
        test_data_dir,
        target_size=(width_x, width_y),
        color_mode='rgb',
        classes=classes,
        class_mode='categorical',
        batch_size=batch_size,
        shuffle=False)

    ##2. CNN Model


    conv_base = VGG16(weights='imagenet',
                      include_top=False,
                      input_shape=(width_x, width_y, 3))
    # conv5 block fine tuning only
    conv_base.trainable = True
    set_trainable = False
    for layer in conv_base.layers:
        if layer.name == 'block5_conv1':
            set_trainable = True
        if set_trainable:
            layer.trainable = True
        else:
            layer.trainable = False

    model = models.Sequential()
    model.add(conv_base)
    model.add(layers.Flatten())
    model.add(layers.Dropout(0.5))
    model.add(layers.Dense(512, activation='relu'))
    model.add(layers.Dense(nb_classes, activation='softmax'))
    model.summary()


    model.compile(loss='categorical_crossentropy',
                  optimizer=optimizers.RMSprop(lr=1e-5),
                  metrics=['acc'])
    model.summary()

    ##3. Training
    # early_stopping = EarlyStopping(patience=20)
    history = model.fit_generator(
        train_generator,
        epochs=epochs,
        steps_per_epoch=num_of_train_samples//batch_size,
        validation_data=val_generator,
        validation_steps= num_of_val_samples//batch_size,
        verbose=2)
    # callbacks=[early_stopping]

    ##5. Model Save
    model.save('./Model/CIFAR10_trained03_seq.h5')

    ##4. Accuracy and Loss Plot
    plot_acc(history)
    plt.figure()
    plot_loss(history)
    plt.show()




## Run code

if __name__=='__main__':
    main()


推論Inference.py

プログラムの構造
image.png

Inferenece.py
## Import
import os
import keras
from keras.models import load_model
from keras.preprocessing.image import ImageDataGenerator

from sklearn.metrics import confusion_matrix, accuracy_score
from sklearn.metrics import classification_report
import numpy as np
import matplotlib.pyplot as plt

##Confusion matrix function

def plot_confusion_matrix(cm, classes, cmap):
    ''' confusion_matrixをheatmap表示する関数
            Keyword arguments:
            cm -- confusion_matrix
            title -- 図の表題
            cmap -- 使用するカラーマップ
            Normalize = True/ False
    '''
    plt.imshow(cm, cmap=cmap)
    plt.colorbar()
    plt.ylabel('True')
    plt.xlabel('Predicted')
    plt.title('Confusion Matrix')
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)
    plt.tight_layout()


##Main Function

def main():

    #01. Initial Setting
    width_x, width_y = 32, 32
    batch_size = 32
    # label_class
    classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']

    #02. load_test data
    base_dir = 'E:\\Dataset\CIFAR10\cifar10_keras_training'
    test_data_dir = os.path.join(base_dir, 'test')

    #02-01. Input Data Generation (with Data Augmentation)
    test_datagen = ImageDataGenerator(rescale=1. / 255)

    test_generator = test_datagen.flow_from_directory(
        test_data_dir,
        target_size=(width_x, width_y),
        color_mode='rgb',
        classes=classes,
        class_mode='categorical',
        batch_size=batch_size,
        shuffle=False) #In case of test generator, Shuffle sholud be turned off.

    #03. Load Trained model
    model_dir = './Model/'
    model_name = 'CIFAR10_trained03_seq.h5'
    model_dir_name = os.path.join(model_dir, model_name)
    print(model_dir_name)
    model=load_model(model_dir_name)

    #04. Evaluating Test Data
    test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
    print('test acc:', test_acc)

    #05. Prediction and Confusion Matrix
    Y_pred = model.predict_generator(test_generator)
    y_pred = np.argmax(Y_pred, axis=-1)
    y_true = test_generator.classes

    print('Confusion Matrix')
    print(confusion_matrix(y_true, y_pred))

    print('Classification Report')
    print(classification_report(y_true, y_pred, target_names=classes))

    cm = confusion_matrix(y_true, y_pred)
    cmap = plt.cm.Blues
    plot_confusion_matrix(cm, classes=classes, cmap=cmap)
    plt.show()


## Run code

if __name__=='__main__':
    main()



6.参考資料

1.【Python】多重分類問題のTraining, Validation, Testフォルダーを簡単に作る方法 https://qiita.com/kotai2003/items/293beaf9d79a05cb74b0
2. 【機械学習】分類器の評価(1) https://qiita.com/kotai2003/items/8d5174cbc121e86a797e
3. Confusion Matrix,https://gist.github.com/RyanAkilos/3808c17f79e77c4117de35aa68447045
4. Keras で CNN 実装およびファインチューニングをやってみる at CIFAR-10 http://blog.livedoor.jp/itukano/archives/52139557.html
5. https://github.com/geifmany/cifar-vgg/blob/master/cifar10vgg.py

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 092 過去問復習

所要時間

スクリーンショット 2020-04-01 12.30.16.png

感想

同時にあげるABC088とまとめて解きました。実質70分ですが、二回分のコンテストを合計で120分で解いていてギリギリで解き切れたのでうれしかったです。
D問題はかなりパズルよりで以前解いたAGCの問題に似ていたので思いつくことができました。

A問題

乗り放題切符が通常切符より安いことがあり得ないと思い少し時間をかけてしまいました。

answerA1.py
a,b,c,d=[int(input()) for i in range(4)]
print(min(a,b)+min(c,d))

B問題

一人ずつ数えてくのではなく、それぞれの日に参加者がチョコレートを食べているのかを判定していきます。
$1,A_i+1,2A_i+1,…$と食べるので、$A_i$で割った時の余りが1の日かどうかを判定すれば良いのですが、A_iが1の時だけ余りが0になるので、注意が必要です。

answerB.py
n=int(input())
d,x=map(int,input().split())
a=[int(input()) for i in range(n)]
ans=0
for i in range(1,d+1):
    for j in range(n):
        if a[j]==1:
            ans+=(i%a[j]==0)
        else:
            ans+=(i%a[j]==1)
print(ans+x)

C問題

コンテスト中には気付きませんでしたが、入力の$A_1,A_2,…,A_n$に$A_0=0$と$A_{n+1}=0$を加えると考えやすいです。ここで、それぞれ一つずつ訪れる観光スポットを取りやめることから、元々の旅行での金額の総和から観光スポットを一つ取りやめた時の差額を考えれば良いのではと考えました。
この時、まず全ての観光スポットを順に訪れた時の総和をSとすれば以下の式が立てられます。
スクリーンショット 2020-04-01 13.22.35.png
さらに、ここから仮にi番目の観光スポットを訪れないとすると、その時の金額の総和は差額を考えて、$S-|A_i-A_{i-1}|-|A_{i+1}-A_i|+|A_{i+1}-A_{i-1}|$になります。
これをi=1~nで考えれば答えを求めることができます。

answerC.py
n=int(input())
a=list(map(int,input().split()))
base=0
for i in range(n+1):
    if i==0:
        base+=abs(a[0])
    elif i==n:
        base+=abs(a[n-1])
    else:
        base+=abs(a[i]-a[i-1])
ans=[]
for i in range(n):
    if i==0:
        ans.append(base+abs(a[1])-abs(a[0])-abs(a[1]-a[0]))
    elif i==n-1:
        ans.append(base+abs(a[n-2])-abs(a[n-1])-abs(a[n-2]-a[n-1]))
    else:
        ans.append(base+abs(a[i+1]-a[i-1])-abs(a[i+1]-a[i])-abs(a[i]-a[i-1]))
for i in range(n):
    print(ans[i])
answerC_better.py
n=int(input())
a=list(map(int,input().split()))
b=[0]
b.extend(a)
b.append(0)
a=b
base=0
for i in range(n+1):
    base+=abs(a[i+1]-a[i])
ans=[]
for i in range(1,n+1):
    ans.append(base+abs(a[i+1]-a[i-1])-abs(a[i+1]-a[i])-abs(a[i]-a[i-1]))
for i in range(n):
    print(ans[i])

D問題

一つ出力されるので自分にとって都合の良いものを探すしかありません。こういう問題は良くAGCで出ますが、何をしたいのかはっきりさせないと迷宮入りしそうです。
まず、この問題ではA,Bの二変数があったので、A,Bのどちらかを0にしようとしました。しかし、片方が0の場合、残りは全て連結になってしまうので、A,Bの間に差がない方が良いのではないかと考えました。そこで、A,Bの間に差をなくすために自由にその差を調節出来るような小構造に分けて考えることにしました。さらに、この際にできるだけ広いグリッドで一つの連結成分はできるだけ小さくした方が自分に都合の良いグリッドを作りやすいのではと考え試行錯誤しました。
まず初めに思いついたのが以下のような最小構造を組み合わせたものになります。

IMG_0155.JPG

上記の構造を逆パターンが隣り合うように組み合わせてA,Bを満たす構造を作ろうと思い試してみましたが、うまくいきませんでした。しかし、この構造で実験しているうちに同じパターンを組み合わせた場合は以下のようになりました。このパターンの場合、白の連結成分は0~50over、黒の連結成分は1なので、調節が非常にしやすいのではないかと考えました。(逆のパターンは黒の連結成分が0~50over、白の連結成分が1)

IMG_0156.JPG

上記のパターンには小構造が重なる部分に黒の少し無駄があるので無駄を減らして改善したものが下の構造になります。

IMG_0157.JPG

ここで上の構造の縦を100で横を50とし、逆パターンをつなげてみます。すると、上記の構造については、白の連結成分が0~25*50で黒の連結成分が1、逆パターンの構造では白の連結成分が1で黒の連結成分が0~25*50になります。したがって、つなげると、白の連結成分が1~25*50で黒の連結成分が1~25*50になります。したがって、1<=A<=500で1<=B<=500の制限であればこの方法でグリッドを作り出すことができる。

answerD.py
a,b=map(int,input().split())
print("100 100")
a-=1
b-=1
for i in range(100):
    if i%2==0:
        print("."*50+"#"*50)
    else:
        for j in range(50):
            if j%2==0 or j==49:
                print(".",end="")
            else:
                if b>0:
                    print("#",end="")
                    b-=1
                else:
                    print(".",end="")
        for j in range(50):
            if j%2==0 or j==49:
                print("#",end="")
            else:
                if a>0:
                    print(".",end="")
                    a-=1
                else:
                    print("#",end="")
        print("")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

70日目 GCI2019Winter無事修了しました!

12月から始まった東大のデータサイエンティスト講座、無事修了しました!
GCI Online Course 東大のデータサイエンティスト/未来のCMO育成講座のオンラインコースです
3ヶ月でデータサイエンティストを要請するという講座でなんと無料です。

●コースの概要
* 大量のデータを自由自在に解析・分析し、隠れた関係性を発見する。そんなスキルを身につけた「データサイエンティスト」に対する需要は、工学分野だけならず、医療・経済・経営・ライフサイエンスなど非常に多くの分野で高まる一方です。
* 本コースでは、あらゆる分野で武器になるデータの解析・分析スキルのコアとなる機械学習およびビッグデータを扱う技術、分析結果を効果的に可視化する技術の基盤を網羅的に身につけ、一人前のデータサイエンティストとして活躍する入り口に立つことを目指します。
* 受講費用は一切発生しません(通信費等は自己負担でお願いします)。

とにかく課題の量が多い。この3ヶ月は毎日が機械学習漬けでした。
前年度のテキストが販売されていたので、目を通してみましたが、あまりの難しさにこりゃ無理だと投げそうになりました。

東京大学のデータサイエンティスト育成講座 塚本 邦尊
https://www.amazon.co.jp/dp/B07PD237GQ/ref=cm_sw_r_tw_dp_U_x_j1bHEb66RA461

ですが、テキストがよく練られていたこと、Slackで随時質問できる事と、チームで参加した事と、サポート体制にずいぶん助けられ、なんとか修了までこぎつけました。

うえっとなったテキストが読めるようになる。これは自信になります。4月からまた受講者を募集しておりますので、これから機械学習をはじめようと思うみなさまにぜひおすすめしたいと思います。

受講テスト(12/7〜12)

受講前にスキルを確認のためのテストがありました。WhirlwindTourOfPythonのような基礎的なレベルですとのこと。リンク先がいきなり英語の解説で面食らいました。Pythonをかじっていたので何とかクリア。
問題は、行列の問題をPythonで解いて出すというもの。数学は中学校で挫折したので問題の意味がさっぱりわからず。数学が得意な人に教えていただきどうにかクリアしました。

第1回(12/18) データサイエンスとは、Python基礎、ライブラリ

IT化によりデータが大量にあるのにほとんど分析されていない。
これを分析すると、ビジネスにとんでもなく生かせる事ができる、、、という講義でした。翌日に動画で配信があり面白かったです。それからPython基礎です。

第2回(12/25) Pythonによる科学計算とデータ加工処理の基礎(Numpy,Pandas)

Pythonの表計算を学びました。全部コマンドのExcelみたいなものです。集計、統計、etcこの後めちゃくちゃ使いました。

コンペその1タイタニック号の生死予測。


KaggleのTitanic: Machine Learning from Disasterと同じです。
最初はまったく分からず、映画を見て予測を思いついても、どうやって実装したらいいか暗中模索でした。Kaggleやチーム、Slackで意見交換しながら粘りました。アイデアがスコアに結びつくと面白くなってきます。結果を次の予測に再利用するなんちゃってブースティングでランクインしました。コンペ後に上位のコードが公開されるのですが、ほんと頭のいい人がいるんだなーと感心するばかりです。

第3回(1/8) データサイエンスにおけるデータの可視化(Matplotlib)

グラフ、相関行列とヒートマップの作り方を学びました。なかなか思うように表現できないので、最初は美しいグラフを集めて、そのコードをコピペしながら慣れると良いようです。

第4回(1/15) 確率統計の基礎

合計、平均などの統計、散布図、ヒストグラムなどの図の書き方。確率・統計はさっぱり分からず。初心者問題を解くのがやっとでした。

コンペその2 ワインのクオリティ


ポルトガルのワインプロジェクトより、4898本のワインの成分とソムリエによる評価を読み込んで、成分からおいしさを予測するというものです。ワインは大好きなのです。何かというとお店のワインのラベルを読んではあれこれ考えるという楽しいコンペでした。

第5回(1/29) 機械学習の基礎(教師あり学習)

教師あり学習、教師なし学習、強化学習について。タイタニックやワインの予想で使っている手法をもう一度丁寧に解説。重回帰、ロジスティック回帰、ラッソ回帰、リッジ回帰、K-NN、 サポートベクターマシンあたりをチュートリアルに沿ってぽちぽちしていけば、なんとなく使えるようになった・・・気がします。

第6回(2/5) 機械学習の基礎(教師なし学習)

教師なし学習、目的変数がない学習モデルの構築を学びました。ざっくり分類、クラスタリング、主成分分析・・・。正直まだよく理解できていません。事前情報なしにデータをふるい分けできるのは便利なので覚えておく・・・というレベル。要復習ですね。

コンペその3 PUBGの勝者予想

KaggleのPUBG Finish Placement Prediction (Kernels Only)と同じです。
PUBGは100人の参加者が島に送られバトルロワイヤルするというゲームです。やってみましたが討ち死にしてばかり。まずはよく遊ぶ人にゲームをしている所を見せていただきました。アイテム、乗り物、武器の活用、刻々と狭まる安全圏にどう潜り込むかが勝負の決め手のようです。
しかしゲームの強さをどうコードにしたらよいかがさっぱり分かりません。
Kaggleにはどのように予測したかがNotebookとして沢山公開されています。目から鱗のアプローチがたくさんありました。
機械学習がものすごいスピードで発展するわけです。強そうなコードを組み合わせて提出するとそこそこいいスコアがとれました。

第7回(2/12) モデルの検証方法とチューニング方法

モデルのパラメータを調整、さまざまなモデルの特徴、複数のモデルを組み合わせての予測を学びました。
ちゃんとやろうとすると前例主義に陥ったり考えすぎで外したりするので、多少アソビを作っておくというイメージです。
それから、バギングというデータをいくつかに分割して学習させあう方法を実装すると、予測精度がぐっと良くなりました。
まだよく理解できていないので、深めたいところです。

第8回(3/4) データサイエンティスト中級者への道

第一線で活躍している方をお呼びして現場の話でした。マーケティング、物を売るには、どう思ってもらいたいかなど、データをビジネスの課題に生かす方法について学びました。CMの効果測定の話などすごく面白かったです。それから深層学習、Pythonを高速に実行するためのアルゴリズム、Pyspark、SparkSQL、その他数学的手法の紹介、エンジニアリングツールの紹介でした。

最終課題 Home Credit社への新規事業提案

東南アジアで展開するクレジット会社、Home Credit社のデータを分析して、新たなビジネスを提案しましょうというもの。

昨年おこなわれたKaggleのHome Credit Default Riskのデータを使って、別の切り口を機械学習を知らない経営陣にプレゼンするのです。これもまた見当もつかないところから、8回目の講義を参考に、こんな予測なら喜ばれるかなと考えてどうにか提出しました。

修了して

こうして振り返るとよく修了できたなーと我ながら感心してしまいます。講座のみなさまの手厚いサポートに感謝しきりです。

コロナでいろいろな予定がキャンセルになりましたので、ぼちぼち復習していこうと思います。
4月からまたGCI 2020 Summerが始まりますので、機械学習に興味のある方はぜひ。おすすめです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCI2019Winter無事修了しました!

12月から始まった東大のデータサイエンティスト講座、無事修了しました!
GCI Online Course 東大のデータサイエンティスト/未来のCMO育成講座のオンラインコースです
3ヶ月でデータサイエンティストを要請するという講座でなんと無料です。

●コースの概要
* 大量のデータを自由自在に解析・分析し、隠れた関係性を発見する。そんなスキルを身につけた「データサイエンティスト」に対する需要は、工学分野だけならず、医療・経済・経営・ライフサイエンスなど非常に多くの分野で高まる一方です。
* 本コースでは、あらゆる分野で武器になるデータの解析・分析スキルのコアとなる機械学習およびビッグデータを扱う技術、分析結果を効果的に可視化する技術の基盤を網羅的に身につけ、一人前のデータサイエンティストとして活躍する入り口に立つことを目指します。
* 受講費用は一切発生しません(通信費等は自己負担でお願いします)。

とにかく課題の量が多い。この3ヶ月は毎日が機械学習漬けでした。
前年度のテキストが販売されていたので、目を通してみましたが、あまりの難しさにこりゃ無理だと投げそうになりました。

東京大学のデータサイエンティスト育成講座 塚本 邦尊
https://www.amazon.co.jp/dp/B07PD237GQ/ref=cm_sw_r_tw_dp_U_x_j1bHEb66RA461

ですが、テキストがよく練られていたこと、Slackで随時質問できる事と、チームで参加した事と、サポート体制にずいぶん助けられ、なんとか修了までこぎつけました。

うえっとなったテキストが読めるようになる。これは自信になります。4月からまた受講者を募集しておりますので、これから機械学習をはじめようと思うみなさまにぜひおすすめしたいと思います。

受講テスト(12/7〜12)

受講前にスキルを確認のためのテストがありました。WhirlwindTourOfPythonのような基礎的なレベルですとのこと。リンク先がいきなり英語の解説で面食らいました。Pythonをかじっていたので何とかクリア。
問題は、行列の問題をPythonで解いて出すというもの。数学は中学校で挫折したので問題の意味がさっぱりわからず。数学が得意な人に教えていただきどうにかクリアしました。

第1回(12/18) データサイエンスとは、Python基礎、ライブラリ

IT化によりデータが大量にあるのにほとんど分析されていない。
これを分析すると、ビジネスにとんでもなく生かせる事ができる、、、という講義でした。翌日に動画で配信があり面白かったです。それからPython基礎です。

第2回(12/25) Pythonによる科学計算とデータ加工処理の基礎(Numpy,Pandas)

Pythonの表計算を学びました。全部コマンドのExcelみたいなものです。集計、統計、etcこの後めちゃくちゃ使いました。

コンペその1タイタニック号の生死予測。


KaggleのTitanic: Machine Learning from Disasterと同じです。
最初はまったく分からず、映画を見て予測を思いついても、どうやって実装したらいいか暗中模索でした。Kaggleやチーム、Slackで意見交換しながら粘りました。アイデアがスコアに結びつくと面白くなってきます。結果を次の予測に再利用するなんちゃってブースティングでランクインしました。コンペ後に上位のコードが公開されるのですが、ほんと頭のいい人がいるんだなーと感心するばかりです。

第3回(1/8) データサイエンスにおけるデータの可視化(Matplotlib)

グラフ、相関行列とヒートマップの作り方を学びました。なかなか思うように表現できないので、最初は美しいグラフを集めて、そのコードをコピペしながら慣れると良いようです。

第4回(1/15) 確率統計の基礎

合計、平均などの統計、散布図、ヒストグラムなどの図の書き方。確率・統計はさっぱり分からず。初心者問題を解くのがやっとでした。

コンペその2 ワインのクオリティ


ポルトガルのワインプロジェクトより、4898本のワインの成分とソムリエによる評価を読み込んで、成分からおいしさを予測するというものです。ワインは大好きなのです。何かというとお店のワインのラベルを読んではあれこれ考えるという楽しいコンペでした。

第5回(1/29) 機械学習の基礎(教師あり学習)

教師あり学習、教師なし学習、強化学習について。タイタニックやワインの予想で使っている手法をもう一度丁寧に解説。重回帰、ロジスティック回帰、ラッソ回帰、リッジ回帰、K-NN、 サポートベクターマシンあたりをチュートリアルに沿ってぽちぽちしていけば、なんとなく使えるようになった・・・気がします。

第6回(2/5) 機械学習の基礎(教師なし学習)

教師なし学習、目的変数がない学習モデルの構築を学びました。ざっくり分類、クラスタリング、主成分分析・・・。正直まだよく理解できていません。事前情報なしにデータをふるい分けできるのは便利なので覚えておく・・・というレベル。要復習ですね。

コンペその3 PUBGの勝者予想

KaggleのPUBG Finish Placement Prediction (Kernels Only)と同じです。
PUBGは100人の参加者が島に送られバトルロワイヤルするというゲームです。やってみましたが討ち死にしてばかり。まずはよく遊ぶ人にゲームをしている所を見せていただきました。アイテム、乗り物、武器の活用、刻々と狭まる安全圏にどう潜り込むかが勝負の決め手のようです。
しかしゲームの強さをどうコードにしたらよいかがさっぱり分かりません。
Kaggleにはどのように予測したかがNotebookとして沢山公開されています。目から鱗のアプローチがたくさんありました。
機械学習がものすごいスピードで発展するわけです。強そうなコードを組み合わせて提出するとそこそこいいスコアがとれました。

第7回(2/12) モデルの検証方法とチューニング方法

モデルのパラメータを調整、さまざまなモデルの特徴、複数のモデルを組み合わせての予測を学びました。
ちゃんとやろうとすると前例主義に陥ったり考えすぎで外したりするので、多少アソビを作っておくというイメージです。
それから、バギングというデータをいくつかに分割して学習させあう方法を実装すると、予測精度がぐっと良くなりました。
まだよく理解できていないので、深めたいところです。

第8回(3/4) データサイエンティスト中級者への道

第一線で活躍している方をお呼びして現場の話でした。マーケティング、物を売るには、どう思ってもらいたいかなど、データをビジネスの課題に生かす方法について学びました。CMの効果測定の話などすごく面白かったです。それから深層学習、Pythonを高速に実行するためのアルゴリズム、Pyspark、SparkSQL、その他数学的手法の紹介、エンジニアリングツールの紹介でした。

最終課題 Home Credit社への新規事業提案

東南アジアで展開するクレジット会社、Home Credit社のデータを分析して、新たなビジネスを提案しましょうというもの。

昨年おこなわれたKaggleのHome Credit Default Riskのデータを使って、別の切り口を機械学習を知らない経営陣にプレゼンするのです。これもまた見当もつかないところから、8回目の講義を参考に、こんな予測なら喜ばれるかなと考えてどうにか提出しました。

修了して

こうして振り返るとよく修了できたなーと我ながら感心してしまいます。講座のみなさまの手厚いサポートに感謝しきりです。

コロナでいろいろな予定がキャンセルになりましたので、ぼちぼち復習していこうと思います。
4月からまたGCI 2020 Summerが始まりますので、機械学習に興味のある方はぜひ。おすすめです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで毎日AtCoder #23

はじめに

前回
今日もCです

#23

ABC154-C
41diff
len(set(A))=len(A)なら全ての要素が異なるので、setするだけ

n = int(input())
a = list(map(int,input().split()))

a_s = set(a)
if len(a) == len(a_s):
    print('YES')
else:
    print('NO')

ABC153-C
36diff
Hをsortする。必殺技は体力が多い順に使った方が攻撃回数は減るのでH[k:]から足していく

n, k = map(int,input().split())
h = list(map(int,input().split()))
h.sort(reverse=True)
ans = 0
for i in range(k,n):
    ans += h[i]
print(ans)

ABC152-C
119diff
そのまま(i,j)の組を全部試すとTLEするので、最小値を更新していく。そうすると$P_j \leq P_i$を満すかどうかを判定できる。

n = int(input())
p = list(map(int,input().split()))

ans = 0
for i in range(n):
    if i == 0:
        ans += 1
        m = p[0]
        continue
    if p[i] <= m:
        ans += 1
        m = p[i]

print(ans)

ABC151-C
239diff
正解した問題をboolで管理する。WAはACした時に足すだけ。

n, m = map(int,input().split())
ps = list(list(map(str,input().split())) for _ in range(m))

check = [True] * (n+1)
wa = [0] * (n+1)
wa_ans = 0
for i in range(m):
    if ps[i][1] == 'AC' and check[int(ps[i][0])]:
        check[int(ps[i][0])] = False
        wa_ans += wa[int(ps[i][0])]
    if ps[i][1] == 'WA':
        wa[int(ps[i][0])] += 1
ans = 0
for i in check:
    if not i:
        ans += 1

print(ans,wa_ans)

ABC150-C
94diff
itertools神。$N\le 8$なので組合せを全列挙する。

import itertools
n = int(input())
p = tuple(((map(int,input().split()))))
q = tuple(((map(int,input().split()))))

num = []
for i in range(1,n+1):
    num.append(i)
ans = []
for i in itertools.permutations(num,n):
    ans.append(i)

print(abs(ans.index(p)-ans.index(q)))

まとめ

まだ解ける。では、また

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rbenv や pyenv で困っている人のための解説

対象読者: Ruby・Pythonなどの初心者で、こんなエラーメッセージが出てきて困っている人

$ fluentd
rbenv: fluentd: command not found

The `fluentd' command exists in these Ruby versions:
   2.6.3

rbenv や pyenv などの、**env を使っていると、このようなエラーメッセージが出てくることがあります。

この問題を解決するには**envの仕組みを理解する必要があります。

**env の仕組み

そもそも、**envを使っていると、このようにディレクトリを移動しただけで、rubypythonのバージョンが自動で切り替わるようになりますが、これはどのように実現されているのでしょう?

$ ruby --version
ruby 2.3.7p456 (2018-03-28 revision 63024) [universal.x86_64-darwin18]
$ cd my-project
$ cat .ruby-version
2.4.5
$ ruby --version
ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-darwin18]

実はここで実行している ruby は、本物のrubyではありません!~/.rbenv/shims/ に置かれたスクリプトなのです。

$ which ruby
/Users/x-xxxxx/.rbenv/shims/ruby

$ cat ~/.rbenv/shims/ruby
#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x

program="${0##*/}"
if [ "$program" = "ruby" ]; then
  for arg; do
    case "$arg" in
    -e* | -- ) break ;;
    */* )
      if [ -f "$arg" ]; then
        export RBENV_DIR="${arg%/*}"
        break
      fi
      ;;
    esac
  done
fi

export RBENV_ROOT="/Users/x-xxxxx/.rbenv"
exec "/Users/x-xxxxx/.rbenv/libexec/rbenv" exec "$program" "$@"

bundler, gem などの同梱コマンドや、Gemによって追加されるコマンドも同様です。

$ which gem
/Users/x-xxxxx/.rbenv/shims/gem

$ which bundle
/Users/x-xxxxx/.rbenv/shims/bundle

$ which fluentd
/Users/x-xxxxx/.rbenv/shims/fluentd

そして、普通にrbenv initでセットアップすると、$PATHの先頭に~/.rbenv/shims/が追加されて最優先で使用されます。

エラーメッセージが表示されるまでの流れ

~/.rbenv/shims/のスクリプトは現在使用中のバージョンのRuby内の同名のコマンドを実行しようとします。

そのため、現在使用中のバージョンのRubyにコマンドがインストールされていなければ、エラーになります;

  1. あなたが fluentdを実行する
  2. $PATHが先頭から検索され、最初に見つかる~/.rbenv/shims/fluentd実行される
  3. ~/.rbenv/shims/fluentdは現在のディレクトリから .rbenv-version を検索し、Rubyのバージョンを決定する
  4. そのバージョンのRuby内のflunetdを実行しようとするがfluentdが見つからない(別バージョンのRubyにはインストールされているが)
  5. 下記のエラーメッセージを表示
$ fluentd
rbenv: fluentd: command not found

The `fluentd' command exists in these Ruby versions:
   2.6.3

対策

コマンドを絶対パスで指定する

~/.rbenv/shims/hogeを経由せずに直接実行します。

~/.rbenv/versions/2.6.1/bin/ruby

$PATHの先頭に追加する

~/.rbenv/shims より先にパスを追加します。

なお、RubyやPythonにはコマンドを、(インタープリターのディレクトリではなく)ホーム直下のディレクトリにインストールする機能があります。そのディレクトリを$PATHに追加すると良いでしょう。

$ export PYTHONUSERBASE=~/.local
$ pip3 install --user awscli
$ ls ~/.local/bin jupyter-notebook
$ ls ~/.local/bin/
jupyter
jupyter-bundlerextension
jupyter-console
jupyter-kernel
...
# ~/.bashrc
eval "$(pyenv init -)"
export PATH=$HOME/.local/bin:$PATH

rbenv init を使わない

実は rbenv init を使わなくても、rbenv install でRubyをビルドすることはできます。

人によってはビルドできるだけで十分かもしれません。

pipenv や bundler 経由で使う

RubyやPythonのコマンドをグローバルにインストールして使うというのが、そもそもトラブルの元です。常に pipenv や bundler 経由で使いましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IISでDjangoプロジェクトにあるpydファイルがインポートできない問題への対応

TL;DR

https://qrunch.net/@sugurunatsuno/entries/wF9aXWZ3x3PBarLR
PYTHONPATHに読み込みたいpydファイルがあるディレクトリを追加する。

はじめに

IISを使ってDjangoのプロジェクトを実行したときにpydファイルが読み込めなくなった。  
結果として環境変数のPYTHONPATHにpydファイルを参照できるディレクトリがなかったことが原因であった。
Djangoのrunserverでの実行時にはpydファイルがインポートできていた。
IISではpydファイルのインポート以外に関しては動作する前提。

バージョン

OS/ソフトウェア/ライブラリ名 バージョン
Windows 10
IIS 10
Python 3.8.0
Django 2.2.8
Cython 0.29.15

IISで設定する場合

IISかPythonのどちらかで設定できればよい。

IISではweb.configなどで環境変数を設定できる。
セミコロンで区切って複数のパスを設定できるので、pydファイルがあるパスを追加する。

<add key="PYTHONPATH" value="project_path;pyd_absolute_path" />

この場合のpydファイルのインポートはPYTHONPATHを参照するため、呼び出すpyファイルからの相対パスにならない。

# 相対パスで参照できない
# from pyd_relative_path import target

# PYTHONPATHに設定しているためそのまま呼び出せる
import target

Python上で設定する場合

IISかPythonのどちらかで設定できればよい。

python上でも環境変数は設定できるので、pydファイルがあるディレクトリを設定できる。

import os
import sys

current_dir = os.path.abspath(os.path.dirname(__file__))
sys.path += [os.path.join(current_dir, 'pyd_relative_path')]

import target
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで2次元リストの置換がうまくいかなかった話

はじめに

pythonの2次元配列の一部を置換しようとしたときに、作成方法をミスして詰まった話です。
ちょっとした備忘録として。。

問題の説明

とても簡単ですが、5*4の0から構成された2次元配列があったとして、その一部を1に置換したかったわけです。

#before:こんな感じの2次元配列を
[[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0]]
'''''''''''''
      |
      |
      |
     \|/
'''''''''''''
#after:こんな風に置換したかった
[[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,0,0]]

何が起こったか?

まずは自分がやってしまった過ちから、、

#間違えた例
#初期化された配列を作成
map_list = [[0]*W]*H

#map_listのindex指定して置換
map_list[1][2] = 1

#結果を表示してみる
for line in map_list:
    print(line)

結果:

[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 1, 0, 0]

どうしてなの!?ちゃんと位置指定したじゃない!?
なぜか全部の列に対して実行された

pythonでは以下の方法でidを確認することができます

print(id(a))

先ほどの2次元配列に対して確認すると、、

[1650962624, 1650962624, 1650962656, 1650962624, 1650962624]
[1650962624, 1650962624, 1650962656, 1650962624, 1650962624]
[1650962624, 1650962624, 1650962656, 1650962624, 1650962624]
[1650962624, 1650962624, 1650962656, 1650962624, 1650962624]

どうやら列のidがかぶっていることが原因のようですね

解決策

さて、問題となっていたのは配列の作成部分です。

map_list = [[0]*5]*4

以下のようにリスト内包表記を用いて作成します

map_list = [[0 for i in range(5)] for r in range(4)]

実際に置換してみましょう

#初期化された配列を作成
map_list = [[0 for i in range(5)] for r in range(4)]

#map_listのindex指定して置換
map_list[1][2] = 1

#結果を表示してみる
for line in map_list:
    print(line)

出力:

[0, 0, 0, 0, 0]
[0, 0, 1, 0, 0]
[0, 0, 0, 0, 0]
[0, 0, 0, 0, 0]

まとめ

ということで、2次元配列の初期化の際には気を付けないといけませんねという話でした。
思わぬところに落とし穴があるものですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでシリアル通信制御してI2C通信する(GPIO8デバイスを使用します)

本記事について

電子回路経験がありGPIO、I2Cという単語は知っている前提としますのでその辺の用語解説を省きます。

GPIO8とは

シリアル通信でGPIOを制御できるデバイスです
Numato Labs社のUSB GPIO(デジタル入出力)ボードで日本だとエレファイン通販から購入できます。
https://www.elefine.jp/SHOP/USBGPIO8.html
ADコンバータも付いているので各種単体実験に便利です。

I2C通信について

http://www.picfun.com/f1/f06.html
こちらの解説がとても分かりやすいです。

なにをするか

python でシリアル通信を制御してGPIO8を制御しEEPROMに1バイト書き込み、その後読み込んで書き込まれている事を確認します。
EEPROM は 24LC64 を使用します。
24LC64はこちらの製品です → http://akizukidenshi.com/catalog/g/gI-00194/

ハードウェアの準備

GPIO8 の 0番ポートをSDA とします。
GPIO8 の 1番ポートをSCL とします。
各々10kΩでプルアップします。
上記構成でブレッドボード上で接続してます。

GPIO8_I2C_回路図.png

GPIO8のコマンドについて

エレファインのサイトや公式からマニュアルがダウンロードできますので詳しくはそちらを参照ください。
最初からプログラム制御ではなくTeraterm等のシリアル端末ソフトから制御実験することをおすすめします。

gpio [command] [arg]\r

という形式です、arg は有ったり無かったりします、末尾の ¥r を忘れないように注意します。
書き込み系のコマンドを送信すると

[送ったコマンド]\n\r>
# 例えば gpio set 0\n\r>

とコマンドにプロンプトを付けて返ってきます。
また読み込み系コマンドを送信すると

[送ったコマンド]\n\r[戻り値]\n\r>
# 例えば gpio read 0\n\r1\n\r>
# \n\r に挟まれている 1 が戻り値

とコマンドに戻り値とプロンプトを付けて返ってきます。

GPIOのLOW-HIGH制御

0番ポートをLOWにするには clear コマンドを使います

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio clear 0\r".encode())

0番ポートをHIGHにするには set コマンドを使います

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio set 0\r".encode())

コマンド

timeout=0.01 としてタイムアウトを設定します、設定しないと一生応答を返さなくなるので設定したほうが良いです

GPIOの読み取り

0番ポートのLOW-HIGH状態を読み取るには

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio read 0\r".encode())

とします

gpio read 0\n\r0\n\r>

と応答が返ってくればGPIOの状態は0(LOW)ということになります。

プログラム構造

0=LOW
1=HIGH
2=読み取りクロック
と定義します。
定義に従い配列を作成し、その配列の通りにSCL、SDAを制御します。


これはEEPROMデバイスのアドレスが0x50、データアドレスが 0xa0、書き込みデータが 0x33 の場合に作られる配列です。

[1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 0, 1, 1, 2]
処理毎に分割するとこうなる
[1, 0, 1, 0, 0, 0, 0, 0, 2]
[0, 0, 0, 0, 0, 0, 0, 0, 2]
[1, 0, 1, 0, 0, 0, 0, 0, 2]
[0, 0, 1, 1, 0, 0, 1, 1, 2]

各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭7ビット 0x50 のアドレスを表し、8ビット目は書き込みフラグ、9ビット目はACKの読み出し
[0, 0, 0, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの上位バイト、9ビット目はACKの読み出し
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの下位バイト、9ビット目はACKの読み出し
[0, 0, 1, 1, 0, 0, 1, 1, 2] 先頭8ビットは書き込む1バイトのデータ、9ビット目はACKの読み出し

という構成になっている。
上記のようにSDAのパターンを配列化しそれに従いクロックとデータを送信・受信する構造とする。

ソースコード

# i2c_byte_write_and_read.py
import serial
import sys
import time

SerialInstance = None

def SerialInit(comString):
    global SerialInstance
    SerialInstance = serial.Serial(comString, 115200, timeout=0.01)
    #SerialInstance = serial.Serial(comString, 19200, timeout=0.1)

def SerialEnd():
    SerialInstance.close()

def SerialTalk(cmd, response=False):
    readLen = len(cmd) + 1 # gpio read 0\n\r # 最初から \r が付いているので +2 ではなく +1 する
    if response == True:
        readLen += 3 # N\n\r
    readLen += 1 # >
    cnt = SerialInstance.write(cmd.encode())
    res = SerialInstance.read(readLen)
    res = res.decode("utf-8").strip()
    return res
    # 返される文字数を正確に読み取り切ること、1文字でも過不足があると応答待ち状態が続く事になる

def gpioHigh(n):
    SerialTalk("gpio set {}\r".format(n))

def gpioLow(n):
    SerialTalk("gpio clear {}\r".format(n))

def gpioRead(n):
    res = SerialTalk("gpio read {}\r".format(n), response=True)
    return res

def ByteToLH(b):
    lh = []
    for i in range(8):
        if (b << i & 128) == 0:
            lh.append(0)
        else:
            lh.append(1)
    return lh

def SDA_LOW():
    gpioLow(0)

def SDA_HIGH():
    gpioHigh(0)

def SCL_LOW():
    gpioLow(1)

def SCL_HIGH():
    gpioHigh(1)
def READ_DATA():
    return gpioRead(0)

def parseData(all):
    res = []
    for l in all:
        a = l.split("\n\r")
        res.append(a[1])
    return res

# 0 = LOW
# 1 = HIGH
# 2 = READ
# スタートコンディション: SCLがHIGHの間にSDAをLOWにする
# ストップコンディション: SCLをHIGHにしてから、SDAをHIGHにする
def WriteProc():
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    write1Byte = ByteToLH(constData)
    cmdWrite = ctrlByte + [2] + addrHigh + [2] + addrLow + [2] + write1Byte + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdWrite)
    data = []
    for i in range(size):
        SCL_LOW()
        d = cmdWrite[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SDA_LOW()
    SCL_HIGH()
    SDA_HIGH()

    time.sleep(0.1)
    print(parseData(data))
    print("write end")

def ReadProc():
    #
    #
    # set address
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    cmdSetAddress = ctrlByte + [2] + addrHigh + [2] + addrLow + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdSetAddress)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdSetAddress[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()

    print(parseData(data))

    #
    #
    # read data
    ctrlByte = ByteToLH((constDeviceAddress << 1) | 0x01)
    readByte = [2] * 8
    cmdReadByte = ctrlByte + [2] + readByte + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdReadByte)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdReadByte[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()

    print(parseData(data))
    print("read end")

# defines
constDeviceAddress = 0x50 # ICの設定に従う
constDataAdress = 0xa0 # ICのアドレス範囲内で適当に決める、今回は最大2バイト(0-65535)と想定したコード、3バイトアドレスの場合はコード修正が必要
constData = 0x33 # 0-255 の範囲で適当に決める

def run(comString):
    SerialInit(comString)

    WriteProc()
    ReadProc()

    SerialEnd()

if __name__ == "__main__":
    run(sys.argv[1])
# python i2c_byte_write_and_read.py COM4

ソースコード解説

使い方

Linuxの場合
python i2c_byte_write_and_read.py /dev/ttyUSB0

Windowsの場合
python i2c_byte_write_and_read.py COM4

戻り値の見方

['0', '0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK、書き込みデータのACK
write end
['0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK
['0', '0', '0', '1', '1', '0', '0', '1', '1', '1'] # コントロールバイトのACK、読み込んだ8ビットの状態、読み込み終了のACK(1だが正常に読み取れている)
read end

以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでシリアル通信制御してI2C通信する(USBGPIO8デバイスを使用します)

本記事について

電子回路経験がありGPIO、I2Cという単語は知っている前提としますのでその辺の用語解説を省きます。

USBGPIO8とは

シリアル通信でGPIOを制御できるデバイスです
Numato Labs社のUSB GPIO(デジタル入出力)ボードで日本だとエレファイン通販から購入できます。
https://www.elefine.jp/SHOP/USBGPIO8.html
ADコンバータも付いているので各種単体実験に便利です。

I2C通信について

http://www.picfun.com/f1/f06.html
こちらの解説がとても分かりやすいです。

なにをするか

python でシリアル通信を制御してUSBGPIO8を制御しEEPROMに1バイト書き込み、その後読み込んで書き込まれている事を確認します。
EEPROM は 24LC64 を使用します。
24LC64はこちらの製品です → http://akizukidenshi.com/catalog/g/gI-00194/

ハードウェアの準備

USBGPIO8 の 0番ポートをSDA とします。
USBGPIO8 の 1番ポートをSCL とします。
各々10kΩでプルアップします。
上記構成でブレッドボード上で接続してます。

GPIO8_I2C_回路図.png

USBGPIO8のコマンドについて

エレファインのサイトや公式からマニュアルがダウンロードできますので詳しくはそちらを参照ください。
最初からプログラム制御ではなくTeraterm等のシリアル端末ソフトから制御実験することをおすすめします。

gpio [command] [arg]\r

という形式です、arg は有ったり無かったりします、末尾の ¥r を忘れないように注意します。
書き込み系のコマンドを送信すると

[送ったコマンド]\n\r>
# 例えば gpio set 0\n\r>

とコマンドにプロンプトを付けて返ってきます。
また読み込み系コマンドを送信すると

[送ったコマンド]\n\r[戻り値]\n\r>
# 例えば gpio read 0\n\r1\n\r>
# \n\r に挟まれている 1 が戻り値

とコマンドに戻り値とプロンプトを付けて返ってきます。

GPIOのLOW-HIGH制御

0番ポートをLOWにするには clear コマンドを使います

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio clear 0\r".encode())

0番ポートをHIGHにするには set コマンドを使います

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio set 0\r".encode())

コマンド

timeout=0.01 としてタイムアウトを設定します、設定しないと一生応答を返さなくなるので設定したほうが良いです

GPIOの読み取り

0番ポートのLOW-HIGH状態を読み取るには

SerialInstance = serial.Serial("COM4", 115200, timeout=0.01)
SerialInstance.write("gpio read 0\r".encode())

とします

gpio read 0\n\r0\n\r>

と応答が返ってくればGPIOの状態は0(LOW)ということになります。

プログラム構造

0=LOW
1=HIGH
2=読み取りクロック
と定義します。
定義に従い配列を作成し、その配列の通りにSCL、SDAを制御します。


これはEEPROMデバイスのアドレスが0x50、データアドレスが 0xa0、書き込みデータが 0x33 の場合に作られる配列です。

[1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 0, 1, 1, 2]
処理毎に分割するとこうなる
[1, 0, 1, 0, 0, 0, 0, 0, 2]
[0, 0, 0, 0, 0, 0, 0, 0, 2]
[1, 0, 1, 0, 0, 0, 0, 0, 2]
[0, 0, 1, 1, 0, 0, 1, 1, 2]

各行を解説する
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭7ビット 0x50 のアドレスを表し、8ビット目は書き込みフラグ、9ビット目はACKの読み出し
[0, 0, 0, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの上位バイト、9ビット目はACKの読み出し
[1, 0, 1, 0, 0, 0, 0, 0, 2] 先頭8ビットは書き込みアドレスの下位バイト、9ビット目はACKの読み出し
[0, 0, 1, 1, 0, 0, 1, 1, 2] 先頭8ビットは書き込む1バイトのデータ、9ビット目はACKの読み出し

という構成になっている。
上記のようにSDAのパターンを配列化しそれに従いクロックとデータを送信・受信する構造とする。

ソースコード

# i2c_byte_write_and_read.py
import serial
import sys
import time

SerialInstance = None

def SerialInit(comString):
    global SerialInstance
    SerialInstance = serial.Serial(comString, 115200, timeout=0.01)
    #SerialInstance = serial.Serial(comString, 19200, timeout=0.1)

def SerialEnd():
    SerialInstance.close()

def SerialTalk(cmd, response=False):
    readLen = len(cmd) + 1 # gpio read 0\n\r # 最初から \r が付いているので +2 ではなく +1 する
    if response == True:
        readLen += 3 # N\n\r
    readLen += 1 # >
    cnt = SerialInstance.write(cmd.encode())
    res = SerialInstance.read(readLen)
    res = res.decode("utf-8").strip()
    return res
    # 返される文字数を正確に読み取り切ること、1文字でも過不足があると応答待ち状態が続く事になる

def gpioHigh(n):
    SerialTalk("gpio set {}\r".format(n))

def gpioLow(n):
    SerialTalk("gpio clear {}\r".format(n))

def gpioRead(n):
    res = SerialTalk("gpio read {}\r".format(n), response=True)
    return res

def ByteToLH(b):
    lh = []
    for i in range(8):
        if (b << i & 128) == 0:
            lh.append(0)
        else:
            lh.append(1)
    return lh

def SDA_LOW():
    gpioLow(0)

def SDA_HIGH():
    gpioHigh(0)

def SCL_LOW():
    gpioLow(1)

def SCL_HIGH():
    gpioHigh(1)
def READ_DATA():
    return gpioRead(0)

def parseData(all):
    res = []
    for l in all:
        a = l.split("\n\r")
        res.append(a[1])
    return res

# 0 = LOW
# 1 = HIGH
# 2 = READ
# スタートコンディション: SCLがHIGHの間にSDAをLOWにする
# ストップコンディション: SCLをHIGHにしてから、SDAをHIGHにする
def WriteProc():
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    write1Byte = ByteToLH(constData)
    cmdWrite = ctrlByte + [2] + addrHigh + [2] + addrLow + [2] + write1Byte + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdWrite)
    data = []
    for i in range(size):
        SCL_LOW()
        d = cmdWrite[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SDA_LOW()
    SCL_HIGH()
    SDA_HIGH()

    time.sleep(0.1)
    print(parseData(data))
    print("write end")

def ReadProc():
    #
    #
    # set address
    ctrlByte = ByteToLH(constDeviceAddress << 1)
    addrHigh = ByteToLH((constDataAdress >> 8) & 0xff)
    addrLow = ByteToLH(constDataAdress & 0xff)
    cmdSetAddress = ctrlByte + [2] + addrHigh + [2] + addrLow + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdSetAddress)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdSetAddress[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()

    print(parseData(data))

    #
    #
    # read data
    ctrlByte = ByteToLH((constDeviceAddress << 1) | 0x01)
    readByte = [2] * 8
    cmdReadByte = ctrlByte + [2] + readByte + [2]

    # START CONDITION
    SCL_HIGH()
    SDA_LOW()

    size = len(cmdReadByte)
    data = []
    for i in range(size):
        SCL_LOW()
        #print(i)
        d = cmdReadByte[i]
        if d == 0:
            SDA_LOW()
        elif d == 1:
            SDA_HIGH()
        SCL_HIGH()
        if d == 2:
            response = READ_DATA()
            data.append(response)
    SCL_LOW() # LOWに戻す
    SDA_LOW() # LOWに戻す

    # STOP CONDITION
    SCL_HIGH()
    SDA_HIGH()

    print(parseData(data))
    print("read end")

# defines
constDeviceAddress = 0x50 # ICの設定に従う
constDataAdress = 0xa0 # ICのアドレス範囲内で適当に決める、今回は最大2バイト(0-65535)と想定したコード、3バイトアドレスの場合はコード修正が必要
constData = 0x33 # 0-255 の範囲で適当に決める

def run(comString):
    SerialInit(comString)

    WriteProc()
    ReadProc()

    SerialEnd()

if __name__ == "__main__":
    run(sys.argv[1])
# python i2c_byte_write_and_read.py COM4

ソースコード解説

使い方

Linuxの場合
python i2c_byte_write_and_read.py /dev/ttyUSB0

Windowsの場合
python i2c_byte_write_and_read.py COM4

戻り値の見方

['0', '0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK、書き込みデータのACK
write end
['0', '0', '0'] # コントロールバイトのACK、アドレス上位バイトのACK、アドレス下位バイトのACK
['0', '0', '0', '1', '1', '0', '0', '1', '1', '1'] # コントロールバイトのACK、読み込んだ8ビットの状態、読み込み終了のACK(1だが正常に読み取れている)
read end

以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google画像検索の画像をオリジナルサイズで取得する

画像検索の画像をスクレイピングする方法についての記事は既に複数あるが、ちょっとした仕様変更が頻繁なのか、そのまま使えるものはほぼなかったので、どこを変更する可能性があるかも含めて記事を書いておきます。
多分動かないときにやることは、ブラウザのディベロッパーツールを使って必要なclass名やid名を調べればよい、というだけですが、わかる人には自明だけど、知らないと何が間違っている調べるのに結構時間かかる。

実行環境

macOS Mojave 10.14.5
Python 3.7.2
Chrome 80.0
date 2020-04-01

スクレイピングに必要なモジュールの準備

  • Seleniumのインストール
pip install selenium

webdriverが使えるかチェック

起動 → google検索 → 終了 をしてみる

from selenium import webdriver

# ダウンロードしたwebdriverのpathを指定
DRIVER_PATH = '/User/hoge/1bin/chromedriver'
# webブラウザを起動
wd = webdriver.Chrome(executable_path=DRIVER_PATH)
# googleにアクセス
wd.get('https://google.com')
# 検索boxを選択する. 選択する方法は複数ある
search_box = wd.find_element_by_name('q') 
# search_box = wd.find_element_by_css_selector('input.gLFyf')
# search_box = wd.find_element_by_class_name('gLFyf')

# Qiitaを検索する
search_box.send_keys('Qiita')
search_box.submit() 
time.sleep(5) 
# webブラウザを終了する
wd.quit()

画像検索をして画像のURLを取得する

サムネイル画像のURLを取得

最初に検索結果のサムネイルのURLを取得する。

# クリックなど動作後に待つ時間(秒)
sleep_between_interactions = 2
# ダウンロードする枚数
download_num = 3
# 検索ワード
query = "cat"
# 画像検索用のurl
search_url = "https://www.google.com/search?safe=off&site=&tbm=isch&source=hp&q={q}&oq={q}&gs_l=img"

wd = webdriver.Chrome(executable_path=DRIVER_PATH)
wd.get(search_url.format(q=query))
# サムネイル画像のリンクを取得(ここでコケる場合はセレクタを実際に確認して変更する)
thumbnail_results = wd.find_elements_by_css_selector("img.rg_i")

サムネイル画像をクリックして元画像のURLを取得する

取得したサムネイルのURLをwebdriverでクリックし、画像を表示してオリジナル画像のURLを取得する。
また、オリジナル画像URLの取得の際に指定するclass nameは変更されることあるので、適宜ディベロッパーツールを使って調べて変更する。

image_urls = set()
for img in thumbnail_results[:download_num]:
    try:
        img.click()
        time.sleep(sleep_between_interactions)
    except Exception:
        continue
    # 一発でurlを取得できないので、候補を出してから絞り込む(やり方あれば教えて下さい)
    # 'n3VNCb'は変更されることあるので、クリックした画像のエレメントをみて適宜変更する
    url_candidates = wd.find_elements_by_class_name('n3VNCb')
    for candidate in url_candidates:
        url = candidate.get_attribute('src')
        if url and 'https' in url:
            image_urls.add(url)
# 少し待たないと正常終了しなかったので3秒追加
time.sleep(sleep_between_interactions+3)
wd.quit()

画像URLから画像を取得する

あとはURLから画像をダウンロードするだけで、webdriverは必要ない。
* PILは画像ライブラリ

import os
from PIL import Image
import hashlib

image_save_folder_path = 'data'
for url in image_urls:
    try:
        image_content = requests.get(url).content
    except Exception as e:
        print(f"ERROR - Could not download {url} - {e}")

    try:
        image_file = io.BytesIO(image_content)
        image = Image.open(image_file).convert('RGB')
        file_path = os.path.join(image_save_folder_path,hashlib.sha1(image_content).hexdigest()[:10] + '.jpg')
        with open(file_path, 'wb') as f:
            image.save(f, "JPEG", quality=90)
        print(f"SUCCESS - saved {url} - as {file_path}")
    except Exception as e:
        print(f"ERROR - Could not save {url} - {e}")

画像リンクを取得するときのelementの探し方

googleの検索窓なら、search_box = wd.find_element_by_name('q')
画像検索結果のオリジナル画像なら、wd.find_elements_by_class_name('n3VNCb')
などで指定するが、これらはブラウザのディベロッパーツールを使って確認できる。

ブラウザの「…」→その他のツール→デベロッパーツール
ショートカットは、 Ctrl Shift J (on Windows) or cmd + Option + J (on Mac)

指定したい画像があれば、画像を右クリックして"検証"を選択すると、画像に対応するエレメントを表示してくれる。
画像検索のサムネイルなら、以下のようなエレメントになる。
エレメントは選択状態で出てくる'...'をクリックしてコピーできる。

<img alt="Dogs | The Humane Society of the United States" class="n3VNCb" src="https://www.humanesociety.org/sites/default/files/styles/1240x698/public/2019/02/dog-451643.jpg?h=bf654dbc&amp;itok=MQGvBmuo" data-noaft="1" jsname="HiaYvf" jsaction="load:XAeZkd;" style="width: 450px; height: 253.306px; margin: 0px;">

class="n3VNCb"とあるので、
wd.find_elements_by_class_name('n3VNCb')としたら選択できる。

コードまとめ

コピペ用

import os
import time
from selenium import webdriver
from PIL import Image
import io
import requests
import hashlib

# クリックなど動作後に待つ時間(秒)
sleep_between_interactions = 2
# ダウンロードする枚数
download_num = 3
# 検索ワード
query = "cat"
# 画像検索用のurl
search_url = "https://www.google.com/search?safe=off&site=&tbm=isch&source=hp&q={q}&oq={q}&gs_l=img"

# サムネイル画像のURL取得
wd = webdriver.Chrome(executable_path=DRIVER_PATH)
wd.get(search_url.format(q=query))
# サムネイル画像のリンクを取得(ここでコケる場合はセレクタを実際に確認して変更する)
thumbnail_results = wd.find_elements_by_css_selector("img.rg_i")

# サムネイルをクリックして、各画像URLを取得
image_urls = set()
for img in thumbnail_results[:download_num]:
    try:
        img.click()
        time.sleep(sleep_between_interactions)
    except Exception:
        continue
    # 一発でurlを取得できないので、候補を出してから絞り込む(やり方あれば教えて下さい)
    # 'n3VNCb'は変更されることあるので、クリックした画像のエレメントをみて適宜変更する
    url_candidates = wd.find_elements_by_class_name('n3VNCb')
    for candidate in url_candidates:
        url = candidate.get_attribute('src')
        if url and 'https' in url:
            image_urls.add(url)
# 少し待たないと正常終了しなかったので3秒追加
time.sleep(sleep_between_interactions+3)
wd.quit()

# 画像のダウンロード
image_save_folder_path = 'data'
for url in image_urls:
    try:
        image_content = requests.get(url).content
    except Exception as e:
        print(f"ERROR - Could not download {url} - {e}")

    try:
        image_file = io.BytesIO(image_content)
        image = Image.open(image_file).convert('RGB')
        file_path = os.path.join(image_save_folder_path,hashlib.sha1(image_content).hexdigest()[:10] + '.jpg')
        with open(file_path, 'wb') as f:
            image.save(f, "JPEG", quality=90)
        print(f"SUCCESS - saved {url} - as {file_path}")
    except Exception as e:
        print(f"ERROR - Could not save {url} - {e}")

参考にした記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む