Rails4で、iOS用にクラウドデータベースを作る
2014年あけましておめでとうございます。
私は正月とか関係なくプログラミングの勉強をしている所存ですが、iOSのプログラミングの勉強をしている際に、ネット通信についての本や簡易なドキュメントが少ないと思ったので、Railsを使ってクラウドデータベースとiOSとの通信について簡単に書いていこうと思います。
今回作成するミニアプリは、Railsで作成したBookmarkデータベースにiOSからアクセスして、BookmarkされているWebページのURLを取得、表示するというものです。
Rails4では、自動でJSONでのHTTP通信に対応したアプリケーションを作成してくれるので、初心者にも非常に簡単にクラウドデータベースを作成することができます。
1. Railsアプリを作成する
まずは、Railsのコマンドで簡単にデータストアを作成していきましょう
$ rails new Bookmarker Takeuchis-MacBook-Air:casts kousuke$ rails new Bookmarker create create README.rdoc create Rakefile create config.ru create .gitignore create Gemfile create app .... Using uglifier (2.4.0) Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
URLを保存するBookmarkモデルを作成します。
$ cd Bookmarker $ rails g scaffold Bookmark title:string bookmark_url:string invoke active_record create db/migrate/20140103123223_create_bookmarks.rb create app/models/bookmark.rb .... $ rake db:migrate == CreateBookmarks: migrating ================================================ -- create_table(:bookmarks) -> 0.0058s == CreateBookmarks: migrated (0.0059s) =======================================
最後に、ローカル環境で実行します。RailsアプリのURLは"http://localhost:3000"になるので覚えておいてください
$ rails s
2. iOSアプリのインターフェイスを作成する
通常、iOSのUIはxibファイルで作成するのですが、今回は説明を簡単にするためにStroyboardを使って作成していきます。
まずはXcodeでプロジェクトを作成していきましょう。私の環境ではXcode 5を使用しています
i. Master-Detail Applicationを作成
ii. Storyboardを開いて、DetailのViewにWebViewを追加
iii. DetailControllerにWebViewのプロパティを追加
UIに関しては以上です。
3. 試しにRailsのデータをiOSから取得してみる。
さて、いよいよデータ通信をしてみようと思います。
はじめに、サンプルデータを最初に作成したRailsに追加してみましょう。
localhost:3000/bookmarksにアクセス > New Bookmarks > Create Bookmarks で作成します。
それでは、iOSからこのデータを取得するプログラムを書いていきます。
i. HTTPでデータを取得する
#MasterViewController.h
#import <UIKit/UIKit.h> @class DetailViewController; @interface MasterViewController : UITableViewController @property (strong, nonatomic) NSMutableArray *bookmarks; - (void)initBookmarks; @end
#MasterViewController.m
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self initBookmarks]; } - (void)initBookmarks { // RailsにHTTPリクエストを送信する NSURL *url = [NSURL URLWithString:@"http://localhost:3000/bookmarks.json"]; NSURLRequest *req = [NSURLRequest requestWithURL:url]; NSError *error; NSData *bookmarks = [NSURLConnection sendSynchronousRequest:req returningResponse:nil error:&error]; if (!error) { // データが取得で来たら、JSONに変換してプロパティに追加 id json = [NSJSONSerialization JSONObjectWithData:bookmarks options:NSJSONReadingMutableContainers error:nil]; _bookmarks = [[NSMutableArray alloc] init]; for (id data in json) { NSMutableDictionary *bookmark = [NSMutableDictionary dictionary]; bookmark[@"title"] = data[@"title"]; bookmark[@"url"] = data[@"url"]; [_bookmarks addObject:bookmark]; } } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー" message:@"ネットに接続できません" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil]; [alert show]; } } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [_bookmarks count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; NSDictionary *bookmark = _bookmarks[indexPath.row]; cell.textLabel.text = bookmark[@"title"]; return cell; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; DetailViewController *destination = [segue destinationViewController]; [destination setUrl:_bookmarks[indexPath.row][@"url"]]; [destination setTitle:_bookmarks[indexPath.row][@"title"]]; // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. }
ii. 取得したURLにアクセス
続いて、取得したURLをDetailViewで表示できるようにします。
#DetailViewController.h
#import <UIKit/UIKit.h> // Webページを表示するために、プロトコルを追加 @interface DetailViewController : UIViewController <UIWebViewDelegate, UIActionSheetDelegate> @property (weak, nonatomic) IBOutlet UIWebView *webviewer; @property (weak, nonatomic) NSString *url; @end
#DetailViewController.m
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. _webviewer.delegate = self; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Bookmarkのurlを取得 NSURL *url = [NSURL URLWithString:_url]; NSURLRequest *req = [NSURLRequest requestWithURL:url]; NSData *bookmark = [NSURLConnection sendSynchronousRequest:req returningResponse:nil error:nil]; id bookmarkJson = [NSJSONSerialization JSONObjectWithData:bookmark options:NSJSONReadingMutableContainers error:nil]; // Bookmarkのurlにアクセス NSURL *bookmarkURL = [NSURL URLWithString:bookmarkJson[@"bookmark_url"]]; NSURLRequest *webReq = [NSURLRequest requestWithURL:bookmarkURL]; [_webviewer loadRequest:webReq]; }
## iii. 実行
最後にシミュレータを立ち上げて、確認してみましょう。
きちんとリストに追加されています
セルをタップすると、Webページにアクセスできます!!
CocoaPodsをXcode5で使う際に困ったこと
久しぶりの投稿になります。
先日バイト先の会社を退社して、次の会社から給料をいただけるまでiOSのアプリを作りまくってる所存です。
さて、Objective-Cのリハビリが終わり、オープンソースライブラリを使おうと、CocoaPodsをインスコしてビルドすると、Xcode5ではリンカーのエラーが出てしまいます。
そこで、ググってStackOverflowを探しまくってましたが、全く解決されず。。。
3時間ほど画面にかじりついていたら、なんとか自力で解決出来ました(汗)
StackOverflowにも載ってないことを自力で解決した!!と調子に乗ってQiitaにも投稿しましたので、このブログでも共有しようかと思います。
あなたにオススメの〜的なサービスを実装する(アルゴリズム改良版)
前回までは、数式通りにゴリゴリとレコメンド行列を作成していきましたが、前回のプログラムでは計算量がO(m^3)になってしまいます。(mはユーザーデータの量)
したがって、ユーザーが1000人、2000人と増えていくに連れて、一気にプログラムの速度が落ちます。
今回は、前回と同様の計算を行う上でより効率的なプログラムを紹介します。
このプログラムでは、理論的に計算量がC(mn)になります。(nはアイテムの個数)
ただし、今回はユーザーが2つのアイテムだけ購入する前提で作成しました。
まずは、前回のプログラムの実行時間を計測できるようにします。
import numpy as np from random import randint import time def generateTable(m, n): a = np.zeros([m, n]) for row in range(0, m): col1 = int(randint(0, n - 1)) a[row][col1] = 1 col2 = int(randint(0, n - 1)) while col2 == col1: col2 = int(randint(0, n - 1)) a[row][col2] = 1 return a start = time.clock() m, n = 1000, 42 A = generateTable(m, n) # create association table fav_table = [] for i in range(len(A[0])): fav_row = [] for j in range(len(A[0])): fav_row.append([]) fav_row[j] = 0 for k in range(len(A)): fav_row[j] += A[k][i] * A[k][j] fav_table.append(fav_row) # calculate association rate from table transposed = [[row[i] for row in fav_table] for i in range(len(A[0]))] def add(x, y): return x + y sum_table = [reduce(add, transposed[i]) for i in range(len(transposed))] for i in range(len(fav_table)): for j in range(len(fav_table[0])): fav_table[i][j] /= float(sum_table[j]) end = time.clock() time = end-start time = str(round(time, 3)) print 'Older Process: ' + time + 'second'
今回は、ユーザーのアイテム購入データが1000件、アイテム数が42個として計算します。
これを実行した結果、4.3秒ほどの時間がかかりました。
続いて新しいプログラムを紹介します。
import numpy as np from random import randint import time def generateTable(m, n): a = np.zeros([m, n]) for row in range(0, m): col1 = int(randint(0, n - 1)) a[row][col1] = 1 col2 = int(randint(0, n - 1)) while col2 == col1: col2 = int(randint(0, n - 1)) a[row][col2] = 1 return a def createRawAssociate(a, m, n): b = np.zeros([n, n]) for k in range(0, m): findAssociate(a, b, n, k) b_tran = np.transpose(b) return b + b_tran def findAssociate(a, b, n, k): col1, col2 = 0, 0 for j in range(0, n): if a[k][j]: col1 = j break for l in range(col1 + 1, n): if a[k][l]: col2 = l break b[col1][col2] += 1 def totalVector(b, n): c = np.zeros(n) for j in range(0, n): for k in range(0, n): c[j] += b[k][j] return c def createAssociateTable(b, c, n): d = np.zeros([n, n]) for i in range(0, n): for j in range(0, n): if c[j]: d[i][j] = round(b[i][j] / c[j], 2) return d start = time.clock() m, n = 1000, 42 A = generateTable(m, n) B = createRawAssociate(A, m, n) C = totalVector(B, n) D = createAssociateTable(B, C, n) end = time.clock() time = end-start time = str(round(time, 3)) print 'Process: ' + time + 'second'
リファクタリングが出来ていないので、少し長ったるいプログラムに見えますが、いざ実行してみると、結果は0.045秒で実行できました!
つまり、たった1000件のユーザーデータを扱う場合でも、今回の改良版アルゴリズムでは100倍の速度で実行が可能になります。
あなたにオススメの〜的なサービスを実装する(数式編)
前回の解析では、Pythonを知らない人にとっては分かり難いプログラムだったと思うので、レコメンドサービスで使用するアソシエーション解析の数学的理論について書こうと思います。
この公式は、こちらのサイトの内容の前半部分を元に自分で作成した公式になりますので、確実性は保証できませんが、簡単なアソシエション解析を行う際には問題ないと思います。
第2回 「ある商品といっしょによく売れる商品は何か?」を見つけるには ~マーケット・バスケット分析の考え方:Mahoutで体感する機械学習の実践|gihyo.jp … 技術評論社
1. 顧客の商品購入データの行列Aから、商品の組み合わせ購入行列Bを作成する。
として、以下の公式で計算する
ただし、より、
あなたにオススメの〜的なサービスを実装する。
AmazonやFacebookに代表されるように、「あなたにオススメの〜」みたいなサービスを構築する方法について考えてみます。いわゆるレコメンド・サービスのためのアソシエーション解析の一環ですね。
まずは解析するデータを自動で作成するために、ExcelでVBAを書きます。
Sub setRnd() For i = 2 To 31 For j = 2 To 17 Worksheets("Sheet1").Cells(i, j).Value = Round(Rnd()) Next Next End Sub
作成したデータがこちら。
例えば(A1, a3)は1となっていますが、これはA1というユーザーがa3という商品を購入した。もしくは、A1というユーザーがa3と友達である。みたいな解釈をしてください。
0の場合は、繋がりが無いといった感じです。
さて、このデータから、例えば「a2とa7は、よく購入される組み合わせだ!」みたいなことを数値的に解析してみましょう。
まずはこのデータをcvsファイルに変換し、pythonで解析できるようにします。
次に、この「生」のデータから、組み合わせ表を作成します。
とりあえずプログラムを見て行きましょう。
1. csvファイルを読み込む
import sys import types with open('Workbook1.csv', 'r') as csv: for line in csv: data = line.strip().split(",") # fix csv to create data table matrix = [] row = [] for l in data: try: num = int(l) except ValueError: matrix.append(row) row = [] else: row.append(num) del matrix[0]
私のcsvファイルはなぜか改行がなされていなかったので、行の先頭に顧客名があることを利用して、データをテーブルにフォーマットします。
2. 購入の関連性表を作成する
# create association table fav_table = [] for i in range(len(matrix[0])): fav_row = [] for j in range(len(matrix[0])): fav_row.append([]) fav_row[j] = 0 for k in range(len(matrix)): fav_row[j] += matrix[k][i] * matrix[k][j] fav_table.append(fav_row)
先ほどの説明のように、ある商品と他の商品がどのくらいの頻度で組み合わせ購入されているかを調べます。
例えば1行目と3行目ではa1とa3の商品が、10行目と18行目ではa10とa18の商品がどれだけ同時購入されたかわかります。
3. レコメンド用のテーブルをファイルに書き込む
# calculate association rate from table transposed = [[row[i] for row in fav_table] for i in range(len(matrix[0]))] def add(x, y): return x + y sum_table = [reduce(add, transposed[i]) for i in range(len(transposed))] for i in range(len(fav_table)): for j in range(len(fav_table[0])): fav_table[i][j] /= float(sum_table[j]) #write association rate table to csv file try: f = open("associated.csv", 'w') w = '' for i in range(len(fav_table[0])): w += "\n" for j in range(len(fav_table)): w += repr(fav_table[i][j]) + ', ' f.write(w) finally:
例えばa1と組み合わせて購入された履歴が200回あるとします。そのうちa2との組み合わせが150回なら、a1とa2との組み合わせ購入率は150/200 = 0.75となります。
このようにそれぞれの組み合わせ購入率を計算して、レコメンド用のテーブルを更新してファイルに書き込みます。
以上の結果、作成したデータはこんな感じになりました。
ラベルが無いので見難いですが、たとえばA2のデータはa1とa2の商品が組み合わせて購入される確率が0.02とかなり低く、あまりこの組み合わせ購入はおすすめできないことが読み取れます。
もし「あなたにオススメの〜は...」といったサービスを構築する際は、こんな感じのプログラムを作成するのがいいのですが、forループをかなり使うため、大規模なデータを扱う際にはアルゴリズムの改善をするべきだと思います。
追記:
a1とa1の商品の組み合わせ購入など、同じ商品同士で購入されることはありえないのですが、今回はその辺の計算を考慮し忘れてしまいました。そのためデータが正確では無いことをお詫び申し上げます。
Hadoopでサービス構築する際に便利なHadoop周りのソフトウェア・ライブラリ群
最近ビッグデータを用いたビジネスやサービス開発が活発になってきて、かのニューヨーク・タイムズでも、ビッグデータを扱うデータアナリストは「21世紀で最も魅力的な職業」といったほど、世の中はデータに対する強い関心を集めているようだ。
実際私の周りで起業した同世代たちも、かなりの方がビッグデータに関するビジョンを持っている。
しかし、ビッグデータと一口に言っても、これを実際にサービスに実装することは熟年の技術者でも難しい。なぜなら、まずビッグデータという言葉自体の意味合いを理解することが難しく、さらにそれを日常のサービス開発に組み込んでいくとなったら一般的な経営者、技術者には困難を極める。
また、ビッグデータが適応される範囲の多くは、「交通情報やサーバーのログなど、日常的なデータ」からデータマイニング(有効なデータの抽出)を行うことであり、今まで気にしなかったデータをむさぼる訳であって、その気付きが発すること自体がビッグデータを一般になかなか浸透させられない原因の一つでもあると思う。
ただし、先ほど言及したように、ビッグデータに関するアイデアを実際に活用しようと試みる人もたくさんいるわけで、今後ビッグデータが身近により広まることは確実である。
今回は、そのビッグデータを用いたサービスを開発する際に、最初にぶち当たる「サービス実装の技術的困難性」を乗り越えるために使えるオープンソースをまとめてみた。
実際にHadoopなどを勉強したり触ったことのある人なら、ここに書いてあることはほとんどご存知だと思うが、今回はこれからビッグデータの砂漠に足を踏み入れようとする勇気ある青年たちに送る文章だと思って眺めていただきたい。
1. Apache Nutch
ビッグデータをまだ所有していないなら、Webページなどからデータを取ってくることも多いと思う。そうすれば大量のデータがタダで手に入る。
ただし、Webページを巡回するボットを作っても、Hadoopなどの分散型データベースに格納する方法は、今までのMySQLなどのデータベース操作とは一味違う。したがって、このNutchが提供する Web検索、Web情報処理機能を利用するのがいいと思う。
2. Apache HBase
分散型データベースソフト。操作を覚えるのには難易度が高く、始めは有効に機能を扱えないと思うが、使いこなせればかなりの恩恵を受けられる。
特に自然言語処理をする際に用いられるデータベース。
3. Apache Mahout
Apache Mahout: Scalable machine learning and data mining
ビッグデータの分析には難関大学でも上級レベルの統計学を用いることが多いが、その統計解析アルゴリズムを簡単に使えるようにライブラリにまとめたものがMahoutである。
有名なものとして、クラスタ分析、クラス分類、協調フィルタリングなどのアルゴリズムを提供。(詳しくはWikipediaなどで調べてください)
4. Apache Pig
実際にデータベースの操作をJavaでやるのは少し難易度が高いため、Hadoop用にPigLatinという高級言語が用意されている。
他にも、最近プログラマならよく耳にするであろう「Clojure」というLispの方言でも操作できるらしい(触ったことないけど)。Clojureならモバイル・アプリの開発もできるらしく、Clojureが好きすぎてClojure専門の会社を作った変態たちもいるらしい。
→Clojure,iOS・Androidアプリ開発の事ならニャンパス株式会社|ニャンパスが提供しているサービス
5. Apache Hive
通常Hadoopを操作するにはJavaのAPIを使って、今までとは違った操作の仕方をするのだが、HiveQLというSQLに似たデータベース操作言語を用いることによって、Hadoop操作をより簡単に実行できる。ただし、MySQLのようなRDBと同じ機能を提供しているわけではないことに注意。
6. Cascading
Cascading | Application Platform for Enterprise Big Data
Javaでシステムを構築する際に、0から構築するのは大変である。そこで、CascadingはJavaアプリケーションを構築するためのフレームワークを提供する。
7. Apache HCatalog
Welcome to Apache Hive™ - HCatalog!
基本的にHadoop周りのシステムはJavaで実装することが多いが、サービスのインターフェースなどはRuby on RailsやPerl, Djangoなどの多言語で実装することがほとんどである。したがって、JavaのシステムであるHadoopに多言語からアクセスしないといけないのだが、その際にそれぞれの言語ごとにデータ定義がバラバラだとデータの一貫性としてよろしくない。
そこでこのHCatalogは、異なる言語間のデータ定義を統一化し、それぞれでデータベースにアクセスすることが可能になる。
8. Apache Bigtop
Hadoopのアプリケーションのテスト、ビルド、パッケージングを自動化するツール。
個人的にはJUnitを気に入っていたので、現在JUnitを用いてHadoopのテスト自動化が出来ないか模索中。
9. Apache Hama
Hama - a general BSP framework on top of Hadoop
Hadoopは直感的に言うと、縦長なデータベースであるため、リレーショナルが関係してくるグラフ計算や行列計算を苦手とする。
そこでこのHamaは、Hadoopの苦手な計算をBSP方式を用いて可能にする機能を提供している。
10. Apache Giraph
Giraph - Welcome To Apache Giraph!
先ほどのHamaとカブるかもしれないが、グラフ計算のライブラリを提供。ページランクなどのアルゴリズムもあったような気がする。(うろ覚えなんで、自分で調べてください)
12. Apache Flume
Welcome to Apache Flume — Apache Flume
ビッグデータを活用しようと思いついた時、多くの場合はWebサーバーのログについてのアイデアだと思う。(私の場合も最初はサーバーログでした)
そのログの収集や、情報の抽出を行うためのツールがFlumeである。
13. Apache Chukwa
先ほどのFlumeと対立?しているツール。ログの集計機能を提供しているが、こちらはサーバーログの情報を表示、監視、分析する機能が優れていると思う。
14. Apache Oozie
Apache Oozie Workflow Scheduler for Hadoop
Hadoop周りのシステムのワークフロー内の各処理を監視、制御できるツール。
私はまだ使ったことが無いので、詳しいことはわかりません。
15. Apache Whirr
Hadoopのためのサービスをクラウド上で実行させるためのライブラリ。CloudStackとかでも活用できるみたいなので、今後CloudStackを用いたクラウド環境構築の記事でも紹介していこうと思う。
18. Apache Avro
コンピュータとコンピュータ同士でデータのやり取りをするための仕組みを提供。基本的にHadoopはサーバーを何台も使うことが多いが、基本的にはじめは1台のサーバーで動くように開発してから複数台のサーバーようにシステムを変換するため、このAvroを使えばその移転作業が効率化できる。
ぜいぜい。長くなりましたが、以上がHadoop周りの便利なツールキットで有名なものの紹介になります。正直全部使いこなしているわけでもない上、全く触ったことも無いものまで紹介してしまったため、一部誤りがあるかもしれないですが、いかんせん日本語のドキュメントも未だ少ないものなので、詳しい情報や実装例は日本語のページではあまり見受けられませんゆえに、この程度の紹介で終わらせていただこうと思います。
喫煙者の1日を観察する (統計解析版)
1週間前くらいに喫煙者がどれだけ損しているかをシュミレーションして解析しましたが、データ処理に関してもっとスマートな方法があったので紹介しようと思います。
前回はRubyでモデルを作ってシュミレーションを行いましたが、今回は「R」を用いて簡単に解析してみます。
そのために、今回用いる分布のモデルを紹介します。
喫煙のように1日あたりの喫煙率がわかっており、これから解析する期間がわかっている場合は「ポアソン分布」を用いると簡単に解析できます。
専門的な数式はこちらを参考にしてください
ポアソン分布 - Wikipedia
ポアソン分布は λ, t, kを変数として確率変数をモデリングするのですが、今回の場合はλ = 20本/日, t= 365日 として一番尤もらしいkの値、つまり1年間タバコを吸う場合に一番可能性が高い消費量を計算したいと思います。
さて、「R」でポアソン分布を扱う場合はdpois関数を使います。
ただし、この分布はλとtを掛け算した値をλとして引数にします。なので、今回の場合はλ = 20本/日 × 365日 = 7300本となります。
plot(dpois(7000:7600,7300))
表示される分布データがこちらです。
ポアソン分布の性質からすると当たり前なのですが、分布の平均値はλになるので年間の平均喫煙本数は7300本になります。
ここで分散を考慮すると、
分散σ = λ = 7300本^2。
ということで、ポアソン分布が正規分布に近似できることを念頭に、68%の確率に相当する1次分散の範囲を計算すると、
(範囲) = μ ± √σ = 7300 ± √7300 = 7300 ± 85.44.... = [7215, 7385]本
したがって、年間少なくても7215本くらいタバコを吸う確率が高いという分析結果が出ました。
これは、前回のシミュレーションと大分違う値となりました。
なぜでしょうか??
私の見解では、前回のシュミレーションでは喫煙時間を考慮して解析したのですが、今回の統計解析では「一瞬でタバコを吸う」ことを前提に解析しています。したがって、シミュレーション結果よりも解析結果が大幅に外れてしまったんだと思います。
今回のように、データ点が「一点」でない(一瞬ではなく数分で1つのデータ)場合にポアソン分布のようなモデルを安易に適応するといけないことがわかりました。
今回の統計解析は利便性があるかは疑問ですが、シミュレーション解析か統計解析のどちらでデータを解析するか迷った場合は、今回の教訓を参考にしてみてもいいかと思います。