automatik

Cross the Rubicon

PyTorch Getting Started : Transfer Learning

昨日の引き続き、PyTorchについてもっと見ていく。今日はTransfer Learning(転移学習)について。
元記事はここを参照。

多くのニューラルネット において、膨大なパラメータを持つためそれらを一から学習させることは非常に困難である。 大規模なデータと、マシンパワーが必要になって来るためである。(特に前者が大変そう。)

転移学習は、大規模データで学習済みのモデルの重みを保ったまま別のタスクに利用すること、利用する技術全般を指す。 PyTorch Tutorialでは、あらかじめ学習したResNetを利用し、アリ or ハチの分類タスクを解いてみよう。

データ用意

必要なライブラリをimportする

from __future__ import print_function, division

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy

次にデータを用意しよう。作業中のディレクトリで、以下のコマンドを叩いてデータを取得する。

wget https://download.pytorch.org/tutorial/hymenoptera_data.zip
unzip hymenoptera_data.zip

訓練データ224枚、テストデータ153枚のデータセット実際訓練データがこの数では最初から学習させるのは難しい。しかし、転移学習を使えばこの枚数でもかなりの精度が出る。それをこれから見ていこう。

データのロード

ここがやや複雑に見えるかもしれない。一つ一つ順を追って見ていく。
前回のように, transformsを定義する。注意すべきは、訓練時とテスト時でデータにかける変換が異なるという点である(ちょっとハマった)。ここでの変換は自前で用意したものではなく、 torchvision.transforms にあらかじめ用意してあるものを利用している。変換の一覧はこちらを参照。変換を紹介するのはまた別の機会に書こうと思う。

data_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

データセットクラスなどの定義。 訓練時とテスト時でデータセットクラスを作り、DataLoader でイテラブルにする感じである。 ここの ImageFolder はあらかじめ root/分類したいクラス/写真.(jpg|png) となるようにクラスを作ると、自動的に分類タスク用にデータセットクラスを作ってくれるというもの。

data_dir = 'hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                          data_transforms[x])
                  for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
                                             shuffle=True, num_workers=4)
              for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

ちなみにサンプルすると以下のようなデータが出てくる。

>> image_datasets['train'][0]
(tensor([[[-0.7137, -0.7137, -0.6965,  ..., -0.6965, -0.6623, -0.6281],
         [-0.7137, -0.6965, -0.6794,  ..., -0.6965, -0.6623, -0.6452],
         [-0.6965, -0.6965, -0.6794,  ..., -0.7137, -0.6794, -0.6794],
         ...,
         [-0.6452, -0.6452, -0.6281,  ..., -0.6794, -0.6623, -0.6623],
         [-0.6281, -0.6281, -0.6452,  ..., -0.7137, -0.6965, -0.6965],
         [-0.6281, -0.6281, -0.6623,  ..., -0.7137, -0.6965, -0.6965]],

        [[ 0.6429,  0.6429,  0.6604,  ...,  0.6604,  0.6954,  0.6954],
         [ 0.6429,  0.6604,  0.6779,  ...,  0.6779,  0.6954,  0.6779],
         [ 0.6604,  0.6604,  0.6779,  ...,  0.6604,  0.6779,  0.6429],
         ...,
         [ 0.7479,  0.7479,  0.7654,  ...,  0.6779,  0.6954,  0.6954],
         [ 0.7654,  0.7654,  0.7479,  ...,  0.6429,  0.6604,  0.6604],
         [ 0.7654,  0.7654,  0.7304,  ...,  0.6429,  0.6429,  0.6429]],

        [[ 2.2391,  2.2391,  2.2914,  ...,  2.3088,  2.3437,  2.3611],
         [ 2.2566,  2.2566,  2.3088,  ...,  2.3263,  2.3437,  2.3437],
         [ 2.2566,  2.2566,  2.3263,  ...,  2.3088,  2.3263,  2.3088],
         ...,
         [ 2.3437,  2.3437,  2.3611,  ...,  2.2914,  2.3088,  2.3088],
         [ 2.3611,  2.3611,  2.3437,  ...,  2.2914,  2.3088,  2.3088],
         [ 2.3611,  2.3611,  2.3263,  ...,  2.3263,  2.3437,  2.3437]]]), 0)

Visualize a few images

実際にデータをのぞいてみよう。ここは特にいうことは無し。

def imshow(inp, title=None):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # pause a bit so that plots are updated


# Get a batch of training data
inputs, classes = next(iter(dataloaders['train']))

# Make a grid from batch
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[class_names[x] for x in classes])

001.png

Training the model

学習を実行する部分。ちょっと長いが、以下が学習するスクリプト。公式のコメントに付け足す形で適宜コメントをつけている。以下の点に注意。

  • schedulerは torch.optim.lr_scheduler クラスのもの。訓練時のみ、各エポックごとに scheduler.step() を実行して進める。
  • torch.max(output, 1) は二つの値を返す。一つ目は、実際にforwardした時の計算結果で、2つ目は出力層のもっとも大きいindexである。
  • deepcopy()してもっとも良いモデルを別メモリとして持っておくこと。
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time()

    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                scheduler.step()
                model.train()  # Set model to training mode
            else:
                model.eval()   # Set model to evaluate mode

            running_loss = 0.0
            running_corrects = 0

            # Iterate over data.
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # zero the parameter gradients
                optimizer.zero_grad()

                # forward
                # track history if only in train
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    """
                     torch.max(outputs, 1)が返すもの
          
                     outputs : (出力層が2つの場合)
                     tensor([[ 0.4015,  0.4264],
                     [ 0.5723, -0.2256],
                     [ 0.3828, -0.4551],
                     [ 0.6952, -0.6357]], grad_fn=<ThAddmmBackward>)

                     torch.max(outputs, 1)
                    (tensor([0.4264, 0.5723, 0.3828, 0.6952], 
                    grad_fn=<MaxBackward0>), tensor([1, 0, 0, 0]))
                    1つ目のTensorはnn.Linear(in_features, 2)での最後の層の結果を,
                    2つ目のTensorはどちらのindexが大きいかを取得している
                    """
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # backward + optimize only if in training phase
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # statistics
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                phase, epoch_loss, epoch_acc))

            # deep copy the model
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

        print()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # load best model weights
    model.load_state_dict(best_model_wts)
    return model

Finetuning the convnet(resnet)

転移学習をするため、あらかじめ学習しておいたResNetをロードし、最後の層のみを分類したいクラス分が出力になるように取り替える(今回は2クラスなので2)。代入するような形でモデルを操作できるため、finetuningを非常に簡単に行うことができる。

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2) # 最後の全結合層を取り替える!

model_ft = model_ft.to(device)

criterion = nn.CrossEntropyLoss()

# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

モデルを学習させる。CPUだとそこそこ時間かかったので(公式によると25分ほどかかるようだ)、GPUで行った。

model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,
                       num_epochs=25)

手元の結果では最終的に88%になった。

Epoch 24 / 24
----------
train Loss : 0.2783 Acc : 0.8770
val Loss : 0.2746 Acc : 0.8824

ConvNet as fixed feature extractor

前回の結果では、訓練済みの重みをロードしたあと、全ての重みを訓練データで再学習させたが、parameter.requires_grad をFalseに設定してあげたら良い。 こうすることで、重みをFreezeしたまま、最後の全結合層のパラメータのみを学習することができる。

# ConvNet as a fixed feature extractor
model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
  param.requires_grad = False # 全結合層以外の重みをFreeze

# Parameters of newly constructed modules have requires_grad=True by default
num_fits = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_fits, 2)

model_conv.to(device)
crietion = nn.CrossEntropyLoss()

# Observe that only parameters of final layer are being optimized as
# opoosed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)

# train model
model_conv = train_model(model_conv, crietion, optimizer_conv,
                         exp_lr_scheduler, num_epochs=25)

最終的な結果として、94%になった。

Epoch 24 / 24
----------
train Loss : 0.3329 Acc : 0.8566
val Loss : 0.1881 Acc : 0.9412

PyTorch Getting Started : Data Loading and Processing Tutorials

来年から所属する研究室ではPyTorchが主流らしいため、PyTorchを使い始めることに決めた。一連の記事では Welcome to PyTorch Tutorials — PyTorch Tutorials 1.0.0.dev20180918 documentationのGetting Startedの内容をまとめ、PyTorchの使い方を見ていくことにする。 この記事では Data Loading and Processing Tutorial — PyTorch Tutorials 1.0.0.dev20180918 documentation について解説する。ソースコードはPyTorch Tutorialと同じだけど、ちょくちょく解説入れています。

DATA LOADING AND PROCESSING TUTORIAL

必要なライブラリをimportする。

from __future__ import print_function, division
import os
import torch
import pandas as pd
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils

# Ignore warnings
import warnings
warnings.filterwarnings("ignore")

データセットは以下のような形でアノテーションされている。これから出て来る imageアノテーションされていない顔単体の画像であり, landmarksアノテーション部分のことを指す。

face2.jpg

これらの画像とアノテーションデータのcsvここから手に入る。これらを作業中のホームディレクトリにおいている前提で以下は続ける。

csvファイルのアノテーションデータと画像を表示するスクリプトは以下。 画像の上にアノテーションデータを重ねる感じ。

landmarks_frame = pd.read_csv('faces/face_landmarks.csv')

n = 65
img_name = landmarks_frame.iloc[n, 0]
landmarks = landmarks_frame.iloc[n, 1:].as_matrix()

# -1をつけると, 他の指定した行列のサイズから変形すべき大きさを推測してくれる。
landmarks = landmarks.astype('float').reshape(-1, 2)

print('Image name: {}'.format(img_name))
print('Landmarks shape: {}'.format(landmarks.shape))
print('First 4 Landmarks: {}'.format(landmarks[:4]))

def show_landmarks(image, landmarks):
    """Show image with landmarks"""
    plt.imshow(image)
    # 0番目に横軸, 1番目にy軸のデータが入っている
    plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
    plt.pause(0.001)  # pause a bit so that plots are updated

plt.figure()
show_landmarks(io.imread(os.path.join('faces/', img_name)),
               landmarks)
plt.show()

ここまで実行すると以下のアノテーションデータが見られる。

face_65.jpg

DATASET CLASS

PyTorchでは torch.utils.data.Dataset という抽象クラスが用意されている。このクラスを継承し、__len__ をデータセットのサイズを返すように、__getitem__ をデータセットから一つサンプルを返すようにオーバーライドすることで簡単にデータセットクラスを作ることができる。画像などは全てのデータを一気にメモリにのせると、あっという間にメモリエラーが起こるのだが、このデータセットクラスで管理してあげると、必要な分をサンプルして返してくれるようになる。

この例ではサンプルして返す値を辞書型にしていて、それが一番使いやすそうではある。公式のドキュメント をみる感じでは、返すのはデータセットからサンプルされるものであればなんでも良さそうである。(辞書型との指定はない)

class FaceLandmarksDataset(Dataset):
    """Face Landmarks dataset."""

    def __init__(self, csv_file, root_dir, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.landmarks_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform

    def __len__(self):
        return len(self.landmarks_frame) # データセットのサイズを返す

    def __getitem__(self, idx):
        img_name = os.path.join(self.root_dir,
                                self.landmarks_frame.iloc[idx, 0])
        image = io.imread(img_name)
        landmarks = self.landmarks_frame.iloc[idx, 1:].as_matrix()
        landmarks = landmarks.astype('float').reshape(-1, 2)
        sample = {'image': image, 'landmarks': landmarks}

        if self.transform: # のちに使う
            sample = self.transform(sample)

        return sample # データセットからsampleした値を返す。

このデータセットクラスを使って、最初の4人分をサンプルしてみよう。ソースコードは以下の感じ。

face_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
                                    root_dir='faces/')

fig = plt.figure()

for i in range(len(face_dataset)):
    # __getitem__をオーバーライドしているので、配列にアクセスしているように書ける。直感的。
    sample = face_dataset[i]

    print(i, sample['image'].shape, sample['landmarks'].shape)

    ax = plt.subplot(1, 4, i + 1)
    plt.tight_layout()
    ax.set_title('Sample #{}'.format(i))
    ax.axis('off')
    show_landmarks(**sample)

    if i == 3:
        plt.show()
        break

faces.jpg

TRANSFORMS

ここまででデータセットからサンプルできるようになったが、問題がある。データセットのそれぞれの画像でサイズがバラバラであるのに対し、ほとんどのニューラルネットでは固定長のサイズを入力とする点である。そこで、前処理を行ってデータのサイズを揃えたい。今回行う前処理は以下の3つである。

  • Rescale : 画像のアスペクト比を保ったままサイズを変更する
  • RandomCrop : 画像からランダムに抜き出す(Data augumentationに使う)
  • ToTensor : numpyからpytorchのTensorへ変換する

実装としては __call__ にデータセットクラスでサンプルした値をいれてあげて、その値に対してどういう変換を行うか?を順に書いていけばいい。

Rescaleクラス

class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        h, w = image.shape[:2]
        if isinstance(self.output_size, int):
            # アスペクト比を保ちながら変更する
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size # 出力がタプルの時、タプルの指定するサイズへ変更する

        new_h, new_w = int(new_h), int(new_w)

        img = transform.resize(image, (new_h, new_w))

        # h and w are swapped for landmarks because for images,
        # x and y axes are axis 1 and 0 respectively
        landmarks = landmarks * [new_w / w, new_h / h]

        return {'image': img, 'landmarks': landmarks}

Random Cropクラス

class RandomCrop(object):
    """Crop randomly the image in a sample.

    Args:
        output_size (tuple or int): Desired output size. If int, square crop
            is made.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        h, w = image.shape[:2]
        new_h, new_w = self.output_size
        
        # out of rangeに注意してCropする
        top = np.random.randint(0, h - new_h)
        left = np.random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]

        landmarks = landmarks - [left, top]

        return {'image': image, 'landmarks': landmarks}

ToTensorクラス
公式のコメントにある通り、Torchでは画像は C * H * Wで持つようで、他のライブラリが H * W * C で持つことが多いので注意が必要だなと感じた。

class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        # swap color axis because
        # numpy image: H x W x C
        # torch image: C X H X W
        image = image.transpose((2, 0, 1))
        return {'image': torch.from_numpy(image),
                'landmarks': torch.from_numpy(landmarks)}

Composed Transforms

これらを適用すると以下のようになる。

scale = Rescale(256)
crop = RandomCrop(128)
composed = transforms.Compose([Rescale(256),
                               RandomCrop(224)])

# Apply each of the above transforms on sample.
fig = plt.figure()
sample = face_dataset[65]
for i, tsfrm in enumerate([scale, crop, composed]):
    transformed_sample = tsfrm(sample)

    ax = plt.subplot(1, 3, i + 1)
    plt.tight_layout()
    ax.set_title(type(tsfrm).__name__)
    show_landmarks(**transformed_sample)

plt.show()

同じスケールで表示されてわかりづらいが、この画像のサイズではRescaleした段階では横軸と縦軸で値が違う点に注意。

face_65_rescale_crop.jpg

ITERATING THROUGH THE DATASET

実際にこれらをデータセットクラスと一緒に使ってみる。まとめると、データセットからサンプルされるたびに

  • 画像が直ちにサンプルされる
  • Transform(上で作った変換処理)がサンプルされた画像に適用される
  • RandomCropクラスがランダム処理を挟むので、サンプルされると水増しされる
transformed_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
                                           root_dir='faces/',
                                           transform=transforms.Compose([
                                               Rescale(256),
                                               RandomCrop(224),
                                               ToTensor()
                                           ]))

for i in range(len(transformed_dataset)):
    sample = transformed_dataset[i]

    print(i, sample['image'].size(), sample['landmarks'].size())

    if i == 3:
        break

出力をみるとわかるように、同じ大きさでサンプリングされていることが分かる。

0 torch.Size([3, 224, 224]) torch.Size([68, 2])
1 torch.Size([3, 224, 224]) torch.Size([68, 2])
2 torch.Size([3, 224, 224]) torch.Size([68, 2])
3 torch.Size([3, 224, 224]) torch.Size([68, 2])

しかし、このままではDeep Learningする上で必要なミニバッチに分ける処理、データをシャッフルする処理、multiprocessing を用いて並列でデータをロードする処理が入っていない。その辺りは、 torch.utils.data.DataLoader を用いて行うようである。データセットクラスはデータセット自体の定義、Transformは前処理の変換、DataLoaderはデータセットクラスからデータを引き出す時に行う処理と考えたらよさそう(?)。

transform入りのデータセットクラスのインスタンスと、バッチサイズやシャッフルの有無などを引数で渡してあげれば良い。 結構引数のオプションがある。ここ を参照。

dataloader = DataLoader(transformed_dataset, batch_size=4,
                        shuffle=True, num_workers=4)


# Helper function to show a batch
def show_landmarks_batch(sample_batched):
    """Show image with landmarks for a batch of samples."""
    images_batch, landmarks_batch = \
            sample_batched['image'], sample_batched['landmarks']
    batch_size = len(images_batch)
    im_size = images_batch.size(2)

    grid = utils.make_grid(images_batch)
    plt.imshow(grid.numpy().transpose((1, 2, 0)))

    for i in range(batch_size):
        plt.scatter(landmarks_batch[i, :, 0].numpy() + i * im_size,
                    landmarks_batch[i, :, 1].numpy(),
                    s=10, marker='.', c='r')

        plt.title('Batch from dataloader')

for i_batch, sample_batched in enumerate(dataloader):
    print(i_batch, sample_batched['image'].size(),
          sample_batched['landmarks'].size())

    # observe 4th batch and stop.
    if i_batch == 3:
        plt.figure()
        show_landmarks_batch(sample_batched)
        plt.axis('off')
        plt.ioff()
        plt.show()
        break

004.jpg

AFTERWORD: TORCHVISION

今回は データセットクラスの定義と諸々の前処理を書いてきたけれど、毎回同じような処理を書くのはしんどいということで、torchvision にその辺りはまとまっておいてあるようだ。特に分類問題の時などはサッと書けるようにしてあるのか、ディレクトリを以下のように 分類したいクラス/画像 という風に作ってあげると ImageFolder クラスがよしなにやってくれるようだ。クラスの順番はアルファベット順で target 側は帰って来ると思われる。この辺を読んでいる限りでは..。

root/ants/xxx.png
root/ants/xxy.jpeg
root/ants/xxz.png
.
.
.
root/bees/123.jpg
root/bees/nsdf3.png
root/bees/asd932_.png

ImageFolderの使い方は以下。

import torch
from torchvision import transforms, datasets

data_transform = transforms.Compose([
        transforms.RandomSizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406],
                             std=[0.229, 0.224, 0.225])
    ])
hymenoptera_dataset = datasets.ImageFolder(root='hymenoptera_data/train',
                                           transform=data_transform)
dataset_loader = torch.utils.data.DataLoader(hymenoptera_dataset,
                                             batch_size=4, shuffle=True,
                                             num_workers=4)

このあたりの使い方は明日、Transfer Learning(転移学習) の項目でじっくり見ていくことにする。

院試後 : 合格とこれから

散々詐欺しまくったが、なぜか番号があってびっくりした。正直よくわからない。まぁ、終わり良ければすべて良しということで。
京都大学の情報学研究科を受ける経緯については、前のブログなどを読んでいただければと。ここでは外部かつ専攻を変える自分がどういう感じで勉強したのかなどを書いておければと思う。

~4月

もともと年明けくらいから外部院進を積極的に考えていた。ただ、僕自身専攻がデザイン系(ということになるのかな)であり、機械学習などを自分で勉強はしてきたものの、コンピュータアーキテクチャ情報理論など、主に情報学科が習う内容はあまり勉強していなかった。あえて言うなら、競技プログラミングを少しやっていたのでアルゴリズムとデータ構造と機械学習くらい。 統計学機械学習の基礎になるため勉強していた。そのため、この頃はnaistを軸に考えていた気がする。あるきっかけでクックパッドの研究開発部の方と仲良くなり、「naistもいいけど、京都大学もいい環境だから、ぜひ一度訪問してみて」と言われたのを機に、せっかくだから他の大学院も見に行こうと思うようになった。

1月にメルカリのBe Bold Internshipで僕のペアとなるビジネス職の人が京大の情報学科の方で、なんとなく京大も視野に入れている、ということを話すと「最近倍率高くて、内部生でも落ちる人多いから、もし受けるならしっかりと勉強したほうがいいよ」という話を聞いて、ちょっと身が引き締まったのを覚えている。(実際同じことを研究室訪問の時にも言われた。)

ここで入試の科目を説明すると、知能情報学専攻では、14科目(くらいだった気がする : 生命学や脳科学なども含む : コンピュータサイエンスと数学で8題)のうちから4題を選択する。また、分野基礎科目という第一志望の研究室から出題される問題を解き, それらとTOEICの点数を加味して合否が決まる。先述した通り, 僕は情報学科が受ける授業の内容を全く受けていないため、そういう科目から勉強し始めた。

春休みに, アルゴリズム, 情報理論, コンピュータアーキテクチャを勉強し始めた。教科書は以下の教科書を読んでいた。
残り1科目何を勉強するのかはこの頃ずっと悩み続けてた。

アルゴリズム

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造

情報理論

情報理論

情報理論

コンピュータアーキテクチャ

コンピュータアーキテクチャ (電子情報通信レクチャーシリーズ)

コンピュータアーキテクチャ (電子情報通信レクチャーシリーズ)

のちに述べるがコンピュータアーキテクチャはこの本はわかりやすいが薄いため、もう少し勉強する必要があることに気づいた。
この頃は過去問見てもさっぱりで、教科書のわからない部分を各大学の講義資料を見たりしながら理解して勉強することを心がけた。

4, 5月

結局他の大学を受けず、京大一本に絞ることに。京大の情報理論の問題演習には、西田先生の授業資料に問題がついていて、一部解答もついているのでそれを使って演習することにした。これが功を奏して過去問がかなりスラスラ解けるようになった。(なお本番は...)

コンピュータアーキテクチャはもっと深く勉強する必要を感じたので、パタヘネを上下巻読んだ。
結構しんどいけど、これが一番早道な気がする。何より面白いし。

コンピュータの構成と設計 第5版 上・下電子合本版

コンピュータの構成と設計 第5版 上・下電子合本版

コンピュータの構成と設計 第5版 下

コンピュータの構成と設計 第5版 下

結局機械学習を勉強していたのもあり、最後の1科目はパターン認識機械学習にした。
最近ニューラルネットばっかりだったので、全体をもう一度復習できたのは良かった。

はじめてのパターン認識

はじめてのパターン認識

ちなみに3月のtoeicが630, 4月になって705だった。これはまずい(せめて京大の合格のためには750は最低欲しいと思っていた。)ということで、TOEICも勉強し始めた。この頃はやるべきことがおおく、パンク気味だった。

公式 TOEIC Listening & Reading 問題集 3

公式 TOEIC Listening & Reading 問題集 3

単語さえ知ってれば解ける!と思ったので, 単語力をつけた。これがおすすめ。

6, 7月

苦しい時期。暑いけどできるだけ大学の図書館で勉強をしていた。たしか出願とかもこの時期だった気がする。
志望理由書は読まれるのか分からないが、自分の経歴を盛り込みながら、かつその研究室でやっている内容を自分なりに発展させて行く方向で書いた。

専門科目の勉強としては、上の勉強に加えて過去問を解く + 解答を作成するのを繰り返しながら問題演習をしていた。他大の問題も少し解いたんだけど、解答をつくるのがしんどすぎてすぐにやめた。4科目しか勉強してないのでは、もし本番に1問難しいのがきたらやばいな、と思い統計も勉強することにした。以下の本で演習したら過去問は解けるようになった。(なお本番は...)

統計学演習

統計学演習

個人的に「その分布を知ってますか?」or知らなければ解けないみたいな問題(例えば超幾何分布とか)が出題されてたりはするので(実際今年カイ二乗分布の数理的な話がきて僕は解きませんでした), 各分布まとめておくといいかも。カイ二乗分布許さん。

7月は全ての教科書を読み終えたので、もう一回読んだり、過去問を解いたりしていた。
toeicは勉強の甲斐があってか6月の結果が840点まで上がった。この頃には過去問も解けるし、toeicも点がよくて合格が見えてきたぞ、と思っていた気がする。

8月

院試本番

当日はやっぱりたくさん人がいたけど、ここまでやってきたことを信じて頑張ろうとそれだけだった。
テスト開始と同時に問題を見るや否や情報理論と統計がはちゃめちゃ難しいことに気づく。予備で1科目用意して1科目ぶんのアクシデントに耐えられる設計にしたのにこの始末。結局ノー勉の数学を解くことに。主成分分析や特異値分解を求めるプロセスで固有値対角化は覚えていたけれど、ヤコビ行列を覚えてすらいないため、全く解けない。ガウス積分などわかるはずもなく、数学撃沈。コンピュータアーキテクチャは比較的簡単だったものの、アルゴリズムでオーダーの収束についての問題が出る。あとで考えればlimitを取ればいいだけなのにn!はlognより増加の速度が速いから〜と数式を一つも書かずに脳内のノリで乗り切る。パターン認識は3/5しか解けてない。

分野基礎科目は「機械学習を用いた解法を述べよ」という出題であり、3問中1問はオレオレニューラルネットワークモデルを書いて提出(思いつかなかった..)。
100%落ちたな...という体感だった。

次の日と合格発表日

僕の専攻は合格不合格のギリギリの人が面接に呼ばれるという噂があり、次の日にそれが決まるのだが、それには呼ばれなかった。
そのため、余裕で受かっているか不合格かという形となり、僕の点数を考えると...と超落ち込んでいろんな人にごめんなさいと連絡をしてた。
最終的になんとか受かってて良かった。自分の番号を5度見くらいした。学部で京大に落ちたので、それもあってか本当に嬉しい。特に今回は、名前で京大を選ばずに自分の好きなことをさらに伸ばしたい思いで受けたので、本当に嬉しかった。

細かい情報

TOEIC

3月(4月受け取り) : 630
4月 : 705
5月 : 730
6月 : 840
最終的に6月の結果を提出。 最後の最後で上がることもあるので、一応全部受けておくのをおすすめする。
実際僕が合格したのもTOEICのおかげだと思っているので。

点数開示

[WIP] : 一応開示してみるつもりです。開示結果が来たら書きます。

今後

気分。

BUMP OF CHICKEN『HAPPY』


無事第一志望の研究室に所属することができたみたいだ(が、怖いので合否通知が届いたら指導教官に連絡しようと思う。)。
しっかりと大学院で研究をし、自分の興味に迫りつつ、自然言語処理に貢献できたらと思う。もちろん、大学院に入ってからもしんどいことは多いと思うけれど、それでも専攻を変えてまで頑張れた、自分の好奇心と信念を信じてみたい。界隈の方々、今後よろしくお願いします。

そして、昔からのフォロワーの方々や高校の同期、大学の同期の友達など、辛い時に相談に乗ってもらえて、いつもと変わらず話しかけてもらえて、それが僕の一番の心の支えになったと本心から思う。これからもよろしくお願いします。

もし受験生でなんか質問とかあればtwitterで@misogil0116に気軽にリプライください。

院試前 : 近況

院試まであと残り数日となった。
正直新しく勉強することもあまりないし、仮に勉強したとしても問題をスラスラと解けるまでに落とし込めるということもない。
今までやったことの確認を今日含めこの土日でやって試験に臨もうと思う。
院試を受けると決めるまでの経緯と、そこからの感情の変化を書いて整理をしようと思う。

最近Twitter意図的に見ていないので、用がある方は
taichitary@gmail.com にてメールください(応援メッセージ待ってるぜ(?))。

-------

僕は大学の学部生の間はずっと機械学習を勉強していた。元々ロックバンドが好きで、そのレコメンドエンジンを作ったのがきっかけである。
それからインターンに行ったり、アプリを流行りの技術で作ったり、GANを試したり、先生のもとで論文を書いたり、と色々やっていた気がする。おかげで楽しく大学生活を送ることができた。 3年の終わりになって、さて就職か、院に進むか迷った。 実際、今の技術力で新卒としてなら研究職ではない、機械学習エンジニアとしてならそこそこ採用されるだろうなと思っていた。ただ、「僕がやりたいことは、本当に、そういったことなんだろうか」と考えるようになった。 秒速何万のレスポンスを捌ける機械学習の基盤とか、ユーザに価値を提供するレコメンドエンジン、もちろんそれらを作るのは面白かったし、就職するのも面白いと思う。ただ、僕の本心からの声を聞こうとしてないんじゃないのだろうか。

機械学習を勉強するにつれて、機械学習の限界が見えてくる。そして僕が最初に感じたワクワクを失っているような気がした。TwitterとかでAIに対する無茶な要求を見るたびに、「無理に決まってるだろう」なんて吐いていた。でも、なんで僕がそれを言えるんだろう。

自分ができること。その殻の中に無意識のうちに閉じこもっているような気がした。できることしかやらないし、できないことはできない。そう言い切ってしまったら楽だけど、その先に何が待っているんだろうか。

まだまだ若いのだし、大学に勉強した事を生かし、未知に到達してみたい。

そんなことを考え、今年の冬くらいからあえて未知なものをじっくり読むという事を行っていた気がする。わからないものを拒絶せずにじっくりと調べていくうちに、「コンピュータが意味を理解する」ということがどうやら僕の興味関心だなと思った。そのための具体的なタスクとして、Q&Aや、Word Embedding、また実データを使って構造を整理している研究室はないだろうか、と調べ、訪問することにした。(とはいえ、金銭的な面もあり、知り合いからすすめられたり、知り合いが現在行っているなどを参考にしながら決めた)

そこで訪問した順に並べると、

にいくことにした。NAISTは外部生から行きやすいという意味では入試は比較的入りやすいのでは、と思ったが、機械翻訳をメインにこれから力を入れて行きたいということで、それなら別の人が入るべきだなと感じてやめた。京大の僕が訪問した研究室も修士からの研究室であり、また、興味内容と合致しているなと思い、志望することにした。博士に進学希望の人が多い点も、いい研究室だからだろうと思った。複数受けようと考え、東工大のO研究室も訪問した。いい研究室だし、自分の興味と合致していると思ったものの、東工大の入試方式的に、僕が訪問した研究室は人気そうで、内部推薦で全ての枠が取られてしまうことを考慮に入れると、ちょっとそこに賭けるのは厳しいなと感じ、受けないことにした。結構悔しくて、GWに深く落ち込んだのを覚えている。

ということで京大一本。どう勉強したか、とかは受かったらまた書く。内部生のようにレジュメなどを持っていない上、専攻を変えることになるため、今まで勉強したことのない内容を必死こいて勉強する必要があった。正直相当しんどい。..とこれまでかなり大変で、周りが就職や、楽しそうなインターンなどを決めていく中で黙々と勉強するのは結構辛いものがある..と思いながら、TOEICの点に悩みながら(多分目標ラインまではたどり着けた)、なんとかここまできた。

-------

最近、「これだけ勉強して合格してもたった2年か」と思うようになった。小町先生の学生募集にもあるように、修士の2年で研究職につくのは難しいということ、そして自分の興味と研究意欲を考慮に入れると、博士に進学したいなと考えている。
大学院に落ちたとしたら、研究生として受け入れてもらえないかと言おうと思っている。学費を一年、生活費を一年払わないといけないのは厳しいが、それを考えなければ、B4から研究するのと同じくらいにテーマを掘り下げられる気もするので。まぁお金についてはお得意のプログラミングでなんとかしよう。
そんな感じでもう悟りの境地。これで落ちたらしょうがない。Twitter愚痴だらけ言ってた気がするけどその辺は申し訳ないです。


最近の気分。 [alexandros]も昔の曲の方がよく聞くかもしれない。

[Alexandros] - city (MV)



そういえばYANSが香川で今年は開催されるようなので、実家に戻るついでに参加しようと思っている(登壇はしません)。
参加される方はよろしくお願いします。

所感

一年ぶりくらいに考えをまとめるべく書く.
最近はどちらかと言うとアプリ開発だとかということよりも, 研究開発に寄っていて幸運にもpixivという会社が僕を雇ってくれたこともあり, pixiv社でオープンソースを開発している.
具体的にはJPEGを見た目の美しさを保ったままでより圧縮するアルゴリズムを開発するお仕事であり, 中々未知な部分が多いのではあるが, なんとか取り組んで行きたい課題だ. もうすぐしたらβ版くらいまでは出せるのではないだろうか.


...


最近の悩みは院進についてである.
来年以降自然言語処理についての研究をしたいと考えていて, NAIST, 京都大学, 東京工業大学と三つの大学をまわった.
個人的には東京工業大学京都大学に惹かれる部分を感じたのだが, 東京工業大学には入試の都合上, A日程とB日程という二つの日程に分かれ, A日程は事実上内部生のための推薦制度のようなものなのだが, これで定員の半数を受け入れることを院試説明会で初めて聞いた(僕はせいぜい一割か二割程度であろうと思っていたので寝耳に水だったが...)
また、志望する研究室はかなり人気になることが期待されるため, これではA日程で席が埋まり, B日程に席は一つも回ってこない可能性が考えられ, 仮にB日程を一位で通過したとしても, 諦めざるを得ない..という制度になりえてしまうのだ. 私の成績だとB日程に回されることは確実であり, 最初から勝ち目のない戦いなのかもしれない.

京都大学はというと, 決してそんなことはなく純粋にペーパーの成績で振り分けられるようである.
ただ, 僕が志望する研究室はおそらく第一人気となることはないと思われるが, それでも自然言語処理ができる研究室で有名であるため, おそらく合格者の中でも中位程度の成績を収めなければならないだろう.
合格したとしてもそれが最下位であったなら。 おそらくそれは辞退をする他選択肢はないだろう.


...


結局どこかに賭けるしかない.
最終的にはそこなのであるが, バンディットアルゴリズムを人生に適用できるほど僕はできていない.
落ちてしまったらどうしようかとか, やはりNLP(というかAIと言われる部門全体?)は人気であり, そう簡単にはいかないだろう...。
研究生として受け入れてもらえるか? はたまた, 落ちてしまった時点で就活をするのか?
仮に就職できたとしても, 僕はMachine Learningを続けられるのか? ...etc.

悩みは渦になってぐるぐると何度も頭の中を反芻する.


ステレオポニー - ツキアカリのミチシルベ

苦しいし, 険しい. 外部院進を誰もが敬遠する理由がわかるし, Hotな分野のせいというのもあるのかもしれない..
それでももうそろそろ賭けるべきを決めて、そこに一点注力をさせないことには希望の光は見えてこない.

そういうタイミングなのかもしれない.

大学二年生でweb系を1からサービスを立ち上げたりするまでに読んだ本

大学三年生になりました. 明日からまた学校なので, 2年生に読んだ本とかまとめてみようと思う.
ちなみに大学2年の段階ではweb系は全くで, 1年の12月くらいにC言語の本を読んだくらいの能力しかなかったけど,
それでも4~8月でサービスをデプロイするところまでは行けたので, 勉強することは多いけど気合いがあればなんとかなると思う.

4~6月

自分はロック音楽が好きで,ART-SCHOOLというバンドが好きなのだが,周りに同じような人がいなかった.
でも,周りの聞いているアーティストとかみてるとこっちも好きになってくれてもいい潜在的なファン(?)がいるように思えた..
ので,そんなサイトを作りたいなーとかちょっと考えたのがきっかけ.

HTML/CSSとかからスタートした.

作りながら学ぶ HTML/CSSデザインの教科書

作りながら学ぶ HTML/CSSデザインの教科書

これ結構分厚かったけど, 一からサイト作るって感じで分かりやすかった.
jQueryとかもちょろっと乗ってて次何をやればいいかとかも明確になった.
同時進行でPHPもスタートしていて,
いきなりはじめるPHP~ワクワク・ドキドキの入門教室~

いきなりはじめるPHP~ワクワク・ドキドキの入門教室~

気づけばプロ並みPHP 改訂版--ゼロから作れる人になる!

気づけばプロ並みPHP 改訂版--ゼロから作れる人になる!

この辺りも読んだ. これも一から作る感じでモチベーションは高く望めた(ただLAMPのバージョンがちと古すぎた気が..)
ショッピングカートの方は変数がたくさん出てきて少し混乱するところもあった気もする.

この辺でどうやら装飾をするならJavaScriptっていうのをやらないといけないなと気づいて,

これを読んで, なんかそのままこれも読んだ. この段階だと多分読まなくていい本なんだけど,
でも後々いろいろJSを触るときに今でも役立っているから読んでよかったと思う.
(JSのオブジェクト指向とかはちょっと違うからなぁと)
それで一応jQueryも読んだ.
改訂版 Webデザイナーのための jQuery入門

改訂版 Webデザイナーのための jQuery入門

わかりやすかった.

さて, この辺でPHPでサービスを作ろう!とはなったのだけれど, 実際にどこから書けばいいのか分からなかった.
調べていくうちにどうやらサービスを作るときは1から作るというより,フレームワークを利用するらしい.
CakePHPの本を読んだのだけれど, MVCがさっぱりな状態からだったので挫折. もっとわかりやすい本ないかなあと調べていたところ,

はじめてのフレームワークとしてのFuelPHP 改訂版

はじめてのフレームワークとしてのFuelPHP 改訂版

これが分かりやすかった.
一応パーフェクトPHPとかも文法を補うべく読んでた.
パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)

7月

なぜかリクナビに登録して, 福岡のベンチャー企業でペーペーなのにインターンさせていただくことに.
(今思うとよく通ったな..), PHPで働くのかと思っていると,その会社はRubyだったので,急遽Rubyを勉強した.
Rubyは書きやすく分かりやすかった.

作りながら学ぶRuby入門 第2版

作りながら学ぶRuby入門 第2版

Ruby, Web開発とくればRuby on Rails!となって, Railsをメンターの方に教えてもらいながら,conpassみたいなサイトを作った.
パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

これだけだとアレだったので, 自分でもう一冊読んでた.
改訂3版 基礎 Ruby on Rails 基礎シリーズ

改訂3版 基礎 Ruby on Rails 基礎シリーズ

こっちの方が初めてなら分かりやすい.

この頃に自作したいアプリは, 機械学習っていうのが必要だ..!って気づいて,
機械学習ならPythonやれみたいな感じだったので,Pythonをはじめた. これがいい言語で, 今もメインで使っている.

Pythonスタートブック

Pythonスタートブック

入門 Python 3

入門 Python 3

ちなみに, はてなインターンは行けずだった。初心者だから当然と言えば当然..
この時に一回だけはてなのエンジニアさんとお話しできる機会があり, 「Web系と言えど基礎が大切」という話を伺った
その助言は今もずっと残っている

8月

ちょうどよく夏休みで, アプリの作り方もわかってきて, この夏に機械学習でロジック部分を完成させてアプリを出してやろうと考えていた.
まず数学を一年レベルはしっかり固めようということで,

微積(ラグランジュの未定乗数法は絶対にいる)

微分積分 (理工系の数学入門コース 1)

微分積分 (理工系の数学入門コース 1)

線形代数
プログラミングのための線形代数

プログラミングのための線形代数

統計学

そして機械学習. この本面白かった, おすすめ.

ITエンジニアのための機械学習理論入門

ITエンジニアのための機械学習理論入門

k-meansやら協調フィルタリングを駆使して, 自分なりに作ってインターンで発表した.
(今見るとなんでそのモデリングなのか不適切だが)高評価だったので嬉しかった.

www.slideshare.net


これは別のところで発表した資料.

9月

アプリ公開. herokuを利用した.

MusicLanguage

アーティストを打ったらおすすめアーティストをレコメンドしてくれるというもの.
はじめてのアプリということになりそう. 正直この月のことはあまり記憶に残っていない..

10月

大学の後期が始まり, 機械学習にのめり込むようになり, 最適化数学を勉強した.

これなら分かる最適化数学―基礎原理から計算手法まで

これなら分かる最適化数学―基礎原理から計算手法まで

この本は程よく数式があり, 具体例豊富でわかりやすく, とてもよかった.
Deep Learningにも手を出し始めたような気がする..。

定番中の定番.

基礎的な技術が身に付けたいと思い, 競技プログラミングを始めた.
あり本をICPCサークルの人と一緒に読み解いたりした.

このあたりでインターンをやめた
もっと学問として情報科学/計算機科学, 機械学習と向き合いたいと思ったからである

11~12月

あまり覚えていないが, DeepLearningにかなり傾倒していくようになり,
Chainerを利用してデータをぶち込んでぐるぐる回したり, あといろんなDeepLearningを試していたりした

Chainerによる実践深層学習

Chainerによる実践深層学習

実装 ディープラーニング

実装 ディープラーニング

Chainerはバージョンアップで内容が古くなってはいるが, 雰囲気を掴むにはいいかもしれない.

1~3月

親と相談して, 15ヶ月食費を抜きにしてDeepLearning用PCを購入することに成功した.
これにより, ようやくまともにビッグデータをぶちこめるようになった. 購入したのはGTX1070がついたBTOのPC. Ubuntu+Chainer+GPUのセットアップで苦労した.
この頃から, PRMLをしっかり読もう, もうそれしかないなと思ってPRMLを購入. この頃はとりあえず歯を食いしばって頑張っていたが, 最近になってようやくベイズ的考え方が身についてきたと思う. もうちょっと数学をやる必要は感じているが..

パターン認識と機械学習 上

パターン認識と機械学習 上

2月末にLINE BOT AWARDS用に「顔をみてそこから髪型をおすすめするアプリ」をDeepLearningで作り, コンペに提出した.
ファイナリストには残らなかったが, そのあとの後夜祭(4月)でLINE本社で発表する機会をもらえて嬉しかった.交通費も出たのはありがたい..
東京は楽しかったし, きている人のエンジニアのレベルが高いのが頭に残っている. LINEのインターンもいけたらいいな.

www.slideshare.net


4月のはブログに別でまとめている.

これからの予定

ニューラルネットワークに限らず, 機械学習/ベイズ推論/確率的プログラミングの基礎を勉強したい.
SICPみたいな本も読んでいきたい.
設計について深く考えたい.
競プロ力あげたい.
アプリ作りたい.

などなど. 基礎技術を勉強しながら手を動かしてアプリやツールも作っていきたいと思う.
OSSに貢献したりもやってみたいなぁ. まだまだこれからなので, どんどん勉強していきたいと思う

numpyについて2

インデキシング, ブロードキャスティングなどについて詳しく見ていく.

基本インデキシング

まず, numpyのデータに変数を与えることは普通の値を渡す状態なのではなく, 参照渡しであることに注意する.
つまり, ひとつのndarrayオブジェクトに変数を複数渡り当てた場合,ひとつの変数から値を変えると他の変数にも影響が出ることになる
これはビューといって, 余計なメモリを生み出さないための方法である.

In [1]: import numpy as np

In [2]: x2d = np.arange(12).reshape(3, 4)

In [3]: x2d
Out[3]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

# ここは, x2d[1, 1:]とも書ける(numpyのみ)
In [4]: a = x2d[1][1:]

In [5]: a
Out[5]: array([5, 6, 7])

In [6]: a[0] = -1

In [7]: a
Out[7]: array([-1,  6,  7])

In [8]: x2d
Out[8]:
array([[ 0,  1,  2,  3],
       [ 4, -1,  6,  7],
       [ 8,  9, 10, 11]])

応用インデキシング

先ほどのように, ビューが生成されるのではなく, コピーが生成される.
このため, メモリの量には気をつける必要がある.(大きいデータを扱うことがメインのため)

In [11]: dat = np.random.rand(2, 3)

In [12]: dat
Out[12]:
array([[ 0.98810811,  0.53605981,  0.80088131],
       [ 0.44439242,  0.4916097 ,  0.07521805]])

In [13]: bmask = dat > 0.5

In [14]: bmask
Out[14]:
array([[ True,  True,  True],
       [False, False, False]], dtype=bool)

In [15]: highd = dat[bmask]

In [16]: highd
Out[16]: array([ 0.98810811,  0.53605981,  0.80088131])

In [17]: highd[0] = 1000

# datには無影響である
In [18]: dat
Out[18]:
array([[ 0.98810811,  0.53605981,  0.80088131],
       [ 0.44439242,  0.4916097 ,  0.07521805]])

In [19]: highd
Out[19]: array([  1.00000000e+03,   5.36059805e-01,   8.00881315e-01])

整数配列でのインデキシングは, リストを渡すことによって可能である

In [24]: nda = np.arange(10)

In [25]: ndb = nda[1:4]

In [26]: ndb
Out[26]: array([1, 2, 3])

In [27]: ndc = nda[[1, 2, 3]]

In [28]: ndc
Out[28]: array([1, 2, 3])

浅いコピー, 深いコピー

numpyには浅いコピーと深いコピーがある.
浅いコピーには参照が渡される. 基本インデキシングはこちらになる.

深いコピーでは, 値が渡されるので, 元々のデータと別物になる.
応用インデキシングで見せた, ブール配列によるインデキシングや, 整数配列によるインデキシングだけでなく,
copy()メソッドや, flatten(一次元にする), a.clip(min, maxを指定し, min以下のものはminへ, max以上のものはmaxへ)などもこちらに属する

ufunc. ユニバーサル関数について

numpyには, map関数として配列の要素のそれぞれにアクセスして特定の処理をして返す関数があり,
ユニバーサル関数と言われる.

In [30]: nda = np.arange(12).reshape(2, 6)

In [31]: nda
Out[31]:
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11]])

In [32]: np.square(nda)
Out[32]:
array([[  0,   1,   4,   9,  16,  25],
       [ 36,  49,  64,  81, 100, 121]])

既存のpythonの関数を, frompyfuncを利用することによってufunc化することもできる

In [34]: hex_array = np.frompyfunc(hex, 1, 1)

In [35]: hex_array((10, 30, 100))
Out[35]: array(['0xa', '0x1e', '0x64'], dtype=object)

ブロードキャスティング

  1. , /, *, -などの計算は, 同じ大きさ同士のものでなければ計算することはできない.

この点を解消してくれるのが, ブロードキャスティングである. これが結構ややこしい(というか, 言葉にするより図に書くべきだと思う).
詳しくは, ここをみてほしい

In [36]: nda = np.arange(24).reshape(4, 3, 2)

In [37]: ndb = np.arange(6).reshape(3, 2)

In [38]: ndc = np.arange(3).reshape(3, 1)

In [39]: nda + ndb - ndc
Out[39]:
array([[[ 0,  2],
        [ 3,  5],
        [ 6,  8]],

       [[ 6,  8],
        [ 9, 11],
        [12, 14]],

       [[12, 14],
        [15, 17],
        [18, 20]],

       [[18, 20],
        [21, 23],
        [24, 26]]])

この例だと, ndaに合わせていく形になり, ndb, ndcの次元を一つ増やして3にする. あいているところは, 今と全く同じベクトル/行列で埋める.
次に大きい軸(z, x, y)ならyで埋めていくと, nbcは(4, 3, 2)の形にまで大きくすることが可能. これにより, (4, 3, 2)を出力の行列として計算することができる. このように, ぴったりと埋まらない場合にブロードキャスティングでエラーが発生する. 例えば, 以下の例である.

In [47]: nda = np.arange(12).reshape(3, 4)

In [48]: ndb = np.arange(4)

In [49]: nda + ndb
Out[49]:
array([[ 0,  2,  4,  6],
       [ 4,  6,  8, 10],
       [ 8, 10, 12, 14]])

In [56]: ndb.reshape(4, 1)
Out[56]:
array([[0],
       [1],
       [2],
       [3]])

In [60]: nda + ndb
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-60-c79787edb196> in <module>()
----> 1 nda + ndb

ValueError: operands could not be broadcast together with shapes (3,4) (4,1)

言葉より図をかいて考えるべきだった..