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

[Blender×Python] モディファイアのつかいかた

目次

0.Subdivision Surface
1.Array
2.Bevel
3.Boolean
4.Build
5.Decimate
6.Mirror
7.Screw
8.Skin
9.Solidify
10.ShrinkWrap
(11.Shape Key)

※コードのコメントは後で追加します。

0.Subdivision Surface

ssss.png

import bpy

def subSurf(level = 3):
    bpy.ops.object.modifier_add(type='SUBSURF')
    bpy.context.object.modifiers["Subdivision"].levels = level

bpy.ops.mesh.primitive_monkey_add()
subSurf()

1.Array

aaaa.png

import bpy

def array(num = 5,o_x = 2,o_y = 0,o_z = 0):
    bpy.ops.object.modifier_add(type='ARRAY')

    array = bpy.context.object.modifiers["Array"]
    array.count = num

    array.relative_offset_displace[0] = o_x
    array.relative_offset_displace[1] = o_y
    array.relative_offset_displace[2] = o_z


bpy.ops.mesh.primitive_cube_add()
array(o_x = 5,o_y = 5,o_z = 5)

2.Bevel

tttttt.png

import bpy

def bevel(_amount = 0.3,_segments = 1):
    bpy.ops.object.modifier_add(type='BEVEL')

    bevel = bpy.context.object.modifiers["Bevel"]
    bevel.width = _amount
    bevel.segments = _segments

bpy.ops.mesh.primitive_ico_sphere_add()
bevel()

2-1.Bevel_animation

ezgif.com-gif-maker (30).gif

import bpy

frame_num = 0
bpy.context.scene.frame_set(frame_num)
bpy.context.scene.frame_end = 50

bpy.ops.mesh.primitive_cube_add()

bpy.ops.object.modifier_add(type='BEVEL')
bevel = bpy.context.object.modifiers["Bevel"]

for i in range(0,2):
    bevel.width = i
    bevel.keyframe_insert(data_path = "width",frame = frame_num)
    frame_num += 50

3.Boolean

eee.png

import bpy

def boolean(obj):
    bpy.ops.object.modifier_add(type='BOOLEAN')

    boolean = bpy.context.object.modifiers["Boolean"]
    boolean.operation = 'DIFFERENCE'
    boolean.object = bpy.data.objects[obj]
    bpy.ops.object.modifier_apply(modifier="Boolean")


bpy.ops.mesh.primitive_cube_add()
bpy.ops.mesh.primitive_cube_add(location = (1,1,1))
boolean("Cube")

4.Build

import bpy

def build(f):
    bpy.ops.object.modifier_add(type='BUILD')

    build = bpy.context.object.modifiers["Build"]
    build.frame_start = f
    build.use_random_order = False
    build.seed = 0


bpy.ops.mesh.primitive_monkey_add()
build(f = 0)

4-1.Build_animation

ezgif.com-gif-maker (27).gif

import bpy

frame_num = 0
bpy.context.scene.frame_set(frame_num)
bpy.context.scene.frame_end = 120

bpy.ops.mesh.primitive_monkey_add()

bpy.ops.object.modifier_add(type='BUILD')
build = bpy.context.object.modifiers["Build"]

for i in range(0,2):
    build.frame_start = 12 -i * 110
    build.keyframe_insert(data_path = "frame_start",frame = frame_num)
    frame_num -= 110

5.Decimate

wwwww.png

import bpy

def decimate(limit_num):
    bpy.ops.object.modifier_add(type='DECIMATE')

    decim = bpy.context.object.modifiers["Decimate"]
    decim.decimate_type = 'DISSOLVE'
    decim.delimit = {'NORMAL'}
    decim.angle_limit = limit_num


bpy.ops.mesh.primitive_monkey_add()
decimate(limit_num = 0.8)

6.Mirror

import bpy

def mirror(obj):
    bpy.ops.object.modifier_add(type='MIRROR')

    mir = bpy.context.object.modifiers["Mirror"]
    mir.use_axis[0] = True
    mir.mirror_object = bpy.data.objects[obj]



bpy.ops.mesh.primitive_monkey_add()
bpy.ops.mesh.primitive_cube_add(location = (3,0,0))
bpy.context.view_layer.objects.active = bpy.data.objects["Suzanne"]
mirror("Cube")

7.Screw

import bpy

def subSurf(level = 3):
    bpy.ops.object.modifier_add(type='SUBSURF')
    bpy.context.object.modifiers["Subdivision"].levels = level

def screw(angle = 6.28,screw = 10,itr = 1,ax = 'Z'):
    bpy.ops.object.modifier_add(type='SCREW')

    scr = bpy.context.object.modifiers["Screw"]
    scr.angle = angle
    scr.screw_offset = screw
    scr.iterations = 2
    scr.axis = ax

bpy.ops.mesh.primitive_grid_add()
screw(itr = 2)
subSurf()

7-1.Screw_animation

ezgif.com-gif-maker (29).gif

import bpy

frame_num = 0
bpy.context.scene.frame_set(frame_num)
bpy.context.scene.frame_end = 120

bpy.ops.mesh.primitive_grid_add()


bpy.ops.object.modifier_add(type='SCREW')
scr = bpy.context.object.modifiers["Screw"]

_angle = 0
_screw_offset = 10


for i in range(0,2):
    scr.angle = _angle
    scr.screw_offset = _screw_offset
    #scr.iterations = i + 1
    scr.keyframe_insert(data_path = "angle",frame = frame_num)
    scr.keyframe_insert(data_path = "screw_offset",frame = frame_num)
    scr.keyframe_insert(data_path = "iterations",frame = frame_num)
    frame_num  += 110
    _angle += 15
    _screw_offset += 15

8.Skin

ssssss.png

import bpy

bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.mode_set(mode='EDIT')

bpy.ops.mesh.delete(type='EDGE_FACE')
bpy.ops.mesh.select_all(action='SELECT')

bpy.ops.object.modifier_add(type='SKIN')
bpy.ops.transform.skin_resize(value=(0.1, 0.1, 0.1))

bpy.ops.object.mode_set(mode='OBJECT')

9.Solidify

import bpy

def solid(thick = -0.15,ofs = -1):
    bpy.ops.object.modifier_add(type='SOLIDIFY')
    solid = bpy.context.object.modifiers["Solidify"]
    solid.thickness = thick
    solid.offset = ofs

bpy.ops.mesh.primitive_monkey_add()
solid()

10.ShrinkWrap

ezgif.com-gif-maker (24).gif

import bpy

def subSurf(level = 3):
    bpy.ops.object.modifier_add(type='SUBSURF')
    bpy.context.object.modifiers["Subdivision"].levels = level

def shrinkWrapAnim(obj):
    bpy.ops.object.modifier_add(type='SHRINKWRAP')
    bpy.context.object.modifiers["Shrinkwrap"].target = bpy.data.objects[obj]
    bpy.ops.object.modifier_apply_as_shapekey(keep_modifier=False, modifier="Shrinkwrap")

def shapeKeyInsert(frame_num = 10,shape_value = 1):
    obj = bpy.context.object
    obj.active_shape_key_index = 1

    for i in range(0,2):
        obj.active_shape_key.value = shape_value
        obj.active_shape_key.keyframe_insert(data_path = "value",frame = frame_num)

        frame_num += 50
        shape_value -= 1

s = 0.3
bpy.context.scene.frame_set(0)
bpy.context.scene.frame_end = 50

bpy.ops.mesh.primitive_cube_add(scale = (s,s,s))
bpy.ops.mesh.primitive_monkey_add()
subSurf()
shrinkWrapAnim("Cube")
shapeKeyInsert()

11.Shape Key

ezgif.com-gif-maker (25).gif

import bpy

bpy.ops.mesh.primitive_cube_add()
bpy.ops.object.shape_key_add(from_mix=False)

obj = bpy.context.object
deform_num = 2

for i in range(0,deform_num):
    obj.active_shape_key_index = i + 1

    obj.data.vertices[i * 2].co.z  += 5
    bpy.ops.object.shape_key_add(from_mix=False)

    frame_num = 0
    for f in range(0,deform_num + 1):

        if(f == i + 1):
            val = 1
        else:
            val = 0

        obj.active_shape_key.value = val
        obj.active_shape_key.keyframe_insert(data_path = "value",frame = frame_num)
        frame_num += 50
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのダックタイピングを理解する手段

概要

Pythonを1、2年ながめているが、ダックタイピングが頭に入っていない。
理解する手段を考えた。

余談ですが、
頭に入っていないのは、ワタシが使っている教科書のせいかも。
ちょっと、ディスった記事↓。

良書『入門 Python3』、説明に失敗しているところN選(N=3)。

理解する手段

簡単です。2手順です。

手順1、自分がダックタイピングだとおもっている例のコードを書いてみる。

以下、ワタシが書いたコード。点数にマイナスをつけていいなら、-60(マイナス60点)。

セールスポイント、

  • 何を思ったか。。。継承させている

なぜ、こんなコードを書いてしまったか。
それは、ダックタイピングは、なんの特徴もない、あたり前のものと記憶していたから。

class Tori():#鳥
    def tobe(self):
        print("bata-bata")
    def nake(self):
        print("ga-ga")

class Duck(Tori):#ダック
    def nake(self):
        print("ga-ga-ga-ga-ga-ga")

class Ahiru(Tori):#アヒル
    def tobe(self):
        print("bata-bata-bata-bata-bata")

duck_1go = Duck()
duck_1go.nake()
duck_1go.tobe()

ahiru_1go = Ahiru()
ahiru_1go.nake()
ahiru_1go.tobe()

手順2、正解を調べる。
よくわからなかったので、wikiに正解を求めました。

https://ja.wikipedia.org/wiki/%E3%83%80%E3%83%83%E3%82%AF%E3%83%BB%E3%82%BF%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0

wikiにrubyのコード例がありました。
pythonに書き直してみました。

def test(foo):
    foo.sound()

class Duck():
    def sound(self):
        print('quack')

class Cat():
    def sound(self):
        print('myaa')

duck_san = Duck()
cat_san = Cat()
test(duck_san)
test(cat_san)

wikiでのコードに対する補足説明は、以下の1行です。

2つのクラスに継承の関係が無いことに注目して欲しい。

まとめ

教科書を読むだけでは、頭に入りませんね。
コメントなどあれば、お願いします。

本来、このダックタイピングが成立している背景とか作用に意味があるのでしょうが、そのあたりは、また、理解が深まってから。。。

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

Effective Python 学習備忘録 11日目 【11/100】

はじめに

Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです!

今回学習する教材

今日の進捗

  • 進行状況:73-78ページ
  • 第3章:クラスと継承
  • 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。

多重継承はmix-inユーティリティクラスだけに使う

多重継承はあまり使うべきではなく、代わりにmix-inを使うべきです。

mix-inとは

クラスが提供すべき一連の追加のメソッドを定義するだけの小さなクラスのことです。
また、通常のクラスと異なり、インスタンス属性を持たず、__init__コンストラクタを呼び出す必要もないです。

mix-inの例を、継承した任意のクラスで追加される新たなメソッドとして次のように定義します。

class ToDictMixin(object):
    def to_dict(self):
        '''
        このオブジェクトの属性を辞書にして返す
        '''

        return self._traverse_dict(self.__dict__)

    def _traverse_dict(self, instance_dict):
        '''
        辞書を受け取り、新たな辞書outputを返す。

        Parameters
        ----------
        instance_dict: dict

        Returns
        -------
        output : dict
            キーに、instance_dictのキー、値に_traverse()
        '''

        output = {}
        for key, value in instance_dict.items():
            output[key] = self._traverse(key, value)
        return output

    def _traverse(self, key, value):
        '''
        辞書の値の型に応じて、関数を呼び出す。
        '''

        if isinstance(value, ToDictMixin):
            return value.to_dict()
        elif isinstance(value, dict):
            return self._traverse_dict(value)
        elif isinstance(value, list):
            return [self._traverse(key, i) for i in value]
        elif hasattr(value, '__dict__'):
            return self._traverse_dict(value.__dict__)
        else: return value

次に、このmix-inを使っ2分木の辞書表現を作るクラスを定義し、オブジェクトの属性を出力します。

class BinaryTree(ToDictMixin):
    def __init__(self, value, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

tree = BinaryTree(5,
    left=BinaryTree('aaa', right=BinaryTree(4)),
    right=BinaryTree(2.4, left=BinaryTree(8)))

# treeオブジェクトの属性を辞書にして表示
print(tree.to_dict())

mix-inの長所は、特定の型に依存せず、必要な時に機能をオーバーライドできる点です。例えば、先ほどの BinaryTreeのサブクラスを親への参照を保持するようにオーバーライドします。

class BinaryTreeWithParent(BinaryTree):
    def __init__(self, value, left=None,
                 right=None, parent=None):
        super().__init__(value, left=left, right=right)
        self.parent = parent

    # デフォルトの実装だと永久にループするため、必要な値だけを処理するように変更
    def _traverse(self, key, value):
        '''
        値の型がBinaryTreeWithParent属性かつ、キーが親クラスだった場合、value.valueを返し、
        それ以外の場合は、親クラスの_traverseと同じ処理をするように変更
        '''
        if (isinstance(value, BinaryTreeWithParent) and
                 key == 'parent'):
            return value.value    # サイクルを防ぐ
        else:
            return super()._traverse(key, value)

見やすくするために、pprintで出力する

import pprint

root = BinaryTreeWithParent(5)
root.left = BinaryTreeWithParent(3, parent=root)
root.left.right = BinaryTreeWithParent(13, parent=root.left)
pprint.pprint(root.to_dict())

出力結果

{'left': {'left': None,
          'parent': 5,
          'right': {'left': None, 'parent': 3, 'right': None, 'value': 13},
          'value': 3},
 'parent': None,
 'right': None,
 'value': 5}

BinaryTreeWithParent._traverse を定義することで、BinaryTreeWithParent型の属性を持つすべてのクラスでも、ToDictMixinが自動的に働きます。

class NamedSubTree(ToDictMixin):
    def __init__(self, name, tree_with_parent):
        self.name = name
        self.tree_with_parent = tree_with_parent

mytree = NamedSubTree('bbb',root.left.right)
pprint.pprint(mytree.to_dict())

出力結果

{'name': 'bbb',
 'tree_with_parent': {'left': None, 'parent': 3, 'right': None, 'value': 13}}

まとめ

  • 基本的には、多重継承よりmix-inを使う
  • mix-inクラスが必要なときに、クラスごとにカスタマイズする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラズパイでPythonから米国株価をWebAPIで取得

以前、Postmanを使って米国株価を取得してみましたが、アプリ作成に転用するため今度はラズパイからPythonを使っての取得を試みました。

PostmanでRapid APIを使って米国株価を取得する
https://qiita.com/ShinPun/items/cc9a124e3bc64b4d3893

Pythonコード

apikeyの取得方法は上の記事をご参照ください。

import requests

url = "https://www.alphavantage.co/query"

querystring = {"interval":"5min","function":"TIME_SERIES_INTRADAY","symbol":"MSFT","datatype":"json","output_size":"compact","apikey":"XXXXXXYYYYYYYZZZZZZZ"}

response = requests.request("GET", url,params=querystring)

print(response.text)

取得結果

Python3.7.3の実行時の画面。
5分毎の株価が取得できました。

image.png

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

【2025年の崖】経産省の「DXレポート2」が刊行されたので読んでみた

「2025年の崖」というキャッチーな用語などおよそ政府の刊行物とは思えないほどキレのある文面で話題になった経済産業省のDXレポート(@2018年)ですが、昨年の暮れに「DXレポート2」が刊行されていたのでそのレポートです

なお、DXレポートについてご存知ない方のために抜粋をすると、DXレポートはこういうものです

「2018 年に公開した DX レポートにおいては、複雑化・ブラックボックス化した既存システムを解消できず DX が実現できない場合、デジタル競争の敗者になってしまうだけでなく、多額の経済損失が生じるとして警鐘を鳴らし(2025 年の崖)、この問題に対応するため、2025 年までに集中的にシステム刷新を実施する必要があると指摘した」

結構「2025年の崖」っていう言葉が話題になったんですよね。
昨年の12月29日に刊行されたDXレポート2もとても面白かったので、ITに携わる人はぜひ知っておいて良いことだと思ったのでQiitaに載せさせていただきました

各ユーザー企業におけるIT活用の指針に加えて、ベンダー企業のあるべき姿などかなり突っ込んだ内容となっており、前回にもましてキレのある文章で読み応えバッチリでした。「2020年の崖」に引くも劣らない名言揃いでしたので、章ごとにまとめていきたいと思います。

オリジナル

経産省のHPにあります。なるべく内容を損なわないようにしましたが、ぜひソースを当たっていただくといいと思います。

https://www.meti.go.jp/press/2020/12/20201228004/20201228004.html

エグゼクティブサマリ

それでは、まずは冒頭の「エグゼクティブサマリ」から追ってます。
「エグゼクティブサマリ」という名前に負けず中身も迫真に迫るものがありました

まず、2018年のDXレポートでDXによる変革の警鐘を鳴らしたにも関わらずなかなか取り組みが進まないことを受けて下記のように断じます。

  • 実に全体の9割以上の企業が DX にまったく取り組めていない(DX 未着手企業)レベルか、散発的な実施に留まっている(DX 途上企業)状況であることが明らかになった。
  • 我が国企業全体における DX への取り組みは全く不十分なレベルにあると認識せざるを得ない と断じます。

そして、結構大企業に勤めている人はニヤリとしてしまうかもしれませんが、それに対してこのようにコメントします

  • DX =「レガシーシステムの刷新」などの本質ではない解釈が是となっていた
  • DX の本質とは単にレガシーなシステムを刷新すると言ったことに留まるのではなく、事業環境の変化に迅速に適応する能力を身につけること、そしてその中で企業文化変革することにあると考えられる

最後、コロナ禍に言及した後、このようにサマリーを締めくくっています。

  • 人々の固定観念が変化している今こそ「2025年の壁」問題の対処に向けて、企業文化を変革するある意味絶好(最後)の機会である

いや、コロナ禍に言及して「これが絶好で最後の機会」という部分が迫真に迫るものがありますね。それでは全体構成を紹介の後、本文を細かく見ていきます。

全体構成と読みどころ

ppt形式のサマリーとWord形式のレポートがあるのですが、全体の構成はこのようになっております(pptのサマリから転載)。

スクリーンショット 2021-01-13 20.55.17.jpg

個人的には、読みどころは下記だと思いました。

  • コロナ禍で表出した本質的な課題
  • 企業の目指すべき事業変革の方向性
  • ベンダー企業の目指すべき変革の方向性
  • 企業の経営・戦略の変革の方向性について、コロナ禍を契機に企業が直ちに取り組むべきもの
  • DX を進めるための短期的、中長期的な対応
  • 変革を加速するための政府の取組

それでは、それぞれについて抜粋する形で紹介していきます。

2章:コロナ禍で表出した本質的な課題

1章はこれまでの部分で説明したので2章からの紹介です。結構大きな題目を掲げて「コロナ禍で表出した本質的な課題」とありますが、いったい何なのでしょうか
まず、2020年を下記のように振り返ります。

  • 2020 年初頭からの新型コロナウイルスの世界的な感染拡大により、企業は「感染拡大を防ぎ顧客・従業員の生命を守りながら、いかに事業を継続するか」という対応を否応なしに求められることとなった

そして、テレワークの増加や新しいデジタル技術を活用した楽しみが人々の中で広まりつつあることを踏まえて
「人々は新たな価値の重要性に気付き、コロナ禍において新しいサービスを大いに利用し、順応している」
と国民を評価します。
しかし、それに追いつける企業と追いつけない企業がいることを記載した上でこのように断じます。

「ビジネスにおける価値創出の中心は急速にデジタル空間へ移行しており、今すぐ企業文化を刷新しビジネスを変革できない企業は、デジタル競争の敗者としての道を歩むことになるであろう
「そして、デジタル技術によるサービスを提供するベンダー企業も、受託開発型の既存のビジネスモデルではこのような変革に対応できないことを認識すべき」

これ政府の刊行物ぽくないですよね、?? そのように断じたのち、目指すべき方向についてテーマが移ります。

3章:企業の目指すべき方向性

3章は「デジタル企業の姿と産業の変革」という章で、ユーザ企業とベンダー企業がそれぞれ何を目指すべきかということを短期、中期長期の視点から分析しています。そして、前段でこのように名言が飛び出します。

  • ビジネスにおける価値創出の源泉はデジタルの領域に移行しつつあり、この流れはコロナ禍が終息した後も元には戻らない
  • 周囲の環境が変わっているにもかかわらず、これまで続けてきた業務形態やビジネスモデルは所与のものであるという固定観念に囚われてしまうと、抜本的な変革を実現することはできない

そして、ベンダー企業の目指すべき方向に章は進みます。

ベンダー企業の目指すべき方向性

  • 価値創造型のビジネスにおいては、ユーザー企業は絶えず変化する顧客のニーズに対応するために自社の IT システムを迅速に更新し続ける必要がある。そのためには、最もニーズの高い機能を迅速に開発し,フィードバックしながら変化に迅速に対応できるアジャイル型に開発を変革しなければ変化の速さに対応できない
  • 従来のウォーターフォール開発による受託開発型のビジネスに固執するベンダー企業は、今後ユーザー企業のニーズ・スピード感に応えられなくなる

そして問題点を指摘した後に目指すべき方向を論じます。

  • 顧客や社会の課題を正確にとらえるために、ベンダー企業はユーザー企業と DX を一体的に推進する共創的パートナーとなっていくことが求められる

なぜなら、その心は、

「米国では、システム開発をユーザー企業で行う等、ベンダー企業との分野の境目がなくなる形で変化が加速している。しかし、わが国では IT 人材がベンダー企業に偏り、雇用環境も米国とは異なる」ためです。したがって「デジタル社会における将来のベンダー企業には、顧客企業と自社の DX をともに進めていくことが求められる」からです。

以上のことはppt形式サマリーのP9を見れば綺麗にまとまっていました。

スクリーンショット 2021-01-13 21.13.54.jpg

そして次にユーザー企業を含む全体の話です。ユーザー企業はどうすればいいのでしょうか。

企業の経営・戦略の変革の方向性

短期、中長期にわけて章立てがありましたが、まずは短期の部分です。これは比較的内容が複雑なのでサマリにまとまっているものを転載させていただきます。政府刊行物のため転載が自由ということですので。

スクリーンショット 2021-01-13 21.23.38.jpg

スクリーンショット 2021-01-13 21.24.24.jpg

以下、各ポイントについての詳細です

DX推進に向けた関係者間の共通理解の形成

まず、DX推進に向けた関係者間の共通理解の形成が短期的にしなければならないことですよと言っているわけですが、これは前提として下記の2点があることを踏まえて

  • DX の推進にあたっては、経営層、事業部門、IT 部門が協働してビジネス変革に向けたコンセプトを描いていく必要がある
  • DX を推進する関係者の間で基礎的な共通理解を初めに形成することが必要

具体的には下記の方向を示しています。

経営層の課題をデータとデジタル技術を活用していかに解決していくかという視点に対しては、経営層や事業部門がアイデアを提示し、デジタルを活用することで可能となるまったく新たなビジネスを模索するという視点に対してはIT 部門がアイデアを提示し、仮説検証のプロセスを推進していくこと

そして最後にとても(!)いいことが書いてあります。

関係者間での協働を促すためにも、アジャイルマインド(俊敏に適応し続ける精神)や、心理的安全性を確保すること(失敗を恐れない・失敗を減点としないマインドを大切にする雰囲気づくり)が求められる

アジャイルマインドで心理的安全、いいですよね。。!

CIO/CDXO の役割・権限等の明確化

その他、短期的にやらないといけないこととしてCIO/CDXO の役割・権限等の明確化もあります。これは抜粋だけで。

  • CIO/CDXO がどのような役割・権限を担うべきか明確にした上で、これに基づき、DX を推進するための適切な人材が配置されるようにするべき
  • 適切なリーダーシップが欠如していると IT 部門が事業部門の現行業務の支援に留まり、業務プロセスが個別最適で縦割りとなってしまうため、DX の目標である事業変革を妨げる
  • デジタル化に係る投資を行うためには、事業部門の業務プロセスの見直しを含めた IT 投資の効率化にとどまらず、場合によっては不要となる業務プロセスと対応する IT システムの廃止・廃棄にまでつなげることが必要

なるほど。

遠隔でのコラボレーションを可能とするインフラ整備

短期的にやること3つ目です。

  • 新型コロナウイルスの感染を防止しながら事業を継続するためのツールとして、リモートワークを実現する IT インフラの整備が急速に進んでいる
  • こうした遠隔でのコラボレーションを可能とするインフラは感染防止の観点にとどまらず、今後のイノベーション創出のインフラとなる可能性がある

業務プロセスの再設計

4つ目。

  • 社会や企業においてこれまで当たり前のこととされていた業務プロセスの中には、前例を踏襲しているだけで実は見直しによって効率化可能なものや、過去の検討の結果積み重ねられてきた個別ルールによりかえって非効率となっているものが潜んでいる可能性がある
  • 「人が作業することを前提とした業務プロセス」を、デジタルを前提とし、かつ顧客起点で見直しを行うことにより大幅な生産性向上や新たな価値創造が期待

最後の部分もポイントです。

  • 業務プロセスの見直しを一度実施したとしても、そこで見直しの活動を停止してしまえば業務プロセスがレガシー化してしまう
  • 業務プロセスが顧客への価値創出に寄与しているか否かという視点をもち、恒常的な見直しが求められる

いいこと言いますよね。

中長期的な対応

以上が短期的な対応で、中長期的な対応についてです。まずはppt形式のサマリーを転載させていただきます。これを見れば概ねわかると思います。

スクリーンショット 2021-01-13 21.41.04.jpg

スクリーンショット 2021-01-13 21.41.28.jpg

スクリーンショット 2021-01-13 21.41.42.jpg

スクリーンショット 2021-01-13 21.41.48.jpg

スクリーンショット 2021-01-13 21.41.55.jpg

長期的に実施することについてもポイントを抜粋していきます。

デジタルプラットフォームの形成

中長期的にやらなければならないこととして、まずデジタルプラットフォームの形成があると言っています。

  • 企業は、協調領域については自前主義を排し、経営トップのリーダーシップの下、業務プロセスの標準化を進めることで SaaS、パッケージソフトウェアを活用し、貴重な IT 投資の予算や従事する人材の投入を抑制すべき
  • IT 投資の効果を高めるために、業界内の他社と協調領域を形成して共通プラットフォーム化することも検討すべき
  • 共通プラットフォームは、特定業界における協調領域をプラットフォーム化した業界プラットフォームや、特定の地域における社会課題の解決のための地域プラットフォーム等が想定
  • こうした共通プラットフォームによって生み出される個社を超えたつながりは、社会課題の迅速な解決と、新たな価値の提供を可能とするため、デジタル社会の重要な基盤となる

変化対応力の高い IT システムを構築するために

中長期的にやらなければならないことの2つ目は、変化対応力の高いIT システムを構築するということのようです。

  • デジタル時代の特徴として、顧客や社会との接点(Engagement)を通して顧客や社会の課題を発見し、解決することで新たな価値提案を行うためのシステム、すなわち、SoE(Systems of Engagement)の領域が広がっている
  • スモールスタートで迅速に仮説としての製品・サービスを市場に提示し、データドリブンで仮説の検証を実施するとともに、その結果を用いて製品・サービスの改善へとつなげる、というサイクルを繰り返すことで、より良い価値提案が可能となる
  • SoE の領域において、大規模ソフトウェアを外部に開発委託することは、これまでの受発注形態では対応が困難
  • 大規模なソフトウェア開発を一括発注し長期間をかけて開発するのではなく、アジャイルな開発体制を社内に構築し、市場の変化をとらえながら小規模な開発を繰り返すべき

ベンダー企業の事業変革とユーザー企業とベンダー企業との新たな関係

中長期の3つ目です。

  • 今後、大規模な受託開発は減少していく
  • 今後、ユーザー企業において DX が進展すると、受託開発の開発規模や案件数が減少するとともに、アジャイル開発による内製が主流になる
  • しかし、内製化する過程で必要となるアジャイル開発の考え方や、クラウドネイティブな開発技術等について、ユーザー企業の内部人材ではすぐに対応できないことが多いため、ベンダー企業が内製開発へ移行するための支援や、伴走しながらスキル移転することに対するニーズが高まる
  • ベンダー企業はこうした事業機会を顧客企業への客先常駐ビジネスとするのではなく、対等なパートナーシップを体現できる拠点において、ユーザー企業とアジャイルの考え方を共有_しながらチームの能力を育て(共育)、内製開発を協力して実践する(共創)べき

ジョブ型人事制度の拡大とDX 人材の確保

中長期4つ目。

  • DX を推進するために必要となる人材については(外部のベンダー企業に任せるのではなく)企業が自ら確保するべき
  • DX の推進においては、企業が市場に対して提案する価値を現実のシステムへと落とし込む技術者の役割が極めて重要
  • 副業・兼業を行いやすくし、人材流動や、社員が多様な価値観と触れる環境を整えることも重要

以上までが4章です。5章の政府の取り組みについてです。

政府の政策の方向性

5章の政府の取り組みについてです。非常に多くの良い取り組みをしてくれているんだと感じました。抜粋はしませんが、ぜひオリジナルでご一読いただくといいと思いました。

以下政府の実施内容です

共通理解形成のためのポイント集の策定,CIO/CDXO の役割再定義,DX 成功パターンの策定,DX 推進状況の把握,デジタルプラットフォームの形成,産業変革の制度的支援,ユーザー企業とベンダー企業の共創の推進,デジタル技術を活用するビジネスモデル変革の支援,研究開発に対する支援,DX 人材確保のためのリスキル・流動化環境の整備ということです。

結構いいことやってるんだなと思いました

最後に

DXレポート自身は6章目もあり、そこでは2018年のDXレポートでの指摘とその後の政策展開を振り返っていますが、今回はDXレポート2の振り返りなのでこちらは割愛させていただきます。最後に1パラグラムだけ6章から抜粋し、終わりたいと思います。

企業の行動変容が進まない理由は、生活習慣病のアナロジーで理解が可能である。誰しも、一般論としてメタボリックシンドロームの状態よりも痩せていたほうが良いことは理解している上、生活習慣病のリスクについても理解しているが、自分自身は健康だと信じている。企業の DX についても同様で、DX が必要だと理解はしていながらも、行動を変容できていない企業は多い

最後はメタボに掛けてわかりやすく説明いただきました。


この記事が役に立ったと思ったらLGTMお願いいたします:thumbsup:

個人的にはこの流れだと、Python、JavaScript、クラウドがいま以上に熱くなると思いました。

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

pip install colourで色の操作をする

はじめに

plotlyやpyqtgraphはカラーパレット等が無い(or 少ない?)のでmatplotlibと比べて色を指定するのに苦労します。
今回は色のグラデーションなどを簡単に作成できるモジュールがあったので紹介します。

環境

Mac OS
Python 3.8.5
colour 0.1.5

pip install colour

詳細

作成

全て赤になります。

from colour import Color

Color('red')
Color(red=1)
Color('#f00')
Color('#ff0000')
Color(rgb=(1, 0, 0))  # 0~1
Color(hsl=(0, 1, 0.5))  # 0~1
Color(Color('red'))

値取得

from colour import Color

c = Color('red')  # <class 'colour.Color'>

cの値を取得します。

RGB

rgb = c.get_rgb()  # (1.0, 0.0, 0.0), tuple
rgb_hex = c.get_hex()  # #f00, str
rgb_hex_long = c.get_hex_l()  # #ff0000, str

HSL

hsl = c.get_hsl()  # hsl色空間 (0.0, 1.0, 0.5), tuple
hue = c.get_hue()  # 色相 0.0, float
saturation = c.get_saturation()  # 彩度 1.0, float
luminance = c.get_luminance()  # 輝度 0.5, float

単色

red = c.get_red()  # 1.0, float
blue = c.get_blue()  # 0.0, float
green = c.get_green()  # 0.0, float

値変更

c = Color('red')  # c = red

cの値を書き換えます

c.set_blue(1)  # c = magenta, set_red(), set_green()もある
c.set_saturation(0.5)  # 彩度の変更, c = #bf40bf, 0~1の間
c.set_luminance(0.2)  # 輝度の変更, c = #4c194c, 0~1の間
# 値が上書きされる
c.set_rgb((1, 0, 0))
c.set_hsl((0, 1, 0.5))

判定

Color('red') == Color('blue')
False
Color('red') == Color('red')
True
Color('red') != Color('blue')
True
Color('red') != Color('red')
False

グラデーション

始めの色と終わりの色, 分割数を指定します

# red - blue間
red = Color('red')
blue = Color('blue')
# 5分割 [<Color red>, <Color yellow>, <Color lime>, <Color cyan>, <Color blue>]
red_blue = list(red.range_to(blue, 5))

# black - white間
black = Color('black')
white = Color('white')
# 6分割 [<Color black>, <Color #333>, <Color #666>, <Color #999>, <Color #ccc>, <Color white>]
black_white = list(black.range_to(white, 6))

使用例

matplotlibやseabornは元々たくさん色が指定できるのであまり使わないかもしれません。
plotlyには結構使えると思います。

スクリーンショット 2021-01-13 22.01.20.png

from colour import Color
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

red = Color('red')
blue = Color('blue')
red_blue = list(red.range_to(blue, 100))

for num, color in enumerate(red_blue, 1):
    ax.plot([i for i in range(10)],
            [i * num for i in range(10)],
            c=color.get_hex_l())

plt.show()

参考

colour · PyPI

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

Raspberry PiにOpenCV-Pythonをインストールした

概要

とにかく,安全にOpenCVをインストールしたいので公式の方法でインストールした.
https://docs.opencv.org/master/d2/de6/tutorial_py_setup_in_ubuntu.html

環境

ハード

Raspberry Pi 3 B+

OS

$ cat /etc/debian_version
10.7
$ cat /etc/os-release 
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

インストール

$ sudo apt-get install python-opencv

実際はこれだけで終わりだそうです.

>>> import cv2
>>> print(cv2.__version__)
4.5.1-dev

Python2ではインストール成功しました.

ただ,Python3だと,

>>> import cv2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'cv2’
>>> 

見つからないそうです.

パスを通そうとしてもcv2.soファイルも見つからないので,
ソースからビルドしてみようと思います.

Building OpenCV from source

とりあえず必要なパッケージインストール

まず,ビルドに必要な彼らをインストール.

sudo apt-get install cmake
sudo apt-get install gcc g++

Python2,3両方欲しいので

sudo apt-get install python-dev python-numpy
sudo apt-get install python3-dev python3-numpy

GTKの関連をインストール
GTKって何でしょうか...→GUIアプリを作るときに使うものだそうです.

sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev
sudo apt-get install libgtk2.0-dev
sudo apt-get install libgtk-3-dev

とりあえずここまででビルドまでできるみたいです.
次のやつは予備的にパッケージを更新してるだけみたいです.

予備的にインストール(やらなくてもよい)

sudo apt-get install libpng-dev
sudo apt-get install libjpeg-dev
sudo apt-get install libopenexr-dev
sudo apt-get install libtiff-dev
sudo apt-get install libwebp-dev

OpenCVをダウンロード

Gitからダウンロードしましょう.

$ sudo apt-get install git
$ git clone https://github.com/opencv/opencv.git

長かったですね.
ビルドのディレクトリを作ります.
名前は取りあえずbuildで

$ cd opencv
$ mkdir build
$ cd build

インストール

続いてインストールです.

cmake ../

出力の終わりの方です.

--   Python 2:
--     Interpreter:                 /usr/bin/python2.7 (ver 2.7.16)
--     Libraries:                   /usr/lib/arm-linux-gnueabihf/libpython2.7.so (ver 2.7.16)
--     numpy:                       /usr/lib/python2.7/dist-packages/numpy/core/include (ver 1.16.2)
--     install path:                lib/python2.7/dist-packages/cv2/python-2.7
-- 
--   Python 3:
--     Interpreter:                 /usr/bin/python3 (ver 3.7.3)
--     Libraries:                   /usr/lib/arm-linux-gnueabihf/libpython3.7m.so (ver 3.7.3)
--     numpy:                       /usr/lib/python3/dist-packages/numpy/core/include (ver 1.16.2)
--     install path:                lib/python3.7/dist-packages/cv2/python-3.7

これだけでインストールを構成できるなんて便利ですよね.
何が起こってるのかはまた勉強します.

最後にビルド,インストールです.

$ make
# sudo make install

めっっっっっっちゃ時間かかりました.5時間くらい
もうしたくない!!!!!!!

確認

$ python3
>>> import cv2
>>> print(cv2.__version__)
4.5.1-dev

できた!

最後に

適当にやってしまったため,OpenCVを最新のdevバージョンにしてしまいました.
バージョンを変えるのにまたビルドしなかんので,超めんどくさいです.

皆さんは気をつけてね.

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

基礎編:木構造の全探索(DFS/BFS)

自己紹介

初めまして。
本誌を訪問頂き有難うございます。
私は半導体(FPGA/DCDC)の FAE(技術営業)を 12年やっている者です。
技術営業は、営業と一緒に製品の売り込み以外にも
技術サポート(製品に関する技術的な質問の対応)と不具合対応(デバイス不良)が業務です。
デバイス不良との名目でデバックに付き合わされることも多々あるので、
ソフト/ハード面の両方をサポートする、ほぼ何でも屋さんです。

そんな私が趣味で始めた python programming。
業務の FPGA では Verilog,VHDL でプログラム言語を扱っていたので
python は趣味の延長として楽しく触っていますが、開発屋さんでは無いので素人です。
ちょっと力試しに leetcode を始めてみました。

早速直面した壁

自慢ではありませんが、私は素人です。基礎がありません。
そんな私が leetcode に挑戦する場合、行動方針は
0からアルゴリズムを頭の中で組み立ててプログラムを書く!!
この一点突破のみです。

この行動方針には思考力を養うメリットがあるかもしれませんが
デメリットの方が多いように感じます。私が思うデメリットは以下です。
1.自分流で 0 から組み立てようとするので、とにかく時間が掛かる!
2.自分流はとにかく、無駄な記述が多い
3.記述が多くなるとミスが増える
4.脳がトップギアに入っていない場合、過去に解けた問題も解けない場合がある

つまり、処理時間が多く、そのアウトプットが安定しない。
全くいいことがありません。

基礎をしっかり学び、吸収した基本形をベースに問題に挑む事で
処理時間の短縮と Output の安定化を図った私なりのチャレンジをすることにしました。
本誌は、その記録です。
勿論、愛のある突っ込みは大歓迎です。

基礎の定義

今回は Leetcode:Explorer Binary Treeを基礎とします。
探索の練習には、復習も兼ねて作った、以下の二分探索木をベースに話を進めていきます(node 削除は割愛)。

BinTree.py
#二分探索木には関係ない記述ですが、
#探索時に deque() を使うので予め記載
from collections import deque 

#node の定義
class node:
    def __init__(self,val,left=None,right=None):
        self.val = val
        self.left = left
        self.right = right

class BinTree:
    #self.root をベースにツリーを作る、初期値 None
    def __init__(self):
        self.root = None

    def add(self,val):
       #以下の def add_node を読み飛ばして、下部のコメントに GO!
        def add_node(root,val):
            if val < root.val:
                if not root.left:
                    root.left = node(val)
                else:
                    add_node(root.left,val)
            if val > root.val:
                if not root.right:
                    root.right = node(val)
                else:
                    add_node(root.right,val)

       #def add() で最初に実行するプログラム。
       #最初に self.root が None か否かを確認し
       #None であれば node を call して val を代入
        if not self.root:
            self.root = node(val)
       #not None であれば add_node を call
        else:
            add_node(self.root,val)        

####Tree_image#####
#         4       #
#      /     \    #
#     2       6   #
#    / \     /    # 
#   1   3   5     # 
###################

T = BinTree()  #self.root の作成
T.add(4)
T.add(2)
T.add(3)
T.add(1)
T.add(6)
T.add(5)

簡単に補足すると、二分探索木として成立させるためには
def add() の中に埋め込んだ def add_node() の記述がミソだと思います。
ノードとしての分岐の度に、分岐元の value より大きいのか、小さいかを
判断する必要があるからです。

探索方法

leetcode によると探索方法は DFS: Pre-order/ In-order/ Post-order, BFS が挙げられています。
下図は、それぞれのアプローチのイメージです。各ノードに振られた番号は探索の順番です。
図1.PNG
この図を見ただけでプログラムを思い描くのは至難です。
本誌では例題から基本形を理解し、覚えてしまいます。
その基本形を組み替えて応用できないか練習する方針で進めます。

例題1:DFS:Preorder/Inorder/Postorder

二分探索木を探索し、各ノードの値を返してください。

Answer_Image.py
Input: root = [4,2,6,1,3,5]
Output:4      #-----Tree_image---#
       2      #         4        #
       1      #      /     \     #
       3      #     2       6    #
       6      #    / \     /     #
       5      #   1   3   5      #
              #------------------#

前述していますが、これは例題なので分からなくて OK です。
以下の流れで行きましょう。

1.いきなり回答を読む
2.動作をイメージする
3.別途用意した GIF で答え合わせ

では早速、回答の登場です。

TreeDFS.py
def DFS(self):                      
    def pre_order(node):            
        print(node.val)             
        if node.left:pre_order(node.left)    
        if node.right:pre_order(node.right)   

    def in_order(node):
        if node.left: in_order(node.left)
        print(node.val)
        if node.right: in_order(node.right)

    def post_order(node):
        if node.left: post_order(node.left)
        if node.right: post_order(node.right)
        print(node.val)
    #-----------------------#
    #--select DFS approach--#
    #-----------------------#
    #1 preorder
    return pre_order(self.root)

    #2 inorder
    #return in_order(self.root)

    #3 postorder
    #return post_order(self.root)

ご覧のとおり記述に差分は殆どないです。
読みながらイメージを作ってみましょう、間違っても良いと思います。

作ったイメージに漏れが無いか確認していきましょう。
以下の GIF はクリックすると拡大するので分かり易くなります。
preorder.gif
inorder.gif
postorder.gif
print() で全て表示が出来たら終わりではなく、
最後まで処理を追いかけて完結することを確認してみると理解が深まります。

念のため別アプローチも検討してみましたがスマートな記述は他にあると思います。
今はこのレベルでの共有で申し訳ないです。

TreeDFS.py
#Preorder
def DFS(self):
   buff = [self.root]
   while buff:
       nums = buff.pop()
       print(nums.val)
       if nums.right: buff.append(nums.right)
       if nums.left: buff.append(nums.left)

"""
#Inorder
def DFS(self):
   B0 = deque([self.root])
   B1 = []

   root = B0.popleft()
   while root or B1:
       while root:
           B1.append(root)
           root = root.left
       root = B1.pop()
       print(root.val)
       root = root.right
"""

"""
#Postorder
def DFS(self):
   buff,ans=deque([self.root]),deque([])
   while buff:
       nums = buff.pop()
       ans.appendleft(nums.val)
       if nums.left:buff.append(nums.left)
       if nums.right:buff.append(nums.right)
   for i in range(len(ans)):
       print(ans[i])
"""

準備が出来たので Leetcode にチャレンジします。

演習1:Binary Tree Preorder/Inorder/Postorder

二分探索木を Preorder で探索し、その値を返してください。

Answer_Image.py
Input: root = [4,2,6,1,3,5]
Output: [4,2,1,3,6,5]

例題1 で解いた(or 覚えた)基本形で何とかしましょう。
しかし、例題の丸コピーでは演習を解くのは難しいと思います。

再帰処理で解く場合、関数の冒頭から入って、
return で再度関数の冒頭に戻る場合、出力用の [ ] の定義が邪魔になります。
例えば、ans = [ ] と定義した場合、再帰的に冒頭に戻ると
必ず ans = [ ] と初期化してしまうので、計算値を蓄積できません。

今の自分では対応策が見当たらなかったので、
今回は def の中に def を埋め込んで検討してみました。

Preorder_list.py
def DFS(self):
   ans = []
   def preorder(node,ans):
      ans.append(node.val)
      if node.left: preorder(node.left,ans)
      if node.right: preorder(node.right,ans)
      return ans
   return preorder(self.root,ans)

前述にあるように DFS の中に preorder を用意して、
preorder で再帰処理させています。
必ず、node.val を格納した ans を次の階層に持っていくために、
preorder(node.left,ans) としています。

御存知の方も多いと思いますが、実はこの ans は省略できます。
ほとんど変わりませんが、以下の記述でも同様の output が得られます。

Preorder_list.py
def DFS(self):
   ans = []
   def preorder(node):
      ans.append(node.val)
      if node.left: preorder(node.left)
      if node.right: preorder(node.right)
      return ans

   def inorder(node):
      if node.left: preorder(node.left)
      ans.append(node.val)
      if node.right: preorder(node.right)
      return ans

   def postorder(node):
      if node.left: preorder(node.left)
      if node.right: preorder(node.right)
      ans.append(node.val)
      return ans

    #-----------------------#
    #--select DFS approach--#
    #-----------------------#
    #1 preorder
    return pre_order(self.root)

    #2 inorder
    #return in_order(self.root)

    #3 postorder
    #return post_order(self.root)

もう一つのポイントは return です。
node.val を追加した ans を呼び出し元に通知しないと、
全ての要素を拾って最終 output が出来ません。
念のため私の説明に嘘が無いか、GIF を作って確認してみました(笑)。
preorder_list.gif
Inorder/Postorder も基本は同じなので GIF は不要だと思いますが、リクエストがあれば検討いたします。

例題2 BFS

二分探索木を探索し、各ノードの値を返してください。

Answer_Image.py
Input: root = [4,2,6,1,3,5]
Output:4      #-----Tree_image---#
       2      #         4        #
       6      #      /     \     #
       1      #     2       6    #
       3      #    / \     /     #
       5      #   1   3   5      #
              #------------------#

例題1 と同じ問題で申し訳ないです。
DFS と BFS を分けたのは基本アプローチが全く違うため、
混乱を避けるために分けました。
まずは、BFS のイメージを共有致します。
bfs_image.gif
このイメージから、各層毎にデータをまとめてバッファすることで BFS を実現していることが分かります。
その実現方法ですが、こちらです。

TreeBFS.py
def BFS(self):
    q = [self.root]
    def left2right(q):
        laylength = len(q)
        for _ in range(laylength):
            nums = q.pop(0)
            print(nums.val)
            if nums.left:q.append(nums.left)
            if nums.right:q.append(nums.right)
        if laylength > 0:left2right(q)
    return left2right(q)

コードの通りではあるのですが、
取り出した nums.val に含まれる nums.left / nums.right を q にバッファします。
上記の作業をlaylength(=len(q))回、繰り返すので、
結果的に同一階層のデータをまとめて q にバッファすることが出来ます。
勿論、q には他の層のデータが混ざる時もありますが、
for 分は laylength 回しか for を回さないので、
他の階層のデータを余分に取り出すようなマネはしません。
bfs.gif

演習2 Binary Tree Level Order Traversal

二分探索木を探索し、各層でリストしてください。

Answer_Image.py
Input: root = [4,2,6,1,3,5]
Output:[          #-----Tree_image---#
        [4],      #         4        #
        [2,6],    #      /     \     #
        [1,3,5],  #     2       6    #
       ]          #    / \     /     #
                  #   1   3   5      #
                  #------------------#

前述の例題では、すべての要素を q にバッファしますが、
各層毎に要素を根こそぎバッファして output しているので BFS が成立していました。
たとえばですが、for で回す処理を 1 階層目、2 階層目、3階層目..etc とナンバリング出来れば、
各層ごとに append() でききるので問題が解決できるのではないでしょうか。
そのアイディアを形にしたのが、こちらです。

TreeBFS.py
def BFS(self):
    q = deque([self.root])
    ans = []
    level = 0
    def helper(q,level):
         ans.append([])
         laylength = len(q)
         for _ in range(laylength):
             nums = q.popleft()
             ans[level].append(nums.val)
             if nums.left:q.append(nums.left)
             if nums.right:q.append(nums.right)
         if len(q) > 0:helper(q,level+1)
         return ans
     return helper(q,level)

#def BFS(self):
#        q = deque([self.root])
#        level = 0
#        ans = []
#        while q:
#            ans.append([])
#            laylength = len(q)
#            for _ in range(laylength):
#                nums = q.popleft()
#                ans[level].append(nums.val)
#                if nums.left:q.append(nums.left)
#                if nums.right:q.append(nums.right)
#            level += 1
#        return ans

コメントアウトで別アプローチを載せましたが、個人的には再帰処理じゃないほうが、
この件に関してはシンプルに書け気がしています。

上記以外にもアプローチはあります。
DFS version を試してみました。

TreeDFS.py
def DFS(self):
    ans = []
    level = 0                      
    def pre_order(node,level):            
        if len(ans)==level:
            ans.append([])
        ans[level].append(node.val)            
        if node.left:pre_order(node.left,level+1)    
        if node.right:pre_order(node.right,level+1)  
        return ans 

    def in_order(node,level):
        if len(ans)==level:
            ans.append([])
        if node.left: in_order(node.left,level+1)
        ans[level].append(node.val) 
        if node.right: in_order(node.right,level+1)
        return ans

    def post_order(node,level):
        if len(ans)==level:
            ans.append([])
        if node.left: post_order(node.left,level+1)
        if node.right: post_order(node.right,level+1)
        ans[level].append(node.val)
        return ans

    #-----------------------#
    #--select DFS approach--#
    #-----------------------#
    #1 preorder
    return pre_order(self.root,level)

    #2 inorder
    #return in_order(self.root,level)

    #3 postorder
    #return post_order(self.root,level)

BFS を解いた後だと、どうしても以下のように解きたがってしまいます。

DFS_badcode.py
    def DFS(self):
        q = [self.root]
        ans = []
        level = 0
        def preorder(q,level):
            if len(ans)==level:
                ans.append([])
            nums = q.pop(0)
            ans[level].append(nums.val)
            if nums.left:
               q.append(nums.left)
               preorder(q,level+1)
            if nums.right:
               q.append(nums.right)
               preorder(q,level+1)
            return ans
        return preorder(q,level)

このコードは BFS でイメージしたような"バッファ"を一旦頭から
切り離して考えられなかった結果です。
DFS はデータ単体で考えても勝手にツリー全体を探索してくれますので、
バッファのイメージを捨てて使っても問題ないのです、良い勉強になりました。
今回は一旦ここまでとします。

もっと色々な問題に挑戦したり、
処理時間で議論したり、やりたいことはまだ色々とあります。
ひっそりと楽しみながら進めますので
気長に update をお待ちください m(_ _)m

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

6ボールパズルをpythonで実装:くそコード注意

はじめに

6ボールパズルをpython上で実装しようと思ってやってたら,くそコードが出来上がりました.
飽きたのでネットの海に放流して終了しよう・・・

操作方法
a:右60deg回転
b:左60deg回転
j:左移動
l:右移動
k:落下開始

参考動画
https://www.youtube.com/watch?v=Q6As1gRT6fE&t=660s

運動シミュレーションを行っている感じがしますが,ちょっと難しいので,まったく同じものは作れそうにないです.

アルゴリズム

1.png
ボールに4方向a,b,c,dを定義します.

条件

  1. ボールは$b,c$にしか動かない
  2. $b$に動くには,$a,b$にボールがない
  3. $c$に動くには,$c,d$にボールがない
  4. $b,c$どちらにも動ける場合,$a,d$にあっても問題ない,動く方向は$b,c$ランダム

2.png
このような状態がちょっと難しいです.

探索方向
下の層から動けるかを確認し,動けなくなるまで動かしていく
3.png

データ構造

列の場所が,行の場所で0.5個分ずれるので,少々扱いにくい
いろいろ悩まないように次のように,番号をつける

4.png

赤,青,黄,紫,緑,空
の6種
空を0とする

19列,12行にする

落下-テストプログラム

----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 4, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 3, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[1, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]

初期状態がこんな感じの時,$4$は落下できます.

----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[4, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 3, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[1, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]

こうなるか,右に落ちるて

[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 3, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[1, ' ', 2, ' ', 4, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]

こうなるかです(ランダム).
結構いい感じの結果が得られて満足です.

import cv2          #   OpenCVライブラリ
import numpy as np      #   Numpyライブラリ
import random

def disp_ball(ball):
    print("----------------")

    for i in range(11,-1,-1):
        hyouji_retu = []
        if i%2 == 0:               #偶数行 偶数列
            for j in range(10):
                hyouji_retu.append(ball[i][j*2])
                if j != 9:
                    hyouji_retu.append(" ")

        if i%2 == 1:               #偶数行 偶数列
            for j in range(9):
                hyouji_retu.append(" ")
                hyouji_retu.append(ball[i][j*2 + 1])

        print(hyouji_retu)

ball=[[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],


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

"""
行偶数 : 列偶数
行奇数 : 列奇数
ball[行][列]
"""

ball[0][0]=1
ball[0][2]=2
ball[1][1]=3
ball[3][1]=4

disp_ball(ball)


"""
落下アルゴリズム
左下から 動けるか,方向はどうか確認する.
flag :True=動ける False =動けない
"""
loop = 0
while True:
    flag_moved = False

    for gyou in range(12):
        if gyou%2 == 0:
            for retu in range(0,19,2):       #0,2,4,::::18 を流す
                if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認

                    if retu == 0:
                        a_flag = False       #壁なので動けない
                    else:
                        if ball[gyou][retu-2]  != 0:
                            a_flag = False
                        else:
                            a_flag = True    #動ける

                    if retu == 18:
                        d_flag = False       #壁なので動けない
                    else:
                        if ball[gyou][retu+2]  != 0:
                            d_flag = False
                        else:
                            d_flag = True    #動ける

                    if retu == 0 or gyou == 0:
                        b_flag = False       #壁または床なので動けない
                    else:
                        if ball[gyou-1][retu-1]  != 0:
                            b_flag = False
                        else:
                            b_flag = True

                    if retu == 18 or gyou == 0:
                        c_flag = False       #壁または床なので動けない
                    else:
                        if ball[gyou-1][retu+1]  != 0:
                            c_flag = False
                        else:
                            c_flag = True

                    if b_flag == True and c_flag == False and a_flag == True:
                        #b 方向に動かす
                        ball[gyou-1][retu-1] = ball[gyou][retu]
                        ball[gyou][retu] = 0                       #動かした後は空にしておく
                        flag_moved = True
                        retu_moved = retu
                        gyou_moved = gyou
                        retu_moved_to = retu-1
                        gyou_moved_to = gyou-1
                        break

                    if c_flag == True and b_flag == False and d_flag == True:
                        #c 方向に動かす
                        ball[gyou-1][retu+1] = ball[gyou][retu]
                        ball[gyou][retu] = 0                       #動かした後は空にしておく
                        flag_moved = True
                        retu_moved = retu
                        gyou_moved = gyou
                        retu_moved_to = retu+1
                        gyou_moved_to = gyou-1
                        break

                    if b_flag == True and c_flag == True:
                        #b,cどちらかに動かす
                        hoge = random.randint(0,1)
                        if hoge == 0:
                            #b 方向に動かす
                            ball[gyou-1][retu-1] = ball[gyou][retu]
                            ball[gyou][retu] = 0                       #動かした後は空にしておく
                            flag_moved = True
                            retu_moved = retu
                            gyou_moved = gyou
                            retu_moved_to = retu-1
                            gyou_moved_to = gyou-1
                            break
                        else:
                             #c 方向に動かす
                            ball[gyou-1][retu+1] = ball[gyou][retu]
                            ball[gyou][retu] = 0                       #動かした後は空にしておく
                            flag_moved = True
                            retu_moved = retu
                            gyou_moved = gyou
                            retu_moved_to = retu+1
                            gyou_moved_to = gyou-1
                            break

        if gyou%2 == 1:  #奇数行
            for retu in range(1,18,2):   #1,3,5,::::17 を流す
                if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認
                    if retu == 1:
                        a_flag = False       #壁なので動けない
                    else:
                        if ball[gyou][retu-2]  != 0:
                            a_flag = False
                        else:
                            a_flag = True    #動ける

                    if retu == 17:
                        d_flag = False       #壁なので動けない
                    else:
                        if ball[gyou][retu+2]  != 0:
                            d_flag = False
                        else:
                            d_flag = True    #動ける

                    if gyou == 0:
                        b_flag = False       #壁または床なので動けない
                    else:
                        if ball[gyou-1][retu-1]  != 0:
                            b_flag = False
                        else:
                            b_flag = True

                    if gyou == 0:
                        c_flag = False       #壁または床なので動けない
                    else:
                        if ball[gyou-1][retu+1]  != 0:
                            c_flag = False
                        else:
                            c_flag = True

                    if b_flag == True and c_flag == False and a_flag == True:
                        #b 方向に動かす
                        ball[gyou-1][retu-1] = ball[gyou][retu]
                        ball[gyou][retu] = 0                       #動かした後は空にしておく
                        flag_moved = True
                        retu_moved = retu
                        gyou_moved = gyou
                        retu_moved_to = retu-1
                        gyou_moved_to = gyou-1
                        break

                    if c_flag == True and b_flag == False and d_flag == True:
                        #c 方向に動かす
                        ball[gyou-1][retu+1] = ball[gyou][retu]
                        ball[gyou][retu] = 0                       #動かした後は空にしておく
                        flag_moved = True
                        retu_moved = retu
                        gyou_moved = gyou
                        retu_moved_to = retu+1
                        gyou_moved_to = gyou-1
                        break

                    if b_flag == True and c_flag == True:
                        #b,cどちらかに動かす
                        hoge = random.randint(0,1)
                        if hoge == 0:
                            #b 方向に動かす
                            ball[gyou-1][retu-1] = ball[gyou][retu]
                            ball[gyou][retu] = 0                       #動かした後は空にしておく
                            flag_moved = True
                            retu_moved = retu
                            gyou_moved = gyou
                            retu_moved_to = retu-1
                            gyou_moved_to = gyou-1
                            break
                        else:
                             #c 方向に動かす
                            ball[gyou-1][retu+1] = ball[gyou][retu]
                            ball[gyou][retu] = 0                       #動かした後は空にしておく
                            flag_moved = True
                            retu_moved = retu
                            gyou_moved = gyou
                            retu_moved_to = retu+1
                            gyou_moved_to = gyou-1
                            break

            if flag_moved == True:
                break             #次の行には行かず,(0,0)から探索しなおす

    if flag_moved == False:  #一つも動かなかった
        break

    print(loop,"loop end flag",flag_moved)
    loop = loop + 1
    print(retu_moved,"retu gyou",gyou_moved)
    print(retu_moved_to,"retu to gyou",gyou_moved_to)
    print("-----------")
    disp_ball(ball)

print("-----result---------------------------------")
disp_ball(ball)           

def fall(ball):
    print("fall")   

そのほかのテストパターン

----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 2, ' ', 3, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[1, ' ', 0, ' ', 4, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
-----result---------------------------------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 2, ' ', 3, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[1, ' ', 0, ' ', 4, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]

ちゃんと$2,3$は落下していない

入力系

ボールセット(3個)をキーボード等で動かし,落下させた際
ボールセットがどこに落下されるか計算したいです.先ほどの説で述べた落下では

----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
0 loop end flag True
4 retu gyou 8
3 retu to gyou 7
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
1 loop end flag True
3 retu gyou 7
4 retu to gyou 6
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
2 loop end flag True
4 retu gyou 6
5 retu to gyou 5
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
3 loop end flag True
5 retu gyou 5
6 retu to gyou 4
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
4 loop end flag True
6 retu gyou 4
7 retu to gyou 3
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
5 loop end flag True
7 retu gyou 3
6 retu to gyou 2
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
6 loop end flag True
6 retu gyou 2
7 retu to gyou 1
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
7 loop end flag True
7 retu gyou 1
8 retu to gyou 0
-----------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
-----result---------------------------------
----------------
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]
[0, ' ', 0, ' ', 0, ' ', 0, ' ', 2, ' ', 0, ' ', 0, ' ', 0, ' ', 0, ' ', 0]

アルゴリズム上仕方ないですが,左右にうねうねしながら落下します.
このままでは使えないので,キーボードで入力した左右移動量でボールセットを動かし,ボールセット落下ボタンが入ったら,直線的に落下させ,3個のボールの内どれかが,すでにあるボールに衝突し,直線的に動けなくなるとボールセットが崩れて,そこからは先ほどの落下方式で処理させようと思います.

5.png
6.png
7.png

直線的落下時の判定ー逆三角

8.png
9.png

直線的落下時の判定ー三角

ちょっと,調べるべき場所が多いためめんどくさいが頑張ろう
10.png

ヘキサゴン・ピラミッドの検出

最後に検出方法を考える
原始的な方法しか思いつかなかったが

11.png

こんな感じで,開始点を走査して検出してみる

最終結果

すごい,くそコード書いてしまった.
飽きたのでや~めよ

import cv2          #   OpenCVライブラリ
import numpy as np      #   Numpyライブラリ
import copy
import random

windowName = "game_home"
cv2.namedWindow(windowName)

img = cv2.imread('BackGround.jpg' )
img_copy = copy.deepcopy(img)
cv2.imshow(windowName,img)

"""
home_width=700
home_height = 800
cv2.resizeWindow(windowName,home_width,home_height)
"""
home_height,home_width,chanel = img.shape



ball=[[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],
      [0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],[0,0,0,0,0,  0,0,0,0,0,  0,0,0,0,0,  0,0,0,0],


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

"""
ball[0][0]=1
ball[1][1]=2
ball[1][3]=3
ball[0][2]=4
ball[2][2]=5
ball[2][4]=6

ball[0][2]=4
ball[0][4]=4
ball[0][6]=4
ball[0][8]=4
ball[0][10]=4
ball[0][12]=4
ball[0][14]=3
ball[0][16]=2
ball[0][18]=1

#ball[8][4] = 2

"""

#色タプル 関数
def Color_ball(hoge):
    if hoge == 1:
        return (0,0,255)
    if hoge == 2:
        return (255,0,0)
    if hoge == 3:
        return (0,255,0)
    if hoge == 4:
        return (0,255,255)
    if hoge == 5:
        return (255,255,255)
    if hoge == 6:
        return (255,255,0)
    if hoge == 7:
        return (0,0,0)

    return (0,0,128)

flag_end = False
key = 100

color_num = 5         #色の数

next_ball = [random.randint(1,color_num+1),random.randint(1,color_num+1),random.randint(1,color_num+1)]
flag_triangle =False  #逆三角形:False
ball_x = 1            #逆三角 なら 一番下    三角なら,左下 の玉の座標
ball_y = 12
now_ball =[random.randint(1,color_num+1),random.randint(1,color_num+1),random.randint(1,color_num+1)]    
#逆三角なら 下から反時計回り, 三角なら 左下から反時計回り

disp_time = 100


while True:



    #ボール表示
    def x_y(home_height,x,y,origin):
        """
        座標系の変換をする,
        originは 左下原点,上yとして
        """
        x_return = origin[0] + x
        y_return = home_height -(origin[1]+y)

        return (x_return,y_return)


    def disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball):

        thick = -1        
        ball_size = 16  #半径

        #枠表示
        LD_edge = (50,50)  #座標方向がこれのみ,左下原点

        LU_edge = (LD_edge[0],home_height - LD_edge[1] - int(11*1.73205*ball_size)-2*ball_size)
        RD_edge = (LD_edge[0] + 20*ball_size,home_height - LD_edge[1])
        cv2.rectangle(img, LU_edge, RD_edge, (255, 0, 0))

        for gyou in range(12):
            if gyou%2 == 0:
                for retu in range(0,19,2):       #0,2,4,::::18 を流す
                    if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認
                        x = ball_size + ball_size *retu
                        y = ball_size + int(1.73205*ball_size*gyou)
                        cv2.circle(img,x_y(home_height,x,y,LD_edge), ball_size, Color_ball(ball[gyou][retu]),thickness=thick ) 


            if gyou%2 == 1:  #奇数行
                for retu in range(1,18,2):   #1,3,5,::::17 を流す
                    if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認
                        x = ball_size*2 + ball_size *(retu-1)
                        y = ball_size + int(1.73205*ball_size*gyou)
                        cv2.circle(img,x_y(home_height,x,y,LD_edge), ball_size, Color_ball(ball[gyou][retu]), thickness=thick) 



        #ボールセット表示
        if flag_triangle == True:  #三角
            x=(ball_x+1)*ball_size
            y=ball_size + int(1.73205*ball_size*ball_y)

            cv2.circle(img,x_y(home_height,x,y,LD_edge), ball_size, Color_ball(now_ball[0]), thickness=thick)
            cv2.circle(img,x_y(home_height,x+2*ball_size,y,LD_edge), ball_size, Color_ball(now_ball[1]), thickness=thick)
            cv2.circle(img,x_y(home_height,x+ball_size,y+int(1.73205*ball_size),LD_edge), ball_size, Color_ball(now_ball[2]), thickness=thick)

        else:#逆三角の時は 一番下基準
            x=(ball_x+1)*ball_size
            y=ball_size + int(1.73205*ball_size*ball_y)

            cv2.circle(img,x_y(home_height,x,y,LD_edge), ball_size, Color_ball(now_ball[0]), thickness=thick)
            cv2.circle(img,x_y(home_height,x+ball_size,y+int(1.73205*ball_size),LD_edge), ball_size, Color_ball(now_ball[1]), thickness=thick)
            cv2.circle(img,x_y(home_height,x-ball_size,y+int(1.73205*ball_size),LD_edge), ball_size, Color_ball(now_ball[2]), thickness=thick)


        #ネクストボールセット表示
        next_ball_position = [50,50]
        cv2.circle(img,(next_ball_position[0],next_ball_position[1]), ball_size, Color_ball(next_ball[0]), thickness=thick) 
        cv2.circle(img,(next_ball_position[0]+2*ball_size,next_ball_position[1]), ball_size, Color_ball(next_ball[1]), thickness=thick) 
        cv2.circle(img,(next_ball_position[0]+ball_size,next_ball_position[1]-int(ball_size * 1.732)), ball_size, Color_ball(next_ball[2]), thickness=thick) 


    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

    cv2.imshow(windowName,img)
    img = copy.deepcopy(img_copy)


    if flag_end == True:
        break

    #キーボード入力処理
    key = cv2.waitKey(100)

    if key == ord('q'):                 #       end 処理
        break

    elif key == ord ('a'):             #左60deg回転
        print("L_rotate")
        if flag_triangle == True:  #三角形
            flag_triangle = False
            #now_ballはそのまま
            ball_x = ball_x + 1

        else:                      #逆三角
            flag_triangle = True
            now_ball = [now_ball[2],now_ball[0],now_ball[1]]
            ball_x = ball_x - 1


    elif key == ord ('s'):             #右60deg回転
        print("R_rotate")
        if flag_triangle == True:  #三角形
            flag_triangle = False
            now_ball = [now_ball[1],now_ball[2],now_ball[0]]
            ball_x = ball_x + 1

        else:                      #逆三角
            flag_triangle = True
            #now_ballはそのまま
            ball_x = ball_x - 1

    elif key == ord ('w'):             #右60deg回転
        print("rotate")
        if flag_triangle == True:  #三角形
            flag_triangle = False


        else:                      #逆三角
            flag_triangle = True



    elif key == ord ('j'):             #左0.5移動
        print("L_move")
        if flag_triangle == True:
            if ball_x != 0:
                ball_x = ball_x -1
        elif flag_triangle == False:
            if ball_x != 1:
                ball_x = ball_x -1

    elif key == ord ('l'):             #右0.5移動

        print("R_move")
        if flag_triangle == True:
            if ball_x != 16:
                ball_x = ball_x +1
        elif flag_triangle == False:
            if ball_x != 17:
                ball_x = ball_x +1

    elif key == ord ('k'):             #落下
        print("fall")

        """
        衝突するまで直線落下させる.
        """
        if flag_triangle == True:    #三角の落下
            """
            少々ややこしい 調べるべき場所が多い
            """
            flag_fall = 0
            while True:
                if ball_y == 0:
                    break             #床についている

                else:
                    if ball_x > 0:
                        if ball[ball_y-1][ball_x-1] !=0:  #左下にある
                            flag_fall = 1
                            break

                    if ball[ball_y-1][ball_x] !=0:    #左真下にある
                        flag_fall = 2

                    if ball[ball_y-1][ball_x+1] !=0:  #中央にある
                        flag_fall = 3
                        break

                    if ball[ball_y-1][ball_x+2] !=0:  #右真下にある
                        flag_fall = 4

                    if ball_x < 16:
                        if ball[ball_y-1][ball_x+3] !=0:  #右下にある
                            flag_fall = 5
                            break


                if flag_fall == 0:
                    ball_y=ball_y -1

                    #描画する
                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                    cv2.imshow(windowName,img)
                    img = copy.deepcopy(img_copy)
                    key = cv2.waitKey(disp_time)


            if flag_fall == 0 or flag_fall == 2 or flag_fall == 4 :
                if ball_x %2 != 0: #床についているが0.5個ずれている. エラーが出ないよう,とりあえずずらしておく
                    if ball_x>=9:
                        ball_x =ball_x -1
                    else:
                        ball_x = ball_x + 1

                ball[ball_y][ball_x] = now_ball[0]
                ball[ball_y][ball_x+2] = now_ball[1]
                ball[ball_y+1][ball_x+1] = now_ball[2]

            elif flag_fall == 1 or flag_fall == 3 or flag_fall == 5:
                ball[ball_y][ball_x] = now_ball[0]
                ball[ball_y][ball_x+2] = now_ball[1]
                ball[ball_y+1][ball_x+1] = now_ball[2]


        elif flag_triangle == False: #逆三角
            flag_fall = 0                    #0:ない  1:真下 2:左 3右 4:左右にある
            while True:
                if ball_y == 0:
                    break             #床についている

                else:

                    if (ball_y-1)%2 == 0:        #偶数行  ,偶数列にある
                        if ball_x %2 ==0:        #偶数列にボールがある,下にあるか確認
                            if ball[ball_y-1][ball_x] !=0:  #下にある
                                flag_fall = 1
                                break
                        else:                    #奇数列にボールがある,右下・左下にあるか確認

                            if ball[ball_y-1][ball_x-1] !=0:  #左下にある
                                if ball[ball_y-1][ball_x+1] !=0: #右下にもある
                                    flag_fall = 4
                                    break
                                else:
                                    flag_fall = 2
                                    break                            
                            else:
                                if ball[ball_y-1][ball_x+1] !=0: #右下にある
                                    flag_fall = 3
                                    break

                    else:                        #奇数行 ,奇数列にボールがある
                        if ball_x %2 ==1:        #奇数列にボールがある,下にあるか確認
                            if ball[ball_y-1][ball_x] !=0:  #下にある
                                flag_fall = 1
                                break

                        else:                    #偶数列にボールがある,右下・左下にあるか確認

                            if ball[ball_y-1][ball_x-1] !=0:  #左下にある
                                if ball[ball_y-1][ball_x+1] !=0: #右下にもある
                                    flag_fall = 4
                                    break
                                else:
                                    flag_fall = 2
                                    break                            
                            else:
                                if ball[ball_y-1][ball_x+1] !=0: #右下にある
                                    flag_fall = 3
                                    break

                if flag_fall == 0:
                    ball_y=ball_y -1

                    #描画する
                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                    cv2.imshow(windowName,img)
                    img = copy.deepcopy(img_copy)
                    key = cv2.waitKey(disp_time)


            if flag_fall == 0:
                if ball_x %2 != 0: #床についているが0.5個ずれている. エラーが出ないよう,とりあえずずらしておく
                    if ball_x>=9:
                        ball_x =ball_x -1
                    else:
                        ball_x = ball_x + 1

                ball[ball_y][ball_x] = now_ball[0]
                ball[ball_y+1][ball_x+1] = now_ball[1]
                ball[ball_y+1][ball_x-1] = now_ball[2]

            elif flag_fall == 1:  #真下にある. 0.5個ずれ エラーが出ないよう,とりあえずずらしておく
                if ball_x>=9:
                    ball_x =ball_x -1
                else:
                    ball_x = ball_x + 1

                ball[ball_y][ball_x] = now_ball[0]
                ball[ball_y+1][ball_x+1] = now_ball[1]
                ball[ball_y+1][ball_x-1] = now_ball[2]

            elif flag_fall == 2 or flag_fall == 3 or flag_fall == 4: #左 または 右 にある. 特に何も考えず,固定                

                ball[ball_y][ball_x] = now_ball[0]
                ball[ball_y+1][ball_x+1] = now_ball[1]
                ball[ball_y+1][ball_x-1] = now_ball[2]

            print(flag_fall)
            #描画する
            disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

            cv2.imshow(windowName,img)
            img = copy.deepcopy(img_copy)
            key = cv2.waitKey(disp_time)


        now_ball = next_ball
        next_ball = [random.randint(1,color_num+1),random.randint(1,color_num+1),random.randint(1,color_num+1)]
        flag_triangle =False  #逆三角形:False
        ball_x = 1            #逆三角 なら 一番下    三角なら,左下 の玉の座標
        ball_y = 12

        """
        test1.pyで作った,落下処理を組み込む

        わかりにくいので,描画を挟む
        """

        loop = 0
        while True:
            flag_moved = False

            for gyou in range(12):
                if gyou%2 == 0:
                    for retu in range(0,19,2):       #0,2,4,::::18 を流す
                        if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認

                            if retu == 0:
                                a_flag = False       #壁なので動けない
                            else:
                                if ball[gyou][retu-2]  != 0:
                                    a_flag = False
                                else:
                                    a_flag = True    #動ける

                            if retu == 18:
                                d_flag = False       #壁なので動けない
                            else:
                                if ball[gyou][retu+2]  != 0:
                                    d_flag = False
                                else:
                                    d_flag = True    #動ける

                            if retu == 0 or gyou == 0:
                                b_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu-1]  != 0:
                                    b_flag = False
                                else:
                                    b_flag = True

                            if retu == 18 or gyou == 0:
                                c_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu+1]  != 0:
                                    c_flag = False
                                else:
                                    c_flag = True

                            if b_flag == True and c_flag == False and a_flag == True:
                                #b 方向に動かす
                                ball[gyou-1][retu-1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu-1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if c_flag == True and b_flag == False and d_flag == True:
                                #c 方向に動かす
                                ball[gyou-1][retu+1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu+1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if b_flag == True and c_flag == True:
                                #b,cどちらかに動かす
                                hoge = random.randint(0,1)
                                if hoge == 0:
                                    #b 方向に動かす
                                    ball[gyou-1][retu-1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu-1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break
                                else:
                                     #c 方向に動かす
                                    ball[gyou-1][retu+1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu+1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break

                if gyou%2 == 1:  #奇数行
                    for retu in range(1,18,2):   #1,3,5,::::17 を流す
                        if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認
                            if retu == 1:
                                a_flag =True       #壁はあるが,偶数行とは違い動ける
                            else:
                                if ball[gyou][retu-2]  != 0:
                                    a_flag = False
                                else:
                                    a_flag = True    #動ける

                            if retu == 17:
                                d_flag = True       #壁はあるが,偶数行とは違い動ける
                            else:
                                if ball[gyou][retu+2]  != 0:
                                    d_flag = False
                                else:
                                    d_flag = True    #動ける

                            if gyou == 0:
                                b_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu-1]  != 0:
                                    b_flag = False
                                else:
                                    b_flag = True

                            if gyou == 0:
                                c_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu+1]  != 0:
                                    c_flag = False
                                else:
                                    c_flag = True

                            if b_flag == True and c_flag == False and a_flag == True:
                                #b 方向に動かす
                                ball[gyou-1][retu-1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu-1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if c_flag == True and b_flag == False and d_flag == True:
                                #c 方向に動かす
                                ball[gyou-1][retu+1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu+1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if b_flag == True and c_flag == True:
                                #b,cどちらかに動かす
                                hoge = random.randint(0,1)
                                if hoge == 0:
                                    #b 方向に動かす
                                    ball[gyou-1][retu-1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu-1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break
                                else:
                                     #c 方向に動かす
                                    ball[gyou-1][retu+1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu+1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break

                    if flag_moved == True:
                        break             #次の行には行かず,(0,0)から探索しなおす

            if flag_moved == False:  #一つも動かなかった
                break

        """
        落下が終わったので,
        ピラミッド・ヘキサゴンの判定  と 消去 と 演出
        """

        flag_hex = False
        hex_color = 100

        for start_y in range(0,10,1):  # 0,2,4,~,8,9 まで
            if start_y%2 == 0:
                for start_x in range(2,17,2):  #2,4,6,~,14,16まで
                    hoge = ball[start_y][start_x]
                    if hoge != 0:
                        if ball[start_y+1][start_x-1] == hoge:
                            if ball[start_y+2][start_x] == hoge:
                                if ball[start_y+2][start_x+2] == hoge:
                                    if ball[start_y+1][start_x+3] == hoge:
                                        if ball[start_y][start_x+2] == hoge:
                                            print("hex !!!!!!")
                                            flag_hex = True
                                            hex_color = hoge
                                            break
            else:
                for start_x in range(1,16,2):  #1,3,5,~,13,15まで
                    hoge = ball[start_y][start_x]
                    if hoge != 0:
                        if ball[start_y+1][start_x-1] == hoge:
                            if ball[start_y+2][start_x] == hoge:
                                if ball[start_y+2][start_x+2] == hoge:
                                    if ball[start_y+1][start_x+3] == hoge:
                                        if ball[start_y][start_x+2] == hoge:
                                            print("hex !!!!!!")
                                            flag_hex = True
                                            hex_color = hoge
                                            break

        if flag_hex == True:
            """
            演出
            """
            #描画する
            disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化
            cv2.putText(img,'Hexagon !!!!!!!!',(300,300),cv2.FONT_HERSHEY_PLAIN,2.0,(255,0,0))
            cv2.imshow(windowName,img)
            img = copy.deepcopy(img_copy)
            key = cv2.waitKey(1000)

            """
            同色の消去
            """
            for gyou in range(12):
                if gyou%2 == 0:
                    for retu in range(0,19,2):       #0,2,4,::::18 を流す
                        if ball[gyou][retu] == hex_color:   
                            ball[gyou][retu] = 0


                if gyou%2 == 1:  #奇数行
                    for retu in range(1,18,2):   #1,3,5,::::17 を流す
                        if ball[gyou][retu] == hex_color:   
                            ball[gyou][retu] = 0

        """
        落下処理:同じことを前で書いているので,関数化すべきだけど,まぁいいか
        """

        loop = 0
        while True:
            flag_moved = False

            for gyou in range(12):
                if gyou%2 == 0:
                    for retu in range(0,19,2):       #0,2,4,::::18 を流す
                        if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認

                            if retu == 0:
                                a_flag = False       #壁なので動けない
                            else:
                                if ball[gyou][retu-2]  != 0:
                                    a_flag = False
                                else:
                                    a_flag = True    #動ける

                            if retu == 18:
                                d_flag = False       #壁なので動けない
                            else:
                                if ball[gyou][retu+2]  != 0:
                                    d_flag = False
                                else:
                                    d_flag = True    #動ける

                            if retu == 0 or gyou == 0:
                                b_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu-1]  != 0:
                                    b_flag = False
                                else:
                                    b_flag = True

                            if retu == 18 or gyou == 0:
                                c_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu+1]  != 0:
                                    c_flag = False
                                else:
                                    c_flag = True

                            if b_flag == True and c_flag == False and a_flag == True:
                                #b 方向に動かす
                                ball[gyou-1][retu-1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu-1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if c_flag == True and b_flag == False and d_flag == True:
                                #c 方向に動かす
                                ball[gyou-1][retu+1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu+1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if b_flag == True and c_flag == True:
                                #b,cどちらかに動かす
                                hoge = random.randint(0,1)
                                if hoge == 0:
                                    #b 方向に動かす
                                    ball[gyou-1][retu-1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu-1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break
                                else:
                                     #c 方向に動かす
                                    ball[gyou-1][retu+1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu+1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break

                if gyou%2 == 1:  #奇数行
                    for retu in range(1,18,2):   #1,3,5,::::17 を流す
                        if ball[gyou][retu] != 0:    #動かそうとしている玉自体が存在することの確認
                            if retu == 1:
                                a_flag =True       #壁はあるが,偶数行とは違い動ける
                            else:
                                if ball[gyou][retu-2]  != 0:
                                    a_flag = False
                                else:
                                    a_flag = True    #動ける

                            if retu == 17:
                                d_flag = True       #壁はあるが,偶数行とは違い動ける
                            else:
                                if ball[gyou][retu+2]  != 0:
                                    d_flag = False
                                else:
                                    d_flag = True    #動ける

                            if gyou == 0:
                                b_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu-1]  != 0:
                                    b_flag = False
                                else:
                                    b_flag = True

                            if gyou == 0:
                                c_flag = False       #壁または床なので動けない
                            else:
                                if ball[gyou-1][retu+1]  != 0:
                                    c_flag = False
                                else:
                                    c_flag = True

                            if b_flag == True and c_flag == False and a_flag == True:
                                #b 方向に動かす
                                ball[gyou-1][retu-1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu-1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if c_flag == True and b_flag == False and d_flag == True:
                                #c 方向に動かす
                                ball[gyou-1][retu+1] = ball[gyou][retu]
                                ball[gyou][retu] = 0                       #動かした後は空にしておく
                                flag_moved = True
                                retu_moved = retu
                                gyou_moved = gyou
                                retu_moved_to = retu+1
                                gyou_moved_to = gyou-1

                                #描画する
                                disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                cv2.imshow(windowName,img)
                                img = copy.deepcopy(img_copy)
                                key = cv2.waitKey(disp_time)
                                break

                            if b_flag == True and c_flag == True:
                                #b,cどちらかに動かす
                                hoge = random.randint(0,1)
                                if hoge == 0:
                                    #b 方向に動かす
                                    ball[gyou-1][retu-1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu-1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break
                                else:
                                     #c 方向に動かす
                                    ball[gyou-1][retu+1] = ball[gyou][retu]
                                    ball[gyou][retu] = 0                       #動かした後は空にしておく
                                    flag_moved = True
                                    retu_moved = retu
                                    gyou_moved = gyou
                                    retu_moved_to = retu+1
                                    gyou_moved_to = gyou-1

                                    #描画する
                                    disp_ball(img,ball,home_height,flag_triangle,ball_x,ball_y,next_ball,now_ball)  #ball map,now ball, next ball 描画を関数化

                                    cv2.imshow(windowName,img)
                                    img = copy.deepcopy(img_copy)
                                    key = cv2.waitKey(disp_time)
                                    break

                    if flag_moved == True:
                        break             #次の行には行かず,(0,0)から探索しなおす

            if flag_moved == False:  #一つも動かなかった
                break

cv2.destroyAllWindows()

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

"Hello World"でみるプログラミング言語

はじめに

プログラミングを学ぶものなら誰しも通る道、Hello world。今回はそんなHello worldを出力するためのプログラムで、5つのプログラミング言語を比較していきます。皆さんが使っていなそうな言語を選びました。が、あくまでも独断と偏見によって決定されています。

1.C

いわずと知れた言語ですね。

#include <stdio.h>
int main(void) {
    printf("Hello world\n");
}

2.Python

簡潔に書けることが利点ですね。

print("Hello world")

3.Ruby

日本生まれの言語ですね。

puts "Hello world"

4.FORTRAN

最初の高水準言語として有名ですね。まだ息してたのか...

program hello
  print *, 'Hello world'
end program hello

5. PHP

HTML/CSSと一緒に使われたりしますね。

<?php
  echo "Hello world\n";
?>

終わりに

以上です。いかがでしたでしょうか?Rubyはやっぱり短かったですね。それが長所ですよね。PHPechoてエコーって読むんですね。初めて知りました。若輩者ゆえ、もしかしたら間違いがあるかもしれないので、その時はコメントなんかで教えてください。

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

Pythonの型アノテーションを使ってInterfaceクラスをそれっぽく扱ってみる

概要

  • 依存関係逆転の原則(DIP)等の場面、つまり、抽象に依存した実装を行いたい場合に、Pythonでそれっぽく実装してみる
    • Python3.5から利用可能なTypeHintsの型アノテーションを利用してみる。
    • Pythonは言語機能としてInterfaceがないため、abcライブラリを使う。
    • 型アノテーションは型が違っていても怒らないので、あくまでそれっぽく書くというだけ。。。

前提

  • 環境
    • python: v.3.6.8

作ってみるもの

  • animalモジュール
    • AnimalというInterfaceクラスと、それを継承したCatクラスを実装する。
    • Animalクラスはcry()methodを持つ。
    • Catなどの具象クラスでは、cry()methodにて、それぞれの具象化された動物泣き声をprintする。というポリモーフィズムを説明する際に出てくるヤツです。
    • Dogクラスも用意するが、このクラスはAnimalを継承しない。
  • myclassモジュール
    • Animalの具象クラスを受け取り、cry()methodを叩くMyClassクラスを実装する。ここでMyClassでは
      1. Catクラスに依存しないようにしたい。(具象クラスの修正や、別の具象クラスが渡ってきても影響を受けないようにしたい
      2. AnimalというInterfaceクラスを継承していることは知っておきたい
  • mainモジュール
    • CatDogクラスを具象化し、MyClassのmethodに渡す。

animalモジュールを用意

Interfaceクラスを用意

  • abcライブラリを使って、継承クラスにてInterfaceクラスを作ってみる
class Animal(object, metaclass=abc.ABCMeta):
    """動物を表すInterfaceクラス。
    """
    @abc.abstractmethod
    def cry(self):
        """動物の鳴き声のInterface
        """
        pass # 具体的な実装はなし

具象クラスを用意

  • Animalクラスを継承して具象クラスを作ってみる
class Cat(Animal):
    """猫を表すクラス
    Args:
        Animal: インターフェースクラス
    """

    def cry(self):
        """猫の鳴き声の具象method
        """
        print('meow')
  • AnimalクラスとCatクラスの動きを確認してみる

    • Catクラスをインスタンス化して、cry()を実行してみる
    >>> cat = Cat()
    >>> cat.cry()
    meow
    
    • Interfaceクラスをそのままインスタンス化しようとすると怒られる
    >>> animal = Animal()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Animal with abstract methods cry
    
    • 具象クラスでmethodを実装しなかった場合も、怒られる
    >>> class Cat(Animal):
    ...     """猫を表すクラス
    ...     Args:
    ...         Animal: インターフェースクラス
    ...     """
    ...     def nocry(self):
    ...         """検証用method
    ...         """
    ...         print('???')
    ... 
    >>> cat = Cat()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Cat with abstract methods cry
    

Interfaceクラスと関係ないクラスを実装してみる

  • ぱっと見は似てるが、Animalクラスを継承して いない Dogクラスを作ってみる
class Dog(object):
    """犬を表すクラス。インターフェースは継承してない
    """
    def cry(self):
        """犬の鳴き声の具象method
        """
        print('bow wow')

myclassモジュールを用意

具象クラスを受け取って、cry()methodを実行するクラスを用意する

  • 型アノテーションを使って、引数がAnimal Interfaceを継承したクラスであることを明示
from animal import Animal

class MyClass(object):
    def run(self, animal: Animal) -> None:
        animal.cry()

mainモジュールを用意

  • 実際の具象クラスをインスタンス化し、処理はMyClassに委譲するロジックを用意
from animal import Cat, Dog
from myclass import MyClass

myclass = MyClass()
cat = Cat()
dog = Dog()

myclass.run(cat)
myclass.run(dog)
  • 結果は
$python main.py 
meow
bow wow
  • やっぱり犬も吠えてるね(´・ω・`)

考察

  • 型アノテーションはあくまで注釈なので、Interfaceクラスの派生クラスでなくても、動いてしまう
  • とりあえず、それっぽいことはできてる(意味があるかはおいておいて)
    • 簡単な実験のメモ程度の情報だけど何かしらの参考になれば幸いです。。。
    • 「複数人で実装するときに、Interfaceクラスに注目してね」くらいの意味は与えられるのかな?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sklearnを活用した特徴量選択を整理した

  • 製造業出身のデータサイエンティストがお送りする記事
  • 今回はscikit-learnで使用できる特徴量選択手法を使ってみました。

はじめに

回帰モデルや分類モデルを構築する際に、特徴量エンジニアリングは重要な要素です。その際、ドメイン知識を使って特徴量選択することが多いですが、scikit-learnを使用して目星を付ける方法を実施してみましたので整理します。

RFEによる特徴量選択

RFE(Recursive Feature Elimination)は再帰的特徴量削減手法になります。
すべての特徴量から開始してモデルを作成し、そのモデルで最も重要度が低い特徴量を削除します。その後またモデルを作成し、最も重要度が低い特徴量を削除します。この手順を定めた数の特徴量になるまで繰り返す手法です。

pythonのコードは下記の通りです。

# 必要なライブラリーのインポート
mport pandas as pd
import numpy as np
from sklearn.datasets import load_boston
from sklearn.feature_selection import RFE
from sklearn.ensemble import GradientBoostingRegressor

# データセットの読込み
boston = load_boston()

# データフレームの作成
# 説明変数の格納
df = pd.DataFrame(boston.data, columns = boston.feature_names)

# 目的変数の追加
df['MEDV'] = boston.target

# estimatorとしてGBDTを使用。特徴量を5個選択
selector = RFE(GradientBoostingRegressor(n_estimators=100, random_state=10), n_features_to_select=5)
selector.fit(df.iloc[:, 0:13], df.iloc[:, 13])
mask = selector.get_support()
print(boston.feature_names)
print(mask)

# 選択した特徴量の列のみ取得
X_selected = selector.transform(df.iloc[:, 0:13])
print("X.shape={}, X_selected.shape={}".format(df.iloc[:, 0:13].shape, X_selected.shape))

実行結果は下記のようになります。

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
[False False False False  True  True False  True False False  True False
  True]
X.shape=(506, 13), X_selected.shape=(506, 5)

SelectFromModelによる特徴量選択

モデルで得られる特徴量の重要性をあらわした feature_importances_を利用して特徴量を選択方法です。

pythonのコードは下記の通りです。

from sklearn.feature_selection import SelectFromModel

# estimatorとしてGBDTを使用。
selector = SelectFromModel(GradientBoostingRegressor(n_estimators=100, random_state=10), threshold="median")    
selector.fit(df.iloc[:, 0:13], df.iloc[:, 13])
mask = selector.get_support()
print(boston.feature_names)
print(mask)

# 選択した特徴量の列のみ取得
X_selected = selector.transform(df.iloc[:, 0:13])
print("X.shape={}, X_selected.shape={}".format(df.iloc[:, 0:13].shape, X_selected.shape))

実行結果は下記のようになります。

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
[ True False False False  True  True False  True False False  True  True
  True]
X.shape=(506, 13), X_selected.shape=(506, 7)

SelectKBestによる特徴量選択

説明変数のうち上位k個を選択する手法です。

pythonのコードは下記の通りです。

from sklearn.feature_selection import SelectKBest, f_regression

# 5つの特徴量を選択
selector = SelectKBest(score_func=f_regression, k=5) 
selector.fit(df.iloc[:, 0:13], df.iloc[:, 13])
mask = selector.get_support()    # 各特徴量を選択したか否かのmaskを取得
print(boston.feature_names)
print(mask)

# 選択した特徴量の列のみ取得
X_selected = selector.transform(df.iloc[:, 0:13])
print("X.shape={}, X_selected.shape={}".format(df.iloc[:, 0:13].shape, X_selected.shape))

実行結果は下記のようになります。

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
[False False  True False False  True False False False  True  True False
  True]
X.shape=(506, 13), X_selected.shape=(506, 5)

SelectPercentileによる特徴量選択

説明変数のうち上位k%を選択する手法です。

pythonのコードは下記の通りです。

from sklearn.feature_selection import SelectPercentile, f_regression

# 特徴量のうち50%を選択
selector = SelectPercentile(score_func=f_regression, percentile=50) 
selector.fit(df.iloc[:, 0:13], df.iloc[:, 13])
mask = selector.get_support()
print(boston.feature_names)
print(mask)

# 選択した特徴量の列のみ取得
X_selected = selector.transform(df.iloc[:, 0:13])
print("X.shape={}, X_selected.shape={}".format(df.iloc[:, 0:13].shape, X_selected.shape))

実行結果は下記のようになります。

['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']
[False False  True False  True  True False False False  True  True False
  True]
X.shape=(506, 13), X_selected.shape=(506, 6)

さいごに

最後まで読んで頂き、ありがとうございました。
今回はsklearnを使用した特徴量選択方法を整理しました。
実際の業務では、ライブラリーを使いながら、ドメイン知識と合わせて適切な特徴量エンジニアリングを実施することが重要かと思います。

訂正要望がありましたら、ご連絡頂けますと幸いです。

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

Discord上でチェスを遊ぶ

はじめに

趣味の領域を出たことがないので、もっときれいに書けるとかありましたら優しく教えて下さい。

やりたいこと

discord.pyとchessのパッケージを使用してDiscord上でchessの対戦ができるようにする

準備

discord developer portalで登録を済ませトークンを取る
discord.pyのインストールpy -3 -m pip install -U discord.py
chessのパッケージインストールpy -m pip install chess

コード

ChessBot.py
import discord
import chess
from discord.ext import commands

token = "token"

bot = commands.Bot(command_prefix="!")
board = None

@bot.event
async def on_ready():
    print("ready")

@bot.command()
async def start(ctx):
    global board
    board = chess.Board()
    await ctx.send("ボードを作成しました")
    await ctx.send("```" + str(board) + "```")

@bot.command()
async def move(ctx,movePos):
    global board
    if board == None:
        await ctx.send("ボードが作成されていません")
        return
    try:
        board.push_san(movePos)
        await ctx.send("```" + str(board) + "```")
    except:
        await ctx.send(movePos + "は有効な値ではありません")
        a = ""
        for i in board.legal_moves:
            a += str(i) + ","
        await ctx.send("> " + a)
    if board.is_game_over():
        await ctx.send("game over")
        board = None

bot.run(token)

結果

1.png

2.png

つまづいたところ

自分の書いたのは間違っていないのに何やらいっぱいエラーが出てくる(connector.pyやhttp.py)

解決方法

原因はSSL証明書の期限切れらしいから新しい証明書をインストールすればいい
1. IEを管理者権限で実行
2. 鍵マークのところを押して証明書を表示
3. インストール

最後に

有効な値を取得できなかった際にlegal_movesを使い移動できる場所をそのまま表示させていたけれど、不必要なものまで表示されてしまったからforで回して結合していった。あそこの部分は綺麗に書ける気がする。

せっかくctxでユーザ情報を取得できているのでdict利用してユーザごとの勝敗など作ってみたい。

参考

https://discord.com/
https://pypi.org/project/chess/
https://teratail.com/questions/267889

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

PyTorch学習メモ2 (事前学習済みモデルを使ってみた)

はじめに

PyTorch 学習メモ (Karasと同じモデルを作ってみた)の続きです。
今度は、同じcifar10を対象として、事前学習済みモデルを利用してモデルを作ってみました。

1. 最終版コード

コードはgithubにあげてあります。URLは下記になります。

https://github.com/makaishi2/sample-data/blob/master/notebooks/cifar10_resnet.ipynb

実装のポイントになる点は、下記になります。

1.1 データ読み込み

前回は、訓練用・検証用で共通だったtransformerを別々にしています。
そのココロは、訓練用に関して「Data Augmentation」(データ水増し)をすることにあります。
詳細については、2.2 学習データの水増しで説明します。

# transformの定義

# 検証データ用 : 正規化のみ実施
transform = transforms.Compose([
  transforms.ToTensor(),
  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# 訓練データ用: 正規化に追加で反転とRandomErasingを実施
transform_train = transforms.Compose([
  transforms.RandomHorizontalFlip(p=0.5), 
  transforms.ToTensor(),
  transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]), 
  transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)
])

1.2 モデルの読み込み

モデル読み込みのコードは以下のようになります。
PyTorchでは、事前学習済みモデルが何パターンか用意されていて、これらのモデルは関数を呼び出すだけで重み付きで読み込むことができます。

# 学習済みモデルの読み込み
# Resnet50を重み付きで読み込む
model_ft = models.resnet50(pretrained = True)

# 最終ノードの出力を10に変更する
model_ft.fc = nn.Linear(model_ft.fc.in_features, 10)

# GPUの利用
net = model_ft.to(device)

# 損失関数に交差エントロピーを利用
criterion = nn.CrossEntropyLoss()

# 最適化に関しては、いくつかのパターンを調べた結果、下記が一番結果がよかった
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

具体的に利用可能なモデルは、下記リンク先に一覧があります。
https://pytorch.org/docs/stable/torchvision/models.html

こうしたモデルを利用する場合は、読み込んだ後で、

model_ft.fc = nn.Linear(model_ft.fc.in_features, 10)

のコードで最終段だけ新しいノードに付け替えることで、目的とする分類モデルを作ることが可能です。
ちなみに、このモデルは元々入力サイズが224x224です。このような大きなサイズのモデルにCIFAR-10のような32x32のサイズのデータを突っ込んでいいのかも最初はよくわからなかったのですが、結論として問題はないようです。多分、大量の重みがまったく使われずに無駄になるだけなのだと思います。
逆に、例えば1024x1024のように元のモデルより解像度の大きなデータをモデルに入れる場合は、前処理で、解像度を224x224に落とすことが必要になります。

1.3 モデルの概要表示

モデルの概要表示をしたい場合は、この段階で netを実行すればいいです。
resnet50の場合の結果を、以下に示します。

 Bottleneck(
      (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (downsample): Sequential(
        (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): Bottleneck(
      (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
    (2): Bottleneck(
      (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
    (3): Bottleneck(
      (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
    (4): Bottleneck(
      (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
    (5): Bottleneck(
      (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
  )
  (layer4): Sequential(
    (0): Bottleneck(
      (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (downsample): Sequential(
        (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): Bottleneck(
      (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
    (2): Bottleneck(
      (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
      (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=2048, out_features=10, bias=True)
)

さすがに長いですね。相当複雑なニューラルネットであることがわかります。

1.4 モデルのサイズ表示

どの段のノードのサイズがいくつであるか確認するためには、この段階で次のコードを実行します。

# モデルのサマリー表示

from torchsummary import summary
summary(net,(3,128,128))

結果は次のとおりです。

----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 64, 16, 16]           9,408
       BatchNorm2d-2           [-1, 64, 16, 16]             128
              ReLU-3           [-1, 64, 16, 16]               0
         MaxPool2d-4             [-1, 64, 8, 8]               0
            Conv2d-5             [-1, 64, 8, 8]           4,096
       BatchNorm2d-6             [-1, 64, 8, 8]             128
              ReLU-7             [-1, 64, 8, 8]               0
            Conv2d-8             [-1, 64, 8, 8]          36,864
       BatchNorm2d-9             [-1, 64, 8, 8]             128
             ReLU-10             [-1, 64, 8, 8]               0
           Conv2d-11            [-1, 256, 8, 8]          16,384
      BatchNorm2d-12            [-1, 256, 8, 8]             512
           Conv2d-13            [-1, 256, 8, 8]          16,384
      BatchNorm2d-14            [-1, 256, 8, 8]             512
             ReLU-15            [-1, 256, 8, 8]               0
       Bottleneck-16            [-1, 256, 8, 8]               0
           Conv2d-17             [-1, 64, 8, 8]          16,384
      BatchNorm2d-18             [-1, 64, 8, 8]             128
             ReLU-19             [-1, 64, 8, 8]               0
           Conv2d-20             [-1, 64, 8, 8]          36,864
      BatchNorm2d-21             [-1, 64, 8, 8]             128
             ReLU-22             [-1, 64, 8, 8]               0
           Conv2d-23            [-1, 256, 8, 8]          16,384
      BatchNorm2d-24            [-1, 256, 8, 8]             512
             ReLU-25            [-1, 256, 8, 8]               0
       Bottleneck-26            [-1, 256, 8, 8]               0
           Conv2d-27             [-1, 64, 8, 8]          16,384
      BatchNorm2d-28             [-1, 64, 8, 8]             128
             ReLU-29             [-1, 64, 8, 8]               0
           Conv2d-30             [-1, 64, 8, 8]          36,864
      BatchNorm2d-31             [-1, 64, 8, 8]             128
             ReLU-32             [-1, 64, 8, 8]               0
           Conv2d-33            [-1, 256, 8, 8]          16,384
      BatchNorm2d-34            [-1, 256, 8, 8]             512
             ReLU-35            [-1, 256, 8, 8]               0
       Bottleneck-36            [-1, 256, 8, 8]               0
           Conv2d-37            [-1, 128, 8, 8]          32,768
      BatchNorm2d-38            [-1, 128, 8, 8]             256
             ReLU-39            [-1, 128, 8, 8]               0
           Conv2d-40            [-1, 128, 4, 4]         147,456
      BatchNorm2d-41            [-1, 128, 4, 4]             256
             ReLU-42            [-1, 128, 4, 4]               0
           Conv2d-43            [-1, 512, 4, 4]          65,536
      BatchNorm2d-44            [-1, 512, 4, 4]           1,024
           Conv2d-45            [-1, 512, 4, 4]         131,072
      BatchNorm2d-46            [-1, 512, 4, 4]           1,024
             ReLU-47            [-1, 512, 4, 4]               0
       Bottleneck-48            [-1, 512, 4, 4]               0
           Conv2d-49            [-1, 128, 4, 4]          65,536
      BatchNorm2d-50            [-1, 128, 4, 4]             256
             ReLU-51            [-1, 128, 4, 4]               0
           Conv2d-52            [-1, 128, 4, 4]         147,456
      BatchNorm2d-53            [-1, 128, 4, 4]             256
             ReLU-54            [-1, 128, 4, 4]               0
           Conv2d-55            [-1, 512, 4, 4]          65,536
      BatchNorm2d-56            [-1, 512, 4, 4]           1,024
             ReLU-57            [-1, 512, 4, 4]               0
       Bottleneck-58            [-1, 512, 4, 4]               0
           Conv2d-59            [-1, 128, 4, 4]          65,536
      BatchNorm2d-60            [-1, 128, 4, 4]             256
             ReLU-61            [-1, 128, 4, 4]               0
           Conv2d-62            [-1, 128, 4, 4]         147,456
      BatchNorm2d-63            [-1, 128, 4, 4]             256
             ReLU-64            [-1, 128, 4, 4]               0
           Conv2d-65            [-1, 512, 4, 4]          65,536
      BatchNorm2d-66            [-1, 512, 4, 4]           1,024
             ReLU-67            [-1, 512, 4, 4]               0
       Bottleneck-68            [-1, 512, 4, 4]               0
           Conv2d-69            [-1, 128, 4, 4]          65,536
      BatchNorm2d-70            [-1, 128, 4, 4]             256
             ReLU-71            [-1, 128, 4, 4]               0
           Conv2d-72            [-1, 128, 4, 4]         147,456
      BatchNorm2d-73            [-1, 128, 4, 4]             256
             ReLU-74            [-1, 128, 4, 4]               0
           Conv2d-75            [-1, 512, 4, 4]          65,536
      BatchNorm2d-76            [-1, 512, 4, 4]           1,024
             ReLU-77            [-1, 512, 4, 4]               0
       Bottleneck-78            [-1, 512, 4, 4]               0
           Conv2d-79            [-1, 256, 4, 4]         131,072
      BatchNorm2d-80            [-1, 256, 4, 4]             512
             ReLU-81            [-1, 256, 4, 4]               0
           Conv2d-82            [-1, 256, 2, 2]         589,824
      BatchNorm2d-83            [-1, 256, 2, 2]             512
             ReLU-84            [-1, 256, 2, 2]               0
           Conv2d-85           [-1, 1024, 2, 2]         262,144
      BatchNorm2d-86           [-1, 1024, 2, 2]           2,048
           Conv2d-87           [-1, 1024, 2, 2]         524,288
      BatchNorm2d-88           [-1, 1024, 2, 2]           2,048
             ReLU-89           [-1, 1024, 2, 2]               0
       Bottleneck-90           [-1, 1024, 2, 2]               0
           Conv2d-91            [-1, 256, 2, 2]         262,144
      BatchNorm2d-92            [-1, 256, 2, 2]             512
             ReLU-93            [-1, 256, 2, 2]               0
           Conv2d-94            [-1, 256, 2, 2]         589,824
      BatchNorm2d-95            [-1, 256, 2, 2]             512
             ReLU-96            [-1, 256, 2, 2]               0
           Conv2d-97           [-1, 1024, 2, 2]         262,144
      BatchNorm2d-98           [-1, 1024, 2, 2]           2,048
             ReLU-99           [-1, 1024, 2, 2]               0
      Bottleneck-100           [-1, 1024, 2, 2]               0
          Conv2d-101            [-1, 256, 2, 2]         262,144
     BatchNorm2d-102            [-1, 256, 2, 2]             512
            ReLU-103            [-1, 256, 2, 2]               0
          Conv2d-104            [-1, 256, 2, 2]         589,824
     BatchNorm2d-105            [-1, 256, 2, 2]             512
            ReLU-106            [-1, 256, 2, 2]               0
          Conv2d-107           [-1, 1024, 2, 2]         262,144
     BatchNorm2d-108           [-1, 1024, 2, 2]           2,048
            ReLU-109           [-1, 1024, 2, 2]               0
      Bottleneck-110           [-1, 1024, 2, 2]               0
          Conv2d-111            [-1, 256, 2, 2]         262,144
     BatchNorm2d-112            [-1, 256, 2, 2]             512
            ReLU-113            [-1, 256, 2, 2]               0
          Conv2d-114            [-1, 256, 2, 2]         589,824
     BatchNorm2d-115            [-1, 256, 2, 2]             512
            ReLU-116            [-1, 256, 2, 2]               0
          Conv2d-117           [-1, 1024, 2, 2]         262,144
     BatchNorm2d-118           [-1, 1024, 2, 2]           2,048
            ReLU-119           [-1, 1024, 2, 2]               0
      Bottleneck-120           [-1, 1024, 2, 2]               0
          Conv2d-121            [-1, 256, 2, 2]         262,144
     BatchNorm2d-122            [-1, 256, 2, 2]             512
            ReLU-123            [-1, 256, 2, 2]               0
          Conv2d-124            [-1, 256, 2, 2]         589,824
     BatchNorm2d-125            [-1, 256, 2, 2]             512
            ReLU-126            [-1, 256, 2, 2]               0
          Conv2d-127           [-1, 1024, 2, 2]         262,144
     BatchNorm2d-128           [-1, 1024, 2, 2]           2,048
            ReLU-129           [-1, 1024, 2, 2]               0
      Bottleneck-130           [-1, 1024, 2, 2]               0
          Conv2d-131            [-1, 256, 2, 2]         262,144
     BatchNorm2d-132            [-1, 256, 2, 2]             512
            ReLU-133            [-1, 256, 2, 2]               0
          Conv2d-134            [-1, 256, 2, 2]         589,824
     BatchNorm2d-135            [-1, 256, 2, 2]             512
            ReLU-136            [-1, 256, 2, 2]               0
          Conv2d-137           [-1, 1024, 2, 2]         262,144
     BatchNorm2d-138           [-1, 1024, 2, 2]           2,048
            ReLU-139           [-1, 1024, 2, 2]               0
      Bottleneck-140           [-1, 1024, 2, 2]               0
          Conv2d-141            [-1, 512, 2, 2]         524,288
     BatchNorm2d-142            [-1, 512, 2, 2]           1,024
            ReLU-143            [-1, 512, 2, 2]               0
          Conv2d-144            [-1, 512, 1, 1]       2,359,296
     BatchNorm2d-145            [-1, 512, 1, 1]           1,024
            ReLU-146            [-1, 512, 1, 1]               0
          Conv2d-147           [-1, 2048, 1, 1]       1,048,576
     BatchNorm2d-148           [-1, 2048, 1, 1]           4,096
          Conv2d-149           [-1, 2048, 1, 1]       2,097,152
     BatchNorm2d-150           [-1, 2048, 1, 1]           4,096
            ReLU-151           [-1, 2048, 1, 1]               0
      Bottleneck-152           [-1, 2048, 1, 1]               0
          Conv2d-153            [-1, 512, 1, 1]       1,048,576
     BatchNorm2d-154            [-1, 512, 1, 1]           1,024
            ReLU-155            [-1, 512, 1, 1]               0
          Conv2d-156            [-1, 512, 1, 1]       2,359,296
     BatchNorm2d-157            [-1, 512, 1, 1]           1,024
            ReLU-158            [-1, 512, 1, 1]               0
          Conv2d-159           [-1, 2048, 1, 1]       1,048,576
     BatchNorm2d-160           [-1, 2048, 1, 1]           4,096
            ReLU-161           [-1, 2048, 1, 1]               0
      Bottleneck-162           [-1, 2048, 1, 1]               0
          Conv2d-163            [-1, 512, 1, 1]       1,048,576
     BatchNorm2d-164            [-1, 512, 1, 1]           1,024
            ReLU-165            [-1, 512, 1, 1]               0
          Conv2d-166            [-1, 512, 1, 1]       2,359,296
     BatchNorm2d-167            [-1, 512, 1, 1]           1,024
            ReLU-168            [-1, 512, 1, 1]               0
          Conv2d-169           [-1, 2048, 1, 1]       1,048,576
     BatchNorm2d-170           [-1, 2048, 1, 1]           4,096
            ReLU-171           [-1, 2048, 1, 1]               0
      Bottleneck-172           [-1, 2048, 1, 1]               0
AdaptiveAvgPool2d-173           [-1, 2048, 1, 1]               0
          Linear-174                   [-1, 10]          20,490
================================================================
Total params: 23,528,522
Trainable params: 20,490
Non-trainable params: 23,508,032
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 5.86
Params size (MB): 89.75
Estimated Total Size (MB): 95.63
----------------------------------------------------------------
[ ]

最後の方は、縦と横の次元数が1次元になってしまっていて、ニューラルネットとしてこれで意味があるのは多少不安になります。
この点については、2.3 モデルの選定で調べてみたので、その結果を参照して下さい。

1.5 学習のメインループ

やっていることは、PyTorch 学習メモ (Karasと同じモデルを作ってみた)の時と同じなのですが、学習中の出力に関しては、Kerasっぽくなるように変えてみました。コメントも前より多めにしたので、だいぶわかりやすくなったかと思います。

for i in range(nb_epoch):
  train_loss = 0
  train_acc = 0
  val_loss = 0
  val_acc = 0

  #学習
  net.train()

  for images, labels in train_loader:

    #勾配の初期化(ループの頭でやる必要あり)
    optimizer.zero_grad()

    # 訓練データの準備
    images = images.to(device)
    labels = labels.to(device)

    # 順伝搬計算
    outputs = net(images)

    # 誤差計算
    loss = criterion(outputs, labels)
    train_loss += loss.item()

    # 学習
    loss.backward()
    optimizer.step()

    #予測値算出
    predicted = outputs.max(1)[1]

    #正解件数算出
    train_acc += (predicted == labels).sum()

  # 訓練データに対する損失と精度の計算
  avg_train_loss = train_loss / len(train_loader.dataset)
  avg_train_acc = train_acc / len(train_loader.dataset)

  #評価
  net.eval()
  with torch.no_grad():

    for images, labels in test_loader:

      # テストデータの準備
      images = images.to(device)
      labels = labels.to(device)

      # 順伝搬計算
      outputs = net(images)

      # 誤差計算
      loss = criterion(outputs, labels)
      val_loss += loss.item()

      #予測値算出
      predicted = outputs.max(1)[1]

      #正解件数算出
      val_acc += (predicted == labels).sum()

    # 検証データに対する損失と精度の計算
    avg_val_loss = val_loss / len(test_loader.dataset)
    avg_val_acc = val_acc / len(test_loader.dataset)

  print (f'Epoch [{(i+1)}/{nb_epoch}], loss: {avg_train_loss:.5f} acc: {avg_train_acc:.5f} val_loss: {avg_val_loss:.5f}, val_acc: {avg_val_acc:.5f}')
  train_loss_list.append(avg_train_loss)
  train_acc_list.append(avg_train_acc)
  val_loss_list.append(avg_val_loss)
  val_acc_list.append(avg_val_acc)

1.6 学習曲線表示

損失関数値と精度の両方について、学習曲線を表示します。コードと結果例は次のとおりです。

損失関数値
# 学習曲線 (損失関数値)
plt.figure(figsize=(8,6))
plt.plot(val_loss_list,label='検証', lw=2, c='b')
plt.plot(train_loss_list,label='訓練', lw=2, c='k')
plt.title('学習曲線 (損失関数値)')
plt.xticks(size=14)
plt.yticks(size=14)
plt.grid(lw=2)
plt.legend(fontsize=14)
plt.xticks(np.arange(0, 21, 2))
plt.show()

スクリーンショット 2021-01-13 18.47.58.png

精度
# 学習曲線 (精度)
plt.figure(figsize=(8,6))
plt.plot(val_acc_list,label='検証', lw=2, c='b')
plt.plot(train_acc_list,label='訓練', lw=2, c='k')
plt.title('学習曲線 (精度)')
plt.xticks(size=14)
plt.yticks(size=14)
plt.grid(lw=2)
plt.legend(fontsize=14)
plt.xticks(np.arange(0, 21, 2))
plt.show()

スクリーンショット 2021-01-13 18.48.09.png

2. チューニング

2.1 最適化パラメータ

詳細は省略しますが、最適化パラメータに関してはいくつかのパターンを調べた結果、下記が一番よさそうという結論になりました。

# 最適化に関しては、いくつかのパターンを調べた結果、下記が一番結果がよかった
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

例えば、最適化関数については、Adamなども試してみましたが、SGDよりかえって学習速度が遅かったです。これは、事前学習済みモデルを使う場合に、一般的に成り立つ話のようです。

2.2 学習データの水増し

訓練データ用のtransformの定義を再掲します。

transform_train = transforms.Compose([
  transforms.RandomHorizontalFlip(p=0.5),   # ランダム化1
  transforms.ToTensor(),
  transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]), 
  transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False)  # ランダム化2
])

ここでは、RandomHorizontalFlipRandomErasingという2つの呼び出しがされています。
この2つは、それぞれ「画像反転」「矩形領域の画像削除」をランダムに行う機能で、これによって、元の学習データのバリエーションを増やしています。
この「データ水増し」がどの程度効果があるのか確認するため、同じモデルに対して、下記の4パターンで学習をして結果を比較してみました。'下の学習曲線はいずれも検証データに対するもの)

なし: ランダム化なし
反転: 上下反転のみ
消去: 矩形消去(RandomErasing)のみ
両方: 反転と消去の両方

損失関数値

スクリーンショット 2021-01-13 14.26.51.png

精度

スクリーンショット 2021-01-13 14.27.14.png

グラフから明らかなように、この2つは精度向上に効果があり、しかも、独立した効果があるので組み合わせて使えることがわかります。

2.3 モデルの選定

次に読み込むモデルを取り替えて、精度がどうなるか試してみました。試したモデルは下の4つです。
Resnetに関しては、層の数が18, 50, 152の3パターンを試しました。

model_ft = models.resnet50(pretrained = True)
# model_ft = models.resnet18(pretrained = True)
# model_ft = models.resnet152(pretrained = True)
# model_ft = models.vgg19_bn(pretrained = True)

その結果(検証データに対するもの)の学習曲線のグラフを、以下に示します。

損失関数値

スクリーンショット 2021-01-13 15.54.57.png

精度

スクリーンショット 2021-01-13 16.18.13.png

まず、Resnet18とResnet50を比較するとResnet50の方がよくなっています。このことから50層程度までは、ネットワークの階層を深くすることが意味があることがわかります。
逆にResnet50とResnet152を比較した場合、ほぼ同等か、むしろResnet152の方が悪くなっている傾向があるので、CIFAR-10のように解像度が小さなデータの場合、Resnetでは50階層以上深くしても意味がないこともわかります。
最後にvgg_19_bn(bnというのはBatch Normalizationという手法を取り入れたモデルということ)ですが、圧倒的にいい結果を出しました。モデルの進化という意味ではVGGよりResNetが後に出てきたのですが、CIFAR-10のように解像度が小さなデータに対してはVGG19のような単純な構造のモデルの方が精度はいいということなのかもしれません。

3. 未解決の問題

実は、まだわからない点が一つあります。この例題自体、最初は「転移学習」のサンプルのつもりだったのです。
転移学習にするためには、モデル読み込みのところで下記のコードにすればいいはずです。

# Resnet50を重み付きで読み込む
model_ft = models.resnet50(pretrained = True)

# 最終段以外の勾配計算をしない
for param in model_ft.parameters():
  param.requires_grad = False

# 最終段だけ付け替える(分類先クラス数=10)
model_ft.fc = nn.Linear(model_ft.fc.in_features, 10)

# GPUの割り当て
net = model_ft.to(device)

# 損失関数は交差エントロピー関数
criterion = nn.CrossEntropyLoss()

# 最適化計算も最終段のみ行う
optimizer = optim.SGD(net.fc.parameters(), lr=0.001, momentum=0.9)

しかし。。。
何度試してみても下記のような結果になり、全然いい精度が出ないのです。
この理由について、わかる方がいらっしゃったら、教えていただけると幸いです。

Epoch [1/20], loss: 0.01858 acc: 0.35722 val_loss: 0.01631, val_acc: 0.45390
Epoch [2/20], loss: 0.01665 acc: 0.43014 val_loss: 0.01546, val_acc: 0.47900
Epoch [3/20], loss: 0.01618 acc: 0.44372 val_loss: 0.01509, val_acc: 0.49140
Epoch [4/20], loss: 0.01587 acc: 0.45340 val_loss: 0.01479, val_acc: 0.49960
Epoch [5/20], loss: 0.01562 acc: 0.45984 val_loss: 0.01468, val_acc: 0.50190
Epoch [6/20], loss: 0.01548 acc: 0.46750 val_loss: 0.01429, val_acc: 0.51690
Epoch [7/20], loss: 0.01537 acc: 0.47066 val_loss: 0.01421, val_acc: 0.51670
Epoch [8/20], loss: 0.01524 acc: 0.47468 val_loss: 0.01412, val_acc: 0.51730
Epoch [9/20], loss: 0.01511 acc: 0.47828 val_loss: 0.01418, val_acc: 0.51480
Epoch [10/20], loss: 0.01504 acc: 0.47974 val_loss: 0.01401, val_acc: 0.52380
Epoch [11/20], loss: 0.01497 acc: 0.48228 val_loss: 0.01390, val_acc: 0.52660
Epoch [12/20], loss: 0.01491 acc: 0.48466 val_loss: 0.01389, val_acc: 0.53050
Epoch [13/20], loss: 0.01485 acc: 0.48774 val_loss: 0.01375, val_acc: 0.53290
Epoch [14/20], loss: 0.01481 acc: 0.48686 val_loss: 0.01379, val_acc: 0.52630
Epoch [15/20], loss: 0.01476 acc: 0.48818 val_loss: 0.01380, val_acc: 0.53200
Epoch [16/20], loss: 0.01476 acc: 0.48818 val_loss: 0.01361, val_acc: 0.54190
Epoch [17/20], loss: 0.01476 acc: 0.48762 val_loss: 0.01362, val_acc: 0.53570
Epoch [18/20], loss: 0.01464 acc: 0.49438 val_loss: 0.01352, val_acc: 0.53940
Epoch [19/20], loss: 0.01462 acc: 0.49442 val_loss: 0.01349, val_acc: 0.54160
Epoch [20/20], loss: 0.01464 acc: 0.49370 val_loss: 0.01355, val_acc: 0.53710
学習曲線(損失関数)

スクリーンショット 2021-01-13 17.20.45.png

学習曲線(精度)

スクリーンショット 2021-01-13 17.21.00.png

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

Pythonでスクレイピングのルール(robots.txt)をチェックするツールを作る

はじめに

スクレイピングを始めるにあたって、一番に気を付けるべきことは法律やルール(明示的なモノや暗黙的なモノも含む)を遵守することである。スクレイピングは、非常に便利であるがゆえにこのようなルールは割と蔑ろにされがち(特に初心者)であると思う。
今回はスクレイピングのルールに関しての記事ではないので、それらについては下記の記事を参考にされたい。

参考記事

robots.txtについて

ルールについては触れないとは言うものの、記事の題材でもあるrobots.txtについては簡単に触れておく。
robots.txtとは、スクレイピングを行うプログラムに対しての指示が書いてある文書である。robots.txtは慣習的にはURLの直下に置かれるが、これは別に義務ではないので、そもそも配置されていないケースもある。例えば、Qiitaであればこちらに配置されている。

Qiitaのrobots.txt
User-agent: *
Disallow: /*/edit$
Disallow: /api/*
Allow:    /api/*/docs$

上記のQiitaのrobots.txtを参考にして軽く解説をするが、User-Agentというのは対象となるクローラーの種類を表す。*は全員に対する指示である。次にAllow / Disallowは、指定されたパスへのクロールの許可もしくは禁止を表す。上の例であれば、https://qiita.com/api/*はクロール禁止だが、https://qiita.com/api/*/docs$は許可されていることがわかる。また、サイトによってはCrawl-delayが設定されていることもあるが、設定されていない場合でも暗黙の了解として、次のリクエストまで1秒間空けることが望ましい。
もっと詳細にrobots.txtについての仕様を知りたい方は、こちらを参照されるといい。

プログラム作成

Step 1. robots.txtの読み取り

pythonの標準ライブラリのurllibには、robots.txtを読み取るためのurllib.robotparserが提供されている。今回はこれを利用して、プログラムを作成する。
urllib.robotparserについては、こちらを参照。

import urllib.robotparser

# robots.txtの読み取り
robots_txt_url = 'https://qiita.com/robots.txt'
rp = urllib.robotparser.RobotFileParser()
rp.set_url(robots_txt_url)
rp.read()

# robots.txtの情報から調査したいURL、User-Agentでクロール可能かを調べる
user_agent = '*'
url = 'https://qiita.com/api/*'
result = rp.can_fetch(user_agent, url)
print(result)
実行結果
False

上記のプログラムでは、まずRobotFileParserオブジェクトを作り、set_url関数でrobots.txtのURLを指定、それを元にreadで読み取りを行う。次に、調査したいUser-Agent、URLをcan_fetch関数に与えることでアクセスが許可されているかどうかが真偽値で取得できる。上記のプログラムでは、先程確認した通りhttps://qiita.com/api/*へのクロールは許可されていないので、Falseが出力されている。

Step 2. 正規表現を用いたrobots.txtのリンクの取得

プログラムの根幹となる部分は、ほとんどStep1で終了しているが、これではライブラリの機能を利用しただけで、プログラムとして有用なものではない。そこで正規表現を用いて、robots.txtのリンクを自動生成したいと思う。とは言っても、あまりイメージが湧かないと思うので、具体的な例を用いて説明する。
例えば、クロールが許可されているか調べたいURLがhttps://qiita.com/api/*であるとすると、このURLを元にhttps://qiita.com/robots.txtというリンクを生成するという事である。これは先程も述べた通りrobots.txtは慣習的にサイトの直下に配置されているので、https://qiita.com/api/*からhttps://qiita.comの部分を抽出できれば、そこに/robots.txtの文字を足すだけでリンクを作成することができる。
pythonの正規表現reについては、こちらを参照されるといい。

import re

# 正規表現によりサイトのURLを取得
def get_root_url(url):
    pattern = r'(?P<root>https?://.*?)\/.*'
    result = re.match(pattern, url)
    if result is not None:
        return result.group('root')

# サイトのURLからrobots.txtのURLを生成
def get_robots_txt_path(root_url):
    return root_url + '/robots.txt'

url = 'https://qiita.com/api/*'
root_url = get_root_url(url)
robots_txt_url = get_robots_txt_path(root_url)
print(f'ROOT URL -> {root_url}')
print(f'ROBOTS.TXT URL -> {robots_txt_url}')
実行結果
ROOT URL -> https://qiita.com
ROBOTS.TXT URL -> https://qiita.com/robots.txt

Step 3. 機能を追加してまとめる

Step1, Step2を元にurllib.robotparserの機能を用いて、Crawl-Delayを取得するなどの機能を追加し、関数などをクラス化してまとめる。ここにコードを置いてもいいのだが、微妙に長いためGitHubに置いておく。60行程度なので、内容を確認したい方はどうぞ。
※GitHubのコードでimportされているcoloramaというライブラリは、ターミナルで文字を着色するために使っているものなので、特に機能に重要なものではない。

参考記事

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

PyCaretを使ってみた

  • 製造業出身のデータサイエンティストがお送りする記事
  • 今回はDataRobotみたいなライブラリー(PyCaret)があったので使ってみました。

はじめに

過去に回帰モデルの手法を実装してみましたが、複数のモデルを簡単に比較できると楽だなと思っておりました。世の中のツールではDataRobotと呼ばれるツールがあるのですが、高くて買えないためPythonで似たようなライブラリーが無いかなと思って探していたらPyCaretという機械学習のモデル開発においてデータ前処理や可視化、モデル開発を数行のコードで出来てしまうライブラリを発見しました。

PyCaretを使ってみた

ライブラリーのインストールは下記で簡単にできます。

pip install pycaret

必要なライブラリーとデータを読み込んで、PyCaretを起動させます。
今回もUCI Machine Learning Repositoryで公開されているボストン住宅の価格データを用いて実施しますが、PyCaret’s Data Repository では、約50種類のデータセットが提供されています。

PyCaretの引数は下記です。

  • 第一引数:解析に用いる data(読み込んだデータ)
  • 第二引数:予測に用いる目的変数の名称
  • 第三引数:分析から外す説明変数の名称
# 必要なライブラリーのインポート
import pandas as pd
from pycaret.regression import *

# データセットの読込み
from pycaret.datasets import get_data 
boston_data = get_data('boston')

# PyCaretを起動
exp1 = setup(boston_data, target = 'medv', ignore_features = None)

スクリーンショット 2021-01-13 16.06.23.png

起動すると、投入したデータに対するデータ型の予測結果が表示されます。
きちっと中身を自分でも確認する必要があります。
問題がなければ、下の白枠にカーソルで「Enter」を押します。

スクリーンショット 2021-01-13 16.08.10.png

セットアップが完了します。

次にモデルを構築します。

# モデルの構築
compare_models()

スクリーンショット 2021-01-13 16.11.19.png

「CatBoost Regressor」や「Gradient Boosting Regressor 」、「Ridge Regression」など主要な回帰モデルを自動で作成し、評価指標も自動で計算してくれます。

ただし、現状はハイパーパラメータのチューニングまでは実施されていません。

次にモデル構築時に交差検証をデフォルトでは10回実施してくれておりますので、その結果を確認してみようと思います。
今回は一番精度の良かったcatboostを選択します。

# catboostのモデルを確認
catboost = create_model('catboost')

スクリーンショット 2021-01-13 16.14.48.png

次にハイパーパラメータのチューニングを実施します。
パラメータのチューニング方法はランダムグリッドサーチになります。

# catboostのモデルを最適化
catboost_tuned = tune_model(catboost, optimize = 'MAE')

スクリーンショット 2021-01-13 16.20.04.png

次に解析結果を確認します。

# 解析結果の確認
evaluate_model(catboost_tuned)

スクリーンショット 2021-01-13 16.20.59.png

「Hyperparameters」で最適化後のハイパーパラメータの値を確認することができます。
「Residuals」で残差分析の結果を確認できます。

スクリーンショット 2021-01-13 16.22.29.png

「Prediction Error」で予測精度を確認できます。

スクリーンショット 2021-01-13 16.23.18.png

「Cooks Distance」でクックの距離を確認できます。クックの距離とは、「i番目の観測値を使用して計算された係数と、観測値を使用しないで計算された係数との間の距離に対する測度」です。

スクリーンショット 2021-01-13 16.24.05.png

他に「Feature Importance:変数重要度」や「Learing Curve:学習曲線」、「Validation Curve:木の深さによる予測精度の変化」等を確認できます。選択した手法によって分析できる内容は異なります。

最後にアンサンブル学習などもできます。

# アンサンブル学習
lgbm = create_model('lightgbm')
xgboost = create_model('xgboost')

ensemble = blend_models([lgbm, xgboost])

スクリーンショット 2021-01-13 16.31.44.png

さいごに

最後まで読んで頂き、ありがとうございました。
こんな便利なライブラリーがあるとは知りませんでした。DataRobot無くても十分満足できる解析ができますね。

訂正要望がありましたら、ご連絡頂けますと幸いです。

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

dict in dict(辞書 in 辞書)を特定のkeyでsortする

dict in dictのsortメモ

# 球場ごとのチームのホームラン本数
homerun_dict = {
    'tokyo_dome': {'home_team': 10, 'hanshin': 8, 'hiroshima': 20},
    'koshien': {'home_team': 20, 'kyojin': 18, 'hiroshima': 12},
    'zoomzoom': {'home_team': 30, 'kyojin': 6, 'hanshin': 30},
}

homerun_dict_sorted = sorted(homerun_dict.items(), key=lambda x:x[1]['home_team'], reverse=True)

print(homerun_dict_sorted)
# [
#   ('zoomzoom', {'home_team': 30, 'kyojin': 6, 'hanshin': 30}),
#   ('koshien', {'home_team': 20, 'kyojin': 18, 'hiroshima': 12}),
#   ('tokyo_dome', {'home_team': 10, 'hiroshima': 20, 'hanshin': 8})
# ]

参考

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

Python logging徹底攻略

loggingの重要ポイント

ルートのloggingを汚してしまうため、getLoggerでloggerオブジェクトを作り、ロギングを行う。

# 良くない例
import logging
logging.debug('this is bad logging')

# 良い例
from logging import getLogger #途中で誤ってloggingを汚さないように個別にimport
logger = getLogger(__name__)
logger.debug('this is good logging')

logger作成時に__name__としておくことで、出力に__name__の値を表示することが出来る。

ログレベルについて

loggingにはログレベルが設定でき、setLevelによりどのログレベルまで表示を行うかを制御することが可能。
ログレベルには、DEBUG, INFO, WARNING, ERROR, CRITICALの5つがあり、ユーザーが任意に指定する必要がある。これらはloggerや後述の各ハンドラーにも個別に設定することが出来る。

from logging import getLogger, INFO

logger = getLogger(__name__)
logger.setLevel(INFO) # この場合、INFO以上のログが出力。つまり、DEBUG以外は出力される

logger.debug('debug log') # 今回の場合、これは表示されない
logger.info('info log')
logger.warning('warning log')
logger.error('error log')
logger.critical('critical log')

※loggerに設定されたログレベルは後述のハンドラーにも継承される。

下記はログレベルの詳細の表である。ログレベルに与えられた数値は任意に設定することで、ユーザー定義のログレベルを設定することもできる。

ログレベル 数値 利用するタイミング
DEBUG 10 問題探求に必要な詳細な情報を出力したい場合
INFO 20 想定内の処理が行われた場合
WARNING 30 想定外の処理やそれが起こりそうな場合
ERROR 40 重大な問題により、機能を実行出来ない場合
CRITICAL 50 プログラムが実行不可となるような重大なエラーが発生した場合

公式ドキュメントより引用

フォーマットについて

loggingにはフォーマットも追加でき、setFormatterによりログに日付や関数、ログレベルの情報など様々な詳細情報を出力できるようになる。これもログレベルと同じく、loggerや各ハンドラーごとに個別に設定することが出来る。

format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.setFormatter(format)
logger.info('this is format')
出力結果
2021-01-13 16:40:23 - __main__ - INFO - this is format

※利用できるフォーマットについては、公式ドキュメントのLogRecord属性を参考にして下さい。

ハンドラーの設定(StreamHandler, FileHandler)

logを出力するためには、ハンドラーを作成する必要がある。
StreamHandlerはコンソール出力用、FileHandlerはログファイル出力用に設定する。

from logging import getLogger, StreamHandler, FileHandler, Formatter, INFO, ERROR
logger = getLogger(__name__)
logger.setLevel(INFO) # loggerがINFOに設定されていると、ハンドラーにもINFO以上のログのみ継承される

# StreamHandlerの設定
ch = StreamHandler()
ch.setLevel(INFO) # ハンドラーにもそれぞれログレベル、フォーマットの設定が可能
ch_formatter = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.addHandler(ch) # StreamHandlerの追加

# FileHandlerの設定
fh = FileHandler('log/test.log') # 引数には出力ファイルのパスを指定
fh.setLevel(ERROR) # ハンドラーには、logger以下のログレベルを設定することは出来ない(この場合、DEBUGは不可)
fh_formatter = Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
logger.addHandler(fh) # FileHandlerの追加

コンフィグを利用したログの設定

logging.configを利用して、上記のようなハンドラーやフォーマットの個別の設定を読み込ませることもできる。

fileConfig(コンフィグファイルで設定する方法)

from logging import getLogger
import logging.config

logging.config.fileConfig('logging_settings.ini') # ファイルから設定を読み込む
logger = getLogger('root') # logging_settings.iniのrootの設定が呼び出される

logger.info('this is fileConfig from logging_settings.ini')
logging_settings.ini
[loggers] ; getLoggerで参照する名前を登録(rootは__main__の時に自動で取得)
keys=root

[handlers] ; 下記のhandler_xxxで設定したものを記述(複数の場合はコンマ区切り)
keys=newStreamhandler

[formatters] ; 下記のformatter_xxxで設定したものを記述(複数の場合はコンマ区切り)
keys=newFormatter

[logger_root] ; 各loggerの設定(logger_<loggerのkey名>とする)
level=INFO
handlers=newStreamhandler

[handler_newStreamhandler]
class=Streamhandler
level=DEBUG
formatter=newFormatter
args=(sys.stderr,)

[formatter_newFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
出力結果
2021-01-13 16:40:23 - root - INFO - this is fileConfig from logging_settings.ini

dictConfig(辞書型で設定する方法)

from logging import getLogger, DEBUG, INFO
import logging.config

dict_logging_settings = {
    'version': 1, # おまじない
# 新しいハンドラーを追加する場合は、handlers内にnewStreamhandlerと同じ形式で追加
    'handlers': {
        'newStreamHandler': {
            'class': 'logging.StreamHandler',
            'formatter': 'newFormatter',
            'level': DEBUG
        }
    },
# 新しいフォーマットを追加する場合は、formatters内にnewFormatterと同じ形式で追加
    'formatters': {
        'newFormatter': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        },
    },
# rootのloggerのみ例外的にloggersの外で設定
    'root': {
        'handlers': ['newStreamHandler'],
        'level': INFO
    },
# 他に追加したいloggerについては、上記のrootと同じようにloggers内に記述する
    'loggers': {}
}

logging.config.dictConfig(dict_logging_settings) # 辞書から設定を読み込む
logger = getLogger('root') # dict_logging_settingsのrootの設定が呼び出される

logger.info('this is dictConfig from dict_logging_settings')
出力結果
2021-01-13 16:40:23 - root - INFO - this is dictConfig from dict_logging_settings

参考記事

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

Pythonにおけるlogging徹底攻略

loggingの重要ポイント

ルートのloggingを汚さないよう、getLoggerでloggerオブジェクトを作り、ロギングを行うことが望ましい。また、コード中で誤ってloggingを汚さないように下記の良い例のようにimportを工夫すると良い。

# 良くない例
import logging
logging.debug('this is bad logging')
# 良い例
from logging import getLogger # 途中で誤ってloggingを汚さないように個別にimport
logger = getLogger(__name__)
logger.debug('this is good logging')

logger作成時に__name__としておくことで、出力に__name__の値を表示することが出来る。

ログレベルについて

loggingにはログレベルが設定でき、setLevelによりどのログレベルまで表示を行うかを制御することが可能。
ログレベルには、DEBUG, INFO, WARNING, ERROR, CRITICALの5つがあり、ユーザーが任意に指定する必要がある。これらはloggerや後述の各ハンドラーにも個別に設定することが出来る。

from logging import getLogger, INFO

logger = getLogger(__name__)
logger.setLevel(INFO) # この場合、INFO以上のログが出力。つまり、DEBUG以外は出力される

logger.debug('debug log') # 今回の場合、これは表示されない
logger.info('info log')
logger.warning('warning log')
logger.error('error log')
logger.critical('critical log')
出力結果
INFO:root:info log
WARNING:root:warning log
ERROR:root:error log
CRITICAL:root:critical log

※loggerに設定されたログレベルは後述のハンドラーにも継承される。

下記はログレベルの詳細の表である。ログレベルに与えられた数値は任意に設定することで、ユーザー定義のログレベルを設定することもできる。利用するタイミングについては任意であるため、必ずしも下記の表に準拠する必要はない。(参考程度)

ログレベル 数値 利用するタイミング(任意)
DEBUG 10 問題探求に必要な詳細な情報を出力したい場合
INFO 20 想定内の処理が行われた場合
WARNING 30 想定外の処理やそれが起こりそうな場合
ERROR 40 重大な問題により、機能を実行出来ない場合
CRITICAL 50 プログラムが実行不可となるような重大なエラーが発生した場合

Logging HOWTOより引用

フォーマットについて

loggingにはフォーマットも追加でき、setFormatterによりログに日付や関数、ログレベルの情報など様々な詳細情報を出力できるようになる。これもログレベルと同じく、loggerや各ハンドラーごとに個別に設定することが出来る。

from logging import getLogger, Formatter

logger = getLogger(__name__)
format = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.setFormatter(format) # loggerにフォーマットを適用

logger.info('this is format')
出力結果
2021-01-13 16:40:23 - __main__ - INFO - this is format

※その他の利用できるフォーマットについては、logging --- Python 用ロギング機能のLogRecord属性を参照

ハンドラーの設定(StreamHandler, FileHandler)

logを出力するためには、ハンドラーを作成する必要がある。
StreamHandlerはコンソール出力用、FileHandlerはログファイル出力用に設定する。

from logging import getLogger, StreamHandler, FileHandler, Formatter, INFO, ERROR

logger = getLogger(__name__)
logger.setLevel(INFO) # loggerがINFOに設定されていると、ハンドラーにもINFO以上のログのみ継承される

# StreamHandlerの設定
ch = StreamHandler()
ch.setLevel(INFO) # ハンドラーにもそれぞれログレベル、フォーマットの設定が可能
ch_formatter = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger.addHandler(ch) # StreamHandlerの追加

# FileHandlerの設定
fh = FileHandler('log/test.log') # 引数には出力ファイルのパスを指定
fh.setLevel(ERROR) # ハンドラーには、logger以下のログレベルを設定することは出来ない(この場合、DEBUGは不可)
fh_formatter = Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(name)s - %(funcName)s - %(message)s')
logger.addHandler(fh) # FileHandlerの追加

※その他の有用なハンドラーは、logging.handlers --- ロギングハンドラを参照

コンフィグを利用したログの設定

logging.configを利用して、上記のようなハンドラーやフォーマットの個別の設定を読み込ませることもできる。

fileConfig(コンフィグファイルで設定する方法)

from logging import getLogger
import logging.config

logging.config.fileConfig('logging_settings.ini') # ファイルから設定を読み込む
logger = getLogger('root') # logging_settings.iniのrootの設定が呼び出される

logger.info('this is fileConfig from logging_settings.ini')
logging_settings.ini
[loggers] ; getLoggerで参照する名前を登録(rootは__main__の時に自動で取得)
keys=root

[handlers] ; 下記のhandler_xxxで設定したものを記述(複数の場合はコンマ区切り)
keys=newStreamhandler

[formatters] ; 下記のformatter_xxxで設定したものを記述(複数の場合はコンマ区切り)
keys=newFormatter

[logger_root] ; 各loggerの設定(logger_<loggerのkey名>とする)
level=INFO
handlers=newStreamhandler

[handler_newStreamhandler]
class=Streamhandler
level=DEBUG
formatter=newFormatter
args=(sys.stderr,)

[formatter_newFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
出力結果
2021-01-13 16:40:23 - root - INFO - this is fileConfig from logging_settings.ini

dictConfig(辞書型で設定する方法)

from logging import getLogger, DEBUG, INFO
import logging.config

dict_logging_settings = {
    'version': 1, # おまじない
# 新しいハンドラーを追加する場合は、handlers内にnewStreamhandlerと同じ形式で追加
    'handlers': {
        'newStreamHandler': {
            'class': 'logging.StreamHandler',
            'formatter': 'newFormatter',
            'level': DEBUG
        }
    },
# 新しいフォーマットを追加する場合は、formatters内にnewFormatterと同じ形式で追加
    'formatters': {
        'newFormatter': {
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        },
    },
# rootのloggerのみ例外的にloggersの外で設定
    'root': {
        'handlers': ['newStreamHandler'],
        'level': INFO
    },
# 他に追加したいloggerについては、上記のrootと同じようにloggers内に記述する
    'loggers': {}
}

logging.config.dictConfig(dict_logging_settings) # 辞書から設定を読み込む
logger = getLogger('root') # dict_logging_settingsのrootの設定が呼び出される

logger.info('this is dictConfig from dict_logging_settings')
出力結果
2021-01-13 16:40:23 - root - INFO - this is dictConfig from dict_logging_settings

参考記事

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

VSCodeでのPython開発でリアルタイムにコードチェックが走るようにする

はじめに

これまでPythonを使うときはちょっとしたスクリプトを書くシーンが多く、とりあえずJupyterNotebookに打ち込み&実行してコーディング規約違反は後から気づいて直す…ということが多かったのですが…

PythonのWebアプリケーションフレームワークであるDjangoでの開発をVisualStudioCodeで行いたい!ということになり、静的コードチェックとフォーマットツールを導入しました。

設定にあたりいくつか記事を参考にさせていただきましたが、JavaScriptのESLintプラグインのように入力時リアルタイムにチェックが走る設定をする記事が見当たらなかったので、備忘も兼ねて記事にしました。

概要

Pythonのコードチェックにはいくつか種類があり、現在VSCodeで設定できるものだけでも以下の通りたくさんあります。

  • bandit
  • flake8
  • mypy
  • prospector
  • pycodestyle
  • pydocstyle
  • pylama
  • pylint (default)

それぞれの特徴などはこの記事には記しませんが、今回インストールしたflake8にはVSCode用にcornflakes-linterというプラグインがあり、導入することで入力に合わせてリアルタイムにコードチェックを走らせることができます!
cornflakes.gif

環境

  • Windows 10 Enterprise
  • Python 3.9.1
  • Visual Studio Code 1.52.1 (Pythonプラグインインストール済)

導入手順

flake8をインストールする

コマンドプロンプト等のコンソールから下記コマンドを実行

pip install flake8

cornflakes-linterプラグインをインストールする

VSCodeのMarketplaceからcornflakes-linterをインストール
image.png

VScodeでcornflakes-linterを設定する

ファイル>ユーザ設定>設定 で設定画面を開く
image.png

cornflake等のキーワードで検索するとCornflakes>Linterの項目が出てくるので、下図のように設定

  • flake8のインストール先パス
  • コードチェックのタイミング(今回はリアルタイムにしたいのでonTypeを選択) image.png

VSCodeでデフォルトのLinting設定を変更する【重要】

※ここを正しく設定しないとcornflakes-linterではなくデフォルトのflake8が動作してしまうのでご注意ください(実際躓きました…)

前のステップと同様にVSCodeの設定画面を開き、Python>Lintingの項目を下図のように設定

  • PythonのLinting自体は 有効化
    image.png

  • 初期状態でPylintが有効化されている場合は 無効化
    image.png

  • デフォルトのflake8は 無効化
    image.png

  • デフォルトのLint On Saveは 無効化
    image.png

以上です!いたってシンプルな手順で設定ができました。
この状態で.pyファイルを編集すると、コードチェックが動作するようになっているはずです。

おわりに

フォーマットツールは別途blackをインストールし、保存時に自動整形されるように設定しています。(2つ目の参考リンク)
どのみち自動整形のために保存はするし、こまめな保存癖もあるのですが、リアルタイムチェックだと誤った入力(変数名のタイポなど…)をしたときにすぐ気づけたり、書きながら「あ、こういう書き方駄目なのか」とすぐフィードバックが得られたりとメリットも多いので、使い分けかなと思います。

PythonでWeb開発をされている方にはぜひ一度お試しいただきたいです…!

参考リンク

https://qiita.com/psychoroid/items/2c2acc06c900d2c0c8cb
https://blog.daisukekonishi.com/post/python-black/

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

[Day 8]TemplateViewでテンプレートを表示する

January 13, 2021
←前回:Day 7 テンプレートの継承とinclude

今回のテーマは「TemplateViewでテンプレートを表示する」です。
テンプレートを表示するためにはViewに書いた関数でrenderを使えば良いことを紹介してきました。ここでもう1つの表示方法を紹介しておきます。

TemplateViewを使ってみる

汎用的なクラスベースビューにはTemplateView, DetailView, ListViewなどがあり、より少ないコードで表示できるよう工夫してあります。
今回はTemplateViewを使ってみます。
TemplateViewはViewクラスをオーバライドしたテンプレートを表示するためのビュークラスです。
現在のbase/views.pyを書き換えてみましょう。

base/views.py
from django.views.generic import TemplateView

class TopView(TemplateView):
    template_name = 'base/top.html'

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['title'] = 'IT学習ちゃんねる(仮)'
        return ctx

base/urls.pyも書き換えます。

base/urls.py
urlpatterns = [
-    path('', views.top, name='top'
+    path('', views.TopView.as_view(), name='top'),
]

これだけでは何のメリットがあるのか分からないかも知れませんが、クラスベースビューは頻繁に使いますので覚えておいて損はないです。
また、テンプレートにコンテキストを渡す際に、get_context_data関数をオーバーライドする方法もよく用います。(この他にも方法はあるのですが、今はこの方法だけ覚えておけばよいかと思います。)

TemplateViewのもう少し便利な使い方を見ていきましょう。
もしコンテキストをviews.pyから渡す必要がない場合はurls.pyのみで完結することも出来ます。
単順にHMTLファイルを出力したいだけの場合はviews.pyからコンテキストを渡す必要はありません。
今回は利用規約ページがそのようなページに相当する想定で実装してみます。

まずはテンプレートを用意します。

templates/base/terms.html
{% extends 'base/base.html' %}
{% block title %}利用規約 - {{ block.super }}{% endblock %}
{% block content %}

<div class="ui grid stackable">
    <div class="eleven wide column">
        <div class="ui breadcrumb">
            <a href="{% url 'base:top' %}" class="section">TOP</a>
            <i class="right angle icon divider"></i>
            <a class="active section">利用規約</a>
        </div>
        <div class="ui segment">
            <div class="content">
                <div class="header"><h3>利用規約</h3></div>
                <p>あんなことやこんなことを守って下さい。などなど</p>
                <p>...............</p>
                <p>...........</p>
                <p>.......</p>
            </div>
        </div>
    </div>
    {% include 'base/sidebar.html' %}
</div>
{% endblock %}

base/urls.pyは以下のようにします。

urls.py
from django.urls import path
+ from django.views.generic import TemplateView
from . import views
app_name = 'base'

urlpatterns = [
    path('', views.top, name='top'),
+     path('terms/', TemplateView.as_view(template_name='base/terms.html'), name='terms'),
]

ここでブログの方は「ブラウザを確認すると以下のようになります。」と書いてありますがその通りにはなりません。
(私のミスかもしれない、、、)

しかし、下記のコメントで画像は間違えているという記述があり、ここは一旦スルーしても問題ないと判断したため、スルーします。

今回の学習の目的がhtmlの学習ではないので今後も見栄えに関しては気にしないことにします。

おわりに

今回はTempateViewを使ってみました。
内容に関してはまだ、わからないところがいくつかありましたがじょじょに理解していければいいかなと思ったりしています。
今心配なことは、urlが適用されているのかというところが心配になっています。
今日はこれで終わりとします。

それではまたまた

←前回:Day 7 テンプレートの継承とinclude
→次回:Day 8 モデルの作成

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

エクセルからのSqlite3のDB読み込み(ODBCなし)とサジェスト検索

はじめに

おそらくですが大企業では、会社の在庫数・在庫金額・出荷数量統計
回転日数等・粗利等々を瞬間に見れるような端末用ソフトを各営業が
使われているのではないかと思います。

私は中小企業勤務の為、そんな便利なシステムを導入してくれるわけでもなく、
基幹のオラクルのRDBMSよりクライアントソフトにて定期的に吐き出される各種の
CSVデータをソートや加工して、必要なデータを参照してました。

いちいち各種データをそれぞれ別々のCSVで閲覧・整理・加工しないといけないのも
非効率ですし、エクセルでシート毎に各種データのCSVを貼り付けてVlookで
参照するのも、データ量が増えてくるとメモリを無駄に使いますしエクセル関数が
動作するだけで数分待つような事が起こります。

※実際に私が使ってたエクセルファイルは単体で200mb超えており、
 開くだけで数分かかっていた状況でした。)

やりたい事

1:SQlite3のデータベースをエクセルで読み込む、ODBCなし

2:DQファイルからのリスト読み込みと、部分一致によるグーグルのようなサジェスト検索の実装

3:サジェスト検索を元に同じリストの他のカラムの値(価格や在庫数値など)を参照する。

  例:商品名の一部など部分一致のキーワードで候補を出して、選択できるよう
    にするまた参照元のリストはSQlite3のデータベースから読み込む

具体的な方法

1:参照元になるCSVデータをSQlite3形式のDBに変換する
  ※事務所用PCのような低スぺPCでも扱えるように
   何十MBあるようなVlook用の参照元データのシートを付けない

2:エクセルのADOやOBDCなどのアドオンを使わない
  ※PC知識がない事務員さんでも使えるように、アドオンは使わない

参考にしたもの

●参考:ExcelからODBCなしでSQLiteを操作する(設定編)
  https://qiita.com/hisayuki/items/9b42624790ba74a2fb35

●参考:ExcelからODBCなしでSQLiteを操作する(実践編1)
  https://qiita.com/hisayuki/items/cd1b6d7bd1a8293647c8

●参考:エクセルでグーグルサジェストっぽい入力をする
  http://suugleblog.blogspot.com/2012/02/blog-post_4988.html

使うエクセル用ライブラリ(ライブラリというかマクロですが)

●SQLite For Excel Version 1.0
  https://github.com/govert/SQLiteForExcel

●最新バージョン
  https://github.com/govert/SQLiteForExcel/releases/tag/1.0

●上記ライセンス
  https://github.com/govert/SQLiteForExcel/blob/master/License.txt

動作環境

現在の使用環境
  WINDOWS10 64BitPro
  Office2019 64Bit
  Python3.8

準備

●ファイルツリー構造
  必要なファイル構成は以下を参照

SQLiteForExcel
 │   ChangeLog.txt
 │   sqlite3.dll
 │   SQLite3_StdCall.dll
 │   SQLiteForExcel_64.xlsm
 │  
 ├─DataBase (SQlite3のDBファイルは自分はここに入れてます。)
 │ Sample.db
 │
 ├─x64_64BitOS_ライブラリ(OSの種類に合わせて、階層上位の”sqlite3.dll”を入れ替える)
 │ sqlite3.dll
 │
 └─x86_32BitOS_ライブラリ(OSの種類に合わせて、階層上位の”sqlite3.dll”を入れ替える)
    sqlite3.dll

データベース元ネタ

以下の2次配列データをSQlite3のDBへ変換
テスト商品マスタ.xlsx
01.jpg

●以下のコードで上記エクセルをSQlite3のデータベースに変換
  Pythonのコードにて上記の元ネタのエクセルファイルをSQlite3のデータベースの
  ファイルに変換します。
  ※先に、変換先のDBを置くパスに”Sample.db”という名前で、テキストファイルの
   拡張子を変えてデータフレーム格納先のDBファイルを作っておいてください。

Excel2SQlite3.py
import sqlite3
import pandas as pd

print("●エクセルファイルをSQLDBへ変換を開始します。")
dbpath = 'D:/Sample.db'
conn = sqlite3.connect(dbpath)

print("●商品マスタの変換開始")
df = pd.read_excel('D:/テスト商品マスタ.xlsx',encoding="cp932",dtype = 'object')


#検索キーを追加
df.insert(0, '検索キー', df['商品名'] + ':' + df['商品コード'])
print(df)

#テーブルを追加
df.to_sql("Master", conn, if_exists="replace")
del df

conn.close()

●Sample.db
検索キーが1列目のカラムにできました。
この検索キーの部分一致(JANコードか商品名の一部)したときに候補をサジェストする
ようなマクロをエクセルで組みます。また、ここで格納された”Sample.db”については、
上記のツリーのようにファイルを配置してください。
02.jpg

エクセルファイルの準備1:ツリー構造

エクセルファイルでマクロを作成して、以下のツリーになるようにしてください。
●シートの種類
  Sheet1(検索)   <検索キーを検索する為のプルダウンメニュー操作用のシート
  Sheet2(リスト用) <検索一致した候補のリストを回す為の作業用シート
●標準モジュール
  Sqlite3 <これは上記のSQLite For ExcelV1.0を開いて、中身のエクセルファイルを
        使ってください。

●実際のエクセルVBAのエクスプローラー表示のツリー
03.jpg

エクセルファイルの準備2:検索窓と名前の定義

04.jpg

エクセルファイルの準備3:マクロの記述

Sheet1(検索)に対して、以下のマクロを記述します。

Private Sub Worksheet_Change.vba

Private Sub Worksheet_Change(ByVal Target As Range)
Application.Volatile  '値に変更があった場合のみ実行

    Dim testFile As String
    Dim RetVal As Long
    Dim myDbHandle As LongPtr
    Dim myStmtHandle As LongPtr
    Dim InitReturn As Long

'SQL実行分の入力用変数
    Dim SqlOrderSet As String

'SQL各指定用の変数
    Dim SqlTableOrder As String
    Dim SqlRecordOrder As String
    Dim SqlSearchKey As String

'検索候補リストのワークシート指定用
    Dim LWs As Worksheet

Select Case Target.Address
Case "$B$3"

'描画と計算をオフ
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    Application.ScreenUpdating = False


Range("B3").NumberFormatLocal = "@"

'特定の箇所が変更掛かったら、更新スタート

    If Intersect(Target, Range("B3")) Is Nothing Then
        Exit Sub
    Else

        Worksheets("リスト用").Cells.ClearContents

'DBのイニシャライズ
        InitReturn = SQLite3Initialize

        If InitReturn <> SQLITE_INIT_OK Then
            Debug.Print "Error Initializing SQLite. Error: " & Err.LastDllError
            Exit Sub
        End If

'パスの指定
        testFile = ActiveWorkbook.path & "\DataBase\" & "Sample.db"

'DBへの接続
        RetVal = SQLite3Open(testFile, myDbHandle)
        Debug.Print "SQLite3Open returned " & RetVal

'SQL構文のセット
        SqlTableOrder = "Master"
        SqlRecordOrder = "検索キー"
        SqlSearchKey = Range("B3")
        SqlOrderSet = ("SELECT * FROM " & SqlTableOrder & " WHERE " & SqlRecordOrder & " LIKE '%" & SqlSearchKey & "%'")

        RetVal = SQLite3PrepareV2(myDbHandle, SqlOrderSet, myStmtHandle)
        Debug.Print "SQLite3PrepareV2 returned " & RetVal

'SQL命令分の実行
        RetVal = SQLite3Step(myStmtHandle)
        Debug.Print "SQLite3Step returned " & RetVal

'選択された明細数分のデータコラム行を指定してを抜き出し
        r = 1
        Do While RetVal <> SQLITE_DONE

            Worksheets("リスト用").Cells(r, 1).Value = SQLite3ColumnText(myStmtHandle, 1)
            Worksheets("リスト用").Cells(r, 2).Value = CStr(SQLite3ColumnText(myStmtHandle, 3))

            RetVal = SQLite3Step(myStmtHandle)
            r = r + 1
        Loop

        RetVal = SQLite3Finalize(myStmtHandle)
        Debug.Print "SQLite3Finalize returned " & RetVal

        RetVal = SQLite3Close(myDbHandle)

'プルダウンメニュー用の名前の再定義
        Set LWs = Worksheets("リスト用")
        LWs.Range(LWs.Cells(1, 1), LWs.Cells(LWs.Cells(Rows.Count, 1).End(xlUp).row, 1)).Name = "リスト"

    End If

'描画と計算をオン
    Application.EnableEvents = True
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True

'リストをプルダウンさせる。
    Worksheets("検索").Range("B3").Select
    SendKeys "%{DOWN}"



End Select

End Sub


※検索窓や検索するファイルを増やしたい場合は、上記の”Case”を増やす事によって対応します。

使い方

検索窓にキーワードの一部を入れるとその条件と部分一致する検索キーを自動で拾ってきます。
以下のような感じです。
05.jpg

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

pythonコードからkubernetesのpodを作成する方法

はじめに

kubernetesでpodを作るためにはまずpodのyamlファイルを作り、作ったyamlファイルをコマンドでapplyすることにより作成することが出来るが、コマンドを使わずにpythonコードからpodを作成するしたいと思って調べたところ記事が見当たらず、プログラムを書くのに苦労したため共有しておきます。

環境

  • Linux
  • python 3.6.9
  • kubernetesが動く環境

podの基本的な作成方法

まず基本的なpodが作成できるか試してみる。podを作成するyamlファイルは何でもいいのですが、とりあえず簡単なpod.yamlのファイルを作成する。

$ vim pod.yaml

:set pasteでコピペすると楽

pod.yaml
# Podを記述する時のきまり
apiVersion: v1
kind: Pod
# Podの名前やラベルをはじめとする細かい情報
metadata:
  name: myapp-pod  # Podに名前をつける
  labels:
    app: myapp  # ラベル app -> myapp をつける
# Podに配置するコンテナ本体の情報
spec:
  containers:
    - name: demo-container  # コンテナに名前をつける
      image: nginx  # コンテナ名(Docker Hubの名前と一致)
      ports:
        - containerPort: 80  # 80番ポートを使う

pod.yamlをapplyしてpodを作成してみる。

$ kubectl apply -f pod.yaml
pod/myapp-pod created

podが出来たか確認してみる。

$ kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
myapp-pod   1/1     Running   0          34s

作ったpodは用済みなので消しておく。

$ kubectl delete -f pod.yaml
pod "myapp-pod" deleted
$ kubectl get pod
No resources found in c0118272 namespace.

pythonから作成

必要なものをインストールする。

$ pip3 install flask
$ pip3 install kubernetes

pod.yamlと同じディレクトリにpodcreate.pyという名前でpodを作成するpythonプログラムを作成する。namespaceのところは自分のnamespaceの名前に変更する。基本はdefaultになる。

$ vim podcreate.py
podcreate.py
from flask import Flask
from flask import request
from kubernetes import client, config, watch
import yaml

app = Flask(__name__)
@app.route('/')
def pod_create():
    config.load_kube_config()
    with open("pod.yaml", "r") as f:
        pod = yaml.safe_load(f)
        k8s_apps_v1 = client.CoreV1Api()
        resp = k8s_apps_v1.create_namespaced_pod(body=pod, namespace="c0118272")
        print("pod created. status='%s'" % resp.metadata.name)
    return "pod created. status='%s'" % resp.metadata.name

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

ファイルの位置関係を確認する。

$ ls
podcreate.py  pod.yaml

flaskで書いたのでブラウザに表示して実行してみる。

$ export FLASK_APP=podcreate.py
$ flask run --host=0.0.0.0
 * Serving Flask app "podcreate.py"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
 * 

ブラウザで実行してみる。urlはローカルならhttp://0.0.0.0:5000でローカルじゃなければポートは5000でIPアドレスが実行環境ごとに異なる。
1-1.PNG

上の画像のようになれば成功!

コマンドで確認してみる。

$ kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
myapp-pod   1/1     Running   0          3m11s

podが出来てたらやりたいことは完了!!

おわりに

kubernetesのapiを使っていますが難しくてよくわからないので応用方法はそれぞれ調べてみてください!
良き研究ライフを~

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

めぐる式二分探索をPythonで実装する。シンプルな全探索との比較あり。

例えばAtCoderのこの問題 (abc146のC)

線形探索で解こうとすると、探索範囲が$ 1≤A≤10^{9} $ となるので時間内に計算が終わりません。
(※$ 10^{8}$から$10^{9} $程度でTLEになります。)

試しに実装してみましょう。

A,B,X = map(int,input().split())

max_int = 0
for i in range(1,10**9+1):
    if (A * i) + ( B * (len(str(i))) ) <= X:
        max_int = i
    else:
        break

print(max_int)

愚直に1から調べ上げる実装ですが、これを提出すると想定通り、TLEになってしまうものが大半です。このアルゴリズムをみてみると、1から順番に1ずつ大きくしていって条件を満たすかチェックしてます。

ということは$1,2,3,4,5,6,, ,,10^{9}$というソート済みの配列に対して探索をしていることと同じです。このようなソート済みの時に使えるアルゴリズムといえば、、、。そう、二分探索ですよね。

ではアルゴリズムの教科書に書いてあるような方法で実装してみましょう。

A,B,X = map(int,input().split())

#探索したい整数の範囲を定義
left = 1
right = 10 ** 9

#満たすべき条件
def validate(arg):
    if (A * arg) + ( B * (len(str(arg))) ) <= X:
        return True

#条件を満たす最大整数を探す二分探索
max_int = 0
while left <= right:
    mid = (left+right) // 2
    if validate(mid):
        max_int = mid
        left = mid + 1
    else:
        right = mid -1

print(max_int)

通常の2分探索であれば、条件を満たす値が見つかった時点でWhile文を抜けますが、この問題は条件を満たす最大の整数をみつける問題なので、そのまま続けます。ただし条件を満たす場合だけ、max_intを更新するのがポイントです。このコードのmidを答えに使うとバグります。

これでも問題ないのですが、めぐる式で指摘されているように、mid + 1 や mid -1 の部分で気をつけないと最後の境界判定のところでバグりやすいです。またWhileの条件判定も、<なのか<=なのか、間違えやすいポイントでもあります。

そこで、めぐる式を参考にして書き換えるとこうなります。

※めぐる式を参考にすると right-left > 1 となるまで条件を満たす最大のleftを探せばOKということになります。詳しくはこちら

※※めぐる式本家こちらです

A,B,X = map(int,input().split())

#探索したい整数の範囲を定義
left = 0
right = 10 ** 9 + 1

#満たすべき条件
def validate(arg):
    if (A * arg) + ( B * (len(str(arg))) ) <= X:
        return True

#条件を満たす最大整数を探す二分探索
max_int = 0
while abs(right - left) > 1:
    mid = (left+right) // 2
    if validate(mid):
        max_int = mid
        left = mid
    else:
        right = mid

print(max_int)

めぐる式の場合、探索したい範囲の上限、下限より1つずつ大きくするのがポイントです。
このコードの場合、left = 0  right = 10 ** 9 + 1 としてやります。

たいした変化ではないように見えますが、+1 -1がなくなるのと、>= なのか>なのかで悩まなくてもすみますね。

abs(right - left)の差が1になったら終了です。

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

不均衡データへの対応方法について

  • 製造業出身のデータサイエンティストがお送りする記事
  • 今回は不均衡データへの対応方法について整理します。

はじめに

業務で分類問題を実施しなければいけない時に、不均衡データを扱う時がありましたので、対応方法を調査していたら「under sampling」と「over sampling」という方法を見つけましたので、整理します。

不均衡データとは

不均衡データとは、目的変数の分布に大きな偏りがあるデータのことを指します。簡単に言うと、ラベル0が99%、ラベル1が1%といったデータが存在するとき、特段の工夫をせずに分類モデルを生成すると少数派の分類精度の低いモデルになることが知られています(全てラベル0と言っても99%は当たるので当たり前ですが)。
不均衡データを取り扱う場合は、少数派データの識別が目的のケースが多いので、工夫する必要があるという事です。

under samplingとは

少数派のデータ件数に合うように多数派データからランダムに抽出する方法です。この方法は簡単ですが、学習に有用なデータをサンプリング時に捨てたり、全体のデータ数が不足したりするため、次の二つの問題を引き起こす原因になることに注意が必要です。

  1. 学習した分類器の分散が大きくなる問題
  2. 学習後に得られる事後分布が歪む問題

上記のような課題に関して、対策や研究はされておりますが、今回は省略させて頂きます。

over samplingとは

少数派のデータをもとに不足分のデータを補完するというものです。新しいデータを生成するシンプルな方法は下記です。

  • 数値データ:平均と分散を使ってランダム生成
  • カテゴリーデータ:構成比をウェイトにランダム生成

ただ、この方法だと特徴量間の相関が考慮できません。例えば、身長と体重のように相関があるにも関わらず、身長170cm・体重60kgというデータが生成される可能性があり、モデルの精度に影響してしまう場合があります。

このような課題を解決する手法として、SMOTE (Synthetic Minority Oversampling TEchnique)があります。SMOTEは、少ない方のラベルが付いた各データ点同士を線でつなぎ、その線分上の任意の点をランダムに人工データとして生成する手法です。
拡張された手法等もありますが、今回は省略します。

不均衡データへの対応(実装)

今回はkaggleで提供されているCredit Card Fraud Detectionのデータセットを使います。
各レコードには不正利用か否かを表す値(1ならば不正利用)を持っていますが、ほとんどが0で極めて不均衡なデータセットとなっています。

pythonのコードは下記の通りです。

# 必要なライブラリーのインポート
import pandas as pd
from sklearn.datasets import make_classification

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix

# Credit Card Fraud Detectionデータを読み込み
df = pd.read_csv('creditcard.csv')

print(df.shape)
df.head()

スクリーンショット 2021-01-13 9.56.57.png

次に不正利用かどうかは'Class'列に入っておりますので、データの偏りを見てみようと思います。

# 分類クラスのデータ数を確認
df['Class'].value_counts()

スクリーンショット 2021-01-13 9.58.41.png

偏っていることが分かるかと思います。最初は何も対応せずに勾配ブースティング木で分類モデルを作成してみようと思います。

# 欠損値の確認
df.isnull().sum()

# 欠損値データの削除
df = df.dropna()

# データを学習用と検証用に分ける
x = df.iloc[:, 1:30]
y = df['Class']
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state = 40)

# 分類モデル作成
model = GradientBoostingClassifier()
model.fit(x_train, y_train)

# 作成したモデルで、テストデータを予測値
y_pred = model.predict(x_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.4f' %accuracy_score(y_test, y_pred))
# Confusion matrix(test):
# [[3473    0]
#  [   2   14]]
# Accuracy(test) : 0.9994

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))
# precision : 1.0000
# recall : 0.8750

# F値
f1_score(y_pred, y_test)
# 0.9333333333333333

次にUnder Samplingを実施してみようと思います。

# ライブラリ
from imblearn.under_sampling import RandomUnderSampler

# 正例の数を保存
positive_count_train = int(y_train.sum())
print('positive count:{}'.format(positive_count_train))

# 正例が10%になるまで負例をダウンサンプリング
rus = RandomUnderSampler(ratio={0:positive_count_train*9, 1:positive_count_train}, random_state=40)

# 学習用データに反映
x_train_resampled, y_train_resampled = rus.fit_sample(x_train, y_train)
print('y_train_undersample:\n{}'.format(pd.Series(y_train_resampled).value_counts()))
# positive count:40
# y_train_undersample:
# 0.0    360
# 1.0     40

# 分類モデル作成
mod = GradientBoostingClassifier()
mod.fit(x_train_resampled, y_train_resampled)

# 予測値算出
y_pred = mod.predict(x_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.4f' %accuracy_score(y_test, y_pred))
# Confusion matrix(test):
# [[3458   15]
#  [   2   14]]
# Accuracy(test) : 0.9951

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))
# precision : 0.4828
# recall : 0.8750

# F値
f1_score(y_pred, y_test)
# 0.6222222222222222

F値の結果を見ると改悪されている結果になっておりますね。
今回は詳細な調査は実施しませんが、Under Samplingは良くないようですね。

次にOver Samplingを実施します。

# ライブラリ
from imblearn.over_sampling import RandomOverSampler

# 正例を10%まであげる
ros = RandomOverSampler(ratio = {0:x_train.shape[0], 1:x_train.shape[0]//9}, random_state = 40)

# 学習用データに反映
x_train_resampled, y_train_resampled = ros.fit_sample(x_train, y_train)

# 分類モデル作成
mod = GradientBoostingClassifier()
mod.fit(x_train_resampled, y_train_resampled)

# 予測値算出
y_pred = mod.predict(x_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.4f' %accuracy_score(y_test, y_pred))
# Confusion matrix(test):
# [[3471    2]
#  [   2   14]]
# Accuracy(test) : 0.9989

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))
# precision : 0.8750
# recall : 0.8750

# F値
f1_score(y_pred, y_test)
# 0.875

Under Samplingよりは、Over Samplingの方が良い結果にはなっておりますが、何も実施しない時の方がF値は良いですね。
最後にUnder SamplingとOver Samplingの組み合わせSOMTEを使ってみようと思います。

# ライブラリ
from imblearn.over_sampling import SMOTE

# SMOTE
smote = SMOTE(ratio={0:x_train.shape[0], 1:x_train.shape[0]//9}, random_state = 40)
x_train_resampled_smoth, y_train_resampled_smoth = smote.fit_sample(x_train, y_train)

# 分類モデル作成
mod = GradientBoostingClassifier()
mod.fit(x_train_resampled_smoth, y_train_resampled_smoth)

# 予測値算出
y_pred = mod.predict(x_test)

# Accuracyと混同行列
print('Confusion matrix(test):\n{}'.format(confusion_matrix(y_test, y_pred)))
print('Accuracy(test) : %.4f' %accuracy_score(y_test, y_pred))
# Confusion matrix(test):
# [[3469    4]
#  [   2   14]]
# Accuracy(test) : 0.9983

# PrecisionとRecall
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()
print('precision : %.4f'%(tp / (tp + fp)))
print('recall : %.4f'%(tp / (tp + fn)))
# precision : 0.7778
# recall : 0.8750

# F値
f1_score(y_pred, y_test)
# 0.823529411764706

これも結果としては、微妙でした。

さいごに

最後まで読んで頂き、ありがとうございました。
今回は不均衡データへの対応方法について整理しました。
結果としては、良い結果が得られませんでしたが、製造現場では不均衡データは頻繁に発生する課題だと思いますので、ご参考になればと思います。

訂正要望がありましたら、ご連絡頂けますと幸いです。

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

Djangoのrequest.POST['hoge']とrequest.POST.get('hoge')の違い

request.POST['hoge']とrequest.POST.get('hoge')の違いは何か?
今まで何も考えずにrequest.POST['hoge']を使っていましたが(ゴメンナサイ)、どのように使い分ければいいのだろうかと疑問に思ったので調べてみました。

フォームに入力された値は辞書で返される

フォームに入力された値は辞書で返されます。
request.POST['hoge']は、辞書['key'] で辞書の値を取り出しているのと同じ。
request.POST.get('hoge')は、辞書.get('key')で辞書の値を取り出しているのと同じです。

辞書['key']と辞書.get('key')の違い

辞書['key']では、存在しないキーを指定したときにKeyErrorが発生します。
辞書.get('key')では、存在しないキーを指定したときはNoneが返されます。
辞書.get('key', 'default')のように第二引数を設定すると、キーが存在しなかったときに、設定した値をデフォルト値として返してくれます。このため、キーが存在しなくてもエラーが発生しません。

どう使い分けるか

エラーを発生させたいときはrequest.POST['hoge']、エラーを発生させたくない時はrequest.POST.get('hoge')を使えばいい、ということですね。
これからはちゃんと区別して使います…(' ▽ ')ゞ

参考

Pythonの辞書型をマスターしよう!基本的な使い方から応用まで網羅

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

tesuto

src/components/Search.vue
<template>
<div id="app">
  <h1>cats</h1>
  <ul>
    <div class="catList">
    <li>
      <label>
        <input
          type="checkbox"
          :checked="IsAllSelected"
          @click="selectedAllCats">
          select all
      </label>
    </li>
    <li v-for="cat in cats">
      <label>
        <input
        type="checkbox"
        v-model="IsSelectedIds"
        :value="cat"
        @click="select">
        <input type="text"
        v-model="cat.name"
        >
      </label>
    </li>
    </div>
  </ul>
  <button @click="newCat">add</button>
  <button @click="print">print</button>
</div>

</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      cats: [
        {id: 1, name: "amesho"},
        {id: 2, name: "suko"},
        {id: 3, name: "shamu"}
      ],
      IsAllSelected: false,
      IsSelectedIds: [],
      addForm: false
    }
  },
  methods: {
    selectedAllCats(){
      if (this.IsAllSelected){
        this.IsSelectedIds = []
        this.IsAllSelected = false
      } else {
        this.IsSelectedIds = []
        for (var cat in this.cats){
          this.IsSelectedIds.push(this.cats[cat])
        }
        this.IsAllSelected = true
      }
    },
    select(){
      if (this.IsSelectedIds.length !== this.cats.length -1){
        this.IsAllSelected = false
      } else {
        this.IsAllSelected = true
      }
    },
    print(){
      console.log(this.IsSelectedIds)
    },
    newCat(){
      if (!this.addForm){
        const addCatForm = {}
        this.cats.unshift(addCatForm);
        this.addForm = true
      }
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}

.catList li {
  display: list-item;  /* 縦に並べる */
  list-style-type: none;
  text-transform: uppercase;
  padding: 0.5em;
}
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google AI Blogの振り返りから見る、AI研究の重要テーマ集

本記事では、Google AI Blogの2020年の振り返り記事から、「どのようなAI系の研究が今重要とされているのか」、19個のテーマを紹介します。

また私が個人的に衝撃を受けた研究を、4つ紹介します。

0. ベース記事

以下のGoogleのブログ記事をベースとしています。

「Google Research: Looking Back at 2020, and Forward to 2021」
https://ai.googleblog.com/2021/01/google-research-looking-back-at-2020.html
(2021年1月12日発行)

1. AI研究の重要テーマ集(Google版)

上記記事の各見出しから、GoogleでのAI研究の重要テーマを羅列します。

  1. COVID-19 and Health
  2. Research in Machine Learning for Medical Diagnostics
  3. Weather, Environment and Climate Change
  4. Accessibility
  5. Applications of ML to Other Fields
  6. Responsible AI
  7. Natural Language Understanding
  8. Language Translation
  9. Machine Learning Algorithms
  10. Reinforcement Learning
  11. AutoML
  12. Better Understanding of ML Algorithms and Models
  13. Algorithmic Foundations and Theory
  14. Machine Perception
  15. Robotics
  16. Quantum Computing
  17. Supporting the Broader Developer and Researcher Community
  18. Open Datasets and Dataset Search
  19. Research Community Interaction

参考記事内では、各テーマに対して、20年に発表した論文のダイジェストが紹介されています。
気になるテーマについては是非、元の英語ブログをご覧ください。

2. 個人的に気に入った研究を紹介

上記のブログ記事より、私個人的に重要だなと衝撃を受けた研究をピックアップします。

もちろんどれもインパクトのある重要な研究ばかりですが、その中でも、

  • AutoML-Zero: Evolving Code that Learns
  • Lookout:On-device Supermarket Product Recognition
  • Agile and Intelligent Locomotion via Deep Reinforcement Learning
  • Chip Design with Deep Reinforcement Learning

の4つに対して、個人的に衝撃を受けました。
各研究を簡単に紹介します。


AutoML-Zero

AutoML-Zero: Evolving Code that Learns
(2020年7月)

は、AutoMLの分野のNAS(Neural Architecture Search)の新手法です。

これまでのNASは、AutoMLとして探索するネットワークの形の候補はある程度人手でのルールベースでした(例えば、カーネルのサイズは[A,B,C]など)

要は今までのAutoMLは、「自動で最適なディープラーニングモデルを作成する」と言っても、
ハイパーパラメータチューニングの延長に近く、
ただ探索空間が広すぎるので、強化学習やその他の最適化手法を活用していました。

AutoML-Zeroはまったくそうした人手でのディープラーニングのルール(知見)を活用せず、与えるのは単純な演算のみです。

そこから遺伝的アルゴリズムでモデルを進化させていき、gradient descent(勾配降下法)やReLUなどを自動的に発見していきました。

これは、人類の研究成果(ReLUの発見など)を、遺伝的アルゴリズムで自動的に発見できることを示唆し、人が研究するよりも、より良い手法・ディープラーニングの枠組みをコンピュータが自動的に発見できる可能性を示唆しています。

以下の図は、横軸が時間(遺伝的アルゴリズムの発展)、縦軸が正解率を示し、途中途中で人類が発見してきた手法をAutoML-Zeroが発見したタイミングを示しています。

image9.png
(図は参考記事より引用)


Lookout:On-device Supermarket Product Recognition

On-device Supermarket Product Recognition(Lookout)
(2020年8月)

様々な物体に対して、物体を検知し、そこに文字データなどをAR(拡張現実)として表示できます。

こちらは、既にアンドロイドアプリ、

Lookout by Google
https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.reveal

として、利用できるように公開されています。

image2.gif
(図は参考記事より引用)

良くある研究と言えばそうなのですが、きちんとアプリ化され、利用しやすいのが良いです。

またブログ記事(On-device Supermarket Product Recognition(Lookout))では、Lookoutのアーキテクチャや手法も紹介されています。

image1.png
(図は参考記事より引用)


Agile and Intelligent Locomotion via Deep Reinforcement Learning

Agile and Intelligent Locomotion via Deep Reinforcement Learning
(2020年5月)

深層強化学習の新たな手法の提案です。

それまでのGoogle自身での優秀な手法であった、Soft Actor-Critic (SAC)の性能を大きく上回る手法です。

SACでは1時間の実ロボットの稼働データが必要だった4足歩行を、5分弱で学習できるようになりました。

2223.gif
(図は参考記事より引用)

手法としては、Hierarchical Reinforcement Learning(階層型強化学習)とメタラーニングがベースにあります。

アルゴリズムの独自実装はやや難しそうで、まだ強化学習の主要ライブラリ(Ray RLLibなど)には搭載されていません。


Chip Design with Deep Reinforcement Learning

Chip Design with Deep Reinforcement Learning
(2020年4月)

多くの方にとって「深層強化学習って実際のビジネスに使えるの??」という疑問があるかと思います。

私もまだまだこれからだと思うのですが、この研究はCPUなどのチップ上の素子の配置を深層強化学習とグラフニューラルネットワークで最適化した研究です。

チップ上の素子を、配線の総延長距離ができるだけ短くなるように配置させます。

graphembedding.png

image4.png

image3.gif
(図は参考記事より引用)

Google自身が記事内で、

The system is able to generate placements that usually outperform those of human chip design experts, and we have been using this system (running on TPUs) to do placement and layout for major portions of future generations of TPUs.

と、次世代のTPUの素子レイアウトにこの技術を使用している最中と述べ、ビジネスで活用していることを紹介しています。

まだ私自身、最適化に近い生成系において、遺伝的アルゴリズム等よりも深層強化学習の方が適しているケースをうまく言語化できていないのですが、

深層強化学習のビジネス適用が21年はますます増えそうな気がします。

3. おわりに & 参考文献

以上、AI研究の重要テーマ集(Google版)の紹介でした。

21年もGoogleのAI研究は要チェックし、Twitterで発信していきたいと思います。

【参考文献】
●Google Research: Looking Back at 2020, and Forward to 2021
https://ai.googleblog.com/2021/01/google-research-looking-back-at-2020.html

●AutoML-Zero: Evolving Code that Learns
https://ai.googleblog.com/2020/07/automl-zero-evolving-code-that-learns.html

●On-device Supermarket Product Recognition(Lookout)
https://ai.googleblog.com/2020/07/on-device-supermarket-product.html

●Agile and Intelligent Locomotion via Deep Reinforcement Learning
https://ai.googleblog.com/2020/05/agile-and-intelligent-locomotion-via.html

●Soft Actor-Critic: Deep Reinforcement Learning for Robotics
https://ai.googleblog.com/2019/01/soft-actor-critic-deep-reinforcement.html

●Chip Design with Deep Reinforcement Learning
https://ai.googleblog.com/2020/04/chip-design-with-deep-reinforcement.html


【記事執筆者】
電通国際情報サービス(ISID)AIトランスフォーメーションセンター 製品開発Gr
小川 雄太郎
主書「つくりながら学ぶ! PyTorchによる発展ディープラーニング」
(自己紹介詳細)

【情報発信】
Twitterアカウント小川雄太郎@ISID_AI_team

IT・AIやビジネス・経営系情報で、面白いと感じた記事やサイトを、Twitterで発信しています。

【採用情報】
・新卒採用(22年)
データサイエンティスト

・中途採用
AIエンジニア/コンサルタント
AIアーキテクト
中途採用一覧

【免責】
本記事の内容そのものは著者の意見/発信であり、著者が属する企業等の公式見解ではございません


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

VBAユーザがPython・Rを使ってみた:基本的な文法

はじめに

機械学習の勉強を始めたVBAユーザです。
備忘録としてPython・Rの文法をVBAと比較しながらまとめていきたいと思います。

目次

基本的な文法

コメント

まずはプログラムに不可欠なコメントの書き方です。

Python

Python3
# ここはコメントです。
x = 1   # xについてのコメント
print(x)   # 1

#の後がコメントアウトされます。

Jupyter Notebook では、Ctrl + / でその行頭に#が挿入されます(その行全体がコメントアウトされます。)。もう一度 Ctrl + / でコメントアウトが解除されます。

R

R
# ここはコメントです。
x <- 1   # xについてのコメント
print(x)   # 1

#の後がコメントアウトされます。

RStudio では、Ctrl + Shift + C でその行頭に#が挿入されます(その行全体がコメントアウトされます。)。もう一度 Ctrl + Shift + C でコメントアウトが解除されます。
また、Ctrl + Shift + R でコメント行を挿入できます。

VBA

VBA
' ここはコメントです。
x = 1   ' xについてのコメント
Debug.Print x   ' 1

'の後がコメントアウトされます。

代入

Python

Python3
x = 1

# 複数同時代入
x, y = 1, 2
print(x, y)   # 1 2
a, b, c = 1, 2, 3
print(a, b, c)   # 1 2 3
a, b, c = 1, 2.345, "abc"
print(a, b, c)   # 1 2.345 abc

Pythonでは、複数の変数に同時に代入することができます(データ型が異なっても構いません。)。

R

R
x <- 1
print(x)   # 1

x = 2
print(x)   # 2

3 -> x
print(x)   # 3

1 + 2 * (3 - 4) / 5 -> x
print(x)   # 0.6

Rでは、代入演算子に <-=-> があります。
-> は左から右に代入します。複雑な式を書いて最後に代入するときに便利です。

VBA

VBA
x = 1
Debug.Print x   ' 1

VBAでは代入演算子と比較演算子に同じ記号=を使います。
なお、VBAではオブジェクトへの代入はSetステートメントを使います。

複合代入演算子

演算と代入を合わせて行う複合代入演算子(累算代入演算子、累積代入文)についてです。

Python

Python3
n = 1
n += 1
print(n)   # 2
n -= 1
print(n)   # 1
n *= 2
print(n)   # 2
n /= 2
print(n)   # 1.0

s = "abc"
s *= 3
print(s)   # abcabcabc

R

R

Rには複合代入演算子はなさそうです。

VBA

VBA

VBAには複合代入演算子はなさそうです。

条件分岐

条件分岐(主に、If文)についてです。

Python

Python3
x = 0
if x == 0:
    print(0)
# 0

if x == 0:
    print(0)
else:
    print(1)
# 0

if x == 0:
    print(0)
elif x == 1:
    print(1)
elif x == 2:
    print(2)
else:
    print(3)
# 0

if x in (0,1,2):
    print(x)
else:
    print(3)
# 0

Pythonにはswitch文、case文はなさそうです。
なお、Pythonでは、If文のまとまり(ブロック)を(次のRのように{}で囲んだり、VBAのようにIfではじまってEnd Ifで閉じたり、というような方法ではなく)インデント(字下げ)で表現します(If文に限らず、他の制御構文(繰り返しのFor文)でも同様です。)。

R

R
x <- 0
if (x == 0) {
  print(0)
}
# 0

if (x == 0) {
  print(0)
} else {
  print(1)
}
# 0

if (x == 0) {
  print(0)
} else if (x == 1) {
  print(1)
} else if (x == 2) {
  print(2)
} else {
  print(3)
}
# 0

if (x %in% c(0,1,2)) {
  print(x)
} else {
  print(3)
}

Rには、switch関数がありますが、使いづらそうです(参考:R-Tips)。

VBA

VBA
x = 0
If x = 0 Then
    Debug.Print 0
End If
' 0

If x = 0 Then
    Debug.Print 0
Else
    Debug.Print 1
End If
' 0

If x = 0 Then
    Debug.Print 0
ElseIf x = 1 Then
    Debug.Print 1
ElseIf x = 2 Then
    Debug.Print 2
Else
    Debug.Print 3
End If
' 0

Select Case x
    Case 0
        Debug.Print 0
    Case 1
        Debug.Print 1
    Case 2
        Debug.Print 2
    Case Else
        Debug.Print 3
End Select
' 0

Select Case x
    Case 0, 1, 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
' 0

Select Case x
    Case 0 To 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
'0

Select Case x
    Case Is <= 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
' 0

Debug.Print Switch(x = 0, 0)
' 0
Debug.Print Switch(x = 0, 0, 1, 1)
' 0
Debug.Print Switch(x = 0, 0, 1, 1, 2, 2)
' 0

VBAにはSelect Case文があります。Switch関数もあります。

まとめ

一覧

各言語で使用する演算子等を一覧にまとめます。比較のために、EXCELでの計算も示しました。

コメント

Python R VBA EXCEL
コメント # # '

代入

Python R VBA EXCEL
代入 = <-
=
->
=
複数同時代入 可能
複合代入演算子 あり

条件分岐

Python R VBA EXCEL
If文 if P:
 ...
elif Q:
 ...
elif R:
 ...
else:
 ...
if (P) {
 ...
} else if (Q) {
 ...
} else if (R) {
 ...
} else {
 ...
}
If P Then
 ...
ElseIf Q Then
 ...
ElseIf R Then
 ...
Else
 ...
End If
=IF(P,...,
IF(Q,...,
IF(R,...,
...)))
Switch文 Select Case X
Case p
 ...
Case q
 ...
Case r
 ...
Case Else
 ...
End Select
=SWITCH(X,
p,...,
q,...,
r,...,
...)

プログラム全体

参考までに使ったプログラムの全体を示します。

Python

Python3
# コメント
# ここはコメントです。
x = 1   # xについてのコメント
print(x)   # 1

# 代入
x = 1
print(x)   # 1

# 複数同時代入
x, y = 1, 2
print(x, y)   # 1 2
a, b, c = 1, 2, 3
print(a, b, c)   # 1 2 3
a, b, c = 1, 2.345, "abc"
print(a, b, c)   # 1 2.345 abc

# 複合代入演算子(累算代入演算子、累積代入文)
n = 1
n += 1
print(n)   # 2
n -= 1
print(n)   # 1
n *= 2
print(n)   # 2
n /= 2
print(n)   # 1.0

s = "abc"
s *= 3
print(s)   # abcabcabc

# 条件分岐
x = 0
if x == 0:
    print(0)
# 0

if x == 0:
    print(0)
else:
    print(1)
# 0

if x == 0:
    print(0)
elif x == 1:
    print(1)
elif x == 2:
    print(2)
else:
    print(3)
# 0

if x in (0,1,2):
    print(x)
else:
    print(3)
# 0

R

R
# コメント
# ここはコメントです。
x <- 1   # xについてのコメント
print(x)   # 1

# 代入
x <- 1
print(x)   # 1

x = 2
print(x)   # 2

3 -> x
print(x)   # 3

1 + 2 * (3 - 4) / 5 -> x
print(x)   # 0.6

# 条件分岐(If)
x <- 0
if (x == 0) {
  print(0)
}
# 0

if (x == 0) {
  print(0)
} else {
  print(1)
}
# 0

if (x == 0) {
  print(0)
} else if (x == 1) {
  print(1)
} else if (x == 2) {
  print(2)
} else {
  print(3)
}
# 0

if (x %in% c(0,1,2)) {
  print(x)
} else {
  print(3)
}
# 0

VBA

VBA
Sub test_operator()
Dim x As Integer
Dim y As Integer
Dim a As Integer
Dim b As Double
Dim c As String
Dim s As String

' コメント
' ここはコメントです。
x = 1   ' xについてのコメント
Debug.Print x   ' 1

' 代入
x = 1
Debug.Print x   ' 1

' 条件分岐
x = 0
If x = 0 Then
    Debug.Print 0
End If
' 0

If x = 0 Then
    Debug.Print 0
Else
    Debug.Print 1
End If
' 0

If x = 0 Then
    Debug.Print 0
ElseIf x = 1 Then
    Debug.Print 1
ElseIf x = 2 Then
    Debug.Print 2
Else
    Debug.Print 3
End If
' 0

Select Case x
    Case 0
        Debug.Print 0
    Case 1
        Debug.Print 1
    Case 2
        Debug.Print 2
    Case Else
        Debug.Print 3
End Select
' 0

Select Case x
    Case 0, 1, 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
' 0

Select Case x
    Case 0 To 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
'0

Select Case x
    Case Is <= 2
        Debug.Print x
    Case Else
        Debug.Print 3
End Select
' 0

Debug.Print Switch(x = 0, 0)
' 0
Debug.Print Switch(x = 0, 0, 1, 1)
' 0
Debug.Print Switch(x = 0, 0, 1, 1, 2, 2)
' 0

End Sub

参考

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

pythonでセグメント木つくったので紹介する

はじめに

他でもたくさんセグメント木の記事を見かけるので、皆さん使いやすいものを選んでください!(この記事のセグメント木が気に入った方は是非是非、競技プログラミング等で使ってください。大歓迎です。)

参考記事

https://algo-logic.info/segment-tree/
https://qiita.com/takayg1/items/c811bd07c21923d7ec69
https://qiita.com/mosamosa/items/d17ab5af6e19f67202cb

コードの紹介

今日紹介するのはmodeでセグメント木の関数を選べるようにしたものです。以下コード
(2021/1/13コメントを受けif羅列→辞書に修正)

segki_simple.py
import sys
import math
sys.setrecursionlimit(10**7)

class segki():
    DEFAULT = {
        'min': 1 << 60,
        'max': -(1 << 60),
        'sum': 0,
        'prd': 1,
        'gcd': 0,
        'lmc': 1,
        '^': 0,
        '&': (1 << 60) - 1,
        '|': 0,
    }

    FUNC = {
        'min': min,
        'max': max,
        'sum': (lambda x, y: x + y),
        'prd': (lambda x, y: x * y),
        'gcd': math.gcd,
        'lmc': (lambda x, y: (x * y) // math.gcd(x, y)),
        '^': (lambda x, y: x ^ y),
        '&': (lambda x, y: x & y),
        '|': (lambda x, y: x | y),
    }

    def __init__(self, N, ls, mode='min'):
        """
        葉の数N, 要素ls, 関数mode (min,max,sum,prd(product),gcd,lmc,^,&,|)
        """
        self.default = self.DEFAULT[mode]
        self.func = self.FUNC[mode]
        self.N = N
        self.K = (N - 1).bit_length()
        self.N2 = 1 << self.K
        self.dat = [self.default] * (2**(self.K + 1))
        for i in range(self.N):  # 葉の構築
            self.dat[self.N2 + i] = ls[i]
        self.build()

    def build(self):
        for j in range(self.N2 - 1, -1, -1):
            self.dat[j] = self.func(self.dat[j << 1], self.dat[j << 1 | 1])  # 親が持つ条件

    def leafvalue(self, x):  # リストのx番目の値
        return self.dat[x + self.N2]

    def update(self, x, y):  # index(x)をyに変更
        i = x + self.N2
        self.dat[i] = y
        while i > 0:  # 親の値を変更
            i >>= 1
            self.dat[i] = self.func(self.dat[i << 1], self.dat[i << 1 | 1])
        return

    def query(self, L, R):  # [L,R)の区間取得
        L += self.N2
        R += self.N2
        vL = self.default
        vR = self.default
        while L < R:
            if L & 1:
                vL = self.func(vL, self.dat[L])
                L += 1
            if R & 1:
                R -= 1
                vR = self.func(self.dat[R], vR)
            L >>= 1
            R >>= 1
        return self.func(vL, vR)

セグメント木に乗せられるモノイドは大体網羅していると思います(min,max,sum,product,gcd,lmc,^,&,|)。間違い等ありましたらご指摘お願いいたします。

説明

木の構築

木の構築に必要な引数は要素数N,初期要素(list),関数(mode)の3つです。単位元に関しては、DEFAULTで定義しています(変更が必要な場合は変更してください)。計算量O(N)

値の更新

updateで変更するインデックスと変更後の値を指定してください。計算量O(log(N))

値の取得

leafvalueで任意のインデックスの値を取得できます。計算量O(1)。(例えば、値の更新とくみあわせてk番目の値にxを足すといった操作が可能です。)

区間の値の取得

queryで区間の値が取得できます。query(L,R)取得できる値は区間[L,R)の値です。計算量O(log(N))

使用例

1, atcoder ABC185F
https://atcoder.jp/contests/abc185/tasks/abc185_f
https://atcoder.jp/contests/abc185/submissions/19042056
xorのやつです。900msくらい。まだまだ改善の余地ありそうですね、、、

2, atcoder Chokudai SpeedRun 001J
https://atcoder.jp/contests/chokudai_S001/tasks/chokudai_S001_j
https://atcoder.jp/contests/chokudai_S001/submissions/19404893
転倒数を求める問題です。sumが使えます。

3, atcoder Chokudai SpeedRun 001H
https://atcoder.jp/contests/chokudai_S001/tasks/chokudai_S001_h
https://atcoder.jp/contests/chokudai_S001/submissions/19404925
LISの問題です。maxが使えます。

おわりに

ここまでお読みいただきありがとうございました。私自身競技プログラミング初学者ですので、間違い等ございましたらご指摘よろしくお願いいたします。共に競技プロライフ楽しみましょう!!

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