GoogleColaboratoryから希望するディレクトリにGoogleスプレッドシートを作成する

希望するディレクトリにGoogleスプレッドシートを作成する

前回のエントリーのなんとなく続きのエントリーになります。

uepon.hatenadiary.com

前回は意図したGoogleDriveのディレクトリにスプレッドシートを作成することができませんでした。作成できるのはルート(マイドライブ)の直下のみの制限付き。(gspreadの制限という感じですが…)

今回は、直接GoogleDriveの指定したパスに対してファイル(スプレッドシート)を作成し、以後そのスプレッドシートのKey(id)またはURLを別途取得してファイルを更新をするにはということを考えたいと思います。

使用するもの

今回はgspread以外にPyDriveというパッケージを使用することにしました。

pypi.python.org

github.com

PyDriveは以下のような機能を持っています。

  • Simplifies OAuth2.0 into just few lines with flexible settings.
  • Wraps Google Drive API into classes of each resource to make your program more object-oriented.
  • Helps common operations else than API calls, such as content fetching and pagination control.

簡単にいうとGoogleDriveの操作用のラッパーパッケージになります。こちらもわざわざGoogleDriveをマウントしなくてもファイル群を使えるので便利です。マウント作業とはいったい何だったのか!

PyDriveモジュールのインストール

PyDriveのインストールは以下のコマンドを実行すればOKです。

!pip install PyDrive

f:id:ueponx:20180406161707p:plain

無事にインストールできました。

基本的にはこれまでと同様に認証してからファイルへのアクセスを行うことになります。 ドキュメントをみると、以下のようなコードを実行すれば認証ができるということだったのですが、

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

gauth = GoogleAuth()
gauth.LocalWebserverAuth()

drive = GoogleDrive(gauth)

ちょっと書き換えをして以下の様にしてみました。

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

# auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

print('--- PyDrive Auth OK ---')

コードを実行してみるとこんな感じになります。

f:id:ueponx:20180408004235p:plain

恒例の認証処理を行っていきます。基本はクリック程度の作業で、一度行ってしまえばインスタンスが有効である間は認証の2回目以降省略されます。

まずは使用するアカウントを選択します。

f:id:ueponx:20180406183813j:plain

アカウントを選択したらGoogleDriveのアクセスへの許可を行います。【許可】ボタンをクリックします。すると以下の様な認証コードが表示されるので

f:id:ueponx:20180406183918j:plain

コードをコピーして

f:id:ueponx:20180407183436p:plain

Notebookのタブに戻って入力ボックスにこのコードをペーストして

f:id:ueponx:20180408004323p:plain

Enterキーを押します。

f:id:ueponx:20180407182758p:plain

これで認証は完了です。自由にGoogleDriveへアクセスができるようになっています。 以下のようにすればファイルの作成ができるようになります。

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

print('--- PyDrive Auth OK ---')

file1 = drive.CreateFile({'title': 'Hello.txt'})
file1.SetContentString('Hello')
file1.Upload()

print('--- PyDrive CreateFile OK ---')

実行すると以下のようになります。

f:id:ueponx:20180407190133p:plain

正常に実行できました。あとはGoogleDriveにファイルが生成されているかを確認します。

f:id:ueponx:20180407184423p:plain

Hello.txtのファイルが無事にできていると思います。内容にも'''Hello```と入っているかなと思います。内容を簡単に説明するとファイルオブジェクトを作成しているのがCreateFile()、ファイルの内容のセットがSetContentString()、具体的なGoogleDriveファイルの生成処理がUpload()になっています。

うまく行ったかなと思うのですが…ちょっと気になる点があります。この処理を2回実行すると同名のファイルが2つ作成されてしました。 画像としては以下の様になります。

f:id:ueponx:20180407190447p:plain

そのままCreateFile()を行うとファイル名は同じですがKeyは異なるファイルが生成されるようです。別途上書きをするメソッドもなさそうです。 つまり、2回目以降のアクセスに関しては名前からファイルを作成するのではなく一意に振られるKeyやURLを使用する必要があります。

重複したファイルが生成されないようにするには?

いろいろ考えてみました。

簡単に考えると以下のような処理になるかなと。

  1. ファイルの所在を確認
  2. ファイルが所在していなければファイルを作成
  3. ファイルのKey(id)を取得
  4. Key(id)を使用してgspreadなどでスプレッドシートのファイルを開く
  5. スプレッドシートの編集

ファイルの所在の確認処理は、PyDriveを使って各ディレクトリを走査し、 ファイル一覧を取得していくことになります。また、ファイルの作成は、GoogleDriveディレクトリ構成を書くファイルの属性値として持っている構成なので、その中のparent_id属性に親フォルダのkey(id)を与えることで指定したディレクトリの下にファイルを作成することになります。

例えば/aaa/bbb/c.sheetなるファイルを処理する場合にはこうなるのかなと思います。

  1. マイドライブのファイルリストからaaaというディレクトリ名のkeyを取り出す
  2. 前の手順で取得したkeyをもとにマイドライブ/aaaのファイルリストからbbbというディレクトリ名のkey(id)を取り出す
  3. 前の手順で取得したkeyをもとにマイドライブ/aaa/bbbのファイルリストの中にc.sheetというファイルがあるか調べる
  4. c.sheetというファイルがなければファイルを作成する。(作成時にKey(id)が取得できるようになる)
  5. c.sheetというファイルがあればKey(id)が取得する
  6. 取得したスプレッドシートファイルのkey(id)を使ってgspreadを使用して処理を行う

もう少しロジックを考えてみた

とはいっても、GoogleDriveはフラットな構成なのでリスト取得は一回やればいいのかなと思うので以下の様に修正してみました。

  1. マイドライブのファイルリストを取得する
  2. ファイルリスト中にparentがマイドライブ(root)でありaaaという名前のディレクトリがあるか調べる
  3. ない場合にはフォルダを作成する
  4. aaaディレクトリのkeyを取得する
  5. ファイルリスト中にparentがkeyでありbbbという名前のディレクトリがあるか調べる
  6. ない場合にはフォルダを作成する
  7. bbbディレクトリのkeyを取得する
  8. ファイルリスト中にparentがkeyでありc.sheetという名前のファイルがあるか調べる
  9. c.sheetというファイルがなければファイルを作成する
  10. c.sheetのkeyを取得する
  11. 取得したスプレッドシートファイルのkeyを使ってgspreadを使用して処理を行う

処理が増えたような気がしますが単純になったこととリストを何回も取得しなくて良くなった点はよいのかなと思います。

このロジックから以下のようなidの取得までのコードをかいてみました。(途中のパス(ディレクトリ)でエラーが発生した場合の対応できてません)

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

print('--- PyDrive Auth OK ---')

def get_id(path_str):

  fileDict = {}

  file_list = drive.ListFile({'q': "trashed=false"}).GetList()
  for f in file_list:
    if(len(f['parents']) != 0): # 他のユーザからの共有ファイルはparents属性がない。
      # print('title: %s, id: %s, parent_id %s' % (f['title'], f['id'], f['parents'][0]['id']))
      tmp = {'title':f['title'], 'ID':f['id'], 'parentsID':f['parents'][0]['id']}
      fileDict[f['title']] = tmp

  #for key, value in fileDict.items():
  #  print(key, value)

  path_str_list = path_str.strip('/').split('/')

  print('-> pathList:')
  for i,p in enumerate(path_str_list):
    if(p in fileDict):
      print(fileDict[p])
    else:
      print('ファイル新規作成')
      nf = drive.CreateFile({'title': p, 'mimeType': 'application/vnd.google-apps.spreadsheet', 'parents': [{'kind': 'drive#parentReference', 'id': fileDict[path_str_list[i-1]]['ID']}]})
      nf.Upload()
      return nf['id']
  else:
    return fileDict[p]['ID']
  
path_string = "/Colab Notebooks/sample/SpreadSample"
d = get_id(path_string)
print('id = %s' % d)

注意点としてはファイルの作成時にMime-Typeを指定していないとスプレッドシートファイルを作成できない点でしょうか。具体的に抜き出すとこの処理になります。

nf = drive.CreateFile({'title': p, 'mimeType': 'application/vnd.google-apps.spreadsheet', 'parents': [{'kind': 'drive#parentReference', 'id': fileDict[path_str_list[i-1]]['ID']}]})

ここ実行するとこんな感じになります。

f:id:ueponx:20180421143800p:plain

スプレッドシートもできています。

f:id:ueponx:20180421144103p:plain

ちなみに2回目の実行をおこなってみたのですが、新規作成は行われませんでした。

f:id:ueponx:20180421145246p:plain

f:id:ueponx:20180421145428p:plain

一応、無事に動作できたようです。

終わりにというか、追記

今回はこんなロジックでつくってみたのですが、GoogleDriveのファイル数多くある場合にはかなり遅くなります。そのため、最初に作成したロジックのものも作ってみました。ファイル数が多い場合にはこちらのほうが有効かもしれません。

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

print('--- PyDrive Auth OK ---')

# ファイルのPathからIDを検索する
def get_id_list(path_str):

  # parentsのidで検索してフォルダ、ファイル名が一致するオブジェクトのidを返す
  def find_path_id(parent_id, name):
    file_list = drive.ListFile({'q': "'%s' in parents and trashed=false" % parent_id}).GetList()
    for f in file_list:
      if (f['title']==name):
        return f['id']
  
  path_str_list = path_str.strip('/').split('/')
  parent_id_dict = {}
  filename = path_str_list[-1]
      
  print('---Process---')
  parent_id = 'root'
  for p in path_str_list:
    current = p
    # print('parent_id: %s current: %s' % (parent_id, current))
    parent_id_dict[current] = parent_id
    parent_id = find_path_id(parent_id, current)
  print('<-- parent_id -->: %s <-- current -->: %s' % (parent_id , current))
  if(parent_id == None):
    print('ファイル新規作成')
    print('<-- parent_id -->: %s <-- filename -->: %s' % ((parent_id_dict[filename]), filename))
    f = drive.CreateFile({'title': filename, 'mimeType': 'application/vnd.google-apps.spreadsheet', 'parents': [{'kind': 'drive#parentReference', 'id': parent_id_dict[filename]}]})
    f.Upload()
    # print(f['id'])
    return f['id']
  else:
    id = find_path_id(parent_id_dict[filename], filename)
    # print(id)
    return id
  
path_string = "/Colab Notebooks/sample/SpreadSample"
try:
  d = get_id_list(path_string)
  print('id = %s' % d)
except:
  print('raise exception')

1回目の実行 f:id:ueponx:20180421153831p:plain

2回目の実行 f:id:ueponx:20180421153910p:plain

あとはこのkey(id)を使用して、以下のようなコードを追記してあげると

import gspread

gc = gspread.authorize(GoogleCredentials.get_application_default())
sh = gc.open_by_key(key)
worksheet = sh.get_worksheet(0)

# set value
worksheet.update_acell('A1', 'foo')

# set values
cell_list = worksheet.range('A2:C3')
for cell in cell_list:
    cell.value = 'bar'

worksheet.update_cells(cell_list)

# get value
val = worksheet.acell('A1').value
print(val)

# get value
val = worksheet.cell(2, 2).value # B2 cell
print(val)

実行するとこんな感じでちゃんと動作していました。

f:id:ueponx:20180421161053p:plain

スプレッドシートにもちゃんと値も設定されていますし、値も取得できました!

f:id:ueponx:20180421161135p:plain

一応、やりたいことはできたのですがかなり辛かった…多分もっと簡単な方法があるに違いない。

【参考】

uepon.hatenadiary.com uepon.hatenadiary.com uepon.hatenadiary.com uepon.hatenadiary.com

Google ColaboratoryでGoogleスプレッドシートを読み書きしてみる

Google ColaboratoryでGoogleスプレッドシートを読み書きしてみる

前回のエントリでは、GoogleColabで画像表示ができるようになりました。これでOpenCVを使った画像処理も安心して勉強ができるようになりました。

uepon.hatenadiary.com

では、そのほかのファイル、特にWebスクレイピングをした結果をGoogleスプレッドシートに格納といった用途はあるかなと思います。 具体的にはセルから値を取り出したり、格納したりとなりますが、それができるかを確認してみようと思います。

f:id:ueponx:20180408110634p:plain

必要になるパッケージ

GoogleColabというかpythonGoogleスプレッドシートのファイルを扱うには‘‘‘gspread‘‘‘というモジュールを用いると便利です。

github.com

特徴としては…

  • Google Sheets API v4.
  • Open a spreadsheet by its title or url.
  • Extract range, entire row or column values.
  • Python 3 support.

python3に対応しているので助かります。また、スプレッドシートを開く際にはファイル名(タイトル)かURLが指定できるので、共有設定の入っているようなGoogle Drive上のスプレッドシートに関してもデータの操作が行えるのが便利です。

gspreadパッケージのインストール

以下のコマンドでインストールすることができます。

!pip install gspread

既にインストール済みであれば-Uつけてインストールしますが今のところ不要の様です。

f:id:ueponx:20180406161141p:plain

このように表示されればインストールは完了です。

gspreadパッケージを使用することで以前のエントリーのような特別なGoogle Driveディレクトをマウントする作業なしで(というかこのパッケージとgoogle.colabパッケージ、oauth2clientパッケージを使用することで、直接Google Driveへアクセスする事ができます)

python側の標準的なファイルアクセス処理を行う場合にはマウントすることにもメリットはありますが、Googleスプレッドシートを単に読み込んだりするだけであればgspreadパッケージを使用するほうが楽かもしれません。(認証作業の回数も減りますし)

基本的な操作

基本的な操作としてはGithubのパッケージのドキュメントで抑えられると思いますが、念のため。

github.com

以下のテストの実行にあたっては、事前にマイドライブ上SpreadsheetSampleというスプレッドシートファイルを作成しておきます。存在しないとエラーが出ます!

Google Drive上は以下のような感じになっていて

f:id:ueponx:20180406162730p:plain

ファイルは存在していればよいので、ワークシートは空で大丈夫です。

f:id:ueponx:20180406183020p:plain

ではNotebookに以下のコードを張り付けて実行してみます。 内容としてはファイルをオープンして指定したセルに値を格納し、格納後さらに値を取得するという単純なものです。

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 'SpreadsheetSample'というスプレッドシートの先頭ワークシートをオープン
worksheet = gc.open('SpreadsheetSample').get_worksheet(0)

# A1セルに'foo'という値を上書き
worksheet.update_acell('A1', 'foo')

# A2からC3のセルエリアに'bar'を一括で上書き
cell_list = worksheet.range('A2:C3')
for cell in cell_list:
    cell.value = 'bar'
worksheet.update_cells(cell_list)

# A1セルの値を取得し、表示
val = worksheet.acell('A1').value
print(val)

# A1セルを0,0とするようなセル指定で
# 2,2(B2)の位置のセルを取得
val = worksheet.cell(2, 2).value
print(val)

このコードを【Shift】+【Enter】で実行します。

すると、認証処理が実行されます。(一度認証すればインスタンス実行中は認証処理は不要の様です)

f:id:ueponx:20180406183720j:plain

認証用のURLと認証キーのinputboxが表示されるので、URLのリンクをクリックします。 すると使用するDriveのアカウント選択に遷移します。

f:id:ueponx:20180406183813j:plain

使用するアカウントを選択すると、使用可能な機能の確認画面に遷移します。

f:id:ueponx:20180406183918j:plain

問題ないかを念のため確認して【許可】ボタンをクリックします。すると画面が遷移し、認証キーが発行されるのでこれを コピーして、Notebookのタブへ移動します。

f:id:ueponx:20180406184041j:plain

あとは認証キーをinputboxに張り付けて

f:id:ueponx:20180406184147j:plain

【Enter】キーを押せば認証が完了します。認証の完了後はPythonのコードが実行され結果表示に以下のような表示がされれば 正常に実行できました。

【実行結果】

foo
bar

f:id:ueponx:20180406163443p:plain

では、スプレッドシートの中身を確認してみます。

f:id:ueponx:20180406184524p:plain

A1のセルに'foo'が入っていて、A2-C2、A3-C3の領域に'bar'が格納されていれば正常に動作しています。 これでスプレッドシートの値の取得や格納もNotebookのコードから実行ができるようになりました。 意外と簡単にできました。

マイドライブ以外のディレクトリに存在する既存のスプレッドシートを開いてみる

先ほどはマイドライブ内にあるスプレッドシートのファイル名を指定して開きましたが、今度は任意のフォルダにあるスプレッドシートを開いてみます。 マイドライブの下にsheetディレクトリを作成し、その中にsampleというスプレッドシートファイルを作成します。

以下の画面のような状況になります。

f:id:ueponx:20180406193026p:plain

f:id:ueponx:20180406193114p:plain

ではこれを開いてみます。

エラーの例(読まなくても問題ないので飛ばしましょう)

単純に以下のようなコードでいいのかなと思うのですが…

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 'マイドライブ/sheet/sample'というスプレッドシートの先頭ワークシートをオープン
worksheet = gc.open('./sheet/sample').get_worksheet(0)```

ダメです。エラーがでます。

f:id:ueponx:20180406193657p:plain

エラーメッセージとしてはSpreadsheetNotFound:となっているのでパス指定に問題がありそうです。 何回か実験してみたのですが、python上でカレントディレクトリの変更をしたりしてみたのですが、パス指定を変えることが出来なさそうです。 Google Drive上のファイルは名前の方やパスを属性として扱い、基本的にはツリー構造ではなくフラットな構造になっているため、 基本はファイルの所在としてはマイドライブの下にあるのも、ある特定のディレクトリにあることも大きな差がないということの様です。

うまくいった例

前のエラーを踏まえて、ではどうするか?

ファイルをオープンする方法としてgspreadモジュールではkeyをしてする方法とURLから指定する方法があるようです。 ドキュメントには以下のような記述があります。

# If you want to be specific, use a key (which can be extracted from
# the spreadsheet's url)
sht1 = gc.open_by_key('0BmgG6nO_6dprdS1MN3d3MkdPa142WFRrdnRRUWl1UFE')

# Or, if you feel really lazy to extract that key, paste the entire url
sht2 = gc.open_by_url('https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl')

Google Driveに格納されるファイルには固有のキーがあるのでそれを使用するようです。URLも基本的には固有キーを使用したURLになっているので同じような感じです。 つまりキーがわかっていれば問題ありません。

ファイルの固有キーをつかって任意のフォルダのスプレッドシートへアクセス

スプレッドシートをブラウザで開いて

f:id:ueponx:20180406201013p:plain

ブラウザのURL表示の中の https://docs.google.com/spreadsheets/d/14TU4**********************************************Hxt1Y/edit#gid=0(自分のファイルのものを使用してください)

/d/のあとから次の/までの間が固有キーになります。それを使用して以下のようなスクリプトを実行します。

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 'マイドライブ/sheet/sample'というスプレッドシートをオープン
sh = gc.open_by_key('14TU4**********************************************Hxt1Y')
worksheet = sh.get_worksheet(0)

# set value
worksheet.update_acell('A1', 'key')

f:id:ueponx:20180406201547p:plain

スプレッドシートにも反映されています。

f:id:ueponx:20180406201640p:plain

URLをつかって任意のフォルダのスプレッドシートへアクセス

スプレッドシートをブラウザで開いて

f:id:ueponx:20180406194727p:plain

ブラウザのURLボックスの中身がそのままURLになります。(URLの末尾に/edit#gid=0がついています。本来はないものが正しいURLですが、末尾に編集情報がついていても問題はないようです)

f:id:ueponx:20180406201055p:plain

このURLをつかってスプレッドシートへアクセスします。 以下のコードを実行します。

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 'マイドライブ/sheet/sample'というスプレッドシートをオープン
sh = gc.open_by_url('https://docs.google.com/spreadsheets/d/14TU4**********************************************Hxt1Y/edit#gid=0')
worksheet = sh.get_worksheet(0)

# set value
worksheet.update_acell('A1', 'URL')

実行結果・編集したスプレッドシートがこのように変化すれば正常に実行されています。

f:id:ueponx:20180406201917p:plain

f:id:ueponx:20180406202020p:plain

オープン処理はできました

これで既存にあるスプレッドシートのファイルに関してはおおよそ処理できるようになりました。 これでもいいのですが…新規にファイルを作成することだってありますよね?

スプレッドシートの作成

先ほどの例ではあらかじめあるスプレッドシートを読み込んでいましたが、今度は新規に作成してみようと思います。

新規にスプレッドシートを作成してみる

マニュアルをみると以下のような記述で大丈夫のようです。

sh = gc.create('test_sheet')

では新規作成してみましょう。新規作成するスプレッドシートの名前は'test_sheet'にしてみます。

from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread

# 認証処理
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())

# 'SpreadsheetSample'というスプレッドシートの先頭ワークシートをオープン
worksheet = gc.create('test_sheet').get_worksheet(0)

# A1セルに'foo'という値を上書き
worksheet.update_acell('A1', 'Create')

# A1セルの値を取得し、表示
val = worksheet.acell('A1').value
print(val)

このコードを実行すると以下のようになります。

f:id:ueponx:20180406203817p:plain

作成されたのはマイドライブの直下に作成されています。

f:id:ueponx:20180406204016p:plain

スプレッドシートのセルも期待通り編集されています。

f:id:ueponx:20180406204119p:plain

これでめでたしめでたし…となるはずなんですが、やっぱりなんとなくしっくりきません。 時系列のデータをファイルを作りつつ保存するような処理をするほうが比較的一般的ではないかと思います。

と入っても、gspreadモジュールでできるのは以下の2つになります。

  • マイドライブ直下へのファイル作成(open()、open_by_url()、open_by_key())
  • マイドライブ・任意のディレクトリのスプレッドシートへのアクセス処理(create())

どうやらgspreadモジュールだけでは難しいようです。

終わりに

長くなったのでこのあとは別のエントリーにしようと思います。 次回はディレクトリを指定してスプレッドシートの読み書きを作成するにはという感じになると思います。 (途中まで書いていてあまりにも長く感じたので分けました)

これまで、あんまりネットワーク上のストレージを使ったプログラムを使ったことがなかったので結構戸惑ったような感じでしたが、 こういう考え方や扱いがむしろ普通になってきているんしょうね。

【関連エントリ】

uepon.hatenadiary.com

uepon.hatenadiary.com

uepon.hatenadiary.com

Google Colaboratoryで画像表示してみる

Google Colaboratoryで画像表示してみる

最近、やっているGoogle Colaboratory関連のエントリーの続きです。

uepon.hatenadiary.com

uepon.hatenadiary.com

Google ColaboratoryOpenCVを使おうと思っていましたが、このあたりの書き方がJupyter Notebookとはちょっと違うようだったのでメモ。

作業を始めてわかったのですが

f:id:ueponx:20180328194454p:plain

ファイルを確認すると「空」でした!

このように、前回のエントリーでインスタンスに接続したGoogle Driveのマウントは消えています。またファイルアップロードを過去に行っていても そのファイルもなくなるようです。つまり、インスタンス起動の制限時間内に収まらなかった場合、最後の状態が保持されるわけではないようです。 過去のエントリーにも追加が必要ですね。(初期インスタンスイメージをロードする感じなんでしょうね)

マウント処理は毎回やらないとダメそうですね…

ファイルをアップロードして表示を行う

では、前回のエントリー同様にファイルアップロードを行ってみます。

from google.colab import files
uploaded = files.upload()

f:id:ueponx:20180328195529p:plain

これでapple.jpgという画像ファイルをアップロードすることができました。 では、画像ファイルを表示してみます。

notebookで単純に画像を表示するならIPython.displayモジュールのdisplay_*メソッドが使えるようです。

IPythonとは、Pythonの対話型インタプリタを強力に拡張したもののようです。

  • 強力なインタラクティブシェル
  • Jupyterのカーネル
  • インタラクティブなデータの視覚化とGUIツールキットの使用をサポート
  • フレキシブルで埋め込み可能な通訳者が自分のプロジェクトに読み込み
  • 使いやすい、並列コンピューティングのための高性能ツール

以上のような機能があります。notebook上で保管がモジュールの保管が聞くのもこのおかげのようです。

ドキュメントをみると

f:id:ueponx:20180328203445p:plain

こんなふうな感じです。jpegの他にもHTML、pdf、pngSVGmarkdownなんかも表示ができるようです。 では実行してみましょう。

from IPython.display import Image,display_jpeg
display_jpeg(Image('apple.jpg'))

f:id:ueponx:20180328204610p:plain

無事に画像が表示されました。 pngの場合にはdisplay_jpeg()の代わりにdisplay_png()とするような感じです。

from IPython.display import Image,display_png
display_png(Image('Lenna.png'))

f:id:ueponx:20180328205307p:plain

こんな感じで実行できます。

透過色があっても問題ありません!

f:id:ueponx:20180328205547p:plain

背景が黒だとこんな風に見えます。

f:id:ueponx:20180328205739p:plain

SVGファイルもこのような感じで表示できます。

from IPython.display import *
display_svg(SVG('svg.svg'))

SVGの場合にはImageオブジェクトではなくSVGオブジェクトを引数にする必要があります。

f:id:ueponx:20180328232838p:plain

pdfとmarkdownはうまく行かなかったみいです。(純粋なJupyterNotebookでは動くかもですが…ちょっと自信ないです)

f:id:ueponx:20180328234512p:plain

そんなことをやりたかったわけではない

そうでした。本当にやりたかったのはOpenCVで処理した結果をGoogle Colabで表示したいのでした。 ということで、OpenCVで使ったサンプルをGoogleColabで実行してみると…

import cv2

img = cv2.imread('Lenna.png', 0)

cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

おや?

f:id:ueponx:20180328235539p:plain

インスタンスの異常終了をして、再起動までされてしまいました。うお。 調べてみるとNotebookで実行するのであればインライン(コマンドの出力)での出力にしないとまずいようです。 つまり先程のサンプルでは別ウインドウで画像を開こうとしているという点が失敗の原因になっていたということです。

今回はグラフなどの描画でもちいるmatplotlibOpenCVの結果を渡すことでNotebook環境(インライン)で表示することにします。 %matplotlib inlineを指定(出力先をインラインに指定)しないとだめそうなのですがうまく行っています。

import cv2
import matplotlib.pyplot as plt
import os.path

file = 'apple.jpg'
if os.path.exists(file):
  img = cv2.imread(file)
  show_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
  plt.imshow(show_img)

f:id:ueponx:20180329000846p:plain

修正点としてはcv2からmatplotlib.pyplotに変更した点が大きく違います。ちゃんと設定をすれば座標軸などは消すことはできますので安心してください。

import cv2
import matplotlib.pyplot as plt
import os.path

# 座標軸の削除処理
fig,ax = plt.subplots()
ax.tick_params(labelbottom="off",bottom="off")
ax.tick_params(labelleft="off",left="off")
ax.set_xticklabels([]) 
ax.axis('off')

file = 'apple.jpg'
if os.path.exists(file):
  img = cv2.imread(file)
  show_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
  plt.imshow(show_img)

f:id:ueponx:20180329002042p:plain

ファイルの書き込みもOpenCVの‘‘‘imwrite()‘‘‘メソッドでOKです。

import cv2 as cv
import matplotlib.pyplot as plt

fig,ax = plt.subplots()
ax.tick_params(labelbottom="off",bottom="off")
ax.tick_params(labelleft="off",left="off")
ax.set_xticklabels([]) 
ax.axis('off')

img = cv.imread('Lenna.png', cv.IMREAD_GRAYSCALE)
cv.imwrite('Lenna_Gray.png', img)
plt.imshow(img)

f:id:ueponx:20180329002427p:plain

では画像を加工していくつかの表示を行いたい場合にはどうすればいいでしょうか。 いくつかの手法はあるのですが、今回は一つのfigure(枠)に複数のaxes(グラフ)をはりつけるという方法で実現をしようと思います。 OpenCVでタイル状に合成するのもいいですが、見た目がわかりにくいこともあるのでこちらの手法にしました。

今回はカラー画像とグレースケール化画像を並べて表示してみます。

import cv2 as cv
import matplotlib.pyplot as plt
import os.path

def clearLabel(_ax):
  _ax.tick_params(labelbottom="off",bottom="off")
  _ax.tick_params(labelleft="off",left="off")
  _ax.set_xticklabels([]) 
  _ax.axis('off')
  return _ax

def readImage(_filename):
  if os.path.exists(_filename):
    img = cv.imread(_filename)
    return img

img = readImage('Lenna.png')
  
fig = plt.figure()

ax1 = fig.add_subplot(1, 2, 1)
clearLabel(ax1)
show_img1 = cv.cvtColor(img, cv.COLOR_BGR2RGB) 
plt.imshow(show_img1)

ax2 = fig.add_subplot(1, 2, 2)
clearLabel(ax2)
show_img2 = cv.cvtColor(img, cv.COLOR_BGR2GRAY) 
plt.imshow(show_img2)

plt.show()

f:id:ueponx:20180330181005p:plain

ポイントとなるのは以下の部分です。

fig = plt.figure() # figureの作成
ax1 = fig.add_subplot(1, 2, 1) # 1 rows 2 columnsの1columns目のaxes(向かって左側に表示される画像)をax1に代入
…
ax2 = fig.add_subplot(1, 2, 2) # 1 rows 2 columnsの2columns目のaxes(向かって右側に表示される画像)をax2に代入

今回はfigure(枠)に2つの画像(axes(グラフ))を左右に並べるので1 rows 2 columns(1行2列)のfigureを作成して画像を配置することになります。 今回は左右に並べましたが、上下にするのであればパラメータを少し変更して

import cv2 as cv
import matplotlib.pyplot as plt
import os.path

def clearLabel(_ax):
  _ax.tick_params(labelbottom="off",bottom="off")
  _ax.tick_params(labelleft="off",left="off")
  _ax.set_xticklabels([]) 
  _ax.axis('off')
  return _ax

def readImage(_filename):
  if os.path.exists(_filename):
    img = cv.imread(_filename)
    return img

img = readImage('Lenna.png')
  
fig = plt.figure()

ax1 = fig.add_subplot(2, 1, 1) #変更
clearLabel(ax1)
show_img1 = cv.cvtColor(img, cv.COLOR_BGR2RGB) 
plt.imshow(show_img1)

ax2 = fig.add_subplot(2, 1, 2) #変更
clearLabel(ax2)
show_img2 = cv.cvtColor(img, cv.COLOR_BGR2GRAY) 
plt.imshow(show_img2)

plt.show()

f:id:ueponx:20180330182209p:plain

こんな感じになりました。パラメータ次第で以下のようにも表示可能です。

f:id:ueponx:20180330182337p:plain

おわりに

これで目的は達成できたかなと思います。

おまけですが、URLを指定して画像をダウンロードし、表示をさせる場合にはrequestsモジュールを使用すれば可能です。 以下のような感じで一応できました(もっときれいに書いたほうがいいけど)

import requests
import cv2 as cv
import matplotlib.pyplot as plt
import os.path

def clearLabel(_ax):
  _ax.tick_params(labelbottom="off",bottom="off")
  _ax.tick_params(labelleft="off",left="off")
  _ax.set_xticklabels([]) 
  _ax.axis('off')
  return _ax

def readImage(_filename):
  if os.path.exists(_filename):
    img = cv.imread(_filename)
    return img

response = requests.get('https://upload.wikimedia.org/wikipedia/commons/e/e1/Harvest_of_MIKAN.jpg', allow_redirects=False)
if response.status_code != 200:
  e = Exception("HTTP status: " + response.status_code)
  raise e

content_type = response.headers["content-type"]
if 'image' not in content_type:
  e = Exception("Content-Type: " + content_type)
  raise e
  
with open('mikan.jpg', "wb") as fout:
  fout.write(response.content)
  
img = readImage('mikan.jpg')

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
clearLabel(ax)
show_img = cv.cvtColor(img, cv.COLOR_BGR2RGB) 
plt.imshow(show_img)

f:id:ueponx:20180330184350p:plain

Google Colab上でOpenCVを使って画像処理(画像表示)ができるようになりました。 Googleスプレッドシートに画像のURLを指定して画像を保存、加工、表示するところまでは比較的簡単にできそうですね。 次はGoogleスプレッドシートの読み込みですかね。

関連エントリー

uepon.hatenadiary.com uepon.hatenadiary.com

Microsoft Cognitive Services の Custom Visionを使用して画像の判別エンジンを作成してみた

Microsoft Cognitive Services の Custom Visionを使用して画像の判別エンジンを作成する

Microsoft Cognitive Services の Custom Vision は、オリジナルの画像判定エンジンを作成して推定できるサービスです。 画像判定ロジックを構築しなくても、画像をアップロードし、事前にタグ付けを行うことで、画像判定エンジンを構築することができます。 ここで作成した画像判定エンジンはAPI経由でアクセスすることもできます。

IBM Cloud(旧Bluemix)やGCPGoogle Cloud Platform)でもできるようですが、まずは自分はこちらから試してみます。

本エントリーでは以前参加した

cogbot.connpass.com

では、イベントの運営のお手伝いをしていたのでハンズオンの内容はほとんど手を付けられなかったため、復習も兼ねてみたという感じです。

なお、以下を参考にさせていただきながら復習しています。

qiita.com

Custom Vision ポータルへ

まずはMicrosoft アカウントが必要になりますので事前に準備が必要になります。

準備が終わったらCustom Vision ポータルにアクセスします。

すると以下のような画面になります。

f:id:ueponx:20180324214334j:plain

画面の中央にある、【SIGN IN】ボタンをクリックします。

f:id:ueponx:20180324215000j:plain

Microsoftアカウントのログインを促されます、ログインをする以下のようなアクセス許可を求められますので、内容を確認して 【はい】ボタンをクリックします。すると次のような画面に遷移します。

f:id:ueponx:20180324215347j:plain

こちらでもサービスに対する同意を求められます。チェックボックスにチェックをいれて下にある【I agree】ボタンをクリックします。

f:id:ueponx:20180324215610j:plain

自分の場合にはAzureのアカウントと紐付けがされてないよーって感じの以下の表示になりました。(bizSparkサブスクリプションを使っていたので、ちょっとあれって感じでした)。

f:id:ueponx:20180324220334j:plain

ここで【Sign up for Azure】ボタンをクリックすると従量課金のサインアップへ進みます。

f:id:ueponx:20180324220731j:plain

支払いは発生しないがカードの登録はしないといけないのかなと思ったのですが、従量課金サブスクリプションと紐づけられるとなんとなく不安になるので、にわかの自分は画面中の【I'll do it later】を選択します。こちらは制限はプロジェクト数の制限があること、アクセス数の制限はありますが、いまは問題ないと思います。

Custom Vision のProjectの作成

では実際にProjectを作成します。画像の判別を行う機能はProjectという単位で作成するようです。

先ほどまでの作業が終わると以下のような画面になっていると思います。

f:id:ueponx:20180324220714j:plain

【New Project】という大きいボタンをクリックします。するとProjectの設定画面が表示されます。

f:id:ueponx:20180324220827j:plain

設定に必須となるのはNameだけです、あとはオプションになる感じです。参考にしたエントリーでは食べ物の判定を行っていたので適当にFoodFinderとしました。 Domainも食べ物関係ということでFoodに設定しています。(特に指定がない場合にはGeneralになるのかな?) 設定が終わったら、画面下にある【Create project】ボタンをクリックします。すると、以下のような画面に遷移します。

f:id:ueponx:20180324221458j:plain

これでプロジェクトの作成は終わりました。あとはこのプロジェクトに画像をアップロードとタグ付けをしていきます。

学習データ画像のアップロードとタグ付け

f:id:ueponx:20180324221458j:plain

画面内中央にある【Add images】ボタンをクリックしてローカルにある画像をアップロードしていきます。

f:id:ueponx:20180324222231j:plain

クリックすると画面の右側にペインが表示されます。その中の【Browse local files】ボタンをクリックします。するとファイル選択のダイアログが開きます。(今回は参考にしたエントリーに事前に準備された画像を使用しています)

画像ファイル

f:id:ueponx:20180324222301j:plain

ファイルを選択して【開く】ボタンをクリックします。(1枚づつでも、まとめてでも指定することはできます)ある程度カテゴリ化された画像であればまとめて行ったほうがいいかなと思います。

f:id:ueponx:20180324222727j:plain

次は指定した画像にタグ付けをする作業になります。(指定した画像にまとめてタグ付けをしていきます)今回はカレーの画像を設定したのでタグには【curry】を設定していきます。タグを入力したら【+】ボタンをクリックするとタグが追加されます。複数のタグの設定ができようです。

f:id:ueponx:20180324223313j:plain

入力ボックスの下部に入力したタグが表示されていればOKです。タグ付けが完了したら画面したにある【Upload 7 files】(数字は指定したファイル数になります)をクリックします。

作業用のペインに戻りますので【Done】ボタンをクリックします。 すると、画面が以下のように変わります。これで無事にトレーニング用のデータに追加が行われました。

あとは、これを繰り返していくだけです。

最終的には以下のような登録状態になります。

f:id:ueponx:20180324223905j:plain

あとは画像の判別の学習(トレーニング)のプロセスを行えばおおむね終了です。

学習(トレーニング)をさせる

画面上部の緑色の【Train】ボタンを押せばOKです。

f:id:ueponx:20180324223905j:plain

あとは時間が解決してくれます。

トレーニングが終了すると画面がこんな感じに変化します。

  • Iterationは「反復」
  • Precisionは「精度」
  • Recallは「呼び返す」

これで学習完了です。あとはその結果を楽しむだけです。

学習の結果を調べてみる

学習後には画面上部にある【Train】の隣に【Quick Test】のボタンがアクティブになります。

f:id:ueponx:20180324225017j:plain

これをクリックすると画面が以下のように遷移します。

f:id:ueponx:20180324225208j:plain

学習した判別エンジンに対して画像を与えて、どのグループに所属するかの推定結果を返してくれます。 URL経由で画像を渡すことができますし、ローカルファイルをアップロードして判別することもできます。 今回はサンプルについていた「お寿司」の画像をアップロードしてみます。【Browse local files】のボタンをクリックして ファイル選択ダイアログからファイルを選択してアップロードを行えば下記のような結果が画面に表示されます。

f:id:ueponx:20180324225645j:plain

これで画像の判別エンジンを作ることができました。事前の作業のほうがめんどくさいぐらい簡単にできてしまいます。

今後は画像をうまく集めることが今後の課題になりそうですね。

APIでアクセスするには?

ここでできた画像判別エンジンをREST API経由でも操作したいと思いますよね? その時の情報は画面内にある【PERFORMANCE】タブの中にある【Prediction URL】をクリックするとアクセスに必要な情報が得られます。

f:id:ueponx:20180324230138j:plain

クリックすると

f:id:ueponx:20180324230222j:plain

エンドポイントのURLやアクセスに必要なPredicton-Keyの情報を得ることができます。

おわりに

これでMicrosoft Cognitive Services の Custom Visionが一応つかえるようになりました。次はIBM Cloudでも使えるようになりたいです。 その前にこれを使って性能を計ってみたいですね。

Google Colaboratoryでファイルを読み込む

Google Colaboratoryでファイルを読み込む

前回のエントリの続きになります。 uepon.hatenadiary.com

前回はGoogleColaboratoryを設定してみたものでした。ただ、これでは画像ファイルを認識させるために読み込ませるようなことができません。ネット上のサーバに置いておくという手もありますが、それもまた面倒です。ということで、その方法をググって見ました。

以下のエントリを参考にしています。ありがとうございます。

qiita.com

ファイルをインスタンスにアップロードする

簡単な方法はpythonのコードでnotebook上にUIを作り出し、ファイルをアップロードするというものです。 以下のようなコードを書いて実行すると…

from google.colab import files
uploaded = files.upload()

f:id:ueponx:20180321135030p:plain

notebook上にアップロード操作用のUIが現れます。 【ファイルの操作】ボタンをクリックするとファイル選択のダイアログが表示されますのでファイルを選択して【OK】をクリックしてください。

f:id:ueponx:20180321135511p:plain

では以下のようなCSV(郵便番号データ)を読み込ませてみます。ファイル名は23AICHI.CSVとなります。

www.post.japanpost.jp

f:id:ueponx:20180321140407p:plain

ファイル選択のダイアログでファイル名を選択し【OK】ボタンをクリックすると以下のようにnotebookが表示が変更します。

f:id:ueponx:20180321141038p:plain

この操作では一応タイムアウト機能もあるようです。再生ボタンの周りがくるくる回っている間は問題ありませんが、タイムアウト後にファイルのアップロード操作を行おうとすると以下の様にアップロード状況などは表示されないようです。(ボタンなどを操作するとファイル操作ダイアログは表示され、ファイルの選択は行われますが、それ以上なにかされることはありません。)

【処理待ちの状態】 f:id:ueponx:20180321141528p:plain

タイムアウト後に操作してもなにも処理は発生しない】 f:id:ueponx:20180321140834p:plain

ファイルが正常にアップロードされたかを確認するにはshコマンドで以下の様に実行すれば無事にアップロードされていることがわかります。

!ls -l

f:id:ueponx:20180321141218p:plain

ちなみにインスタンスのデフォルトではlessコマンドはありませんし、moreコマンドでもエンコードShift_JISのため?文字化けしています。

f:id:ueponx:20180321141939p:plain

では、参考にしたエントリではcsvファイルをpandasで読み込ませていたので自分もやってみます。(日本語なのでかなり不安ですが)

pandasは初めて使うので調べてみました。

Pandasは、プログラミング言語Pythonにおいて、データ解析を支援する機能を提供するライブラリである。特に、数表および時系列データを操作するためのデータ構造と演算を提供する。PandasはBSDライセンスのもとで提供されている。

Python Data Analysis Library — pandas: Python Data Analysis Library

ってことらしい、入力時の面倒くささを解決してくれるような感じなのかな? では実験です。

import pandas as pd
import io
data = pd.read_csv(io.StringIO(uploaded['sample.csv'].decode('utf-8')), header=-1)
data.head()

f:id:ueponx:20180321143454p:plain

おや?文字化けはするかなと思っていたのですが、エラーが発生しました。それも原因がファイル名。とはいえErrorが出たらStackOverFlowに検索にいけボタンがあるというのはすごいですね。

CSVファイルのエンコードutf-8にしてしまえば、正常にいくかと思えば全く同じ状況でした。 怪しいのはファイル名の頭にある数字?あと小文字化も必要なのかなってことで以下の様にファイル名を変えてみました。 23AICHI.CSV → aichi.csv

これを変更してみると…moreも正常になりました。なんだろこれ?

f:id:ueponx:20180321145428p:plain

先程のpandasのコードも以下のように修正して実行すると

import pandas as pd
import io
data = pd.read_csv(io.StringIO(uploaded['aichi.csv'].decode('utf-8')), header=-1)
data.head()

f:id:ueponx:20180321150543p:plain

うまくいきました。こんなところで引っかかるなんて…

【追記】

以下とすれば問題ありませんでした。

import pandas as pd
import io
data = pd.read_csv('23AICHI.CSV', header=-1)
data.head()

GoogleDriveにあるディレクトリをマウントする

インスタンスへのディレクトリのマウントにはFUSE filesystem over Google Driveというものを使用します。

github.com

要約するとGoogleDriveのディレクトリをLinuxファイルシステムにマウントするもののようです。

以下参考リンク。

stackoverflow.com

https://colab.research.google.com/notebook#fileId=1srw_HFWQ2SMgmWIawucXfusGzrj1_U0q&scrollTo=c99EvWo1s9-x

↑これと同じことをそのまま行えばいいようです。

1)google-drive-ocamlfuseのインストール

インストールに必要となるパッケージも含めてインストールを行います。

# Install a Drive FUSE wrapper.
# https://github.com/astrada/google-drive-ocamlfuse
!apt-get install -y -qq software-properties-common python-software-properties module-init-tools
!add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null
!apt-get update -qq 2>&1 > /dev/null
!apt-get -y install -qq google-drive-ocamlfuse fuse

【実行の様子】

f:id:ueponx:20180321155839p:plain

2)Colabratory用の認証トークンの生成

以下のコードを実行すると認証のリンクが表示され、リンク先に行って認証を行います。

# Generate auth tokens for Colab
from google.colab import auth
auth.authenticate_user()

【実行の様子】

実行するとリンクが表示されるので、それをブラウザの別タブで開き

f:id:ueponx:20180321161310j:plain

対象となるユーザを選択し、

f:id:ueponx:20180321161514p:plain

アクセスの許可を与えます。【OK】ボタンをクリックします。

f:id:ueponx:20180321161615p:plain

すると、認証トークンが発行されるので、これをコピーして

f:id:ueponx:20180321161751p:plain

colab側の入力ボックスにペーストしてEnterを押します。

3)Drive FUSE library用の証明書の生成

以下のコードを実行すると認証のリンクが表示され、リンク先で認証を行います。

# Generate creds for the Drive FUSE library.
from oauth2client.client import GoogleCredentials
creds = GoogleCredentials.get_application_default()
import getpass
!google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URL
vcode = getpass.getpass()
!echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret}

【実行の様子】

実行するとリンクが表示されるので、それをブラウザの別タブで開き

f:id:ueponx:20180321162502j:plain

対象となるアカウントを選択し、

f:id:ueponx:20180321162120p:plain

Google Driveのアクセス許可を与えます。【OK】ボタンをクリックします。

f:id:ueponx:20180321163044j:plain

すると、認証コードが発行されるので、これをコピーしてcolab側の入力ボックスにペーストしてEnterを押します。

f:id:ueponx:20180321163225p:plain

認証が終わると以下のような状態になります。

f:id:ueponx:20180321163405j:plain

4)インスタンスdriveというディレクトリを作り、そこにGoogle Driveをマウントする

# Create a directory and mount Google Drive using that directory.
!mkdir -p drive
!google-drive-ocamlfuse drive

print('Files in Drive:')
!ls drive/

# Create a file in Drive.
!echo "This newly created file will appear in your Drive file list." > drive/created.txt

※ リンクそのままではPython2系のprintを使用しているのでPython3系に合わせる編集をしています。

【実行の様子】

f:id:ueponx:20180321160448p:plain

5)マウントの確認

マウントされたGoogle Driveの状況をlsコマンドで確認してみるとこんな感じになります。

f:id:ueponx:20180321163545p:plain

無事にマウントが行われたようです。

では先程のpandasを使ったcsvをオープンするコードでも確かめてみます。予めGoogleDriveに先程使用したcsvファイルを保存しておきます。

f:id:ueponx:20180321164147p:plain

import pandas as pd
import io
data = pd.read_csv('drive/Colab Notebooks/aichi.csv', header=-1)
data.head()

【実行の様子】

f:id:ueponx:20180321164645p:plain

【注意点】 このマウントはインスタンスの永続限界時間である12時間を超えるとインスタンスが初期化されるためマウントも解除されてしまいます。 ファイルが削除されるわけではないので、再度インスタンスの接続を行ったらマウント処理を行うことを忘れないようにしてください。 アイドル状態が90分続くと停止しますが、12時間を超えていなければ大丈夫なのかなと思っています。

終わりに

tensorflowとKerasを使ってアップルとオレンジを判別するコードを実行してみます。

以前のエントリーで「AI Business Challenge Day:第4回 機械学習実践勉強会 画像認識技術ハンズオン」で使用したコードをGoogle Colabで動作させてみます。 画像はGoogle Driveに格納させています。

www.softopia.or.jp

uepon.hatenadiary.com

画像の格納されたパスの部分のみ変更していますが、基本的には同じです。

from keras.models import Sequential
from keras.layers import Activation, Dense, Dropout
from keras.utils.np_utils import to_categorical
from keras.optimizers import Adagrad
from keras.optimizers import Adam
import numpy as np
from PIL import Image
import os

image_list = []
label_list = []

basePath = "drive/Colab Notebooks/" 

# トレーニングデータを読み込む
for dir in os.listdir(basePath + "data/train"):

    traindir = basePath + "data/train/" + dir
    if os.path.isdir(traindir) == False:
        continue

    label = 0              # 正解ラベル

    if dir == "apple":
        label = 0          # りんごの場合は、0
    elif dir == "orange":
        label = 1          # オレンジの場合は、1

    for file in os.listdir(traindir):
        if file != ".DS_Store":

            label_list.append(label)            # 正解ラベルを配列に入れる

            filepath = traindir + "/" + file  # ファイルパス

            resized_img = Image.open(filepath).resize((25, 25))                                                    # 画像を25x25にリサイズする
            image = np.array(resized_img)                                                                       # 25x25の2次元配列にする→[[R,G,B], [R,G,B]...]
            image = image.transpose(2, 0, 1)                                                                 # 配列を次元を変換する→[[R,R,R,...], [G,G,G,...], [B,B,B,...]]
            image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]     # 1次元配列に変換→[R,R,R,...,G,G,G,...,B,B,B]
            image_list.append(image / 255.)                                                            # 0.0〜1.0までの値にして配列に入れる

image_list = np.array(image_list)       # 画像リストをnumpy配列に変換

Y = to_categorical(label_list)          # 正解ラベルを配列にする(0→[1,0], 1→[0,1])

# 層を構築
model = Sequential()
# 入力層
model.add(Dense(200, input_dim=1875))
model.add(Activation("relu"))
model.add(Dropout(0.2))

# 隠れ層
model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

# 出力層
model.add(Dense(2))
model.add(Activation("softmax"))

# オプティマイザにAdamを使用
opt = Adam(lr=0.001)
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
# nb_epoch: 学習回数
# batch_size: 1度に処理する分量(GPUモードの際は、メモリ制限がある場合がある)
model.fit(image_list, Y, nb_epoch=1500, batch_size=100, validation_split=0.1)
# model.fit(image_list, Y, nb_epoch=10, batch_size=100, validation_split=0.1)

total = 0.
ok_count = 0.

for dir in os.listdir(basePath + "data/test"):
    
    testdir = basePath + "data/test/" + dir
    if os.path.isdir(testdir) == False:
        continue

    label = 0

    if dir == "apple":
        label = 0          # りんごの場合は、0
    elif dir == "orange":
        label = 1          # オレンジの場合は、1

    for file in os.listdir(testdir):
        if file != ".DS_Store":
            label_list.append(label)
            filepath = testdir + "/" + file

            resized_img = Image.open(filepath).resize((25, 25))    
            image = np.array(resized_img)
            image = image.transpose(2, 0, 1)
            image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]

            # 予測する
            print(filepath)
            result = model.predict_classes(np.array([image / 255.]))
            print("label:", label, "result:", result[0])

            total += 1.

            if label == result[0]:
                ok_count += 1.

print(ok_count / total * 100, "%")

【実行の様子】

f:id:ueponx:20180321175209p:plain

(略)

f:id:ueponx:20180321175408p:plain

問題なく動いたようです。 インスタンスのマシンスペックの凄さはあんまりわかりませんでしたが、セットアップをほとんどしなくてもここまでできるのは本当に嬉しいことです。

関連エントリー

uepon.hatenadiary.com uepon.hatenadiary.com

Google Colaboratoryを使ってみた

Google Colaboratoryを使ってみた

今年はじめに以下のようなニュースがでていました。気にはなっていたのですが試す時間がなかったので試してみました。 Google Colaboratoryが正しいのかGoogle Colabが正しいのか…

控えめに言ってもすごすぎ。その他のプラットフォームを殺すつもりできてるのかと思いました。

以下に説明というか、FAQページのリンクを貼っておきます。

Colaboratory – Google

簡単に説明すると、ChromeなどのブラウザからJupyter Notebook環境を使えて、更にGPUリソースが使えるという優れものでした。 もう、初学者はanacondaもインストールせずこれ一択でいいと思います。

このエントリーは

qiita.com

を参考に作成しています。

とりあえず使ってみる

とりあえず、使用してみましょう。インストールも何もいりません。Googleアカウントだけは必要でなので事前にログオンはしておきましょう。 以下のURLにアクセスすれば準備OKです。

Colaboratory – Google

アクセスすると以下のような画面になると思います。

f:id:ueponx:20180319233415j:plain

おめでとうございます!これでJupyter Notebook環境が使えることになりました!インストールばっかりしている自分にとってはこれはあまりにも衝撃的ですが、目的はインストールすることではないはずのでこれが理想的な姿だと思います。

ダイアログの下のほうにある【ノートブックの新規作成】をクリックします。

f:id:ueponx:20180319233912j:plain

すると、プルダウンで【PYTHON2の新しいノートブック】と【PYTHON3の新しいノートブック】が表示されます。もうPythonはVersion3でやっていきましょう!(今年の目標)

f:id:ueponx:20180319234142j:plain

【PYTHON3の新しいノートブック】を選びます。これだけでほぼ準備完了です。あとはpythonのコードを打ち込っていけばいいのです。 コードは画面内の入力ボックスに記入します。環境としてはJupyter NotebookなのでREPLの様に改行をしても実行はされません。

f:id:ueponx:20180319234732j:plain

打ち込んだコードを実行するには【Shift + Enter】または左側の再生ボタン(実行ボタン?)になります。まずは、Hello Worldを画面内に表示してみます。コードを行のところに

print('Hello World')

f:id:ueponx:20180320001208j:plain

と入力し、実行(【Shift + Enter】)してみます。以下の様に出力されます。 初回の実行にはインスタンスへの接続が行われるため時間はかかりますが、それ以降ではサクサクと実行されるのでご安心を。

実行中は下記のように表示が読み込み中のような表示になります。

f:id:ueponx:20180320001232j:plain

【実行結果】

Hello World

f:id:ueponx:20180320001249j:plain

実行は成功です。 続いてもう少しコードを書いてみます。今日の日付を出力するのですが、datetimeモジュールをimportをしている点が先程と違います。

import datetime

today = datetime.date.today()
print(today)

これを実行すると以下の様になります。

f:id:ueponx:20180320001500p:plain

通常のpythonの開発も問題なさそうですね。OSモジュールなども正常にインストールできていました。

あとはノートブックを保存してみましょう。作成時には【Untitled**.ipynb】というような名前でノートブックが作成されますのでブラウザの左上の名前の部分を編集します。

f:id:ueponx:20180320002128j:plain

今回は【sample.ipynb】という名前に変更してみました。入力ボックスに名前を入れれば作業は完了するのですが、保存されているのはgoogle colabの環境化になります。実際にはGoogleDriveに保存しておくのが普通だと思いますので、そちらに保存します。

f:id:ueponx:20180320002420j:plain

f:id:ueponx:20180320002727j:plain

メニューバーから【ファイル】→【保存】を選択します。

f:id:ueponx:20180320002828j:plain

すると自分のGoogleDriveのマイドライブに【Colab Notebooks】というフォルダが作成(すでにあれば再作成はされません)され先程つけた名前のipynbファイルが保存されます。これを読み込めば同じ状態から作業を始めることができるようになります。

f:id:ueponx:20180320002950j:plain

接続するインスタンスを調べてみる

先程、pythonの初回実行時にはインスタンスに接続することでpythonのプログラムが実行されるという話をしました。ではこのインスタンスに直接アクセスすることはできるのでしょうか。コマンド行にの先頭に!をいれてshコマンドを入れると実行することができるようです。

いろいろネットを調べてみるとこのインスタンス強力すぎます。

  • Ubuntu 17.10
  • n1-highmem-2 instance
  • 2vCPU @ 2.2GHz
  • 13GB RAM
  • 40GB Free Space
  • GPU NVIDIA Tesla K80

*1 90分間なにもしないとインスタンスはシャットダウンします。また、最大でも12時間しか使用できません。

上記のK80を無料で使えるのはすごい!(間違ってる?)ただ、通常に起動した場合にはGPUはOFFになっています。既に作成したnodebookでGPUをONにするには以下の作業が必要になります。

メニューバーから【ランタイム】→【ランタイムのタイプを変更】を選択します。

f:id:ueponx:20180320005852j:plain

すると以下のようなダイアログが表示されますので、【ハードウエアアクセラレータ】のプルダウン【None】から【GPU】に変更します。念のため【このノートブックを保持する際にコードセルの出力を除外する】にチェックをを付けておきます。設定が終わったら【保存】ボタンをクリックします。

f:id:ueponx:20180320005855j:plain

ページ再読み込みのダイアログが出ますので、ここも【再読み込み】ボタンをクリックします。

f:id:ueponx:20180320005858j:plain

これでGPUが有効になります。

f:id:ueponx:20180320005901j:plain

見た目にはほとんど変化はありませんが、先ほど実行したコードの実行結果が消えています。

f:id:ueponx:20180320010014j:plain

ではGPUがONになっているかを確認してみます。 確認方法はtensorflow経由となりますが、以下のようなコードで確認できます。

import tensorflow as tf
tf.test.gpu_device_name()

【実行結果:GPU設定あり】

'/device:GPU:0'

f:id:ueponx:20180320122650p:plain

問題なさそうです。

ちなみにGPUがNoneの場合には以下のような結果になります。

【実行結果:GPU設定なし】

''

f:id:ueponx:20180320123451p:plain

あとはpythonのバージョンなどを調べてみましょう。最初に言っていたインスタンスのshコマンドを実行する場合には冒頭に!をつけて実行を行います。 pythonのバージョンを調べるときにはpython --versionと打込むので、今回は以下のように入力します。

!python --version

【実行結果】

Python 3.6.3

f:id:ueponx:20180320124219p:plain

ということで、更にインスタンスのハードウエアスペックを見てみます。

!cat /proc/cpuinfo

【実行結果】

processor  : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 63
model name  : Intel(R) Xeon(R) CPU @ 2.30GHz
stepping    : 0
microcode   : 0x1
cpu MHz     : 2300.000
cache size  : 46080 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms xsaveopt
bugs        :
bogomips    : 4600.00
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

processor   : 1
vendor_id   : GenuineIntel
cpu family  : 6
model       : 63
model name  : Intel(R) Xeon(R) CPU @ 2.30GHz
stepping    : 0
microcode   : 0x1
cpu MHz     : 2300.000
cache size  : 46080 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 1
initial apicid  : 1
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms xsaveopt
bugs        :
bogomips    : 4600.00
clflush size    : 64
cache_alignment : 64
address sizes   : 46 bits physical, 48 bits virtual
power management:

f:id:ueponx:20180320010028j:plain

CPUがXeonなのか…

それではGPUの情報は…

!cat /proc/driver/nvidia/gpus/0000:00:04.0/information

【実行結果】

Model:          Tesla K80
IRQ:         33
GPU UUID:    GPU-9743217b-7464-7627-8efc-0f60f5217deb
Video BIOS:      80.21.25.00.01
Bus Type:    PCI
DMA Size:    40 bits
DMA Mask:    0xffffffffff
Bus Location:    0000:00:04.0
Device Minor:    0

情報通りのTesla K80すげー!

f:id:ueponx:20180320010046j:plain

では、pythonのパッケージpip経由で見てみます。

!pip freeze

【実行結果】

absl-py==0.1.11
astor==0.6.2
beautifulsoup4==4.6.0
bleach==1.5.0
cachetools==2.0.1
certifi==2018.1.18
chardet==3.0.4
crcmod==1.7
cycler==0.10.0
decorator==4.2.1
dill==0.2.7.1
entrypoints==0.2.3
future==0.15.2
futures==3.0.5
gapic-google-cloud-datastore-v1==0.15.3
gapic-google-cloud-error-reporting-v1beta1==0.15.3
gapic-google-cloud-logging-v2==0.91.3
gast==0.2.0
google-api-core==0.1.4
google-api-python-client==1.6.5
google-auth==1.3.0
google-auth-httplib2==0.0.3
google-auth-oauthlib==0.2.0
google-cloud==0.32.0
google-cloud-bigquery==0.30.0
google-cloud-bigquery-datatransfer==0.1.1
google-cloud-bigtable==0.28.1
google-cloud-container==0.1.1
google-cloud-core==0.28.1
google-cloud-datastore==1.4.0
google-cloud-dns==0.28.0
google-cloud-error-reporting==0.28.0
google-cloud-firestore==0.28.0
google-cloud-language==1.0.1
google-cloud-logging==1.4.0
google-cloud-monitoring==0.28.1
google-cloud-pubsub==0.30.1
google-cloud-resource-manager==0.28.1
google-cloud-runtimeconfig==0.28.1
google-cloud-spanner==0.29.0
google-cloud-speech==0.30.0
google-cloud-storage==1.6.0
google-cloud-trace==0.17.0
google-cloud-translate==1.3.1
google-cloud-videointelligence==1.0.1
google-cloud-vision==0.29.0
google-colab==0.0.1a1
google-gax==0.15.16
google-resumable-media==0.3.1
googleapis-common-protos==1.5.3
grpc-google-iam-v1==0.11.4
grpcio==1.10.0
h5py==2.7.1
html5lib==0.9999999
httplib2==0.10.3
idna==2.6
ipykernel==4.6.1
ipython==5.5.0
ipython-genutils==0.2.0
Jinja2==2.8
jsonschema==2.5.1
jupyter-client==5.2.3
jupyter-core==4.4.0
Keras==2.1.5
Markdown==2.6.11
MarkupSafe==1.0
matplotlib==2.1.2
mistune==0.8.3
mpmath==1.0.0
nbconvert==5.3.1
nbformat==4.4.0
networkx==2.1
nltk==3.2.1
notebook==5.2.2
numpy==1.14.2
oauth2client==4.1.2
oauthlib==2.0.6
olefile==0.45.1
opencv-python==3.4.0.12
pandas==0.22.0
pandas-gbq==0.3.1
pandocfilters==1.3.0
patsy==0.5.0
pexpect==4.4.0
pickleshare==0.7.4
Pillow==4.0.0
plotly==1.12.12
ply==3.8
portpicker==1.2.0
prompt-toolkit==1.0.15
proto-google-cloud-datastore-v1==0.90.4
proto-google-cloud-error-reporting-v1beta1==0.15.3
proto-google-cloud-logging-v2==0.91.3
protobuf==3.5.2
psutil==4.3.1
ptyprocess==0.5.2
pyasn1==0.4.2
pyasn1-modules==0.2.1
Pygments==2.1.3
pyparsing==2.2.0
python-dateutil==2.5.3
pytz==2016.7
PyWavelets==0.5.2
PyYAML==3.11
pyzmq==16.0.4
requests==2.18.4
requests-oauthlib==0.8.0
rsa==3.4.2
scikit-image==0.13.1
scikit-learn==0.19.1
scipy==0.19.1
seaborn==0.7.1
simplegeneric==0.8.1
six==1.11.0
statsmodels==0.8.0
sympy==1.1.1
tensorboard==1.6.0
tensorflow==1.6.0
termcolor==1.1.0
terminado==0.8.1
testpath==0.3.1
tornado==4.5.3
traitlets==4.3.2
uritemplate==3.0.0
urllib3==1.22
wcwidth==0.1.7
webencodings==0.5.1
Werkzeug==0.14.1
xgboost==0.7.post3

f:id:ueponx:20180320010048j:plain

おお! ネットの情報ではkeraspipで別途インストールという情報でしたが、既にデフォルト状態でインストールされている用です。最高! 使いそうなモジュールのバージョンを実行してみました。(2018/03/19現在)

f:id:ueponx:20180320010055j:plain

OpenCVは3系、tensorflowも1.6と新しく、kerasも2系と素晴らしい!

念のため、TensorflowでHello Worldもしてみました!

f:id:ueponx:20180320125729p:plain

問題ないようです。

おわりに

今後のAI関連の環境としてはこちらが定番になりそうです。環境などの構築にかける時間も不要なので、本来の目的へストレートに進めればいいのだと思います。

後は画像処理などを考えるとGoogleDriveとの連携などがわかるといいのかもしれません。

とはいえ、これまで機種依存などがあり勉強会などでもかなり苦労していた部分のほとんどが解消されるかなと思います。

関連エントリー

uepon.hatenadiary.com uepon.hatenadiary.com

*1:90分間なにもしないとインスタンスはシャットダウンします。また、最大でも12時間しか使用できません。

ハッカソンの運営をした話

ご注意

今回のエントリーは一個人の感想あるいはポエムになりますので、所属する会社とは全く関係はありません。 ご承知おきください。


ハッカソンの運営をした話

f:id:ueponx:20180306000944j:plain

先月、2月17日、24日-25日というスケジュールで会社で開催するハッカソンイベントHACK-CHU!の運営をしていました。 東海地区では今ハッカソン熱が少し盛り上がっていて、自社では今回で2回目のイベントとなります。

他にもここ一年では

も開催されています。

ハッカソンの運営は昨年に引き続きなので、社内にも経験者が増え、運営は楽になるのかなと思っていたのですが、 全くそんなこともなく運営する上では辛い感じの人事異動。メンバーは旧メンバーが1減、新規にハッカソンを全く知らない2名追加(約5名) での船出でした。また、スケジュールに関しても前回が3月と年度末で会社としてもひとのアサインは難しく、参加者のアンケートの結果から 参加者も結構辛そうな印象もあって、2月に開催することになりました。つまり、準備期間が1か月短いw。

とはいえ、開催自体は決まっていたので、告知は知り合い伝手にいったり、東海地方のIT勉強会コミュニティーのLTタイムを使ったりして かけずりまわってなんとかできた感じです。放送告知も一応行ったのですが、実は全くの以下略状態だったので、これでよかったのかという先行きの不安。 ハッカソンの参加者は自社がリーチができていない層であることは自覚しているので、なんとかリーチしたいいう希望があったのですが、 放送告知の効果がないことが、如実に現れるとかなり厳しい…。

募集を開始した12月某日、前回はもっと早くやってたんだけどなーという不安しかない状態。 ただ少し安心できたのは会場準備部分。前回一回やっていたのを資料として残していた点とチームメンバでのTrelloの使用がかなり活きました。

そんなこんなでいろいろなところでLTをやってきました…CodeforAichi、GeekBar、JAWS名古屋…。 それでもなかなか集まらない参加者、まだ決められていない審査員。問題山積のまま年末年始のお休みへ。 締め切りを2/1にしていたので正味あと一か月。傾向としては申し込みは締め切り近くにアップするのは知ってはいるのですが、それでも不安でした。

そんな中、子供連れの参加はありでしょうかという相談がありました。 運営側としては広い世代の方に参加していただきたいというのは当初からありましたので、 「参加については問題ないですよ」という感じでお答えしています。 今後もそのことに関しては変更はしないです。(お弁当が子供向けメニューになりにくいのはご容赦ください)

運命の2/1の17:00の締め切り、100名を超える方の参加があり少し肩の荷がおりました(ここでおろしてはいけない)が、 そこからは連絡作業が大量に。うれしい悲鳴なんですけど、参加者に大量の情報を伝えなくては行けないことも含めて連絡をしていました。

また今回そのころには素敵な審査員も決定し、スケジュールが近づくにつれて「自分が参加したくなるようなハッカソンを開催したい!やるぞ!」という気持ちに 切り替わって変わっていったような気がします。


少し話は変わるのですが、今回のテーマは「テクノロジーで暮らしを楽しくオモシロク!」というものでした。 この決定に関しても、いろいろありました。昨年同様にタッグを組んだ番組のテーマに近いところに据えるか、 はたまたテレビ局らしく番組を面白くするということにするか、あるいは地域振興(名古屋市)のような形にするか。 このあたりは社内でもかなり議論になりました。 議論なったのは、会社が開催をするにあたりどういうことを思ってテーマを設定するかということでした。

(このあたりからかなりポエム) 会社のスローガンが少し前に変更となり「あなたの真ん中へ」というものになったことをなんとなく覚えていました。

そこで、このスローガンがどうやったらこのイベントで参加者に伝わるかを考えれば、 テーマや自社でやる意義につながっていくのでないかと感じました。 その中で、より身近にあることをテーマにしないと問題意識が起きにくいのではないか、 自分の腹落ちした事柄でなければ議論が難しくないかというような方向で考えていき、 地域より街、街より暮らしというようなより近いキーワードにしていけば、 自分事の悩みや発想につながり「あなたの真ん中」に響いていくのではないかと思うようになりました。

(テーマ決めの前にMashupAward名古屋予選のテーマ決めや CivicTechMeetup 2017 Kanazawaに参加して刺激を もらったような気がします。)

そして二転三転のダメ出しの末、「テクノロジーで暮らしを楽しくオモシロク!」に決定しました。 参加者からはふわっとしすぎという話は結構もらったんですが、このテーマでいろいろなアイデアが出てきたので個人的には気に入ってます。

f:id:ueponx:20180306000805j:plain


参加者への連絡し後は前日の会場の準備を行い開催日を待つだけとなるのですが、今年は新たなチャレンジにも挑戦してみました。

前回のイベント時にテクノロジーサポート企業の方々の提供サービスの説明が数分と短くなかなか発想に伝わらないという悩みを参加者からは聞いていました。 確かに事前にかなりインプットをしていかないとすぐにアイデアと結びつけていくのはハードルがあるなあとは思っていました。 そこで今回は直前にテクノロジー企業の方で前日入りをしていただける方々にお願いし、プレイベントを開催しました。

mashup-nagoya.connpass.com

通常の倍以上の時間をかけて各企業様にはテクノロジーの説明をいただき、参加者も金曜日の夜にもかかわらず50名に迫る人数でした。 IBM様、ウフル様、Seeed様、Twilio様、Microsoft様、お忙しいところありがとうございました。

この時の作者の気持ちを20文字程度で答えよ。

なんで自分はこのハッカソンに参加できないんだろう。


初日のアイデアソンの受付は正直すこし不安でした。この近辺のコミュニティ勉強会を含めてですが、 欠席率が少ないというのが珍しい地域特性ではあるのですが、それでも不安でした。 季節がらインフルエンザも流行っているし…。 実際にスタートの時間での参加者は101名でした。前回も多いなぁとは思っていたのですが、 今回は100人の大台を超えたので数字にびっくりしていました。 遠方からも関東から九州までという放送エリアを超えたところからの参加もあり、本当うれしい限りです。

そして、開会宣言… 開催前日の夕方に話すことが急遽決まったのでうまく考えがまとまらず、気の利いた事いえなくてすみませんでした。

f:id:ueponx:20180305235454j:plain


イデアソン・ハッカソンはイベントが始まってしまうと、運営側が予想できない状況になります。

ただ、去年もそうでしたが、今年もやんわりした感じでアイデアを出していただけたんじゃないかなと思います。 更にハッカソンでもいろいろな方々との出会いや奇抜なアイデアにもあえて本当に良かったです。 また、会社の中からも2名ではありますが、参加者が出せたのは、このイベントの開催が社内にも少しは響いて きているのかなという感覚で収穫だったかなと思います。

開催の内容に関しては いろいろな方のブログエントリーがありますのでそちらをご覧いただいたほうが臨場感あるかなと思います。

mashupaward.jp

chris4403.hateblo.jp

kuxumarin.hatenablog.com

ghz2000.com

復活するエンジニア — 中京テレビハッカソンhack-chuに参加して来ました。...

hiiiiiiihikaru.hatenadiary.com

hiiiiiiihikaru.hatenadiary.com

niwasawa.hatenablog.jp

niwasawa.hatenablog.jp

niwasawa.hatenablog.jp

twilio.kddi-web.com

thefilament.jp


取り留めのないエントリーポエムではありますが、 今後もこのエリアでまたハッカソンに限らずIT系のイベントが開催ができないか模索しておりますので、お会いした際にはアドバイスなどいただけると助かります。 あと、個人活動としてイベントのお手伝いもできるかもです。

f:id:ueponx:20180306001507j:plain

御礼

伴野さま、まなみさま

フィラメント 角さま、牧さま

ご協賛の凸版印刷

審査員をお引き受け頂いたの粟生様、加藤様、久下様、江龍様、栗栖様

テクノロジーサポート企業のみなさま(アマゾン様、アプレッソ様、ヴァル研究所様、ウフル様、エーアイ様、KDDIウェブコミュニケーションズ様、サイボウズ様、Seeed様、IBM様、Microsoft様、Yahoo!様)

ご協力いただいた、たくさんの方々

そして、参加者のみなさま

本当にありがとうございました!

f:id:ueponx:20180305234851j:plain