{"cells":[
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "import os\nimport numpy as np\nimport pandas as pd\nimport IPython\nfrom IPython.display import SVG\n\nimport matplotlib.pyplot as plt\nfrom scipy import misc\n\n%matplotlib inline\nplt.rcParams['figure.figsize'] = (15.0, 15.0) # set default size of plots"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Yelp Data Crawler Class"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "# default image preprocess\ndef dflt_img_preprocess_fn(img, resize_dim=(480, 480, 3)):\n    return misc.imresize(img, resize_dim, interp=\"nearest\")\n                \ndef mkdir_p(path):\n    try:\n        os.makedirs(path)\n    except OSError as exc:  # Python >2.5\n        if exc.errno == errno.EEXIST and os.path.isdir(path):\n            pass\n        else:\n            raise\n\n# prepare image for vggnet\ndef prepare_image_for_vggnet(img, resize_dim=(224, 224, 3), sub_vgg_mean=False, depth_first=True, dtype=np.float32):\n\n    if resize_dim == -1:\n        img = img.astype(dtype)\n    else:\n        img = misc.imresize(img, resize_dim, interp=\"nearest\").astype(dtype)\n\n    if sub_vgg_mean:\n        img[:,:,0] -= dtype(103.939)\n        img[:,:,1] -= dtype(116.779)\n        img[:,:,2] -= dtype(123.68)\n\n    if depth_first:\n        img = img.transpose((2,0,1))\n\n    return img\n\n\"\"\"\ntrain_photo_biz_tbl: training dataframe, photo - biz id \ntrain_biz_attrib_tbl: training dataframe, biz id - attributes\ntest_photo_biz_tbl: test dataframe, photo - biz id\n\"\"\"\nclass YelpDataCrawler(object):\n    \n    restaurant_attributes = [\"good_for_lunch\", \"good_for_dinner\", \"takes_reservations\", \n                             \"outdoor_seating\", \"restaurant_is_expensive\", \"has_alcohol\", \n                             \"has_table_service\", \"ambience_is_classy\", \"good_for_kids\"]\n   \n    def __init__(self, save_processed_data_at=\"../pdata\"):\n        \n        self.save_processed = save_processed_data_at != None\n        self.processed_data_dir = save_processed_data_at\n        \n    def load_train_data(self, data_dir=\"../input\", \n                                        images_dir=\"train_photos\",\n                                        photos_biz_tbl=\"train_photo_to_biz_ids.csv\", \n                                        biz_attrib_tbl=\"train.csv\"):\n        \n        self.data_dir = data_dir\n        self.train_images_dir = os.path.join(data_dir, images_dir)\n        train_pdata_dir = os.path.join(self.processed_data_dir,'train') if self.save_processed else \"\"\n        \n        if self.save_processed and os.path.isdir(train_pdata_dir):\n            \n            self.train_photo_biz_tbl = pd.read_csv(os.path.join(train_pdata_dir, photos_biz_tbl))\n            self.train_biz_attrib_tbl = pd.read_csv(os.path.join(train_pdata_dir, biz_attrib_tbl))\n        else:\n            \n            self.train_photo_biz_tbl = pd.read_csv(os.path.join(data_dir, photos_biz_tbl))\n            self.train_biz_attrib_tbl = pd.read_csv(os.path.join(data_dir, biz_attrib_tbl))\n            \n            # remove nas and duplicates\n            self.train_biz_attrib_tbl = self.train_biz_attrib_tbl.dropna()\n            self.train_biz_attrib_tbl = self.train_biz_attrib_tbl.drop_duplicates()\n            \n            # remove entries with no attributes\n            self.train_photo_biz_tbl = self.train_photo_biz_tbl[self.train_photo_biz_tbl['business_id'].apply(\n                                                        lambda x: x in self.train_biz_attrib_tbl.business_id.values)]\n            \n            if self.save_processed:\n                \n                # create dir to store processed data \n                mkdir_p(os.path.join(train_pdata_dir))\n\n                # save processed tables to disk\n                self.train_photo_biz_tbl.to_csv(os.path.join(train_pdata_dir, photos_biz_tbl), index=False)\n                self.train_biz_attrib_tbl.to_csv(os.path.join(train_pdata_dir, biz_attrib_tbl), index=False)\n                \n        self.num_train_images = self.train_photo_biz_tbl.photo_id.size\n        self.num_train_biz = self.train_photo_biz_tbl.business_id.unique().size\n        self.num_out_classes = len(self.restaurant_attributes)\n        \n    def load_test_data(self, test_data_dir=\"../input\",\n                                        images_dir=\"test_photos\",\n                                        photos_biz_tbl=\"test_photo_to_biz.csv\"):\n        \n        self.test_data_dir = test_data_dir\n        self.test_images_dir = os.path.join(test_data_dir, images_dir)\n        test_pdata_dir = os.path.join(self.processed_data_dir,'test') if self.save_processed else \"\"\n    \n        if self.save_processed and os.path.isdir(test_pdata_dir):\n            \n            self.test_photo_biz_tbl = pd.read_csv(os.path.join(test_pdata_dir, photos_biz_tbl))\n        else:\n            \n            self.test_photo_biz_tbl = pd.read_csv(os.path.join(test_data_dir, photos_biz_tbl))\n            \n            if self.save_processed:\n                \n                # create dir to store processed data \n                mkdir_p(os.path.join(test_pdata_dir))\n\n                # save processed tables to disk\n                self.test_photo_biz_tbl.to_csv(os.path.join(test_pdata_dir, photos_biz_tbl), index=False)\n                \n        self.num_test_images = self.test_photo_biz_tbl.photo_id.size\n        self.num_test_biz = self.test_photo_biz_tbl.business_id.unique().size\n    \n    # get business attributes by biz id\n    def get_train_biz_attribs_by_biz_id(self, biz_id):\n        return self.train_biz_attrib_tbl[\n                        self.train_biz_attrib_tbl.business_id == biz_id]['labels'].as_matrix()[0].split()\n    \n    # get one-hot encoded business attributes by biz id\n    def get_train_biz_attribs_one_hot_by_biz_id(self, biz_id):\n        return np.array([(0,1)[str(i) in self.get_train_biz_attribs_by_biz_id(biz_id)] \\\n                                                            for i in range(len(self.restaurant_attributes))])\n    \n    # read image from disk\n    def read_image(self, img_file_path):\n        return misc.imread(img_file_path)\n    \n    # get training image by id\n    def get_train_image_by_img_id(self, img_id):\n        return self.read_image(os.path.join(self.train_images_dir, str(img_id)+\".jpg\"))\n    \n    # get training image by index (image to biz table)\n    def get_train_image_by_img_idx(self, img_idx):\n        return self.get_train_image_by_img_id(self.train_photo_biz_tbl.iloc[img_idx][\"photo_id\"])\n    \n    # get training image ids associated with a biz id\n    def get_train_image_ids_by_biz_id(self, biz_id):\n        return self.train_photo_biz_tbl[self.train_photo_biz_tbl.business_id == biz_id][\"photo_id\"].as_matrix()\n    \n    # get training images by biz id\n    def get_train_images_by_biz_id(self, biz_id, img_preprocess_fn=dflt_img_preprocess_fn, \n                                                                    req_max_imgs=10, shuffle=False):\n        \n        biz_img_ids = self.get_train_image_ids_by_biz_id(biz_id)[:req_max_imgs]\n        if shuffle: \n            np.random.shuffle(biz_img_ids)\n        return np.array([img_preprocess_fn(self.get_train_image_by_img_id(img_id)) for img_id in biz_img_ids])\n\n    # read test image by id\n    def get_test_image_by_img_id(self, img_id):\n        return self.read_image(os.path.join(self.test_images_dir, str(img_id)+\".jpg\"))    \n    \n    # get test image by index (image to biz table)\n    def get_test_image_by_img_idx(self, img_idx):\n        return self.get_test_image_by_img_id(self.test_photo_biz_tbl.iloc[img_idx][\"photo_id\"])\n\n    # get test image ids associated with a biz id\n    def get_test_image_ids_by_biz_id(self, biz_id):\n        return self.test_photo_biz_tbl[self.test_photo_biz_tbl.business_id == biz_id][\"photo_id\"].as_matrix()\n    \n    # get test images by biz id\n    def get_test_images_by_biz_id(self, biz_id, img_preprocess_fn=dflt_img_preprocess_fn,\n                                                                      req_max_imgs=10, shuffle=False):\n        \n        biz_img_ids = self.get_test_image_ids_by_biz_id(biz_id)[:req_max_imgs]\n        if shuffle: \n            np.random.shuffle(biz_img_ids)\n        return np.array([img_preprocess_fn(self.get_test_image_by_img_id(img_id)) for img_id in biz_img_ids])\n    \n    def sample_train_images(self, sample_size, img_preprocess_fn=dflt_img_preprocess_fn, \n                                                                        replace=False, rand_seed=None):\n\n        shuffle = True\n        \n        if self.train_photo_biz_tbl.index.size < sample_size:\n            raise Exception(\"TrainSampleFinished\")\n        \n        sample_photobiz_tbl = self.train_photo_biz_tbl.sample(n=sample_size, replace=replace, random_state=rand_seed) \n        \n        X_inputs = np.array([img_preprocess_fn(self.get_train_image_by_img_id(img_id)) \\\n                                                for img_id in sample_photobiz_tbl[\"photo_id\"] ])\n        y_labels = np.array([self.get_train_biz_attribs_one_hot_by_biz_id(biz_id) \\\n                                                for biz_id in sample_photobiz_tbl[\"business_id\"]])\n        \n        if replace == False:\n            self.train_photo_biz_tbl = self.train_photo_biz_tbl.drop(sample_photobiz_tbl.index)            \n            \n        return X_inputs, y_labels\n    \n    def iterate_train_images_by_class(self, class_name, req_max_biz=10, req_max_imgs=10, shuffle=False):\n        \n        bizattrib_tbl = self.train_biz_attrib_tbl\n        photobiz_tbl = self.train_photo_biz_tbl\n        filtered_tbl = bizattrib_tbl[bizattrib_tbl[\"labels\"].apply(\n                                lambda x: str(self.restaurant_attributes.index(class_name)) in str(x).split())]\n        \n        if shuffle:\n            filtered_tbl = filtered_tbl.reindex(np.random.permutation(filtered_tbl.index))\n        \n        for biz_id in filtered_tbl[:req_max_biz][\"business_id\"]:\n            for img_id in photobiz_tbl[photobiz_tbl.business_id == biz_id][:req_max_imgs][\"photo_id\"]:\n                yield self.get_train_image_by_img_id(img_id)\n    \n    def iterate_train_biz_images(self, img_preprocess_fn=dflt_img_preprocess_fn, req_max_imgs=10, shuffle=False):\n            \n        for biz_id in self.train_photo_biz_tbl.business_id.unique():\n            biz_imgs = self.get_train_images_by_biz_id(biz_id, img_preprocess_fn, req_max_imgs, shuffle)\n            yield biz_id, biz_imgs\n            \n    def iterate_test_biz_images(self, img_preprocess_fn=dflt_img_preprocess_fn, req_max_imgs=10, shuffle=False):\n\n        for biz_id in self.test_photo_biz_tbl.business_id.unique():\n            \n            biz_imgs = self.get_test_images_by_biz_id(biz_id, img_preprocess_fn, req_max_imgs, shuffle)\n            yield biz_id, biz_imgs"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Reading training data"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "# To save loading time, use 'save_processed_data_at = {your preferred dir or use default}'\nYelpData = YelpDataCrawler(save_processed_data_at=\"None\")\nYelpData.load_train_data()"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Sampling training data"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "num_samples = 16\nxtr, ytr = YelpData.sample_train_images(num_samples, img_preprocess_fn=dflt_img_preprocess_fn, replace=False)\nfor i in range(num_samples):\n    plt.subplot(np.sqrt(num_samples)+1, np.sqrt(num_samples), i+1)\n    title_str = \"\".join([ YelpData.restaurant_attributes[idx] + \"\\n\" for idx,val in enumerate(ytr[i]) if val])\n    plt.title(title_str)\n    plt.imshow(xtr[i])\n    plt.axis('off')\nplt.tight_layout(pad=0)"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Sampling images by restaurant class"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "class_names =  YelpData.restaurant_attributes\nreq_max_biz, req_max_imgs_per_biz = 3, 3\n\nfor i, class_name in enumerate(class_names):\n    fig = plt.figure(i+1, figsize=(10,10))\n    st = fig.suptitle(class_name + \" (raw images)\", fontsize=\"x-large\")    \n    fig.subplots_adjust(wspace=0.01, hspace=0.01)\n    for image_no, img in enumerate(YelpData.iterate_train_images_by_class(\n                                       class_name, req_max_biz, req_max_imgs_per_biz, shuffle=True)):\n        fig.add_subplot(req_max_biz, req_max_imgs_per_biz, image_no+1)\n        plt.axis(\"off\")\n        plt.imshow(img)"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Sampling images by business id"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "biz_id = YelpData.train_photo_biz_tbl.business_id[np.random.randint(0, YelpData.num_train_biz)]\nX_train = YelpData.get_train_images_by_biz_id(biz_id, req_max_imgs=36)\ny_train = YelpData.get_train_biz_attribs_by_biz_id(biz_id)\nnum_imgs = len(X_train)\n\nfig = plt.figure(1, figsize=(15,15))\nfig.subplots_adjust(wspace=0.01, hspace=0.01)\ntitle_str = \"biz_id = \" + str(biz_id) + \"\\n\" \\\n            + \"\".join([ YelpData.restaurant_attributes[idx] + \"\\n\" for idx,val in enumerate(y_train) if val])\nplt.suptitle(title_str, fontsize=\"x-large\")\nfor i in range(num_imgs):\n    plt.subplot(np.ceil(np.sqrt(num_imgs)), np.ceil(np.sqrt(num_imgs)), i+1)\n    plt.imshow(X_train[i])\n    plt.axis(\"off\")"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Training in batches"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "def image_proprocess_vggnet(img):\n    img = prepare_image_for_vggnet(img, resize_dim=(224,224,3), \n                                            sub_vgg_mean=True, depth_first=False, dtype=np.float32)\n    return img\n\nmax_epoch = 1\nnum_epoch = 0\nbatch_size = 16\nbatch_count = 0\nwhile num_epoch < max_epoch:\n    try:\n        batch_count += 1\n        print(\"Training batch {}/{}\".format(batch_count, int(YelpData.num_train_images/batch_size)))\n        xtr, ytr = YelpData.sample_train_images(batch_size, img_preprocess_fn=image_proprocess_vggnet, replace=False)\n        \"\"\"\n        TRAIN YOUR MODEL HERE\n        \"\"\"\n        fig = plt.figure(i+1, figsize=(15,15))\n        st = fig.suptitle(\"Training Input Sample\", fontsize=\"x-large\")\n        # e.g. code\n        for i in range(batch_size):\n            plt.subplot(np.sqrt(batch_size)+1, np.sqrt(batch_size), i+1)\n            plt.axis('off')\n            title_str = \"\".join([ YelpData.restaurant_attributes[idx] + \"\\n\" \\\n                                             for idx,val in enumerate(ytr[i]) if val])\n            plt.title(title_str)\n            plt.imshow(xtr[i])\n        plt.tight_layout(pad=0)\n        break\n        \n\n    except Exception as train_except:\n        if \"TrainSampleFinished\" == train_except.args[0]:\n            num_epoch += 1\n            print(\"Train Epoch {}/{}\".format(num_epoch, max_epoch))\n            # reload training data\n            YelpData.load_train_data()\n            continue\nprint(\"Finished training...\")"
 },
 {
  "cell_type": "markdown",
  "metadata": {},
  "source": "## Tile Business Images to learn Business -> Attribute classifier maybe"
 },
 {
  "cell_type": "code",
  "execution_count": null,
  "metadata": {
   "collapsed": false
  },
  "outputs": [],
  "source": "# Tile business images to feed into network\n\nnum_biz_sampled = 0\ntrain_biz_ids = YelpData.train_biz_attrib_tbl.business_id.unique()\n\n# reset train parameters\ndef restart_train_minibatch_biz_indexed(start_from_biz_no=0):\n    global num_biz_sampled \n    num_biz_sampled = start_from_biz_no\n\nnum_biz_image_tiles = 9 # must be a perfect square\nW, H, C = 224, 224, 3\n\ndef image_proprocess_vggnet(img):\n    img = prepare_image_for_vggnet(img, resize_dim=(W,H,C), sub_vgg_mean=True, depth_first=True, dtype=np.float32)\n    return img\n\ndef get_train_minibatch_biz_indexed(num_biz):\n    \n    global num_biz_sampled\n    batch_indx = 0\n    W_tile_cnt = H_tile_cnt = int(np.sqrt(num_biz_image_tiles))\n    WT, HT = W_tile_cnt*W, H_tile_cnt*H\n    \n    Xtr_tiled = np.zeros((num_biz, 3, WT, HT))\n    ytr_tiled = np.zeros((num_biz, num_biz_image_tiles))\n    \n    for biz_id in train_biz_ids[num_biz_sampled: num_biz_sampled+num_biz]:\n        \n        # discard business with insufficient images\n        biz_img_count = YelpData.get_train_image_ids_by_biz_id(biz_id).size\n        if biz_img_count < num_biz_image_tiles:\n            print(\"Discarding business {} (insufficient no. of images {}, required )\").format(\n                                                                biz_id, biz_img_count, num_biz_image_tiles)\n            continue\n            \n        xtr = YelpData.get_train_images_by_biz_id(\n                        biz_id, image_proprocess_vggnet, req_max_imgs=num_biz_image_tiles, shuffle=True)\n        \n        Xtr_tiled[batch_indx, :, :, :] = xtr.transpose(1,0,2,3).reshape(\n                        C,W_tile_cnt,H_tile_cnt,W,H).transpose(0,1,3,2,4).reshape(C, WT, HT)\n        ytr_tiled[batch_indx,:] = YelpData.get_train_biz_attribs_one_hot_by_biz_id(biz_id)\n        \n        num_biz_sampled += 1\n        batch_indx += 1\n    return Xtr_tiled, ytr_tiled"
 }
],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"}}, "nbformat": 4, "nbformat_minor": 0}