プログラミングにおける循環参照 なぜいけないのか

循環参照という言葉はExcelで見たことがある人もいるかと思いますが、プログラミングの現場においても、起こることはあります。

普通に循環参照って検索すると、大体Excelの話が出てきますが、循環参照自体は下記のサイトの説明がわかりやすいと思います。

https://gimo.jp/glossary/details/circular_definition.html

今回は、弊社のプログラマーさんと話していて、このような構成になっていたところ

「なんでいけないんですか?」

と言われてパッと答えられなかったので書いておきます。

例えば、ここに構成が複雑なオブジェクト、Itemクラスがあります。

Itemオブジェクトはあまりに複雑なので、ユーザーもItemからコピーして作りたいという要望があったとします。

なので、ItemMasterというクラスが爆誕します。で、ItemMasterはItemを参照して、そのItemのコピーをバンバン作っていきます。

ちなみに、できたItemはユーザーが個別にプロパティをちょこちょこ編集できる仕様です。

クラスとプロパティを抜粋して書くと、次のようになります。

<?php


class ItemMaster{

    public $id;
    public $item_id;

}


class Item{

    public $id;
    public $item_master_id;


}

文章で書くと、

「10番のアイテムができがよいので、コピペして作りたい。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。」

ということです。

ここまでは問題がなさそうです。

実際の問題①

アイテムを更新することを考えてみましょう。

「10番のアイテムのプロパティにちょっと変更を加えた、アイテムマスター (Id:1) から作られた アイテムたちに都度変更を加えるのは大変だから、マスターが更新されたらマスターから作られたアイテムはマスターの変更を反映して変更する。つまり11番、12番、13番のアイテムに10番の変更が反映される。」

そのために、次のようにアップデートメソッドを実装します。

class ItemMaster{

    public $id;
    public $item_id;

    public function updateItem(){
        //このマスターから作られたItemを更新する
    }
    //以下続く
}


class Item{

    public $id;
    public $item_master_id;

    public function updateItemMaster(){
        //このアイテムから作られたItemMasterを更新する
    }
    //以下続く

}

ここで、もしItemのプロパティ $item_master_id と ItemMasterのプロパティ $item_id がお互い同じものを見ることになったとします。

つまり

Item $id=10, $item_master_id=1

ItemMaster $id=1, $item_id=10

文章にすると

「10番のアイテムは、アイテムマスター (Id:1) から作られた。」

「アイテムマスター (Id:1) は10番のアイテムから作られた。」

が両立します。すでに頭の中がぐちゃぐちゃになっちゃいますよね。とりあえず、次のような状態です。

「10番のアイテムができがよいので、コピペして作る。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。後日、誤操作で10番もアイテムマスター (Id:1)から作られたことになった。」

実際の問題①としては、では10番のアイテムを更新したとき、10番をもとに作られたアイテムマスター(Id:1)が更新され、そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されることになります

10番はそこで更新されているので、またアイテムマスター (Id:1) を更新します。 そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されます。10番が更新されたので…以下無限に続く。

という無限ループになってしまいます。

実際の問題②

実際の問題②として、ではアイテムマスターから生まれたアイテムをまたアイテムマスターにした時はどうでしょうか?実際の問題①を忘れて頂いて、初期状態になったとします。

「13番のアイテムもできがいいので、アイテムマスターにしよう。」

となった場合

「13番のアイテムをもとに、アイテムマスター(Id:2)を作る。アイテムマスター (Id:2) から、14番、15番のアイテムを作る。13番のアイテムは、アイテムマスター (Id:1)から作られており、 アイテムマスター (Id:1) はアイテム10番から作られている。」

こうなった場合、アイテム10番に更新をした場合、

「 アイテムマスター(Id:1)が更新される。 アイテムマスター(Id:1) から作られた11番、12番、13番が10番と同じ内容に更新される。13番が更新されたので、 アイテムマスター (Id:2) を更新する。 アイテムマスター (Id:2) から、14番、15番のアイテムを更新する。内容は、14番、15番は10番と同じ内容のアイテムになる。 」

ということになります。


この話は、絶対に循環参照がダメという話ではないです。やむを得ずこういう設計になってしまうこともあるでしょう(´ω`)。その場合、こういうリスクがありますよ。ということなので、上記の例でいえば

・絶対にアイテムマスターとアイテムは同じオブジェクトを参照しない

・アイテムマスターから作られたアイテムを、アイテムマスターにしない

などを気を付ければうまく使えるかもしれません。

複雑なオブジェクト イメージ
この世のものは全部複雑ですよねー。


GoogleのCloud Natural Languageを使って文書から日本の住所を抜き出してみる

前回の

に引き続き、今回はGoogleさんが提供しているNLP(自然言語処理)のAPIを試してみたいと思います。

今回の目的としては、ある文章に住所が含まれれば、その住所を抜き出すことが
目的です。

これも、やってみたレベルですし、前回に引き続き、Googleさんがサンプルコードを提供してくれているのでコードのオリジナリティはまったくないですが(笑)、やり方と現時点(2021年7月21日)での Cloud Natural Language が、日本語の住所が文章から抜き出せるかという結果などをまとめておきたいので書いておきます。

さて、GoogleさんのCloud Natural Languageには3種類あります。一つは医療文書向けなので、今回省くとして、ほかの二つはというと下記の二つです。

https://cloud.google.com/natural-language

ふみゅ。私なりに解釈すると、Auto MLは自分でAIをトレーニングしないといけないみたいですね。なので、今回はNatural Language APIというのを使います。

①まずは、Natural Language APIを設定

前回のVision APIと手順は一緒です。面倒ですが、やりましょう。

ちなみに、別にVision APIはNatural Language APIに必要なわけではなく、私がVision API→Natural Language APIの順番でやっているだけです。

手順は下記に書かれています。

https://cloud.google.com/natural-language/docs/setup

②クライアントライブラリをインストール

私は今回もPythonでやるので、

pip install --upgrade google-cloud-language 

とやります。


③いよいよコードを実行

今回は、下記のサンプルを使います。

なぜなら、目的は文章から住所を抜き出すことが目的なので、エンティティ分析というのをやってみたいからです。


https://cloud.google.com/natural-language/docs/samples/language-entities-text

このまま使うと

google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started

というエラーが出ます。前回と一緒ですね!なので、キーファイルをコード内で指定します。

全文は下記の通りです。

from google.cloud import language_v1
import os

#下記はサンプルなので、ご自分のキーファイルの場所に置き換えてください。
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/natural_language_api_key/key.json"

def sample_analyze_entities(text_content):
    """
    Analyzing Entities in a String

    Args:
      text_content The text content to analyze
    """

    client = language_v1.LanguageServiceClient()
    type_ = language_v1.Document.Type.PLAIN_TEXT

    # サンプルではen ですが、日本語でjaに変更します。
    language = "ja"
    
    document = {"content": text_content, "type_": type_, "language": language}

    # Available values: NONE, UTF8, UTF16, UTF32
    encoding_type = language_v1.EncodingType.UTF8

    response = client.analyze_entities(request = {'document': document, 'encoding_type': encoding_type})

    # Loop through entitites returned from the API
    for entity in response.entities:
        print(u"Representative name for the entity: {}".format(entity.name))

        print(u"Entity type: {}".format(language_v1.Entity.Type(entity.type_).name))

        print(u"Salience score: {}".format(entity.salience))

        # Loop over the metadata associated with entity. For many known entities,
        # the metadata is a Wikipedia URL (wikipedia_url) and Knowledge Graph MID (mid).
        # Some entity types may have additional metadata, e.g. ADDRESS entities
        # may have metadata for the address street_name, postal_code, et al.
        for metadata_name, metadata_value in entity.metadata.items():
            print(u"{}: {}".format(metadata_name, metadata_value))

        # Loop over the mentions of this entity in the input document.
        # The API currently supports proper noun mentions.
        for mention in entity.mentions:
            print(u"Mention text: {}".format(mention.text.content))

            # Get the mention type, e.g. PROPER for proper noun
            print(
                u"Mention type: {}".format(language_v1.EntityMention.Type(mention.type_).name)
            )

    # Get the language of the text, which will be the same as
    # the language specified in the request or, if not specified,
    # the automatically-detected language.
    print(u"Language of the text: {}".format(response.language))

sample_analyze_entities('昔々、あるところに一匹のこざるがおりました。')

‘昔々、あるところに一匹のこざるがおりました。’

という文章をエンティティ分析にかけたわけです。

結果は次の通り。

Representative name for the entity: ところ
Entity type: OTHER
Salience score: 1.0
Mention text: ところ
Mention type: COMMON
Representative name for the entity: 1
Entity type: NUMBER
Salience score: 0.0
value: 1
Mention text: 1
Mention type: TYPE_UNKNOWN
Language of the text: ja

“ところ”、と漢数字の”一”をちゃんと認識してくれていますね!

entityの各意味については、下記をご覧ください。

https://cloud.google.com/natural-language/docs/reference/rest/v1beta2/Entity

さて、もっと具体的な住所を入れてみます。エンティティ分析をする文章を次のように変更します。

‘昔々、神奈川県横浜市西区北幸2丁目10−39 日総第5ビル 9Fに一匹のこざるがおりました。’

これでやってみると、ドン!

Representative name for the entity: 神奈川県
Entity type: LOCATION
Salience score: 0.28228825330734253
wikipedia_url: https://en.wikipedia.org/wiki/Kanagawa_Prefecture
mid: /m/0gqm3
Mention text: 神奈川県
Mention type: PROPER
Representative name for the entity: 横浜市
Entity type: LOCATION
Salience score: 0.2538958787918091
wikipedia_url: https://en.wikipedia.org/wiki/Yokohama
mid: /m/0kstw
Mention text: 横浜市
Mention type: PROPER
Representative name for the entity: 西区
Entity type: LOCATION
Salience score: 0.2538958787918091
mid: /m/0d28cy
wikipedia_url: https://en.wikipedia.org/wiki/Nishi-ku,_Yokohama
Mention text: 西区
Mention type: PROPER
Representative name for the entity: 北幸
Entity type: LOCATION
Salience score: 0.2099200040102005
mid: /g/121d1tc6
wikipedia_url: https://ja.wikipedia.org/wiki/北幸
Mention text: 北幸
Mention type: PROPER
Representative name for the entity: 神奈川県横浜市西区北幸2丁目10−39
Entity type: ADDRESS
Salience score: 0.0
locality: 横浜市
street_number: 1039
sublocality: 西区
broad_region: 神奈川県
country: JP
Mention text: 神奈川県横浜市西区北幸2丁目10−39
Mention type: TYPE_UNKNOWN
Representative name for the entity: 2
Entity type: NUMBER
Salience score: 0.0
value: 2
Mention text: 2
Mention type: TYPE_UNKNOWN
Representative name for the entity: 10
Entity type: NUMBER
Salience score: 0.0
value: 10
Mention text: 10
Mention type: TYPE_UNKNOWN
Representative name for the entity: 39
Entity type: NUMBER
Salience score: 0.0
value: 39
Mention text: 39
Mention type: TYPE_UNKNOWN
Representative name for the entity: 5
Entity type: NUMBER
Salience score: 0.0
value: 5
Mention text: 5
Mention type: TYPE_UNKNOWN
Representative name for the entity: 9
Entity type: NUMBER
Salience score: 0.0
value: 9
Mention text: 9
Mention type: TYPE_UNKNOWN
Representative name for the entity: 一
Entity type: NUMBER
Salience score: 0.0
value: 1
Mention text: 一
Mention type: TYPE_UNKNOWN
Language of the text: ja

一気に情報量が増えましたね。( ˊᵕˋ )

下記のあたりに注目すると、

Representative name for the entity: 神奈川県横浜市西区北幸2丁目10−39
Entity type: ADDRESS
Salience score: 0.0
locality: 横浜市
street_number: 1039
sublocality: 西区
broad_region: 神奈川県
country: JP
Mention text: 神奈川県横浜市西区北幸2丁目10−39
Mention type: TYPE_UNKNOWN

神奈川県横浜市西区北幸2丁目10−39は住所(Entity typeがADDRESS)として認識できたわけです!

しかも、神奈川県→横浜市→西区 と構造も認識できていますね。しかし、番地がどうかというと

street_number: 1039

となっていて、10-39という形の住所が認識できなかったようです(泣)。

また、 ビル名とフロア数の「日総第5ビル 9F」は、住所の一部として認識してくれなかったようです。(泣)

これは、 「日総第5ビル 9F」 の後に半角スペースを入れて、文章を

‘昔々、神奈川県横浜市西区北幸2丁目10−39 日総第5ビル 9F に一匹のこざるがおりました。’

としてみても結果は一緒でした。

ImaLittleMonkey


Google Cloud Vision APIを使って画像から文字を読み取る

ほぼ、「やってみた」レベルではありますが、自分のメモとしても書いておこうと思います。

やりたかったことは、画像から文字を読み取りたい、ということです。そのために、Google Cloud Visionを使います。

最初に言うと、プログラミング的なことはほぼGoogleさんが提供するサンプルコードでできるので、ここで独創的なことは何もやっていないのですが、わかりづらかったのはGoogle CloudのAPIを有効にすることなので、そこを絡めてメモしておきたいと思いました(笑)。
なので、その辺を含め紹介していきます。

①Google Cloudにアカウントがなければ作りましょう。
Google Cloud Console

②Vision APIを設定にします。
下記を読んで、手順通りにやります。
https://cloud.google.com/vision/docs/setup

途中、秘密鍵を設定する、環境変数でそれを設定する、など面倒なくだりがありますが、やります。

Cloud SDKのインストールもします。これ、結構時間がかかります。

③②まで終わったら、次は下記のURLを見ましょう。

https://cloud.google.com/vision/docs/quickstart-client-libraries

クライアント ライブラリをインストールする

のあたりから始めます。私はPythonで行きます。(`・ω・´)

pip install --upgrade google-cloud-vision

とやります。

で、ついにサンプルを動作させることができます!

下記の猫ちゃんの写真を読み込むサンプルを、Googleさんが用意してくれています。ありがたや~

https://raw.githubusercontent.com/googleapis/python-vision/master/samples/snippets/quickstart/resources/wakeupcat.jpg

import io
import os

# Imports the Google Cloud client library
from google.cloud import vision

# Instantiates a client
client = vision.ImageAnnotatorClient()

# The name of the image file to annotate
file_name = os.path.abspath('resources/wakeupcat.jpg')

# Loads the image into memory
with io.open(file_name, 'rb') as image_file:
    content = image_file.read()

image = vision.Image(content=content)

# Performs label detection on the image file
response = client.label_detection(image=image)
labels = response.label_annotations

print('Labels:')
for label in labels:
    print(label.description)

上記を実行してみるも…

 
Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see... 

というエラーが出ます。

セイセイセイ~ ちょっと待ってくださいよ~ 

と言いたくなりますが、落ち着いてググると、下記に情報があります

https://stackoverflow.com/questions/45501082/set-google-application-credentials-in-python-project-to-use-google-api

下記の一文を、コードに追加します。

os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"
#上記はただのサンプルなので、D:/以下はご自分のキーファイルの場所を指定してください。 

すると、次は課金が有効ではない、というエラーが出ます。

Google Cloudで課金を有効にします。下記のリンクを見てやりましょう。
https://cloud.google.com/billing/docs/how-to/modify-project?hl=ja&visit_id=637623682365584203-1106306741&rd=1#confirm_billing_is_enabled_on_a_project

はぁ、はぁ、ついに実行できましたかね?

コードは全文でこのようになります。

import io
import os

# Imports the Google Cloud client library
from google.cloud import vision

os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"

# Instantiates a client
client = vision.ImageAnnotatorClient()

# The name of the image file to annotate
file_name = os.path.abspath('wakeupcat.jpg')

# Loads the image into memory
with io.open(file_name, 'rb') as image_file:
    content = image_file.read()

image = vision.Image(content=content)

# Performs label detection on the image file
response = client.label_detection(image=image)
labels = response.label_annotations

print('Labels:')
for label in labels:
    print(label.description)

実行すると

Labels:
Cat
Window
Felidae
Carnivore
Jaw
Ear
Small to medium-sized cats
Window blind
Gesture
Whiskers

と表示され、おお~ 画像が読み込まれた!というのがわかります。

えー、しかし、上記のは画像にラベルをつけるというサンプルです。

我々の目標は、書いてある字を読み取ることでした!

今度はこちらのサンプルを使います。

https://cloud.google.com/vision/docs/ocr?hl=ja

今回は、下記の画像を使います。とある日の私のコンビニの領収書です。

なので、ちょっと上記を変更します。text_detection()というメソッドを使います。


import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="D:/vision_api_key/key.json"

def detect_text(path):
    """Detects text in the file."""
    from google.cloud import vision
    import io
    client = vision.ImageAnnotatorClient()

    with io.open(path, 'rb') as image_file:
        content = image_file.read()

    image = vision.Image(content=content)

    response = client.text_detection(image=image)
    texts = response.text_annotations
     
    return texts

texts = detect_text("receipt.jpg")
print('\n"{}"'.format(texts[0].description))

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

"横浜北幸2丁
神奈川県横浜市西区北幸 2丁目9-
目店
電話: 045-324-5611
レジ#4
領 収書
7PLメンスヒ*オレSイ化粧水ソープ
スターハックスアイスチャイティーラテ
7Pセフンフレット3枚入
009
00乙米
*88
小 計 (税抜8%)
消費税等( 8%)
小計 (税抜10%)
消費税等(10%)
合 計
(税率 8%対象
(税率10%対象
(内消費税等8%
(内消費税等10%
nanaco支払
お買上明細は上記のとおりです。
[]マークは軽滅税率対象です。 nanaco番号 nanaco残高 今回ポイント ポ”イント残高 伝票番号 半288 半23 009夫 09夫 ギ971 半311) (099
半23)
(09夫
工L6夫

****4717
ギ3, 357
LTLヤ
4 P
240P
210-621-454-5134
Thet x帆術海戦
プレゼントキャンペーン
セブンネットでお買い物&セブンイレブン受取りで
応募者から抽選で711名様に
呪術迴戰ォリジナルQUOカード
をプレゼント
thopping
*判>11
vG-MーC4ト
O芥見下々/菜社,呪術圈戦割作载同会
"

ちょっとね~ うーん、残念!

という感じですね。ただ、定型文みたいなものはほぼ読み込めていると思われます。

モデルを作ろう

弊社では、プログラマー向けに月に一度ぐらいの頻度で勉強会を行っています。

この度、私もやることになって💦 話した内容になります。

プログラミング中級者以上向けに話をしました。

複雑なことをプログラムして、動かす。それをメンテナンスしていく。

とても難しいことです。

私が思う、一つのカギは、よいモデルを作ることです。

でも、

「モデルって何なの?」

という話があまりネットにありません。MVCという文脈で語られるモデルについてはいっぱいあると思うんですが。

なので、まずはモデルの説明と、モデルをどう作っていくべきかという話、そしてまずはモデルを作っていってほしいをチームの皆さんに話したほうがいいと思い、社内勉強会でその話をしました。

スライドシェアにその内容を、多少加工したものをアップしました。

スライド中のサンプルはPHPで書いてますが、オブジェクト指向ができる言語なら、言語はなんでもいいと思います。

ご参考になれば幸いですし、ご意見があればお寄せください。

Android CPUの動作をモニターする

時に、Androidの開発でCPUがどれぐらい使われてるかな~

とかモニタリングしたい時があります。

CPUやネットワークを使う動きは、バッテリーを消耗するからモニタリングしたいとか、CPUを動作させるようなプログラムがちゃんと意図したとおりに定期的に動作しているか、などを確認することができます。

やり方はとってもカンタン!です。

1.AndroidStudioの下にある「Profiler」というタブをクリックするか、[View] > [Tool Windows] > [Profiler] を選択でProfilerを開きます。

2.動作を確認したいアプリをRunします。

そんだけ。

Profilerの画面に、下記のようにCPUとかメモリとか、ネットワーク利用とか、バッテリ使用量が出てきます。便利!

停止するときは、赤い■ボタンを押します。

CPUの行をクリックすると、CPUがどのように使われているかが一覧の画面より詳しくわかります。

どのスレッドがいつ発生しているかとかもわかりますね。

ライブラリを組み込んでいるときは、ライブラリのスレッドの動作も出てきます。

画面をタップして違う画面に遷移したりしていろいろ計測できますので、やってみてください!

公式は下記です。

https://developer.android.com/studio/profile/cpu-profiler?hl=ja